完善日程提醒功能
This commit is contained in:
Vendored
+1
@@ -25,6 +25,7 @@ declare module 'vue' {
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup>
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function createReminder(data) {
|
||||
export function updateReminder(id, data) {
|
||||
return request({
|
||||
url: `/platform/reminder/${id}`,
|
||||
method: "post",
|
||||
method: "put",
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -51,3 +51,20 @@ export function batchDeleteReminder(ids) {
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
/** 测试提醒渠道 */
|
||||
export function testReminder(data) {
|
||||
return request({
|
||||
url: "/platform/reminder/test",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 结束提醒 */
|
||||
export function finishReminder(id) {
|
||||
return request({
|
||||
url: `/platform/reminder/${id}/finish`,
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Bark配置 -->
|
||||
<el-tab-pane name="Bark配置">
|
||||
<el-tab-pane name="bark">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><ChatRound /></el-icon>
|
||||
|
||||
@@ -1,69 +1,49 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="提醒详情" size="520px" destroy-on-close>
|
||||
<el-drawer v-model="visible" title="日程提醒详情" size="560px" destroy-on-close>
|
||||
<div v-if="detail" class="detail-wrap" v-loading="loading">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="ID" label-width="110px">
|
||||
<el-descriptions-item label="ID" label-width="120px">
|
||||
{{ detail.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态" label-width="110px">
|
||||
<el-tag :type="detail.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ detail.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
<el-descriptions-item label="日程发生时间" label-width="120px">
|
||||
{{ detail.schedule_time || '—' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="标题" :span="2" label-width="110px">
|
||||
<el-descriptions-item label="日程标题" :span="2" label-width="120px">
|
||||
{{ detail.title }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="类型" label-width="110px">
|
||||
<el-tag :type="typeTagType(detail.type)" size="small">
|
||||
{{ typeText(detail.type) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="接收范围" label-width="110px">
|
||||
<el-tag type="info" size="small">{{ targetTypeText(detail.targetType) }}</el-tag>
|
||||
<el-descriptions-item label="提醒渠道" :span="2" label-width="120px">
|
||||
<div class="channel-tags">
|
||||
<el-tag
|
||||
v-for="ch in (detail.remind_channels || [])"
|
||||
:key="ch"
|
||||
:type="channelTagType(ch)"
|
||||
size="small"
|
||||
class="ch-tag"
|
||||
>
|
||||
{{ channelText(ch) }}
|
||||
</el-tag>
|
||||
<span v-if="!(detail.remind_channels?.length)">无渠道</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item
|
||||
v-if="[2, 3].includes(detail.targetType)"
|
||||
label="接收 ID"
|
||||
:span="2"
|
||||
label-width="110px"
|
||||
>
|
||||
{{ detail.targetIds || '—' }}
|
||||
<el-descriptions-item label="提前提醒时间" :span="2" label-width="120px">
|
||||
<el-tag type="info" size="small">提前 {{ detail.advance_minutes ?? 0 }} 分钟</el-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="提醒时间" label-width="110px">
|
||||
{{ detail.remindTime || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否重复" label-width="110px">
|
||||
<el-tag :type="detail.isRepeat === 1 ? 'warning' : 'info'" size="small">
|
||||
{{ detail.isRepeat === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<template v-if="hasRepeatChannel">
|
||||
<el-descriptions-item label="重复间隔" label-width="120px">
|
||||
{{ detail.repeat_interval_minutes || 0 }} 分钟
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最大发送次数" label-width="120px">
|
||||
{{ detail.max_send_count || 1 }} 次
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<el-descriptions-item
|
||||
v-if="detail.isRepeat === 1"
|
||||
label="重复周期"
|
||||
label-width="110px"
|
||||
>
|
||||
{{ repeatCycleText(detail.repeatCycle) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="内容" :span="2" label-width="110px">
|
||||
<el-descriptions-item label="日程内容" :span="2" label-width="120px">
|
||||
<div class="content-text">{{ detail.content || '—' }}</div>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="detail.remark" label="备注" :span="2" label-width="110px">
|
||||
<div class="content-text">{{ detail.remark }}</div>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="创建时间" label-width="110px">
|
||||
{{ detail.createTime || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" label-width="110px">
|
||||
{{ detail.updateTime || '—' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
@@ -82,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getReminderDetail } from '@/api/reminder'
|
||||
|
||||
@@ -90,26 +70,32 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const detail = ref(null)
|
||||
|
||||
function typeText(type) {
|
||||
const map = { 1: '系统通知', 2: '待办提醒', 3: '活动提醒', 4: '自定义' }
|
||||
return map[type] ?? '未知'
|
||||
const channelTextMap = {
|
||||
SMS: '短信',
|
||||
EMAIL: '邮件',
|
||||
BARK: 'Bark 推送',
|
||||
SITE_MSG: '站内信',
|
||||
}
|
||||
|
||||
function typeTagType(type) {
|
||||
const map = { 1: 'primary', 2: 'warning', 3: 'success', 4: '' }
|
||||
return map[type] ?? 'info'
|
||||
const channelColorMap = {
|
||||
SMS: 'success',
|
||||
EMAIL: 'warning',
|
||||
BARK: 'danger',
|
||||
SITE_MSG: 'primary',
|
||||
}
|
||||
|
||||
function targetTypeText(targetType) {
|
||||
const map = { 1: '全体用户', 2: '指定用户', 3: '指定角色', 4: '仅管理员' }
|
||||
return map[targetType] ?? '—'
|
||||
function channelText(ch) {
|
||||
return channelTextMap[ch] || ch
|
||||
}
|
||||
|
||||
function repeatCycleText(cycle) {
|
||||
const map = { daily: '每天', weekly: '每周', monthly: '每月' }
|
||||
return map[cycle] ?? cycle ?? '—'
|
||||
function channelTagType(ch) {
|
||||
return channelColorMap[ch] || 'info'
|
||||
}
|
||||
|
||||
const hasRepeatChannel = computed(() => {
|
||||
return detail.value?.remind_channels?.some(ch => ['EMAIL', 'BARK'].includes(ch))
|
||||
})
|
||||
|
||||
async function open(id) {
|
||||
detail.value = null
|
||||
visible.value = true
|
||||
@@ -145,4 +131,10 @@ defineExpose({ open })
|
||||
.empty-wrap {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.channel-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
:title="isAdd ? '新增提醒' : '编辑提醒'"
|
||||
size="520px"
|
||||
:title="isAdd ? '新增日程提醒' : '编辑日程提醒'"
|
||||
size="560px"
|
||||
destroy-on-close
|
||||
@closed="onClosed"
|
||||
>
|
||||
@@ -10,174 +10,144 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-width="120px"
|
||||
v-loading="loading"
|
||||
label-position="right"
|
||||
class="reminder-form"
|
||||
>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入提醒标题" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="form.type" placeholder="请选择类型" style="width: 100%">
|
||||
<el-option label="系统通知" :value="1" />
|
||||
<el-option label="待办提醒" :value="2" />
|
||||
<el-option label="活动提醒" :value="3" />
|
||||
<el-option label="自定义" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-form-item label="日程内容" prop="content">
|
||||
<el-input
|
||||
v-model="form.content"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="请输入提醒内容"
|
||||
:rows="6"
|
||||
placeholder="请输入日程详细内容"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="提醒时间" prop="remindTime">
|
||||
<el-form-item label="日程发生时间" prop="schedule_time">
|
||||
<el-date-picker
|
||||
v-model="form.remindTime"
|
||||
v-model="form.schedule_time"
|
||||
type="datetime"
|
||||
placeholder="请选择提醒时间"
|
||||
placeholder="请选择发生时间"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="接收范围" prop="targetType">
|
||||
<el-select v-model="form.targetType" placeholder="请选择接收范围" style="width: 100%">
|
||||
<el-option label="全体用户" :value="1" />
|
||||
<el-option label="指定用户" :value="2" />
|
||||
<el-option label="指定角色" :value="3" />
|
||||
<el-option label="仅管理员" :value="4" />
|
||||
</el-select>
|
||||
<el-form-item label="提醒渠道" prop="remind_channels">
|
||||
<el-checkbox-group v-model="form.remind_channels">
|
||||
<el-checkbox value="SMS">短信 (SMS)</el-checkbox>
|
||||
<el-checkbox value="EMAIL">邮件 (EMAIL)</el-checkbox>
|
||||
<el-checkbox value="BARK">Bark 推送</el-checkbox>
|
||||
<el-checkbox value="SITE_MSG">站内信 (SITE_MSG)</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 指定用户时展示 -->
|
||||
<el-form-item
|
||||
v-if="form.targetType === 2"
|
||||
label="指定用户"
|
||||
prop="targetIds"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.targetIds"
|
||||
placeholder="多个用户 ID,用英文逗号分隔,如:1,2,3"
|
||||
<el-form-item label="提前提醒分钟" prop="advance_minutes">
|
||||
<el-input-number
|
||||
v-model="form.advance_minutes"
|
||||
:min="0"
|
||||
:max="1440"
|
||||
style="width: 100%"
|
||||
controls-position="right"
|
||||
/>
|
||||
<span class="form-tip">提前多少分钟开始发送第一次提醒</span>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 指定角色时展示 -->
|
||||
<el-form-item
|
||||
v-if="form.targetType === 3"
|
||||
label="指定角色"
|
||||
prop="targetIds"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.targetIds"
|
||||
placeholder="多个角色 ID,用英文逗号分隔,如:1,2,3"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 仅在勾选了邮件或Bark时展示重复配置 -->
|
||||
<template v-if="hasRepeatChannel">
|
||||
<el-divider content-position="left">重复发送设置 (仅EMAIL/BARK生效)</el-divider>
|
||||
|
||||
<el-form-item label="是否重复">
|
||||
<el-switch v-model="form.isRepeat" :active-value="1" :inactive-value="0" />
|
||||
<span class="form-tip">开启后按下方周期重复发送</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="重复间隔(分钟)" prop="repeat_interval_minutes">
|
||||
<el-input-number
|
||||
v-model="form.repeat_interval_minutes"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
style="width: 100%"
|
||||
controls-position="right"
|
||||
/>
|
||||
<span class="form-tip">未确认前,每隔多少分钟重新发送一次</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.isRepeat === 1" label="重复周期" prop="repeatCycle">
|
||||
<el-select v-model="form.repeatCycle" placeholder="请选择" style="width: 100%">
|
||||
<el-option label="每天" value="daily" />
|
||||
<el-option label="每周" value="weekly" />
|
||||
<el-option label="每月" value="monthly" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="选填"
|
||||
maxlength="200"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio-button :value="1">启用</el-radio-button>
|
||||
<el-radio-button :value="0">禁用</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大发送次数" prop="max_send_count">
|
||||
<el-input-number
|
||||
v-model="form.max_send_count"
|
||||
:min="1"
|
||||
:max="100"
|
||||
style="width: 100%"
|
||||
controls-position="right"
|
||||
/>
|
||||
<span class="form-tip">防骚扰兜底,发送达到该次数后自动停止</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
|
||||
<div class="drawer-footer">
|
||||
<el-button
|
||||
type="warning"
|
||||
:loading="testing"
|
||||
:disabled="!form.remind_channels.length"
|
||||
@click="handleTest"
|
||||
>
|
||||
测试通道
|
||||
</el-button>
|
||||
<div style="flex: 1;"></div>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getReminderDetail, createReminder, updateReminder } from '@/api/reminder'
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getReminderDetail, createReminder, updateReminder, testReminder } from '@/api/reminder'
|
||||
|
||||
const emit = defineEmits(['saved'])
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const testing = ref(false)
|
||||
const isAdd = ref(true)
|
||||
const formRef = ref(null)
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
title: '',
|
||||
type: 1,
|
||||
content: '',
|
||||
remindTime: '',
|
||||
targetType: 1,
|
||||
targetIds: '',
|
||||
isRepeat: 0,
|
||||
repeatCycle: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
schedule_time: '',
|
||||
remind_channels: [],
|
||||
advance_minutes: 0,
|
||||
repeat_interval_minutes: 10,
|
||||
max_send_count: 5,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
|
||||
remindTime: [{ required: true, message: '请选择提醒时间', trigger: 'change' }],
|
||||
targetType: [{ required: true, message: '请选择接收范围', trigger: 'change' }],
|
||||
repeatCycle: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (form.isRepeat === 1 && !value) {
|
||||
callback(new Error('请选择重复周期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
content: [{ required: true, message: '请输入日程内容', trigger: 'blur' }],
|
||||
schedule_time: [{ required: true, message: '请选择日程发生时间', trigger: 'change' }],
|
||||
remind_channels: [{ type: 'array', required: true, message: '请选择至少一个提醒渠道', trigger: 'change' }],
|
||||
repeat_interval_minutes: [{ required: true, message: '请输入重复提醒间隔', trigger: 'blur' }],
|
||||
max_send_count: [{ required: true, message: '请输入最大发送次数', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
// 是否包含了需要确认/重复发送的渠道
|
||||
const hasRepeatChannel = computed(() => {
|
||||
return form.remind_channels.includes('EMAIL') || form.remind_channels.includes('BARK')
|
||||
})
|
||||
|
||||
function resetForm() {
|
||||
form.id = 0
|
||||
form.title = ''
|
||||
form.type = 1
|
||||
form.content = ''
|
||||
form.remindTime = ''
|
||||
form.targetType = 1
|
||||
form.targetIds = ''
|
||||
form.isRepeat = 0
|
||||
form.repeatCycle = ''
|
||||
form.remark = ''
|
||||
form.status = 1
|
||||
form.schedule_time = ''
|
||||
form.remind_channels = []
|
||||
form.advance_minutes = 0
|
||||
form.repeat_interval_minutes = 10
|
||||
form.max_send_count = 5
|
||||
}
|
||||
|
||||
async function open(id) {
|
||||
@@ -198,16 +168,12 @@ async function open(id) {
|
||||
}
|
||||
const d = res.data
|
||||
form.id = d.id
|
||||
form.title = d.title || ''
|
||||
form.type = d.type ?? 1
|
||||
form.content = d.content || ''
|
||||
form.remindTime = d.remindTime || ''
|
||||
form.targetType = d.targetType ?? 1
|
||||
form.targetIds = d.targetIds || ''
|
||||
form.isRepeat = d.isRepeat ?? 0
|
||||
form.repeatCycle = d.repeatCycle || ''
|
||||
form.remark = d.remark || ''
|
||||
form.status = d.status ?? 1
|
||||
form.schedule_time = d.schedule_time || ''
|
||||
form.remind_channels = d.remind_channels || []
|
||||
form.advance_minutes = d.advance_minutes ?? 0
|
||||
form.repeat_interval_minutes = d.repeat_interval_minutes ?? 10
|
||||
form.max_send_count = d.max_send_count ?? 5
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -218,6 +184,44 @@ function onClosed() {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
if (form.remind_channels.length === 0) {
|
||||
ElMessage.warning('请先选择至少一个提醒渠道')
|
||||
return
|
||||
}
|
||||
|
||||
testing.value = true
|
||||
try {
|
||||
const res = await testReminder({
|
||||
title: '日程提醒',
|
||||
content: form.content || '这是一条验证日程提醒配置的测试通知。',
|
||||
remind_channels: form.remind_channels,
|
||||
})
|
||||
|
||||
if (res?.code === 200 && Array.isArray(res.data)) {
|
||||
const lines = res.data.map(item => {
|
||||
const name = { SMS: '短信', EMAIL: '邮件', BARK: 'Bark推送', SITE_MSG: '站内信' }[item.channel] || item.channel
|
||||
const status = item.success
|
||||
? '<span style="color: #67C23A; font-weight: bold;">发送成功</span>'
|
||||
: `<span style="color: #F56C6C; font-weight: bold;">发送失败 (${item.msg})</span>`
|
||||
return `<p style="margin: 8px 0;"><strong>${name}</strong>: ${status}</p>`
|
||||
}).join('')
|
||||
|
||||
await ElMessageBox.alert(
|
||||
`<div style="font-size: 14px; line-height: 1.6; padding: 10px 0;">${lines}</div>`,
|
||||
'渠道测试结果',
|
||||
{ dangerouslyUseHTMLString: true, confirmButtonText: '确定' }
|
||||
)
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '测试发送失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('测试出错:' + (err.message || err))
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!formRef.value) return
|
||||
try {
|
||||
@@ -229,16 +233,12 @@ async function submit() {
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
title: form.title,
|
||||
type: form.type,
|
||||
content: form.content,
|
||||
remindTime: form.remindTime,
|
||||
targetType: form.targetType,
|
||||
targetIds: [2, 3].includes(form.targetType) ? form.targetIds : undefined,
|
||||
isRepeat: form.isRepeat,
|
||||
repeatCycle: form.isRepeat === 1 ? form.repeatCycle : undefined,
|
||||
remark: form.remark || undefined,
|
||||
status: form.status,
|
||||
schedule_time: form.schedule_time,
|
||||
remind_channels: form.remind_channels,
|
||||
advance_minutes: Number(form.advance_minutes || 0),
|
||||
repeat_interval_minutes: hasRepeatChannel.value ? Number(form.repeat_interval_minutes || 0) : 0,
|
||||
max_send_count: hasRepeatChannel.value ? Number(form.max_send_count || 1) : 1,
|
||||
}
|
||||
|
||||
let res
|
||||
@@ -264,9 +264,22 @@ defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.reminder-form {
|
||||
padding: 10px 20px 40px 0;
|
||||
}
|
||||
.form-tip {
|
||||
margin-left: 10px;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.el-divider {
|
||||
margin: 24px 0 16px;
|
||||
}
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>提醒管理</h2>
|
||||
<h2>日程提醒管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="editRef.open()">
|
||||
<el-icon><Plus /></el-icon>
|
||||
@@ -21,49 +21,13 @@
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="标题/内容"
|
||||
placeholder="内容"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 130px"
|
||||
>
|
||||
<el-option label="系统通知" :value="1" />
|
||||
<el-option label="待办提醒" :value="2" />
|
||||
<el-option label="活动提醒" :value="3" />
|
||||
<el-option label="自定义" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 110px"
|
||||
>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="提醒时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 240px"
|
||||
@change="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
@@ -87,34 +51,68 @@
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="title" label="标题" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="类型" width="110" align="center">
|
||||
<el-table-column type="selection" width="50" align="center" :selectable="checkSelectable" />
|
||||
<el-table-column prop="id" label="ID" width="75" align="center" />
|
||||
<el-table-column prop="content" label="日程提醒内容" min-width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="schedule_time" label="日程发生时间" width="170" align="center" />
|
||||
<el-table-column prop="is_finished" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="typeTagType(row.type)" size="small">{{ typeText(row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" label="内容摘要" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="remindTime" label="提醒时间" width="170" align="center" />
|
||||
<el-table-column prop="targetType" label="接收范围" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">{{ targetTypeText(row.targetType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
<el-tag :type="row.is_finished ? 'info' : 'success'" size="small">
|
||||
{{ row.is_finished ? '已结束' : '提醒中' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<el-table-column prop="remind_channels" label="提醒渠道" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="channel-tags">
|
||||
<el-tag
|
||||
v-for="ch in (row.remind_channels || [])"
|
||||
:key="ch"
|
||||
:type="channelTagType(ch)"
|
||||
size="small"
|
||||
class="ch-tag"
|
||||
>
|
||||
{{ channelText(ch) }}
|
||||
</el-tag>
|
||||
<span v-if="!(row.remind_channels?.length)" style="color: #909399;">无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="advance_minutes" label="提前提醒" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">提前 {{ row.advance_minutes ?? 0 }} 分</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" size="small" @click="handleViewDetail(row)">详情</el-button>
|
||||
<el-button text type="primary" size="small" @click="editRef.open(row.id)">编辑</el-button>
|
||||
<el-button text type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button
|
||||
v-if="!row.is_finished"
|
||||
text
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleFinish(row)"
|
||||
>
|
||||
结束
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!row.is_finished"
|
||||
text
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="editRef.open(row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!row.is_finished"
|
||||
text
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -144,7 +142,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { getReminderList, deleteReminder, batchDeleteReminder } from '@/api/reminder'
|
||||
import { getReminderList, deleteReminder, batchDeleteReminder, finishReminder } from '@/api/reminder'
|
||||
import ReminderEdit from './components/edit.vue'
|
||||
import ReminderDetail from './components/detail.vue'
|
||||
|
||||
@@ -154,14 +152,9 @@ const detailRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const selectedIds = ref([])
|
||||
const dateRange = ref(null)
|
||||
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
type: null,
|
||||
status: null,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
@@ -170,22 +163,30 @@ const pagination = reactive({
|
||||
total: 0,
|
||||
})
|
||||
|
||||
// 类型文本
|
||||
function typeText(type) {
|
||||
const map = { 1: '系统通知', 2: '待办提醒', 3: '活动提醒', 4: '自定义' }
|
||||
return map[type] ?? '未知'
|
||||
const channelTextMap = {
|
||||
SMS: '短信',
|
||||
EMAIL: '邮件',
|
||||
BARK: 'Bark',
|
||||
SITE_MSG: '站内信',
|
||||
}
|
||||
|
||||
// 类型标签颜色
|
||||
function typeTagType(type) {
|
||||
const map = { 1: 'primary', 2: 'warning', 3: 'success', 4: '' }
|
||||
return map[type] ?? 'info'
|
||||
const channelColorMap = {
|
||||
SMS: 'success',
|
||||
EMAIL: 'warning',
|
||||
BARK: 'danger',
|
||||
SITE_MSG: 'primary',
|
||||
}
|
||||
|
||||
// 接收范围文本
|
||||
function targetTypeText(targetType) {
|
||||
const map = { 1: '全体用户', 2: '指定用户', 3: '指定角色', 4: '仅管理员' }
|
||||
return map[targetType] ?? '—'
|
||||
function channelText(ch) {
|
||||
return channelTextMap[ch] || ch
|
||||
}
|
||||
|
||||
function channelTagType(ch) {
|
||||
return channelColorMap[ch] || 'info'
|
||||
}
|
||||
|
||||
function checkSelectable(row) {
|
||||
return !row.is_finished
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
@@ -196,10 +197,6 @@ async function fetchList() {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: searchForm.keyword || undefined,
|
||||
type: searchForm.type != null ? searchForm.type : undefined,
|
||||
status: searchForm.status != null ? searchForm.status : undefined,
|
||||
startDate: searchForm.startDate || undefined,
|
||||
endDate: searchForm.endDate || undefined,
|
||||
}
|
||||
const res = await getReminderList(params)
|
||||
if (res?.code === 200 && res.data) {
|
||||
@@ -216,13 +213,6 @@ async function fetchList() {
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
searchForm.startDate = dateRange.value[0]
|
||||
searchForm.endDate = dateRange.value[1]
|
||||
} else {
|
||||
searchForm.startDate = ''
|
||||
searchForm.endDate = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
@@ -230,11 +220,6 @@ function handleSearch() {
|
||||
// 重置搜索
|
||||
function resetSearch() {
|
||||
searchForm.keyword = ''
|
||||
searchForm.type = null
|
||||
searchForm.status = null
|
||||
searchForm.startDate = ''
|
||||
searchForm.endDate = ''
|
||||
dateRange.value = null
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
@@ -249,10 +234,34 @@ function handleViewDetail(row) {
|
||||
detailRef.value?.open(row.id)
|
||||
}
|
||||
|
||||
// 结束提醒
|
||||
async function handleFinish(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定结束该日程提醒吗?结束后将不再发送任何提醒。', '提示', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定结束',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const res = await finishReminder(row.id)
|
||||
if (res?.code === 200) {
|
||||
ElMessage.success('已结束')
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '结束失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除单条
|
||||
async function handleDelete(row) {
|
||||
if (row.is_finished) {
|
||||
ElMessage.warning('已结束的日程无法删除')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除提醒「${row.title}」吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm('确定删除该日程及其所有关联提醒吗?', '提示', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -268,7 +277,7 @@ async function handleDelete(row) {
|
||||
// 批量删除
|
||||
async function handleBatchDelete() {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定批量删除选中的 ${selectedIds.value.length} 条提醒吗?`, '提示', {
|
||||
await ElMessageBox.confirm(`确定批量删除选中的 ${selectedIds.value.length} 个日程吗?`, '提示', {
|
||||
type: 'warning',
|
||||
})
|
||||
} catch {
|
||||
@@ -315,4 +324,15 @@ onMounted(() => {
|
||||
.search-form {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.channel-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ch-tag {
|
||||
margin: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user