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/push-modal.vue |  268 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 240 insertions(+), 28 deletions(-)

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