From 2b695bfabdceccb0ee19c12c8e655bd7f47a6e2f Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期二, 27 十月 2026 18:06:44 +0800
Subject: [PATCH] feat(wxcp): 通知前端支持图片/视频/Markdown/多图文与成员预览
---
src/views/wxcp/notice/modules/article-editor.vue | 157 +++++++++++++
src/views/wxcp/notice/modules/form.vue | 151 +++++++++--
src/views/wxcp/notice/index.vue | 23 +
src/views/wxcp/notice/data.ts | 60 ++++
src/views/wxcp/notice/modules/push-modal.vue | 268 ++++++++++++++++++++--
src/api/wxcp/notice.ts | 57 ++++
6 files changed, 648 insertions(+), 68 deletions(-)
diff --git a/src/api/wxcp/notice.ts b/src/api/wxcp/notice.ts
index fe3ebb9..de46b1c 100644
--- a/src/api/wxcp/notice.ts
+++ b/src/api/wxcp/notice.ts
@@ -10,6 +10,41 @@
REVOKED: 2,
} as const;
+ /** 閫氱煡妯℃澘绫诲瀷 */
+ export const NOTICE_TYPE = {
+ TEXT: 1,
+ CARD: 2,
+ FILE: 3,
+ NEWS: 4,
+ IMAGE: 5,
+ VIDEO: 6,
+ MARKDOWN: 7,
+ MULTI_NEWS: 8,
+ } as const;
+
+ /** 澶氬浘鏂囨潯鐩� */
+ export interface NoticeArticle {
+ id?: number;
+ title?: string;
+ description?: string;
+ linkUrl?: string;
+ coverBlobId?: number;
+ coverAttachment?: NoticeAttachment;
+ sortNo?: number;
+ }
+
+ /** 閫氱煡闄勪欢锛堝浘鐗�/鏂囦欢/瑙嗛閫氱敤锛� */
+ export interface NoticeAttachment {
+ id?: number;
+ name?: string;
+ originalFilename?: string;
+ contentType?: string;
+ byteSize?: number;
+ url?: string;
+ previewURL?: string;
+ downloadURL?: string;
+ }
+
/** 閫氱煡 */
export interface Notice {
id?: number;
@@ -21,8 +56,26 @@
linkUrl?: string;
linkBtnText?: string;
coverUrl?: string;
+ /** 鏃х増鍗曢檮浠跺瓧娈碉紝鍏煎鍘嗗彶鏁版嵁 */
fileBlobId?: number;
+ /** 鏃х増鍗曢檮浠舵枃浠跺悕 */
fileName?: string;
+ /** 鍥剧墖閫氱煡鐨勫浘鐗� blobId 鍒楄〃 */
+ imageBlobIds?: number[];
+ /** 鍥剧墖閫氱煡鐨勫浘鐗囬檮浠跺垪琛� */
+ imageAttachmentList?: NoticeAttachment[];
+ /** 闄勪欢閫氱煡鐨勯檮浠� blobId 鍒楄〃 */
+ fileBlobIds?: number[];
+ /** 闄勪欢閫氱煡鐨勯檮浠跺垪琛� */
+ fileAttachmentList?: NoticeAttachment[];
+ /** 瑙嗛閫氱煡鐨勮棰� blobId 鍒楄〃 */
+ videoBlobIds?: number[];
+ /** 瑙嗛閫氱煡鐨勮棰戝垪琛� */
+ videoAttachmentList?: NoticeAttachment[];
+ /** 澶氬浘鏂囨秷鎭殑鍥炬枃鍒楄〃 */
+ articleList?: NoticeArticle[];
+ /** 鎴愬憳棰勮椤靛湴鍧� */
+ previewUrl?: string;
status?: number;
statusName?: string;
pushScope?: number;
@@ -100,6 +153,10 @@
coverUrl?: string;
fileBlobId?: number;
fileName?: string;
+ imageBlobIds?: number[];
+ fileBlobIds?: number[];
+ videoBlobIds?: number[];
+ articleList?: WxCpNoticeApi.NoticeArticle[];
}) {
return requestClient.post<{
success: boolean;
diff --git a/src/views/wxcp/notice/data.ts b/src/views/wxcp/notice/data.ts
index 1b28e06..e09d0fb 100644
--- a/src/views/wxcp/notice/data.ts
+++ b/src/views/wxcp/notice/data.ts
@@ -13,12 +13,20 @@
CARD: 2,
FILE: 3,
NEWS: 4,
+ IMAGE: 5,
+ VIDEO: 6,
+ MARKDOWN: 7,
+ MULTI_NEWS: 8,
} as const;
export const NOTICE_TYPE_OPTIONS = [
{ label: '绾枃鏈�氱煡', value: NOTICE_TYPE.TEXT },
{ label: '鍥炬枃鍗$墖锛堝~鎶�/閾炬帴锛�', value: NOTICE_TYPE.CARD },
{ label: '鏂囦欢閫氱煡锛堜笂浼犳枃浠舵帹閫侊級', value: NOTICE_TYPE.FILE },
+ { label: '鍥剧墖閫氱煡锛堜笂浼犲浘鐗囨帹閫侊級', value: NOTICE_TYPE.IMAGE },
+ { label: '瑙嗛閫氱煡锛堜笂浼犺棰戞帹閫侊級', value: NOTICE_TYPE.VIDEO },
+ { label: 'Markdown 閫氱煡锛堝瘜鏂囨湰鎺掔増锛�', value: NOTICE_TYPE.MARKDOWN },
+ { label: '澶氬浘鏂囨秷鎭紙澶氭潯閾炬帴鍥炬枃锛�', value: NOTICE_TYPE.MULTI_NEWS },
{ label: '鍥炬枃娑堟伅锛堟爣棰�+鎽樿+灏侀潰锛�', value: NOTICE_TYPE.NEWS },
];
@@ -58,7 +66,7 @@
placeholder: '绾枃鏈�氱煡蹇呭~锛涘叾瀹冩ā鏉垮彲浣滀负琛ュ厖璇存槑',
disabled: readonly,
},
- help: '绾枃鏈�氱煡鎺ㄩ�佹椂瓒呰繃 600 瀛椾細鑷姩鎴柇锛屽缓璁妸鍏抽敭淇℃伅鏀惧墠闈�',
+ help: '绾枃鏈帹閫佽秴杩� 600 瀛椾細鑷姩鎴柇锛汳arkdown 妯℃澘鏀寔 # 鏍囬銆�**鍔犵矖**銆�- 鍒楄〃銆�> 寮曠敤锛屾鏂囬渶鎺у埗鍦� 2048 瀛楄妭鍐�',
formItemClass: 'col-span-2',
},
{
@@ -122,13 +130,33 @@
formItemClass: 'col-span-2',
},
{
- fieldName: 'fileBlobId',
- label: '閫氱煡鏂囦欢',
+ // 鍥剧墖閫氱煡锛氭帹閫佹椂閫愬紶鍙戦�佸浘鐗囨秷鎭紝鏈�澶� 9 寮�
+ fieldName: 'imageBlobIds',
+ label: '鎺ㄩ�佸浘鐗�',
+ component: 'ImageUpload',
+ componentProps: {
+ valueKey: 'id',
+ resultField: 'id',
+ maxNumber: 9,
+ multiple: true,
+ disabled: readonly,
+ },
+ dependencies: {
+ triggerFields: ['noticeType'],
+ show: (values) => values.noticeType === NOTICE_TYPE.IMAGE,
+ },
+ help: '鎺ㄩ�佹椂閫愬紶鍙戦�佸浘鐗囨秷鎭紝鍗曞紶涓嶈秴杩� 10MB锛屾渶澶� 9 寮狅紱鎺ㄩ�佸悗闄勩�屾煡鐪嬪叏閮ㄥ浘鐗囥�嶅崱鐗囷紝鎴愬憳鍙湪椤甸潰鍐呮斁澶ф煡鐪�',
+ formItemClass: 'col-span-2',
+ },
+ {
+ // 闄勪欢閫氱煡锛氭帹閫佹椂閫愪釜鍙戞枃浠舵秷鎭紝鎺ユ敹浜哄彲鐩存帴涓嬭浇
+ fieldName: 'fileBlobIds',
+ label: '鎺ㄩ�侀檮浠�',
component: 'FileUpload',
componentProps: {
valueKey: 'id',
- maxNumber: 1,
- multiple: false,
+ maxNumber: 5,
+ multiple: true,
showDescription: !readonly,
disabled: readonly,
},
@@ -136,7 +164,27 @@
triggerFields: ['noticeType'],
show: (values) => values.noticeType === NOTICE_TYPE.FILE,
},
- help: '浼佷笟寰俊鏂囦欢娑堟伅涓婇檺 20MB',
+ help: '鎺ㄩ�佹椂閫愪釜鍙戦�佹枃浠舵秷鎭紝鍗曚釜涓嶈秴杩� 20MB锛屾渶澶� 5 涓紱鎺ㄩ�佸悗闄勩�屾煡鐪嬪叏閮ㄩ檮浠躲�嶅崱鐗�',
+ formItemClass: 'col-span-2',
+ },
+ {
+ // 瑙嗛閫氱煡锛氫笂浼� 1 涓棰戯紝鎺ㄩ�佹椂鍙戦�佽棰戞秷鎭�
+ fieldName: 'videoBlobIds',
+ label: '鎺ㄩ�佽棰�',
+ component: 'FileUpload',
+ componentProps: {
+ valueKey: 'id',
+ maxNumber: 1,
+ multiple: false,
+ accept: ['mp4', 'mov', 'm4v'],
+ showDescription: !readonly,
+ disabled: readonly,
+ },
+ dependencies: {
+ triggerFields: ['noticeType'],
+ show: (values) => values.noticeType === NOTICE_TYPE.VIDEO,
+ },
+ help: '鎺ㄩ�佹椂鍙戦�佽棰戞秷鎭紝寤鸿浣跨敤 mp4锛屽崟涓笉瓒呰繃 10MB锛涙帹閫佸悗闄勩�屾煡鐪嬮�氱煡璇︽儏銆嶅崱鐗囷紝鎴愬憳鍙叏灞忔挱鏀�',
formItemClass: 'col-span-2',
},
{
diff --git a/src/views/wxcp/notice/index.vue b/src/views/wxcp/notice/index.vue
index 541b046..1245199 100644
--- a/src/views/wxcp/notice/index.vue
+++ b/src/views/wxcp/notice/index.vue
@@ -16,6 +16,7 @@
import { NOTICE_STATUS, NOTICE_STATUS_OPTIONS, toLabelMap } from '../data';
import {
+ NOTICE_TYPE,
useNoticeGridColumns,
useNoticeSearchFormSchema,
} from './data';
@@ -67,6 +68,18 @@
await revokeNotice(row.id!);
message.success('宸叉挙鍥�');
handleRefresh();
+}
+
+/** 澶嶅埗鎴愬憳棰勮閾炬帴锛屽彲鐩存帴鍙戝埌浼佷笟寰俊缇ら噷 */
+async function handleCopyPreview(row: WxCpNoticeApi.Notice) {
+ if (!row.previewUrl) {
+ message.warning(
+ '鏈厤缃缃戝湴鍧�鍓嶇紑锛坵x.cp.signup-url-prefix锛夛紝鏃犳硶鐢熸垚棰勮閾炬帴',
+ );
+ return;
+ }
+ await navigator.clipboard.writeText(row.previewUrl);
+ message.success('棰勮閾炬帴宸插鍒�');
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -176,6 +189,16 @@
onClick: () => pushModalApi.setData({ notice: row }).open(),
},
{
+ label: '棰勮閾炬帴',
+ type: 'link',
+ ifShow:
+ row.status === NOTICE_STATUS.PUBLISHED &&
+ (row.noticeType === NOTICE_TYPE.IMAGE ||
+ row.noticeType === NOTICE_TYPE.FILE ||
+ row.noticeType === NOTICE_TYPE.VIDEO),
+ onClick: handleCopyPreview.bind(null, row),
+ },
+ {
label: '鎾ゅ洖',
type: 'link',
auth: ['wxcp:notice:update'],
diff --git a/src/views/wxcp/notice/modules/article-editor.vue b/src/views/wxcp/notice/modules/article-editor.vue
new file mode 100644
index 0000000..a1253c8
--- /dev/null
+++ b/src/views/wxcp/notice/modules/article-editor.vue
@@ -0,0 +1,157 @@
+<script lang="ts" setup>
+import type { WxCpNoticeApi } from '#/api/wxcp/notice';
+
+import { Button, Input } from 'ant-design-vue';
+
+import { ImageUpload } from '#/components/upload';
+import { toAccessibleFileUrl } from '#/utils';
+
+defineOptions({ name: 'WxCpNoticeArticleEditor' });
+
+interface Props {
+ /** 澶氬浘鏂囨潯鐩� */
+ modelValue?: WxCpNoticeApi.NoticeArticle[];
+ disabled?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ modelValue: () => [],
+ disabled: false,
+});
+
+const emit = defineEmits<{
+ 'update:modelValue': [value: WxCpNoticeApi.NoticeArticle[]];
+}>();
+
+function update(index: number, patch: Partial<WxCpNoticeApi.NoticeArticle>) {
+ const list = [...props.modelValue];
+ list[index] = { ...list[index], ...patch };
+ emit('update:modelValue', list);
+}
+
+function handleAdd() {
+ emit('update:modelValue', [
+ ...props.modelValue,
+ {
+ title: '',
+ description: '',
+ linkUrl: '',
+ sortNo: props.modelValue.length + 1,
+ },
+ ]);
+}
+
+function handleRemove(index: number) {
+ emit(
+ 'update:modelValue',
+ props.modelValue.filter((_, i) => i !== index),
+ );
+}
+
+/** 灏侀潰鍥惧洖鏄炬垚涓婁紶缁勪欢璇嗗埆鐨勬枃浠跺璞� */
+function toCoverValue(item: WxCpNoticeApi.NoticeArticle): unknown[] {
+ const cover = item.coverAttachment;
+ if (!cover || !item.coverBlobId) {
+ return [];
+ }
+ const url = toAccessibleFileUrl(cover.url || cover.previewURL);
+ return [
+ {
+ uid: String(item.coverBlobId),
+ name: cover.name || cover.originalFilename || '',
+ url,
+ status: 'done',
+ id: item.coverBlobId,
+ response: { id: item.coverBlobId },
+ },
+ ];
+}
+
+/** 涓婁紶缁勪欢杩斿洖鍊肩粺涓�鍙栧嚭 blobId */
+function handleCoverChange(index: number, value: unknown) {
+ const list = Array.isArray(value) ? value : value ? [value] : [];
+ const first = list[0];
+ const blobId =
+ first && typeof first === 'object' && 'id' in first
+ ? Number((first as { id: unknown }).id)
+ : Number(first);
+ update(index, {
+ coverBlobId: Number.isFinite(blobId) && blobId > 0 ? blobId : undefined,
+ });
+}
+</script>
+
+<template>
+ <div class="mb-4">
+ <div class="mb-2 flex items-center justify-between">
+ <span class="text-sm font-medium">鍥炬枃鍐呭锛堟渶澶� 8 绡囷級</span>
+ <Button
+ size="small"
+ :disabled="disabled || modelValue.length >= 8"
+ @click="handleAdd"
+ >
+ 娣诲姞鍥炬枃
+ </Button>
+ </div>
+
+ <div v-if="!modelValue.length" class="text-xs text-gray-400">
+ 杩樻病鏈夊浘鏂囥�備竴鏉″鍥炬枃娑堟伅鏈�澶氬彲甯� 8 绡囷紝姣忕瘒闇�瑕佹爣棰樺拰璺宠浆閾炬帴銆�
+ </div>
+
+ <div
+ v-for="(item, index) in modelValue"
+ :key="index"
+ class="mb-3 rounded border border-gray-200 p-3 dark:border-gray-700"
+ >
+ <div class="mb-2 flex items-center justify-between">
+ <span class="text-xs text-gray-500">绗� {{ index + 1 }} 绡�</span>
+ <Button
+ danger
+ type="link"
+ size="small"
+ :disabled="disabled"
+ @click="handleRemove(index)"
+ >
+ 鍒犻櫎
+ </Button>
+ </div>
+
+ <div class="grid grid-cols-1 gap-2">
+ <Input
+ :value="item.title"
+ placeholder="鍥炬枃鏍囬锛堝繀濉級"
+ :disabled="disabled"
+ @update:value="(value: string) => update(index, { title: value })"
+ />
+ <Input
+ :value="item.linkUrl"
+ placeholder="璺宠浆閾炬帴锛堝繀濉級锛屽 https://..."
+ :disabled="disabled"
+ @update:value="(value: string) => update(index, { linkUrl: value })"
+ />
+ <Input
+ :value="item.description"
+ placeholder="鍥炬枃鎽樿锛堥�夊~锛�"
+ :disabled="disabled"
+ @update:value="
+ (value: string) => update(index, { description: value })
+ "
+ />
+ <div>
+ <div class="mb-1 text-xs text-gray-500">灏侀潰鍥撅紙閫夊~锛�</div>
+ <ImageUpload
+ :value="(toCoverValue(item) as unknown as number[])"
+ :max-number="1"
+ :show-description="false"
+ :disabled="disabled"
+ value-key="id"
+ result-field="id"
+ @update:value="
+ (value: number[]) => handleCoverChange(index, value)
+ "
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
\ No newline at end of file
diff --git a/src/views/wxcp/notice/modules/form.vue b/src/views/wxcp/notice/modules/form.vue
index cab9ba2..15b9099 100644
--- a/src/views/wxcp/notice/modules/form.vue
+++ b/src/views/wxcp/notice/modules/form.vue
@@ -9,6 +9,7 @@
import { Button, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
+import { toAccessibleFileUrl } from '#/utils';
import {
createNotice,
getNotice,
@@ -16,6 +17,7 @@
} from '#/api/wxcp/notice';
import { NOTICE_TYPE, useNoticeFormSchema } from '../data';
+import ArticleEditor from './article-editor.vue';
defineOptions({ name: 'WxCpNoticeForm' });
@@ -24,6 +26,14 @@
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(() => {
@@ -31,6 +41,82 @@
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: {
@@ -42,6 +128,9 @@
schema: [],
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
+ handleValuesChange: (values) => {
+ noticeType.value = (values.noticeType as number) ?? NOTICE_TYPE.TEXT;
+ },
});
const [Modal, modalApi] = useVbenModal({
@@ -59,24 +148,18 @@
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);
}
},
@@ -91,30 +174,23 @@
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();
@@ -131,6 +207,13 @@
<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>
diff --git a/src/views/wxcp/notice/modules/push-modal.vue b/src/views/wxcp/notice/modules/push-modal.vue
index 0a82a04..969f41e 100644
--- a/src/views/wxcp/notice/modules/push-modal.vue
+++ b/src/views/wxcp/notice/modules/push-modal.vue
@@ -6,12 +6,13 @@
import { useVbenModal } from '@vben/common-ui';
import { Alert, Button, Input, Select, Textarea, message } from 'ant-design-vue';
-import { pushNotice } from '#/api/wxcp/notice';
-import FileUpload from '#/components/upload/file-upload.vue';
+import { getNotice, pushNotice } from '#/api/wxcp/notice';
+import { FileUpload, ImageUpload } from '#/components/upload';
import PushScopePicker from '../../components/push-scope-picker.vue';
import { PUSH_SCOPE } from '../../data';
import { NOTICE_TYPE, NOTICE_TYPE_OPTIONS } from '../data';
+import ArticleEditor from './article-editor.vue';
defineOptions({ name: 'WxCpNoticePushModal' });
@@ -29,20 +30,87 @@
const linkUrl = ref('');
const linkBtnText = ref('');
const coverUrl = ref('');
-const fileName = ref('');
-const fileList = ref<number[]>([]);
+/**
+ * 涓婁紶缁勪欢鐨勫洖鏄惧��
+ *
+ * 缁勪欢 props 澹版槑鐨勭被鍨嬫槸 string | string[] | number[]锛屼絾寮�浜� valueKey/resultField
+ * 鍚庡疄闄呭洖浼犵殑鏄枃浠跺璞°�傝繖閲岀敤 local 绫诲瀷淇濆瓨瀵硅薄锛堜负浜嗗睍绀烘枃浠跺悕锛夛紝
+ * 鍙湪缁戝畾缁欑粍浠舵椂鍋氫竴娆$被鍨嬫敹绐勶紝閬垮厤杩愯閫昏緫琚� props 绫诲瀷闄愬埗銆�
+ */
+type UploadItem = Record<string, unknown>;
+const imageList = ref<UploadItem[]>([]);
+const fileList = ref<UploadItem[]>([]);
+const videoList = ref<UploadItem[]>([]);
+/** 澶氬浘鏂囨潯鐩� */
+const articles = ref<WxCpNoticeApi.NoticeArticle[]>([]);
+
+/** 缁勪欢鍊肩粺涓�鎴愭枃浠跺璞℃暟缁� */
+function toUploadItems(value: unknown): UploadItem[] {
+ const list = Array.isArray(value)
+ ? value
+ : value === '' || value === null || value === undefined
+ ? []
+ : [value];
+ return list.map((item) =>
+ item && typeof item === 'object'
+ ? (item as UploadItem)
+ : { uid: String(item), id: Number(item), name: `鏂囦欢 ${String(item)}` },
+ );
+}
+
+const imageUploadValue = computed({
+ get: () => imageList.value as unknown as number[],
+ set: (value: number[]) => {
+ imageList.value = toUploadItems(value);
+ },
+});
+const fileUploadValue = computed({
+ get: () => fileList.value as unknown as number[],
+ set: (value: number[]) => {
+ fileList.value = toUploadItems(value);
+ },
+});
+const videoUploadValue = computed({
+ get: () => videoList.value as unknown as number[],
+ set: (value: number[]) => {
+ videoList.value = toUploadItems(value);
+ },
+});
const isCard = computed(() => noticeType.value === NOTICE_TYPE.CARD);
const isNews = computed(() => noticeType.value === NOTICE_TYPE.NEWS);
const isFile = computed(() => noticeType.value === NOTICE_TYPE.FILE);
+const isImage = computed(() => noticeType.value === NOTICE_TYPE.IMAGE);
+const isVideo = computed(() => noticeType.value === NOTICE_TYPE.VIDEO);
+const isMarkdown = computed(() => noticeType.value === NOTICE_TYPE.MARKDOWN);
+const isMultiNews = computed(
+ () => noticeType.value === NOTICE_TYPE.MULTI_NEWS,
+);
const showLink = computed(() => isCard.value || isNews.value);
+
+// 鍚庣鏈厤缃缃戝湴鍧�鍓嶇紑鏃朵笉杩斿洖棰勮閾炬帴锛屾鏃朵笉鎻愮ず銆屽彲鏌ョ湅鍏ㄩ儴銆�
+const previewUrl = computed(() => notice.value.previewUrl || '');
const pushHint = computed(() => {
switch (noticeType.value) {
case NOTICE_TYPE.CARD:
return '鎴愬憳浼氭敹鍒板浘鏂囧崱鐗囷紝鐐瑰嚮鎸夐挳鍙烦杞埌濉姤鎴栬鎯呴〉闈€��';
case NOTICE_TYPE.FILE:
- return '鎴愬憳浼氭敹鍒颁竴鏉¤鏄庢枃鏈拰涓�涓枃浠讹紝鏂囦欢涓婇檺 20MB銆�';
+ return previewUrl.value
+ ? '鎴愬憳浼氶�愪釜鏀跺埌鏂囦欢锛屽彟鏀跺埌涓�寮犮�屾煡鐪嬪叏閮ㄩ檮浠躲�嶅崱鐗囷紝鐐瑰紑鍙瑙堟垨涓嬭浇銆�'
+ : '鎴愬憳浼氶�愪釜鏀跺埌鏂囦欢娑堟伅锛屾枃浠朵笂闄� 20MB銆�';
+ case NOTICE_TYPE.IMAGE:
+ return previewUrl.value
+ ? '鎴愬憳浼氶�愬紶鏀跺埌鍥剧墖锛屽彟鏀跺埌涓�寮犮�屾煡鐪嬪叏閮ㄥ浘鐗囥�嶅崱鐗囷紝鐐瑰紑鍙湪椤甸潰鍐呮斁澶ф煡鐪嬨��'
+ : '鎴愬憳浼氶�愬紶鏀跺埌鍥剧墖娑堟伅锛屽崟寮犱笂闄� 10MB銆�';
+ case NOTICE_TYPE.VIDEO:
+ return previewUrl.value
+ ? '鎴愬憳浼氭敹鍒拌棰戞秷鎭紝鍙︽敹鍒颁竴寮犮�屾煡鐪嬮�氱煡璇︽儏銆嶅崱鐗囷紝鐐瑰紑鍙叏灞忔挱鏀俱��'
+ : '鎴愬憳浼氭敹鍒拌棰戞秷鎭紝瑙嗛涓婇檺 10MB銆�';
+ case NOTICE_TYPE.MARKDOWN:
+ return '鎴愬憳浼氭敹鍒� Markdown 娑堟伅锛屾敮鎸佹爣棰樸�佸姞绮椼�佸垪琛ㄣ�佸紩鐢ㄧ瓑鎺掔増锛屾鏂囦笂闄� 2048 瀛楄妭銆�';
+ case NOTICE_TYPE.MULTI_NEWS:
+ return '鎴愬憳浼氭敹鍒颁竴鏉″鍥炬枃娑堟伅锛屽彲涓�娆″睍绀烘渶澶� 8 绡囧浘鏂囷紝鐐瑰嚮浠绘剰涓�绡囪烦杞搴旈摼鎺ャ��';
case NOTICE_TYPE.NEWS:
return '鎴愬憳浼氭敹鍒板浘鏂囨秷鎭紝灞曠ず鏍囬銆佹憳瑕併�佸皝闈㈠苟鍙偣鍑昏烦杞��';
default:
@@ -51,7 +119,7 @@
});
const [Modal, modalApi] = useVbenModal({
- onOpenChange(isOpen: boolean) {
+ async onOpenChange(isOpen: boolean) {
if (!isOpen) {
notice.value = {};
resultMessage.value = '';
@@ -67,22 +135,74 @@
linkUrl.value = item.linkUrl ?? '';
linkBtnText.value = item.linkBtnText ?? '';
coverUrl.value = item.coverUrl ?? '';
- fileName.value = item.fileName ?? '';
- fileList.value = item.fileBlobId ? [item.fileBlobId] : [];
+ imageList.value = toUploadFiles(item.imageAttachmentList, item.imageBlobIds);
+ fileList.value = toUploadFiles(item.fileAttachmentList, item.fileBlobIds, item.fileBlobId);
+ videoList.value = toUploadFiles(item.videoAttachmentList, item.videoBlobIds);
+ articles.value = (item.articleList ?? []).map((article) => ({ ...article }));
+
+ // 鍒楄〃琛屼笉甯﹂檮浠舵槑缁嗭紝杩涘脊绐楀悗琛ユ媺涓�娆¤鎯咃紝淇濊瘉鍥剧墖/闄勪欢鑳芥纭洖鏄�
+ if (!item.imageAttachmentList?.length && !item.fileAttachmentList?.length && item.id) {
+ try {
+ const detail = await getNotice(item.id);
+ notice.value = { ...item, ...detail };
+ noticeType.value = detail.noticeType ?? noticeType.value;
+ summary.value = detail.summary ?? summary.value;
+ linkUrl.value = detail.linkUrl ?? linkUrl.value;
+ linkBtnText.value = detail.linkBtnText ?? linkBtnText.value;
+ coverUrl.value = detail.coverUrl ?? coverUrl.value;
+ imageList.value = toUploadFiles(detail.imageAttachmentList, detail.imageBlobIds);
+ fileList.value = toUploadFiles(detail.fileAttachmentList, detail.fileBlobIds, detail.fileBlobId);
+ videoList.value = toUploadFiles(detail.videoAttachmentList, detail.videoBlobIds);
+ articles.value = (detail.articleList ?? []).map((article) => ({
+ ...article,
+ }));
+ } catch {
+ // 鎷夎鎯呭け璐ヤ笉闃诲鎺ㄩ�侊紝鎸夊垪琛ㄨ宸叉湁淇℃伅缁х画
+ }
+ }
},
});
-/** 涓婁紶缁勪欢鍙兘杩斿洖鏁扮粍鎴栧璞★紝缁熶竴鍙栧嚭 blobId */
-function resolveFileBlobId(): number | undefined {
- const raw = fileList.value as unknown;
- if (Array.isArray(raw)) {
- const first = raw[0];
- if (typeof first === 'number') {
- return first;
- }
- return (first as { id?: number } | undefined)?.id;
+/** 鎶婇檮浠跺垪琛紙鎴� blobId 鍒楄〃锛夎浆鎴愪笂浼犵粍浠剁殑鍥炴樉瀵硅薄 */
+function toUploadFiles(
+ attachments: WxCpNoticeApi.NoticeAttachment[] | undefined,
+ blobIds?: number[],
+ fallbackBlobId?: number,
+): UploadItem[] {
+ if (attachments?.length) {
+ return attachments.map((item) => ({
+ uid: String(item.id),
+ name: item.name || item.originalFilename || '',
+ url: item.url || item.previewURL || '',
+ status: 'done',
+ id: item.id,
+ response: { id: item.id, name: item.name || item.originalFilename || '' },
+ }));
}
- return typeof raw === 'number' ? raw : undefined;
+ const ids = blobIds?.length ? blobIds : fallbackBlobId ? [fallbackBlobId] : [];
+ return ids.map((id) => ({
+ uid: String(id),
+ name: `鏂囦欢 ${id}`,
+ status: 'done',
+ id,
+ response: { id },
+ }));
+}
+
+/** 涓婁紶缁勪欢鍙兘杩斿洖鏁扮粍鎴栧璞★紝缁熶竴鍙栧嚭 blobId 鏁扮粍 */
+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));
}
async function handlePush() {
@@ -101,9 +221,41 @@
message.warning('鍥炬枃鍗$墖/鍥炬枃娑堟伅鐨勮烦杞摼鎺ヤ笉鑳戒负绌�');
return;
}
- const fileBlobId = resolveFileBlobId();
- if (isFile.value && !fileBlobId) {
- message.warning('鏂囦欢閫氱煡蹇呴』涓婁紶鏂囦欢');
+ const imageBlobIds = normalizeBlobIds(imageList.value);
+ if (isImage.value && !imageBlobIds.length) {
+ message.warning('鍥剧墖閫氱煡蹇呴』鑷冲皯涓婁紶 1 寮犲浘鐗�');
+ return;
+ }
+ const fileBlobIds = normalizeBlobIds(fileList.value);
+ if (isFile.value && !fileBlobIds.length) {
+ message.warning('鏂囦欢閫氱煡蹇呴』鑷冲皯涓婁紶 1 涓檮浠�');
+ return;
+ }
+ const videoBlobIds = normalizeBlobIds(videoList.value);
+ if (isVideo.value && !videoBlobIds.length) {
+ message.warning('瑙嗛閫氱煡蹇呴』涓婁紶 1 涓棰�');
+ return;
+ }
+ if (isMarkdown.value && !notice.value.content?.trim()) {
+ message.warning('Markdown 閫氱煡鐨勬鏂囦笉鑳戒负绌�');
+ return;
+ }
+ if (isMultiNews.value && !articles.value.length) {
+ message.warning('澶氬浘鏂囨秷鎭嚦灏戦渶瑕� 1 绡囧浘鏂�');
+ return;
+ }
+ if (
+ isMultiNews.value &&
+ articles.value.some((article) => !article.title?.trim())
+ ) {
+ message.warning('璇峰~鍐欐瘡绡囧浘鏂囩殑鏍囬');
+ return;
+ }
+ if (
+ isMultiNews.value &&
+ articles.value.some((article) => !article.linkUrl?.trim())
+ ) {
+ message.warning('璇峰~鍐欐瘡绡囧浘鏂囩殑璺宠浆閾炬帴');
return;
}
@@ -119,8 +271,10 @@
linkUrl: linkUrl.value,
linkBtnText: linkBtnText.value,
coverUrl: coverUrl.value,
- fileBlobId,
- fileName: fileName.value || undefined,
+ imageBlobIds,
+ fileBlobIds,
+ videoBlobIds,
+ articleList: isMultiNews.value ? articles.value : undefined,
});
if (res?.success) {
message.success('鎺ㄩ�佹垚鍔�');
@@ -187,18 +341,76 @@
/>
</template>
+ <template v-if="isImage">
+ <div class="mb-2 text-sm font-medium">鎺ㄩ�佸浘鐗�</div>
+ <ImageUpload
+ v-model:value="imageUploadValue"
+ class="mb-1"
+ :max-number="9"
+ :multiple="true"
+ value-key="id"
+ result-field="id"
+ />
+ <div class="mb-3 text-xs text-gray-500">
+ 鍗曞紶涓嶈秴杩� 10MB锛屾渶澶� 9 寮狅紱鎺ㄩ�佸悗鎴愬憳鍙湪棰勮椤靛唴鏀惧ぇ鏌ョ湅鍏ㄩ儴鍥剧墖
+ </div>
+ </template>
+
<template v-if="isFile">
- <div class="mb-2 text-sm font-medium">閫氱煡鏂囦欢</div>
+ <div class="mb-2 text-sm font-medium">鎺ㄩ�侀檮浠�</div>
<FileUpload
- v-model:value="fileList"
- class="mb-3"
- :max-number="1"
- :multiple="false"
+ v-model:value="fileUploadValue"
+ class="mb-1"
+ :max-number="5"
+ :multiple="true"
value-key="id"
:show-description="true"
/>
+ <div class="mb-3 text-xs text-gray-500">
+ 鍗曚釜涓嶈秴杩� 20MB锛屾渶澶� 5 涓紱鎺ㄩ�佸悗鎴愬憳鍙湪棰勮椤靛唴鏌ョ湅鍏ㄩ儴闄勪欢
+ </div>
</template>
+ <template v-if="isVideo">
+ <div class="mb-2 text-sm font-medium">鎺ㄩ�佽棰�</div>
+ <FileUpload
+ v-model:value="videoUploadValue"
+ class="mb-1"
+ :max-number="1"
+ :multiple="false"
+ :accept="['mp4', 'mov', 'm4v']"
+ value-key="id"
+ :show-description="true"
+ />
+ <div class="mb-3 text-xs text-gray-500">
+ 寤鸿浣跨敤 mp4锛屽崟涓笉瓒呰繃 10MB锛涙帹閫佸悗鎴愬憳鍙湪棰勮椤靛叏灞忔挱鏀�
+ </div>
+ </template>
+
+ <template v-if="isMarkdown">
+ <div class="mb-2 text-sm font-medium">Markdown 姝f枃</div>
+ <div
+ class="mb-3 max-h-40 overflow-auto whitespace-pre-wrap rounded bg-gray-50 p-2 text-xs text-gray-600 dark:bg-gray-800 dark:text-gray-300"
+ >
+ {{ notice.content || '锛堟鏂囦负绌猴紝璇峰厛鍦ㄧ紪杈戦�氱煡涓~鍐� Markdown 鍐呭锛�' }}
+ </div>
+ <div class="mb-3 text-xs text-gray-500">
+ 鏀寔 # 鏍囬銆�**鍔犵矖**銆�- 鍒楄〃銆�> 寮曠敤锛涙鏂囦笂闄� 2048 瀛楄妭
+ </div>
+ </template>
+
+ <template v-if="isMultiNews">
+ <ArticleEditor v-model="articles" />
+ </template>
+
+ <div v-if="previewUrl && (isImage || isFile || isVideo)" class="mb-3">
+ <div class="mb-1 text-sm font-medium">鎴愬憳棰勮椤�</div>
+ <Input :value="previewUrl" readonly />
+ <div class="mt-1 text-xs text-gray-500">
+ 鎺ㄩ�佹椂浼氳嚜鍔ㄩ檮甯︺�屾煡鐪嬪叏閮ㄥ浘鐗�/闄勪欢銆嶅崱鐗囷紝涔熷彲鐩存帴澶嶅埗姝ら摼鎺ュ彂缁欐垚鍛�
+ </div>
+ </div>
+
<div class="mb-2 mt-4 text-sm font-medium">鎺ㄩ�佽寖鍥�</div>
<PushScopePicker v-model:scope="scope" v-model:target="target" />
--
Gitblit v1.9.3