747 lines
23 KiB
Vue
747 lines
23 KiB
Vue
<template>
|
||
<div class="art-page-view">
|
||
<!-- 1. 顶部操作区 -->
|
||
<ElCard class="mb-4" shadow="never">
|
||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||
<div class="text-lg font-semibold">
|
||
{{
|
||
isEditMode ? $t('tenant.announcement.editTitle') : $t('tenant.announcement.createTitle')
|
||
}}
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<ElTag v-if="draftStatus" type="info">{{ draftStatus }}</ElTag>
|
||
<ElButton @click="handleBack">{{ $t('tenant.announcement.action.back') }}</ElButton>
|
||
<ElButton type="primary" :loading="loading" @click="handleSubmit" v-ripple>
|
||
{{
|
||
isEditMode
|
||
? $t('tenant.announcement.action.update')
|
||
: $t('tenant.announcement.action.create')
|
||
}}
|
||
</ElButton>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<ElAlert v-if="errorMessage" type="error" show-icon :title="errorMessage" class="mb-4" />
|
||
<ElAlert
|
||
v-else
|
||
type="info"
|
||
show-icon
|
||
:title="$t('tenant.announcement.tip.tenantScope')"
|
||
class="mb-4"
|
||
/>
|
||
|
||
<ElAlert
|
||
v-if="draftLoaded"
|
||
type="success"
|
||
show-icon
|
||
:title="$t('tenant.announcement.tip.draftLoaded')"
|
||
class="mb-4"
|
||
/>
|
||
|
||
<!-- 2. 表单区 -->
|
||
<ElCard shadow="never" v-loading="loading || loadingData">
|
||
<ElForm ref="formRef" :model="formState" :rules="rules" label-width="110px">
|
||
<!-- 2.1 基础信息 -->
|
||
<ElRow :gutter="16">
|
||
<ElCol :span="12">
|
||
<ElFormItem :label="$t('tenant.announcement.field.title')" prop="title">
|
||
<ElInput
|
||
v-model="formState.title"
|
||
:placeholder="$t('tenant.announcement.placeholder.title')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<ElFormItem
|
||
:label="$t('tenant.announcement.field.announcementType')"
|
||
prop="announcementType"
|
||
>
|
||
<ElSelect
|
||
v-model="formState.announcementType"
|
||
:placeholder="$t('tenant.announcement.placeholder.type')"
|
||
class="w-full"
|
||
>
|
||
<ElOption
|
||
v-for="item in typeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<ElFormItem :label="$t('tenant.announcement.field.priority')" prop="priority">
|
||
<ElInputNumber
|
||
v-model="formState.priority"
|
||
:min="1"
|
||
:max="10"
|
||
controls-position="right"
|
||
class="w-full"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElRow :gutter="16">
|
||
<ElCol :span="12">
|
||
<ElFormItem
|
||
:label="$t('tenant.announcement.field.effectiveRange')"
|
||
prop="effectiveRange"
|
||
>
|
||
<ElDatePicker
|
||
v-model="formState.effectiveRange"
|
||
type="datetimerange"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
:placeholder="$t('tenant.announcement.placeholder.effectiveRange')"
|
||
class="w-full"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12">
|
||
<ElFormItem :label="$t('tenant.announcement.field.targetType')" prop="targetType">
|
||
<ElSelect
|
||
v-model="formState.targetType"
|
||
:placeholder="$t('tenant.announcement.placeholder.targetType')"
|
||
class="w-full"
|
||
>
|
||
<ElOption
|
||
v-for="item in targetTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElDivider class="my-4" />
|
||
|
||
<!-- 2.2 公告内容 -->
|
||
<div class="mb-3 text-sm font-semibold">
|
||
{{ $t('tenant.announcement.section.content') }}
|
||
</div>
|
||
<ElFormItem :label="$t('tenant.announcement.field.content')" prop="content">
|
||
<RichTextEditor
|
||
v-model="formState.content"
|
||
:placeholder="$t('tenant.announcement.placeholder.content')"
|
||
height="320px"
|
||
/>
|
||
</ElFormItem>
|
||
|
||
<ElDivider class="my-4" />
|
||
|
||
<!-- 2.3 目标受众 -->
|
||
<div class="mb-3 text-sm font-semibold">
|
||
{{ $t('tenant.announcement.section.audience') }}
|
||
</div>
|
||
<ElRow :gutter="16">
|
||
<ElCol :span="12" v-if="formState.targetType === 'roles'">
|
||
<ElFormItem :label="$t('tenant.announcement.field.targetParameters')">
|
||
<ElInput
|
||
v-model="formState.roleIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.roleIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12" v-if="formState.targetType === 'users'">
|
||
<ElFormItem :label="$t('tenant.announcement.field.targetParameters')">
|
||
<ElInput
|
||
v-model="formState.userIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.userIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="12" v-if="formState.targetType === 'manual'">
|
||
<ElFormItem :label="$t('tenant.announcement.field.targetParameters')">
|
||
<ElInput
|
||
v-model="formState.userIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.userIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElRow :gutter="16" v-if="formState.targetType === 'rules'">
|
||
<ElCol :span="8">
|
||
<ElFormItem :label="$t('tenant.announcement.field.departmentIds')">
|
||
<ElInput
|
||
v-model="formState.departmentIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.departmentIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem :label="$t('tenant.announcement.field.roleIds')">
|
||
<ElInput
|
||
v-model="formState.roleIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.roleIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem :label="$t('tenant.announcement.field.tagIds')">
|
||
<ElInput
|
||
v-model="formState.tagIds"
|
||
:placeholder="$t('tenant.announcement.placeholder.tagIds')"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</ElForm>
|
||
</ElCard>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { storeToRefs } from 'pinia'
|
||
import {
|
||
ElAlert,
|
||
ElButton,
|
||
ElCard,
|
||
ElCol,
|
||
ElDatePicker,
|
||
ElDivider,
|
||
ElForm,
|
||
ElFormItem,
|
||
ElInput,
|
||
ElInputNumber,
|
||
ElMessage,
|
||
ElOption,
|
||
ElRow,
|
||
ElSelect,
|
||
ElTag,
|
||
type FormInstance,
|
||
type FormRules
|
||
} from 'element-plus'
|
||
import { useAnnouncementStore } from '@/store/modules/announcement'
|
||
import { useUserStore } from '@/store/modules/user'
|
||
import { normalizeAnnouncementStatus } from '@/utils/announcementStatus'
|
||
import type {
|
||
AnnouncementFormData,
|
||
AnnouncementTargetType,
|
||
TargetRules
|
||
} from '@/types/announcement'
|
||
import { TenantAnnouncementType } from '@/types/announcement'
|
||
import RichTextEditor from '@/components/common/RichTextEditor.vue'
|
||
import { tenantAnnouncementApi } from '@/api/announcement'
|
||
import type { TenantAnnouncementDto } from '@/types/announcement'
|
||
|
||
defineOptions({ name: 'TenantAnnouncementCreate' })
|
||
|
||
const { t } = useI18n()
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const userStore = useUserStore()
|
||
const announcementStore = useAnnouncementStore()
|
||
|
||
const { loading, currentDraft } = storeToRefs(announcementStore)
|
||
|
||
// 1. 租户ID(优先路由参数,其次用户信息)
|
||
const tenantId = computed(() => {
|
||
const paramId = route.params.tenantId
|
||
const queryId = route.query.tenantId
|
||
const rawId = Array.isArray(paramId)
|
||
? paramId[0]
|
||
: paramId || (Array.isArray(queryId) ? queryId[0] : queryId)
|
||
return String(rawId || userStore.info?.tenantId || '')
|
||
})
|
||
|
||
// 1.5 编辑模式检测与数据加载
|
||
const announcementId = computed(() => {
|
||
const routeId = route.params.id
|
||
return Array.isArray(routeId) ? routeId[0] : routeId
|
||
})
|
||
|
||
const isEditMode = computed(() => !!announcementId.value)
|
||
const loadingData = ref(false)
|
||
const currentRowVersion = ref<string | null>(null)
|
||
|
||
// 加载公告数据(编辑模式)
|
||
const loadAnnouncementData = async () => {
|
||
if (!isEditMode.value || !tenantId.value || !announcementId.value) return
|
||
|
||
loadingData.value = true
|
||
errorMessage.value = null
|
||
try {
|
||
const data = await tenantAnnouncementApi.detail(tenantId.value, announcementId.value)
|
||
|
||
// 检查是否只有草稿可编辑
|
||
if (normalizeAnnouncementStatus(data.status) !== 'Draft') {
|
||
errorMessage.value = t('tenant.announcement.message.onlyDraftEditable')
|
||
return
|
||
}
|
||
|
||
// 保存 RowVersion
|
||
currentRowVersion.value = data.rowVersion || null
|
||
|
||
// 映射 DTO 到表单数据
|
||
const dtoToFormData = (dto: TenantAnnouncementDto): Partial<AnnouncementFormData> => {
|
||
let targetRules: TargetRules | undefined
|
||
let targetUserIds: string[] | undefined
|
||
|
||
// 解析 targetParameters JSON 字符串
|
||
if (dto.targetParameters) {
|
||
try {
|
||
const params = JSON.parse(dto.targetParameters)
|
||
if (dto.targetType === 'rules') {
|
||
targetRules = params
|
||
} else if (dto.targetType === 'users' || dto.targetType === 'manual') {
|
||
targetUserIds = params.userIds || []
|
||
} else if (dto.targetType === 'roles') {
|
||
targetRules = { roles: params.roles || [] }
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to parse targetParameters:', error)
|
||
}
|
||
}
|
||
|
||
return {
|
||
title: dto.title,
|
||
content: dto.content,
|
||
announcementType: dto.announcementType,
|
||
priority: dto.priority,
|
||
effectiveFrom: dto.effectiveFrom,
|
||
effectiveTo: dto.effectiveTo,
|
||
targetType: dto.targetType,
|
||
targetRules,
|
||
targetUserIds
|
||
}
|
||
}
|
||
|
||
applyDraftToForm(dtoToFormData(data))
|
||
} catch (error: any) {
|
||
console.error('Failed to load announcement:', error)
|
||
errorMessage.value =
|
||
error?.response?.data?.message || t('tenant.announcement.message.loadFailed')
|
||
} finally {
|
||
loadingData.value = false
|
||
}
|
||
}
|
||
|
||
// 2. 错误提示与草稿状态
|
||
const errorMessage = ref<string | null>(null)
|
||
const draftLoaded = ref(false)
|
||
|
||
const draftStatus = computed(() => {
|
||
if (!currentDraft.value?.lastSaved) {
|
||
return t('tenant.announcement.tip.draftAutoSave')
|
||
}
|
||
return t('tenant.announcement.tip.draftSaved', {
|
||
time: formatDateTime(currentDraft.value.lastSaved)
|
||
})
|
||
})
|
||
|
||
// 3. 表单数据(UI 形态)
|
||
interface AnnouncementFormState {
|
||
title: string
|
||
content: string
|
||
announcementType: TenantAnnouncementType
|
||
priority: number
|
||
effectiveRange: string[]
|
||
targetType: AnnouncementTargetType
|
||
roleIds: string
|
||
userIds: string
|
||
departmentIds: string
|
||
tagIds: string
|
||
}
|
||
|
||
const formState = reactive<AnnouncementFormState>({
|
||
title: '',
|
||
content: '',
|
||
announcementType: TenantAnnouncementType.TENANT_INTERNAL,
|
||
priority: 3,
|
||
effectiveRange: [],
|
||
targetType: 'all',
|
||
roleIds: '',
|
||
userIds: '',
|
||
departmentIds: '',
|
||
tagIds: ''
|
||
})
|
||
|
||
// 4. 草稿数据(对接 Store)
|
||
const draftState = reactive<AnnouncementFormData>({
|
||
title: '',
|
||
content: '',
|
||
announcementType: TenantAnnouncementType.TENANT_INTERNAL,
|
||
priority: 3,
|
||
effectiveFrom: '',
|
||
effectiveTo: null,
|
||
targetType: 'all'
|
||
})
|
||
|
||
// 5. 选项数据
|
||
const typeOptions = computed(() => [
|
||
{ label: t('tenant.announcement.type.system'), value: TenantAnnouncementType.System },
|
||
{ label: t('tenant.announcement.type.billing'), value: TenantAnnouncementType.Billing },
|
||
{ label: t('tenant.announcement.type.operation'), value: TenantAnnouncementType.Operation },
|
||
{
|
||
label: t('tenant.announcement.type.systemPlatformUpdate'),
|
||
value: TenantAnnouncementType.SYSTEM_PLATFORM_UPDATE
|
||
},
|
||
{
|
||
label: t('tenant.announcement.type.systemSecurityNotice'),
|
||
value: TenantAnnouncementType.SYSTEM_SECURITY_NOTICE
|
||
},
|
||
{
|
||
label: t('tenant.announcement.type.systemCompliance'),
|
||
value: TenantAnnouncementType.SYSTEM_COMPLIANCE
|
||
},
|
||
{
|
||
label: t('tenant.announcement.type.tenantInternal'),
|
||
value: TenantAnnouncementType.TENANT_INTERNAL
|
||
},
|
||
{
|
||
label: t('tenant.announcement.type.tenantFinance'),
|
||
value: TenantAnnouncementType.TENANT_FINANCE
|
||
},
|
||
{
|
||
label: t('tenant.announcement.type.tenantOperation'),
|
||
value: TenantAnnouncementType.TENANT_OPERATION
|
||
}
|
||
])
|
||
|
||
const targetTypeOptions = computed(() => [
|
||
{ label: t('tenant.announcement.targetType.all'), value: 'all' },
|
||
{ label: t('tenant.announcement.targetType.roles'), value: 'roles' },
|
||
{ label: t('tenant.announcement.targetType.users'), value: 'users' },
|
||
{ label: t('tenant.announcement.targetType.rules'), value: 'rules' },
|
||
{ label: t('tenant.announcement.targetType.manual'), value: 'manual' }
|
||
])
|
||
|
||
// 6. 表单验证
|
||
const rules: FormRules = {
|
||
title: [
|
||
{
|
||
required: true,
|
||
message: t('tenant.announcement.validation.titleRequired'),
|
||
trigger: 'blur'
|
||
},
|
||
{
|
||
min: 2,
|
||
max: 128,
|
||
message: t('tenant.announcement.validation.titleLength'),
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
content: [
|
||
{
|
||
required: true,
|
||
message: t('tenant.announcement.validation.contentRequired'),
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
announcementType: [
|
||
{
|
||
required: true,
|
||
message: t('tenant.announcement.validation.typeRequired'),
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
priority: [
|
||
{
|
||
required: true,
|
||
message: t('tenant.announcement.validation.priorityRequired'),
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
effectiveRange: [
|
||
{
|
||
required: true,
|
||
type: 'array',
|
||
min: 2,
|
||
message: t('tenant.announcement.validation.effectiveRangeRequired'),
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
targetType: [
|
||
{
|
||
required: true,
|
||
message: t('tenant.announcement.validation.targetTypeRequired'),
|
||
trigger: 'change'
|
||
}
|
||
]
|
||
}
|
||
|
||
const formRef = ref<FormInstance>()
|
||
|
||
// 7. 时间处理
|
||
const formatDateTime = (value?: string | null) => {
|
||
if (!value) return '-'
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) return '-'
|
||
return date.toLocaleString()
|
||
}
|
||
|
||
const toIsoDateTime = (value: string) => {
|
||
if (!value) return ''
|
||
const normalized = value.includes('T') ? value : value.replace(' ', 'T')
|
||
const date = new Date(normalized)
|
||
if (Number.isNaN(date.getTime())) return ''
|
||
return date.toISOString()
|
||
}
|
||
|
||
const formatDateTimeInput = (value?: string | null) => {
|
||
if (!value) return ''
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) return ''
|
||
const pad = (num: number) => String(num).padStart(2, '0')
|
||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
|
||
date.getHours()
|
||
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||
}
|
||
|
||
// 8. 目标受众处理
|
||
const splitIds = (value: string) => {
|
||
return value
|
||
.split(/[,,\s]+/)
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
}
|
||
|
||
const buildTargetRules = () => {
|
||
if (formState.targetType === 'roles') {
|
||
return { roles: splitIds(formState.roleIds) }
|
||
}
|
||
if (formState.targetType === 'rules') {
|
||
return {
|
||
departments: splitIds(formState.departmentIds),
|
||
roles: splitIds(formState.roleIds),
|
||
tags: splitIds(formState.tagIds)
|
||
}
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
const buildTargetUserIds = () => {
|
||
if (formState.targetType === 'users' || formState.targetType === 'manual') {
|
||
return splitIds(formState.userIds)
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
const validateTargetInputs = () => {
|
||
if (formState.targetType === 'roles' && splitIds(formState.roleIds).length === 0) {
|
||
ElMessage.warning(t('tenant.announcement.validation.roleIdsRequired'))
|
||
return false
|
||
}
|
||
|
||
if (
|
||
(formState.targetType === 'users' || formState.targetType === 'manual') &&
|
||
splitIds(formState.userIds).length === 0
|
||
) {
|
||
ElMessage.warning(t('tenant.announcement.validation.userIdsRequired'))
|
||
return false
|
||
}
|
||
|
||
if (formState.targetType === 'rules') {
|
||
const hasRule =
|
||
splitIds(formState.departmentIds).length > 0 ||
|
||
splitIds(formState.roleIds).length > 0 ||
|
||
splitIds(formState.tagIds).length > 0
|
||
if (!hasRule) {
|
||
ElMessage.warning(t('tenant.announcement.validation.targetRuleRequired'))
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 9. 同步草稿数据
|
||
const syncDraftState = () => {
|
||
// 1. 同步基础字段
|
||
const [start, end] = formState.effectiveRange
|
||
draftState.title = formState.title
|
||
draftState.content = formState.content
|
||
draftState.announcementType = formState.announcementType
|
||
draftState.priority = formState.priority
|
||
|
||
// 2. 同步有效期
|
||
draftState.effectiveFrom = toIsoDateTime(start)
|
||
draftState.effectiveTo = end ? toIsoDateTime(end) : null
|
||
|
||
// 3. 同步受众参数
|
||
draftState.targetType = formState.targetType
|
||
draftState.targetRules = buildTargetRules()
|
||
draftState.targetUserIds = buildTargetUserIds()
|
||
}
|
||
|
||
const applyDraftToForm = (draft: Partial<AnnouncementFormData>) => {
|
||
// 1. 回填基础字段
|
||
formState.title = draft.title || ''
|
||
formState.content = draft.content || ''
|
||
formState.announcementType = draft.announcementType ?? TenantAnnouncementType.TENANT_INTERNAL
|
||
formState.priority = draft.priority ?? 3
|
||
formState.targetType = draft.targetType ?? 'all'
|
||
|
||
// 2. 回填有效期
|
||
const start = formatDateTimeInput(draft.effectiveFrom)
|
||
const end = formatDateTimeInput(draft.effectiveTo ?? null)
|
||
formState.effectiveRange = start && end ? [start, end] : []
|
||
|
||
// 3. 回填受众参数
|
||
formState.roleIds = draft.targetRules?.roles?.join(',') || ''
|
||
formState.departmentIds = draft.targetRules?.departments?.join(',') || ''
|
||
formState.tagIds = draft.targetRules?.tags?.join(',') || ''
|
||
formState.userIds = draft.targetUserIds?.join(',') || ''
|
||
}
|
||
|
||
// 10. 草稿自动保存
|
||
const stopAutoSave = ref<(() => void) | null>(null)
|
||
|
||
const initDraft = () => {
|
||
// 1. 同步草稿数据
|
||
syncDraftState()
|
||
|
||
// 2. 若无草稿则先写入本地
|
||
if (!currentDraft.value) {
|
||
announcementStore.saveDraft(draftState)
|
||
}
|
||
|
||
// 3. 启动自动保存
|
||
stopAutoSave.value = announcementStore.autoSaveDraft(draftState, 30000)
|
||
}
|
||
|
||
// 11. 提交创建/更新
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
if (!tenantId.value) {
|
||
ElMessage.error(t('tenant.announcement.message.tenantIdMissing'))
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 1. 表单校验
|
||
await formRef.value.validate()
|
||
if (!validateTargetInputs()) return
|
||
|
||
// 2. 组装请求参数
|
||
syncDraftState()
|
||
const payload: AnnouncementFormData = {
|
||
...draftState,
|
||
title: formState.title.trim(),
|
||
content: formState.content.trim()
|
||
}
|
||
|
||
// 3. 编辑模式:包含 RowVersion
|
||
if (isEditMode.value && announcementId.value) {
|
||
if (currentRowVersion.value) {
|
||
payload.rowVersion = currentRowVersion.value
|
||
}
|
||
|
||
const result = await tenantAnnouncementApi.update(
|
||
tenantId.value,
|
||
announcementId.value,
|
||
payload
|
||
)
|
||
ElMessage.success(t('tenant.announcement.message.updateSuccess'))
|
||
|
||
// 跳转详情页
|
||
router.push({
|
||
path: `${listPath.value}/${result.id}`,
|
||
query: tenantId.value ? { tenantId: tenantId.value } : undefined
|
||
})
|
||
return
|
||
}
|
||
|
||
// 4. 创建模式
|
||
const result = await announcementStore.createAnnouncement(payload, {
|
||
scope: 'tenant',
|
||
tenantId: tenantId.value
|
||
})
|
||
|
||
if (result) {
|
||
ElMessage.success(t('tenant.announcement.message.createSuccess'))
|
||
|
||
// 5. 清理草稿
|
||
if (currentDraft.value?.draftId) {
|
||
announcementStore.clearDraft(currentDraft.value.draftId)
|
||
}
|
||
|
||
// 6. 跳转详情页
|
||
router.push({
|
||
path: `${listPath.value}/${result.id}`,
|
||
query: tenantId.value ? { tenantId: tenantId.value } : undefined
|
||
})
|
||
}
|
||
} catch (error: any) {
|
||
console.error(error)
|
||
// 处理并发冲突
|
||
if (error?.response?.status === 409) {
|
||
ElMessage.error(t('tenant.announcement.message.concurrentConflict'))
|
||
} else {
|
||
ElMessage.error(
|
||
error?.response?.data?.message || t('tenant.announcement.message.operationFailed')
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 12. 返回列表
|
||
const listPath = computed(() => route.path.replace(/\/(create|edit\/[^/]+)$/, ''))
|
||
|
||
const handleBack = () => {
|
||
router.push({
|
||
path: listPath.value,
|
||
query: tenantId.value ? { tenantId: tenantId.value } : undefined
|
||
})
|
||
}
|
||
|
||
// 13. 初始化草稿与错误提示
|
||
watch(
|
||
tenantId,
|
||
(value) => {
|
||
if (!value) {
|
||
errorMessage.value = t('tenant.announcement.message.tenantIdMissing')
|
||
} else {
|
||
errorMessage.value = null
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
watch(
|
||
formState,
|
||
() => {
|
||
// 1. 表单变化时同步草稿数据
|
||
syncDraftState()
|
||
},
|
||
{ deep: true }
|
||
)
|
||
|
||
const draftIdFromRoute = computed(() => {
|
||
const draftId = route.query.draftId
|
||
return Array.isArray(draftId) ? draftId[0] : draftId
|
||
})
|
||
|
||
// 14. 初始化逻辑
|
||
if (isEditMode.value) {
|
||
// 编辑模式:加载公告数据
|
||
loadAnnouncementData()
|
||
} else if (draftIdFromRoute.value) {
|
||
// 创建模式:加载草稿
|
||
const draft = announcementStore.loadDraft(String(draftIdFromRoute.value))
|
||
if (draft) {
|
||
applyDraftToForm(draft)
|
||
draftLoaded.value = true
|
||
}
|
||
}
|
||
|
||
// 创建模式下初始化草稿自动保存
|
||
if (!isEditMode.value) {
|
||
initDraft()
|
||
}
|
||
|
||
onBeforeUnmount(() => {
|
||
// 1. 组件卸载时停止自动保存
|
||
stopAutoSave.value?.()
|
||
})
|
||
</script>
|