|
|
@@ -0,0 +1,672 @@
|
|
|
+<template>
|
|
|
+ <div class="schedule-panel">
|
|
|
+ <div class="schedule-panel-header">
|
|
|
+ <span class="schedule-panel-title">定时任务</span>
|
|
|
+ <div class="schedule-panel-close" @click="emit('close')">
|
|
|
+ <div class="close-icon"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="schedule-panel-body" v-if="!loading">
|
|
|
+ <div class="schedule-panel-desc">
|
|
|
+ <span class="schedule-desc-text">让"通安智风"大模型按计划自动执行任务,并把结果定时送达</span>
|
|
|
+ <a-button type="primary" class="schedule-create-btn" @click="handleCreate">
|
|
|
+ <template #icon><PlusOutlined /></template>
|
|
|
+ 创建
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 有任务列表 -->
|
|
|
+ <div v-if="scheduleList.length > 0" class="schedule-list">
|
|
|
+ <div v-for="item in scheduleList" :key="item.id" class="schedule-item">
|
|
|
+ <div class="schedule-item-header">
|
|
|
+ <div class="schedule-item-name">
|
|
|
+ <div class="schedule-item-icon"></div>
|
|
|
+ <span>{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="schedule-item-actions">
|
|
|
+ <a-switch :checked="item.enabled" size="small" class="schedule-switch" @change="handleToggle(item)" />
|
|
|
+ <div class="schedule-run-btn" @click.stop="handleRunNow(item)" :class="{ running: runningIds.has(item.id) }" title="立即执行"></div>
|
|
|
+ <div class="schedule-item-edit" @click.stop="handleEdit(item)" title="编辑"></div>
|
|
|
+ <div class="schedule-item-delete" @click.stop="handleDelete(item)" title="删除"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="schedule-item-info">
|
|
|
+ <div class="schedule-item-prompt">{{ item.prompt }}</div>
|
|
|
+ <div class="schedule-item-meta">
|
|
|
+ <div class="schedule-item-cron">
|
|
|
+ <span class="info-label">执行计划:</span>
|
|
|
+ <span class="info-value">{{ item.cron_human || item.cron_expr }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="item.next_run_at" class="schedule-item-next">
|
|
|
+ <span class="info-label">下次执行:</span>
|
|
|
+ <span class="info-value">{{ item.next_run_at }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="schedule-item-history-toggle" @click.stop="toggleHistory(item.id)">
|
|
|
+ <span>{{ expandedIds.has(item.id) ? '收起历史' : '执行历史' }}</span>
|
|
|
+ <div :class="['toggle-arrow', { expanded: expandedIds.has(item.id) }]"></div>
|
|
|
+ <template v-if="historyStatsMap[item.id]">
|
|
|
+ <span class="history-stats">
|
|
|
+ <span class="stats-total">共{{ historyStatsMap[item.id].total }}次</span>
|
|
|
+ <span class="stats-sep">·</span>
|
|
|
+ <span class="stats-success">{{ historyStatsMap[item.id].success }}次成功</span>
|
|
|
+ <span class="stats-sep">·</span>
|
|
|
+ <span class="stats-failed">{{ historyStatsMap[item.id].failed }}次失败</span>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div v-if="expandedIds.has(item.id)" class="schedule-history">
|
|
|
+ <a-spin v-if="historyLoadingMap[item.id]" size="small" />
|
|
|
+ <template v-else-if="historyMap[item.id]?.length">
|
|
|
+ <div v-for="run in historyMap[item.id]" :key="run.id" class="history-item">
|
|
|
+ <div class="history-item-header">
|
|
|
+ <a-tag :color="runStatusColor(run.status)" class="history-status">{{ runStatusLabel(run.status) }}</a-tag>
|
|
|
+ <span class="history-time">{{ run.started_at }}</span>
|
|
|
+ <span v-if="run.duration_ms" class="history-duration">{{ (run.duration_ms / 1000).toFixed(0) }}s</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="run.output" class="history-output">{{ run.output }}</div>
|
|
|
+ <div v-if="run.error" class="history-error">{{ run.error }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div v-else class="history-empty">暂无执行记录</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <div v-else class="schedule-empty" @click="handleCreate">
|
|
|
+ <div class="schedule-empty-icon"></div>
|
|
|
+ <span class="schedule-empty-text">创建定时任务</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="schedule-panel-loading" v-else>
|
|
|
+ <a-spin />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <CreateScheduleModal
|
|
|
+ :visible="showCreateModal"
|
|
|
+ :editData="editData"
|
|
|
+ @close="showCreateModal = false"
|
|
|
+ @created="refreshAndNotify"
|
|
|
+ @updated="refreshAndNotify"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, reactive, onMounted } from 'vue';
|
|
|
+ import { PlusOutlined } from '@ant-design/icons-vue';
|
|
|
+ import { getScheduleList, deleteSchedule, pauseSchedule, resumeSchedule, getScheduleRuns, runScheduleNow } from '../../api';
|
|
|
+ import { message, Modal } from 'ant-design-vue';
|
|
|
+ import CreateScheduleModal from './CreateScheduleModal.vue';
|
|
|
+
|
|
|
+ const emit = defineEmits<{ (e: 'close'): void; (e: 'open-session', sessionId: string): void; (e: 'data-changed'): void }>();
|
|
|
+
|
|
|
+ interface ScheduleItem {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ cron_expr: string;
|
|
|
+ cron_human: string;
|
|
|
+ prompt: string;
|
|
|
+ enabled: boolean;
|
|
|
+ next_run_at: string | null;
|
|
|
+ feedback_session_id?: string;
|
|
|
+ schedule?: { freq: string; human?: string };
|
|
|
+ }
|
|
|
+
|
|
|
+ const scheduleList = ref<ScheduleItem[]>([]);
|
|
|
+ const loading = ref(false);
|
|
|
+ const showCreateModal = ref(false);
|
|
|
+ const editData = ref<ScheduleItem | null>(null);
|
|
|
+ const runningIds = ref(new Set<number>());
|
|
|
+ const expandedIds = ref(new Set<number>());
|
|
|
+ const historyMap = reactive<Record<number, any[]>>({});
|
|
|
+ const historyLoadingMap = reactive<Record<number, boolean>>({});
|
|
|
+ const historyStatsMap = reactive<Record<number, { total: number; success: number; failed: number }>>({});
|
|
|
+
|
|
|
+ const fetchScheduleList = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await getScheduleList();
|
|
|
+ scheduleList.value = res?.tasks || res?.data || (Array.isArray(res) ? res : []);
|
|
|
+ } catch {
|
|
|
+ scheduleList.value = [];
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const refreshAndNotify = async () => {
|
|
|
+ await fetchScheduleList();
|
|
|
+ emit('data-changed');
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCreate = () => {
|
|
|
+ editData.value = null;
|
|
|
+ showCreateModal.value = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleEdit = (item: ScheduleItem) => {
|
|
|
+ editData.value = item;
|
|
|
+ showCreateModal.value = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleToggle = async (item: ScheduleItem) => {
|
|
|
+ try {
|
|
|
+ if (item.enabled) {
|
|
|
+ await pauseSchedule(item.id);
|
|
|
+ message.success(`已暂停任务「${item.name}」`);
|
|
|
+ } else {
|
|
|
+ await resumeSchedule(item.id);
|
|
|
+ message.success(`已恢复任务「${item.name}」`);
|
|
|
+ }
|
|
|
+ item.enabled = !item.enabled;
|
|
|
+ } catch {
|
|
|
+ message.error('操作失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDelete = (item: ScheduleItem) => {
|
|
|
+ Modal.confirm({
|
|
|
+ title: '提示',
|
|
|
+ content: `是否删除定时任务「${item.name}」?`,
|
|
|
+ okText: '确认',
|
|
|
+ cancelText: '取消',
|
|
|
+ wrapClassName: 'delete-task-confirm-modal',
|
|
|
+ onOk: async () => {
|
|
|
+ try {
|
|
|
+ await deleteSchedule(item.id);
|
|
|
+ message.success('删除成功');
|
|
|
+ refreshAndNotify();
|
|
|
+ } catch {
|
|
|
+ message.error('删除失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRunNow = async (item: ScheduleItem) => {
|
|
|
+ runningIds.value.add(item.id);
|
|
|
+ try {
|
|
|
+ await runScheduleNow(item.id);
|
|
|
+ message.success(`任务「${item.name}」已开始执行`);
|
|
|
+ if (item.feedback_session_id) {
|
|
|
+ emit('open-session', item.feedback_session_id);
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ message.error('执行失败');
|
|
|
+ } finally {
|
|
|
+ runningIds.value.delete(item.id);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const toggleHistory = async (taskId: number) => {
|
|
|
+ if (expandedIds.value.has(taskId)) {
|
|
|
+ expandedIds.value.delete(taskId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ expandedIds.value.add(taskId);
|
|
|
+ if (!historyMap[taskId]) {
|
|
|
+ historyLoadingMap[taskId] = true;
|
|
|
+ try {
|
|
|
+ const res = await getScheduleRuns(taskId);
|
|
|
+ const runs = res?.runs || (Array.isArray(res) ? res : []);
|
|
|
+ historyMap[taskId] = runs;
|
|
|
+ historyStatsMap[taskId] = {
|
|
|
+ total: res?.total ?? runs.length,
|
|
|
+ success: runs.filter((r: any) => r.status === 'success').length,
|
|
|
+ failed: runs.filter((r: any) => r.status === 'failed').length,
|
|
|
+ };
|
|
|
+ } catch {
|
|
|
+ historyMap[taskId] = [];
|
|
|
+ } finally {
|
|
|
+ historyLoadingMap[taskId] = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const runStatusLabel = (status: string) => {
|
|
|
+ const map: Record<string, string> = { running: '执行中', success: '成功', failed: '失败', skipped: '跳过' };
|
|
|
+ return map[status] || status;
|
|
|
+ };
|
|
|
+
|
|
|
+ const runStatusColor = (status: string) => {
|
|
|
+ const map: Record<string, string> = { running: 'blue', success: 'green', failed: 'red', skipped: 'default' };
|
|
|
+ return map[status] || 'default';
|
|
|
+ };
|
|
|
+
|
|
|
+ defineExpose({ fetchScheduleList });
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ fetchScheduleList();
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+ .schedule-panel {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ background-image: var(--img-chat-panel-bg);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-panel-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 14px 20px;
|
|
|
+ border-bottom: 1px solid rgba(63, 80, 106, 0.25);
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ .schedule-panel-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #e8edf3;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-panel-close {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: background 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(255, 77, 79, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .close-icon {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ background-image: var(--img-chat-close-icon-btn);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-panel-desc {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ .schedule-desc-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6a7a8a;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-create-btn {
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-panel-body {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-panel-loading {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 任务列表 */
|
|
|
+ .schedule-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item {
|
|
|
+ padding: 16px 18px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid rgba(63, 80, 106, 0.15);
|
|
|
+ background: rgba(10, 132, 255, 0.03);
|
|
|
+ transition: all 0.25s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.08);
|
|
|
+ border-color: rgba(10, 132, 255, 0.25);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-name {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #e8edf3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-icon {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ background-image: var(--img-chat-options-schedule-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ background-position: center;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-edit {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: background 0.2s;
|
|
|
+ background-image: var(--img-chat-edit-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 14px 14px;
|
|
|
+ background-position: center;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: rgba(10, 132, 255, 0.15);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-delete {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: background 0.2s;
|
|
|
+ background-image: var(--img-chat-delete-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 14px 14px;
|
|
|
+ background-position: center;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: rgba(255, 77, 79, 0.15);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ padding-left: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-cron,
|
|
|
+ .schedule-item-next {
|
|
|
+ .info-label {
|
|
|
+ color: #6a7a8a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-value {
|
|
|
+ color: #5cb8ff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-prompt {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #8b949e;
|
|
|
+ line-height: 1.6;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-switch {
|
|
|
+ :deep(.zxm-switch) {
|
|
|
+ min-width: 36px;
|
|
|
+ height: 20px;
|
|
|
+ background: rgba(63, 80, 106, 0.5);
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ top: 2px;
|
|
|
+ left: 2px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
|
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.zxm-switch-checked {
|
|
|
+ background: rgba(10, 132, 255, 0.6);
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ left: calc(100% - 18px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-run-btn {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-image: var(--img-chat-schedule-run-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 14px 14px;
|
|
|
+ background-position: center;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: rgba(10, 132, 255, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.running {
|
|
|
+ opacity: 0.5;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-item-history-toggle {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #5cb8ff;
|
|
|
+ cursor: pointer;
|
|
|
+ margin-top: 4px;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ width: fit-content;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-arrow {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 4px solid transparent;
|
|
|
+ border-right: 4px solid transparent;
|
|
|
+ border-top: 5px solid #5cb8ff;
|
|
|
+ transition: transform 0.2s;
|
|
|
+
|
|
|
+ &.expanded {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-stats {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0;
|
|
|
+ margin-left: 6px;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 1.5;
|
|
|
+
|
|
|
+ .stats-total {
|
|
|
+ color: #8b949e;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-sep {
|
|
|
+ color: #4a5568;
|
|
|
+ margin: 0 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-success {
|
|
|
+ color: #52c41a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-failed {
|
|
|
+ color: #ff7875;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-history {
|
|
|
+ margin-top: 10px;
|
|
|
+ padding: 12px;
|
|
|
+ background: rgba(0, 0, 0, 0.15);
|
|
|
+ border: 1px solid rgba(63, 80, 106, 0.18);
|
|
|
+ border-radius: 6px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ max-height: 170px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background: rgba(63, 80, 106, 0.4);
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: rgba(10, 132, 255, 0.04);
|
|
|
+ border: 1px solid rgba(63, 80, 106, 0.15);
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-item-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+
|
|
|
+ .history-status {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6a7a8a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-duration {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8b949e;
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-output {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #c9d1d9;
|
|
|
+ line-height: 1.6;
|
|
|
+ max-height: 60px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 3;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-error {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #ff7875;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .history-empty {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6a7a8a;
|
|
|
+ text-align: center;
|
|
|
+ padding: 8px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 空状态 */
|
|
|
+ .schedule-empty {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ gap: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 40px;
|
|
|
+ border-radius: 12px;
|
|
|
+ transition: background 0.25s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.06);
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-empty-icon {
|
|
|
+ width: 120px;
|
|
|
+ height: 120px;
|
|
|
+ background-image: var(--img-chat-schedule-empty-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ background-position: center;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-empty-text {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #fff;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|