@@ -56,7 +90,13 @@ function SkillsPage({ onSkillClick }) {
-
+ setSearchQuery(e.target.value)}
+ />
@@ -85,15 +125,40 @@ function SkillsPage({ onSkillClick }) {
-
- {filteredSkills.map(skill => (
- onSkillClick(skill.id)} />
- ))}
-
+ {searchedSkills.length > 0 ? (
+
+ {searchedSkills.map(skill => (
+ onSkillClick(skill.id)}
+ onSubscribe={handleSubscribeClick}
+ />
+ ))}
+
+ ) : (
+
}
+ message="暂无匹配技能"
+ description={searchQuery ? `未找到与"${searchQuery}"相关的技能` : '当前筛选条件下没有技能'}
+ />
+ )}
+
+ {modalTarget?.subscribed
+ ? `确定要取消订阅"${modalTarget?.name}"吗?取消后将无法使用该技能。`
+ : `确定要订阅"${modalTarget?.name}"吗?`
+ }
+
>
);
}
diff --git a/src/pages/console/TasksPage.jsx b/src/pages/console/TasksPage.jsx
index 4897c9c..ee15a86 100644
--- a/src/pages/console/TasksPage.jsx
+++ b/src/pages/console/TasksPage.jsx
@@ -1,8 +1,12 @@
import { useState } from 'react';
+import { FiClock } from 'react-icons/fi';
import { scheduledTasks } from '../../data/tasks.js';
+import EmptyState from '../../components/common/EmptyState.jsx';
+import Modal from '../../components/common/Modal.jsx';
function TasksPage({ onViewDetail }) {
const [tasks, setTasks] = useState(scheduledTasks);
+ const [deleteTarget, setDeleteTarget] = useState(null);
const toggleTask = (taskId) => {
setTasks(prev => prev.map(task =>
@@ -10,50 +14,84 @@ function TasksPage({ onViewDetail }) {
));
};
+ const handleDeleteClick = (task) => {
+ setDeleteTarget(task);
+ };
+
+ const confirmDelete = () => {
+ if (deleteTarget) {
+ setTasks(prev => prev.filter(task => task.id !== deleteTarget.id));
+ setDeleteTarget(null);
+ }
+ };
+
+ const cancelDelete = () => {
+ setDeleteTarget(null);
+ };
+
return (
-
-
-
定时任务
+ <>
+
+
+
+ {tasks.length > 0 ? (
+
+
+
+ | 任务名称 |
+ 频率 |
+ 上次触发 |
+ 下次触发 |
+ 上次运行状态 |
+ 状态 |
+ 操作 |
+
+
+
+ {tasks.map(task => (
+
+ | {task.name} |
+ {task.frequency} |
+ {task.lastTriggered} |
+ {task.nextTrigger} |
+
+
+ {task.lastStatus}
+
+ |
+ {task.enabled ? '启用' : '禁用'} |
+
+
+
+
+ |
+
+ ))}
+
+
+ ) : (
+
}
+ message="暂无定时任务"
+ description="还没有创建任何定时任务"
+ />
+ )}
+
-
-
-
-
- | 任务名称 |
- 频率 |
- 上次触发 |
- 下次触发 |
- 上次运行状态 |
- 状态 |
- 操作 |
-
-
-
- {tasks.map(task => (
-
- | {task.name} |
- {task.frequency} |
- {task.lastTriggered} |
- {task.nextTrigger} |
-
-
- {task.lastStatus}
-
- |
- {task.enabled ? '启用' : '禁用'} |
-
-
-
-
- |
-
- ))}
-
-
-
-
+
+ 确定要删除任务"{deleteTarget?.name}"吗?此操作不可撤销。
+
+ >
);
}
diff --git a/src/styles/global.scss b/src/styles/global.scss
index 81a2039..461b102 100644
--- a/src/styles/global.scss
+++ b/src/styles/global.scss
@@ -1058,6 +1058,7 @@ input:checked + .slider:before {
display: flex;
flex-direction: column;
height: 100%;
+ min-height: 0;
background: var(--color-bg-1);
}
@@ -1272,6 +1273,8 @@ input:checked + .slider:before {
display: flex;
flex-direction: column;
background: var(--color-bg-1);
+ min-height: 0;
+ overflow: hidden;
}
.chat-messages {
@@ -2542,3 +2545,174 @@ input:checked + .slider:before {
.message-thinking {
cursor: pointer;
}
+
+/* ===== Modal 弹窗样式 ===== */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 2000;
+ backdrop-filter: blur(2px);
+}
+
+.modal {
+ background: var(--color-bg-1);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 32px rgba(15, 23, 42, 0.16);
+ width: 420px;
+ max-width: 90vw;
+ animation: modal-in 0.2s ease-out;
+}
+
+@keyframes modal-in {
+ from {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--color-border-2);
+}
+
+.modal-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--color-text-1);
+}
+
+.modal-close {
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ color: var(--color-text-3);
+ transition: all 0.2s;
+
+ &:hover {
+ background: var(--color-bg-2);
+ color: var(--color-text-1);
+ }
+}
+
+.modal-body {
+ padding: 20px;
+ font-size: 14px;
+ color: var(--color-text-2);
+ line-height: 1.6;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 12px 20px;
+ border-top: 1px solid var(--color-border-2);
+}
+
+/* ===== Toast 消息提示样式 ===== */
+.toast {
+ position: fixed;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 16px;
+ border-radius: var(--radius-md);
+ box-shadow: 0 4px 16px rgba(15, 23, 42, 0.12);
+ z-index: 3000;
+ animation: toast-in 0.3s ease-out;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+@keyframes toast-in {
+ from {
+ opacity: 0;
+ transform: translateX(-50%) translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
+
+.toast-success {
+ background: var(--color-success-light);
+ color: var(--color-success);
+ border: 1px solid rgba(16, 185, 129, 0.2);
+}
+
+.toast-error {
+ background: var(--color-danger-light);
+ color: var(--color-danger);
+ border: 1px solid rgba(239, 68, 68, 0.2);
+}
+
+.toast-warning {
+ background: var(--color-warning-light);
+ color: var(--color-warning);
+ border: 1px solid rgba(245, 158, 11, 0.2);
+}
+
+.toast-info {
+ background: var(--color-primary-light);
+ color: var(--color-primary);
+ border: 1px solid rgba(59, 130, 246, 0.2);
+}
+
+.toast-icon {
+ display: flex;
+ align-items: center;
+ font-size: 16px;
+}
+
+.toast-message {
+ flex: 1;
+}
+
+.toast-close {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ opacity: 0.7;
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 1;
+ }
+}
+
+/* ===== 表单校验错误样式 ===== */
+.form-control.is-invalid {
+ border-color: var(--color-danger);
+
+ &:focus {
+ box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1);
+ }
+}
+
+.form-error {
+ font-size: 12px;
+ color: var(--color-danger);
+ margin-top: 4px;
+}