mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-06 17:09:08 +08:00
feat(manager): 优化定时任务管理、敏感词场景配置与商品选择器多规格匹配
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { managerApiUrl, getRequest, postRequest } from '@/libs/axios';
|
||||
import { managerApiUrl, getRequest, postRequest, putRequestWithNoForm } from '@/libs/axios';
|
||||
|
||||
// 获取定时任务列表
|
||||
export const getTimedTaskList = () => {
|
||||
return getRequest(`${managerApiUrl}/manager/setting/timed-task`);
|
||||
};
|
||||
|
||||
// 手动触发定时任务
|
||||
export const triggerTimedTask = (type) => {
|
||||
return postRequest(`${managerApiUrl}/manager/setting/timed-task/trigger/${type}`);
|
||||
// 修改定时任务(Cron表达式、启用/暂停状态)
|
||||
export const updateTimedTask = (params) => {
|
||||
return putRequestWithNoForm(`${managerApiUrl}/manager/setting/timed-task`, params);
|
||||
};
|
||||
|
||||
// 手动触发定时任务
|
||||
export const triggerTimedTask = (taskId) => {
|
||||
return postRequest(`${managerApiUrl}/manager/setting/timed-task/trigger/${taskId}`);
|
||||
};
|
||||
|
||||
@@ -189,7 +189,7 @@ function syncTableSelection() {
|
||||
return;
|
||||
}
|
||||
goodsData.value.forEach((item) => {
|
||||
const selected = selectedList.value.some((e) => e.id && e.id === item.id);
|
||||
const selected = selectedList.value.some((e) => (e.id || e.skuId) && String(e.id || e.skuId) === String(item.id || item.skuId));
|
||||
table.toggleRowSelection(item, selected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,48 +1,105 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<div class="operation">
|
||||
<el-button type="primary" @click="add">添加</el-button>
|
||||
<el-button @click="delAll">批量删除</el-button>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab" @tab-change="clickTab">
|
||||
<el-tab-pane label="词库管理" name="words">
|
||||
<div class="operation">
|
||||
<el-button type="primary" @click="add">添加</el-button>
|
||||
<el-button @click="delAll">批量删除</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="data"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
@selection-change="changeSelect"
|
||||
>
|
||||
<el-table-column type="selection" width="60" align="center" />
|
||||
<el-table-column prop="sensitiveWord" label="敏感词" min-width="120" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="updateTime" label="更新时间" width="200" />
|
||||
<el-table-column prop="createBy" label="操作人" min-width="150" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row">
|
||||
<a class="link-text" @click="detail(row)">修改</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="remove(row)">删除</a>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="data"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
@selection-change="changeSelect"
|
||||
>
|
||||
<el-table-column type="selection" width="60" align="center" />
|
||||
<el-table-column prop="sensitiveWord" label="敏感词" min-width="120" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="updateTime" label="更新时间" width="200" />
|
||||
<el-table-column prop="createBy" label="操作人" min-width="150" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row">
|
||||
<a class="link-text" @click="detail(row)">修改</a>
|
||||
<span class="op-split">|</span>
|
||||
<a class="link-text" @click="remove(row)">删除</a>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="敏感词设置" name="switch">
|
||||
<div v-loading="settingLoading" class="switch-setting">
|
||||
<ul class="side-menu">
|
||||
<li
|
||||
:class="{ active: sideKey === 'func' }"
|
||||
@click="sideKey = 'func'"
|
||||
>
|
||||
功能开关
|
||||
</li>
|
||||
<li
|
||||
:class="{ active: sideKey === 'scene' }"
|
||||
@click="sideKey = 'scene'"
|
||||
>
|
||||
应用场景配置
|
||||
</li>
|
||||
</ul>
|
||||
<div class="setting-content">
|
||||
<template v-if="sideKey === 'func'">
|
||||
<div class="setting-row">
|
||||
<span class="row-label">敏感词过滤:</span>
|
||||
<el-switch v-model="settingForm.enabled" />
|
||||
</div>
|
||||
<div class="row-desc">关闭后,所有场景的敏感词过滤均不生效</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="group in sensitiveGroups"
|
||||
:key="group.key"
|
||||
class="scene-row"
|
||||
>
|
||||
<div class="setting-row">
|
||||
<span class="row-label">{{ group.name }}:</span>
|
||||
<el-switch
|
||||
:model-value="!settingForm.disabledGroups.includes(group.key)"
|
||||
@update:model-value="toggleGroup(group.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="row-desc">涉及:{{ group.desc }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="save-bar">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="settingSaving"
|
||||
@click="saveSetting"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
@@ -72,11 +129,16 @@ import {
|
||||
delSensitive,
|
||||
insertSensitiveWords,
|
||||
updateSensitiveWords,
|
||||
getSetting,
|
||||
setSetting,
|
||||
} from "@/api/index";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
|
||||
defineOptions({ name: "sensitive-words" });
|
||||
|
||||
const SETTING_KEY = "SENSITIVE_WORDS_SETTING";
|
||||
|
||||
const activeTab = ref("words");
|
||||
const loading = ref(true);
|
||||
const modalType = ref(0);
|
||||
const modalVisible = ref(false);
|
||||
@@ -111,9 +173,90 @@ let id = null;
|
||||
const table = ref(null);
|
||||
const formRef = ref(null);
|
||||
|
||||
// 开关设置
|
||||
const sideKey = ref("func");
|
||||
const settingLoading = ref(false);
|
||||
const settingSaving = ref(false);
|
||||
const settingLoaded = ref(false);
|
||||
const settingForm = reactive({
|
||||
enabled: true,
|
||||
disabledGroups: [],
|
||||
});
|
||||
const sensitiveGroups = [
|
||||
{ key: "MEMBER", name: "会员信息", desc: "用户昵称、收货地址信息" },
|
||||
{ key: "EVALUATION", name: "产品评价", desc: "用户发布评论信息" },
|
||||
{ key: "STORE", name: "商家", desc: "商家入驻信息" },
|
||||
{ key: "GOODS", name: "产品信息", desc: "商品发布、商品修改、商品分类信息" },
|
||||
{ key: "PROMOTION", name: "促销活动", desc: "活动名称、优惠券名称信息" },
|
||||
{ key: "DECORATION", name: "装修模块", desc: "导航名称、装修楼层中的标题和子标题" },
|
||||
{ key: "REASON", name: "原因管理", desc: "退款/退货/换货原因信息" },
|
||||
{ key: "ARTICLE", name: "资讯管理", desc: "分类名称、文章标题、文章详情" },
|
||||
];
|
||||
|
||||
function init() {
|
||||
getDataList();
|
||||
}
|
||||
function clickTab(name) {
|
||||
if (name === "switch" && !settingLoaded.value) {
|
||||
loadSetting();
|
||||
}
|
||||
}
|
||||
function loadSetting() {
|
||||
settingLoading.value = true;
|
||||
getSetting(SETTING_KEY)
|
||||
.then((res) => {
|
||||
let parsed = {};
|
||||
const raw = res ? res.result : null;
|
||||
if (typeof raw === "string") {
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
parsed = {};
|
||||
}
|
||||
} else if (raw && typeof raw === "object") {
|
||||
parsed = raw;
|
||||
}
|
||||
settingForm.enabled =
|
||||
parsed.enabled !== undefined ? Boolean(parsed.enabled) : true;
|
||||
settingForm.disabledGroups = Array.isArray(parsed.disabledGroups)
|
||||
? parsed.disabledGroups
|
||||
: [];
|
||||
})
|
||||
.catch(() => {
|
||||
// 设置记录不存在时按默认值处理
|
||||
settingForm.enabled = true;
|
||||
settingForm.disabledGroups = [];
|
||||
})
|
||||
.finally(() => {
|
||||
settingLoading.value = false;
|
||||
settingLoaded.value = true;
|
||||
});
|
||||
}
|
||||
function toggleGroup(key, enabled) {
|
||||
const index = settingForm.disabledGroups.indexOf(key);
|
||||
if (enabled && index > -1) {
|
||||
settingForm.disabledGroups.splice(index, 1);
|
||||
} else if (!enabled && index === -1) {
|
||||
settingForm.disabledGroups.push(key);
|
||||
}
|
||||
}
|
||||
function saveSetting() {
|
||||
settingSaving.value = true;
|
||||
setSetting(SETTING_KEY, {
|
||||
enabled: settingForm.enabled,
|
||||
disabledGroups: settingForm.disabledGroups,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("保存成功");
|
||||
} else {
|
||||
Message.error(res.message || "保存失败");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
settingSaving.value = false;
|
||||
});
|
||||
}
|
||||
function changePage(v) {
|
||||
searchForm.pageNumber = v;
|
||||
getDataList();
|
||||
@@ -236,3 +379,81 @@ onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.switch-setting {
|
||||
display: flex;
|
||||
min-height: 300px;
|
||||
border: 1px solid #e8eaec;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.side-menu {
|
||||
width: 140px;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
list-style: none;
|
||||
border-right: 1px solid #e8eaec;
|
||||
|
||||
li {
|
||||
padding: 10px 16px;
|
||||
color: #2d8cf0;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
border-left: 2px solid transparent;
|
||||
|
||||
&.active {
|
||||
color: #2d8cf0;
|
||||
font-weight: 600;
|
||||
background-color: #f0f8ff;
|
||||
border-left-color: #2d8cf0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-content {
|
||||
flex: 1;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row-label {
|
||||
display: inline-block;
|
||||
min-width: 80px;
|
||||
margin-right: 12px;
|
||||
color: #515a6e;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.row-desc {
|
||||
margin: 8px 0 0 92px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.scene-row {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.row-desc {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.save-bar {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,54 +1,281 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="每日任务" name="每日任务" />
|
||||
<el-tab-pane label="每小时任务" name="每小时" />
|
||||
<el-tab-pane label="每分钟任务" name="每分钟" />
|
||||
<div class="timed-task-page">
|
||||
<el-card shadow="never">
|
||||
<!-- 顶部统计与操作栏 -->
|
||||
<div class="header-bar">
|
||||
<div class="stats-info">
|
||||
<span class="stat-item">全部任务:<b>{{ taskList.length }}</b></span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="stat-item text-success">运行中:<b>{{ runningCount }}</b></span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="stat-item text-muted">已暂停:<b>{{ pausedCount }}</b></span>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索任务名称 / 标识"
|
||||
clearable
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:prefix-icon="Search"
|
||||
/>
|
||||
<el-button :icon="Refresh" @click="loadData" :loading="loading">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分组 Tabs -->
|
||||
<el-tabs v-model="activeGroup" style="margin-top: 12px">
|
||||
<el-tab-pane label="全部" name="ALL" />
|
||||
<el-tab-pane
|
||||
v-for="g in groupList"
|
||||
:key="g"
|
||||
:label="g"
|
||||
:name="g"
|
||||
/>
|
||||
</el-tabs>
|
||||
|
||||
<el-table v-loading="loading" :data="currentTasks" border style="width: 100%; margin-top: 12px">
|
||||
<el-table-column prop="name" label="任务名称" width="220" />
|
||||
<el-table-column prop="cron" label="Cron表达式" width="140" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<!-- 任务列表表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="filteredTasks"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%; margin-top: 8px"
|
||||
>
|
||||
<el-table-column label="任务名称 / 标识" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div class="task-title-cell">
|
||||
<span class="task-name">{{ row.name }}</span>
|
||||
<span class="task-id">{{ row.id }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="group" label="分组" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" effect="plain">{{ row.group || '其他' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="description" label="任务说明" min-width="240" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="Cron 表达式" width="180">
|
||||
<template #default="{ row }">
|
||||
<div class="cron-cell">
|
||||
<code class="cron-tag">{{ row.cron }}</code>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
:icon="Edit"
|
||||
title="修改Cron"
|
||||
@click="openEditDialog(row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:loading="row.statusLoading"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
@change="(val) => handleStatusChange(row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="lastExecTime" label="上次执行时间" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.lastExecTime || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="上次执行结果" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.lastExecStatus === 1">
|
||||
<el-tag type="success" size="small">
|
||||
成功
|
||||
<span v-if="row.lastExecCost != null">({{ row.lastExecCost }}ms)</span>
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else-if="row.lastExecStatus === 0">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="row.lastExecError || '未知错误'"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger" size="small" style="cursor: pointer">
|
||||
失败 ℹ
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="info" size="small">未执行</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="openEditDialog(row)"
|
||||
>
|
||||
配置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
link
|
||||
size="small"
|
||||
:loading="row.triggering"
|
||||
@click="handleTrigger(row)"
|
||||
>
|
||||
手动触发
|
||||
立即执行
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 修改 Cron 表达式弹窗 -->
|
||||
<el-dialog
|
||||
v-model="editDialogVisible"
|
||||
:title="`配置任务: ${currentRow?.name || ''}`"
|
||||
width="540px"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form label-width="110px" style="margin-top: 10px">
|
||||
<el-form-item label="任务标识">
|
||||
<el-input :model-value="currentRow?.id" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务名称">
|
||||
<el-input :model-value="currentRow?.name" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务说明">
|
||||
<div class="desc-text">{{ currentRow?.description || '-' }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="Cron 表达式" required>
|
||||
<el-input
|
||||
v-model="editForm.cron"
|
||||
placeholder="例如: 0 0/5 * * * ?"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="快捷预设">
|
||||
<div class="cron-presets">
|
||||
<el-button
|
||||
v-for="p in cronPresets"
|
||||
:key="p.cron"
|
||||
size="small"
|
||||
text
|
||||
bg
|
||||
@click="editForm.cron = p.cron"
|
||||
>
|
||||
{{ p.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="editForm.status" />
|
||||
<span style="margin-left: 10px; color: #909399; font-size: 13px">
|
||||
{{ editForm.status ? '保存后自动开启调度' : '保存后暂停该任务调度' }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { getTimedTaskList, triggerTimedTask } from "@/api/timedTask.js";
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Search, Refresh, Edit } from '@element-plus/icons-vue';
|
||||
import { getTimedTaskList, updateTimedTask, triggerTimedTask } from '@/api/timedTask.js';
|
||||
|
||||
defineOptions({ name: "timed-task-manage" });
|
||||
defineOptions({ name: 'timed-task-manage' });
|
||||
|
||||
const loading = ref(false);
|
||||
const activeTab = ref("每日任务");
|
||||
const taskList = ref([]);
|
||||
const activeGroup = ref('ALL');
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const currentTasks = computed(() => {
|
||||
return taskList.value.filter((t) => t.group === activeTab.value);
|
||||
// 对话框表单
|
||||
const editDialogVisible = ref(false);
|
||||
const currentRow = ref(null);
|
||||
const saving = ref(false);
|
||||
const editForm = ref({
|
||||
id: '',
|
||||
cron: '',
|
||||
status: true,
|
||||
});
|
||||
|
||||
function init() {
|
||||
// 快捷 Cron 模板
|
||||
const cronPresets = [
|
||||
{ label: '每1分钟', cron: '0 * * * * ?' },
|
||||
{ label: '每5分钟', cron: '0 0/5 * * * ?' },
|
||||
{ label: '每10分钟', cron: '0 0/10 * * * ?' },
|
||||
{ label: '每30分钟', cron: '0 0/30 * * * ?' },
|
||||
{ label: '每小时整点', cron: '0 0 * * * ?' },
|
||||
{ label: '每日0点', cron: '0 0 0 * * ?' },
|
||||
{ label: '每日凌晨2点', cron: '0 0 2 * * ?' },
|
||||
{ label: '每日凌晨3点', cron: '0 0 3 * * ?' },
|
||||
{ label: '每日凌晨6点', cron: '0 0 6 * * ?' },
|
||||
];
|
||||
|
||||
const runningCount = computed(() => {
|
||||
return taskList.value.filter((t) => t.status).length;
|
||||
});
|
||||
|
||||
const pausedCount = computed(() => {
|
||||
return taskList.value.filter((t) => !t.status).length;
|
||||
});
|
||||
|
||||
const groupList = computed(() => {
|
||||
const set = new Set();
|
||||
taskList.value.forEach((t) => {
|
||||
if (t.group) set.add(t.group);
|
||||
});
|
||||
return Array.from(set);
|
||||
});
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let list = taskList.value;
|
||||
if (activeGroup.value !== 'ALL') {
|
||||
list = list.filter((t) => t.group === activeGroup.value);
|
||||
}
|
||||
if (searchKeyword.value.trim()) {
|
||||
const kw = searchKeyword.value.trim().toLowerCase();
|
||||
list = list.filter(
|
||||
(t) =>
|
||||
(t.name && t.name.toLowerCase().includes(kw)) ||
|
||||
(t.id && t.id.toLowerCase().includes(kw)) ||
|
||||
(t.description && t.description.toLowerCase().includes(kw))
|
||||
);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
getTimedTaskList()
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
taskList.value = res.result.map((t) => ({ ...t, triggering: false }));
|
||||
if (res.success && res.result) {
|
||||
taskList.value = res.result.map((t) => ({
|
||||
...t,
|
||||
statusLoading: false,
|
||||
triggering: false,
|
||||
}));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -56,11 +283,70 @@ function init() {
|
||||
});
|
||||
}
|
||||
|
||||
function handleStatusChange(row, newStatus) {
|
||||
row.statusLoading = true;
|
||||
updateTimedTask({
|
||||
id: row.id,
|
||||
status: newStatus,
|
||||
})
|
||||
.then((res) => {
|
||||
row.statusLoading = false;
|
||||
if (res.success) {
|
||||
ElMessage.success(`任务 "${row.name}" 已${newStatus ? '开启' : '暂停'}`);
|
||||
} else {
|
||||
row.status = !newStatus;
|
||||
ElMessage.error(res.message || '修改状态失败');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
row.statusLoading = false;
|
||||
row.status = !newStatus;
|
||||
ElMessage.error('网络异常,修改状态失败');
|
||||
});
|
||||
}
|
||||
|
||||
function openEditDialog(row) {
|
||||
currentRow.value = row;
|
||||
editForm.value = {
|
||||
id: row.id,
|
||||
cron: row.cron,
|
||||
status: row.status,
|
||||
};
|
||||
editDialogVisible.value = true;
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
if (!editForm.value.cron || !editForm.value.cron.trim()) {
|
||||
ElMessage.warning('Cron 表达式不能为空');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
updateTimedTask({
|
||||
id: editForm.value.id,
|
||||
cron: editForm.value.cron.trim(),
|
||||
status: editForm.value.status,
|
||||
})
|
||||
.then((res) => {
|
||||
saving.value = false;
|
||||
if (res.success) {
|
||||
ElMessage.success('定时任务配置保存成功');
|
||||
editDialogVisible.value = false;
|
||||
loadData();
|
||||
} else {
|
||||
ElMessage.error(res.message || '保存失败');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
saving.value = false;
|
||||
ElMessage.error('保存失败');
|
||||
});
|
||||
}
|
||||
|
||||
function handleTrigger(row) {
|
||||
ElMessageBox.confirm(`确认要手动触发 "${row.name}" 吗?`, "提示", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
ElMessageBox.confirm(`确认要手动立即执行一次 "${row.name}" 任务吗?`, '提示', {
|
||||
confirmButtonText: '立即执行',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
row.triggering = true;
|
||||
@@ -68,20 +354,100 @@ function handleTrigger(row) {
|
||||
.then((res) => {
|
||||
row.triggering = false;
|
||||
if (res.success) {
|
||||
ElMessage.success(`"${row.name}" 已触发执行`);
|
||||
ElMessage.success(`"${row.name}" 执行成功`);
|
||||
loadData();
|
||||
} else {
|
||||
ElMessage.error(res.message || "触发失败");
|
||||
ElMessage.error(res.message || '执行失败');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
row.triggering = false;
|
||||
ElMessage.error("触发失败");
|
||||
ElMessage.error(err?.response?.data?.message || '执行失败');
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
loadData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timed-task-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.stat-item b {
|
||||
margin-left: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.text-success b {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.text-muted b {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.task-title-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.task-id {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
.cron-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cron-tag {
|
||||
background-color: #f4f4f5;
|
||||
color: #409eff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cron-presets {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user