439 lines
17 KiB
HTML
439 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}消息推送管理{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">消息推送管理</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#createTaskModal">
|
|
<i class="fas fa-plus"></i> 新建任务
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- 搜索栏 -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<select class="form-control" id="statusFilter">
|
|
<option value="">全部状态</option>
|
|
<option value="draft">草稿</option>
|
|
<option value="scheduled">已排期</option>
|
|
<option value="paused">已暂停</option>
|
|
<option value="completed">已完成</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input type="date" class="form-control" id="startTimeFilter" placeholder="开始时间">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input type="date" class="form-control" id="endTimeFilter" placeholder="结束时间">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button type="button" class="btn btn-info" id="searchBtn">
|
|
<i class="fas fa-search"></i> 搜索
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="resetBtn">
|
|
<i class="fas fa-redo"></i> 重置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 任务列表 -->
|
|
<table class="table table-bordered table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>任务ID</th>
|
|
<th>任务名称</th>
|
|
<th>状态</th>
|
|
<th>计划时间</th>
|
|
<th>创建时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="taskList">
|
|
<!-- 任务列表将通过JavaScript动态加载 -->
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 分页 -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="dataTables_info" id="taskInfo" role="status" aria-live="polite">
|
|
显示 0 到 0 条,共 0 条记录
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="dataTables_paginate paging_simple_numbers" id="taskPagination">
|
|
<!-- 分页将通过JavaScript动态加载 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 新建任务模态框 -->
|
|
<div class="modal fade" id="createTaskModal" tabindex="-1" role="dialog" aria-labelledby="createTaskModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createTaskModalLabel">新建任务</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="createTaskForm">
|
|
<div class="form-group">
|
|
<label for="taskName">任务名称</label>
|
|
<input type="text" class="form-control" id="taskName" name="task_name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="scheduleTime">计划时间</label>
|
|
<input type="datetime-local" class="form-control" id="scheduleTime" name="schedule_time" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="groups">目标群组</label>
|
|
<select class="form-control" id="groups" name="groups" multiple required>
|
|
<!-- 群组列表将通过JavaScript动态加载 -->
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="contentText">文本内容</label>
|
|
<textarea class="form-control" id="contentText" name="content_text" rows="3"></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="contentImage">图片内容</label>
|
|
<input type="file" class="form-control-file" id="contentImage" name="content_image">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="contentLink">链接内容</label>
|
|
<input type="url" class="form-control" id="contentLink" name="content_link">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="contentMiniprogram">小程序内容</label>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" id="miniprogramTitle" name="miniprogram_title" placeholder="标题">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" id="miniprogramPath" name="miniprogram_path" placeholder="路径">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="previewRecipients">预览接收人</label>
|
|
<select class="form-control" id="previewRecipients" name="preview_recipients" multiple>
|
|
<!-- 用户列表将通过JavaScript动态加载 -->
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
|
|
<button type="button" class="btn btn-primary" id="saveTaskBtn">保存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 任务日志模态框 -->
|
|
<div class="modal fade" id="taskLogsModal" tabindex="-1" role="dialog" aria-labelledby="taskLogsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="taskLogsModalLabel">任务日志</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>时间</th>
|
|
<th>操作</th>
|
|
<th>操作人</th>
|
|
<th>变更内容</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="taskLogsList">
|
|
<!-- 日志列表将通过JavaScript动态加载 -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
// 全局变量
|
|
let currentPage = 1;
|
|
let pageSize = 20;
|
|
let totalPages = 0;
|
|
|
|
// 加载任务列表
|
|
function loadTasks(page = 1) {
|
|
const status = $('#statusFilter').val();
|
|
const startTime = $('#startTimeFilter').val();
|
|
const endTime = $('#endTimeFilter').val();
|
|
|
|
$.get('/message_push/api/tasks', {
|
|
page: page,
|
|
limit: pageSize,
|
|
status: status,
|
|
start_time: startTime,
|
|
end_time: endTime
|
|
}, function(response) {
|
|
if (response.success) {
|
|
const { tasks, total } = response.data;
|
|
totalPages = Math.ceil(total / pageSize);
|
|
|
|
// 渲染任务列表
|
|
let html = '';
|
|
tasks.forEach(task => {
|
|
html += `
|
|
<tr>
|
|
<td>${task.task_id}</td>
|
|
<td>${task.task_name}</td>
|
|
<td>${getStatusText(task.status)}</td>
|
|
<td>${formatDateTime(task.schedule_time)}</td>
|
|
<td>${formatDateTime(task.created_at)}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-info" onclick="previewTask('${task.task_id}')">
|
|
<i class="fas fa-eye"></i> 预览
|
|
</button>
|
|
<button class="btn btn-sm btn-warning" onclick="editTask('${task.task_id}')">
|
|
<i class="fas fa-edit"></i> 编辑
|
|
</button>
|
|
${task.status === 'scheduled' ? `
|
|
<button class="btn btn-sm btn-secondary" onclick="pauseTask('${task.task_id}')">
|
|
<i class="fas fa-pause"></i> 暂停
|
|
</button>
|
|
` : task.status === 'paused' ? `
|
|
<button class="btn btn-sm btn-success" onclick="resumeTask('${task.task_id}')">
|
|
<i class="fas fa-play"></i> 恢复
|
|
</button>
|
|
` : ''}
|
|
<button class="btn btn-sm btn-danger" onclick="deleteTask('${task.task_id}')">
|
|
<i class="fas fa-trash"></i> 删除
|
|
</button>
|
|
<button class="btn btn-sm btn-primary" onclick="viewLogs('${task.task_id}')">
|
|
<i class="fas fa-history"></i> 日志
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
$('#taskList').html(html);
|
|
|
|
// 更新分页信息
|
|
updatePagination(page, total);
|
|
} else {
|
|
showError('加载任务列表失败:' + response.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 更新分页信息
|
|
function updatePagination(currentPage, total) {
|
|
// 更新信息文本
|
|
const start = (currentPage - 1) * pageSize + 1;
|
|
const end = Math.min(currentPage * pageSize, total);
|
|
$('#taskInfo').text(`显示 ${start} 到 ${end} 条,共 ${total} 条记录`);
|
|
|
|
// 更新分页按钮
|
|
let html = '<ul class="pagination">';
|
|
|
|
// 上一页
|
|
html += `
|
|
<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="return loadTasks(${currentPage - 1})">上一页</a>
|
|
</li>
|
|
`;
|
|
|
|
// 页码
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
|
|
html += `
|
|
<li class="page-item ${i === currentPage ? 'active' : ''}">
|
|
<a class="page-link" href="#" onclick="return loadTasks(${i})">${i}</a>
|
|
</li>
|
|
`;
|
|
} else if (i === currentPage - 3 || i === currentPage + 3) {
|
|
html += '<li class="page-item disabled"><a class="page-link">...</a></li>';
|
|
}
|
|
}
|
|
|
|
// 下一页
|
|
html += `
|
|
<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="return loadTasks(${currentPage + 1})">下一页</a>
|
|
</li>
|
|
`;
|
|
|
|
html += '</ul>';
|
|
$('#taskPagination').html(html);
|
|
}
|
|
|
|
// 工具函数
|
|
function getStatusText(status) {
|
|
const statusMap = {
|
|
'draft': '草稿',
|
|
'scheduled': '已排期',
|
|
'paused': '已暂停',
|
|
'completed': '已完成'
|
|
};
|
|
return statusMap[status] || status;
|
|
}
|
|
|
|
function formatDateTime(datetime) {
|
|
return new Date(datetime).toLocaleString();
|
|
}
|
|
|
|
function showError(message) {
|
|
toastr.error(message);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
toastr.success(message);
|
|
}
|
|
|
|
// 事件处理
|
|
$('#searchBtn').click(function() {
|
|
loadTasks(1);
|
|
});
|
|
|
|
$('#resetBtn').click(function() {
|
|
$('#statusFilter').val('');
|
|
$('#startTimeFilter').val('');
|
|
$('#endTimeFilter').val('');
|
|
loadTasks(1);
|
|
});
|
|
|
|
$('#saveTaskBtn').click(function() {
|
|
const formData = new FormData($('#createTaskForm')[0]);
|
|
const data = {};
|
|
formData.forEach((value, key) => {
|
|
if (key === 'groups' || key === 'preview_recipients') {
|
|
data[key] = Array.from($(`#${key}`).val());
|
|
} else {
|
|
data[key] = value;
|
|
}
|
|
});
|
|
|
|
// 处理小程序内容
|
|
data.content_miniprogram = {
|
|
title: $('#miniprogramTitle').val(),
|
|
path: $('#miniprogramPath').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: '/message_push/api/tasks',
|
|
type: 'POST',
|
|
contentType: 'application/json',
|
|
data: JSON.stringify(data),
|
|
success: function(response) {
|
|
if (response.success) {
|
|
showSuccess('创建任务成功');
|
|
$('#createTaskModal').modal('hide');
|
|
loadTasks(1);
|
|
} else {
|
|
showError('创建任务失败:' + response.error);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// 初始化
|
|
loadTasks(1);
|
|
});
|
|
|
|
// 任务操作函数
|
|
function previewTask(taskId) {
|
|
$.post(`/message_push/api/tasks/${taskId}/preview`, function(response) {
|
|
if (response.success) {
|
|
showSuccess('预览已发送');
|
|
} else {
|
|
showError('发送预览失败:' + response.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function editTask(taskId) {
|
|
// TODO: 实现编辑任务功能
|
|
}
|
|
|
|
function pauseTask(taskId) {
|
|
$.post(`/message_push/api/tasks/${taskId}/pause`, function(response) {
|
|
if (response.success) {
|
|
showSuccess('任务已暂停');
|
|
loadTasks(currentPage);
|
|
} else {
|
|
showError('暂停任务失败:' + response.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function resumeTask(taskId) {
|
|
$.post(`/message_push/api/tasks/${taskId}/resume`, function(response) {
|
|
if (response.success) {
|
|
showSuccess('任务已恢复');
|
|
loadTasks(currentPage);
|
|
} else {
|
|
showError('恢复任务失败:' + response.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteTask(taskId) {
|
|
if (confirm('确定要删除这个任务吗?')) {
|
|
$.ajax({
|
|
url: `/message_push/api/tasks/${taskId}`,
|
|
type: 'DELETE',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
showSuccess('任务已删除');
|
|
loadTasks(currentPage);
|
|
} else {
|
|
showError('删除任务失败:' + response.error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function viewLogs(taskId) {
|
|
$.get(`/message_push/api/tasks/${taskId}/logs`, function(response) {
|
|
if (response.success) {
|
|
const { logs } = response.data;
|
|
let html = '';
|
|
logs.forEach(log => {
|
|
html += `
|
|
<tr>
|
|
<td>${formatDateTime(log.timestamp)}</td>
|
|
<td>${log.action}</td>
|
|
<td>${log.operator_id}</td>
|
|
<td>${JSON.stringify(log.changes)}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
$('#taskLogsList').html(html);
|
|
$('#taskLogsModal').modal('show');
|
|
} else {
|
|
showError('获取任务日志失败:' + response.error);
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |