优化
This commit is contained in:
@@ -1,41 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import { PlatformTenantSelector } from '@/modules/iam'
|
||||
import { createNotification } from '../api/notificationCenter'
|
||||
import type { NotificationTemplate, RecipientType } from '../types'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
|
||||
const props = defineProps<{ templates: NotificationTemplate[] }>()
|
||||
const open = defineModel<boolean>({ default: false })
|
||||
const scheduled = ref(false)
|
||||
const form = reactive({ type: '系统通知', priority: '普通', title: '', content: '', target: '全部企业用户', receivers: '', scheduleAt: '' })
|
||||
const contractHint = computed(() => scheduled.value ? '定时发送规则与接口待后端契约冻结' : '草稿与立即发送接口待后端契约冻结')
|
||||
const emit = defineEmits<{ success: [] }>()
|
||||
const submitting = ref(false)
|
||||
const error = ref<ApiErrorDetails | null>(null)
|
||||
const scope = ref<'platform' | 'customer'>('platform')
|
||||
const recipientIdsText = ref('')
|
||||
const variables = reactive<Record<string, string>>({})
|
||||
const form = reactive({ tenantId: '0', templateCode: '', recipientType: 'PLATFORM_ROLE' as RecipientType, roleCode: '', scheduledAt: '' })
|
||||
const selectedTemplate = computed(() => props.templates.find((item) => item.templateCode === form.templateCode) ?? null)
|
||||
const scheduled = computed({ get: () => Boolean(form.scheduledAt), set: (value) => { if (!value) form.scheduledAt = '' } })
|
||||
const needsIds = computed(() => ['USER', 'MEMBERSHIP'].includes(form.recipientType))
|
||||
const needsRole = computed(() => ['TENANT_ROLE', 'PLATFORM_ROLE'].includes(form.recipientType))
|
||||
const allowedRecipients = computed(() => scope.value === 'platform'
|
||||
? [{ label: '指定平台用户', value: 'USER' }, { label: '平台角色', value: 'PLATFORM_ROLE' }]
|
||||
: [{ label: '指定企业成员', value: 'MEMBERSHIP' }, { label: '企业角色', value: 'TENANT_ROLE' }, { label: '企业全部成员', value: 'TENANT_ALL' }])
|
||||
|
||||
watch(scope, (value) => { form.tenantId = value === 'platform' ? '0' : ''; form.recipientType = value === 'platform' ? 'PLATFORM_ROLE' : 'TENANT_ALL'; form.roleCode = ''; recipientIdsText.value = '' })
|
||||
watch(selectedTemplate, (value) => { for (const key of Object.keys(variables)) delete variables[key]; for (const key of value?.variableWhitelist ?? []) variables[key] = '' })
|
||||
|
||||
function reset() { scope.value = 'platform'; Object.assign(form, { tenantId: '0', templateCode: '', recipientType: 'PLATFORM_ROLE', roleCode: '', scheduledAt: '' }); recipientIdsText.value = ''; error.value = null; for (const key of Object.keys(variables)) delete variables[key] }
|
||||
function parseIds() { return [...new Set(recipientIdsText.value.split(/[\s,,]+/).map((value) => value.trim()).filter(Boolean))] }
|
||||
function idempotencyKey() { return typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `notification-${Date.now()}-${Math.random().toString(36).slice(2)}` }
|
||||
async function submit() {
|
||||
error.value = null
|
||||
const recipientIds = parseIds()
|
||||
if (!form.templateCode) return ElMessage.warning('请选择已激活模板')
|
||||
if (scope.value === 'customer' && !form.tenantId) return ElMessage.warning('请选择目标企业')
|
||||
if (needsIds.value && (!recipientIds.length || recipientIds.some((value) => !/^\d+$/.test(value)))) return ElMessage.warning('请输入有效的数字用户或成员 ID')
|
||||
if (needsRole.value && !form.roleCode.trim()) return ElMessage.warning('请输入角色编码')
|
||||
if (Object.values(variables).some((value) => !value.trim())) return ElMessage.warning('请填写全部模板变量')
|
||||
submitting.value = true
|
||||
try {
|
||||
await createNotification({ tenantId: form.tenantId, templateCode: form.templateCode, recipientType: form.recipientType, ...(needsIds.value ? { recipientIds } : {}), ...(needsRole.value ? { roleCode: form.roleCode.trim() } : {}), variables: { ...variables }, scheduledAt: form.scheduledAt ? new Date(form.scheduledAt).toISOString() : null, idempotencyKey: idempotencyKey() })
|
||||
ElMessage.success(form.scheduledAt ? '消息已定时' : '消息发送任务已创建')
|
||||
open.value = false; reset(); emit('success')
|
||||
} catch (reason) { error.value = getApiErrorDetails(reason, '消息发送失败') } finally { submitting.value = false }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppDialog v-model="open" title="新建消息" width="min(680px, calc(100vw - 28px))" :show-confirm="false" destroy-on-close>
|
||||
<el-alert :title="contractHint" type="info" :closable="false" show-icon />
|
||||
<AppDialog v-model="open" title="新建消息" width="min(680px, calc(100vw - 28px))" :show-confirm="false" destroy-on-close @cancel="reset">
|
||||
<el-alert v-if="error" type="error" :closable="false" show-icon><template #title><span>{{ error.message }}</span><ApiErrorMeta :details="error" /></template></el-alert>
|
||||
<el-form class="message-form" label-position="top">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="消息类型" required><el-select v-model="form.type"><el-option label="系统通知" value="系统通知" /><el-option label="业务提醒" value="业务提醒" /><el-option label="异常告警" value="异常告警" /><el-option label="运单动态" value="运单动态" /></el-select></el-form-item>
|
||||
<el-form-item label="优先级"><el-select v-model="form.priority"><el-option label="普通" value="普通" /><el-option label="重要" value="重要" /><el-option label="紧急" value="紧急" /></el-select></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="消息标题" required><el-input v-model="form.title" placeholder="请输入消息标题" /></el-form-item>
|
||||
<el-form-item label="消息内容" required><el-input v-model="form.content" type="textarea" :rows="5" placeholder="请输入消息内容" /></el-form-item>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="接收对象" required><el-select v-model="form.target"><el-option label="全部企业用户" value="全部企业用户" /><el-option label="全部企业管理员" value="全部企业管理员" /><el-option label="全部平台操作员" value="全部平台操作员" /><el-option label="全部平台财务" value="全部平台财务" /><el-option label="指定用户" value="指定用户" /></el-select></el-form-item>
|
||||
<el-form-item label="发送方式"><el-input model-value="站内信" disabled /></el-form-item>
|
||||
</div>
|
||||
<el-form-item v-if="form.target === '指定用户'" label="指定接收人"><el-input v-model="form.receivers" type="textarea" :rows="2" placeholder="接收人选择契约待确认" disabled /></el-form-item>
|
||||
<el-form-item label="定时发送"><div class="schedule-row"><el-switch v-model="scheduled" aria-label="启用定时发送" /><span>启用</span><el-date-picker v-model="form.scheduleAt" type="datetime" placeholder="选择发送时间" :disabled="!scheduled" /></div></el-form-item>
|
||||
<div class="form-grid"><el-form-item label="发送范围" required><el-segmented v-model="scope" :options="[{ label: '平台员工', value: 'platform' }, { label: '指定企业', value: 'customer' }]" /></el-form-item><el-form-item label="发送方式"><el-input model-value="站内信" disabled /></el-form-item></div>
|
||||
<PlatformTenantSelector v-if="scope === 'customer'" v-model="form.tenantId" label="目标企业" />
|
||||
<el-form-item label="消息模板" required><el-select v-model="form.templateCode" filterable placeholder="请选择已激活模板"><el-option v-for="item in templates" :key="item.id" :label="`${item.templateName}(${item.templateCode})`" :value="item.templateCode" /></el-select></el-form-item>
|
||||
<div class="form-grid"><el-form-item label="接收对象" required><el-select v-model="form.recipientType"><el-option v-for="item in allowedRecipients" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item><el-form-item v-if="needsRole" label="角色编码" required><el-input v-model="form.roleCode" maxlength="64" placeholder="请输入服务端角色编码" /></el-form-item></div>
|
||||
<el-form-item v-if="needsIds" label="接收人 ID" required><el-input v-model="recipientIdsText" type="textarea" :rows="2" placeholder="多个数字 ID 使用逗号或换行分隔,最多 1000 个" /></el-form-item>
|
||||
<section v-if="selectedTemplate?.variableWhitelist.length" class="variables"><strong>模板变量</strong><el-form-item v-for="key in selectedTemplate.variableWhitelist" :key="key" :label="key" required><el-input v-model="variables[key]" maxlength="2000" /></el-form-item></section>
|
||||
<el-form-item label="定时发送"><div class="schedule-row"><el-switch v-model="scheduled" aria-label="启用定时发送" /><span>启用</span><el-date-picker v-model="form.scheduledAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选择发送时间" :disabled="!scheduled" /></div></el-form-item>
|
||||
</el-form>
|
||||
<template #footer="{ close }"><div class="dialog-actions"><el-button @click="close">取消</el-button><el-button disabled>存为草稿</el-button><el-button type="primary" disabled>{{ scheduled ? '确认定时发送' : '立即发送' }}</el-button></div></template>
|
||||
<template #footer="{ close }"><div class="dialog-actions"><el-button :disabled="submitting" @click="close">取消</el-button><el-button type="primary" :loading="submitting" @click="submit">{{ scheduled ? '确认定时发送' : '立即发送' }}</el-button></div></template>
|
||||
</AppDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-form { margin-top: 18px; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
||||
.form-grid :deep(.el-select), .schedule-row :deep(.el-date-editor) { width: 100%; }
|
||||
.schedule-row { display: grid; width: 100%; grid-template-columns: auto auto minmax(180px, 1fr); align-items: center; gap: 8px; }
|
||||
.dialog-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
|
||||
.dialog-actions :deep(.el-button) { margin-left: 0; }
|
||||
@media (max-width: 560px) { .form-grid { grid-template-columns: 1fr; gap: 0; } .schedule-row { grid-template-columns: auto 1fr; } .schedule-row :deep(.el-date-editor) { grid-column: 1 / -1; } }
|
||||
.message-form { margin-top: 18px; }.form-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }.form-grid :deep(.el-select),.message-form > :deep(.el-form-item .el-select),.schedule-row :deep(.el-date-editor) { width: 100%; }.variables { margin-bottom: 16px; padding: 14px; border: 1px solid var(--cc-border); border-radius: 8px; }.variables > strong { display: block; margin-bottom: 12px; font-size: 13px; }.schedule-row { display:grid;width:100%;grid-template-columns:auto auto minmax(180px,1fr);align-items:center;gap:8px }.dialog-actions {display:flex;justify-content:flex-end;gap:10px}.dialog-actions :deep(.el-button){margin-left:0}@media(max-width:560px){.form-grid{grid-template-columns:1fr;gap:0}.schedule-row{grid-template-columns:auto 1fr}.schedule-row :deep(.el-date-editor){grid-column:1/-1}}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user