From 71beff1699701e18c2503791c7ba1437c89966aa Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期三, 30 九月 2026 15:26:43 +0800
Subject: [PATCH] feat(wxcp): 活动封面改用附件中间表,并修正附件地址同源

---
 src/views/wxcp/activity/modules/form.vue        |   54 ++++++++++++++++-
 src/views/wxcp/activity/modules/signup-list.vue |   17 ++++-
 src/utils/index.ts                              |    1 
 src/views/wxcp/activity/index.vue               |   23 ++++++-
 src/api/wxcp/activity.ts                        |   10 +++
 src/views/wxcp/activity/data.ts                 |   13 +++-
 src/utils/fileUrl.ts                            |   42 ++++++++++++++
 7 files changed, 146 insertions(+), 14 deletions(-)

diff --git a/src/api/wxcp/activity.ts b/src/api/wxcp/activity.ts
index 5d5b1c8..ed27ef6 100644
--- a/src/api/wxcp/activity.ts
+++ b/src/api/wxcp/activity.ts
@@ -23,7 +23,15 @@
     id?: number;
     title?: string;
     content?: string;
-    coverUrl?: string;
+    /**
+     * 灏侀潰鍥炬枃浠剁紪鍙凤紙鏈�澶� 1 寮狅級
+     *
+     * 灏侀潰涓嶈惤涓氬姟琛ㄥ瓧娈碉紝缁熶竴璧� system_storage_attachment 涓棿琛紝
+     * 淇濆瓨鏃舵彁浜� blobIds锛屽洖鏄炬椂璇� attachmentList銆�
+     */
+    blobIds?: number[];
+    /** 灏侀潰鍥鹃檮浠跺垪琛紙鍚庣杩斿洖锛屾渶澶� 1 寮狅級 */
+    attachmentList?: SignupAttachment[];
     location?: string;
     startTime?: string;
     endTime?: string;
diff --git a/src/utils/fileUrl.ts b/src/utils/fileUrl.ts
new file mode 100644
index 0000000..6590ae3
--- /dev/null
+++ b/src/utils/fileUrl.ts
@@ -0,0 +1,42 @@
+/**
+ * 淇鍚庣杩斿洖鐨勯檮浠惰闂湴鍧�
+ *
+ * 鍚庣鐢熸垚 url / previewURL / downloadURL 鏃朵細甯︿笂瀹冭嚜宸遍厤缃殑璁块棶鍩熷悕
+ * 锛堝綋鍓嶅悗绔厤鐨勬槸 192.168.1.6:48080锛夈�傚彧瑕佽鍩熷悕涓庡墠绔笉鍚屾簮銆佹垨鑰呰涓绘満
+ * 鍦ㄥ綋鍓嶇綉缁滈噷涓嶅彲杈撅紝`<img src>` 灏变細鐩存帴鍔犺浇澶辫触锛岄〉闈笂琛ㄧ幇涓恒�屽浘鐗囦笉鏄剧ず銆嶃��
+ *
+ * 杩欓噷鎶婅繖绫荤粷瀵瑰湴鍧�鏀瑰啓鎴愮浉瀵� API 鍓嶇紑鐨勮矾寰勶紝璁╁畠璧板綋鍓嶇珯鐐圭殑鍚屾簮璇锋眰锛�
+ * 寮�鍙戠幆澧冪粡 vite 鐨� /admin-api 浠g悊杞彂鍒板悗绔紝鐢熶骇鐜鍓嶅悗绔悓婧愭椂鍚屾牱鎴愮珛銆�
+ *
+ * 宸茬粡鏄浉瀵硅矾寰勩�佸悓婧愬湴鍧�锛屼互鍙� data:/blob: 涓�寰嬪師鏍疯繑鍥炪��
+ */
+export function toAccessibleFileUrl(raw?: string): string {
+  if (!raw) {
+    return '';
+  }
+  // 鐩稿鍦板潃銆乨ata:銆乥lob: 鐩存帴鏀捐
+  if (!/^https?:\/\//i.test(raw)) {
+    return raw;
+  }
+  try {
+    const url = new URL(raw);
+    // 鏈潵灏卞悓婧愶紝鏃犻渶澶勭悊
+    if (url.origin === window.location.origin) {
+      return raw;
+    }
+    const apiPrefix = (import.meta.env.VITE_GLOB_API_URL || '/admin-api').replace(
+      /\/+$/,
+      '',
+    );
+    // 鍛戒腑 API 鍓嶇紑鏃讹紝鎴彇鍓嶇紑鍙婂叾涔嬪悗鐨勮矾寰勪笌鏌ヨ涓�
+    const at = url.pathname.indexOf(`${apiPrefix}/`);
+    if (at >= 0) {
+      return `${url.pathname.slice(at)}${url.search}`;
+    }
+    // 涓嶆槸鏂囦欢鎺ュ彛鐨勫湴鍧�锛屼笉鍋氬鐞�
+    return raw;
+  } catch {
+    // URL 瑙f瀽澶辫触鏃朵繚鎸佸師鍊硷紝閬垮厤鎶婂彲鐢ㄥ湴鍧�鏀瑰潖
+    return raw;
+  }
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index cdedaf7..490f71a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,5 +1,6 @@
 import type { Recordable } from '@vben/types';
 
+export * from './fileUrl';
 export * from './rangePickerProps';
 export * from './routerHelper';
 
diff --git a/src/views/wxcp/activity/data.ts b/src/views/wxcp/activity/data.ts
index da4118e..4e883d8 100644
--- a/src/views/wxcp/activity/data.ts
+++ b/src/views/wxcp/activity/data.ts
@@ -85,10 +85,17 @@
       help: '濉� 0 琛ㄧず涓嶉檺鍚嶉',
     },
     {
-      fieldName: 'coverUrl',
+      // 灏侀潰璧伴檮浠朵腑闂磋〃锛氳繖閲屾彁浜� blobIds锛屽洖鏄捐 attachmentList锛堣 form.vue 鐨勬槧灏勫嚱鏁帮級
+      fieldName: 'blobIds',
       label: '灏侀潰鍥�',
-      component: 'Input',
-      componentProps: { placeholder: '灏侀潰鍥惧湴鍧�锛堥�夊~锛�', disabled: readonly },
+      component: 'ImageUpload',
+      componentProps: {
+        valueKey: 'id',
+        maxNumber: 1,
+        multiple: false,
+        disabled: readonly,
+      },
+      help: '閫夊~锛屽缓璁� 16:9锛涗繚瀛樺悗灞曠ず鍦ㄦ椿鍔ㄦ姤鍚嶉〉椤堕儴',
       formItemClass: 'col-span-2',
     },
     {
diff --git a/src/views/wxcp/activity/index.vue b/src/views/wxcp/activity/index.vue
index 9370cbb..1530c6e 100644
--- a/src/views/wxcp/activity/index.vue
+++ b/src/views/wxcp/activity/index.vue
@@ -14,6 +14,7 @@
   getActivityPage,
   publishActivity,
 } from '#/api/wxcp/activity';
