优化
This commit is contained in:
@@ -1,50 +1,47 @@
|
||||
<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 { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { createTemplate, updateTemplate } from '../api/notificationCenter'
|
||||
import type { NotificationTemplate } from '../types'
|
||||
|
||||
const props = defineProps<{ template?: NotificationTemplate | null }>()
|
||||
const open = defineModel<boolean>({ default: false })
|
||||
const emit = defineEmits<{ success: [] }>()
|
||||
const submitting = ref(false)
|
||||
const error = ref<ApiErrorDetails | null>(null)
|
||||
const contentInput = ref<{ textarea?: HTMLTextAreaElement } | null>(null)
|
||||
const form = reactive({ code: '', title: '', content: '' })
|
||||
const variables = [{ label: '客户名称', value: 'customerName' }, { label: '订单号', value: 'orderNo' }, { label: '柜号', value: 'containerNo' }, { label: '金额', value: 'amount' }, { label: '日期', value: 'date' }]
|
||||
const preview = computed(() => form.title || form.content ? `[系统] ${form.title || '未填写标题'}\n${form.content || '未填写模板内容'}` : '填写标题和内容后,将在这里显示预览效果。')
|
||||
const whitelistText = ref('')
|
||||
const form = reactive({ code: '', name: '', category: '', title: '', content: '' })
|
||||
const editing = computed(() => Boolean(props.template))
|
||||
const preview = computed(() => form.title || form.content ? `[站内信] ${form.title || '未填写标题'}\n${form.content || '未填写模板内容'}` : '填写标题和内容后,将在这里显示预览效果。')
|
||||
const whitelist = computed(() => [...new Set(whitelistText.value.split(/[\s,,]+/).map((v) => v.trim()).filter(Boolean))])
|
||||
|
||||
watch([open, () => props.template], ([isOpen]) => { if (!isOpen) return; const item = props.template; Object.assign(form, item ? { code: item.templateCode, name: item.templateName, category: item.category, title: item.titleTemplate, content: item.bodyTemplate } : { code: '', name: '', category: '', title: '', content: '' }); whitelistText.value = item?.variableWhitelist.join(', ') ?? ''; error.value = null }, { immediate: true })
|
||||
function normalizeCode(value: string) { form.code = value.replace(/[^A-Za-z0-9_]/g, '').toUpperCase() }
|
||||
function insertVariable(name: string) {
|
||||
const textarea = contentInput.value?.textarea
|
||||
const value = `\${${name}}`
|
||||
const start = textarea?.selectionStart ?? form.content.length
|
||||
const end = textarea?.selectionEnd ?? start
|
||||
form.content = `${form.content.slice(0, start)}${value}${form.content.slice(end)}`.slice(0, 1000)
|
||||
function insertVariable(name: string) { if (!whitelist.value.includes(name)) whitelistText.value = [...whitelist.value, name].join(', '); const textarea = contentInput.value?.textarea; const value = `\${${name}}`; const start = textarea?.selectionStart ?? form.content.length; const end = textarea?.selectionEnd ?? start; form.content = `${form.content.slice(0,start)}${value}${form.content.slice(end)}`.slice(0,10000) }
|
||||
async function submit() {
|
||||
error.value = null
|
||||
if (!/^[A-Z][A-Z0-9_]{0,63}$/.test(form.code)) return ElMessage.warning('模板CODE须以字母开头,仅支持大写字母、数字和下划线')
|
||||
if (!form.name.trim() || !form.category.trim() || !form.title.trim() || !form.content.trim()) return ElMessage.warning('请填写完整模板信息')
|
||||
if (whitelist.value.some((key) => !/^[a-z][a-z0-9_]{0,31}$/.test(key))) return ElMessage.warning('变量名须以小写字母开头,仅支持小写字母、数字和下划线')
|
||||
const payload = { templateName: form.name.trim(), category: form.category.trim(), titleTemplate: form.title.trim(), bodyTemplate: form.content.trim(), variableWhitelist: whitelist.value }
|
||||
submitting.value = true
|
||||
try { if (props.template) await updateTemplate(props.template.id, { ...payload, version: props.template.version }); else await createTemplate({ templateCode: form.code, ...payload }); ElMessage.success(props.template ? '模板已更新' : '模板草稿已创建'); open.value = false; emit('success') } catch (reason) { error.value = getApiErrorDetails(reason, '模板保存失败') } finally { submitting.value = false }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppDialog v-model="open" title="新增消息模板" width="min(760px, calc(100vw - 28px))" :show-confirm="false" destroy-on-close>
|
||||
<el-alert title="当前仅承载站内系统消息模板;保存接口待后端契约冻结" type="info" :closable="false" show-icon />
|
||||
<el-form class="template-form" label-position="top">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="模板CODE" required><el-input :model-value="form.code" maxlength="40" placeholder="如:ORDER_SHIPPED_NOTICE" @update:model-value="normalizeCode(String($event))" /><small>仅支持字母、数字和下划线</small></el-form-item>
|
||||
<el-form-item label="模板类型" required><el-input model-value="系统" disabled /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="模板标题" required><el-input v-model="form.title" maxlength="60" show-word-limit placeholder="请输入模板标题" /></el-form-item>
|
||||
<el-form-item label="模板内容" required><el-input ref="contentInput" v-model="form.content" type="textarea" :rows="6" maxlength="1000" show-word-limit placeholder="请输入模板内容" /></el-form-item>
|
||||
<div class="variable-row"><span>常用变量</span><el-button v-for="item in variables" :key="item.value" size="small" @click="insertVariable(item.value)">{{ item.label }}</el-button></div>
|
||||
<section class="template-preview"><strong>内容预览</strong><p>{{ preview }}</p></section>
|
||||
</el-form>
|
||||
<template #footer="{ close }"><div class="dialog-actions"><el-button @click="close">取消</el-button><el-button type="primary" disabled>保存模板</el-button></div></template>
|
||||
<AppDialog v-model="open" :title="editing ? '编辑消息模板' : '新增消息模板'" width="min(760px, calc(100vw - 28px))" :show-confirm="false" destroy-on-close>
|
||||
<el-alert v-if="error" type="error" :closable="false" show-icon><template #title><span>{{ error.message }}</span><ApiErrorMeta :details="error" /></template></el-alert>
|
||||
<el-alert v-else title="首期仅支持站内信;新模板保存为草稿,草稿激活后不可再编辑。" type="info" :closable="false" show-icon />
|
||||
<el-form class="template-form" label-position="top"><div class="form-grid"><el-form-item label="模板CODE" required><el-input :model-value="form.code" maxlength="64" :disabled="editing" placeholder="如:ORDER_SHIPPED_NOTICE" @update:model-value="normalizeCode(String($event))" /></el-form-item><el-form-item label="模板名称" required><el-input v-model="form.name" maxlength="128" /></el-form-item></div><div class="form-grid"><el-form-item label="业务分类" required><el-input v-model="form.category" maxlength="64" /></el-form-item><el-form-item label="模板类型"><el-input model-value="站内信" disabled /></el-form-item></div><el-form-item label="消息标题模板" required><el-input v-model="form.title" maxlength="256" show-word-limit /></el-form-item><el-form-item label="消息正文模板" required><el-input ref="contentInput" v-model="form.content" type="textarea" :rows="6" maxlength="10000" show-word-limit /></el-form-item><el-form-item label="变量白名单"><el-input v-model="whitelistText" placeholder="多个变量使用逗号分隔,如 customer_name, order_no" /></el-form-item><div class="variable-row"><span>常用变量</span><el-button v-for="name in ['customer_name','order_no','container_no','amount','date']" :key="name" size="small" @click="insertVariable(name)">{{ name }}</el-button></div><section class="template-preview"><strong>内容预览</strong><p>{{ preview }}</p></section></el-form>
|
||||
<template #footer="{ close }"><div class="dialog-actions"><el-button :disabled="submitting" @click="close">取消</el-button><el-button type="primary" :loading="submitting" @click="submit">保存模板</el-button></div></template>
|
||||
</AppDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.template-form { margin-top: 18px; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
||||
small { display: block; margin-top: 5px; color: var(--cc-muted); font-size: 12px; }
|
||||
.variable-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 18px; color: var(--cc-muted); font-size: 12px; }
|
||||
.variable-row :deep(.el-button) { margin-left: 0; }
|
||||
.template-preview { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; }
|
||||
.template-preview strong { display: block; padding: 9px 14px; border-bottom: 1px solid var(--cc-border); background: #f8fafc; color: var(--cc-text); font-size: 13px; }
|
||||
.template-preview p { min-height: 76px; margin: 0; padding: 16px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--cc-muted); font-size: 13px; line-height: 1.7; }
|
||||
.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; } }
|
||||
.template-form{margin-top:18px}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.variable-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:18px;color:var(--cc-muted);font-size:12px}.variable-row :deep(.el-button){margin-left:0}.template-preview{overflow:hidden;border:1px solid var(--cc-border);border-radius:8px}.template-preview strong{display:block;padding:9px 14px;border-bottom:1px solid var(--cc-border);background:#f8fafc;font-size:13px}.template-preview p{min-height:76px;margin:0;padding:16px;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--cc-muted);font-size:13px;line-height:1.7}.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}}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user