| | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { toAccessibleFileUrl } from '#/utils'; |
| | | import { |
| | | createNotice, |
| | | getNotice, |
| | |
| | | } from '#/api/wxcp/notice'; |
| | | |
| | | import { NOTICE_TYPE, useNoticeFormSchema } from '../data'; |
| | | import ArticleEditor from './article-editor.vue'; |
| | | |
| | | defineOptions({ name: 'WxCpNoticeForm' }); |
| | | |
| | |
| | | const formType = ref<NoticeFormType>('create'); |
| | | const id = ref<number>(); |
| | | const submitting = ref(false); |
| | | /** 当前模板类型,用于控制多图文编辑器显隐 */ |
| | | const noticeType = ref<number>(NOTICE_TYPE.TEXT); |
| | | /** 多图文条目(表单项之外单独维护) */ |
| | | const articles = ref<WxCpNoticeApi.NoticeArticle[]>([]); |
| | | |
| | | const showArticleEditor = computed( |
| | | () => noticeType.value === NOTICE_TYPE.MULTI_NEWS, |
| | | ); |
| | | |
| | | const isEdit = computed(() => formType.value !== 'detail'); |
| | | const title = computed(() => { |
| | |
| | | if (id.value) return '编辑通知'; |
| | | return '新建通知'; |
| | | }); |
| | | |
| | | /** |
| | | * 把附件列表转成上传组件能识别的文件对象 |
| | | * |
| | | * 回显对象必须带 response:组件设了 resultField 后只认 item.response, |
| | | * 不带的话「进编辑页不动图直接保存」会走 URL 分支 → NaN → [] → 图被清空 |
| | | */ |
| | | function attachmentsToUploadFiles( |
| | | list: WxCpNoticeApi.NoticeAttachment[] | undefined, |
| | | ) { |
| | | return (list ?? []).map((item) => { |
| | | const name = item.name || item.originalFilename || ''; |
| | | const url = toAccessibleFileUrl(item.url || item.previewURL); |
| | | return { |
| | | uid: String(item.id), |
| | | name, |
| | | url, |
| | | status: 'done', |
| | | id: item.id, |
| | | response: { id: item.id, name, url }, |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 把上传组件的值规整成 blobId 数组 |
| | | * |
| | | * ImageUpload 在 maxNumber=1 时返回单个值而不是数组,这里统一包成数组再提交 |
| | | */ |
| | | function normalizeBlobIds(value: unknown): number[] { |
| | | const list = Array.isArray(value) |
| | | ? value |
| | | : value === '' || value === null || value === undefined |
| | | ? [] |
| | | : [value]; |
| | | return list |
| | | .map((item) => |
| | | item && typeof item === 'object' && 'id' in item |
| | | ? Number((item as { id: unknown }).id) |
| | | : Number(item), |
| | | ) |
| | | .filter((blobId) => Number.isFinite(blobId)); |
| | | } |
| | | |
| | | /** |
| | | * 把通知详情映射成表单值 |
| | | * |
| | | * 图片/附件不落业务表字段,统一存在附件中间表, |
| | | * 回显时把 imageAttachmentList / fileAttachmentList 转成上传组件文件对象。 |
| | | * 历史数据只有旧版 fileBlobId,这里兜底成单个附件,避免编辑后附件丢失。 |
| | | */ |
| | | function noticeToForm( |
| | | detail: WxCpNoticeApi.Notice, |
| | | ): Record<string, unknown> { |
| | | const form: Record<string, unknown> = { ...detail }; |
| | | form.imageBlobIds = attachmentsToUploadFiles(detail.imageAttachmentList); |
| | | form.videoBlobIds = attachmentsToUploadFiles(detail.videoAttachmentList); |
| | | form.fileBlobIds = |
| | | detail.fileAttachmentList?.length |
| | | ? attachmentsToUploadFiles(detail.fileAttachmentList) |
| | | : detail.fileBlobId |
| | | ? [ |
| | | { |
| | | uid: String(detail.fileBlobId), |
| | | name: detail.fileName || '', |
| | | status: 'done', |
| | | id: detail.fileBlobId, |
| | | response: { |
| | | id: detail.fileBlobId, |
| | | name: detail.fileName || '', |
| | | }, |
| | | }, |
| | | ] |
| | | : []; |
| | | return form; |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-1', |
| | | handleValuesChange: (values) => { |
| | | noticeType.value = (values.noticeType as number) ?? NOTICE_TYPE.TEXT; |
| | | }, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | |
| | | modalApi.lock(); |
| | | try { |
| | | const detail = await getNotice(data.id); |
| | | const values: Record<string, unknown> = { ...detail }; |
| | | // 文件通知:把 blobId 还原成上传组件可识别的文件对象 |
| | | if (detail.noticeType === NOTICE_TYPE.FILE && detail.fileBlobId) { |
| | | values.fileBlobId = [ |
| | | { |
| | | uid: String(detail.fileBlobId), |
| | | name: detail.fileName || '', |
| | | status: 'done', |
| | | id: detail.fileBlobId, |
| | | }, |
| | | ]; |
| | | } |
| | | await formApi.setValues(values); |
| | | noticeType.value = detail.noticeType ?? NOTICE_TYPE.TEXT; |
| | | articles.value = (detail.articleList ?? []).map((item) => ({ |
| | | ...item, |
| | | })); |
| | | await formApi.setValues(noticeToForm(detail)); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | } else { |
| | | await formApi.resetForm(); |
| | | noticeType.value = NOTICE_TYPE.TEXT; |
| | | articles.value = []; |
| | | await formApi.setValues({ noticeType: NOTICE_TYPE.TEXT }, false); |
| | | } |
| | | }, |
| | |
| | | try { |
| | | const data = (await formApi.getValues()) as WxCpNoticeApi.Notice; |
| | | // 保存时只做文件归一化;具体模板必填项在推送时按所选模板校验 |
| | | const noticeType = data.noticeType ?? NOTICE_TYPE.TEXT; |
| | | if (noticeType === NOTICE_TYPE.FILE) { |
| | | const raw = data.fileBlobId as unknown; |
| | | let blobId: number | undefined; |
| | | if (Array.isArray(raw)) { |
| | | const first = raw[0]; |
| | | blobId = |
| | | typeof first === 'number' |
| | | ? first |
| | | : (first as { id?: number } | undefined)?.id; |
| | | } else if (typeof raw === 'number') { |
| | | blobId = raw; |
| | | } else if (raw && typeof raw === 'object') { |
| | | blobId = (raw as { id?: number }).id; |
| | | } |
| | | data.fileBlobId = blobId; |
| | | } else { |
| | | data.fileBlobId = undefined; |
| | | } |
| | | const payload = { |
| | | ...data, |
| | | // null = 不改动,[] = 清空;这里统一提交数组,切换模板后旧附件会被清掉 |
| | | imageBlobIds: normalizeBlobIds(data.imageBlobIds), |
| | | fileBlobIds: normalizeBlobIds(data.fileBlobIds), |
| | | videoBlobIds: normalizeBlobIds(data.videoBlobIds), |
| | | // 多图文条目单独维护,只有该模板提交;null 表示不改动 |
| | | articleList: showArticleEditor.value ? articles.value : undefined, |
| | | // 旧版单附件字段不再使用,避免和 fileBlobIds 重复 |
| | | fileBlobId: undefined, |
| | | fileName: undefined, |
| | | } as WxCpNoticeApi.Notice; |
| | | if (id.value) { |
| | | await updateNotice({ ...data, id: id.value }); |
| | | await updateNotice({ ...payload, id: id.value }); |
| | | message.success('修改成功'); |
| | | } else { |
| | | await createNotice(data); |
| | | await createNotice(payload); |
| | | message.success('创建成功,请在列表中发布后再推送'); |
| | | } |
| | | await modalApi.close(); |
| | |
| | | <Modal :title="title" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | |
| | | <div v-if="showArticleEditor" class="mx-4 mt-2"> |
| | | <ArticleEditor |
| | | v-model="articles" |
| | | :disabled="formType === 'detail'" |
| | | /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button @click="modalApi.close()">取消</Button> |