+import { toAccessibleFileUrl } from '#/utils';
 
 import {
   ACTIVITY_STATUS,
@@ -61,6 +62,13 @@
 
 function handleCreate() {
   formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鍒楄〃缂╃暐鍥惧彇灏侀潰闄勪欢鐨勭涓�寮狅紝娌′紶灏侀潰杩斿洖绌轰覆 */
+function coverUrlOf(row: WxCpActivityApi.Activity): string {
+  const cover = row.attachmentList?.[0];
+  // 鍚庣杩斿洖鐨勬槸瀹冭嚜宸遍厤缃煙鍚嶇殑缁濆鍦板潃锛岄渶鏀瑰啓鎴愬悓婧愯矾寰勬墠鑳借 <img> 鍔犺浇
+  return toAccessibleFileUrl(cover?.url || cover?.previewURL);
 }
 
 function handleDetail(row: WxCpActivityApi.Activity) {
@@ -144,9 +152,18 @@
       </template>
 
       <template #title="{ row }">
-        <Button type="link" class="p-0" @click="handleDetail(row)">
-          {{ row.title }}
-        </Button>
+        <div class="flex items-center gap-2">
+          <!-- 灏侀潰鏉ヨ嚜闄勪欢涓棿琛紝鏈笂浼犳椂涓嶅崰浣� -->
+          <img
+            v-if="coverUrlOf(row)"
+            :src="coverUrlOf(row)"
+            alt="灏侀潰"
+            class="h-9 w-14 shrink-0 rounded object-cover"
+          />
+          <Button type="link" class="p-0" @click="handleDetail(row)">
+            {{ row.title }}
+          </Button>
+        </div>
       </template>
 
       <template #signupCount="{ row }">
diff --git a/src/views/wxcp/activity/modules/form.vue b/src/views/wxcp/activity/modules/form.vue
index 05d379b..29b38d3 100644
--- a/src/views/wxcp/activity/modules/form.vue
+++ b/src/views/wxcp/activity/modules/form.vue
@@ -9,6 +9,7 @@
 import { Button, message } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter/form';
+import { toAccessibleFileUrl } from '#/utils';
 import {
   createActivity,
   getActivity,
@@ -44,6 +45,47 @@
   wrapperClass: 'grid-cols-2',
 });
 
+/**
+ * 鎶婃椿鍔ㄨ鎯呮槧灏勬垚琛ㄥ崟鍊�
+ *
+ * 灏侀潰鍦ㄥ悗绔笉钀戒笟鍔¤〃瀛楁锛岃�屾槸瀛樺湪闄勪欢涓棿琛ㄩ噷锛�
+ * 鍥炴樉鏃惰鎶� attachmentList 杞垚涓婁紶缁勪欢鑳借瘑鍒殑鏂囦欢瀵硅薄銆�
+ */
+function activityToForm(
+  activity: WxCpActivityApi.Activity,
+): Record<string, unknown> {
+  const form: Record<string, unknown> = { ...activity };
+  form.blobIds = (activity.attachmentList ?? []).map((item) => ({
+    uid: String(item.id),
+    name: item.name || item.originalFilename || '',
+    url: toAccessibleFileUrl(item.url || item.previewURL),
+    status: 'done',
+    id: item.id,
+  }));
+  return form;
+}
+
+/**
+ * 鎶婁笂浼犵粍浠剁殑鍊艰鏁存垚 blobId 鏁扮粍
+ *
+ * ImageUpload 鍦� maxNumber=1 鏃惰繑鍥炵殑鏄崟涓�硷紙鑰屼笉鏄暟缁勶級锛�
+ * 鍚庣绛惧悕鏄� List<Long>锛岃繖閲岀粺涓�鍖呮垚鏁扮粍鍐嶆彁浜ゃ��
+ */
+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));
+}
+
 const [Modal, modalApi] = useVbenModal({
   async onOpenChange(isOpen: boolean) {
     if (!isOpen) {
@@ -59,7 +101,7 @@
       modalApi.lock();
       try {
         const detail = await getActivity(data.id);
-        await formApi.setValues(detail as unknown as Record<string, unknown>);
+        await formApi.setValues(activityToForm(detail));
       } finally {
         modalApi.unlock();
       }
@@ -76,12 +118,16 @@
   }
   submitting.value = true;
   try {
-    const data = (await formApi.getValues()) as WxCpActivityApi.Activity;
+    const data = (await formApi.getValues()) as Record<string, unknown>;
+    const payload = {
+      ...data,
+      blobIds: normalizeBlobIds(data.blobIds),
+    } as WxCpActivityApi.Activity;
     if (id.value) {
-      await updateActivity({ ...data, id: id.value });
+      await updateActivity({ ...payload, id: id.value });
       message.success('淇敼鎴愬姛');
     } else {
-      await createActivity(data);
+      await createActivity(payload);
       message.success('鍒涘缓鎴愬姛锛岃鍦ㄥ垪琛ㄤ腑鍙戝竷鍚庡啀鎺ㄩ��');
     }
     await modalApi.close();
diff --git a/src/views/wxcp/activity/modules/signup-list.vue b/src/views/wxcp/activity/modules/signup-list.vue
index 2c1b953..fabaec0 100644
--- a/src/views/wxcp/activity/modules/signup-list.vue
+++ b/src/views/wxcp/activity/modules/signup-list.vue
@@ -16,6 +16,7 @@
 } from 'ant-design-vue';
 
 import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { toAccessibleFileUrl } from '#/utils';
 import {
   cancelSignup,
   checkInSignup,
@@ -59,6 +60,16 @@
 
 function isImage(item: WxCpActivityApi.SignupAttachment) {
   return (item.contentType || '').startsWith('image/');
+}
+
+/** 鍚庣闄勪欢鍦板潃甯︾殑鏄悗绔嚜宸遍厤缃殑鍩熷悕锛屾敼鍐欐垚鍚屾簮璺緞鍚庢墠鑳借 <img> 鍔犺浇 */
+function fileUrl(item: WxCpActivityApi.SignupAttachment) {
+  return toAccessibleFileUrl(item.previewURL || item.url);
+}
+
+/** 涓嬭浇閾炬帴鍚岀悊锛屼紭鍏堢敤 downloadURL */
+function downloadUrl(item: WxCpActivityApi.SignupAttachment) {
+  return toAccessibleFileUrl(item.downloadURL || item.url);
 }
 
 function formatSize(bytes?: number) {
@@ -215,18 +226,18 @@
             </div>
             <a
               v-if="isImage(item)"
-              :href="item.previewURL || item.url"
+              :href="fileUrl(item)"
               target="_blank"
               rel="noopener noreferrer"
             >
               <img
-                :src="item.previewURL || item.url"
+                :src="fileUrl(item)"
                 alt="闄勪欢棰勮"
                 class="max-h-40 rounded object-contain"
               />
             </a>
             <a
-              :href="item.downloadURL || item.url"
+              :href="downloadUrl(item)"
               target="_blank"
               rel="noopener noreferrer"
               class="text-xs text-blue-500 hover:underline"

--
Gitblit v1.9.3