chore: 初始化平台管理端
This commit is contained in:
746
src/views/tenant/announcements/create.vue
Normal file
746
src/views/tenant/announcements/create.vue
Normal file
@@ -0,0 +1,746 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user