795 lines
32 KiB
HTML
795 lines
32 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="task_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
|
||
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-radio-group {% raw %}v-model="taskForm.schedule_type" {% endraw %}>
|
||
<el-radio label="once">单次任务</el-radio>
|
||
<el-radio label="recurring">重复任务</el-radio>
|
||
</el-radio-group>
|
||
</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_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" {% endraw %}></el-input>
|
||
</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: '',
|
||
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' }
|
||
],
|
||
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: ''
|
||
}
|
||
},
|
||
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: '',
|
||
content_miniprogram: {
|
||
title: '',
|
||
path: ''
|
||
},
|
||
preview_recipients: []
|
||
};
|
||
this.imageList = [];
|
||
this.taskDialogVisible = true;
|
||
},
|
||
|
||
// 保存任务
|
||
async saveTask() {
|
||
this.$refs.taskForm.validate(async (valid) => {
|
||
if (valid) {
|
||
try {
|
||
const response = await axios.post('/message_push/api/tasks', this.taskForm);
|
||
if (response.data.success) {
|
||
this.$message.success('保存任务成功');
|
||
this.taskDialogVisible = false;
|
||
this.loadTasks();
|
||
this.loadStatistics();
|
||
}
|
||
} catch (error) {
|
||
this.$message.error('保存任务失败');
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 预览任务
|
||
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 };
|
||
if (task.content_image) {
|
||
this.imageList = [{
|
||
name: '已上传图片',
|
||
url: task.content_image
|
||
}];
|
||
}
|
||
this.taskDialogVisible = 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;
|
||
this.imageList = [{
|
||
name: file.name,
|
||
url: response.data.url
|
||
}];
|
||
} 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) {
|
||
this.previewUrl = file.url;
|
||
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) {
|
||
return new Date(datetime).toLocaleString();
|
||
},
|
||
refreshTasks() {
|
||
this.loadTasks();
|
||
this.loadStatistics();
|
||
this.$message.success('数据已刷新');
|
||
}
|
||
}
|
||
});
|
||
</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 %} |