2026-08-12 18:24:05 +08:00
< script setup lang = "ts" >
2026-08-12 18:50:03 +08:00
import { computed , reactive , ref , watch } from 'vue'
import { ElMessage } from 'element-plus'
2026-08-12 18:24:05 +08:00
import AppDialog from '@/shared/components/AppDialog.vue'
2026-08-12 18:50:03 +08:00
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'
2026-08-12 18:24:05 +08:00
2026-08-12 18:50:03 +08:00
const props = defineProps < { templates : NotificationTemplate [] } > ()
2026-08-12 18:24:05 +08:00
const open = defineModel < boolean >({ default : false })
2026-08-12 18:50:03 +08:00
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 }
}
2026-08-12 18:24:05 +08:00
</ script >
< template >
2026-08-12 18:50:03 +08:00
< 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 >
2026-08-12 18:24:05 +08:00
< el-form class = "message-form" label -position =" top " >
2026-08-12 18:50:03 +08:00
< 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 >
2026-08-12 18:24:05 +08:00
</ el-form >
2026-08-12 18:50:03 +08:00
< 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 >
2026-08-12 18:24:05 +08:00
</ AppDialog >
</ template >
< style scoped >
2026-08-12 18:50:03 +08:00
. message - form { margin - top : 18 px ; }. form - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 16 px ; }. form - grid : deep (. el - select ),. message - form > : deep (. el - form - item . el - select ),. schedule - row : deep (. el - date - editor ) { width : 100 % ; }. variables { margin - bottom : 16 px ; padding : 14 px ; border : 1 px solid var ( -- cc - border ); border - radius : 8 px ; }. variables > strong { display : block ; margin - bottom : 12 px ; font - size : 13 px ; }. schedule - row { display : grid ; width : 100 % ; grid - template - columns : auto auto minmax ( 180 px , 1 fr ); align - items : center ; gap : 8 px }. dialog - actions { display : flex ; justify - content : flex - end ; gap : 10 px }. dialog - actions : deep (. el - button ){ margin - left : 0 } @ media ( max - width : 560 px ){. form - grid { grid - template - columns : 1 fr ; gap : 0 }. schedule - row { grid - template - columns : auto 1 fr }. schedule - row : deep (. el - date - editor ){ grid - column : 1 /- 1 }}
2026-08-12 18:24:05 +08:00
</ style >