Files
abot/admin/dashboard/templates/message_push_management.html
T
2025-06-11 09:33:21 +08:00

1023 lines
43 KiB
HTML

{% extends "base.html" %}
{% block title %}消息推送管理 - 机器人管理后台{% endblock %}
{% block content %}
<div>
<el-row {% raw %}:gutter="20" {% endraw %}>
<el-col {% raw %}:span="24" {% endraw %}>
<el-card shadow="hover">
<div slot="header" class="clearfix">
<span>消息推送管理</span>
<el-button
type="primary"
size="small"
style="float: right; margin-left: 10px;"
{% raw %}@click="refreshTasks" {% endraw %}>
刷新数据
</el-button>
<el-button
type="success"
size="small"
style="float: right;"
{% raw %}@click="showCreateTaskDialog" {% endraw %}>
新建任务
</el-button>
</div>
<!-- 统计卡片 -->
<el-row {% raw %}:gutter="20" {% endraw %} style="margin-bottom: 20px;">
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">总任务数</div>
<div class="stat-value">{% raw %}{{ statistics.total }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">已排期</div>
<div class="stat-value">{% raw %}{{ statistics.scheduled }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">已暂停</div>
<div class="stat-value">{% raw %}{{ statistics.paused }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">已完成</div>
<div class="stat-value">{% raw %}{{ statistics.completed }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">失败</div>
<div class="stat-value">{% raw %}{{ statistics.failed }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="4" {% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">今日任务</div>
<div class="stat-value">{% raw %}{{ statistics.today }}{% endraw %}</div>
</el-card>
</el-col>
</el-row>
<!-- 搜索栏 -->
<el-form {% raw %}:inline="true" :model="searchForm" {% endraw %} class="search-form">
<el-form-item label="状态">
<el-select {% raw %}v-model="searchForm.status" {% endraw %} placeholder="全部状态" clearable>
<el-option label="草稿" value="draft"></el-option>
<el-option label="已排期" value="scheduled"></el-option>
<el-option label="运行中" value="running"></el-option>
<el-option label="已暂停" value="paused"></el-option>
<el-option label="已完成" value="completed"></el-option>
<el-option label="失败" value="failed"></el-option>
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
{% raw %}v-model="searchForm.timeRange" {% endraw %}
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" {% raw %}@click="searchTasks" {% endraw %}>搜索</el-button>
<el-button {% raw %}@click="resetSearch" {% endraw %}>重置</el-button>
</el-form-item>
</el-form>
<!-- 批量操作工具栏 -->
<div class="batch-toolbar" {% raw %}v-if="selectedTasks.length > 0" {% endraw %}>
<el-button-group>
<el-button size="small" type="primary" {% raw %}@click="batchPause" {% endraw %}>批量暂停</el-button>
<el-button size="small" type="success" {% raw %}@click="batchResume" {% endraw %}>批量恢复</el-button>
<el-button size="small" type="danger" {% raw %}@click="batchDelete" {% endraw %}>批量删除</el-button>
</el-button-group>
<span class="selected-count">已选择 {% raw %}{{ selectedTasks.length }}{% endraw %} 项</span>
</div>
<!-- 任务列表 -->
<el-table
{% raw %}:data="taskList" {% endraw %}
style="width: 100%"
border
{% raw %}v-loading="loading"
@selection-change="handleSelectionChange" {% endraw %}>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column prop="task_id" label="任务ID" width="180"></el-table-column>
<el-table-column prop="name" label="任务名称"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<el-tag {% raw %}:type="getStatusType(scope.row.status)" {% endraw %}>
{% raw %}{{ getStatusText(scope.row.status) }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="schedule_time" label="计划时间" width="180">
<template slot-scope="scope">
{% raw %}{{ formatDateTime(scope.row.schedule_time) }}{% endraw %}
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180">
<template slot-scope="scope">
{% raw %}{{ formatDateTime(scope.row.created_at) }}{% endraw %}
</template>
</el-table-column>
<el-table-column label="进度" width="200">
<template slot-scope="scope">
<el-progress
{% raw %}:percentage="scope.row.progress || 0"
:status="getProgressStatus(scope.row.status)" {% endraw %}>
</el-progress>
</template>
</el-table-column>
<el-table-column label="操作" width="300">
<template slot-scope="scope">
<el-button
{% raw %}v-if="scope.row.status === 'draft'" {% endraw %}
size="mini"
type="success"
{% raw %}@click="auditTask(scope.row)" {% endraw %}>
审核
</el-button>
<el-button
size="mini"
type="primary"
{% raw %}@click="previewTask(scope.row)" {% endraw %}>
预览
</el-button>
<el-button
size="mini"
type="warning"
{% raw %}@click="editTask(scope.row)" {% endraw %}>
编辑
</el-button>
<el-button
{% raw %}v-if="scope.row.status === 'scheduled'" {% endraw %}
size="mini"
type="info"
{% raw %}@click="pauseTask(scope.row)" {% endraw %}>
暂停
</el-button>
<el-button
{% raw %}v-if="scope.row.status === 'paused'" {% endraw %}
size="mini"
type="success"
{% raw %}@click="resumeTask(scope.row)" {% endraw %}>
恢复
</el-button>
<el-button
size="mini"
type="danger"
{% raw %}@click="deleteTask(scope.row)" {% endraw %}>
删除
</el-button>
<el-button
size="mini"
type="text"
{% raw %}@click="viewLogs(scope.row)" {% endraw %}>
日志
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
{% raw %}@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total" {% endraw %}>
</el-pagination>
</div>
</el-card>
</el-col>
</el-row>
<!-- 新建/编辑任务对话框 -->
<el-dialog {% raw %}:title="dialogTitle" :visible.sync="taskDialogVisible" {% endraw %} width="60%">
<el-form {% raw %}:model="taskForm" :rules="taskRules" ref="taskForm" {% endraw %} label-width="100px">
<el-form-item label="任务类型" prop="schedule_type">
<el-select
{% raw %}v-model="taskForm.schedule_type" {% endraw %}
placeholder="请选择任务类型"
{% raw %}:disabled="isEdit"{% endraw %}>
<el-option label="一次性任务" value="once"></el-option>
<el-option label="重复任务" value="recurring"></el-option>
</el-select>
</el-form-item>
<el-form-item label="任务名称" prop="name">
<el-input {% raw %}v-model="taskForm.name" {% endraw %}></el-input>
</el-form-item>
<el-form-item label="计划时间" prop="schedule_time">
<el-date-picker
{% raw %}v-model="taskForm.schedule_time" {% endraw %}
type="datetime"
placeholder="选择日期时间"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
<el-form-item
label="重复间隔"
prop="recurring_interval"
{% raw %}v-if="taskForm.schedule_type === 'recurring'" {% endraw %}>
<el-select {% raw %}v-model="taskForm.recurring_interval" {% endraw %} placeholder="请选择重复间隔">
<el-option label="每天" value="daily"></el-option>
<el-option label="每周" value="weekly"></el-option>
<el-option label="每月" value="monthly"></el-option>
</el-select>
</el-form-item>
<el-form-item
label="执行时间"
prop="recurring_time"
{% raw %}v-if="taskForm.schedule_type === 'recurring'" {% endraw %}>
<el-time-picker
{% raw %}v-model="taskForm.recurring_time" {% endraw %}
format="HH:mm"
placeholder="选择时间"
value-format="HH:mm">
</el-time-picker>
</el-form-item>
<el-form-item
label="每周执行日"
prop="weekly_days"
{% raw %}v-if="taskForm.schedule_type === 'recurring' && taskForm.recurring_interval === 'weekly'" {% endraw %}>
<el-select
{% raw %}v-model="taskForm.weekly_days" {% endraw %}
multiple
placeholder="请选择每周执行日">
<el-option label="周一" value="1"></el-option>
<el-option label="周二" value="2"></el-option>
<el-option label="周三" value="3"></el-option>
<el-option label="周四" value="4"></el-option>
<el-option label="周五" value="5"></el-option>
<el-option label="周六" value="6"></el-option>
<el-option label="周日" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item
label="每月执行日"
prop="monthly_day"
{% raw %}v-if="taskForm.schedule_type === 'recurring' && taskForm.recurring_interval === 'monthly'" {% endraw %}>
<el-select
{% raw %}v-model="taskForm.monthly_day" {% endraw %}
placeholder="请选择每月执行日">
<el-option
{% raw %}v-for="day in 31"
:key="day"
:label="`${day}日`"
:value="day" {% endraw %}>
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="重复结束时间"
prop="recurring_end"
{% raw %}v-if="taskForm.schedule_type === 'recurring'" {% endraw %}>
<el-date-picker
{% raw %}v-model="taskForm.recurring_end" {% endraw %}
type="datetime"
placeholder="选择结束时间"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-select {% raw %}v-model="taskForm.priority" {% endraw %} placeholder="请选择优先级">
<el-option label="高" value="high"></el-option>
<el-option label="中" value="medium"></el-option>
<el-option label="低" value="low"></el-option>
</el-select>
</el-form-item>
<el-form-item label="目标群组" prop="groups">
<el-select
{% raw %}v-model="taskForm.groups" {% endraw %}
multiple
filterable
placeholder="请选择群组">
<el-option
{% raw %}v-for="group in groupList"
:key="group.wxid"
:label="group.name"
:value="group.wxid" {% endraw %}>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="消息内容">
<el-tabs {% raw %}v-model="activeContentTab" {% endraw %}>
<el-tab-pane label="文本" name="text">
<el-input
type="textarea"
{% raw %}v-model="taskForm.content_text" {% endraw %}
:rows="4"
placeholder="请输入文本内容">
</el-input>
</el-tab-pane>
<el-tab-pane label="图片" name="image">
<el-upload
class="upload-demo"
action="/message_push/api/upload"
{% raw %}:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
:on-preview="handleImagePreview"
:file-list="imageList" {% endraw %}>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2MB</div>
</el-upload>
<el-dialog :visible.sync="previewVisible" append-to-body>
<img width="100%" :src="previewUrl" alt="Preview">
</el-dialog>
</el-tab-pane>
<el-tab-pane label="链接" name="link">
<el-form-item label="链接标题">
<el-input {% raw %}v-model="taskForm.content_link.title" {% endraw %}></el-input>
</el-form-item>
<el-form-item label="链接描述">
<el-input type="textarea" {% raw %}v-model="taskForm.content_link.des" {% endraw %}></el-input>
</el-form-item>
<el-form-item label="链接地址">
<el-input {% raw %}v-model="taskForm.content_link.url" {% endraw %}></el-input>
</el-form-item>
<el-form-item label="缩略图">
<el-upload
class="upload-demo"
action="/message_push/api/upload"
{% raw %}:on-success="handleThumbnailSuccess"
:before-upload="beforeImageUpload"
:on-preview="handleThumbnailPreview"
:file-list="thumbnailList" {% endraw %}>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2MB</div>
</el-upload>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="小程序" name="miniprogram">
<el-form-item label="标题">
<el-input {% raw %}v-model="taskForm.content_miniprogram.title" {% endraw %}></el-input>
</el-form-item>
<el-form-item label="路径">
<el-input {% raw %}v-model="taskForm.content_miniprogram.path" {% endraw %}></el-input>
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form-item>
<el-form-item label="预览接收人">
<el-select
{% raw %}v-model="taskForm.preview_recipients" {% endraw %}
multiple
filterable
placeholder="请选择预览接收人">
<el-option
{% raw %}v-for="user in userList"
:key="user.wxid"
:label="user.name"
:value="user.wxid" {% endraw %}>
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button {% raw %}@click="taskDialogVisible = false" {% endraw %}>取 消</el-button>
<el-button type="primary" {% raw %}@click="saveTask" {% endraw %}>确 定</el-button>
</div>
</el-dialog>
<!-- 任务日志对话框 -->
<el-dialog title="任务日志" {% raw %}:visible.sync="logsDialogVisible" {% endraw %} width="70%">
<el-table {% raw %}:data="taskLogs" {% endraw %} border style="width: 100%">
<el-table-column prop="timestamp" label="时间" width="180">
<template slot-scope="scope">
{% raw %}{{ formatDateTime(scope.row.timestamp) }}{% endraw %}
</template>
</el-table-column>
<el-table-column prop="action" label="操作" width="120"></el-table-column>
<el-table-column prop="operator_id" label="操作人" width="120"></el-table-column>
<el-table-column prop="changes" label="变更内容">
<template slot-scope="scope">
<pre>{% raw %}{{ JSON.stringify(scope.row.changes, null, 2) }}{% endraw %}</pre>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
currentView: '16', // 设置当前视图为消息推送管理
loading: false,
searchForm: {
status: '',
timeRange: []
},
taskList: [],
currentPage: 1,
pageSize: 10,
total: 0,
taskDialogVisible: false,
logsDialogVisible: false,
dialogTitle: '新建任务',
activeContentTab: 'text',
taskForm: {
name: '',
schedule_time: '',
schedule_type: 'once',
groups: [],
content_text: '',
content_image: '',
content_link: {
title: '',
des: '',
url: '',
thumburl: ''
},
content_miniprogram: {
title: '',
path: ''
},
preview_recipients: []
},
taskRules: {
name: [
{ required: true, message: '请输入任务名称', trigger: 'blur' }
],
schedule_time: [
{ required: true, message: '请选择计划时间', trigger: 'change' }
],
schedule_type: [
{ required: true, message: '请选择任务类型', trigger: 'change' }
],
recurring_interval: [
{ required: true, message: '请选择重复间隔', trigger: 'change' }
],
recurring_time: [
{ required: true, message: '请选择执行时间', trigger: 'change' }
],
weekly_days: [
{ required: true, message: '请选择每周执行日', trigger: 'change' }
],
monthly_day: [
{ required: true, message: '请选择每月执行日', trigger: 'change' }
],
recurring_end: [
{ required: true, message: '请选择重复结束时间', trigger: 'change' }
],
groups: [
{ required: true, message: '请选择目标群组', trigger: 'change' }
]
},
groupList: [],
userList: [],
taskLogs: [],
selectedTasks: [],
statistics: {
total: 0,
scheduled: 0,
paused: 0,
completed: 0,
failed: 0,
today: 0
},
imageList: [],
previewVisible: false,
previewUrl: '',
thumbnailList: [],
isEdit: false
}
},
mounted() {
this.loadTasks();
this.loadGroups();
this.loadUsers();
this.loadStatistics();
},
methods: {
// 加载任务列表
async loadTasks() {
this.loading = true;
try {
const params = {
page: this.currentPage,
limit: this.pageSize,
status: this.searchForm.status,
start_time: this.searchForm.timeRange[0],
end_time: this.searchForm.timeRange[1]
};
const response = await axios.get('/message_push/api/tasks', { params });
if (response.data.success) {
this.taskList = response.data.data.tasks;
this.total = response.data.data.total;
}
} catch (error) {
this.$message.error('加载任务列表失败');
}
this.loading = false;
},
// 加载统计数据
async loadStatistics() {
try {
const response = await axios.get('/message_push/api/statistics');
if (response.data.success) {
this.statistics = response.data.data;
}
} catch (error) {
this.$message.error('加载统计数据失败');
}
},
// 加载群组列表
async loadGroups() {
try {
const response = await axios.get('/contacts/api/groups');
if (response.data.success) {
const groups = response.data.data.groups;
this.groupList = Object.entries(groups).map(([wxid, name]) => ({
wxid,
name
}));
}
} catch (error) {
this.$message.error('加载群组列表失败');
}
},
// 加载用户列表
async loadUsers() {
try {
const response = await axios.get('/contacts/api/personal');
if (response.data.success) {
const users = response.data.data.personal;
this.userList = Object.entries(users).map(([wxid, name]) => ({
wxid,
name
}));
}
} catch (error) {
this.$message.error('加载用户列表失败');
}
},
// 搜索任务
searchTasks() {
this.currentPage = 1;
this.loadTasks();
},
// 重置搜索
resetSearch() {
this.searchForm = {
status: '',
timeRange: []
};
this.searchTasks();
},
// 显示新建任务对话框
showCreateTaskDialog() {
this.dialogTitle = '新建任务';
this.taskForm = {
name: '',
schedule_time: '',
schedule_type: 'once',
groups: [],
content_text: '',
content_image: '',
content_link: {
title: '',
des: '',
url: '',
thumburl: ''
},
content_miniprogram: {
title: '',
path: ''
},
preview_recipients: []
};
this.imageList = [];
this.taskDialogVisible = true;
this.isEdit = false;
},
// 保存任务
async saveTask() {
this.$refs.taskForm.validate(async (valid) => {
if (valid) {
try {
// 创建任务数据对象
const taskData = { ...this.taskForm };
// 处理重复任务的特殊字段
if (taskData.schedule_type === 'recurring') {
// 确保链接内容是JSON字符串
if (taskData.content_link) {
taskData.content_link = JSON.stringify(taskData.content_link);
}
// 处理每周执行日
if (taskData.weekly_days) {
taskData.weekly_days = JSON.stringify(taskData.weekly_days);
}
}
let response;
if (taskData.task_id) {
// 更新任务
response = await axios.put(`/message_push/api/tasks/${taskData.task_id}`, taskData);
} else {
// 创建新任务
response = await axios.post('/message_push/api/tasks', taskData);
}
if (response.data.success) {
this.$message.success(this.taskForm.task_id ? '更新任务成功' : '创建任务成功');
this.taskDialogVisible = false;
this.loadTasks();
this.loadStatistics();
}
} catch (error) {
this.$message.error(this.taskForm.task_id ? '更新任务失败' : '创建任务失败');
}
}
});
},
// 预览任务
async previewTask(task) {
try {
const response = await axios.post(`/message_push/api/tasks/${task.task_id}/preview`);
if (response.data.success) {
this.$message.success('预览已发送');
}
} catch (error) {
this.$message.error('发送预览失败');
}
},
// 编辑任务
editTask(task) {
this.dialogTitle = '编辑任务';
// 创建一个新对象,并确保日期字段被正确格式化
this.taskForm = {
...task,
schedule_time: task.schedule_time ? new Date(task.schedule_time).toISOString().slice(0, 19).replace('T', ' ') : '',
recurring_end: task.recurring_end ? new Date(task.recurring_end).toISOString().slice(0, 19).replace('T', ' ') : ''
};
// 处理 weekly_days
if (task.weekly_days) {
try {
this.taskForm.weekly_days = typeof task.weekly_days === 'string'
? JSON.parse(task.weekly_days)
: task.weekly_days;
} catch (e) {
console.error('解析每周执行日失败:', e);
this.taskForm.weekly_days = [];
}
} else {
this.taskForm.weekly_days = [];
}
// 处理链接内容
if (task.content_link) {
try {
this.taskForm.content_link = typeof task.content_link === 'string'
? JSON.parse(task.content_link)
: task.content_link;
// 如果有缩略图,显示缩略图
if (this.taskForm.content_link.thumburl) {
const fileName = this.taskForm.content_link.thumburl.split('/').pop();
this.thumbnailList = [{
name: '已上传缩略图',
url: `/static/uploads/${fileName}`
}];
}
} catch (e) {
console.error('解析链接内容失败:', e);
this.taskForm.content_link = {
title: '',
des: '',
url: '',
thumburl: ''
};
}
}
if (task.content_image) {
// 编辑时显示图片
const fileName = task.content_image.split('/').pop();
this.imageList = [{
name: '已上传图片',
url: `/static/uploads/${fileName}` // 显示时使用相对路径
}];
}
this.taskDialogVisible = true;
this.isEdit = true;
},
// 暂停任务
async pauseTask(task) {
try {
const response = await axios.post(`/message_push/api/tasks/${task.task_id}/pause`);
if (response.data.success) {
this.$message.success('任务已暂停');
this.loadTasks();
this.loadStatistics();
}
} catch (error) {
this.$message.error('暂停任务失败');
}
},
// 恢复任务
async resumeTask(task) {
try {
const response = await axios.post(`/message_push/api/tasks/${task.task_id}/resume`);
if (response.data.success) {
this.$message.success('任务已恢复');
this.loadTasks();
this.loadStatistics();
}
} catch (error) {
this.$message.error('恢复任务失败');
}
},
// 删除任务
deleteTask(task) {
this.$confirm('确认删除该任务吗?', '提示', {
type: 'warning'
}).then(async () => {
try {
const response = await axios.delete(`/message_push/api/tasks/${task.task_id}`);
if (response.data.success) {
this.$message.success('任务已删除');
this.loadTasks();
this.loadStatistics();
}
} catch (error) {
this.$message.error('删除任务失败');
}
});
},
// 查看日志
async viewLogs(task) {
try {
const response = await axios.get(`/message_push/api/tasks/${task.task_id}/logs`);
if (response.data.success) {
this.taskLogs = response.data.data.logs;
this.logsDialogVisible = true;
}
} catch (error) {
this.$message.error('获取任务日志失败');
}
},
// 处理分页
handleSizeChange(size) {
this.pageSize = size;
this.loadTasks();
},
handleCurrentChange(page) {
this.currentPage = page;
this.loadTasks();
},
// 处理表格选择
handleSelectionChange(selection) {
this.selectedTasks = selection;
},
// 批量操作
async batchPause() {
try {
const promises = this.selectedTasks.map(task =>
axios.post(`/message_push/api/tasks/${task.task_id}/pause`)
);
await Promise.all(promises);
this.$message.success('批量暂停成功');
this.loadTasks();
this.loadStatistics();
} catch (error) {
this.$message.error('批量暂停失败');
}
},
async batchResume() {
try {
const promises = this.selectedTasks.map(task =>
axios.post(`/message_push/api/tasks/${task.task_id}/resume`)
);
await Promise.all(promises);
this.$message.success('批量恢复成功');
this.loadTasks();
this.loadStatistics();
} catch (error) {
this.$message.error('批量恢复失败');
}
},
batchDelete() {
this.$confirm(`确认删除选中的 ${this.selectedTasks.length} 个任务吗?`, '提示', {
type: 'warning'
}).then(async () => {
try {
const promises = this.selectedTasks.map(task =>
axios.delete(`/message_push/api/tasks/${task.task_id}`)
);
await Promise.all(promises);
this.$message.success('批量删除成功');
this.loadTasks();
this.loadStatistics();
} catch (error) {
this.$message.error('批量删除失败');
}
});
},
// 图片上传相关
handleImageSuccess(response, file) {
if (response.success) {
this.taskForm.content_image = response.data.url; // 存储绝对路径
// 显示时使用文件名
const fileName = file.name;
this.imageList = [{
name: fileName,
url: `/static/uploads/${response.data.url.split('/').pop()}` // 显示时使用相对路径
}];
} else {
this.$message.error('上传失败');
}
},
beforeImageUpload(file) {
const isImage = file.type.startsWith('image/');
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
this.$message.error('只能上传图片文件!');
return false;
}
if (!isLt2M) {
this.$message.error('图片大小不能超过 2MB!');
return false;
}
return true;
},
handleImagePreview(file) {
// 预览时使用相对路径
const fileName = file.url.split('/').pop();
this.previewUrl = `/static/uploads/${fileName}`;
this.previewVisible = true;
},
// 缩略图上传相关
handleThumbnailSuccess(response, file) {
if (response.success) {
this.taskForm.content_link.thumburl = response.data.url; // 存储绝对路径
// 显示时使用文件名
const fileName = file.name;
this.thumbnailList = [{
name: fileName,
url: `/static/uploads/${response.data.url.split('/').pop()}` // 显示时使用相对路径
}];
} else {
this.$message.error('上传失败');
}
},
handleThumbnailPreview(file) {
// 预览时使用相对路径
const fileName = file.url.split('/').pop();
this.previewUrl = `/static/uploads/${fileName}`;
this.previewVisible = true;
},
// 工具函数
getStatusType(status) {
const typeMap = {
'draft': 'info',
'scheduled': 'success',
'running': 'warning',
'paused': 'warning',
'completed': '',
'failed': 'danger'
};
return typeMap[status] || 'info';
},
getStatusText(status) {
const textMap = {
'draft': '草稿',
'scheduled': '已排期',
'running': '运行中',
'paused': '已暂停',
'completed': '已完成',
'failed': '失败'
};
return textMap[status] || status;
},
getProgressStatus(status) {
const statusMap = {
'completed': 'success',
'failed': 'exception',
'running': 'warning',
'scheduled': 'warning',
'paused': 'info'
};
return statusMap[status] || '';
},
formatDateTime(datetime) {
if (!datetime) return '';
try {
// 确保输入是有效的日期字符串
const date = new Date(datetime);
if (isNaN(date.getTime())) {
return datetime; // 如果解析失败,返回原始字符串
}
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} catch (e) {
console.error('Date parsing error:', e);
return datetime; // 如果出错,返回原始字符串
}
},
refreshTasks() {
this.loadTasks();
this.loadStatistics();
this.$message.success('数据已刷新');
},
// 审核任务
async auditTask(task) {
try {
const response = await axios.post(`/message_push/api/tasks/${task.task_id}/audit`);
if (response.data.success) {
this.$message.success('任务已审核通过');
this.loadTasks();
this.loadStatistics();
}
} catch (error) {
this.$message.error('审核任务失败');
}
}
}
});
</script>
<style>
.search-form {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.stat-card {
text-align: center;
padding: 10px;
}
.stat-title {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #409EFF;
}
.batch-toolbar {
margin-bottom: 15px;
display: flex;
align-items: center;
}
.selected-count {
margin-left: 15px;
color: #606266;
}
</style>
{% endblock %}