Files
abot/admin/dashboard/templates/message_push_management.html
2026-02-26 14:48:15 +08:00

1159 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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="voice">
<el-upload
class="upload-demo"
action="/message_push/api/upload"
{% raw %}:on-success="handleVoiceSuccess"
:before-upload="beforeVoiceUpload"
:on-preview="handleVoicePreview"
:file-list="voiceList" {% endraw %}>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传mp3/wav文件且不超过10MB</div>
</el-upload>
<el-dialog :visible.sync="voicePreviewVisible" append-to-body>
<audio controls :src="voicePreviewUrl" style="width: 100%"></audio>
</el-dialog>
</el-tab-pane>
<el-tab-pane label="视频" name="video">
<el-upload
class="upload-demo"
action="/message_push/api/upload"
{% raw %}:on-success="handleVideoSuccess"
:before-upload="beforeVideoUpload"
:on-preview="handleVideoPreview"
:file-list="videoList" {% endraw %}>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传mp4文件且不超过20MB</div>
</el-upload>
<el-dialog :visible.sync="videoPreviewVisible" append-to-body>
<video controls :src="videoPreviewUrl" style="width: 100%"></video>
</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_voice: '',
content_video: '',
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: [],
voiceList: [],
voicePreviewVisible: false,
voicePreviewUrl: '',
videoList: [],
videoPreviewVisible: false,
videoPreviewUrl: '',
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_voice: '',
content_video: '',
content_link: {
title: '',
des: '',
url: '',
thumburl: ''
},
content_miniprogram: {
title: '',
path: ''
},
preview_recipients: []
};
this.imageList = [];
this.voiceList = []; // 清空语音列表
this.videoList = []; // 清空视频列表
this.thumbnailList = []; // 清空缩略图列表
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();
}else{
this.$message.error(response.data.message);
}
} 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}` // 显示时使用相对路径
}];
}
// 处理语音
if (task.content_voice) {
const fileName = task.content_voice.split('/').pop();
this.voiceList = [{
name: '已上传语音',
url: `/static/uploads/${fileName}` // 显示时使用相对路径
}];
}
// 处理视频
if (task.content_video) {
const fileName = task.content_video.split('/').pop();
this.videoList = [{
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;
},
// 语音上传相关
handleVoiceSuccess(response, file) {
if (response.success) {
this.taskForm.content_voice = response.data.url;
const fileName = file.name;
this.voiceList = [{
name: fileName,
url: `/static/uploads/${response.data.url.split('/').pop()}`
}];
} else {
this.$message.error('上传失败');
}
},
beforeVoiceUpload(file) {
const isVoice = file.type === 'audio/mp3' || file.type === 'audio/wav';
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isVoice) {
this.$message.error('只能上传mp3/wav文件!');
return false;
}
if (!isLt10M) {
this.$message.error('语音文件大小不能超过 10MB!');
return false;
}
return true;
},
handleVoicePreview(file) {
const fileName = file.url.split('/').pop();
this.voicePreviewUrl = `/static/uploads/${fileName}`;
this.voicePreviewVisible = true;
},
// 视频上传相关
handleVideoSuccess(response, file) {
if (response.success) {
this.taskForm.content_video = response.data.url;
const fileName = file.name;
this.videoList = [{
name: fileName,
url: `/static/uploads/${response.data.url.split('/').pop()}`
}];
} else {
this.$message.error('上传失败');
}
},
beforeVideoUpload(file) {
const isVideo = file.type === 'video/mp4';
const isLt20M = file.size / 1024 / 1024 < 20;
if (!isVideo) {
this.$message.error('只能上传mp4文件!');
return false;
}
if (!isLt20M) {
this.$message.error('视频文件大小不能超过 20MB!');
return false;
}
return true;
},
handleVideoPreview(file) {
const fileName = file.url.split('/').pop();
this.videoPreviewUrl = `/static/uploads/${fileName}`;
this.videoPreviewVisible = 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: #6b86a8;
margin-bottom: 10px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #5bd3ff;
}
.batch-toolbar {
margin-bottom: 15px;
display: flex;
align-items: center;
}
.selected-count {
margin-left: 15px;
color: #6b86a8;
}
</style>
{% endblock %}