From 4a71c6beb268ba17ca472f45401c8ee97846e97e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 26 六月 2026 13:14:57 +0800
Subject: [PATCH] 项目多余功能删除
---
src/views/infra/apiErrorLog/index.vue | 9
src/views/mes/wm/productissue/index.vue | 12
src/views/mes/wm/warehouse/index.vue | 12
src/views/ai/mindmap/manager/index.vue | 9
src/views/mes/wm/itemreceipt/index.vue | 12
src/views/wms/inventory/history/index.vue | 12
src/views/erp/purchase/return/index.vue | 11
src/views/infra/job/logger/index.vue | 11
src/views/mes/cal/holiday/index.vue | 11
src/views/system/tenant/index.vue | 9
src/views/system/operatelog/index.vue | 9
src/views/mes/wm/miscissue/index.vue | 12
src/views/erp/stock/out/index.vue | 12
src/views/mes/dv/machinery/type/index.vue | 11
src/views/mes/dv/subject/index.vue | 11
src/views/system/tenantPackage/index.vue | 9
src/views/crm/contract/index.vue | 16
src/views/crm/customer/limitConfig/index.vue | 16
src/views/crm/receivable/index.vue | 16
src/views/erp/purchase/order/index.vue | 12
src/views/mes/wm/returnissue/index.vue | 12
src/views/infra/swagger/index.vue | 9
src/views/bpm/processInstance/index.vue | 12
src/views/infra/druid/index.vue | 13
src/views/mes/home/index.vue | 12
src/views/system/notify/message/index.vue | 9
src/views/mes/dv/checkplan/index.vue | 11
src/views/wms/md/warehouse/index.vue | 12
src/views/ai/model/model/index.vue | 8
src/views/erp/stock/record/index.vue | 12
src/views/mes/cal/team/index.vue | 11
src/views/crm/product/category/index.vue | 12
src/views/infra/server/index.vue | 9
src/views/_core/authentication/login.vue | 97 -
src/views/ai/chat/manager/index.vue | 9
src/views/erp/purchase/supplier/index.vue | 12
src/views/erp/finance/payment/index.vue | 12
src/views/erp/stock/move/index.vue | 12
src/views/system/area/index.vue | 9
src/views/mes/pro/workorder/index.vue | 12
src/views/mes/md/vendor/index.vue | 12
src/views/bpm/oa/leave/index.vue | 12
src/views/report/jmreport/bi.vue | 9
src/views/system/role/index.vue | 13
src/views/mes/pro/card/index.vue | 12
src/views/mes/qc/iqc/index.vue | 12
src/views/bpm/form/index.vue | 12
src/views/mes/md/client/index.vue | 12
src/views/ai/model/tool/index.vue | 11
src/views/mes/qc/pendinginspect/index.vue | 12
src/views/mes/cal/plan/index.vue | 11
src/views/mes/wm/barcode/index.vue | 12
src/views/infra/apiAccessLog/index.vue | 9
src/views/system/menu/index.vue | 13
src/views/mes/pro/feedback/index.vue | 11
src/views/report/jmreport/index.vue | 9
src/views/mes/qc/rqc/index.vue | 12
src/views/erp/finance/account/index.vue | 12
src/views/bpm/processExpression/index.vue | 12
src/views/wms/order/shipment/index.vue | 11
src/views/system/sms/template/index.vue | 9
src/views/ai/model/apiKey/index.vue | 8
src/views/mes/wm/arrivalnotice/index.vue | 12
src/views/erp/home/index.vue | 12
src/views/bpm/processListener/index.vue | 12
src/views/mes/wm/barcode/config/index.vue | 12
src/views/system/mail/template/index.vue | 9
src/views/system/loginlog/index.vue | 9
src/views/report/goview/index.vue | 12
src/views/wms/order/movement/index.vue | 11
src/views/mes/md/unitmeasure/index.vue | 12
src/views/infra/redis/index.vue | 10
src/views/mes/pro/process/index.vue | 12
src/views/mes/qc/ipqc/index.vue | 12
src/views/mes/qc/oqc/index.vue | 12
src/views/bpm/category/index.vue | 9
src/views/mes/wm/outsourcereceipt/index.vue | 12
src/views/bpm/task/manager/index.vue | 9
src/views/infra/job/index.vue | 11
src/views/mes/cal/calendar/index.vue | 11
src/views/system/sms/channel/index.vue | 9
src/views/bpm/task/done/index.vue | 18
src/views/erp/product/category/index.vue | 12
src/views/crm/receivable/plan/index.vue | 16
src/views/mes/wm/packages/index.vue | 12
src/views/mes/dv/machinery/index.vue | 11
src/views/mes/wm/returnsales/index.vue | 12
src/views/system/notify/template/index.vue | 9
src/views/erp/stock/in/index.vue | 12
src/views/crm/business/index.vue | 16
src/views/erp/stock/warehouse/index.vue | 12
src/views/mes/wm/warehouse/area/index.vue | 12
src/packages/effects/common-ui/src/ui/authentication/types.ts | 5
src/views/infra/skywalking/index.vue | 9
src/views/mes/wm/warehouse/location/index.vue | 12
src/views/mes/qc/batchtrace/index.vue | 12
src/views/bpm/processInstance/manager/index.vue | 9
src/views/mes/md/workstation/index.vue | 12
src/views/system/notify/my/index.vue | 9
src/views/ai/music/manager/index.vue | 8
src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js | 2
src/views/mes/pro/task/edit/index.vue | 12
src/views/mes/dv/repair/index.vue | 11
src/views/erp/sale/out/index.vue | 12
src/views/system/oauth2/token/index.vue | 12
src/views/wms/order/check/index.vue | 11
src/views/bpm/model/definition/index.vue | 8
src/views/mes/tm/tool/type/index.vue | 12
src/views/erp/finance/receipt/index.vue | 12
src/views/mes/qc/indicator/index.vue | 12
src/views/mes/wm/materialstock/index.vue | 12
src/views/system/mail/log/index.vue | 9
src/views/wms/order/receipt/index.vue | 9
src/views/mes/pro/route/index.vue | 12
src/views/erp/stock/check/index.vue | 12
src/views/mes/qc/template/index.vue | 12
src/views/erp/sale/order/index.vue | 12
src/views/mes/pro/task/index.vue | 12
src/views/mes/dv/checkrecord/index.vue | 11
src/views/system/mail/account/index.vue | 9
src/views/mes/wm/productreceipt/index.vue | 12
src/views/mes/wm/productsales/index.vue | 12
src/views/system/dict/index.vue | 9
src/views/mes/md/workstation/workshop/index.vue | 12
src/views/mes/wm/sn/index.vue | 12
pnpm-workspace.yaml | 2
src/views/infra/codegen/index.vue | 21
src/views/erp/sale/customer/index.vue | 11
src/views/crm/customer/pool/index.vue | 16
src/views/bpm/group/index.vue | 9
src/views/mes/pro/andon/record/index.vue | 11
src/views/mes/wm/transfer/index.vue | 12
src/views/ai/image/manager/index.vue | 8
src/views/system/oauth2/client/index.vue | 12
src/views/mes/wm/outsourceissue/index.vue | 12
src/views/mes/wm/returnvendor/index.vue | 12
src/views/erp/product/unit/index.vue | 11
src/views/mes/tm/tool/index.vue | 12
src/layouts/basic.vue | 4
src/views/infra/webSocket/index.vue | 12
src/views/crm/customer/index.vue | 16
src/views/system/social/client/index.vue | 9
src/views/system/social/user/index.vue | 9
src/views/mes/wm/miscreceipt/index.vue | 12
src/views/mes/wm/salesnotice/index.vue | 12
src/views/ai/knowledge/knowledge/index.vue | 8
src/views/mes/md/autocode/index.vue | 12
src/views/bpm/task/todo/index.vue | 18
src/views/crm/contact/index.vue | 16
src/views/mes/wm/stocktaking/task/index.vue | 12
src/views/mes/dv/maintenrecord/index.vue | 11
src/views/crm/business/status/index.vue | 16
src/views/bpm/task/copy/index.vue | 12
src/views/mes/qc/defect/index.vue | 12
src/views/ai/model/chatRole/index.vue | 8
src/views/system/sms/log/index.vue | 9
src/views/wms/home/index.vue | 11
/dev/null | 101 --
src/views/system/user/index.vue | 14
src/views/wms/inventory/index/index.vue | 12
src/views/crm/clue/index.vue | 16
src/views/wms/md/item/index.vue | 12
src/views/wms/md/merchant/index.vue | 12
src/views/mes/md/item/index.vue | 12
src/views/mes/md/item/type/index.vue | 11
src/views/ai/write/manager/index.vue | 8
package.json | 2
src/packages/effects/common-ui/src/ui/authentication/login.vue | 3
src/views/erp/product/product/index.vue | 12
src/views/mes/pro/workrecord/index.vue | 12
src/views/mes/wm/stocktaking/plan/index.vue | 12
src/views/erp/stock/stock/index.vue | 12
src/views/erp/purchase/in/index.vue | 12
src/views/erp/sale/return/index.vue | 12
174 files changed, 369 insertions(+), 1,754 deletions(-)
diff --git a/package.json b/package.json
index 497f9c3..58da272 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,6 @@
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"ant-design-vue": "catalog:",
- "benz-amr-recorder": "catalog:",
"bpmn-js": "catalog:",
"bpmn-js-properties-panel": "catalog:",
"bpmn-js-token-simulation": "catalog:",
@@ -58,7 +57,6 @@
"diagram-js": "catalog:",
"fast-xml-parser": "catalog:",
"highlight.js": "catalog:",
- "livekit-client": "catalog:",
"pinia": "catalog:",
"steady-xml": "catalog:",
"tinymce": "catalog:",
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index b04e7dc..f01f48f 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -89,7 +89,6 @@
archiver: ^7.0.1
axios: ^1.17.0
axios-mock-adapter: ^2.1.0
- benz-amr-recorder: ^1.1.5
bpmn-js: ^18.16.1
bpmn-js-properties-panel: 5.54.0
bpmn-js-token-simulation: ^0.39.3
@@ -134,7 +133,6 @@
jsbarcode: ^3.12.3
jsencrypt: ^3.5.4
json-bigint: ^1.0.0
- livekit-client: ^2.18.9
lodash.clonedeep: ^4.5.0
markdown-it: ^14.2.0
markmap-common: ^0.18.9
diff --git a/src/api/im/channel/material/index.ts b/src/api/im/channel/material/index.ts
deleted file mode 100644
index 9f0338f..0000000
--- a/src/api/im/channel/material/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImChannelMaterialApi {
- /** 鐢ㄦ埛绔兘鐪嬪埌鐨勯閬撶礌鏉愯鎯� */
- export interface Material {
- id: number;
- channelId: number;
- type: number;
- title: string;
- coverUrl?: string;
- summary?: string;
- content?: string;
- url?: string;
- }
-}
-
-
-/** 鑾峰彇棰戦亾绱犳潗璇︽儏锛涚敤浜庡鎴风鐐瑰嚮鍥炬枃鍗$墖娓叉煋璇︽儏椤� */
-export function getChannelMaterial(id: number) {
- return requestClient.get<ImChannelMaterialApi.Material>(
- '/im/channel/material/get',
- { params: { id } },
- );
-}
diff --git a/src/api/im/conversation/read/index.ts b/src/api/im/conversation/read/index.ts
deleted file mode 100644
index cc2d3a8..0000000
--- a/src/api/im/conversation/read/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImConversationReadApi {
- /** IM 浼氳瘽璇讳綅缃� Response VO */
- export interface ConversationReadRespVO {
- id: number; // 璇讳綅缃紪鍙凤紙澧為噺鎷夊彇娓告爣鐢級
- conversationType: number; // 浼氳瘽绫诲瀷锛屽弬瑙� ImConversationType
- targetId: number; // 浼氳瘽鐩爣缂栧彿
- messageId: number; // 鏈�澶у凡璇绘秷鎭紪鍙�
- updateTime?: number; // 鏈�杩戞洿鏂版椂闂达紙姣鏃堕棿鎴筹紝澧為噺鎷夊彇娓告爣鐢級
- }
-}
-
-
-/** 澧為噺鎷夊彇褰撳墠鐢ㄦ埛鐨勪細璇濊浣嶇疆锛堥噸杩� / 绂荤嚎琛ュ伩锛� */
-export function pullMyConversationReadList(params: {
- lastId?: number;
- lastUpdateTime?: number;
- limit: number;
-}) {
- return requestClient.get<ImConversationReadApi.ConversationReadRespVO[]>(
- '/im/conversation-read/pull',
- { params },
- );
-}
diff --git a/src/api/im/face/pack/index.ts b/src/api/im/face/pack/index.ts
deleted file mode 100644
index 58ad781..0000000
--- a/src/api/im/face/pack/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImFacePackApi {
- /** 鐢ㄦ埛绔〃鎯呭寘椤癸紙绮剧畝鐗堬級 */
- export interface FacePackUserItem {
- id: number;
- url: string;
- name?: string;
- width: number;
- height: number;
- }
-
- /** 鐢ㄦ埛绔〃鎯呭寘 + 宓屽 items */
- export interface FacePackUser {
- id: number;
- name: string;
- icon?: string;
- items: FacePackUserItem[];
- }
-}
-
-
-/** 鎷夊彇鎵�鏈夊惎鐢ㄧ殑绯荤粺琛ㄦ儏鍖咃紙鍚〃鎯呭垪琛級 */
-export function getFacePackList() {
- return requestClient.get<ImFacePackApi.FacePackUser[]>('/im/face-pack/list');
-}
diff --git a/src/api/im/face/userItem/index.ts b/src/api/im/face/userItem/index.ts
deleted file mode 100644
index 07bcf1c..0000000
--- a/src/api/im/face/userItem/index.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImFaceUserItemApi {
- /** 涓汉琛ㄦ儏 */
- export interface FaceUserItem {
- id: number;
- url: string;
- name?: string;
- width: number;
- height: number;
- }
-
- /** 娣诲姞涓汉琛ㄦ儏璇锋眰 */
- export interface FaceUserItemSaveReqVO {
- url: string;
- name?: string;
- width: number;
- height: number;
- }
-}
-
-
-/** 鑾峰彇鎴戠殑涓汉琛ㄦ儏鍒楄〃 */
-export function getFaceUserItemList() {
- return requestClient.get<ImFaceUserItemApi.FaceUserItem[]>('/im/face-user-item/list');
-}
-
-/** 娣诲姞涓汉琛ㄦ儏 */
-export function createFaceUserItem(data: ImFaceUserItemApi.FaceUserItemSaveReqVO) {
- return requestClient.post<number>('/im/face-user-item/create', data);
-}
-
-/** 鍒犻櫎涓汉琛ㄦ儏 */
-export function deleteFaceUserItem(id: number) {
- return requestClient.delete<boolean>('/im/face-user-item/delete', {
- params: { id },
- });
-}
diff --git a/src/api/im/friend/index.ts b/src/api/im/friend/index.ts
deleted file mode 100644
index 4819158..0000000
--- a/src/api/im/friend/index.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImFriendApi {
- /** IM 濂藉弸 Response VO */
- export interface FriendRespVO {
- id: number; // 鍏崇郴璁板綍缂栧彿
- friendUserId: number; // 濂藉弸鐨勭敤鎴风紪鍙�
- silent?: boolean; // 鏄惁鍏嶆墦鎵�
- displayName?: string; // 濂藉弸灞曠ず澶囨敞锛堜粎鑷繁鍙锛�
- displayNamePinyin?: string; // 澶囨敞鐨勬嫾闊筹紙灏忓啓鏃犵┖鏍硷紝鍓嶇鎸夐瀛楁瘝鍒嗘《 / 鎷奸煶鎼滅储锛�
- addSource?: number; // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum
- pinned?: boolean; // 鏄惁缃《鑱旂郴浜�
- blocked?: boolean; // 鏄惁鎷夐粦
- status?: number; // 濂藉弸鐘舵�侊紙0=姝e父锛�1=宸插垹闄わ級
- addTime?: string; // 娣诲姞濂藉弸鏃堕棿
- deleteTime?: string; // 鍒犻櫎濂藉弸鏃堕棿
- updateTime?: number; // 鏈�杩戞洿鏂版椂闂达紙姣鏃堕棿鎴筹紝澧為噺鎷夊彇娓告爣鐢級
- nickname?: string; // 濂藉弸鏄电О
- nicknamePinyin?: string; // 鏄电О鐨勬嫾闊筹紙灏忓啓鏃犵┖鏍硷紝鍓嶇鎸夐瀛楁瘝鍒嗘《 / 鎷奸煶鎼滅储锛�
- avatar?: string; // 濂藉弸澶村儚
- }
-
- /** IM 濂藉弸鏇存柊 Request VO */
- export interface FriendUpdateReqVO {
- friendUserId: number; // 濂藉弸鐨勭敤鎴风紪鍙�
- silent?: boolean; // 鏄惁鍏嶆墦鎵�
- displayName?: string; // 濂藉弸灞曠ず澶囨敞
- pinned?: boolean; // 鏄惁缃《鑱旂郴浜�
- }
-}
-
-
-/** 鑾峰緱褰撳墠鐧诲綍鐢ㄦ埛鐨勫ソ鍙嬪垪琛� */
-export function getMyFriendList() {
- return requestClient.get<ImFriendApi.FriendRespVO[]>('/im/friend/list');
-}
-
-/** 澧為噺鎷夊彇褰撳墠鐢ㄦ埛鐨勫ソ鍙嬪叧绯伙紙閲嶈繛 / 绂荤嚎琛ュ伩锛� */
-export function pullMyFriendList(params: {
- lastId?: number;
- lastUpdateTime?: number;
- limit: number;
-}) {
- return requestClient.get<ImFriendApi.FriendRespVO[]>('/im/friend/pull', { params });
-}
-
-/** 鑾峰緱濂藉弸璇︽儏 */
-export function getFriend(friendUserId: number | string) {
- return requestClient.get<ImFriendApi.FriendRespVO>('/im/friend/get', {
- params: { friendUserId },
- });
-}
-
-/** 鍒犻櫎濂藉弸锛堝崟鍚戣蒋鍒犻櫎锛� */
-export function deleteFriend(friendUserId: number | string, clear: boolean) {
- return requestClient.delete<boolean>('/im/friend/delete', {
- params: { friendUserId, clear },
- });
-}
-
-/** 鏇存柊濂藉弸淇℃伅锛堝娉� / 鍏嶆墦鎵� / 鑱旂郴浜虹疆椤讹級 */
-export function updateFriend(data: ImFriendApi.FriendUpdateReqVO) {
- return requestClient.put<boolean>('/im/friend/update', data);
-}
-
-/** 鎷夐粦濂藉弸锛堝繀椤诲厛鏄ソ鍙嬶紱鍗曡竟灞忚斀瀵规柟绉佽亰娑堟伅锛� */
-export function blockFriend(friendUserId: number | string) {
- return requestClient.put<boolean>('/im/friend/block', undefined, {
- params: { friendUserId },
- });
-}
-
-/** 绉诲嚭榛戝悕鍗� */
-export function unblockFriend(friendUserId: number | string) {
- return requestClient.put<boolean>('/im/friend/unblock', undefined, {
- params: { friendUserId },
- });
-}
diff --git a/src/api/im/friend/request/index.ts b/src/api/im/friend/request/index.ts
deleted file mode 100644
index 00c98f0..0000000
--- a/src/api/im/friend/request/index.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImFriendRequestApi {
- /** IM 濂藉弸鐢宠 Response VO */
- export interface FriendRequestRespVO {
- id: number; // 鐢宠缂栧彿
- fromUserId: number; // 鍙戣捣鏂圭敤鎴风紪鍙�
- toUserId: number; // 鎺ユ敹鏂圭敤鎴风紪鍙�
- handleResult: number; // 澶勭悊缁撴灉锛�0=鏈鐞嗭紱1=鍚屾剰锛�2=鎷掔粷
- applyContent?: string; // 鐢宠鐞嗙敱
- handleContent?: string; // 澶勭悊鐞嗙敱锛堟帴鏀舵柟鎷掔粷鏃跺彲閫夊~锛�
- addSource?: number; // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum
- handleTime?: string; // 澶勭悊鏃堕棿
- createTime: string; // 鐢宠鍒涘缓鏃堕棿
- updateTime?: number; // 鏈�杩戞洿鏂版椂闂达紙姣鏃堕棿鎴筹紝澧為噺鎷夊彇娓告爣鐢級
- fromNickname?: string; // 鍙戣捣鏂规樀绉�
- fromAvatar?: string; // 鍙戣捣鏂瑰ご鍍�
- toNickname?: string; // 鎺ユ敹鏂规樀绉�
- toAvatar?: string; // 鎺ユ敹鏂瑰ご鍍�
- }
-
- /** IM 濂藉弸鐢宠鍙戣捣 Request VO */
- export interface FriendRequestApplyReqVO {
- toUserId: number; // 鎺ユ敹鏂圭敤鎴风紪鍙�
- applyContent?: string; // 鐢宠鐞嗙敱
- displayName?: string; // 瀵规帴鏀舵柟鐨勫娉紙浠呰嚜宸卞彲瑙侊級
- addSource?: number; // 娣诲姞鏉ユ簮
- }
-}
-
-
-/** 鍙戣捣濂藉弸鐢宠 */
-export function applyFriendRequest(data: ImFriendRequestApi.FriendRequestApplyReqVO) {
- return requestClient.post<null | number>('/im/friend-request/apply', data);
-}
-
-/** 鍚屾剰濂藉弸鐢宠 */
-export function agreeFriendRequest(id: number | string) {
- return requestClient.put<boolean>('/im/friend-request/agree', undefined, {
- params: { id },
- });
-}
-
-/** 鎷掔粷濂藉弸鐢宠 */
-export function refuseFriendRequest(
- id: number | string,
- handleContent?: string,
-) {
- return requestClient.put<boolean>('/im/friend-request/refuse', undefined, {
- params: { id, handleContent },
- });
-}
-
-/** 鏌ヨ銆屾垜鐩稿叧銆嶇殑濂藉弸鐢宠鍒楄〃锛堟父鏍囧垎椤碉細浼� maxId 鍔犺浇鏇村锛� */
-export function getMyFriendRequestList(limit: number, maxId?: number) {
- const params: Record<string, number> = { limit };
- if (maxId != null) {
- params.maxId = maxId;
- }
- return requestClient.get<ImFriendRequestApi.FriendRequestRespVO[]>(
- '/im/friend-request/list',
- { params },
- );
-}
-
-/** 澧為噺鎷夊彇銆屾垜鐩稿叧銆嶇殑濂藉弸鐢宠鍙樻洿锛堥噸杩� / 绂荤嚎琛ュ伩锛� */
-export function pullMyFriendRequestList(params: {
- lastId?: number;
- lastUpdateTime?: number;
- limit: number;
-}) {
- return requestClient.get<ImFriendRequestApi.FriendRequestRespVO[]>(
- '/im/friend-request/pull',
- { params },
- );
-}
-
-/** 鎸� id 鍗曟煡銆屾垜鐩稿叧銆嶇殑鐢宠璁板綍锛堝甫瓒婃潈杩囨护锛沇ebSocket 閫氱煡鍒拌揪鍚庣敤锛� */
-export function getMyFriendRequest(id: number) {
- return requestClient.get<ImFriendRequestApi.FriendRequestRespVO | null>(
- '/im/friend-request/get',
- { params: { id } },
- );
-}
diff --git a/src/api/im/group/index.ts b/src/api/im/group/index.ts
deleted file mode 100644
index ca5e78d..0000000
--- a/src/api/im/group/index.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import type { ImGroupMessageApi } from '#/api/im/message/group';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImGroupApi {
- /** 缇� Response VO */
- export interface GroupRespVO {
- id: number; // 缂栧彿
- name: string; // 缇ゅ悕绉�
- ownerUserId: number; // 缇や富鐢ㄦ埛缂栧彿
- avatar?: string; // 缇ゅご鍍�
- notice?: string; // 缇ゅ叕鍛�
- banned?: boolean; // 鏄惁灏佺
- mutedAll?: boolean; // 鏄惁鍏ㄧ兢绂佽█
- joinApproval?: boolean; // 杩涚兢鏄惁闇�缇や富 / 绠$悊鍛樺鎵�
- bannedTime?: string; // 灏佺鏃堕棿
- status: number; // 缇ょ姸鎬侊紙0=姝e父锛�1=宸茶В鏁o級
- dissolvedTime?: string; // 瑙f暎鏃堕棿
- createTime?: string; // 鍒涘缓鏃堕棿
- pinnedMessages?: ImGroupMessageApi.GroupMessageRespVO[]; // 缇ょ疆椤舵秷鎭垪琛紙鍚庣鍏宠仈鍥炲~锛屼粎褰撶櫥褰曠敤鎴锋槸缇ゆ垚鍛樻椂闈炵┖锛�
- joinStatus?: number; // 褰撳墠鐧诲綍鐢ㄦ埛鍦ㄨ缇ょ殑鎴愬憳鐘舵�侊紙鍙傝 CommonStatusEnum锛�0 鍦ㄧ兢 / 1 宸查��缇わ級锛涘巻鍙查��缇ょ兢浠嶈繑鍥烇紝渚涘睍绀虹绾挎秷鎭殑缇ゅ悕 / 澶村儚
- groupRemark?: string; // 褰撳墠鐧诲綍鐢ㄦ埛瀵硅缇ょ殑澶囨敞
- silent?: boolean; // 褰撳墠鐧诲綍鐢ㄦ埛鏄惁鍏嶆墦鎵�
- }
-
- /** 缇ゆ秷鎭疆椤� / 鍙栨秷缃《 Request VO */
- export interface GroupMessagePinReqVO {
- id: number; // 缇ょ紪鍙�
- messageId: number; // 娑堟伅缂栧彿
- }
-
- /** 缇ゅ垱寤� Request VO */
- export interface GroupCreateReqVO {
- name: string; // 缇ゅ悕绉�
- memberUserIds?: number[]; // 鍒濆鎴愬憳鐢ㄦ埛缂栧彿鍒楄〃锛堝缓缇ゅ悓鏃堕個璇风殑濂藉弸锛屼笉鍚垱寤鸿�呰嚜宸憋級
- joinApproval?: boolean; // 杩涚兢鏄惁闇�瀹℃壒锛涗笉浼犻粯璁� false 鑷敱杩涚兢
- }
-
- /** 缇ゆ洿鏂� Request VO */
- export interface GroupUpdateReqVO {
- id: number; // 缇ょ紪鍙�
- name?: string; // 缇ゅ悕绉�
- avatar?: string; // 缇ゅご鍍�
- notice?: string; // 缇ゅ叕鍛�
- joinApproval?: boolean; // 杩涚兢鏄惁闇�瀹℃壒
- }
-
- /** 娣诲姞 / 鎾ら攢缇ょ鐞嗗憳 Request VO */
- export interface GroupAdminReqVO {
- id: number; // 缇ょ紪鍙�
- userIds: number[]; // 鐩爣鐢ㄦ埛缂栧彿鍒楄〃
- }
-
- /** 缇や富杞 Request VO */
- export interface GroupTransferOwnerReqVO {
- id: number; // 缇ょ紪鍙�
- newOwnerUserId: number; // 鏂扮兢涓荤敤鎴风紪鍙�
- }
-
- /** 鍏ㄧ兢绂佽█ / 鍙栨秷 Request VO */
- export interface GroupMuteAllReqVO {
- id: number; // 缇ょ紪鍙�
- mutedAll: boolean; // 鏄惁鍏ㄧ兢绂佽█
- }
-
- /** 鎴愬憳绂佽█ Request VO */
- export interface GroupMuteMemberReqVO {
- id: number; // 缇ょ紪鍙�
- userId: number; // 琚瑷�鐨勭敤鎴风紪鍙�
- mutedSeconds: number; // 绂佽█鏃堕暱锛堢锛夛紝0 琛ㄧず姘镐箙绂佽█
- }
-
- /** 鍙栨秷鎴愬憳绂佽█ Request VO */
- export interface GroupCancelMuteMemberReqVO {
- id: number; // 缇ょ紪鍙�
- userId: number; // 琚彇娑堢瑷�鐨勭敤鎴风紪鍙�
- }
-}
-
-
-/** 鑾峰緱褰撳墠鐧诲綍鐢ㄦ埛鐨勭兢鍒楄〃 */
-export function getMyGroupList() {
- return requestClient.get<ImGroupApi.GroupRespVO[]>('/im/group/list');
-}
-
-/** 鑾峰緱缇よ鎯� */
-export function getGroup(id: number | string) {
- return requestClient.get<ImGroupApi.GroupRespVO>('/im/group/get', { params: { id } });
-}
-
-/** 鍒涘缓缇� */
-export function createGroup(data: ImGroupApi.GroupCreateReqVO) {
- return requestClient.post<ImGroupApi.GroupRespVO>('/im/group/create', data);
-}
-
-/** 鏇存柊缇� */
-export function updateGroup(data: ImGroupApi.GroupUpdateReqVO) {
- return requestClient.put<ImGroupApi.GroupRespVO>('/im/group/update', data);
-}
-
-/** 瑙f暎缇� */
-export function dissolveGroup(id: number | string) {
- return requestClient.delete<boolean>('/im/group/dissolve', {
- params: { id },
- });
-}
-
-/** 娣诲姞缇ょ鐞嗗憳锛堜粎缇や富鍙皟锛� */
-export function addGroupAdmin(data: ImGroupApi.GroupAdminReqVO) {
- return requestClient.put<boolean>('/im/group/add-admin', data);
-}
-
-/** 鎾ら攢缇ょ鐞嗗憳锛堜粎缇や富鍙皟锛� */
-export function removeGroupAdmin(data: ImGroupApi.GroupAdminReqVO) {
- return requestClient.put<boolean>('/im/group/remove-admin', data);
-}
-
-/** 杞缇や富锛堜粎鑰佺兢涓诲彲璋冿紱鏃х兢涓昏浆璁╁悗闄嶄负鏅�氭垚鍛橈級 */
-export function transferGroupOwner(data: ImGroupApi.GroupTransferOwnerReqVO) {
- return requestClient.put<boolean>('/im/group/transfer-owner', data);
-}
-
-/** 缃《缇ゆ秷鎭紙浠呯兢涓� / 绠$悊鍛樺彲璋冿級 */
-export function pinGroupMessage(data: ImGroupApi.GroupMessagePinReqVO) {
- return requestClient.put<boolean>('/im/group/pin-message', data);
-}
-
-/** 鍙栨秷缃《缇ゆ秷鎭紙浠呯兢涓� / 绠$悊鍛樺彲璋冿級 */
-export function unpinGroupMessage(data: ImGroupApi.GroupMessagePinReqVO) {
- return requestClient.put<boolean>('/im/group/unpin-message', data);
-}
-
-/** 鍏ㄧ兢绂佽█ / 鍙栨秷锛堜粎缇や富 / 绠$悊鍛樺彲璋冿級 */
-export function muteAll(data: ImGroupApi.GroupMuteAllReqVO) {
- return requestClient.put<boolean>('/im/group/mute-all', data);
-}
-
-/** 绂佽█鎴愬憳 */
-export function muteMember(data: ImGroupApi.GroupMuteMemberReqVO) {
- return requestClient.put<boolean>('/im/group/mute-member', data);
-}
-
-/** 鍙栨秷鎴愬憳绂佽█ */
-export function cancelMuteMember(data: ImGroupApi.GroupCancelMuteMemberReqVO) {
- return requestClient.put<boolean>('/im/group/cancel-mute-member', data);
-}
diff --git a/src/api/im/group/member/index.ts b/src/api/im/group/member/index.ts
deleted file mode 100644
index bd27a15..0000000
--- a/src/api/im/group/member/index.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImGroupMemberApi {
- /** 缇ゆ垚鍛� Response VO */
- export interface GroupMemberRespVO {
- id: number; // 缂栧彿
- groupId: number; // 缇ょ紪鍙�
- userId: number; // 鐢ㄦ埛缂栧彿
- displayUserName?: string; // 缁勫唴鏄剧ず鍚嶏紙缇や富璁剧疆鐨勫娉級
- groupRemark?: string; // 缇ゅ娉紙褰撳墠鐢ㄦ埛瀵圭兢鐨勫娉級
- silent?: boolean; // 鏄惁鍏嶆墦鎵�
- status?: number; // 鎴愬憳鐘舵�侊紙0=鍦ㄧ兢锛�1=閫�缇わ級
- role?: number; // 鎴愬憳瑙掕壊锛屽弬瑙� ImGroupMemberRole 鏋氫妇
- joinTime?: string; // 鍏ョ兢鏃堕棿
- quitTime?: string; // 閫�缇ゆ椂闂�
- muteEndTime?: string; // 绂佽█鍒版湡鏃堕棿
- createTime?: string; // 鍒涘缓鏃堕棿
- nickname?: string; // 鐢ㄦ埛鏄电О
- avatar?: string; // 鐢ㄦ埛澶村儚
- }
-
- /** 缇ゆ垚鍛橀個璇� Request VO */
- export interface GroupMemberInviteReqVO {
- groupId: number; // 缇ょ紪鍙�
- memberUserIds: number[]; // 琚個璇风殑鐢ㄦ埛缂栧彿鍒楄〃
- }
-
- /** 缇ゆ垚鍛樼Щ闄� Request VO */
- export interface GroupMemberRemoveReqVO {
- groupId: number; // 缇ょ紪鍙�
- memberUserIds: number[]; // 琚Щ闄ょ殑鐢ㄦ埛缂栧彿鍒楄〃
- }
-
- /** 缇ゆ垚鍛樻洿鏂� Request VO */
- export interface GroupMemberUpdateReqVO {
- groupId: number; // 缇ょ紪鍙�
- displayUserName?: string; // 缇ゅ唴鏄电О
- groupRemark?: string; // 缇ゅ娉�
- silent?: boolean; // 鏄惁鍏嶆墦鎵�
- }
-}
-
-
-/** 閭�璇风敤鎴峰姞鍏ョ兢 */
-export function inviteGroupMember(data: ImGroupMemberApi.GroupMemberInviteReqVO) {
- return requestClient.post<boolean>('/im/group/invite', data);
-}
-
-/** 閫�鍑虹兢 */
-export function quitGroup(groupId: number | string) {
- return requestClient.delete<boolean>('/im/group/quit', {
- params: { groupId },
- });
-}
-
-/** 绉婚櫎缇ゆ垚鍛� */
-export function removeGroupMember(data: ImGroupMemberApi.GroupMemberRemoveReqVO) {
- return requestClient.delete<boolean>('/im/group/kicking', { data });
-}
-
-/** 鑾峰緱缇ゆ垚鍛樿鎯� */
-export function getGroupMember(groupId: number, userId: number) {
- return requestClient.get<ImGroupMemberApi.GroupMemberRespVO>('/im/group-member/get', {
- params: { groupId, userId },
- });
-}
-
-/** 鑾峰緱鎸囧畾缇ょ殑鎴愬憳鍒楄〃锛堣仛鍚� AdminUser 鏄电О / 澶村儚锛� */
-export function getGroupMemberList(groupId: number | string) {
- return requestClient.get<ImGroupMemberApi.GroupMemberRespVO[]>('/im/group-member/list', {
- params: { groupId },
- });
-}
-
-/** 鏇存柊缇ゆ垚鍛� */
-export function updateGroupMember(data: ImGroupMemberApi.GroupMemberUpdateReqVO) {
- return requestClient.put<boolean>('/im/group-member/update', data);
-}
diff --git a/src/api/im/group/request/index.ts b/src/api/im/group/request/index.ts
deleted file mode 100644
index 09eaf24..0000000
--- a/src/api/im/group/request/index.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImGroupRequestApi {
- /** IM 鍔犵兢鐢宠 Response VO */
- export interface GroupRequestRespVO {
- id: number; // 鐢宠缂栧彿
- groupId: number; // 缇ょ紪鍙�
- userId: number; // 鐢宠浜� / 琚個璇蜂汉鐢ㄦ埛缂栧彿
- inviterUserId?: number; // 閭�璇蜂汉锛汵ULL 琛ㄧず鐢ㄦ埛涓诲姩鐢宠
- handleResult: number; // 澶勭悊缁撴灉锛�0=鏈鐞嗭紱1=鍚屾剰锛�2=鎷掔粷
- applyContent?: string; // 鐢宠鐞嗙敱
- handleContent?: string; // 澶勭悊鐞嗙敱锛堟嫆缁濇椂鍙�夊~锛�
- handleUserId?: number; // 澶勭悊浜虹敤鎴风紪鍙�
- addSource?: number; // 鍔犲叆鏉ユ簮锛涘弬瑙� ImGroupAddSourceEnum
- handleTime?: string; // 澶勭悊鏃堕棿
- createTime: string; // 鐢宠鍒涘缓鏃堕棿
- updateTime?: number; // 鏈�杩戞洿鏂版椂闂达紙姣鏃堕棿鎴筹紝澧為噺鎷夊彇娓告爣鐢級
- userNickname?: string; // 鐢宠浜� / 琚個璇蜂汉鏄电О
- userAvatar?: string; // 鐢宠浜� / 琚個璇蜂汉澶村儚
- inviterNickname?: string; // 閭�璇蜂汉鏄电О
- inviterAvatar?: string; // 閭�璇蜂汉澶村儚
- groupName?: string; // 缇ゅ悕绉�
- groupAvatar?: string; // 缇ゅご鍍�
- }
-
- /** IM 鍔犵兢鐢宠鍙戣捣 Request VO */
- export interface GroupRequestApplyReqVO {
- groupId: number; // 缇ょ紪鍙�
- applyContent?: string; // 鐢宠鐞嗙敱
- addSource?: number; // 鍔犲叆鏉ユ簮
- }
-}
-
-
-/** 鐢宠鍔犵兢 */
-export function applyJoinGroup(data: ImGroupRequestApi.GroupRequestApplyReqVO) {
- return requestClient.post<null | number>('/im/group-request/apply', data);
-}
-
-/** 鍚屾剰鍔犵兢鐢宠锛堢兢涓绘垨绠$悊鍛橈級 */
-export function agreeGroupRequest(id: number | string) {
- return requestClient.put<boolean>('/im/group-request/agree', undefined, {
- params: { id },
- });
-}
-
-/** 鎷掔粷鍔犵兢鐢宠锛堢兢涓绘垨绠$悊鍛橈級 */
-export function refuseGroupRequest(
- id: number | string,
- handleContent?: string,
-) {
- return requestClient.put<boolean>('/im/group-request/refuse', undefined, {
- params: { id, handleContent },
- });
-}
-
-/** 鏌ヨ銆屾垜绠$悊鐨勬墍鏈夌兢銆嶄笅鐨勬湭澶勭悊鍔犵兢鐢宠鍒楄〃锛堜笉鍒嗛〉锛夛紱鍓嶇 store 鎹娲剧敓妯箙绾㈢偣 + Drawer 鍒楄〃 */
-export function getUnhandledRequestList() {
- return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
- '/im/group-request/unhandled-list',
- );
-}
-
-/** 鏌ヨ鎸囧畾缇や笅鐨勫叏閮ㄥ姞缇ょ敵璇凤紙鍚凡澶勭悊锛夛紱浠呯兢涓� / 绠$悊鍛樺彲鏌� */
-export function getGroupRequestListByGroupId(groupId: number) {
- return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
- '/im/group-request/list-by-group',
- { params: { groupId } },
- );
-}
-
-/** 鎸� id 鍗曟煡鐢宠璁板綍锛堝甫瓒婃潈杩囨护锛沇ebSocket 閫氱煡鍒拌揪鍚庣敤锛� */
-export function getMyGroupRequest(id: number) {
- return requestClient.get<ImGroupRequestApi.GroupRequestRespVO | null>(
- '/im/group-request/get',
- { params: { id } },
- );
-}
-
-/** 澧為噺鎷夊彇鎴戠鐞嗙殑鎵�鏈夌兢涓嬪姞缇ょ敵璇峰彉鏇达紙閲嶈繛 / 绂荤嚎琛ュ伩锛� */
-export function pullMyGroupRequestList(params: {
- lastId?: number;
- lastUpdateTime?: number;
- limit: number;
-}) {
- return requestClient.get<ImGroupRequestApi.GroupRequestRespVO[]>(
- '/im/group-request/pull',
- { params },
- );
-}
diff --git a/src/api/im/manager/channel/index.ts b/src/api/im/manager/channel/index.ts
deleted file mode 100644
index f11ce74..0000000
--- a/src/api/im/manager/channel/index.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerChannelApi {
- /** 棰戦亾 */
- export interface Channel {
- id: number;
- code: string;
- name: string;
- avatar?: string;
- sort: number;
- status: number;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱棰戦亾鍒嗛〉 */
-export function getManagerChannelPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerChannelApi.Channel>>(
- '/im/manager/channel/page',
- { params },
- );
-}
-
-/** 鑾峰緱棰戦亾璇︽儏 */
-export function getManagerChannel(id: number) {
- return requestClient.get<ImManagerChannelApi.Channel>('/im/manager/channel/get', {
- params: { id },
- });
-}
-
-/** 鏂板棰戦亾 */
-export function createManagerChannel(data: ImManagerChannelApi.Channel) {
- return requestClient.post<number>('/im/manager/channel/create', data);
-}
-
-/** 淇敼棰戦亾 */
-export function updateManagerChannel(data: ImManagerChannelApi.Channel) {
- return requestClient.put<boolean>('/im/manager/channel/update', data);
-}
-
-/** 鍒犻櫎棰戦亾 */
-export function deleteManagerChannel(id: number) {
- return requestClient.delete<boolean>('/im/manager/channel/delete', {
- params: { id },
- });
-}
-
-/** 鑾峰緱鍚敤鐨勯閬撶簿绠�鍒楄〃锛堣〃鍗曢�夋嫨鐢級 */
-export function getSimpleChannelList() {
- return requestClient.get<ImManagerChannelApi.Channel[]>(
- '/im/manager/channel/simple-list',
- );
-}
diff --git a/src/api/im/manager/channel/material/index.ts b/src/api/im/manager/channel/material/index.ts
deleted file mode 100644
index 8272081..0000000
--- a/src/api/im/manager/channel/material/index.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerChannelMaterialApi {
- /** 棰戦亾绱犳潗 */
- export interface Material {
- id: number;
- channelId: number;
- channelName?: string;
- type: number;
- title: string;
- coverUrl?: string;
- summary?: string;
- content?: string;
- url?: string;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱绱犳潗鍒嗛〉 */
-export function getManagerChannelMaterialPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerChannelMaterialApi.Material>>(
- '/im/manager/channel-material/page',
- { params },
- );
-}
-
-/** 鑾峰緱鎸囧畾棰戦亾涓嬬殑绱犳潗绮剧畝鍒楄〃 */
-export function getSimpleManagerChannelMaterialList(channelId: number) {
- return requestClient.get<ImManagerChannelMaterialApi.Material[]>(
- '/im/manager/channel-material/simple-list',
- { params: { channelId } },
- );
-}
-
-/** 鑾峰緱绱犳潗璇︽儏 */
-export function getManagerChannelMaterial(id: number) {
- return requestClient.get<ImManagerChannelMaterialApi.Material>(
- '/im/manager/channel-material/get',
- { params: { id } },
- );
-}
-
-/** 鏂板绱犳潗 */
-export function createManagerChannelMaterial(
- data: ImManagerChannelMaterialApi.Material,
-) {
- return requestClient.post<number>('/im/manager/channel-material/create', data);
-}
-
-/** 淇敼绱犳潗 */
-export function updateManagerChannelMaterial(
- data: ImManagerChannelMaterialApi.Material,
-) {
- return requestClient.put<boolean>(
- '/im/manager/channel-material/update',
- data,
- );
-}
-
-/** 鍒犻櫎绱犳潗 */
-export function deleteManagerChannelMaterial(id: number) {
- return requestClient.delete<boolean>('/im/manager/channel-material/delete', {
- params: { id },
- });
-}
diff --git a/src/api/im/manager/channel/message/index.ts b/src/api/im/manager/channel/message/index.ts
deleted file mode 100644
index d81f9ef..0000000
--- a/src/api/im/manager/channel/message/index.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerChannelMessageApi {
- /** 棰戦亾娑堟伅 */
- export interface ChannelMessage {
- id: number;
- channelId: number;
- channelName?: string;
- materialId: number;
- materialTitle?: string;
- materialCoverUrl?: string;
- type: number;
- content?: string;
- receiverUserIds?: number[];
- sendTime?: Date;
- }
-
- /** 棰戦亾娑堟伅鍙戦�佽姹� */
- export interface ChannelMessageSendReqVO {
- materialId: number;
- receiverUserIds?: number[];
- }
-}
-
-
-/** 绔嬪嵆鎺ㄩ�侀閬撴秷鎭� */
-export function sendManagerChannelMessage(
- data: ImManagerChannelMessageApi.ChannelMessageSendReqVO,
-) {
- return requestClient.post<number>('/im/manager/channel-message/send', data);
-}
-
-/** 鍒犻櫎棰戦亾娑堟伅 */
-export function deleteManagerChannelMessage(id: number) {
- return requestClient.delete<boolean>('/im/manager/channel-message/delete', {
- params: { id },
- });
-}
-
-/** 鑾峰緱棰戦亾娑堟伅鍒嗛〉 */
-export function getManagerChannelMessagePage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerChannelMessageApi.ChannelMessage>>(
- '/im/manager/channel-message/page',
- { params },
- );
-}
diff --git a/src/api/im/manager/face/item/index.ts b/src/api/im/manager/face/item/index.ts
deleted file mode 100644
index c75bceb..0000000
--- a/src/api/im/manager/face/item/index.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerFacePackItemApi {
- /** 琛ㄦ儏椤� */
- export interface FacePackItem {
- id: number;
- packId: number;
- url: string;
- name?: string;
- width: number;
- height: number;
- sort: number;
- status: number;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱琛ㄦ儏鍒嗛〉 */
-export function getManagerFacePackItemPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerFacePackItemApi.FacePackItem>>(
- '/im/manager/face-pack-item/page',
- { params },
- );
-}
-
-/** 鑾峰緱琛ㄦ儏璇︽儏 */
-export function getManagerFacePackItem(id: number) {
- return requestClient.get<ImManagerFacePackItemApi.FacePackItem>(
- '/im/manager/face-pack-item/get',
- { params: { id } },
- );
-}
-
-/** 鏂板琛ㄦ儏 */
-export function createManagerFacePackItem(data: ImManagerFacePackItemApi.FacePackItem) {
- return requestClient.post<number>('/im/manager/face-pack-item/create', data);
-}
-
-/** 淇敼琛ㄦ儏 */
-export function updateManagerFacePackItem(data: ImManagerFacePackItemApi.FacePackItem) {
- return requestClient.put<boolean>(
- '/im/manager/face-pack-item/update',
- data,
- );
-}
-
-/** 鍒犻櫎琛ㄦ儏 */
-export function deleteManagerFacePackItem(id: number) {
- return requestClient.delete<boolean>('/im/manager/face-pack-item/delete', {
- params: { id },
- });
-}
-
-/** 鎵归噺鍒犻櫎琛ㄦ儏 */
-export function deleteManagerFacePackItemList(ids: number[]) {
- return requestClient.delete<boolean>(
- '/im/manager/face-pack-item/delete-list',
- { params: { ids: ids.join(',') } },
- );
-}
diff --git a/src/api/im/manager/face/pack/index.ts b/src/api/im/manager/face/pack/index.ts
deleted file mode 100644
index ac3f882..0000000
--- a/src/api/im/manager/face/pack/index.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerFacePackApi {
- /** 琛ㄦ儏鍖� */
- export interface FacePack {
- id: number;
- name: string;
- icon?: string;
- sort: number;
- status: number;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱琛ㄦ儏鍖呭垎椤� */
-export function getManagerFacePackPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerFacePackApi.FacePack>>(
- '/im/manager/face-pack/page',
- { params },
- );
-}
-
-/** 鑾峰緱琛ㄦ儏鍖呰鎯� */
-export function getManagerFacePack(id: number) {
- return requestClient.get<ImManagerFacePackApi.FacePack>('/im/manager/face-pack/get', {
- params: { id },
- });
-}
-
-/** 鏂板琛ㄦ儏鍖� */
-export function createManagerFacePack(data: ImManagerFacePackApi.FacePack) {
- return requestClient.post<number>('/im/manager/face-pack/create', data);
-}
-
-/** 淇敼琛ㄦ儏鍖� */
-export function updateManagerFacePack(data: ImManagerFacePackApi.FacePack) {
- return requestClient.put<boolean>('/im/manager/face-pack/update', data);
-}
-
-/** 鍒犻櫎琛ㄦ儏鍖� */
-export function deleteManagerFacePack(id: number) {
- return requestClient.delete<boolean>('/im/manager/face-pack/delete', {
- params: { id },
- });
-}
-
-/** 鎵归噺鍒犻櫎琛ㄦ儏鍖� */
-export function deleteManagerFacePackList(ids: number[]) {
- return requestClient.delete<boolean>('/im/manager/face-pack/delete-list', {
- params: { ids: ids.join(',') },
- });
-}
diff --git a/src/api/im/manager/face/userItem/index.ts b/src/api/im/manager/face/userItem/index.ts
deleted file mode 100644
index 44b3fe8..0000000
--- a/src/api/im/manager/face/userItem/index.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerFaceUserItemApi {
- /** 鐢ㄦ埛琛ㄦ儏 */
- export interface FaceUserItem {
- id: number;
- userId: number;
- userNickname?: string;
- url: string;
- name?: string;
- width?: number;
- height?: number;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱鐢ㄦ埛琛ㄦ儏鍒嗛〉 */
-export function getManagerFaceUserItemPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerFaceUserItemApi.FaceUserItem>>(
- '/im/manager/face-user-item/page',
- { params },
- );
-}
-
-/** 鍒犻櫎鐢ㄦ埛琛ㄦ儏 */
-export function deleteManagerFaceUserItem(id: number) {
- return requestClient.delete<boolean>('/im/manager/face-user-item/delete', {
- params: { id },
- });
-}
diff --git a/src/api/im/manager/friend/index.ts b/src/api/im/manager/friend/index.ts
deleted file mode 100644
index 0c128ff..0000000
--- a/src/api/im/manager/friend/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerFriendApi {
- /** 濂藉弸鍏崇郴 */
- export interface Friend {
- id: number;
- userId: number;
- userNickname?: string;
- friendUserId: number;
- friendNickname?: string;
- displayName?: string;
- addSource?: number;
- silent: boolean;
- pinned: boolean;
- blocked: boolean;
- status: number;
- addTime?: Date;
- deleteTime?: Date;
- createTime: Date;
- }
-}
-
-
-/** 鑾峰緱濂藉弸鍏崇郴鍒嗛〉 */
-export function getManagerFriendPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerFriendApi.Friend>>(
- '/im/manager/friend/page',
- { params },
- );
-}
diff --git a/src/api/im/manager/friend/request/index.ts b/src/api/im/manager/friend/request/index.ts
deleted file mode 100644
index 93e57ea..0000000
--- a/src/api/im/manager/friend/request/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerFriendRequestApi {
- /** 濂藉弸鐢宠 */
- export interface FriendRequest {
- id: number;
- fromUserId: number;
- fromNickname?: string;
- toUserId: number;
- toNickname?: string;
- applyContent?: string;
- displayName?: string;
- addSource?: number;
- handleResult: number;
- handleContent?: string;
- handleTime?: Date;
- createTime: Date;
- }
-}
-
-
-/** 鑾峰緱濂藉弸鐢宠鍒嗛〉 */
-export function getManagerFriendRequestPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerFriendRequestApi.FriendRequest>>(
- '/im/manager/friend-request/page',
- { params },
- );
-}
diff --git a/src/api/im/manager/group/index.ts b/src/api/im/manager/group/index.ts
deleted file mode 100644
index c27c413..0000000
--- a/src/api/im/manager/group/index.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerGroupApi {
- /** 缇� */
- export interface Group {
- id: number;
- name: string;
- avatar?: string;
- notice?: string;
- ownerUserId: number;
- ownerNickname?: string;
- memberCount?: number;
- status: number;
- banned: boolean;
- mutedAll?: boolean; // 鏄惁鍏ㄧ兢绂佽█
- bannedReason?: string;
- bannedTime?: Date;
- dissolvedTime?: Date;
- createTime: Date;
- }
-
- /** 缇ゆ垚鍛� */
- export interface GroupMember {
- userId: number;
- nickname?: string;
- avatar?: string;
- displayUserName?: string;
- groupRemark?: string;
- silent?: boolean;
- status: number;
- role?: number; // 鎴愬憳瑙掕壊锛屽弬瑙� ImGroupMemberRole 鏋氫妇
- joinTime?: Date;
- quitTime?: Date;
- muteEndTime?: Date; // 绂佽█鍒版湡鏃堕棿
- }
-
- /** 缇ゅ皝绂佽姹� */
- export interface GroupBanReqVO {
- id: number;
- reason: string;
- }
-}
-
-
-/** 鑾峰緱缇ゅ垎椤� */
-export function getManagerGroupPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerGroupApi.Group>>(
- '/im/manager/group/page',
- { params },
- );
-}
-
-/** 鑾峰緱缇よ鎯� */
-export function getManagerGroup(id: number) {
- return requestClient.get<ImManagerGroupApi.Group>('/im/manager/group/get', {
- params: { id },
- });
-}
-
-/** 灏佺缇� */
-export function banManagerGroup(data: ImManagerGroupApi.GroupBanReqVO) {
- return requestClient.put<boolean>('/im/manager/group/ban', data);
-}
-
-/** 瑙e皝缇� */
-export function unbanManagerGroup(id: number) {
- return requestClient.put<boolean>('/im/manager/group/unban', undefined, {
- params: { id },
- });
-}
-
-/** 瑙f暎缇� */
-export function dissolveManagerGroup(id: number) {
- return requestClient.delete<boolean>('/im/manager/group/dissolve', {
- params: { id },
- });
-}
-
-/** 鑾峰緱缇ゆ垚鍛樺垪琛紙鍚凡閫�缇ゆ垚鍛橈紝鐢卞墠绔寜闇�杩囨护锛� */
-export function getManagerGroupMemberList(groupId: number) {
- return requestClient.get<ImManagerGroupApi.GroupMember[]>(
- '/im/manager/group/member/list',
- { params: { groupId } },
- );
-}
diff --git a/src/api/im/manager/group/request/index.ts b/src/api/im/manager/group/request/index.ts
deleted file mode 100644
index 3302cbf..0000000
--- a/src/api/im/manager/group/request/index.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerGroupRequestApi {
- /** 鍔犵兢鐢宠 */
- export interface GroupRequest {
- id: number;
- groupId: number;
- groupName?: string;
- userId: number;
- userNickname?: string;
- inviterUserId?: number;
- inviterNickname?: string;
- applyContent?: string;
- addSource?: number;
- handleResult: number;
- handleUserId?: number;
- handleNickname?: string;
- handleContent?: string;
- handleTime?: Date;
- createTime: Date;
- }
-}
-
-
-/** 鑾峰緱鍔犵兢鐢宠鍒嗛〉 */
-export function getManagerGroupRequestPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerGroupRequestApi.GroupRequest>>(
- '/im/manager/group-request/page',
- { params },
- );
-}
diff --git a/src/api/im/manager/message/group/index.ts b/src/api/im/manager/message/group/index.ts
deleted file mode 100644
index 348af63..0000000
--- a/src/api/im/manager/message/group/index.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerGroupMessageApi {
- /** 缇よ亰娑堟伅 */
- export interface GroupMessage {
- id: number;
- clientMessageId?: string;
- groupId: number;
- groupName?: string;
- senderId: number;
- senderNickname?: string;
- type: number;
- content: string;
- status: number;
- atUserIds?: number[];
- atUserNicknames?: (null | string)[];
- receiptStatus: number;
- sendTime: Date;
- createTime: Date;
- }
-}
-
-
-/** 鑾峰緱缇よ亰娑堟伅鍒嗛〉 */
-export function getManagerGroupMessagePage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerGroupMessageApi.GroupMessage>>(
- '/im/manager/message/group/page',
- { params },
- );
-}
-
-/** 鑾峰緱缇よ亰娑堟伅璇︽儏 */
-export function getManagerGroupMessage(id: number) {
- return requestClient.get<ImManagerGroupMessageApi.GroupMessage>(
- '/im/manager/message/group/get',
- { params: { id } },
- );
-}
diff --git a/src/api/im/manager/message/private/index.ts b/src/api/im/manager/message/private/index.ts
deleted file mode 100644
index 0da3ed4..0000000
--- a/src/api/im/manager/message/private/index.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerPrivateMessageApi {
- /** 绉佽亰娑堟伅 */
- export interface PrivateMessage {
- id: number;
- clientMessageId?: string;
- senderId: number;
- senderNickname?: string;
- receiverId: number;
- receiverNickname?: string;
- type: number;
- content: string;
- status: number;
- receiptStatus?: number;
- sendTime: Date;
- createTime: Date;
- }
-}
-
-
-/** 鑾峰緱绉佽亰娑堟伅鍒嗛〉 */
-export function getManagerPrivateMessagePage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerPrivateMessageApi.PrivateMessage>>(
- '/im/manager/message/private/page',
- { params },
- );
-}
-
-/** 鑾峰緱绉佽亰娑堟伅璇︽儏 */
-export function getManagerPrivateMessage(id: number) {
- return requestClient.get<ImManagerPrivateMessageApi.PrivateMessage>(
- '/im/manager/message/private/get',
- { params: { id } },
- );
-}
diff --git a/src/api/im/manager/rtc/index.ts b/src/api/im/manager/rtc/index.ts
deleted file mode 100644
index 26249e0..0000000
--- a/src/api/im/manager/rtc/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerRtcApi {
- /** RTC 閫氳瘽 */
- export interface RtcCall {
- id: number;
- room: string;
- conversationType: number;
- mediaType: number;
- inviterUserId: number;
- inviterNickname?: string;
- groupId?: number;
- groupName?: string;
- status: number;
- endReason?: number;
- startTime: Date;
- acceptTime?: Date;
- endTime?: Date;
- createTime: Date;
- }
-
- /** RTC 閫氳瘽鍙備笌鑰� */
- export interface RtcParticipant {
- id: number;
- callId: number;
- userId: number;
- userNickname?: string;
- role: number;
- status: number;
- inviteTime: Date;
- acceptTime?: Date;
- leaveTime?: Date;
- }
-}
-
-
-/** 鑾峰緱閫氳瘽璁板綍鍒嗛〉 */
-export function getManagerRtcCallPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerRtcApi.RtcCall>>(
- '/im/manager/rtc/page',
- { params },
- );
-}
-
-/** 鑾峰緱閫氳瘽鍙備笌鑰呭垪琛� */
-export function getManagerRtcCallParticipantList(id: number) {
- return requestClient.get<ImManagerRtcApi.RtcParticipant[]>(
- '/im/manager/rtc/participant-list',
- { params: { id } },
- );
-}
diff --git a/src/api/im/manager/sensitiveword/index.ts b/src/api/im/manager/sensitiveword/index.ts
deleted file mode 100644
index a45c536..0000000
--- a/src/api/im/manager/sensitiveword/index.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerSensitiveWordApi {
- /** 鏁忔劅璇� */
- export interface SensitiveWord {
- id: number;
- word: string;
- status: number;
- creator?: string;
- creatorName?: string;
- createTime?: Date;
- }
-}
-
-
-/** 鑾峰緱鏁忔劅璇嶅垎椤� */
-export function getManagerSensitiveWordPage(params: PageParam) {
- return requestClient.get<PageResult<ImManagerSensitiveWordApi.SensitiveWord>>(
- '/im/manager/sensitive-word/page',
- { params },
- );
-}
-
-/** 鑾峰緱鏁忔劅璇嶈鎯� */
-export function getManagerSensitiveWord(id: number) {
- return requestClient.get<ImManagerSensitiveWordApi.SensitiveWord>(
- '/im/manager/sensitive-word/get',
- { params: { id } },
- );
-}
-
-/** 鏂板鏁忔劅璇� */
-export function createManagerSensitiveWord(data: ImManagerSensitiveWordApi.SensitiveWord) {
- return requestClient.post<number>('/im/manager/sensitive-word/create', data);
-}
-
-/** 淇敼鏁忔劅璇� */
-export function updateManagerSensitiveWord(data: ImManagerSensitiveWordApi.SensitiveWord) {
- return requestClient.put<boolean>(
- '/im/manager/sensitive-word/update',
- data,
- );
-}
-
-/** 鍒犻櫎鏁忔劅璇� */
-export function deleteManagerSensitiveWord(id: number) {
- return requestClient.delete<boolean>('/im/manager/sensitive-word/delete', {
- params: { id },
- });
-}
-
-/** 鎵归噺鍒犻櫎鏁忔劅璇� */
-export function deleteManagerSensitiveWordList(ids: number[]) {
- return requestClient.delete<boolean>(
- '/im/manager/sensitive-word/delete-list',
- { params: { ids: ids.join(',') } },
- );
-}
diff --git a/src/api/im/manager/statistics/index.ts b/src/api/im/manager/statistics/index.ts
deleted file mode 100644
index fb4a040..0000000
--- a/src/api/im/manager/statistics/index.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImManagerStatisticsApi {
- /** 缁熻姒傝 */
- export interface Overview {
- totalUser: number;
- newUserToday: number;
- totalGroup: number;
- newGroupToday: number;
- activeUserDaily: number;
- activeUserWeekly: number;
- activeUserMonthly: number;
- privateMessageToday: number;
- groupMessageToday: number;
- privateMessageYesterday: number;
- groupMessageYesterday: number;
- }
-
- /** 瓒嬪娍鏁版嵁 */
- export interface Trend {
- dates: string[];
- series: Record<string, number[]>;
- }
-
- /** 娑堟伅绫诲瀷鍒嗗竷 */
- export interface MessageType {
- type: number; // 鍙傝 ImContentTypeEnum 鏋氫妇绫伙紝鐢卞墠绔寜 DICT_TYPE.IM_CONTENT_TYPE 缈昏瘧
- value: number;
- }
-
- /** 缇よ妯″垎甯� */
- export interface GroupSize {
- range: string;
- count: number;
- }
-
- /** 娑堟伅鍙戦�佹帓琛� */
- export interface TopSender {
- userId: number;
- nickname: string;
- messageCount: number;
- }
-}
-
-
-/** 鑾峰緱 KPI 姒傝 */
-export function getStatisticsOverview() {
- return requestClient.get<ImManagerStatisticsApi.Overview>(
- '/im/manager/statistics/overview',
- );
-}
-
-/** 鑾峰緱娑堟伅瓒嬪娍锛堢鑱� + 缇よ亰鍙岀嚎锛� */
-export function getMessageTrend(days: number) {
- return requestClient.get<ImManagerStatisticsApi.Trend>(
- '/im/manager/statistics/message-trend',
- { params: { days } },
- );
-}
-
-/** 鑾峰緱鐢ㄦ埛瓒嬪娍锛堟柊澧炴敞鍐� + 鏃ユ椿鍙岀嚎锛� */
-export function getUserTrend(days: number) {
- return requestClient.get<ImManagerStatisticsApi.Trend>(
- '/im/manager/statistics/user-trend',
- { params: { days } },
- );
-}
-
-/** 鑾峰緱鍐呭绫诲瀷鍒嗗竷锛堟渶杩� 30 澶╋級 */
-export function getMessageTypeDistribution() {
- return requestClient.get<ImManagerStatisticsApi.MessageType[]>(
- '/im/manager/statistics/message-type-distribution',
- );
-}
-
-/** 鑾峰緱缇よ妯″垎甯� */
-export function getGroupSizeDistribution() {
- return requestClient.get<ImManagerStatisticsApi.GroupSize[]>(
- '/im/manager/statistics/group-size-distribution',
- );
-}
-
-/** 鑾峰緱娑堟伅 TOP 鍙戦�佽�咃紙鏈�杩� 30 澶╋級 */
-export function getTopSenders() {
- return requestClient.get<ImManagerStatisticsApi.TopSender[]>(
- '/im/manager/statistics/top-senders',
- );
-}
diff --git a/src/api/im/message/channel/index.ts b/src/api/im/message/channel/index.ts
deleted file mode 100644
index 10d8617..0000000
--- a/src/api/im/message/channel/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImChannelMessageApi {
- /** 棰戦亾娑堟伅 */
- export interface ChannelMessageRespVO {
- id: number;
- clientMessageId?: string;
- channelId: number;
- materialId: number;
- type: number;
- content: string;
- receiptStatus?: number;
- sendTime: string;
- }
-}
-
-
-/** 鎷夊彇褰撳墠鐢ㄦ埛搴旀敹鐨勯閬撴秷鎭紙绂荤嚎澧為噺锛夛紱鎸� minId 娓告爣鍒嗛〉 */
-export function pullChannelMessageList(
- params: { minId: number; size?: number },
- signal?: AbortSignal,
-) {
- return requestClient.get<ImChannelMessageApi.ChannelMessageRespVO[]>(
- '/im/channel/message/pull',
- { params, signal },
- );
-}
-
-/** 涓婃姤棰戦亾娑堟伅宸茶浣嶇疆锛涘垏鍒伴閬撲細璇濇垨鎷夊埌鏂版秷鎭悗璋� */
-export function readChannelMessages(channelId: number, messageId: number) {
- return requestClient.put<boolean>('/im/channel/message/read', undefined, {
- params: { channelId, messageId },
- });
-}
diff --git a/src/api/im/message/group/index.ts b/src/api/im/message/group/index.ts
deleted file mode 100644
index 200e1c6..0000000
--- a/src/api/im/message/group/index.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImGroupMessageApi {
- /** 缇よ亰娑堟伅 Response VO */
- export interface GroupMessageRespVO {
- id: number; // 娑堟伅缂栧彿
- clientMessageId: string; // 瀹㈡埛绔秷鎭紪鍙�
- senderId: number; // 鍙戦�佷汉缂栧彿
- groupId: number; // 缇ょ紪鍙�
- type: number; // 鍐呭绫诲瀷
- content: string; // 娑堟伅鍐呭锛圝SON 鏍煎紡锛�
- status: number; // 娑堟伅鐘舵��
- sendTime: string; // 鍙戦�佹椂闂�
- atUserIds?: number[]; // @ 鐩爣鐢ㄦ埛缂栧彿鍒楄〃
- receiverUserIds?: number[]; // 瀹氬悜鎺ユ敹鐢ㄦ埛缂栧彿鍒楄〃
- receiptStatus?: number; // 鍥炴墽鐘舵��
- readCount?: number; // 宸茶浜烘暟锛堝洖鎵ф秷鎭�佷笖鍙戦�佷汉涓哄綋鍓嶇敤鎴锋椂鏈夊�硷級
- }
-
- /** 缇よ亰娑堟伅鍙戦�� Request VO */
- export interface GroupMessageSendReqVO {
- clientMessageId: string; // 瀹㈡埛绔秷鎭紪鍙�
- groupId: number; // 缇ょ紪鍙�
- type: number; // 鍐呭绫诲瀷
- content: string; // 娑堟伅鍐呭锛圝SON 鏍煎紡锛�
- atUserIds?: number[]; // @ 鐩爣鐢ㄦ埛缂栧彿鍒楄〃
- receipt?: boolean; // 鏄惁闇�瑕佸洖鎵�
- }
-
- /** 缇よ亰鍘嗗彶娑堟伅鍒楄〃 Request VO */
- export interface GroupMessageListReqVO {
- groupId: number | string; // 缇ょ紪鍙�
- maxId?: number | string; // 璧峰娑堟伅缂栧彿锛堜笉鍚級锛屼负绌哄垯浠庢渶鏂版秷鎭紑濮�
- limit: number; // 鎷夊彇鏁伴噺锛�1 ~ 200锛�
- }
-}
-
-
-/** 鍙戦�佺兢鑱婃秷鎭� */
-export function sendGroupMessage(data: ImGroupMessageApi.GroupMessageSendReqVO) {
- return requestClient.post<ImGroupMessageApi.GroupMessageRespVO>(
- '/im/message/group/send',
- data,
- );
-}
-
-/** 鎷夊彇缇よ亰娑堟伅锛堝閲忥級 */
-export function pullGroupMessageList(
- params: { minId: number | string; size: number },
- signal?: AbortSignal,
-) {
- return requestClient.get<ImGroupMessageApi.GroupMessageRespVO[]>(
- '/im/message/group/pull',
- { params, signal },
- );
-}
-
-/** 鏌ヨ缇よ亰鍘嗗彶娑堟伅 */
-export function getGroupMessageList(params: ImGroupMessageApi.GroupMessageListReqVO) {
- return requestClient.get<ImGroupMessageApi.GroupMessageRespVO[]>(
- '/im/message/group/list',
- { params },
- );
-}
-
-/** 鏍囪缇よ亰娑堟伅宸茶 */
-export function readGroupMessages(
- groupId: number | string,
- messageId: number | string,
-) {
- return requestClient.put<boolean>('/im/message/group/read', undefined, {
- params: { groupId, messageId },
- });
-}
-
-/** 鎾ゅ洖缇よ亰娑堟伅 */
-export function recallGroupMessage(id: number | string) {
- return requestClient.delete<ImGroupMessageApi.GroupMessageRespVO>(
- '/im/message/group/recall',
- { params: { id } },
- );
-}
-
-/** 鑾峰彇缇ゆ秷鎭凡璇荤敤鎴峰垪琛� */
-export function getGroupReadUsers(params: {
- groupId: number | string;
- messageId: number | string;
-}) {
- return requestClient.get<number[]>('/im/message/group/get-read-user-ids', {
- params,
- });
-}
diff --git a/src/api/im/message/private/index.ts b/src/api/im/message/private/index.ts
deleted file mode 100644
index 7c40ab6..0000000
--- a/src/api/im/message/private/index.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImPrivateMessageApi {
- /** 绉佽亰娑堟伅 Response VO */
- export interface PrivateMessageRespVO {
- id: number; // 娑堟伅缂栧彿
- clientMessageId: string; // 瀹㈡埛绔秷鎭紪鍙�
- senderId: number; // 鍙戦�佷汉缂栧彿
- receiverId: number; // 鎺ユ敹浜虹紪鍙�
- type: number; // 鍐呭绫诲瀷
- content: string; // 娑堟伅鍐呭锛圝SON 鏍煎紡锛�
- status: number; // 娑堟伅鐘舵�侊紙姝e父 / 宸叉挙鍥烇級
- receiptStatus?: number; // 鍥炴墽鐘舵�侊紙涓嶉渶瑕� / 寰呭畬鎴� / 宸插畬鎴愶級锛屽榻� ImMessageReceiptStatus
- sendTime: string; // 鍙戦�佹椂闂�
- }
-
- /** 绉佽亰娑堟伅鍙戦�� Request VO */
- export interface PrivateMessageSendReqVO {
- clientMessageId: string; // 瀹㈡埛绔秷鎭紪鍙�
- receiverId: number; // 鎺ユ敹浜虹紪鍙�
- type: number; // 鍐呭绫诲瀷
- content: string; // 娑堟伅鍐呭锛圝SON 鏍煎紡锛�
- receipt?: boolean; // 鏄惁闇�瑕佸洖鎵э紱涓嶄紶鍚庣榛樿 true锛堟櫘閫氱鑱婄敤鎴锋秷鎭級
- }
-
- /** 绉佽亰鍘嗗彶娑堟伅鍒楄〃 Request VO */
- export interface PrivateMessageListReqVO {
- receiverId: number | string; // 鎺ユ敹浜虹紪鍙凤紙瀵规柟锛�
- maxId?: number | string; // 璧峰娑堟伅缂栧彿锛堜笉鍚級锛屼负绌哄垯浠庢渶鏂版秷鎭紑濮�
- limit: number; // 鎷夊彇鏁伴噺锛�1 ~ 200锛�
- }
-}
-
-
-/** 鍙戦�佺鑱婃秷鎭� */
-export function sendPrivateMessage(data: ImPrivateMessageApi.PrivateMessageSendReqVO) {
- return requestClient.post<ImPrivateMessageApi.PrivateMessageRespVO>(
- '/im/message/private/send',
- data,
- );
-}
-
-/** 鎷夊彇绉佽亰娑堟伅锛堝閲忥級 */
-export function pullPrivateMessageList(
- params: { minId: number | string; size: number },
- signal?: AbortSignal,
-) {
- return requestClient.get<ImPrivateMessageApi.PrivateMessageRespVO[]>(
- '/im/message/private/pull',
- { params, signal },
- );
-}
-
-/** 鏌ヨ绉佽亰鍘嗗彶娑堟伅 */
-export function getPrivateMessageList(params: ImPrivateMessageApi.PrivateMessageListReqVO) {
- return requestClient.get<ImPrivateMessageApi.PrivateMessageRespVO[]>(
- '/im/message/private/list',
- { params },
- );
-}
-
-/** 鏍囪绉佽亰娑堟伅宸茶 */
-export function readPrivateMessages(
- receiverId: number | string,
- messageId: number | string,
-) {
- return requestClient.put<boolean>('/im/message/private/read', undefined, {
- params: { receiverId, messageId },
- });
-}
-
-/** 鏌ヨ瀵规柟宸茶鍒版垜鍙戠殑鏈�澶ф秷鎭� id锛堝绔� / 绂荤嚎鍚庣敤浜庤ˉ榻愬凡璇荤姸鎬侊級 */
-export function getPrivateMaxReadMessageId(
- peerId: number | string,
- signal?: AbortSignal,
-) {
- return requestClient.get<null | number>(
- '/im/message/private/max-read-message-id',
- { params: { peerId }, signal },
- );
-}
-
-/** 鎾ゅ洖绉佽亰娑堟伅 */
-export function recallPrivateMessage(id: number | string) {
- return requestClient.delete<ImPrivateMessageApi.PrivateMessageRespVO>(
- '/im/message/private/recall',
- { params: { id } },
- );
-}
diff --git a/src/api/im/rtc/index.ts b/src/api/im/rtc/index.ts
deleted file mode 100644
index 801df2a..0000000
--- a/src/api/im/rtc/index.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace ImRtcApi {
- /** 鍒涘缓鏂伴�氳瘽璇锋眰 VO */
- export interface RtcCallCreateReqVO {
- conversationType: number;
- mediaType: number;
- groupId?: number;
- inviteeIds: number[]; // 琚個璇风殑鐢ㄦ埛缂栧彿闆嗗悎锛涚鑱婂繀浼� 1 涓绔紝缇よ亰蹇呬紶鑷冲皯 1 浜�
- }
-
- /** 閫氳瘽涓拷鍔犻個璇疯姹� VO锛涗粎缇ら�氳瘽鍙敤 */
- export interface RtcCallInviteReqVO {
- room: string;
- inviteeIds: number[];
- }
-
- /** 閫氳瘽浼氳瘽 VO锛沜reate / join / accept / refreshToken 鍏辩敤 */
- export interface RtcCallRespVO {
- room: string; // 涓氬姟閫氳瘽缂栧彿锛堝悓鏃朵綔涓� LiveKit 鎴块棿鍚嶏級
- livekitUrl: string;
- token?: string; // ENDED 鐘舵�佹椂涓� null锛堟棤闇� connect LiveKit锛�
- conversationType: number;
- mediaType: number;
- status: number;
- endReason?: number; // 缁撴潫鍘熷洜锛涗粎 status=ENDED 鏃舵湁鍊�
- inviterId: number;
- groupId?: number;
- inviteeIds?: number[];
- joinedUserIds?: number[];
- }
-
- /** 缇ゆ椿璺冮�氳瘽鏌ヨ鍝嶅簲锛涗笉鍚� token */
- export interface RtcGroupCallRespVO {
- room: string;
- groupId: number;
- mediaType: number;
- inviterId: number;
- joinedUserIds?: number[];
- inviteeIds?: number[];
- }
-}
-
-
-/** 鍒涘缓鏂伴�氳瘽锛涚鑱婃垨缇よ亰鏍规嵁 conversationType 鍖哄垎 */
-export function createCall(data: ImRtcApi.RtcCallCreateReqVO) {
- return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/create', data);
-}
-
-/** 閫氳瘽涓拷鍔犻個璇凤紱浠呯兢閫氳瘽鍙敤 */
-export function inviteCall(data: ImRtcApi.RtcCallInviteReqVO) {
- return requestClient.post<boolean>('/im/rtc/invite', data);
-}
-
-/** 鍔犲叆宸叉湁缇ら�氳瘽锛涚敤浜庤兌鍥婃潯銆屽姞鍏ャ�嶆寜閽� */
-export function joinCall(room: string) {
- return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/join', undefined, {
- params: { room },
- });
-}
-
-/** 鎺ュ惉閫氳瘽 */
-export function acceptCall(room: string) {
- return requestClient.post<ImRtcApi.RtcCallRespVO>('/im/rtc/accept', undefined, {
- params: { room },
- });
-}
-
-/** 鎷掔粷閫氳瘽 */
-export function rejectCall(room: string) {
- return requestClient.post<boolean>('/im/rtc/reject', undefined, {
- params: { room },
- });
-}
-
-/** 鍙栨秷閭�璇凤紱涓诲彨鎺ラ�氬墠璋冪敤 */
-export function cancelCall(room: string) {
- return requestClient.post<boolean>('/im/rtc/cancel', undefined, {
- params: { room },
- });
-}
-
-/** 绂诲紑閫氳瘽锛涙帴閫氬悗璋冪敤 */
-export function leaveCall(room: string) {
- return requestClient.post<boolean>('/im/rtc/leave', undefined, {
- params: { room },
- });
-}
-
-/** 鎸搩瓒呮椂妫�鏌ワ紱RUNNING 绔� timer 鍏滃簳锛岃Е鍙戝悗绔珛鍗虫壂鎻忚 room 鐨勮秴鏃� INVITING锛堟帴鍙i潤榛橈級 */
-export function noAnswerCallCheck(room: string) {
- return requestClient.post<boolean>(
- '/im/rtc/no-answer-call-check',
- undefined,
- { params: { room } },
- );
-}
-
-/** 鏌ヨ褰撳墠杩涜涓殑閫氳瘽锛涚洰鍓嶄粎缇よ亰鍦烘櫙锛堣兌鍥婃潯锛夛紝杩斿洖 null 琛ㄧず鏃犳椿璺冮�氳瘽 */
-export function getActiveCall(groupId: number) {
- return requestClient.get<ImRtcApi.RtcGroupCallRespVO | null>(
- '/im/rtc/get-active-call',
- { params: { groupId } },
- );
-}
diff --git a/src/api/iot/alert/config/index.ts b/src/api/iot/alert/config/index.ts
deleted file mode 100644
index bc37a6c..0000000
--- a/src/api/iot/alert/config/index.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace AlertConfigApi {
- /** IoT 鍛婅閰嶇疆 */
- export interface AlertConfig {
- id?: number;
- name?: string;
- description?: string;
- level?: number;
- status?: number;
- sceneRuleIds?: number[];
- receiveUserIds?: number[];
- receiveUserNames?: string[];
- receiveTypes?: number[];
- smsTemplateCode?: string;
- mailTemplateCode?: string;
- notifyTemplateCode?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鍛婅閰嶇疆鍒嗛〉 */
-export function getAlertConfigPage(params: PageParam) {
- return requestClient.get<PageResult<AlertConfigApi.AlertConfig>>(
- '/iot/alert-config/page',
- { params },
- );
-}
-
-/** 鏌ヨ鍛婅閰嶇疆璇︽儏 */
-export function getAlertConfig(id: number) {
- return requestClient.get<AlertConfigApi.AlertConfig>(
- `/iot/alert-config/get?id=${id}`,
- );
-}
-
-/** 鑾峰彇鍛婅閰嶇疆绠�鍗曞垪琛� */
-export function getSimpleAlertConfigList() {
- return requestClient.get<AlertConfigApi.AlertConfig[]>(
- '/iot/alert-config/simple-list',
- );
-}
-
-/** 鏂板鍛婅閰嶇疆 */
-export function createAlertConfig(data: AlertConfigApi.AlertConfig) {
- return requestClient.post('/iot/alert-config/create', data);
-}
-
-/** 淇敼鍛婅閰嶇疆 */
-export function updateAlertConfig(data: AlertConfigApi.AlertConfig) {
- return requestClient.put('/iot/alert-config/update', data);
-}
-
-/** 鍒犻櫎鍛婅閰嶇疆 */
-export function deleteAlertConfig(id: number) {
- return requestClient.delete(`/iot/alert-config/delete?id=${id}`);
-}
diff --git a/src/api/iot/alert/record/index.ts b/src/api/iot/alert/record/index.ts
deleted file mode 100644
index c8d91d7..0000000
--- a/src/api/iot/alert/record/index.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace AlertRecordApi {
- /** IoT 鍛婅璁板綍 */
- export interface AlertRecord {
- id?: number;
- configId?: number;
- configName?: string;
- configLevel?: number;
- deviceId?: number;
- productId?: number;
- deviceMessage?: any;
- processStatus?: boolean;
- processRemark?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鍛婅璁板綍鍒嗛〉 */
-export function getAlertRecordPage(params: PageParam) {
- return requestClient.get<PageResult<AlertRecordApi.AlertRecord>>(
- '/iot/alert-record/page',
- { params },
- );
-}
-
-/** 鏌ヨ鍛婅璁板綍璇︽儏 */
-export function getAlertRecord(id: number) {
- return requestClient.get<AlertRecordApi.AlertRecord>(
- `/iot/alert-record/get?id=${id}`,
- );
-}
-
-/** 澶勭悊鍛婅璁板綍 */
-export function processAlertRecord(id: number, processRemark?: string) {
- return requestClient.put('/iot/alert-record/process', {
- id,
- processRemark,
- });
-}
diff --git a/src/api/iot/device/device/index.ts b/src/api/iot/device/device/index.ts
deleted file mode 100644
index dd46585..0000000
--- a/src/api/iot/device/device/index.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IotDeviceApi {
- /** 璁惧 */
- export interface Device {
- id?: number; // 璁惧缂栧彿
- deviceName: string; // 璁惧鍚嶇О
- nickname?: string; // 澶囨敞鍚嶇О
- serialNumber?: string; // 璁惧搴忓垪鍙�
- picUrl?: string; // 璁惧鍥剧墖
- groupIds?: number[]; // 璁惧鍒嗙粍缂栧彿鏁扮粍
- productId: number; // 浜у搧缂栧彿
- productKey?: string; // 浜у搧鏍囪瘑
- productName?: string; // 浜у搧鍚嶇О锛堝彧鏈夐儴鍒嗘帴鍙h繑鍥烇紝渚嬪 getDeviceLocationList锛�
- deviceType?: number; // 璁惧绫诲瀷
- gatewayId?: number; // 缃戝叧璁惧 ID
- state?: number; // 璁惧鐘舵��
- onlineTime?: Date; // 鏈�鍚庝笂绾挎椂闂�
- offlineTime?: Date; // 鏈�鍚庣绾挎椂闂�
- activeTime?: Date; // 璁惧婵�娲绘椂闂�
- deviceSecret?: string; // 璁惧瀵嗛挜锛岀敤浜庤澶囪璇�
- config?: string; // 璁惧閰嶇疆
- latitude?: number; // 璁惧浣嶇疆鐨勭含搴�
- longitude?: number; // 璁惧浣嶇疆鐨勭粡搴�
- createTime?: Date; // 鍒涘缓鏃堕棿
- }
-
- /** 璁惧鏇存柊鍒嗙粍 Request VO */
- export interface DeviceUpdateGroupReqVO {
- ids: number[]; // 璁惧缂栧彿鍒楄〃锛堝繀濉級
- groupIds: number[]; // 鍒嗙粍缂栧彿鍒楄〃锛堝繀濉級
- }
-
- /** 璁惧璁よ瘉淇℃伅 Response VO */
- export interface DeviceAuthInfoRespVO {
- clientId: string; // 瀹㈡埛绔� ID
- username: string; // 鐢ㄦ埛鍚�
- password: string; // 瀵嗙爜
- }
-
- /** 璁惧瀵煎叆 Response VO */
- export interface DeviceImportRespVO {
- createDeviceNames?: string[]; // 鍒涘缓鎴愬姛鐨勮澶囧悕绉板垪琛�
- updateDeviceNames?: string[]; // 鏇存柊鎴愬姛鐨勮澶囧悕绉板垪琛�
- failureDeviceNames?: Record<string, string>; // 澶辫触鐨勮澶囧悕绉板強鍘熷洜
- }
-
- /** IoT 璁惧灞炴�ц缁� VO */
- export interface DevicePropertyDetail {
- identifier: string; // 灞炴�ф爣璇嗙
- value: string; // 鏈�鏂板��
- updateTime: Date; // 鏇存柊鏃堕棿
- name: string; // 灞炴�у悕绉�
- dataType: string; // 鏁版嵁绫诲瀷
- dataSpecs: any; // 鏁版嵁瀹氫箟
- dataSpecsList: any[]; // 鏁版嵁瀹氫箟鍒楄〃
- }
-
- /** 璁惧灞炴�� VO */
- export interface DeviceProperty {
- identifier: string; // 灞炴�ф爣璇嗙
- value: string; // 鏈�鏂板��
- updateTime: Date; // 鏇存柊鏃堕棿
- }
-
- /** 璁惧鍙戦�佹秷鎭� Request VO */
- export interface DeviceMessageSendReq {
- deviceId: number; // 璁惧缂栧彿
- method: string; // 璇锋眰鏂规硶
- params?: any; // 璇锋眰鍙傛暟
- }
-}
-
-/** 鏌ヨ璁惧鍒嗛〉 */
-export function getDevicePage(params: PageParam) {
- return requestClient.get<PageResult<IotDeviceApi.Device>>(
- '/iot/device/page',
- { params },
- );
-}
-
-/** 鏌ヨ璁惧璇︽儏 */
-export function getDevice(id: number) {
- return requestClient.get<IotDeviceApi.Device>(`/iot/device/get?id=${id}`);
-}
-
-/** 鏂板璁惧 */
-export function createDevice(data: IotDeviceApi.Device) {
- return requestClient.post<number>('/iot/device/create', data);
-}
-
-/** 淇敼璁惧 */
-export function updateDevice(data: IotDeviceApi.Device) {
- return requestClient.put<boolean>('/iot/device/update', data);
-}
-
-/** 淇敼璁惧鍒嗙粍 */
-export function updateDeviceGroup(data: IotDeviceApi.DeviceUpdateGroupReqVO) {
- return requestClient.put<boolean>('/iot/device/update-group', data);
-}
-
-/** 鍒犻櫎鍗曚釜璁惧 */
-export function deleteDevice(id: number) {
- return requestClient.delete<boolean>(`/iot/device/delete?id=${id}`);
-}
-
-/** 鍒犻櫎澶氫釜璁惧 */
-export function deleteDeviceList(ids: number[]) {
- return requestClient.delete<boolean>('/iot/device/delete-list', {
- params: { ids: ids.join(',') },
- });
-}
-
-/** 瀵煎嚭璁惧 */
-export function exportDeviceExcel(params: PageParam) {
- return requestClient.download('/iot/device/export-excel', { params });
-}
-
-/** 鑾峰彇璁惧鏁伴噺 */
-export function getDeviceCount(productId: number) {
- return requestClient.get<number>(`/iot/device/count?productId=${productId}`);
-}
-
-/** 鑾峰彇璁惧鐨勭簿绠�淇℃伅鍒楄〃 */
-export function getSimpleDeviceList(deviceType?: number, productId?: number) {
- return requestClient.get<IotDeviceApi.Device[]>('/iot/device/simple-list', {
- params: { deviceType, productId },
- });
-}
-
-/** 鏍规嵁浜у搧缂栧彿锛岃幏鍙栬澶囩殑绮剧畝淇℃伅鍒楄〃 */
-export function getDeviceListByProductId(productId: number) {
- return requestClient.get<IotDeviceApi.Device[]>('/iot/device/simple-list', {
- params: { productId },
- });
-}
-
-/** 鑾峰彇璁惧浣嶇疆鍒楄〃锛堢敤浜庡湴鍥惧睍绀猴級 */
-export function getDeviceLocationList() {
- return requestClient.get<IotDeviceApi.Device[]>('/iot/device/location-list');
-}
-
-/** 鑾峰彇瀵煎叆妯℃澘 */
-export function importDeviceTemplate() {
- return requestClient.download('/iot/device/get-import-template');
-}
-
-/** 瀵煎叆璁惧 */
-export function importDevice(file: File, updateSupport: boolean) {
- return requestClient.upload<IotDeviceApi.DeviceImportRespVO>(
- `/iot/device/import?updateSupport=${updateSupport}`,
- { file },
- );
-}
-
-/** 鑾峰彇璁惧灞炴�ф渶鏂版暟鎹� */
-export function getLatestDeviceProperties(params: any) {
- return requestClient.get<IotDeviceApi.DevicePropertyDetail[]>(
- '/iot/device/property/get-latest',
- { params },
- );
-}
-
-/** 鑾峰彇璁惧灞炴�у巻鍙叉暟鎹� */
-export function getHistoryDevicePropertyList(params: any) {
- return requestClient.get<IotDeviceApi.DeviceProperty[]>(
- '/iot/device/property/history-list',
- { params },
- );
-}
-
-/** 鑾峰彇璁惧璁よ瘉淇℃伅 */
-export function getDeviceAuthInfo(id: number) {
- return requestClient.get<IotDeviceApi.DeviceAuthInfoRespVO>(
- '/iot/device/get-auth-info',
- { params: { id } },
- );
-}
-
-/** 鏌ヨ璁惧娑堟伅鍒嗛〉 */
-export function getDeviceMessagePage(params: PageParam) {
- return requestClient.get<PageResult<any>>('/iot/device/message/page', {
- params,
- });
-}
-
-/** 鏌ヨ璁惧娑堟伅閰嶅鍒嗛〉 */
-export function getDeviceMessagePairPage(params: PageParam) {
- return requestClient.get<PageResult<any>>('/iot/device/message/pair-page', {
- params,
- });
-}
-
-/** 鍙戦�佽澶囨秷鎭� */
-export function sendDeviceMessage(params: IotDeviceApi.DeviceMessageSendReq) {
- return requestClient.post('/iot/device/message/send', params);
-}
-
-/** 缁戝畾瀛愯澶囧埌缃戝叧璁惧 */
-export function bindDeviceGateway(gatewayId: number, subIds: number[]) {
- return requestClient.put<boolean>('/iot/device/bind-gateway', {
- gatewayId,
- subIds,
- });
-}
-
-/** 瑙g粦瀛愯澶囦笌缃戝叧璁惧 */
-export function unbindDeviceGateway(gatewayId: number, subIds: number[]) {
- return requestClient.put<boolean>('/iot/device/unbind-gateway', {
- gatewayId,
- subIds,
- });
-}
-
-/** 鑾峰彇缃戝叧璁惧鐨勫瓙璁惧鍒楄〃 */
-export function getSubDeviceList(gatewayId: number) {
- return requestClient.get<IotDeviceApi.Device[]>(
- '/iot/device/sub-device-list',
- { params: { gatewayId } },
- );
-}
-
-/** 鑾峰彇鏈粦瀹氱殑瀛愯澶囧垎椤� */
-export function getUnboundSubDevicePage(params: PageParam) {
- return requestClient.get<PageResult<IotDeviceApi.Device>>(
- '/iot/device/unbound-sub-device-page',
- { params },
- );
-}
diff --git a/src/api/iot/device/group/index.ts b/src/api/iot/device/group/index.ts
deleted file mode 100644
index 0ff562f..0000000
--- a/src/api/iot/device/group/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IotDeviceGroupApi {
- /** 璁惧鍒嗙粍 */
- export interface DeviceGroup {
- id?: number; // 鍒嗙粍 ID
- name: string; // 鍒嗙粍鍚嶅瓧
- status?: number; // 鍒嗙粍鐘舵��
- description?: string; // 鍒嗙粍鎻忚堪
- deviceCount?: number; // 璁惧鏁伴噺
- }
-}
-
-/** 鏌ヨ璁惧鍒嗙粍鍒嗛〉 */
-export function getDeviceGroupPage(params: PageParam) {
- return requestClient.get<PageResult<IotDeviceGroupApi.DeviceGroup>>(
- '/iot/device-group/page',
- { params },
- );
-}
-
-/** 鏌ヨ璁惧鍒嗙粍璇︽儏 */
-export function getDeviceGroup(id: number) {
- return requestClient.get<IotDeviceGroupApi.DeviceGroup>(
- `/iot/device-group/get?id=${id}`,
- );
-}
-
-/** 鏂板璁惧鍒嗙粍 */
-export function createDeviceGroup(data: IotDeviceGroupApi.DeviceGroup) {
- return requestClient.post('/iot/device-group/create', data);
-}
-
-/** 淇敼璁惧鍒嗙粍 */
-export function updateDeviceGroup(data: IotDeviceGroupApi.DeviceGroup) {
- return requestClient.put('/iot/device-group/update', data);
-}
-
-/** 鍒犻櫎璁惧鍒嗙粍 */
-export function deleteDeviceGroup(id: number) {
- return requestClient.delete(`/iot/device-group/delete?id=${id}`);
-}
-
-/** 鑾峰彇璁惧鍒嗙粍鐨勭簿绠�淇℃伅鍒楄〃 */
-export function getSimpleDeviceGroupList() {
- return requestClient.get<IotDeviceGroupApi.DeviceGroup[]>(
- '/iot/device-group/simple-list',
- );
-}
diff --git a/src/api/iot/device/modbus/config/index.ts b/src/api/iot/device/modbus/config/index.ts
deleted file mode 100644
index 3ee1e4f..0000000
--- a/src/api/iot/device/modbus/config/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace IotDeviceModbusConfigApi {
- /** Modbus 杩炴帴閰嶇疆 VO */
- export interface ModbusConfig {
- id?: number; // 涓婚敭
- deviceId: number; // 璁惧缂栧彿
- ip: string; // Modbus 鏈嶅姟鍣� IP 鍦板潃
- port: number; // Modbus 鏈嶅姟鍣ㄧ鍙�
- slaveId: number; // 浠庣珯鍦板潃
- timeout: number; // 杩炴帴瓒呮椂鏃堕棿锛屽崟浣嶏細姣
- retryInterval: number; // 閲嶈瘯闂撮殧锛屽崟浣嶏細姣
- mode: number; // 妯″紡
- frameFormat: number; // 甯ф牸寮�
- status: number; // 鐘舵��
- }
-}
-
-/** 鑾峰彇璁惧鐨� Modbus 杩炴帴閰嶇疆 */
-export function getModbusConfig(deviceId: number) {
- return requestClient.get<IotDeviceModbusConfigApi.ModbusConfig>(
- '/iot/device-modbus-config/get',
- { params: { deviceId } },
- );
-}
-
-/** 淇濆瓨 Modbus 杩炴帴閰嶇疆 */
-export function saveModbusConfig(data: IotDeviceModbusConfigApi.ModbusConfig) {
- return requestClient.post('/iot/device-modbus-config/save', data);
-}
diff --git a/src/api/iot/device/modbus/point/index.ts b/src/api/iot/device/modbus/point/index.ts
deleted file mode 100644
index 0edcee0..0000000
--- a/src/api/iot/device/modbus/point/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IotDeviceModbusPointApi {
- /** Modbus 鐐逛綅閰嶇疆 VO */
- export interface ModbusPoint {
- id?: number; // 涓婚敭
- deviceId: number; // 璁惧缂栧彿
- thingModelId?: number; // 鐗╂ā鍨嬪睘鎬х紪鍙�
- identifier: string; // 灞炴�ф爣璇嗙
- name: string; // 灞炴�у悕绉�
- functionCode?: number; // Modbus 鍔熻兘鐮�
- registerAddress?: number; // 瀵勫瓨鍣ㄨ捣濮嬪湴鍧�
- registerCount?: number; // 瀵勫瓨鍣ㄦ暟閲�
- byteOrder?: string; // 瀛楄妭搴�
- rawDataType?: string; // 鍘熷鏁版嵁绫诲瀷
- scale: number; // 缂╂斁鍥犲瓙
- pollInterval: number; // 杞闂撮殧锛屽崟浣嶏細姣
- status: number; // 鐘舵��
- }
-}
-
-/** 鑾峰彇璁惧鐨� Modbus 鐐逛綅鍒嗛〉 */
-export function getModbusPointPage(params: PageParam) {
- return requestClient.get<PageResult<IotDeviceModbusPointApi.ModbusPoint>>(
- '/iot/device-modbus-point/page',
- { params },
- );
-}
-
-/** 鑾峰彇 Modbus 鐐逛綅璇︽儏 */
-export function getModbusPoint(id: number) {
- return requestClient.get<IotDeviceModbusPointApi.ModbusPoint>(
- `/iot/device-modbus-point/get?id=${id}`,
- );
-}
-
-/** 鍒涘缓 Modbus 鐐逛綅閰嶇疆 */
-export function createModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
- return requestClient.post('/iot/device-modbus-point/create', data);
-}
-
-/** 鏇存柊 Modbus 鐐逛綅閰嶇疆 */
-export function updateModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
- return requestClient.put('/iot/device-modbus-point/update', data);
-}
-
-/** 鍒犻櫎 Modbus 鐐逛綅閰嶇疆 */
-export function deleteModbusPoint(id: number) {
- return requestClient.delete(`/iot/device-modbus-point/delete?id=${id}`);
-}
diff --git a/src/api/iot/ota/firmware/index.ts b/src/api/iot/ota/firmware/index.ts
deleted file mode 100644
index 2a57c30..0000000
--- a/src/api/iot/ota/firmware/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IoTOtaFirmwareApi {
- /** IoT OTA 鍥轰欢淇℃伅 */
- export interface Firmware {
- id?: number;
- name?: string;
- description?: string;
- version?: string;
- productId?: number;
- productName?: string;
- fileUrl?: string;
- fileSize?: number;
- fileDigestAlgorithm?: string;
- fileDigestValue?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ OTA 鍥轰欢鍒嗛〉 */
-export function getOtaFirmwarePage(params: PageParam) {
- return requestClient.get<PageResult<IoTOtaFirmwareApi.Firmware>>(
- '/iot/ota/firmware/page',
- { params },
- );
-}
-
-/** 鏌ヨ OTA 鍥轰欢璇︽儏 */
-export function getOtaFirmware(id: number) {
- return requestClient.get<IoTOtaFirmwareApi.Firmware>(
- `/iot/ota/firmware/get?id=${id}`,
- );
-}
-
-/** 鏂板 OTA 鍥轰欢 */
-export function createOtaFirmware(data: IoTOtaFirmwareApi.Firmware) {
- return requestClient.post('/iot/ota/firmware/create', data);
-}
-
-/** 淇敼 OTA 鍥轰欢 */
-export function updateOtaFirmware(data: IoTOtaFirmwareApi.Firmware) {
- return requestClient.put('/iot/ota/firmware/update', data);
-}
-
-/** 鍒犻櫎 OTA 鍥轰欢 */
-export function deleteOtaFirmware(id: number) {
- return requestClient.delete(`/iot/ota/firmware/delete?id=${id}`);
-}
diff --git a/src/api/iot/ota/task/index.ts b/src/api/iot/ota/task/index.ts
deleted file mode 100644
index 80f10e3..0000000
--- a/src/api/iot/ota/task/index.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IoTOtaTaskApi {
- /** IoT OTA 鍗囩骇浠诲姟 */
- export interface Task {
- id?: number;
- name: string;
- description?: string;
- firmwareId?: number;
- status?: number;
- deviceScope: number;
- deviceIds?: number[];
- deviceTotalCount?: number;
- deviceSuccessCount?: number;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ OTA 鍗囩骇浠诲姟鍒嗛〉 */
-export function getOtaTaskPage(params: PageParam) {
- return requestClient.get<PageResult<IoTOtaTaskApi.Task>>(
- '/iot/ota/task/page',
- { params },
- );
-}
-
-/** 鏌ヨ OTA 鍗囩骇浠诲姟璇︽儏 */
-export function getOtaTask(id: number) {
- return requestClient.get<IoTOtaTaskApi.Task>(`/iot/ota/task/get?id=${id}`);
-}
-
-/** 鏂板 OTA 鍗囩骇浠诲姟 */
-export function createOtaTask(data: IoTOtaTaskApi.Task) {
- return requestClient.post('/iot/ota/task/create', data);
-}
-
-/** 鍙栨秷 OTA 鍗囩骇浠诲姟 */
-export function cancelOtaTask(id: number) {
- return requestClient.post(`/iot/ota/task/cancel?id=${id}`);
-}
diff --git a/src/api/iot/ota/task/record/index.ts b/src/api/iot/ota/task/record/index.ts
deleted file mode 100644
index e5a57c4..0000000
--- a/src/api/iot/ota/task/record/index.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IoTOtaTaskRecordApi {
- /** IoT OTA 鍗囩骇浠诲姟璁板綍 */
- export interface TaskRecord {
- id?: number;
- firmwareId?: number;
- firmwareVersion?: string;
- taskId?: number;
- deviceId?: string;
- deviceName?: string;
- currentVersion?: string;
- fromFirmwareId?: number;
- fromFirmwareVersion?: string;
- status?: number;
- progress?: number;
- description?: string;
- updateTime?: Date;
- }
-}
-
-/** 鏌ヨ OTA 鍗囩骇浠诲姟璁板綍鍒嗛〉 */
-export function getOtaTaskRecordPage(params: PageParam) {
- return requestClient.get<PageResult<IoTOtaTaskRecordApi.TaskRecord>>(
- '/iot/ota/task/record/page',
- { params },
- );
-}
-
-/** 鍙栨秷 OTA 鍗囩骇浠诲姟璁板綍 */
-export function cancelOtaTaskRecord(id: number) {
- return requestClient.put(`/iot/ota/task/record/cancel?id=${id}`);
-}
-
-/** 鑾峰彇 OTA 鍗囩骇浠诲姟璁板綍鐘舵�佺粺璁� */
-export function getOtaTaskRecordStatusStatistics(
- firmwareId?: number,
- taskId?: number,
-) {
- return requestClient.get<Record<string, number>>(
- '/iot/ota/task/record/get-status-statistics',
- { params: { firmwareId, taskId } },
- );
-}
diff --git a/src/api/iot/product/category/index.ts b/src/api/iot/product/category/index.ts
deleted file mode 100644
index c53397b..0000000
--- a/src/api/iot/product/category/index.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IotProductCategoryApi {
- /** 浜у搧鍒嗙被 */
- export interface ProductCategory {
- id?: number; // 鍒嗙被 ID
- name: string; // 鍒嗙被鍚嶇О
- sort?: number; // 鍒嗙被鎺掑簭
- status?: number; // 鍒嗙被鐘舵��
- description?: string; // 鍒嗙被鎻忚堪
- createTime?: Date; // 鍒涘缓鏃堕棿
- }
-}
-
-/** 鏌ヨ浜у搧鍒嗙被鍒嗛〉 */
-export function getProductCategoryPage(params: PageParam) {
- return requestClient.get<PageResult<IotProductCategoryApi.ProductCategory>>(
- '/iot/product-category/page',
- { params },
- );
-}
-
-/** 鏌ヨ浜у搧鍒嗙被璇︽儏 */
-export function getProductCategory(id: number) {
- return requestClient.get<IotProductCategoryApi.ProductCategory>(
- `/iot/product-category/get?id=${id}`,
- );
-}
-
-/** 鏂板浜у搧鍒嗙被 */
-export function createProductCategory(
- data: IotProductCategoryApi.ProductCategory,
-) {
- return requestClient.post('/iot/product-category/create', data);
-}
-
-/** 淇敼浜у搧鍒嗙被 */
-export function updateProductCategory(
- data: IotProductCategoryApi.ProductCategory,
-) {
- return requestClient.put('/iot/product-category/update', data);
-}
-
-/** 鍒櫎浜у搧鍒嗙被 */
-export function deleteProductCategory(id: number) {
- return requestClient.delete(`/iot/product-category/delete?id=${id}`);
-}
-
-/** 鑾峰彇浜у搧鍒嗙被绮剧畝鍒楄〃 */
-export function getSimpleProductCategoryList() {
- return requestClient.get<IotProductCategoryApi.ProductCategory[]>(
- '/iot/product-category/simple-list',
- );
-}
diff --git a/src/api/iot/product/product/index.ts b/src/api/iot/product/product/index.ts
deleted file mode 100644
index d3cbcd2..0000000
--- a/src/api/iot/product/product/index.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace IotProductApi {
- /** 浜у搧 */
- export interface Product {
- id?: number; // 浜у搧缂栧彿
- name: string; // 浜у搧鍚嶇О
- productKey?: string; // 浜у搧鏍囪瘑
- productSecret?: string; // 浜у搧瀵嗛挜
- protocolId?: number; // 鍗忚缂栧彿
- protocolType?: string; // 鍗忚绫诲瀷
- categoryId?: number; // 浜у搧鎵�灞炲搧绫绘爣璇嗙
- categoryName?: string; // 浜у搧鎵�灞炲搧绫诲悕绉�
- icon?: string; // 浜у搧鍥炬爣
- picUrl?: string; // 浜у搧鍥剧墖
- description?: string; // 浜у搧鎻忚堪
- status?: number; // 浜у搧鐘舵��
- deviceType?: number; // 璁惧绫诲瀷
- netType?: number; // 鑱旂綉鏂瑰紡
- serializeType?: string; // 搴忓垪鍖栫被鍨�
- registerEnabled?: boolean; // 鏄惁寮�鍚姩鎬佹敞鍐�
- deviceCount?: number; // 璁惧鏁伴噺
- createTime?: Date; // 鍒涘缓鏃堕棿
- }
-}
-
-// IoT 鍗忚绫诲瀷鏋氫妇
-export enum ProtocolTypeEnum {
- COAP = 'coap',
- EMQX = 'emqx',
- HTTP = 'http',
- MODBUS_TCP_CLIENT = 'modbus_tcp_client',
- MODBUS_TCP_SERVER = 'modbus_tcp_server',
- MQTT = 'mqtt',
- TCP = 'tcp',
- UDP = 'udp',
- WEBSOCKET = 'websocket',
-}
-
-// IoT 搴忓垪鍖栫被鍨嬫灇涓�
-export enum SerializeTypeEnum {
- BINARY = 'binary',
- JSON = 'json',
-}
-
-/** 鏌ヨ浜у搧鍒嗛〉 */
-export function getProductPage(params: PageParam) {
- return requestClient.get<PageResult<IotProductApi.Product>>(
- '/iot/product/page',
- { params },
- );
-}
-
-/** 鏌ヨ浜у搧璇︽儏 */
-export function getProduct(id: number) {
- return requestClient.get<IotProductApi.Product>(`/iot/product/get?id=${id}`);
-}
-
-/** 鏂板浜у搧 */
-export function createProduct(data: IotProductApi.Product) {
- return requestClient.post('/iot/product/create', data);
-}
-
-/** 淇敼浜у搧 */
-export function updateProduct(data: IotProductApi.Product) {
- return requestClient.put('/iot/product/update', data);
-}
-
-/** 鍒犻櫎浜у搧 */
-export function deleteProduct(id: number) {
- return requestClient.delete(`/iot/product/delete?id=${id}`);
-}
-
-/** 瀵煎嚭浜у搧 Excel */
-export function exportProduct(params: any) {
- return requestClient.download('/iot/product/export-excel', { params });
-}
-
-/** 鏇存柊浜у搧鐘舵�� */
-export function updateProductStatus(id: number, status: number) {
- return requestClient.put(
- `/iot/product/update-status?id=${id}&status=${status}`,
- );
-}
-
-/** 鏌ヨ浜у搧锛堢簿绠�锛夊垪琛� */
-export function getSimpleProductList(deviceType?: number) {
- return requestClient.get<IotProductApi.Product[]>(
- '/iot/product/simple-list',
- {
- params: { deviceType },
- },
- );
-}
-
-/** 鏍规嵁 ProductKey 鑾峰彇浜у搧淇℃伅 */
-export function getProductByKey(productKey: string) {
- return requestClient.get<IotProductApi.Product>('/iot/product/get-by-key', {
- params: { productKey },
- });
-}
-
-/** 鍚屾浜у搧鐗╂ā鍨� TDengine 瓒呯骇琛ㄧ粨鏋� */
-export function syncProductPropertyTable(productId: number) {
- return requestClient.post(
- `/iot/product/sync-property-table?productId=${productId}`,
- );
-}
diff --git a/src/api/iot/rule/data/rule/index.ts b/src/api/iot/rule/data/rule/index.ts
deleted file mode 100644
index 3c44e98..0000000
--- a/src/api/iot/rule/data/rule/index.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace DataRuleApi {
- /** IoT 鏁版嵁娴佽浆瑙勫垯 */
- export interface DataRule {
- id?: number;
- name?: string;
- description?: string;
- status?: number;
- sourceConfigs?: any[];
- sinkIds?: number[];
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鏁版嵁娴佽浆瑙勫垯鍒嗛〉 */
-export function getDataRulePage(params: PageParam) {
- return requestClient.get<PageResult<DataRuleApi.DataRule>>(
- '/iot/data-rule/page',
- { params },
- );
-}
-
-/** 鏌ヨ鏁版嵁娴佽浆瑙勫垯璇︽儏 */
-export function getDataRule(id: number) {
- return requestClient.get<DataRuleApi.DataRule>(`/iot/data-rule/get?id=${id}`);
-}
-
-/** 鏂板鏁版嵁娴佽浆瑙勫垯 */
-export function createDataRule(data: DataRuleApi.DataRule) {
- return requestClient.post('/iot/data-rule/create', data);
-}
-
-/** 淇敼鏁版嵁娴佽浆瑙勫垯 */
-export function updateDataRule(data: DataRuleApi.DataRule) {
- return requestClient.put('/iot/data-rule/update', data);
-}
-
-/** 鍒犻櫎鏁版嵁娴佽浆瑙勫垯 */
-export function deleteDataRule(id: number) {
- return requestClient.delete(`/iot/data-rule/delete?id=${id}`);
-}
diff --git a/src/api/iot/rule/data/sink/index.ts b/src/api/iot/rule/data/sink/index.ts
deleted file mode 100644
index 23b4f3a..0000000
--- a/src/api/iot/rule/data/sink/index.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-interface BaseConfig {
- type: string;
-}
-
-export namespace DataSinkApi {
- /** IoT 鏁版嵁娴佽浆鐩殑 VO */
- export interface DataSink {
- id?: number;
- name?: string;
- description?: string;
- status?: number;
- direction?: number;
- type?: number;
- config?:
- | DatabaseConfig
- | HttpConfig
- | KafkaMQConfig
- | MqttConfig
- | RabbitMQConfig
- | RedisStreamMQConfig
- | RocketMQConfig
- | TcpConfig
- | WebSocketConfig;
- createTime?: Date;
- }
-
- /** HTTP 閰嶇疆 */
- export interface HttpConfig extends BaseConfig {
- url: string;
- method: string;
- headers: Record<string, string>;
- query: Record<string, string>;
- body: string;
- }
-
- /** TCP 閰嶇疆 */
- export interface TcpConfig extends BaseConfig {
- host: string;
- port: number;
- connectTimeoutMs: number;
- readTimeoutMs: number;
- ssl: boolean;
- sslCertPath: string;
- dataFormat: string;
- heartbeatIntervalMs: number;
- reconnectIntervalMs: number;
- maxReconnectAttempts: number;
- }
-
- /** WebSocket 閰嶇疆 */
- export interface WebSocketConfig extends BaseConfig {
- serverUrl: string;
- connectTimeoutMs: number;
- sendTimeoutMs: number;
- heartbeatIntervalMs: number;
- heartbeatMessage: string;
- subprotocols: string;
- customHeaders: string;
- verifySslCert: boolean;
- dataFormat: string;
- reconnectIntervalMs: number;
- maxReconnectAttempts: number;
- enableCompression: boolean;
- sendRetryCount: number;
- sendRetryIntervalMs: number;
- }
-
- /** MQTT 閰嶇疆 */
- export interface MqttConfig extends BaseConfig {
- url: string;
- username: string;
- password: string;
- clientId: string;
- topic: string;
- }
-
- /** Database 閰嶇疆 */
- export interface DatabaseConfig extends BaseConfig {
- jdbcUrl: string;
- username: string;
- password: string;
- tableName: string;
- }
-
- /** RocketMQ 閰嶇疆 */
- export interface RocketMQConfig extends BaseConfig {
- nameServer: string;
- accessKey: string;
- secretKey: string;
- group: string;
- topic: string;
- tags: string;
- }
-
- /** Kafka 閰嶇疆 */
- export interface KafkaMQConfig extends BaseConfig {
- bootstrapServers: string;
- username: string;
- password: string;
- ssl: boolean;
- topic: string;
- }
-
- /** RabbitMQ 閰嶇疆 */
- export interface RabbitMQConfig extends BaseConfig {
- host: string;
- port: number;
- virtualHost: string;
- username: string;
- password: string;
- exchange: string;
- routingKey: string;
- queue: string;
- }
-
- /** Redis Stream MQ 閰嶇疆 */
- export interface RedisStreamMQConfig extends BaseConfig {
- host: string;
- port: number;
- password: string;
- database: number;
- topic: string;
- }
-}
-
-/** 鏁版嵁娴佽浆鐩殑绫诲瀷 */
-export const IotDataSinkTypeEnum = {
- HTTP: 1,
- TCP: 2,
- WEBSOCKET: 3,
- MQTT: 10,
- DATABASE: 20,
- REDIS_STREAM: 21,
- ROCKETMQ: 30,
- RABBITMQ: 31,
- KAFKA: 32,
-} as const;
-
-/** 鏌ヨ鏁版嵁娴佽浆鐩殑鍒嗛〉 */
-export function getDataSinkPage(params: PageParam) {
- return requestClient.get<PageResult<DataSinkApi.DataSink>>(
- '/iot/data-sink/page',
- { params },
- );
-}
-
-/** 鏌ヨ鏁版嵁娴佽浆鐩殑璇︽儏 */
-export function getDataSink(id: number) {
- return requestClient.get<DataSinkApi.DataSink>(`/iot/data-sink/get?id=${id}`);
-}
-
-/** 鏌ヨ鏁版嵁娴佽浆鐩殑锛堢簿绠�锛夊垪琛� */
-export function getDataSinkSimpleList() {
- return requestClient.get<DataSinkApi.DataSink[]>(
- '/iot/data-sink/simple-list',
- );
-}
-
-/** 鏂板鏁版嵁娴佽浆鐩殑 */
-export function createDataSink(data: DataSinkApi.DataSink) {
- return requestClient.post('/iot/data-sink/create', data);
-}
-
-/** 淇敼鏁版嵁娴佽浆鐩殑 */
-export function updateDataSink(data: DataSinkApi.DataSink) {
- return requestClient.put('/iot/data-sink/update', data);
-}
-
-/** 鍒犻櫎鏁版嵁娴佽浆鐩殑 */
-export function deleteDataSink(id: number) {
- return requestClient.delete(`/iot/data-sink/delete?id=${id}`);
-}
diff --git a/src/api/iot/rule/scene/index.ts b/src/api/iot/rule/scene/index.ts
deleted file mode 100644
index 512c169..0000000
--- a/src/api/iot/rule/scene/index.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace RuleSceneApi {
- /** 鍦烘櫙鑱斿姩瑙勫垯 */
- export interface SceneRule {
- id?: number;
- name: string;
- description?: string;
- status?: number;
- triggers?: Trigger[];
- actions?: Action[];
- lastTriggerTime?: Date;
- createTime?: Date;
- }
-
- /** 鍦烘櫙鑱斿姩瑙勫垯鐨勮Е鍙戝櫒 */
- export interface Trigger {
- type?: number;
- productId?: number;
- deviceId?: number;
- identifier?: string;
- operator?: string;
- value?: any;
- cronExpression?: string;
- conditionGroups?: TriggerCondition[][]; // 鍚庣缁撴瀯锛歀ist<List<TriggerCondition>>锛涘灞傘�屾垨銆嶃�佺粍鍐呫�屼笖銆�
- }
-
- /** 鍦烘櫙鑱斿姩瑙勫垯鐨勮Е鍙戞潯浠� */
- export interface TriggerCondition {
- productId?: number;
- deviceId?: number;
- identifier?: string;
- operator?: string;
- value?: any;
- type?: number;
- param?: string;
- }
-
- /** 鍦烘櫙鑱斿姩瑙勫垯鐨勫姩浣� */
- export interface Action {
- type?: number;
- productId?: number;
- deviceId?: number;
- identifier?: string;
- value?: any;
- alertConfigId?: number;
- params?: string;
- }
-}
-
-/** 鏌ヨ鍦烘櫙鑱斿姩瑙勫垯鍒嗛〉 */
-export function getSceneRulePage(params: PageParam) {
- return requestClient.get<PageResult<RuleSceneApi.SceneRule>>(
- '/iot/scene-rule/page',
- { params },
- );
-}
-
-/** 鏌ヨ鍦烘櫙鑱斿姩瑙勫垯璇︽儏 */
-export function getSceneRule(id: number) {
- return requestClient.get<RuleSceneApi.SceneRule>(
- `/iot/scene-rule/get?id=${id}`,
- );
-}
-
-/** 鏂板鍦烘櫙鑱斿姩瑙勫垯 */
-export function createSceneRule(data: RuleSceneApi.SceneRule) {
- return requestClient.post('/iot/scene-rule/create', data);
-}
-
-/** 淇敼鍦烘櫙鑱斿姩瑙勫垯 */
-export function updateSceneRule(data: RuleSceneApi.SceneRule) {
- return requestClient.put('/iot/scene-rule/update', data);
-}
-
-/** 鍒犻櫎鍦烘櫙鑱斿姩瑙勫垯 */
-export function deleteSceneRule(id: number) {
- return requestClient.delete(`/iot/scene-rule/delete?id=${id}`);
-}
-
-/** 鏇存柊鍦烘櫙鑱斿姩瑙勫垯鐘舵�� */
-export function updateSceneRuleStatus(id: number, status: number) {
- return requestClient.put(`/iot/scene-rule/update-status`, {
- id,
- status,
- });
-}
-
-/** 鑾峰彇鍦烘櫙鑱斿姩瑙勫垯绠�鍗曞垪琛� */
-export function getSimpleRuleSceneList() {
- return requestClient.get<RuleSceneApi.SceneRule[]>(
- '/iot/scene-rule/simple-list',
- );
-}
diff --git a/src/api/iot/statistics/index.ts b/src/api/iot/statistics/index.ts
deleted file mode 100644
index dc7971e..0000000
--- a/src/api/iot/statistics/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace IotStatisticsApi {
- /** 缁熻鎽樿鏁版嵁 */
- export interface StatisticsSummaryRespVO {
- productCategoryCount: number; // 鍝佺被鏁伴噺
- productCount: number; // 浜у搧鏁伴噺
- deviceCount: number; // 璁惧鏁伴噺
- deviceMessageCount: number; // 涓婃姤鏁伴噺
- productCategoryTodayCount: number; // 浠婃棩鏂板鍝佺被鏁伴噺
- productTodayCount: number; // 浠婃棩鏂板浜у搧鏁伴噺
- deviceTodayCount: number; // 浠婃棩鏂板璁惧鏁伴噺
- deviceMessageTodayCount: number; // 浠婃棩鏂板涓婃姤鏁伴噺
- deviceOnlineCount: number; // 鍦ㄧ嚎鏁伴噺
- deviceOfflineCount: number; // 绂荤嚎鏁伴噺
- deviceInactiveCount: number; // 寰呮縺娲昏澶囨暟閲�
- productCategoryDeviceCounts: Record<string, number>; // 鎸夊搧绫荤粺璁$殑璁惧鏁伴噺
- }
-
- /** 璁惧娑堟伅鏁伴噺缁熻锛堟寜鏃ユ湡锛� */
- export interface DeviceMessageSummaryByDateRespVO {
- time: string; // 鏃堕棿杞�
- upstreamCount: number; // 涓婅娑堟伅鏁伴噺
- downstreamCount: number; // 涓嬭娑堟伅鏁伴噺
- }
-
- /** 璁惧娑堟伅缁熻璇锋眰 */
- export interface DeviceMessageReqVO {
- interval: number;
- times?: string[];
- }
-}
-
-/** 鑾峰彇 IoT 缁熻鎽樿鏁版嵁 */
-export function getStatisticsSummary() {
- return requestClient.get<IotStatisticsApi.StatisticsSummaryRespVO>(
- '/iot/statistics/get-summary',
- );
-}
-
-/** 鑾峰彇璁惧娑堟伅鐨勬暟鎹粺璁★紙鎸夋棩鏈燂級 */
-export function getDeviceMessageSummaryByDate(
- params: IotStatisticsApi.DeviceMessageReqVO,
-) {
- return requestClient.get<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
- '/iot/statistics/get-device-message-summary-by-date',
- { params },
- );
-}
diff --git a/src/api/iot/thingmodel/index.ts b/src/api/iot/thingmodel/index.ts
deleted file mode 100644
index f575d7b..0000000
--- a/src/api/iot/thingmodel/index.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-import type { Rule } from 'ant-design-vue/es/form';
-
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { isEmpty } from '../../../packages/utils/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace ThingModelApi {
- /** IoT 鐗╂ā鍨嬫暟鎹� */
- export interface ThingModel {
- id?: number;
- productId?: number;
- productKey?: string;
- identifier?: string;
- name?: string;
- description?: string;
- dataType?: string;
- type?: number; // 鍙傝 IoTThingModelTypeEnum 鏋氫妇绫�
- property?: Property;
- event?: Event;
- service?: Service;
- }
-
- /** IoT 鐗╂ā鍨嬪睘鎬� */
- export interface Property {
- identifier?: string;
- name?: string;
- accessMode?: string;
- required?: boolean;
- dataType?: string;
- description?: string;
- dataSpecs?: ThingModelDataSpecs;
- dataSpecsList?: ThingModelPropertyDataSpecs[];
- }
-
- /** IoT 鐗╂ā鍨嬫湇鍔� */
- export interface Service {
- identifier?: string;
- name?: string;
- required?: boolean;
- callType?: string;
- description?: string;
- inputParams?: Param[];
- outputParams?: Param[];
- method?: string;
- }
-
- /** IoT 鐗╂ā鍨嬩簨浠� */
- export interface Event {
- identifier?: string;
- name?: string;
- required?: boolean;
- type?: string;
- description?: string;
- outputParams?: Param[];
- method?: string;
- }
-
- /** IoT 鐗╂ā鍨嬪弬鏁� */
- export interface Param {
- identifier?: string;
- name?: string;
- direction?: string;
- paraOrder?: number;
- dataType?: string;
- dataSpecs?: ThingModelDataSpecs;
- dataSpecsList?: ThingModelPropertyDataSpecs[];
- }
-
- /** IoT 鐗╂ā鍨� TSL锛堟爲褰級鍝嶅簲 */
- export interface ThingModelTSL {
- productId?: number;
- productKey?: string;
- properties?: Property[];
- events?: Event[];
- services?: Service[];
- }
-
- /** IoT 鏁版嵁瀹氫箟锛堟暟鍊煎瀷锛� */
- export interface ThingModelDataSpecs {
- accessMode?: string;
- childDataType?: string;
- dataSpecs?: ThingModelDataSpecs;
- dataSpecsList?: ThingModelPropertyDataSpecs[];
- dataType?: string;
- defaultValue?: string;
- description?: string;
- identifier?: string;
- length?: number | string;
- min?: number | string;
- max?: number | string;
- name?: string;
- precise?: string;
- required?: boolean;
- size?: number | string;
- step?: number | string;
- unit?: string;
- unitName?: string;
- value?: number | string;
- }
-
- /** IoT 鏁版嵁瀹氫箟锛堟暟鍊煎瀷锛� */
- export type DataSpecsNumberData = ThingModelDataSpecs;
-
- /** IoT 鏁版嵁瀹氫箟锛堟灇涓�/甯冨皵鍨嬶級 */
- export type DataSpecsEnumOrBoolData = ThingModelDataSpecs;
-
- export type ThingModelPropertyDataSpecs = Property & ThingModelDataSpecs;
-}
-
-/** 鐢熸垚銆屽繀濉� + 鏁板瓧銆嶇被鏍¢獙鍣細鎷煎埌 size / length / 鏋氫妇鍊间笂 */
-function buildRequiredNumberValidator(label: string) {
- return (_rule: any, value: any, callback: any) => {
- if (isEmpty(value)) {
- callback(new Error(`${label}涓嶈兘涓虹┖`));
- return;
- }
- if (Number.isNaN(Number(value))) {
- callback(new Error(`${label}蹇呴』鏄暟瀛梎));
- return;
- }
- callback();
- };
-}
-
-/** 鐢熸垚銆屾爣璇嗙鏍峰紡銆嶅悕绉版牎楠屽櫒锛氬紑澶撮渶涓轰腑鏂� / 鑻辨枃 / 鏁板瓧锛屾暣浣撲粎鍏佽涓枃銆佽嫳鏂囥�佹暟瀛椼�佷笅鍒掔嚎銆佺煭鍒掔嚎锛岄暱搴� 鈮� 20 */
-export function buildIdentifierLikeNameValidator(label: string) {
- return (_rule: any, value: string, callback: any) => {
- if (isEmpty(value)) {
- callback(new Error(`${label}涓嶈兘涓虹┖`));
- return;
- }
- if (!/^[涓�-榫-Za-z0-9]/.test(value)) {
- callback(new Error(`${label}蹇呴』浠ヤ腑鏂囥�佽嫳鏂囧瓧姣嶆垨鏁板瓧寮�澶碻));
- return;
- }
- if (!/^[涓�-榫-Za-z0-9][\w涓�-榫�-]*$/.test(value)) {
- callback(
- new Error(`${label}鍙兘鍖呭惈涓枃銆佽嫳鏂囧瓧姣嶃�佹暟瀛椼�佷笅鍒掔嚎鍜岀煭鍒掔嚎`),
- );
- return;
- }
- if (value.length > 20) {
- callback(new Error(`${label}闀垮害涓嶈兘瓒呰繃 20 涓瓧绗));
- return;
- }
- callback();
- };
-}
-
-/** IoT 鐗╂ā鍨嬭〃鍗曟牎楠岃鍒� */
-export const ThingModelFormRules: Record<string, Rule[]> = {
- name: [
- { required: true, message: '鍔熻兘鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- {
- pattern: /^[涓�-榫-Za-z0-9][涓�-榫-Za-z0-9\-_/.]{0,29}$/,
- message:
- '鏀寔涓枃銆佸ぇ灏忓啓瀛楁瘝銆佹棩鏂囥�佹暟瀛椼�佺煭鍒掔嚎銆佷笅鍒掔嚎銆佹枩鏉犲拰灏忔暟鐐癸紝蹇呴』浠ヤ腑鏂囥�佽嫳鏂囨垨鏁板瓧寮�澶达紝涓嶈秴杩� 30 涓瓧绗�',
- trigger: 'blur',
- },
- ],
- type: [{ required: true, message: '鍔熻兘绫诲瀷涓嶈兘涓虹┖', trigger: 'blur' }],
- identifier: [
- { required: true, message: '鏍囪瘑绗︿笉鑳戒负绌�', trigger: 'blur' },
- {
- pattern: /^[a-zA-Z][a-zA-Z0-9_]{0,31}$/,
- message: '鏀寔澶у皬鍐欏瓧姣嶃�佹暟瀛楀拰涓嬪垝绾匡紝蹇呴』浠ュ瓧姣嶅紑澶达紝涓嶈秴杩� 32 涓瓧绗�',
- trigger: 'blur',
- },
- {
- validator: (_rule: any, value: string, callback: any) => {
- const reservedKeywords = [
- 'set',
- 'get',
- 'post',
- 'property',
- 'event',
- 'time',
- 'value',
- ];
- if (reservedKeywords.includes(value)) {
- callback(
- new Error(
- 'set, get, post, property, event, time, value 鏄郴缁熶繚鐣欏瓧娈碉紝涓嶈兘鐢ㄤ簬鏍囪瘑绗﹀畾涔�',
- ),
- );
- return;
- }
- if (/^\d+$/.test(value)) {
- callback(new Error('鏍囪瘑绗︿笉鑳芥槸绾暟瀛�'));
- return;
- }
- callback();
- },
- trigger: 'blur',
- },
- ],
- childDataType: [{ required: true, message: '鍏冪礌绫诲瀷涓嶈兘涓虹┖' }],
- size: [
- {
- required: true,
- validator: buildRequiredNumberValidator('鍏冪礌涓暟'),
- trigger: 'blur',
- },
- ],
- length: [
- {
- required: true,
- validator: buildRequiredNumberValidator('鏂囨湰闀垮害'),
- trigger: 'blur',
- },
- ],
- accessMode: [
- { required: true, message: '璇烽�夋嫨璇诲啓绫诲瀷', trigger: 'change' },
- ],
- callType: [{ required: true, message: '璇烽�夋嫨璋冪敤鏂瑰紡', trigger: 'change' }],
- eventType: [{ required: true, message: '璇烽�夋嫨浜嬩欢绫诲瀷', trigger: 'change' }],
-};
-
-/** 鏍¢獙甯冨皵鍊煎悕绉� */
-export const validateBoolName = buildIdentifierLikeNameValidator('甯冨皵鍊煎悕绉�');
-
-/** 鏌ヨ浜у搧鐗╂ā鍨嬪垎椤� */
-export function getThingModelPage(params: PageParam) {
- return requestClient.get<PageResult<ThingModelApi.ThingModel>>(
- '/iot/thing-model/page',
- { params },
- );
-}
-
-/** 鏌ヨ浜у搧鐗╂ā鍨嬭鎯� */
-export function getThingModel(id: number) {
- return requestClient.get<ThingModelApi.ThingModel>(
- `/iot/thing-model/get?id=${id}`,
- );
-}
-
-/** 鏍规嵁浜у搧 ID 鏌ヨ鐗╂ā鍨嬪垪琛� */
-export function getThingModelListByProductId(productId: number) {
- return requestClient.get<ThingModelApi.ThingModel[]>(
- '/iot/thing-model/list',
- {
- params: { productId },
- },
- );
-}
-
-/** 鏂板鐗╂ā鍨� */
-export function createThingModel(data: ThingModelApi.ThingModel) {
- return requestClient.post('/iot/thing-model/create', data);
-}
-
-/** 淇敼鐗╂ā鍨� */
-export function updateThingModel(data: ThingModelApi.ThingModel) {
- return requestClient.put('/iot/thing-model/update', data);
-}
-
-/** 鍒犻櫎鐗╂ā鍨� */
-export function deleteThingModel(id: number) {
- return requestClient.delete(`/iot/thing-model/delete?id=${id}`);
-}
-
-/** 鑾峰彇鐗╂ā鍨� TSL */
-export function getThingModelTSLByProductId(productId: number) {
- return requestClient.get<ThingModelApi.ThingModelTSL>(
- '/iot/thing-model/get-tsl',
- {
- params: { productId },
- },
- );
-}
diff --git a/src/api/mall/product/brand/index.ts b/src/api/mall/product/brand/index.ts
deleted file mode 100644
index 10c8b9a..0000000
--- a/src/api/mall/product/brand/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBrandApi {
- /** 鍟嗗搧鍝佺墝 */
- export interface Brand {
- id?: number; // 鍝佺墝缂栧彿
- name: string; // 鍝佺墝鍚嶇О
- picUrl: string; // 鍝佺墝鍥剧墖
- sort?: number; // 鍝佺墝鎺掑簭
- description?: string; // 鍝佺墝鎻忚堪
- status: number; // 寮�鍚姸鎬�
- }
-}
-
-/** 鍒涘缓鍟嗗搧鍝佺墝 */
-export function createBrand(data: MallBrandApi.Brand) {
- return requestClient.post('/product/brand/create', data);
-}
-
-/** 鏇存柊鍟嗗搧鍝佺墝 */
-export function updateBrand(data: MallBrandApi.Brand) {
- return requestClient.put('/product/brand/update', data);
-}
-
-/** 鍒犻櫎鍟嗗搧鍝佺墝 */
-export function deleteBrand(id: number) {
- return requestClient.delete(`/product/brand/delete?id=${id}`);
-}
-
-/** 鑾峰緱鍟嗗搧鍝佺墝 */
-export function getBrand(id: number) {
- return requestClient.get<MallBrandApi.Brand>(`/product/brand/get?id=${id}`);
-}
-
-/** 鑾峰緱鍟嗗搧鍝佺墝鍒楄〃 */
-export function getBrandPage(params: PageParam) {
- return requestClient.get<PageResult<MallBrandApi.Brand>>(
- '/product/brand/page',
- {
- params,
- },
- );
-}
-
-/** 鑾峰緱鍟嗗搧鍝佺墝绮剧畝淇℃伅鍒楄〃 */
-export function getSimpleBrandList() {
- return requestClient.get<MallBrandApi.Brand[]>(
- '/product/brand/list-all-simple',
- );
-}
diff --git a/src/api/mall/product/category/index.ts b/src/api/mall/product/category/index.ts
deleted file mode 100644
index a67662a..0000000
--- a/src/api/mall/product/category/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MallCategoryApi {
- /** 浜у搧鍒嗙被 */
- export interface Category {
- id?: number; // 鍒嗙被缂栧彿
- parentId?: number; // 鐖跺垎绫荤紪鍙�
- name: string; // 鍒嗙被鍚嶇О
- picUrl: string; // 绉诲姩绔垎绫诲浘
- sort: number; // 鍒嗙被鎺掑簭
- status: number; // 寮�鍚姸鎬�
- }
-}
-
-/** 鍒涘缓鍟嗗搧鍒嗙被 */
-export function createCategory(data: MallCategoryApi.Category) {
- return requestClient.post('/product/category/create', data);
-}
-
-/** 鏇存柊鍟嗗搧鍒嗙被 */
-export function updateCategory(data: MallCategoryApi.Category) {
- return requestClient.put('/product/category/update', data);
-}
-
-/** 鍒犻櫎鍟嗗搧鍒嗙被 */
-export function deleteCategory(id: number) {
- return requestClient.delete(`/product/category/delete?id=${id}`);
-}
-
-/** 鑾峰緱鍟嗗搧鍒嗙被 */
-export function getCategory(id: number) {
- return requestClient.get<MallCategoryApi.Category>(
- `/product/category/get?id=${id}`,
- );
-}
-
-/** 鑾峰緱鍟嗗搧鍒嗙被鍒楄〃 */
-export function getCategoryList(params: any) {
- return requestClient.get<MallCategoryApi.Category[]>(
- '/product/category/list',
- {
- params,
- },
- );
-}
diff --git a/src/api/mall/product/comment/index.ts b/src/api/mall/product/comment/index.ts
deleted file mode 100644
index 2078b6d..0000000
--- a/src/api/mall/product/comment/index.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallCommentApi {
- /** 鍟嗗搧璇勮 */
- export interface Comment {
- id: number; // 璇勮缂栧彿
- userId: number; // 鐢ㄦ埛缂栧彿
- userNickname: string; // 鐢ㄦ埛鏄电О
- userAvatar: string; // 鐢ㄦ埛澶村儚
- anonymous: boolean; // 鏄惁鍖垮悕
- orderId: number; // 璁㈠崟缂栧彿
- orderItemId: number; // 璁㈠崟椤圭紪鍙�
- spuId: number; // 鍟嗗搧SPU缂栧彿
- spuName: string; // 鍟嗗搧鍚嶇О
- skuId: number; // 鍟嗗搧SKU缂栧彿
- visible: boolean; // 鏄惁鍙
- scores: number; // 鎬昏瘎鍒�
- descriptionScores: number; // 鎻忚堪璇勫垎
- benefitScores: number; // 鏈嶅姟璇勫垎
- content: string; // 璇勮鍐呭
- picUrls: string[]; // 璇勮鍥剧墖
- replyStatus: boolean; // 鏄惁鍥炲
- replyUserId: number; // 鍥炲浜虹紪鍙�
- replyContent: string; // 鍥炲鍐呭
- replyTime: Date; // 鍥炲鏃堕棿
- createTime: Date; // 鍒涘缓鏃堕棿
- skuProperties: {
- propertyId: number; // 灞炴�� ID
- propertyName: string; // 灞炴�у悕绉�
- valueId: number; // 灞炴�у�� ID
- valueName: string; // 灞炴�у�煎悕绉�
- }[]; // SKU 灞炴�ф暟缁�
- }
-
- /** 璇勮鍙鎬ф洿鏂拌姹� */
- export interface CommentVisibleUpdateReqVO {
- id: number; // 璇勮缂栧彿
- visible: boolean; // 鏄惁鍙
- }
-
- /** 璇勮鍥炲璇锋眰 */
- export interface CommentReplyReqVO {
- id: number; // 璇勮缂栧彿
- replyContent: string; // 鍥炲鍐呭
- }
-}
-
-/** 鏌ヨ鍟嗗搧璇勮鍒楄〃 */
-export function getCommentPage(params: PageParam) {
- return requestClient.get<PageResult<MallCommentApi.Comment>>(
- '/product/comment/page',
- { params },
- );
-}
-
-/** 鏌ヨ鍟嗗搧璇勮璇︽儏 */
-export function getComment(id: number) {
- return requestClient.get<MallCommentApi.Comment>(
- `/product/comment/get?id=${id}`,
- );
-}
-
-/** 娣诲姞鑷瘎 */
-export function createComment(data: MallCommentApi.Comment) {
- return requestClient.post('/product/comment/create', data);
-}
-
-/** 鏄剧ず / 闅愯棌璇勮 */
-export function updateCommentVisible(
- data: MallCommentApi.CommentVisibleUpdateReqVO,
-) {
- return requestClient.put('/product/comment/update-visible', data);
-}
-
-/** 鍟嗗鍥炲 */
-export function replyComment(data: MallCommentApi.CommentReplyReqVO) {
- return requestClient.put('/product/comment/reply', data);
-}
diff --git a/src/api/mall/product/favorite/index.ts b/src/api/mall/product/favorite/index.ts
deleted file mode 100644
index 06a2fa4..0000000
--- a/src/api/mall/product/favorite/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallFavoriteApi {
- /** 鍟嗗搧鏀惰棌 */
- export interface Favorite {
- id?: number; // 鏀惰棌缂栧彿
- userId?: string; // 鐢ㄦ埛缂栧彿
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- }
-}
-
-/** 鑾峰緱鍟嗗搧鏀惰棌鍒楄〃 */
-export function getFavoritePage(params: PageParam) {
- return requestClient.get<PageResult<MallFavoriteApi.Favorite>>(
- '/product/favorite/page',
- { params },
- );
-}
diff --git a/src/api/mall/product/history/index.ts b/src/api/mall/product/history/index.ts
deleted file mode 100644
index 406e802..0000000
--- a/src/api/mall/product/history/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallHistoryApi {
- /** 鍟嗗搧娴忚璁板綍 */
- export interface BrowseHistory {
- id?: number; // 璁板綍缂栧彿
- userId?: number; // 鐢ㄦ埛缂栧彿
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- createTime?: Date; // 娴忚鏃堕棿
- }
-}
-
-/** 鑾峰緱鍟嗗搧娴忚璁板綍鍒嗛〉 */
-export function getBrowseHistoryPage(params: PageParam) {
- return requestClient.get<PageResult<MallHistoryApi.BrowseHistory>>(
- '/product/browse-history/page',
- { params },
- );
-}
diff --git a/src/api/mall/product/property/index.ts b/src/api/mall/product/property/index.ts
deleted file mode 100644
index df13591..0000000
--- a/src/api/mall/product/property/index.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallPropertyApi {
- /** 鍟嗗搧灞炴�� */
- export interface Property {
- id?: number; // 灞炴�х紪鍙�
- name: string; // 鍚嶇О
- remark?: string; // 澶囨敞
- }
-
- /** 灞炴�у�� */
- export interface PropertyValue {
- id?: number; // 灞炴�у�肩紪鍙�
- propertyId?: number; // 灞炴�ч」鐨勭紪鍙�
- name: string; // 鍚嶇О
- remark?: string; // 澶囨敞
- }
-}
-
-/** 鍒涘缓灞炴�ч」 */
-export function createProperty(data: MallPropertyApi.Property) {
- return requestClient.post('/product/property/create', data);
-}
-
-/** 鏇存柊灞炴�ч」 */
-export function updateProperty(data: MallPropertyApi.Property) {
- return requestClient.put('/product/property/update', data);
-}
-
-/** 鍒犻櫎灞炴�ч」 */
-export function deleteProperty(id: number) {
- return requestClient.delete(`/product/property/delete?id=${id}`);
-}
-
-/** 鑾峰緱灞炴�ч」 */
-export function getProperty(id: number) {
- return requestClient.get<MallPropertyApi.Property>(
- `/product/property/get?id=${id}`,
- );
-}
-
-/** 鑾峰緱灞炴�ч」鍒嗛〉 */
-export function getPropertyPage(params: PageParam) {
- return requestClient.get<PageResult<MallPropertyApi.Property>>(
- '/product/property/page',
- { params },
- );
-}
-
-/** 鑾峰緱灞炴�ч」绮剧畝鍒楄〃 */
-export function getPropertySimpleList() {
- return requestClient.get<MallPropertyApi.Property[]>(
- '/product/property/simple-list',
- );
-}
-
-/** 鑾峰緱灞炴�у�煎垎椤� */
-export function getPropertyValuePage(params: PageParam) {
- return requestClient.get<PageResult<MallPropertyApi.PropertyValue>>(
- '/product/property/value/page',
- { params },
- );
-}
-
-/** 鑾峰緱灞炴�у�� */
-export function getPropertyValue(id: number) {
- return requestClient.get<MallPropertyApi.PropertyValue>(
- `/product/property/value/get?id=${id}`,
- );
-}
-
-/** 鍒涘缓灞炴�у�� */
-export function createPropertyValue(data: MallPropertyApi.PropertyValue) {
- return requestClient.post('/product/property/value/create', data);
-}
-
-/** 鏇存柊灞炴�у�� */
-export function updatePropertyValue(data: MallPropertyApi.PropertyValue) {
- return requestClient.put('/product/property/value/update', data);
-}
-
-/** 鍒犻櫎灞炴�у�� */
-export function deletePropertyValue(id: number) {
- return requestClient.delete(`/product/property/value/delete?id=${id}`);
-}
-
-/** 鑾峰緱灞炴�у�肩簿绠�鍒楄〃 */
-export function getPropertyValueSimpleList(propertyId: number) {
- return requestClient.get<MallPropertyApi.PropertyValue[]>(
- '/product/property/value/simple-list',
- {
- params: { propertyId },
- },
- );
-}
diff --git a/src/api/mall/product/spu/index.ts b/src/api/mall/product/spu/index.ts
deleted file mode 100644
index 9e92153..0000000
--- a/src/api/mall/product/spu/index.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallSpuApi {
- /** 鍟嗗搧 SPU */
- export interface Spu {
- id?: number; // 鍟嗗搧缂栧彿
- name?: string; // 鍟嗗搧鍚嶇О
- categoryId?: number; // 鍟嗗搧鍒嗙被
- keyword?: string; // 鍏抽敭瀛�
- unit?: number; // 鍗曚綅
- picUrl?: string; // 鍟嗗搧灏侀潰鍥�
- sliderPicUrls?: string[]; // 鍟嗗搧杞挱鍥�
- introduction?: string; // 鍟嗗搧绠�浠�
- deliveryTypes?: number[]; // 閰嶉�佹柟寮�
- deliveryTemplateId?: number; // 杩愯垂妯$増
- brandId?: number; // 鍟嗗搧鍝佺墝缂栧彿
- specType?: boolean; // 鍟嗗搧瑙勬牸
- subCommissionType?: boolean; // 鍒嗛攢绫诲瀷
- skus?: Sku[]; // sku鏁扮粍
- description?: string; // 鍟嗗搧璇︽儏
- sort?: number; // 鍟嗗搧鎺掑簭
- giveIntegral?: number; // 璧犻�佺Н鍒�
- virtualSalesCount?: number; // 铏氭嫙閿�閲�
- price?: number; // 鍟嗗搧浠锋牸
- combinationPrice?: number; // 鍟嗗搧鎷煎洟浠锋牸
- seckillPrice?: number; // 鍟嗗搧绉掓潃浠锋牸
- salesCount?: number; // 鍟嗗搧閿�閲�
- marketPrice?: number; // 甯傚満浠�
- costPrice?: number; // 鎴愭湰浠�
- stock?: number; // 鍟嗗搧搴撳瓨
- createTime?: Date; // 鍟嗗搧鍒涘缓鏃堕棿
- status?: number; // 鍟嗗搧鐘舵��
- browseCount?: number; // 娴忚閲�
- }
-
- /** 鍟嗗搧 SKU */
- export interface Sku {
- id?: number; // 鍟嗗搧 SKU 缂栧彿
- name?: string; // 鍟嗗搧 SKU 鍚嶇О
- spuId?: number; // SPU 缂栧彿
- properties?: Property[]; // 灞炴�ф暟缁�
- price?: number | string; // 鍟嗗搧浠锋牸
- marketPrice?: number | string; // 甯傚満浠�
- costPrice?: number | string; // 鎴愭湰浠�
- barCode?: string; // 鍟嗗搧鏉$爜
- picUrl?: string; // 鍥剧墖鍦板潃
- stock?: number; // 搴撳瓨
- weight?: number; // 鍟嗗搧閲嶉噺锛屽崟浣嶏細kg 鍗冨厠
- volume?: number; // 鍟嗗搧浣撶Н锛屽崟浣嶏細m^3 骞崇背
- firstBrokeragePrice?: number | string; // 涓�绾у垎閿�鐨勪剑閲�
- secondBrokeragePrice?: number | string; // 浜岀骇鍒嗛攢鐨勪剑閲�
- salesCount?: number; // 鍟嗗搧閿�閲�
- }
-
- /** 鍟嗗搧灞炴�� */
- export interface Property {
- propertyId?: number; // 灞炴�х紪鍙�
- propertyName?: string; // 灞炴�у悕绉�
- valueId?: number; // 灞炴�у�肩紪鍙�
- valueName?: string; // 灞炴�у�煎悕绉�
- }
-
- /** 鍟嗗搧鐘舵�佹洿鏂拌姹� */
- export interface SpuStatusUpdateReqVO {
- id: number; // 鍟嗗搧缂栧彿
- status: number; // 鍟嗗搧鐘舵��
- }
-}
-
-/** 鑾峰緱鍟嗗搧 SPU 鍒楄〃 */
-export function getSpuPage(params: PageParam) {
- return requestClient.get<PageResult<MallSpuApi.Spu>>('/product/spu/page', {
- params,
- });
-}
-
-/** 鑾峰緱鍟嗗搧 SPU 鍒楄〃 tabsCount */
-export function getTabsCount() {
- return requestClient.get<Record<string, number>>('/product/spu/get-count');
-}
-
-/** 鍒涘缓鍟嗗搧 SPU */
-export function createSpu(data: MallSpuApi.Spu) {
- return requestClient.post('/product/spu/create', data);
-}
-
-/** 鏇存柊鍟嗗搧 SPU */
-export function updateSpu(data: MallSpuApi.Spu) {
- return requestClient.put('/product/spu/update', data);
-}
-
-/** 鏇存柊鍟嗗搧 SPU 鐘舵�� */
-export function updateStatus(data: MallSpuApi.SpuStatusUpdateReqVO) {
- return requestClient.put('/product/spu/update-status', data);
-}
-
-/** 鑾峰緱鍟嗗搧 SPU */
-export function getSpu(id: number) {
- return requestClient.get<MallSpuApi.Spu>(`/product/spu/get-detail?id=${id}`);
-}
-
-/** 鑾峰緱鍟嗗搧 SPU 璇︽儏鍒楄〃 */
-export function getSpuDetailList(ids: number[]) {
- return requestClient.get<MallSpuApi.Spu[]>(`/product/spu/list?spuIds=${ids}`);
-}
-
-/** 鍒犻櫎鍟嗗搧 SPU */
-export function deleteSpu(id: number) {
- return requestClient.delete(`/product/spu/delete?id=${id}`);
-}
-
-/** 瀵煎嚭鍟嗗搧 SPU Excel */
-export function exportSpu(params: PageParam) {
- return requestClient.download('/product/spu/export-excel', { params });
-}
-
-/** 鑾峰緱鍟嗗搧 SPU 绮剧畝鍒楄〃 */
-export function getSpuSimpleList() {
- return requestClient.get<MallSpuApi.Spu[]>('/product/spu/list-all-simple');
-}
diff --git a/src/api/mall/promotion/article/category/index.ts b/src/api/mall/promotion/article/category/index.ts
deleted file mode 100644
index 62dc386..0000000
--- a/src/api/mall/promotion/article/category/index.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallArticleCategoryApi {
- /** 鏂囩珷鍒嗙被 */
- export interface ArticleCategory {
- id: number; // 鍒嗙被缂栧彿
- name: string; // 鍒嗙被鍚嶇О
- picUrl: string; // 鍒嗙被鍥剧墖
- status: number; // 鐘舵��
- sort: number; // 鎺掑簭
- }
-}
-
-/** 鏌ヨ鏂囩珷鍒嗙被鍒楄〃 */
-export function getArticleCategoryPage(params: PageParam) {
- return requestClient.get<PageResult<MallArticleCategoryApi.ArticleCategory>>(
- '/promotion/article-category/page',
- { params },
- );
-}
-
-/** 鏌ヨ鏂囩珷鍒嗙被绮剧畝淇℃伅鍒楄〃 */
-export function getSimpleArticleCategoryList() {
- return requestClient.get<MallArticleCategoryApi.ArticleCategory[]>(
- '/promotion/article-category/list-all-simple',
- );
-}
-
-/** 鏌ヨ鏂囩珷鍒嗙被璇︽儏 */
-export function getArticleCategory(id: number) {
- return requestClient.get<MallArticleCategoryApi.ArticleCategory>(
- `/promotion/article-category/get?id=${id}`,
- );
-}
-
-/** 鏂板鏂囩珷鍒嗙被 */
-export function createArticleCategory(
- data: MallArticleCategoryApi.ArticleCategory,
-) {
- return requestClient.post('/promotion/article-category/create', data);
-}
-
-/** 淇敼鏂囩珷鍒嗙被 */
-export function updateArticleCategory(
- data: MallArticleCategoryApi.ArticleCategory,
-) {
- return requestClient.put('/promotion/article-category/update', data);
-}
-
-/** 鍒犻櫎鏂囩珷鍒嗙被 */
-export function deleteArticleCategory(id: number) {
- return requestClient.delete(`/promotion/article-category/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/article/index.ts b/src/api/mall/promotion/article/index.ts
deleted file mode 100644
index 789f51c..0000000
--- a/src/api/mall/promotion/article/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallArticleApi {
- /** 鏂囩珷绠$悊 */
- export interface Article {
- id: number; // 鏂囩珷缂栧彿
- categoryId: number; // 鍒嗙被缂栧彿
- title: string; // 鏂囩珷鏍囬
- author: string; // 浣滆��
- picUrl: string; // 灏侀潰鍥�
- introduction: string; // 鏂囩珷绠�浠�
- browseCount: string; // 娴忚鏁伴噺
- sort: number; // 鎺掑簭
- status: number; // 鐘舵��
- spuId: number; // 鍟嗗搧缂栧彿
- recommendHot: boolean; // 鏄惁鐑棬
- recommendBanner: boolean; // 鏄惁杞挱鍥�
- content: string; // 鏂囩珷鍐呭
- }
-}
-
-/** 鏌ヨ鏂囩珷绠$悊鍒楄〃 */
-export function getArticlePage(params: PageParam) {
- return requestClient.get<PageResult<MallArticleApi.Article>>(
- '/promotion/article/page',
- { params },
- );
-}
-
-/** 鏌ヨ鏂囩珷绠$悊璇︽儏 */
-export function getArticle(id: number) {
- return requestClient.get<MallArticleApi.Article>(
- `/promotion/article/get?id=${id}`,
- );
-}
-
-/** 鏂板鏂囩珷绠$悊 */
-export function createArticle(data: MallArticleApi.Article) {
- return requestClient.post('/promotion/article/create', data);
-}
-
-/** 淇敼鏂囩珷绠$悊 */
-export function updateArticle(data: MallArticleApi.Article) {
- return requestClient.put('/promotion/article/update', data);
-}
-
-/** 鍒犻櫎鏂囩珷绠$悊 */
-export function deleteArticle(id: number) {
- return requestClient.delete(`/promotion/article/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/banner/index.ts b/src/api/mall/promotion/banner/index.ts
deleted file mode 100644
index a23c215..0000000
--- a/src/api/mall/promotion/banner/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBannerApi {
- /** Banner 淇℃伅 */
- export interface Banner {
- id: number; // Banner 缂栧彿
- title: string; // Banner 鏍囬
- picUrl: string; // Banner 鍥剧墖
- status: number; // 鐘舵��
- url: string; // 閾炬帴鍦板潃
- position: number; // Banner 浣嶇疆
- sort: number; // 鎺掑簭
- memo: string; // 澶囨敞
- }
-}
-
-/** 鏌ヨ Banner 绠$悊鍒楄〃 */
-export function getBannerPage(params: PageParam) {
- return requestClient.get<PageResult<MallBannerApi.Banner>>(
- '/promotion/banner/page',
- { params },
- );
-}
-
-/** 鏌ヨ Banner 绠$悊璇︽儏 */
-export function getBanner(id: number) {
- return requestClient.get<MallBannerApi.Banner>(
- `/promotion/banner/get?id=${id}`,
- );
-}
-
-/** 鏂板 Banner 绠$悊 */
-export function createBanner(data: MallBannerApi.Banner) {
- return requestClient.post('/promotion/banner/create', data);
-}
-
-/** 淇敼 Banner 绠$悊 */
-export function updateBanner(data: MallBannerApi.Banner) {
- return requestClient.put('/promotion/banner/update', data);
-}
-
-/** 鍒犻櫎 Banner 绠$悊 */
-export function deleteBanner(id: number) {
- return requestClient.delete(`/promotion/banner/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/bargain/bargainActivity.ts b/src/api/mall/promotion/bargain/bargainActivity.ts
deleted file mode 100644
index 2a7d564..0000000
--- a/src/api/mall/promotion/bargain/bargainActivity.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBargainActivityApi {
- /** 鐮嶄环娲诲姩 */
- export interface BargainActivity {
- id?: number; // 娲诲姩缂栧彿
- name?: string; // 娲诲姩鍚嶇О
- startTime?: Date; // 寮�濮嬫椂闂�
- endTime?: Date; // 缁撴潫鏃堕棿
- status?: number; // 鐘舵��
- helpMaxCount?: number; // 杈惧埌璇ヤ汉鏁帮紝鎵嶈兘鐮嶅埌浣庝环
- bargainCount?: number; // 鏈�澶у府鐮嶆鏁�
- totalLimitCount?: number; // 鏈�澶ц喘涔版鏁�
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- bargainFirstPrice: number; // 鐮嶄环璧峰浠锋牸锛屽崟浣嶅垎
- bargainMinPrice: number; // 鐮嶄环搴曚环
- stock: number; // 娲诲姩搴撳瓨
- randomMinPrice?: number; // 鐢ㄦ埛姣忔鐮嶄环鐨勬渶灏忛噾棰濓紝鍗曚綅锛氬垎
- randomMaxPrice?: number; // 鐢ㄦ埛姣忔鐮嶄环鐨勬渶澶ч噾棰濓紝鍗曚綅锛氬垎
- }
-
- /** 鐮嶄环娲诲姩鎵�闇�灞炴�с�傞�夋嫨鐨勫晢鍝佸拰灞炴�х殑鏃跺�欎娇鐢ㄦ柟渚夸娇鐢ㄦ椿鍔ㄧ殑閫氱敤灏佽 */
- export interface BargainProduct {
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- bargainFirstPrice: number; // 鐮嶄环璧峰浠锋牸锛屽崟浣嶅垎
- bargainMinPrice: number; // 鐮嶄环搴曚环
- stock: number; // 娲诲姩搴撳瓨
- }
-}
-
-/** 鏌ヨ鐮嶄环娲诲姩鍒楄〃 */
-export function getBargainActivityPage(params: PageParam) {
- return requestClient.get<PageResult<MallBargainActivityApi.BargainActivity>>(
- '/promotion/bargain-activity/page',
- { params },
- );
-}
-
-/** 鏌ヨ鐮嶄环娲诲姩璇︽儏 */
-export function getBargainActivity(id: number) {
- return requestClient.get<MallBargainActivityApi.BargainActivity>(
- `/promotion/bargain-activity/get?id=${id}`,
- );
-}
-
-/** 鏂板鐮嶄环娲诲姩 */
-export function createBargainActivity(
- data: MallBargainActivityApi.BargainActivity,
-) {
- return requestClient.post('/promotion/bargain-activity/create', data);
-}
-
-/** 淇敼鐮嶄环娲诲姩 */
-export function updateBargainActivity(
- data: MallBargainActivityApi.BargainActivity,
-) {
- return requestClient.put('/promotion/bargain-activity/update', data);
-}
-
-/** 鍏抽棴鐮嶄环娲诲姩 */
-export function closeBargainActivity(id: number) {
- return requestClient.put(`/promotion/bargain-activity/close?id=${id}`);
-}
-
-/** 鍒犻櫎鐮嶄环娲诲姩 */
-export function deleteBargainActivity(id: number) {
- return requestClient.delete(`/promotion/bargain-activity/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/bargain/bargainHelp.ts b/src/api/mall/promotion/bargain/bargainHelp.ts
deleted file mode 100644
index 746edac..0000000
--- a/src/api/mall/promotion/bargain/bargainHelp.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBargainHelpApi {
- /** 鐮嶄环璁板綍 */
- export interface BargainHelp {
- id: number; // 璁板綍缂栧彿
- record: number; // 鐮嶄环璁板綍缂栧彿
- userId: number; // 鐢ㄦ埛缂栧彿
- reducePrice: number; // 鐮嶆帀閲戦
- endTime: Date; // 缁撴潫鏃堕棿
- }
-}
-
-/** 鏌ヨ鐮嶄环璁板綍鍒楄〃 */
-export function getBargainHelpPage(params: PageParam) {
- return requestClient.get<PageResult<MallBargainHelpApi.BargainHelp>>(
- '/promotion/bargain-help/page',
- { params },
- );
-}
diff --git a/src/api/mall/promotion/bargain/bargainRecord.ts b/src/api/mall/promotion/bargain/bargainRecord.ts
deleted file mode 100644
index 138e030..0000000
--- a/src/api/mall/promotion/bargain/bargainRecord.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBargainRecordApi {
- /** 鐮嶄环璁板綍 */
- export interface BargainRecord {
- id: number; // 璁板綍缂栧彿
- activityId: number; // 娲诲姩缂栧彿
- userId: number; // 鐢ㄦ埛缂栧彿
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- bargainFirstPrice: number; // 鐮嶄环璧峰浠锋牸
- bargainPrice: number; // 鐮嶄环浠锋牸
- status: number; // 鐘舵��
- orderId: number; // 璁㈠崟缂栧彿
- endTime: Date; // 缁撴潫鏃堕棿
- }
-}
-
-/** 鏌ヨ鐮嶄环璁板綍鍒楄〃 */
-export function getBargainRecordPage(params: PageParam) {
- return requestClient.get<PageResult<MallBargainRecordApi.BargainRecord>>(
- '/promotion/bargain-record/page',
- { params },
- );
-}
diff --git a/src/api/mall/promotion/combination/combinationActivity.ts b/src/api/mall/promotion/combination/combinationActivity.ts
deleted file mode 100644
index 5e44663..0000000
--- a/src/api/mall/promotion/combination/combinationActivity.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallCombinationActivityApi {
- /** 鎷煎洟娲诲姩 */
- export interface CombinationActivity {
- id?: number; // 娲诲姩缂栧彿
- name?: string; // 娲诲姩鍚嶇О
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- spuName?: string; // 鍟嗗搧 SPU 鍚嶇О
- totalLimitCount?: number; // 鎬婚檺璐暟閲�
- singleLimitCount?: number; // 鍗曟闄愯喘鏁伴噺
- startTime?: Date; // 寮�濮嬫椂闂�
- endTime?: Date; // 缁撴潫鏃堕棿
- userSize?: number; // 鐢ㄦ埛鏁伴噺
- totalCount?: number; // 鎬绘暟閲�
- successCount?: number; // 鎴愬姛鏁伴噺
- orderUserCount?: number; // 璁㈠崟鐢ㄦ埛鏁伴噺
- virtualGroup?: number; // 铏氭嫙鎴愬洟
- status?: number; // 鐘舵��
- limitDuration?: number; // 闄愬埗鏃堕暱
- combinationPrice?: number; // 鎷煎洟浠锋牸
- products: CombinationProduct[]; // 鍟嗗搧鍒楄〃
- picUrl?: string; // 鍟嗗搧鍥剧墖
- }
-
- /** 鎷煎洟娲诲姩鎵�闇�灞炴�� */
- export interface CombinationProduct {
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- combinationPrice: number; // 鎷煎洟浠锋牸
- }
-}
-
-/** 鏌ヨ鎷煎洟娲诲姩鍒楄〃 */
-export function getCombinationActivityPage(params: PageParam) {
- return requestClient.get<
- PageResult<MallCombinationActivityApi.CombinationActivity>
- >('/promotion/combination-activity/page', { params });
-}
-
-/** 鏌ヨ鎷煎洟娲诲姩璇︽儏 */
-export function getCombinationActivity(id: number) {
- return requestClient.get<MallCombinationActivityApi.CombinationActivity>(
- `/promotion/combination-activity/get?id=${id}`,
- );
-}
-
-/** 鑾峰緱鎷煎洟娲诲姩鍒楄〃锛屽熀浜庢椿鍔ㄧ紪鍙锋暟缁� */
-export function getCombinationActivityListByIds(ids: number[]) {
- return requestClient.get<MallCombinationActivityApi.CombinationActivity[]>(
- `/promotion/combination-activity/list-by-ids?ids=${ids}`,
- );
-}
-
-/** 鏂板鎷煎洟娲诲姩 */
-export function createCombinationActivity(
- data: MallCombinationActivityApi.CombinationActivity,
-) {
- return requestClient.post('/promotion/combination-activity/create', data);
-}
-
-/** 淇敼鎷煎洟娲诲姩 */
-export function updateCombinationActivity(
- data: MallCombinationActivityApi.CombinationActivity,
-) {
- return requestClient.put('/promotion/combination-activity/update', data);
-}
-
-/** 鍏抽棴鎷煎洟娲诲姩 */
-export function closeCombinationActivity(id: number) {
- return requestClient.put(`/promotion/combination-activity/close?id=${id}`);
-}
-
-/** 鍒犻櫎鎷煎洟娲诲姩 */
-export function deleteCombinationActivity(id: number) {
- return requestClient.delete(
- `/promotion/combination-activity/delete?id=${id}`,
- );
-}
diff --git a/src/api/mall/promotion/combination/combinationRecord.ts b/src/api/mall/promotion/combination/combinationRecord.ts
deleted file mode 100644
index d2b090c..0000000
--- a/src/api/mall/promotion/combination/combinationRecord.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallCombinationRecordApi {
- /** 鎷煎洟璁板綍 */
- export interface CombinationRecord {
- id: number; // 鎷煎洟璁板綍缂栧彿
- activityId: number; // 鎷煎洟娲诲姩缂栧彿
- nickname: string; // 鐢ㄦ埛鏄电О
- avatar: string; // 鐢ㄦ埛澶村儚
- headId: number; // 鍥㈤暱缂栧彿
- expireTime: string; // 杩囨湡鏃堕棿
- userSize: number; // 鍙弬鍥汉鏁�
- userCount: number; // 宸插弬鍥汉鏁�
- status: number; // 鎷煎洟鐘舵��
- spuName: string; // 鍟嗗搧鍚嶅瓧
- picUrl: string; // 鍟嗗搧鍥剧墖
- virtualGroup: boolean; // 鏄惁铏氭嫙鎴愬洟
- startTime: string; // 寮�濮嬫椂闂� (璁㈠崟浠樻鍚庡紑濮嬬殑鏃堕棿)
- endTime: string; // 缁撴潫鏃堕棿锛堟垚鍥㈡椂闂�/澶辫触鏃堕棿锛�
- }
-
- /** 鎷煎洟璁板綍姒傝淇℃伅 */
- export interface RecordSummary {
- pendingCount: number; // 寰呮垚鍥㈡暟閲�
- successCount: number; // 宸叉垚鍥㈡暟閲�
- failCount: number; // 宸插け璐ユ暟閲�
- }
-}
-
-/** 鏌ヨ鎷煎洟璁板綍鍒楄〃 */
-export function getCombinationRecordPage(params: PageParam) {
- return requestClient.get<
- PageResult<MallCombinationRecordApi.CombinationRecord>
- >('/promotion/combination-record/page', { params });
-}
-
-/** 鑾峰緱鎷煎洟璁板綍鐨勬瑕佷俊鎭� */
-export function getCombinationRecordSummary() {
- return requestClient.get<MallCombinationRecordApi.RecordSummary>(
- '/promotion/combination-record/get-summary',
- );
-}
diff --git a/src/api/mall/promotion/coupon/coupon.ts b/src/api/mall/promotion/coupon/coupon.ts
deleted file mode 100644
index 6b4f2df..0000000
--- a/src/api/mall/promotion/coupon/coupon.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallCouponApi {
- /** 浼樻儬鍒� */
- export interface Coupon {
- id: number; // 浼樻儬鍒哥紪鍙�
- name: string; // 浼樻儬鍒稿悕绉�
- status: number; // 浼樻儬鍒哥姸鎬�
- type: number; // 浼樻儬鍒哥被鍨�
- price: number; // 浼樻儬鍒搁噾棰�
- usePrice: number; // 浣跨敤闂ㄦ
- productScope: number; // 鍟嗗搧鑼冨洿
- productSpuIds: number[]; // 鍟嗗搧缂栧彿鏁扮粍
- validityType: number; // 鏈夋晥鏈熺被鍨�
- validStartTime: Date; // 鍥哄畾鏃ユ湡-鐢熸晥寮�濮嬫椂闂�
- validEndTime: Date; // 鍥哄畾鏃ユ湡-鐢熸晥缁撴潫鏃堕棿
- fixedStartTerm: number; // 棰嗗彇鏃ユ湡-寮�濮嬪ぉ鏁�
- fixedEndTerm: number; // 棰嗗彇鏃ユ湡-缁撴潫澶╂暟
- takeLimitCount: number; // 姣忎汉闄愰涓暟
- usePriceEnabled: boolean; // 鏄惁璁剧疆婊″灏戦噾棰濆彲鐢�
- productCategoryIds: number[]; // 鍟嗗搧鍒嗙被缂栧彿鏁扮粍
- }
-
- /** 鍙戦�佷紭鎯犲埜 */
- export interface CouponSendReqVO {
- templateId: number; // 浼樻儬鍒哥紪鍙�
- userIds: number[]; // 鐢ㄦ埛缂栧彿鏁扮粍
- }
-}
-
-/** 鍒犻櫎浼樻儬鍔� */
-export function deleteCoupon(id: number) {
- return requestClient.delete(`/promotion/coupon/delete?id=${id}`);
-}
-
-/** 鑾峰緱浼樻儬鍔靛垎椤� */
-export function getCouponPage(params: PageParam) {
- return requestClient.get<PageResult<MallCouponApi.Coupon>>(
- '/promotion/coupon/page',
- { params },
- );
-}
-
-/** 鍙戦�佷紭鎯犲埜 */
-export function sendCoupon(data: MallCouponApi.CouponSendReqVO) {
- return requestClient.post('/promotion/coupon/send', data);
-}
diff --git a/src/api/mall/promotion/coupon/couponTemplate.ts b/src/api/mall/promotion/coupon/couponTemplate.ts
deleted file mode 100644
index 164bb2f..0000000
--- a/src/api/mall/promotion/coupon/couponTemplate.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallCouponTemplateApi {
- /** 浼樻儬鍒告ā鏉� */
- export interface CouponTemplate {
- id: number; // 妯℃澘缂栧彿
- name: string; // 妯℃澘鍚嶇О
- status: number; // 鐘舵��
- totalCount: number; // 鍙戞斁鏁伴噺
- takeLimitCount: number; // 姣忎汉闄愰涓暟
- takeType: number; // 棰嗗彇鏂瑰紡
- usePrice: number; // 浣跨敤闂ㄦ
- productScope: number; // 鍟嗗搧鑼冨洿
- productScopeValues: number[]; // 鍟嗗搧鑼冨洿鍊�
- validityType: number; // 鏈夋晥鏈熺被鍨�
- validStartTime: Date; // 鍥哄畾鏃ユ湡-鐢熸晥寮�濮嬫椂闂�
- validEndTime: Date; // 鍥哄畾鏃ユ湡-鐢熸晥缁撴潫鏃堕棿
- fixedStartTerm: number; // 棰嗗彇鏃ユ湡-寮�濮嬪ぉ鏁�
- fixedEndTerm: number; // 棰嗗彇鏃ユ湡-缁撴潫澶╂暟
- discountType: number; // 浼樻儬绫诲瀷
- discountPercent?: number; // 鎶樻墸鐧惧垎姣�
- discountPrice: number; // 浼樻儬閲戦
- discountLimitPrice?: number; // 鎶樻墸涓婇檺
- takeCount: number; // 宸查鍙栨暟閲�
- useCount: number; // 宸蹭娇鐢ㄦ暟閲�
- }
-}
-
-/** 鍒涘缓浼樻儬鍔垫ā鏉� */
-export function createCouponTemplate(
- data: MallCouponTemplateApi.CouponTemplate,
-) {
- return requestClient.post('/promotion/coupon-template/create', data);
-}
-
-/** 鏇存柊浼樻儬鍔垫ā鏉� */
-export function updateCouponTemplate(
- data: MallCouponTemplateApi.CouponTemplate,
-) {
- return requestClient.put('/promotion/coupon-template/update', data);
-}
-
-/** 鏇存柊浼樻儬鍔垫ā鏉跨殑鐘舵�� */
-export function updateCouponTemplateStatus(id: number, status: number) {
- return requestClient.put('/promotion/coupon-template/update-status', {
- id,
- status,
- });
-}
-
-/** 鍒犻櫎浼樻儬鍔垫ā鏉� */
-export function deleteCouponTemplate(id: number) {
- return requestClient.delete(`/promotion/coupon-template/delete?id=${id}`);
-}
-
-/** 鑾峰緱浼樻儬鍔垫ā鏉� */
-export function getCouponTemplate(id: number) {
- return requestClient.get<MallCouponTemplateApi.CouponTemplate>(
- `/promotion/coupon-template/get?id=${id}`,
- );
-}
-
-/** 鑾峰緱浼樻儬鍔垫ā鏉垮垎椤� */
-export function getCouponTemplatePage(params: PageParam) {
- return requestClient.get<PageResult<MallCouponTemplateApi.CouponTemplate>>(
- '/promotion/coupon-template/page',
- { params },
- );
-}
-
-/** 鑾峰緱浼樻儬鍔垫ā鏉垮垪琛� */
-export function getCouponTemplateList(ids: number[]) {
- return requestClient.get<MallCouponTemplateApi.CouponTemplate[]>(
- `/promotion/coupon-template/list?ids=${ids}`,
- );
-}
diff --git a/src/api/mall/promotion/discount/discountActivity.ts b/src/api/mall/promotion/discount/discountActivity.ts
deleted file mode 100644
index c2b0208..0000000
--- a/src/api/mall/promotion/discount/discountActivity.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDiscountActivityApi {
- /** 闄愭椂鎶樻墸鐩稿叧灞炴�� */
- export interface DiscountProduct {
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- discountType: number; // 鎶樻墸绫诲瀷
- discountPercent: number; // 鎶樻墸鐧惧垎姣�
- discountPrice: number; // 鎶樻墸浠锋牸
- }
-
- /** 闄愭椂鎶樻墸娲诲姩 */
- export interface DiscountActivity {
- id?: number; // 娲诲姩缂栧彿
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- name?: string; // 娲诲姩鍚嶇О
- status?: number; // 鐘舵��
- remark?: string; // 澶囨敞
- startTime?: Date; // 寮�濮嬫椂闂�
- endTime?: Date; // 缁撴潫鏃堕棿
- products?: DiscountProduct[]; // 鍟嗗搧鍒楄〃
- }
-}
-
-/** 鏌ヨ闄愭椂鎶樻墸娲诲姩鍒楄〃 */
-export function getDiscountActivityPage(params: PageParam) {
- return requestClient.get<
- PageResult<MallDiscountActivityApi.DiscountActivity>
- >('/promotion/discount-activity/page', { params });
-}
-
-/** 鏌ヨ闄愭椂鎶樻墸娲诲姩璇︽儏 */
-export function getDiscountActivity(id: number) {
- return requestClient.get<MallDiscountActivityApi.DiscountActivity>(
- `/promotion/discount-activity/get?id=${id}`,
- );
-}
-
-/** 鏂板闄愭椂鎶樻墸娲诲姩 */
-export function createDiscountActivity(
- data: MallDiscountActivityApi.DiscountActivity,
-) {
- return requestClient.post('/promotion/discount-activity/create', data);
-}
-
-/** 淇敼闄愭椂鎶樻墸娲诲姩 */
-export function updateDiscountActivity(
- data: MallDiscountActivityApi.DiscountActivity,
-) {
- return requestClient.put('/promotion/discount-activity/update', data);
-}
-
-/** 鍏抽棴闄愭椂鎶樻墸娲诲姩 */
-export function closeDiscountActivity(id: number) {
- return requestClient.put(`/promotion/discount-activity/close?id=${id}`);
-}
-
-/** 鍒犻櫎闄愭椂鎶樻墸娲诲姩 */
-export function deleteDiscountActivity(id: number) {
- return requestClient.delete(`/promotion/discount-activity/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/diy/page.ts b/src/api/mall/promotion/diy/page.ts
deleted file mode 100644
index d4a2555..0000000
--- a/src/api/mall/promotion/diy/page.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDiyPageApi {
- /** 瑁呬慨椤甸潰 */
- export interface DiyPage {
- id?: number; // 椤甸潰缂栧彿
- templateId?: number; // 妯℃澘缂栧彿
- name: string; // 椤甸潰鍚嶇О
- remark: string; // 澶囨敞
- previewPicUrls: string[]; // 棰勮鍥剧墖鍦板潃鏁扮粍
- property: string; // 椤甸潰灞炴��
- }
-}
-
-/** 鏌ヨ瑁呬慨椤甸潰鍒楄〃 */
-export function getDiyPagePage(params: PageParam) {
- return requestClient.get<PageResult<MallDiyPageApi.DiyPage>>(
- '/promotion/diy-page/page',
- { params },
- );
-}
-
-/** 鏌ヨ瑁呬慨椤甸潰璇︽儏 */
-export function getDiyPage(id: number) {
- return requestClient.get<MallDiyPageApi.DiyPage>(
- `/promotion/diy-page/get?id=${id}`,
- );
-}
-
-/** 鏂板瑁呬慨椤甸潰 */
-export function createDiyPage(data: MallDiyPageApi.DiyPage) {
- return requestClient.post('/promotion/diy-page/create', data);
-}
-
-/** 淇敼瑁呬慨椤甸潰 */
-export function updateDiyPage(data: MallDiyPageApi.DiyPage) {
- return requestClient.put('/promotion/diy-page/update', data);
-}
-
-/** 鍒犻櫎瑁呬慨椤甸潰 */
-export function deleteDiyPage(id: number) {
- return requestClient.delete(`/promotion/diy-page/delete?id=${id}`);
-}
-
-/** 鑾峰緱瑁呬慨椤甸潰灞炴�� */
-export function getDiyPageProperty(id: number) {
- return requestClient.get(`/promotion/diy-page/get-property?id=${id}`);
-}
-
-/** 鏇存柊瑁呬慨椤甸潰灞炴�� */
-export function updateDiyPageProperty(data: MallDiyPageApi.DiyPage) {
- return requestClient.put('/promotion/diy-page/update-property', data);
-}
diff --git a/src/api/mall/promotion/diy/template.ts b/src/api/mall/promotion/diy/template.ts
deleted file mode 100644
index 21e8231..0000000
--- a/src/api/mall/promotion/diy/template.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import type { MallDiyPageApi } from './page';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDiyTemplateApi {
- /** 瑁呬慨妯℃澘 */
- export interface DiyTemplate {
- id?: number; // 妯℃澘缂栧彿
- name: string; // 妯℃澘鍚嶇О
- used: boolean; // 鏄惁浣跨敤
- usedTime?: Date; // 浣跨敤鏃堕棿
- remark: string; // 澶囨敞
- previewPicUrls: string[]; // 棰勮鍥剧墖鍦板潃鏁扮粍
- property: string; // 妯℃澘灞炴��
- }
-
- /** 瑁呬慨妯℃澘灞炴�э紙鍖呭惈椤甸潰鍒楄〃锛� */
- export interface DiyTemplateProperty extends DiyTemplate {
- pages: MallDiyPageApi.DiyPage[]; // 椤甸潰鍒楄〃
- }
-}
-
-/** 鏌ヨ瑁呬慨妯℃澘鍒楄〃 */
-export function getDiyTemplatePage(params: PageParam) {
- return requestClient.get<PageResult<MallDiyTemplateApi.DiyTemplate>>(
- '/promotion/diy-template/page',
- { params },
- );
-}
-
-/** 鏌ヨ瑁呬慨妯℃澘璇︽儏 */
-export function getDiyTemplate(id: number) {
- return requestClient.get<MallDiyTemplateApi.DiyTemplate>(
- `/promotion/diy-template/get?id=${id}`,
- );
-}
-
-/** 鏂板瑁呬慨妯℃澘 */
-export function createDiyTemplate(data: MallDiyTemplateApi.DiyTemplate) {
- return requestClient.post('/promotion/diy-template/create', data);
-}
-
-/** 淇敼瑁呬慨妯℃澘 */
-export function updateDiyTemplate(data: MallDiyTemplateApi.DiyTemplate) {
- return requestClient.put('/promotion/diy-template/update', data);
-}
-
-/** 鍒犻櫎瑁呬慨妯℃澘 */
-export function deleteDiyTemplate(id: number) {
- return requestClient.delete(`/promotion/diy-template/delete?id=${id}`);
-}
-
-/** 浣跨敤瑁呬慨妯℃澘 */
-export function useDiyTemplate(id: number) {
- return requestClient.put(`/promotion/diy-template/use?id=${id}`);
-}
-
-/** 鑾峰緱瑁呬慨妯℃澘灞炴�� */
-export function getDiyTemplateProperty(id: number) {
- return requestClient.get<MallDiyTemplateApi.DiyTemplateProperty>(
- `/promotion/diy-template/get-property?id=${id}`,
- );
-}
-
-/** 鏇存柊瑁呬慨妯℃澘灞炴�� */
-export function updateDiyTemplateProperty(
- data: MallDiyTemplateApi.DiyTemplate,
-) {
- return requestClient.put('/promotion/diy-template/update-property', data);
-}
diff --git a/src/api/mall/promotion/kefu/conversation/index.ts b/src/api/mall/promotion/kefu/conversation/index.ts
deleted file mode 100644
index 2b647a0..0000000
--- a/src/api/mall/promotion/kefu/conversation/index.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MallKefuConversationApi {
- /** 瀹㈡湇浼氳瘽 */
- export interface Conversation {
- id: number; // 缂栧彿
- userId: number; // 浼氳瘽鎵�灞炵敤鎴�
- userAvatar: string; // 浼氳瘽鎵�灞炵敤鎴峰ご鍍�
- userNickname: string; // 浼氳瘽鎵�灞炵敤鎴锋樀绉�
- lastMessageTime: Date; // 鏈�鍚庤亰澶╂椂闂�
- lastMessageContent: string; // 鏈�鍚庤亰澶╁唴瀹�
- lastMessageContentType: number; // 鏈�鍚庡彂閫佺殑娑堟伅绫诲瀷
- adminPinned: boolean; // 绠$悊绔疆椤�
- userDeleted: boolean; // 鐢ㄦ埛鏄惁鍙
- adminDeleted: boolean; // 绠$悊鍛樻槸鍚﹀彲瑙�
- adminUnreadMessageCount: number; // 绠$悊鍛樻湭璇绘秷鎭暟
- createTime?: string; // 鍒涘缓鏃堕棿
- }
-
- /** 浼氳瘽缃《璇锋眰 */
- export interface ConversationPinnedUpdateReqVO {
- id: number; // 浼氳瘽缂栧彿
- pinned: boolean; // 鏄惁缃《
- }
-}
-
-/** 鑾峰緱瀹㈡湇浼氳瘽鍒楄〃 */
-export function getConversationList() {
- return requestClient.get<MallKefuConversationApi.Conversation[]>(
- '/promotion/kefu-conversation/list',
- );
-}
-
-/** 鑾峰緱瀹㈡湇浼氳瘽 */
-export function getConversation(id: number) {
- return requestClient.get<MallKefuConversationApi.Conversation>(
- `/promotion/kefu-conversation/get?id=${id}`,
- );
-}
-
-/** 瀹㈡湇浼氳瘽缃《 */
-export function updateConversationPinned(
- data: MallKefuConversationApi.ConversationPinnedUpdateReqVO,
-) {
- return requestClient.put(
- '/promotion/kefu-conversation/update-conversation-pinned',
- data,
- );
-}
-
-/** 鍒犻櫎瀹㈡湇浼氳瘽 */
-export function deleteConversation(id: number) {
- return requestClient.delete(`/promotion/kefu-conversation/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/kefu/message/index.ts b/src/api/mall/promotion/kefu/message/index.ts
deleted file mode 100644
index 9312354..0000000
--- a/src/api/mall/promotion/kefu/message/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallKefuMessageApi {
- /** 瀹㈡湇娑堟伅 */
- export interface Message {
- id: number; // 缂栧彿
- conversationId: number; // 浼氳瘽缂栧彿
- senderId: number; // 鍙戦�佷汉缂栧彿
- senderAvatar: string; // 鍙戦�佷汉澶村儚
- senderType: number; // 鍙戦�佷汉绫诲瀷
- receiverId: number; // 鎺ユ敹浜虹紪鍙�
- receiverType: number; // 鎺ユ敹浜虹被鍨�
- contentType: number; // 娑堟伅绫诲瀷
- content: string; // 娑堟伅鍐呭
- readStatus: boolean; // 鏄惁宸茶
- createTime: Date; // 鍒涘缓鏃堕棿
- }
-
- /** 鍙戦�佹秷鎭姹� */
- export interface MessageSend {
- conversationId: number; // 浼氳瘽缂栧彿
- contentType: number; // 娑堟伅绫诲瀷
- content: string; // 娑堟伅鍐呭
- }
-}
-
-/** 鍙戦�佸鏈嶆秷鎭� */
-export function sendKeFuMessage(data: MallKefuMessageApi.MessageSend) {
- return requestClient.post('/promotion/kefu-message/send', data);
-}
-
-/** 鏇存柊瀹㈡湇娑堟伅宸茶鐘舵�� */
-export function updateKeFuMessageReadStatus(conversationId: number) {
- return requestClient.put(
- `/promotion/kefu-message/update-read-status?conversationId=${conversationId}`,
- );
-}
-
-/** 鑾峰緱娑堟伅鍒楄〃锛堟祦寮忓姞杞斤級 */
-export function getKeFuMessageList(params: PageParam) {
- return requestClient.get<PageResult<MallKefuMessageApi.Message>>(
- '/promotion/kefu-message/list',
- { params },
- );
-}
diff --git a/src/api/mall/promotion/point/index.ts b/src/api/mall/promotion/point/index.ts
deleted file mode 100644
index fab08b4..0000000
--- a/src/api/mall/promotion/point/index.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallPointActivityApi {
- /** 绉垎鍟嗗煄鍟嗗搧 */
- export interface PointProduct {
- id?: number; // 绉垎鍟嗗煄鍟嗗搧缂栧彿
- activityId?: number; // 绉垎鍟嗗煄娲诲姩 id
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- count: number; // 鍙厬鎹㈡暟閲�
- point: number; // 鍏戞崲绉垎
- price: number; // 鍏戞崲閲戦锛屽崟浣嶏細鍒�
- stock: number; // 绉垎鍟嗗煄鍟嗗搧搴撳瓨
- activityStatus?: number; // 绉垎鍟嗗煄鍟嗗搧鐘舵��
- }
-
- /** 绉垎鍟嗗煄娲诲姩 */
- export interface PointActivity {
- id: number; // 绉垎鍟嗗煄娲诲姩缂栧彿
- spuId: number; // 绉垎鍟嗗煄娲诲姩鍟嗗搧
- status: number; // 娲诲姩鐘舵��
- stock: number; // 绉垎鍟嗗煄娲诲姩搴撳瓨
- totalStock: number; // 绉垎鍟嗗煄娲诲姩鎬诲簱瀛�
- remark?: string; // 澶囨敞
- sort: number; // 鎺掑簭
- createTime: string; // 鍒涘缓鏃堕棿
- products: PointProduct[]; // 绉垎鍟嗗煄鍟嗗搧
- spuName: string; // 鍟嗗搧鍚嶇О
- picUrl: string; // 鍟嗗搧涓诲浘
- marketPrice: number; // 鍟嗗搧甯傚満浠凤紝鍗曚綅锛氬垎
- point: number; // 鍏戞崲绉垎
- price: number; // 鍏戞崲閲戦锛屽崟浣嶏細鍒�
- }
-
- /** 鎵╁睍 SPU 閰嶇疆锛堝甫绉垎淇℃伅锛� */
- export interface SpuExtensionWithPoint extends MallSpuApi.Spu {
- pointStock: number; // 绉垎鍟嗗煄娲诲姩搴撳瓨
- pointTotalStock: number; // 绉垎鍟嗗煄娲诲姩鎬诲簱瀛�
- point: number; // 鍏戞崲绉垎
- pointPrice: number; // 鍏戞崲閲戦锛屽崟浣嶏細鍒�
- }
-}
-
-/** 鏌ヨ绉垎鍟嗗煄娲诲姩鍒嗛〉 */
-export function getPointActivityPage(params: PageParam) {
- return requestClient.get<PageResult<MallPointActivityApi.PointActivity>>(
- '/promotion/point-activity/page',
- { params },
- );
-}
-
-/** 鏌ヨ绉垎鍟嗗煄娲诲姩璇︽儏 */
-export function getPointActivity(id: number) {
- return requestClient.get<MallPointActivityApi.PointActivity>(
- `/promotion/point-activity/get?id=${id}`,
- );
-}
-
-/** 鏌ヨ绉垎鍟嗗煄娲诲姩鍒楄〃锛屽熀浜庢椿鍔ㄧ紪鍙锋暟缁� */
-export function getPointActivityListByIds(ids: number[]) {
- return requestClient.get<MallPointActivityApi.PointActivity[]>(
- `/promotion/point-activity/list-by-ids?ids=${ids}`,
- );
-}
-
-/** 鏂板绉垎鍟嗗煄娲诲姩 */
-export function createPointActivity(data: MallPointActivityApi.PointActivity) {
- return requestClient.post('/promotion/point-activity/create', data);
-}
-
-/** 淇敼绉垎鍟嗗煄娲诲姩 */
-export function updatePointActivity(data: MallPointActivityApi.PointActivity) {
- return requestClient.put('/promotion/point-activity/update', data);
-}
-
-/** 鍒犻櫎绉垎鍟嗗煄娲诲姩 */
-export function deletePointActivity(id: number) {
- return requestClient.delete(`/promotion/point-activity/delete?id=${id}`);
-}
-
-/** 鍏抽棴绉垎鍟嗗煄娲诲姩 */
-export function closePointActivity(id: number) {
- return requestClient.put(`/promotion/point-activity/close?id=${id}`);
-}
diff --git a/src/api/mall/promotion/reward/rewardActivity.ts b/src/api/mall/promotion/reward/rewardActivity.ts
deleted file mode 100644
index e1cdd55..0000000
--- a/src/api/mall/promotion/reward/rewardActivity.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallRewardActivityApi {
- /** 浼樻儬瑙勫垯 */
- export interface RewardRule {
- limit?: number; // 婊¤冻閲戦
- discountPrice?: number; // 浼樻儬閲戦
- freeDelivery?: boolean; // 鏄惁鍖呴偖
- point: number; // 璧犻�佺Н鍒�
- giveCouponTemplateCounts?: {
- [key: number]: number;
- }; // 璧犻�佷紭鎯犲埜鏁伴噺
- }
-
- /** 婊″噺閫佹椿鍔� */
- export interface RewardActivity {
- id?: number; // 娲诲姩缂栧彿
- name?: string; // 娲诲姩鍚嶇О
- status?: number; // 娲诲姩鐘舵��
- startTime?: Date; // 寮�濮嬫椂闂�
- endTime?: Date; // 缁撴潫鏃堕棿
- startAndEndTime?: Date[]; // 寮�濮嬪拰缁撴潫鏃堕棿锛堜粎鍓嶇浣跨敤锛�
- remark?: string; // 澶囨敞
- conditionType?: number; // 鏉′欢绫诲瀷
- productScope?: number; // 鍟嗗搧鑼冨洿
- rules: RewardRule[]; // 浼樻儬瑙勫垯鍒楄〃
- productScopeValues: number[]; // 鍟嗗搧鑼冨洿鍊硷紙浠呰〃鍗曚娇鐢級锛氬�间负鍝佺被缂栧彿鍒楄〃銆佸晢鍝佺紪鍙峰垪琛�
- productCategoryIds: number[]; // 鍟嗗搧鍒嗙被缂栧彿鍒楄〃锛堜粎琛ㄥ崟浣跨敤锛�
- productSpuIds: number[]; // 鍟嗗搧 SPU 缂栧彿鍒楄〃锛堜粎琛ㄥ崟浣跨敤锛�
- }
-}
-
-/** 鏂板婊″噺閫佹椿鍔� */
-export function createRewardActivity(
- data: MallRewardActivityApi.RewardActivity,
-) {
- return requestClient.post('/promotion/reward-activity/create', data);
-}
-
-/** 鏇存柊婊″噺閫佹椿鍔� */
-export function updateRewardActivity(
- data: MallRewardActivityApi.RewardActivity,
-) {
- return requestClient.put('/promotion/reward-activity/update', data);
-}
-
-/** 鏌ヨ婊″噺閫佹椿鍔ㄥ垪琛� */
-export function getRewardActivityPage(params: PageParam) {
- return requestClient.get<PageResult<MallRewardActivityApi.RewardActivity>>(
- '/promotion/reward-activity/page',
- { params },
- );
-}
-
-/** 鏌ヨ婊″噺閫佹椿鍔ㄨ鎯� */
-export function getReward(id: number) {
- return requestClient.get<MallRewardActivityApi.RewardActivity>(
- `/promotion/reward-activity/get?id=${id}`,
- );
-}
-
-/** 鍒犻櫎婊″噺閫佹椿鍔� */
-export function deleteRewardActivity(id: number) {
- return requestClient.delete(`/promotion/reward-activity/delete?id=${id}`);
-}
-
-/** 鍏抽棴婊″噺閫佹椿鍔� */
-export function closeRewardActivity(id: number) {
- return requestClient.put(`/promotion/reward-activity/close?id=${id}`);
-}
diff --git a/src/api/mall/promotion/seckill/seckillActivity.ts b/src/api/mall/promotion/seckill/seckillActivity.ts
deleted file mode 100644
index 3c72095..0000000
--- a/src/api/mall/promotion/seckill/seckillActivity.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallSeckillActivityApi {
- /** 绉掓潃鍟嗗搧 */
- export interface SeckillProduct {
- skuId: number; // 鍟嗗搧 SKU 缂栧彿
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- seckillPrice: number; // 绉掓潃浠锋牸
- stock: number; // 绉掓潃搴撳瓨
- }
-
- /** 绉掓潃娲诲姩 */
- export interface SeckillActivity {
- id?: number; // 娲诲姩缂栧彿
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- name?: string; // 娲诲姩鍚嶇О
- status?: number; // 娲诲姩鐘舵��
- remark?: string; // 澶囨敞
- startTime?: Date; // 寮�濮嬫椂闂�
- endTime?: Date; // 缁撴潫鏃堕棿
- sort?: number; // 鎺掑簭
- configIds?: string; // 閰嶇疆缂栧彿
- orderCount?: number; // 璁㈠崟鏁伴噺
- userCount?: number; // 鐢ㄦ埛鏁伴噺
- totalPrice?: number; // 鎬婚噾棰�
- totalLimitCount?: number; // 鎬婚檺璐暟閲�
- singleLimitCount?: number; // 鍗曟闄愯喘鏁伴噺
- stock?: number; // 绉掓潃搴撳瓨
- totalStock?: number; // 绉掓潃鎬诲簱瀛�
- seckillPrice?: number; // 绉掓潃浠锋牸
- products?: SeckillProduct[]; // 绉掓潃鍟嗗搧鍒楄〃
- picUrl?: string; // 鍟嗗搧鍥剧墖
- }
-}
-
-/** 鏌ヨ绉掓潃娲诲姩鍒楄〃 */
-export function getSeckillActivityPage(params: PageParam) {
- return requestClient.get<PageResult<MallSeckillActivityApi.SeckillActivity>>(
- '/promotion/seckill-activity/page',
- { params },
- );
-}
-
-/** 鏌ヨ绉掓潃娲诲姩鍒楄〃锛屽熀浜庢椿鍔ㄧ紪鍙锋暟缁� */
-export function getSeckillActivityListByIds(ids: number[]) {
- return requestClient.get<MallSeckillActivityApi.SeckillActivity[]>(
- `/promotion/seckill-activity/list-by-ids?ids=${ids}`,
- );
-}
-
-/** 鏌ヨ绉掓潃娲诲姩璇︽儏 */
-export function getSeckillActivity(id: number) {
- return requestClient.get<MallSeckillActivityApi.SeckillActivity>(
- `/promotion/seckill-activity/get?id=${id}`,
- );
-}
-
-/** 鏂板绉掓潃娲诲姩 */
-export function createSeckillActivity(
- data: MallSeckillActivityApi.SeckillActivity,
-) {
- return requestClient.post('/promotion/seckill-activity/create', data);
-}
-
-/** 淇敼绉掓潃娲诲姩 */
-export function updateSeckillActivity(
- data: MallSeckillActivityApi.SeckillActivity,
-) {
- return requestClient.put('/promotion/seckill-activity/update', data);
-}
-
-/** 鍏抽棴绉掓潃娲诲姩 */
-export function closeSeckillActivity(id: number) {
- return requestClient.put(`/promotion/seckill-activity/close?id=${id}`);
-}
-
-/** 鍒犻櫎绉掓潃娲诲姩 */
-export function deleteSeckillActivity(id: number) {
- return requestClient.delete(`/promotion/seckill-activity/delete?id=${id}`);
-}
diff --git a/src/api/mall/promotion/seckill/seckillConfig.ts b/src/api/mall/promotion/seckill/seckillConfig.ts
deleted file mode 100644
index f3155b5..0000000
--- a/src/api/mall/promotion/seckill/seckillConfig.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallSeckillConfigApi {
- /** 绉掓潃鏃舵 */
- export interface SeckillConfig {
- id: number; // 缂栧彿
- name: string; // 绉掓潃鏃舵鍚嶇О
- startTime: string; // 寮�濮嬫椂闂寸偣
- endTime: string; // 缁撴潫鏃堕棿鐐�
- sliderPicUrls: string[]; // 绉掓潃杞挱鍥�
- status: number; // 娲诲姩鐘舵��
- }
-}
-
-/** 鏌ヨ绉掓潃鏃舵鍒嗛〉 */
-export function getSeckillConfigPage(params: PageParam) {
- return requestClient.get<PageResult<MallSeckillConfigApi.SeckillConfig>>(
- '/promotion/seckill-config/page',
- { params },
- );
-}
-
-/** 鏌ヨ绉掓潃鏃舵鍒楄〃 */
-export function getSimpleSeckillConfigList() {
- return requestClient.get<MallSeckillConfigApi.SeckillConfig[]>(
- '/promotion/seckill-config/list',
- );
-}
-
-/** 鏌ヨ绉掓潃鏃舵璇︽儏 */
-export function getSeckillConfig(id: number) {
- return requestClient.get<MallSeckillConfigApi.SeckillConfig>(
- `/promotion/seckill-config/get?id=${id}`,
- );
-}
-
-/** 鏂板绉掓潃鏃舵 */
-export function createSeckillConfig(data: MallSeckillConfigApi.SeckillConfig) {
- return requestClient.post('/promotion/seckill-config/create', data);
-}
-
-/** 淇敼绉掓潃鏃舵 */
-export function updateSeckillConfig(data: MallSeckillConfigApi.SeckillConfig) {
- return requestClient.put('/promotion/seckill-config/update', data);
-}
-
-/** 鍒犻櫎绉掓潃鏃舵 */
-export function deleteSeckillConfig(id: number) {
- return requestClient.delete(`/promotion/seckill-config/delete?id=${id}`);
-}
-
-/** 淇敼鏃舵閰嶇疆鐘舵�� */
-export function updateSeckillConfigStatus(id: number, status: number) {
- return requestClient.put('/promotion/seckill-config/update-status', {
- id,
- status,
- });
-}
diff --git a/src/api/mall/statistics/common.ts b/src/api/mall/statistics/common.ts
deleted file mode 100644
index 3b1fad6..0000000
--- a/src/api/mall/statistics/common.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-/** 鏁版嵁瀵圭収 Response */
-export interface DataComparisonRespVO<T> {
- value: T;
- reference: T;
-}
diff --git a/src/api/mall/statistics/member.ts b/src/api/mall/statistics/member.ts
deleted file mode 100644
index 8a44e55..0000000
--- a/src/api/mall/statistics/member.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import type { Dayjs } from 'dayjs';
-
-import type { DataComparisonRespVO } from './common';
-
-import { formatDateTime } from '../../../packages/utils/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallMemberStatisticsApi {
- /** 浼氬憳鍒嗘瀽 Request */
- export interface MemberAnalyseReqVO {
- times: Date[] | Dayjs[]; // 鏃堕棿鑼冨洿
- }
-
- /** 浼氬憳鍒嗘瀽 Response */
- export interface AnalyseRespVO {
- visitUserCount: number; // 璁块棶鐢ㄦ埛鏁�
- orderUserCount: number; // 涓嬪崟鐢ㄦ埛鏁�
- payUserCount: number; // 鏀粯鐢ㄦ埛鏁�
- atv: number; // 骞冲潎瀹㈠崟浠�
- comparison: DataComparisonRespVO<AnalyseDataRespVO>; // 瀵圭収鏁版嵁
- }
-
- /** 浼氬憳鍒嗘瀽瀵圭収鏁版嵁 Response */
- export interface AnalyseDataRespVO {
- registerUserCount: number; // 娉ㄥ唽鐢ㄦ埛鏁�
- visitUserCount: number; // 璁块棶鐢ㄦ埛鏁�
- rechargeUserCount: number; // 鍏呭�肩敤鎴锋暟
- }
-
- /** 浼氬憳鍦板尯缁熻 Response */
- export interface AreaStatisticsRespVO {
- areaId: number; // 鍦板尯ID
- areaName: string; // 鍦板尯鍚嶇О
- userCount: number; // 鐢ㄦ埛鏁�
- orderCreateUserCount: number; // 涓嬪崟鐢ㄦ埛鏁�
- orderPayUserCount: number; // 鏀粯鐢ㄦ埛鏁�
- orderPayPrice: number; // 鏀粯閲戦
- }
-
- /** 浼氬憳鎬у埆缁熻 Response */
- export interface SexStatisticsRespVO {
- sex: number; // 鎬у埆
- userCount: number; // 鐢ㄦ埛鏁�
- }
-
- /** 浼氬憳缁熻 Response */
- export interface SummaryRespVO {
- userCount: number; // 鐢ㄦ埛鏁�
- rechargeUserCount: number; // 鍏呭�肩敤鎴锋暟
- rechargePrice: number; // 鍏呭�奸噾棰�
- expensePrice: number; // 娑堣垂閲戦
- }
-
- /** 浼氬憳缁堢缁熻 Response */
- export interface TerminalStatisticsRespVO {
- terminal: number; // 缁堢
- userCount: number; // 鐢ㄦ埛鏁�
- }
-
- /** 浼氬憳鏁伴噺缁熻 Response */
- export interface MemberCountRespVO {
- visitUserCount: string; // 鐢ㄦ埛璁块棶閲�
- registerUserCount: number; // 娉ㄥ唽鐢ㄦ埛鏁伴噺
- }
-
- /** 浼氬憳娉ㄥ唽鏁伴噺 Response */
- export interface RegisterCountRespVO {
- date: string; // 鏃ユ湡
- count: number; // 鏁伴噺
- }
-}
-
-/** 鏌ヨ浼氬憳缁熻 */
-export function getMemberSummary() {
- return requestClient.get<MallMemberStatisticsApi.SummaryRespVO>(
- '/statistics/member/summary',
- );
-}
-
-/** 鏌ヨ浼氬憳鍒嗘瀽鏁版嵁 */
-export function getMemberAnalyse(
- params: MallMemberStatisticsApi.MemberAnalyseReqVO,
-) {
- return requestClient.get<MallMemberStatisticsApi.AnalyseRespVO>(
- '/statistics/member/analyse',
- {
- params: {
- times: [
- formatDateTime(params.times[0]),
- formatDateTime(params.times[1]),
- ],
- },
- },
- );
-}
-
-/** 鎸夌収鐪佷唤锛屾煡璇細鍛樼粺璁″垪琛� */
-export function getMemberAreaStatisticsList() {
- return requestClient.get<MallMemberStatisticsApi.AreaStatisticsRespVO[]>(
- '/statistics/member/area-statistics-list',
- );
-}
-
-/** 鎸夌収鎬у埆锛屾煡璇細鍛樼粺璁″垪琛� */
-export function getMemberSexStatisticsList() {
- return requestClient.get<MallMemberStatisticsApi.SexStatisticsRespVO[]>(
- '/statistics/member/sex-statistics-list',
- );
-}
-
-/** 鎸夌収缁堢锛屾煡璇細鍛樼粺璁″垪琛� */
-export function getMemberTerminalStatisticsList() {
- return requestClient.get<MallMemberStatisticsApi.TerminalStatisticsRespVO[]>(
- '/statistics/member/terminal-statistics-list',
- );
-}
-
-/** 鑾峰緱鐢ㄦ埛鏁伴噺閲忓鐓� */
-export function getUserCountComparison() {
- return requestClient.get<
- DataComparisonRespVO<MallMemberStatisticsApi.MemberCountRespVO>
- >('/statistics/member/user-count-comparison');
-}
-
-/** 鑾峰緱浼氬憳娉ㄥ唽鏁伴噺鍒楄〃 */
-export function getMemberRegisterCountList(beginTime: Date, endTime: Date) {
- return requestClient.get<MallMemberStatisticsApi.RegisterCountRespVO[]>(
- '/statistics/member/register-count-list',
- {
- params: {
- times: [formatDateTime(beginTime), formatDateTime(endTime)],
- },
- },
- );
-}
diff --git a/src/api/mall/statistics/pay.ts b/src/api/mall/statistics/pay.ts
deleted file mode 100644
index d78ea03..0000000
--- a/src/api/mall/statistics/pay.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MallPayStatisticsApi {
- /** 鏀粯缁熻 */
- export interface PaySummaryResp {
- /** 鍏呭�奸噾棰濓紝鍗曚綅鍒� */
- rechargePrice: number;
- }
-}
-
-/** 鑾峰彇閽卞寘鍏呭�奸噾棰� */
-export function getWalletRechargePrice() {
- return requestClient.get<MallPayStatisticsApi.PaySummaryResp>(
- '/statistics/pay/summary',
- );
-}
diff --git a/src/api/mall/statistics/product.ts b/src/api/mall/statistics/product.ts
deleted file mode 100644
index cacfc2e..0000000
--- a/src/api/mall/statistics/product.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import type { DataComparisonRespVO } from './common';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallProductStatisticsApi {
- /** 鍟嗗搧缁熻鏁版嵁 */
- export interface ProductStatisticsRespVO {
- id: number; // 缂栧彿
- day: string; // 缁熻鏃ユ湡
- spuId: number; // 鍟嗗搧 SPU 缂栧彿
- spuName: string; // 鍟嗗搧 SPU 鍚嶇О
- spuPicUrl: string; // 鍟嗗搧 SPU 鍥剧墖
- browseCount: number; // 娴忚娆℃暟
- browseUserCount: number; // 娴忚浜烘暟
- favoriteCount: number; // 鏀惰棌娆℃暟
- cartCount: number; // 鍔犺喘娆℃暟
- orderCount: number; // 涓嬪崟娆℃暟
- orderPayCount: number; // 鏀粯娆℃暟
- orderPayPrice: number; // 鏀粯閲戦
- afterSaleCount: number; // 鍞悗娆℃暟
- afterSaleRefundPrice: number; // 閫�娆鹃噾棰�
- browseConvertPercent: number; // 娴忚杞寲鐜�
- }
-}
-
-/** 鑾峰緱鍟嗗搧缁熻鍒嗘瀽 */
-export function getProductStatisticsAnalyse(params: any) {
- return requestClient.get<
- DataComparisonRespVO<MallProductStatisticsApi.ProductStatisticsRespVO>
- >('/statistics/product/analyse', { params });
-}
-
-/** 鑾峰緱鍟嗗搧鐘跺喌鏄庣粏 */
-export function getProductStatisticsList(params: any) {
- return requestClient.get<MallProductStatisticsApi.ProductStatisticsRespVO[]>(
- '/statistics/product/list',
- { params },
- );
-}
-
-/** 瀵煎嚭鑾峰緱鍟嗗搧鐘跺喌鏄庣粏 Excel */
-export function exportProductStatisticsExcel(params: any) {
- return requestClient.download('/statistics/product/export-excel', { params });
-}
-
-/** 鑾峰緱鍟嗗搧鎺掕姒滃垎椤� */
-export function getProductStatisticsRankPage(params: PageParam) {
- return requestClient.get<
- PageResult<MallProductStatisticsApi.ProductStatisticsRespVO>
- >('/statistics/product/rank-page', { params });
-}
diff --git a/src/api/mall/statistics/trade.ts b/src/api/mall/statistics/trade.ts
deleted file mode 100644
index 942c5df..0000000
--- a/src/api/mall/statistics/trade.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import type { DataComparisonRespVO } from './common';
-
-import { formatDateTime } from '../../../packages/utils/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallTradeStatisticsApi {
- /** 浜ゆ槗鐘跺喌 Request */
- export interface TradeTrendReqVO {
- times?: string[];
- }
-
- /** 浜ゆ槗缁熻 Response */
- export interface TradeSummaryRespVO {
- yesterdayOrderCount: number;
- monthOrderCount: number;
- yesterdayPayPrice: number;
- monthPayPrice: number;
- }
-
- /** 浜ゆ槗鐘跺喌缁熻 Response */
- export interface TradeTrendSummaryRespVO {
- time: string;
- turnoverPrice: number;
- orderPayPrice: number;
- rechargePrice: number;
- expensePrice: number;
- walletPayPrice: number;
- brokerageSettlementPrice: number;
- afterSaleRefundPrice: number;
- }
-
- /** 浜ゆ槗璁㈠崟鏁伴噺 Response */
- export interface TradeOrderCountRespVO {
- undelivered?: number; // 寰呭彂璐�
- pickUp?: number; // 寰呮牳閿�
- afterSaleApply?: number; // 閫�娆句腑
- auditingWithdraw?: number; // 鎻愮幇寰呭鏍�
- }
-
- /** 浜ゆ槗璁㈠崟缁熻 Response */
- export interface TradeOrderSummaryRespVO {
- orderPayCount?: number; // 鏀粯璁㈠崟鍟嗗搧鏁�
- orderPayPrice?: number; // 鎬绘敮浠橀噾棰濓紝鍗曚綅锛氬垎
- }
-
- /** 璁㈠崟閲忚秼鍔跨粺璁� Response */
- export interface TradeOrderTrendRespVO {
- date: string; // 鏃ユ湡
- orderPayCount: number; // 璁㈠崟鏁伴噺
- orderPayPrice: number; // 璁㈠崟鏀粯閲戦
- }
-}
-
-/** 鏌ヨ浜ゆ槗缁熻 */
-export function getTradeStatisticsSummary() {
- return requestClient.get<
- DataComparisonRespVO<MallTradeStatisticsApi.TradeSummaryRespVO>
- >('/statistics/trade/summary');
-}
-
-/** 鑾峰緱浜ゆ槗鐘跺喌缁熻 */
-export function getTradeStatisticsAnalyse(
- params: MallTradeStatisticsApi.TradeTrendReqVO,
-) {
- return requestClient.get<
- DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummaryRespVO>
- >('/statistics/trade/analyse', { params });
-}
-
-/** 鑾峰緱浜ゆ槗鐘跺喌鏄庣粏 */
-export function getTradeStatisticsList(params: any) {
- return requestClient.get<MallTradeStatisticsApi.TradeTrendSummaryRespVO[]>(
- '/statistics/trade/list',
- { params },
- );
-}
-
-/** 瀵煎嚭浜ゆ槗鐘跺喌鏄庣粏 */
-export function exportTradeStatisticsExcel(params: any) {
- return requestClient.download('/statistics/trade/export-excel', { params });
-}
-
-/** 鑾峰緱浜ゆ槗璁㈠崟鏁伴噺 */
-export function getOrderCount() {
- return requestClient.get<MallTradeStatisticsApi.TradeOrderCountRespVO>(
- '/statistics/trade/order-count',
- );
-}
-
-/** 鑾峰緱浜ゆ槗璁㈠崟鏁伴噺瀵圭収 */
-export function getOrderComparison() {
- return requestClient.get<
- DataComparisonRespVO<MallTradeStatisticsApi.TradeOrderSummaryRespVO>
- >('/statistics/trade/order-comparison');
-}
-
-/** 鑾峰緱璁㈠崟閲忚秼鍔跨粺璁� */
-export function getOrderCountTrendComparison(
- type: number,
- beginTime: Date,
- endTime: Date,
-) {
- return requestClient.get<
- DataComparisonRespVO<MallTradeStatisticsApi.TradeOrderTrendRespVO>[]
- >('/statistics/trade/order-count-trend', {
- params: {
- type,
- beginTime: formatDateTime(beginTime),
- endTime: formatDateTime(endTime),
- },
- });
-}
diff --git a/src/api/mall/trade/afterSale/index.ts b/src/api/mall/trade/afterSale/index.ts
deleted file mode 100644
index a1aeb71..0000000
--- a/src/api/mall/trade/afterSale/index.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallAfterSaleApi {
- /** 浜ゆ槗鍞悗 */
- export interface AfterSale {
- id?: number; // 鍞悗缂栧彿
- no?: string; // 鍞悗鍗曞彿
- status?: number; // 閫�娆剧姸鎬�
- way?: number; // 鍞悗鏂瑰紡
- type?: number; // 鍞悗绫诲瀷
- userId?: number; // 鐢ㄦ埛缂栧彿
- applyReason?: string; // 鐢宠鍘熷洜
- applyDescription?: string; // 琛ュ厖鎻忚堪
- applyPicUrls?: string[]; // 琛ュ厖鍑瘉鍥剧墖
- orderId?: number; // 浜ゆ槗璁㈠崟缂栧彿
- orderNo?: string; // 璁㈠崟娴佹按鍙�
- orderItemId?: number; // 浜ゆ槗璁㈠崟椤圭紪鍙�
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- spuName?: string; // 鍟嗗搧 SPU 鍚嶇О
- skuId?: number; // 鍟嗗搧 SKU 缂栧彿
- properties?: {
- propertyId?: number; // 灞炴�х殑缂栧彿
- propertyName?: string; // 灞炴�х殑鍚嶇О
- valueId?: number; // 灞炴�у�肩殑缂栧彿
- valueName?: string; // 灞炴�у�肩殑鍚嶇О
- }[]; // 灞炴�ф暟缁�
- picUrl?: string; // 鍟嗗搧鍥剧墖
- count?: number; // 閫�璐у晢鍝佹暟閲�
- auditTime?: Date; // 瀹℃壒鏃堕棿
- auditUserId?: number; // 瀹℃壒浜�
- auditReason?: string; // 瀹℃壒澶囨敞
- refundPrice?: number; // 閫�娆鹃噾棰濓紝鍗曚綅锛氬垎
- payRefundId?: number; // 鏀粯閫�娆剧紪鍙�
- refundTime?: Date; // 閫�娆炬椂闂�
- logisticsId?: number; // 閫�璐х墿娴佸叕鍙哥紪鍙�
- logisticsNo?: string; // 閫�璐х墿娴佸崟鍙�
- deliveryTime?: Date; // 閫�璐ф椂闂�
- receiveTime?: Date; // 鏀惰揣鏃堕棿
- receiveReason?: string; // 鏀惰揣澶囨敞
- order?: MallOrderApi.Order; // 鍏宠仈璁㈠崟
- orderItem?: MallOrderApi.OrderItem; // 鍏宠仈璁㈠崟椤�
- logs?: any[]; // 鍏宠仈鍞悗鏃ュ織
- }
-
- /** 鎷掔粷鍞悗璇锋眰 */
- export interface AfterSaleDisagreeReqVO {
- /** 鍞悗缂栧彿 */
- id: number;
- /** 鎷掔粷鍘熷洜 */
- reason: string;
- }
-}
-
-/** 鑾峰緱浜ゆ槗鍞悗鍒嗛〉 */
-export function getAfterSalePage(params: PageParam) {
- return requestClient.get<PageResult<MallAfterSaleApi.AfterSale>>(
- '/trade/after-sale/page',
- { params },
- );
-}
-
-/** 鑾峰緱浜ゆ槗鍞悗璇︽儏 */
-export function getAfterSale(id: number) {
- return requestClient.get<MallAfterSaleApi.AfterSale>(
- `/trade/after-sale/get-detail?id=${id}`,
- );
-}
-
-/** 鍚屾剰鍞悗 */
-export function agreeAfterSale(id: number) {
- return requestClient.put(`/trade/after-sale/agree?id=${id}`);
-}
-
-/** 鎷掔粷鍞悗 */
-export function disagreeAfterSale(
- data: MallAfterSaleApi.AfterSaleDisagreeReqVO,
-) {
- return requestClient.put('/trade/after-sale/disagree', data);
-}
-
-/** 纭鏀惰揣 */
-export function receiveAfterSale(id: number) {
- return requestClient.put(`/trade/after-sale/receive?id=${id}`);
-}
-
-/** 鎷掔粷鏀惰揣 */
-export function refuseAfterSale(id: number) {
- return requestClient.put(`/trade/after-sale/refuse?id=${id}`);
-}
-
-/** 纭閫�娆� */
-export function refundAfterSale(id: number) {
- return requestClient.put(`/trade/after-sale/refund?id=${id}`);
-}
diff --git a/src/api/mall/trade/brokerage/record/index.ts b/src/api/mall/trade/brokerage/record/index.ts
deleted file mode 100644
index 8f7d1f7..0000000
--- a/src/api/mall/trade/brokerage/record/index.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBrokerageRecordApi {
- /** 浣i噾璁板綍 */
- export interface BrokerageRecord {
- id: number; // 缂栧彿
- userId: number; // 鐢ㄦ埛缂栧彿
- userNickname: string; // 鐢ㄦ埛鏄电О
- userAvatar: string; // 鐢ㄦ埛澶村儚
- price: number; // 浣i噾閲戦锛屽崟浣嶏細鍒�
- type: number; // 浣i噾绫诲瀷
- orderId: number; // 鍏宠仈璁㈠崟缂栧彿
- orderNo: string; // 鍏宠仈璁㈠崟鍙�
- createTime: Date; // 鍒涘缓鏃堕棿
- status: number; // 鐘舵��
- settlementTime: Date; // 缁撶畻鏃堕棿
- }
-}
-
-/** 鏌ヨ浣i噾璁板綍鍒楄〃 */
-export function getBrokerageRecordPage(params: PageParam) {
- return requestClient.get<PageResult<MallBrokerageRecordApi.BrokerageRecord>>(
- '/trade/brokerage-record/page',
- { params },
- );
-}
-
-/** 鏌ヨ浣i噾璁板綍璇︽儏 */
-export function getBrokerageRecord(id: number) {
- return requestClient.get<MallBrokerageRecordApi.BrokerageRecord>(
- `/trade/brokerage-record/get?id=${id}`,
- );
-}
diff --git a/src/api/mall/trade/brokerage/user/index.ts b/src/api/mall/trade/brokerage/user/index.ts
deleted file mode 100644
index ea20011..0000000
--- a/src/api/mall/trade/brokerage/user/index.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBrokerageUserApi {
- /** 鍒嗛攢鐢ㄦ埛 */
- export interface BrokerageUser {
- id: number; // 缂栧彿
- bindUserId: number; // 鎺ㄥ箍鍛樼紪鍙�
- bindUserTime: Date; // 鎺ㄥ箍鍛樼粦瀹氭椂闂�
- brokerageEnabled: boolean; // 鏄惁鍚敤鍒嗛攢
- brokerageTime: Date; // 鍒嗛攢璧勬牸鏃堕棿
- price: number; // 鍙彁鐜伴噾棰濓紝鍗曚綅锛氬垎
- frozenPrice: number; // 鍐荤粨閲戦锛屽崟浣嶏細鍒�
- nickname: string; // 鐢ㄦ埛鏄电О
- avatar: string; // 鐢ㄦ埛澶村儚
- }
-
- /** 鍒涘缓鍒嗛攢鐢ㄦ埛璇锋眰 */
- export interface BrokerageUserCreateReqVO {
- userId: number; // 鐢ㄦ埛缂栧彿
- bindUserId: number; // 鎺ㄥ箍鍛樼紪鍙�
- }
-
- /** 淇敼鎺ㄥ箍鍛樿姹� */
- export interface BrokerageUserUpdateReqVO {
- id: number; // 鐢ㄦ埛缂栧彿
- bindUserId: number; // 鎺ㄥ箍鍛樼紪鍙�
- }
-
- /** 娓呴櫎鎺ㄥ箍鍛樿姹� */
- export interface BrokerageUserClearBrokerageUserReqVO {
- id: number; // 鐢ㄦ埛缂栧彿
- }
-
- /** 淇敼鎺ㄥ箍璧勬牸璇锋眰 */
- export interface BrokerageUserUpdateBrokerageEnabledReqVO {
- id: number; // 鐢ㄦ埛缂栧彿
- enabled: boolean; // 鏄惁鍚敤鍒嗛攢
- }
-}
-
-/** 鍒涘缓鍒嗛攢鐢ㄦ埛 */
-export function createBrokerageUser(
- data: MallBrokerageUserApi.BrokerageUserCreateReqVO,
-) {
- return requestClient.post('/trade/brokerage-user/create', data);
-}
-
-/** 鏌ヨ鍒嗛攢鐢ㄦ埛鍒楄〃 */
-export function getBrokerageUserPage(params: PageParam) {
- return requestClient.get<PageResult<MallBrokerageUserApi.BrokerageUser>>(
- '/trade/brokerage-user/page',
- { params },
- );
-}
-
-/** 鏌ヨ鍒嗛攢鐢ㄦ埛璇︽儏 */
-export function getBrokerageUser(id: number) {
- return requestClient.get<MallBrokerageUserApi.BrokerageUser>(
- `/trade/brokerage-user/get?id=${id}`,
- );
-}
-
-/** 淇敼鎺ㄥ箍鍛� */
-export function updateBindUser(
- data: MallBrokerageUserApi.BrokerageUserUpdateReqVO,
-) {
- return requestClient.put('/trade/brokerage-user/update-bind-user', data);
-}
-
-/** 娓呴櫎鎺ㄥ箍鍛� */
-export function clearBindUser(
- data: MallBrokerageUserApi.BrokerageUserClearBrokerageUserReqVO,
-) {
- return requestClient.put('/trade/brokerage-user/clear-bind-user', data);
-}
-
-/** 淇敼鎺ㄥ箍璧勬牸 */
-export function updateBrokerageEnabled(
- data: MallBrokerageUserApi.BrokerageUserUpdateBrokerageEnabledReqVO,
-) {
- return requestClient.put(
- '/trade/brokerage-user/update-brokerage-enable',
- data,
- );
-}
diff --git a/src/api/mall/trade/brokerage/withdraw/index.ts b/src/api/mall/trade/brokerage/withdraw/index.ts
deleted file mode 100644
index 0fec38d..0000000
--- a/src/api/mall/trade/brokerage/withdraw/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallBrokerageWithdrawApi {
- /** 浣i噾鎻愮幇 */
- export interface BrokerageWithdraw {
- id: number; // 缂栧彿
- userId: number; // 鐢ㄦ埛缂栧彿
- price: number; // 鎻愮幇閲戦锛屽崟浣嶏細鍒�
- feePrice: number; // 鎵嬬画璐癸紝鍗曚綅锛氬垎
- totalPrice: number; // 鎬婚噾棰濓紝鍗曚綅锛氬垎
- type: number; // 鎻愮幇绫诲瀷
- userName: string; // 鐢ㄦ埛鍚嶇О
- userAccount: string; // 鐢ㄦ埛璐﹀彿
- bankName: string; // 閾惰鍚嶇О
- bankAddress: string; // 閾惰鍦板潃
- qrCodeUrl: string; // 鏀舵鐮佸湴鍧�
- status: number; // 鐘舵��
- auditReason: string; // 瀹℃牳澶囨敞
- auditTime: Date; // 瀹℃牳鏃堕棿
- remark: string; // 澶囨敞
- payTransferId?: number; // 鏀粯杞处缂栧彿
- transferChannelCode?: string; // 杞处娓犻亾缂栫爜
- transferTime?: Date; // 杞处鏃堕棿
- transferErrorMsg?: string; // 杞处閿欒淇℃伅
- }
-
- /** 椹冲洖鐢宠璇锋眰 */
- export interface BrokerageWithdrawRejectReqVO {
- id: number; // 缂栧彿
- auditReason: string; // 椹冲洖鍘熷洜
- }
-}
-
-/** 鏌ヨ浣i噾鎻愮幇鍒楄〃 */
-export function getBrokerageWithdrawPage(params: PageParam) {
- return requestClient.get<
- PageResult<MallBrokerageWithdrawApi.BrokerageWithdraw>
- >('/trade/brokerage-withdraw/page', { params });
-}
-
-/** 浣i噾鎻愮幇 - 閫氳繃鐢宠 */
-export function approveBrokerageWithdraw(id: number) {
- return requestClient.put(`/trade/brokerage-withdraw/approve?id=${id}`);
-}
-
-/** 瀹℃牳浣i噾鎻愮幇 - 椹冲洖鐢宠 */
-export function rejectBrokerageWithdraw(
- data: MallBrokerageWithdrawApi.BrokerageWithdrawRejectReqVO,
-) {
- return requestClient.put('/trade/brokerage-withdraw/reject', data);
-}
diff --git a/src/api/mall/trade/config/index.ts b/src/api/mall/trade/config/index.ts
deleted file mode 100644
index c46e3c6..0000000
--- a/src/api/mall/trade/config/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MallTradeConfigApi {
- /** 浜ゆ槗涓績閰嶇疆 */
- export interface Config {
- id?: number;
- afterSaleRefundReasons: string[];
- afterSaleReturnReasons: string[];
- deliveryExpressFreeEnabled: boolean;
- deliveryExpressFreePrice: number;
- deliveryPickUpEnabled: boolean;
- brokerageEnabled?: boolean;
- brokerageEnabledCondition?: number;
- brokerageBindMode?: number;
- brokeragePosterUrls: string[];
- brokerageFirstPercent?: number;
- brokerageSecondPercent?: number;
- brokerageWithdrawMinPrice: number;
- brokerageFrozenDays: number;
- brokerageWithdrawFeePercent: number;
- brokerageWithdrawTypes: number[];
- tencentLbsKey?: string;
- }
-}
-
-/** 鏌ヨ浜ゆ槗涓績閰嶇疆璇︽儏 */
-export function getTradeConfig() {
- return requestClient.get<MallTradeConfigApi.Config>('/trade/config/get');
-}
-
-/** 淇濆瓨浜ゆ槗涓績閰嶇疆 */
-export function saveTradeConfig(data: MallTradeConfigApi.Config) {
- return requestClient.put('/trade/config/save', data);
-}
diff --git a/src/api/mall/trade/delivery/express/index.ts b/src/api/mall/trade/delivery/express/index.ts
deleted file mode 100644
index b0e0a9e..0000000
--- a/src/api/mall/trade/delivery/express/index.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDeliveryExpressApi {
- /** 蹇�掑叕鍙� */
- export interface DeliveryExpress {
- id: number; // 缂栧彿
- code: string; // 蹇�掑叕鍙哥紪鐮�
- name: string; // 蹇�掑叕鍙稿悕绉�
- logo: string; // 蹇�掑叕鍙� logo
- sort: number; // 鎺掑簭
- status: number; // 鐘舵��
- }
-}
-
-/** 鏌ヨ蹇�掑叕鍙稿垪琛� */
-export function getDeliveryExpressPage(params: PageParam) {
- return requestClient.get<PageResult<MallDeliveryExpressApi.DeliveryExpress>>(
- '/trade/delivery/express/page',
- { params },
- );
-}
-
-/** 鏌ヨ蹇�掑叕鍙歌鎯� */
-export function getDeliveryExpress(id: number) {
- return requestClient.get<MallDeliveryExpressApi.DeliveryExpress>(
- `/trade/delivery/express/get?id=${id}`,
- );
-}
-
-/** 鑾峰緱蹇�掑叕鍙哥簿绠�淇℃伅鍒楄〃 */
-export function getSimpleDeliveryExpressList() {
- return requestClient.get<MallDeliveryExpressApi.DeliveryExpress[]>(
- '/trade/delivery/express/list-all-simple',
- );
-}
-
-/** 鏂板蹇�掑叕鍙� */
-export function createDeliveryExpress(
- data: MallDeliveryExpressApi.DeliveryExpress,
-) {
- return requestClient.post('/trade/delivery/express/create', data);
-}
-
-/** 淇敼蹇�掑叕鍙� */
-export function updateDeliveryExpress(
- data: MallDeliveryExpressApi.DeliveryExpress,
-) {
- return requestClient.put('/trade/delivery/express/update', data);
-}
-
-/** 鍒犻櫎蹇�掑叕鍙� */
-export function deleteDeliveryExpress(id: number) {
- return requestClient.delete(`/trade/delivery/express/delete?id=${id}`);
-}
-
-/** 瀵煎嚭蹇�掑叕鍙� Excel */
-export function exportDeliveryExpress(params: PageParam) {
- return requestClient.download('/trade/delivery/express/export-excel', {
- params,
- });
-}
diff --git a/src/api/mall/trade/delivery/expressTemplate/index.ts b/src/api/mall/trade/delivery/expressTemplate/index.ts
deleted file mode 100644
index 17e1f5f..0000000
--- a/src/api/mall/trade/delivery/expressTemplate/index.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDeliveryExpressTemplateApi {
- /** 蹇�掕繍璐规ā鏉� */
- export interface DeliveryExpressTemplate {
- id: number; // 缂栧彿
- name: string; // 妯℃澘鍚嶇О
- chargeMode: number; // 璁¤垂鏂瑰紡
- sort: number; // 鎺掑簭
- charges: DeliveryExpressTemplateCharge[]; // 璁¤垂鍖哄煙鍒楄〃
- frees: DeliveryExpressTemplateFree[]; // 鍖呴偖鍖哄煙鍒楄〃
- }
-
- /** 杩愯垂妯℃澘璁¤垂 */
- export interface DeliveryExpressTemplateCharge {
- areaIds: number[]; // 鍖哄煙缂栧彿鍒楄〃
- startCount: number; // 棣栦欢鏁伴噺
- startPrice: number; // 棣栦欢浠锋牸锛屽崟浣嶏細鍒�
- extraCount: number; // 缁欢鏁伴噺
- extraPrice: number; // 缁欢浠锋牸锛屽崟浣嶏細鍒�
- }
-
- /** 杩愯垂妯℃澘鍖呴偖 */
- export interface DeliveryExpressTemplateFree {
- areaIds: number[]; // 鍖哄煙缂栧彿鍒楄〃
- freeCount: number; // 鍖呴偖浠舵暟
- freePrice: number; // 鍖呴偖閲戦锛屽崟浣嶏細鍒�
- }
-}
-
-/** 鏌ヨ蹇�掕繍璐规ā鏉垮垪琛� */
-export function getDeliveryExpressTemplatePage(params: PageParam) {
- return requestClient.get<
- PageResult<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>
- >('/trade/delivery/express-template/page', { params });
-}
-
-/** 鏌ヨ蹇�掕繍璐规ā鏉胯鎯� */
-export function getDeliveryExpressTemplate(id: number) {
- return requestClient.get<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>(
- `/trade/delivery/express-template/get?id=${id}`,
- );
-}
-
-/** 鏌ヨ蹇�掕繍璐规ā鏉胯鎯� */
-export function getSimpleTemplateList() {
- return requestClient.get<
- MallDeliveryExpressTemplateApi.DeliveryExpressTemplate[]
- >('/trade/delivery/express-template/list-all-simple');
-}
-
-/** 鏂板蹇�掕繍璐规ā鏉� */
-export function createDeliveryExpressTemplate(
- data: MallDeliveryExpressTemplateApi.DeliveryExpressTemplate,
-) {
- return requestClient.post('/trade/delivery/express-template/create', data);
-}
-
-/** 淇敼蹇�掕繍璐规ā鏉� */
-export function updateDeliveryExpressTemplate(
- data: MallDeliveryExpressTemplateApi.DeliveryExpressTemplate,
-) {
- return requestClient.put('/trade/delivery/express-template/update', data);
-}
-
-/** 鍒犻櫎蹇�掕繍璐规ā鏉� */
-export function deleteDeliveryExpressTemplate(id: number) {
- return requestClient.delete(
- `/trade/delivery/express-template/delete?id=${id}`,
- );
-}
diff --git a/src/api/mall/trade/delivery/pickUpStore/index.ts b/src/api/mall/trade/delivery/pickUpStore/index.ts
deleted file mode 100644
index fd87bff..0000000
--- a/src/api/mall/trade/delivery/pickUpStore/index.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallDeliveryPickUpStoreApi {
- /** 鑷彁闂ㄥ簵 */
- export interface DeliveryPickUpStore {
- id: number; // 缂栧彿
- name: string; // 闂ㄥ簵鍚嶇О
- introduction: string; // 闂ㄥ簵绠�浠�
- phone: string; // 鑱旂郴鐢佃瘽
- areaId: number; // 鍖哄煙缂栧彿
- detailAddress: string; // 璇︾粏鍦板潃
- logo: string; // 闂ㄥ簵 logo
- openingTime: string; // 钀ヤ笟寮�濮嬫椂闂�
- closingTime: string; // 钀ヤ笟缁撴潫鏃堕棿
- latitude: number; // 绾害
- longitude: number; // 缁忓害
- status: number; // 鐘舵��
- rangeTime: any[]; // 钀ヤ笟鏃堕棿锛岀敤浜� fieldMappingTime
- verifyUserIds?: number[]; // 缁戝畾鐢ㄦ埛缂栧彿缁勬暟
- verifyUsers?: any[];
- }
-
- /** 缁戝畾鑷彁搴楀憳璇锋眰 */
- export interface DeliveryPickUpBindReqVO {
- id?: number;
- /** 鐢ㄦ埛缂栧彿鍒楄〃 */
- verifyUserIds: number[];
- }
-}
-
-/** 鏌ヨ鑷彁闂ㄥ簵鍒楄〃 */
-export function getDeliveryPickUpStorePage(params: PageParam) {
- return requestClient.get<
- PageResult<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>
- >('/trade/delivery/pick-up-store/page', { params });
-}
-
-/** 鏌ヨ鑷彁闂ㄥ簵璇︽儏 */
-export function getDeliveryPickUpStore(id: number) {
- return requestClient.get<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>(
- `/trade/delivery/pick-up-store/get?id=${id}`,
- );
-}
-
-/** 鏌ヨ鑷彁闂ㄥ簵绮剧畝鍒楄〃 */
-export function getSimpleDeliveryPickUpStoreList() {
- return requestClient.get<MallDeliveryPickUpStoreApi.DeliveryPickUpStore[]>(
- '/trade/delivery/pick-up-store/simple-list',
- );
-}
-
-/** 鏂板鑷彁闂ㄥ簵 */
-export function createDeliveryPickUpStore(
- data: MallDeliveryPickUpStoreApi.DeliveryPickUpStore,
-) {
- return requestClient.post('/trade/delivery/pick-up-store/create', data);
-}
-
-/** 淇敼鑷彁闂ㄥ簵 */
-export function updateDeliveryPickUpStore(
- data: MallDeliveryPickUpStoreApi.DeliveryPickUpStore,
-) {
- return requestClient.put('/trade/delivery/pick-up-store/update', data);
-}
-
-/** 鍒犻櫎鑷彁闂ㄥ簵 */
-export function deleteDeliveryPickUpStore(id: number) {
- return requestClient.delete(`/trade/delivery/pick-up-store/delete?id=${id}`);
-}
-
-/** 缁戝畾鑷彁搴楀憳 */
-export function bindDeliveryPickUpStore(
- data: MallDeliveryPickUpStoreApi.DeliveryPickUpBindReqVO,
-) {
- return requestClient.post('/trade/delivery/pick-up-store/bind', data);
-}
diff --git a/src/api/mall/trade/order/index.ts b/src/api/mall/trade/order/index.ts
deleted file mode 100644
index bf2b411..0000000
--- a/src/api/mall/trade/order/index.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MallOrderApi {
- /** 璁㈠崟淇℃伅 */
- export interface Order {
- id?: number; // 璁㈠崟缂栧彿
- no?: string; // 璁㈠崟娴佹按鍙�
- createTime?: Date; // 涓嬪崟鏃堕棿
- type?: number; // 璁㈠崟绫诲瀷
- terminal?: number; // 璁㈠崟鏉ユ簮
- userId?: number; // 鐢ㄦ埛缂栧彿
- userIp?: string; // 鐢ㄦ埛 IP
- userRemark?: string; // 鐢ㄦ埛澶囨敞
- status?: number; // 璁㈠崟鐘舵��
- productCount?: number; // 璐拱鐨勫晢鍝佹暟閲�
- finishTime?: Date; // 璁㈠崟瀹屾垚鏃堕棿
- cancelTime?: Date; // 璁㈠崟鍙栨秷鏃堕棿
- cancelType?: number; // 鍙栨秷绫诲瀷
- remark?: string; // 鍟嗗澶囨敞
- payOrderId?: number; // 鏀粯璁㈠崟缂栧彿
- payStatus?: boolean; // 鏄惁宸叉敮浠�
- payTime?: Date; // 浠樻鏃堕棿
- payChannelCode?: string; // 鏀粯娓犻亾
- totalPrice?: number; // 鍟嗗搧鍘熶环锛堟�伙級
- discountPrice?: number; // 璁㈠崟浼樻儬锛堟�伙級
- deliveryPrice?: number; // 杩愯垂閲戦
- adjustPrice?: number; // 璁㈠崟璋冧环锛堟�伙級
- payPrice?: number; // 搴斾粯閲戦锛堟�伙級
- deliveryType?: number; // 鍙戣揣鏂瑰紡
- pickUpStoreId?: number; // 鑷彁闂ㄥ簵缂栧彿
- pickUpVerifyCode?: string; // 鑷彁鏍搁攢鐮�
- deliveryTemplateId?: number; // 閰嶉�佹ā鏉跨紪鍙�
- logisticsId?: number; // 鍙戣揣鐗╂祦鍏徃缂栧彿
- logisticsNo?: string; // 鍙戣揣鐗╂祦鍗曞彿
- deliveryTime?: Date; // 鍙戣揣鏃堕棿
- receiveTime?: Date; // 鏀惰揣鏃堕棿
- receiverName?: string; // 鏀朵欢浜哄悕绉�
- receiverMobile?: string; // 鏀朵欢浜烘墜鏈�
- receiverPostCode?: number; // 鏀朵欢浜洪偖缂�
- receiverAreaId?: number; // 鏀朵欢浜哄湴鍖虹紪鍙�
- receiverAreaName?: string; // 鏀朵欢浜哄湴鍖哄悕瀛�
- receiverDetailAddress?: string; // 鏀朵欢浜鸿缁嗗湴鍧�
- afterSaleStatus?: number; // 鍞悗鐘舵��
- refundPrice?: number; // 閫�娆鹃噾棰�
- couponId?: number; // 浼樻儬鍔电紪鍙�
- couponPrice?: number; // 浼樻儬鍔靛噺鍏嶉噾棰�
- pointPrice?: number; // 绉垎鎶垫墸鐨勯噾棰�
- vipPrice?: number; // VIP 鍑忓厤閲戦
- items?: OrderItem[]; // 璁㈠崟椤瑰垪琛�
- user?: {
- // 涓嬪崟鐢ㄦ埛淇℃伅
- avatar?: string; // 鐢ㄦ埛澶村儚
- id?: number; // 鐢ㄦ埛缂栧彿
- nickname?: string; // 鐢ㄦ埛鏄电О
- };
- brokerageUser?: {
- // 鎺ㄥ箍鐢ㄦ埛淇℃伅
- avatar?: string; // 鐢ㄦ埛澶村儚
- id?: number; // 鐢ㄦ埛缂栧彿
- nickname?: string; // 鐢ㄦ埛鏄电О
- }; // 鎺ㄥ箍鐢ㄦ埛淇℃伅
- logs?: OrderLog[]; // 璁㈠崟鎿嶄綔鏃ュ織
- }
-
- /** 璁㈠崟椤� */
- export interface OrderItem {
- id?: number; // 缂栧彿
- userId?: number; // 鐢ㄦ埛缂栧彿
- orderId?: number; // 璁㈠崟缂栧彿
- spuId?: number; // 鍟嗗搧 SPU 缂栧彿
- spuName?: string; // 鍟嗗搧 SPU 鍚嶇О
- skuId?: number; // 鍟嗗搧 SKU 缂栧彿
- picUrl?: string; // 鍟嗗搧鍥剧墖
- count?: number; // 璐拱鏁伴噺
- originalPrice?: number; // 鍟嗗搧鍘熶环锛堟�伙級
- originalUnitPrice?: number; // 鍟嗗搧鍘熶环锛堝崟锛�
- discountPrice?: number; // 鍟嗗搧浼樻儬锛堟�伙級
- payPrice?: number; // 鍟嗗搧瀹炰粯閲戦锛堟�伙級
- orderPartPrice?: number; // 瀛愯鍗曞垎鎽婇噾棰濓紙鎬伙級
- orderDividePrice?: number; // 鍒嗘憡鍚庡瓙璁㈠崟瀹炰粯閲戦锛堟�伙級
- afterSaleStatus?: number; // 鍞悗鐘舵��
- properties?: {
- propertyId?: number; // 灞炴�х殑缂栧彿
- propertyName?: string; // 灞炴�х殑鍚嶇О
- valueId?: number; // 灞炴�у�肩殑缂栧彿
- valueName?: string; // 灞炴�у�肩殑鍚嶇О
- }[]; // 灞炴�ф暟缁�
- price?: number;
- }
-
- /** 璁㈠崟鏃ュ織 */
- export interface OrderLog {
- content?: string; // 鏃ュ織鍐呭
- createTime?: Date; // 鍒涘缓鏃堕棿
- userType?: number; // 鐢ㄦ埛绫诲瀷
- userId?: number; // 鐢ㄦ埛缂栧彿
- }
-
- /** 浜ゆ槗璁㈠崟缁熻鍝嶅簲 */
- export interface OrderSummaryRespVO {
- orderCount: number; // 璁㈠崟鏁伴噺
- orderPayPrice: number; // 璁㈠崟閲戦
- afterSaleCount: number; // 閫�娆惧崟鏁�
- afterSalePrice: number; // 閫�娆鹃噾棰�
- }
-
- /** 璁㈠崟鍙戣揣璇锋眰 */
- export interface OrderUpdateDeliveryReqVO {
- id?: number; // 璁㈠崟缂栧彿
- expressType: string; // 鍙戣揣鏂瑰紡
- logisticsId: number; // 鐗╂祦鍏徃缂栧彿
- logisticsNo: string; // 鐗╂祦缂栧彿
- }
-
- /** 璁㈠崟澶囨敞璇锋眰 */
- export interface OrderUpdateRemarkReqVO {
- id: number; // 璁㈠崟缂栧彿
- remark: string; // 澶囨敞
- }
-
- /** 璁㈠崟璋冧环璇锋眰 */
- export interface OrderUpdatePriceReqVO {
- id: number; // 璁㈠崟缂栧彿
- adjustPrice: number; // 璋冩暣閲戦锛屽崟浣嶏細鍒�
- }
-
- /** 璁㈠崟鍦板潃璇锋眰 */
- export interface OrderUpdateAddressReqVO {
- id: number; // 璁㈠崟缂栧彿
- receiverName: string; // 鏀朵欢浜哄悕绉�
- receiverMobile: string; // 鏀朵欢浜烘墜鏈�
- receiverAreaId: number; // 鏀朵欢浜哄湴鍖虹紪鍙�
- receiverDetailAddress: string; // 鏀朵欢浜鸿缁嗗湴鍧�
- }
-}
-
-/** 鏌ヨ浜ゆ槗璁㈠崟鍒楄〃 */
-export function getOrderPage(params: PageParam) {
- return requestClient.get<PageResult<MallOrderApi.Order>>(
- '/trade/order/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ浜ゆ槗璁㈠崟缁熻 */
-export function getOrderSummary(params: any) {
- return requestClient.get<MallOrderApi.OrderSummaryRespVO>(
- '/trade/order/summary',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ浜ゆ槗璁㈠崟璇︽儏 */
-export function getOrder(id: number) {
- return requestClient.get<MallOrderApi.Order>(
- `/trade/order/get-detail?id=${id}`,
- );
-}
-
-/** 鏌ヨ浜ゆ槗璁㈠崟鐗╂祦璇︽儏 */
-export function getExpressTrackList(id: number) {
- return requestClient.get(`/trade/order/get-express-track-list?id=${id}`);
-}
-
-/** 璁㈠崟鍙戣揣 */
-export function deliveryOrder(data: MallOrderApi.OrderUpdateDeliveryReqVO) {
- return requestClient.put('/trade/order/delivery', data);
-}
-
-/** 璁㈠崟澶囨敞 */
-export function updateOrderRemark(data: MallOrderApi.OrderUpdateRemarkReqVO) {
- return requestClient.put('/trade/order/update-remark', data);
-}
-
-/** 璁㈠崟璋冧环 */
-export function updateOrderPrice(data: MallOrderApi.OrderUpdatePriceReqVO) {
- return requestClient.put('/trade/order/update-price', data);
-}
-
-/** 淇敼璁㈠崟鍦板潃 */
-export function updateOrderAddress(data: MallOrderApi.OrderUpdateAddressReqVO) {
- return requestClient.put('/trade/order/update-address', data);
-}
-
-/** 璁㈠崟鏍搁攢 */
-export function pickUpOrder(id: number) {
- return requestClient.put(`/trade/order/pick-up-by-id?id=${id}`);
-}
-
-/** 璁㈠崟鏍搁攢 */
-export function pickUpOrderByVerifyCode(pickUpVerifyCode: string) {
- return requestClient.put('/trade/order/pick-up-by-verify-code', undefined, {
- params: { pickUpVerifyCode },
- });
-}
-
-/** 鏌ヨ鏍搁攢鐮佸搴旂殑璁㈠崟 */
-export function getOrderByPickUpVerifyCode(pickUpVerifyCode: string) {
- return requestClient.get<MallOrderApi.Order>(
- '/trade/order/get-by-pick-up-verify-code',
- { params: { pickUpVerifyCode } },
- );
-}
diff --git a/src/api/member/address/index.ts b/src/api/member/address/index.ts
deleted file mode 100644
index 74f6615..0000000
--- a/src/api/member/address/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MemberAddressApi {
- /** 鏀朵欢鍦板潃淇℃伅 */
- export interface Address {
- id?: number;
- name: string;
- mobile: string;
- areaId: number;
- areaName?: string;
- detailAddress: string;
- defaultStatus: boolean;
- }
-}
-
-/** 鏌ヨ鐢ㄦ埛鏀朵欢鍦板潃鍒楄〃 */
-export function getAddressList(params: any) {
- return requestClient.get<MemberAddressApi.Address[]>('/member/address/list', {
- params,
- });
-}
diff --git a/src/api/member/config/index.ts b/src/api/member/config/index.ts
deleted file mode 100644
index 799e4c4..0000000
--- a/src/api/member/config/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MemberConfigApi {
- /** 绉垎璁剧疆淇℃伅 */
- export interface Config {
- id?: number;
- pointTradeDeductEnable: boolean;
- pointTradeDeductUnitPrice: number;
- pointTradeDeductMaxPrice: number;
- pointTradeGivePoint: number;
- }
-}
-
-/** 鏌ヨ绉垎璁剧疆璇︽儏 */
-export function getConfig() {
- return requestClient.get<MemberConfigApi.Config>('/member/config/get');
-}
-
-/** 鏂板淇敼绉垎璁剧疆 */
-export function saveConfig(data: MemberConfigApi.Config) {
- return requestClient.put('/member/config/save', data);
-}
diff --git a/src/api/member/experience-record/index.ts b/src/api/member/experience-record/index.ts
deleted file mode 100644
index 5a40880..0000000
--- a/src/api/member/experience-record/index.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberExperienceRecordApi {
- /** 浼氬憳缁忛獙璁板綍淇℃伅 */
- export interface ExperienceRecord {
- id?: number;
- userId: number;
- bizId: string;
- bizType: number;
- title: string;
- description: string;
- experience: number;
- totalExperience: number;
- }
-}
-
-/** 鏌ヨ浼氬憳缁忛獙璁板綍鍒楄〃 */
-export function getExperienceRecordPage(params: PageParam) {
- return requestClient.get<
- PageResult<MemberExperienceRecordApi.ExperienceRecord>
- >('/member/experience-record/page', {
- params,
- });
-}
-
-/** 鏌ヨ浼氬憳缁忛獙璁板綍璇︽儏 */
-export function getExperienceRecord(id: number) {
- return requestClient.get<MemberExperienceRecordApi.ExperienceRecord>(
- `/member/experience-record/get?id=${id}`,
- );
-}
diff --git a/src/api/member/group/index.ts b/src/api/member/group/index.ts
deleted file mode 100644
index d54b0af..0000000
--- a/src/api/member/group/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberGroupApi {
- /** 鐢ㄦ埛鍒嗙粍淇℃伅 */
- export interface Group {
- id?: number;
- name: string;
- remark: string;
- status: number;
- }
-}
-
-/** 鏌ヨ鐢ㄦ埛鍒嗙粍鍒楄〃 */
-export function getGroupPage(params: PageParam) {
- return requestClient.get<PageResult<MemberGroupApi.Group>>(
- '/member/group/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ鐢ㄦ埛鍒嗙粍璇︽儏 */
-export function getGroup(id: number) {
- return requestClient.get<MemberGroupApi.Group>(`/member/group/get?id=${id}`);
-}
-
-/** 鏂板鐢ㄦ埛鍒嗙粍 */
-export function createGroup(data: MemberGroupApi.Group) {
- return requestClient.post('/member/group/create', data);
-}
-
-/** 鏌ヨ鐢ㄦ埛鍒嗙粍 - 绮剧畝淇℃伅鍒楄〃 */
-export function getSimpleGroupList() {
- return requestClient.get<MemberGroupApi.Group[]>(
- '/member/group/list-all-simple',
- );
-}
-
-/** 淇敼鐢ㄦ埛鍒嗙粍 */
-export function updateGroup(data: MemberGroupApi.Group) {
- return requestClient.put('/member/group/update', data);
-}
-
-/** 鍒犻櫎鐢ㄦ埛鍒嗙粍 */
-export function deleteGroup(id: number) {
- return requestClient.delete(`/member/group/delete?id=${id}`);
-}
diff --git a/src/api/member/level/index.ts b/src/api/member/level/index.ts
deleted file mode 100644
index a856e50..0000000
--- a/src/api/member/level/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MemberLevelApi {
- /** 浼氬憳绛夌骇淇℃伅 */
- export interface Level {
- id?: number;
- name: string;
- experience: number;
- value: number;
- discountPercent: number;
- icon: string;
- bgUrl: string;
- status: number;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ浼氬憳绛夌骇鍒楄〃 */
-export function getLevelList(params: MemberLevelApi.Level) {
- return requestClient.get<MemberLevelApi.Level[]>('/member/level/list', {
- params,
- });
-}
-
-/** 鏌ヨ浼氬憳绛夌骇璇︽儏 */
-export function getLevel(id: number) {
- return requestClient.get<MemberLevelApi.Level>(`/member/level/get?id=${id}`);
-}
-
-/** 鏌ヨ浼氬憳绛夌骇 - 绮剧畝淇℃伅鍒楄〃 */
-export function getSimpleLevelList() {
- return requestClient.get<MemberLevelApi.Level[]>(
- '/member/level/list-all-simple',
- );
-}
-
-/** 鏂板浼氬憳绛夌骇 */
-export function createLevel(data: MemberLevelApi.Level) {
- return requestClient.post('/member/level/create', data);
-}
-
-/** 淇敼浼氬憳绛夌骇 */
-export function updateLevel(data: MemberLevelApi.Level) {
- return requestClient.put('/member/level/update', data);
-}
-
-/** 鍒犻櫎浼氬憳绛夌骇 */
-export function deleteLevel(id: number) {
- return requestClient.delete(`/member/level/delete?id=${id}`);
-}
diff --git a/src/api/member/point/record/index.ts b/src/api/member/point/record/index.ts
deleted file mode 100644
index 0cfef12..0000000
--- a/src/api/member/point/record/index.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberPointRecordApi {
- /** 鐢ㄦ埛绉垎璁板綍淇℃伅 */
- export interface Record {
- id?: number;
- bizId: string;
- bizType: string;
- title: string;
- description: string;
- point: number;
- totalPoint: number;
- userId: number;
- createDate: Date;
- }
-}
-
-/** 鏌ヨ鐢ㄦ埛绉垎璁板綍鍒楄〃 */
-export function getRecordPage(params: PageParam) {
- return requestClient.get<PageResult<MemberPointRecordApi.Record>>(
- '/member/point/record/page',
- {
- params,
- },
- );
-}
diff --git a/src/api/member/signin/config/index.ts b/src/api/member/signin/config/index.ts
deleted file mode 100644
index cf113b7..0000000
--- a/src/api/member/signin/config/index.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MemberSignInConfigApi {
- /** 绉垎绛惧埌瑙勫垯淇℃伅 */
- export interface SignInConfig {
- id?: number;
- day?: number;
- point?: number;
- experience?: number;
- status?: number;
- }
-}
-
-/** 鏌ヨ绉垎绛惧埌瑙勫垯鍒楄〃 */
-export function getSignInConfigList() {
- return requestClient.get<MemberSignInConfigApi.SignInConfig[]>(
- '/member/sign-in/config/list',
- );
-}
-
-/** 鏌ヨ绉垎绛惧埌瑙勫垯璇︽儏 */
-export function getSignInConfig(id: number) {
- return requestClient.get<MemberSignInConfigApi.SignInConfig>(
- `/member/sign-in/config/get?id=${id}`,
- );
-}
-
-/** 鏂板绉垎绛惧埌瑙勫垯 */
-export function createSignInConfig(data: MemberSignInConfigApi.SignInConfig) {
- return requestClient.post('/member/sign-in/config/create', data);
-}
-
-/** 淇敼绉垎绛惧埌瑙勫垯 */
-export function updateSignInConfig(data: MemberSignInConfigApi.SignInConfig) {
- return requestClient.put('/member/sign-in/config/update', data);
-}
-
-/** 鍒犻櫎绉垎绛惧埌瑙勫垯 */
-export function deleteSignInConfig(id: number) {
- return requestClient.delete(`/member/sign-in/config/delete?id=${id}`);
-}
diff --git a/src/api/member/signin/record/index.ts b/src/api/member/signin/record/index.ts
deleted file mode 100644
index 90e7fa5..0000000
--- a/src/api/member/signin/record/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberSignInRecordApi {
- /** 鐢ㄦ埛绛惧埌绉垎淇℃伅 */
- export interface SignInRecord {
- id?: number;
- userId: number;
- day: number;
- point: number;
- }
-}
-
-/** 鏌ヨ鐢ㄦ埛绛惧埌绉垎鍒楄〃 */
-export function getSignInRecordPage(params: PageParam) {
- return requestClient.get<PageResult<MemberSignInRecordApi.SignInRecord>>(
- '/member/sign-in/record/page',
- {
- params,
- },
- );
-}
diff --git a/src/api/member/tag/index.ts b/src/api/member/tag/index.ts
deleted file mode 100644
index 75924b7..0000000
--- a/src/api/member/tag/index.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberTagApi {
- /** 浼氬憳鏍囩淇℃伅 */
- export interface Tag {
- id?: number;
- name: string;
- }
-}
-
-/** 鏌ヨ浼氬憳鏍囩鍒楄〃 */
-export function getMemberTagPage(params: PageParam) {
- return requestClient.get<PageResult<MemberTagApi.Tag>>('/member/tag/page', {
- params,
- });
-}
-
-/** 鏌ヨ浼氬憳鏍囩璇︽儏 */
-export function getMemberTag(id: number) {
- return requestClient.get<MemberTagApi.Tag>(`/member/tag/get?id=${id}`);
-}
-
-/** 鏌ヨ浼氬憳鏍囩 - 绮剧畝淇℃伅鍒楄〃 */
-export function getSimpleTagList() {
- return requestClient.get<MemberTagApi.Tag[]>('/member/tag/list-all-simple');
-}
-
-/** 鏂板浼氬憳鏍囩 */
-export function createMemberTag(data: MemberTagApi.Tag) {
- return requestClient.post('/member/tag/create', data);
-}
-
-/** 淇敼浼氬憳鏍囩 */
-export function updateMemberTag(data: MemberTagApi.Tag) {
- return requestClient.put('/member/tag/update', data);
-}
-
-/** 鍒犻櫎浼氬憳鏍囩 */
-export function deleteMemberTag(id: number) {
- return requestClient.delete(`/member/tag/delete?id=${id}`);
-}
diff --git a/src/api/member/user/index.ts b/src/api/member/user/index.ts
deleted file mode 100644
index 194f92f..0000000
--- a/src/api/member/user/index.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MemberUserApi {
- /** 浼氬憳鐢ㄦ埛淇℃伅 */
- export interface User {
- id?: number;
- avatar?: string;
- birthday?: number;
- createTime?: number;
- loginDate?: number;
- loginIp?: string;
- mark?: string;
- mobile?: string;
- email?: string;
- name?: string;
- nickname?: string;
- registerIp?: string;
- sex?: number;
- status?: number;
- areaId?: number;
- areaName?: string;
- tagIds?: number[];
- groupId?: number;
- levelId?: number;
- levelName?: null | string;
- point?: null | number;
- totalPoint?: null | number;
- experience?: null | number;
- }
-
- /** 浼氬憳鐢ㄦ埛绛夌骇鏇存柊淇℃伅 */
- export interface UserUpdateLevelReqVO {
- id: number;
- levelId: number;
- reason: string;
- }
-
- /** 浼氬憳鐢ㄦ埛绉垎鏇存柊淇℃伅 */
- export interface UserPointUpdateReqVO {
- id: number;
- point: number;
- }
-}
-
-/** 鏌ヨ浼氬憳鐢ㄦ埛鍒楄〃 */
-export function getUserPage(params: PageParam) {
- return requestClient.get<PageResult<MemberUserApi.User>>(
- '/member/user/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ浼氬憳鐢ㄦ埛璇︽儏 */
-export function getUser(id: number) {
- return requestClient.get<MemberUserApi.User>(`/member/user/get?id=${id}`);
-}
-
-/** 淇敼浼氬憳鐢ㄦ埛 */
-export function updateUser(data: MemberUserApi.User) {
- return requestClient.put('/member/user/update', data);
-}
-
-/** 淇敼浼氬憳鐢ㄦ埛绛夌骇 */
-export function updateUserLevel(data: MemberUserApi.UserUpdateLevelReqVO) {
- return requestClient.put('/member/user/update-level', data);
-}
-
-/** 淇敼浼氬憳鐢ㄦ埛绉垎 */
-export function updateUserPoint(data: MemberUserApi.UserPointUpdateReqVO) {
- return requestClient.put('/member/user/update-point', data);
-}
diff --git a/src/api/mp/account/index.ts b/src/api/mp/account/index.ts
deleted file mode 100644
index b05f94a..0000000
--- a/src/api/mp/account/index.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpAccountApi {
- /** 鍏紬鍙疯处鍙蜂俊鎭� */
- export interface Account {
- id: number;
- name: string;
- account?: string;
- appId?: string;
- appSecret?: string;
- token?: string;
- aesKey?: string;
- qrCodeUrl?: string;
- remark?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鍏紬鍙疯处鍙峰垪琛� */
-export function getAccountPage(params: PageParam) {
- return requestClient.get<PageResult<MpAccountApi.Account>>(
- '/mp/account/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ鍏紬鍙疯处鍙疯鎯� */
-export function getAccount(id: number) {
- return requestClient.get<MpAccountApi.Account>(`/mp/account/get?id=${id}`);
-}
-
-/** 鏌ヨ鍏紬鍙疯处鍙峰垪琛� */
-export function getSimpleAccountList() {
- return requestClient.get<MpAccountApi.Account[]>(
- '/mp/account/list-all-simple',
- );
-}
-
-/** 鏂板鍏紬鍙疯处鍙� */
-export function createAccount(data: MpAccountApi.Account) {
- return requestClient.post('/mp/account/create', data);
-}
-
-/** 淇敼鍏紬鍙疯处鍙� */
-export function updateAccount(data: MpAccountApi.Account) {
- return requestClient.put('/mp/account/update', data);
-}
-
-/** 鍒犻櫎鍏紬鍙疯处鍙� */
-export function deleteAccount(id: number) {
- return requestClient.delete(`/mp/account/delete?id=${id}`);
-}
-
-/** 鐢熸垚鍏紬鍙疯处鍙蜂簩缁寸爜 */
-export function generateAccountQrCode(id: number) {
- return requestClient.put(`/mp/account/generate-qr-code?id=${id}`);
-}
-
-/** 娓呯┖鍏紬鍙疯处鍙� API 閰嶉 */
-export function clearAccountQuota(id: number) {
- return requestClient.put(`/mp/account/clear-quota?id=${id}`);
-}
diff --git a/src/api/mp/autoReply/index.ts b/src/api/mp/autoReply/index.ts
deleted file mode 100644
index 9a01ff7..0000000
--- a/src/api/mp/autoReply/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpAutoReplyApi {
- /** 鑷姩鍥炲淇℃伅 */
- export interface AutoReply {
- id?: number;
- accountId: number;
- type: number;
- keyword: string;
- content: string;
- status: number;
- remark?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鑷姩鍥炲鍒楄〃 */
-export function getAutoReplyPage(params: PageParam) {
- return requestClient.get<PageResult<MpAutoReplyApi.AutoReply>>(
- '/mp/auto-reply/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ鑷姩鍥炲璇︽儏 */
-export function getAutoReply(id: number) {
- return requestClient.get<MpAutoReplyApi.AutoReply>(
- `/mp/auto-reply/get?id=${id}`,
- );
-}
-
-/** 鏂板鑷姩鍥炲 */
-export function createAutoReply(data: MpAutoReplyApi.AutoReply) {
- return requestClient.post('/mp/auto-reply/create', data);
-}
-
-/** 淇敼鑷姩鍥炲 */
-export function updateAutoReply(data: MpAutoReplyApi.AutoReply) {
- return requestClient.put('/mp/auto-reply/update', data);
-}
-
-/** 鍒犻櫎鑷姩鍥炲 */
-export function deleteAutoReply(id: number) {
- return requestClient.delete(`/mp/auto-reply/delete?id=${id}`);
-}
diff --git a/src/api/mp/draft/index.ts b/src/api/mp/draft/index.ts
deleted file mode 100644
index e6f5b97..0000000
--- a/src/api/mp/draft/index.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpDraftApi {
- /** 鑽夌鏂囩珷淇℃伅 */
- export interface Article {
- title: string;
- author: string;
- digest: string;
- content: string;
- contentSourceUrl: string;
- thumbMediaId: string;
- showCoverPic: number;
- needOpenComment: number;
- onlyFansCanComment: number;
- }
-
- /** 鑽夌淇℃伅 */
- export interface Draft {
- id?: number;
- accountId: number;
- mediaId: string;
- articles: Article[];
- createTime?: Date;
- }
-
- /** 鍥炬枃椤癸紙鍖呭惈棰勮瀛楁锛� */
- export interface NewsItem {
- title: string;
- thumbMediaId: string;
- author: string;
- digest: string;
- showCoverPic: number;
- content: string;
- contentSourceUrl: string;
- needOpenComment: number;
- onlyFansCanComment: number;
- thumbUrl: string;
- picUrl?: string; // 鐢ㄤ簬棰勮灏侀潰
- }
-
- /** 鍥炬枃鍒楄〃 */
- export interface NewsItemList {
- newsItem: NewsItem[];
- }
-
- /** 鑽夌鏂囩珷锛堢敤浜庡睍绀猴級 */
- export interface DraftArticle {
- mediaId: string;
- content: NewsItemList;
- updateTime: number;
- }
-}
-
-/** 鍒涘缓绌虹殑鍥炬枃椤� */
-export function createEmptyNewsItem(): MpDraftApi.NewsItem {
- return {
- title: '',
- thumbMediaId: '',
- author: '',
- digest: '',
- showCoverPic: 0,
- content: '',
- contentSourceUrl: '',
- needOpenComment: 0,
- onlyFansCanComment: 0,
- thumbUrl: '',
- };
-}
-
-/** 鏌ヨ鑽夌鍒楄〃 */
-export function getDraftPage(params: PageParam) {
- return requestClient.get<PageResult<MpDraftApi.Draft>>('/mp/draft/page', {
- params,
- });
-}
-
-/** 鍒涘缓鑽夌 */
-export function createDraft(accountId: number, articles: MpDraftApi.Article[]) {
- return requestClient.post(
- '/mp/draft/create',
- { articles },
- {
- params: { accountId },
- },
- );
-}
-
-/** 鏇存柊鑽夌 */
-export function updateDraft(
- accountId: number,
- mediaId: string,
- articles: MpDraftApi.Article[],
-) {
- return requestClient.put('/mp/draft/update', articles, {
- params: { accountId, mediaId },
- });
-}
-
-/** 鍒犻櫎鑽夌 */
-export function deleteDraft(accountId: number, mediaId: string) {
- return requestClient.delete('/mp/draft/delete', {
- params: { accountId, mediaId },
- });
-}
diff --git a/src/api/mp/freePublish/index.ts b/src/api/mp/freePublish/index.ts
deleted file mode 100644
index 5cc26b1..0000000
--- a/src/api/mp/freePublish/index.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpFreePublishApi {
- /** 鍥炬枃鏂囩珷鍐呭 */
- export interface FreePublishArticle {
- title?: string;
- thumbUrl?: string;
- picUrl?: string;
- url?: string;
- }
-
- /** 鍥炬枃鍐呭 */
- export interface FreePublishContent {
- newsItem?: FreePublishArticle[];
- }
-
- /** 鑷敱鍙戝竷鏂囩珷淇℃伅 */
- export interface FreePublish {
- id?: number;
- accountId: number;
- mediaId: string;
- articleId: string;
- title: string;
- author: string;
- digest: string;
- content?: FreePublishContent;
- thumbUrl: string;
- status: number;
- publishTime?: Date;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ鑷敱鍙戝竷鏂囩珷鍒楄〃 */
-export function getFreePublishPage(params: PageParam) {
- return requestClient.get<PageResult<MpFreePublishApi.FreePublish>>(
- '/mp/free-publish/page',
- {
- params,
- },
- );
-}
-
-/** 鍒犻櫎鑷敱鍙戝竷鏂囩珷 */
-export function deleteFreePublish(accountId: number, articleId: string) {
- return requestClient.delete('/mp/free-publish/delete', {
- params: { accountId, articleId },
- });
-}
-
-/** 鍙戝竷鑷敱鍙戝竷鏂囩珷 */
-export function submitFreePublish(accountId: number, mediaId: string) {
- return requestClient.post('/mp/free-publish/submit', null, {
- params: { accountId, mediaId },
- });
-}
diff --git a/src/api/mp/material/index.ts b/src/api/mp/material/index.ts
deleted file mode 100644
index d61d295..0000000
--- a/src/api/mp/material/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { MaterialType } from '../../../packages/constants/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpMaterialApi {
- /** 绱犳潗淇℃伅 */
- export interface Material {
- id?: number;
- accountId: number;
- type: MaterialType;
- mediaId: string;
- url: string;
- name: string;
- size: number;
- remark?: string;
- createTime?: Date;
- }
-}
-
-/** 鏌ヨ绱犳潗鍒楄〃 */
-export function getMaterialPage(params: PageParam) {
- return requestClient.get<PageResult<MpMaterialApi.Material>>(
- '/mp/material/page',
- {
- params,
- },
- );
-}
-
-/** 鍒犻櫎姘镐箙绱犳潗 */
-export function deletePermanentMaterial(id: number) {
- return requestClient.delete('/mp/material/delete-permanent', {
- params: { id },
- });
-}
diff --git a/src/api/mp/menu/index.ts b/src/api/mp/menu/index.ts
deleted file mode 100644
index 86ae111..0000000
--- a/src/api/mp/menu/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { MenuType } from '../../../packages/constants/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpMenuApi {
- /** 鑿滃崟鎸夐挳淇℃伅 */
- export interface MenuButton {
- type: MenuType;
- name: string;
- key?: string;
- url?: string;
- mediaId?: string;
- appId?: string;
- pagePath?: string;
- subButtons?: MenuButton[];
- }
-
- /** 鑿滃崟淇℃伅 */
- export interface Menu {
- accountId: number;
- menus: MenuButton[];
- }
-}
-
-/** 鏌ヨ鑿滃崟鍒楄〃 */
-export function getMenuList(accountId: number) {
- return requestClient.get<MpMenuApi.MenuButton[]>('/mp/menu/list', {
- params: { accountId },
- });
-}
-
-/** 淇濆瓨鑿滃崟 */
-export function saveMenu(accountId: number, menus: MpMenuApi.MenuButton[]) {
- return requestClient.post('/mp/menu/save', {
- accountId,
- menus,
- });
-}
-
-/** 鍒犻櫎鑿滃崟 */
-export function deleteMenu(accountId: number) {
- return requestClient.delete('/mp/menu/delete', {
- params: { accountId },
- });
-}
diff --git a/src/api/mp/message/index.ts b/src/api/mp/message/index.ts
deleted file mode 100644
index 09a1d2d..0000000
--- a/src/api/mp/message/index.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { MessageType } from '../../../packages/constants/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpMessageApi {
- /** 娑堟伅淇℃伅 */
- export interface Message {
- id?: number;
- accountId: number;
- type: MessageType | string;
- openid: string;
- content: string;
- mediaId?: string;
- status: number;
- remark?: string;
- createTime?: Date;
- sendFrom?: number;
- userId?: number;
- event?: string;
- eventKey?: string;
- mediaUrl?: string;
- recognition?: string;
- url?: string;
- title?: string;
- label?: string;
- locationX?: number;
- locationY?: number;
- thumbMediaUrl?: string;
- musicUrl?: string;
- hqMusicUrl?: string;
- description?: string;
- articles?: any[];
- }
-
- /** 鍙戦�佹秷鎭姹� */
- export interface MessageSendRequestVO {
- accountId: number;
- openid: string;
- type: MessageType;
- content: string;
- mediaId?: string;
- }
-}
-
-/** 鏌ヨ娑堟伅鍒楄〃 */
-export function getMessagePage(params: PageParam) {
- return requestClient.get<PageResult<MpMessageApi.Message>>(
- '/mp/message/page',
- {
- params,
- },
- );
-}
-
-/** 鍙戦�佹秷鎭� */
-export function sendMessage(data: MpMessageApi.MessageSendRequestVO) {
- return requestClient.post('/mp/message/send', data);
-}
diff --git a/src/api/mp/messageTemplate/index.ts b/src/api/mp/messageTemplate/index.ts
deleted file mode 100644
index 6b0d682..0000000
--- a/src/api/mp/messageTemplate/index.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MpMessageTemplateApi {
- /** 娑堟伅妯℃澘淇℃伅 */
- export interface MessageTemplate {
- id: number;
- accountId: number;
- appId: string;
- templateId: string;
- title: string;
- content: string;
- example: string;
- primaryIndustry: string;
- deputyIndustry: string;
- createTime?: Date;
- }
-
- /** 鍙戦�佹秷鎭ā鏉胯姹� */
- export interface MessageTemplateSendVO {
- id: number;
- userId: number;
- data?: Record<string, string>;
- url?: string;
- miniProgramAppId?: string;
- miniProgramPagePath?: string;
- miniprogram?: string;
- }
-}
-
-/** 鏌ヨ娑堟伅妯℃澘鍒楄〃 */
-export function getMessageTemplateList(params: { accountId: number }) {
- return requestClient.get<MpMessageTemplateApi.MessageTemplate[]>(
- '/mp/message-template/list',
- { params },
- );
-}
-
-/** 鍒犻櫎娑堟伅妯℃澘 */
-export function deleteMessageTemplate(id: number) {
- return requestClient.delete('/mp/message-template/delete', {
- params: { id },
- });
-}
-
-/** 鍚屾鍏紬鍙锋ā鏉� */
-export function syncMessageTemplate(accountId: number) {
- return requestClient.post('/mp/message-template/sync', null, {
- params: { accountId },
- });
-}
-
-/** 鍙戦�佹秷鎭ā鏉� */
-export function sendMessageTemplate(
- data: MpMessageTemplateApi.MessageTemplateSendVO,
-) {
- return requestClient.post('/mp/message-template/send', data);
-}
diff --git a/src/api/mp/statistics/index.ts b/src/api/mp/statistics/index.ts
deleted file mode 100644
index 7c053ab..0000000
--- a/src/api/mp/statistics/index.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace MpStatisticsApi {
- /** 缁熻鏌ヨ鍙傛暟 */
- export interface StatisticsGetReqVO {
- accountId: number;
- date: Date[];
- }
-
- /** 娑堟伅鍙戦�佹鍐垫暟鎹� */
- export interface StatisticsUpstreamMessageRespVO {
- refDate: string;
- msgType: string;
- msgUser: number;
- msgCount: number;
- }
-
- /** 鐢ㄦ埛澧炲噺鏁版嵁 */
- export interface StatisticsUserSummaryRespVO {
- refDate: string;
- userSource: number;
- newUser: number;
- cancelUser: number;
- cumulateUser: number;
- }
-
- /** 鐢ㄦ埛绱鏁版嵁 */
- export interface StatisticsUserCumulateRespVO {
- refDate: string;
- cumulateUser: number;
- }
-
- /** 鎺ュ彛鍒嗘瀽鏁版嵁 */
- export interface StatisticsInterfaceSummaryRespVO {
- refDate: string;
- callbackCount: number;
- failCount: number;
- totalTimeCost: number;
- maxTimeCost: number;
- }
-}
-
-/** 鑾峰彇娑堟伅鍙戦�佹鍐垫暟鎹� */
-export function getUpstreamMessage(params: MpStatisticsApi.StatisticsGetReqVO) {
- return requestClient.get<MpStatisticsApi.StatisticsUpstreamMessageRespVO[]>(
- '/mp/statistics/upstream-message',
- {
- params,
- },
- );
-}
-
-/** 鑾峰彇鐢ㄦ埛澧炲噺鏁版嵁 */
-export function getUserSummary(params: MpStatisticsApi.StatisticsGetReqVO) {
- return requestClient.get<MpStatisticsApi.StatisticsUserSummaryRespVO[]>(
- '/mp/statistics/user-summary',
- {
- params,
- },
- );
-}
-
-/** 鑾峰彇鐢ㄦ埛绱鏁版嵁 */
-export function getUserCumulate(params: MpStatisticsApi.StatisticsGetReqVO) {
- return requestClient.get<MpStatisticsApi.StatisticsUserCumulateRespVO[]>(
- '/mp/statistics/user-cumulate',
- {
- params,
- },
- );
-}
-
-/** 鑾峰彇鎺ュ彛鍒嗘瀽鏁版嵁 */
-export function getInterfaceSummary(
- params: MpStatisticsApi.StatisticsGetReqVO,
-) {
- return requestClient.get<MpStatisticsApi.StatisticsInterfaceSummaryRespVO[]>(
- '/mp/statistics/interface-summary',
- {
- params,
- },
- );
-}
diff --git a/src/api/mp/tag/index.ts b/src/api/mp/tag/index.ts
deleted file mode 100644
index 37acbb7..0000000
--- a/src/api/mp/tag/index.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpTagApi {
- /** 鏍囩淇℃伅 */
- export interface Tag {
- id?: number;
- accountId: number;
- name: string;
- count?: number;
- createTime?: Date;
- }
-
- /** 鏍囩绮剧畝淇℃伅 */
- export interface SimpleTag {
- tagId: number;
- name: string;
- }
-}
-
-/** 鍒涘缓鍏紬鍙锋爣绛� */
-export function createTag(data: MpTagApi.Tag) {
- return requestClient.post('/mp/tag/create', data);
-}
-
-/** 鏇存柊鍏紬鍙锋爣绛� */
-export function updateTag(data: MpTagApi.Tag) {
- return requestClient.put('/mp/tag/update', data);
-}
-
-/** 鍒犻櫎鍏紬鍙锋爣绛� */
-export function deleteTag(id: number) {
- return requestClient.delete('/mp/tag/delete', {
- params: { id },
- });
-}
-
-/** 鑾峰彇鍏紬鍙锋爣绛� */
-export function getTag(id: number) {
- return requestClient.get<MpTagApi.Tag>('/mp/tag/get', {
- params: { id },
- });
-}
-
-/** 鑾峰彇鍏紬鍙锋爣绛惧垎椤� */
-export function getTagPage(params: PageParam) {
- return requestClient.get<PageResult<MpTagApi.Tag>>('/mp/tag/page', {
- params,
- });
-}
-
-/** 鑾峰彇鍏紬鍙锋爣绛剧簿绠�淇℃伅鍒楄〃 */
-export function getSimpleTagList() {
- return requestClient.get<MpTagApi.SimpleTag[]>('/mp/tag/list-all-simple');
-}
-
-/** 鍚屾鍏紬鍙锋爣绛� */
-export function syncTag(accountId: number) {
- return requestClient.post('/mp/tag/sync', null, {
- params: { accountId },
- });
-}
diff --git a/src/api/mp/user/index.ts b/src/api/mp/user/index.ts
deleted file mode 100644
index 7b28ceb..0000000
--- a/src/api/mp/user/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace MpUserApi {
- /** 鐢ㄦ埛淇℃伅 */
- export interface User {
- id?: number;
- accountId: number;
- openid: string;
- nickname: string;
- avatar: string;
- sex: number;
- country: string;
- province: string;
- city: string;
- language: string;
- subscribe: boolean;
- subscribeTime?: Date;
- remark?: string;
- tagIds?: number[];
- createTime?: Date;
- }
-}
-
-/** 鏇存柊鍏紬鍙风矇涓� */
-export function updateUser(data: MpUserApi.User) {
- return requestClient.put('/mp/user/update', data);
-}
-
-/** 鑾峰彇鍏紬鍙风矇涓� */
-export function getUser(id: number) {
- return requestClient.get<MpUserApi.User>('/mp/user/get', {
- params: { id },
- });
-}
-
-/** 鑾峰彇鍏紬鍙风矇涓濆垎椤� */
-export function getUserPage(params: PageParam) {
- return requestClient.get<PageResult<MpUserApi.User>>('/mp/user/page', {
- params,
- });
-}
-
-/** 鍚屾鍏紬鍙风矇涓� */
-export function syncUser(accountId: number) {
- return requestClient.post('/mp/user/sync', null, {
- params: { accountId },
- });
-}
diff --git a/src/api/pay/app/index.ts b/src/api/pay/app/index.ts
deleted file mode 100644
index 1fbf7b6..0000000
--- a/src/api/pay/app/index.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayAppApi {
- /** 鏀粯搴旂敤淇℃伅 */
- export interface App {
- id?: number;
- appKey: string;
- name: string;
- status: number;
- remark: string;
- payNotifyUrl: string;
- refundNotifyUrl: string;
- transferNotifyUrl: string;
- merchantId: number;
- merchantName: string;
- createTime?: Date;
- channelCodes?: string[];
- }
-
- /** 鏇存柊鐘舵�佽姹� */
- export interface AppUpdateStatusReqVO {
- id: number;
- status: number;
- }
-}
-
-/** 鏌ヨ鏀粯搴旂敤鍒楄〃 */
-export function getAppPage(params: PageParam) {
- return requestClient.get<PageResult<PayAppApi.App>>('/pay/app/page', {
- params,
- });
-}
-
-/** 鏌ヨ鏀粯搴旂敤璇︽儏 */
-export function getApp(id: number) {
- return requestClient.get<PayAppApi.App>(`/pay/app/get?id=${id}`);
-}
-
-/** 鏂板鏀粯搴旂敤 */
-export function createApp(data: PayAppApi.App) {
- return requestClient.post('/pay/app/create', data);
-}
-
-/** 淇敼鏀粯搴旂敤 */
-export function updateApp(data: PayAppApi.App) {
- return requestClient.put('/pay/app/update', data);
-}
-
-/** 淇敼鏀粯搴旂敤鐘舵�� */
-export function updateAppStatus(data: PayAppApi.AppUpdateStatusReqVO) {
- return requestClient.put('/pay/app/update-status', data);
-}
-
-/** 鍒犻櫎鏀粯搴旂敤 */
-export function deleteApp(id: number) {
- return requestClient.delete(`/pay/app/delete?id=${id}`);
-}
-
-/** 鑾峰彇鏀粯搴旂敤鍒楄〃 */
-export function getAppList() {
- return requestClient.get<PayAppApi.App[]>('/pay/app/list');
-}
diff --git a/src/api/pay/channel/index.ts b/src/api/pay/channel/index.ts
deleted file mode 100644
index 9900502..0000000
--- a/src/api/pay/channel/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace PayChannelApi {
- /** 鏀粯娓犻亾淇℃伅 */
- export interface Channel {
- id: number;
- code: string;
- config: string;
- status: number;
- remark: string;
- feeRate: number;
- appId: number;
- createTime: Date;
- }
-}
-
-/** 鏌ヨ鏀粯娓犻亾璇︽儏 */
-export function getChannel(appId: number, code: string) {
- return requestClient.get<PayChannelApi.Channel>('/pay/channel/get', {
- params: { appId, code },
- });
-}
-
-/** 鏂板鏀粯娓犻亾 */
-export function createChannel(data: PayChannelApi.Channel) {
- return requestClient.post('/pay/channel/create', data);
-}
-
-/** 淇敼鏀粯娓犻亾 */
-export function updateChannel(data: PayChannelApi.Channel) {
- return requestClient.put('/pay/channel/update', data);
-}
diff --git a/src/api/pay/demo/order/index.ts b/src/api/pay/demo/order/index.ts
deleted file mode 100644
index 7f5eaa1..0000000
--- a/src/api/pay/demo/order/index.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace DemoOrderApi {
- /** 绀轰緥璁㈠崟淇℃伅 */
- export interface Order {
- id?: number;
- userId?: number;
- spuName?: string;
- price?: number;
- payStatus?: boolean;
- payOrderId?: number;
- payTime?: Date;
- payChannelCode?: string;
- payRefundId?: number;
- refundPrice?: number;
- refundTime?: Date;
- spuId?: number;
- createTime?: Date;
- }
-}
-
-/** 鍒涘缓绀轰緥璁㈠崟 */
-export function createDemoOrder(data: DemoOrderApi.Order) {
- return requestClient.post('/pay/demo-order/create', data);
-}
-
-/** 鑾峰緱绀轰緥璁㈠崟鍒嗛〉 */
-export function getDemoOrderPage(params: PageParam) {
- return requestClient.get<PageResult<DemoOrderApi.Order>>(
- '/pay/demo-order/page',
- {
- params,
- },
- );
-}
-
-/** 閫�娆剧ず渚嬭鍗� */
-export function refundDemoOrder(id: number) {
- return requestClient.put(`/pay/demo-order/refund?id=${id}`);
-}
diff --git a/src/api/pay/demo/withdraw/index.ts b/src/api/pay/demo/withdraw/index.ts
deleted file mode 100644
index 98dfcbd..0000000
--- a/src/api/pay/demo/withdraw/index.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace DemoWithdrawApi {
- /** 绀轰緥鎻愮幇鍗曚俊鎭� */
- export interface Withdraw {
- id?: number;
- subject: string;
- price: number;
- userName: string;
- userAccount: string;
- type: number;
- status?: number;
- payTransferId?: number;
- transferChannelCode?: string;
- transferTime?: Date;
- transferErrorMsg?: string;
- }
-}
-
-/** 鏌ヨ绀轰緥鎻愮幇鍗曞垪琛� */
-export function getDemoWithdrawPage(params: PageParam) {
- return requestClient.get<PageResult<DemoWithdrawApi.Withdraw>>(
- '/pay/demo-withdraw/page',
- {
- params,
- },
- );
-}
-
-/** 鍒涘缓绀轰緥鎻愮幇鍗� */
-export function createDemoWithdraw(data: DemoWithdrawApi.Withdraw) {
- return requestClient.post('/pay/demo-withdraw/create', data);
-}
-
-/** 鍙戣捣鎻愮幇鍗曡浆璐� */
-export function transferDemoWithdraw(id: number) {
- return requestClient.post(`/pay/demo-withdraw/transfer?id=${id}`);
-}
diff --git a/src/api/pay/notify/index.ts b/src/api/pay/notify/index.ts
deleted file mode 100644
index 9117d0b..0000000
--- a/src/api/pay/notify/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayNotifyApi {
- /** 鏀粯閫氱煡鏃ュ織 */
- export interface NotifyLog {
- id?: number;
- status?: number;
- notifyTimes?: number;
- lastExecuteTime?: Date;
- createTime?: Date;
- response?: string;
- }
-
- /** 鏀粯閫氱煡浠诲姟 */
- export interface NotifyTask {
- id: number;
- appId: number;
- appName: string;
- type: number;
- dataId: number;
- status: number;
- merchantOrderId: string;
- merchantRefundId?: string;
- merchantTransferId?: string;
- lastExecuteTime: Date;
- nextNotifyTime: Date;
- notifyTimes: number;
- maxNotifyTimes: number;
- createTime: Date;
- updateTime: Date;
- logs?: NotifyLog[];
- }
-}
-
-/** 鑾峰緱鏀粯閫氱煡鏄庣粏 */
-export function getNotifyTaskDetail(id: number) {
- return requestClient.get<PayNotifyApi.NotifyTask>(`/pay/notify/get-detail?id=${id}`);
-}
-
-/** 鑾峰緱鏀粯閫氱煡鍒嗛〉 */
-export function getNotifyTaskPage(params: PageParam) {
- return requestClient.get<PageResult<PayNotifyApi.NotifyTask>>(
- '/pay/notify/page',
- {
- params,
- },
- );
-}
diff --git a/src/api/pay/order/index.ts b/src/api/pay/order/index.ts
deleted file mode 100644
index 2bad2f2..0000000
--- a/src/api/pay/order/index.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayOrderApi {
- /** 鏀粯璁㈠崟淇℃伅 */
- export interface Order {
- id: number;
- no: string;
- price: number;
- channelFeePrice: number;
- refundPrice: number;
- merchantId: number;
- appId: number;
- appName: string;
- channelId: number;
- channelCode: string;
- merchantOrderId: string;
- subject: string;
- body: string;
- notifyUrl: string;
- notifyStatus: number;
- amount: number;
- channelFeeRate: number;
- channelFeeAmount: number;
- status: number;
- userIp: string;
- expireTime: Date;
- successTime: Date;
- notifyTime: Date;
- successExtensionId: number;
- refundStatus: number;
- refundTimes: number;
- refundAmount: number;
- channelUserId: string;
- channelOrderNo: string;
- channelNotifyData: string;
- createTime: Date;
- updateTime: Date;
- }
-}
-
-/** 鏌ヨ鏀粯璁㈠崟鍒楄〃 */
-export function getOrderPage(params: PageParam) {
- return requestClient.get<PageResult<PayOrderApi.Order>>('/pay/order/page', {
- params,
- });
-}
-
-/** 鏌ヨ鏀粯璁㈠崟璇︽儏 */
-export function getOrder(id: number, sync?: boolean) {
- return requestClient.get<PayOrderApi.Order>('/pay/order/get', {
- params: {
- id,
- sync,
- },
- });
-}
-
-/** 鑾峰緱鏀粯璁㈠崟鐨勬槑缁� */
-export function getOrderDetail(id: number) {
- return requestClient.get<PayOrderApi.Order>(`/pay/order/get-detail?id=${id}`);
-}
-
-/** 鎻愪氦鏀粯璁㈠崟 */
-export function submitOrder(data: any) {
- return requestClient.post('/pay/order/submit', data);
-}
-
-/** 瀵煎嚭鏀粯璁㈠崟 */
-export function exportOrder(params: any) {
- return requestClient.download('/pay/order/export-excel', { params });
-}
diff --git a/src/api/pay/refund/index.ts b/src/api/pay/refund/index.ts
deleted file mode 100644
index 16f11ee..0000000
--- a/src/api/pay/refund/index.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayRefundApi {
- /** 閫�娆捐鍗曚俊鎭� */
- export interface Refund {
- id: number;
- merchantId: number;
- appId: number;
- channelId: number;
- channelCode: string;
- orderId: string;
- tradeNo: string;
- merchantOrderId: string;
- merchantRefundNo: string;
- merchantRefundId: string;
- notifyUrl: string;
- notifyStatus: number;
- status: number;
- payPrice: number;
- refundPrice: number;
- type: number;
- payAmount: number;
- refundAmount: number;
- reason: string;
- userIp: string;
- channelOrderNo: string;
- channelRefundNo: string;
- channelErrorCode: string;
- channelErrorMsg: string;
- channelExtras: string;
- expireTime: Date;
- successTime: Date;
- notifyTime: Date;
- createTime: Date;
- updateTime: Date;
- }
-}
-
-/** 鏌ヨ閫�娆捐鍗曞垪琛� */
-export function getRefundPage(params: PageParam) {
- return requestClient.get<PageResult<PayRefundApi.Refund>>(
- '/pay/refund/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ閫�娆捐鍗曡鎯� */
-export function getRefund(id: number) {
- return requestClient.get<PayRefundApi.Refund>(`/pay/refund/get?id=${id}`);
-}
-
-/** 瀵煎嚭閫�娆捐鍗� */
-export function exportRefund(params: any) {
- return requestClient.download('/pay/refund/export-excel', { params });
-}
diff --git a/src/api/pay/transfer/index.ts b/src/api/pay/transfer/index.ts
deleted file mode 100644
index 017ea22..0000000
--- a/src/api/pay/transfer/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { PageParam, PageResult } from '../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayTransferApi {
- /** 杞处鍗曚俊鎭� */
- export interface Transfer {
- id: number;
- no: string;
- appId: number;
- appName: string;
- channelId: number;
- channelCode: string;
- merchantTransferId: string;
- channelTransferNo: string;
- price: number;
- subject: string;
- userName: string;
- userAccount: string;
- userIp: string;
- status: number;
- successTime: Date;
- createTime: Date;
- updateTime: Date;
- notifyUrl: string;
- channelNotifyData: string;
- }
-}
-
-/** 鏌ヨ杞处鍗曞垪琛� */
-export function getTransferPage(params: PageParam) {
- return requestClient.get<PageResult<PayTransferApi.Transfer>>(
- '/pay/transfer/page',
- {
- params,
- },
- );
-}
-
-/** 鏌ヨ杞处鍗曡鎯� */
-export function getTransfer(id: number) {
- return requestClient.get<PayTransferApi.Transfer>(
- `/pay/transfer/get?id=${id}`,
- );
-}
-
-/** 瀵煎嚭杞处鍗� */
-export function exportTransfer(params: any) {
- return requestClient.download('/pay/transfer/export-excel', {
- params,
- });
-}
diff --git a/src/api/pay/wallet/balance/index.ts b/src/api/pay/wallet/balance/index.ts
deleted file mode 100644
index a70b124..0000000
--- a/src/api/pay/wallet/balance/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace PayWalletApi {
- /** 閽卞寘淇℃伅 */
- export interface Wallet {
- id: number;
- userId: number;
- userType: number;
- balance: number;
- totalExpense: number;
- totalRecharge: number;
- freezePrice: number;
- }
-
- /** 閽卞寘鏌ヨ鍙傛暟 */
- export interface WalletUserQueryReqVO {
- userId: number;
- }
-
- /** 閽卞寘淇敼浣欓 */
- export interface WalletUpdateBalanceReqVO {
- userId: number;
- balance: number;
- }
-}
-
-/** 鏌ヨ鐢ㄦ埛閽卞寘璇︽儏 */
-export function getWallet(params: PayWalletApi.WalletUserQueryReqVO) {
- return requestClient.get<PayWalletApi.Wallet>('/pay/wallet/get', {
- params,
- });
-}
-
-/** 鏌ヨ浼氬憳閽卞寘鍒楄〃 */
-export function getWalletPage(params: PageParam) {
- return requestClient.get<PageResult<PayWalletApi.Wallet>>(
- '/pay/wallet/page',
- {
- params,
- },
- );
-}
-
-/** 淇敼浼氬憳閽卞寘浣欓 */
-export function updateWalletBalance(
- data: PayWalletApi.WalletUpdateBalanceReqVO,
-) {
- return requestClient.put('/pay/wallet/update-balance', data);
-}
diff --git a/src/api/pay/wallet/rechargePackage/index.ts b/src/api/pay/wallet/rechargePackage/index.ts
deleted file mode 100644
index 6ee968c..0000000
--- a/src/api/pay/wallet/rechargePackage/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace WalletRechargePackageApi {
- /** 鍏呭�煎椁愪俊鎭� */
- export interface WalletRechargePackage {
- id?: number;
- name: string;
- payPrice: number;
- bonusPrice: number;
- status: number;
- }
-}
-
-/** 鏌ヨ鍏呭�煎椁愬垪琛� */
-export function getWalletRechargePackagePage(params: PageParam) {
- return requestClient.get<
- PageResult<WalletRechargePackageApi.WalletRechargePackage>
- >('/pay/wallet-recharge-package/page', {
- params,
- });
-}
-
-/** 鏌ヨ鍏呭�煎椁愯鎯� */
-export function getWalletRechargePackage(id: number) {
- return requestClient.get<WalletRechargePackageApi.WalletRechargePackage>(
- `/pay/wallet-recharge-package/get?id=${id}`,
- );
-}
-
-/** 鏂板鍏呭�煎椁� */
-export function createWalletRechargePackage(
- data: WalletRechargePackageApi.WalletRechargePackage,
-) {
- return requestClient.post('/pay/wallet-recharge-package/create', data);
-}
-
-/** 淇敼鍏呭�煎椁� */
-export function updateWalletRechargePackage(
- data: WalletRechargePackageApi.WalletRechargePackage,
-) {
- return requestClient.put('/pay/wallet-recharge-package/update', data);
-}
-
-/** 鍒犻櫎鍏呭�煎椁� */
-export function deleteWalletRechargePackage(id: number) {
- return requestClient.delete(`/pay/wallet-recharge-package/delete?id=${id}`);
-}
diff --git a/src/api/pay/wallet/transaction/index.ts b/src/api/pay/wallet/transaction/index.ts
deleted file mode 100644
index 3a4f173..0000000
--- a/src/api/pay/wallet/transaction/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
-
-import { requestClient } from '#/api/request';
-
-export namespace WalletTransactionApi {
- /** 閽卞寘浜ゆ槗娴佹按淇℃伅 */
- export interface Transaction {
- id: number;
- walletId: number;
- title: string;
- price: number;
- balance: number;
- }
-}
-
-/** 鏌ヨ閽卞寘浜ゆ槗娴佹按鍒楄〃 */
-export function getTransactionPage(params: PageParam) {
- return requestClient.get<PageResult<WalletTransactionApi.Transaction>>(
- '/pay/wallet-transaction/page',
- {
- params,
- },
- );
-}
diff --git a/src/layouts/basic.vue b/src/layouts/basic.vue
index 212bcc2..0fcd333 100644
--- a/src/layouts/basic.vue
+++ b/src/layouts/basic.vue
@@ -293,7 +293,7 @@
@on-click="handleClick"
/>
</template>
- <template #header-right-1>
+ <!-- <template #header-right-1>
<div v-if="tenantEnable">
<TenantDropdown
class="mr-2"
@@ -302,7 +302,7 @@
@success="handleTenantChange"
/>
</div>
- </template>
+ </template> -->
<template #extra>
<AuthenticationLoginExpiredModal
v-model:open="accessStore.loginExpired"
diff --git a/src/packages/effects/common-ui/src/ui/authentication/login.vue b/src/packages/effects/common-ui/src/ui/authentication/login.vue
index 4490925..3402dac 100644
--- a/src/packages/effects/common-ui/src/ui/authentication/login.vue
+++ b/src/packages/effects/common-ui/src/ui/authentication/login.vue
@@ -33,6 +33,7 @@
qrCodeLoginPath: '/auth/qrcode-login',
registerPath: '/auth/register',
showCodeLogin: true,
+ showDocLink: true,
showForgetPassword: true,
showQrcodeLogin: true,
showRegister: true,
@@ -198,6 +199,6 @@
</slot>
<!-- 钀屾柊蹇呰 -->
- <DocLink />
+ <DocLink v-if="showDocLink" />
</div>
</template>
diff --git a/src/packages/effects/common-ui/src/ui/authentication/types.ts b/src/packages/effects/common-ui/src/ui/authentication/types.ts
index cb297e2..3d2ee9f 100644
--- a/src/packages/effects/common-ui/src/ui/authentication/types.ts
+++ b/src/packages/effects/common-ui/src/ui/authentication/types.ts
@@ -53,6 +53,11 @@
showThirdPartyLogin?: boolean;
/**
+ * @zh_CN 鏄惁鏄剧ず钀屾柊蹇呰
+ */
+ showDocLink?: boolean;
+
+ /**
* @zh_CN 鐧诲綍妗嗗瓙鏍囬
*/
subTitle?: string;
diff --git a/src/router/routes/modules/im.ts b/src/router/routes/modules/im.ts
deleted file mode 100644
index b2c0205..0000000
--- a/src/router/routes/modules/im.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/im/home',
- name: 'ImHome',
- component: () => import('#/views/im/home/index.vue'),
- redirect: '/im/home/conversation',
- meta: {
- title: 'IM 鍗虫椂閫氳',
- hideInMenu: true,
- hideInTab: true,
- keepAlive: true,
- noBasicLayout: true,
- },
- children: [
- {
- path: 'conversation',
- name: 'ImHomeConversation',
- component: () => import('#/views/im/home/pages/conversation/index.vue'),
- meta: {
- title: '娑堟伅',
- activePath: '/im/home/conversation',
- hideInMenu: true,
- hideInTab: true,
- },
- },
- {
- path: 'contact',
- name: 'ImHomeContact',
- component: () => import('#/views/im/home/pages/contact/index.vue'),
- meta: {
- title: '閫氳褰�',
- activePath: '/im/home/contact',
- hideInMenu: true,
- hideInTab: true,
- },
- },
- ],
- },
-];
-
-export default routes;
diff --git a/src/router/routes/modules/iot.ts b/src/router/routes/modules/iot.ts
deleted file mode 100644
index a1d2203..0000000
--- a/src/router/routes/modules/iot.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/iot',
- name: 'IoTCenter',
- meta: {
- title: 'IoT 鐗╄仈缃�',
- icon: 'lucide:cpu',
- keepAlive: true,
- hideInMenu: true,
- },
- children: [
- {
- path: 'product/product/detail/:id',
- name: 'IoTProductDetail',
- meta: {
- title: '浜у搧璇︽儏',
- activePath: '/iot/device/product',
- },
- component: () => import('#/views/iot/product/product/detail/index.vue'),
- },
- {
- path: 'device/detail/:id',
- name: 'IoTDeviceDetail',
- meta: {
- title: '璁惧璇︽儏',
- activePath: '/iot/device/device',
- },
- component: () => import('#/views/iot/device/device/detail/index.vue'),
- },
- {
- path: 'ota/operation/firmware/detail/:id',
- name: 'IoTOtaFirmwareDetail',
- meta: {
- title: '鍥轰欢璇︽儏',
- activePath: '/iot/operation/ota/firmware',
- },
- component: () => import('#/views/iot/ota/firmware/detail/index.vue'),
- },
- ],
- },
-];
-
-export default routes;
diff --git a/src/router/routes/modules/mall.ts b/src/router/routes/modules/mall.ts
deleted file mode 100644
index db8fcab..0000000
--- a/src/router/routes/modules/mall.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/mall/product',
- name: 'ProductCenter',
- meta: {
- title: '鍟嗗搧涓績',
- icon: 'lucide:shopping-bag',
- keepAlive: true,
- hideInMenu: true,
- },
- children: [
- {
- path: 'spu/add',
- name: 'ProductSpuAdd',
- meta: {
- title: '鍟嗗搧娣诲姞',
- activePath: '/mall/product/spu',
- },
- component: () => import('#/views/mall/product/spu/form/index.vue'),
- },
- {
- path: String.raw`spu/edit/:id(\d+)`,
- name: 'ProductSpuEdit',
- meta: {
- title: '鍟嗗搧缂栬緫',
- activePath: '/mall/product/spu',
- },
- component: () => import('#/views/mall/product/spu/form/index.vue'),
- },
- {
- path: String.raw`spu/detail/:id(\d+)`,
- name: 'ProductSpuDetail',
- meta: {
- title: '鍟嗗搧璇︽儏',
- activePath: '/mall/product/spu',
- },
- component: () => import('#/views/mall/product/spu/form/index.vue'),
- },
- ],
- },
- {
- path: '/mall/trade',
- name: 'TradeCenter',
- meta: {
- title: '浜ゆ槗涓績',
- icon: 'lucide:shopping-cart',
- keepAlive: true,
- hideInMenu: true,
- },
- children: [
- {
- path: String.raw`order/detail/:id(\d+)`,
- name: 'TradeOrderDetail',
- meta: {
- title: '璁㈠崟璇︽儏',
- activePath: '/mall/trade/order',
- },
- component: () => import('#/views/mall/trade/order/detail/index.vue'),
- },
- {
- path: String.raw`after-sale/detail/:id(\d+)`,
- name: 'TradeAfterSaleDetail',
- meta: {
- title: '閫�娆捐鎯�',
- activePath: '/mall/trade/after-sale',
- },
- component: () =>
- import('#/views/mall/trade/afterSale/detail/index.vue'),
- },
- ],
- },
- {
- path: '/diy',
- name: 'DiyCenter',
- meta: {
- title: '钀ラ攢涓績',
- icon: 'lucide:shopping-bag',
- keepAlive: true,
- hideInMenu: true,
- },
- children: [
- {
- path: String.raw`template/decorate/:id(\d+)`,
- name: 'DiyTemplateDecorate',
- meta: {
- title: '妯℃澘瑁呬慨',
- activePath: '/mall/promotion/diy-template/diy-template',
- },
- component: () =>
- import('#/views/mall/promotion/diy/template/decorate/index.vue'),
- },
- {
- path: 'page/decorate/:id',
- name: 'DiyPageDecorate',
- meta: {
- title: '椤甸潰瑁呬慨',
- noCache: false,
- hidden: true,
- activePath: '/mall/promotion/diy-template/diy-page',
- },
- component: () =>
- import('#/views/mall/promotion/diy/page/decorate/index.vue'),
- },
- ],
- },
-];
-
-export default routes;
diff --git a/src/router/routes/modules/member.ts b/src/router/routes/modules/member.ts
deleted file mode 100644
index fc8bece..0000000
--- a/src/router/routes/modules/member.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/member/user/detail',
- component: () => import('#/views/member/user/detail/index.vue'),
- name: 'MemberUserDetail',
- meta: {
- title: '浼氬憳璇︽儏',
- icon: 'lucide:user',
- activePath: '/member/user',
- hideInMenu: true,
- },
- },
-];
-
-export default routes;
diff --git a/src/router/routes/modules/pay.ts b/src/router/routes/modules/pay.ts
deleted file mode 100644
index f7711fc..0000000
--- a/src/router/routes/modules/pay.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-const routes: RouteRecordRaw[] = [
- {
- path: '/pay/cashier',
- component: () => import('#/views/pay/cashier/index.vue'),
- name: 'PayCashier',
- meta: {
- title: '鏀堕摱鍙�',
- icon: 'lucide:badge-japanese-yen',
- hideInMenu: true,
- },
- },
-];
-
-export default routes;
diff --git a/src/views/_core/authentication/login.vue b/src/views/_core/authentication/login.vue
index 9df6483..e63e399 100644
--- a/src/views/_core/authentication/login.vue
+++ b/src/views/_core/authentication/login.vue
@@ -4,7 +4,6 @@
import type { AuthApi } from '#/api/core/auth';
import { computed, onMounted, ref } from 'vue';
-import { useRoute } from 'vue-router';
import { AuthenticationLogin, Verification, z } from '../../../packages/effects/common-ui/src';
import { isCaptchaEnable, isTenantEnable } from '../../../packages/effects/hooks/src';
@@ -16,13 +15,11 @@
getCaptcha,
getTenantByWebsite,
getTenantSimpleList,
- socialAuthRedirect,
} from '#/api/core/auth';
import { useAuthStore } from '#/store';
defineOptions({ name: 'Login' });
-const { query } = useRoute();
const authStore = useAuthStore();
const accessStore = useAccessStore();
const tenantEnable = isTenantEnable();
@@ -33,35 +30,15 @@
const captchaType = 'blockPuzzle'; // 楠岃瘉鐮佺被鍨嬶細'blockPuzzle' | 'clickWord'
-/** 鑾峰彇绉熸埛鍒楄〃锛屽苟榛樿閫変腑 */
+/** 鑾峰彇绉熸埛鍒楄〃 */
const tenantList = ref<AuthApi.TenantResult[]>([]); // 绉熸埛鍒楄〃
async function fetchTenantList() {
if (!tenantEnable) {
return;
}
try {
- // 鑾峰彇绉熸埛鍒楄〃銆佸煙鍚嶅搴旂鎴�
- const websiteTenantPromise = getTenantByWebsite(window.location.hostname);
+ // 鑾峰彇绉熸埛鍒楄〃
tenantList.value = await getTenantSimpleList();
-
- // 閫変腑绉熸埛锛氬煙鍚� > store 涓殑绉熸埛 > 棣栦釜绉熸埛
- let tenantId: null | number = null;
- const websiteTenant = await websiteTenantPromise;
- if (websiteTenant?.id) {
- tenantId = websiteTenant.id;
- }
- // 濡傛灉娌℃湁浠庡煙鍚嶈幏鍙栧埌绉熸埛锛屽皾璇曚粠 store 涓幏鍙�
- if (!tenantId && accessStore.tenantId) {
- tenantId = accessStore.tenantId;
- }
- // 濡傛灉杩樻槸娌℃湁绉熸埛锛屼娇鐢ㄥ垪琛ㄤ腑鐨勭涓�涓�
- if (!tenantId && tenantList.value?.[0]?.id) {
- tenantId = tenantList.value[0].id;
- }
-
- // 璁剧疆閫変腑鐨勭鎴风紪鍙�
- accessStore.setTenantId(tenantId);
- loginRef.value.getFormApi().setFieldValue('tenantId', tenantId?.toString());
} catch (error) {
console.error('鑾峰彇绉熸埛鍒楄〃澶辫触:', error);
}
@@ -90,28 +67,6 @@
}
}
-/** 澶勭悊绗笁鏂圭櫥褰� */
-const redirect = query?.redirect;
-async function handleThirdLogin(type: number) {
- if (type <= 0) {
- return;
- }
- try {
- // 璁$畻 redirectUri
- // tricky: type銆乺edirect 闇�瑕佸厛 encode 涓�娆★紝鍚﹀垯閽夐拤鍥炶皟浼氫涪澶便�傞厤鍚� social-login.vue#getUrlValue() 浣跨敤
- const redirectUri = `${
- location.origin
- }/auth/social-login?${encodeURIComponent(
- `type=${type}&redirect=${redirect || '/'}`,
- )}`;
-
- // 杩涜璺宠浆
- window.location.href = await socialAuthRedirect(type, redirectUri);
- } catch (error) {
- console.error('绗笁鏂圭櫥褰曞鐞嗗け璐�:', error);
- }
-}
-
/** 缁勪欢鎸傝浇鏃惰幏鍙栫鎴蜂俊鎭� */
onMounted(() => {
fetchTenantList();
@@ -119,28 +74,25 @@
const formSchema = computed((): VbenFormSchema[] => {
return [
- {
- component: 'VbenSelect',
- componentProps: {
- options: tenantList.value.map((item) => ({
- label: item.name,
- value: item.id.toString(),
- })),
- placeholder: $t('authentication.tenantTip'),
- },
- fieldName: 'tenantId',
- label: $t('authentication.tenant'),
- rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
- dependencies: {
- triggerFields: ['tenantId'],
- if: tenantEnable,
- trigger(values) {
- if (values.tenantId) {
- accessStore.setTenantId(Number(values.tenantId));
- }
- },
- },
- },
+ // {
+ // component: 'VbenSelect',
+ // componentProps: {
+ // options: tenantList.value.map((item) => ({
+ // label: item.name,
+ // value: item.id.toString(),
+ // })),
+ // placeholder: $t('authentication.tenantTip'),
+ // },
+ // fieldName: 'tenantId',
+ // label: $t('authentication.tenant'),
+ // dependencies: {
+ // triggerFields: ['tenantId'],
+ // if: tenantEnable,
+ // trigger(values) {
+ // accessStore.setTenantId(values.tenantId ? Number(values.tenantId) : null);
+ // },
+ // },
+ // },
{
component: 'VbenInput',
componentProps: {
@@ -175,8 +127,13 @@
ref="loginRef"
:form-schema="formSchema"
:loading="authStore.loginLoading"
+ :show-code-login="false"
+ :show-qrcode-login="false"
+ :show-third-party-login="false"
+ :show-register="false"
+ :show-forget-password="false"
+ :show-doc-link="false"
@submit="handleLogin"
- @third-login="handleThirdLogin"
/>
<Verification
ref="verifyRef"
diff --git a/src/views/ai/chat/manager/index.vue b/src/views/ai/chat/manager/index.vue
index e1dadbd..97b80fc 100644
--- a/src/views/ai/chat/manager/index.vue
+++ b/src/views/ai/chat/manager/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { Tabs } from 'ant-design-vue';
@@ -12,12 +12,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 瀵硅瘽鑱婂ぉ" url="https://doc.iocoder.cn/ai/chat/" />
- </template>
-
- <Tabs v-model:active-key="activeTabName">
+ <Page auto-content-height><Tabs v-model:active-key="activeTabName">
<Tabs.TabPane tab="瀵硅瘽鍒楄〃" key="conversation">
<ChatConversationList />
</Tabs.TabPane>
diff --git a/src/views/ai/image/manager/index.vue b/src/views/ai/image/manager/index.vue
index b5c259a..434403c 100644
--- a/src/views/ai/image/manager/index.vue
+++ b/src/views/ai/image/manager/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiImageApi } from '#/api/ai/image';
-import { confirm, DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -85,11 +85,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 缁樺浘鍒涗綔" url="https://doc.iocoder.cn/ai/image/" />
- </template>
- <Grid table-title="缁樼敾绠$悊鍒楄〃">
+ <Page auto-content-height><Grid table-title="缁樼敾绠$悊鍒楄〃">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/ai/knowledge/knowledge/index.vue b/src/views/ai/knowledge/knowledge/index.vue
index 2c870d5..ab622fb 100644
--- a/src/views/ai/knowledge/knowledge/index.vue
+++ b/src/views/ai/knowledge/knowledge/index.vue
@@ -4,7 +4,7 @@
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -102,11 +102,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 鎵嬪唽" url="https://doc.iocoder.cn/ai/build/" />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="AI 鐭ヨ瘑搴撳垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/ai/mindmap/manager/index.vue b/src/views/ai/mindmap/manager/index.vue
index ff6d061..fea7476 100644
--- a/src/views/ai/mindmap/manager/index.vue
+++ b/src/views/ai/mindmap/manager/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiMindmapApi } from '#/api/ai/mindmap';
-import { DocAlert, Page, useVbenDrawer } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenDrawer } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -76,12 +76,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 鎬濈淮瀵煎浘" url="https://doc.iocoder.cn/ai/mindmap/" />
- </template>
-
- <Drawer class="w-3/5">
+ <Page auto-content-height><Drawer class="w-3/5">
<Right
:generated-content="drawerApi.getData() as any"
:is-end="true"
diff --git a/src/views/ai/model/apiKey/index.vue b/src/views/ai/model/apiKey/index.vue
index 3c5437e..ee71421 100644
--- a/src/views/ai/model/apiKey/index.vue
+++ b/src/views/ai/model/apiKey/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiModelApiKeyApi } from '#/api/ai/model/apiKey';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,11 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 鎵嬪唽" url="https://doc.iocoder.cn/ai/build/" />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="API 瀵嗛挜鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/ai/model/chatRole/index.vue b/src/views/ai/model/chatRole/index.vue
index 3d69a16..dbf99f5 100644
--- a/src/views/ai/model/chatRole/index.vue
+++ b/src/views/ai/model/chatRole/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiModelChatRoleApi } from '#/api/ai/model/chatRole';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,11 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 瀵硅瘽鑱婂ぉ" url="https://doc.iocoder.cn/ai/chat/" />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鑱婂ぉ瑙掕壊鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/ai/model/model/index.vue b/src/views/ai/model/model/index.vue
index 4e16aea..896fb5b 100644
--- a/src/views/ai/model/model/index.vue
+++ b/src/views/ai/model/model/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiModelModelApi } from '#/api/ai/model/model';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,11 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 鎵嬪唽" url="https://doc.iocoder.cn/ai/build/" />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="妯″瀷閰嶇疆鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/ai/model/tool/index.vue b/src/views/ai/model/tool/index.vue
index 0e4bfd4..2beb323 100644
--- a/src/views/ai/model/tool/index.vue
+++ b/src/views/ai/model/tool/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiModelToolApi } from '#/api/ai/model/tool';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,14 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="AI 宸ュ叿璋冪敤锛坒unction calling锛�"
- url="https://doc.iocoder.cn/ai/tool/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="宸ュ叿鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/ai/music/manager/index.vue b/src/views/ai/music/manager/index.vue
index 0e659c2..6443d38 100644
--- a/src/views/ai/music/manager/index.vue
+++ b/src/views/ai/music/manager/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiMusicApi } from '#/api/ai/music';
-import { confirm, DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page } from '../../../../packages/effects/common-ui/src';
import { Button, message } from 'ant-design-vue';
@@ -85,11 +85,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 闊充箰鍒涗綔" url="https://doc.iocoder.cn/ai/music/" />
- </template>
- <Grid table-title="闊充箰绠$悊鍒楄〃">
+ <Page auto-content-height><Grid table-title="闊充箰绠$悊鍒楄〃">
<template #toolbar-tools>
<TableAction :actions="[]" />
</template>
diff --git a/src/views/ai/write/manager/index.vue b/src/views/ai/write/manager/index.vue
index c760d04..15621ad 100644
--- a/src/views/ai/write/manager/index.vue
+++ b/src/views/ai/write/manager/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiWriteApi } from '#/api/ai/write';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -64,11 +64,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="AI 鍐欎綔鍔╂墜" url="https://doc.iocoder.cn/ai/write/" />
- </template>
- <Grid table-title="鍐欎綔绠$悊鍒楄〃">
+ <Page auto-content-height><Grid table-title="鍐欎綔绠$悊鍒楄〃">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/bpm/category/index.vue b/src/views/bpm/category/index.vue
index 354fb97..35d795c 100644
--- a/src/views/bpm/category/index.vue
+++ b/src/views/bpm/category/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,12 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="宸ヤ綔娴佹墜鍐�" url="https://doc.iocoder.cn/bpm/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="娴佺▼鍒嗙被">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js b/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js
index 8aabdb3..3fbe498 100644
--- a/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js
+++ b/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js
@@ -1,4 +1,4 @@
-import { isFunction, isObject } from '../../../../../../../../packages/utils/src';
+import { isFunction, isObject } from '../../../../../../../../../packages/utils/src';
const WILDCARD = '*';
diff --git a/src/views/bpm/form/index.vue b/src/views/bpm/form/index.vue
index 7037999..d8c44e8 100644
--- a/src/views/bpm/form/index.vue
+++ b/src/views/bpm/form/index.vue
@@ -4,7 +4,7 @@
import { onActivated } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { $t } from '../../../packages/locales/src';
import { message } from 'ant-design-vue';
@@ -117,15 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="瀹℃壒鎺ュ叆锛堟祦绋嬭〃鍗曪級"
- url="https://doc.iocoder.cn/bpm/use-bpm-form/"
- />
- </template>
-
- <DetailModal />
+ <Page auto-content-height><DetailModal />
<Grid table-title="娴佺▼琛ㄥ崟">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/bpm/group/index.vue b/src/views/bpm/group/index.vue
index c9fe174..d24f2a3 100644
--- a/src/views/bpm/group/index.vue
+++ b/src/views/bpm/group/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,12 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="宸ヤ綔娴佹墜鍐�" url="https://doc.iocoder.cn/bpm/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢ㄦ埛鍒嗙粍">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/bpm/model/definition/index.vue b/src/views/bpm/model/definition/index.vue
index 063aca7..41d2990 100644
--- a/src/views/bpm/model/definition/index.vue
+++ b/src/views/bpm/model/definition/index.vue
@@ -5,7 +5,7 @@
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { BpmModelFormType } from '../../../../packages/constants/src';
import { Button, Tooltip } from 'ant-design-vue';
@@ -89,11 +89,7 @@
<template>
<Page auto-content-height>
- <FormCreateDetailModal />
- <template #doc>
- <DocAlert title="宸ヤ綔娴佹墜鍐�" url="https://doc.iocoder.cn/bpm/" />
- </template>
- <Grid table-title="娴佺▼瀹氫箟鍒楄〃">
+ <FormCreateDetailModal /><Grid table-title="娴佺▼瀹氫箟鍒楄〃">
<template #startUsers="{ row }">
<template v-if="!row.startUsers || row.startUsers.length === 0">
鍏ㄩ儴鍙
diff --git a/src/views/bpm/oa/leave/index.vue b/src/views/bpm/oa/leave/index.vue
index 40703fa..dd61d5d 100644
--- a/src/views/bpm/oa/leave/index.vue
+++ b/src/views/bpm/oa/leave/index.vue
@@ -4,7 +4,7 @@
import { h, onActivated } from 'vue';
-import { DocAlert, Page, prompt } from '../../../../packages/effects/common-ui/src';
+import { Page, prompt } from '../../../../packages/effects/common-ui/src';
import { BpmProcessInstanceStatus } from '../../../../packages/constants/src';
import { message, Textarea } from 'ant-design-vue';
@@ -134,15 +134,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="瀹℃壒鎺ュ叆锛堜笟鍔¤〃鍗曪級"
- url="https://doc.iocoder.cn/bpm/use-business-form/"
- />
- </template>
-
- <Grid table-title="璇峰亣鍒楄〃">
+ <Page auto-content-height><Grid table-title="璇峰亣鍒楄〃">
<template #toolbar-tools>
<TableAction
:actions="[
diff --git a/src/views/bpm/processExpression/index.vue b/src/views/bpm/processExpression/index.vue
index 2d9d554..f7b8f92 100644
--- a/src/views/bpm/processExpression/index.vue
+++ b/src/views/bpm/processExpression/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -83,15 +83,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="娴佺▼琛ㄨ揪寮�"
- url="https://doc.iocoder.cn/bpm/expression/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="娴佺▼琛ㄨ揪寮�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index 24e0763..20c4c4b 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -4,7 +4,7 @@
import { h } from 'vue';
-import { DocAlert, Page, prompt } from '../../../packages/effects/common-ui/src';
+import { Page, prompt } from '../../../packages/effects/common-ui/src';
import {
BpmModelFormType,
BpmProcessInstanceStatus,
@@ -126,15 +126,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="娴佺▼鍙戣捣銆佸彇娑堛�侀噸鏂板彂璧�"
- url="https://doc.iocoder.cn/bpm/process-instance"
- />
- </template>
-
- <Grid table-title="娴佺▼鐘舵��">
+ <Page auto-content-height><Grid table-title="娴佺▼鐘舵��">
<template #slot-summary="{ row }">
<div
class="flex flex-col py-2"
diff --git a/src/views/bpm/processInstance/manager/index.vue b/src/views/bpm/processInstance/manager/index.vue
index 8440f0d..5d65b11 100644
--- a/src/views/bpm/processInstance/manager/index.vue
+++ b/src/views/bpm/processInstance/manager/index.vue
@@ -4,7 +4,7 @@
import { h } from 'vue';
-import { DocAlert, Page, prompt } from '../../../../packages/effects/common-ui/src';
+import { Page, prompt } from '../../../../packages/effects/common-ui/src';
import { BpmProcessInstanceStatus } from '../../../../packages/constants/src';
import { Button, message, Textarea } from 'ant-design-vue';
@@ -99,12 +99,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="宸ヤ綔娴佹墜鍐�" url="https://doc.iocoder.cn/bpm" />
- </template>
-
- <Grid table-title="娴佺▼瀹炰緥">
+ <Page auto-content-height><Grid table-title="娴佺▼瀹炰緥">
<template #tasks="{ row }">
<template v-if="row.tasks && row.tasks.length > 0">
<Button
diff --git a/src/views/bpm/processListener/index.vue b/src/views/bpm/processListener/index.vue
index bac875d..28f3584 100644
--- a/src/views/bpm/processListener/index.vue
+++ b/src/views/bpm/processListener/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -83,15 +83,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鎵ц鐩戝惉鍣ㄣ�佷换鍔$洃鍚櫒"
- url="https://doc.iocoder.cn/bpm/listener/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="娴佺▼鐩戝惉鍣�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/bpm/task/copy/index.vue b/src/views/bpm/task/copy/index.vue
index 52bf2dd..e0c1837 100644
--- a/src/views/bpm/task/copy/index.vue
+++ b/src/views/bpm/task/copy/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessInstanceCopyPage } from '#/api/bpm/processInstance';
@@ -57,15 +57,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="瀹℃壒杞姙銆佸娲俱�佹妱閫�"
- url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
- />
- </template>
-
- <Grid table-title="鎶勯�佷换鍔�">
+ <Page auto-content-height><Grid table-title="鎶勯�佷换鍔�">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue
index 52bb882..b32ea9c 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -72,21 +72,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="瀹℃壒閫氳繃銆佷笉閫氳繃銆侀┏鍥�"
- url="https://doc.iocoder.cn/bpm/task-todo-done/"
- />
- <DocAlert title="瀹℃壒鍔犵銆佸噺绛�" url="https://doc.iocoder.cn/bpm/sign/" />
- <DocAlert
- title="瀹℃壒杞姙銆佸娲俱�佹妱閫�"
- url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
- />
- <DocAlert title="瀹℃壒鍔犵銆佸噺绛�" url="https://doc.iocoder.cn/bpm/sign/" />
- </template>
-
- <Grid table-title="宸插姙浠诲姟">
+ <Page auto-content-height><Grid table-title="宸插姙浠诲姟">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/bpm/task/manager/index.vue b/src/views/bpm/task/manager/index.vue
index 46a7d27..eda3db7 100644
--- a/src/views/bpm/task/manager/index.vue
+++ b/src/views/bpm/task/manager/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskManagerPage } from '#/api/bpm/task';
@@ -54,12 +54,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="宸ヤ綔娴佹墜鍐�" url="https://doc.iocoder.cn/bpm/" />
- </template>
-
- <Grid table-title="娴佺▼浠诲姟">
+ <Page auto-content-height><Grid table-title="娴佺▼浠诲姟">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/bpm/task/todo/index.vue b/src/views/bpm/task/todo/index.vue
index 821873a..241a508 100644
--- a/src/views/bpm/task/todo/index.vue
+++ b/src/views/bpm/task/todo/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskTodoPage } from '#/api/bpm/task';
@@ -55,21 +55,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="瀹℃壒閫氳繃銆佷笉閫氳繃銆侀┏鍥�"
- url="https://doc.iocoder.cn/bpm/task-todo-done/"
- />
- <DocAlert title="瀹℃壒鍔犵銆佸噺绛�" url="https://doc.iocoder.cn/bpm/sign/" />
- <DocAlert
- title="瀹℃壒杞姙銆佸娲俱�佹妱閫�"
- url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
- />
- <DocAlert title="瀹℃壒鍔犵銆佸噺绛�" url="https://doc.iocoder.cn/bpm/sign/" />
- </template>
-
- <Grid table-title="寰呭姙浠诲姟">
+ <Page auto-content-height><Grid table-title="寰呭姙浠诲姟">
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/crm/business/index.vue b/src/views/crm/business/index.vue
index 961239e..de7250c 100644
--- a/src/views/crm/business/index.vue
+++ b/src/views/crm/business/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -118,19 +118,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬晢鏈恒�戝晢鏈虹鐞嗐�佸晢鏈虹姸鎬�"
- url="https://doc.iocoder.cn/crm/business/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
diff --git a/src/views/crm/business/status/index.vue b/src/views/crm/business/status/index.vue
index fe9651c..3bc3529 100644
--- a/src/views/crm/business/status/index.vue
+++ b/src/views/crm/business/status/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { CrmBusinessStatusApi } from '#/api/crm/business/status';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -80,19 +80,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬晢鏈恒�戝晢鏈虹鐞嗐�佸晢鏈虹姸鎬�"
- url="https://doc.iocoder.cn/crm/business/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍟嗘満鐘舵�佸垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/crm/clue/index.vue b/src/views/crm/clue/index.vue
index 1d69977..7a438f4 100644
--- a/src/views/crm/clue/index.vue
+++ b/src/views/crm/clue/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -108,19 +108,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮嚎绱€�戠嚎绱㈢鐞�"
- url="https://doc.iocoder.cn/crm/clue/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
diff --git a/src/views/crm/contact/index.vue b/src/views/crm/contact/index.vue
index 5ffe2aa..9f66a39 100644
--- a/src/views/crm/contact/index.vue
+++ b/src/views/crm/contact/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -118,19 +118,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬鎴枫�戝鎴风鐞嗐�佸叕娴峰鎴�"
- url="https://doc.iocoder.cn/crm/customer/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
diff --git a/src/views/crm/contract/index.vue b/src/views/crm/contract/index.vue
index 7d847ac..374c07c 100644
--- a/src/views/crm/contract/index.vue
+++ b/src/views/crm/contract/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -152,19 +152,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬悎鍚屻�戝悎鍚岀鐞嗐�佸悎鍚屾彁閱�"
- url="https://doc.iocoder.cn/crm/contract/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
diff --git a/src/views/crm/customer/index.vue b/src/views/crm/customer/index.vue
index 6cac06a..a2de05d 100644
--- a/src/views/crm/customer/index.vue
+++ b/src/views/crm/customer/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -124,19 +124,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬鎴枫�戝鎴风鐞�"
- url="https://doc.iocoder.cn/crm/customer/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
diff --git a/src/views/crm/customer/limitConfig/index.vue b/src/views/crm/customer/limitConfig/index.vue
index 720e6dc..0e60eef 100644
--- a/src/views/crm/customer/limitConfig/index.vue
+++ b/src/views/crm/customer/limitConfig/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message, Tabs } from 'ant-design-vue';
@@ -100,19 +100,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬鎴枫�戝鎴风鐞嗐�佸叕娴峰鎴�"
- url="https://doc.iocoder.cn/crm/customer/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeConfigType">
diff --git a/src/views/crm/customer/pool/index.vue b/src/views/crm/customer/pool/index.vue
index c6d309d..ce4a1a6 100644
--- a/src/views/crm/customer/pool/index.vue
+++ b/src/views/crm/customer/pool/index.vue
@@ -4,7 +4,7 @@
import { useRouter } from 'vue-router';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button } from 'ant-design-vue';
@@ -61,19 +61,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬鎴枫�戝鎴风鐞嗐�佸叕娴峰鎴�"
- url="https://doc.iocoder.cn/crm/customer/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <Grid>
+ <Page auto-content-height><Grid>
<template #toolbar-tools>
<TableAction
:actions="[
diff --git a/src/views/crm/product/category/index.vue b/src/views/crm/product/category/index.vue
index 09b6557..aecd809 100644
--- a/src/views/crm/product/category/index.vue
+++ b/src/views/crm/product/category/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -103,15 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪骇鍝併�戜骇鍝佺鐞嗐�佷骇鍝佸垎绫�"
- url="https://doc.iocoder.cn/crm/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-tools>
<TableAction
diff --git a/src/views/crm/receivable/index.vue b/src/views/crm/receivable/index.vue
index a1620e3..f0ce55b 100644
--- a/src/views/crm/receivable/index.vue
+++ b/src/views/crm/receivable/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -147,19 +147,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬洖娆俱�戝洖娆剧鐞嗐�佸洖娆捐鍒�"
- url="https://doc.iocoder.cn/crm/receivable/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
diff --git a/src/views/crm/receivable/plan/index.vue b/src/views/crm/receivable/plan/index.vue
index b305922..eae3da8 100644
--- a/src/views/crm/receivable/plan/index.vue
+++ b/src/views/crm/receivable/plan/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message, Tabs } from 'ant-design-vue';
@@ -129,19 +129,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬洖娆俱�戝洖娆剧鐞嗐�佸洖娆捐鍒�"
- url="https://doc.iocoder.cn/crm/receivable/"
- />
- <DocAlert
- title="銆愰�氱敤銆戞暟鎹潈闄�"
- url="https://doc.iocoder.cn/crm/permission/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ReceivableFormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
diff --git a/src/views/erp/finance/account/index.vue b/src/views/erp/finance/account/index.vue
index f3db341..23ff26e 100644
--- a/src/views/erp/finance/account/index.vue
+++ b/src/views/erp/finance/account/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpAccountApi } from '#/api/erp/finance/account';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -111,15 +111,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯储鍔°�戦噰璐粯娆俱�侀攢鍞敹娆�"
- url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="缁撶畻璐︽埛鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/finance/payment/index.vue b/src/views/erp/finance/payment/index.vue
index 8312657..b3ee2b3 100644
--- a/src/views/erp/finance/payment/index.vue
+++ b/src/views/erp/finance/payment/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯储鍔°�戦噰璐粯娆俱�侀攢鍞敹娆�"
- url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浠樻鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/finance/receipt/index.vue b/src/views/erp/finance/receipt/index.vue
index ca2e9da..d9a5597 100644
--- a/src/views/erp/finance/receipt/index.vue
+++ b/src/views/erp/finance/receipt/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯储鍔°�戦噰璐粯娆俱�侀攢鍞敹娆�"
- url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鏀舵鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/home/index.vue b/src/views/erp/home/index.vue
index 7ec99fe..52fa2c3 100644
--- a/src/views/erp/home/index.vue
+++ b/src/views/erp/home/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import { Col, Row, Spin } from 'ant-design-vue';
@@ -15,15 +15,7 @@
</script>
<template>
- <Page>
- <template #doc>
- <DocAlert
- title="ERP 鎵嬪唽锛堝姛鑳藉紑鍚級"
- url="https://doc.iocoder.cn/erp/build/"
- />
- </template>
-
- <Spin :spinning="loading">
+ <Page><Spin :spinning="loading">
<div class="flex flex-col gap-4">
<!-- 閿�鍞�/閲囪喘鐨勫叏灞�缁熻 -->
<SummaryCard />
diff --git a/src/views/erp/product/category/index.vue b/src/views/erp/product/category/index.vue
index bd0cffb..04097d9 100644
--- a/src/views/erp/product/category/index.vue
+++ b/src/views/erp/product/category/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -110,15 +110,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪骇鍝併�戜骇鍝佷俊鎭�佸垎绫汇�佸崟浣�"
- url="https://doc.iocoder.cn/erp/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浜у搧鍒嗙被鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/product/product/index.vue b/src/views/erp/product/product/index.vue
index 84a7823..2ed278f 100644
--- a/src/views/erp/product/product/index.vue
+++ b/src/views/erp/product/product/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpProductApi } from '#/api/erp/product/product';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,15 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪骇鍝併�戜骇鍝佷俊鎭�佸垎绫汇�佸崟浣�"
- url="https://doc.iocoder.cn/erp/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浜у搧鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/product/unit/index.vue b/src/views/erp/product/unit/index.vue
index 201c9e2..52b2ac3 100644
--- a/src/views/erp/product/unit/index.vue
+++ b/src/views/erp/product/unit/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpProductUnitApi } from '#/api/erp/product/unit';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,14 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪骇鍝併�戜骇鍝佷俊鎭�佸垎绫汇�佸崟浣�"
- url="https://doc.iocoder.cn/erp/product/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浜у搧鍗曚綅鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/purchase/in/index.vue b/src/views/erp/purchase/in/index.vue
index 875bca6..a9a2e4b 100644
--- a/src/views/erp/purchase/in/index.vue
+++ b/src/views/erp/purchase/in/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰噰璐�戦噰璐鍗曘�佸叆搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/purchase/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閲囪喘鍏ュ簱鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/purchase/order/index.vue b/src/views/erp/purchase/order/index.vue
index c759ee2..288b0b0 100644
--- a/src/views/erp/purchase/order/index.vue
+++ b/src/views/erp/purchase/order/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰噰璐�戦噰璐鍗曘�佸叆搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/purchase/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閲囪喘璁㈠崟鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/purchase/return/index.vue b/src/views/erp/purchase/return/index.vue
index b799ffa..7a566ff 100644
--- a/src/views/erp/purchase/return/index.vue
+++ b/src/views/erp/purchase/return/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,14 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰噰璐�戦噰璐鍗曘�佸叆搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/purchase/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閲囪喘閫�璐у垪琛�">
<template #toolbar-tools>
diff --git a/src/views/erp/purchase/supplier/index.vue b/src/views/erp/purchase/supplier/index.vue
index 071b183..06458f6 100644
--- a/src/views/erp/purchase/supplier/index.vue
+++ b/src/views/erp/purchase/supplier/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpSupplierApi } from '#/api/erp/purchase/supplier';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -94,15 +94,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰噰璐�戦噰璐鍗曘�佸叆搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/purchase/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="渚涘簲鍟嗗垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/sale/customer/index.vue b/src/views/erp/sale/customer/index.vue
index 6456d3e..1d416ce 100644
--- a/src/views/erp/sale/customer/index.vue
+++ b/src/views/erp/sale/customer/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpCustomerApi } from '#/api/erp/sale/customer';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,14 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰攢鍞�戦攢鍞鍗曘�佸嚭搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/sale/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="瀹㈡埛鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index 2237935..8c663a6 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/src/views/erp/sale/order/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰攢鍞�戦攢鍞鍗曘�佸嚭搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/sale/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閿�鍞鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/sale/out/index.vue b/src/views/erp/sale/out/index.vue
index 072bdad..28d23fc 100644
--- a/src/views/erp/sale/out/index.vue
+++ b/src/views/erp/sale/out/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -130,15 +130,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰攢鍞�戦攢鍞鍗曘�佸嚭搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/sale/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閿�鍞嚭搴撳垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/sale/return/index.vue b/src/views/erp/sale/return/index.vue
index 72e0e1f..ce668db 100644
--- a/src/views/erp/sale/return/index.vue
+++ b/src/views/erp/sale/return/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愰攢鍞�戦攢鍞鍗曘�佸嚭搴撱�侀��璐�"
- url="https://doc.iocoder.cn/erp/sale/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閿�鍞��璐у垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/stock/check/index.vue b/src/views/erp/stock/check/index.vue
index 243425b..3003290 100644
--- a/src/views/erp/stock/check/index.vue
+++ b/src/views/erp/stock/check/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝簱瀛樿皟鎷ㄣ�佸簱瀛樼洏鐐�"
- url="https://doc.iocoder.cn/erp/stock-move-check/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="搴撳瓨鐩樼偣鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/stock/in/index.vue b/src/views/erp/stock/in/index.vue
index 2d24695..ed5eb29 100644
--- a/src/views/erp/stock/in/index.vue
+++ b/src/views/erp/stock/in/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -130,15 +130,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝叾瀹冨叆搴撱�佸叾瀹冨嚭搴�"
- url="https://doc.iocoder.cn/erp/stock-in-out/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍏跺畠鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/stock/move/index.vue b/src/views/erp/stock/move/index.vue
index 45a2829..934df2d 100644
--- a/src/views/erp/stock/move/index.vue
+++ b/src/views/erp/stock/move/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝簱瀛樿皟鎷ㄣ�佸簱瀛樼洏鐐�"
- url="https://doc.iocoder.cn/erp/stock-move-check/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="搴撳瓨璋冩嫧鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/stock/out/index.vue b/src/views/erp/stock/out/index.vue
index d7b8600..ab77bf6 100644
--- a/src/views/erp/stock/out/index.vue
+++ b/src/views/erp/stock/out/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -133,15 +133,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝叾瀹冨叆搴撱�佸叾瀹冨嚭搴�"
- url="https://doc.iocoder.cn/erp/stock-in-out/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍏跺畠鍑哄簱鍗曞垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/erp/stock/record/index.vue b/src/views/erp/stock/record/index.vue
index 02ae030..2c8c072 100644
--- a/src/views/erp/stock/record/index.vue
+++ b/src/views/erp/stock/record/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpStockRecordApi } from '#/api/erp/stock/record';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -52,15 +52,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戜骇鍝佸簱瀛樸�佸簱瀛樻槑缁�"
- url="https://doc.iocoder.cn/erp/stock/"
- />
- </template>
-
- <Grid table-title="浜у搧搴撳瓨鏄庣粏鍒楄〃">
+ <Page auto-content-height><Grid table-title="浜у搧搴撳瓨鏄庣粏鍒楄〃">
<template #toolbar-tools>
<TableAction
:actions="[
diff --git a/src/views/erp/stock/stock/index.vue b/src/views/erp/stock/stock/index.vue
index b819d4a..49b3cef 100644
--- a/src/views/erp/stock/stock/index.vue
+++ b/src/views/erp/stock/stock/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpStockApi } from '#/api/erp/stock/stock';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -52,15 +52,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戜骇鍝佸簱瀛樸�佸簱瀛樻槑缁�"
- url="https://doc.iocoder.cn/erp/stock/"
- />
- </template>
-
- <Grid table-title="浜у搧搴撳瓨鍒楄〃">
+ <Page auto-content-height><Grid table-title="浜у搧搴撳瓨鍒楄〃">
<template #toolbar-tools>
<TableAction
:actions="[
diff --git a/src/views/erp/stock/warehouse/index.vue b/src/views/erp/stock/warehouse/index.vue
index a42aff2..5c3e26c 100644
--- a/src/views/erp/stock/warehouse/index.vue
+++ b/src/views/erp/stock/warehouse/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { ErpWarehouseApi } from '#/api/erp/stock/warehouse';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -114,15 +114,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戜骇鍝佸簱瀛樸�佸簱瀛樻槑缁�"
- url="https://doc.iocoder.cn/erp/stock/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浠撳簱鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/im/home/components/card/card-bubble.vue b/src/views/im/home/components/card/card-bubble.vue
deleted file mode 100644
index 0fb56da..0000000
--- a/src/views/im/home/components/card/card-bubble.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { isPrivateConversation } from '#/views/im/utils/constants'
-import {
- type CardMessage,
- type CardTarget,
- getCardLabelInfo
-} from '#/views/im/utils/message'
-
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImCardBubble' })
-
-const props = withDefaults(
- defineProps<{
- /** 鍚嶇墖鏁版嵁锛汣ardMessage锛堟帴鏀朵晶娑堟伅浣擄級鎴� CardTarget锛堝彂閫佷晶棰勮锛夊叡鐢ㄧ粨鏋� */
- card: CardMessage | CardTarget
- /** 鏄惁鏄剧ず cursor: pointer锛涜皟鐢ㄦ柟璐熻矗缁� @click 鐩戝惉 */
- clickable?: boolean
- }>(),
- { clickable: false }
-)
-
-/** 鏄惁鐢ㄦ埛鍚嶇墖锛氬喅瀹� UserAvatar 鏄惁甯� id 瑙﹀彂 UserInfoCard */
-const isUser = computed(() => isPrivateConversation(props.card.targetType))
-/** 鍚嶇墖鏍囩淇℃伅 */
-const labelInfo = computed(() => getCardLabelInfo(props.card))
-</script>
-
-<template>
- <!--
- 鍚嶇墖娑堟伅姘旀场 / 鍚嶇墖棰勮鍗★紙240px锛夛細鐢ㄦ埛鍚嶇墖 + 缇ゅ悕鐗囬�氱敤
- - 澶村儚 + 鍚嶅瓧 + 缇ゆ垚鍛樻暟鍓爣棰橈紙浠呯兢鍚嶇墖锛�+ 搴曢儴鍒嗛殧鏉°�岀兢鍚嶇墖 / 涓汉鍚嶇墖銆�
- - 鐢ㄦ埛鍚嶇墖鎶� :id 浼犵粰 UserAvatar 璁╃偣鍑� avatar 寮� UserInfoCard锛涚兢鍚嶇墖涓嶄紶 id
- - 鏁村崱 click 鐢辫皟鐢ㄦ柟鐩戝惉锛園click锛夛紝缁勪欢涓嶅唴宓屼笟鍔¢�昏緫
- -->
- <div
- class="flex flex-col w-[240px] rounded-md overflow-hidden bg-[var(--ant-color-bg-container)] border border-solid border-[var(--im-border-color-lighter)]"
- :class="{ 'cursor-pointer': clickable }"
- >
- <div class="flex gap-2.5 items-center px-3 py-2.5">
- <UserAvatar
- :id="isUser ? card.targetId : undefined"
- :url="card.avatar"
- :name="card.name"
- :size="40"
- :clickable="false"
- />
- <div class="flex flex-col flex-1 min-w-0">
- <div class="text-sm font-medium truncate text-[var(--ant-color-text)]">
- {{ card.name }}
- </div>
- <div
- v-if="!isUser && card.memberCount"
- class="text-12px truncate text-[var(--ant-color-text-placeholder)]"
- >
- {{ card.memberCount }} 浜虹兢鑱�
- </div>
- </div>
- </div>
- <div
- class="px-3 py-1 text-12px border-t border-t-solid text-[var(--ant-color-text-placeholder)] border-[var(--im-border-color-lighter)] bg-[var(--ant-color-fill-tertiary)]"
- >
- {{ labelInfo.label }}
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/card/card-line-label.vue b/src/views/im/home/components/card/card-line-label.vue
deleted file mode 100644
index 941fd74..0000000
--- a/src/views/im/home/components/card/card-line-label.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { getCardLabelInfo } from '#/views/im/utils/message'
-
-defineOptions({ name: 'ImCardLineLabel' })
-
-const props = withDefaults(
- defineProps<{
- /** 鍚嶇墖鏁版嵁锛涘彧璇� targetType / name 娲剧敓鏍囩 + 鏄剧ず锛岀粨鏋勬�х被鍨嬪吋瀹� CardMessage / 寮曠敤棰勮鐨� partial */
- card: null | undefined | { name?: string; targetType?: number; }
- iconSize?: number
- }>(),
- { iconSize: 14 }
-)
-
-/** 鏍囩 + 鍥炬爣鎸� targetType 浜屽垎锛涘厹搴曘�屼釜浜哄悕鐗囥�嶉伩鍏� null 鏃� UI 绌虹櫧 */
-const labelInfo = computed(() => getCardLabelInfo(props.card))
-</script>
-
-<template>
- <!-- 鍚嶇墖鍗曡 inline label锛氥�孾icon] 缇ゅ悕鐗� / 涓汉鍚嶇墖锛歺x銆嶏紱鍒楄〃鎽樿 / 寮曠敤棰勮 / 鍚庡彴棰勮澶嶇敤 -->
- <span class="inline-flex gap-1.5 items-center">
- <Icon :icon="labelInfo.icon" :size="iconSize" />
- <span>{{ labelInfo.label }}锛歿{ card?.name || '' }}</span>
- </span>
-</template>
diff --git a/src/views/im/home/components/card/index.ts b/src/views/im/home/components/card/index.ts
deleted file mode 100644
index ac62b1b..0000000
--- a/src/views/im/home/components/card/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as CardBubble } from './card-bubble.vue';
-export { default as CardLineLabel } from './card-line-label.vue';
diff --git a/src/views/im/home/components/context-menu.vue b/src/views/im/home/components/context-menu.vue
deleted file mode 100644
index 1ba2056..0000000
--- a/src/views/im/home/components/context-menu.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../packages/icons/src'
-
-import { useImUiStore } from '../store/uiStore'
-
-defineOptions({ name: 'ImContextMenu' })
-
-const uiStore = useImUiStore()
-const contextMenu = computed(() => uiStore.contextMenu)
-
-/**
- * 璁$畻鑿滃崟瀹為檯娓叉煋鍧愭爣锛氶潬杩戣鍙e彸 / 涓嬭竟缂樻椂鍥炲脊锛岄伩鍏嶈彍鍗曡瑁佸壀
- *
- * itemHeight / menuWidth 鏄拰妯℃澘閲� px-4 py-2 + text-13px / min-w-30 閰嶅鐨勫疄闄呭昂瀵革紱
- * dividerHeight = 9px锛坢y-1 涓婁笅鍚� 4 + 1px border锛夛紝浠呴潪棣栭」鐨� divided 璁″叆锛�
- * menuHeight 棰濆鍔� 8 鏄灞� py-1 鐨勪笂涓� padding 涔嬪拰锛�4px 脳 2锛�
- */
-const adjustedPosition = computed(() => {
- const items = contextMenu.value.items
- const itemHeight = 34
- const dividerCount = items.filter((it, i) => it.divided && i > 0).length
- const menuHeight = items.length * itemHeight + dividerCount * 9 + 8
- const menuWidth = 120
- let x = contextMenu.value.position.x
- let y = contextMenu.value.position.y
- // SSR 鍏滃簳锛歸indow 涓嶅彲鐢ㄦ椂鐩存帴杩斿洖鍘熷鍧愭爣
- if (typeof window !== 'undefined') {
- if (y + menuHeight > window.innerHeight) {
- y = window.innerHeight - menuHeight
- }
- if (x + menuWidth > window.innerWidth) {
- x = window.innerWidth - menuWidth
- }
- }
- // 瑙嗗彛寰堝皬 / 鑿滃崟椤瑰緢澶氭椂涓婇潰鍑忔硶浼氱畻鍑鸿礋鍊硷紝鎶婅彍鍗曢《 / 宸﹁竟鎺ㄥ埌 0 鍏滃簳
- return { x: Math.max(0, x), y: Math.max(0, y) }
-})
-
-type MenuItem = (typeof contextMenu.value.items)[number]
-
-/** 閫変腑鑿滃崟椤癸細disabled 椤瑰拷鐣ワ紱姝e父椤硅皟 onSelect 鍥炶皟鍚庡叧闂彍鍗� */
-function handleSelect(item: MenuItem) {
- if (item.disabled) {
- return
- }
- uiStore.contextMenu.onSelect?.(item)
- uiStore.closeContextMenu()
-}
-
-/** 鍏抽棴鑿滃崟锛氱偣閬僵 / 鍦ㄩ伄缃╀笂鍐嶆鍙抽敭閮戒細瑙﹀彂 */
-function handleClose() {
- uiStore.closeContextMenu()
-}
-</script>
-
-<template>
- <!--
- 閫氱敤鍙抽敭鑿滃崟
- 鐢� useImUiStore.openContextMenu(position, items, onSelect) 瑙﹀彂鍏ㄥ眬鍗曚緥灞曠ず
- 璋冪敤鏂瑰湪 @contextmenu.prevent 浜嬩欢閲岃皟 openContextMenu 鍗冲彲锛屼笉闇�瑕佽嚜宸辨寕缁勪欢
- -->
- <teleport to="body">
- <div
- v-if="contextMenu.show"
- class="fixed inset-0 z-9999"
- @click.stop="handleClose"
- @contextmenu.prevent="handleClose"
- >
- <div
- class="fixed min-w-30 py-1 bg-[var(--ant-color-bg-elevated)] rounded-md shadow-lg"
- :style="{ left: `${adjustedPosition.x }px`, top: `${adjustedPosition.y }px` }"
- >
- <template v-for="(item, index) in contextMenu.items" :key="item.key">
- <!-- divided 椤逛笂鏂规彃涓�鏉″垎鍓茬嚎锛堥椤硅烦杩囷紝閬垮厤绌虹櫧锛� -->
- <div
- v-if="item.divided && index > 0"
- class="my-1 mx-2 h-[1px] bg-[var(--ant-color-border-secondary)]"
- ></div>
- <div
- class="flex gap-2 items-center px-4 py-2 text-13px text-left cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="[
- item.disabled
- ? '!text-[var(--ant-color-text-disabled)] cursor-not-allowed hover:!bg-transparent'
- : item.danger
- ? 'text-[#f56c6c]'
- : 'text-[var(--ant-color-text)]'
- ]"
- @click.stop="handleSelect(item)"
- >
- <Icon v-if="item.icon" :icon="item.icon" :size="14" />
- <span>{{ item.name }}</span>
- </div>
- </template>
- </div>
- </div>
- </teleport>
-</template>
diff --git a/src/views/im/home/components/friend/friend-add-dialog.vue b/src/views/im/home/components/friend/friend-add-dialog.vue
deleted file mode 100644
index d6b6d82..0000000
--- a/src/views/im/home/components/friend/friend-add-dialog.vue
+++ /dev/null
@@ -1,289 +0,0 @@
-<script lang="ts" setup>
-import type { SystemUserApi } from '#/api/system/user'
-
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-import { useUserStore } from '../../../../../packages/stores/src'
-
-import { Button, Input, message, Modal, Spin } from 'ant-design-vue'
-
-import { getSimpleUserListByNickname } from '#/api/system/user'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { ImFriendAddSource } from '../../../utils/constants'
-import { getGenderColor, getGenderIcon } from '../../../utils/user'
-import { useFriendStore } from '../../store/friendStore'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImFriendAddDialog' })
-
-const visible = ref(false) // 寮圭獥鏄惁鍙
-const presetUser = ref<null | SystemUserApi.UserSimple>(null) // 棰勫~鐩爣鐢ㄦ埛锛氶潪 null 鏃惰烦杩囨悳绱㈡楠わ紝鐩存帴杩涘叆鐢宠琛ㄥ崟锛堢兢鎴愬憳鍔犲ソ鍙� / 鍚嶇墖鍔犲ソ鍙嬬瓑鍦烘櫙锛�
-const addSource = ref<number>(ImFriendAddSource.SEARCH) // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum锛岄粯璁� SEARCH
-const addSourceExtra = ref<string>('') // 鏉ユ簮闄勫甫淇℃伅锛歛ddSource=ImFriendAddSource.GROUP 鏃朵紶缇ゅ悕锛岃瘽鏈嫾涓恒�屾垜鏄� XX 缇ょ殑 YY銆�
-
-defineExpose({
- /** 鎵撳紑鍔犲ソ鍙嬪脊绐楋細reset 鈫� 鐏屽弬 鈫� visible=true锛涗笉浼� opts 璧版悳绱㈡ā寮� */
- open(opts?: { addSource?: number; addSourceExtra?: string; presetUser?: null | SystemUserApi.UserSimple; }) {
- presetUser.value = opts?.presetUser ?? null
- addSource.value = opts?.addSource ?? ImFriendAddSource.SEARCH
- addSourceExtra.value = opts?.addSourceExtra ?? ''
- resetAll()
- visible.value = true
- }
-})
-
-const friendStore = useFriendStore()
-const userStore = useUserStore()
-
-/** 褰撳墠鐧诲綍鐢ㄦ埛缂栧彿锛涚敤 computed 鍖呬竴灞傦紝鍒囪处鍙峰悗闅� wsCache 閲嶅彇锛岄伩鍏嶉《灞傛眰鍊煎湪 keep-alive 瀹炰緥閲屾寔鏈夋棫 id */
-const currentUserId = computed(() => getCurrentUserId())
-
-/** 鎼滅储缁撴灉杩囨护鎺夎嚜宸憋紱鐢� v-if 鑰岄潪 v-show锛岄伩鍏� DOM 鍗犱綅 + 澶村儚鏃犳晥璇锋眰 */
-const visibleUsers = computed(() =>
- users.value.filter((user) => user.id !== currentUserId.value)
-)
-const keyword = ref('') // 鎼滅储鍏抽敭瀛�
-const users = ref<SystemUserApi.UserSimple[]>([]) // 鎼滅储缁撴灉
-const searched = ref(false) // 鏄惁宸叉悳绱�
-const loading = ref(false) // 鎼滅储鍔犺浇涓�
-const step = ref<'apply' | 'search'>('search') // 褰撳墠姝ラ锛歴earch=鎼滅储鍒楄〃锛沘pply=鐢宠琛ㄥ崟
-const targetUser = ref<null | SystemUserApi.UserSimple>(null) // 鐢宠鐩爣鐢ㄦ埛
-const applyContent = ref('') // 鐢宠鐞嗙敱锛堥粯璁ゅ~銆屾垜鏄� ${褰撳墠鏄电О}銆嶏紝瀵归綈寰俊浜や簰锛�
-const displayName = ref('') // 瀵规帴鏀舵柟鐨勫娉紙浠呰嚜宸卞彲瑙侊級
-const submitting = ref(false) // 鎻愪氦涓�
-
-/** 寮圭獥鏍囬闅忔楠ゅ垏鎹� */
-const dialogTitle = computed(() => (step.value === 'apply' ? '鐢宠娣诲姞鏈嬪弸' : '娣诲姞濂藉弸'))
-
-/** 鏄惁棰勫~妯″紡锛坧resetUser 涓嶄负绌� 鈫� 璺宠繃鎼滅储锛屽叧闂嵆閿�姣侊紝鏃犮�屽彇娑堣繑鍥炴悳绱€�嶆寜閽級 */
-const presetMode = computed(() => !!presetUser.value)
-
-function resetAll() {
- keyword.value = ''
- users.value = []
- searched.value = false
- // 棰勫~妯″紡锛氱洿鎺ヨ繘鐢宠琛ㄥ崟锛宼argetUser 鍙栬嚜 presetUser锛涚敵璇风悊鐢辨寜 addSource 鍖哄垎璇濇湳
- if (presetUser.value) {
- targetUser.value = presetUser.value
- applyContent.value = buildPresetApplyContent()
- displayName.value = ''
- step.value = 'apply'
- return
- }
- // 闈為濉ā寮忥細榛樿杩涙悳绱㈡楠�
- step.value = 'search'
- targetUser.value = null
- applyContent.value = ''
- displayName.value = ''
-}
-
-/** 棰勫~妯″紡涓嬬殑鐢宠鐞嗙敱璇濇湳锛氱兢鑱娿�屾垜鏄�"XX 缇�"鐨� YY銆嶏紱鍏跺畠銆屾垜鏄� YY銆� */
-function buildPresetApplyContent(): string {
- const myNickname = userStore.userInfo?.nickname || ''
- if (!myNickname) {
- return ''
- }
- // 缇よ亰鍦烘櫙鎷煎甫缇ゅ悕鐨勮瘽鏈紱鍏跺畠鍦烘櫙榛樿銆屾垜鏄� YY銆�
- const groupExtra = addSource.value === ImFriendAddSource.GROUP ? addSourceExtra.value : ''
- return groupExtra ? `鎴戞槸"${groupExtra}"鐨�${myNickname}` : `鎴戞槸${myNickname}`
-}
-
-/** 鎸夋樀绉版悳绱㈢敤鎴凤細绌哄叧閿瓧鐩存帴娓呯┖缁撴灉 */
-async function handleSearch() {
- searched.value = true
- if (!keyword.value.trim()) {
- users.value = []
- return
- }
- loading.value = true
- try {
- users.value = (await getSimpleUserListByNickname(keyword.value.trim())) || []
- } finally {
- loading.value = false
- }
-}
-
-/** 杩涘叆鐢宠姝ラ锛氶濉敵璇风悊鐢便�屾垜鏄� ${褰撳墠鐢ㄦ埛鏄电О}銆嶏紙瀵归綈寰俊浜や簰锛� */
-function enterApply(user: SystemUserApi.UserSimple) {
- targetUser.value = user
- const myNickname = userStore.userInfo?.nickname || ''
- applyContent.value = myNickname ? `鎴戞槸${myNickname}` : ''
- displayName.value = ''
- step.value = 'apply'
-}
-
-/** 鍙栨秷鐢宠锛屽洖鍒版悳绱㈡楠� */
-function backToSearch() {
- step.value = 'search'
- targetUser.value = null
-}
-
-/** 鎻愪氦濂藉弸鐢宠锛氳繑鍥� requestId 璧般�岀瓑寰呴獙璇併�嶏紱杩斿洖 null 琛ㄧず鍚庣鍛戒腑銆屽崟鍚戝ソ鍙嬮潤榛橀噸鍚�嶅垎鏀紝宸茬洿鎺ユ垚涓哄ソ鍙� */
-async function handleSubmitApply() {
- if (!targetUser.value) {
- return
- }
- // 棰勬牎楠岋細涓嶈兘鍔犺嚜宸憋紙鎼滅储鍒楄〃宸茶繃婊わ紝杩欓噷鍏滃簳 presetUser / 鍚嶇墖鍏ュ彛绛夊満鏅級
- if (targetUser.value.id === currentUserId.value) {
- message.warning('涓嶈兘娣诲姞鑷繁涓哄ソ鍙�')
- return
- }
- submitting.value = true
- try {
- const requestId = await friendStore.applyFriendRequest({
- toUserId: targetUser.value.id,
- applyContent: applyContent.value.trim() || undefined,
- displayName: displayName.value.trim() || undefined,
- addSource: addSource.value
- })
- // silent 鍒嗘敮锛堝凡鏄崟鍚戝ソ鍙嬭闈欓粯閲嶅惎锛夛細涓诲姩 fetchFriendInfo 鍏ュ簱锛屼笉渚濊禆 WS FRIEND_ADD 鎺ㄩ�侊紝閬垮厤涓㈡帹鏃跺垪琛ㄧ湅涓嶅埌
- if (requestId === null) {
- await friendStore.fetchFriendInfo(targetUser.value.id)
- }
- message.success(requestId ? '鐢宠宸插彂閫侊紝绛夊緟瀵规柟楠岃瘉' : '宸叉坊鍔犱负濂藉弸')
- visible.value = false
- } catch {
- // 涓氬姟閿欒锛堝凡鏄ソ鍙� / 琚鏂规媺榛� / 鐢ㄦ埛琚鐢� 绛夛級锛氬叏灞�鎷︽埅鍣ㄥ凡寮归敊璇彁绀猴紝鏈湴鍏冲脊绐楅伩鍏嶈剰鐘舵�佸仠鐣�
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 娣诲姞濂藉弸瀵硅瘽妗嗭紙鍙屽眰娴佺▼锛�
- - 绗竴灞� search锛氭寜鏄电О鎼滅储鐢ㄦ埛鍒楄〃
- - 绗簩灞� apply锛氶�変腑鐢ㄦ埛鍚庡睍寮�銆岀敵璇锋坊鍔犳湅鍙嬨�嶈〃鍗曪紙鐢宠鐞嗙敱 + 澶囨敞锛�
- -->
- <Modal
- v-model:open="visible"
- :title="dialogTitle"
- width="480px"
- :mask-closable="false"
- :footer="step === 'apply' ? undefined : null"
- >
- <!-- 绗竴灞傦細鎼滅储 + 鐢ㄦ埛鍒楄〃 -->
- <template v-if="step === 'search'">
- <Input
- v-model:value="keyword"
- placeholder="杈撳叆鏄电О鍥炶溅鎼滅储锛堟渶澶氬睍绀� 20 鏉★級"
- allow-clear
- @keyup.enter="handleSearch"
- >
- <template #suffix>
- <Icon icon="ant-design:search-outlined" class="cursor-pointer" @click="handleSearch" />
- </template>
- </Input>
-
- <Spin :spinning="loading" wrapper-class-name="w-full">
- <div class="h-[400px] mt-2.5">
- <div
- v-if="visibleUsers.length === 0"
- class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ searched ? '娌℃湁鎼滃埌鐢ㄦ埛' : '杈撳叆鍏抽敭瀛楀悗鍥炶溅寮�濮嬫悳绱�' }}
- </div>
- <div
- v-for="user in visibleUsers"
- :key="user.id"
- class="flex gap-3 items-center px-2 py-2.5 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <UserAvatar
- :id="user.id"
- :url="user.avatar"
- :name="user.nickname"
- :size="42"
- :clickable="false"
- />
- <div class="flex-1 min-w-0 overflow-hidden">
- <!-- 鏄电О + 鎬у埆鍥炬爣 -->
- <div
- class="flex items-center gap-1 text-sm font-semibold text-[var(--ant-color-text)]"
- >
- <span class="truncate">{{ user.nickname }}</span>
- <Icon
- v-if="getGenderIcon(user.sex)"
- :icon="getGenderIcon(user.sex)"
- :size="14"
- :color="getGenderColor(user.sex)"
- class="flex-shrink-0"
- />
- </div>
- <!-- 閮ㄩ棬 -->
- <div
- v-if="user.deptName"
- class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
- >
- {{ user.deptName }}
- </div>
- </div>
- <!-- 宸叉槸濂藉弸鏄剧ず銆屽凡娣诲姞銆嶏紱鍚﹀垯鏄剧ず銆屾坊鍔犮�嶏紙鐐瑰嚮杩涘叆 apply 姝ラ锛� -->
- <Button
- v-if="!friendStore.isActiveFriend(user.id)"
- type="primary"
- size="small"
- @click="enterApply(user)"
- >
- 娣诲姞
- </Button>
- <Button v-else size="small" disabled>宸叉坊鍔�</Button>
- </div>
- </div>
- </Spin>
- </template>
-
- <!-- 绗簩灞傦細鐢宠琛ㄥ崟锛堝榻愬井淇°�岀敵璇锋坊鍔犳湅鍙嬨�嶏級 -->
- <template v-if="step === 'apply' && targetUser">
- <!-- 閫変腑鐨勭敤鎴峰崱鐗� -->
- <div
- class="flex gap-3 items-center px-2 py-3 mb-4 rounded-md bg-[var(--ant-color-fill-secondary)]"
- >
- <UserAvatar
- :id="targetUser.id"
- :url="targetUser.avatar"
- :name="targetUser.nickname"
- :size="42"
- :clickable="false"
- />
- <div class="flex-1 min-w-0 overflow-hidden">
- <div class="text-sm font-semibold text-[var(--ant-color-text)] truncate">
- {{ targetUser.nickname }}
- </div>
- <div
- v-if="targetUser.deptName"
- class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
- >
- {{ targetUser.deptName }}
- </div>
- </div>
- </div>
-
- <div class="text-13px text-[var(--ant-color-text-secondary)] mb-1.5">鍙戦�佹坊鍔犳湅鍙嬬敵璇�</div>
- <Input.TextArea
- v-model:value="applyContent"
- :rows="3"
- :maxlength="255"
- show-count
- placeholder="璇峰~鍐欑敵璇风悊鐢�"
- />
-
- <div class="text-13px text-[var(--ant-color-text-secondary)] mt-3 mb-1.5">澶囨敞</div>
- <Input
- v-model:value="displayName"
- :maxlength="16"
- placeholder="缁欏鏂硅捣涓娉紙浠呰嚜宸卞彲瑙侊紝鍙笉濉級"
- />
- </template>
-
- <!-- 浠呭湪 apply 姝ラ鏄剧ず footer 鎿嶄綔鎸夐挳锛坰lot 蹇呴』鏄� el-dialog 鐩存帴瀛愯妭鐐癸級 -->
- <template v-if="step === 'apply'" #footer>
- <!-- 棰勫~妯″紡鏃犳悳绱㈡楠わ紝銆屽彇娑堛�嶇洿鎺ュ叧闂脊绐� -->
- <Button @click="presetMode ? (visible = false) : backToSearch()">鍙栨秷</Button>
- <Button type="primary" :loading="submitting" @click="handleSubmitApply"> 纭畾 </Button>
- </template>
- </Modal>
-</template>
diff --git a/src/views/im/home/components/friend/friend-item.vue b/src/views/im/home/components/friend/friend-item.vue
deleted file mode 100644
index 9feebab..0000000
--- a/src/views/im/home/components/friend/friend-item.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<script lang="ts" setup>
-import type { FriendLite } from '../../types'
-
-import { useImUiStore } from '../../store/uiStore'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImFriendItem' })
-
-const props = withDefaults(
- defineProps<{
- active?: boolean
- friend: FriendLite
- menu?: boolean // 鏄惁鍚敤鍙抽敭鑿滃崟锛涘湪閫夋嫨鍣ㄥ脊绐楅噷涓�鑸叧闂�
- }>(),
- {
- active: false,
- menu: true
- }
-)
-
-const emit = defineEmits<{
- chat: [friend: FriendLite]
- click: [friend: FriendLite]
- delete: [friend: FriendLite]
-}>()
-
-const uiStore = useImUiStore()
-
-/** 鍙抽敭鑿滃崟锛氬彂閫佹秷鎭� / 鍒犻櫎濂藉弸 */
-function handleContextMenu(event: MouseEvent) {
- if (!props.menu) {
- return
- }
- uiStore.openContextMenu(
- { x: event.clientX, y: event.clientY },
- [
- { key: 'chat', name: '鍙戦�佹秷鎭�' },
- { key: 'delete', name: '鍒犻櫎濂藉弸' }
- ],
- (item) => {
- if (item.key === 'chat') emit('chat', props.friend)
- else if (item.key === 'delete') emit('delete', props.friend)
- }
- )
-}
-</script>
-
-<template>
- <!--
- 濂藉弸鍗曡椤�
- - 澶村儚 + 鏄电О
- - 閫変腑鎬� active
- - 鍙抽敭鑿滃崟锛堝彂娑堟伅 / 鍒犻櫎濂藉弸锛夌敱鍏ㄥ眬 ContextMenu 鎵挎帴
- -->
- <div
- class="relative flex items-center gap-2.5 px-4 py-3 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="{ '!bg-[#d9ecff] dark:!bg-[var(--ant-color-primary-bg-hover)]': active }"
- @click="$emit('click', friend)"
- @contextmenu.prevent="handleContextMenu"
- >
- <!-- prefix slot锛氭斁鍦ㄥご鍍忓墠锛岀粰閫夋嫨绫诲脊绐楃殑 checkbox / 鍦嗙偣鐢紝涓嶄紶鍒欎笉娓叉煋 -->
- <slot name="prefix"></slot>
- <!-- 澶村儚 -->
- <UserAvatar
- :id="friend.id"
- :url="friend.avatar"
- :name="friend.nickname"
- :size="42"
- :clickable="false"
- />
- <!-- 鍗曡灞曠ず displayName 浼樺厛锛涙樀绉颁粎鍦ㄥソ鍙嬭鎯呴潰鏉垮睍绀猴紝鍒楄〃閲屼笉閲嶅 -->
- <div class="flex flex-1 min-w-0">
- <div class="overflow-hidden text-sm truncate text-[var(--ant-color-text)]">
- {{ friend.displayName || friend.nickname }}
- </div>
- </div>
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/im/home/components/friend/index.ts b/src/views/im/home/components/friend/index.ts
deleted file mode 100644
index 443154c..0000000
--- a/src/views/im/home/components/friend/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as FriendAddDialog } from './friend-add-dialog.vue';
-export { default as FriendItem } from './friend-item.vue';
diff --git a/src/views/im/home/components/group/group-admin-set-dialog.vue b/src/views/im/home/components/group/group-admin-set-dialog.vue
deleted file mode 100644
index 060355a..0000000
--- a/src/views/im/home/components/group/group-admin-set-dialog.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { ref } from 'vue'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { addGroupAdmin, removeGroupAdmin } from '#/api/im/group'
-import { GROUP_ADMIN_MAX_COUNT } from '#/views/im/utils/config'
-
-import { GroupMemberPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImGroupAdminSetDialog' })
-
-const emit = defineEmits<{
- /** 绠$悊鍛樺彉鏇存垚鍔燂紱鐖朵晶閫氬父鐢ㄦ潵 reload 缇ゆ暟鎹� */
- reload: []
-}>()
-
-const visible = ref(false)
-const submitting = ref(false)
-const groupId = ref(0)
-const members = ref<GroupMemberLite[]>([])
-const currentAdminIds = ref<number[]>([]) // 褰撳墠绠$悊鍛� userId 鍒楄〃锛氶粯璁ゅ嬀閫� + 鎻愪氦鏃� diff
-const hideIds = ref<number[]>([])
-const maxSize = ref(GROUP_ADMIN_MAX_COUNT)
-const selectedIds = ref<number[]>([])
-
-defineExpose({
- /** 鎵撳紑璁剧疆绠$悊鍛樺脊绐楋細reset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: {
- /** 褰撳墠绠$悊鍛� userId 鍒楄〃锛堥粯璁ゅ嬀閫夛級 */
- currentAdminIds: number[]
- groupId: number
- /** 闅愯棌 userId锛堢兢涓伙級 */
- hideIds?: number[]
- /** 宸查�夋暟涓婇檺锛涗笉浼犺蛋 GROUP_ADMIN_MAX_COUNT */
- maxSize?: number
- members: GroupMemberLite[]
- }) {
- groupId.value = opts.groupId
- members.value = opts.members
- currentAdminIds.value = [...opts.currentAdminIds]
- hideIds.value = opts.hideIds ? [...opts.hideIds] : []
- maxSize.value = opts.maxSize ?? GROUP_ADMIN_MAX_COUNT
- selectedIds.value = [...opts.currentAdminIds]
- submitting.value = false
- visible.value = true
- }
-})
-
-/** 璺熷綋鍓嶇鐞嗗憳鍒楄〃鍋氬樊闆嗭紝鍒嗗埆鎷垮埌瑕佹柊澧� / 鎾ら攢鐨� userId */
-async function handleOk() {
- if (!groupId.value) {
- return
- }
- const previousIds = currentAdminIds.value
- const previousIdSet = new Set(previousIds)
- const nextIds = selectedIds.value
- const nextIdSet = new Set(nextIds)
- const addedIds = nextIds.filter((id) => !previousIdSet.has(id))
- const removedIds = previousIds.filter((id) => !nextIdSet.has(id))
- if (addedIds.length === 0 && removedIds.length === 0) {
- visible.value = false
- return
- }
- submitting.value = true
- try {
- if (addedIds.length > 0) {
- await addGroupAdmin({ id: groupId.value, userIds: addedIds })
- }
- if (removedIds.length > 0) {
- await removeGroupAdmin({ id: groupId.value, userIds: removedIds })
- }
- message.success(`宸叉洿鏂扮兢绠$悊鍛橈紙鏂板 ${addedIds.length} 浣嶏紝鎾ら攢 ${removedIds.length} 浣嶏級`)
- emit('reload')
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 璁剧疆缇ょ鐞嗗憳锛氫竴涓脊绐楀悎骞跺 / 鍒狅紝鎻愪氦鏃惰窡褰撳墠绠$悊鍛樺垪琛� diff
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 GroupMemberPickerPanel锛坓rid 褰㈡�佸榻愬綋鍓嶈瑙夛級
- - 缇や富浠庡�欓�夐噷闅愯棌锛堜笉鑳借涓虹鐞嗗憳锛�
- - 瀵瑰鎺ュ彛锛歳ef + open({ groupId, members, currentAdminIds, hideIds, maxSize }) + emit reload()
- -->
- <Modal
- v-model:open="visible"
- title="璁剧疆缇ょ鐞嗗憳"
- width="700px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <GroupMemberPickerPanel
- v-model:selected-ids="selectedIds"
- :members="members"
- :hide-ids="hideIds"
- :max-size="maxSize"
- selected-display="grid"
- />
- </div>
-
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button type="primary" :loading="submitting" @click="handleOk">纭畾</Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/group/group-avatar.vue b/src/views/im/home/components/group/group-avatar.vue
deleted file mode 100644
index 021714f..0000000
--- a/src/views/im/home/components/group/group-avatar.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMember } from '../../types'
-
-import { computed, ref, watch } from 'vue'
-
-import {
- buildGroupAvatar,
- getCachedGroupAvatar,
- setCachedGroupAvatar
-} from '../../../utils/group'
-import { getMemberDisplayName } from '../../../utils/user'
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupAvatar' })
-
-const props = withDefaults(
- defineProps<{
- clickable?: boolean // 鏄惁鍙偣鍑伙紙榛樿 false锛屽垪琛ㄩ噷浠呭睍绀猴級
- groupId: number // 缇ょ紪鍙凤紱鐢ㄤ簬鏌� store 鎷挎垚鍛樺ご鍍�
- name?: string // 缇ゅ悕锛涜壊鍗″厹搴曟枃瀛�
- previewable?: boolean // 鏄惁鐐瑰ご鍍忕洿鎺ユ斁澶ч瑙堬紙缇よ鎯呭ぇ澶村儚浣嶇敤锛�
- previewZIndex?: number // 棰勮灞� z-index
- radius?: string // 鍦嗚锛孋SS 闀垮害
- size?: number // 灏哄锛坧x锛夛紝姝f柟褰�
- url?: string // 鏈嶅姟绔凡璁剧疆鐨勭兢澶村儚 URL锛涢潪绌哄垯鐩存帴鐢紝涓嶆嫾鍥�
- }>(),
- {
- clickable: false,
- name: '',
- previewable: false,
- previewZIndex: 2000,
- radius: '15%',
- size: 42,
- url: ''
- }
-)
-
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-const mergedUrl = ref('')
-// 绔炴�佷繚鎶わ細涓㈠純杩囨湡 await 缁撴灉
-let mergeToken = 0
-
-/** 鎸夊鍣� size 脳 DPR 绠� canvas 瀹為檯鍍忕礌锛岄伩鍏� 2x / 3x retina 灞忔嫾鍥剧硦锛汥PR 灏侀《 3 闃叉瓒呴珮鍒嗚鲸鐜囩敾甯冭繃澶� */
-function getTargetSize(size: number): number {
- const dpr = Math.min(window.devicePixelRatio || 1, 3)
- return Math.max(Math.round(size * dpr), 64)
-}
-
-/** store 閲屾暣缇ゆ垚鍛樻槸鍚︺�屽畬鏁村姞杞姐�嶈繃锛涘彧鍦ㄤ负 true 鏃舵墠鎷煎浘锛岄伩鍏嶅垪琛ㄥ満鏅壒閲忓彂鎺ュ彛 */
-const loadedMembers = computed<GroupMember[] | null>(() => {
- const g = groupStore.getGroup(props.groupId)
- if (!g?.membersLoaded || !g.members) {
- return null
- }
- return g.members
-})
-
-/** 鍓� 9 涓垚鍛樼殑鎷煎浘鍏ュ弬锛沶ame 璧� getMemberDisplayName 鍙e緞锛堝ソ鍙嬪娉� > 缇ゆ樀绉� > 鐪熷疄鏄电О锛� */
-const memberItems = computed(() => {
- const members = loadedMembers.value
- if (!members) {
- return []
- }
- return members.slice(0, 9).map((m) => ({
- avatar: m.avatar || '',
- name: getMemberDisplayName(m, friendStore.getFriend(m.userId))
- }))
-})
-
-/** 鎴愬憳蹇収绛惧悕锛氭嫾 (avatar, name) 瀛楁锛屽師鍦颁慨鏀逛换涓�瀛楁閮戒細璁� watch 閲嶇畻 */
-const memberSignature = computed(() =>
- memberItems.value.map((it) => `${it.avatar}#${it.name}`).join('|')
-)
-
-/** 璧� buildGroupAvatar 鎷煎浘骞跺啓鍥� mergedUrl锛沵ergeToken 鏍¢獙閬垮厤鑰� await 瑕嗙洊鏂扮粨鏋� */
-async function applyMerge(key: string, targetSize: number): Promise<void> {
- const myToken = ++mergeToken
- const cached = getCachedGroupAvatar(key)
- if (cached) {
- mergedUrl.value = cached
- return
- }
- const dataUrl = await buildGroupAvatar(memberItems.value, { targetSize })
- if (myToken !== mergeToken) {
- return
- }
- if (dataUrl) {
- setCachedGroupAvatar(key, dataUrl)
- }
- mergedUrl.value = dataUrl
-}
-
-watch(
- () => [props.url, props.groupId, props.size, memberSignature.value] as const,
- ([url, groupId, size, signature]) => {
- if (url) {
- mergedUrl.value = ''
- return
- }
- if (!signature) {
- mergeToken++
- mergedUrl.value = ''
- groupStore.loadGroupMemberList(groupId)
- return
- }
- const targetSize = getTargetSize(size)
- const key = `${groupId}:${targetSize}:${signature}`
- applyMerge(key, targetSize)
- },
- { immediate: true }
-)
-
-/** 鏈�缁堝睍绀� url锛氭湇鍔$ url 浼樺厛 鈫� 鎷煎浘 鈫� 绌哄瓧绗︿覆锛堣 UserAvatar 璧拌壊鍗★級 */
-const finalUrl = computed(() => props.url || mergedUrl.value)
-</script>
-
-<template>
- <!-- url 闈炵┖璧板師鍥撅紱url 绌烘椂鍙栧墠 9 涓垚鍛樺ご鍍忔嫾涔濆鏍� dataURL锛屾垚鍛樻湭鍦� store 缂撳瓨鏃惰蛋鑹插崱鍏滃簳 -->
- <UserAvatar
- :url="finalUrl"
- :name="name"
- :size="size"
- :radius="radius"
- :clickable="clickable"
- :previewable="previewable"
- :preview-z-index="previewZIndex"
- />
-</template>
diff --git a/src/views/im/home/components/group/group-create-dialog.vue b/src/views/im/home/components/group/group-create-dialog.vue
deleted file mode 100644
index 18b87a5..0000000
--- a/src/views/im/home/components/group/group-create-dialog.vue
+++ /dev/null
@@ -1,144 +0,0 @@
-<script lang="ts" setup>
-import type { FriendLite } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { createGroup } from '#/api/im/group'
-
-import { buildDefaultGroupName } from '../../../utils/group'
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import { FriendPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImGroupCreateDialog' })
-
-const emit = defineEmits<{
- /** 鍒涘缓鎴愬姛锛屾惡甯︽柊缇ょ紪鍙凤紱鐖朵晶閫氬父鐢ㄦ潵璺宠浆鍒版柊缇や細璇� */
- created: [groupId: number]
-}>()
-
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-
-const visible = ref(false)
-const submitting = ref(false)
-const lockedIds = ref<number[]>([])
-const selectedIds = ref<number[]>([])
-
-defineExpose({
- /** 鎵撳紑鍙戣捣缇よ亰寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts?: { lockedIds?: number[] }) {
- lockedIds.value = opts?.lockedIds ? [...opts.lockedIds] : []
- selectedIds.value = []
- submitting.value = false
- visible.value = true
- }
-})
-
-/** 鍏ㄩ噺濂藉弸锛氱洿鎺ュ鐢� friendStore Lite 瑙嗗浘锛堝甫鎷奸煶瀛楁渚涘垎妗剁敤锛� */
-const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
-
-/** 瀹屾垚鎸夐挳鍙偣锛氳嚦灏戞湁 1 涓潪 locked 鍕鹃�夛紙locked 鏄叆鍙i攣瀹氶」锛屼笉绠�"鐢ㄦ埛涓诲姩閫夋嫨"锛� */
-const canSubmit = computed(() => selectedIds.value.length > 0)
-
-/** 鎷垮埌鎵�鏈夎杩涚兢鐨勫ソ鍙嬶紙locked + selected锛夛紱寤虹兢榛樿缇ゅ悕鎸夎繖鎵逛汉鐢熸垚 */
-function resolveMembersToInvite(): FriendLite[] {
- const seen = new Set<number>()
- const result: FriendLite[] = []
- const byId = new Map(friends.value.map((f) => [f.id, f]))
- for (const id of lockedIds.value) {
- if (seen.has(id)) {
- continue
- }
- const friend = byId.get(id)
- if (friend) {
- seen.add(id)
- result.push(friend)
- }
- }
- for (const id of selectedIds.value) {
- if (seen.has(id)) {
- continue
- }
- const friend = byId.get(id)
- if (friend) {
- seen.add(id)
- result.push(friend)
- }
- }
- return result
-}
-
-/** 鍒涘缓缇よ亰锛氬缓缇わ紙鍚屾椂閭�璇峰垵濮嬫垚鍛橈級鈫� upsert groupStore 鈫� emit created 璁╃埗椤佃烦杞柊浼氳瘽 */
-async function handleOk() {
- const members = resolveMembersToInvite()
- if (members.length === 0) {
- return
- }
- submitting.value = true
- try {
- const memberUserIds = members.map((m) => m.id)
- const name = buildDefaultGroupName(members)
- const group = await createGroup({ name, memberUserIds, joinApproval: false })
- if (!group?.id) {
- throw new Error('鍒涘缓缇ゅけ璐ワ細鏈繑鍥炵兢缂栧彿')
- }
- // 鐩存帴 upsert 杩� groupStore锛岀渷涓�娆� fetchGroupList 鈥斺�� 鏈嶅姟绔繑鍥� VO 宸茬粡澶熷缓浼氳瘽浜�
- groupStore.upsertGroup({
- id: group.id,
- name: group.name,
- avatar: group.avatar,
- notice: group.notice,
- ownerUserId: group.ownerUserId
- })
- message.success('缇よ亰鍒涘缓鎴愬姛')
- emit('created', group.id)
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 鍙戣捣缇よ亰锛氶�夊ソ鍙� 鈫� 榛樿鎸夋墍閫夋垚鍛樺悕鐢熸垚缇ゅ悕 鈫� createGroup
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 FriendPickerPanel
- - lockedIds 鐢辫皟鐢ㄦ柟閫氳繃 open({ lockedIds }) 浼犲叆锛涚鑱婁晶 +寤虹兢閿佸畾瀵规柟
- - 涓嶅啀瑕佹眰鍏堣緭鍏ョ兢鍚� / 涓嶅啀灞曠ず銆岃繘缇ゅ鎵广�嶅紑鍏筹紝瀵归綈寰俊 PC
- - 瀵瑰鎺ュ彛锛歳ef + open({ lockedIds }) + emit created(groupId)
- -->
- <Modal
- v-model:open="visible"
- title="鍙戣捣缇よ亰"
- width="720px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <FriendPickerPanel
- v-model:selected-ids="selectedIds"
- :friends="friends"
- :locked-ids="lockedIds"
- />
- </div>
-
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleOk">
- 瀹屾垚
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/group/group-info-card.vue b/src/views/im/home/components/group/group-info-card.vue
deleted file mode 100644
index e327d40..0000000
--- a/src/views/im/home/components/group/group-info-card.vue
+++ /dev/null
@@ -1,116 +0,0 @@
-<script lang="ts" setup>
-import type { GroupLite } from '../../types'
-
-import { computed, onMounted, onUnmounted } from 'vue'
-import { useRouter } from 'vue-router'
-
-import { prompt } from '../../../../../packages/effects/common-ui/src'
-
-import { Input, message } from 'ant-design-vue'
-
-import { applyJoinGroup } from '#/api/im/group/request'
-
-import { ImConversationType, ImGroupAddSource } from '../../../utils/constants'
-import { getGroupDisplayName } from '../../../utils/user'
-import { useConversationStore } from '../../store/conversationStore'
-import { useGroupStore } from '../../store/groupStore'
-import { useImUiStore } from '../../store/uiStore'
-import GroupInfo from './group-info.vue'
-
-defineOptions({ name: 'ImGroupInfoCard' })
-
-const uiStore = useImUiStore()
-const conversationStore = useConversationStore()
-const groupStore = useGroupStore()
-const router = useRouter()
-
-const card = computed(() => uiStore.groupInfoCard)
-
-/** 鍏抽棴娴眰 */
-function handleClose() {
- uiStore.closeGroupInfoCard()
-}
-
-/** Esc 鍏抽棴 */
-function handleKeydown(e: KeyboardEvent) {
- if (e.key === 'Escape' && card.value.show) {
- handleClose()
- }
-}
-
-onMounted(() => window.addEventListener('keydown', handleKeydown))
-onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
-
-/** 杩涘叆缇よ亰锛氬彇鏈湴鏈�鏂扮兢淇℃伅锛堝惈 silent / 缇ゅ娉級锛屾柊寤烘垨婵�娲讳細璇� + 璺宠矾鐢� */
-function handleChat(group: GroupLite) {
- const cached = groupStore.getGroup(group.id)
- // cached 鍛戒腑璧� getGroupDisplayName 璁╃兢澶囨敞浼樺厛锛堜笌 contact / 浼氳瘽鍒楄〃鐨勫睍绀哄悕涓�鑷达級锛涚己 cached 鏃跺洖钀� showGroupName / 鍘熺兢鍚�
- const displayName = cached
- ? getGroupDisplayName(cached)
- : group.showGroupName || group.name || ''
- // 鎵撳紑鎴栨柊寤轰細璇�
- conversationStore.openConversation(
- group.id,
- ImConversationType.GROUP,
- displayName,
- cached?.avatar || group.showImage || '',
- { silent: !!cached?.silent }
- )
-
- // 濡傛灉涓嶅湪浼氳瘽椤碉紝鍏堣烦杩囧幓锛堝鏋滃湪浜嗭紝MessagePanel 浼氳嚜宸辨劅鐭ヤ細璇濆彉鍖栧埛鏂帮級
- if (router.currentRoute.value.name !== 'ImHomeConversation') {
- router.push({ name: 'ImHomeConversation' })
- }
- handleClose()
-}
-
-/** 鍔犲叆缇よ亰锛氬厛鍏虫诞灞傦紙閬垮厤涓� prompt 鐨� mask 浜掔浉閬尅锛夆啋 寮圭敵璇风悊鐢憋紙鍙�夛級鈫� applyJoinGroup */
-async function handleApply(group: GroupLite) {
- handleClose()
- let applyContent: string
- try {
- const result = await prompt<string>({
- cancelText: '鍙栨秷',
- component: Input,
- componentProps: {
- allowClear: true,
- placeholder: '璇峰~鍐欓獙璇佹秷鎭紙鍙�夛級'
- },
- content: '',
- defaultValue: '',
- confirmText: '鍙戦�佺敵璇�',
- modelPropName: 'value',
- title: `鐢宠鍔犲叆銆�${group.name || ''}銆峘
- })
- applyContent = (result || '').trim()
- } catch {
- return
- }
- await applyJoinGroup({
- groupId: group.id,
- applyContent: applyContent || undefined,
- addSource: ImGroupAddSource.SHARE_LINK
- })
- message.success('鍔犵兢鐢宠宸插彂閫�')
-}
-</script>
-
-<template>
- <!--
- 缇や俊鎭诞灞傦紙涓� UserInfoCard 瀵逛綅锛�
- - 浠呮壙鎷呫�屾诞灞傚畾浣� + 鍏抽棴閫昏緫锛堢偣閬僵 / Esc锛夈�嶏紝缇や俊鎭瑙夎蛋 <GroupInfo>锛屼笌閫氳褰曡鎯呭叡鐢ㄤ竴浠界粍浠�
- - 瑙﹀彂锛歶seImUiStore.openGroupInfoCardAtEvent(group, e)
- - GroupInfo 鍐呴儴鎸� groupStore 缂撳瓨鎺ㄥ member / stranger锛屾诞灞傚彧璐熻矗鎺� chat / apply 浜嬩欢鍋氫笟鍔�
- -->
- <teleport to="body">
- <div v-if="card.show" class="fixed inset-0 z-9998" @click.self="handleClose">
- <div
- class="fixed w-80 p-4 bg-[var(--ant-color-bg-elevated)] rounded-md shadow-xl"
- :style="{ left: `${card.position.x }px`, top: `${card.position.y }px` }"
- @click.stop
- >
- <GroupInfo v-if="card.group" :group="card.group" @chat="handleChat" @apply="handleApply" />
- </div>
- </div>
- </teleport>
-</template>
diff --git a/src/views/im/home/components/group/group-info.vue b/src/views/im/home/components/group/group-info.vue
deleted file mode 100644
index 2b9ddb5..0000000
--- a/src/views/im/home/components/group/group-info.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-<script lang="ts" setup>
-import type { Friend, GroupLite, GroupMember } from '../../types'
-import type { GroupMemberLite } from './group-member.vue'
-
-import { computed, ref, watch } from 'vue'
-
-import { CommonStatusEnum } from '../../../../../packages/constants/src'
-
-import { Button } from 'ant-design-vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { getMemberDisplayName, isGroupQuit } from '../../../utils/user'
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import GroupAvatar from './group-avatar.vue'
-import GroupMemberGrid from './group-member-grid.vue'
-
-defineOptions({ name: 'ImGroupInfo' })
-
-const props = defineProps<{
- group: GroupLite
-}>()
-
-const emit = defineEmits<{
- /** stranger 鐐广�屽姞鍏ョ兢鑱娿�嶏紱鐖剁骇璐熻矗寮圭敵璇风悊鐢� + 璋� applyJoinGroup */
- apply: [group: GroupLite]
- /** member 鐐广�岃繘鍏ョ兢鑱娿�嶏紱鐖剁骇璐熻矗鍒囦細璇� + 鍏虫诞灞� */
- chat: [group: GroupLite]
-}>()
-
-const groupStore = useGroupStore()
-const friendStore = useFriendStore()
-
-const members = ref<GroupMemberLite[]>([])
-
-/**
- * 鏄惁宸插姞缇わ細鍩轰簬"鑷繁纭疄鍦ㄦ垚鍛樺垪琛ㄩ噷"鍒ゆ柇
- * - 缂撳瓨鏈懡涓細鐩存帴 false锛堥檶鐢熺兢锛�
- * - 鍛戒腑涓� members 宸叉媺锛氱簿鍑嗘煡 self.userId 鍦ㄤ笉鍦�
- * - 鍛戒腑浣� members 鏈媺锛歠etchGroupList 鎺ュ彛璇箟鍗炽�屾垜鍔犲叆鐨勭兢銆嶏紝鍛戒腑瑙嗕负 member锛堟媺鎴愬憳鍚庝細鑷姩鏀舵暃锛�
- */
-const isMember = computed(() => {
- if (!props.group?.id) {
- return false
- }
- const cached = groupStore.getGroup(props.group.id)
- if (!cached) {
- return false
- }
- // 鍘嗗彶閫�缇ょ兢锛氱洿鎺ュ垽 false锛岄伩鍏嶆垚鍛樻湭鍔犺浇鏃惰鏄剧ず銆岃繘鍏ョ兢鑱娿��
- if (isGroupQuit(cached)) {
- return false
- }
- if (cached.membersLoaded && cached.members) {
- const myId = getCurrentUserId()
- return cached.members.some((m) => m.userId === myId && m.status === CommonStatusEnum.ENABLE)
- }
- return true
-})
-/** 鍘嗗彶閫�缇ょ兢锛氬彧璇伙紝鍔ㄤ綔鍖轰袱涓寜閽兘涓嶆覆鏌擄紙鏃笉銆岃繘鍏ョ兢鑱娿�嶄篃涓嶃�屽姞鍏ョ兢鑱娿�嶏級 */
-const isQuitGroup = computed(() => {
- const id = props.group?.id
- return id != null && isGroupQuit(groupStore.getGroup(id))
-})
-/** 鏄惁鏈姞缇わ細鏈� id銆侀潪鎴愬憳銆佷笖闈炲巻鍙查��缇ょ兢锛涘彧鏈夌湡路闄岀敓浜烘墠缁欍�屽姞鍏ョ兢鑱娿�� */
-const isStranger = computed(() => !!props.group?.id && !isMember.value && !isQuitGroup.value)
-
-/** 鎴愬憳鏁版枃妗堬細member 浼樺厛鐢ㄦ湰鍦版媺鍒扮殑鍒楄〃闀垮害锛宻tranger 鐢� props.group.memberCount 鍗$墖蹇収 */
-const memberCountText = computed(() => {
- const count = isMember.value
- ? props.group.memberCount || members.value.length
- : props.group.memberCount
- return count ? `${count} 浣嶆垚鍛榒 : ''
-})
-
-/** member 鍒囩兢 / 棣栨寕锛氭媺鎴愬憳锛涚珵鎬佺敤 group.id 姣斿涓㈠純闄堟棫鍝嶅簲閬垮厤涓婁竴鏉$兢鎴愬憳閿欎綅 */
-watch(
- () => [props.group?.id, isMember.value] as const,
- async ([id, member]) => {
- members.value = []
- if (!id || !member) {
- return
- }
- const list = await groupStore.fetchGroupMemberList(id, true)
- if (props.group?.id !== id) {
- return
- }
- members.value = list.map((m) => convertGroupMemberLite(m, friendStore.getFriend(m.userId)))
- },
- { immediate: true }
-)
-
-/** 缇ゆ垚鍛� 鈫� 鍒楄〃椤� */
-function convertGroupMemberLite(member: GroupMember, friend: Friend | undefined): GroupMemberLite {
- return {
- userId: member.userId,
- showName: getMemberDisplayName(member, friend),
- nickname: member.nickname,
- avatar: member.avatar,
- status: member.status,
- role: member.role
- }
-}
-</script>
-
-<template>
- <!--
- 缇や俊鎭唴瀹圭粍浠讹紙涓� UserInfo 瀵逛綅锛�
- - 澶村儚 + 缇ゅ悕 + 鎴愬憳鏁� + 鎴愬憳瀹牸 + 鍔ㄤ綔鍖猴紝绾睍绀� + 鎶涗簨浠讹紝涓氬姟鐢辩埗绾ф壙鎺�
- - relation 璧� groupStore 缂撳瓨鎺ㄥ锛氬懡涓� = member锛堝凡鍔犵兢锛夛紝鍚﹀垯 = stranger锛堟湭鍔犵兢锛夛紝鏃� id = readonly
- - 鎴愬憳瀹牸浠� member 鏃舵媺鍙栵紙闄岀敓缇ゆ媺涓嶅埌锛屾墍鏈変俊鎭蛋 props.group 鍗$墖蹇収锛�
- -->
- <div class="flex justify-center">
- <div class="w-full max-w-[320px] flex flex-col gap-3 items-center">
- <GroupAvatar
- :group-id="group.id"
- :url="group.showImage || group.showImageThumb"
- :name="group.showGroupName || group.name"
- :size="72"
- :previewable="isMember"
- />
- <div
- class="w-full text-lg font-semibold leading-snug text-[var(--ant-color-text)] truncate text-center"
- >
- {{ group.showGroupName || group.name }}
- </div>
- <div v-if="memberCountText" class="text-13px text-[var(--ant-color-text-secondary)]">
- {{ memberCountText }}
- </div>
-
- <!-- 鎴愬憳瀹牸锛氫粎 member 娓叉煋锛堥檶鐢熺兢鎷変笉鍒版垚鍛橈級 -->
- <div
- v-if="isMember && members.length > 0"
- class="flex flex-wrap gap-2 justify-center w-full pt-2"
- >
- <GroupMemberGrid
- v-for="member in members"
- :key="member.userId"
- :member="member"
- :group-name="group.name"
- />
- </div>
-
- <!-- 鍔ㄤ綔鍖猴細member 杩涘叆缇よ亰 / stranger 鍔犲叆缇よ亰 / readonly 涓嶆覆鏌� -->
- <div v-if="isMember" class="mt-4">
- <Button type="primary" @click="emit('chat', group)">杩涘叆缇よ亰</Button>
- </div>
- <div v-else-if="isStranger" class="mt-4">
- <Button type="primary" @click="emit('apply', group)">鍔犲叆缇よ亰</Button>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/group/group-item.vue b/src/views/im/home/components/group/group-item.vue
deleted file mode 100644
index 9fa60e2..0000000
--- a/src/views/im/home/components/group/group-item.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<script lang="ts" setup>
-import type { GroupLite } from '../../types'
-
-import GroupAvatar from './group-avatar.vue'
-
-defineOptions({ name: 'ImGroupItem' })
-
-defineProps<{
- active?: boolean
- group: GroupLite
-}>()
-
-defineEmits<{
- click: [group: GroupLite]
-}>()
-</script>
-
-<template>
- <!--
- 缇ゅ崟琛岄」
- - 澶村儚 + 缇ゅ悕
- - 閫変腑鎬� active
- -->
- <div
- class="relative flex items-center gap-2.5 px-4 py-3 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="{ '!bg-[#d9ecff] dark:!bg-[var(--ant-color-primary-bg-hover)]': active }"
- @click="$emit('click', group)"
- >
- <GroupAvatar
- :group-id="group.id"
- :url="group.showImage || group.showImageThumb"
- :name="group.showGroupName || group.name"
- :size="42"
- />
- <div class="flex flex-1 min-w-0">
- <!-- 鍗曡灞曠ず缇ゅ悕锛涙垚鍛樻暟浠呭湪缇よ鎯呴潰鏉垮睍绀猴紝鍒楄〃閲屼笉閲嶅 -->
- <div class="overflow-hidden text-sm truncate text-[var(--ant-color-text)]">
- {{ group.showGroupName || group.name }}
- </div>
- </div>
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/im/home/components/group/group-member-add-dialog.vue b/src/views/im/home/components/group/group-member-add-dialog.vue
deleted file mode 100644
index 126e6d4..0000000
--- a/src/views/im/home/components/group/group-member-add-dialog.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { computed, ref } from 'vue'
-
-import { CommonStatusEnum } from '../../../../../packages/constants/src'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { inviteGroupMember } from '#/api/im/group/member'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { GROUP_MAX_MEMBER } from '#/views/im/utils/config'
-import { ImGroupMemberRole } from '#/views/im/utils/constants'
-
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import { FriendPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImGroupMemberAddDialog' })
-
-const emit = defineEmits<{
- /** 閭�璇锋垚鍔燂紝鎼哄甫琚個璇风殑濂藉弸 id 鍒楄〃锛涚埗渚ч�氬父鐢ㄦ潵 reload 缇ゆ垚鍛� */
- reload: [friendIds: number[]]
-}>()
-
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-
-const visible = ref(false)
-const submitting = ref(false)
-const groupId = ref(0)
-const selectedIds = ref<number[]>([])
-
-defineExpose({
- /** 鎵撳紑娣诲姞缇ゆ垚鍛樺脊绐楋細reset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: { groupId: number }) {
- groupId.value = opts.groupId
- selectedIds.value = []
- submitting.value = false
- visible.value = true
- }
-})
-
-/** 褰撳墠缇ゆ垚鍛樺垪琛細浠� groupStore 鐜板彇锛岄伩鍏嶉殢 groupId 鍙樺寲鏃剁埗渚� prop 鏇存柊寤惰繜 */
-const members = computed<GroupMemberLite[]>(() => {
- const group = groupStore.getGroup(groupId.value)
- return (group?.members || []).map((member) => ({
- userId: member.userId,
- nickname: member.nickname,
- showName: member.displayUserName || member.nickname,
- avatar: member.avatar,
- status: member.status,
- role: member.role
- }))
-})
-
-/** 鍏ㄩ噺濂藉弸锛氱洿鎺ュ鐢� friendStore Lite 瑙嗗浘 */
-const friends = computed(() => friendStore.getActiveFriendLiteList)
-
-/** 宸插湪缇ら噷鐨勫ソ鍙� id锛氫紶缁� Panel 鐨� disabledIds 缃伆 + 涓嶈鍏ュ凡閫� */
-const disabledIds = computed<number[]>(() =>
- members.value
- .filter((member) => member.status !== CommonStatusEnum.DISABLE)
- .map((member) => member.userId)
-)
-
-/** 鏄惁璧板鎵瑰垎鏀細缇ゅ紑鍚� joinApproval + 褰撳墠鐢ㄦ埛鏄櫘閫氭垚鍛橈紙缇や富 / 绠$悊鍛橀個璇风洿杩涳級 */
-const willGoApproval = computed(() => {
- const group = groupStore.getGroup(groupId.value)
- if (!group?.joinApproval) {
- return false
- }
- const myId = getCurrentUserId()
- if (!myId) {
- return false
- }
- // 缇や富鐩村垽锛岄伩寮� members 寮傛鍔犺浇鐨勭獥鍙o紱admin 浠嶄緷璧� members
- if (group.ownerUserId === myId) {
- return false
- }
- // members 鏈埌浣嶆椂鏃犳硶鍒ゅ畾 admin锛屼繚瀹堟寜闈炲鎵瑰鐞嗭紝瀹佸彲婕忔姤銆岀瓑寰呭鎵广�嶄篃涓嶈鎶ョ粰鐪熷疄绠$悊鍛�
- const myRole = members.value.find((member) => member.userId === myId)?.role
- if (myRole == null) {
- return false
- }
- return myRole !== ImGroupMemberRole.ADMIN
-})
-
-/** 褰撳墠缇ゅ凡鍚敤鎴愬憳鏁帮紙DISABLE 鍗抽��缇� / 琚涪涓嶈鍏ワ級锛岀敤浜庝笂闄愬垽瀹� */
-const activeMemberCount = computed(
- () => members.value.filter((member) => member.status !== CommonStatusEnum.DISABLE).length
-)
-
-/** 閭�璇峰悗缇ゆ�讳汉鏁拌嫢瓒� GROUP_MAX_MEMBER锛屽墠绔厛鎷︼紱activeMemberCount + selectedIds.length 鍗抽個璇峰悗鐨勬垚鍛樻暟 */
-const willExceedLimit = computed(
- () => activeMemberCount.value + selectedIds.value.length > GROUP_MAX_MEMBER
-)
-
-/** 娣诲姞鎸夐挳鍙偣锛氳嚦灏戞湁 1 涓柊閭�璇风殑濂藉弸 + 涓嶈秴缇や汉鏁颁笂闄� */
-const canSubmit = computed(() => selectedIds.value.length > 0 && !willExceedLimit.value)
-
-/** 閭�璇峰叆缇わ細璋� /im/group/invite锛屾垚鍔熷悗 emit reload 璁╃埗渚у埛鏂扮兢鎴愬憳 */
-async function handleOk() {
- if (!groupId.value) {
- return
- }
- const memberUserIds = [...selectedIds.value]
- if (memberUserIds.length === 0) {
- return
- }
- // 缇や汉鏁颁笂闄愬啑浣欓槻寰★細涓� canSubmit 閲嶅鍒や竴娆★紝闃叉鐘舵�佸湪 await 闂撮殭鍙樺寲鎴栬皟鐢ㄦ柟缁曡繃鎸夐挳鐩存帴璋�
- if (activeMemberCount.value + memberUserIds.length > GROUP_MAX_MEMBER) {
- message.warning(`缇ゆ垚鍛樹笂闄愪负 ${GROUP_MAX_MEMBER} 浜篳)
- return
- }
- submitting.value = true
- try {
- await inviteGroupMember({ groupId: groupId.value, memberUserIds })
- // 瀹℃壒鍒嗘敮锛氬悗绔粎钀藉鎵硅褰曪紝鏈叆缇�
- message.success(willGoApproval.value ? '閭�璇峰凡鍙戣捣锛岀瓑寰呯兢涓� / 绠$悊鍛樺鎵�' : '閭�璇锋垚鍔�')
- emit('reload', memberUserIds)
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 娣诲姞缇ゆ垚鍛橈細閫夊ソ鍙嬮個璇峰叆缇わ紙宸插湪缇ゆ垚鍛樼疆鐏般�佷笉璁″叆宸查�夛級
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 FriendPickerPanel
- - 宸插湪缇ゆ垚鍛橀�氳繃 disabledIds 浼犲叆锛屼笉鍐嶈蛋 checked+disabled 鐨�"宸插嬀閫夌伆鎬�"
- - 瀵瑰鎺ュ彛锛歳ef + open({ groupId }) + emit reload(friendIds)
- -->
- <Modal
- v-model:open="visible"
- title="娣诲姞缇ゆ垚鍛�"
- width="720px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <FriendPickerPanel
- v-model:selected-ids="selectedIds"
- :friends="friends"
- :disabled-ids="disabledIds"
- />
- </div>
-
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleOk">
- 娣诲姞
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/group/group-member-grid.vue b/src/views/im/home/components/group/group-member-grid.vue
deleted file mode 100644
index e7cc7d6..0000000
--- a/src/views/im/home/components/group/group-member-grid.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { ImFriendAddSource } from '../../../utils/constants'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupMemberGrid' })
-
-withDefaults(
- defineProps<{
- clickable?: boolean // 澶村儚鐐瑰嚮鏄惁寮� UserInfoCard锛涢�夋嫨鍣ㄥ鏍奸噷闇�瑕佷繚鎸佸叧闂紝閬垮厤鍜屽嬀閫変氦浜掑啿绐�
- groupName?: string // 缇ゅ悕锛氬姞濂藉弸鏃舵嫾銆屾垜鏄� 'XX 缇�' 鐨� YY銆嶈瘽鏈紝钀藉簱 add_source=GROUP
- member: GroupMemberLite
- size?: number // 澶村儚鍍忕礌澶у皬锛涢粯璁� 38锛堝吋瀹归�夋嫨鍣ㄥ彸渚у凡閫夊尯锛夛紝缇や俊鎭娊灞変紶 50 瀵归綈寰俊 PC
- }>(),
- {
- clickable: false,
- size: 38,
- groupName: ''
- }
-)
-</script>
-
-<template>
- <!--
- 缇ゆ垚鍛樺鏍煎崟鍏�
- - 瀹牸灞曠ず鐨勬渶灏忓崟浣嶏細澶村儚鍦ㄤ笂銆佸悕瀛楀湪涓嬶紱鍒楀 = size + 16锛岃嚜閫傚簲 size 鐣欏懠鍚哥┖闂�
- - 琚� GroupMemberPickerPanel 鍙充晶宸查�夊尯锛坓rid 褰㈡�侊級銆丆onversationGroupSide 缇ゆ垚鍛樺尯寰幆浣跨敤
- -->
- <div
- class="relative flex flex-col items-center px-0.5 py-1"
- :style="{ width: `${size! + 16}px` }"
- >
- <UserAvatar
- :id="member.userId"
- :url="member.avatar"
- :name="member.nickname"
- :size="size"
- :clickable="clickable"
- :add-source="ImFriendAddSource.GROUP"
- :add-source-extra="groupName"
- />
- <div
- class="w-full mt-1 overflow-hidden text-12px leading-[18px] text-center truncate text-[var(--ant-color-text)]"
- >
- {{ member.showName }}
- </div>
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/im/home/components/group/group-member-item.vue b/src/views/im/home/components/group/group-member-item.vue
deleted file mode 100644
index 7992b41..0000000
--- a/src/views/im/home/components/group/group-member-item.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { computed } from 'vue'
-
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-
-import { ImGroupMemberRole } from '../../../utils/constants'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupMemberItem' })
-
-const props = withDefaults(
- defineProps<{
- active?: boolean
- height?: number
- member: GroupMemberLite
- }>(),
- {
- height: 50,
- active: false
- }
-)
-
-defineEmits<{
- click: [member: GroupMemberLite]
-}>()
-
-const avatarSize = computed(() => Math.ceil(props.height * 0.75))
-
-/** 瑙掕壊鏍囩鏂囨锛氭櫘閫氭垚鍛樹笉鏄剧ず锛屽叾浣欏彇 im_group_member_role 瀛楀吀 label */
-const roleLabel = computed(() => {
- if (props.member.role == null || props.member.role === ImGroupMemberRole.NORMAL) {
- return ''
- }
- return getDictLabel(DICT_TYPE.IM_GROUP_MEMBER_ROLE, props.member.role)
-})
-
-/** 瑙掕壊鏍囩鏍峰紡锛氱兢涓荤敤涓昏壊锛涚鐞嗗憳鐢ㄦ瑕佽壊 */
-const roleLabelClass = computed(() => {
- if (props.member.role === ImGroupMemberRole.OWNER) {
- return 'text-[var(--ant-color-primary)] bg-[var(--ant-color-primary-bg)]'
- }
- return 'text-[var(--ant-color-info)] bg-[var(--ant-color-fill)]'
-})
-</script>
-
-<template>
- <!--
- 缇ゆ垚鍛樿褰㈡��
- - 妯帓銆佸甫 hover 鎬侊紱slot 鏀� checkbox / 鎿嶄綔鎸夐挳绛�
- - 涓� GroupMember 鐨勫樊鍒細甯� hover 鎬� + slot 鎵╁睍鐐癸紝閫傚悎 selector / admin 鍒楄〃
- -->
- <div
- class="relative flex gap-2.5 items-center mx-px px-4 box-border whitespace-nowrap rounded cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="{ '!bg-[#e1eaf7] dark:!bg-[var(--ant-color-primary-bg-hover)]': active }"
- :style="{ height: `${height }px` }"
- @click="$emit('click', member)"
- >
- <UserAvatar
- :id="member.userId"
- :url="member.avatar"
- :name="member.nickname"
- :size="avatarSize"
- :clickable="false"
- />
- <div
- class="flex-1 h-full pl-1 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- :style="{ lineHeight: `${height }px` }"
- >
- {{ member.showName }}
- </div>
- <!-- 瑙掕壊鏍囩锛氱兢涓� / 绠$悊鍛橈紱鏅�氭垚鍛樹笉鏄剧ず銆備粎鍦ㄤ紶鍏� member.role 鏃剁敓鏁� -->
- <span
- v-if="roleLabel"
- class="px-1.5 py-px rounded text-xs whitespace-nowrap"
- :class="roleLabelClass"
- >
- {{ roleLabel }}
- </span>
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/im/home/components/group/group-member-remove-dialog.vue b/src/views/im/home/components/group/group-member-remove-dialog.vue
deleted file mode 100644
index b81b003..0000000
--- a/src/views/im/home/components/group/group-member-remove-dialog.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { ref } from 'vue'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { removeGroupMember } from '#/api/im/group/member'
-
-import { GroupMemberPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImGroupMemberRemoveDialog' })
-
-const emit = defineEmits<{
- /** 绉诲嚭鎴愬姛锛涚埗渚ч�氬父鐢ㄦ潵 reload 缇ゆ暟鎹� */
- reload: []
-}>()
-
-const visible = ref(false)
-const submitting = ref(false)
-const groupId = ref(0)
-const members = ref<GroupMemberLite[]>([])
-const hideIds = ref<number[]>([])
-const selectedIds = ref<number[]>([])
-
-defineExpose({
- /** 鎵撳紑绉婚櫎缇ゆ垚鍛樺脊绐楋細reset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: {
- groupId: number
- /** 闅愯棌 userId锛氱兢涓诲缁堥殣钘忥紱绠$悊鍛樿瑙掗澶栭殣钘忓叾瀹冪鐞嗗憳 */
- hideIds?: number[]
- members: GroupMemberLite[]
- }) {
- groupId.value = opts.groupId
- members.value = opts.members
- hideIds.value = opts.hideIds ? [...opts.hideIds] : []
- selectedIds.value = []
- submitting.value = false
- visible.value = true
- }
-})
-
-/** 涓�娆℃�ф壒閲忚涪浜猴細閫変腑鎴愬憳 userId 鏁扮粍浼犵粰鍚庣锛屾瘮寰幆璋� N 娆℃帴鍙g渷寰�杩� */
-async function handleOk() {
- if (!groupId.value || selectedIds.value.length === 0) {
- return
- }
- submitting.value = true
- try {
- const memberUserIds = [...selectedIds.value]
- await removeGroupMember({ groupId: groupId.value, memberUserIds })
- message.success(`宸茬Щ闄� ${memberUserIds.length} 浣嶆垚鍛榒)
- emit('reload')
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 绉婚櫎缇ゆ垚鍛橈細閫夋垚鍛� 鈫� removeGroupMember 鎵归噺韪汉
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 GroupMemberPickerPanel
- - 缇や富 / 绠$悊鍛樿瑙掔殑涓嶅彲绉婚櫎鎴愬憳閫氳繃 hideIds 闅愯棌锛岀敱璋冪敤鏂逛紶鍏�
- - 瀵瑰鎺ュ彛锛歳ef + open({ groupId, members, hideIds }) + emit reload()
- -->
- <Modal
- v-model:open="visible"
- title="绉诲嚭缇ゆ垚鍛�"
- width="700px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <GroupMemberPickerPanel
- v-model:selected-ids="selectedIds"
- :members="members"
- :hide-ids="hideIds"
- :max-size="50"
- />
- </div>
-
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="submitting"
- :disabled="selectedIds.length === 0"
- @click="handleOk"
- >
- 绉诲嚭
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/group/group-member.vue b/src/views/im/home/components/group/group-member.vue
deleted file mode 100644
index 2ae97a2..0000000
--- a/src/views/im/home/components/group/group-member.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { ImFriendAddSource } from '../../../utils/constants'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupMember' })
-
-const props = withDefaults(
- defineProps<{
- active?: boolean // 閫変腑鎬侊紙@鍊欓�夐敭鐩橀珮浜瓑锛�
- clickable?: boolean // 澶村儚鐐瑰嚮鏄惁寮� UserInfoCard锛汙鍊欓�夊満鏅�氬父绂佺敤锛堥伩鍏嶅祵濂椾氦浜掞級
- groupName?: string // 缇ゅ悕锛氬姞濂藉弸鏃舵嫾銆屾垜鏄� 'XX 缇�' 鐨� YY銆嶈瘽鏈紝钀藉簱 add_source=GROUP
- height?: number // 琛岄珮锛坧x锛夛紝褰卞搷澶村儚澶у皬
- member: GroupMemberLite
- }>(),
- {
- height: 50,
- active: false,
- clickable: false,
- groupName: ''
- }
-)
-
-/** 缇ゆ垚鍛樼粨鏋勶紙璺ㄥ澶勪娇鐢紝鏀捐繖閲屽仛绐勬帴鍙o紱鐙珛浜� types/index.ts锛� */
-export interface GroupMemberLite {
- userId: number // 鐢ㄦ埛缂栧彿锛涚壒娈婂�艰 IM_AT_ALL_USER_ID锛園 鍏ㄤ綋鎴愬憳锛�
- nickname: string // 鐪熷疄鏄电О锛氭案杩滄槸鐢ㄦ埛鐨� nickname锛屼笓缁� UserAvatar 鑹插崱鐢紝淇濊瘉鍚屼竴涓汉鑹插崱棣栧瓧姣嶅湪鎵�鏈夌晫闈竴鑷�
- showName: string // 灞曠ず鏄电О锛氬ソ鍙嬪娉� > 鐢ㄦ埛缇ゅ娉紙displayUserName锛� > 鐪熷疄鏄电О锛坣ickname锛夛紝缁�"鏄剧ず缁欑敤鎴风湅"鐨勪綅缃敤锛堣鍐呮枃瀛椼�丂鍊欓�夋爣绛剧瓑锛�
- avatar?: string
- status?: number
- role?: number // 鎴愬憳瑙掕壊锛屼粎鍦ㄧ兢淇℃伅鎶藉眽绛夐渶瑕佸睍绀鸿鑹叉爣绛剧殑鍦烘櫙閫忎紶锛汙鍊欓�� / 宸茶鍒楄〃绛夊満鏅彲涓嶄紶
-}
-
-const avatarSize = computed(() => Math.ceil(props.height * 0.75))
-</script>
-
-<template>
- <!--
- 缇ゆ垚鍛樺崟琛�
- 璺ㄥ瓙鍩熷鐢細@鍊欓�� (MentionPicker) / 宸茶鍒楄〃 (MessageReadStatus) / 缇ゆ垚鍛樺鏍� (ConversationGroupSide)
- -->
- <div
- class="relative flex items-center px-[5px] box-border whitespace-nowrap"
- :class="{ 'bg-[#e1eaf7] dark:bg-[var(--ant-color-primary-bg)]': active }"
- :style="{ height: `${height }px` }"
- >
- <UserAvatar
- :size="avatarSize"
- :name="member.nickname"
- :url="member.avatar"
- :clickable="clickable"
- :id="member.userId"
- :add-source="ImFriendAddSource.GROUP"
- :add-source-extra="groupName"
- />
- <div
- class="flex-1 h-full pl-2.5 overflow-hidden text-sm text-left truncate text-[var(--ant-color-text)]"
- :style="{ lineHeight: `${height }px` }"
- >
- {{ member.showName }}
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/group/group-mute-member-dialog.vue b/src/views/im/home/components/group/group-mute-member-dialog.vue
deleted file mode 100644
index 5f5315c..0000000
--- a/src/views/im/home/components/group/group-mute-member-dialog.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { muteMember } from '#/api/im/group'
-
-defineOptions({ name: 'ImGroupMuteMemberDialog' })
-
-const emit = defineEmits<{
- success: []
-}>()
-
-const visible = ref(false)
-const loading = ref(false)
-const groupId = ref(0)
-const userId = ref(0)
-const memberName = ref('')
-const selected = ref(600) // 榛樿 10 鍒嗛挓
-
-const presets = [
- { label: '10 鍒嗛挓', value: 600 },
- { label: '1 灏忔椂', value: 3600 },
- { label: '12 灏忔椂', value: 43_200 },
- { label: '1 澶�', value: 86_400 },
- { label: '7 澶�', value: 604_800 },
- { label: '30 澶�', value: 2_592_000 },
- { label: '姘镐箙', value: 0 }
-]
-
-/** 鎵撳紑寮圭獥 */
-function open(gid: number, uid: number, name: string) {
- groupId.value = gid
- userId.value = uid
- memberName.value = name
- selected.value = 600
- visible.value = true
-}
-
-/** 纭绂佽█ */
-async function handleConfirm() {
- loading.value = true
- try {
- await muteMember({
- id: groupId.value,
- userId: userId.value,
- mutedSeconds: selected.value
- })
- message.success('绂佽█鎴愬姛')
- visible.value = false
- emit('success')
- } finally {
- loading.value = false
- }
-}
-
-defineExpose({ open })
-</script>
-
-<template>
- <!-- 绂佽█鏃堕暱閫夋嫨寮圭獥 -->
- <Modal v-model:open="visible" title="璁剧疆绂佽█" width="560px" :mask-closable="false">
- <div class="flex flex-col gap-4">
- <!-- 鎴愬憳淇℃伅鍗★細鍜� FriendAddDialog 鐨� user 鍗′繚鎸佷竴鑷寸殑娴呰壊鑳屾櫙 -->
- <div
- class="flex items-center gap-2 px-3 py-2.5 rounded-md bg-[var(--ant-color-fill-secondary)]"
- >
- <span class="text-13px text-[var(--ant-color-text-secondary)]">绂佽█鎴愬憳</span>
- <span
- class="text-sm font-medium text-[var(--ant-color-text)] truncate"
- >
- {{ memberName }}
- </span>
- </div>
-
- <!-- 绂佽█鏃堕暱閫夐」锛氱敤 el-button 骞抽摵锛岄�変腑璧� primary锛岄潬 gap-2 鐣欓棿璺� -->
- <div>
- <div class="mb-2 text-13px text-[var(--ant-color-text-secondary)]">绂佽█鏃堕暱</div>
- <div class="grid grid-cols-3 gap-2">
- <Button
- v-for="opt in presets"
- :key="opt.value"
- :type="selected === opt.value ? 'primary' : undefined"
- class="!ml-0 w-full"
- @click="selected = opt.value"
- >
- {{ opt.label }}
- </Button>
- </div>
- </div>
- </div>
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button type="primary" :loading="loading" @click="handleConfirm">纭畾</Button>
- </template>
- </Modal>
-</template>
diff --git a/src/views/im/home/components/group/group-owner-transfer-dialog.vue b/src/views/im/home/components/group/group-owner-transfer-dialog.vue
deleted file mode 100644
index 4af497d..0000000
--- a/src/views/im/home/components/group/group-owner-transfer-dialog.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from './group-member.vue'
-
-import { computed, ref } from 'vue'
-
-import { confirm } from '../../../../../packages/effects/common-ui/src'
-
-import { Button, message, Modal } from 'ant-design-vue'
-
-import { transferGroupOwner } from '#/api/im/group'
-
-import { GroupMemberPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImGroupOwnerTransferDialog' })
-
-const emit = defineEmits<{
- /** 杞鎴愬姛锛涚埗渚ч�氬父鐢ㄦ潵 reload 缇ゆ暟鎹� */
- reload: []
-}>()
-
-const visible = ref(false)
-const submitting = ref(false)
-const groupId = ref(0)
-const members = ref<GroupMemberLite[]>([])
-const hideIds = ref<number[]>([])
-const selectedIds = ref<number[]>([])
-
-defineExpose({
- /** 鎵撳紑杞缇や富寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: {
- groupId: number
- /** 闅愯棌 userId锛氬綋鍓嶇敤鎴凤紙涓嶈兘杞粰鑷繁锛� */
- hideIds?: number[]
- members: GroupMemberLite[]
- }) {
- groupId.value = opts.groupId
- members.value = opts.members
- hideIds.value = opts.hideIds ? [...opts.hideIds] : []
- selectedIds.value = []
- submitting.value = false
- visible.value = true
- }
-})
-
-/** 閫変腑鐨勬柊缇や富瀵硅薄锛堝彇鏁扮粍棣栭」锛� */
-const newOwner = computed<GroupMemberLite | undefined>(() => {
- if (selectedIds.value.length === 0) {
- return undefined
- }
- return members.value.find((member) => member.userId === selectedIds.value[0])
-})
-
-/** 浜屾纭杞锛氳浆璁╁悗鏃х兢涓婚檷涓烘櫘閫氭垚鍛橈紝鏃犳硶鎾ら攢 */
-async function handleOk() {
- if (!groupId.value || !newOwner.value) {
- return
- }
- try {
- await confirm(
- `纭畾灏嗙兢涓昏浆璁╃粰 ${newOwner.value.showName}锛熻浆璁╁悗浣犲皢鍙樹负鏅�氭垚鍛橈紝鏃犳硶鎾ら攢銆俙,
- '纭杞缇や富'
- )
- } catch {
- return
- }
- submitting.value = true
- try {
- await transferGroupOwner({
- id: groupId.value,
- newOwnerUserId: newOwner.value.userId
- })
- message.success('缇や富杞鎴愬姛')
- emit('reload')
- visible.value = false
- } finally {
- submitting.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 杞缇や富锛氶�� 1 浣嶆柊缇や富 鈫� 浜屾纭 鈫� transferGroupOwner
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 GroupMemberPickerPanel
- - 褰撳墠鐢ㄦ埛浠庡�欓�夐噷闅愯棌锛堜笉鑳借浆缁欒嚜宸憋級
- - maxSize=1 闄愬畾鍗曢��
- - 瀵瑰鎺ュ彛锛歳ef + open({ groupId, members, hideIds }) + emit reload()
- -->
- <Modal
- v-model:open="visible"
- title="閫夋嫨鏂扮兢涓�"
- width="700px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <GroupMemberPickerPanel
- v-model:selected-ids="selectedIds"
- :members="members"
- :hide-ids="hideIds"
- :max-size="1"
- />
- </div>
-
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="submitting"
- :disabled="selectedIds.length === 0"
- @click="handleOk"
- >
- 纭畾
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/group/group-request-list-dialog.vue b/src/views/im/home/components/group/group-request-list-dialog.vue
deleted file mode 100644
index f67d51e..0000000
--- a/src/views/im/home/components/group/group-request-list-dialog.vue
+++ /dev/null
@@ -1,377 +0,0 @@
-<script lang="ts" setup>
-import type { ImGroupRequestApi } from '#/api/im/group/request'
-
-import { computed, ref, watch } from 'vue'
-
-import { prompt } from '../../../../../packages/effects/common-ui/src'
-
-import { Empty, Input, message, Modal, Spin } from 'ant-design-vue'
-
-import { getGroupRequestListByGroupId } from '#/api/im/group/request'
-import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
-
-import { useGroupRequestStore } from '../../store/groupRequestStore'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupRequestListDialog' })
-
-const groupRequestStore = useGroupRequestStore()
-
-const visible = ref(false)
-const groupId = ref<number | undefined>() // 褰撳墠灞曠ず鐨勭兢缂栧彿锛泆ndefined 鏃惰蛋鍏ㄥ眬鏈鐞嗗垪琛紙store.unhandledList锛�
-const loading = ref(false)
-const groupList = ref<ImGroupRequestApi.GroupRequestRespVO[]>([])
-const actingId = ref<null | number>(null)
-
-defineExpose({
- /** 鎵撳紑杩涚兢鐢宠寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true锛涗笉浼� groupId 璧板叏灞�鏈鐞嗗垪琛� */
- open(opts?: { groupId?: number }) {
- groupId.value = opts?.groupId
- actingId.value = null
- visible.value = true
- }
-})
-
-/** 鏁版嵁婧愶細鍗曠兢妯″紡鐢� fetch 鍥炴潵鐨� groupList锛涘叏灞�妯″紡鐩存帴璇� store.unhandledList锛屽鐞嗗悗 store 鑷姩 reactive 鍚屾 */
-const list = computed<ImGroupRequestApi.GroupRequestRespVO[]>(() =>
- groupId.value ? groupList.value : groupRequestStore.unhandledList
-)
-
-/** 椤堕儴鍗$墖锛氭渶鏂颁竴鏉★紱绌烘暟缁勬椂涓� null */
-const latest = computed(() => list.value[0] || null)
-/** 鍘嗗彶鍒楄〃锛氶櫎鏈�鏂颁竴鏉″鐨勫叾浣� */
-const histories = computed(() => list.value.slice(1))
-
-/** 鎵撳紑 dialog 鏃舵媺鏁版嵁锛氬崟缇ゆ媺 API锛涘叏灞�鐩存帴璇� store锛涘叧闂椂娓呮帀鍗曠兢缂撳瓨 */
-watch(
- [visible, groupId],
- ([isVisible, currentGroupId]) => {
- if (isVisible && currentGroupId) {
- void fetchList(currentGroupId)
- } else if (!isVisible) {
- groupList.value = []
- }
- },
- { immediate: true }
-)
-
-/**
- * 鍗曠兢妯″紡涓嬭闃� store 涓綊灞炴湰缇ょ殑鏈鐞嗗垪琛ㄥ彉鍖栵細杩滅浜嬩欢锛圵S 1503 鏂扮敵璇� / 鍏朵粬绠$悊鍛樺鐞嗭級瑙﹀彂鏃� refetch
- * 鎷挎渶鏂� handleResult锛涙湰绔� agree / refuse 鏈熼棿 actingId 閿佷綇锛岃烦杩囨湰绔姩浣滃紩鍙戠殑 store 鍙樺寲閬垮厤鍐椾綑 RTT
- *
- * key 涓嶈兘鍙� join id锛氬鐢ㄦ棫璁板綍鏃跺悓涓� requestId 鐨� applyContent / inviterUserId 浼氬埛鏂颁絾 id 涓嶅彉锛屽繀椤绘妸鍐呭瀛楁涔熺撼鍏ヨЕ鍙�
- */
-watch(
- () =>
- groupId.value && visible.value
- ? groupRequestStore.unhandledList
- .filter((request) => request.groupId === groupId.value)
- .map(
- (request) =>
- `${request.id}:${request.inviterUserId ?? ''}:${request.applyContent ?? ''}`
- )
- .join(',')
- : null,
- (current, previous) => {
- if (current === null || previous === undefined || current === previous) {
- return
- }
- if (actingId.value !== null) {
- return
- }
- if (groupId.value) {
- void fetchList(groupId.value)
- }
- }
-)
-
-let fetchSeq = 0 // 鍗曡皟閫掑璇锋眰搴忓彿锛涘悓缇や篃浼氬洜涓� WS 1503 鎺ㄩ�佽Е鍙戦澶� fetch锛屼贡搴忚繑鍥炴椂鏃у搷搴斾笉鑳借鐩栨柊鏁版嵁
-async function fetchList(targetGroupId: number) {
- const seq = ++fetchSeq
- loading.value = true
- try {
- const data = (await getGroupRequestListByGroupId(targetGroupId)) || []
- // 鏈熼棿鍒囩兢 / 鍏冲脊绐� / 鍙堣Е鍙戞洿鏂� fetch锛氫涪鍝嶅簲
- if (seq !== fetchSeq || !visible.value || groupId.value !== targetGroupId) {
- return
- }
- groupList.value = data
- } finally {
- // 鏃ц姹� finally 鍛戒腑鏃舵柊璇锋眰浠嶅湪璺戯紝璺宠繃閬垮厤鎻愬墠鍏� loading
- if (seq === fetchSeq) {
- loading.value = false
- }
- }
-}
-
-/** 鍚屾剰锛氳蛋 store 鍚屾鍏ㄥ眬鏈鐞嗗垪琛� + 鏈湴鏇存柊 handleResult 璁╂寜閽彉鐏� */
-async function handleAgree(item: ImGroupRequestApi.GroupRequestRespVO) {
- if (actingId.value !== null) return
- actingId.value = item.id
- try {
- await groupRequestStore.agreeGroupRequest(item.id)
- updateLocalResult(item.id, ImGroupRequestHandleResult.AGREED)
- message.success('宸插悓鎰�')
- } finally {
- actingId.value = null
- }
-}
-
-/** 鎷掔粷锛氬脊鐞嗙敱杈撳叆妗嗭紱涓虹┖鍒欎笉甯� handleContent */
-async function handleRefuse(item: ImGroupRequestApi.GroupRequestRespVO) {
- if (actingId.value !== null) return
- let handleContent: string
- try {
- const result = await prompt<string>({
- component: Input,
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ嫆缁濈悊鐢憋紙鍙�夛級'
- },
- content: '',
- modelPropName: 'value',
- title: '鎷掔粷鐢宠'
- })
- handleContent = result || ''
- } catch {
- return
- }
- actingId.value = item.id
- try {
- await groupRequestStore.refuseGroupRequest(item.id, handleContent || undefined)
- updateLocalResult(item.id, ImGroupRequestHandleResult.REFUSED)
- message.success('宸叉嫆缁�')
- } finally {
- actingId.value = null
- }
-}
-
-/** 鍗曠兢妯″紡涓嬪鐞嗗悗鏇存柊 groupList 閲岀殑 handleResult锛屾寜閽浆銆屽凡鍚屾剰 / 宸叉嫆缁濄�嶇伆鎬侊紱鍏ㄥ眬妯″紡 store 鐩存帴绉婚櫎璇ラ」鏃犻渶鏇存柊 */
-function updateLocalResult(id: number, handleResult: number) {
- const target = groupList.value.find((r) => r.id === id)
- if (target) {
- target.handleResult = handleResult
- }
-}
-</script>
-
-<template>
- <!--
- 缇ゃ�岃繘缇ょ敵璇枫�嶅垪琛ㄥ璇濇
- - 浠呯兢涓� / 绠$悊鍛樺叆鍙e彲杈撅紱灞曠ず褰撳墠缇や笅鍏ㄩ儴鐢宠锛堝惈宸插鐞嗭級
- - 椤堕儴鏈�鏂颁竴鏉″崱鐗囧寲绐佸嚭锛堝甫鐢宠鐞嗙敱锛夛紝鍏朵綑鎸� id 鍊掑簭绱у噾鍒楄〃
- - 鍚屾剰 / 鎷掔粷璧� groupRequestStore 鐨� action锛屽鐞嗗悗鏈湴鏇存柊 handleResult 璁╂寜閽浆鐏版��
- -->
- <Modal
- v-model:open="visible"
- title="杩涚兢鐢宠"
- width="560px"
- :footer="null"
- :mask-closable="false"
- class="im-group-request-list__dialog"
- >
- <Spin :spinning="loading" wrapper-class-name="w-full">
- <div class="flex flex-col gap-3 max-h-[60vh] overflow-y-auto pr-1">
- <!-- 绌烘�� -->
- <Empty v-if="!loading && list.length === 0" description="鏆傛棤杩涚兢鐢宠" />
-
- <!-- 椤堕儴鍗$墖锛氭渶鏂颁竴鏉� -->
- <div
- v-if="latest"
- class="flex flex-col gap-2.5 p-3.5 rounded-[10px] border border-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-bg-container)] shadow-[0_1px_3px_rgba(0,0,0,0.04)]"
- >
- <div class="flex items-center gap-3">
- <UserAvatar
- :url="latest.userAvatar"
- :name="latest.userNickname"
- :size="44"
- :clickable="false"
- />
- <div class="flex-1 min-w-0">
- <div
- class="truncate text-sm font-medium leading-[1.4] text-[var(--ant-color-text)]"
- >
- {{ latest.userNickname || `鐢ㄦ埛 ${latest.userId}` }}
- </div>
- <div
- class="truncate mt-[2px] text-12px leading-[1.5] text-[var(--ant-color-text-secondary)]"
- >
- <template v-if="latest.inviterUserId">
- 閫氳繃
- <span class="text-[var(--ant-color-primary)]">
- {{ latest.inviterNickname || `鐢ㄦ埛 ${latest.inviterUserId}` }}
- </span>
- 鐨勯個璇疯繘缇�
- </template>
- <template v-else>鐢宠鍔犲叆</template>
- </div>
- </div>
- <span
- v-if="latest.handleResult === ImGroupRequestHandleResult.AGREED"
- class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
- >
- 宸插悓鎰�
- </span>
- <span
- v-else-if="latest.handleResult === ImGroupRequestHandleResult.REFUSED"
- class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
- >
- 宸叉嫆缁�
- </span>
- <div v-else class="flex gap-1.5 flex-shrink-0">
- <button
- class="im-group-request-list__btn im-group-request-list__btn--primary"
- :disabled="actingId === latest.id"
- @click="handleAgree(latest)"
- >
- 纭
- </button>
- <button
- class="im-group-request-list__btn im-group-request-list__btn--ghost"
- :disabled="actingId === latest.id"
- @click="handleRefuse(latest)"
- >
- 鎷掔粷
- </button>
- </div>
- </div>
- <!-- 鐢宠鐞嗙敱锛氶個璇峰満鏅樉绀洪個璇蜂汉 + 鐣欒█锛涗富鍔ㄧ敵璇锋樉绀虹敵璇蜂汉 + 鐣欒█ -->
- <div
- v-if="latest.applyContent"
- class="px-3 py-2 rounded-md text-[13px] leading-[1.5] break-all bg-[var(--ant-color-fill-secondary)] text-[var(--ant-color-text)]"
- >
- <span class="text-[var(--ant-color-primary)]">
- {{
- latest.inviterUserId
- ? latest.inviterNickname || `鐢ㄦ埛 ${latest.inviterUserId}`
- : latest.userNickname || `鐢ㄦ埛 ${latest.userId}`
- }}锛�
- </span>
- {{ latest.applyContent }}
- </div>
- </div>
-
- <!-- 鍒嗗壊绾匡細浠呭湪鏈夋洿鏃╃敵璇锋椂鍑虹幇 -->
- <div
- v-if="histories.length > 0"
- class="flex items-center justify-center mt-1.5 -mb-0.5 text-12px text-[var(--ant-color-text-placeholder)]"
- >
- <span>浠ヤ笅涓烘洿鏃╃殑鐢宠</span>
- </div>
-
- <!-- 鍘嗗彶鐢宠鍒楄〃 -->
- <div
- v-for="item in histories"
- :key="item.id"
- class="flex flex-col gap-2.5 px-3.5 py-2.5 rounded-[10px] border border-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-bg-container)] shadow-[0_1px_3px_rgba(0,0,0,0.04)]"
- >
- <div class="flex items-center gap-3">
- <UserAvatar
- :url="item.userAvatar"
- :name="item.userNickname"
- :size="40"
- :clickable="false"
- />
- <div class="flex-1 min-w-0">
- <div
- class="truncate text-sm font-medium leading-[1.4] text-[var(--ant-color-text)]"
- >
- {{ item.userNickname || `鐢ㄦ埛 ${item.userId}` }}
- </div>
- <div
- class="truncate mt-[2px] text-12px leading-[1.5] text-[var(--ant-color-text-secondary)]"
- >
- <template v-if="item.inviterUserId">
- 閫氳繃
- <span class="text-[var(--ant-color-primary)]">
- {{ item.inviterNickname || `鐢ㄦ埛 ${item.inviterUserId}` }}
- </span>
- 鐨勯個璇疯繘缇�
- </template>
- <template v-else>鐢宠鍔犲叆</template>
- </div>
- </div>
- <span
- v-if="item.handleResult === ImGroupRequestHandleResult.AGREED"
- class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
- >
- 宸插悓鎰�
- </span>
- <span
- v-else-if="item.handleResult === ImGroupRequestHandleResult.REFUSED"
- class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
- >
- 宸叉嫆缁�
- </span>
- <div v-else class="flex gap-1.5 flex-shrink-0">
- <button
- class="im-group-request-list__btn im-group-request-list__btn--primary"
- :disabled="actingId === item.id"
- @click="handleAgree(item)"
- >
- 纭
- </button>
- <button
- class="im-group-request-list__btn im-group-request-list__btn--ghost"
- :disabled="actingId === item.id"
- @click="handleRefuse(item)"
- >
- 鎷掔粷
- </button>
- </div>
- </div>
- </div>
- </div>
- </Spin>
- </Modal>
-</template>
-
-<style scoped>
-/* 鑷粯鎸夐挳锛氳创杩戝井淇″皬鑽父鏍峰紡锛涗笌 :disabled銆�:hover:not(:disabled) 绛変吉绫诲彔鍔� modifier 绫荤殑缁勫悎閫夋嫨鍣ㄥ啓鍦� class 閲屾垚鏈珮锛岀暀 SCSS */
-.im-group-request-list__btn {
- flex-shrink: 0;
- min-width: 56px;
- height: 28px;
- padding: 0 12px;
- font-size: 13px;
- border-radius: 4px;
- cursor: pointer;
- border: 1px solid transparent;
- transition:
- background-color 0.15s,
- border-color 0.15s,
- color 0.15s;
-}
-.im-group-request-list__btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
-}
-
-.im-group-request-list__btn--primary {
- color: #fff;
- background-color: var(--ant-color-primary);
- border-color: var(--ant-color-primary);
-}
-.im-group-request-list__btn--primary:hover:not(:disabled) {
- background-color: var(--ant-color-primary-hover);
- border-color: var(--ant-color-primary-hover);
-}
-
-.im-group-request-list__btn--ghost {
- color: var(--ant-color-text);
- background-color: var(--ant-color-bg-container);
- border-color: var(--ant-color-border);
-}
-.im-group-request-list__btn--ghost:hover:not(:disabled) {
- color: var(--ant-color-primary);
- border-color: var(--ant-color-primary);
-}
-</style>
-
-<style>
-/* el-dialog 鍐呴儴 body 閫氳繃 teleport 娓叉煋鍒� body锛宻coped 閫変笉鍒帮紝鐣欓潪 scoped 鍏ㄥ眬瑕嗙洊 */
-.im-group-request-list__dialog .el-dialog__body {
- padding: 12px 20px 8px;
- background-color: var(--ant-color-fill-secondary);
-}
-</style>
diff --git a/src/views/im/home/components/group/index.ts b/src/views/im/home/components/group/index.ts
deleted file mode 100644
index 3e082af..0000000
--- a/src/views/im/home/components/group/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export { default as GroupAdminSetDialog } from './group-admin-set-dialog.vue';
-export { default as GroupAvatar } from './group-avatar.vue';
-export { default as GroupCreateDialog } from './group-create-dialog.vue';
-export { default as GroupInfoCard } from './group-info-card.vue';
-export { default as GroupInfo } from './group-info.vue';
-export { default as GroupItem } from './group-item.vue';
-export { default as GroupMemberAddDialog } from './group-member-add-dialog.vue';
-export { default as GroupMemberGrid } from './group-member-grid.vue';
-export { default as GroupMemberItem } from './group-member-item.vue';
-export { default as GroupMemberRemoveDialog } from './group-member-remove-dialog.vue';
-export { default as GroupMember } from './group-member.vue';
-export type { GroupMemberLite } from './group-member.vue';
-export { default as GroupMuteMemberDialog } from './group-mute-member-dialog.vue';
-export { default as GroupOwnerTransferDialog } from './group-owner-transfer-dialog.vue';
-export { default as GroupRequestListDialog } from './group-request-list-dialog.vue';
diff --git a/src/views/im/home/components/index.ts b/src/views/im/home/components/index.ts
deleted file mode 100644
index 0d65403..0000000
--- a/src/views/im/home/components/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export { default as ContextMenu } from './context-menu.vue';
-export { default as PagedScroller } from './paged-scroller.vue';
-export { default as ResizableAside } from './resizable-aside.vue';
-export { default as ToolBar } from './tool-bar.vue';
diff --git a/src/views/im/home/components/paged-scroller.vue b/src/views/im/home/components/paged-scroller.vue
deleted file mode 100644
index ed1842a..0000000
--- a/src/views/im/home/components/paged-scroller.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-<script lang="ts" setup generic="T">
-import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
-
-defineOptions({ name: 'ImPagedScroller' })
-
-const props = withDefaults(
- defineProps<{
- itemKey?: string // 涓氬姟 id 瀛楁鍚嶏紙濡� 'userId' / 'id'锛夛紱涓嶄紶 / 瀛楁鍊奸潪 string|number 鏃跺洖閫� idx
- items: T[] // 鍏ㄩ噺鏁版嵁
- pageSize?: number // 姣忛〉娓叉煋鏉℃暟
- threshold?: number // 璺濆簳澶氬皯 px 瑙﹀彂涓嬩竴椤�
- }>(),
- {
- itemKey: undefined,
- pageSize: 30,
- threshold: 30
- }
-)
-
-/** 瑙f瀽姣忔潯 item 鐨� :key锛歝aller 浼� itemKey 鍒欐寜瀛楁鍙栵紝鏃犳晥 / 缂哄け鍥為��绱㈠紩锛岄伩鍏嶄紶閿欏瓧娈垫椂鍏ㄨ〃 undefined key */
-function resolveItemKey(item: T, idx: number): number | string {
- if (!props.itemKey || item == null || typeof item !== 'object') {
- return idx
- }
- const value = (item as Record<string, unknown>)[props.itemKey]
- return typeof value === 'string' || typeof value === 'number' ? value : idx
-}
-
-const scrollbarRef = useTemplateRef<HTMLDivElement>('scrollbarRef')
-const page = ref(1)
-
-const displayItems = computed(() => {
- const limit = Math.min(page.value * props.pageSize, props.items.length)
- return props.items.slice(0, limit)
-})
-
-const allLoaded = computed(() => displayItems.value.length >= props.items.length)
-
-/** 浠呭綋瓒呰繃涓�椤垫椂鎵嶆樉绀恒�屽凡鍒板簳閮ㄣ�嶏紝閬垮厤鐭垪琛ㄤ篃鍑虹幇杩欐潯鎻愮ず */
-const showFooter = computed(() => allLoaded.value && props.items.length > props.pageSize)
-
-let wrapEl: HTMLElement | null = null
-
-onMounted(() => {
- wrapEl = scrollbarRef.value
- wrapEl?.addEventListener('scroll', onScroll)
-})
-
-onBeforeUnmount(() => {
- wrapEl?.removeEventListener('scroll', onScroll)
-})
-
-/** 鍒囨崲鏁版嵁婧愶紙濡傚垏浼氳瘽锛夋椂閲嶇疆鍒嗛〉锛氶伩鍏嶆柊鍒楄〃娌跨敤鏃� page锛岄灞忓嚭鐜扮┖娈� */
-watch(
- () => props.items,
- () => {
- page.value = 1
- }
-)
-
-/** 婊氬埌璺濆簳 threshold 鍐呮椂鑷 page锛屾墿鍑轰笅涓�娈靛垏鐗� */
-function onScroll(e: Event) {
- const el = e.target as HTMLElement
- if (el.scrollTop + el.clientHeight < el.scrollHeight - props.threshold) {
- return
- }
- if (allLoaded.value) {
- return
- }
- page.value++
-}
-
-defineExpose({
- /** 鎵嬪姩婊氬埌椤堕儴 */
- scrollTop: () => {
- if (wrapEl) {
- wrapEl.scrollTop = 0
- }
- },
- /** 鎵嬪姩婊氬埌搴曢儴 */
- scrollBottom: () => {
- if (wrapEl) {
- wrapEl.scrollTop = wrapEl.scrollHeight
- }
- }
-})
-</script>
-
-<template>
- <!--
- 鍒嗛〉澧為噺婊氬姩瀹瑰櫒
- - 婊氬埌搴曢儴鑷姩 page++锛岀洿鍒板叏閮ㄦ覆鏌撳畬
- - 閫氳繃 slot 鏆撮湶姣忎竴椤癸紝璁╄皟鐢ㄦ柟鑷繁鍐冲畾娓叉煋
- -->
- <div ref="scrollbarRef" class="w-full h-full overflow-y-auto">
- <slot
- v-for="(item, idx) in displayItems"
- :item="item"
- :index="idx"
- :key="resolveItemKey(item, idx)"
- ></slot>
- <div
- v-if="showFooter"
- class="py-3 text-xs text-center text-[var(--ant-color-text-secondary)]"
- >
- 宸插埌搴曢儴
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/picker/conversation-picker-panel.vue b/src/views/im/home/components/picker/conversation-picker-panel.vue
deleted file mode 100644
index 5f072db..0000000
--- a/src/views/im/home/components/picker/conversation-picker-panel.vue
+++ /dev/null
@@ -1,370 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Input, message } from 'ant-design-vue'
-
-import { ImConversationType } from '../../../utils/constants'
-import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
-import { GroupAvatar } from '../group'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImConversationPickerPanel' })
-
-const props = withDefaults(
- defineProps<{
- /** 鍏ㄩ噺浼氳瘽鍒楄〃 */
- conversations: Conversation[]
- /** 闅愯棌 key锛氫粠鍊欓�� / 宸查�� / 鏈�杩戣浆鍙戦噷閮藉墧闄わ紙涓嶈兘杞彂鍥炶嚜宸便�佹帹鑽愬悕鐗囪嚜韬殑浼氳瘽绛夛級 */
- hideKeys?: string[]
- /** 宸查�夋暟涓婇檺锛涗笉浼犳垨 <=0 鏃朵笉闄� */
- maxSize?: number
- /** 鏈�杩戣浆鍙戜細璇� key 鍒楄〃锛涘睍绀哄湪宸︽爮椤堕儴妯悜澶村儚鍖� */
- recentForwardConversationKeys?: string[]
- /** 宸查�変細璇� key锛坴-model锛夛紱key 鐢� getConversationKey 鐢熸垚 */
- selectedKeys: string[]
- /** 鏄惁灞曠ず銆屽垱寤鸿亰澶┿�嶅叆鍙� */
- showCreateChat?: boolean
- }>(),
- {
- recentForwardConversationKeys: () => [],
- hideKeys: () => [],
- maxSize: 0,
- showCreateChat: false
- }
-)
-
-const emit = defineEmits<{
- createChat: []
- /** 鐢ㄦ埛鍦ㄣ�屾渶杩戣浆鍙戙�嶆杩涘叆绉婚櫎妯″紡鍚庣偣 脳锛涗笟鍔″3鏀跺埌鍚庤皟 conversationStore.removeRecentForwardConversationKey 钀界洏 */
- removeRecent: [key: string]
- 'update:selectedKeys': [value: string[]]
-}>()
-
-const keyword = ref('')
-const recentRemoveMode = ref(false) // 銆屾渶杩戣浆鍙戙�嶆鏄惁澶勪簬绉婚櫎妯″紡锛歵rue 鏃跺ご鍍忓彸涓婅鍙� 脳 涓嶅啀鍒囧嬀閫�
-
-/** 鍏ㄩ噺浼氳瘽鐨� key鈫扖onversation 鏄犲皠锛屽凡閫� / 鏈�杩戣浆鍙戝弽鏌ュ叡鐢紝閬垮厤姣忔 O(N) 鎵� */
-const byKey = computed(() => {
- const map = new Map<string, Conversation>()
- for (const conversation of props.conversations) {
- map.set(getConversationKey(conversation), conversation)
- }
- return map
-})
-
-/** 闅愯棌闆嗗悎锛氭瘡娆¤繃婊ゅ鐢� */
-const hideSet = computed(() => new Set(props.hideKeys))
-
-/** 宸查�夐泦鍚堬細鍦嗗舰鎸囩ず鍣� isSelected 璧� set 蹇煡 */
-const selectedSet = computed(() => new Set(props.selectedKeys))
-
-/** 鍊欓�変細璇濓細鍓旈櫎 hideKeys */
-const candidateConversations = computed(() =>
- props.conversations.filter((c) => !hideSet.value.has(getConversationKey(c)))
-)
-
-/** 宸︽爮灞曠ず鍒楄〃锛氬湪鍊欓�夊熀纭�涓婃寜 keyword 杩囨护 */
-const shownConversations = computed(() =>
- filterConversationsByKeyword(candidateConversations.value, keyword.value)
-)
-
-/** 鏈�杩戣浆鍙戠殑浼氳瘽瀵硅薄鍒楄〃锛氫粠 recentForwardConversationKeys 鍙嶆煡锛涘墧闄� hide / 涓嶅瓨鍦ㄧ殑 key */
-const recentForwardConversations = computed(() =>
- props.recentForwardConversationKeys
- .map((key) => byKey.value.get(key))
- .filter((c): c is Conversation => c != null && !hideSet.value.has(getConversationKey(c)))
-)
-
-/** 鏄惁灞曠ず銆屾渶杩戣浆鍙戙�嶆锛歬eyword 涓虹┖ + 鏈夋暟鎹椂鎵嶅睍绀猴紝鎼滅储鏃惰浣� */
-const showRecentSection = computed(
- () => !keyword.value.trim() && recentForwardConversations.value.length > 0
-)
-
-/** 宸查�変細璇濆垪琛細鎸� selectedKeys 鏁扮粍椤哄簭锛堝嵆鐐瑰嚮椤哄簭锛夊弽鏌ワ紱杩囨护 hideSet 閬垮厤鐖剁粍浠跺姩鎬侀殣钘忕殑浼氳瘽浠嶅湪鍙充晶娓叉煋 / 鎻愪氦 */
-const selectedConversations = computed(() =>
- props.selectedKeys
- .map((key) => byKey.value.get(key))
- .filter(
- (conversation): conversation is Conversation =>
- conversation != null && !hideSet.value.has(getConversationKey(conversation))
- )
-)
-
-/** 鍙虫爮鏍囬鏂囨锛氬崟閫夈�屽彂閫佺粰銆嶃�佸閫夈�屽垎鍒彂閫佺粰銆� */
-const sendTitle = computed(() => (props.selectedKeys.length > 1 ? '鍒嗗埆鍙戦�佺粰' : '鍙戦�佺粰'))
-
-/** 鏄惁宸查�変腑锛氬乏鏍忓渾褰㈡寚绀哄櫒 / 鏈�杩戣浆鍙戝ご鍍忚鏍囧叡鐢� */
-function isSelected(conversation: Conversation): boolean {
- return selectedSet.value.has(getConversationKey(conversation))
-}
-
-/** 銆屾渶杩戣浆鍙戙�嶅ご鍍忕偣鍑伙細绉婚櫎妯″紡涓嬩笉鍒囧嬀閫夛紙绉婚櫎鐢� 脳 瑙掓爣澶勭悊锛� */
-function handleRecentTileClick(conversation: Conversation) {
- if (recentRemoveMode.value) {
- return
- }
- handleToggle(conversation)
-}
-
-/** 鍒囨崲閫変腑鎬侊細宸︽爮 row / 鏈�杩戣浆鍙戝ご鍍� / 鍙虫爮 脳 绉婚櫎閮借蛋杩欓噷 */
-function handleToggle(conversation: Conversation) {
- const key = getConversationKey(conversation)
- const next = [...props.selectedKeys]
- const index = next.indexOf(key)
- if (index === -1) {
- // 鐖剁粍浠舵爣璁伴殣钘忕殑浼氳瘽鍗充究鏈夎矾寰勮Е杈句篃涓嶅簲鍏ラ��
- if (hideSet.value.has(key)) {
- return
- }
- if (props.maxSize > 0 && next.length >= props.maxSize) {
- message.error(`鏈�澶氶�夋嫨 ${props.maxSize} 涓細璇漙)
- return
- }
- next.push(key)
- } else {
- next.splice(index, 1)
- }
- emit('update:selectedKeys', next)
-}
-</script>
-
-<template>
- <!--
- 浼氳瘽閫夋嫨闈㈡澘锛氱敤浜庢帹鑽愬悕鐗� / 杞彂娑堟伅绛�"閫夊凡鏈変細璇�"鍦烘櫙
- - 宸︼細鎼滅储 + 鏈�杩戣浆鍙戞í鍚戝ご鍍� + 鍒涘缓鑱婂ぉ鍏ュ彛 + 鏈�杩戣亰澶╁垪琛紙鍦嗗舰鍕鹃�夛級
- - 鍙筹細宸查�夋暟鏍囬 + 宸查�変細璇濆垪琛紙鎸夌偣鍑婚『搴忥級+ footer slot
- - Panel 涓嶅甫 el-dialog 澹筹紱dialog 鐢变笟鍔″3鎸佹湁
- - footer slot 娓叉煋鍦ㄥ彸鏍忓凡閫夊垪琛ㄤ笅鏂癸紝涓氬姟澹虫斁棰勮鍗� / 鐣欒█ / 鎻愪氦鎸夐挳
- -->
- <div class="flex h-full">
- <!-- 宸︽爮 -->
- <div
- class="flex flex-col w-[280px] border-r border-r-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- <!-- 鎼滅储妗� -->
- <div class="flex-shrink-0 px-3 py-2">
- <Input v-model:value="keyword" placeholder="鎼滅储" allow-clear>
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- </div>
-
- <div class="flex-1">
- <!-- 鏈�杩戣浆鍙戞í鍚戝ご鍍忓尯锛歬eyword 涓虹┖ + 鏈夋渶杩戣浆鍙戞暟鎹椂灞曠ず -->
- <template v-if="showRecentSection">
- <div class="flex justify-between items-center pl-3 pr-2 pb-1.5">
- <span class="text-13px text-[var(--ant-color-text-secondary)]">鏈�杩戣浆鍙�</span>
- <span
- class="px-1 cursor-pointer text-13px text-[var(--ant-color-primary)] hover:opacity-80"
- @click="recentRemoveMode = !recentRemoveMode"
- >
- {{ recentRemoveMode ? '瀹屾垚' : '绉婚櫎' }}
- </span>
- </div>
- <div
- class="flex gap-2 pl-3 pr-2 pt-1 pb-2 overflow-x-auto im-conversation-picker__recent"
- >
- <div
- v-for="conversation in recentForwardConversations"
- :key="getConversationKey(conversation)"
- class="flex flex-col flex-shrink-0 gap-1 items-center"
- :class="{ 'cursor-pointer': !recentRemoveMode }"
- @click="handleRecentTileClick(conversation)"
- >
- <div class="relative">
- <GroupAvatar
- v-if="conversation.type === ImConversationType.GROUP"
- :group-id="conversation.targetId"
- :url="conversation.avatar"
- :name="conversation.name"
- :size="36"
- />
- <UserAvatar
- v-else
- :url="conversation.avatar"
- :name="conversation.name"
- :size="36"
- :clickable="false"
- />
- <!-- 绉婚櫎妯″紡锛氬彸涓婅 脳 鍦嗚鏍囷紝鐐瑰嚮鎶婅繖鏉� key 浠� recentForwardConversationKeys 鍒犳帀 -->
- <span
- v-if="recentRemoveMode"
- class="flex absolute -top-1 -right-1 justify-center items-center w-4 h-4 rounded-full cursor-pointer bg-[var(--ant-color-fill-dark)] text-[var(--ant-color-text)]"
- @click.stop="emit('removeRecent', getConversationKey(conversation))"
- >
- <Icon icon="ant-design:close-outlined" :size="10" />
- </span>
- <!-- 闈炵Щ闄ゆā寮忥細鍙充笂瑙掑渾褰㈠嬀閫夋寚绀哄櫒锛涙湭閫夌伆绌哄績鍦堛�侀�変腑缁垮簳鐧藉鍕� -->
- <span
- v-else
- class="flex absolute -top-1 -right-1 justify-center items-center w-4 h-4 rounded-full transition-colors"
- :class="
- isSelected(conversation)
- ? 'bg-[#07c160] border border-solid border-[#07c160]'
- : 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
- "
- >
- <Icon
- v-if="isSelected(conversation)"
- icon="ant-design:check-outlined"
- :size="10"
- color="#fff"
- />
- </span>
- </div>
- <span
- class="overflow-hidden max-w-[48px] text-12px truncate text-[var(--ant-color-text)]"
- >
- {{ conversation.name }}
- </span>
- </div>
- </div>
- </template>
-
- <!-- 鍒涘缓鑱婂ぉ鍏ュ彛锛歬eyword 涓虹┖ + showCreateChat=true 鏃跺睍绀� -->
- <div
- v-if="showCreateChat && !keyword.trim()"
- class="flex gap-2.5 items-center px-3 py-1.5 cursor-pointer hover:bg-[var(--ant-color-fill)]"
- @click="emit('createChat')"
- >
- <span
- class="flex flex-shrink-0 justify-center items-center w-8 h-8 rounded-full bg-[var(--ant-color-fill)] text-[var(--ant-color-text-secondary)]"
- >
- <Icon icon="ant-design:plus-outlined" :size="16" />
- </span>
- <span class="text-sm text-[var(--ant-color-text)]">鍒涘缓鑱婂ぉ</span>
- </div>
-
- <!-- 鏈�杩戣亰澶╁垎缁勬爣棰� -->
- <div class="px-3 pb-1.5 text-13px text-[var(--ant-color-text-secondary)]">鏈�杩戣亰澶�</div>
-
- <!-- 浼氳瘽鍒楄〃 -->
- <div
- v-for="conversation in shownConversations"
- :key="getConversationKey(conversation)"
- class="flex gap-2.5 items-center px-3 py-1.5 cursor-pointer hover:bg-[var(--ant-color-fill)]"
- @click="handleToggle(conversation)"
- >
- <!-- 鍦嗗舰鍕鹃�夋寚绀哄櫒锛氭湭閫夌伆鑹茬┖蹇冨渾锛岄�変腑瀹炲績寰俊缁� + 鐧藉鍕� -->
- <span
- class="flex flex-shrink-0 justify-center items-center w-5 h-5 rounded-full transition-colors"
- :class="
- isSelected(conversation)
- ? 'bg-[#07c160] border border-solid border-[#07c160]'
- : 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
- "
- >
- <Icon
- v-if="isSelected(conversation)"
- icon="ant-design:check-outlined"
- :size="12"
- color="#fff"
- />
- </span>
- <GroupAvatar
- v-if="conversation.type === ImConversationType.GROUP"
- :group-id="conversation.targetId"
- :url="conversation.avatar"
- :name="conversation.name"
- :size="32"
- />
- <UserAvatar
- v-else
- :url="conversation.avatar"
- :name="conversation.name"
- :size="32"
- :clickable="false"
- />
- <span
- class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- >
- {{ conversation.name }}
- </span>
- </div>
-
- <!-- 绌烘�� -->
- <div
- v-if="shownConversations.length === 0"
- class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ keyword ? '娌℃湁婊¤冻鏉′欢鐨勪細璇�' : '鏆傛棤浼氳瘽' }}
- </div>
- </div>
- </div>
-
- <!-- 鍙虫爮 -->
- <div class="flex flex-col flex-1 min-w-0">
- <!-- 鏍囬锛氶�� 0/1銆屽彂閫佺粰銆嶃�佸涓�屽垎鍒彂閫佺粰銆嶏紙涓庡井淇℃枃妗堜竴鑷达級 -->
- <div
- class="flex-shrink-0 px-4 py-3 border-b border-b-solid text-13px text-[var(--ant-color-text-secondary)] border-[var(--ant-color-border-secondary)]"
- >
- {{ sendTitle }}
- </div>
-
- <!-- 宸查�夐瑙堬細鎸� selectedKeys 鏁扮粍椤哄簭锛堢偣鍑婚『搴忥級灞曠ず -->
- <div class="flex-1">
- <div
- v-for="conversation in selectedConversations"
- :key="getConversationKey(conversation)"
- class="flex gap-2.5 items-center px-4 py-2"
- >
- <GroupAvatar
- v-if="conversation.type === ImConversationType.GROUP"
- :group-id="conversation.targetId"
- :url="conversation.avatar"
- :name="conversation.name"
- :size="32"
- />
- <UserAvatar
- v-else
- :url="conversation.avatar"
- :name="conversation.name"
- :size="32"
- :clickable="false"
- />
- <span
- class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- >
- {{ conversation.name }}
- </span>
- <Icon
- icon="ant-design:close-outlined"
- :size="14"
- class="flex-shrink-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
- @click="handleToggle(conversation)"
- />
- </div>
- <div
- v-if="selectedConversations.length === 0"
- class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
- >
- 浠庡乏渚ч�夋嫨濂藉弸鎴栫兢鑱�
- </div>
- </div>
-
- <!-- 涓氬姟澹冲棰勮鍗� / 鐣欒█ / 鎻愪氦鎸夐挳鐨勪綅缃� -->
- <div
- v-if="$slots.footer"
- class="flex-shrink-0 border-t border-t-solid border-[var(--ant-color-border-secondary)]"
- >
- <slot name="footer"></slot>
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 妯悜婊氬姩鏉″仛绐勪竴鐐归伩鍏嶅崰瑙嗚锛涜蛋 ::-webkit-scrollbar 娴忚鍣ㄤ吉鍏冪礌 */
-.im-conversation-picker__recent::-webkit-scrollbar {
- height: 4px;
-}
-.im-conversation-picker__recent::-webkit-scrollbar-thumb {
- background-color: var(--ant-color-border);
- border-radius: 2px;
-}
-</style>
diff --git a/src/views/im/home/components/picker/friend-picker-panel.vue b/src/views/im/home/components/picker/friend-picker-panel.vue
deleted file mode 100644
index 084e3c7..0000000
--- a/src/views/im/home/components/picker/friend-picker-panel.vue
+++ /dev/null
@@ -1,245 +0,0 @@
-<script lang="ts" setup>
-import type { FriendLite } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Input, message } from 'ant-design-vue'
-
-import { PagedScroller } from '..'
-import { useFriendBuckets } from '../../composables/useFriendBuckets'
-import { useSelectedItems } from '../../composables/useSelectedItems'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImFriendPickerPanel' })
-
-const props = withDefaults(
- defineProps<{
- /** 绂佺敤 id锛氬垪琛ㄩ噷灞曠ず缃伆銆佷笉鍙嬀閫夈�佷笉璁″叆宸查�夋暟锛堝吀鍨嬶細閭�璇峰叆缇ゆ椂宸插湪缇ゆ垚鍛橈級 */
- disabledIds?: number[]
- /** 鍏ㄩ噺濂藉弸鍒楄〃 */
- friends: FriendLite[]
- /** 闅愯棌 id锛氫笉灞曠ず锛坔ide > locked > disabled锛� */
- hideIds?: number[]
- /** 閿佸畾 id锛氶粯璁ゅ嬀閫夈�佷笉鍙彇娑堛�佽鍏ュ凡閫夋暟锛堝吀鍨嬶細绉佽亰渚� +寤虹兢閿佸畾瀵规柟锛� */
- lockedIds?: number[]
- /** 宸查�夋暟涓婇檺锛涗笉浼犳垨 <=0 鏃朵笉闄� */
- maxSize?: number
- /** 宸查�夊ソ鍙� id锛坴-model锛夛紱鎸夋暟缁勯『搴忓嵆涓虹偣鍑婚『搴� */
- selectedIds: number[]
- }>(),
- {
- lockedIds: () => [],
- disabledIds: () => [],
- hideIds: () => [],
- maxSize: 0
- }
-)
-
-const emit = defineEmits<{
- 'update:selectedIds': [value: number[]]
-}>()
-
-const keyword = ref('')
-
-/** id 鈫� friend 鏄犲皠锛屽凡閫夊弽鏌� / 涓夋�佸垽瀹氬叡鐢紝閬垮厤姣忔 O(N) 鎵� */
-const byId = computed(() => {
- const map = new Map<number, FriendLite>()
- for (const friend of props.friends) {
- map.set(friend.id, friend)
- }
- return map
-})
-
-/** 涓夋�� id 闆嗗悎锛氭瘡娆¤繃婊ゅ鐢� */
-const hideSet = computed(() => new Set(props.hideIds))
-const lockedSet = computed(() => new Set(props.lockedIds))
-const disabledSet = computed(() => new Set(props.disabledIds))
-const selectedSet = computed(() => new Set(props.selectedIds))
-
-/** 鍊欓�夊ソ鍙嬶細鍓旈櫎 hideIds锛坔ide 浼樺厛绾ф渶楂橈級 */
-const candidates = computed(() =>
- props.friends.filter((friend) => !hideSet.value.has(friend.id))
-)
-
-/** 濮旀墭 useFriendBuckets锛氭悳绱㈣鍒欏鐢紝宸︿晶鍒楄〃鎸夋粴鍔ㄥ垎椤垫覆鏌� */
-const { filtered } = useFriendBuckets(candidates, keyword)
-
-/** 宸查�夋暟 + 宸查�夊ソ鍙嬪垪琛細涓夋�佷紭鍏堢骇 + 椤哄簭鎷兼帴鐢� useSelectedItems 缁熶竴鎵挎媴 */
-const { selectedCount, selectedItems: selectedFriends } = useSelectedItems<FriendLite>(
- () => props.selectedIds,
- () => props.lockedIds,
- () => props.disabledIds,
- () => props.hideIds,
- byId
-)
-
-/** 鏄惁琚攣瀹� */
-function isLocked(friend: FriendLite): boolean {
- return lockedSet.value.has(friend.id)
-}
-
-/** 鏄惁琚鐢細locked / hide 宸茶鍓嶇疆杩囨护锛屽墿涓嬬殑鎵嶇畻 disabled */
-function isDisabled(friend: FriendLite): boolean {
- return !lockedSet.value.has(friend.id) && disabledSet.value.has(friend.id)
-}
-
-/** 鏄惁閫変腑锛歭ocked 瑙嗕负姘歌繙閫変腑 */
-function isSelected(friend: FriendLite): boolean {
- return selectedSet.value.has(friend.id)
-}
-
-/** 鍦嗗舰鍕鹃�夋寚绀哄櫒鐨� class锛氶�変腑 / 閿佸畾璧扮豢搴曪紝绂佺敤鐏板簳锛屾湭閫夌┖蹇冨渾 */
-function getCheckClass(friend: FriendLite): string {
- if (isLocked(friend) || isSelected(friend)) {
- return 'bg-[#07c160] border border-solid border-[#07c160]'
- }
- if (isDisabled(friend)) {
- return 'bg-[var(--ant-color-fill)] border border-solid border-[var(--ant-color-border)]'
- }
- return 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
-}
-
-/** 鍒囨崲閫変腑鎬侊細locked / disabled 涓嶅搷搴旓紱鍙虫爮 脳 绉婚櫎 / 琛� click 閮借蛋杩欓噷 */
-function handleToggle(friend: FriendLite) {
- if (isLocked(friend) || isDisabled(friend)) {
- return
- }
- const next = [...props.selectedIds]
- const index = next.indexOf(friend.id)
- if (index === -1) {
- if (props.maxSize > 0 && selectedCount.value >= props.maxSize) {
- message.error(`鏈�澶氶�夋嫨 ${props.maxSize} 浣嶅ソ鍙媊)
- return
- }
- next.push(friend.id)
- } else {
- next.splice(index, 1)
- }
- emit('update:selectedIds', next)
-}
-</script>
-
-<template>
- <!--
- 濂藉弸閫夋嫨闈㈡澘锛氱敤浜庛�屾柊寤虹兢鑱� / 閭�璇峰ソ鍙� / 鎺ㄨ崘鏃跺垱寤鸿亰澶┿�嶇瓑濂藉弸閫夋嫨鍦烘櫙
- - 宸︼細鎼滅储 + 濂藉弸鍒楄〃锛堝渾褰㈠嬀閫夛級
- - 鍙筹細宸查�夋暟鏍囬 + 宸查�夊ソ鍙嬪垪琛紙鎸夌偣鍑婚『搴忥級
- - Panel 涓嶅甫 el-dialog 澹筹紱dialog 鐢变笟鍔″3鎸佹湁
- - 涓夋�佽涔夛細hide > locked > disabled锛堣瑙� contract锛�
- -->
- <div class="flex h-full">
- <!-- 宸︽爮 -->
- <div
- class="flex flex-col flex-1 min-w-0 border-r border-r-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- <!-- 鎼滅储妗� -->
- <div class="flex-shrink-0 px-3 py-2">
- <Input v-model:value="keyword" placeholder="鎼滅储濂藉弸" allow-clear>
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- </div>
-
- <div class="flex-1 min-h-0">
- <PagedScroller v-if="filtered.length > 0" :items="filtered" :page-size="30" item-key="id">
- <template #default="{ item }">
- <div
- :key="(item as FriendLite).id"
- class="flex gap-2.5 items-center px-3 py-2 cursor-pointer hover:bg-[var(--ant-color-fill)]"
- :class="{
- 'opacity-60 cursor-not-allowed hover:bg-transparent': isDisabled(item as FriendLite)
- }"
- @click="handleToggle(item as FriendLite)"
- >
- <!-- 鍦嗗舰鍕鹃�夋寚绀哄櫒锛氭湭閫夌伆鑹茬┖蹇冨渾锛岄�変腑瀹炲績寰俊缁� + 鐧藉鍕撅紱閿佸畾 / 绂佺敤璧扮伆搴� -->
- <span
- class="flex flex-shrink-0 justify-center items-center w-5 h-5 rounded-full transition-colors"
- :class="getCheckClass(item as FriendLite)"
- >
- <Icon
- v-if="isSelected(item as FriendLite) || isLocked(item as FriendLite)"
- icon="ant-design:check-outlined"
- :size="12"
- color="#fff"
- />
- </span>
- <UserAvatar
- :id="(item as FriendLite).id"
- :url="(item as FriendLite).avatar"
- :name="(item as FriendLite).nickname"
- :size="36"
- :clickable="false"
- />
- <!-- 琛屽唴鍚嶅瓧锛氬娉ㄤ紭鍏堬紝鍒楄〃閲屼笉閲嶅灞曠ず鏄电О -->
- <span
- class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- >
- {{ (item as FriendLite).displayName || (item as FriendLite).nickname }}
- </span>
- </div>
- </template>
- </PagedScroller>
- <div v-else class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]">
- {{ keyword ? '娌℃湁鍖归厤鐨勫ソ鍙�' : '鏆傛棤濂藉弸' }}
- </div>
- </div>
- </div>
-
- <!-- 鍙虫爮 -->
- <div class="flex flex-col flex-1 min-w-0">
- <!-- 鏍囬锛氬凡閫夋暟锛涢珮搴﹀榻愬乏渚� input default锛�32px锛夛紝淇濊瘉涓や晶绗竴椤硅捣鐐瑰悓姘村钩 -->
- <div
- class="flex-shrink-0 h-12 px-4 leading-[3rem] border-b border-b-solid text-13px text-[var(--ant-color-text-secondary)] border-[var(--ant-color-border-secondary)]"
- >
- 宸查�夋嫨 {{ selectedCount }} 涓ソ鍙�
- </div>
-
- <!-- 宸查�夐瑙堬細鎸� selectedIds + lockedIds 鏁扮粍椤哄簭锛堢偣鍑婚『搴忥級灞曠ず -->
- <div class="flex-1">
- <div
- v-for="friend in selectedFriends"
- :key="friend.id"
- class="flex gap-2.5 items-center px-4 py-2"
- >
- <UserAvatar
- :id="friend.id"
- :url="friend.avatar"
- :name="friend.nickname"
- :size="36"
- :clickable="false"
- />
- <span
- class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- >
- {{ friend.displayName || friend.nickname }}
- </span>
- <!-- 閿佸畾椤逛笉娓叉煋 脳锛岄伩鍏嶈浠ヤ负鍙Щ闄� -->
- <Icon
- v-if="!isLocked(friend)"
- icon="ant-design:close-outlined"
- :size="14"
- class="flex-shrink-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
- @click="handleToggle(friend)"
- />
- </div>
- <div
- v-if="selectedFriends.length === 0"
- class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
- >
- 璇蜂粠宸︿晶閫夋嫨濂藉弸
- </div>
- </div>
-
- <!-- 涓氬姟澹冲棰濆鍐呭鐨勪綅缃紱FriendPickerPanel 涓绘祦鍦烘櫙涓嶉渶瑕� footer -->
- <div
- v-if="$slots.footer"
- class="flex-shrink-0 border-t border-t-solid border-[var(--ant-color-border-secondary)]"
- >
- <slot name="footer"></slot>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/picker/group-member-picker-panel.vue b/src/views/im/home/components/picker/group-member-picker-panel.vue
deleted file mode 100644
index 43d6582..0000000
--- a/src/views/im/home/components/picker/group-member-picker-panel.vue
+++ /dev/null
@@ -1,242 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../group'
-
-import { computed, ref } from 'vue'
-
-import { CommonStatusEnum } from '../../../../../packages/constants/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Input, message } from 'ant-design-vue'
-
-import { PagedScroller } from '..'
-import { useSelectedItems } from '../../composables/useSelectedItems'
-import { GroupMemberGrid, GroupMemberItem } from '../group'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImGroupMemberPickerPanel' })
-
-const props = withDefaults(
- defineProps<{
- /** 绂佺敤 userId锛氬垪琛ㄩ噷灞曠ず缃伆銆佷笉鍙嬀閫夈�佷笉璁″叆宸查�夋暟 */
- disabledIds?: number[]
- /** 闅愯棌 userId锛氫笉灞曠ず锛坔ide > locked > disabled锛� */
- hideIds?: number[]
- /** 閿佸畾 userId锛氶粯璁ゅ嬀閫夈�佷笉鍙彇娑堛�佽鍏ュ凡閫夋暟 */
- lockedIds?: number[]
- /** 宸查�夋暟涓婇檺锛涗笉浼犳垨 <=0 鏃朵笉闄� */
- maxSize?: number
- /** 缇ゆ垚鍛樺垪琛� */
- members: GroupMemberLite[]
- /** 宸查�夊尯灞曠ず褰㈡�侊細榛樿 list 瀵归綈寰俊鏂拌瑙� */
- selectedDisplay?: 'grid' | 'list'
- /** 宸查�� userId锛坴-model锛夛紱鎸夋暟缁勯『搴忓嵆涓虹偣鍑婚『搴� */
- selectedIds: number[]
- }>(),
- {
- lockedIds: () => [],
- disabledIds: () => [],
- hideIds: () => [],
- maxSize: 0,
- selectedDisplay: 'list'
- }
-)
-
-const emit = defineEmits<{
- 'update:selectedIds': [value: number[]]
-}>()
-
-const keyword = ref('')
-
-/** userId 鈫� member 鏄犲皠锛屽凡閫夊弽鏌� / 涓夋�佸垽瀹氬叡鐢� */
-const byId = computed(() => {
- const map = new Map<number, GroupMemberLite>()
- for (const member of props.members) {
- map.set(member.userId, member)
- }
- return map
-})
-
-const hideSet = computed(() => new Set(props.hideIds))
-const lockedSet = computed(() => new Set(props.lockedIds))
-const disabledSet = computed(() => new Set(props.disabledIds))
-const selectedSet = computed(() => new Set(props.selectedIds))
-
-/** 褰撳墠灞曠ず鐨勬垚鍛橈細鍓旈櫎 hideIds銆佸墧闄ゅ凡閫�缇わ紙DISABLE锛夈�佹寜鍏抽敭瀛楀ぇ灏忓啓鏃犲叧杩囨护 */
-const shownMembers = computed(() => {
- const keywordLower = keyword.value.trim().toLowerCase()
- return props.members.filter(
- (member) =>
- !hideSet.value.has(member.userId) &&
- member.status !== CommonStatusEnum.DISABLE &&
- (!keywordLower || member.showName.toLowerCase().includes(keywordLower))
- )
-})
-
-/** 宸查�夋暟 + 宸查�夋垚鍛樺垪琛細涓夋�佷紭鍏堢骇 + 椤哄簭鎷兼帴鐢� useSelectedItems 缁熶竴鎵挎媴 */
-const { selectedCount, selectedItems: selectedMembers } = useSelectedItems<GroupMemberLite>(
- () => props.selectedIds,
- () => props.lockedIds,
- () => props.disabledIds,
- () => props.hideIds,
- byId
-)
-
-/** 鏄惁琚攣瀹� */
-function isLocked(member: GroupMemberLite): boolean {
- return lockedSet.value.has(member.userId)
-}
-
-/** 鏄惁琚鐢細locked / hide 宸茶鍓嶇疆杩囨护锛屽墿涓嬬殑鎵嶇畻 disabled */
-function isDisabled(member: GroupMemberLite): boolean {
- return !lockedSet.value.has(member.userId) && disabledSet.value.has(member.userId)
-}
-
-/** 鏄惁閫変腑锛歭ocked 瑙嗕负姘歌繙閫変腑 */
-function isSelected(member: GroupMemberLite): boolean {
- return selectedSet.value.has(member.userId)
-}
-
-/** 鍦嗗舰鍕鹃�夋寚绀哄櫒鐨� class */
-function getCheckClass(member: GroupMemberLite): string {
- if (isLocked(member) || isSelected(member)) {
- return 'bg-[#07c160] border border-solid border-[#07c160]'
- }
- if (isDisabled(member)) {
- return 'bg-[var(--ant-color-fill)] border border-solid border-[var(--ant-color-border)]'
- }
- return 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
-}
-
-/** 鍒囨崲閫変腑鎬侊細locked / disabled 涓嶅搷搴旓紱鍙虫爮 脳 / 琛� click 閮借蛋杩欓噷 */
-function handleToggle(member: GroupMemberLite) {
- if (isLocked(member) || isDisabled(member)) {
- return
- }
- const next = [...props.selectedIds]
- const index = next.indexOf(member.userId)
- if (index === -1) {
- if (props.maxSize > 0 && selectedCount.value >= props.maxSize) {
- message.error(`鏈�澶氶�夋嫨 ${props.maxSize} 浣嶆垚鍛榒)
- return
- }
- next.push(member.userId)
- } else {
- next.splice(index, 1)
- }
- emit('update:selectedIds', next)
-}
-</script>
-
-<template>
- <!--
- 缇ゆ垚鍛橀�夋嫨闈㈡澘锛氱敤浜庛�岀Щ闄ゆ垚鍛� / 璁剧疆绠$悊鍛� / 杞缇や富 / 绂佽█鎴愬憳銆嶇瓑鍦烘櫙
- - 宸︼細鎼滅储 + 缇ゆ垚鍛樺垪琛紙鍦嗗舰鍕鹃�夛級
- - 鍙筹細宸查�夋暟鏍囬 + 宸查�夋垚鍛橈紙list / grid 涓ょ褰㈡�佸彲閰嶏紝榛樿 list 瀵归綈寰俊鏂拌瑙夛級
- - Panel 涓嶅甫 el-dialog 澹筹紱dialog 鐢变笟鍔″3鎸佹湁
- - 涓夋�佽涔夛細hide > locked > disabled
- -->
- <div class="flex h-full">
- <!-- 宸︽爮 -->
- <div
- class="flex flex-col flex-1 min-w-0 border-r border-r-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- <!-- 鎼滅储妗� -->
- <div class="flex-shrink-0 px-3 py-2">
- <Input v-model:value="keyword" placeholder="鎼滅储鎴愬憳" allow-clear>
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- </div>
-
- <div class="flex-1 min-h-0">
- <PagedScroller :items="shownMembers" :page-size="30" item-key="userId">
- <template #default="{ item }">
- <GroupMemberItem
- :member="item as GroupMemberLite"
- :height="46"
- @click="handleToggle(item as GroupMemberLite)"
- >
- <span
- class="flex flex-shrink-0 justify-center items-center w-5 h-5 rounded-full transition-colors"
- :class="getCheckClass(item as GroupMemberLite)"
- >
- <Icon
- v-if="isSelected(item as GroupMemberLite) || isLocked(item as GroupMemberLite)"
- icon="ant-design:check-outlined"
- :size="12"
- color="#fff"
- />
- </span>
- </GroupMemberItem>
- </template>
- </PagedScroller>
- </div>
- </div>
-
- <!-- 鍙虫爮 -->
- <div class="flex flex-col flex-1 min-w-0">
- <!-- 鏍囬锛氬凡閫夋暟锛涢珮搴﹀榻愬乏渚� input default锛�32px锛� -->
- <div
- class="flex-shrink-0 h-12 px-4 leading-[3rem] border-b border-b-solid text-13px text-[var(--ant-color-text-secondary)] border-[var(--ant-color-border-secondary)]"
- >
- 宸查�夋嫨 {{ selectedCount }} 浣嶆垚鍛�
- </div>
-
- <div class="flex-1">
- <!-- list 褰㈡�侊細绾靛悜琛岋紝姣忚甯� 脳 绉婚櫎锛坙ocked 涓嶆覆鏌� 脳锛� -->
- <template v-if="selectedDisplay === 'list'">
- <div
- v-for="member in selectedMembers"
- :key="member.userId"
- class="flex gap-2.5 items-center px-4 py-2"
- >
- <UserAvatar
- :id="member.userId"
- :url="member.avatar"
- :name="member.nickname"
- :size="36"
- :clickable="false"
- />
- <span
- class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
- >
- {{ member.showName }}
- </span>
- <Icon
- v-if="!isLocked(member)"
- icon="ant-design:close-outlined"
- :size="14"
- class="flex-shrink-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
- @click="handleToggle(member)"
- />
- </div>
- </template>
-
- <!-- grid 褰㈡�侊細瀹牸棰勮锛涢潪 locked 鎴愬憳鍙充笂瑙掑彔鍔� 脳 绉婚櫎锛坙ocked 涓嶆覆鏌擄級 -->
- <div v-else class="flex flex-wrap p-2.5">
- <GroupMemberGrid
- v-for="member in selectedMembers"
- :key="member.userId"
- :member="member"
- >
- <Icon
- v-if="!isLocked(member)"
- icon="ant-design:close-circle-filled"
- :size="16"
- class="absolute top-0 right-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
- @click="handleToggle(member)"
- />
- </GroupMemberGrid>
- </div>
-
- <div
- v-if="selectedMembers.length === 0"
- class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
- >
- 璇蜂粠宸︿晶閫夋嫨鎴愬憳
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/picker/index.ts b/src/views/im/home/components/picker/index.ts
deleted file mode 100644
index 7a534a8..0000000
--- a/src/views/im/home/components/picker/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as ConversationPickerPanel } from './conversation-picker-panel.vue';
-export { default as FriendPickerPanel } from './friend-picker-panel.vue';
-export { default as GroupMemberPickerPanel } from './group-member-picker-panel.vue';
diff --git a/src/views/im/home/components/picker/picker-dialog.scss b/src/views/im/home/components/picker/picker-dialog.scss
deleted file mode 100644
index 629b856..0000000
--- a/src/views/im/home/components/picker/picker-dialog.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-// IM 閫夋嫨绫诲脊绐楃殑鍏叡鏍峰紡 mixin
-// 姣忎釜涓氬姟澹冲湪鑷繁鐨� <style scoped lang="scss"> 鍐� @use + @include 鍗冲彲锛岄伩鍏嶅叏灞�姹℃煋
-@mixin styles {
- :deep(.el-dialog__body) {
- padding: 0;
- border-top: 1px solid var(--ant-color-border-secondary);
- }
-
- :deep(.el-dialog__header) {
- margin-right: 0;
- padding-bottom: 16px;
- }
-}
diff --git a/src/views/im/home/components/resizable-aside.vue b/src/views/im/home/components/resizable-aside.vue
deleted file mode 100644
index e2785c1..0000000
--- a/src/views/im/home/components/resizable-aside.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-<script lang="ts" setup>
-import { onBeforeUnmount, onMounted, ref } from 'vue'
-
-defineOptions({ name: 'ImResizableAside' })
-
-const props = withDefaults(
- defineProps<{
- defaultWidth?: number // 榛樿瀹藉害
- maxWidth?: number // 鏈�澶у搴�
- minWidth?: number // 鏈�灏忓搴�
- storageKey: string // localStorage 瀛樺偍 key锛屽繀濉紱璋冪敤鏂逛紶 StorageKeys.localStorage.asideWidth
- }>(),
- {
- defaultWidth: 260,
- minWidth: 200,
- maxWidth: 500
- }
-)
-
-const asideWidth = ref<number>(props.defaultWidth)
-const isResizing = ref(false)
-let startX = 0
-let startWidth = 0
-
-onMounted(() => {
- const saved = localStorage.getItem(props.storageKey)
- if (saved) {
- const w = Number.parseInt(saved, 10)
- if (!Number.isNaN(w)) {
- asideWidth.value = clamp(w)
- }
- }
- document.addEventListener('mousemove', handleResize)
- document.addEventListener('mouseup', stopResize)
-})
-
-onBeforeUnmount(() => {
- // 鎷栨嫿涓嵏杞斤細澶嶇敤 stopResize 澶嶄綅 body cursor/userSelect 骞跺啓鍥炲綋鍓嶅搴︼紝閬垮厤鍏ㄥ眬鐘舵�佹硠婕�
- if (isResizing.value) {
- stopResize()
- }
- document.removeEventListener('mousemove', handleResize)
- document.removeEventListener('mouseup', stopResize)
-})
-
-/** 鎶婂搴﹀す鍒� [minWidth, maxWidth] 鍖洪棿锛屾仮澶� / 鎷栨嫿璺緞閮借蛋瀹冨厹搴� */
-function clamp(w: number) {
- return Math.max(props.minWidth, Math.min(props.maxWidth, w))
-}
-
-/** 鎸変笅鎷栨嫿鎵嬫焺锛氳褰曡捣濮嬩綅缃� + 閿佸畾 body cursor/userSelect锛岄伩鍏嶆嫋鎷戒腑璇�夋枃鏈� */
-function startResize(e: MouseEvent) {
- isResizing.value = true
- startX = e.clientX
- startWidth = asideWidth.value
- document.body.style.cursor = 'col-resize'
- document.body.style.userSelect = 'none'
- e.preventDefault()
-}
-
-/** 鎷栨嫿涓細鎸夐紶鏍囦綅绉昏绠楁柊瀹藉害骞� clamp 鍒板厑璁稿尯闂� */
-function handleResize(e: MouseEvent) {
- if (!isResizing.value) {
- return
- }
- const deltaX = e.clientX - startX
- asideWidth.value = clamp(startWidth + deltaX)
-}
-
-/** 鏉惧紑榧犳爣锛氳В閿� body 鍏ㄥ眬鎬佸苟鎶婂綋鍓嶅搴﹀啓鍏� localStorage 鎸佷箙鍖� */
-function stopResize() {
- if (!isResizing.value) {
- return
- }
- isResizing.value = false
- document.body.style.cursor = ''
- document.body.style.userSelect = ''
- localStorage.setItem(props.storageKey, String(asideWidth.value))
-}
-</script>
-
-<template>
- <!--
- 鍙嫋鎷藉搴︾殑宸︿晶 Aside
- - 浣跨敤 localStorage 璁颁綇鐢ㄦ埛涓婃璋冩暣鐨勫搴︼紙storageKey 蹇呭~锛�
- - 鎷栨嫿鍖哄湪鍙宠竟缂橈紝榧犳爣鍙� col-resize
- -->
- <aside
- class="relative flex flex-col shrink-0 bg-[var(--ant-color-fill-secondary)] border-r border-r-solid border-[var(--im-border-color-lighter)] shadow-[2px_0_8px_rgba(0,0,0,0.05)]"
- :style="{ width: `${asideWidth }px` }"
- >
- <slot></slot>
- <div
- class="im-resizable-aside__handle absolute top-0 right--0.75 z-10 flex items-center justify-center w-1.5 h-full cursor-col-resize transition-colors"
- :class="{ 'is-resizing': isResizing }"
- title="鎷栨嫿璋冩暣瀹藉害"
- @mousedown="startResize"
- >
- <div class="im-resizable-aside__line w-0.5 h-full rounded-0.5 bg-transparent transition-all"></div>
- </div>
- </aside>
-</template>
-
-<style scoped>
-/* hover / 鎷栨嫿涓� 鎶婂唴閮� line 鍔犵矖鍙樻繁锛屾彁绀烘墜鏌勫彲鎷栵紱鐘舵�佸湪鐖� handle 涓� 鈫� 閫氳繃鍚庝唬閫夋嫨鍣ㄨ仈鍔ㄥ瓙 line */
-.im-resizable-aside__handle:hover .im-resizable-aside__line,
-.im-resizable-aside__handle.is-resizing .im-resizable-aside__line {
- width: 3px;
- background-color: var(--im-resize-line-color);
-}
-</style>
diff --git a/src/views/im/home/components/rtc/index.ts b/src/views/im/home/components/rtc/index.ts
deleted file mode 100644
index fafdd7a..0000000
--- a/src/views/im/home/components/rtc/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export { default as RtcCallContainer } from './rtc-call-container.vue';
-export { default as RtcCallIncoming } from './rtc-call-incoming.vue';
-export { default as RtcCallInviting } from './rtc-call-inviting.vue';
-export { default as RtcCallMemberPickerDialog } from './rtc-call-member-picker-dialog.vue';
-export { default as RtcCallParticipantTile } from './rtc-call-participant-tile.vue';
-export type { CallParticipantVM } from './rtc-call-participant-tile.vue';
-export { default as RtcCallRunning } from './rtc-call-running.vue';
-export { default as RtcGroupCallBanner } from './rtc-group-call-banner.vue';
diff --git a/src/views/im/home/components/rtc/rtc-call-container.vue b/src/views/im/home/components/rtc/rtc-call-container.vue
deleted file mode 100644
index 02be586..0000000
--- a/src/views/im/home/components/rtc/rtc-call-container.vue
+++ /dev/null
@@ -1,488 +0,0 @@
-<script lang="ts" setup>
-import type { CallParticipantVM } from './rtc-call-participant-tile.vue'
-
-import { computed, ref, watch } from 'vue'
-
-import { useIntervalFn } from '@vueuse/core'
-import { message } from 'ant-design-vue'
-import { Track } from 'livekit-client'
-
-import {
- acceptCall,
- cancelCall,
- inviteCall,
- leaveCall,
- noAnswerCallCheck,
- rejectCall
-} from '#/api/im/rtc'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS } from '#/views/im/utils/config'
-import {
- ImConversationType,
- ImRtcCallMediaType,
- ImRtcCallStage
-} from '#/views/im/utils/constants'
-import { getSenderAvatar, getSenderDisplayName } from '#/views/im/utils/user'
-
-import { useLiveKitRoom } from '../../composables/useLiveKitRoom'
-import { useRtcStore } from '../../store/rtcStore'
-import RtcCallIncoming from './rtc-call-incoming.vue'
-import RtcCallInviting from './rtc-call-inviting.vue'
-import RtcCallMemberPickerDialog from './rtc-call-member-picker-dialog.vue'
-import RtcCallRunning from './rtc-call-running.vue'
-
-defineOptions({ name: 'ImRtcCallContainer' })
-
-const rtcStore = useRtcStore()
-const lk = useLiveKitRoom()
-
-const memberPickerRef = ref<InstanceType<typeof RtcCallMemberPickerDialog>>()
-const connecting = ref(false)
-const accepting = ref(false)
-const rejecting = ref(false)
-const cancelling = ref(false)
-const hangingUp = ref(false)
-
-// ==================== 瑙嗗浘妯″瀷 ====================
-
-/** 褰撳墠鏄惁瑙嗛閫氳瘽 */
-const isVideo = computed(() => {
- const t =
- rtcStore.call?.mediaType ||
- rtcStore.incomingPayload?.mediaType ||
- ImRtcCallMediaType.VOICE
- return t === ImRtcCallMediaType.VIDEO
-})
-
-/** 褰撳墠鏄惁缇ら�氳瘽锛涘喅瀹氭诞鍔ㄧ獥澶у皬 */
-const isGroup = computed(
- () =>
- (rtcStore.call?.conversationType ??
- rtcStore.incomingPayload?.conversationType) === ImConversationType.GROUP
-)
-
-/** 鍒濆鎽勫儚澶存槸鍚︽墦寮�锛涚兢閫氳瘽榛樿鍏ㄩ儴鍏抽棴锛岃繘鍏ュ悗鐢ㄦ埛涓诲姩寮� */
-const initialCamera = computed(() => {
- if (rtcStore.call?.conversationType === ImConversationType.GROUP) {
- return false
- }
- return isVideo.value
-})
-
-/** 鏈瑙嗛娴侊紱浼樺厛 ScreenShare锛堝睆鍏辨椂涔熼摵搴曪級锛屾棤鍒� Camera锛涙樉寮忚闃� screenShareEnabled / cameraEnabled 瑙﹀彂閲嶇畻 */
-const localStream = computed<MediaStream | null>(() => {
- // 瑙︽懜鍝嶅簲寮忎緷璧栵紝纭繚鍒囧睆鍏变韩 / 鎽勫儚澶村悗 computed 閲嶆柊姹傚�硷紙pickStream 鍐呴儴鐢ㄦ櫘閫� Map 缂撳瓨锛岃嚜韬笉鍝嶅簲锛�
- void lk.screenShareEnabled.value
- void lk.cameraEnabled.value
- const lp = lk.localParticipant.value
- if (!lp) {
- return null
- }
- return lk.pickStream(lp, Track.Source.ScreenShare) || lk.pickStream(lp, Track.Source.Camera)
-})
-
-/** 杩滅瑙嗛娴侊紙浠� 1v1 鐢級锛涗紭鍏� ScreenShare锛屾棤鍒欏彇 Camera */
-const remoteVideoStream = computed<MediaStream | null>(() => {
- if (isGroup.value) {
- return null
- }
- for (const rp of lk.remoteParticipants.value) {
- const screen = lk.pickStream(rp, Track.Source.ScreenShare)
- if (screen) {
- return screen
- }
- const camera = lk.pickStream(rp, Track.Source.Camera)
- if (camera) {
- return camera
- }
- }
- return null
-})
-
-/** 杩滅闊抽娴侊紙浠� 1v1 鐢級 */
-const remoteAudioStream = computed<MediaStream | null>(() => {
- if (isGroup.value) {
- return null
- }
- for (const rp of lk.remoteParticipants.value) {
- const stream = lk.pickStream(rp, Track.Source.Microphone)
- if (stream) {
- return stream
- }
- }
- return null
-})
-
-/** 缇ら�氳瘽缃戞牸鐢細鑷繁 + 杩滅鍦ㄦ埧 + 寰呭姞鍏ユ垚鍛橈紱鏄电О / 澶村儚璧� user.ts helper 鑷姩澶勭悊 self / 缇ゆ垚鍛� / 濂藉弸 / 鍏滃簳 */
-const participants = computed<CallParticipantVM[]>(() => {
- const call = rtcStore.call
- if (!call) {
- return []
- }
- const conversationType = call.conversationType
- const targetId = call.groupId ?? 0
- const myId = getCurrentUserId()
- const result: CallParticipantVM[] = [{
- userId: myId,
- nickname: getSenderDisplayName(myId, conversationType, targetId),
- avatar: getSenderAvatar(myId, conversationType, targetId) || undefined,
- isLocal: true,
- videoStream: localStream.value
- }]
-
- // 宸插姞鍏ョ殑杩滅锛氬疄闄呮帹娴侊紱灞忓箷鍏变韩鍦ㄧ綉鏍奸噷鐙崰璇ユ垚鍛樼殑鏍煎瓙锛屾棤鍒欓檷绾� Camera
- const joined = new Set<number>()
- for (const rp of lk.remoteParticipants.value) {
- const userId = Number(rp.identity)
- if (Number.isNaN(userId)) {
- continue
- }
- joined.add(userId)
- result.push({
- userId,
- nickname: getSenderDisplayName(userId, conversationType, targetId),
- avatar: getSenderAvatar(userId, conversationType, targetId) || undefined,
- isLocal: false,
- videoStream:
- lk.pickStream(rp, Track.Source.ScreenShare) || lk.pickStream(rp, Track.Source.Camera),
- audioStream: lk.pickStream(rp, Track.Source.Microphone)
- })
- }
-
- // 缇ら�氳瘽锛氭湭鍔犲叆鐨勮閭�璇蜂汉浣滀负 pending 鍗犱綅锛涘凡閫�鍑� / 宸叉嫆缁濈殑浜轰笉娓叉煋
- if (conversationType === ImConversationType.GROUP) {
- const inviteeIds = call.inviteeIds || []
- for (const userId of inviteeIds) {
- if (userId === myId || joined.has(userId) || rtcStore.isUserLeft(userId)) {
- continue
- }
- result.push({
- userId,
- nickname: getSenderDisplayName(userId, ImConversationType.GROUP, targetId),
- avatar: getSenderAvatar(userId, ImConversationType.GROUP, targetId) || undefined,
- isLocal: false,
- pending: true
- })
- }
- }
- return result
-})
-
-// ==================== LiveKit 杩炴帴 ====================
-
-/** 杩炲叆 LiveKit 鎴块棿骞舵敞鍐岀寮�鍥炶皟锛汭NVITING 涓诲彨棰勮繛鍜岃鍙� accept 鍚庤繛鍏ュ叡鐢� */
-async function connectLiveKit(livekitUrl: string, token: string) {
- // 骞傜瓑锛歭k.connect 鍐呴儴杩涘叆鍚庡氨鎶� room.value 璧嬪�硷紱闈炵┖琛ㄧず宸茬粡鍦ㄨ繛鎺ユ垨宸茶繛鎺ワ紱stage 澶氭鍒囨崲鏃堕噸澶嶈Е鍙戜篃璺宠繃
- if (lk.room.value || connecting.value) {
- return
- }
- connecting.value = true
- try {
- // 鍏堟敞鍐屽洖璋冿紝鍐� connect锛涗俊浠ゆ彙鎵嬭繃绋嬩細鍗虫椂鎺ㄩ�佸凡鍦ㄦ埧鍙備笌鑰咃紝涓氬姟 handler 蹇呴』鍏堝氨缁�
- lk.onDisconnected(() => handlePeerDisconnected())
- lk.onParticipantConnected(maybeEnterRunning)
- lk.onParticipantDisconnected((userId) => rtcStore.markUserLeft(userId))
- await lk.connect(livekitUrl, token, { audio: true, video: initialCamera.value })
- // 鍏滃簳锛歝onnect 鏈熼棿鑻ュ凡鏈夎繙绔湪鎴匡紝浜嬩欢鍙兘鍦� handler 娉ㄥ唽鍓嶅凡瑙﹀彂锛屼富鍔ㄥ垏鍒� RUNNING
- if (lk.remoteParticipants.value.length > 0) {
- maybeEnterRunning()
- }
- } finally {
- connecting.value = false
- }
-}
-
-/** 涓诲彨绔細浠� INVITING 鍒囧埌 RUNNING锛涘叾瀹冮樁娈典笉澶勭悊 */
-function maybeEnterRunning() {
- if (rtcStore.stage === ImRtcCallStage.INVITING && rtcStore.call) {
- rtcStore.enterRunning(rtcStore.call)
- }
-}
-
-watch(
- () => rtcStore.stage,
- async (stage) => {
- if (
- stage === ImRtcCallStage.INVITING &&
- rtcStore.call?.token &&
- rtcStore.call?.livekitUrl
- ) {
- try {
- await connectLiveKit(rtcStore.call.livekitUrl, rtcStore.call.token)
- } catch (error) {
- console.error('[Call] connect 澶辫触', { room: rtcStore.call?.room }, error)
- message.error('閫氳瘽杩炴帴澶辫触')
- await handleCancel()
- }
- }
- if (stage === ImRtcCallStage.IDLE) {
- await lk.disconnect()
- }
- }
-)
-
-/** 琚彨绔� accept 鍚庝細鎷垮埌 token锛涜繖閲岀洃鍚� stage + token 鍙樺寲瑙﹀彂杩炴帴 */
-watch(
- () => [rtcStore.stage, rtcStore.call?.token],
- async ([stage, token], [prevStage]) => {
- if (
- stage === ImRtcCallStage.RUNNING &&
- prevStage !== ImRtcCallStage.RUNNING &&
- token &&
- !lk.isConnected.value &&
- rtcStore.call?.livekitUrl
- ) {
- try {
- await connectLiveKit(rtcStore.call.livekitUrl, token as string)
- } catch (error) {
- console.error('[Call] accept connect 澶辫触', { room: rtcStore.call?.room }, error)
- message.error('閫氳瘽杩炴帴澶辫触')
- // 鍚庣 accept 宸插啓 JOINED锛涘墠绔繛鎺ュけ璐ラ渶璋� leave 鍥炴粴锛岄伩鍏嶅悗绔褰曟畫鐣欏繖绾�
- if (rtcStore.call?.room) {
- leaveCall(rtcStore.call.room).catch(() => undefined)
- }
- rtcStore.reset()
- }
- }
- }
-)
-
-// ==================== 閫氳瘽鐢熷懡鍛ㄦ湡 ====================
-
-/** 涓诲彨鍙栨秷閭�璇� */
-async function handleCancel() {
- if (cancelling.value) {
- return
- }
- cancelling.value = true
- const room = rtcStore.call?.room
- try {
- if (room) {
- await cancelCall(room)
- }
- await lk.disconnect()
- rtcStore.reset()
- } finally {
- cancelling.value = false
- }
-}
-
-/** 琚彨鎷掔粷鏉ョ數 */
-async function handleReject() {
- if (rejecting.value) {
- return
- }
- rejecting.value = true
- const payload = rtcStore.incomingPayload
- try {
- if (payload?.room) {
- await rejectCall(payload.room)
- // 鏈鍏堣浠庤兌鍥婃潯绉婚櫎鑷繁锛屽厤绛夊悗绔� RTC_CALL(REJECTED) 鎺ㄥ洖锛涚鑱婂満鏅� store 鍐呴儴 no-op
- rtcStore.applyParticipantRejected({
- room: payload.room,
- conversationType: payload.conversationType,
- groupId: payload.groupId,
- operatorUserId: getCurrentUserId()
- })
- }
- rtcStore.reset()
- } finally {
- rejecting.value = false
- }
-}
-
-/** 琚彨鎺ュ惉鏉ョ數 */
-async function handleAccept() {
- if (accepting.value) {
- return
- }
- const payload = rtcStore.incomingPayload
- if (!payload) return
- accepting.value = true
- try {
- const data = await acceptCall(payload.room)
- rtcStore.enterRunning(data)
- } finally {
- accepting.value = false
- }
-}
-
-/** 閫氳瘽涓寕鏂� */
-async function handleHangup() {
- if (hangingUp.value) {
- return
- }
- hangingUp.value = true
- const call = rtcStore.call
- try {
- if (call?.room) {
- await leaveCall(call.room)
- // 鏈鍏堣浠庤兌鍥婃潯绉婚櫎鑷繁锛屽厤绛夊悗绔� RTC_PARTICIPANT_DISCONNECTED 鎺ㄥ洖锛涚鑱婂満鏅� store 鍐呴儴 no-op锛屾暣閫氳瘽鐢� END 鍏虫帀
- rtcStore.applyParticipantDisconnected({
- room: call.room,
- userId: getCurrentUserId(),
- conversationType: call.conversationType,
- groupId: call.groupId
- })
- }
- await lk.disconnect()
- rtcStore.reset()
- } finally {
- hangingUp.value = false
- }
-}
-
-/** LiveKit Room 寮傚父鏂紑锛涘瑙佷簬缃戠粶涓柇 */
-function handlePeerDisconnected() {
- if (!rtcStore.isActive) {
- return
- }
- const room = rtcStore.call?.room
- // 缁� RTC_CALL_END WebSocket 鎺ㄩ�佷竴涓皬绐楀彛锛涚鑱婅秴鏃� / 涓诲姩鎸傛柇绛夊満鏅笅锛屽悗绔� endSession 浼氬厛鎺� RTC_CALL_END锛�
- // 璁╁墠绔寜涓氬姟璇箟锛�"瀵规柟鏈帴鍚�" / "宸插彇娑�" 绛夛級reset锛岄伩鍏嶉敊鎶婁笟鍔℃柇寮� toast 鎴愩�岄�氳瘽宸叉柇寮�銆�
- setTimeout(() => {
- if (!rtcStore.isActive) {
- return
- }
- // 涓婃姤绂诲紑鎴块棿
- if (room) {
- leaveCall(room).catch(() => undefined)
- }
- // 娓呯悊鏈湴閫氳瘽鐘舵��
- message.warning('閫氳瘽宸叉柇寮�')
- rtcStore.reset()
- }, 100)
-}
-
-// ==================== 鎸搩瓒呮椂鍏滃簳 ====================
-
-/** 閫氳瘽瀛樻椿鏈熼棿锛圛NVITING / INCOMING / RUNNING锛夊懆鏈熸�цЕ鍙戝悗绔壂璇� room 鐨勮秴鏃� INVITING锛涗繚鎸� timer 鏄负浜� inviteCall 杩藉姞鏂颁汉鍚庝篃鑳借鐩栵紱闃堝�肩敱鍚庣閰嶇疆鍐冲畾锛屽墠绔彧璐熻矗 trigger */
-const { resume: resumeNoAnswerTimer, pause: pauseNoAnswerTimer } = useIntervalFn(
- triggerNoAnswerCallCheck, RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS, { immediate: false }
-)
-watch(
- () => rtcStore.isActive,
- (active) => (active ? resumeNoAnswerTimer() : pauseNoAnswerTimer()),
- { immediate: true }
-)
-
-/** 鏈湴浠嶆湁 pending 鎵嶈皟锛汭NVITING / RUNNING 鍙� call銆両NCOMING 鍙� incomingPayload锛涙帴鍙i潤榛橀敊璇� fire-and-forget */
-function triggerNoAnswerCallCheck() {
- const source = rtcStore.call ?? rtcStore.incomingPayload
- if (!source?.room || !source.inviteeIds?.length) {
- return
- }
- noAnswerCallCheck(source.room).catch(() => undefined)
-}
-
-// ==================== 璁惧鎺у埗 ====================
-
-async function toggleMic() {
- await lk.setMicEnabled(!lk.micEnabled.value)
-}
-async function toggleCamera() {
- await lk.setCameraEnabled(!lk.cameraEnabled.value)
-}
-function toggleSpeaker() {
- lk.setSpeakerEnabled(!lk.speakerEnabled.value)
-}
-
-/** 鍒囧睆骞曞叡浜紱娴忚鍣ㄥ脊鍘熺敓銆岄�夋嫨鍏变韩鍐呭銆嶅璇濇锛岀敤鎴峰彇娑堟椂浼氭姏閿欙紝UI 涓嶅脊鎻愮ず */
-async function handleScreenShare() {
- const enabled = !lk.screenShareEnabled.value
- try {
- await lk.setScreenShareEnabled(enabled)
- } catch (error: any) {
- // 鐢ㄦ埛鍙栨秷閫夋嫨锛屼笉褰撲綔閿欒锛涘叾瀹冨紓甯告墦鏃ュ織
- if (error?.name !== 'NotAllowedError' && error?.message !== 'permission denied') {
- console.warn('[Call] screenShare 鍒囨崲澶辫触', { enabled }, error)
- }
- }
-}
-
-// ==================== 娣诲姞鎴愬憳 ====================
-
-/** 鎵撳紑銆屾坊鍔犳垚鍛樸�嶅脊绐楋紱鍗犱綅缇ら�氳瘽 + 鎺ラ�氫腑鐘舵�佹墠鍏佽 */
-function openAddMember() {
- const call = rtcStore.call
- if (!call?.groupId) {
- return
- }
- memberPickerRef.value?.open({
- groupId: call.groupId,
- mode: 'add',
- excludeUserIds: participants.value.map((p) => p.userId)
- })
-}
-
-/** picker 閫夊畬鎴愬憳锛涜蛋 invite 杩藉姞閭�璇锋帴鍙o紝鍚庣鎺� RTC_INVITE 缁欐柊鎴愬憳 */
-async function handleAddMemberSuccess(userIds: number[]) {
- const call = rtcStore.call
- if (!call?.room || userIds.length === 0) {
- return
- }
- await inviteCall({ room: call.room, inviteeIds: userIds })
- // 鍚屾鏈湴 inviteeIds锛岃鏂版垚鍛樼珛鍗充綔涓� pending 鍗犱綅鍑虹幇鍦ㄧ綉鏍奸噷
- rtcStore.appendInvitees(userIds)
- message.success('宸插彂閫侀個璇�')
-}
-</script>
-
-<template>
- <!-- 閫氳瘽闃舵瀵瑰簲寮圭獥锛汭NVITING / INCOMING / RUNNING 涓夐�変竴浜掓枼 -->
- <template v-if="rtcStore.isActive">
- <!-- 涓诲彨绔細绛夊緟瀵规柟鎺ュ惉 -->
- <RtcCallInviting
- v-if="rtcStore.stage === ImRtcCallStage.INVITING && rtcStore.call"
- :peer-nickname="rtcStore.peerNickname"
- :peer-avatar="rtcStore.peerAvatar"
- :is-group="isGroup"
- :is-video="isVideo"
- :mic-enabled="lk.micEnabled.value"
- :camera-enabled="lk.cameraEnabled.value"
- :speaker-enabled="lk.speakerEnabled.value"
- :local-stream="localStream"
- @cancel="handleCancel"
- @toggle-mic="toggleMic"
- @toggle-camera="toggleCamera"
- @toggle-speaker="toggleSpeaker"
- />
- <!-- 琚彨绔細鏉ョ數鍝嶉搩 -->
- <RtcCallIncoming
- v-else-if="rtcStore.stage === ImRtcCallStage.INCOMING"
- :payload="rtcStore.incomingPayload"
- :is-group="isGroup"
- :accepting="accepting"
- :rejecting="rejecting"
- @accept="handleAccept"
- @reject="handleReject"
- />
- <!-- 閫氳瘽杩涜涓細1v1 瑙嗛 / 璇煶 + 缇ら�氳瘽瀹牸 -->
- <RtcCallRunning
- v-else-if="rtcStore.stage === ImRtcCallStage.RUNNING && rtcStore.call"
- :is-group="isGroup"
- :is-video="isVideo"
- :mic-enabled="lk.micEnabled.value"
- :camera-enabled="lk.cameraEnabled.value"
- :speaker-enabled="lk.speakerEnabled.value"
- :screen-share-enabled="lk.screenShareEnabled.value"
- :reconnecting="lk.reconnecting.value"
- :started-at="rtcStore.startedAt"
- :participants="participants"
- :peer-nickname="rtcStore.peerNickname"
- :peer-avatar="rtcStore.peerAvatar"
- :local-stream="localStream"
- :remote-video-stream="remoteVideoStream"
- :remote-audio-stream="remoteAudioStream"
- :hanging-up="hangingUp"
- @hangup="handleHangup"
- @toggle-mic="toggleMic"
- @toggle-camera="toggleCamera"
- @toggle-speaker="toggleSpeaker"
- @toggle-screen-share="handleScreenShare"
- @add-member="openAddMember"
- />
- </template>
- <!-- 閫氳瘽涓�屾坊鍔犳垚鍛樸�嶉�変汉寮圭獥锛涙寕鍦� isActive 澶栵紝閬垮厤 stage 鍒囨崲鐬棿寮圭獥琚嵏杞� -->
- <RtcCallMemberPickerDialog ref="memberPickerRef" @success="handleAddMemberSuccess" />
-</template>
diff --git a/src/views/im/home/components/rtc/rtc-call-incoming.vue b/src/views/im/home/components/rtc/rtc-call-incoming.vue
deleted file mode 100644
index 2b6e862..0000000
--- a/src/views/im/home/components/rtc/rtc-call-incoming.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script lang="ts" setup>
-import type { ImRtcCallNotification } from '../../store/rtcStore'
-
-import { computed } from 'vue'
-
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { useGroupCallMembers } from '../../composables/useGroupCallMembers'
-import { UserAvatar } from '../user'
-
-const props = defineProps<{
- accepting?: boolean
- isGroup?: boolean
- payload: ImRtcCallNotification | null
- rejecting?: boolean
-}>()
-
-defineEmits<{ accept: []; reject: [] }>()
-
-/** 鏉ョ數鎻愮ず鏂囨锛涘尯鍒嗚闊� / 瑙嗛 */
-const tipText = computed(() => {
- if (!props.payload) return ''
- return `閭�璇蜂綘${getDictLabel(DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE, props.payload.mediaType)}閫氳瘽`
-})
-
-/** 鎺ュ惉鎸夐挳绂佺敤鎬� */
-const acceptDisabled = computed(() => !!props.accepting || !!props.rejecting)
-
-/** 鎷掔粷鎸夐挳绂佺敤鎬� */
-const rejectDisabled = computed(() => !!props.rejecting || !!props.accepting)
-
-// 缇ら�氳瘽鎴愬憳锛涚紦瀛樹负绌烘椂鐢� INVITE 杞借嵎閲岀殑涓诲彨鍏滃簳锛岄伩鍏嶇┖鐧�
-const callMembers = useGroupCallMembers(
- computed(() => (props.isGroup ? props.payload?.groupId : undefined)),
- computed(() => props.payload?.inviterUserId)
-)
-</script>
-
-<template>
- <!--
- 琚彨绔潵鐢靛皬鏉★紱鍙傝�冨井淇★紝鍙充笂瑙掑浐瀹氭偓娴紱
- 缇よ亰锛氬乏澶村儚 + 涓紙閭�璇蜂汉 + 鏂囨涓�琛� / 閫氳瘽鎴愬憳琛岋級+ 鍙充笅瑙掓嫆缁� / 鎺ュ惉锛�
- 绉佽亰锛氬乏澶村儚 + 涓紙閭�璇蜂汉 + 鏂囨涓よ锛�+ 鍙充笅瑙掓嫆缁� / 鎺ュ惉
- -->
- <div
- class="fixed top-5 right-5 rounded-2xl overflow-hidden shadow-[0_12px_36px_rgba(0,0,0,0.4)] z-[9999] flex gap-3 items-end p-3 text-white bg-[#2a2a2c]"
- :class="isGroup ? 'w-[360px]' : 'w-[340px]'"
- >
- <!-- 宸︼細閭�璇疯�呭ご鍍� -->
- <UserAvatar
- :url="payload?.inviterAvatar"
- :name="payload?.inviterNickname"
- :size="48"
- radius="8px"
- :clickable="false"
- class="self-start"
- />
-
- <!-- 涓細缇よ亰鍗曡锛堥個璇蜂汉 + 鏂囨锛�+ 閫氳瘽鎴愬憳锛涚鑱婁袱琛岋紙鍚� / 鏂囨锛� -->
- <div class="flex flex-col flex-1 gap-1 self-start min-w-0">
- <!-- 鍚� + 鏂囨锛氱兢鍗曡鍐呰仈锛岀鑱婁笂涓嬩袱琛� -->
- <div v-if="isGroup" class="text-sm truncate">
- <span class="font-medium">{{ payload?.inviterNickname || '瀵规柟' }}</span>
- <span class="ml-1 text-white/60">{{ tipText }}</span>
- </div>
- <template v-else>
- <div class="text-sm font-medium truncate">{{ payload?.inviterNickname || '瀵规柟' }}</div>
- <div class="text-13px text-white/60 truncate">{{ tipText }}</div>
- </template>
-
- <!-- 缇ら�氳瘽鎴愬憳琛岋紱绉佽亰鏃狅紱鎺ュ叆涓殑浜哄崐閫忔槑灞曠ず -->
- <template v-if="isGroup && callMembers.length > 0">
- <div class="mt-1 text-xs text-white/45">閫氳瘽鎴愬憳</div>
- <div class="flex flex-wrap gap-1">
- <UserAvatar
- v-for="member in callMembers"
- :key="member.userId"
- :url="member.avatar"
- :name="member.nickname"
- :size="22"
- radius="4px"
- :clickable="false"
- :class="{ 'opacity-50': member.pending }"
- :title="member.pending ? `${member.nickname}锛堟帴鍏ヤ腑锛塦 : member.nickname"
- />
- </div>
- </template>
- </div>
-
- <!-- 鍙充笅瑙掞細鎷掔粷 / 鎺ュ惉 -->
- <div class="flex flex-shrink-0 gap-2 items-center">
- <button
- class="flex flex-shrink-0 justify-center items-center w-10 h-10 text-white rounded-full transition-opacity bg-[#f04a4a] hover:opacity-90"
- :class="{ 'opacity-60 cursor-not-allowed': rejectDisabled }"
- :disabled="rejectDisabled"
- @click="$emit('reject')"
- >
- <Icon icon="ant-design:phone-outlined" :size="18" class="rotate-[135deg]" />
- </button>
- <button
- class="flex flex-shrink-0 justify-center items-center w-10 h-10 text-white rounded-full transition-opacity bg-[#2ec27e] hover:opacity-90"
- :class="{ 'opacity-60 cursor-not-allowed': acceptDisabled }"
- :disabled="acceptDisabled"
- @click="$emit('accept')"
- >
- <Icon icon="ant-design:phone-outlined" :size="18" />
- </button>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/rtc/rtc-call-inviting.vue b/src/views/im/home/components/rtc/rtc-call-inviting.vue
deleted file mode 100644
index d6db4d7..0000000
--- a/src/views/im/home/components/rtc/rtc-call-inviting.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<script lang="ts" setup>
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
-import { UserAvatar } from '../user'
-
-const props = defineProps<{
- cameraEnabled: boolean
- isGroup?: boolean
- isVideo: boolean
- localStream?: MediaStream | null // 鏈湴瑙嗛娴侊紱瑙嗛鍛煎彨棰勮閾哄簳
- micEnabled: boolean
- peerAvatar?: string
- peerNickname?: string
- speakerEnabled: boolean
-}>()
-
-defineEmits<{
- cancel: []
- toggleCamera: []
- toggleMic: []
- toggleSpeaker: []
-}>()
-
-const setLocalVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.localStream)
-</script>
-
-<template>
- <!-- 涓诲彨绛夊緟瀵规柟鎺ュ惉鐨勬偓娴獥锛�1v1 绉佽亰 320脳540锛涚兢閫氳瘽鍒囧ぇ绐� 720脳560 -->
- <div
- class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl overflow-hidden shadow-[0_12px_36px_rgba(0,0,0,0.35)] z-[1000] flex flex-col text-white bg-gradient-to-b from-[#2a2a2c] to-[#1a1a1c]"
- :class="isGroup ? 'w-[720px] h-[560px]' : 'w-[320px] h-[540px]'"
- >
- <div class="flex relative flex-1 justify-center items-center">
- <!-- 瑙嗛鍛煎彨锛氳嚜宸辨憚鍍忓ご棰勮閾哄簳锛屽鏂瑰ご鍍忔偓娴《閮� -->
- <video
- v-if="isVideo && localStream"
- :ref="setLocalVideoRef"
- class="absolute inset-0 object-cover w-full h-full scale-x-[-1]"
- autoplay
- muted
- playsinline
- ></video>
- <div
- class="flex relative z-[1] flex-col gap-4 items-center"
- :class="{ 'self-start mt-16': isVideo }"
- >
- <UserAvatar
- :url="peerAvatar"
- :name="peerNickname"
- :size="96"
- radius="8px"
- :clickable="false"
- />
- <div class="text-[17px] font-medium">{{ peerNickname || '瀵规柟' }}</div>
- <div class="text-13px text-white/60">绛夊緟瀵规柟鎺ュ彈閭�璇封�︹��</div>
- </div>
- </div>
-
- <!-- 搴曢儴鎿嶄綔鍖猴細楹﹀厠椋� / 鍙栨秷 / (鎽勫儚澶� | 鎵0鍣�) -->
- <div class="flex flex-shrink-0 gap-4 justify-around items-center pt-4 px-5 pb-5">
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none"
- @click="$emit('toggleMic')"
- >
- <!-- Iconify 閲� mic 鏈夐潤闊冲彉浣擄紱speaker / camera 娌℃湁鍚屾簮闈欓煶鍙樹綋锛宱ff 鎬佸�� tabler:*-off 琛ㄨ揪鏂滅嚎 -->
- <span
- class="flex justify-center items-center w-12 h-12 rounded-full"
- :class="micEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="micEnabled ? 'ant-design:audio-outlined' : 'ant-design:audio-muted-outlined'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ micEnabled ? '楹﹀厠椋庡凡寮�' : '楹﹀厠椋庡凡鍏�' }}
- </span>
- </div>
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none"
- @click="$emit('cancel')"
- >
- <span
- class="flex justify-center items-center w-12 h-12 text-white rounded-full bg-[#f04a4a]"
- >
- <Icon icon="ant-design:phone-outlined" :size="22" class="rotate-[135deg]" />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">鍙栨秷</span>
- </div>
- <div
- v-if="isVideo"
- class="flex flex-col gap-2 items-center cursor-pointer select-none"
- @click="$emit('toggleCamera')"
- >
- <span
- class="flex justify-center items-center w-12 h-12 rounded-full"
- :class="cameraEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="cameraEnabled ? 'ant-design:video-camera-outlined' : 'tabler:video-off'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ cameraEnabled ? '鎽勫儚澶村凡寮�' : '鎽勫儚澶村凡鍏�' }}
- </span>
- </div>
- <div
- v-else
- class="flex flex-col gap-2 items-center cursor-pointer select-none"
- @click="$emit('toggleSpeaker')"
- >
- <span
- class="flex justify-center items-center w-12 h-12 rounded-full"
- :class="speakerEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="speakerEnabled ? 'ant-design:sound-outlined' : 'tabler:volume-off'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ speakerEnabled ? '鎵0鍣ㄥ凡寮�' : '鎵0鍣ㄥ凡鍏�' }}
- </span>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/components/rtc/rtc-call-member-picker-dialog.vue b/src/views/im/home/components/rtc/rtc-call-member-picker-dialog.vue
deleted file mode 100644
index 2844987..0000000
--- a/src/views/im/home/components/rtc/rtc-call-member-picker-dialog.vue
+++ /dev/null
@@ -1,109 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../group'
-
-import { computed, ref } from 'vue'
-
-import { Button, Modal } from 'ant-design-vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { useGroupStore } from '../../store/groupStore'
-import { GroupMemberPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImRtcCallMemberPickerDialog' })
-
-const emit = defineEmits<{
- /** 閫夊畬鐐瑰畬鎴愶紱鎼哄甫閫変腑鐨� userId 鍒楄〃 */
- success: [selectedIds: number[]]
-}>()
-
-type PickerMode = 'add' | 'invite'
-
-const groupStore = useGroupStore()
-
-const visible = ref(false) // 寮圭獥鏄鹃殣
-const groupId = ref(0) // 褰撳墠缇ょ紪鍙凤紱open 鏃剁敱璋冪敤鏂逛紶鍏�
-const mode = ref<PickerMode>('invite') // 寮圭獥鐢ㄩ�旓紱invite=鍙戣捣缇ら�氳瘽閫夐個璇蜂汉 / add=閫氳瘽涓拷鍔犳垚鍛�
-const excludeUserIds = ref<number[]>([]) // 缃伆鐨� userId 鍒楄〃锛沘dd 鍦烘櫙鎶婂凡鍦ㄩ�氳瘽鍐呯殑浜虹鐢�
-const selectedIds = ref<number[]>([]) // 褰撳墠閫変腑鐨� userId 鍒楄〃锛汫roupMemberPickerPanel v-model 缁戣繃鏉�
-
-/** 鏍囬锛涙寜鐢ㄩ�斿垏鎹� */
-const title = computed(() => (mode.value === 'add' ? '娣诲姞鎴愬憳' : '閫夋嫨鎴愬憳'))
-
-/** 缇ゆ垚鍛樺垪琛紱浠� groupStore 鐜板彇锛宮ap 鎴� GroupMemberLite */
-const members = computed<GroupMemberLite[]>(() => {
- const group = groupStore.getGroup(groupId.value)
- return (group?.members || []).map((member) => ({
- userId: member.userId,
- nickname: member.nickname,
- showName: member.displayUserName || member.nickname,
- avatar: member.avatar,
- status: member.status,
- role: member.role
- }))
-})
-
-/** 鑷繁涓嶅嚭鐜板湪閫夐」閲� */
-const hideIds = computed<number[]>(() => {
- const myId = getCurrentUserId()
- return myId ? [myId] : []
-})
-
-/** 宸插湪閫氳瘽鍐呯殑鎴愬憳缃伆 */
-const disabledIds = computed<number[]>(() => excludeUserIds.value)
-
-/** 鏄惁鍙彁浜わ細鑷冲皯閫� 1 涓� */
-const canSubmit = computed(() => selectedIds.value.length > 0)
-
-/** 鎵撳紑寮圭獥锛沞xcludeUserIds 鐢ㄤ簬銆屾坊鍔犳垚鍛樸�嶆椂鎶婂凡鍦ㄩ�氳瘽鍐呯殑浜虹疆鐏� */
-function open(opts: { excludeUserIds?: number[]; groupId: number; mode?: PickerMode; }) {
- groupId.value = opts.groupId
- mode.value = opts.mode || 'invite'
- excludeUserIds.value = opts.excludeUserIds || []
- selectedIds.value = []
- visible.value = true
-}
-defineExpose({ open }) // 鎻愪緵 open 鏂规硶锛岀敤浜庢墦寮�寮圭獥
-
-/** 鐐瑰畬鎴愶細emit 閫変腑 ID 鍒楄〃缁欑埗绾� + 鍏抽棴寮圭獥锛涙彁浜ゆ寜閽� disabled 宸蹭繚璇� selectedIds 闈炵┖ */
-function handleOk() {
- emit('success', [...selectedIds.value])
- visible.value = false
-}
-</script>
-
-<template>
- <!--
- 閫氳瘽鎴愬憳閫夋嫨寮圭獥锛氬彂璧风兢閫氳瘽鏃堕�夐個璇蜂汉 / 閫氳瘽涓坊鍔犳垚鍛橈紱
- 閫夋嫨 UI 濮旀墭 GroupMemberPickerPanel锛涜嚜宸� hide銆佸凡鍦ㄩ�氳瘽鍐呯殑鎴愬憳 disabled
- -->
- <Modal
- v-model:open="visible"
- :title="title"
- width="720px"
- :mask-closable="false"
- class="im-picker-dialog"
- >
- <div class="h-[480px]">
- <GroupMemberPickerPanel
- v-model:selected-ids="selectedIds"
- :members="members"
- :disabled-ids="disabledIds"
- :hide-ids="hideIds"
- />
- </div>
- <template #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button type="primary" :disabled="!canSubmit" @click="handleOk">瀹屾垚</Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/rtc/rtc-call-participant-tile.vue b/src/views/im/home/components/rtc/rtc-call-participant-tile.vue
deleted file mode 100644
index 6a12111..0000000
--- a/src/views/im/home/components/rtc/rtc-call-participant-tile.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script lang="ts" setup>
-import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
-import { UserAvatar } from '../user'
-
-export interface CallParticipantVM {
- userId: number
- nickname: string
- avatar?: string
- isLocal: boolean
- videoStream?: MediaStream | null
- audioStream?: MediaStream | null
- /** 绛夊緟鍔犲叆锛沀I 鏄剧ず涓夌偣鍔ㄧ敾 */
- pending?: boolean
-}
-
-const props = defineProps<{
- participant: CallParticipantVM
- /** 鎵0鍣ㄥ紑鍏筹紱涓� false 鏃堕潤闊宠鏍煎瓙鐨勮繙绔煶棰� */
- speakerEnabled: boolean
-}>()
-
-const setVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.participant.videoStream)
-const setAudioRef = useMediaStreamElement<HTMLAudioElement>(() => props.participant.audioStream)
-</script>
-
-<template>
- <!--
- 缇ら�氳瘽瀹牸閲岀殑鍗曚釜鍙備笌鑰呮牸瀛愶紱
- 浼樺厛娓叉煋杩滅瑙嗛锛堟湁 videoStream 鏃堕摵婊★級锛屾棤瑙嗛闄嶇骇娓叉煋澶村儚 + 鍚嶅瓧鑳跺泭锛�
- pending=true 鏃跺彔鍔犮�屾帴鍏ヤ腑銆嶄笁鐐瑰姩鐢诲崰浣嶏紝鐢ㄤ簬琚個璇蜂汉鏈帴閫氬墠鐨勫崰浣嶆覆鏌擄紱
- 杩滅闊抽閫氳繃闅愯棌 audio 鎾斁锛堟湰绔� isLocal 涓嶆覆鏌撻伩鍏嶅洖澹帮級
- -->
- <div
- class="flex relative overflow-hidden justify-center items-center w-full h-full rounded-md bg-[#2a2a2c]"
- >
- <!-- 瑙嗛鍙敤锛氭覆鏌� video锛涘惁鍒欐覆鏌撳ご鍍忔垨榛樿鍗犱綅 -->
- <video
- v-if="participant.videoStream"
- :ref="setVideoRef"
- class="object-cover w-full h-full"
- autoplay
- playsinline
- :muted="participant.isLocal"
- ></video>
- <div v-else class="flex justify-center items-center w-full h-full">
- <UserAvatar
- :url="participant.avatar"
- :name="participant.nickname"
- :size="64"
- radius="50%"
- :clickable="false"
- />
- </div>
-
- <!-- 杩滅闊抽锛涢�氳繃 audio 鍏冪礌鎾斁锛屾湰绔潤闊抽伩鍏嶅洖澹帮紱鎵0鍣ㄥ叧闂椂鏁翠綋闈欓煶 -->
- <audio
- v-if="participant.audioStream && !participant.isLocal"
- :ref="setAudioRef"
- autoplay
- :muted="!speakerEnabled"
- ></audio>
-
- <!-- 宸︿笅瑙掑悕瀛楄兌鍥� -->
- <div
- class="flex absolute bottom-3 left-3 gap-1.5 items-center py-[3px] pr-2.5 pl-[3px] text-13px text-white rounded-full bg-black/45 max-w-[calc(100%-60px)]"
- >
- <UserAvatar
- :url="participant.avatar"
- :name="participant.nickname"
- :size="22"
- radius="50%"
- :clickable="false"
- />
- <span class="truncate min-w-0">{{ participant.nickname }}</span>
- </div>
-
- <!-- 绛夊緟瀵规柟鍔犲叆锛氫笁鐐瑰姩鐢� +銆屾帴鍏ヤ腑銆嶆枃妗堬紱浣嶇疆鍦ㄦ牸瀛愪腑蹇冧笅鏂� 60px -->
- <div
- v-if="participant.pending"
- class="flex absolute left-1/2 flex-col gap-1.5 items-center -translate-x-1/2 -translate-y-1/2 top-[calc(50%+60px)]"
- >
- <div class="flex gap-1.5">
- <span
- v-for="i in 3"
- :key="i"
- class="tile-dot w-1.5 h-1.5 rounded-full bg-white/60"
- :style="{ animationDelay: `${(i - 1) * 0.2}s` }"
- ></span>
- </div>
- <span class="text-xs text-white/70">鎺ュ叆涓�</span>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 涓夌偣娣″叆娣″嚭鍔ㄧ敾锛汙keyframes 蹇呴』 CSS 瀹氫箟 */
-.tile-dot {
- animation: tile-dot 1.4s infinite ease-in-out both;
-}
-@keyframes tile-dot {
- 0%,
- 80%,
- 100% {
- opacity: 0.3;
- }
- 40% {
- opacity: 1;
- }
-}
-</style>
diff --git a/src/views/im/home/components/rtc/rtc-call-running.vue b/src/views/im/home/components/rtc/rtc-call-running.vue
deleted file mode 100644
index b23b437..0000000
--- a/src/views/im/home/components/rtc/rtc-call-running.vue
+++ /dev/null
@@ -1,291 +0,0 @@
-<script lang="ts" setup>
-import { computed, onUnmounted, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { formatCallDuration } from '#/views/im/utils/time'
-
-import { useMediaStreamElement } from '../../composables/useMediaStreamElement'
-import { UserAvatar } from '../user'
-import RtcCallParticipantTile, { type CallParticipantVM } from './rtc-call-participant-tile.vue'
-
-const props = defineProps<{
- cameraEnabled: boolean
- hangingUp?: boolean
- /** 鏄惁缇ら�氳瘽锛涘喅瀹氱綉鏍� / 鍗曠偣甯冨眬 + 娴獥澶у皬 */
- isGroup: boolean
- isVideo: boolean
- localStream?: MediaStream | null
- micEnabled: boolean
- /** 缃戞牸瑙嗗浘鐢細鎵�鏈夊弬涓庤�咃紙鍚嚜宸憋級 */
- participants: CallParticipantVM[]
- peerAvatar?: string
- /** 1v1 瑙嗗浘鐢� */
- peerNickname?: string
- /** 鏄惁澶勪簬缃戠粶閲嶈繛涓紱鏄剧ず椤堕儴榛勮壊鎻愮ず鏉� */
- reconnecting?: boolean
- remoteAudioStream?: MediaStream | null
- remoteVideoStream?: MediaStream | null
- /** 鏄惁姝e湪灞忓箷鍏变韩锛涙寜閽珮浜� + 鏂囨鍒囨崲 */
- screenShareEnabled?: boolean
- /** 鎵0鍣ㄥ紑鍏筹紱true 鏃舵甯告挱鏀捐繙绔煶棰戯紝false 鏃舵墍鏈夎繙绔� audio 鍏冪礌闈欓煶 */
- speakerEnabled: boolean
- startedAt: number
-}>()
-
-defineEmits<{
- addMember: []
- hangup: []
- toggleCamera: []
- toggleMic: []
- toggleScreenShare: []
- toggleSpeaker: []
-}>()
-
-/** 缃戞牸鍒楁暟锛涙寜浜烘暟鑷�傚簲锛涜繑鍥� UnoCSS class 瀛楅潰閲忚 JIT 鎵弿鍣ㄩ潤鎬佽瘑鍒� */
-const gridColsClass = computed(() => {
- const n = props.participants.length
- if (n <= 1) return 'grid-cols-1'
- if (n <= 4) return 'grid-cols-2'
- return 'grid-cols-3'
-})
-
-const setLocalVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.localStream)
-const setRemoteVideoRef = useMediaStreamElement<HTMLVideoElement>(() => props.remoteVideoStream)
-const setRemoteAudioRef = useMediaStreamElement<HTMLAudioElement>(() => props.remoteAudioStream)
-
-/** 1v1 瑙嗛锛氭槸鍚︽湁杩滅瑙嗛娴� */
-const hasRemoteVideo = computed(() => !props.isGroup && !!props.remoteVideoStream)
-
-const now = ref(Date.now()) // 閫氳瘽鏃堕暱锛涗粎 1v1 璇煶瑙嗗浘闇�瑕佸睍绀猴紝鍏跺畠瑙嗗浘涓嶅惎 tick
-let tick = 0
-watch(
- () => props.isGroup || props.isVideo,
- (suppressTick) => {
- if (suppressTick) {
- if (tick) {
- clearInterval(tick)
- tick = 0
- }
- return
- }
- now.value = Date.now()
- tick = window.setInterval(() => {
- now.value = Date.now()
- }, 1000)
- },
- { immediate: true }
-)
-onUnmounted(() => {
- if (tick) {
- clearInterval(tick)
- }
-})
-
-/** 閫氳瘽鏃堕暱 MM:SS / HH:MM:SS */
-const formattedDuration = computed(() =>
- formatCallDuration(Math.floor((now.value - props.startedAt) / 1000))
-)
-</script>
-
-<template>
- <!-- 閫氳瘽杩涜涓殑鎮诞绐楋紱1v1 绉佽亰 320脳540锛涚兢閫氳瘽鍒囧ぇ绐� 720脳560 -->
- <div
- class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl overflow-hidden shadow-[0_12px_36px_rgba(0,0,0,0.35)] z-[1000] flex flex-col text-white bg-[#1a1a1c]"
- :class="isGroup ? 'w-[720px] h-[560px]' : 'w-[320px] h-[540px]'"
- >
- <!-- 閲嶈繛涓í骞咃紱缃戠粶鎶栧姩鏃舵樉绀猴紝鐩村埌 Reconnected 浜嬩欢娓呴櫎 -->
- <div
- v-if="reconnecting"
- class="inline-flex absolute top-3 left-1/2 z-10 gap-2 items-center px-3.5 py-1.5 text-13px text-[#ffd45e] rounded-full -translate-x-1/2 bg-[rgba(255,196,0,0.18)]"
- >
- <span class="reconnect-dot w-2 h-2 rounded-full bg-[#ffd45e]"></span>
- 缃戠粶涓嶄匠锛屾鍦ㄩ噸杩炩�︹��
- </div>
- <div class="flex relative flex-1 justify-center items-center">
- <!-- 缇ら�氳瘽锛氱綉鏍煎竷灞�锛屽垪鏁伴殢浜烘暟鑷�傚簲 -->
- <div
- v-if="isGroup"
- class="grid gap-1 p-1 w-full h-full content-stretch"
- :class="gridColsClass"
- >
- <RtcCallParticipantTile
- v-for="participant in participants"
- :key="participant.userId"
- :participant="participant"
- :speaker-enabled="speakerEnabled"
- />
- </div>
-
- <!-- 1v1 瑙嗛锛氳繙绔摵搴� + 鏈湴缂╃暐 -->
- <template v-else-if="isVideo">
- <div v-show="hasRemoteVideo" class="absolute inset-0">
- <video
- :ref="setRemoteVideoRef"
- class="object-cover w-full h-full"
- autoplay
- playsinline
- ></video>
- </div>
- <div v-if="!hasRemoteVideo" class="flex z-[1] flex-col gap-4 items-center">
- <UserAvatar
- :url="peerAvatar"
- :name="peerNickname"
- :size="96"
- radius="8px"
- :clickable="false"
- />
- <div class="text-[17px] font-medium">{{ peerNickname }}</div>
- <div class="text-13px text-white/60">绛夊緟瀵规柟寮�鍚憚鍍忓ご鈥︹��</div>
- </div>
- <div
- v-if="localStream"
- class="absolute top-4 right-4 z-[2] overflow-hidden w-30 rounded-lg aspect-[9/16] bg-[#333]"
- >
- <video
- :ref="setLocalVideoRef"
- class="object-cover w-full h-full scale-x-[-1]"
- autoplay
- muted
- playsinline
- ></video>
- </div>
- </template>
-
- <!-- 1v1 璇煶锛氬ご鍍� + 鏃堕暱 -->
- <template v-else>
- <div class="flex z-[1] flex-col gap-4 items-center">
- <UserAvatar
- :url="peerAvatar"
- :name="peerNickname"
- :size="96"
- radius="8px"
- :clickable="false"
- />
- <div class="text-[17px] font-medium">{{ peerNickname }}</div>
- <div class="text-13px text-white/60">{{ formattedDuration }}</div>
- </div>
- </template>
- <audio
- v-if="!isGroup && remoteAudioStream"
- :ref="setRemoteAudioRef"
- autoplay
- :muted="!speakerEnabled"
- ></audio>
- </div>
-
- <!-- 搴曢儴鎿嶄綔鍖猴細楹﹀厠椋� / 鎵0鍣� / 鎽勫儚澶� / (缇よ亰锛氬叡浜睆骞� / 娣诲姞鎴愬憳) / 鎸傛柇 -->
- <div
- class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20"
- >
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- @click="$emit('toggleMic')"
- >
- <span
- class="flex justify-center items-center w-[52px] h-[52px] rounded-full"
- :class="micEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="micEnabled ? 'ant-design:audio-outlined' : 'ant-design:audio-muted-outlined'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ micEnabled ? '楹﹀厠椋庡凡寮�' : '楹﹀厠椋庡凡鍏�' }}
- </span>
- </div>
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- @click="$emit('toggleSpeaker')"
- >
- <span
- class="flex justify-center items-center w-[52px] h-[52px] rounded-full"
- :class="speakerEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="speakerEnabled ? 'ant-design:sound-outlined' : 'tabler:volume-off'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ speakerEnabled ? '鎵0鍣ㄥ凡寮�' : '鎵0鍣ㄥ凡鍏�' }}
- </span>
- </div>
- <!-- 鎽勫儚澶存寜閽細绉佽亰瑙嗛鍥哄畾鏄剧ず锛涚兢鑱婁笉璁鸿捣濮� mediaType 閮芥樉绀猴紝璁╃敤鎴锋寜闇�寮� -->
- <div
- v-if="isVideo || isGroup"
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- @click="$emit('toggleCamera')"
- >
- <span
- class="flex justify-center items-center w-[52px] h-[52px] rounded-full"
- :class="cameraEnabled ? 'bg-white text-[#1a1a1c]' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="cameraEnabled ? 'ant-design:video-camera-outlined' : 'tabler:video-off'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ cameraEnabled ? '鎽勫儚澶村凡寮�' : '鎽勫儚澶村凡鍏�' }}
- </span>
- </div>
- <!-- 缇ら�氳瘽鎵嶆湁锛氬叡浜睆骞� / 娣诲姞鎴愬憳锛涘叡浜腑鎸夐挳楂樹寒 + 鏂囨鎹㈡垚銆屽仠姝㈠叡浜�� -->
- <template v-if="isGroup">
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- @click="$emit('toggleScreenShare')"
- >
- <span
- class="flex justify-center items-center w-[52px] h-[52px] rounded-full"
- :class="screenShareEnabled ? 'bg-[#07c160] text-white' : 'bg-white/15 text-white'"
- >
- <Icon
- :icon="screenShareEnabled ? 'ant-design:laptop-outlined' : 'tabler:device-laptop-off'"
- :size="22"
- />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">
- {{ screenShareEnabled ? '鍋滄鍏变韩' : '鍏变韩灞忓箷' }}
- </span>
- </div>
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- @click="$emit('addMember')"
- >
- <span class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-white/15">
- <Icon icon="ant-design:plus-outlined" :size="22" />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">娣诲姞鎴愬憳</span>
- </div>
- </template>
- <div
- class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
- :class="{ 'opacity-60 pointer-events-none': hangingUp }"
- @click="$emit('hangup')"
- >
- <span class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-[#f04a4a]">
- <Icon icon="ant-design:phone-outlined" :size="22" class="rotate-[135deg]" />
- </span>
- <span class="text-xs text-white/70 whitespace-nowrap">鎸傛柇</span>
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 閲嶈繛灏忕偣娣″叆娣″嚭锛汙keyframes 蹇呴』 CSS 瀹氫箟 */
-.reconnect-dot {
- animation: reconnect-pulse 1s ease-in-out infinite;
-}
-@keyframes reconnect-pulse {
- 0%,
- 100% {
- opacity: 0.3;
- }
- 50% {
- opacity: 1;
- }
-}
-</style>
diff --git a/src/views/im/home/components/rtc/rtc-group-call-banner.vue b/src/views/im/home/components/rtc/rtc-group-call-banner.vue
deleted file mode 100644
index 6087ac7..0000000
--- a/src/views/im/home/components/rtc/rtc-group-call-banner.vue
+++ /dev/null
@@ -1,205 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref, watch } from 'vue'
-
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { message, Popover } from 'ant-design-vue'
-
-import { getActiveCall, joinCall } from '#/api/im/rtc'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { useGroupCallMembers } from '../../composables/useGroupCallMembers'
-import { useGroupStore } from '../../store/groupStore'
-import { useRtcStore } from '../../store/rtcStore'
-import { UserAvatar } from '../user'
-
-defineOptions({ name: 'ImRtcGroupCallBanner' })
-
-const props = defineProps<{
- groupId: number
-}>()
-
-const rtcStore = useRtcStore()
-const groupStore = useGroupStore()
-
-const popoverVisible = ref(false)
-
-/** 褰撳墠缇ょ殑娲昏穬閫氳瘽锛況tcStore 缁存姢锛屽弬涓庤�呭姞鍏� / 绂诲紑閫氱煡澧炲垹 joinedUserIds锛岄�氳瘽缁撴潫绉婚櫎 */
-const activeCall = computed(() => rtcStore.getGroupCall(props.groupId))
-
-/** 鑳跺泭鏉℃枃妗堬紱鏈夋垚鍛橈紙宸插姞鍏� + 鎺ュ叆涓級鍒欏甫浜烘暟锛屽垵濮� 0 浜烘椂鍙樉绀哄獟浣撶被鍨� */
-const pillText = computed(() => {
- const media = getDictLabel(DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE, activeCall.value?.mediaType)
- const count = memberList.value.length
- return count > 0 ? `姝e湪${media}閫氳瘽锛�${count} 浜猴級` : `姝e湪${media}閫氳瘽`
-})
-
-/**
- * 鍒囧埌缇� / 閫氳瘽 room 鍙樺寲鏃舵媺涓�娆℃渶鏂板弬涓庤�呭垪琛紱
- * 涓や釜瑙﹀彂鍦烘櫙锛�1锛夌敤鎴峰垏缇わ紝鏈鍙兘娌℃湁璇ョ兢閫氳瘽鐨勬渶鏂扮紦瀛橈紱2锛夊弬涓庤�呴�氱煡棣栨濉厖鍚庡彧鍚湰娆″姞鍏ヨ�咃紝缂哄巻鍙插姞鍏ヨ�咃紱
- * 鐢� [groupId, room] 鍙屾簮鐩戝惉 + 宸插~鍏呭畧鍗紝閬垮厤鍒囩兢 / 棣栨濉厖瑙﹀彂鐨勫弻娆¢噸澶嶆媺鍙�
- */
-watch(
- () =>
- [
- props.groupId,
- activeCall.value?.room,
- groupStore.isGroupActiveCallExpired(props.groupId)
- ] as const,
- async ([groupId, room], oldValues) => {
- if (!groupId) {
- return
- }
-
- if (!activeCall.value) {
- if (!groupStore.isGroupActiveCallExpired(groupId)) {
- return
- }
- try {
- const data = await getActiveCall(groupId)
- if (data) {
- rtcStore.setGroupCall(data, true)
- } else {
- rtcStore.removeGroupCall(groupId)
- }
- } catch (error) {
- console.warn('[GroupCallBanner] getActiveCall 澶辫触', { groupId }, error)
- }
- return
- }
-
- // 鍐崇瓥鏄惁闇�瑕佹媺鍙栵細琛ラ綈鏈湴宸叉湁閫氳瘽锛涙病鏈夋湰鍦伴�氳瘽鏃舵寜缇ょ紦瀛樿繃鏈熺姸鎬佹噿鎺㈡祴涓�娆�
- const groupChanged = !oldValues || oldValues[0] !== groupId
- const roomChanged = oldValues && oldValues[1] !== room
- const participantsLoaded = (activeCall.value?.joinedUserIds?.length ?? 0) > 1
- const activeCallExpired = groupStore.isGroupActiveCallExpired(groupId)
- if (
- !activeCallExpired &&
- (rtcStore.isGroupCallParticipantsLoaded(groupId, room) ||
- (!groupChanged && !roomChanged && participantsLoaded))
- ) {
- return
- }
-
- // 鎷夋渶鏂板弬涓庤�呭啓鍥� store锛涙帴鍙h繑鍥炵┖ 鈫� 璇ョ兢宸叉棤娲昏穬閫氳瘽锛岀Щ闄ゆ湰鍦扮紦瀛�
- try {
- const data = await getActiveCall(groupId)
- if (data) {
- rtcStore.setGroupCall(data, true)
- } else {
- rtcStore.removeGroupCall(groupId)
- }
- } catch (error) {
- console.warn('[GroupCallBanner] getActiveCall 澶辫触', { groupId }, error)
- }
- },
- { immediate: true }
-)
-
-/** 鍦ㄩ�氳瘽涓殑鎴愬憳锛堝凡鍔犲叆锛�+ 鎺ュ叆涓殑鎴愬憳锛堝凡閭�璇锋湭鎺ラ�氾級 */
-const memberList = useGroupCallMembers(computed(() => props.groupId))
-
-/** 鏈鏄惁姝e湪璇ユ埧闂撮�氳瘽锛堝浜� INVITING / RUNNING锛� */
-const isInThisCall = computed(
- () => rtcStore.isActive && rtcStore.call?.room === activeCall.value?.room
-)
-
-/**
- * 鏈嶅姟绔槸鍚﹁褰曟垜宸插姞鍏ワ紱鍒锋柊鍚� LiveKit 杩炴帴宸叉柇浣� webhook 杩樻病鎶� status 鏍囦负 LEFT 鏃朵粛涓� true锛�
- * 鐢ㄤ簬鎶婃寜閽枃妗堝垏鍒般�岄噸鏂板姞鍏ャ�嶏紝浣嗕笉 disable 鎸夐挳
- */
-const serverSaysJoined = computed(() => {
- const myId = getCurrentUserId()
- return activeCall.value?.joinedUserIds?.includes(myId) ?? false
-})
-
-/** 鍔犲叆鎸夐挳绂佺敤锛氫粎鍦ㄦ湰绔疄闄呮寔鏈� LiveKit 杩炴帴鏃剁鐢� */
-const joinDisabled = computed(() => isInThisCall.value)
-
-/** 鍔犲叆鎸夐挳鏂囨锛涙湰绔繛鐫� 鈫� 宸插湪閫氳瘽涓紱鏈嶅姟绔繕娈嬬暀鎴戜絾鏈鏂簡 鈫� 閲嶆柊鍔犲叆锛涘叾瀹� 鈫� 鍔犲叆 */
-const joinLabel = computed(() => {
- if (isInThisCall.value) return '宸插湪閫氳瘽涓�'
- if (serverSaysJoined.value) return '閲嶆柊鍔犲叆'
- return '鍔犲叆'
-})
-
-/** 涓诲姩鍔犲叆锛氳皟 invite 鍛戒腑宸叉湁 call 鎷� token锛況tcStore 鎸� status 鑷姩杩� RUNNING */
-async function handleJoin() {
- const call = activeCall.value
- if (!call || joinDisabled.value) {
- return
- }
- if (rtcStore.isActive) {
- message.warning('鎮ㄦ鍦ㄩ�氳瘽涓�')
- return
- }
- popoverVisible.value = false
- const data = await joinCall(call.room)
- rtcStore.startInviting(data)
-}
-</script>
-
-<template>
- <!-- 浠呭綋璇ョ兢鏈夋椿璺冮�氳瘽鏃舵樉绀猴紱鐐瑰嚮鑳跺泭鏉″睍寮� popover 鐪嬪湪閫氳瘽鎴愬憳 + 鍔犲叆 -->
- <div v-if="activeCall" class="flex-shrink-0 px-4 pb-2 bg-[var(--ant-color-fill-secondary)]">
- <Popover
- v-model:open="popoverVisible"
- placement="bottomLeft"
- :overlay-style="{ width: '280px' }"
- trigger="click"
- >
- <!-- 鑳跺泭鏉℃湰浣擄細鐢佃瘽鍥炬爣 + 鏂囨锛堝惈浜烘暟锛�+ 鍙崇澶� -->
- <div
- class="inline-flex gap-2 items-center px-2.5 py-1 text-13px rounded-full cursor-pointer select-none transition-colors duration-150 bg-[var(--ant-color-success-bg)] text-[var(--ant-color-text)] hover:bg-[var(--ant-color-success-bg-hover)]"
- >
- <span
- class="inline-flex flex-shrink-0 justify-center items-center w-[18px] h-[18px] text-white rounded-full bg-[#07c160]"
- >
- <Icon icon="ant-design:phone-filled" :size="14" />
- </span>
- <span class="font-medium">{{ pillText }}</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="12"
- class="text-[var(--ant-color-text-secondary)]"
- />
- </div>
-
- <!-- 灞曞紑闈㈡澘锛氶�氳瘽鎴愬憳锛堝惈鎺ュ叆涓級澶村儚妯帓 + 鍔犲叆鎸夐挳 -->
- <template #content>
- <div class="flex flex-col gap-4 items-center pt-2 pb-1">
- <div class="flex flex-wrap gap-1.5 justify-center max-w-[240px]">
- <UserAvatar
- v-for="member in memberList"
- :key="member.userId"
- :url="member.avatar"
- :name="member.nickname"
- :size="40"
- radius="6px"
- :clickable="false"
- :class="{ 'opacity-50': member.pending }"
- :title="member.pending ? `${member.nickname}锛堟帴鍏ヤ腑锛塦 : member.nickname"
- />
- <!-- 棣栨濉厖鏃舵埧鍐呭彲鑳芥殏鏃� 0 浜猴紱鍔犲叆鍚庣敱 ParticipantConnected 浜嬩欢杩藉姞 -->
- <div
- v-if="memberList.length === 0"
- class="p-3 text-13px text-[var(--ant-color-text-secondary)]"
- >
- 鏆傛棤鎴愬憳鍦ㄩ�氳瘽
- </div>
- </div>
- <!-- 鏈鍦ㄩ�氳瘽鍐呮椂缃伆銆屽凡鍦ㄩ�氳瘽涓�嶏紱鏈嶅姟绔畫鐣欐垜浣嗘湰绔繛鎺ユ柇浜嗘樉绀恒�岄噸鏂板姞鍏ャ�嶏紙鍒锋柊椤甸潰鍚庡満鏅級 -->
- <button
- class="w-[200px] h-9 text-sm font-medium rounded-lg cursor-pointer border-none bg-[#f1f1f3] text-[#1a1a1c] transition-colors duration-150 disabled:cursor-not-allowed disabled:text-[#999] hover:[&:not(:disabled)]:bg-[#e7e7ea]"
- :disabled="joinDisabled"
- @click="handleJoin"
- >
- {{ joinLabel }}
- </button>
- </div>
- </template>
- </Popover>
- </div>
-</template>
diff --git a/src/views/im/home/components/tool-bar.vue b/src/views/im/home/components/tool-bar.vue
deleted file mode 100644
index a2ccc71..0000000
--- a/src/views/im/home/components/tool-bar.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
-
-import { IconifyIcon as Icon } from '../../../../packages/icons/src'
-import { useUserStore } from '../../../../packages/stores/src'
-
-import { Badge } from 'ant-design-vue'
-
-import { useConversationStore } from '../store/conversationStore'
-import { useFriendStore } from '../store/friendStore'
-import { useImUiStore } from '../store/uiStore'
-import { UserAvatar } from './user'
-
-defineOptions({ name: 'ImToolBar' })
-
-const route = useRoute()
-const router = useRouter()
-const userStore = useUserStore()
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const uiStore = useImUiStore()
-
-/** 娑堟伅 Tab 鐨勭孩鐐癸細鎵�鏈夐潪鍏嶆墦鎵颁細璇濈殑鏈鎬诲拰 */
-const totalUnread = computed(() => conversationStore.getTotalUnreadCount)
-/** 閫氳褰� Tab 鐨勭孩鐐癸細鏈鐞嗗ソ鍙嬬敵璇锋暟锛堟帴鏀舵柟=鎴戯級 */
-const unhandledRequestCount = computed(() => friendStore.getUnhandledRequestCount)
-
-const tabs = [
- { name: 'ImHomeConversation', icon: 'ep:chat-round' },
- { name: 'ImHomeContact', icon: 'mingcute:contacts-line' }
-] // 涓や釜涓� Tab锛涚敤璺敱 name 鑰岄潪 path锛岄伩鍏嶅墠缂� / 宓屽璋冩暣鍚庡け鏁�
-
-// 褰撳墠璺敱鏄惁鍛戒腑 Tab锛氱洿鎺ユ瘮瀵� route.name
-const isActive = (name: string) => route.name === name
-
-// 鍒囨崲 Tab锛氬綋鍓嶅凡閫変腑鏃讹紝娑堟伅 Tab 瑙﹀彂"婊氬姩鍒颁笅涓�涓湭璇�"锛堝榻愬井淇� PC锛夛紝鍏跺畠 Tab 鏃犲姩浣�
-const goTab = (name: string) => {
- if (route.name === name) {
- if (name === 'ImHomeConversation') {
- uiStore.requestNextUnreadJump()
- }
- return
- }
- router.push({ name })
-}
-
-// 璺宠浆涓汉涓績锛堣矾鐢� name=Profile锛�
-const goProfile = () => router.push({ name: 'Profile' })
-</script>
-
-<template>
- <!--
- ToolBar锛欼M 宸︿晶宸ュ叿鏍�
- 甯冨眬锛氶《閮ㄥご鍍� 鈫� 涓棿涓� Tab锛堟秷鎭�/濂藉弸/缇よ亰锛夆啋 搴曢儴璁剧疆
- -->
- <div
- class="flex flex-col items-center w-14 pt-4 pb-3 gap-2 flex-shrink-0 select-none bg-[#2b2b2b]"
- >
- <!-- 椤堕儴鐢ㄦ埛澶村儚锛岀偣鍑昏烦涓汉涓績锛涜蛋 UserAvatar 缁熶竴棣栧瓧 / 鍝堝笇閰嶈壊瑙勫垯 -->
- <div class="mb-2 cursor-pointer" @click="goProfile">
- <UserAvatar
- :url="userStore.userInfo?.avatar"
- :name="userStore.userInfo?.nickname"
- :size="36"
- :clickable="false"
- />
- </div>
-
- <!-- 涓棿涓� Tab -->
- <div class="flex flex-col items-center gap-2 flex-1 w-full">
- <div
- v-for="item in tabs"
- :key="item.name"
- class="flex items-center justify-center w-10 h-10 rounded-lg text-[#a0a0a0] cursor-pointer transition-all hover:text-white hover:bg-white/10"
- :class="{ 'bg-white/15 text-white': isActive(item.name) }"
- @click="goTab(item.name)"
- >
- <Badge
- v-if="item.name === 'ImHomeConversation' && totalUnread > 0"
- :count="totalUnread"
- :max="99"
- class="tool-bar__badge"
- >
- <Icon :icon="item.icon" class="tool-bar__icon" />
- </Badge>
- <Badge
- v-else-if="item.name === 'ImHomeContact' && unhandledRequestCount > 0"
- :count="unhandledRequestCount"
- :max="99"
- class="tool-bar__badge"
- >
- <Icon :icon="item.icon" class="tool-bar__icon" />
- </Badge>
- <Icon v-else :icon="item.icon" class="tool-bar__icon" />
- </div>
- </div>
-
- <!-- 搴曢儴璁剧疆鎸夐挳锛氱偣鍑昏烦涓汉涓績 -->
- <div class="flex flex-col items-center gap-2 w-full">
- <div
- class="flex items-center justify-center w-10 h-10 rounded-lg text-[#a0a0a0] cursor-pointer transition-all hover:text-white hover:bg-white/10"
- @click="goProfile"
- >
- <Icon icon="ant-design:setting-outlined" class="tool-bar__icon" />
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-.tool-bar__icon {
- width: 22px;
- height: 22px;
-}
-
-/* 璋冩暣 AntD Badge 鍙充笂瑙掔孩鐐逛綅缃� */
-.tool-bar__badge :deep(.ant-badge-count) {
- top: 4px;
- right: 8px;
- border: none;
-}
-</style>
diff --git a/src/views/im/home/components/user/index.ts b/src/views/im/home/components/user/index.ts
deleted file mode 100644
index d25750e..0000000
--- a/src/views/im/home/components/user/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { default as RecommendCardDialog } from './recommend-card-dialog.vue';
-export { default as UserAvatar } from './user-avatar.vue';
-export { default as UserInfoCard } from './user-info-card.vue';
-export { default as UserInfo } from './user-info.vue';
-export type { UserInfoRelation } from './user-info.vue';
diff --git a/src/views/im/home/components/user/recommend-card-dialog.vue b/src/views/im/home/components/user/recommend-card-dialog.vue
deleted file mode 100644
index 7516191..0000000
--- a/src/views/im/home/components/user/recommend-card-dialog.vue
+++ /dev/null
@@ -1,322 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation, FriendLite } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Button, Input, message, Modal } from 'ant-design-vue'
-
-import { createGroup } from '#/api/im/group'
-import { CardBubble } from '#/views/im/home/components/card'
-
-import { ImContentType, ImConversationType, isGroupConversation } from '../../../utils/constants'
-import { getConversationKey } from '../../../utils/conversation'
-import { buildDefaultGroupName } from '../../../utils/group'
-import { type CardTarget, serializeMessage } from '../../../utils/message'
-import { getGroupDisplayName, isGroupQuit } from '../../../utils/user'
-import { useMessageSender } from '../../composables/useMessageSender'
-import { FacePicker } from '../../pages/conversation/components/input'
-import { useConversationStore } from '../../store/conversationStore'
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import { ConversationPickerPanel } from '../picker'
-import { FriendPickerPanel } from '../picker'
-
-defineOptions({ name: 'ImRecommendCardDialog' })
-
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-const { sendRaw, send } = useMessageSender()
-
-const visible = ref(false)
-const target = ref<CardTarget | null>(null)
-const view = ref<'contact' | 'conversation'>('conversation') // 褰撳墠瑙嗗浘锛氶粯璁や細璇濋�夋嫨锛屻�屽垱寤鸿亰澶┿�嶅叆鍙e垏鍒板ソ鍙嬮�夋嫨
-const selectedKeys = ref<string[]>([])
-const selectedFriendIds = ref<number[]>([])
-const leaveMessage = ref('')
-const sending = ref(false)
-const emojiVisible = ref(false) // 琛ㄦ儏闈㈡澘鏄鹃殣锛氬彸渚� smile icon 鍒囨崲
-
-defineExpose({
- /** 鎵撳紑鎺ㄨ崘寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: { target: CardTarget }) {
- target.value = opts.target
- view.value = 'conversation'
- selectedKeys.value = []
- selectedFriendIds.value = []
- leaveMessage.value = ''
- emojiVisible.value = false
- sending.value = false
- visible.value = true
- }
-})
-
-/** 寮圭獥鏍囬锛氫細璇濊鍥炬寜 target 绫诲瀷鍒嗘枃妗堬紱濂藉弸瑙嗗浘鍥哄畾涓恒�岄�夋嫨濂藉弸銆� */
-const headerTitle = computed(() => {
- if (view.value === 'contact') {
- return '閫夋嫨濂藉弸'
- }
- return isGroupConversation(target.value?.targetType) ? '鎶婅繖涓兢鎺ㄨ崘缁欐湅鍙�' : '鎶婁粬鎺ㄨ崘缁欐湅鍙�'
-})
-
-/** 鍊欓�変細璇濓細浠� store 鎷挎帓搴忓悗鐨勫垪琛紙hide 鐢� Panel 鎺� hideKeys 杩囨护锛夛紱鍘嗗彶閫�缇ょ兢涓嶅彲琚帹鑽愰�変腑锛堥�変簡鍚庣涔熶細鎷掞級 */
-const candidateConversations = computed<Conversation[]>(() =>
- conversationStore.getSortedConversationList.filter(
- (conversation) =>
- !(
- conversation.type === ImConversationType.GROUP &&
- isGroupQuit(groupStore.getGroup(conversation.targetId))
- )
- )
-)
-
-/** 闅愯棌 key锛氫笉鑳芥妸鍚嶇墖鎺ㄥ洖鍚嶇墖鏈韩鐨勪細璇濓紙鐢ㄦ埛鍚嶇墖閬垮厤鑷帹銆佺兢鍚嶇墖閬垮厤鎺ㄥ洖璇ョ兢锛� */
-const hideKeys = computed<string[]>(() => {
- const t = target.value
- if (!t) {
- return []
- }
- return [getConversationKey({ type: t.targetType, targetId: t.targetId })]
-})
-
-/** 濂藉弸瑙嗗浘鍊欓�夊垪琛細鐩存帴澶嶇敤 friendStore Lite 瑙嗗浘 */
-const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
-
-/** 鎶婇�変腑鐨� emoji 鎷煎埌鐣欒█鏈熬锛汧acePicker 鑷韩璐熻矗鍏抽棴闈㈡澘 */
-function handleEmojiSelect(emoji: string) {
- leaveMessage.value = `${leaveMessage.value}${emoji}`
-}
-
-/** 鍒囧埌濂藉弸瑙嗗浘锛氭竻鎺変箣鍓嶅湪浼氳瘽瑙嗗浘杈撳叆鐨勭暀瑷�锛岄伩鍏嶅湪涓嶅彲瑙佽緭鍏ユ閲屾妸鐣欒█闈欓粯鍙戝埌鏂扮兢 */
-function handleSwitchToContact() {
- view.value = 'contact'
- leaveMessage.value = ''
- emojiVisible.value = false
-}
-
-/**
- * 纭鍙戦�侊紙浼氳瘽瑙嗗浘锛夛細姣忎釜閫変腑浼氳瘽鍏堝彂 CARD锛孋ARD 鎴愬姛鍚庢墠鍙戠暀瑷�锛堜繚璇併�屽厛鐪嬪埌鍚嶇墖銆嶇殑椤哄簭鎰忓浘锛�
- *
- * 鏂囨鑱氬悎锛氬叏閮ㄦ垚鍔熴�屽凡杞彂銆嶃�佸叏閮ㄥけ璐ャ�岃浆鍙戝け璐ワ細A銆丅銆嶃�侀儴鍒嗗け璐ャ�屽凡杞彂锛屼絾 X銆乊 澶辫触銆嶏紱
- * 澶辫触鐨勬秷鎭互 FAILED 鐘舵�佺暀鍦ㄥ搴斾細璇濇皵娉¢噷锛屽彲鍙抽敭閲嶈瘯
- */
-async function handleSend() {
- const card = target.value
- if (!card?.targetId || selectedKeys.value.length === 0) {
- return
- }
- const byKey = new Map(candidateConversations.value.map((c) => [getConversationKey(c), c]))
- const targets = selectedKeys.value
- .map((key) => byKey.get(key))
- .filter((c): c is Conversation => c != null)
- if (targets.length === 0) {
- return
- }
- const cardContent = serializeMessage({ ...card })
- const leaveText = leaveMessage.value.trim()
- sending.value = true
- try {
- const tasks = targets.map(async (conversation) => {
- const cardOk = await sendRaw(ImContentType.CARD, cardContent, { conversation })
- if (!cardOk) {
- return { conversation, ok: false }
- }
- const ok = leaveText ? await send(leaveText, { conversation }) : true
- return { conversation, ok }
- })
- const results = await Promise.all(tasks)
- const failedNames = results.filter((r) => !r.ok).map((r) => r.conversation.name || '鏈懡鍚嶄細璇�')
- // 鎶婂懡涓殑鐩爣鎺ㄥ埌鏈�杩戣浆鍙戝垪琛紙閮ㄥ垎澶辫触涔熸帹锛氱敤鎴风殑"鎰忓浘"宸茶〃杈撅級
- conversationStore.pushRecentForwardConversationKeyList(targets.map((c) => getConversationKey(c)))
- if (failedNames.length === 0) {
- message.success('宸茶浆鍙�')
- } else if (failedNames.length === targets.length) {
- message.error(`杞彂澶辫触锛�${failedNames.join('銆�')}`)
- } else {
- message.warning(`宸茶浆鍙戯紝浣� ${failedNames.join('銆�')} 澶辫触`)
- }
- visible.value = false
- } finally {
- sending.value = false
- }
-}
-
-/**
- * 濂藉弸瑙嗗浘鍙戦�侊細鍏堝缓缇わ紙鍚屾椂閭�璇锋墍閫夊ソ鍙嬶級鈫� 缁欐柊缇ゅ彂鍚嶇墖 鈫� 鍙戠暀瑷� 鈫� 鍏冲脊绐�
- *
- * 璺熶細璇濊鍥剧殑宸埆锛氬厛瑕� createGroup 鎷垮埌 groupId锛屼箣鍚庢瀯閫犱竴涓� GROUP 绫诲瀷鐨� conversation 瀵硅薄缁� sendRaw 鐢�
- * 锛坰endRaw 鍐呴儴浼氳嚜鍔� insertMessage 鎶婃柊缇ょ櫥璁拌繘 store锛屾渶杩戣浆鍙戝垪琛ㄤ篃鑳芥甯告帹锛�
- */
-async function handleCreateGroupAndSend() {
- const card = target.value
- if (!card?.targetId || selectedFriendIds.value.length === 0) {
- return
- }
- const byId = new Map(friends.value.map((f) => [f.id, f]))
- const members = selectedFriendIds.value
- .map((id) => byId.get(id))
- .filter((f): f is FriendLite => f != null)
- if (members.length === 0) {
- return
- }
- sending.value = true
- try {
- const memberUserIds = members.map((m) => m.id)
- const name = buildDefaultGroupName(members)
- const group = await createGroup({ name, memberUserIds, joinApproval: false })
- if (!group?.id) {
- throw new Error('鍒涘缓缇ゅけ璐ワ細鏈繑鍥炵兢缂栧彿')
- }
- // upsert 杩� groupStore锛岀渷涓�娆� fetchGroupList
- groupStore.upsertGroup({
- id: group.id,
- name: group.name,
- avatar: group.avatar,
- notice: group.notice,
- ownerUserId: group.ownerUserId
- })
- // 缁欐柊缇ゆ瀯閫犱竴涓复鏃� conversation 瀵硅薄缁� sendRaw 鐢紱sendRaw 鍐呴儴浼氳嚜鍔� insertMessage 鐧昏
- const newConversation: Conversation = {
- type: ImConversationType.GROUP,
- targetId: group.id,
- name: getGroupDisplayName(group) || name,
- avatar: group.avatar || '',
- unreadCount: 0,
- lastContent: '',
- lastSendTime: 0
- }
- const cardOk = await sendRaw(ImContentType.CARD, serializeMessage({ ...card }), {
- conversation: newConversation
- })
- if (!cardOk) {
- message.warning('缇ゅ凡鍒涘缓锛屼絾鍚嶇墖鍙戦�佸け璐ワ紝璇风◢鍚庡湪缇ら噷閲嶈瘯')
- visible.value = false
- return
- }
- const leaveText = leaveMessage.value.trim()
- if (leaveText) {
- await send(leaveText, { conversation: newConversation })
- }
- conversationStore.pushRecentForwardConversationKeyList([getConversationKey(newConversation)])
- message.success('宸插垱寤虹兢鑱婂苟鍙戦��')
- visible.value = false
- } finally {
- sending.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 鎶婂悕鐗囨帹鑽愮粰鏈嬪弸锛堢敤鎴� / 缇ら�氱敤锛�
- - dialog 澹崇敱鏈粍浠舵寔鏈夛紱閫夋嫨 UI 濮旀墭 ConversationPickerPanel / FriendPickerPanel
- - view='conversation'锛氶�夊凡鏈変細璇濆彂鍚嶇墖锛堥粯璁よ鍥撅級
- - view='contact'锛氫粠銆屽垱寤鸿亰澶┿�嶅叆鍙h繘鍏ワ紝閫夊ソ鍙嬪缓缇ゅ啀鍙戝悕鐗囷紝涓氬姟澹冲眰鍒囪鍥撅紝涓や釜 Panel 浜掍笉鐭ラ亾瀵规柟
- - 閫� 1 璧般�屽彂閫併�嶃�佸涓蛋銆屽垎鍒彂閫�(n)銆嶆枃妗堬紝涓庡井淇′竴鑷�
- - 澶辫触鐨勬秷鎭互 FAILED 鐘舵�佺暀鍦ㄥ搴斾細璇濇皵娉¢噷锛屼緵鍙抽敭閲嶈瘯
- - 瀵瑰鎺ュ彛锛歳ef + open({ target })锛屼笉鍐嶈蛋 v-model
- -->
- <Modal
- v-model:open="visible"
- width="720px"
- :mask-closable="false"
- :footer="view === 'conversation' ? null : undefined"
- class="im-picker-dialog im-recommend-dialog"
- >
- <template #header>
- <div class="flex gap-2 items-center">
- <Icon
- v-if="view === 'contact'"
- icon="ant-design:arrow-left-outlined"
- :size="16"
- class="cursor-pointer text-[var(--ant-color-text-secondary)] transition-colors duration-150 hover:text-[var(--ant-color-primary)]"
- @click="view = 'conversation'"
- />
- <span class="text-base text-[var(--ant-color-text)]">
- {{ headerTitle }}
- </span>
- </div>
- </template>
-
- <div class="h-[480px]">
- <!-- 浼氳瘽瑙嗗浘锛氶�夊凡鏈変細璇濆彂閫� -->
- <ConversationPickerPanel
- v-if="view === 'conversation'"
- v-model:selected-keys="selectedKeys"
- :conversations="candidateConversations"
- :recent-forward-conversation-keys="conversationStore.recentForwardConversationKeys"
- :hide-keys="hideKeys"
- :show-create-chat="true"
- @create-chat="handleSwitchToContact"
- @remove-recent="conversationStore.removeRecentForwardConversationKey"
- >
- <template #footer>
- <div class="flex flex-col gap-3 px-4 py-3">
- <!-- 鍚嶇墖棰勮鍗� -->
- <CardBubble v-if="target" :card="target" />
-
- <!-- 鐣欒█锛堝崟琛岋級锛氬彸渚ц〃鎯呮寜閽Е鍙� FacePicker锛涢�変腑 emoji 鎷煎埌鏈熬 -->
- <div class="relative">
- <Input v-model:value="leaveMessage" :maxlength="100" placeholder="缁欐湅鍙嬬暀瑷�">
- <template #suffix>
- <Icon
- icon="ant-design:smile-outlined"
- :size="18"
- class="cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-primary)]"
- @click.stop="emojiVisible = !emojiVisible"
- />
- </template>
- </Input>
- <FacePicker
- v-model:visible="emojiVisible"
- mode="emoji"
- class="bottom-full right-0 mb-2"
- @select-emoji="handleEmojiSelect"
- />
- </div>
-
- <!-- 鎿嶄綔鎸夐挳锛氶�� 0/1 鏄剧ず銆屽彂閫併�嶃�佸涓樉绀恒�屽垎鍒彂閫�(n)銆� -->
- <div class="flex gap-2 justify-end">
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="sending"
- :disabled="selectedKeys.length === 0"
- @click="handleSend"
- >
- {{ selectedKeys.length > 1 ? `鍒嗗埆鍙戦�侊紙${selectedKeys.length}锛塦 : '鍙戦��' }}
- </Button>
- </div>
- </div>
- </template>
- </ConversationPickerPanel>
-
- <!-- 濂藉弸瑙嗗浘锛氶�夊ソ鍙嬪缓缇ゅ悗鍙戦�� -->
- <FriendPickerPanel v-else v-model:selected-ids="selectedFriendIds" :friends="friends" />
- </div>
-
- <!-- 濂藉弸瑙嗗浘鐨� dialog footer锛氬缓缇ゅ苟鍙戦�� -->
- <template v-if="view === 'contact'" #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="sending"
- :disabled="selectedFriendIds.length === 0"
- @click="handleCreateGroupAndSend"
- >
- 鍒涘缓缇よ亰骞跺彂閫�
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-@use '../picker/picker-dialog' as picker;
-
-/* :deep 绌块�� el-dialog 鍐呴儴绫伙紱澶嶇敤 picker 鍏叡 mixin */
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/components/user/user-avatar.vue b/src/views/im/home/components/user/user-avatar.vue
deleted file mode 100644
index de5f633..0000000
--- a/src/views/im/home/components/user/user-avatar.vue
+++ /dev/null
@@ -1,134 +0,0 @@
-<script lang="ts" setup>
-import type { User } from '../../types'
-
-import { computed } from 'vue'
-
-import { Image } from 'ant-design-vue'
-
-import { ImFriendAddSource } from '../../../utils/constants'
-import { getAvatarBgColor, getAvatarText } from '../../../utils/user'
-import { useImUiStore } from '../../store/uiStore'
-
-defineOptions({ name: 'ImUserAvatar', inheritAttrs: false })
-
-const props = withDefaults(
- defineProps<{
- addSource?: number // 鍔犲ソ鍙嬫潵婧愶紱鐐瑰ご鍍忓脊 UserInfoCard 鏃堕�忎紶缁� FriendAddDialog锛堥粯璁� 1=鎼滅储锛�
- addSourceExtra?: string // 鍔犲ソ鍙嬫潵婧愰檮鍔狅細addSource=2锛堢兢鑱婏級鏃朵紶缇ゅ悕锛岃瘽鏈嫾涓恒�屾垜鏄� XX 缇ょ殑 YY銆�
- clickable?: boolean // 鏄惁鐐瑰嚮寮瑰嚭 UserInfoCard锛涢粯璁� true
- id?: number | string // 鐢ㄦ埛 id锛涗紶浜嗘墠鑳界偣寮�鍚嶇墖
- name?: string // 鐢ㄦ埛鍚嶏紙鑹插崱鏂囧瓧 + 鍚嶇墖澶囩敤锛�
- previewable?: boolean // 鏄惁鐐瑰ご鍍忕洿鎺ユ斁澶ч瑙堬紱寮�鍚悗蹇界暐 clickable锛屼笉鍐嶅脊鍚嶇墖
- previewZIndex?: number // 棰勮灞� z-index锛涙斁鍦ㄩ珮 z-index 寮瑰眰锛堝 UserInfoCard锛夐噷鏃堕渶鎵嬪姩鎶珮
- radius?: string // 鍦嗚锛屾敮鎸� CSS 闀垮害锛涢粯璁� 15% 鏂瑰潡灏忓渾瑙掞紙鍙傝�冨井淇★級
- size?: number // 灏哄锛坧x锛夛紝姝f柟褰紱浼樺厛浜� width/height
- url?: string // 澶村儚鍥剧墖 URL锛涗负绌烘椂璧拌壊鍗℃枃瀛�
- user?: User // 棰濆鐨勭敤鎴蜂俊鎭紝浼犱簡鐐瑰嚮灏变笉鐢ㄧ幇鎷夋帴鍙o紙寮瑰悕鐗囩敤锛�
- }>(),
- {
- addSourceExtra: undefined,
- clickable: true,
- id: undefined,
- name: undefined,
- previewable: false,
- previewZIndex: 2000,
- radius: '15%',
- size: 42,
- url: undefined,
- user: undefined,
- addSource: ImFriendAddSource.SEARCH
- }
-)
-
-const uiStore = useImUiStore()
-
-const imgStyle = computed(() => ({
- width: `${props.size}px`,
- height: `${props.size}px`,
- borderRadius: props.radius
-}))
-
-const textStyle = computed(() => ({
- width: `${props.size}px`,
- height: `${props.size}px`,
- fontSize: `${Math.floor(props.size * (avatarText.value.length > 1 ? 0.34 : 0.42))}px`,
- background: textColor.value,
- borderRadius: props.radius
-}))
-
-/** 鑹插崱棣栧瓧锛氫腑鏂囧彇 1 涓瓧銆佽嫳鏂� / 鎷変竵鍙栧墠 2 涓瓧姣� */
-const avatarText = computed(() => getAvatarText(props.name))
-
-/** 鑹插崱搴曡壊锛氭寜鏄电О charCode 鍝堝笇鍙栬皟鑹叉澘鑹� */
-const textColor = computed(() => getAvatarBgColor(props.name))
-
-/** 澶村儚鐐瑰嚮锛歱reviewable 璧� el-image 棰勮涓嶅脊鍚嶇墖锛涘惁鍒欐寜 user / id 浠讳竴鍏ュ弬鎵撳紑鍚嶇墖 */
-function handleClick(e: MouseEvent) {
- if (props.previewable) {
- return
- }
- if (!props.clickable) {
- return
- }
- // 鎯呭喌涓�锛氭湁棰勪紶 user 淇℃伅锛氬氨鐩存帴鐢紝鐪佷竴娆℃帴鍙�
- if (props.user) {
- uiStore.openUserInfoCardAtEvent(props.user, e, props.addSource, props.addSourceExtra)
- return
- }
- // 鎯呭喌浜岋細鏃犻浼� user 淇℃伅锛氭墦寮�鍚嶇墖锛屼紶鏈�灏忓繀瑕佷俊鎭紙id + 鏄电О + 澶村儚锛夛紝浣嶇疆鍦ㄩ紶鏍囧彸渚�
- const numId = Number(props.id)
- if (!numId || numId <= 0) {
- return
- }
- uiStore.openUserInfoCardAtEvent(
- {
- id: numId,
- nickname: props.name,
- avatar: props.url
- },
- e,
- props.addSource,
- props.addSourceExtra
- )
-}
-</script>
-
-<template>
- <!--
- 閫氱敤鐢ㄦ埛澶村儚缁勪欢
- - 鏈� url 鏃跺睍绀哄浘鐗囷紱鏃� url 鏃跺睍绀鸿壊鍗� + 棣栧瓧姣�/棣栧瓧
- - 鐐瑰嚮榛樿瑙﹀彂 UserInfoCard锛坈lickable锛�
- - previewable=true 鏃舵敼涓虹偣澶村儚鐩存帴鏀惧ぇ棰勮锛堢敤浜庡悕鐗� / 璇︽儏椤电瓑澶уご鍍忎綅锛�
- -->
- <div
- class="relative inline-flex"
- :style="{ cursor: clickable && !previewable ? 'pointer' : 'default' }"
- v-bind="$attrs"
- @click="handleClick"
- >
- <Image
- v-if="url && previewable"
- class="block overflow-hidden"
- :src="url"
- :preview="{ src: url, zIndex: previewZIndex }"
- :style="imgStyle"
- />
- <img
- v-else-if="url"
- class="block overflow-hidden object-cover"
- :src="url"
- :style="imgStyle"
- loading="lazy"
- :alt="name || 'avatar'"
- />
- <div
- v-else
- class="flex items-center justify-center text-white font-medium select-none"
- :style="textStyle"
- >
- {{ avatarText }}
- </div>
- <!-- 鍏佽澶栭儴鎻掑叆瑁呴グ锛屽缇よ亰瑙掓爣 -->
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/im/home/components/user/user-info-card.vue b/src/views/im/home/components/user/user-info-card.vue
deleted file mode 100644
index ec33489..0000000
--- a/src/views/im/home/components/user/user-info-card.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<script lang="ts" setup>
-import { computed, onMounted, onUnmounted } from 'vue'
-import { useRouter } from 'vue-router'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { ImConversationType } from '../../../utils/constants'
-import { getFriendDisplayName } from '../../../utils/user'
-import { useConversationStore } from '../../store/conversationStore'
-import { useFriendStore } from '../../store/friendStore'
-import { useImUiStore } from '../../store/uiStore'
-import UserInfo, { type UserInfoRelation } from './user-info.vue'
-
-defineOptions({ name: 'ImUserInfoCard' })
-
-const uiStore = useImUiStore()
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const router = useRouter()
-
-const card = computed(() => uiStore.userInfoCard)
-const user = computed(() => card.value.user)
-
-const isSelf = computed(() => {
- const myId = getCurrentUserId()
- return !!user.value?.id && user.value.id === myId
-})
-const isActiveFriend = computed(() => {
- if (!user.value?.id || isSelf.value) {
- return false
- }
- return friendStore.isActiveFriend(user.value.id)
-})
-const relation = computed<UserInfoRelation>(() => {
- if (!user.value) {
- return 'readonly'
- }
- if (isSelf.value) {
- return 'self'
- }
- if (isActiveFriend.value) {
- return 'friend'
- }
- return 'stranger'
-})
-
-const remark = computed(() => {
- if (!isActiveFriend.value || !user.value?.id) {
- return undefined
- }
- return friendStore.getFriend(user.value.id)?.displayName || ''
-})
-
-/** 鍏抽棴鍚嶇墖锛氱偣鍑婚伄缃� / Esc / UserInfo 鎶涗笂鏉ョ殑鍒犻櫎鎴愬姛浜嬩欢 */
-function handleClose() {
- uiStore.closeUserInfoCard()
-}
-
-/** 閿洏浜嬩欢锛欵sc 閿叧闂悕鐗� */
-function handleKeydown(e: KeyboardEvent) {
- if (e.key === 'Escape' && card.value.show) {
- handleClose()
- }
-}
-
-onMounted(() => window.addEventListener('keydown', handleKeydown))
-onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
-
-/** 鍙戞秷鎭細UserInfo 鎶涗笂鏉ョ殑"鍙戞秷鎭�"锛岀敱鏈粍浠舵帴鍔涘仛 openConversation + 璺虫秷鎭� Tab + 鍏虫诞灞� */
-function handleSendMessage() {
- if (!user.value) {
- return
- }
- // 鍙� friendStore 閲岀殑鏈�鏂板娉� / 鍏嶆墦鎵帮紝閬垮厤鏂板缓浼氳瘽鐢ㄨ繃鏈熸暟鎹�
- const friend = friendStore.getFriend(user.value.id)
- const conversationName = friend
- ? getFriendDisplayName(friend)
- : user.value.nickname || ''
- conversationStore.openConversation(
- user.value.id,
- ImConversationType.PRIVATE,
- conversationName,
- user.value.avatar || '',
- { silent: !!friend?.silent }
- )
- // 璺宠浆浼氳瘽 Tab锛堝鏋滀笉鍦ㄧ殑璇濓級锛屽苟涓斾笉绠″綋鍓嶈矾鐢辨槸鍟ラ兘鍏堝垏鍒颁細璇濆垪琛ㄩ〉
- if (router.currentRoute.value.name !== 'ImHomeConversation') {
- router.push({ name: 'ImHomeConversation' })
- }
- uiStore.closeUserInfoCard()
-}
-</script>
-
-<template>
- <!--
- 鐢ㄦ埛鍚嶇墖娴眰
- - 浠呮壙鎷�"娴眰瀹氫綅 + 鍏抽棴閫昏緫锛堢偣閬僵 / Esc锛�"锛屽悕鐗囪瑙夎蛋 <UserInfo>锛屼笌 contact 璇︽儏鍏辩敤涓�浠界粍浠�
- - 瑙﹀彂锛歶seImUiStore.openUserInfoCard(user, position)锛涙湰缁勪欢璁㈤槄 store锛屽叏灞�鍙寕涓�浠藉疄渚�
- - 鍏崇郴鎬佺敱 isSelf / isActiveFriend 娲剧敓 relation prop 閫忓埌 UserInfo锛涘垹闄� / 鍔犲ソ鍙� / 澶囨敞钀藉簱閮藉湪 UserInfo 鍐呴棴鐜�
- -->
- <teleport to="body">
- <div v-if="card.show" class="fixed inset-0 z-9998" @click.self="handleClose">
- <div
- class="fixed w-80 p-4 bg-[var(--ant-color-bg-elevated)] rounded-md shadow-xl"
- :style="{ left: `${card.position.x }px`, top: `${card.position.y }px` }"
- @click.stop
- >
- <UserInfo
- :user="user"
- :display-name="remark"
- :relation="relation"
- :preview-z-index="10000"
- :add-source="card.addSource"
- :add-source-extra="card.addSourceExtra"
- @chat="handleSendMessage"
- @deleted="handleClose"
- />
- </div>
- </div>
- </teleport>
-</template>
diff --git a/src/views/im/home/components/user/user-info.vue b/src/views/im/home/components/user/user-info.vue
deleted file mode 100644
index 9ffab3a..0000000
--- a/src/views/im/home/components/user/user-info.vue
+++ /dev/null
@@ -1,447 +0,0 @@
-<script lang="ts" setup>
-import type { User } from '../../types'
-
-import { computed, h, nextTick, ref, watch } from 'vue'
-
-import { confirm } from '../../../../../packages/effects/common-ui/src'
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Button, Checkbox, Dropdown, Input, Menu, message } from 'ant-design-vue'
-
-import { getSimpleUser, type SystemUserApi } from '#/api/system/user'
-import { formatDate } from '#/views/im/utils/time'
-
-import { ImFriendAddSource } from '../../../utils/constants'
-import { toUserCardTarget } from '../../../utils/message'
-import { getGenderColor, getGenderIcon } from '../../../utils/user'
-import { useFriendStore } from '../../store/friendStore'
-import { FriendAddDialog } from '../friend'
-import RecommendCardDialog from './recommend-card-dialog.vue'
-import UserAvatar from './user-avatar.vue'
-
-defineOptions({ name: 'ImUserInfo' })
-
-const props = withDefaults(
- defineProps<{
- /** 鍔犲ソ鍙嬫潵婧愶紱榛樿 SEARCH锛涘弬瑙� ImFriendAddSource */
- addSource?: number
- /** 鏉ユ簮闄勫甫淇℃伅锛歛ddSource=GROUP锛堢兢鑱婏級鏃朵紶缇ゅ悕锛岀敤浜庛�屾垜鏄� XX 缇ょ殑 YY銆嶉濉瘽鏈� */
- addSourceExtra?: string
- /** 澶囨敞锛氫粎 relation=friend 鏃舵湁鏁堬紱绌轰覆 鈫� 鏄剧ず"娣诲姞澶囨敞"鍗犱綅 */
- displayName?: string
- /** UserAvatar 棰勮灞� z-index锛涙斁鍦ㄩ珮 z-index 娴眰锛堝 UserInfoCard锛夐噷闇�鎵嬪姩鎶珮 */
- previewZIndex?: number
- relation?: UserInfoRelation
- /** 璧锋墜鐢ㄦ埛锛氳嚦灏戣鏈� id锛涙�у埆 / 閮ㄩ棬鐢辩粍浠舵寜 id 鎳掓媺鍚堝苟琛ラ綈 */
- user: null | User
- }>(),
- {
- addSource: ImFriendAddSource.SEARCH,
- addSourceExtra: '',
- displayName: '',
- previewZIndex: 2000,
- relation: 'readonly'
- }
-)
-
-const emit = defineEmits<{
- /** 鐢ㄦ埛鐐�"鍙戞秷鎭�"锛氬鑸� / 鍏虫诞灞傜瓑鍦烘櫙鐩稿叧鍔ㄤ綔鐢辩埗绾ф壙鎷咃紙姣斿 UserInfoCard 瑕� close锛� */
- chat: [user: User]
- /** 鍒犻櫎鑱旂郴浜烘垚鍔熷悗閫氱煡鐖剁骇锛坈onfirm + 璋� store 閮藉湪鏈粍浠跺唴鍋氬畬锛夛細鐖剁骇鍏虫诞灞� / 娓呴�変腑绛� */
- deleted: [user: User]
- /** 澶囨敞钀藉簱鎴愬姛鍚庨�氱煡鐖剁骇锛岀敤浜庡悓姝ョ埗绾ф寔鏈夌殑鏈湴 FriendLite / Friend 鍓湰锛堝 contact 椤电殑 selection锛� */
- saved: [value: string]
-}>()
-
-/**
- * 鍏崇郴鎬侊細鍐冲畾澶囨敞琛� / 鍙充笂 "..." 鑿滃崟 / 搴曢儴鍔ㄤ綔鍖虹殑鍐呭
- * - friend: 澶囨敞锛堝彲缂栬緫锛�+ "..." 鍒犻櫎鑿滃崟 + 3 鍥炬爣鍔ㄤ綔
- * - stranger: 鍗曟寜閽�"鍔犱负濂藉弸"
- * - self: 鍗曟寜閽�"涓嶈兘鍜岃嚜宸辫亰澶�" disabled
- * - readonly: 涓嶆覆鏌撳娉� / 鑿滃崟 / 鍔ㄤ綔鍖猴紝浠呭睍绀哄ご閮ㄤ俊鎭�
- */
-export type UserInfoRelation = 'friend' | 'readonly' | 'self' | 'stranger'
-
-const friendStore = useFriendStore()
-
-const full = ref<null | User>(props.user) // 璧锋墜 user + getSimpleUser 鍚堝苟鍚庣殑瀹屾暣瀵硅薄锛堟�у埆 / 閮ㄩ棬琛ラ綈鐢級
-
-/** 涓绘爣棰橈細澶囨敞浼樺厛锛堝ソ鍙嬪満鏅級锛屽叾娆″師鏄电О */
-const headerName = computed(() => props.displayName || full.value?.nickname || '')
-
-const deptText = computed(() => full.value?.deptName || '-')
-
-const genderIcon = computed(() => getGenderIcon(full.value?.sex))
-const genderColor = computed(() => getGenderColor(full.value?.sex))
-
-/** 濂藉弸鍏崇郴璁板綍锛氭潵婧� / 娣诲姞鏃堕棿 / 鏄惁鎷夐粦浠庤繖閲屽彇锛堜粎 friend 鎬佷笅鎵嶆湁鎰忎箟锛� */
-const friendInfo = computed(() =>
- props.user?.id ? friendStore.getFriend(props.user.id) : undefined
-)
-
-/** 鏄惁宸叉媺榛戯細鑿滃崟椤广�屽姞鍏ラ粦鍚嶅崟 / 绉诲嚭榛戝悕鍗曘�嶆寜杩欎釜鍒囨崲 */
-const isBlocked = computed(() => !!friendInfo.value?.blocked)
-
-const editingRemark = ref(false) // 澶囨敞鍐呰仈缂栬緫锛歟ditingRemark 鎺у埗杈撳叆鎬侊紱user 鍒囨崲鏃剁敱涓嬮潰鐨� watch 澶嶄綅閬垮厤鑴忔�佹硠婕�
-const remarkInput = ref('')
-const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(null)
-
-/**
- * user.id 鍙樺寲鐨勭粺涓�澶勭悊锛�
- * 1. 璧锋墜鐢� prop 鍏滃簳棣栧睆锛坒ull = props.user锛夛紝鍐� getSimpleUser 鍛戒腑鍚庡悎骞舵浛鎹�
- * 2. 椤轰究澶嶄綅澶囨敞缂栬緫鎬侊紝閬垮厤涓婁竴涓敤鎴风殑鑴忚緭鍏ユ硠婕忓埌涓嬩竴涓�
- * 3. 绔炴�佺敤 id 姣斿涓㈠純闄堟棫鍝嶅簲
- */
-watch(
- () => props.user?.id,
- async (id) => {
- full.value = props.user
- editingRemark.value = false
- if (!id) {
- return
- }
- const data = (await getSimpleUser(id)) as User
- if (props.user?.id !== id) {
- return
- }
- full.value = { ...props.user, ...data }
- },
- { immediate: true }
-)
-
-/** 澶囨敞琛岀偣鍑伙細杩涚紪杈戞�� + 鎶婂綋鍓嶅娉ㄧ亴杩涜緭鍏ユ锛屼笅涓�甯ф妸鐒︾偣 / 鍏ㄩ�変氦缁� el-input */
-async function handleRowClick() {
- if (editingRemark.value) {
- return
- }
- remarkInput.value = props.displayName || ''
- editingRemark.value = true
- await nextTick()
- remarkInputRef.value?.focus()
- remarkInputRef.value?.select?.()
-}
-
-/**
- * 淇濆瓨澶囨敞锛�
- * 1. 閲嶅叆淇濇姢鈥斺�攂lur + Enter 鍚屾椂瑙﹀彂鍙蛋涓�娆★紙缂栬緫鎬佸厛澶嶄綅锛�
- * 2. 鏃犲彉鍖栬烦杩囧悗绔皟鐢� + 涓嶆姏 saved锛岄伩鍏嶇埗绾ц鍚屾
- * 3. 钀藉簱鎴愬姛 / 澶辫触閮藉湪缁勪欢鍐呰嚜闂細鎴愬姛鎶� saved 璁╃埗绾у悓姝ユ湰鍦板壇鏈紝澶辫触鐣欑紪杈戞�佸鐨勫睍绀哄�间笉鍔�
- */
-async function saveRemark() {
- if (!editingRemark.value) {
- return
- }
- const userId = props.user?.id
- if (!userId) {
- editingRemark.value = false
- return
- }
- const next = remarkInput.value.trim()
- editingRemark.value = false
- if (next === (props.displayName || '')) {
- return
- }
- await friendStore.setFriendDisplayName(userId, next)
- message.success('宸叉洿鏂板娉�')
- emit('saved', next)
-}
-
-function cancelEditRemark() {
- editingRemark.value = false
-}
-
-/** 鍙戞秷鎭細瀵艰埅 / 鍏虫诞灞傝繖浜�"涓氬姟渚�"鍔ㄤ綔浜ょ粰鐖剁骇锛屾湰缁勪欢鍙礋璐i�氱煡 */
-function handleChat() {
- if (!props.user) {
- return
- }
- emit('chat', props.user)
-}
-
-/** 鍗犱綅鎻愮ず锛氳闊� / 瑙嗛鑱婂ぉ鑳藉姏灏氭湭鎺ュ叆锛屽厛浠�"寮�鍙戜腑"鍙嬪ソ鎻愮ず */
-function handleComingSoon(featureName: string) {
- message.info(`${featureName} 鍔熻兘寮�鍙戜腑`)
-}
-
-// ==================== 娣诲姞濂藉弸 / 鍒犻櫎濂藉弸 ====================
-
-const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 鍔犲ソ鍙嬪脊绐� ref锛歨andleAddFriend 璋� open({ presetUser, addSource, addSourceExtra }) 瑙﹀彂
-
-const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 鎺ㄨ崘鍚嶇墖寮圭獥 ref锛歨andleRecommend 璋冪敤 open({ target }) 鎵撳紑
-/** 鎶婁粬鎺ㄨ崘缁欐湅鍙嬶細寮� RecommendCardDialog 閫夌洰鏍囦細璇� */
-function handleRecommend() {
- if (!props.user?.id) {
- return
- }
- const target = toUserCardTarget(full.value)
- if (!target) {
- return
- }
- recommendDialogRef.value?.open({ target })
-}
-
-/** 鍔犱负濂藉弸锛氬脊 FriendAddDialog锛堝甫棰勫~鐢ㄦ埛锛夛紝璁╃敤鎴峰~鐢宠鐞嗙敱 + 澶囨敞鍚庡啀鍙戠敵璇� */
-function handleAddFriend() {
- if (!props.user?.id) {
- return
- }
- const presetUser: SystemUserApi.UserSimple = {
- id: props.user.id,
- nickname: props.user.nickname,
- avatar: props.user.avatar,
- sex: props.user.sex,
- deptId: props.user.deptId,
- deptName: props.user.deptName
- } as SystemUserApi.UserSimple
- friendAddDialogRef.value?.open({
- presetUser,
- addSource: props.addSource,
- addSourceExtra: props.addSourceExtra
- })
-}
-
-/** 鍔犲叆榛戝悕鍗曪細confirm 鈫� friendStore.blockFriend锛涘悗绔� FRIEND_BLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍚屾澶氱 */
-async function handleBlock() {
- if (!props.user?.id) {
- return
- }
- const target = props.user
- try {
- await confirm(`纭畾灏嗐��${target.nickname || ''}銆嶅姞鍏ラ粦鍚嶅崟鍚楋紵`, '鍔犲叆榛戝悕鍗�')
- } catch {
- return
- }
- await friendStore.blockFriend(target.id)
- message.success('宸插姞鍏ラ粦鍚嶅崟')
-}
-
-/** 绉诲嚭榛戝悕鍗曪細鎿嶄綔娓╁拰涓嶅脊 confirm锛涘悗绔� FRIEND_UNBLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍚屾澶氱 */
-async function handleUnblock() {
- if (!props.user?.id) {
- return
- }
- await friendStore.unblockFriend(props.user.id)
- message.success('宸茬Щ鍑洪粦鍚嶅崟')
-}
-
-/** 鍒犻櫎鑱旂郴浜猴細寮硅嚜瀹氫箟纭锛堝惈銆屽悓鏃舵竻绌鸿亰澶╄褰曘�嶉�夐」锛夆啋 friendStore.deleteFriend 鈫� 閫氱煡鐖剁骇鍏虫诞灞� / 娓呴�変腑 */
-async function handleDeleteFriend() {
- if (!props.user?.id) {
- return
- }
- const target = props.user
- const clearConversation = ref(true)
- try {
- await confirm({
- cancelText: '鍙栨秷',
- confirmText: '鍒犻櫎',
- content: h('div', { class: 'flex flex-col gap-3 text-sm' }, [
- h('div', `纭畾鍒犻櫎濂藉弸銆�${target.nickname || ''}銆�?`),
- h(
- Checkbox,
- {
- checked: clearConversation.value,
- 'onUpdate:checked': (value: boolean) => {
- clearConversation.value = value
- }
- },
- () => '鍚屾椂娓呯┖鑱婂ぉ璁板綍'
- )
- ]),
- icon: 'warning',
- title: '鍒犻櫎鑱旂郴浜�'
- })
- } catch {
- return
- }
- await friendStore.deleteFriend(target.id, clearConversation.value)
- message.success('宸插垹闄ゅソ鍙�')
- emit('deleted', target)
-}
-</script>
-
-<template>
- <!--
- 鐢ㄦ埛鍚嶇墖鑷寘鍚粍浠讹紙娴眰 / 琛屽唴閫氱敤锛�
- - 鐢� UserInfoCard锛堟诞灞傦級鍜� contact/index.vue锛堣鍐咃級鍏辩敤锛歎serInfoCard 鎶婂畠鏀捐繘 teleport 娴眰锛宑ontact 鐩存帴 mount 鍒板彸鏍�
- - 鍏崇郴鎬佺敱 relation prop 鍐冲畾锛坒riend / stranger / self / readonly锛夛紝瀵瑰簲鍙充笂 "..." 鑿滃崟 + 搴曢儴鍔ㄤ綔鍖轰袱鍧楅兘鍐呭寲鍦ㄦ湰缁勪欢
- - 澶囨敞 / 鍒犻櫎鑱旂郴浜� / 鍔犱负濂藉弸绛� store 鎿嶄綔閮藉湪鏈粍浠跺唴闂幆锛岀埗绾т粎鐩戝惉 chat / deleted / saved 绛夐�氱煡鍋氬鑸� / 鍏虫诞灞� / 鍚屾鍓湰
- -->
- <div>
- <!-- 椤堕儴锛氬ご鍍忥紙宸︼級 + 鍚嶅瓧 + 鎬у埆鍥炬爣 + 鏄电О / 閮ㄩ棬锛堝彸锛� + 鍙充笂 "..." 鑿滃崟锛堜粎 friend 鎬侊級 -->
- <div class="flex gap-3 items-start">
- <UserAvatar
- :id="full?.id"
- :url="full?.avatar"
- :name="full?.nickname"
- :size="56"
- :clickable="false"
- previewable
- :preview-z-index="previewZIndex"
- />
- <div class="flex-1 min-w-0">
- <div class="flex items-center gap-1.5">
- <span
- class="text-lg font-semibold leading-snug truncate text-[var(--ant-color-text)]"
- >
- {{ headerName }}
- </span>
- <!-- 鎬у埆灏忓浘鏍囷細鐢疯摑銆佸コ绮夛紝鏈煡 / 0 涓嶅睍绀猴紝瀵归綈寰俊鐣欑櫧鍋氭硶 -->
- <Icon
- v-if="genderIcon"
- :icon="genderIcon"
- :size="16"
- :color="genderColor"
- class="flex-shrink-0"
- />
- </div>
- <div class="mt-2 space-y-1 text-13px text-[var(--ant-color-text-secondary)]">
- <!-- 浠呭綋澶囨敞宸茶鏃跺睍绀烘樀绉板壇琛岋紱鏈缃椂涓绘爣棰樺氨鏄� nickname锛岄伩鍏嶉噸澶� -->
- <div v-if="displayName" class="truncate">鏄电О锛歿{ full?.nickname }}</div>
- <div class="truncate">閮ㄩ棬锛歿{ deptText }}</div>
- </div>
- </div>
- <!-- 鍙充笂 "..." 鑿滃崟锛氫粎 friend 鎬佸睍绀猴紱鍚�屽姞鍏�/绉诲嚭榛戝悕鍗曘�� + 銆屽垹闄よ仈绯讳汉銆� -->
- <div v-if="relation === 'friend'" class="flex-shrink-0">
- <Dropdown :trigger="['click']" placement="bottomRight" overlay-class-name="im-user-info__more-menu">
- <div
- class="flex items-center justify-center w-7 h-7 rounded cursor-pointer hover:bg-[var(--ant-color-fill-secondary)]"
- >
- <Icon icon="ep:more-filled" :size="18" class="text-[var(--ant-color-text-secondary)]" />
- </div>
- <template #overlay>
- <Menu>
- <!-- 鎶婁粬鎺ㄨ崘缁欐湅鍙嬶細浠ヤ釜浜哄悕鐗囨秷鎭舰寮忓彂鍒伴�変腑鐨勪細璇� -->
- <Menu.Item @click="handleRecommend">鎶婁粬鎺ㄨ崘缁欐湅鍙�</Menu.Item>
- <!-- 鎷夐粦 / 绉诲嚭榛戝悕鍗曪細鎸� friendInfo.blocked 鍒囨崲鏂囨 -->
- <Menu.Item v-if="!isBlocked" divided @click="handleBlock">
-鍔犲叆榛戝悕鍗�
-</Menu.Item>
- <Menu.Item v-else divided @click="handleUnblock">绉诲嚭榛戝悕鍗�</Menu.Item>
- <Menu.Item divided @click="handleDeleteFriend">
- <span class="text-[var(--ant-color-error)]">鍒犻櫎鑱旂郴浜�</span>
- </Menu.Item>
- </Menu>
- </template>
- </Dropdown>
- </div>
- </div>
-
- <!-- 澶囨敞琛岋細浠� friend 鎬佸睍绀猴紝鏈紪杈戞椂鏁磋鍙偣锛涚紪杈戞�佺敤 el-input 琛屽唴鏇挎崲 value 鍒� -->
- <template v-if="relation === 'friend'">
- <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
- <div
- class="flex gap-5 items-center px-1.5 py-1.5 text-sm"
- :class="
- !editingRemark
- ? 'group cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill-tertiary)]'
- : ''
- "
- @click="handleRowClick"
- >
- <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">澶囨敞</span>
- <Input
- v-if="editingRemark"
- ref="remarkInputRef"
- v-model:value="remarkInput"
- size="small"
- :maxlength="20"
- placeholder="娣诲姞澶囨敞"
- class="flex-1"
- @click.stop
- @blur="saveRemark"
- @keyup.enter="saveRemark"
- @keyup.esc="cancelEditRemark"
- />
- <template v-else>
- <span
- class="flex-1 min-w-0 truncate"
- :class="
- displayName
- ? 'text-[var(--ant-color-text)]'
- : 'text-[var(--ant-color-text-placeholder)]'
- "
- >
- {{ displayName || '娣诲姞澶囨敞' }}
- </span>
- <!-- 榛樿 opacity:0 鍗犱綅閬垮厤 hover 鏃跺叾瀹冨垪浣嶇Щ锛涗粎鐖惰 hover 鏃舵诞鐜� -->
- <Icon
- icon="ant-design:edit-outlined"
- :size="14"
- class="flex-shrink-0 text-[var(--ant-color-text-secondary)] opacity-0 group-hover:opacity-100 transition-opacity"
- />
- </template>
- </div>
-
- <!-- ==================== 鏇村淇℃伅锛氭潵婧� / 娣诲姞鏃堕棿锛屽榻愬井淇°�屾湅鍙嬭祫鏂欍�嶅垎鍧� ==================== -->
- <template v-if="friendInfo?.addSource || friendInfo?.addTime">
- <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
- <div v-if="friendInfo?.addSource" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
- <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">鏉ユ簮</span>
- <span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
- {{ getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, friendInfo.addSource) }}
- </span>
- </div>
- <div v-if="friendInfo?.addTime" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
- <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">娣诲姞鏃堕棿</span>
- <span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
- {{ formatDate(new Date(friendInfo.addTime), 'YYYY-MM-DD') }}
- </span>
- </div>
- </template>
- </template>
-
- <!-- 鍔ㄤ綔鍖猴細濂藉弸 = 3 鍥炬爣锛涢檶鐢熶汉 = 鍔犱负濂藉弸鎸夐挳锛涜嚜宸� = disabled锛況eadonly 涓嶆覆鏌� -->
- <template v-if="relation !== 'readonly'">
- <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
- <div v-if="relation === 'friend'" class="flex justify-around">
- <!-- 涓夎繛鍥炬爣鎸夐挳锛氬浘涓婂瓧涓嬨�佷富鑹蹭綔涓哄彲鐐规殫绀猴紝hover 闄嶄笉閫忔槑搴﹀仛鍙嶉 -->
- <div
- class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
- @click="handleChat"
- >
- <Icon icon="ant-design:message-outlined" :size="22" />
- <span>鍙戞秷鎭�</span>
- </div>
- <div
- class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
- @click="handleComingSoon('璇煶鑱婂ぉ')"
- >
- <Icon icon="ant-design:phone-outlined" :size="22" />
- <span>璇煶鑱婂ぉ</span>
- </div>
- <div
- class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
- @click="handleComingSoon('瑙嗛鑱婂ぉ')"
- >
- <Icon icon="ant-design:video-camera-outlined" :size="22" />
- <span>瑙嗛鑱婂ぉ</span>
- </div>
- </div>
- <div v-else-if="relation === 'self'" class="flex justify-center">
- <Button type="primary" disabled>涓嶈兘鍜岃嚜宸辫亰澶�</Button>
- </div>
- <div v-else class="flex justify-center">
- <Button type="primary" @click="handleAddFriend">鍔犱负濂藉弸</Button>
- </div>
- </template>
-
- <!-- 鍔犲ソ鍙嬪脊绐楋細鎼哄甫棰勫~鐢ㄦ埛璺宠繃鎼滅储姝ラ锛岀洿鎺ヨ繘鐢宠琛ㄥ崟锛堢悊鐢辨寜 addSource 鍖哄垎璇濇湳锛� -->
- <FriendAddDialog ref="friendAddDialogRef" />
-
- <!-- 鎶婁粬鎺ㄨ崘缁欐湅鍙嬪脊绐楋細浠� friend 鎬佷笅鍑虹幇鍏ュ彛 -->
- <RecommendCardDialog ref="recommendDialogRef" />
- </div>
-</template>
-
-<style>
-/*
- 闈� scoped锛歟l-dropdown 鐨勪笅鎷夎彍鍗曡蛋 teleport 鍒� body锛宻coped 閫変笉鍒般��
- UserInfoCard 娴眰鐢� z-9998锛岃鎶婅繖鍧楁姮鍒版洿楂橈紙榛樿 --ant-z-index-popup-base ~2050 浼氳閬僵鍘嬩綇锛夈��
-*/
-.im-user-info__more-menu {
- z-index: 10000 !important;
-}
-</style>
diff --git a/src/views/im/home/composables/useFriendBuckets.ts b/src/views/im/home/composables/useFriendBuckets.ts
deleted file mode 100644
index 9b2ad1a..0000000
--- a/src/views/im/home/composables/useFriendBuckets.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { FriendLite } from '../types'
-
-import { computed, type ComputedRef, type Ref } from 'vue'
-
-/** 瀛楁瘝鍒嗘《缁撴灉锛歭etter 鍙� 'A'-'Z' 鎴栧厹搴� '#'锛沴ist 妗跺唴鎸夋嫾闊� / 鍚嶅瓧鑷劧搴� */
-export interface FriendBucket {
- letter: string
- list: FriendLite[]
-}
-
-/** 鍙栧垎妗� / 鎺掑簭閿細澶囨敞鎷奸煶浼樺厛 鈫� 鏄电О鎷奸煶 鈫� 鍚嶅瓧鏈韩锛堝厹搴曡嫳鏂� / 鏁板瓧锛� */
-function getSortKey(friend: FriendLite): string {
- return (
- friend.displayNamePinyin ||
- friend.nicknamePinyin ||
- (friend.displayName || friend.nickname || '').toLowerCase()
- )
-}
-
-/** 鍙栧垎妗跺瓧姣嶏細鎷奸煶棣栧瓧姣嶅ぇ鍐欙紝闈炲瓧姣嶏紙绾鍙� / 鏁板瓧 / 涓枃锛夊厹搴� '#' */
-function getBucketLetter(friend: FriendLite): string {
- const first = getSortKey(friend).charAt(0)
- return /^[a-zA-Z]$/.test(first) ? first.toUpperCase() : '#'
-}
-
-/** 鎷奸煶棣栧瓧姣嶆嫾鎺ワ細銆宭ao zhang銆嶁啋 'lz'锛屾敮鎸併�岃緭 lz 鎼滆�佸紶銆� */
-function pinyinInitials(pinyin?: string): string {
- if (!pinyin) {
- return ''
- }
- return pinyin
- .split(' ')
- .map((word) => word.charAt(0))
- .join('')
-}
-
-/**
- * 濂藉弸鍒楄〃鐨勬悳绱� + 瀛楁瘝鍒嗘《
- *
- * - 鎼滅储鍖归厤锛氬娉� / 鏄电О / 鍏ㄦ嫾锛堝幓绌烘牸锛�/ 棣栧瓧姣嶄换涓�鍛戒腑
- * - 鍒嗘《瑙勫垯锛欰-Z + '#' 鍏滃簳锛屾《鍐呮寜 getSortKey 鑷劧搴�
- *
- * 閫氳褰曢〉 FriendList 涓庨�夋嫨绫诲脊绐� FriendPickerPanel 鍏辩敤锛岄伩鍏嶈鍒欏悇鑷疄鐜拌蛋鍋�
- */
-export function useFriendBuckets(
- friends: ComputedRef<FriendLite[]> | Ref<FriendLite[]>,
- keyword: Ref<string>
-): {
- buckets: ComputedRef<FriendBucket[]>
- filtered: ComputedRef<FriendLite[]>
-} {
- const filtered = computed(() => {
- const keywordLower = keyword.value.trim().toLowerCase()
- if (!keywordLower) {
- return friends.value
- }
- return friends.value.filter((friend) => {
- const nicknamePinyin = friend.nicknamePinyin || ''
- const displayNamePinyin = friend.displayNamePinyin || ''
- // 鍏ㄦ嫾鎼滅储鍘绘帀绌烘牸锛岃銆宭aozhang銆嶄篃鑳藉懡涓�宭ao zhang銆�
- return (
- (friend.nickname || '').toLowerCase().includes(keywordLower) ||
- (friend.displayName || '').toLowerCase().includes(keywordLower) ||
- nicknamePinyin.replaceAll(/\s/g, '').includes(keywordLower) ||
- displayNamePinyin.replaceAll(/\s/g, '').includes(keywordLower) ||
- pinyinInitials(nicknamePinyin).includes(keywordLower) ||
- pinyinInitials(displayNamePinyin).includes(keywordLower)
- )
- })
- })
-
- const buckets = computed<FriendBucket[]>(() => {
- const map = new Map<string, FriendLite[]>()
- for (const friend of filtered.value) {
- const letter = getBucketLetter(friend)
- const bucket = map.get(letter) ?? []
- bucket.push(friend)
- map.set(letter, bucket)
- }
- const letters = [...map.keys()].toSorted((a, b) => {
- // '#' 姘歌繙鎺掓湯灏撅紝A-Z 璧� localeCompare
- if (a === '#') {
- return 1
- }
- if (b === '#') {
- return -1
- }
- return a.localeCompare(b)
- })
- return letters.map((letter) => ({
- letter,
- list: (map.get(letter) ?? []).toSorted((a, b) =>
- getSortKey(a).localeCompare(getSortKey(b))
- )
- }))
- })
-
- return { filtered, buckets }
-}
diff --git a/src/views/im/home/composables/useGroupCallMembers.ts b/src/views/im/home/composables/useGroupCallMembers.ts
deleted file mode 100644
index b6fb416..0000000
--- a/src/views/im/home/composables/useGroupCallMembers.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { computed, type ComputedRef, type Ref } from 'vue'
-
-import { ImConversationType } from '../../utils/constants'
-import { getSenderAvatar, getSenderDisplayName } from '../../utils/user'
-import { useRtcStore } from '../store/rtcStore'
-
-/** 缇ら�氳瘽鎴愬憳瑙嗗浘妯″瀷锛氬凡鍔犲叆 + 鎺ュ叆涓紱pending 澶村儚 UI 鍗婇�忔槑锛宩oined 涓嶉�忔槑 */
-export interface GroupCallMember {
- userId: number
- nickname: string
- avatar?: string
- pending: boolean
-}
-
-/**
- * 缇ら�氳瘽鎴愬憳鍒楄〃 computed锛歫oined 鍦ㄥ墠锛屾湭 joined 鐨� invitee 鍦ㄥ悗锛�
- * 缂撳瓨涓虹┖锛堟潵鐢甸灞忔覆鏌撴棭浜� syncGroupActiveCall 鍥炲啓锛夋椂鐢� fallback 涓诲彨鍏滃簳锛岄伩鍏嶇┖鐧�
- *
- * @param groupId 缇ょ紪鍙�
- * @param fallbackInviterId 鍏滃簳涓诲彨锛涚紦瀛樹负绌烘椂濉竴涓ご鍍忥紝鏍囪涓哄凡鍔犲叆鑰岄潪 pending
- */
-export function useGroupCallMembers(
- groupId: Ref<number | undefined>,
- fallbackInviterId?: Ref<number | undefined>
-): ComputedRef<GroupCallMember[]> {
- const rtcStore = useRtcStore()
- return computed(() => {
- const gid = groupId.value
- if (!gid) {
- return []
- }
- const groupCall = rtcStore.getGroupCall(gid)
- const joinedIds = groupCall?.joinedUserIds ?? []
- const inviteeIds = groupCall?.inviteeIds ?? []
- const joinedSet = new Set(joinedIds)
- const orderedIds = [...joinedIds, ...inviteeIds.filter((id) => !joinedSet.has(id))]
- if (orderedIds.length > 0) {
- return orderedIds.map((userId) => toVM(userId, gid, !joinedSet.has(userId)))
- }
- const fallback = fallbackInviterId?.value
- return fallback ? [toVM(fallback, gid, false)] : []
- })
-}
-
-/** 鎶� userId 缈昏瘧鎴愯鍥炬ā鍨嬶紝缁熶竴璧� user.ts helper 瑙f瀽鏄电О / 澶村儚 */
-function toVM(userId: number, groupId: number, pending: boolean): GroupCallMember {
- return {
- userId,
- nickname: getSenderDisplayName(userId, ImConversationType.GROUP, groupId),
- avatar: getSenderAvatar(userId, ImConversationType.GROUP, groupId) || undefined,
- pending
- }
-}
diff --git a/src/views/im/home/composables/useLiveKitRoom.ts b/src/views/im/home/composables/useLiveKitRoom.ts
deleted file mode 100644
index 7cc0a5a..0000000
--- a/src/views/im/home/composables/useLiveKitRoom.ts
+++ /dev/null
@@ -1,331 +0,0 @@
-import { computed, ref, shallowRef } from 'vue'
-
-import {
- ConnectionQuality,
- type LocalParticipant,
- type Participant,
- type RemoteParticipant,
- Room,
- RoomEvent,
- Track,
- VideoPresets
-} from 'livekit-client'
-
-type ParticipantEventHandler = (userId: number) => void
-
-/** LiveKit Room 杩炴帴 / 璁惧 / 浜嬩欢鐨勮杽灏佽锛沀I 缁勪欢鍙叧蹇冨搷搴斿紡鐘舵�� */
-export function useLiveKitRoom() {
- /** Room 瀹炰緥锛涙ā鍧楀唴閮ㄧ姸鎬侊紝涓嶅澶栨毚闇诧紝閬垮厤璋冪敤鏂硅鍐� */
- const _room = shallowRef<null | Room>(null)
- /** 鍙 room 寮曠敤锛涜皟鐢ㄦ柟浠呯敤浜庡箓绛夊垽瀹� */
- const room = computed(() => _room.value)
- /** 鏈湴鍙備笌鑰咃紱杩炴帴鎴愬姛鍚庤祴鍊� */
- const localParticipant = shallowRef<LocalParticipant | null>(null)
- /** 杩滅鍙備笌鑰呭垪琛紱ParticipantConnected / Disconnected 鏃跺埛鏂帮紱shallowRef 閬垮厤 Vue 娣卞害浠g悊 SDK class 鍐呴儴 */
- const remoteParticipants = shallowRef<RemoteParticipant[]>([])
- /** 杩炴帴鐘舵�� */
- const isConnected = ref(false)
- /** 杩炴帴璐ㄩ噺 */
- const connectionQuality = ref<ConnectionQuality>(ConnectionQuality.Unknown)
- /** 楹﹀厠椋庡紑鍏� */
- const micEnabled = ref(true)
- /** 鎽勫儚澶村紑鍏� */
- const cameraEnabled = ref(false)
- /** 鎵0鍣ㄥ紑鍏筹紱娴忚鍣ㄦ棤绯荤粺绾� API锛岄�氳繃 audio 鍏冪礌 muted 灞炴�у疄鐜拌繙绔煶棰戦潤闊� */
- const speakerEnabled = ref(true)
- /** 灞忓箷鍏变韩寮�鍏� */
- const screenShareEnabled = ref(false)
- /** 褰撳墠鏄惁澶勪簬銆岄噸杩炰腑銆嶏紱鐬柇鏃� UI 鏄剧ず鎻愮ず鑰屼笉寮哄埗缁撴潫閫氳瘽 */
- const reconnecting = ref(false)
- /** 杩滅鏂紑璁㈤槄鑰咃紱閫氳瘽缁撴潫鏃剁粺涓�娓呯┖ */
- const disconnectedHandlers = new Set<() => void>()
- /** 鎴垮唴鏌愪汉鍔犲叆璁㈤槄鑰咃紱涓诲彨绔敤浜庝粠 INVITING 鍒囧埌 RUNNING */
- const participantConnectedHandlers = new Set<ParticipantEventHandler>()
- /** 鎴垮唴鏌愪汉绂诲紑璁㈤槄鑰咃紱鐢ㄤ簬鎶� userId 鏍囪涓恒�屽凡閫�鍑恒�嶄粠 pending 鍗犱綅涓Щ闄� */
- const participantDisconnectedHandlers = new Set<ParticipantEventHandler>()
-
- /** 鍚屾杩滅鍙備笌鑰呭垪琛ㄥ埌鍝嶅簲寮忔暟缁� */
- function syncRemotes(r: Room) {
- remoteParticipants.value = [...r.remoteParticipants.values()]
- }
-
- /** 杩炴帴 LiveKit Server锛沘udio / video 鎺у埗鍒濆榛樿寮�鍏� */
- async function connect(url: string, token: string, opts: { audio?: boolean; video?: boolean }) {
- // 鏂拌繛鎺ュ墠鍏堟柇寮�鏃� Room锛涗繚鐣欐湰娆℃敞鍐岀殑浜嬩欢鍥炶皟
- if (_room.value) {
- await disconnectRoom(false)
- }
- const r = new Room({
- // 鎸夋牸瀛愬昂瀵歌嚜鍔ㄩ�� simulcast 灞�
- adaptiveStream: true,
- // 鏈闃呯殑灞傚姩鎬佸仠鍙戯紝鑺傜渷涓婅
- dynacast: true,
- // 閲囬泦鍒嗚鲸鐜� 720p锛岀‘淇濆ぇ鏍煎瓙娓呮櫚
- videoCaptureDefaults: {
- resolution: VideoPresets.h720.resolution
- },
- // 鍙戝竷缂栫爜涓婇檺 1.5 Mbps / 30fps锛涗繚鐣欓粯璁� simulcast 涓夊眰锛�180p / 360p / 720p锛�
- publishDefaults: {
- videoEncoding: {
- maxBitrate: 1_500_000,
- maxFramerate: 30,
- priority: 'high'
- },
- // 灞忓箷鍏变韩鐮佺巼 3 Mbps锛屾枃瀛楃晫闈㈡竻鏅�
- screenShareEncoding: {
- maxBitrate: 3_000_000,
- maxFramerate: 15,
- priority: 'medium'
- }
- }
- })
- _room.value = r
-
- r.on(RoomEvent.ParticipantConnected, (rp) => {
- syncRemotes(r)
- const userId = parseUserId(rp.identity)
- if (userId != null) {
- participantConnectedHandlers.forEach((cb) => cb(userId))
- }
- })
- .on(RoomEvent.ParticipantDisconnected, (rp) => {
- syncRemotes(r)
- // 绂诲紑鐨勫弬涓庤�呯紦瀛樻竻鎺夛紝閬垮厤涓嬫鍚� sid 閲嶈繛鍛戒腑澶辨晥寮曠敤
- for (const key of streamCache.keys()) {
- if (key.startsWith(`${rp.sid}:`)) {
- streamCache.delete(key)
- }
- }
- const userId = parseUserId(rp.identity)
- if (userId != null) {
- participantDisconnectedHandlers.forEach((cb) => cb(userId))
- }
- })
- .on(RoomEvent.TrackSubscribed, () => syncRemotes(r))
- .on(RoomEvent.TrackUnsubscribed, () => syncRemotes(r))
- // mute / unmute 璁� pickStream 鐨� isMuted 鐭矾閲嶇畻锛寁ideo 鍏冪礌鑳借В缁� srcObject 鑰屼笉鏄崱鏈�鍚庝竴甯�
- .on(RoomEvent.TrackMuted, () => syncRemotes(r))
- .on(RoomEvent.TrackUnmuted, () => syncRemotes(r))
- .on(RoomEvent.ConnectionQualityChanged, (quality) => {
- connectionQuality.value = quality
- })
- // 鐬柇 鈫� 鏄剧ず銆岄噸杩炰腑銆嶏紱涓嶅叧閫氳瘽绐楋紝鐢� SDK 鍐呴儴閲嶈繛鏈哄埗鎭㈠
- .on(RoomEvent.Reconnecting, () => {
- reconnecting.value = true
- })
- .on(RoomEvent.Reconnected, () => {
- reconnecting.value = false
- })
- // 閲嶈繛澶辫触 / 涓诲姩鏂� / 琚涪鏃惰Е鍙戞竻鐞�
- .on(RoomEvent.Disconnected, () => {
- isConnected.value = false
- reconnecting.value = false
- disconnectedHandlers.forEach((cb) => cb())
- })
-
- // 棰勭儹 getUserMedia 涓� WebSocket 鎻℃墜骞惰锛岀渷 100锝�300ms 涓茶寤惰繜锛�
- // 鎷垮埌鐨� stream 浠呯敤浜庤Е鍙戞潈闄愬脊绐� + 璁惧灏辩华锛屾彙鎵嬪畬鎴愬悗鐢� LiveKit 鍐呴儴閲嶆柊璇锋眰璁惧鍙戝竷杞�
- const warmup = prewarmMedia(opts)
- // 寤虹珛 WebSocket 淇′护 + WebRTC 濯掍綋閫氶亾锛涘畬鎴愬悗 localParticipant 鍙敤锛屽凡鍦ㄦ埧鍙備笌鑰呬細閫氳繃 ParticipantConnected 浜嬩欢鎵归噺鎺ㄩ��
- await r.connect(url, token)
- // 鏈熼棿琚閮� disconnect 鏇挎崲锛涗腑姝㈠悗缁� publish锛岄伩鍏嶆憚鍍忓ご琚噸鏂板惎鐢�
- if (_room.value !== r) {
- return
- }
- localParticipant.value = r.localParticipant
- isConnected.value = true
-
- // 棰勭儹缁撴灉涓嶇洿鎺ュ彂甯冿紙閬垮厤 SDK 涓庡閮� track 鐢熷懡鍛ㄦ湡绾犵紶锛夛紝浠呯瓑寰呮潈闄愬氨缁悗鍐嶈蛋鏍囧噯 setXxxEnabled
- await warmup
- if (_room.value !== r) {
- return
- }
- // 楹﹀厠椋庝笌鎽勫儚澶存潈闄愮浉浜掔嫭绔嬶紝骞惰鍚敤鍙戝竷
- const inits: Promise<unknown>[] = []
- if (opts.audio) {
- inits.push(r.localParticipant.setMicrophoneEnabled(true))
- }
- if (opts.video) {
- inits.push(r.localParticipant.setCameraEnabled(true))
- }
- if (inits.length > 0) {
- await Promise.all(inits)
- }
- micEnabled.value = !!opts.audio
- cameraEnabled.value = !!opts.video
-
- // 鍏滃簳鍚屾涓�娆¤繙绔垪琛細r.connect 鏈熼棿 ParticipantConnected 浜嬩欢鍙兘鍦� handler 缁戝畾鍓嶈Е鍙戣鍚烇紝瀵艰嚧棣栧睆婕忎汉
- syncRemotes(r)
- }
-
- /** 鎻愬墠瑙﹀彂鏉冮檺寮圭獥 + 璁惧鍞よ捣锛屼覆琛屽欢杩熷湪 r.connect 鏈熼棿涓�璧疯窇锛涘け璐ラ潤榛橈紙杩炴帴鍚庝細鍐嶈瘯涓�娆★級 */
- async function prewarmMedia(opts: { audio?: boolean; video?: boolean }): Promise<void> {
- if (!opts.audio && !opts.video) {
- return
- }
- try {
- const stream = await navigator.mediaDevices.getUserMedia({
- audio: !!opts.audio,
- video: !!opts.video
- })
- // 鎷垮埌鏉冮檺鍗冲彲锛岀珛鍗冲仠鎺夋墍鏈� track 閲婃斁璁惧锛涙寮忓彂甯冭蛋 SDK 娴佺▼閲嶆柊璇锋眰
- stream.getTracks().forEach((t) => t.stop())
- } catch {
- // 鐢ㄦ埛鎷掔粷 / 璁惧鍗犵敤绛夊紓甯革紝浜ょ粰鍚庣画 setXxxEnabled 鍐嶆灏濊瘯鎶ラ敊
- }
- }
-
- /** 鍒囬害鍏嬮 */
- async function setMicEnabled(enabled: boolean) {
- if (!_room.value) {
- return
- }
- await _room.value.localParticipant.setMicrophoneEnabled(enabled)
- micEnabled.value = enabled
- }
-
- /** 鍒囨憚鍍忓ご */
- async function setCameraEnabled(enabled: boolean) {
- if (!_room.value) {
- return
- }
- await _room.value.localParticipant.setCameraEnabled(enabled)
- cameraEnabled.value = enabled
- }
-
- /** 鍒囨壃澹板櫒锛涗粎鍒囧搷搴斿紡鐘舵�侊紝瀹為檯闈欓煶鐢辨ā鏉夸笂 audio 鍏冪礌 :muted 缁戝畾鐢熸晥 */
- function setSpeakerEnabled(enabled: boolean) {
- speakerEnabled.value = enabled
- }
-
- /**
- * 鍒囧睆骞曞叡浜紱
- *
- * 娴忚鍣ㄤ細寮瑰師鐢熴�岄�夋嫨鍏变韩鍐呭銆嶅璇濇锛岀敤鎴峰湪寮圭獥閲岀偣鍙栨秷鏃� setScreenShareEnabled 浼氭姏閿欙紝鎹曡幏骞舵妸鐘舵�佸浣嶅洖 SDK 鐨勫疄闄呭��
- */
- async function setScreenShareEnabled(enabled: boolean) {
- if (!_room.value) return
- try {
- await _room.value.localParticipant.setScreenShareEnabled(enabled)
- screenShareEnabled.value = enabled
- } catch (error) {
- // 鐢ㄦ埛鍦ㄦ祻瑙堝櫒鍘熺敓瀵硅瘽妗嗛噷鍙栨秷閫夋嫨锛屼笉褰撲綔閿欒
- screenShareEnabled.value = _room.value.localParticipant.isScreenShareEnabled
- throw error
- }
- }
-
- /** 娉ㄥ唽銆岃繙绔繛鎺ュ紓甯告柇寮�銆嶅洖璋冿紱杩斿洖鍙嶆敞鍐屽嚱鏁� */
- function onDisconnected(cb: () => void): () => void {
- disconnectedHandlers.add(cb)
- return () => disconnectedHandlers.delete(cb)
- }
-
- /** 娉ㄥ唽銆屾埧鍐呮煇浜哄姞鍏ャ�嶅洖璋冿紱杩斿洖鍙嶆敞鍐屽嚱鏁� */
- function onParticipantConnected(cb: ParticipantEventHandler): () => void {
- participantConnectedHandlers.add(cb)
- return () => participantConnectedHandlers.delete(cb)
- }
-
- /** 娉ㄥ唽銆屾埧鍐呮煇浜虹寮�銆嶅洖璋冿紱杩斿洖鍙嶆敞鍐屽嚱鏁� */
- function onParticipantDisconnected(cb: ParticipantEventHandler): () => void {
- participantDisconnectedHandlers.add(cb)
- return () => participantDisconnectedHandlers.delete(cb)
- }
-
- /** identity 鏄悗绔 token 鏃跺鐨� userId 瀛楃涓诧紝杞� number 杩斿洖锛涢潪鏁板瓧锛堝吋瀹规�у厹搴曪級杩斿洖 null */
- function parseUserId(identity: string): null | number {
- const id = Number(identity)
- return Number.isNaN(id) ? null : id
- }
-
- /**
- * MediaStream 缂撳瓨锛沰ey 涓� `${participantSid}:${source}`锛寁alue 涓� `{ track, stream }`锛�
- * 鍚屼竴鏉� MediaStreamTrack 澶嶇敤鍚屼竴涓� MediaStream锛岄伩鍏� <video>.srcObject 鍙嶅閲嶆寕瀵艰嚧瑙g爜绠$嚎閲嶅缓锛堣棰戦棯鐑侊級锛�
- * track 寮曠敤鍒囨崲锛堥噸鏂拌闃� / 鍒囨祦锛夋椂鎸夐渶鏂板缓骞舵浛鎹�
- */
- const streamCache = new Map<string, { stream: MediaStream; track: MediaStreamTrack; }>()
-
- /**
- * 鍙栧弬涓庤�呮寚瀹氭潵婧愮殑杞ㄩ亾骞舵墦鍖呬负 MediaStream锛�
- * 鍙備笌鑰呰蛋 unknown 鏄洜涓哄搷搴斿紡绯荤粺浼氫涪澶� livekit-client 鐨勭被绛惧悕锛屽嚱鏁板唴鎵嬪姩 cast 鍥炲弬涓庤�呯被鍨嬶紱
- * 鍛戒腑缂撳瓨杩斿洖鍚屼竴 MediaStream 寮曠敤锛屼笅娓� watch / srcObject 鏃犻渶閲嶆寕锛�
- * 杞ㄩ亾琚� mute锛堟湰绔叧鎽勫儚澶� / 杩滅鍏虫憚鍍忓ご锛夋椂杩斿洖 null锛岃 video 鍏冪礌瑙g粦 srcObject 鑰屼笉鏄崱鍦ㄦ渶鍚庝竴甯�
- */
- function pickStream(participant: unknown, source: Track.Source): MediaStream | null {
- const p = participant as Participant
- const pub = p.getTrackPublication(source)
- if (!pub || pub.isMuted) {
- return null
- }
- const track = pub.track?.mediaStreamTrack
- if (!track) {
- return null
- }
- const key = `${p.sid}:${source}`
- const cached = streamCache.get(key)
- if (cached && cached.track === track) {
- return cached.stream
- }
- const stream = new MediaStream([track])
- streamCache.set(key, { track, stream })
- return stream
- }
-
- /** 鏂紑褰撳墠 Room锛沜learHandlers 涓� true 鏃跺悓姝ユ竻鐞嗗閮ㄦ敞鍐岀殑浜嬩欢鍥炶皟 */
- async function disconnectRoom(clearHandlers: boolean) {
- // 娓呯悊閫氳瘽缁撴潫鍚庝笉鍐嶅鐢ㄧ殑璁㈤槄鍥炶皟
- if (clearHandlers) {
- disconnectedHandlers.clear()
- participantConnectedHandlers.clear()
- participantDisconnectedHandlers.clear()
- }
- // 娓呯悊闊宠棰戣建閬撶紦瀛�
- streamCache.clear()
- if (_room.value) {
- // 鍗歌浇 Room 浜嬩欢骞舵柇寮�杩炴帴
- _room.value.removeAllListeners()
- await _room.value.disconnect()
- _room.value = null
- }
- // 閲嶇疆杩炴帴鍜岃澶囩姸鎬�
- localParticipant.value = null
- remoteParticipants.value = []
- isConnected.value = false
- reconnecting.value = false
- micEnabled.value = true
- cameraEnabled.value = false
- speakerEnabled.value = true
- screenShareEnabled.value = false
- }
-
- /** 涓诲姩鏂紑锛涢�氳瘽缁撴潫缁熶竴璋� */
- async function disconnect() {
- await disconnectRoom(true)
- }
-
- return {
- room,
- localParticipant,
- remoteParticipants,
- isConnected,
- connectionQuality,
- micEnabled,
- cameraEnabled,
- speakerEnabled,
- screenShareEnabled,
- reconnecting,
- connect,
- disconnect,
- setMicEnabled,
- setCameraEnabled,
- setSpeakerEnabled,
- setScreenShareEnabled,
- pickStream,
- onDisconnected,
- onParticipantConnected,
- onParticipantDisconnected
- }
-}
-
-export type ImLiveKitRoom = ReturnType<typeof useLiveKitRoom>
diff --git a/src/views/im/home/composables/useMediaStreamElement.ts b/src/views/im/home/composables/useMediaStreamElement.ts
deleted file mode 100644
index aa6ae09..0000000
--- a/src/views/im/home/composables/useMediaStreamElement.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { type VNodeRef, watch } from 'vue'
-
-/**
- * 鎶婂搷搴斿紡 MediaStream 鎸傚埌 `<video>` / `<audio>` 鍏冪礌鐨� srcObject 涓婏紱
- * stream 涓虹┖鏃舵竻鎺� srcObject锛岄伩鍏嶈澶囧叧闂悗鐢婚潰鍗″湪鏈�鍚庝竴甯�
- */
-export function useMediaStreamElement<T extends HTMLMediaElement>(
- streamSource: () => MediaStream | null | undefined
-): VNodeRef {
- let el: T | null = null
- let currentStream: MediaStream | null | undefined
-
- const syncStream = () => {
- if (el) {
- el.srcObject = currentStream || null
- }
- }
-
- watch(
- streamSource,
- (stream) => {
- currentStream = stream
- syncStream()
- },
- { flush: 'post', immediate: true }
- )
-
- return (value) => {
- if (value instanceof HTMLMediaElement) {
- el = value as T
- syncStream()
- return
- }
-
- if (el) {
- el.srcObject = null
- }
- el = null
- }
-}
diff --git a/src/views/im/home/composables/useMediaUploader.ts b/src/views/im/home/composables/useMediaUploader.ts
deleted file mode 100644
index 821d8e8..0000000
--- a/src/views/im/home/composables/useMediaUploader.ts
+++ /dev/null
@@ -1,408 +0,0 @@
-import type { Conversation, Message } from '../types'
-
-import type { AxiosProgressEvent } from '#/api/infra/file'
-
-import { isOpenableUrl } from '../../../../packages/utils/src'
-
-import { message } from 'ant-design-vue'
-
-import { uploadFile } from '#/api/infra/file'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import {
- MESSAGE_FILE_MAX_MB,
- MESSAGE_IMAGE_MAX_MB,
- MESSAGE_VIDEO_MAX_MB,
- MESSAGE_VOICE_MAX_MB
-} from '../../utils/config'
-import { ImContentType, ImMessageStatus } from '../../utils/constants'
-import { getConversationKey } from '../../utils/conversation'
-import {
- type AudioMessage,
- BLOB_URL_PREFIX,
- type FileMessage,
- generateClientMessageId,
- type ImageMessage,
- parseMessage,
- type QuoteMessage,
- serializeMessage,
- type VideoMessage,
- withQuotePayload
-} from '../../utils/message'
-import { useConversationStore } from '../store/conversationStore'
-import { useMessageStore } from '../store/messageStore'
-import { useMessageSender } from './useMessageSender'
-import { useMuteOverlay } from './useMuteOverlay'
-
-type UploadProgressEvent = Parameters<NonNullable<AxiosProgressEvent>>[0]
-
-/** 鍗曟潯濯掍綋 payload 鑱斿悎锛堣鐩� IMAGE / FILE / VOICE / VIDEO 鍥涚锛� */
-export type MediaPayload = AudioMessage | FileMessage | ImageMessage | VideoMessage
-
-/**
- * 濯掍綋鐗瑰畾鐨勫厓鏁版嵁涓婁笅鏂囷細棣栧彂 / 閲嶄紶鍏辩敤鍏ュ弬锛涗笉鍚� type 鍏冲績涓嶅悓瀛楁
- *
- * - voiceDuration锛氳闊虫椂闀匡紙绉掞級锛岄鍙戠敱 VoiceRecorder 缁欙紝閲嶄紶浠庢棫 AudioMessage.duration 鍙�
- * - videoProbe锛氳棰戝厓淇℃伅锛堥鍙戠敱 probeVideoFile 瑙e嚭锛岄噸浼犱粠鏃� VideoMessage 鐩存帴鎷峰瓧娈碉級
- * - videoCoverUrl锛氳棰戝皝闈㈢湡瀹� URL锛涘崰浣嶉樁娈典笉璁撅紙閬垮厤浼� blob 褰� poster 鍦ㄩ儴鍒嗘祻瑙堝櫒閫�鍖栵級锛宑ommit 闃舵鐢� cover 涓婁紶缁撴灉濉叆锛涢噸浼犳椂浠庢棫 VideoMessage.coverUrl 澶嶇敤锛屾棫鍊艰嫢鏄� blob 浼氳璺宠繃
- */
-export interface MediaTypeContext {
- voiceDuration?: number
- videoProbe?: { duration?: number; height?: number; width?: number; }
- videoCoverUrl?: string
-}
-
-export interface MediaTypeHandler {
- /** 涓枃鍚嶏紝浠呮棩蹇楃敤锛堟浛浠d箣鍓嶆暎钀� 9 澶勭殑 kind 瀛楃涓诧級 */
- kind: string
- /** 鐢� file + url + context 鐢熸垚 payload锛涘崰浣嶆椂 url 鏄� blob URL锛宑ommit 鏃舵槸鐪熷疄 url */
- build: (file: File, url: string, context: MediaTypeContext) => MediaPayload
- /** 閲嶄紶鍦烘櫙锛氫粠鏃� content 鎻愬彇 context锛堣閲嶄紶涓嶉渶瑕侀噸鍋� probe / 閲嶅綍璇煶锛� */
- extractResendContext: (oldContent: string) => MediaTypeContext
-}
-
-/** 濯掍綋绫诲瀷娉ㄥ唽琛細image / file / voice / video 鍚勮嚜鐨� kind + 棣栧彂 / 閲嶄紶鍏辩敤鐨� build / extract */
-export const mediaTypeHandlers: Partial<Record<number, MediaTypeHandler>> = {
- [ImContentType.IMAGE]: {
- kind: '鍥剧墖',
- build: (_file, url) => ({ url }) as ImageMessage,
- extractResendContext: () => ({})
- },
- [ImContentType.FILE]: {
- kind: '鏂囦欢',
- build: (file, url) => ({ url, name: file.name, size: file.size }) as FileMessage,
- extractResendContext: () => ({})
- },
- [ImContentType.VOICE]: {
- kind: '璇煶',
- build: (_file, url, context) => ({ url, duration: context.voiceDuration ?? 0 }) as AudioMessage,
- extractResendContext: (oldContent) => {
- const old = parseMessage<AudioMessage>(oldContent)
- return { voiceDuration: old?.duration ?? 0 }
- }
- },
- [ImContentType.VIDEO]: {
- kind: '瑙嗛',
- build: (file, url, context) =>
- ({
- url,
- coverUrl: context.videoCoverUrl,
- duration: context.videoProbe?.duration,
- width: context.videoProbe?.width,
- height: context.videoProbe?.height,
- size: file.size
- }) as VideoMessage,
- extractResendContext: (oldContent) => {
- const old = parseMessage<VideoMessage>(oldContent)
- // 鏃� coverUrl 鏄� blob 璇存槑涓婁紶鏈熷け璐ワ紙cover 娌′紶鎴愬姛锛夛紝涓嶅鐢紱鐪熷疄 URL 鐩存帴澶嶇敤锛岀渷涓�娆″皝闈笂浼�
- const reuseCover =
- old?.coverUrl && !old.coverUrl.startsWith(BLOB_URL_PREFIX) ? old.coverUrl : undefined
- return {
- videoProbe: { duration: old?.duration, width: old?.width, height: old?.height },
- videoCoverUrl: reuseCover
- }
- }
- }
-}
-
-/** 鍗曟濯掍綋涓婁紶鐨勫叆鍙傦紙image / file / voice 璧� uploadAndSendMedia锛泇ideo 璧颁綆灞� helper 鑷缁勮锛� */
-export interface UploadAndSendMediaOptions {
- file: File
- /** 瀵归綈 ImContentType锛沵ediaTypeHandlers 蹇呴』鏈夊搴旈」 */
- type: number
- /** 濯掍綋鐗瑰畾鐨勫厓鏁版嵁锛堝璇煶鏃堕暱 / 瑙嗛鍏冧俊鎭級锛涗笉浼犳寜绌哄璞″鐞� */
- context?: MediaTypeContext
- /** 寮曠敤娑堟伅锛堣嫢鏈夛級锛屽啓杩� payload.quote */
- quote?: QuoteMessage
- /** 閿佸畾璧峰浼氳瘽锛屼笂浼犳湡闂翠細璇濆垏璧板垯鏀惧純鍙戦�� */
- conversation: Conversation
- /** 閲嶈瘯宸叉湁鍗犱綅娑堟伅鏃跺鐢ㄧ殑瀹㈡埛绔秷鎭紪鍙� */
- existingClientMessageId?: string
-}
-
-/**
- * 濯掍綋涓婁紶 + 鍙戦�� composable锛坕mage / file / voice / video 鍏辩敤搴曞眰 helper锛�
- *
- * 涓� useMessageSender.sendRaw 鐨勩�屽厛鍙戣姹傚啀 ack銆嶄笉鍚岋紝濯掍綋閾捐矾蹇呴』銆屽厛鍗犱綅鍐嶄笂浼犮�嶏細
- * 1. 绔嬪嵆 insertMessage 鍐欏叆鍗犱綅娑堟伅锛坰tatus=SENDING銆乧ontent 鐢� blob URL銆乢localFile 鍐呭瓨鐣� File锛�
- * 2. uploadFile 涓婁紶锛宱nUploadProgress 鍥炶皟 patchMessage 鏇存柊 uploadProgress锛沀I 瀹炴椂鏄剧ず杩涘害鏉�
- * 3. 涓婁紶鎴愬姛鍚庣敤鐪熷疄 url 閲嶇敓 content锛宲atchMessage 鏇挎崲锛涙棫 blob URL 鐢� store 鑷姩 revoke
- * 4. 璧� sendRaw(existingClientMessageId) 澶嶇敤鍗犱綅鍙戦�佽姹傦紝閬垮厤閲嶅鎻掑叆涓ゆ潯
- *
- * 浠绘剰澶辫触鎶婃秷鎭姸鎬佺疆 FAILED锛汳essageItem 涓婄偣閲嶈瘯鍐嶈蛋涓�娆℃湰鍑芥暟锛坃localFile 杩樺湪鍐呭瓨灏辫锛�
- */
-/** 鎸夊唴瀹圭被鍨嬫槧灏勪綋绉笂闄愶紙MB锛夛紱鏈瘑鍒被鍨嬭繑鍥� 0 琛ㄧず涓嶉檺 */
-function resolveMediaMaxMb(type: number): number {
- switch (type) {
- case ImContentType.FILE: {
- return MESSAGE_FILE_MAX_MB
- }
- case ImContentType.IMAGE: {
- return MESSAGE_IMAGE_MAX_MB
- }
- case ImContentType.VIDEO: {
- return MESSAGE_VIDEO_MAX_MB
- }
- case ImContentType.VOICE: {
- return MESSAGE_VOICE_MAX_MB
- }
- default: {
- return 0
- }
- }
-}
-
-/** 鏍¢獙濯掍綋鏂囦欢澶у皬鏄惁瓒呰繃鍐呭绫诲瀷涓婇檺锛涜秴闄愯Е鍙� warn 骞惰繑鍥� false锛岃皟鐢ㄦ柟涓嶅簲杩涘叆鍗犱綅 / 涓婁紶閾捐矾 */
-export function ensureMediaSizeWithinLimit(
- file: File,
- type: number,
- warn: (text: string) => void
-): boolean {
- const maxMb = resolveMediaMaxMb(type)
- if (maxMb && file.size > maxMb * 1024 * 1024) {
- warn(`鏂囦欢澶у皬瓒呰繃涓婇檺 ${maxMb}MB锛岃鍘嬬缉鍚庡啀鍙慲)
- return false
- }
- return true
-}
-
-export const useMediaUploader = () => {
- const conversationStore = useConversationStore()
- const messageStore = useMessageStore()
- const muteOverlay = useMuteOverlay()
- const { sendRaw } = useMessageSender()
-
- /**
- * 绔嬪嵆鍐欏叆濯掍綋鍗犱綅娑堟伅锛堜綆灞� helper锛沬mage/file/voice 璧� uploadAndSendMedia 鍖呰锛寁ideo 鐩存帴鐢ㄦ湰鍑芥暟锛�
- *
- * 鐢� createObjectURL(file) 鐢熸垚涓存椂 blob URL 鍠傜粰 buildContent锛涘崰浣� status=SENDING + uploadProgress=0锛�
- * file 鎸傚湪 _localFile 涓婁緵澶辫触閲嶈瘯鏃堕噸璧颁笂浼�
- */
- const insertMediaPlaceholder = (opts: {
- buildContent: (blobUrl: string) => string
- conversation: Conversation
- existingClientMessageId?: string
- file: File
- type: number
- }): { blobUrl: string; clientMessageId: string; } => {
- const { conversation } = opts
- const blobUrl = URL.createObjectURL(opts.file)
- const clientMessageId = opts.existingClientMessageId || generateClientMessageId()
- if (opts.existingClientMessageId) {
- messageStore.patchMessage(conversation.type, conversation.targetId, clientMessageId, {
- content: opts.buildContent(blobUrl),
- status: ImMessageStatus.SENDING,
- uploadProgress: 0,
- _localFile: opts.file
- })
- return { clientMessageId, blobUrl }
- }
- const placeholder: Message = {
- clientMessageId,
- type: opts.type,
- content: opts.buildContent(blobUrl),
- status: ImMessageStatus.SENDING,
- sendTime: Date.now(),
- senderId: getCurrentUserId(),
- targetId: conversation.targetId,
- selfSend: true,
- uploadProgress: 0,
- _localFile: opts.file
- }
- void messageStore.insertMessage(
- {
- type: conversation.type,
- targetId: conversation.targetId,
- name: conversation.name || String(conversation.targetId),
- avatar: conversation.avatar || ''
- },
- placeholder
- ).catch(() => undefined)
- return { clientMessageId, blobUrl }
- }
-
- /**
- * 鎶婂崰浣嶆秷鎭疆涓� FAILED锛堜笂浼犲け璐� / 浼氳瘽鍒囪蛋 / 绂佽█鏈熷埌鐐� 绛夊満鏅粺涓�鏀跺熬锛�
- *
- * 鍚屾椂娓呮帀 uploadProgress 鈥斺�� 澶辫触鍚� MessageItem 鐨� isUploading 涓嶅啀鍛戒腑锛岃繘搴﹂伄缃� / 鏂囦欢鐐瑰嚮绂佺敤 / 璇煶 loading 鎶戝埗 閮藉悓姝ヨВ闄わ紱
- * _localFile 淇濈暀锛岃鐢ㄦ埛鐐归噸璇曞彲浠ヨ蛋 uploadAndSendMedia 閲嶄紶
- */
- const markMediaFailed = (
- conversationType: number,
- targetId: number,
- clientMessageId: string
- ): void => {
- messageStore.patchMessage(conversationType, targetId, clientMessageId, {
- status: ImMessageStatus.FAILED,
- uploadProgress: undefined
- })
- }
-
- /**
- * 鐢熸垚 axios `onUploadProgress` 鍥炶皟锛氱敤 closure 缂撳瓨涓婃鐧惧垎姣旓紝鏈彉鍖栫洿鎺� return 涓嶈繘 store
- *
- * XHR onProgress 澶ф枃浠朵笅姣忕瑙﹀彂 10-50 娆★紝浣� Math.round 鍚庣櫨鍒嗘瘮鏈夊ぇ閲忛噸澶嶏紙涓�绉掑唴鍙兘鍗佸嚑娆″悓涓�涓暟瀛楋級锛�
- * 鍦ㄦ簮澶村幓閲嶏紝鑳界渷鎺� store 鐨� find + Object.assign + Vue reactivity 瑙﹀彂閾�
- */
- const createUploadProgressHandler = (conversation: Conversation, clientMessageId: string) => {
- let lastPercent = -1
- return (event: UploadProgressEvent): void => {
- if (!event.total) {
- return
- }
- const percent = Math.round((event.loaded / event.total) * 100)
- if (percent === lastPercent) {
- return
- }
- lastPercent = percent
- messageStore.patchMessage(conversation.type, conversation.targetId, clientMessageId, {
- uploadProgress: percent
- })
- }
- }
-
- /** 鍙栧獟浣撶被鍨嬩腑鏂囧悕锛堜粎鏃ュ織鐢級锛涙湭娉ㄥ唽 type 閫�鍖栦负閫氱敤銆屽獟浣撱�� */
- const getMediaKind = (type: number): string => mediaTypeHandlers[type]?.kind ?? '濯掍綋'
-
- /**
- * 鎸� type 鍙� handler锛岀己鍒欐姏閿欙紙绋嬪簭閿欒闆嗕腑鍦ㄨ繖涓�澶勶級
- *
- * 璋冪敤鏂规嬁鍒扮殑鏄� `MediaTypeHandler`锛堥潪 undefined锛夛紝涓嶅啀闇�瑕� `!` 鏂█銆�
- * 浠呯粰銆岀‘瀹� type 鍦ㄨ〃閲屻�嶇殑璋冪敤鏂圭敤 鈥斺�� image/file/voice/video 鍥涚被鍏ュ彛锛涢�氱敤 dispatcher 浠嶅彲鐢� `mediaTypeHandlers[type]?.` optional chain
- */
- const requireMediaHandler = (type: number): MediaTypeHandler => {
- const handler = mediaTypeHandlers[type]
- if (!handler) {
- throw new Error(`[IM] 鏈敞鍐岀殑濯掍綋绫诲瀷 ${type}`)
- }
- return handler
- }
-
- /**
- * 涓婁紶瀹屾垚鍚庣殑鏀跺彛鏍¢獙锛氫細璇濅粛鏄崰浣嶆椂閿佸畾鐨勯偅涓� + 褰撳墠鏈绂佽█锛涗换涓�涓嶆弧瓒� markMediaFailed + 杩斿洖 false
- *
- * image / file / voice / video 閾捐矾閮借鍦ㄣ�屾嬁鍒扮湡瀹� url 鍚庛�佽皟 sendRaw 涔嬪墠銆嶈繃涓�閬嶈繖涓ら亾
- */
- const verifyMediaUploadStillAllowed = (
- conversation: Conversation,
- startKey: string,
- type: number,
- clientMessageId: string
- ): boolean => {
- const activeConversation = conversationStore.activeConversation
- if (!activeConversation || getConversationKey(activeConversation) !== startKey) {
- console.warn(`[IM] ${getMediaKind(type)}涓婁紶鏈熼棿鍒囨崲浜嗕細璇濓紝鏀惧純鍙戦�乣, { startKey })
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return false
- }
- if (muteOverlay.value) {
- console.warn(`[IM] ${getMediaKind(type)}涓婁紶鏈熼棿琚瑷�锛屾斁寮冨彂閫乣, { startKey })
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return false
- }
- return true
- }
-
- /**
- * 鍗犱綅瀹屾垚鍚庣敤鐪熷疄 url 鏇挎崲 content锛屽啀璧� sendRaw 瀹屾垚鍙戦��
- *
- * 涓婁紶鎴愬姛 鈫� patch content 鈫� sendRaw 澶嶇敤 existingClientMessageId锛泂tore 鍐呴儴 revoke 鏃� blob URL
- */
- const commitMediaPlaceholder = async (opts: {
- clientMessageId: string
- conversation: Conversation
- realContent: string
- type: number
- }): Promise<void> => {
- messageStore.patchMessage(
- opts.conversation.type,
- opts.conversation.targetId,
- opts.clientMessageId,
- { content: opts.realContent }
- )
- // 鏄惧紡浼� conversation 鑰岄潪渚濊禆 sendRaw 鍐呴儴鍙� active锛�
- // verifyMediaUploadStillAllowed 涓� sendRaw 涔嬮棿瀛樺湪寰绐楀彛锛屾湡闂寸敤鎴峰垏浼氳瘽涔熻兘淇濊瘉鍙戝埌鍘熶細璇�
- await sendRaw(opts.type, opts.realContent, {
- existingClientMessageId: opts.clientMessageId,
- targetId: opts.conversation.targetId,
- conversation: opts.conversation
- })
- }
-
- /**
- * 涓婁紶濯掍綋鏂囦欢骞跺彂閫佹秷鎭紙楂樺眰鍏ュ彛锛沬mage / file / voice 鐢紝video 璧颁綆灞� helper 鑷缁勮锛�
- *
- * @returns 鍗犱綅娑堟伅鐨� clientMessageId锛堣皟鐢ㄦ柟鎸夐渶鐢ㄤ簬鍚庣画 patch / 绉婚櫎锛涗笂浼犲け璐ユ椂鍗犱綅浠嶄繚鐣欎负 FAILED 鎬侊級
- */
- const uploadAndSendMedia = async (opts: UploadAndSendMediaOptions): Promise<string> => {
- const { conversation } = opts
- const handler = mediaTypeHandlers[opts.type]
- if (!handler) {
- console.warn('[IM] uploadAndSendMedia 鏀跺埌鏈敞鍐岀殑濯掍綋绫诲瀷', { type: opts.type })
- return ''
- }
- // 浣撶Н涓婇檺鎷︽埅锛氬ぇ鏂囦欢娴忚鍣ㄥ唴鎴抚 / 瑙g爜鍙嚧 OOM锛涜秴闄愮洿鎺� warning锛屼笉杩涘叆鍗犱綅 / 涓婁紶閾捐矾
- if (!ensureMediaSizeWithinLimit(opts.file, opts.type, message.warning)) {
- return ''
- }
- const startKey = getConversationKey(conversation)
- const context = opts.context ?? {}
- const buildContent = (url: string): string =>
- serializeMessage(withQuotePayload(handler.build(opts.file, url, context), opts.quote))
-
- // 1. 绔嬪嵆鍗犱綅
- const { clientMessageId } = insertMediaPlaceholder({
- file: opts.file,
- type: opts.type,
- conversation,
- buildContent,
- existingClientMessageId: opts.existingClientMessageId
- })
-
- // 2. 涓婁紶锛氳繘搴﹀洖璋� patch uploadProgress锛涘け璐ヤ繚鐣� _localFile 渚涢噸璇�
- let url: string | undefined
- try {
- url = await uploadFile(
- { file: opts.file },
- createUploadProgressHandler(conversation, clientMessageId)
- )
- } catch (error) {
- console.error(`[IM] ${handler.kind}涓婁紶澶辫触`, error)
- }
- if (!url) {
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return clientMessageId
- }
- if (!isOpenableUrl(url)) {
- console.warn(`[IM] ${handler.kind}涓婁紶杩斿洖浜嗕笉鏀寔鎵撳紑鐨� URL`, { url })
- message.warning('涓婁紶杩斿洖鐨勬枃浠跺湴鍧�涓嶆敮鎸佹墦寮�')
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return clientMessageId
- }
-
- // 3. 涓婁紶鏈熼棿浼氳瘽鍒囨崲 / 鐢ㄦ埛鐧诲嚭 / 琚瑷�锛氫换涓�鎯呭喌閮芥斁寮冨彂閫侊紝鍗犱綅缃� FAILED
- if (!verifyMediaUploadStillAllowed(conversation, startKey, opts.type, clientMessageId)) {
- return clientMessageId
- }
-
- // 4. patch content + sendRaw 鏀跺熬
- await commitMediaPlaceholder({
- type: opts.type,
- conversation,
- clientMessageId,
- realContent: buildContent(url)
- })
- return clientMessageId
- }
-
- return {
- uploadAndSendMedia,
- insertMediaPlaceholder,
- markMediaFailed,
- commitMediaPlaceholder,
- createUploadProgressHandler,
- verifyMediaUploadStillAllowed,
- getMediaKind,
- requireMediaHandler
- }
-}
diff --git a/src/views/im/home/composables/useMessageMultiSelect.ts b/src/views/im/home/composables/useMessageMultiSelect.ts
deleted file mode 100644
index 6b0d656..0000000
--- a/src/views/im/home/composables/useMessageMultiSelect.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { Message } from '../types'
-
-import { computed, reactive } from 'vue'
-
-/**
- * 娑堟伅澶氶�夋ā寮�
- *
- * 妯″潡绾у崟渚� reactive state锛汳essagePanel 瀛愭爲鍐呭澶勫叡浜細
- * - MessageItem 娓叉煋鍕鹃�� + 鍒囨崲鍕鹃��
- * - MessageMultiSelectBar 搴曟爮璇婚�変腑鏁�
- * - MessageForwardDialog 杞彂鎴愬姛鍚� exit
- * - MessagePanel watch activeConversation 鍒囦細璇� exit
- */
-const state = reactive({
- active: false,
- /** 宸查�� clientMessageId 鍒楄〃锛屾寜閫変腑椤哄簭淇濆簭 */
- selectedClientMessageIds: [] as string[]
-})
-
-/** 宸查�� clientMessageId 闆嗗悎锛汳essageItem 澶ч噺 has 鏌ヨ璧板畠锛岄伩鍏� array.includes O(N虏) */
-const selectedIdSet = computed(() => new Set(state.selectedClientMessageIds))
-
-/** 杩涘叆澶氶�夋ā寮忥紝鍙檮甯﹀垵濮嬪嬀閫夐」 */
-function enter(initialMessage?: Message) {
- state.active = true
- state.selectedClientMessageIds = initialMessage ? [initialMessage.clientMessageId] : []
-}
-
-/** 閫�鍑哄閫夋ā寮� */
-function exit() {
- state.active = false
- state.selectedClientMessageIds = []
-}
-
-/** 鍒囨崲鏌愭潯娑堟伅鐨勯�変腑鎬� */
-function toggle(message: Message) {
- const ids = state.selectedClientMessageIds
- const index = ids.indexOf(message.clientMessageId)
- if (index === -1) {
- ids.push(message.clientMessageId)
- } else {
- ids.splice(index, 1)
- }
-}
-
-export function useMessageMultiSelect() {
- return { state, selectedIdSet, enter, exit, toggle }
-}
diff --git a/src/views/im/home/composables/useMessagePuller.ts b/src/views/im/home/composables/useMessagePuller.ts
deleted file mode 100644
index 95cf36d..0000000
--- a/src/views/im/home/composables/useMessagePuller.ts
+++ /dev/null
@@ -1,464 +0,0 @@
-import type { Message } from '../types'
-
-import type { ImChannelMessageApi } from '#/api/im/message/channel'
-import type { ImGroupMessageApi } from '#/api/im/message/group'
-import type { ImPrivateMessageApi } from '#/api/im/message/private'
-
-import { watch } from 'vue'
-
-import { pullChannelMessageList as apiPullChannelMessageList } from '#/api/im/message/channel'
-import { pullGroupMessageList as apiPullGroupMessageList } from '#/api/im/message/group'
-import { getPrivateMaxReadMessageId as apiGetPrivateMaxReadMessageId, pullPrivateMessageList as apiPullPrivateMessageList } from '#/api/im/message/private'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { buildChannelConversationStub } from '../../utils/channel'
-import {
- MESSAGE_GROUP_PULL_SIZE,
- MESSAGE_PRIVATE_PULL_SIZE,
- MESSAGE_PRIVATE_READ_ENABLED
-} from '../../utils/config'
-import {
- ImContentType,
- ImConversationType,
- ImMessageStatus,
- isFriendChatTip,
- isFriendNotification
-} from '../../utils/constants'
-import { generateClientMessageId, getPrivateMessagePeerId } from '../../utils/message'
-import { runMinIdPull } from '../../utils/pull'
-import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
-import { useConversationStore } from '../store/conversationStore'
-import { useFriendStore } from '../store/friendStore'
-import { useGroupRequestStore } from '../store/groupRequestStore'
-import { useGroupStore } from '../store/groupStore'
-import { type PulledMessage, useMessageStore } from '../store/messageStore'
-import { useRtcStore } from '../store/rtcStore'
-import { useImWebSocketStore } from '../store/websocketStore'
-
-/** 涓夌被娑堟伅 pull 鎺ュ彛杩斿洖鐨勫師濮� VO 鑱斿悎绫诲瀷锛況unMinIdPull 鍙渶 id 鎺ㄨ繘娓告爣锛屽叿浣撳垎鍙戝湪 applyPage 鍐呮寜绫诲瀷 cast */
-type PulledRawMessage = ImChannelMessageApi.ChannelMessageRespVO | ImGroupMessageApi.GroupMessageRespVO | ImPrivateMessageApi.PrivateMessageRespVO
-
-/**
- * 娑堟伅澧為噺鎷夊彇锛氱櫥褰曞悗鍒嗛〉鎷夊彇绂荤嚎鏈熼棿鐨勬柊娑堟伅
- *
- * 璁捐瑕佺偣锛�
- * 1. 鍚屾椂鎷夊彇绉佽亰 + 缇よ亰锛屼娇鐢ㄥ悇鑷殑 `minId` 娓告爣锛坧rivateMessageMaxId / groupMessageMaxId锛�
- * 2. 鍚庣涓�娆℃渶澶氳繑鍥� size 鏉★紱鍓嶇鎸� minId 鎸佺画缈婚〉锛岀洿鍒版帴鍙h繑鍥炵┖鍒楄〃涓烘
- * 3. 鎷夊彇鏈熼棿 conversationStore.loading=true锛�
- * - conversationStore 璺宠繃鎵归噺鎸佷箙鍖栵紝閬垮厤棰戠箒鍐欏叆鍗¢】
- * - websocketStore 鎶婃柊鏉ョ殑 WS 鏅�氭秷鎭涪杩涚紦鍐插尯锛岀瓑寰幆缁撴潫鍚庣粺涓�鍥炴斁
- * 4. WebSocket 閲嶈繛鍚庝細鍐嶈Е鍙戜竴娆℃媺鍙栵紝琛ラ綈鏂綉鏈熼棿閿欒繃鐨勬秷鎭�
- */
-export const useMessagePuller = () => {
- const conversationStore = useConversationStore()
- const messageStore = useMessageStore()
- const wsStore = useImWebSocketStore()
- const friendStore = useFriendStore()
- const groupStore = useGroupStore()
- const groupRequestStore = useGroupRequestStore()
- const rtcStore = useRtcStore()
- const currentUserId = getCurrentUserId()
-
- /** 鍒ゆ柇璇锋眰鏄惁琚富鍔ㄥ彇娑� */
- const isAbortError = (e: unknown): boolean => {
- const error = e as { code?: string; message?: string; name?: string; }
- return (
- error?.name === 'CanceledError' ||
- error?.code === 'ERR_CANCELED' ||
- error?.message === 'canceled'
- )
- }
-
- /** 绉佽亰浼氳瘽褰掑睘锛氳嚜宸卞彂鐨勭畻"鍙戠粰 receiverId 鐨勪細璇�"锛屽惁鍒欑畻"鍙戦�佹柟鐨勪細璇�"锛沜urry currentUserId 杩涢棴鍖呭噺灏� 3 澶勮皟鐢ㄦ柟鐨勬牱鏉� */
- const getPrivatePeerId = (message: ImPrivateMessageApi.PrivateMessageRespVO) =>
- getPrivateMessagePeerId(message, currentUserId)
-
- /** 鏈嶅姟绔鑱婃秷鎭� -> 鏈湴 Message锛歵argetId 鏄細璇濅富閿紙瀵圭 userId锛� */
- const convertPrivateMessage = (message: ImPrivateMessageApi.PrivateMessageRespVO): Message => {
- return {
- id: message.id,
- clientMessageId: message.clientMessageId || generateClientMessageId(),
- type: message.type,
- content: message.content,
- status: message.status,
- receiptStatus: message.receiptStatus,
- sendTime: new Date(message.sendTime).getTime(),
- senderId: message.senderId,
- targetId: getPrivatePeerId(message),
- selfSend: message.senderId === currentUserId
- }
- }
-
- /** 鏈嶅姟绔兢鑱婃秷鎭� -> 鏈湴 Message */
- const convertGroupMessage = (message: ImGroupMessageApi.GroupMessageRespVO): Message => {
- return {
- id: message.id,
- clientMessageId: message.clientMessageId || generateClientMessageId(),
- type: message.type,
- content: message.content,
- status: message.status,
- sendTime: new Date(message.sendTime).getTime(),
- senderId: message.senderId,
- targetId: message.groupId,
- selfSend: message.senderId === currentUserId,
- atUserIds: message.atUserIds || [],
- receiverUserIds: message.receiverUserIds || [],
- receiptStatus: message.receiptStatus,
- readCount: message.readCount
- }
- }
-
- /** 鏈嶅姟绔閬撴秷鎭� -> 鏈湴 Message */
- const convertChannelMessage = (message: ImChannelMessageApi.ChannelMessageRespVO): Message => {
- return {
- id: message.id,
- clientMessageId: message.clientMessageId || generateClientMessageId(),
- type: message.type,
- content: message.content,
- status: ImMessageStatus.NORMAL, // 棰戦亾鏃犳挙鍥烇紝鎭掍负姝e父
- receiptStatus: message.receiptStatus, // 棰戦亾宸茶鎬侊細DONE 宸茶 / PENDING 鏈
- sendTime: new Date(message.sendTime).getTime(),
- senderId: 0, // 绯荤粺涓嬪彂锛屾棤鍙戦�佷汉
- targetId: message.channelId, // 浼氳瘽褰掑睘鍒伴閬撶紪鍙�
- selfSend: false,
- materialId: message.materialId // 璇︽儏椤垫媺瀵屾枃鏈敤
- }
- }
-
- /** 棰戦亾锛氫細璇濆綊灞炲埌 channelId锛沶ame / avatar 鏆傜敤鍗犱綅锛屽皢鏉ユ帴鍏� channelStore 鍚庡啀濉湡鍊� */
- const convertChannelConversation = (message: ImChannelMessageApi.ChannelMessageRespVO) =>
- buildChannelConversationStub(message.channelId)
-
- /** 绉佽亰锛氫細璇濆綊灞炲埌瀵圭 userId */
- const convertPrivateConversation = (message: ImPrivateMessageApi.PrivateMessageRespVO) => {
- const targetId = getPrivatePeerId(message)
- const friend = friendStore.getFriend(targetId)
- return {
- type: ImConversationType.PRIVATE,
- targetId,
- name: friend ? getFriendDisplayName(friend) : String(targetId), // 浼氳瘽鍒楄〃 / 椤堕儴鏍囬灞曠ず锛氬ソ鍙嬪娉� > 鐪熷疄鏄电О
- avatar: friend?.avatar || '',
- silent: friend?.silent
- }
- }
-
- /** 缇よ亰锛氫細璇濆綊灞炲埌 groupId */
- const convertGroupConversation = (message: ImGroupMessageApi.GroupMessageRespVO) => {
- const group = groupStore.getGroup(message.groupId)
- return {
- type: ImConversationType.GROUP,
- targetId: message.groupId,
- name: group ? getGroupDisplayName(group) : String(message.groupId),
- avatar: group?.avatar || '',
- silent: group?.silent
- }
- }
-
- /**
- * 鍒嗙被鍨嬫媺鍙栫绾挎秷鎭細缈婚〉 / minId 娓告爣鎺ㄨ繘 / 绌洪〉鍋滅敱 runMinIdPull 璐熻矗锛岃繖閲屽彧鍋氭帴鍙e垎鏀� + 閫愭潯涓氬姟鍒嗗彂
- * 锛堟挙鍥� / 濂藉弸閫氱煡 / 鏅�氭秷鎭級+ 鍏ュ簱銆�
- *
- * 鍙栨秷璇箟涓ゅ眰瀹堝崼锛岀粡 isActive 浼犲叆 runMinIdPull锛屼换涓�涓嶇瓑鍗充涪寮冩湰鎵逛笉鍏ュ簱銆佸仠姝㈢炕椤碉紝閬垮厤鏃� session 鍝嶅簲钀藉埌鏂� store锛�
- * 1. startEpoch锛歝ancelPull() 閫掑 pullEpoch锛涚寮� IM / 鍒囪处鍙锋椂璺冲嚭
- * 2. startUserId锛氭瘡鎵� await 鍚庢瘮瀵瑰綋鍓嶇櫥褰� userId锛涢槻寰� logout / 澶� tab 涓嬬敤鎴峰凡鍒囦絾 cancelPull 鏈Е鍙�
- */
- const pullByType = async (
- conversationType: number,
- startMinId: number,
- startEpoch: number,
- startUserId: number,
- signal: AbortSignal
- ) => {
- // 绉佽亰 / 缇よ亰 / 棰戦亾鍚勮嚜涓�濂楁帴鍙o紱鎸� conversationType 鍒嗘敮璋冨害銆傜炕椤垫満鍒讹紙minId 娓告爣 / 绌洪〉鍒ゆ柇 / 闃叉缈伙級浜ょ粰 runMinIdPull
- const isPrivate = conversationType === ImConversationType.PRIVATE
- const isChannel = conversationType === ImConversationType.CHANNEL
- const size = isPrivate ? MESSAGE_PRIVATE_PULL_SIZE : MESSAGE_GROUP_PULL_SIZE
- const isStillValid = () =>
- !signal.aborted && pullEpoch === startEpoch && getCurrentUserId() === startUserId
- await runMinIdPull<PulledRawMessage>({
- initialMinId: startMinId,
- pageSize: size,
- isActive: isStillValid,
- fetchPage: ({ minId, size }) => {
- if (isPrivate) {
- return apiPullPrivateMessageList({ minId, size }, signal)
- }
- if (isChannel) {
- return apiPullChannelMessageList({ minId, size }, signal)
- }
- return apiPullGroupMessageList({ minId, size }, signal)
- },
- applyPage: async (list, nextMinId) => {
- const pulledMessages: PulledMessage[] = []
- // 閫愭潯 dispatch锛氬師娑堟伅璧版壒閲� insert锛汻ECALL 淇″彿璧版壒閲� recall 鎶婂悓鎵瑰唴宸� insert 鐨勫師娑堟伅鏇存柊涓烘挙鍥炴彁绀恒��
- // 鍚庣鎸� id 鍗囧簭杩斿洖锛屼笖淇″彿 id 涓�瀹� > 鍘熸秷鎭� id锛堝厛鏇存柊 status 鍐嶆彃淇″彿锛夛紝鎵�浠ュ師娑堟伅涓�瀹氬厛鍒般�乺ecallMessage 鎵惧緱鍒�
- for (const raw of list) {
- if (isChannel) {
- const message = raw as ImChannelMessageApi.ChannelMessageRespVO
- pulledMessages.push({
- kind: 'insert',
- conversationInfo: convertChannelConversation(message),
- message: convertChannelMessage(message)
- })
- continue
- }
- if (isPrivate) {
- const message = raw as ImPrivateMessageApi.PrivateMessageRespVO
- // 鐗规畩锛氭挙鍥炴秷鎭殑澶勭悊
- if (message.type === ImContentType.RECALL) {
- pulledMessages.push({
- kind: 'recall',
- conversationType: ImConversationType.PRIVATE,
- targetId: getPrivatePeerId(message),
- recallSignalContent: message.content
- })
- continue
- }
- // 鐗规畩锛氬巻鍙插ソ鍙嬩簨浠跺彧杩樺師鑱婂ぉ姘旀场锛涘ソ鍙嬩富鏁版嵁鐢卞ソ鍙嬪閲忚ˉ鍋垮悓姝�
- // 浠� FRIEND_ADD / FRIEND_DELETE 鎵嶄綔涓轰細璇濇皵娉″叆娑堟伅鍒楄〃
- if (isFriendNotification(message.type) && !isFriendChatTip(message.type)) {
- continue
- }
- // 鍏跺畠娑堟伅姝e父鍏ヤ細璇濇秷鎭垪琛�
- pulledMessages.push({
- kind: 'insert',
- conversationInfo: convertPrivateConversation(message),
- message: convertPrivateMessage(message)
- })
- } else {
- const message = raw as ImGroupMessageApi.GroupMessageRespVO
- // 鐗规畩锛氭挙鍥炴秷鎭殑澶勭悊
- if (message.type === ImContentType.RECALL) {
- pulledMessages.push({
- kind: 'recall',
- conversationType: ImConversationType.GROUP,
- targetId: message.groupId,
- recallSignalContent: message.content
- })
- continue
- }
- pulledMessages.push({
- kind: 'insert',
- conversationInfo: convertGroupConversation(message),
- message: convertGroupMessage(message)
- })
- }
- }
- // 鍏ュ簱 + 鎺ㄨ繘 messageMaxId锛沶extMinId 涓虹┖锛堟湰鎵规棤鏈夋晥 id锛夋椂涓嶆帹杩涙父鏍囷紝涓庢棫閫昏緫涓�鑷�
- await messageStore.applyPulledMessageList(pulledMessages, conversationType, nextMinId)
- }
- })
- }
-
- /** 鍚屼竴鏃跺埢鍙厑璁镐竴娆� pull锛歩ndex.vue 鐨勬墜鍔ㄨ皟鐢ㄤ笌閲嶈繛 watch 瑙﹀彂鍙兘骞跺彂锛屽叡鐢ㄥ悓涓�涓� promise 鍗冲彲鍘婚噸 */
- let pullPromise: null | Promise<void> = null
- let pullAbortController: AbortController | null = null
-
- /**
- * 棣栨 pull 鏄惁宸插畬鎴愩�備粎鍦ㄧ疆 true 鍚庯紝isConnected watch 鎵嶄細瑙﹀彂 pull銆�
- * 闃叉 socket onopen 姣� friendStore/groupStore 棰勬媺鍏堝埌杈炬椂锛寃atcher 鎶㈣窇閫犳垚娑堟伅鎻掑叆鏃╀簬浼氳瘽鍏冩暟鎹彲瑙�
- */
- let initialPulled = false
-
- /**
- * pull 杞璁℃暟锛涘垏璐﹀彿 / 绂诲紑 IM 鏃� cancelPull() 閫掑锛屾棫 pullByType 寰幆鎸� epoch 鑷鍚庤烦鍑�
- * 閬垮厤鏃� session 鐨勬帴鍙e搷搴斿湪鏂� session 钀藉湴锛岄�犳垚璺ㄨ处鍙锋秷鎭硠婕�
- *
- * 娉ㄦ剰锛氭櫘閫氭柇杩烇紙WS 鐭柇锛変笉鍙栨秷 pull鈥斺�旂綉缁滄姈鍔� / 鏈嶅姟绔噸鍚兘灞炰簬鏈处鍙锋甯哥敓鍛藉懆鏈燂紝
- * 鍙栨秷浼氬鑷撮鎷夎涓柇鍚� initialPulled 姘歌繙鍋滃湪 false锛屽悗缁噸杩� watcher 涓嶅啀琛ユ媺
- */
- let pullEpoch = 0
-
- /** 鏄惧紡鍙栨秷锛氫粎鐢� index.vue onUnmounted锛堢寮� IM / 鍒囪处鍙� / 璺敱璺冲嚭锛夎皟鐢� */
- const cancelPull = () => {
- pullEpoch++
- pullAbortController?.abort()
- pullAbortController = null
- // 鏃� promise 浠嶅湪 finally 闃舵璺戯紝浣� epoch 瀹堝崼宸查樆鏂悗缁壇浣滅敤锛涜繖閲岀珛鍒昏 pullPromise = null 璁╂柊涓�杞彲閲嶅叆
- pullPromise = null
- // 鍚屾涓㈠純 WS 缂撳啿甯э紱鏃� pull 宸蹭笉浼� flushBuffer锛岃嫢涓嶆竻涓嬫杩� IM 绗竴娆� pullOnce 浼氭妸鏃� session 鐨勫抚鍥炴斁杩涙柊 store
- wsStore.discardBuffer()
- }
-
- /**
- * 鐘舵�佷簨浠惰ˉ鍋匡細濂藉弸 / 濂藉弸鐢宠璧板閲忥紱缇ゅ垪琛ㄥ拰缇ょ敵璇风孩鐐硅蛋蹇収鍒锋柊
- *
- * 棣栫櫥涓绘暟鎹敱 index.vue 椹卞姩锛岄噸杩炴椂鍚� store 宸插氨浣嶏紝澶氳矾 allSettled 骞跺彂浜掍笉褰卞搷锛屽崟璺け璐ヤ粎璁版棩蹇椼��
- * 缇ゆ垚鍛樹笉鍋氬叏灞�澧為噺鍚屾锛岄噸杩炲彧鏍囪鏈湴缇ゆ垚鍛� cache 杩囨湡锛岃繘鍏ョ兢浼氳瘽鎴栨垚鍛樺垪琛ㄦ椂鍐嶆寜 groupId 鍒锋柊銆�
- */
- const pullStateEvents = async (): Promise<void> => {
- // 1. 娓呯悊杩炴帴绾х紦瀛�
- messageStore.clearPrivateReadMaxIdCache()
- rtcStore.clearGroupCallCache()
- groupStore.markAllGroupActiveCallsExpired()
- groupStore.markAllGroupInfoExpired()
- groupStore.markAllGroupMembersExpired()
- // 2. 骞跺彂琛ュ伩杩滅鐘舵��
- const results = await Promise.allSettled([
- friendStore.pullFriends(),
- friendStore.pullFriendRequests(),
- conversationStore.pullConversationReads(),
- groupStore.fetchGroupList(true),
- groupRequestStore.pullGroupRequests(),
- groupRequestStore.fetchUnhandledGroupRequestList()
- ])
- for (const result of results) {
- if (result.status === 'rejected') {
- console.warn('[IM] 鐘舵�佷簨浠跺閲忚ˉ鍋垮け璐�', result.reason)
- }
- }
- }
-
- /** 鎵ц涓�娆″叏閲忓閲忔媺鍙栵紙閲嶅叆瀹夊叏锛氳繘琛屼腑鍐嶆璋冪敤澶嶇敤鍚屼竴涓� promise锛� */
- const pullOnce = (): Promise<void> => {
- if (!currentUserId) {
- return Promise.resolve()
- }
- if (pullPromise) {
- return pullPromise
- }
- const startEpoch = pullEpoch
- // 鍚姩鏃剁殑鐢ㄦ埛蹇収锛沺ullByType 姣忔壒 await 鍚庢瘮瀵瑰綋鍓嶇櫥褰曠敤鎴凤紝璐﹀彿鍙樹簡绔嬪埢涓㈠純
- const startUserId = currentUserId
- const abortController = new AbortController()
- pullAbortController = abortController
- // 鏈疆 pull 浠嶅睘浜庡綋鍓� session锛歟poch 鏈紓 + 鐢ㄦ埛鏈垏锛涗换浣曞姩鏂� store 鐘舵�佺殑鍓綔鐢ㄩ兘瑕佸厛杩囪繖閬撳叧
- const isCurrentPull = () =>
- !abortController.signal.aborted &&
- pullEpoch === startEpoch &&
- getCurrentUserId() === startUserId
- pullPromise = (async () => {
- try {
- // 鏃� puller 鍦� cancelPull 鏈Е鍙戠殑寮傚父璺緞涓婂啀杩涙潵鏃讹紝鍏堜簬浠讳綍鍓綔鐢ㄩ��鍑猴紝閬垮厤姹℃煋鏂� session 鐨� loading
- if (!isCurrentPull()) {
- return
- }
- conversationStore.loading = true
- let messagePullSucceeded = false
- try {
- // 骞跺彂鎷夊彇绉佽亰 + 缇よ亰 + 棰戦亾娑堟伅锛岄檷浣庡垵濮嬪姞杞借�楁椂
- await Promise.all([
- pullByType(
- ImConversationType.PRIVATE,
- messageStore.privateMessageMaxId,
- startEpoch,
- startUserId,
- abortController.signal
- ),
- pullByType(
- ImConversationType.GROUP,
- messageStore.groupMessageMaxId,
- startEpoch,
- startUserId,
- abortController.signal
- ),
- pullByType(
- ImConversationType.CHANNEL,
- messageStore.channelMessageMaxId,
- startEpoch,
- startUserId,
- abortController.signal
- )
- ])
- messagePullSucceeded = true
- } catch (error) {
- if (isAbortError(error)) {
- return
- }
- console.error('[IM] 鎷夊彇绂荤嚎娑堟伅澶辫触:', error)
- } finally {
- // 浠嶅睘鏈疆鎵嶅浣� loading锛涙棫杞 cancel / 鍒囪处鍙锋椂鐢辨柊涓�杞嚜绠★紝閬垮厤瑕嗙洊鏂� session 鐨� true
- if (isCurrentPull()) {
- conversationStore.loading = false
- }
- }
-
- // 鍙栨秷 / 鍒囪处鍙峰悗璺宠繃 flushBuffer / 鎺掑簭 / 宸茶浣嶇疆琛ラ綈
- if (!isCurrentPull()) {
- return
- }
- if (!messagePullSucceeded) {
- return
- }
-
- // 鍥炴斁 WebSocket 鍦� loading 鏈熼棿鏀跺埌鐨勭紦鍐叉秷鎭�
- const buffered = wsStore.flushBuffer()
- const replayPersistPromises: Promise<void>[] = []
- for (const item of buffered) {
- if (item.conversationType === ImConversationType.PRIVATE) {
- replayPersistPromises.push(wsStore.handlePrivateMessage(item.payload))
- } else if (item.conversationType === ImConversationType.CHANNEL) {
- replayPersistPromises.push(wsStore.handleChannelMessage(item.payload))
- } else {
- replayPersistPromises.push(wsStore.handleGroupMessage(item.payload))
- }
- }
- await Promise.all(replayPersistPromises)
-
- // pull + replay 閮藉畬鎴愬悗鍐嶆帓搴忥紝閬垮厤鍥炴斁娑堟伅鎵撲贡椤哄簭
- conversationStore.sortConversationList()
-
- // 閲嶈繛 / 鍐峰惎鍔ㄥ悗琛ラ綈褰撳墠婵�娲荤鑱婁細璇濈殑銆屽鏂瑰凡璇讳綅缃��
- // 绂荤嚎鏈熼棿閿欒繃鐨� RECEIPT 鎺ㄩ�佷細琚繖閲岃ˉ鍥烇紱鍏朵粬绉佽亰浼氳瘽绛夌敤鎴风偣寮�鏃剁敱 index.vue 鐨� watch 瑙﹀彂
- // 绉佽亰宸茶鍏抽棴鏃惰烦杩囷紝閬垮厤鎵撳埌宸茬鐢ㄦ帴鍙hЕ鍙戦敊璇棩蹇�
- const active = conversationStore.activeConversation
- if (MESSAGE_PRIVATE_READ_ENABLED && active && active.type === ImConversationType.PRIVATE) {
- try {
- const maxReadId = await apiGetPrivateMaxReadMessageId(
- active.targetId,
- abortController.signal
- )
- if (!isCurrentPull()) {
- return
- }
- messageStore.updatePrivateReadMaxId(active.targetId, maxReadId)
- if (maxReadId) {
- messageStore.applyMessageReadReceipt({
- conversationType: ImConversationType.PRIVATE,
- targetId: active.targetId,
- privateReadMaxId: maxReadId
- })
- }
- } catch (error) {
- if (isAbortError(error)) {
- return
- }
- console.warn('[IM] 鎷夊彇瀵规柟宸茶浣嶇疆澶辫触', error)
- }
- }
- } finally {
- // 浠嶅睘鏈疆锛氭甯稿畬鎴愰鎷夛紱epoch 绛変絾 userId 鍒囦簡锛氭竻 pullPromise 闃插崱姝汇�佷笉鏍囬鎷夛紱epoch 婕傦細cancelPull 宸叉竻锛宯o-op
- if (isCurrentPull()) {
- pullPromise = null
- initialPulled = true
- if (pullAbortController === abortController) {
- pullAbortController = null
- }
- } else if (pullEpoch === startEpoch) {
- pullPromise = null
- if (pullAbortController === abortController) {
- pullAbortController = null
- }
- }
- }
- })()
- return pullPromise
- }
-
- /**
- * 鏂綉鏈熼棿 WS 鏀朵笉鍒版帹閫侊細閲嶈繛鍚庢棦瑕佹寜 minId 琛ラ綈娑堟伅锛屼篃瑕佹寜 update_time + id 琛ラ綈濂藉弸 / 缇� / 缇ょ敵璇风姸鎬併��
- * 棣栨杩炴帴鐢� index.vue 鏄惧紡椹卞姩锛坧ullOnce 鎷夋秷鎭� + 鍚� store 棣栨媺锛夛紝杩欓噷浠呰鐩栦箣鍚庣殑閲嶈繛銆�
- * 閲嶈繛鏃� store 宸插氨浣嶏紝pullStateEvents 涓� pullOnce 骞跺彂鍗冲彲锛屾棤闇�銆屽厛灏变綅鍐嶆媺娑堟伅銆嶇殑棣栫櫥椤哄簭绾︽潫銆�
- */
- watch(
- () => wsStore.isConnected,
- (isConnected) => {
- if (isConnected && initialPulled) {
- void pullOnce()
- void pullStateEvents()
- }
- }
- )
-
- return { pullOnce, cancelPull, convertPrivateMessage, convertGroupMessage }
-}
diff --git a/src/views/im/home/composables/useMessageSender.ts b/src/views/im/home/composables/useMessageSender.ts
deleted file mode 100644
index 4562128..0000000
--- a/src/views/im/home/composables/useMessageSender.ts
+++ /dev/null
@@ -1,337 +0,0 @@
-import type { Conversation, Message } from '../types'
-
-import { readChannelMessages as apiReadChannelMessages } from '#/api/im/message/channel'
-import {
- readGroupMessages as apiReadGroupMessages,
- recallGroupMessage as apiRecallGroupMessage,
- sendGroupMessage as apiSendGroupMessage
-} from '#/api/im/message/group'
-import {
- getPrivateMaxReadMessageId as apiGetPrivateMaxReadMessageId,
- readPrivateMessages as apiReadPrivateMessages,
- recallPrivateMessage as apiRecallPrivateMessage,
- sendPrivateMessage as apiSendPrivateMessage
-} from '#/api/im/message/private'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { MESSAGE_GROUP_READ_ENABLED, MESSAGE_PRIVATE_READ_ENABLED } from '../../utils/config'
-import { ImContentType, ImConversationType, ImMessageStatus } from '../../utils/constants'
-import { getClientConversationId } from '../../utils/db'
-import {
- generateClientMessageId,
- type QuoteMessage,
- serializeMessage,
- type TextMessage,
- withQuotePayload
-} from '../../utils/message'
-import { useConversationStore } from '../store/conversationStore'
-import { useMessageStore } from '../store/messageStore'
-
-/** 闈炴枃鏈秷鎭殑鎵╁睍閫夐」锛堥�氱敤锛� */
-interface SendExtOptions {
- atUserIds?: number[] // 缇よ亰 @ 鐨勭敤鎴风紪鍙峰垪琛�
- receipt?: boolean // 鏄惁闇�瑕佺兢鍥炴墽锛堥粯璁� false锛�
- targetId?: number // 瑕嗙洊榛樿鐨� targetId
- /**
- * 鏄惧紡鎸囧畾鐩爣浼氳瘽锛堣浆鍙� / 鍚嶇墖鎺ㄨ崘鍦烘櫙锛�
- *
- * 涓嶄紶鏃堕粯璁ゅ彇 conversationStore.activeConversation锛涗紶鍏ユ椂鎸夋湰鍊煎彂閫� + 涔愯鏇存柊鍒板搴斾細璇濓紝
- * 涓嶈姹傝浼氳瘽褰撳墠鏄縺娲荤姸鎬侊紙閫傚悎鍙戠粰銆岄潪褰撳墠浼氳瘽銆嶇殑澶氫釜鐩爣锛�
- */
- conversation?: Conversation
- /** 琚紩鐢ㄦ秷鎭紙鍙�夛級锛氬啓杩� content.quote 鐢ㄤ簬涔愯娓叉煋锛屾湇鍔$鎸� quote.messageId 鍙嶆煡閲嶇畻瑕嗙洊 */
- quote?: QuoteMessage
- /**
- * 澶嶇敤宸插瓨鍦ㄧ殑鏈湴鍗犱綅娑堟伅 clientMessageId锛堝獟浣撲笂浼犲満鏅級
- *
- * 濯掍綋涓婁紶閾捐矾鍦ㄨ姹傛湇鍔$鍓嶅凡缁� insertMessage 浜嗗崰浣嶏紙甯� blob URL + 杩涘害鏉★級锛�
- * 杩欓噷璺宠繃 buildLocalMessage / insertMessage锛岀洿鎺ユ嬁杩欎釜 id 璧� ackMessage 鏀跺熬锛岄伩鍏嶉噸澶嶆彃鍏ヤ袱鏉�
- */
- existingClientMessageId?: string
-}
-
-/**
- * 娑堟伅鍙戦�� / 鎾ゅ洖 / 宸茶 缁勫悎寮忛�昏緫
- *
- * 璁捐瑕佺偣锛�
- * 1. 绉佽亰 / 缇よ亰鎺ュ彛绛惧悕瀵圭О锛屾寜 conversation.type 鍒嗘敮璋冨害锛屽樊寮傚湪鍒嗘敮鍐呴儴娑堝寲
- * 2. 鍙戦�佽蛋銆屼箰瑙傛洿鏂般�嶏細鍏� insertMessage 鍐欏叆 SENDING 鍗犱綅锛岃姹傛垚鍔� ackMessage 鏇存柊涓� NORMAL锛屽け璐ユ洿鏂颁负 FAILED
- * 3. 鎾ゅ洖涓嶅仛涔愯鏇存柊锛氭湇鍔$閫氳繃 WebSocket RECALL 浜嬩欢鍥炰紶锛岀敱 websocketStore 缁熶竴鏇存柊鐘舵�侊紝閬垮厤缃戠粶澶辫触鍚庝笉鍙洖閫�
- * 4. 宸茶涓婃姤锛氭湰绔珛鍒绘竻鏈鏁板苟璁板綍鏈湴璇讳綅缃紱鎺ュ彛澶辫触浠呰褰曟棩蹇�
- */
-export const useMessageSender = () => {
- const conversationStore = useConversationStore()
- const messageStore = useMessageStore()
-
- /** 鏋勯�犳湰鍦颁箰瑙傛秷鎭璞� */
- const buildLocalMessage = (opts: {
- atUserIds?: number[]
- clientMessageId: string
- content: string
- targetId: number
- type: number
- }): Message => {
- return {
- clientMessageId: opts.clientMessageId,
- type: opts.type,
- content: opts.content,
- status: ImMessageStatus.SENDING,
- sendTime: Date.now(),
- senderId: getCurrentUserId(),
- targetId: opts.targetId,
- selfSend: true,
- atUserIds: opts.atUserIds
- }
- }
-
- /**
- * 鍙戦�佷换鎰忕被鍨嬬殑娑堟伅锛堝簳灞傚疄鐜帮級
- * 1. 鏂囨湰銆佸浘鐗囥�佹枃浠躲�佽闊崇瓑閮借蛋杩欓噷
- * 2. type / content 鐢辫皟鐢ㄦ柟鏋勯��
- * 3. 杩斿洖鍊硷細鎴愬姛 true / 澶辫触 false锛堝け璐ユ椂鏈湴鍗犱綅宸叉爣 FAILED锛夛紱鍙傛暟缂哄け绛夋棤娉曞彂閫佺殑鍦烘櫙涔熻繑 false
- * 杞彂 / 鍚嶇墖鎺ㄨ崘绛夊満鏅寜杩斿洖鍊煎喅瀹氭槸鍚︾户缁悗缁姩浣滐紙濡傛湁鐣欒█鏃朵粎鍦ㄥ悕鐗囨垚鍔熷悗鍐嶅彂鐣欒█锛�
- */
- const sendRaw = async (
- type: number,
- content: string,
- options?: SendExtOptions
- ): Promise<boolean> => {
- // 1. 鍙傛暟鏍¢獙锛氫紭鍏堢敤鏄惧紡浼犲叆鐨� conversation锛堣浆鍙戝満鏅級锛屽惁鍒欏彇婵�娲讳細璇�
- const conversation = options?.conversation ?? conversationStore.activeConversation
- if (!conversation) {
- return false
- }
- const realTarget = options?.targetId || conversation.targetId
- if (!realTarget) {
- return false
- }
-
- // 2. 鍑嗗 clientMessageId锛氬獟浣撲笂浼犻摼璺湪 step 1 宸茬粡 insertMessage 鍗犱綅锛岃繖閲岀洿鎺ュ鐢� id锛涘叾浣欏満鏅蛋榛樿涔愯鎻掑叆
- let clientMessageId: string
- if (options?.existingClientMessageId) {
- clientMessageId = options.existingClientMessageId
- // 鍗犱綅鑻ュ凡琚垹闄わ紙涓婁紶鏈熼棿鐢ㄦ埛鍙抽敭鍒犻櫎 / 鎾ゅ洖 / removeMessage 绛夛級鍒欐斁寮冨彂閫侊紝
- // 鍚﹀垯 sendRaw 浠嶄細鎶婃秷鎭帹鍒版湇鍔$锛屽鑷�"鏈湴鏃犳皵娉� / 瀵规柟鍗存敹鍒颁竴鏉�"
- const stillExists = messageStore
- .getMessageList(conversation.type, realTarget)
- .some((message) => message.clientMessageId === clientMessageId && !message._ackMerging)
- if (!stillExists) {
- return false
- }
- } else {
- clientMessageId = generateClientMessageId()
- const message = buildLocalMessage({
- clientMessageId,
- content,
- targetId: realTarget,
- type,
- atUserIds: options?.atUserIds
- })
- const conversationInfo = {
- type: conversation.type,
- targetId: realTarget,
- name: conversation.name || String(realTarget),
- avatar: conversation.avatar || ''
- }
- void messageStore.insertMessage(conversationInfo, message).catch(() => undefined)
- }
-
- // 3. 鍙戦�佽姹傦細鎸変細璇濈被鍨嬪垎鍙戝埌涓嶅悓鎺ュ彛锛涙垚鍔熷悗 ackMessage 鏇存柊涓� NORMAL锛屽け璐ユ洿鏂颁负 FAILED
- try {
- if (conversation.type === ImConversationType.PRIVATE) {
- const data = await apiSendPrivateMessage({
- clientMessageId,
- receiverId: realTarget,
- type,
- content
- })
- void messageStore
- .ackMessage(conversation.type, realTarget, clientMessageId, {
- id: data.id,
- sendTime: new Date(data.sendTime).getTime(),
- status: data.status,
- receiptStatus: data.receiptStatus,
- content: data.content
- })
- .catch(() => undefined)
- } else if (conversation.type === ImConversationType.GROUP) {
- const data = await apiSendGroupMessage({
- clientMessageId,
- groupId: realTarget,
- type,
- content,
- atUserIds: options?.atUserIds,
- receipt: options?.receipt
- })
- void messageStore
- .ackMessage(conversation.type, realTarget, clientMessageId, {
- id: data.id,
- sendTime: new Date(data.sendTime).getTime(),
- status: data.status,
- receiptStatus: data.receiptStatus,
- readCount: data.readCount,
- content: data.content
- })
- .catch(() => undefined)
- }
- return true
- } catch (error) {
- console.error('[IM] 娑堟伅鍙戦�佸け璐�', { type, realTarget, clientMessageId }, error)
- void messageStore
- .ackMessage(conversation.type, realTarget, clientMessageId, {
- status: ImMessageStatus.FAILED
- })
- .catch(() => undefined)
- return false
- }
- }
-
- /**
- * 鍙戦�佹枃鏈秷鎭紙鏈�甯哥敤鐨勫揩鎹峰叆鍙o級锛歮essage-input.vue 鏂囨湰鍥炶溅璧拌繖閲�
- * 杩斿洖鍊硷細鎴愬姛 true / 澶辫触 false / 绌烘枃鏈� false锛堜笌 sendRaw 瀵归綈锛岃浆鍙戝満鏅寜杩斿洖鍊煎垽鏂級
- */
- const send = async (text: string, options?: SendExtOptions): Promise<boolean> => {
- if (!text.trim()) {
- return false
- }
- const payload = withQuotePayload<TextMessage>({ content: text }, options?.quote)
- return sendRaw(ImContentType.TEXT, serializeMessage(payload), options)
- }
-
- /**
- * 鎾ゅ洖鏌愭潯娑堟伅
- * 1. 鏈嶅姟绔細閫氳繃 WebSocket RECALL 浜嬩欢鍥炰紶锛屾湰绔� UI 鐢� websocketStore 缁熶竴鏇存柊
- * 2. 姝ゅ涓嶅仛涔愯鎾ゅ洖锛岄伩鍏嶇綉缁滃け璐ュ悗鐘舵�佷笉鍙洖閫�
- */
- const recall = async (message: Message) => {
- // 鍙傛暟鏍¢獙锛氭湰鍦板崰浣嶆秷鎭笉鑳芥挙鍥�
- if (!message.id) {
- return
- }
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- // 绉佽亰 / 缇よ亰鎺ュ彛绛惧悕涓�鑷达紝鎸変細璇濈被鍨嬪垎鍙�
- const isPrivate = conversation.type === ImConversationType.PRIVATE
- try {
- await (isPrivate ? apiRecallPrivateMessage(message.id) : apiRecallGroupMessage(message.id))
- } catch (error) {
- console.error('[IM] 鎾ゅ洖澶辫触', { messageId: message.id, type: conversation.type }, error)
- }
- }
-
- /**
- * 瑙﹀彂褰撳墠浼氳瘽鐨勫凡璇讳笂鎶ワ紙鍒囦細璇� / 杩涘叆椤甸潰鏃惰皟鐢級
- * 1. 鏈绔嬪埢娓呮湭璇绘暟骞舵帹杩涜浣嶇疆
- * 2. 宸茶浣嶇疆鍙栧凡鍔犺浇娑堟伅鍜屼細璇濇湯鏉℃秷鎭殑鏈�澶ф湇鍔$ id
- */
- const readActive = async () => {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- let loadedMaxMessageId = 0
- for (const message of messageStore.getMessages(
- getClientConversationId(conversation.type, conversation.targetId)
- )) {
- if (message.id && message.id > loadedMaxMessageId) {
- loadedMaxMessageId = message.id
- }
- }
- const maxMessageId = Math.max(loadedMaxMessageId, conversation.lastMessageId || 0)
- const readReported = conversationStore.isReportedReadPositionCovered(
- conversation.type,
- conversation.targetId,
- maxMessageId
- )
- if (readReported) {
- conversationStore.markConversationRead(conversation.type, conversation.targetId)
- return
- }
- const isPrivate = conversation.type === ImConversationType.PRIVATE
- const isGroup = conversation.type === ImConversationType.GROUP
- const isChannel = conversation.type === ImConversationType.CHANNEL
- // 鏈湴鏍囪宸茶锛氭湭璇绘暟娓呴浂锛圲I 绔嬪埢鍝嶅簲锛�
- conversationStore.markConversationRead(conversation.type, conversation.targetId, maxMessageId)
- if (!maxMessageId) {
- return
- }
- // 鎺ュ彛璋冪敤锛氭寜浼氳瘽绫诲瀷鍒嗗彂锛屽苟鎸夊搴斿凡璇诲紑鍏虫帶鍒�
- if (!isPrivate && !isGroup && !isChannel) {
- return
- }
- if (isPrivate && !MESSAGE_PRIVATE_READ_ENABLED) {
- return
- }
- if (isGroup && !MESSAGE_GROUP_READ_ENABLED) {
- return
- }
- try {
- if (isPrivate) {
- await apiReadPrivateMessages(conversation.targetId, maxMessageId)
- } else if (isGroup) {
- await apiReadGroupMessages(conversation.targetId, maxMessageId)
- } else {
- await apiReadChannelMessages(conversation.targetId, maxMessageId)
- }
- conversationStore.markConversationReadReported(
- conversation.type,
- conversation.targetId,
- maxMessageId
- )
- } catch (error) {
- console.error(
- '[IM] 鏍囪宸茶澶辫触',
- { type: conversation.type, targetId: conversation.targetId, maxMessageId },
- error
- )
- }
- }
-
- /**
- * 鎷夊彇銆屽鏂瑰凡璇诲埌鎴戝摢鏉℃秷鎭�嶅苟琛ラ綈鏈湴鐘舵��
- *
- * 1. 寮ヨˉ绂荤嚎 / 澶氱鏈熼棿閿欒繃鐨� RECEIPT 鎺ㄩ�侊細杩涘叆绉佽亰浼氳瘽鎴栨柇绾块噸杩炲悗璋冧竴娆★紝
- * 鎶婂鏂� maxReadId 鍚屾鍒版湰鍦版秷鎭� status锛岄伩鍏嶅鏂规槑鏄庤浜嗐�佹湰绔嵈浠嶆樉绀烘湭璇�
- * 2. 浠呯鑱婁娇鐢細缇よ亰宸茶浣嶇疆鍦ㄦ瘡鏉℃秷鎭殑 readCount / receiptStatus 瀛楁锛岀绾挎媺鍙栬嚜甯﹀洖
- */
- const syncPrivateReadStatus = async (peerId: number) => {
- if (!peerId) {
- return
- }
- // 绉佽亰宸茶鍏抽棴锛氳烦杩囧鏂瑰凡璇讳綅缃悓姝ワ紝閬垮厤鏃犺皳鎺ュ彛璋冪敤
- if (!MESSAGE_PRIVATE_READ_ENABLED) {
- return
- }
- const cachedMaxReadId = messageStore.getPrivateReadMaxId(peerId)
- if (cachedMaxReadId !== undefined) {
- if (cachedMaxReadId > 0) {
- messageStore.applyMessageReadReceipt({
- conversationType: ImConversationType.PRIVATE,
- targetId: peerId,
- privateReadMaxId: cachedMaxReadId
- })
- }
- return
- }
- try {
- // 鎷夊彇瀵规柟宸茶鍒扮殑鏈�澶ф秷鎭� id
- const maxReadId = await apiGetPrivateMaxReadMessageId(peerId)
- messageStore.updatePrivateReadMaxId(peerId, maxReadId)
- if (!maxReadId) {
- return
- }
- // applyMessageReadReceipt 鍐呴儴鎶� 鈮� maxReadId 鐨勬湰绔秷鎭洖鎵ф洿鏂颁负 DONE
- messageStore.applyMessageReadReceipt({
- conversationType: ImConversationType.PRIVATE,
- targetId: peerId,
- privateReadMaxId: maxReadId
- })
- } catch (error) {
- console.warn('[IM] 鎷夊彇瀵规柟宸茶浣嶇疆澶辫触', { peerId }, error)
- }
- }
-
- return { send, sendRaw, recall, readActive, syncPrivateReadStatus }
-}
diff --git a/src/views/im/home/composables/useMuteOverlay.ts b/src/views/im/home/composables/useMuteOverlay.ts
deleted file mode 100644
index b0ed764..0000000
--- a/src/views/im/home/composables/useMuteOverlay.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { computed, type ComputedRef, onScopeDispose, ref } from 'vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { ImConversationType, ImGroupMemberRole } from '../../utils/constants'
-import { isGroupQuit } from '../../utils/user'
-import { useConversationStore } from '../store/conversationStore'
-import { useGroupStore } from '../store/groupStore'
-
-export type MuteOverlayInfo = { icon: string; text: string; }
-
-/**
- * 妯″潡绾у叡浜� now tick + 璁㈤槄璁℃暟锛�
- * MessageItem v-for 鏃舵瘡鏉℃秷鎭兘浼� useMuteOverlay()锛屽崟瀹炰緥鑷捣 timer 浼氬彉鎴愬嚑鐧句釜 30s 瀹氭椂鍣紱
- * 鏀规垚妯″潡绾у叡浜悗鎵�鏈夎闃呰�呭叡鐢ㄤ竴浠� setInterval锛岃闃呮暟娓呴浂鏃朵篃娓呮帀 timer锛岄伩鍏嶅唴瀛樹笌鏃堕挓婕傜Щ
- */
-const sharedNow = ref(Date.now())
-let sharedTickTimer: null | number = null
-let subscriberCount = 0
-
-function subscribeNowTick(): void {
- subscriberCount++
- if (!sharedTickTimer) {
- sharedTickTimer = window.setInterval(() => {
- sharedNow.value = Date.now()
- }, 30_000)
- }
-}
-
-function unsubscribeNowTick(): void {
- subscriberCount--
- if (subscriberCount <= 0) {
- subscriberCount = 0
- if (sharedTickTimer) {
- window.clearInterval(sharedTickTimer)
- sharedTickTimer = null
- }
- }
-}
-
-/**
- * 褰撳墠婵�娲讳細璇濈殑绂佽█ / 灏佺鐘舵�侊紱闈炵兢鑱婃垨鏃犵瑷�鏃惰繑鍥� null
- *
- * 浼樺厛绾э細灏佺 > 鍏ㄧ兢绂佽█锛堢兢涓� / 绠$悊鍛樿眮鍏嶏級> 鎴愬憳绂佽█
- *
- * MessageInput 椤堕儴 overlay / handleResend 閲嶈瘯 / uploadAndSendMedia 涓婁紶瀹屽墠閮藉叡鐢ㄤ竴浠斤紝
- * 閬垮厤銆岃緭鍏ユ鎷︿簡浣嗛噸璇曠粫杩囥�嶃�屼笂浼犳湡闂磋绂佽█浣� sendRaw 浠嶅彂鍑哄幓銆嶄箣绫荤殑涓嶄竴鑷�
- */
-export function useMuteOverlay(): ComputedRef<MuteOverlayInfo | null> {
- const conversationStore = useConversationStore()
- const groupStore = useGroupStore()
-
- // 璁㈤槄妯″潡绾� tick锛泂cope 閿�姣佹椂鍙嶈闃咃紝鏈�鍚庝竴涓闃呰�呴��鍦哄悗 timer 涔熻窡鐫�娓�
- subscribeNowTick()
- onScopeDispose(unsubscribeNowTick)
-
- return computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return null
- }
- const group = groupStore.getGroup(conversation.targetId)
- if (!group) {
- return null
- }
- // 鍘嗗彶閫�缇ょ兢锛氬凡閫�缇ゅ彧鑳芥煡鐪嬪巻鍙诧紝绂佹鍙戦�侊紙鏂囨湰 / 鍥剧墖 / 鏂囦欢 / 璇煶 / 閲嶈瘯鍏辩敤杩欎竴灞傛嫤鎴級
- if (isGroupQuit(group)) {
- return { text: '浣犲凡閫�鍑虹兢鑱婏紝浠呭彲鏌ョ湅鍘嗗彶娑堟伅', icon: 'ant-design:logout-outlined' }
- }
- const myId = getCurrentUserId()
- // 缇ゅ皝绂侊細绠$悊鍚庡彴鎿嶄綔锛屾墍鏈変汉涓嶅彲鍙戦��
- if (group.banned) {
- return { text: '璇ョ兢宸茶绠$悊鍛樺皝绂侊紝鏃犳硶鍙戦�佹秷鎭�', icon: 'ant-design:stop-outlined' }
- }
- // 鍏ㄧ兢绂佽█锛氱兢涓昏蛋 ownerUserId 姣旇緝鐩存帴璞佸厤锛涘叾瀹冧汉闇�瑕佹垚鍛樺垪琛ㄥ姞杞藉畬鎵嶈兘鍖哄垎绠$悊鍛� vs 鏅�氭垚鍛�
- // - 鍔犺浇瀹� + 鎴戞槸绠$悊鍛� 鈫� 璞佸厤
- // - 鍔犺浇瀹� + 鎴戜笉鏄鐞嗗憳锛堝惈宸查��缇わ級鈫� 鎷�
- // - 鍔犺浇鏈畬 鈫� 涓嶆樉绀� overlay锛屽悗绔厹搴曟嫆缁濇櫘閫氭垚鍛橈紱閬垮厤璇嫤绠$悊鍛�
- if (group.mutedAll && myId !== group.ownerUserId && group.membersLoaded) {
- const myMember = group.members?.find((m) => m.userId === myId)
- if (myMember?.role !== ImGroupMemberRole.ADMIN) {
- return { text: '鍏ㄧ兢绂佽█涓紝鏆傛椂鏃犳硶鍙戦�佹秷鎭�', icon: 'ant-design:audio-muted-outlined' }
- }
- }
- // 鎴愬憳绂佽█锛歮uteEndTime 鍦ㄦ湭鏉ユ墠绠楋紱鐢ㄥ搷搴斿紡 sharedNow 姣斿锛屽埌鏈熷悗涓嬩竴涓� tick 灏辫 overlay 娑堝け
- const myMember = group.members?.find((m) => m.userId === myId)
- if (myMember?.muteEndTime) {
- const endTime = new Date(myMember.muteEndTime)
- if (endTime.getTime() > sharedNow.value) {
- const pad = (n: number) => n.toString().padStart(2, '0')
- const timeStr =
- `${pad(endTime.getMonth() + 1)}-${pad(endTime.getDate())} ` +
- `${pad(endTime.getHours())}:${pad(endTime.getMinutes())}`
- return {
- text: `鎮ㄥ凡琚瑷�锛岃В闄ゆ椂闂达細${timeStr}`,
- icon: 'ant-design:audio-muted-outlined'
- }
- }
- }
- return null
- })
-}
diff --git a/src/views/im/home/composables/useSelectedItems.ts b/src/views/im/home/composables/useSelectedItems.ts
deleted file mode 100644
index f8364b3..0000000
--- a/src/views/im/home/composables/useSelectedItems.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { computed, type ComputedRef, type Ref } from 'vue'
-
-/**
- * 涓夋�侀�夋嫨闈㈡澘鐨勩�屽凡閫夋暟 + 宸查�夐」鍒楄〃銆嶆淳鐢�
- *
- * - 涓夋�佷紭鍏堢骇锛堜笌 dialog-picker-contract 瀵归綈锛夛細hide > locked > disabled
- * - hide锛氭案杩滃墧闄わ紝涓嶈鏁� / 涓嶈繘宸查�夊垪琛�
- * - locked锛氶粯璁ゅ嬀閫� + 璁℃暟 + 浼樺厛鎺掑湪鍓嶉潰锛涘嵆浣胯澶栭儴濉炶繘 disabledIds 涔熻儨鍑�
- * - disabled锛氫粎杩囨护 selectedIds锛屼笉璁℃暟 / 涓嶈繘宸查�夊垪琛�
- * - 椤哄簭锛歭ockedIds 鍦ㄥ墠锛宻electedIds 绱ч殢锛涙寜鏁扮粍椤哄簭鍗充负銆岀偣鍑婚『搴忋��
- *
- * FriendPickerPanel / GroupMemberPickerPanel 鍏辩敤锛岄伩鍏嶄袱渚� 25 琛屽悓鏋� computed 鍚勫啓涓�浠斤紱
- * Panel 鍐呴儴鐨� isLocked / isDisabled / isSelected 绛夋ā鏉垮垽瀹氬嚱鏁颁粛鍚勮嚜缁存姢锛屾湰 composable 鍙壙鎷呮淳鐢熼噺
- */
-export function useSelectedItems<T>(
- selectedIds: () => readonly number[],
- lockedIds: () => readonly number[],
- disabledIds: () => readonly number[],
- hideIds: () => readonly number[],
- byId: ComputedRef<Map<number, T>> | Ref<Map<number, T>>
-): {
- selectedCount: ComputedRef<number>
- selectedItems: ComputedRef<T[]>
-} {
- const hideSet = computed(() => new Set(hideIds()))
- const disabledSet = computed(() => new Set(disabledIds()))
-
- const selectedCount = computed(() => {
- const merged = new Set<number>()
- for (const id of selectedIds()) {
- if (hideSet.value.has(id) || disabledSet.value.has(id)) {
- continue
- }
- merged.add(id)
- }
- // locked 浠呰 hide 杩囨护锛涘绾﹂噷 locked 鑳滆繃 disabled锛岀‘淇濋攣瀹氶」濮嬬粓璁″叆
- for (const id of lockedIds()) {
- if (hideSet.value.has(id)) {
- continue
- }
- merged.add(id)
- }
- return merged.size
- })
-
- const selectedItems = computed(() => {
- const seen = new Set<number>()
- const result: T[] = []
- // locked 鍦ㄥ墠锛涗粎琚� hide 杩囨护
- for (const id of lockedIds()) {
- if (seen.has(id) || hideSet.value.has(id)) {
- continue
- }
- const item = byId.value.get(id)
- if (item) {
- seen.add(id)
- result.push(item)
- }
- }
- // selectedIds 绱ч殢锛涢澶栬繃婊� disabled
- for (const id of selectedIds()) {
- if (seen.has(id) || disabledSet.value.has(id) || hideSet.value.has(id)) {
- continue
- }
- const item = byId.value.get(id)
- if (item) {
- seen.add(id)
- result.push(item)
- }
- }
- return result
- })
-
- return { selectedCount, selectedItems }
-}
diff --git a/src/views/im/home/composables/useVoicePlayer.ts b/src/views/im/home/composables/useVoicePlayer.ts
deleted file mode 100644
index b396449..0000000
--- a/src/views/im/home/composables/useVoicePlayer.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { ref } from 'vue'
-
-/**
- * 璇煶鎾斁鍏ㄥ眬浜掓枼
- *
- * 妯″潡绾у崟渚嬶細鍚屼竴鏃跺埢鏈�澶氬彧鏈変竴鏉¤闊冲湪鎾紱鏂扮偣鐨勮闊冲仠鎺夋棫鐨�
- * 涓夊鍏ュ彛鍏变韩鍚屼竴鎾斁鎬侊細
- * - MessageBubble锛氭皵娉$偣鍑� toggle 褰撳墠鏉�
- * - MessagePanel锛氬垏浼氳瘽 stop()
- * - MessageHistory / MessageMergeDetailDialog锛氬叧闂椂 stop()
- *
- * key 鐢� MessageBubble 鍦� setup 閲� Symbol() 鐢熸垚瀹炰緥绾у敮涓�韬唤锛涗笉鍚屾潵婧愶紙涓婚潰鏉� / 鍘嗗彶鎶藉眽 /
- * 鍚堝苟璇︽儏锛夌殑鍚屼竴鏉¤闊� url 鍦ㄤ笉鍚屾皵娉¢噷涔熸槸涓嶅悓 key锛岄伩鍏嶄竴澶勫嵏杞借鍋滃彟涓�澶勪粛鍙鐨勬挱鏀俱��
- */
-export type VoiceKey = symbol
-
-interface VoiceTask {
- key: VoiceKey
- url: string
- audio: HTMLAudioElement
-}
-
-const currentTask = ref<null | VoiceTask>(null)
-
-/**
- * 鏄惧紡鍋滄
- *
- * - 涓嶄紶 key锛氬己鍋滐紙鍒囦細璇� / 鍏冲脊绐楃敤锛�
- * - 浼� key锛氫粎褰撳綋鍓� task 鏄 key 鏃舵墠鍋滐紙姘旀场鍗歌浇鍏滃簳鐢紝閬垮厤璇仠鍒汉锛�
- */
-function stop(key?: VoiceKey) {
- const task = currentTask.value
- if (!task) {
- return
- }
- if (key !== undefined && task.key !== key) {
- return
- }
- task.audio.pause()
- // removeAttribute('src') + load() 鏄� W3C 鎺ㄨ崘鐨勯噴鏀惧Э鍔匡細涓嶄細瑙﹀彂绌� src 鍔犺浇瀵艰嚧鐨� error 浜嬩欢锛�
- // 涔熻兘璁╂祻瑙堝櫒绔嬪嵆閲婃斁搴曞眰 decoder buffer锛屾瘮 audio.src = '' 鏇村共鍑�
- task.audio.removeAttribute('src')
- task.audio.load()
- currentTask.value = null
-}
-
-/**
- * 鍒囨崲鎾斁
- *
- * - 鍚� key 鍐嶇偣锛氬仠鎺�
- * - 鍒囧埌鏂� key锛氬仠鏃ц捣鏂�
- */
-function play(key: VoiceKey, url: string) {
- if (!url) {
- return
- }
- if (currentTask.value?.key === key) {
- stop(key)
- return
- }
- stop()
- const audio = new Audio(url)
- const task: VoiceTask = { key, url, audio }
- /** 鎾斁缁撴潫 / 寮傚父娓呮爤锛涘彧娓呭綋鍓嶄换鍔★紝閬垮厤琚悗缁柊浠诲姟鐨勫洖璋冭娓� */
- const finalize = () => {
- if (currentTask.value === task) {
- currentTask.value = null
- }
- }
- audio.addEventListener('ended', finalize, { once: true })
- audio.addEventListener('error', finalize, { once: true })
- currentTask.value = task
- audio.play().catch(finalize)
-}
-
-export function useVoicePlayer() {
- /** 鎸囧畾 key 鏄惁姝e湪鎾斁 */
- function isPlaying(key: VoiceKey): boolean {
- return currentTask.value?.key === key
- }
- return { isPlaying, play, stop }
-}
diff --git a/src/views/im/home/index.vue b/src/views/im/home/index.vue
deleted file mode 100644
index fcee805..0000000
--- a/src/views/im/home/index.vue
+++ /dev/null
@@ -1,292 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation } from './types'
-
-import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
-import { useRoute } from 'vue-router'
-
-import { preferences } from '../../../packages/preferences/src'
-
-import { ImConversationType } from '../utils/constants'
-import { initDb, stopRequests, StorageKeys } from '../utils/db'
-import { ContextMenu, ToolBar } from './components'
-import { GroupInfoCard } from './components/group'
-import { RtcCallContainer } from './components/rtc'
-import { UserInfoCard } from './components/user'
-import { useMessagePuller } from './composables/useMessagePuller'
-import { useMessageSender } from './composables/useMessageSender'
-import { useVoicePlayer } from './composables/useVoicePlayer'
-import { useChannelStore } from './store/channelStore'
-import { useConversationStore } from './store/conversationStore'
-import { useFaceStore } from './store/faceStore'
-import { useFriendStore } from './store/friendStore'
-import { useGroupRequestStore } from './store/groupRequestStore'
-import { useGroupStore } from './store/groupStore'
-import { useMessageStore } from './store/messageStore'
-import { useImWebSocketStore } from './store/websocketStore'
-
-defineOptions({ name: 'ImIndex' })
-
-const route = useRoute()
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-const webSocketStore = useImWebSocketStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-const groupRequestStore = useGroupRequestStore()
-const faceStore = useFaceStore()
-const channelStore = useChannelStore()
-const { pullOnce, cancelPull } = useMessagePuller()
-const { readActive, syncPrivateReadStatus } = useMessageSender()
-const voicePlayer = useVoicePlayer()
-const childRouteReady = ref(false) // 瀛愯矾鐢辨槸鍚﹀彲鎸傝浇
-
-/** 鍒濆鍖栵細鍏堝悆鏈湴缂撳瓨璁╅灞忕珛鍗虫覆鏌擄紝鍐嶈繙绔埛鏂版渶鏂版暟鎹紝鏈�鍚庡缓瀹炴椂閫氫俊鎷夌绾挎秷鎭� */
-onMounted(async () => {
- // 0.1 绯荤粺琛ㄦ儏鍖呭悗鍙伴鎷夛細鐙珛閾捐矾涓庨灞� IDB / 杩滅鎷夊彇骞跺彂锛屾秷闄よ〃鎯呴潰鏉块娆″睍寮�鐧藉睆锛涘け璐ヤ粎璁版棩蹇楋紝涓嶉樆濉炰富娴佺▼
- void faceStore.ensureFacePackList().catch((error) => console.warn('[IM] 鍚庡彴棰勬媺琛ㄦ儏鍖呭け璐�', error))
- // 1.1 鏁存 loading=true 闃绘柇浼氳瘽鍒楄〃鎶栧姩鍐欑洏 + WebSocket 鏅�氭秷鎭繘缂撳啿锛岄伩鍏� connect 鍒� pullOnce 涔嬮棿鏀跺埌鐨勫疄鏃舵秷鎭帹杩� maxId 瀵艰嚧 pull 璺宠繃鏂嚎绉帇娑堟伅
- conversationStore.loading = true
- try {
- // 1.2 鎵撳紑褰撳墠鐢ㄦ埛 IM DB
- await initDb()
- // 1.3 澶氫釜 store 骞跺彂浠� IDB 璇诲彇鏈湴缂撳瓨
- const cacheResults = await Promise.all([
- conversationStore.loadConversationList(),
- messageStore.loadMessageCursorList(),
- friendStore.loadFriendData(),
- groupStore.loadGroupList(),
- channelStore.loadChannelList(),
- groupRequestStore.loadGroupRequestList()
- ])
- const hasFriendRows = cacheResults[2]
- const hasGroupRows = cacheResults[3]
- const hasChannelRows = cacheResults[4]
- groupStore.markAllGroupActiveCallsExpired()
- groupStore.markAllGroupMembersExpired()
- childRouteReady.value = true
- // 1.4 鎴戠鐞嗙殑缇や笅鏈鐞嗗姞缇ょ敵璇风孩鐐癸細棣栫櫥鐢� unhandled-list锛堟湇鍔$鐩存帴杩囨护鏈鐞嗭紝璇箟绮惧噯銆佸惎鍔ㄨ交锛夛紱
- // pullGroupRequests 鍙湪閲嶈繛 / 鍚庣画琛ュ伩鏃惰窇锛堣 useMessagePuller.pullStateEvents锛夛紝涓嶈繘棣栫櫥涓婚摼璺�
- void groupRequestStore
- .fetchUnhandledGroupRequestList()
- .catch((error) => console.warn('[IM] 鎷夊彇鏈鐞嗗姞缇ょ敵璇峰け璐�', error))
-
- // 2. 濂藉弸涓绘暟鎹仮澶嶈蛋 pull锛涚兢鍒楄〃鐢ㄥ揩鐓у埛鏂帮紝瑕嗙洊绂荤嚎鏈熼棿鑷繁鐨勫叆缇� / 閫�缇ょ姸鎬佸彉鍖�
- // 2.1 鏈夌紦瀛橈細寮傛鑳屾櫙澧為噺鍒锋柊锛屽け璐ヤ粎璁版棩蹇楋紙IDB 鏁版嵁宸茬粡澶熸拺棣栧睆锛宲ullOnce 涔熻兘姝e父鍏ュ簱锛�
- // 2.2 鏃犵紦瀛橈紙棣栫櫥 / 鍒囪处鍙峰洖鍒囷級锛氬繀椤� await + 澶辫触鎶涘嚭涓柇鏈疆 onMounted锛�
- // 鍚﹀垯 pullOnce 浼氱敤 senderId 鏁板瓧缁欎細璇濊捣鍚嶈惤鍒� IDB 鍚庣画鍩烘湰鏃犳硶鑷剤锛涙棤缂撳瓨鍒嗘敮骞跺彂 Promise.all 鐪佷竴涓� RTT
- const requiredFetches: Promise<unknown>[] = []
- if (hasFriendRows) {
- void friendStore.pullFriends().catch((error) => console.warn('[IM] 鍚庡彴澧為噺鎷夊ソ鍙嬪け璐�', error))
- } else {
- requiredFetches.push(friendStore.pullFriends())
- }
- if (hasGroupRows) {
- void groupStore.fetchGroupList(true).catch((error) => console.warn('[IM] 鍚庡彴鍒锋柊缇ゅ垪琛ㄥけ璐�', error))
- } else {
- requiredFetches.push(groupStore.fetchGroupList(true))
- }
- // 2.3 棰戦亾鏃犲閲� pull 鎺ュ彛锛岀户缁蛋 list
- if (hasChannelRows) {
- void channelStore.fetchChannelList().catch((error) => console.warn('[IM] 鍚庡彴鍒烽閬撳垪琛ㄥけ璐�', error))
- } else {
- requiredFetches.push(channelStore.fetchChannelList())
- }
- // 2.4 鎵ц鍔犺浇
- if (requiredFetches.length > 0) {
- await Promise.all(requiredFetches)
- }
-
- // 2.5 濂藉弸鐢宠澧為噺琛ュ伩锛氶鐧讳篃瑕佽窇锛岀绾挎湡闂村ソ鍙嬬敵璇峰彉鏇翠笉浼氬奖鍝嶅ソ鍙嬩富琛�
- void friendStore
- .pullFriendRequests()
- .catch((error) => console.warn('[IM] 鍚庡彴澧為噺鎷夊ソ鍙嬬敵璇峰け璐�', error))
-
- // 3. 浼氳瘽璇讳綅缃厛琛ュ伩锛屾秷鎭叆搴撴椂鍙洿鎺ヨ繃婊ゅ凡璇诲巻鍙叉秷鎭�
- await conversationStore
- .pullConversationReads()
- .catch((error) => console.warn('[IM] 鎷夊彇浼氳瘽璇讳綅缃け璐�', error))
-
- // 4. 瀹炴椂閫氫俊锛氬缓 WebSocket 闀胯繛鎺� + 鎷夌绾挎秷鎭紙pullOnce finally 鎶� loading 褰掍綅锛�
- webSocketStore.connect()
- await pullOnce()
-
- // 5. 榛樿閫変腑绗竴涓細璇濓紱鑻ョ疆椤跺垎缁勫浜庢姌鍙犳�侊紝闇�璺宠繃琚姌鍙犻殣钘忕殑缃《椤癸紝閬垮厤鑷姩灞曞紑鎶樺彔
- const sorted = conversationStore.getSortedConversationList
- const firstVisible = pickFirstVisibleConversation(sorted)
- if (firstVisible && !conversationStore.activeConversation) {
- conversationStore.setActiveConversation(firstVisible)
- }
- } catch (error) {
- // 1. 棣栨媺澶辫触锛氭墜鍔ㄥ浣� loading锛坧ullOnce 娌¤窇鍒帮紝瀹冪殑 finally 鍏滀笉鍒拌繖閲岋級锛屽惁鍒欏悗缁細璇濆垪琛ㄥ啓鍏ュ叏琚棭 return 闃绘柇
- // 2. WebSocket 涓嶅湪杩欓噷 disconnect鈥斺�旇矾鐢辩寮�浼氳蛋 onUnmounted 鑷劧娓呯悊锛岀敤鎴蜂篃鍙互鍒锋柊閲嶈瘯
- conversationStore.loading = false
- console.error('[IM] 鍒濆鍖栧け璐�', error)
- }
-})
-
-/**
- * 閫夐灞忚嚜鍔ㄦ縺娲荤殑浼氳瘽锛涚疆椤跺垎缁勬姌鍙犳椂璺宠繃琚姌鍙犻殣钘忕殑缃《椤癸紝閬垮厤婵�娲诲悗琚嚜鍔ㄩ《涓婃潵
- *
- * 鎶樺彔鎬佸垽瀹氬彧鐪嬬敤鎴峰紑鍏筹紱闈炵疆椤� / 鏈夋湭璇荤殑缃《椤瑰缁堝彲瑙侊紝鍏ㄦ槸鍙姌鍙犵疆椤舵椂鍥為��鍒� sorted[0] 鍏滃簳
- */
-function pickFirstVisibleConversation(sorted: Conversation[]): Conversation | undefined {
- if (sorted.length === 0) {
- return undefined
- }
- const pinnedExpanded =
- localStorage.getItem(StorageKeys.localStorage.conversationPinnedExpanded) === 'true'
- if (pinnedExpanded) {
- return sorted[0]
- }
- return sorted.find((c) => !c.top || (!c.silent && (c.unreadCount || 0) > 0)) ?? sorted[0]
-}
-
-/** 鏍囩鍏抽棴鍓� flush 鑽夌闃熷垪锛沝ebounce 榛樿 trail-edge 瑙﹀彂锛屾渶鍚庝竴娆¤緭鍏ュ彲鑳借繕鍘嬪湪闃熷垪閲� */
-function onBeforeUnload() {
- conversationStore.flushConversationDraftSave()
-}
-window.addEventListener('beforeunload', onBeforeUnload)
-
-/** 绂诲紑 IM 涓诲3锛氬彇娑� pull銆佹柇寮� WebSocket銆佷繚瀛樿崏绋裤�佸仠姝㈣闊炽�佽В缁� unload锛屽苟缁撴潫褰撳墠 IM session */
-onUnmounted(() => {
- cancelPull()
- webSocketStore.disconnect()
- conversationStore.flushConversationDraftSave()
- faceStore.clear()
- // 妯″潡绾у崟渚� audio 涓嶄細闅忚鍥惧嵏杞借嚜鍔ㄥ仠锛屼富鍔ㄥ仠鎺夐伩鍏嶅垏璺敱鍚庤闊崇户缁搷
- voicePlayer.stop()
- window.removeEventListener('beforeunload', onBeforeUnload)
- // 鍋滄褰撳墠 IM session 骞舵竻鐞嗗悇 store 鍐呭瓨
- void stopRequests()
-})
-
-/**
- * 褰撳墠浼氳瘽鍒囨崲锛氭湰鍦版竻闆舵湭璇� + 涓婃姤鍚庣宸茶 + 绉佽亰琛�"瀵规柟宸茶鍒板摢鏉�"
- *
- * type+targetId 涓�璧风洃鍚細绉佽亰涓庣兢鑱� id 鍚屽彿鏃跺垏鎹篃鑳借Е鍙戯紱鍏跺畠浼氳瘽宸茶鐘舵�佺敱 WebSocket
- * READ / RECEIPT 浜嬩欢琚姩鍚屾銆傜鑱婅ˉ涓�娆℃媺瀵规柟宸茶浣嶇疆锛屽讥琛ョ绾� / 澶氱婕忔帀鐨� RECEIPT 鎺ㄩ��
- */
-watch(
- () => [
- conversationStore.activeConversation?.type,
- conversationStore.activeConversation?.targetId
- ],
- async ([type, targetId]) => {
- if (!targetId) {
- return
- }
- // 鏈湴娓呴浂鏈 + 涓婃姤鍚庣宸茶锛岃鍏跺畠绔� / 瀵规柟 UI 鍚屾
- await readActive()
- // 绉佽亰琛ヤ竴娆�"瀵规柟宸茶鍒板摢鏉�"锛屽讥琛ョ绾� / 澶氱婕忔帀鐨� RECEIPT 鎺ㄩ��
- if (type === ImConversationType.PRIVATE) {
- void syncPrivateReadStatus(targetId)
- }
- }
-)
-
-/**
- * 娴忚鍣ㄦ爣绛� title 鎷间笂鏈鏁板墠缂�锛涗緥锛�(63鏉℃湭璇�)鑺嬮亾婧愮爜
- *
- * 璺敱鍒囨崲鏃� router.afterEach 浼氳皟 useTitle 閲嶇疆 title锛涚敤 nextTick 鎺掑湪瀹冧箣鍚庡啀瑕嗙洊
- * 涓�骞剁洃鍚� route.fullPath锛孖M 瀛愯矾鐢卞垏鎹紙娑堟伅 / 閫氳褰曪級涔熻兘閲嶆柊鍔犱笂鍓嶇紑
- */
-watch(
- [() => conversationStore.getTotalUnreadCount, () => route.fullPath],
- ([count]) => {
- nextTick(() => {
- const base = preferences.app.name
- document.title = count > 0 ? `(${count > 99 ? '99+' : count}鏉℃湭璇�)${base}` : base
- })
- },
- { immediate: true }
-)
-</script>
-
-<template>
- <!--
- IM 澶栧眰瀹瑰櫒锛氳亰澶╂ā鍧楃殑鍏ㄥ睆娌夋蹈寮忓3
- - 宸︿晶 ToolBar锛氬ご鍍� + 涓� Tab锛堟秷鎭�/濂藉弸/缇よ亰锛�+ 搴曢儴璁剧疆
- - 鍙充晶 <router-view>锛氭寜璺敱娓叉煋 MessagePage / FriendPage / GroupPage
- - 鎸傝浇鍏ㄥ眬寮瑰眰锛歎serInfoCard / GroupInfoCard / ContextMenu
- -->
- <div
- class="im-home flex w-full h-full overflow-hidden bg-[var(--ant-color-bg-layout)] text-[var(--ant-color-text)]"
- >
- <ToolBar />
- <!--
- keep-alive 缂撳瓨瀛愰〉闈細
- - 鍒� Tab 涓嶉噸寤虹粍浠讹紝MessagePanel 婊氬姩浣嶇疆銆佽緭鍏ユ鑽夌绛� UI 鐘舵�佷笉涓�
- - Vue 3 閲� keep-alive 涓嶈兘鐩存帴鍖� <router-view>锛堜細鏈夎鍛婏級锛屽繀椤昏蛋 v-slot 鎷� Component
- -->
- <router-view v-if="childRouteReady" v-slot="{ Component }">
- <keep-alive>
- <component :is="Component" />
- </keep-alive>
- </router-view>
- <div v-else class="flex-1 bg-[var(--ant-color-bg-container)]"></div>
-
- <!-- 鍏ㄥ眬寮瑰眰锛氱敱 useImUiStore 缁熶竴璋冨害 -->
- <UserInfoCard />
- <GroupInfoCard />
- <ContextMenu />
-
- <!-- 瀹炴椂閫氳瘽娴眰锛涚洃鍚� rtcStore 鍏ㄥ眬鐘舵�侊紝鍙湪浠绘剰 IM 瀛愰〉寮瑰嚭 -->
- <RtcCallContainer />
- </div>
-</template>
-
-<style scoped>
-:global(:root) {
- --im-border-color-lighter: #e8eaed;
- --im-resize-line-color: #d8dde5;
-
- /*
- * antd 4.x 涓嶈緭鍑� --ant-color-* 鍏ㄥ眬 CSS 鍙橀噺锛堜粎 React antd 鐨� theme.cssVar 鏀寔锛夛紝
- * 鑰� IM 鑱婂ぉ绔ぇ閲忕洿鎺ヤ娇鐢� var(--ant-color-*)銆傛澶勭敤 Vben design token 鍏滃簳瀹氫箟锛�
- * 鏄犲皠瑙勫垯涓� useAntdDesignTokens 鍠傜粰 antd 鐨� token 瀹屽叏涓�鑷达紝闅忎富棰橈紙浜�/鏆楋級鑷姩鍒囨崲銆�
- * 缂哄け鏃惰繖浜涘彉閲忎负绌� 鈫� 鑳屾櫙閫忔槑銆佽竟妗�/濉厖澶辨晥锛堝吀鍨嬶細琛ㄦ儏闈㈡澘閫忓嚭杈撳叆妗嗗崰浣嶆枃瀛楋級銆�
- */
- --ant-color-bg-container: hsl(var(--card));
- --ant-color-bg-elevated: hsl(var(--popover));
- --ant-color-bg-layout: hsl(var(--background-deep));
- --ant-color-border: hsl(var(--border));
- --ant-color-border-secondary: hsl(var(--border));
- --ant-color-text: hsl(var(--foreground));
- --ant-color-text-secondary: hsl(var(--foreground) / 65%);
- --ant-color-text-placeholder: hsl(var(--foreground) / 45%);
- --ant-color-text-disabled: hsl(var(--foreground) / 30%);
- /*
- * fill 绯诲垪锛氭祬鑹蹭笅鐢� Element Plus 椋庢牸鐨勩�屽喎璋冩祬鐏板疄鑹层�嶈�岄潪鍗婇�忔槑娣辫壊锛�
- * 鍚﹀垯闈㈡澘锛堜細璇濆垪琛� / 娑堟伅闈㈡澘绛夛級鍙犲湪鐏板簳涓婁細鏄捐剰鍙戞殫锛屽拰 Vue3+EP 鐨勫共鍑�鐧藉樊璺濇槑鏄俱��
- * 鍙栧�间緷缁勪欢瀹為檯鐢ㄦ硶锛歴econdary 澶氱敤浜庨潰鏉垮簳锛堝彇鏈�娴咃紝瀵归綈 EP --el-bg-color-page #f5f7fa锛夛紝
- * tertiary 澶氱敤浜� hover / 鍥炬爣鍧楋紙姣旈潰鏉跨暐娣憋紝淇濊瘉 hover 鍙锛夈�傛繁鑹插湪 .dark 閲屽彟琛岃鐩栥��
- */
- --ant-color-fill: #e2e6ec;
- --ant-color-fill-secondary: #f4f6f9;
- --ant-color-fill-tertiary: #eaedf2;
- --ant-color-fill-dark: #d5dae2;
- --ant-color-primary: hsl(var(--primary));
- --ant-color-primary-hover: hsl(var(--primary) / 80%);
- --ant-color-primary-bg: hsl(var(--primary) / 12%);
- --ant-color-primary-bg-hover: hsl(var(--primary) / 18%);
- --ant-color-info: hsl(var(--primary));
- --ant-color-success: hsl(var(--success));
- --ant-color-success-bg: hsl(var(--success) / 12%);
- --ant-color-success-bg-hover: hsl(var(--success) / 18%);
- --ant-color-warning: hsl(var(--warning));
- --ant-color-warning-bg: hsl(var(--warning) / 12%);
- --ant-color-warning-bg-hover: hsl(var(--warning) / 18%);
- --ant-color-error: hsl(var(--destructive));
-}
-
-:global(.dark) {
- --im-border-color-lighter: rgb(255 255 255 / 12%);
- --im-resize-line-color: rgb(255 255 255 / 18%);
-
- /* 娣辫壊涓� fill 鍥炲埌銆屽熀浜庡墠鏅壊鐨勫崐閫忔槑浜壊銆嶏紝鍙犲湪娣卞簳涓婂緱鍒拌嚜鐒剁殑寰寒琛ㄩ潰 */
- --ant-color-fill: hsl(var(--foreground) / 12%);
- --ant-color-fill-secondary: hsl(var(--foreground) / 8%);
- --ant-color-fill-tertiary: hsl(var(--foreground) / 4%);
- --ant-color-fill-dark: hsl(var(--foreground) / 18%);
-}
-</style>
diff --git a/src/views/im/home/pages/contact/friend-list.vue b/src/views/im/home/pages/contact/friend-list.vue
deleted file mode 100644
index e2b8ddb..0000000
--- a/src/views/im/home/pages/contact/friend-list.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<script lang="ts" setup>
-import type { FriendLite } from '../../types'
-
-import { ref, toRef } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { FriendItem } from '../../components/friend'
-import { useFriendBuckets } from '../../composables/useFriendBuckets'
-
-defineOptions({ name: 'ImContactFriendList' })
-
-const props = defineProps<{
- activeId?: number
- friends: FriendLite[]
- keyword: string
-}>()
-
-const emit = defineEmits<{
- chat: [friend: FriendLite]
- delete: [friend: FriendLite]
- select: [friend: FriendLite]
-}>()
-
-const expanded = ref(true)
-
-const { filtered, buckets } = useFriendBuckets(toRef(props, 'friends'), toRef(props, 'keyword'))
-</script>
-
-<template>
- <!--
- 閫氳褰� - 濂藉弸鍒嗙粍
- - 鑷不锛氭姌鍙犵姸鎬� + 鍏抽敭瀛楄繃婊� + 瀛楁瘝鍒嗘《 鏈粍浠跺唴闂幆
- - 瀛楁瘝鍒嗘《 / 鎷奸煶鎼滅储濮旀墭 useFriendBuckets锛屼笌閫夋嫨绫诲脊绐� FriendPickerPanel 鍏辩敤涓�浠借鍒�
- - 閫変腑鎬佺敱鐖剁骇 activeId 閫忎紶锛沜hat / delete 閫忎紶鍒扮埗绾ц蛋 store 鏀归��
- -->
- <div>
- <!-- 鎶樺彔鍒嗙粍澶达細瀛楀彿瀵归綈寰俊 PC锛�15px锛夛紝hover 娴呭簳鑹插弽棣� -->
- <div
- class="flex gap-2 items-center px-3.5 py-2.5 cursor-pointer select-none text-15px text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill-secondary)]"
- @click="expanded = !expanded"
- >
- <Icon :icon="expanded ? 'ep:caret-bottom' : 'ep:caret-right'" :size="14" />
- <span class="flex-1">濂藉弸</span>
- <span class="text-sm text-[var(--ant-color-text-secondary)]">{{ filtered.length }}</span>
- </div>
- <div v-show="expanded">
- <template v-for="bucket in buckets" :key="bucket.letter">
- <!-- 瀛楁瘝鍒嗘《 header锛氭祬搴� + 灏忓瓧鍙凤紝浣滀负濂藉弸鍒楄〃鍐呴儴鍒嗛殧 -->
- <div
- class="pt-1 pb-0.5 px-3.5 text-12px text-[var(--ant-color-text-secondary)] bg-[var(--ant-color-fill-tertiary)]"
- >
- {{ bucket.letter }}
- </div>
- <FriendItem
- v-for="friend in bucket.list"
- :key="friend.id"
- :friend="friend"
- :active="activeId === friend.id"
- @click="emit('select', friend)"
- @chat="emit('chat', $event)"
- @delete="emit('delete', $event)"
- />
- </template>
- <div
- v-if="filtered.length === 0"
- class="py-3 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ keyword ? '娌℃湁鍖归厤鐨勫ソ鍙�' : '鏆傛棤濂藉弸' }}
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/contact/friend-request-detail.vue b/src/views/im/home/pages/contact/friend-request-detail.vue
deleted file mode 100644
index 7733c2e..0000000
--- a/src/views/im/home/pages/contact/friend-request-detail.vue
+++ /dev/null
@@ -1,229 +0,0 @@
-<script lang="ts" setup>
-import type { FriendRequest, User } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { prompt } from '../../../../../packages/effects/common-ui/src'
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-
-import { Button, Input, message } from 'ant-design-vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { ImFriendRequestHandleResult } from '../../../utils/constants'
-import { UserAvatar } from '../../components/user'
-import { UserInfo } from '../../components/user'
-import { useFriendStore } from '../../store/friendStore'
-
-defineOptions({ name: 'ImContactFriendRequestDetail' })
-
-const props = defineProps<{
- request: FriendRequest
-}>()
-
-const emit = defineEmits<{
- chat: [peerUserId: number]
-}>()
-
-const friendStore = useFriendStore()
-
-/** 褰撳墠鐧诲綍鐢ㄦ埛缂栧彿锛涚敤 computed 鍖呬竴灞傦紝鍒囪处鍙峰悗闅� wsCache 閲嶅彇锛岄伩鍏嶉《灞傛眰鍊煎湪 keep-alive 瀹炰緥閲屾寔鏈夋棫 id */
-const currentUserId = computed(() => getCurrentUserId())
-
-/** 鏄笉鏄垜鍙戣捣鐨勶紙fromUserId === currentUserId锛� */
-const iSentIt = computed(() => props.request.fromUserId === currentUserId.value)
-
-/** 鏄惁銆屽凡鎷掔粷銆嶆�侊細妯℃澘閲屽澶勭敤鍒帮紝computed 涓�娆$渷寰楀埌澶勫啓鏋氫妇姣斿 */
-const refused = computed(
- () => props.request.handleResult === ImFriendRequestHandleResult.REFUSED
-)
-
-/** 鏄惁銆屽凡閫氳繃銆嶆�侊細杞蛋 UserInfo 濂藉弸璇︽儏鍏ュ彛 */
-const agreed = computed(
- () => props.request.handleResult === ImFriendRequestHandleResult.AGREED
-)
-
-/** 瀵圭鐨勭敤鎴风紪鍙� / 鏄电О / 澶村儚 */
-const peerUserId = computed(() =>
- iSentIt.value ? props.request.toUserId : props.request.fromUserId
-)
-const peerNickname = computed(() =>
- iSentIt.value
- ? props.request.toNickname || String(props.request.toUserId)
- : props.request.fromNickname || String(props.request.fromUserId)
-)
-const peerAvatar = computed(() =>
- iSentIt.value ? props.request.toAvatar : props.request.fromAvatar
-)
-
-/** 閫忕粰 UserInfo 鐨勬渶灏忕敤鎴蜂俊鎭紱UserInfo 鍐呴儴浼氭寜 id 璋� getSimpleUser 琛ラ綈鎬у埆 / 閮ㄩ棬 */
-const peerUser = computed<User>(() => ({
- id: peerUserId.value,
- nickname: peerNickname.value,
- avatar: peerAvatar.value
-}))
-
-// 鍚勮嚜鐨� loading 鐢ㄤ簬鎸夐挳 spinner 鏄剧ず锛沺rocessing 鏄法鎸夐挳浜掓枼閿侊紝閬垮厤鍚屾剰 / 鎷掔粷骞跺彂鎻愪氦鍚屼竴鐢宠
-const agreeing = ref(false)
-const refusing = ref(false)
-const processing = ref(false)
-
-/** 鍚屾剰鐢宠锛氫簰鏂ラ攣 + 鐘舵�佷簩娆℃牎楠岋紝閬垮厤骞跺彂 / 鏈嶅姟绔凡澶勭悊鍚庡啀娆℃彁浜� */
-async function handleAgree() {
- if (processing.value) {
- return
- }
- if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
- return
- }
- processing.value = true
- agreeing.value = true
- try {
- await friendStore.agreeFriendRequest(props.request.id)
- message.success('宸插悓鎰忓ソ鍙嬬敵璇�')
- } finally {
- agreeing.value = false
- processing.value = false
- }
-}
-
-/** 鎷掔粷鐢宠锛氬脊 prompt 鏀堕泦鍙�夋嫆缁濈悊鐢憋紙鐐瑰彇娑堝垯涓锛夛紝闅忓悗璋� store 钀藉簱 + 鎻愮ず */
-async function handleRefuse() {
- if (processing.value) {
- return
- }
- if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
- return
- }
- // 1. 寮� prompt 鏀堕泦鎷掔粷鐞嗙敱锛堟渶澶� 255 瀛楋級锛涚敤鎴风偣銆屽彇娑堛�嶄細 reject锛屼腑姝㈠悗缁祦绋�
- let handleContent: string | undefined
- try {
- const result = await prompt<string>({
- beforeClose(scope) {
- if (!scope.isConfirm) {
- return
- }
- if ((scope.value || '').length > 255) {
- message.error('鏈�澶� 255 涓瓧绗�')
- return false
- }
- },
- cancelText: '鍙栨秷',
- component: Input.TextArea,
- componentProps: {
- allowClear: true,
- maxlength: 255,
- placeholder: '涓嶅~鍒欎笉鍛婄煡瀵规柟鍘熷洜',
- rows: 3
- },
- content: '',
- defaultValue: '',
- confirmText: '鎷掔粷',
- modelPropName: 'value',
- title: '鎷掔粷濂藉弸鐢宠'
- })
- handleContent = result || undefined
- } catch {
- return
- }
- // 2. prompt 鏈熼棿鐘舵�佸彲鑳借璺ㄧ鏀规垚 AGREED / REFUSED锛屽啀鏍¢獙涓�娆¢伩鍏嶉噸澶嶆彁浜�
- if (processing.value) {
- return
- }
- if (props.request.handleResult !== ImFriendRequestHandleResult.UNHANDLED) {
- return
- }
- processing.value = true
- refusing.value = true
- try {
- await friendStore.refuseFriendRequest(props.request.id, handleContent)
- message.success('宸叉嫆缁濆ソ鍙嬬敵璇�')
- } finally {
- refusing.value = false
- processing.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 鏂扮殑鏈嬪弸璇︽儏闈㈡澘
- - 宸查�氳繃 鈫� 鐩存帴璧� UserInfo 濂藉弸璇︽儏锛岃窡閫氳褰曢噷鐐瑰紑濂藉弸鐨勪綋楠屽畬鍏ㄤ竴鑷�
- - 鏈鐞� / 宸叉嫆缁� 鈫� 鐢宠鎬侀潰鏉匡細澶村儚 + 鐢宠/鎷掔粷瀵硅瘽姘旀场 + 鏉ユ簮 + 鎿嶄綔鎸夐挳
- -->
- <div v-if="agreed" class="flex justify-center pt-12 px-6">
- <div class="w-full max-w-[320px]">
- <UserInfo
- :user="peerUser"
- :display-name="friendStore.getFriend(peerUserId)?.displayName || ''"
- relation="friend"
- @chat="emit('chat', peerUserId)"
- />
- </div>
- </div>
-
- <div v-else class="flex flex-col items-center px-6 pt-12">
- <UserAvatar
- :id="peerUserId"
- :url="peerAvatar"
- :name="peerNickname"
- :size="64"
- :clickable="false"
- />
- <div class="mt-3 text-base font-semibold text-[var(--ant-color-text)]">
- {{ peerNickname }}
- </div>
-
- <!-- 鐢宠鐞嗙敱鍧楋細浠垮井淇$伆搴曟皵娉★紝鎸夌敵璇锋柟韬唤鍓嶇紑锛涢暱鏂囨湰 break-words 鎶樿 -->
- <div
- v-if="request.applyContent"
- class="w-full max-w-[420px] mt-6 px-3.5 py-3 rounded-md bg-[var(--ant-color-fill-secondary)] text-13px text-[var(--ant-color-text)] break-words"
- >
- <span class="text-[var(--ant-color-text-secondary)]">
- {{ iSentIt ? '鎴�' : peerNickname }}:
- </span>
- <span class="ml-1">{{ request.applyContent }}</span>
- </div>
- <!-- 鏉ユ簮琛岋細label 宸︺�乿alue 鍙筹紝瀵归綈寰俊銆屾潵婧愩�嶈 -->
- <div
- v-if="request.addSource"
- class="w-full max-w-[420px] mt-3 flex items-center text-13px text-[var(--ant-color-text-secondary)]"
- >
- <span class="w-16 flex-shrink-0 whitespace-nowrap">鏉ユ簮</span>
- <span class="text-[var(--ant-color-text)]">
- {{ getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, request.addSource) }}
- </span>
- </div>
- <!-- 鎷掔粷鐞嗙敱锛歭abel 宸︺�乿alue 鍙筹紱闀挎枃鏈� break-words 鑷姩鎶樿 -->
- <div
- v-if="refused && request.handleContent"
- class="w-full max-w-[420px] mt-3 flex items-start text-13px text-[var(--ant-color-text-secondary)]"
- >
- <span class="w-16 flex-shrink-0 whitespace-nowrap">鎷掔粷鐞嗙敱</span>
- <span class="flex-1 min-w-0 break-words text-[var(--ant-color-text)]">
- {{ request.handleContent }}
- </span>
- </div>
-
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="w-full max-w-[420px] mt-8 flex justify-center">
- <!-- 鎴戝彂璧� + 绛夊緟涓細绂佺敤銆岀瓑寰呭鏂归獙璇併�� -->
- <Button
- v-if="iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED"
- disabled
- >
- 绛夊緟瀵规柟楠岃瘉
- </Button>
- <!-- 鍒汉鍔犳垜 + 绛夊緟涓細鍚屾剰 / 鎷掔粷 -->
- <template v-if="!iSentIt && request.handleResult === ImFriendRequestHandleResult.UNHANDLED">
- <Button @click="handleRefuse" :loading="refusing" :disabled="processing">鎷掔粷</Button>
- <Button type="primary" @click="handleAgree" :loading="agreeing" :disabled="processing">
- 鍚屾剰
- </Button>
- </template>
- <!-- 宸叉嫆缁濓細鍗犱綅绂佺敤鎸夐挳 -->
- <Button v-if="refused" disabled>宸叉嫆缁�</Button>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/contact/friend-request-list.vue b/src/views/im/home/pages/contact/friend-request-list.vue
deleted file mode 100644
index 00195c9..0000000
--- a/src/views/im/home/pages/contact/friend-request-list.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-<script lang="ts" setup>
-import type { FriendRequest } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictLabel } from '../../../../../packages/effects/hooks/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Badge } from 'ant-design-vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { UserAvatar } from '../../components/user'
-import { useFriendStore } from '../../store/friendStore'
-
-defineOptions({ name: 'ImContactFriendRequestList' })
-
-const props = defineProps<{
- activeId?: number
- requests: FriendRequest[]
-}>()
-
-const emit = defineEmits<{
- select: [request: FriendRequest]
-}>()
-
-const friendStore = useFriendStore()
-const expanded = ref(true)
-/** 褰撳墠鐧诲綍鐢ㄦ埛缂栧彿锛涚敤 computed 鍖呬竴灞傦紝鍒囪处鍙峰悗闅� wsCache 閲嶅彇锛岄伩鍏嶉《灞傛眰鍊煎湪 keep-alive 瀹炰緥閲屾寔鏈夋棫 id */
-const currentUserId = computed(() => getCurrentUserId())
-
-/** 鍒楄〃椤瑰睍绀哄绔細fromUserId == 鎴� 鈫� 瀵圭 = toUser锛涘惁鍒欏绔� = fromUser */
-function getPeer(request: FriendRequest) {
- const sentByMe = request.fromUserId === currentUserId.value
- return {
- id: sentByMe ? request.toUserId : request.fromUserId,
- nickname: sentByMe
- ? request.toNickname || String(request.toUserId)
- : request.fromNickname || String(request.fromUserId),
- avatar: sentByMe ? request.toAvatar : request.fromAvatar
- }
-}
-
-/** 鍒楄〃椤归鍏堥檮 peer 瀛楁锛屾ā鏉块噷鐩存帴 {{ peer.xxx }} 涓�娆℃垚鍨嬶紝鐪� 3 娆� helper 璋冪敤 */
-const enrichedRequests = computed(() =>
- props.requests.map((request) => ({ request, peer: getPeer(request) }))
-)
-
-const loadingMore = ref(false) // 鐐瑰嚮銆屽姞杞芥洿澶氥�嶆媺涓嬩竴椤碉紱store 鍐呴儴鎸� maxId 娓告爣鍒嗛〉 + pending 鍘婚噸
-async function handleLoadMore() {
- if (loadingMore.value) {
- return
- }
- loadingMore.value = true
- try {
- await friendStore.loadMoreFriendRequestList()
- } finally {
- loadingMore.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 閫氳褰� - 鏂扮殑鏈嬪弸鍒嗙粍
- - 鑷不锛氭姌鍙犵姸鎬佺敱缁勪欢鍐� ref 绠$悊锛岄粯璁ゅ睍寮�
- - 鍒楄〃椤瑰睍绀猴細澶村儚 + 鏄电О + 鐢宠鐞嗙敱 + 鐘舵�佹爣绛撅紙绛夊緟楠岃瘉 / 宸叉坊鍔� / 宸叉嫆缁濓級
- - 閫変腑鎬佺敱鐖剁骇 activeId 閫忎紶锛涚偣鍑昏Е鍙� select 浜嬩欢
- -->
- <div>
- <!-- 鎶樺彔鍒嗙粍澶� -->
- <div
- class="flex gap-2 items-center px-3.5 py-2.5 text-15px text-[var(--ant-color-text)] cursor-pointer select-none hover:bg-[var(--ant-color-fill-secondary)]"
- @click="expanded = !expanded"
- >
- <Icon :icon="expanded ? 'ep:caret-bottom' : 'ep:caret-right'" :size="14" />
- <span class="flex-1">鏂扮殑鏈嬪弸</span>
- <!-- 绾㈢偣锛氭湭澶勭悊涓斿埆浜哄姞鎴戠殑锛堢粺涓�璧� store getter锛岄伩鍏嶆湰鍦� computed 璺� store 鍙屽彛寰勶級 -->
- <Badge
- v-if="friendStore.getUnhandledRequestCount > 0"
- :count="friendStore.getUnhandledRequestCount"
- :max="99"
- class="mr-2"
- />
- <span class="text-sm text-[var(--ant-color-text-secondary)]">{{ requests.length }}</span>
- </div>
- <div v-show="expanded">
- <div
- v-for="{ request, peer } in enrichedRequests"
- :key="request.id"
- class="flex gap-3 items-start px-3.5 py-2.5 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill-secondary)]"
- :class="{
- 'bg-[var(--ant-color-fill)]': activeId === request.id
- }"
- @click="emit('select', request)"
- >
- <UserAvatar
- :id="peer.id"
- :url="peer.avatar"
- :name="peer.nickname"
- :size="36"
- :clickable="false"
- />
- <div class="flex-1 min-w-0 overflow-hidden">
- <div class="flex justify-between gap-2 items-center">
- <span class="flex-1 text-sm font-medium truncate text-[var(--ant-color-text)]">
- {{ peer.nickname }}
- </span>
- <span class="flex-shrink-0 text-12px text-[var(--ant-color-text-secondary)]">
- {{ getDictLabel(DICT_TYPE.IM_FRIEND_REQUEST_HANDLE_RESULT, request.handleResult) }}
- </span>
- </div>
- <div
- v-if="request.applyContent"
- class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
- >
- {{ request.applyContent }}
- </div>
- </div>
- </div>
- <div
- v-if="requests.length === 0"
- class="py-3 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- 鏆傛棤鏂扮殑鏈嬪弸
- </div>
- <!-- 鍔犺浇鏇村锛氭寜鏈湴鏈�鏃� requestId 娓告爣鍒嗛〉鎷変笅涓�鎵癸紱hasMore=false 涓嶅睍绀� -->
- <div
- v-else-if="friendStore.hasMoreFriendRequests"
- class="py-2 text-12px text-center cursor-pointer text-[var(--ant-color-text-secondary)] hover:bg-[var(--ant-color-fill-secondary)]"
- @click="handleLoadMore"
- >
- {{ loadingMore ? '鍔犺浇涓��' : '鍔犺浇鏇村' }}
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/contact/group-detail.vue b/src/views/im/home/pages/contact/group-detail.vue
deleted file mode 100644
index e93bb35..0000000
--- a/src/views/im/home/pages/contact/group-detail.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script lang="ts" setup>
-import type { GroupLite } from '../../types'
-
-import { GroupInfo } from '../../components/group'
-
-defineOptions({ name: 'ImContactGroupDetail' })
-
-defineProps<{
- group: GroupLite
-}>()
-
-const emit = defineEmits<{
- chat: [group: GroupLite]
-}>()
-</script>
-
-<template>
- <!--
- 閫氳褰� - 缇よ亰璇︽儏
- - 鍐呭璧板叡浜� <GroupInfo>锛屼笌 GroupInfoCard 娴眰鐢ㄥ悓涓�浠界粍浠�
- - 閫氳褰曢噷閮芥槸宸插姞缇わ紙GroupInfo 鍐呴儴鎸� groupStore 鎺ㄥ锛夛紱chat 鎶涗笂鍘荤敱鐖剁骇鎵挎帴
- -->
- <div class="pt-12 px-6">
- <GroupInfo :group="group" @chat="emit('chat', $event)" />
- </div>
-</template>
diff --git a/src/views/im/home/pages/contact/group-list.vue b/src/views/im/home/pages/contact/group-list.vue
deleted file mode 100644
index 0cc05ea..0000000
--- a/src/views/im/home/pages/contact/group-list.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script lang="ts" setup>
-import type { GroupLite } from '../../types'
-
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { GroupItem } from '../../components/group'
-
-defineOptions({ name: 'ImContactGroupList' })
-
-const props = defineProps<{
- activeId?: number
- groups: GroupLite[]
- keyword: string
-}>()
-
-const emit = defineEmits<{ select: [group: GroupLite] }>()
-
-const expanded = ref(true)
-
-const filtered = computed(() => {
- const keywordLower = props.keyword.trim().toLowerCase()
- if (!keywordLower) {
- return props.groups
- }
- return props.groups.filter((group) =>
- (group.showGroupName || group.name || '').toLowerCase().includes(keywordLower)
- )
-})
-</script>
-
-<template>
- <!--
- 閫氳褰� - 缇よ亰鍒嗙粍
- - 鑷不锛氭姌鍙犵姸鎬� + 鍏抽敭瀛楄繃婊ゆ湰缁勪欢鍐呴棴鐜�
- - 閫変腑鎬佺敱鐖剁骇 activeId 閫忎紶锛岄伩鍏嶅瓙缁勪欢鎸佹湁 selection 鐭ヨ瘑
- -->
- <div>
- <!-- 鎶樺彔鍒嗙粍澶达細瀛楀彿瀵归綈寰俊 PC锛�15px锛夛紝hover 娴呭簳鑹插弽棣� -->
- <div
- class="flex gap-2 items-center px-3.5 py-2.5 text-15px text-[var(--ant-color-text)] cursor-pointer select-none hover:bg-[var(--ant-color-fill-secondary)]"
- @click="expanded = !expanded"
- >
- <Icon :icon="expanded ? 'ep:caret-bottom' : 'ep:caret-right'" :size="14" />
- <span class="flex-1">缇よ亰</span>
- <span class="text-sm text-[var(--ant-color-text-secondary)]">{{ filtered.length }}</span>
- </div>
- <div v-show="expanded">
- <GroupItem
- v-for="group in filtered"
- :key="group.id"
- :group="group"
- :active="activeId === group.id"
- @click="emit('select', group)"
- />
- <div
- v-if="filtered.length === 0"
- class="py-3 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ keyword ? '娌℃湁鍖归厤鐨勭兢鑱�' : '鏆傛棤缇よ亰' }}
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/contact/index.vue b/src/views/im/home/pages/contact/index.vue
deleted file mode 100644
index c1662bf..0000000
--- a/src/views/im/home/pages/contact/index.vue
+++ /dev/null
@@ -1,309 +0,0 @@
-<script lang="ts" setup>
-import type { FriendLite, FriendRequest, Group, GroupLite, User } from '../../types'
-
-import { computed, onMounted, ref, watch } from 'vue'
-import { useRouter } from 'vue-router'
-
-import { confirm } from '../../../../../packages/effects/common-ui/src'
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Input, message } from 'ant-design-vue'
-
-import { ImConversationType } from '../../../utils/constants'
-import { StorageKeys } from '../../../utils/db'
-import { getFriendDisplayName, getGroupDisplayName, isGroupQuit } from '../../../utils/user'
-import { ResizableAside } from '../../components'
-import { UserInfo } from '../../components/user'
-import { useConversationStore } from '../../store/conversationStore'
-import { useFriendStore } from '../../store/friendStore'
-import { useGroupStore } from '../../store/groupStore'
-import FriendList from './friend-list.vue'
-import FriendRequestDetail from './friend-request-detail.vue'
-import FriendRequestList from './friend-request-list.vue'
-import GroupDetail from './group-detail.vue'
-import GroupList from './group-list.vue'
-
-defineOptions({ name: 'ImContactPage' })
-
-const router = useRouter()
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-
-/** 鐢� type 鍒ゅ埆閫変腑鏄ソ鍙� / 缇よ亰 / 濂藉弸鐢宠 */
-type Selection =
- | { friend: FriendLite; type: 'friend'; }
- | { group: GroupLite; type: 'group'; }
- | { request: FriendRequest; type: 'request'; }
-
-const selection = ref<null | Selection>(null)
-const keyword = ref('')
-
-/** 閫変腑鐢宠璇︽儏锛氳鎯呯敤 store 閲岀殑鏈�鏂板壇鏈紙鍚屾剰 / 鎷掔粷鍚庣姸鎬佷細鍙橈級 */
-const currentRequest = computed<FriendRequest>(() => {
- const req = selection.value?.type === 'request' ? selection.value.request : null
- if (!req) {
- return {} as FriendRequest
- }
- return friendStore.getFriendRequest(req.id) || req
-})
-
-/** 鎴戠浉鍏崇殑鐢宠鍒楄〃锛堢敤 friendStore 閲岀殑瀹炴椂鍓湰锛屼究浜庨�氱煡鍒拌揪鍚庤嚜鍔ㄥ埛鏂帮級 */
-const friendRequests = computed<FriendRequest[]>(() => friendStore.friendRequests)
-
-/** 濂藉弸鍒楄〃鐨勫睍绀哄揩鐓э細闄勫甫鍚庣绠楀ソ鐨勬嫾闊筹紝缁� FriendList 鍋氬瓧姣嶅垎妗� / 鎷奸煶鎼滅储 */
-const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
-
-const groups = computed<GroupLite[]>(() =>
- // 閫氳褰曞彧灞曠ず褰撳墠浠嶅湪缇ょ殑锛涘凡閫�缇ゅ巻鍙茬兢鍙暀鍦� store 閲屼緵娑堟伅灞曠ず缇ゅ悕 / 澶村儚锛屼笉杩涢�氳褰�
- groupStore.groups
- .filter((group: Group) => !isGroupQuit(group))
- .map((group: Group) => ({
- id: group.id,
- name: group.name,
- showGroupName: getGroupDisplayName(group), // 浼樺厛鐢ㄧ兢澶囨敞 groupRemark锛屾病璁剧疆鏃跺洖钀藉埌鍘熺兢鍚嶏紱閬垮厤鐐�"杩涘叆缇よ亰"鏃舵妸宸插悓姝ョ殑澶囨敞浼氳瘽鍚嶅埛鍥炲師鍚�
- showImage: group.avatar,
- showImageThumb: group.avatar,
- memberCount: group.memberCount
- }))
-)
-
-/**
- * store 鍒楄〃鍙樺寲鏃跺悓姝� selection 鎸佺殑瀵硅薄鍓湰锛氬绔帹閫� / 璺ㄧ鍔ㄤ綔鏀� store 鍚庯紝鍙充晶璇︽儏鑳借窡涓婏細
- * - 鍛戒腑鍒欐浛鎹负鏈�鏂板紩鐢紝璧勬枡 / 澶囨敞 / 鐢宠鐘舵�佸彉鏇寸珛鍒诲弽鏄�
- * - 鎵句笉鍒帮紙鍒犻櫎 / 鎷掔粷 / 宸查�氳繃绛夎璁板綍娑堝け锛夊垯缃� null 鏀惰捣璇︽儏
- */
-watch(
- friends,
- (list) => {
- const selected = selection.value
- if (selected?.type !== 'friend') {
- return
- }
- const fresh = list.find((friend) => friend.id === selected.friend.id)
- if (!fresh) {
- selection.value = null
- } else if (fresh !== selected.friend) {
- selection.value = { type: 'friend', friend: fresh }
- }
- },
- { deep: true }
-)
-watch(
- groups,
- (list) => {
- const selected = selection.value
- if (selected?.type !== 'group') {
- return
- }
- const fresh = list.find((group) => group.id === selected.group.id)
- if (!fresh) {
- selection.value = null
- } else if (fresh !== selected.group) {
- selection.value = { type: 'group', group: fresh }
- }
- },
- { deep: true }
-)
-watch(
- friendRequests,
- (list) => {
- const selected = selection.value
- if (selected?.type !== 'request') {
- return
- }
- const fresh = list.find((request) => request.id === selected.request.id)
- if (!fresh) {
- selection.value = null
- } else if (fresh !== selected.request) {
- selection.value = { type: 'request', request: fresh }
- }
- },
- { deep: true }
-)
-
-const friendUser = computed<null | User>(() => {
- if (selection.value?.type !== 'friend') {
- return null
- }
- const friend = selection.value.friend
- return {
- id: friend.id,
- nickname: friend.nickname,
- avatar: friend.avatar
- }
-})
-
-onMounted(async () => {
- await Promise.all([
- friendStore.fetchFriendList(),
- friendStore.fetchFriendRequestList(),
- groupStore.fetchGroupList()
- ])
-})
-
-/** 閫変腑濂藉弸 鈫� 鍒囧埌濂藉弸璇︽儏 */
-function handleSelectFriend(friend: FriendLite) {
- selection.value = { type: 'friend', friend }
-}
-
-/** 閫変腑缇よ亰 鈫� 鍒囧埌缇よ鎯� */
-function handleSelectGroup(group: GroupLite) {
- selection.value = { type: 'group', group }
-}
-
-/** 閫変腑濂藉弸鐢宠 鈫� 鍒囧埌銆屾柊鐨勬湅鍙嬨�嶈鎯� */
-function handleSelectRequest(request: FriendRequest) {
- selection.value = { type: 'request', request }
-}
-
-/** 鐢宠璇︽儏閲岀偣銆屽彂娑堟伅銆嶏細鐩存帴杩涗笌瀵圭鐨勭鑱婁細璇� */
-function handleChatPeer(peerUserId: number) {
- const friend = friendStore.getFriend(peerUserId)
- const conversationName = friend ? getFriendDisplayName(friend) : String(peerUserId)
- conversationStore.openConversation(
- peerUserId,
- ImConversationType.PRIVATE,
- conversationName,
- friend?.avatar || '',
- { silent: !!friend?.silent }
- )
- router.push({ name: 'ImHomeConversation' })
-}
-
-/** 杩涘叆涓庤濂藉弸鐨勭鑱婁細璇� */
-function handleChatFriend(friend: FriendLite) {
- // 浠� friendStore 鍚屾澶囨敞 + 鍏嶆墦鎵帮紝閬垮厤鏂板缓浼氳瘽鐢ㄨ繃鏈熸暟鎹�
- const entry = friendStore.getFriend(friend.id)
- const conversationName = entry ? getFriendDisplayName(entry) : friend.nickname
- conversationStore.openConversation(
- friend.id,
- ImConversationType.PRIVATE,
- conversationName,
- friend.avatar || '',
- { silent: !!entry?.silent }
- )
- router.push({ name: 'ImHomeConversation' })
-}
-
-/** 杩涘叆璇ョ兢鐨勭兢鑱婁細璇� */
-function handleChatGroup(group: GroupLite) {
- const entry = groupStore.getGroup(group.id)
- conversationStore.openConversation(
- group.id,
- ImConversationType.GROUP,
- group.showGroupName || group.name || '',
- group.showImage || group.showImageThumb || '',
- { silent: !!entry?.silent }
- )
- router.push({ name: 'ImHomeConversation' })
-}
-
-/** 鍒犻櫎濂藉弸锛氫簩娆$‘璁� 鈫� store 钀藉簱 鈫� 娓呯┖褰撳墠閫変腑 */
-async function handleDeleteFriend(friend: FriendLite) {
- try {
- await confirm(`纭畾鍒犻櫎濂藉弸銆�${friend.nickname}銆嶅悧锛焋, '鍒犻櫎鑱旂郴浜�')
- // friendStore.deleteFriend 鍐呴儴宸茬粡绾ц仈娓呯悊瀵瑰簲绉佽亰浼氳瘽
- await friendStore.deleteFriend(friend.id)
- if (selection.value?.type === 'friend' && selection.value.friend.id === friend.id) {
- selection.value = null
- }
- message.success('宸插垹闄ゅソ鍙�')
- } catch {}
-}
-
-/** 澶囨敞宸蹭繚瀛橈細UserInfo 鍐呴儴宸茬粡璧板畬 friendStore 钀藉簱 + 鎻愮ず锛屾湰渚у彧璐熻矗鍚屾 selection 鎸佺殑鏃� FriendLite 鍓湰 */
-function onRemarkSaved(displayName: string) {
- if (selection.value?.type === 'friend') {
- selection.value.friend.displayName = displayName
- }
-}
-</script>
-
-<template>
- <!--
- 閫氳褰� Tab 鏁撮〉锛堝弬鑰冨井淇� PC 閫氳褰曪級
- - 宸︼細鎼滅储 + GroupList / FriendList 涓や釜鎶樺彔鍒嗙粍锛堝ソ鍙嬫寜瀛楁瘝鍒嗘《锛�
- - 鍙筹細閫変腑椤圭殑璇︽儏锛屽ソ鍙嬭蛋鍏变韩 <UserInfo>锛坮elation=friend锛夛紝缇よ亰璧� GroupDetail
- - 鏈〉浠呭仛锛氶�変腑鍒嗗彂 + 鏁版嵁婧愯浆鎹� + 璺ㄧ粍浠朵簨浠惰惤 store
- -->
- <div class="flex flex-1 h-full min-w-0 bg-[var(--ant-color-bg-container)]">
- <ResizableAside :default-width="260" :storage-key="StorageKeys.localStorage.asideWidth">
- <!-- 椤堕儴锛氫粎鎼滅储妗嗭紱h-14 涓庢秷鎭� Tab 椤堕儴瀵归綈锛岄伩鍏嶅垏鎹㈡椂鎼滅储妗嗕笂涓嬫姈鍔� -->
- <div
- class="flex flex-shrink-0 items-center h-14 px-4 border-b border-b-solid border-[var(--im-border-color-lighter)]"
- >
- <Input v-model:value="keyword" placeholder="鎼滅储" allow-clear class="flex-1">
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- </div>
-
- <!-- 鍒楄〃涓讳綋锛氭媶 FriendRequestList / GroupList / FriendList 涓変釜瀛愮粍浠讹紝鍚勮嚜绠$悊鎶樺彔 + 杩囨护锛涙湰椤靛彧閫忎紶閫変腑鎬� -->
- <!-- overflow-y-auto锛氳仈绯讳汉澶氭椂鏈垪琛ㄥ彲婊氬姩锛堢埗 ResizableAside 涓嶆彁渚涙粴鍔級锛屽榻� Vue3 鐨� el-scrollbar -->
- <div class="flex-1 overflow-y-auto">
- <FriendRequestList
- :requests="friendRequests"
- :active-id="selection?.type === 'request' ? selection.request.id : undefined"
- @select="handleSelectRequest"
- />
- <GroupList
- :groups="groups"
- :keyword="keyword"
- :active-id="selection?.type === 'group' ? selection.group.id : undefined"
- @select="handleSelectGroup"
- />
- <FriendList
- :friends="friends"
- :keyword="keyword"
- :active-id="selection?.type === 'friend' ? selection.friend.id : undefined"
- @select="handleSelectFriend"
- @chat="handleChatFriend"
- @delete="handleDeleteFriend"
- />
- </div>
- </ResizableAside>
-
- <!-- 鍙充晶璇︽儏鍖� -->
- <div class="flex-1 min-w-0">
- <!-- 绌烘�侊細鍗犱綅鍥炬爣 + 鎻愮ず鏂囨 -->
- <div
- v-if="!selection"
- class="flex flex-col items-center justify-center h-full gap-3 text-[var(--ant-color-text-secondary)]"
- >
- <Icon
- icon="ant-design:contacts-outlined"
- :size="64"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- <span class="text-sm">鍦ㄥ乏渚ч�夋嫨濂藉弸鎴栫兢鑱婃煡鐪嬭鎯�</span>
- </div>
- <!-- 濂藉弸璇︽儏 -->
- <div v-else-if="selection.type === 'friend'" class="flex justify-center pt-12 px-6">
- <div class="w-full max-w-[320px]">
- <UserInfo
- :user="friendUser"
- :display-name="selection.friend.displayName || ''"
- relation="friend"
- @chat="handleChatFriend(selection.friend)"
- @deleted="selection = null"
- @saved="onRemarkSaved"
- />
- </div>
- </div>
- <!-- 缇よ鎯� -->
- <GroupDetail
- v-else-if="selection.type === 'group'"
- :group="selection.group"
- @chat="handleChatGroup"
- />
- <!-- 鏂扮殑鏈嬪弸 - 鐢宠璇︽儏 -->
- <FriendRequestDetail
- v-else-if="selection.type === 'request'"
- :request="currentRequest"
- @chat="handleChatPeer"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue b/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue
deleted file mode 100644
index 51b060d..0000000
--- a/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue
+++ /dev/null
@@ -1,832 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../../../../components/group'
-import type { Conversation, GroupLite } from '../../../../types'
-
-import { computed, ref, watch } from 'vue'
-
-import { confirm } from '../../../../../../../packages/effects/common-ui/src'
-import { CommonStatusEnum } from '../../../../../../../packages/constants/src'
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { Button, Drawer, Input, message, Popover, Switch } from 'ant-design-vue'
-
-import {
- dissolveGroup,
- muteAll,
- updateGroup
-} from '#/api/im/group'
-import { quitGroup, updateGroupMember } from '#/api/im/group/member'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants'
-import { toGroupCardTarget } from '#/views/im/utils/message'
-import { isGroupQuit } from '#/views/im/utils/user'
-
-import { GroupAdminSetDialog } from '../../../../components/group'
-import { GroupMemberAddDialog } from '../../../../components/group'
-import { GroupMemberGrid } from '../../../../components/group'
-import { GroupMemberRemoveDialog } from '../../../../components/group'
-import { GroupOwnerTransferDialog } from '../../../../components/group'
-import { GroupRequestListDialog } from '../../../../components/group'
-import { RecommendCardDialog } from '../../../../components/user'
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useGroupStore } from '../../../../store/groupStore'
-
-defineOptions({ name: 'ImConversationGroupSide' })
-
-// 澶х兢榛樿鍙睍绀哄墠 N 涓垚鍛橈紙4脳4 瀹牸 鈭� 2 涓摝鐗囬鐣欑粰"娣诲姞 / 绉诲嚭"鎸夐挳锛�
-
-const props = withDefaults(
- defineProps<{
- conversation?: Conversation | null // 褰撳墠浼氳瘽锛氱敤浜庤 / 鍒囧厤鎵撴壈銆佺疆椤剁姸鎬�
- group?: GroupLite & { groupRemark?: string; notice?: string; remarkNickName?: string; } // 褰撳墠缇や俊鎭紙鍙┖锛氭棤婵�娲荤兢浼氳瘽鏃讹級
- members?: GroupMemberLite[]
- modelValue?: boolean // 鎶藉眽鏄惁鎵撳紑锛坴-model锛�
- }>(),
- {
- conversation: null,
- group: undefined,
- modelValue: false,
- members: () => []
- }
-)
-
-const emit = defineEmits<{
- openHistory: [] // 鐐瑰嚮 "鏌ユ壘鑱婂ぉ鍐呭" 琛� 鈫� 鐖剁粍浠舵墦寮� MessageHistory 寮圭獥
- reload: [friendIds?: number[]] // 閭�璇� / 绉婚櫎 / 淇敼缇よ祫鏂欏悗锛岀埗缁勪欢閲嶆柊鎷夌兢鏁版嵁锛涢個璇锋柊鎴愬憳鍦烘櫙閫忓嚭 friendIds 璁╀笂灞傚彲閫夌簿鍑嗗埛鏂�
- 'update:modelValue': [value: boolean]
-}>()
-
-const MEMBER_PREVIEW_COUNT = 14
-const conversationStore = useConversationStore()
-const groupStore = useGroupStore()
-
-const visible = computed({
- get: () => props.modelValue,
- set: (v) => emit('update:modelValue', v)
-})
-
-// ==================== 瑙掕壊 / 鎴愬憳灞曠ず ====================
-
-const searchText = ref('')
-const showAllMembers = ref(false)
-
-const inviteDialogRef = ref<InstanceType<typeof GroupMemberAddDialog>>() // 閭�璇峰ソ鍙嬪叆缇ゅ脊绐� ref锛歨andleOpenInvite 璋� open({ groupId }) 鎵撳紑
-/** 鎵撳紑閭�璇峰ソ鍙嬪叆缇ゅ脊绐� */
-function handleOpenInvite() {
- if (!props.group?.id) {
- return
- }
- inviteDialogRef.value?.open({ groupId: props.group.id })
-}
-
-const myId = computed(() => getCurrentUserId())
-/** 鍘嗗彶閫�缇ょ兢锛氱鎵�鏈夌兢鎿嶄綔鍏ュ彛锛堥個璇� / 绉诲嚭 / 鏀硅祫鏂� / 绂佽█ / 瀹℃壒 / 閫�鍑虹瓑锛夛紝鍙繚鐣欏睍绀猴紱props.group 鏄� GroupLite 鏃� joinStatus锛屽洖 store 鍙栧叏閲� */
-const isQuitGroup = computed(() => {
- const id = props.group?.id
- return id != null && isGroupQuit(groupStore.getGroup(id))
-})
-const isOwner = computed(
- () => !isQuitGroup.value && props.group != null && props.group.ownerId === myId.value
-)
-/** 褰撳墠鐢ㄦ埛鍦ㄧ兢閲岀殑瑙掕壊锛堟潵鑷� props.members 鐨� me 琛岋級锛涚敤浜庡垽瀹氭槸鍚﹀彲绉诲嚭浠栦汉 */
-const myRole = computed(() => props.members.find((m) => m.userId === myId.value)?.role)
-/** 缇や富鎴栫鐞嗗憳锛氬湪鎶藉眽閲屾湁 "绉诲嚭缇ゆ垚鍛�" 鍏ュ彛锛涘巻鍙查��缇ょ兢涓�寰嬭涓烘棤鏉冮檺 */
-const isOwnerOrAdmin = computed(
- () =>
- !isQuitGroup.value &&
- (myRole.value === ImGroupMemberRole.OWNER || myRole.value === ImGroupMemberRole.ADMIN)
-)
-
-/** 鎺掗櫎宸查��缇ゆ垚鍛� + 鍏抽敭瀛楄繃婊わ紱鎸夎鑹叉帓搴忥細缇や富鈫掔鐞嗗憳鈫掓櫘閫氭垚鍛橈紙鍚岃鑹叉寜 userId 绋冲畾锛� */
-const visibleMembers = computed(() => {
- return props.members
- .filter(
- (member) =>
- member.status !== CommonStatusEnum.DISABLE &&
- (member.showName || '').includes(searchText.value)
- )
- .toSorted((a, b) => {
- const roleA = a.role ?? ImGroupMemberRole.NORMAL
- const roleB = b.role ?? ImGroupMemberRole.NORMAL
- return roleA === roleB ? a.userId - b.userId : roleA - roleB
- })
-})
-
-/** 鎶樺彔瑙勫垯锛氭悳绱� / 宸插睍寮� 鏃朵笉鎶樺彔锛屽叾浣欏彧鍙栧墠 N 涓� */
-const moreMembersHidden = computed(
- () =>
- !searchText.value && !showAllMembers.value && visibleMembers.value.length > MEMBER_PREVIEW_COUNT
-)
-const displayMembers = computed(() =>
- moreMembersHidden.value
- ? visibleMembers.value.slice(0, MEMBER_PREVIEW_COUNT)
- : visibleMembers.value
-)
-
-// 鎶藉眽鍏抽棴鏃舵竻鎺夋垚鍛樺尯涓存椂鎬侊紙鎼滅储鍏抽敭瀛椼�佹煡鐪嬫洿澶氬睍寮�锛�
-watch(visible, (v) => {
- if (!v) {
- searchText.value = ''
- showAllMembers.value = false
- }
-})
-
-// ==================== 缇や俊鎭紪杈� ====================
-
-const namePopoverVisible = ref(false)
-const noticePopoverVisible = ref(false)
-const remarkPopoverVisible = ref(false)
-const groupRemarkPopoverVisible = ref(false)
-const editName = ref('')
-const editNotice = ref('')
-const editRemark = ref('')
-const editGroupRemark = ref('')
-
-// popover 寮瑰嚭鏃舵妸褰撳墠鍊肩亴杩涚紪杈戞�侊紝閬垮厤涓婃鏈繚瀛樼殑鑴忓��
-watch(namePopoverVisible, (v) => {
- if (v) editName.value = props.group?.name || ''
-})
-watch(noticePopoverVisible, (v) => {
- if (v) editNotice.value = props.group?.notice || ''
-})
-watch(remarkPopoverVisible, (v) => {
- if (v) editRemark.value = props.group?.remarkNickName || ''
-})
-watch(groupRemarkPopoverVisible, (v) => {
- if (v) editGroupRemark.value = props.group?.groupRemark || ''
-})
-
-// 鎶藉眽鍏抽棴鏃舵竻鎺夋墍鏈� popover锛岄伩鍏嶄笅娆℃墦寮�浠嶅脊鐫�
-watch(visible, (v) => {
- if (!v) {
- namePopoverVisible.value = false
- noticePopoverVisible.value = false
- remarkPopoverVisible.value = false
- groupRemarkPopoverVisible.value = false
- }
-})
-
-/** 缇や富锛氫繚瀛樼兢鍚嶏紙璧� /im/group/update锛夛紱trim 鍚庣┖瀛楃涓叉嫆鎻愪氦锛屼笌 saveGroupRemark 琛屼负瀵归綈 */
-async function saveName() {
- if (!props.group) {
- return
- }
- const trimmed = editName.value.trim()
- if (!trimmed) {
- message.warning('缇ゅ悕绉颁笉鑳戒负绌�')
- return
- }
- await updateGroup({ id: props.group.id, name: trimmed })
- namePopoverVisible.value = false
- message.success('淇濆瓨鎴愬姛')
- emit('reload')
-}
-
-/** 缇や富锛氫繚瀛樼兢鍏憡 */
-async function saveNotice() {
- if (!props.group) {
- return
- }
- await updateGroup({ id: props.group.id, notice: editNotice.value })
- noticePopoverVisible.value = false
- message.success('淇濆瓨鎴愬姛')
- emit('reload')
-}
-
-/** 缇や富锛氬垏鎹€�岃繘缇ゅ鎵广�嶅紑鍏筹紱寮�鍚悗鎵�鏈夈�岀敵璇枫�嶃�岄個璇枫�嶈矾寰勯兘闇�缇や富 / 绠$悊鍛樺悓鎰� */
-async function handleJoinApprovalChange(value: boolean | number | string) {
- if (!props.group) {
- return
- }
- await updateGroup({ id: props.group.id, joinApproval: !!value })
- message.success('淇濆瓨鎴愬姛')
- emit('reload')
-}
-
-/** 浠讳綍鎴愬憳锛氫繚瀛樼兢澶囨敞锛堜粎鑷繁鍙锛屼細鏇挎崲浼氳瘽鍒楄〃 / 椤堕儴缇ゅ悕灞曠ず锛� */
-async function saveGroupRemark() {
- if (!props.group) {
- return
- }
- await updateGroupMember({
- groupId: props.group.id,
- groupRemark: editGroupRemark.value.trim()
- })
- groupRemarkPopoverVisible.value = false
- message.success('淇濆瓨鎴愬姛')
- emit('reload')
-}
-
-/** 浠讳綍鎴愬憳锛氫繚瀛樿嚜宸卞湪缇ら噷鐨勬樀绉帮紙璧� /im/group-member/update锛� */
-async function saveRemark() {
- if (!props.group) {
- return
- }
- await updateGroupMember({
- groupId: props.group.id,
- displayUserName: editRemark.value
- })
- remarkPopoverVisible.value = false
- message.success('淇濆瓨鎴愬姛')
- emit('reload')
-}
-
-// ==================== 寮�鍏冲垏鎹� ====================
-
-/**
- * 娑堟伅鍏嶆墦鎵帮細鏈湴 conversationStore 绔嬪嵆鍒囷紱鍚庣 /silent 寮傛鍚屾锛屽け璐ュ洖婊氭湰鍦�
- *
- * 涓� ConversationItem 鍙抽敭鑿滃崟鐨�"娑堟伅鍏嶆墦鎵�"璇箟涓�鑷达紱鍖哄埆浠呭湪 UI 鍏ュ彛
- */
-function onMutedChange(value: boolean | number | string) {
- if (!props.conversation) {
- return
- }
- const next = !!value
- const { type, targetId } = props.conversation
- conversationStore.setConversationSilent(type, targetId, next)
- groupStore.setGroupSilent(targetId, next).catch((error) => {
- console.error('[IM ConversationGroupSide] setGroupSilent 澶辫触', { targetId }, error)
- conversationStore.setConversationSilent(type, targetId, !next)
- })
-}
-
-/** 缃《鑱婂ぉ锛氱函鏈湴 conversationStore 鎺掑簭鎬侊紙鏃犲悗绔瓧娈碉級 */
-function onTopChange(value: boolean | number | string) {
- if (!props.conversation) {
- return
- }
- conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value)
-}
-
-// ==================== 鍏ㄧ兢绂佽█ ====================
-
-/** 褰撳墠缇ゆ槸鍚﹀叏缇ょ瑷� */
-const currentMutedAll = computed(() => {
- if (!props.group) {
- return false
- }
- return groupStore.getGroup(props.group.id)?.mutedAll ?? false
-})
-
-/** 鍏ㄧ兢绂佽█寮�鍏冲垏鎹� */
-async function onMuteAllChange(value: boolean | number | string) {
- if (!props.group) {
- return
- }
- const newValue = !!value
- await muteAll({ id: props.group.id, mutedAll: newValue })
- message.success(newValue ? '宸插紑鍚叏缇ょ瑷�' : '宸插叧闂叏缇ょ瑷�')
- emit('reload')
-}
-
-// ==================== 杩涚兢瀹℃壒 ====================
-
-const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>() // 杩涚兢鐢宠鍒楄〃寮圭獥 ref锛歨andleOpenRequestList 璋� open({ groupId }) 瑙﹀彂
-
-/** 鎵撳紑褰撳墠缇ょ殑杩涚兢鐢宠鍒楄〃 */
-function handleOpenRequestList() {
- if (!props.group?.id) {
- return
- }
- requestListDialogRef.value?.open({ groupId: props.group.id })
-}
-
-// ==================== 鍒嗕韩缇ゅ悕鐗� ====================
-
-const recommendCardDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 鍒嗕韩缇ゅ悕鐗囧脊绐� ref锛歨andleShareGroupCard 璋冪敤 open({ target }) 鎵撳紑
-
-/** 鍒嗕韩缇ゅ悕鐗囷細鎶婂綋鍓嶇兢浣滀负鍚嶇墖娑堟伅鎺ㄨ崘缁欏叾浠栦細璇� */
-function handleShareGroupCard() {
- const target = toGroupCardTarget(props.group)
- if (!target) {
- return
- }
- recommendCardDialogRef.value?.open({ target })
-}
-
-// ==================== 閫�鍑虹兢鑱� ====================
-
-/** 閫�鍑虹兢鑱婏紙鏅�氭垚鍛樺叆鍙o紱缇や富閫�鍑鸿蛋"瑙f暎缇�"鏄彟涓�鏉¤矾寰勶紝杩欓噷涓嶅鐞嗭級 */
-async function handleQuit() {
- if (!props.group) {
- return
- }
- // 浜屾纭
- try {
- await confirm('閫�鍑虹兢鑱婂悗灏嗕笉鍐嶆帴鏀剁兢閲岀殑娑堟伅锛岀‘璁ら��鍑哄悧锛�', '纭閫�鍑�')
- } catch {
- return
- }
- const groupId = props.group.id
- await quitGroup(groupId)
- // 鏈湴绔嬪嵆鍝嶅簲锛氬厛鎶� self.member 缃� DISABLE锛堣 GroupInfo 绛� isMember 鏀舵暃锛夛紝鍐嶆竻浼氳瘽 + 缇� store
- if (myId.value) {
- groupStore.updateGroupMemberStatus(groupId, myId.value, CommonStatusEnum.DISABLE)
- }
- conversationStore.removeConversation(ImConversationType.GROUP, groupId)
- groupStore.removeGroup(groupId)
- message.success('宸查��鍑虹兢鑱�')
- visible.value = false
-}
-
-/** 瑙f暎缇よ亰锛堜粎缇や富鍏ュ彛锛� */
-async function handleDissolve() {
- if (!props.group) {
- return
- }
- try {
- await confirm('瑙f暎鍚庢墍鏈夋垚鍛樺皢琚Щ鍑猴紝涓旀棤娉曟仮澶嶏紝纭瑙f暎鍚楋紵', '纭瑙f暎')
- } catch {
- return
- }
- const groupId = props.group.id
- await dissolveGroup(groupId)
- conversationStore.removeConversation(ImConversationType.GROUP, groupId)
- groupStore.removeGroup(groupId)
- message.success('缇よ亰宸茶В鏁�')
- visible.value = false
-}
-
-// ==================== 缇や富鎿嶄綔 ====================
-// 绉婚櫎缇ゆ垚鍛橈紙缇や富 / 绠$悊鍛樺彲瑙侊級+ 璁剧疆缇ょ鐞嗗憳锛堜粎缇や富锛�+ 缇や富绠$悊鏉冭浆璁╋紙浠呯兢涓伙級
-
-const removeDialogRef = ref<InstanceType<typeof GroupMemberRemoveDialog>>() // 绉婚櫎缇ゆ垚鍛樺脊绐� ref
-const adminSetDialogRef = ref<InstanceType<typeof GroupAdminSetDialog>>() // 璁剧疆缇ょ鐞嗗憳寮圭獥 ref
-const ownerTransferDialogRef = ref<InstanceType<typeof GroupOwnerTransferDialog>>() // 杞缇や富寮圭獥 ref
-
-// ---------- 绉婚櫎缇ゆ垚鍛� ----------
-
-/** 鎵撳紑绉婚櫎缇ゆ垚鍛樺脊绐楋細濮嬬粓闅愯棌缇や富锛涚鐞嗗憳瑙嗚棰濆闅愯棌鍏跺畠绠$悊鍛橈紙绠$悊鍛樹笉鑳界Щ鍑虹鐞嗗憳锛� */
-function handleOpenRemove() {
- if (!props.group?.id) {
- return
- }
- const hideIds: number[] = []
- if (props.group.ownerId) {
- hideIds.push(props.group.ownerId)
- }
- if (myRole.value === ImGroupMemberRole.ADMIN) {
- props.members
- .filter((m) => m.role === ImGroupMemberRole.ADMIN)
- .forEach((m) => hideIds.push(m.userId))
- }
- removeDialogRef.value?.open({
- groupId: props.group.id,
- members: props.members,
- hideIds
- })
-}
-
-// ---------- 璁剧疆缇ょ鐞嗗憳 ----------
-
-/** 鎵撳紑璁剧疆缇ょ鐞嗗憳寮圭獥锛氬綋鍓嶇鐞嗗憳榛樿鍕鹃�夛紱缇や富浠庡�欓�夐噷闅愯棌 */
-function handleOpenAdminSet() {
- if (!props.group?.id) {
- return
- }
- // 杩囨护宸查��缇ゆ垚鍛橈紝閬垮厤 maxSize 鍚嶉琚殣钘忔垚鍛樺崰鐢ㄥ鑷存棤娉曟柊澧炵鐞嗗憳
- const currentAdminIds = props.members
- .filter(
- (member) =>
- member.role === ImGroupMemberRole.ADMIN && member.status !== CommonStatusEnum.DISABLE
- )
- .map((member) => member.userId)
- const hideIds = props.group.ownerId ? [props.group.ownerId] : []
- adminSetDialogRef.value?.open({
- groupId: props.group.id,
- members: props.members,
- currentAdminIds,
- hideIds
- })
-}
-
-// ---------- 缇や富绠$悊鏉冭浆璁� ----------
-
-/** 鎵撳紑杞缇や富寮圭獥锛氬綋鍓嶇敤鎴蜂粠鍊欓�夐噷闅愯棌锛堜笉鑳借浆缁欒嚜宸憋級 */
-function handleOpenTransferOwner() {
- if (!props.group?.id) {
- return
- }
- ownerTransferDialogRef.value?.open({
- groupId: props.group.id,
- members: props.members,
- hideIds: [myId.value]
- })
-}
-</script>
-
-<template>
- <!-- 鑱婂ぉ闈㈡澘鍙充晶缇や俊鎭娊灞夛細鎴愬憳瀹牸 + 缇や俊鎭� + 寮�鍏� + 閫�鍑虹兢鑱婏紝鏁翠綋瀵归綈寰俊 PC -->
- <Drawer
- v-model:open="visible"
- :closable="false"
- placement="right"
- width="380px"
- root-class-name="im-conversation-group-side__modal"
- >
- <div v-if="group" class="flex flex-col h-full bg-[var(--ant-color-bg-container)]">
- <!-- 涓婇儴锛氬彲婊氬姩鍐呭鍖� -->
- <div class="flex-1 overflow-y-auto bg-[var(--ant-color-fill-secondary)]">
- <!-- ==================== 缇ゆ垚鍛樺尯 ==================== -->
- <div class="px-4 pt-4 pb-[10px] bg-[var(--ant-color-bg-container)]">
- <Input v-model:value="searchText" placeholder="鎼滅储缇ゆ垚鍛�" allow-clear>
- <template #prefix>
- <Icon
- icon="ant-design:search-outlined"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </template>
- </Input>
-
- <div class="flex flex-wrap gap-x-1 gap-y-[14px] mt-[14px]">
- <GroupMemberGrid
- v-for="member in displayMembers"
- :key="member.userId"
- :member="member"
- :size="50"
- clickable
- :group-name="group.name"
- />
-
- <!-- 娣诲姞锛堜换浣曟垚鍛橀兘鑳介個璇凤紱鍘嗗彶閫�缇ょ兢闅愯棌锛� -->
- <div
- v-if="!isQuitGroup"
- class="im-conversation-group-side__tile-wrap flex flex-col items-center w-[66px] cursor-pointer"
- title="閭�璇峰ソ鍙嬪叆缇�"
- @click="handleOpenInvite"
- >
- <div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200">
- <Icon icon="ant-design:plus-outlined" />
- </div>
- <div class="mt-1.5 text-12px leading-[1.5] text-[var(--ant-color-text)] text-center">娣诲姞</div>
- </div>
-
- <!-- 绉诲嚭锛堢兢涓绘垨绠$悊鍛橈紱绠$悊鍛樺彧鑳界Щ鍑烘櫘閫氭垚鍛橈紝鐢卞悗绔牎楠岋級 -->
- <div
- v-if="isOwnerOrAdmin"
- class="im-conversation-group-side__tile-wrap flex flex-col items-center w-[66px] cursor-pointer"
- title="绉诲嚭缇ゆ垚鍛�"
- @click="handleOpenRemove"
- >
- <div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200">
- <Icon icon="ant-design:minus-outlined" />
- </div>
- <div class="mt-1.5 text-12px leading-[1.5] text-[var(--ant-color-text)] text-center">绉诲嚭</div>
- </div>
- </div>
-
- <!-- 澶х兢鎶樺彔锛氶粯璁ゅ彧灞曠ず鍓� N 涓紝鐐� "鏌ョ湅鏇村" 鍏ㄥ睍寮�锛堟悳绱㈡椂涓嶆姌鍙狅級 -->
- <div
- v-if="moreMembersHidden"
- class="flex items-center justify-center gap-1 mt-[10px] pt-1.5 pb-0.5 text-12px text-[var(--ant-color-text-secondary)] cursor-pointer transition-colors duration-150 hover:text-[var(--ant-color-primary)]"
- @click="showAllMembers = true"
- >
- 鏌ョ湅鏇村
- <Icon icon="ant-design:down-outlined" :size="10" />
- </div>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- ==================== 缇や俊鎭� ==================== -->
- <!-- label 鍦ㄤ笂銆乿alue 鍦ㄤ笅锛岀旱鍚戝爢鍙狅紙瀵归綈寰俊 PC 璁捐锛夛紱鍙湁 "缇ゅ叕鍛�" 鍥犱负鍐呭闀垮姞 > chevron -->
- <div class="bg-[var(--ant-color-bg-container)]">
- <!-- 缇よ亰鍚嶇О锛堢兢涓诲彲鏀癸級 -->
- <Popover
- v-if="isOwner"
- v-model:open="namePopoverVisible"
- trigger="click"
- placement="leftTop"
- :overlay-style="{ width: '280px' }"
- >
- <div
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇よ亰鍚嶇О</span>
- <span class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate">{{ group.name }}</span>
- </div>
- <template #content>
- <div class="flex flex-col gap-2">
- <Input v-model:value="editName" :maxlength="20" placeholder="璇疯緭鍏ョ兢鑱婂悕绉�" />
- <div class="flex justify-end gap-2">
- <Button size="small" @click="namePopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="saveName">淇濆瓨</Button>
- </div>
- </div>
- </template>
- </Popover>
- <div
- v-else
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇よ亰鍚嶇О</span>
- <span class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate">{{ group.name }}</span>
- </div>
-
- <!-- 缇ゅ叕鍛婏紙缇や富鍙敼锛夛細鍐呭鍙兘寰堥暱锛屽姞 > chevron 琛ㄧず鍙睍寮�缂栬緫 -->
- <Popover
- v-if="isOwner"
- v-model:open="noticePopoverVisible"
- trigger="click"
- placement="leftTop"
- :overlay-style="{ width: '320px' }"
- >
- <div
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- >
- <div class="flex items-center justify-between gap-2">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇ゅ叕鍛�</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- <span
- v-if="group.notice"
- class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2"
- >
- {{ group.notice }}
- </span>
- <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">鏈缃�</span>
- </div>
- <template #content>
- <div class="flex flex-col gap-2">
- <Input.TextArea
- v-model:value="editNotice"
- :rows="4"
- :maxlength="1024"
- show-count
- placeholder="璇疯緭鍏ョ兢鍏憡"
- />
- <div class="flex justify-end gap-2">
- <Button size="small" @click="noticePopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="saveNotice">淇濆瓨</Button>
- </div>
- </div>
- </template>
- </Popover>
- <div
- v-else
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇ゅ叕鍛�</span>
- <span
- v-if="group.notice"
- class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2"
- >
- {{ group.notice }}
- </span>
- <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">鏈缃�</span>
- </div>
-
- <!-- 澶囨敞锛堜粎鑷繁鍙锛涗繚瀛樺悗浼氭浛鎹細璇濆垪琛� / 椤堕儴缇ゅ悕灞曠ず锛夛紱鍘嗗彶閫�缇ょ兢闅愯棌锛氭敼澶囨敞璧� updateGroupMember锛屽凡閫�缇や細琚悗绔嫆 -->
- <Popover
- v-if="!isQuitGroup"
- v-model:open="groupRemarkPopoverVisible"
- trigger="click"
- placement="leftTop"
- :overlay-style="{ width: '280px' }"
- >
- <div
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">澶囨敞</span>
- <span
- v-if="group.groupRemark"
- class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2"
- >
- {{ group.groupRemark }}
- </span>
- <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">
- 缇よ亰鐨勫娉ㄤ粎鑷繁鍙
- </span>
- </div>
- <template #content>
- <div class="flex flex-col gap-2">
- <Input.TextArea
- v-model:value="editGroupRemark"
- :rows="3"
- :maxlength="64"
- show-count
- placeholder="浠呰嚜宸卞彲瑙�"
- />
- <div class="flex justify-end gap-2">
- <Button size="small" @click="groupRemarkPopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="saveGroupRemark">淇濆瓨</Button>
- </div>
- </div>
- </template>
- </Popover>
-
- <!-- 鎴戝湪鏈兢鐨勬樀绉帮紙浠讳綍鎴愬憳閮借兘鏀硅嚜宸辩殑锛夛紱鍘嗗彶閫�缇ょ兢闅愯棌锛氳蛋 updateGroupMember锛屽凡閫�缇や細琚悗绔嫆 -->
- <Popover
- v-if="!isQuitGroup"
- v-model:open="remarkPopoverVisible"
- trigger="click"
- placement="leftTop"
- :overlay-style="{ width: '280px' }"
- >
- <div
- class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">鎴戝湪鏈兢鐨勬樀绉�</span>
- <span
- v-if="group.remarkNickName"
- class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate"
- >
- {{ group.remarkNickName }}
- </span>
- <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">鐐瑰嚮璁剧疆</span>
- </div>
- <template #content>
- <div class="flex flex-col gap-2">
- <Input v-model:value="editRemark" :maxlength="20" placeholder="璇疯緭鍏ユ湰缇ゆ樀绉�" />
- <div class="flex justify-end gap-2">
- <Button size="small" @click="remarkPopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="saveRemark">淇濆瓨</Button>
- </div>
- </div>
- </template>
- </Popover>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- ==================== 鏌ユ壘鑱婂ぉ鍐呭 ==================== -->
- <!-- 鐐瑰嚮 鈫� 鐖剁粍浠舵墦寮� MessageHistory 寮圭獥 -->
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <div class="bg-[var(--ant-color-bg-container)]">
- <div
- class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="emit('openHistory')"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">鏌ユ壘鑱婂ぉ鍐呭</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- <!-- 鍒嗕韩缇ゅ悕鐗囷細寮� RecommendCardDialog锛屾妸褰撳墠缇や綔涓哄悕鐗囨秷鎭帹鑽愮粰鍏朵粬浼氳瘽 -->
- <div
- v-if="group"
- class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleShareGroupCard"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">鍒嗕韩缇ゅ悕鐗�</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- ==================== 寮�鍏抽」 ==================== -->
- <div class="bg-[var(--ant-color-bg-container)]">
- <div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">娑堟伅鍏嶆墦鎵�</span>
- <Switch :checked="!!conversation?.silent" @change="onMutedChange" />
- </div>
- <div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缃《鑱婂ぉ</span>
- <Switch :checked="!!conversation?.top" @change="onTopChange" />
- </div>
- <!-- 鍏ㄧ兢绂佽█锛氫粎缇や富鎴栫鐞嗗憳鍙搷浣� -->
- <div v-if="isOwnerOrAdmin" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">鍏ㄧ兢绂佽█</span>
- <Switch :checked="!!currentMutedAll" @change="onMuteAllChange" />
- </div>
- </div>
-
- <!-- ==================== 杩涚兢瀹℃壒 ==================== -->
- <!-- 鍗曠嫭涓�娈碉細缇や富寮�鍏� + 绱ц窡銆�- 杩涚兢鐢宠銆嶅瓙椤癸紱涓庡井淇$兢绠$悊甯冨眬瀵归綈 -->
- <template v-if="isOwner || (isOwnerOrAdmin && !!group.joinApproval)">
- <div class="flex-shrink-0 h-[10px]"></div>
- <div class="bg-[var(--ant-color-bg-container)]">
- <!-- 杩涚兢瀹℃壒锛氫粎缇や富鍙搷浣滐紱寮�鍚悗鏅�氭垚鍛樼殑銆岀敵璇枫�嶃�岄個璇枫�嶈矾寰勯兘闇�缇や富 / 绠$悊鍛樺悓鎰忥紱缇や富 / 绠$悊鍛橀個璇风洿杩� -->
- <div v-if="isOwner" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">杩涚兢闇�瑕佺兢涓� / 缇ょ鐞嗙‘璁�</span>
- <Switch :checked="!!group.joinApproval" @change="handleJoinApprovalChange" />
- </div>
- <!-- 杩涚兢鐢宠瀛愰」锛氫粎褰撳紑鍚鎵� + 褰撳墠鐢ㄦ埛鏄� owner / admin 鏃跺嚭鐜帮紱鐐瑰嚮杩涘垪琛� dialog -->
- <div
- v-if="isOwnerOrAdmin && !!group.joinApproval"
- class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleOpenRequestList"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">- 杩涚兢鐢宠</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- </div>
- </template>
-
- <!-- ==================== 缇や富鎿嶄綔 ==================== -->
- <!-- 浠呯兢涓诲彲瑙侊紝鍚鐞嗗憳璁剧疆 + 缇や富绠$悊鏉冭浆璁� -->
- <template v-if="isOwner">
- <div class="flex-shrink-0 h-[10px]"></div>
- <div class="bg-[var(--ant-color-bg-container)]">
- <div
- class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleOpenAdminSet"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇ょ鐞嗗憳</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- <div
- class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleOpenTransferOwner"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缇や富绠$悊鏉冭浆璁�</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- </div>
- </template>
- </div>
-
- <!-- ==================== 搴曢儴锛氶��鍑� / 瑙f暎缇よ亰锛堝巻鍙查��缇ょ兢闅愯棌锛屽凡閫�缇ゆ棤闇�鍐嶉��锛� ==================== -->
- <div
- v-if="!isQuitGroup"
- class="flex-shrink-0 px-4 pt-[14px] pb-[18px] bg-[var(--ant-color-bg-container)] border-t border-t-solid border-[var(--im-border-color-lighter)]"
- >
- <!-- 缇や富锛氳В鏁g兢鑱� -->
- <Button
- v-if="isOwner"
- class="w-full !h-9 text-14px"
- danger
- @click="handleDissolve"
- >
- 瑙f暎缇よ亰
- </Button>
- <!-- 闈炵兢涓伙細閫�鍑虹兢鑱� -->
- <Button
- v-else
- class="w-full !h-9 text-14px"
- danger
- @click="handleQuit"
- >
- 閫�鍑虹兢鑱�
- </Button>
- </div>
- </div>
-
- <!-- ==================== 瀛愬璇濇 ==================== -->
- <!-- 閭�璇锋柊鎴愬憳 / 閫夋垚鍛樼Щ闄� -->
- <GroupMemberAddDialog ref="inviteDialogRef" @reload="(ids) => $emit('reload', ids)" />
- <GroupMemberRemoveDialog ref="removeDialogRef" @reload="$emit('reload')" />
-
- <!-- 缇や富鎿嶄綔锛氱鐞嗗憳璁剧疆锛堜竴涓脊绐楀悎骞跺 / 鍒狅紝鎻愪氦鏃� diff锛�+ 缇や富绠$悊鏉冭浆璁� -->
- <GroupAdminSetDialog ref="adminSetDialogRef" @reload="$emit('reload')" />
- <GroupOwnerTransferDialog ref="ownerTransferDialogRef" @reload="$emit('reload')" />
-
- <!-- 杩涚兢鐢宠鍒楄〃锛堜粎褰撳紑鍚鎵� + 褰撳墠鐢ㄦ埛鏄� owner / admin 鏃跺叆鍙e彲瑙侊級 -->
- <GroupRequestListDialog ref="requestListDialogRef" />
-
- <!-- 鍒嗕韩缇ゅ悕鐗囷細鎶婂綋鍓嶇兢浣滀负鍚嶇墖娑堟伅鎺ㄨ崘缁欏叾浠栦細璇� -->
- <RecommendCardDialog ref="recommendCardDialogRef" />
- </Drawer>
-</template>
-
-<style scoped>
-/* 銆屾坊鍔� / 绉诲嚭銆嶇摝鐗囷細hover 鏃惰仈鍔ㄥ唴閮� icon-tile 璧颁富鑹诧紝璺ㄥ瓙鍏冪礌鐨� hover 鑱斿姩鏃犳硶鐢ㄥ崟鍏冪礌宸ュ叿绫昏〃杈� */
-.im-conversation-group-side__tile-wrap:hover .im-conversation-group-side__icon-tile {
- color: var(--ant-color-primary);
- border-color: var(--ant-color-primary);
- background-color: var(--ant-color-primary-bg);
-}
-
-/* :deep 绌块�� Icon 鍐呴儴 svg锛� el-icon 鍏ㄥ眬 color 鍦ㄦ殫鑹叉ā寮忎笅琚富棰樼洊杩囷紝閿� fill 鍒板綋鍓嶈壊 */
-.im-conversation-group-side__icon-tile :deep(svg) {
- fill: currentColor !important;
-}
-
-/* 鐩搁偦淇℃伅琛屽姞鍒嗛殧绾匡紱 鐩搁偦鍏勫紵閫夋嫨鍣ㄦ棤娉曠敤宸ュ叿绫昏〃杈� */
-.im-conversation-group-side__row + .im-conversation-group-side__row {
- border-top: 1px solid var(--im-border-color-lighter);
-}
-</style>
-
-<!-- AntD Drawer 琚紶閫佸嚭褰撳墠 scoped 杈圭晫锛岃繖閲岄潬 root-class-name 鍘嬫帀榛樿 padding -->
-<style>
-.im-conversation-group-side__modal .ant-drawer-body {
- padding: 0;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/conversation/conversation-item.vue b/src/views/im/home/pages/conversation/components/conversation/conversation-item.vue
deleted file mode 100644
index ff2123d..0000000
--- a/src/views/im/home/pages/conversation/components/conversation/conversation-item.vue
+++ /dev/null
@@ -1,330 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation } from '../../../../types'
-
-import { computed } from 'vue'
-
-import { confirm } from '../../../../../../../packages/effects/common-ui/src'
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { Tag } from 'ant-design-vue'
-
-import { buildRecallTip } from '#/views/im/utils/conversation'
-import { formatConversationTime } from '#/views/im/utils/time'
-import { getSenderDisplayName } from '#/views/im/utils/user'
-
-import { ImContentType, ImConversationType, isNormalMessage } from '../../../../../utils/constants'
-import { GroupAvatar } from '../../../../components/group'
-import { UserAvatar } from '../../../../components/user'
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useFriendStore } from '../../../../store/friendStore'
-import { useGroupRequestStore } from '../../../../store/groupRequestStore'
-import { useGroupStore } from '../../../../store/groupStore'
-import { useImUiStore } from '../../../../store/uiStore'
-
-defineOptions({ name: 'ImConversationItem' })
-
-// 鍛ㄤ腑鏂囧悕锛坉ayjs 鐨� day() 杩斿洖 0-6锛�0=鍛ㄦ棩锛夛紱椤圭洰娌″叏灞�瑁� dayjs/locale/zh-cn锛屾湰鍦版槧灏勯伩鍏嶅紩鍓綔鐢�
-const props = defineProps<{
- conversation: Conversation
-}>()
-
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-const groupRequestStore = useGroupRequestStore()
-const uiStore = useImUiStore()
-
-const isActive = computed(
- () =>
- conversationStore.activeConversation?.targetId === props.conversation.targetId &&
- conversationStore.activeConversation?.type === props.conversation.type
-)
-
-/**
- * 褰撳墠浼氳瘽鐨勮崏绋垮揩鐓э細瀛樺湪鏃跺垪琛ㄦ樉绀� [鑽夌] 鍓嶇紑 + 鏂囨湰锛岀洊鎺� sender 鍓嶇紑鍜� @鎴� 绾㈠瓧
- * 褰撳墠鎵撳紑鐨勪細璇濅笉灞曠ず鑽夌锛涜緭鍏ュ唴瀹瑰凡缁忓湪缂栬緫鍣ㄩ噷鍙锛屽垪琛ㄥ啀鏄剧ず [鑽夌] 鍙嶈�屽啑浣�
- */
-const draft = computed(() => {
- if (isActive.value) {
- return undefined
- }
- return conversationStore.getConversationDraft(props.conversation)
-})
-
-const isGroup = computed(() => props.conversation.type === ImConversationType.GROUP)
-
-/** 鏈�鍚庝竴鏉℃秷鎭彂閫佽�呯殑灞曠ず鍚嶏細瀹炴椂绠� + 蹇収 fallback锛坓etSenderDisplayName 绠椾笉鍑烘椂鍏滃簳锛� */
-const lastSenderDisplayName = computed(() => {
- const senderId = props.conversation.lastSenderId
- if (!senderId) {
- return ''
- }
- return getSenderDisplayName(
- senderId,
- props.conversation.type,
- props.conversation.targetId,
- props.conversation.lastSenderDisplayName
- )
-})
-
-/** 缇よ亰 + 鏈夋渶鍚庡彂閫佽�� + 鏈�鍚庝竴鏉℃槸鏅�氭秷鎭椂锛屾樉绀哄彂閫佽�呭墠缂�锛團RIEND_* / GROUP_* / RECALL / 鑽夌鎬佷笉甯﹀墠缂�锛� */
-const showSendName = computed(() => {
- if (draft.value) {
- return false
- }
- if (!isGroup.value) {
- return false
- }
- if (!props.conversation.lastSenderId) {
- return false
- }
- const lastType = props.conversation.lastMessageType
- return lastType != null && isNormalMessage(lastType)
-})
-
-/** 鍒楄〃灞曠ず鏂囨锛氳崏绋夸紭鍏堬紙瀵归綈寰俊 PC锛氭湁鑽夌鏃剁洊鎺夋渶鍚庝竴鏉¢瑙堬級鈫� 鎾ゅ洖瀹炴椂绠� 鈫� lastContent 鍏滃簳 */
-const lastContentDisplay = computed(() => {
- if (draft.value) {
- return draft.value.plain
- }
- if (
- props.conversation.lastMessageType === ImContentType.RECALL &&
- props.conversation.lastSenderId != null
- ) {
- return buildRecallTip(
- props.conversation.lastSenderId,
- !!props.conversation.lastSelfSend,
- props.conversation.type,
- props.conversation.targetId,
- props.conversation.lastSenderDisplayName
- )
- }
- return props.conversation.lastContent
-})
-
-/** 浼氳瘽鍒楄〃 "[鑽夌]" / "@ 鎴�" / "@ 鍏ㄤ綋鎴愬憳" 绾㈠瓧鎻愮ず锛涜崏绋夸紭鍏堬紙瀵归綈寰俊 PC锛� */
-const atText = computed(() => {
- if (draft.value) {
- return '[鑽夌]'
- }
- if (props.conversation.atMe) {
- return '[鏈変汉@鎴慮'
- }
- if (props.conversation.atAll) {
- return '[@鍏ㄤ綋鎴愬憳]'
- }
- return ''
-})
-
-/** 鍏嶆墦鎵颁細璇濇湭璇绘潯鏁版枃妗� */
-const mutedUnreadText = computed(() => {
- if (!props.conversation.silent || props.conversation.unreadCount <= 0) {
- return ''
- }
- const count = props.conversation.unreadCount > 99 ? '99+' : props.conversation.unreadCount
- return `[${count}鏉`
-})
-
-/** 缇よ亰鏈鐞嗗姞缇ょ敵璇风孩瀛楀墠缂�锛泂tore 宸茬粡鎸夈�屾垜绠$悊鐨勭兢銆嶈繃婊よ繃锛宑ount > 0 鍗冲彲鏄剧ず */
-const requestText = computed(() => {
- if (!isGroup.value) {
- return ''
- }
- const count = groupRequestStore.getUnhandledGroupRequestCountMap.get(props.conversation.targetId) ?? 0
- return count > 0 ? `[${count}鏉¤繘缇ょ敵璇穄` : ''
-})
-
-/** 鐐瑰嚮鍒囦細璇� */
-function handleClick() {
- conversationStore.setActiveConversation(props.conversation)
-}
-
-/** 鍒囨崲缃《 */
-function handleTop() {
- conversationStore.setConversationTop(
- props.conversation.type,
- props.conversation.targetId,
- !props.conversation.top
- )
-}
-
-/** 鍒囨崲鍏嶆墦鎵帮細涔愯 UI锛堝厛鏈湴鍒囨崲锛岃彍鍗曠珛鍗冲叧锛涘悗绔け璐ュ洖婊� conversation 鐘舵�侊級 */
-function handleMuted() {
- const next = !props.conversation.silent
- const { type, targetId } = props.conversation
- conversationStore.setConversationSilent(type, targetId, next)
- const sync =
- type === ImConversationType.PRIVATE
- ? friendStore.setFriendSilent(targetId, next)
- : groupStore.setGroupSilent(targetId, next)
- sync.catch((error) => {
- console.error('[IM] 鍒囨崲鍏嶆墦鎵板け璐�', error)
- conversationStore.setConversationSilent(type, targetId, !next)
- })
-}
-
-/** 鍒犻櫎浼氳瘽锛氫簩娆$‘璁ゅ悗杞垹 */
-async function handleDelete() {
- try {
- await confirm(`纭畾鍒犻櫎涓庛��${props.conversation.name}銆嶇殑浼氳瘽鍚楋紵`, '鍒犻櫎浼氳瘽')
- conversationStore.removeConversation(props.conversation.type, props.conversation.targetId)
- } catch {}
-}
-
-/** 鍙抽敭鑿滃崟锛氱疆椤� / 鍏嶆墦鎵� / 鍒犻櫎 */
-function handleContextMenu(e: MouseEvent) {
- uiStore.openContextMenu(
- { x: e.clientX, y: e.clientY },
- [
- { key: 'TOP', name: props.conversation.top ? '鍙栨秷缃《' : '缃《' },
- { key: 'MUTED', name: props.conversation.silent ? '鍏佽娑堟伅閫氱煡' : '娑堟伅鍏嶆墦鎵�' },
- { key: 'DELETE', name: '鍒犻櫎', divided: true, danger: true }
- ],
- (item) => {
- switch (item.key) {
- case 'DELETE': {
- void handleDelete()
-
- break;
- }
- case 'MUTED': {
- handleMuted()
-
- break;
- }
- case 'TOP': {
- handleTop()
-
- break;
- }
- // No default
- }
- }
- )
-}
-
-</script>
-
-<template>
- <div
- class="relative flex items-center gap-2.5 px-4 py-3 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="{ '!bg-[#d9ecff] dark:!bg-[var(--ant-color-primary-bg-hover)]': isActive }"
- :data-conversation-key="`${conversation.type}-${conversation.targetId}`"
- @click="handleClick"
- @contextmenu.prevent="handleContextMenu"
- >
- <!-- 澶村儚 + 鏈鎻愮ず -->
- <div class="relative">
- <GroupAvatar
- v-if="isGroup"
- :group-id="conversation.targetId"
- :url="conversation.avatar"
- :name="conversation.name"
- :size="40"
- />
- <UserAvatar
- v-else
- :url="conversation.avatar"
- :name="conversation.name"
- :size="40"
- :clickable="false"
- />
- <!-- 鏁板瓧寰芥爣锛氶潪鍏嶆墦鎵颁笖鏈夋湭璇绘椂鏄剧ず鍏蜂綋鏉℃暟 -->
- <span
- v-show="!conversation.silent && conversation.unreadCount > 0"
- class="absolute -top-1.5 -right-1.5 min-w-[18px] h-[18px] px-1.5 text-11px leading-[18px] text-white text-center bg-[#f56c6c] border border-solid border-white dark:border-[var(--ant-color-bg-container)] rounded-full box-border whitespace-nowrap"
- >
- {{ conversation.unreadCount > 99 ? '99+' : conversation.unreadCount }}
- </span>
- <!-- 灏忕孩鐐癸細鍏嶆墦鎵颁笖鏈夋湭璇绘椂鎻愮ず瀛樺湪鏂版秷鎭� -->
- <span
- v-show="conversation.silent && conversation.unreadCount > 0"
- class="absolute -top-1 -right-1 w-2.5 h-2.5 bg-[#f56c6c] border border-solid border-white dark:border-[var(--ant-color-bg-container)] rounded-full box-border"
- ></span>
- </div>
-
- <div class="flex-1 min-w-0">
- <div class="flex items-center justify-between">
- <span class="flex flex-1 items-center gap-1 min-w-0">
- <span class="overflow-hidden text-sm truncate text-[var(--ant-color-text)]">
- {{ conversation.name }}
- </span>
- <Tag
- v-if="isGroup"
- type="primary"
- size="small"
- class="conversation-item__tag flex-shrink-0 !h-[18px] !px-1 !leading-4"
- >
- 缇�
- </Tag>
- </span>
- <span class="flex-shrink-0 ml-1 text-12px text-[var(--ant-color-text-secondary)]">
- {{ formatConversationTime(conversation.lastSendTime) }}
- </span>
- </div>
- <div class="flex items-center mt-1 leading-5">
- <!-- 杩涚兢鐢宠绾㈠瓧鍓嶇紑锛氱兢涓� / 绠$悊鍛樼湅鍒拌嚜宸辩鐞嗙殑缇や笅杩樻湁鏈鐞嗙敵璇锋椂鏄剧ず -->
- <span
- v-if="requestText"
- class="conversation-item__prefix flex-shrink overflow-hidden text-12px text-[#c70b0b] truncate whitespace-nowrap"
- >
- {{ requestText }}
- </span>
- <!-- @绾㈠瓧鎻愮ず锛歛tMe 浼樺厛浜� atAll -->
- <span
- v-if="atText"
- class="conversation-item__prefix flex-shrink overflow-hidden text-12px text-[#c70b0b] truncate whitespace-nowrap"
- >
- {{ atText }}
- </span>
- <!-- 鍏嶆墦鎵版湭璇绘潯鏁� -->
- <span
- v-if="mutedUnreadText"
- class="flex-shrink-0 mr-1 text-12px text-[var(--ant-color-text-secondary)] whitespace-nowrap"
- >
- {{ mutedUnreadText }}
- </span>
- <!-- 缇よ亰鏈�鍚庝竴鏉″彂閫佽�呭墠缂�锛氭寜 lastSenderId + 褰撳墠浼氳瘽涓婁笅鏂囧疄鏃剁畻鍚嶅瓧 -->
- <span
- v-if="showSendName"
- class="conversation-item__sender flex-shrink overflow-hidden text-12px text-[var(--ant-color-text-secondary)] truncate whitespace-nowrap"
- >
- {{ lastSenderDisplayName }}:
- </span>
- <span
- class="flex-1 min-w-0 overflow-hidden text-12px truncate text-[var(--ant-color-text-secondary)]"
- >
- {{ lastContentDisplay }}
- </span>
- <!-- 鍏嶆墦鎵板浘鏍� -->
- <Icon
- v-if="conversation.silent"
- icon="mdi:bell-off-outline"
- :size="14"
- class="conversation-item__silent flex-shrink-0 ml-1 text-[var(--ant-color-text-disabled)]"
- title="娑堟伅鍏嶆墦鎵�"
- />
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 娑堟帀 el-tag 鍒囦細璇濇椂 active 搴曡壊鍙樺寲鐨勬笎鍙橈紙鐪嬭捣鏉ュ儚闂儊锛夛紱 :deep 绌块�� el-tag 鑷韩鏍峰紡 */
-.conversation-item__tag {
- transition: none !important;
-}
-
-/* el-icon 鐨勫叏灞� color:var(--color) 鍦ㄦ殫鑹叉ā寮忎笅浼氭覆鏌撴垚鐧借壊锛岃繖閲岀敤 :deep + !important 閿佸畾 */
-.conversation-item__silent :deep(svg) {
- fill: currentColor !important;
-}
-
-.conversation-item__prefix {
- max-width: 45%;
-}
-
-.conversation-item__sender {
- max-width: 50%;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue b/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue
deleted file mode 100644
index b3cb8a3..0000000
--- a/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue
+++ /dev/null
@@ -1,285 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation, Friend } from '../../../../types'
-
-import { computed, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { Button, Drawer, Input, message, Popover, Spin, Switch } from 'ant-design-vue'
-
-import { useConversationStore } from '#/views/im/home/store/conversationStore'
-import { useFriendStore } from '#/views/im/home/store/friendStore'
-import { useGroupStore } from '#/views/im/home/store/groupStore'
-import { ImConversationType } from '#/views/im/utils/constants'
-import { getFriendDisplayName, getGroupDisplayName } from '#/views/im/utils/user'
-
-import { GroupCreateDialog } from '../../../../components/group'
-import { UserAvatar } from '../../../../components/user'
-
-defineOptions({ name: 'ImConversationPrivateSide' })
-
-const props = withDefaults(
- defineProps<{
- conversation?: Conversation | null // 褰撳墠浼氳瘽锛堝彇缃《 / 鍏嶆墦鎵版�侊級
- friend?: Friend // 瀵规柟濂藉弸淇℃伅锛堝彇澶村儚 / 鏄电О锛�
- modelValue?: boolean // 鎶藉眽寮�鍏筹紙v-model锛�
- }>(),
- {
- conversation: null,
- friend: undefined,
- modelValue: false
- }
-)
-
-const emit = defineEmits<{
- openHistory: [] // 鐐瑰嚮 "鏌ユ壘鑱婂ぉ鍐呭" 琛� 鈫� 鐖剁粍浠舵墦寮� MessageHistory 寮圭獥
- 'update:modelValue': [value: boolean]
-}>()
-
-const visible = computed({
- get: () => props.modelValue,
- set: (value) => emit('update:modelValue', value)
-})
-
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-
-/** tile 鏍囩 / 鍚庣画鑱婂ぉ鐣岄潰鐢ㄧ殑灞曠ず鍚嶏細澶囨敞浼樺厛 */
-const displayName = computed(() => (props.friend ? getFriendDisplayName(props.friend) : ''))
-
-const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // 鍙戣捣缇よ亰寮圭獥 ref锛歨andleOpenCreateGroup 璋� open({ lockedIds }) 閿佸畾瀵规柟
-
-/** 鎵撳紑鍙戣捣缇よ亰寮圭獥锛氭妸瀵规柟榛樿鍕句笂涓斾笉鍙彇娑堬紝瀵瑰簲寰俊"鍩轰簬绉佽亰鍙戣捣缇よ亰" */
-function handleOpenCreateGroup() {
- const lockedIds = props.friend ? [props.friend.friendUserId] : []
- createGroupDialogRef.value?.open({ lockedIds })
-}
-
-const displayNamePopoverVisible = ref(false)
-const editDisplayName = ref('')
-
-// popover 寮瑰嚭鏃舵妸褰撳墠澶囨敞鐏岃繘缂栬緫鎬侊紝閬垮厤涓婃鏈繚瀛樼殑鑴忓��
-watch(displayNamePopoverVisible, (open) => {
- if (open) {
- editDisplayName.value = props.friend?.displayName || ''
- }
-})
-
-// 鎶藉眽鍏抽棴鏃舵妸杩樻病鏀舵帀鐨� popover 涓�骞舵竻鎺夛紝閬垮厤涓嬫鎵撳紑闂竴涓�
-watch(visible, (open) => {
- if (!open) {
- displayNamePopoverVisible.value = false
- }
-})
-
-/** 澶囨敞 popover 鐐瑰嚮淇濆瓨 */
-async function handleSaveDisplayName() {
- if (!props.friend) {
- return
- }
- await friendStore.setFriendDisplayName(props.friend.friendUserId, editDisplayName.value)
- displayNamePopoverVisible.value = false
- message.success('淇濆瓨鎴愬姛')
-}
-
-/**
- * 鍒囧厤鎵撴壈锛氫箰瑙傚弻鍐� conversationStore + friendStore锛涘悗绔け璐ュ洖婊� conversation 鐘舵�侊紝淇濇寔涓� ConversationItem.handleMuted 涓�鑷�
- */
-function handleMutedChange(value: boolean | number | string) {
- if (!props.conversation) {
- return
- }
- const next = !!value
- const { type, targetId } = props.conversation
- conversationStore.setConversationSilent(type, targetId, next)
- if (type !== ImConversationType.PRIVATE) {
- return
- }
- friendStore.setFriendSilent(targetId, next).catch((error) => {
- console.error('[IM ConversationPrivateSide] 鍒囨崲鍏嶆墦鎵板け璐�', { targetId }, error)
- conversationStore.setConversationSilent(type, targetId, !next)
- })
-}
-
-/** 鍒囩疆椤讹細绾湰鍦� conversationStore 鎺掑簭鎬侊紙鏃犲悗绔瓧娈碉級 */
-function handleTopChange(value: boolean | number | string) {
- if (!props.conversation) {
- return
- }
- conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value)
-}
-
-/** 缇ゅ垱寤烘垚鍔燂細璺冲埌鏂扮兢浼氳瘽 + 鍏虫帀鏈晶鎶藉眽锛岃鐢ㄦ埛涓撴敞鏂扮兢 */
-function handleGroupCreated(groupId: number) {
- const group = groupStore.getGroup(groupId)
- if (!group) {
- return
- }
- conversationStore.openConversation(
- groupId,
- ImConversationType.GROUP,
- getGroupDisplayName(group),
- group.avatar || '',
- { silent: !!group.silent }
- )
- visible.value = false
-}
-</script>
-
-<template>
- <!--
- 绉佽亰渚ц竟鎶藉眽
- - 鏁翠綋缁撴瀯瀵归綈 ConversationGroupSide锛氬鏍� + 淇℃伅琛� + 寮�鍏�
- - 椤堕儴濂藉弸瀹牸 + "+" tile锛氱偣 + 璋冭捣 GroupCreateDialog 骞堕攣瀹氬鏂癸紝瀵归綈寰俊"鍩轰簬绉佽亰鍙戣捣缇よ亰"
- - "娓呯┖鑱婂ぉ璁板綍"鎸夐挳鍦� WeChat 閲屾湁锛屼絾鐩墠鍚庣娌″缓娑堟伅娓呯┖鑳藉姏锛屽厛涓嶅姞閬垮厤鍋氬崐鍚婂瓙
- -->
- <Drawer
- v-model:open="visible"
- :closable="false"
- placement="right"
- width="340px"
- root-class-name="im-conversation-private-side__modal"
- >
- <!-- friend 缂哄け鍦烘櫙锛氶檶鐢熶汉浼氳瘽鍒氭墦寮� / 濂藉弸鏁版嵁杩樻病琛ユ媺鍒帮紱绌虹櫧浼氳鐢ㄦ埛浠ヤ负鎶藉眽鍧忎簡锛岀粰涓姞杞藉崰浣� -->
- <div
- v-if="!friend"
- class="flex flex-col items-center justify-center h-full text-13px text-[var(--ant-color-text-placeholder)] bg-[var(--ant-color-bg-container)]"
- >
- <Spin tip="鍔犺浇涓�..." />
- </div>
- <div v-else class="flex flex-col h-full bg-[var(--ant-color-bg-container)]">
- <div class="flex-1 overflow-y-auto bg-[var(--ant-color-fill-secondary)]">
- <!-- 濂藉弸瀹牸锛氬師 tile + "+" tile锛屽榻� GroupSide 瑙嗚锛岃涓ょ鎶藉眽鐪嬭捣鏉ユ槸涓�瀹剁殑 -->
- <div class="flex flex-wrap gap-1 px-4 pt-4 pb-[14px] bg-[var(--ant-color-bg-container)]">
- <div class="flex flex-col items-center w-[66px]">
- <UserAvatar
- :id="friend.friendUserId"
- :url="friend.avatar"
- :name="friend.nickname"
- :size="50"
- />
- <div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate">
- {{ displayName }}
- </div>
- </div>
-
- <!-- + tile锛氱偣鍑昏皟璧� GroupCreateDialog锛屾妸瀵规柟 id 浣滀负 lockedIds 浼犲叆 -->
- <div
- class="im-conversation-private-side__tile-wrap-clickable flex flex-col items-center w-[66px] cursor-pointer"
- title="鍙戣捣缇よ亰"
- @click="handleOpenCreateGroup"
- >
- <div class="im-conversation-private-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200">
- <Icon icon="ant-design:plus-outlined" />
- </div>
- <div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate">娣诲姞</div>
- </div>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- 澶囨敞锛堜粎鑷繁鍙锛夛細鐐瑰嚮寮� popover 缂栬緫锛屼繚瀛樺悗绔嬪嵆鍒锋柊鏈娊灞� + 浼氳瘽鍒楄〃灞曠ず鍚� -->
- <div class="bg-[var(--ant-color-bg-container)]">
- <Popover
- v-model:open="displayNamePopoverVisible"
- trigger="click"
- placement="leftTop"
- :overlay-style="{ width: '280px' }"
- >
- <div
- class="im-conversation-private-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">澶囨敞</span>
- <span
- v-if="friend.displayName"
- class="text-13px leading-[1.6] text-[var(--ant-color-text)] break-all line-clamp-2"
- >
- {{ friend.displayName }}
- </span>
- <span v-else class="text-13px leading-[1.6] text-[var(--ant-color-text-placeholder)]">
- 濂藉弸澶囨敞浠呰嚜宸卞彲瑙�
- </span>
- </div>
- <template #content>
- <div class="flex flex-col gap-2">
- <Input
- v-model:value="editDisplayName"
- :maxlength="16"
- show-count
- placeholder="璇疯緭鍏ュ娉ㄥ悕"
- />
- <div class="flex justify-end gap-2">
- <Button size="small" @click="displayNamePopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="handleSaveDisplayName">
- 淇濆瓨
- </Button>
- </div>
- </div>
- </template>
- </Popover>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- 鏌ユ壘鑱婂ぉ鍐呭 -->
- <div class="bg-[var(--ant-color-bg-container)]">
- <div
- class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="emit('openHistory')"
- >
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">鏌ユ壘鑱婂ぉ鍐呭</span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- </div>
-
- <div class="flex-shrink-0 h-[10px]"></div>
-
- <!-- 寮�鍏抽」 -->
- <div class="bg-[var(--ant-color-bg-container)]">
- <div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">娑堟伅鍏嶆墦鎵�</span>
- <Switch :checked="!!conversation?.silent" @change="handleMutedChange" />
- </div>
- <div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150">
- <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">缃《鑱婂ぉ</span>
- <Switch :checked="!!conversation?.top" @change="handleTopChange" />
- </div>
- </div>
- </div>
- </div>
-
- <!-- 瀛愬璇濇锛氬彂璧风兢鑱婏紙閿佸畾瀵规柟涓哄凡閫夛級 -->
- <GroupCreateDialog ref="createGroupDialogRef" @created="handleGroupCreated" />
- </Drawer>
-</template>
-
-<style scoped>
-/* 銆�+銆� tile锛� hover 鏃惰仈鍔ㄥ唴閮� icon-tile 璧颁富鑹诧紱 璺ㄥ瓙鍏冪礌鐨� hover 鑱斿姩鏃犳硶鐢ㄥ崟鍏冪礌宸ュ叿绫昏〃杈� */
-.im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-side__icon-tile {
- color: var(--ant-color-primary);
- border-color: var(--ant-color-primary);
- background-color: var(--ant-color-primary-bg);
-}
-
-/* :deep 绌块�� Icon 鍐呴儴 svg锛� el-icon 鍏ㄥ眬 color 鍦ㄦ殫鑹叉ā寮忎笅琚富棰樼洊杩囷紝閿� fill 鍒板綋鍓嶈壊 */
-.im-conversation-private-side__icon-tile :deep(svg) {
- fill: currentColor !important;
-}
-
-/* 鐩搁偦淇℃伅琛屽姞鍒嗛殧绾匡紱 鐩搁偦鍏勫紵閫夋嫨鍣ㄦ棤娉曠敤宸ュ叿绫昏〃杈� */
-.im-conversation-private-side__row + .im-conversation-private-side__row {
- border-top: 1px solid var(--im-border-color-lighter);
-}
-</style>
-
-<!-- 鍚� GroupSide锛歛ntd Drawer 浼犻�佸埌 body 鍚� scoped data-v 钀戒笉鍒� body 涓婏紝闈� root-class-name 浣滅鍏堥�夋嫨鍣ㄥ啓鍏ㄥ眬瑙勫垯鍘嬫帀榛樿 padding -->
-<style>
-.im-conversation-private-side__modal .ant-drawer-body {
- padding: 0;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/conversation/index.ts b/src/views/im/home/pages/conversation/components/conversation/index.ts
deleted file mode 100644
index 97c89ac..0000000
--- a/src/views/im/home/pages/conversation/components/conversation/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as ConversationGroupSide } from './conversation-group-side.vue';
-export { default as ConversationItem } from './conversation-item.vue';
-export { default as ConversationPrivateSide } from './conversation-private-side.vue';
diff --git a/src/views/im/home/pages/conversation/components/index.ts b/src/views/im/home/pages/conversation/components/index.ts
deleted file mode 100644
index 9d4f6d4..0000000
--- a/src/views/im/home/pages/conversation/components/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './conversation';
-export * from './input';
-export * from './message';
diff --git a/src/views/im/home/pages/conversation/components/input/face-picker.vue b/src/views/im/home/pages/conversation/components/input/face-picker.vue
deleted file mode 100644
index 477e1c0..0000000
--- a/src/views/im/home/pages/conversation/components/input/face-picker.vue
+++ /dev/null
@@ -1,353 +0,0 @@
-<script lang="ts" setup>
-import type { ImFacePackApi } from '#/api/im/face/pack'
-import type { ImFaceUserItemApi } from '#/api/im/face/userItem'
-
-import { computed, onUnmounted, ref, useTemplateRef, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { message, Tooltip } from 'ant-design-vue'
-
-import { uploadFile } from '#/api/infra/file'
-import { useFaceStore } from '#/views/im/home/store/faceStore'
-import { IM_EMOJI_LIST } from '#/views/im/utils/emoji'
-import { probeImageSize } from '#/views/im/utils/image'
-
-defineOptions({ name: 'ImFacePicker' })
-
-const props = withDefaults(
- defineProps<{
- /** full锛歟moji + 涓汉琛ㄦ儏 + 绯荤粺鍖咃紙鑱婂ぉ涓昏緭鍏ョ敤锛夛紱emoji锛氫粎 emoji锛堢暀瑷� / 璇勮鍦烘櫙锛� */
- mode?: FacePickerMode
- visible: boolean
- }>(),
- { mode: 'full' }
-)
-
-const emit = defineEmits<{
- /** 閫変腑 Unicode emoji锛堝 馃榾锛夛紝璋冪敤鏂瑰簲鎻掑叆鍒拌緭鍏ユ璧� TEXT 閫氶亾 */
- selectEmoji: [emoji: string]
- /** 閫変腑琛ㄦ儏璐村浘锛岃皟鐢ㄦ柟搴旇蛋 FACE 娑堟伅鍙戦�� */
- selectFace: [face: { height: number; name?: string; url: string; width: number; }]
- 'update:visible': [value: boolean]
-}>()
-
-/** 闈㈡澘妯″紡 */
-type FacePickerMode = 'emoji' | 'full'
-
-const rootRef = useTemplateRef<HTMLDivElement>('rootRef')
-const uploadInputRef = useTemplateRef<HTMLInputElement>('uploadInputRef')
-
-const faceStore = useFaceStore()
-
-// tab 鏍囪瘑甯搁噺锛沺ack:N 绫荤敤 packTabKey() 鎷煎嚭锛岄伩鍏嶆暎钀藉瓧绗︿覆瀛楅潰閲�
-const FACE_TAB = {
- EMOJI: 'emoji',
- MINE: 'mine'
-} as const
-const packTabKey = (packId: number) => `pack:${packId}`
-
-const activeTab = ref<string>(FACE_TAB.EMOJI) // 褰撳墠婵�娲荤殑 tab
-
-/** 鏄惁瀹屾暣妯″紡锛堝惈涓汉 / 绯荤粺鍖� tab锛� */
-const isFullMode = computed(() => props.mode === 'full')
-
-const uploading = ref(false) // 涓婁紶涓爣璁帮紝閬垮厤杩炵画鐐瑰嚮瑙﹀彂骞跺彂涓婁紶
-
-/** 閫� emoji 瀛楃锛氭彃鍒拌緭鍏ユ锛涢�夊畬涓嶅叧闈㈡澘锛屾柟渚跨敤鎴疯繛鍙戝涓� */
-function handleSelectEmoji(emoji: string) {
- emit('selectEmoji', emoji)
-}
-
-/** 閫変釜浜鸿〃鎯咃細鐩存帴鍙戯紱鐐瑰畬鍏抽潰鏉匡紝瀵归綈寰俊 */
-function handleSelectFaceUserItem(item: ImFaceUserItemApi.FaceUserItem) {
- emit('selectFace', { url: item.url, width: item.width, height: item.height, name: item.name })
- emit('update:visible', false)
-}
-
-/** 閫夌郴缁熻〃鎯呭寘鍐呰〃鎯咃細鐩存帴鍙戯紱鐐瑰畬鍏抽潰鏉� */
-function handleSelectPackItem(item: ImFacePackApi.FacePackUserItem) {
- emit('selectFace', { url: item.url, width: item.width, height: item.height, name: item.name })
- emit('update:visible', false)
-}
-
-/** 闀挎寜 / 鍙抽敭鍒犻櫎涓汉琛ㄦ儏 */
-async function handleDeleteUserItem(item: ImFaceUserItemApi.FaceUserItem) {
- if (!confirm('纭鍒犻櫎璇ヨ〃鎯咃紵')) {
- return
- }
- await faceStore.removeFaceUserItem(item.id)
-}
-
-/** 鐐� + 瑙﹀彂鏂囦欢閫夋嫨 */
-function onUploadClick() {
- uploadInputRef.value?.click()
-}
-
-/** 鏂囦欢閫夊畬鍗充笂浼狅紝鎴愬姛鍚庡啓鍏� faceStore 涓汉琛ㄦ儏鍒楄〃 */
-async function onUploadPicked(e: Event) {
- const input = e.target as HTMLInputElement
- const file = input.files?.[0]
- input.value = ''
- if (!file) {
- return
- }
- uploading.value = true
- let size: { height: number; width: number; }
- try {
- size = await probeImageSize(file)
- } catch (error) {
- console.warn('[IM] 瑙f瀽涓汉琛ㄦ儏澶辫触', error)
- message.error('鍥剧墖瑙f瀽澶辫触')
- uploading.value = false
- return
- }
-
- try {
- const url = await uploadFile({ file })
- if (!url) {
- message.error('涓婁紶澶辫触')
- return
- }
- const payload = { url, width: size.width, height: size.height }
- await faceStore.addFaceUserItem(payload)
- } finally {
- uploading.value = false
- }
-}
-
-/** 闈㈡澘灞曞紑鏃舵媺鏁版嵁 + 鎸傚叏灞� click锛沵ode='emoji' 鏃朵笉鎷夌郴缁熷寘 / 涓汉琛ㄦ儏 */
-watch(
- () => props.visible,
- (visible) => {
- if (visible) {
- document.addEventListener('click', handleDocumentClick)
- if (isFullMode.value) {
- // 绯荤粺鍖呴�氬父宸茶 home onMounted 棰勬媺杩囷紱ensureXxx 鍐呴儴 promise 缂撳瓨閬垮厤閲嶅璇锋眰
- void faceStore.ensureFacePackList()
- void faceStore.ensureFaceUserItemList()
- }
- } else {
- document.removeEventListener('click', handleDocumentClick)
- }
- },
- { immediate: true }
-)
-
-/** 鐐瑰嚮闈㈡澘澶栭儴鍏抽棴 */
-function handleDocumentClick(e: MouseEvent) {
- if (!props.visible || !rootRef.value) {
- return
- }
- if (!rootRef.value.contains(e.target as Node)) {
- emit('update:visible', false)
- }
-}
-
-onUnmounted(() => {
- document.removeEventListener('click', handleDocumentClick)
-})
-</script>
-
-<template>
- <!--
- 琛ㄦ儏闈㈡澘锛堝 tab锛夛細emoji / 涓汉琛ㄦ儏 / N 涓郴缁熻〃鎯呭寘
- - 瀵归綈寰俊 PC锛氬簳閮� tab 鏍忓垏鎹㈤潰鏉垮唴瀹癸紱emoji 淇濇寔 Unicode锛堜粛鐢� TEXT 閫氶亾鍙戦�侊級
- - 涓汉琛ㄦ儏 / 绯荤粺琛ㄦ儏璧� FACE 鍐呭绫诲瀷锛岄�氳繃 selectFace 浜嬩欢鐢辫皟鐢ㄦ柟璧� sendRaw 鍙戦��
- - mode='emoji' 鏃跺彧鏄剧ず emoji tab + 闅愯棌搴曢儴 tab 鏍忥紝缁欑暀瑷� / 璇勮杩欑被鍙彂鏂囨湰鐨勫満鏅敤
- - 瀹氫綅鐢辫皟鐢ㄦ柟鍐冲畾锛堥�氬父娴湪琛ㄦ儏鎸夐挳涓婃柟锛�
- -->
- <div
- v-if="visible"
- ref="rootRef"
- class="im-popover-arrow absolute z-100 flex flex-col w-[380px] rounded-md bg-[var(--ant-color-bg-container)] shadow-[0_4px_16px_rgba(0,0,0,0.12)]"
- @click.stop
- >
- <!-- 涓诲唴瀹瑰尯锛氶珮搴﹀浐瀹� + 鍚� tab 鐢� v-show 鍒囨崲锛岄伩鍏嶆瘡娆″垏 tab 閲嶅缓 scrollbar 閫犳垚婊氬姩浣嶇疆涓㈠け -->
- <div class="relative h-[300px] overflow-hidden">
- <!-- emoji 缃戞牸 -->
- <div v-show="activeTab === FACE_TAB.EMOJI" style="height: 300px; overflow-y: auto">
- <div class="grid grid-cols-10 gap-0.5 p-2">
- <button
- v-for="emoji in IM_EMOJI_LIST"
- :key="emoji"
- class="p-1 text-xl leading-none bg-transparent border-none rounded cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- type="button"
- @click="handleSelectEmoji(emoji)"
- >
- {{ emoji }}
- </button>
- </div>
- </div>
-
- <!-- 涓汉琛ㄦ儏锛�5 鍒楁柟鏍硷紝鏃犲悕瀛楁爣绛撅紱鏈熬銆�+銆嶄笂浼� -->
- <div v-if="isFullMode" v-show="activeTab === FACE_TAB.MINE" style="height: 300px; overflow-y: auto">
- <div class="grid grid-cols-5 gap-2 p-3">
- <!-- 涓婁紶鍏ュ彛鍥哄畾鏀剧涓�鏍硷紱dashed border 涓庤〃鎯呮牸瀛愬尯鍒嗚瑙夎涔夛紝瀵归綈 el-upload 瑙傛劅 -->
- <button
- class="aspect-square flex items-center justify-center rounded-md cursor-pointer transition-colors border border-dashed border-[var(--ant-color-border)] bg-transparent text-[var(--ant-color-text-placeholder)] hover:border-[var(--ant-color-primary)] hover:text-[var(--ant-color-primary)] disabled:cursor-not-allowed disabled:text-[var(--ant-color-text-disabled)]"
- type="button"
- :disabled="uploading"
- :title="uploading ? '涓婁紶涓��' : '涓婁紶鍥剧墖鍒颁釜浜鸿〃鎯�'"
- @click="onUploadClick"
- >
- <Icon
- :icon="uploading ? 'eos-icons:bubble-loading' : 'ant-design:plus-outlined'"
- :size="22"
- />
- </button>
- <div
- v-for="item in faceStore.faceUserItems"
- :key="item.id"
- class="im-face-grid-cell group relative aspect-square flex items-center justify-center rounded-md bg-[var(--ant-color-fill-tertiary)] cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :title="item.name || '鐐瑰嚮鍙戦�� / 鍙抽敭鍒犻櫎'"
- @click="handleSelectFaceUserItem(item)"
- @contextmenu.prevent="handleDeleteUserItem(item)"
- >
- <img
- :src="item.url"
- :alt="item.name || '琛ㄦ儏'"
- class="max-w-full max-h-full object-contain"
- draggable="false"
- />
- </div>
- <div
- v-if="faceStore.faceUserItems.length === 0"
- class="col-span-5 flex items-center justify-center py-12 text-sm text-[var(--ant-color-text-placeholder)]"
- >
- 杩樻病鏈変釜浜鸿〃鎯咃紝鐐� + 涓婁紶 / 鍦ㄨ亰澶╅噷闀挎寜娑堟伅娣诲姞
- </div>
- </div>
- </div>
-
- <!-- 绯荤粺琛ㄦ儏鍖咃細5 鍒楁柟鏍� + 涓嬫柟琛ㄦ儏鍚嶏紙鏃� name 鏃朵笉鏄剧ず鏍囩锛屼繚鎸侀珮搴︿竴鑷达級 -->
- <template v-if="isFullMode">
- <div
- v-for="pack in faceStore.facePacks"
- v-show="activeTab === packTabKey(pack.id)"
- :key="pack.id"
- style="height: 300px; overflow-y: auto"
- >
- <div class="grid grid-cols-5 gap-2 p-3">
- <div
- v-for="item in pack.items"
- :key="item.id"
- class="im-face-grid-cell flex flex-col items-center gap-1 cursor-pointer rounded-md p-1 transition-colors hover:bg-[var(--ant-color-fill)]"
- :title="item.name || ''"
- @click="handleSelectPackItem(item)"
- >
- <div
- class="aspect-square w-full flex items-center justify-center bg-[var(--ant-color-fill-tertiary)] rounded"
- >
- <img
- :src="item.url"
- :alt="item.name || '琛ㄦ儏'"
- class="max-w-full max-h-full object-contain"
- draggable="false"
- />
- </div>
- <div
- class="h-4 text-xs text-[var(--ant-color-text-secondary)] truncate w-full text-center leading-4"
- >
- {{ item.name || '' }}
- </div>
- </div>
- <div
- v-if="pack.items.length === 0"
- class="col-span-5 flex items-center justify-center py-12 text-sm text-[var(--ant-color-text-placeholder)]"
- >
- 璇ヨ〃鎯呭寘鏆傛棤琛ㄦ儏
- </div>
- </div>
- </div>
- </template>
- </div>
-
- <!-- 搴曢儴 tab 鏍忥細[ emoji / 涓汉 / 绯荤粺鍖� 1..N ]锛沵ode='emoji' 鏃堕殣钘� -->
- <div
- v-if="isFullMode"
- class="flex flex-shrink-0 items-center gap-1 px-2 py-1.5 border-t border-t-solid border-[var(--ant-color-border-secondary)]"
- >
- <Tooltip title="Emoji 琛ㄦ儏" placement="top" :mouse-enter-delay="300">
- <button
- class="inline-flex items-center justify-center w-[30px] h-[30px] border-none rounded bg-transparent cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="
- activeTab === FACE_TAB.EMOJI
- ? 'bg-[var(--ant-color-fill)] text-[var(--ant-color-primary)]'
- : 'text-[var(--ant-color-text)]'
- "
- type="button"
- @click="activeTab = FACE_TAB.EMOJI"
- >
- <Icon icon="ant-design:smile-outlined" :size="18" />
- </button>
- </Tooltip>
- <Tooltip title="涓汉琛ㄦ儏" placement="top" :mouse-enter-delay="300">
- <button
- class="inline-flex items-center justify-center w-[30px] h-[30px] border-none rounded bg-transparent cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="
- activeTab === FACE_TAB.MINE
- ? 'bg-[var(--ant-color-fill)] text-[var(--ant-color-primary)]'
- : 'text-[var(--ant-color-text)]'
- "
- type="button"
- @click="activeTab = FACE_TAB.MINE"
- >
- <Icon icon="ant-design:heart-outlined" :size="18" />
- </button>
- </Tooltip>
- <Tooltip
- v-for="pack in faceStore.facePacks"
- :key="pack.id"
- :title="pack.name"
- placement="top"
- :mouse-enter-delay="300"
- >
- <button
- class="inline-flex items-center justify-center w-[30px] h-[30px] border-none rounded bg-transparent cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="
- activeTab === packTabKey(pack.id)
- ? 'bg-[var(--ant-color-fill)] text-[var(--ant-color-primary)]'
- : 'text-[var(--ant-color-text)]'
- "
- type="button"
- @click="activeTab = packTabKey(pack.id)"
- >
- <img
- v-if="pack.icon"
- :src="pack.icon"
- :alt="pack.name"
- class="w-[18px] h-[18px] object-contain"
- draggable="false"
- />
- <Icon v-else icon="ant-design:appstore-outlined" :size="18" />
- </button>
- </Tooltip>
- </div>
-
- <!-- 闅愯棌鐨勬枃浠堕�夋嫨鍣紙鐐� + 鏃惰Е鍙戯級 -->
- <input
- v-if="isFullMode"
- ref="uploadInputRef"
- type="file"
- accept="image/*"
- hidden
- @change="onUploadPicked"
- />
- </div>
-</template>
-
-<style scoped>
-/* 搴曢儴灏忎笁瑙掞細鎸囧悜瑙﹀彂鍥炬爣锛屼豢寰俊 PC 姘旀场鎸囬拡锛沴eft 鍋忕Щ瀵瑰簲琛ㄦ儏鎸夐挳锛堝伐鍏锋爮 1st icon锛� */
-.im-popover-arrow::after {
- content: '';
- position: absolute;
- top: calc(100% - 1px);
- left: 10px;
- border-style: solid;
- border-width: 6px 6px 0 6px;
- border-color: var(--ant-color-bg-container) transparent transparent transparent;
- filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.08));
-}
-
-</style>
diff --git a/src/views/im/home/pages/conversation/components/input/index.ts b/src/views/im/home/pages/conversation/components/input/index.ts
deleted file mode 100644
index f48ba37..0000000
--- a/src/views/im/home/pages/conversation/components/input/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { default as FacePicker } from './face-picker.vue';
-export { default as MentionPicker } from './mention-picker.vue';
-export { default as MessageInput } from './message-input.vue';
-export { default as MessageMultiSelectBar } from './message-multi-select-bar.vue';
-export { default as VoiceRecorder } from './voice-recorder.vue';
diff --git a/src/views/im/home/pages/conversation/components/input/mention-picker.vue b/src/views/im/home/pages/conversation/components/input/mention-picker.vue
deleted file mode 100644
index 0ebfe60..0000000
--- a/src/views/im/home/pages/conversation/components/input/mention-picker.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref, useTemplateRef, watch } from 'vue'
-
-import { CommonStatusEnum } from '../../../../../../../packages/constants/src'
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '#/views/im/utils/constants'
-
-import { GroupMember, type GroupMemberLite } from '../../../../components/group'
-
-defineOptions({ name: 'ImMentionPicker' })
-
-const props = withDefaults(
- defineProps<{
- canAtAll?: boolean // 褰撳墠鐢ㄦ埛鏄惁鑳� @ 鍏ㄥ憳锛堢兢涓� / 绠$悊鍛橈級锛岀埗缁勪欢鎸夎鑹茬畻濂戒紶鍏�
- members: GroupMemberLite[] // 褰撳墠缇ょ殑鎴愬憳鍒楄〃
- // 娴眰浣嶇疆锛歺 妯潗鏍� + top / bottom 浜岄�変竴锛坆ottom 閿氬畾鏃� picker 涓嬫部璐� @ 涓婃柟锛�
- position?: { bottom?: number; top?: number; x: number; }
- searchText?: string // @ 鍚庤緭鍏ョ殑杩囨护鏂囨湰
- visible: boolean // 鏄惁鏄剧ず
- }>(),
- {
- searchText: '',
- position: () => ({ x: 0, bottom: 0 })
- }
-)
-
-const emit = defineEmits<{
- select: [member: GroupMemberLite]
- 'update:visible': [value: boolean]
-}>()
-
-const scrollRef = useTemplateRef<HTMLDivElement>('scrollRef')
-const activeIdx = ref(0)
-
-/** 褰撳墠鐧诲綍鐢ㄦ埛 id锛堟垚鍛樺垪琛ㄨ繃婊ゆ帀鑷繁锛� */
-const selfUserId = computed(() => getCurrentUserId())
-
-/**
- * 铏氭嫙"鎵�鏈変汉"椤癸細缇や富 / 绠$悊鍛橈紙canAtAll=true锛�+ 鍏抽敭瀛楀懡涓�"鎵�鏈変汉"鍓嶇紑鏃跺瓨鍦�
- *
- * MessageInput 璧� token data-id 鏀堕泦 atUserIds锛屼笉渚濊禆鏂囨瀛楃涓诧紱
- * 杩欓噷鐨� userId / 鏂囨閮戒粠 im/utils/constants 鍙栵紝閬垮厤鏁h惤
- */
-const allItem = computed<GroupMemberLite | null>(() => {
- if (!props.canAtAll) {
- return null
- }
- if (!IM_AT_ALL_NICKNAME.startsWith(props.searchText)) {
- return null
- }
- // @鎵�鏈変汉 鏄釜浼垚鍛橈紝nickname 缁� IM_AT_ALL_NICKNAME 璁╁ご鍍� :name 琛屼负瀵归綈鏅�氭垚鍛�
- return {
- userId: IM_AT_ALL_USER_ID,
- showName: IM_AT_ALL_NICKNAME,
- nickname: IM_AT_ALL_NICKNAME
- }
-})
-
-/** 鐪熸垚鍛橈細杩囨护鑷繁 / 閫�缇� / 涓嶅尮閰嶅叧閿瓧锛涗笉鎴柇鏁伴噺锛屾诞灞� max-height + el-scrollbar 鎾戞粴鍔� */
-const memberItems = computed<GroupMemberLite[]>(() =>
- props.members.filter(
- (member) =>
- member.userId !== selfUserId.value &&
- member.status !== CommonStatusEnum.DISABLE &&
- !!member.showName &&
- member.showName.startsWith(props.searchText)
- )
-)
-
-/** 閿洏瀵艰埅涓� pickActive 璧扮殑鎵佸钩鍒楄〃锛宎llItem 鍦ㄥ墠銆乵emberItems 鍦ㄥ悗 */
-const showMembers = computed<GroupMemberLite[]>(() => {
- return allItem.value ? [allItem.value, ...memberItems.value] : memberItems.value
-})
-
-/** 鍊欓�夊垪琛ㄥ彉鍖栵紙鐢ㄦ埛杈撳叆鍏抽敭璇嶅湪杩囨护锛夆啋 閲嶇疆楂樹寒鍒伴椤� + 婊氬洖椤� */
-watch(showMembers, (list) => {
- activeIdx.value = list.length > 0 ? 0 : -1
- scrollToTop()
-})
-
-/** 娴眰閲嶆柊鎵撳紑 鈫� 閲嶇疆楂樹寒 + 婊氬洖椤讹紙閬垮厤涓婃鐨勪腑闂寸姸鎬佹畫鐣欙級 */
-watch(
- () => props.visible,
- (v) => {
- if (v) {
- activeIdx.value = showMembers.value.length > 0 ? 0 : -1
- scrollToTop()
- }
- }
-)
-
-/** el-scrollbar 娌℃毚闇� scrollTo锛岀洿鎺ユ嬁鍐呴儴 wrap 璋� scrollTop */
-function scrollToTop() {
- const scrollWrap = scrollRef.value
- if (scrollWrap) {
- scrollWrap.scrollTop = 0
- }
-}
-
-/** 閿洏涓婁笅瀵艰埅鏃舵妸楂樹寒椤规粴鍒板彲瑙嗗尯锛氳秴鍑哄簳杈逛笅鎺ㄣ�佽秴鍑洪《杈逛笂鎷夛紝鍚﹀垯涓嶅姩 */
-function scrollToActive() {
- const scrollWrap = scrollRef.value
- if (!scrollWrap) {
- return
- }
- const itemHeight = 40
- const activeOffsetTop = activeIdx.value * itemHeight
- if (activeOffsetTop + itemHeight > scrollWrap.scrollTop + scrollWrap.clientHeight) {
- scrollWrap.scrollTop = activeOffsetTop + itemHeight - scrollWrap.clientHeight
- } else if (activeOffsetTop < scrollWrap.scrollTop) {
- scrollWrap.scrollTop = activeOffsetTop
- }
-}
-
-/** 閫変腑涓�椤癸細emit 缁� MessageInput 钀� token锛屽悓鏃跺叧鎺夋诞灞� */
-function handleSelect(member: GroupMemberLite) {
- emit('select', member)
- emit('update:visible', false)
-}
-
-// 鏆撮湶缁欑埗缁勪欢鐨勯敭鐩樺鑸柟娉�
-defineExpose({
- moveUp() {
- if (activeIdx.value > 0) {
- activeIdx.value--
- scrollToActive()
- }
- },
- moveDown() {
- if (activeIdx.value < showMembers.value.length - 1) {
- activeIdx.value++
- scrollToActive()
- }
- },
- pickActive() {
- const member = showMembers.value[activeIdx.value]
- if (activeIdx.value >= 0 && member) {
- handleSelect(member)
- }
- },
- hasCandidates: () => showMembers.value.length > 0
-})
-</script>
-
-<template>
- <!--
- @ 鎴愬憳閫夋嫨娴眰锛堝榻愬井淇� PC锛氭墍鏈変汉鍦ㄩ《 + 缇ゆ垚鍛樺垎缁� + 搴曢儴涓夎鎸囬拡锛�
- - 鐖剁粍浠堕�氳繃 v-model:visible 鎺у埗鏄鹃殣锛宻earchText 杩囨护
- - 鐖剁粍浠堕�氳繃 ref 璋� moveUp / moveDown / pickActive 瀹炵幇閿洏涓婁笅 + Enter 閫変腑
- - @select 鍙戝皠琚�変腑鐨勬垚鍛�
- -->
- <div
- v-show="visible && showMembers.length > 0"
- class="message-input__mention-picker !fixed z-100 w-50 rounded-md bg-[var(--ant-color-bg-container)] shadow-[0_4px_16px_rgba(0,0,0,0.12)]"
- :style="{
- left: `${position.x }px`,
- top: position.top != null ? `${position.top }px` : 'auto',
- bottom: position.bottom != null ? `${position.bottom }px` : 'auto'
- }"
- >
- <div ref="scrollRef" style="max-height: 300px; overflow-y: auto">
- <!-- 鎵�鏈変汉锛氳櫄鎷熼」锛屼粎缇や富鍙�夛紱钃濊壊鏂瑰潡 + 缇や綋鍥炬爣锛岃窡涓嬮潰鐨勬垚鍛樺ご鍍忓尯鍒� -->
- <div
- v-if="allItem"
- class="flex items-center gap-2.5 px-[5px] h-10 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
- :class="{ 'bg-[#e1eaf7] dark:bg-[var(--ant-color-primary-bg)]': activeIdx === 0 }"
- @click.stop="handleSelect(allItem)"
- >
- <div
- class="flex items-center justify-center w-[30px] h-[30px] rounded text-white bg-[var(--ant-color-primary)] flex-shrink-0"
- >
- <Icon icon="ep:user-filled" :size="18" />
- </div>
- <span class="overflow-hidden text-sm truncate text-[var(--ant-color-text)]">
- {{ allItem.showName }}
- </span>
- </div>
-
- <!-- 缇ゆ垚鍛� section header锛氬彧鏈�"鎵�鏈変汉 + 鐪熸垚鍛�"涓よ竟閮藉湪鏃舵墠鍑虹幇锛岄伩鍏嶅崟涓�鏉ユ簮鏃舵樉寰楀浣� -->
- <div
- v-if="allItem && memberItems.length > 0"
- class="px-2 pt-2 pb-1 text-12px text-[var(--ant-color-text-secondary)]"
- >
- 缇ゆ垚鍛�
- </div>
-
- <!-- 鐪熸垚鍛樿 -->
- <GroupMember
- v-for="(member, idx) in memberItems"
- :key="member.userId"
- :member="member"
- :height="40"
- :active="activeIdx === (allItem ? idx + 1 : idx)"
- :clickable="false"
- @click.stop="handleSelect(member)"
- />
- </div>
-
- <!-- 涓夎鎸囬拡锛歱icker 鍦� @ 涓嬫柟锛坧osition.top 閿氬畾锛夆啋 绠ご鏈濅笂璐撮《锛沺icker 鍦� @ 涓婃柟锛坧osition.bottom 閿氬畾锛夆啋 绠ご鏈濅笅璐村簳 -->
- <div
- class="absolute left-4 w-3 h-3 rotate-45 bg-[var(--ant-color-bg-container)]"
- :class="position.top != null ? '-top-1.5' : '-bottom-1.5'"
- ></div>
- </div>
-</template>
diff --git a/src/views/im/home/pages/conversation/components/input/message-input.vue b/src/views/im/home/pages/conversation/components/input/message-input.vue
deleted file mode 100644
index e3901a3..0000000
--- a/src/views/im/home/pages/conversation/components/input/message-input.vue
+++ /dev/null
@@ -1,1216 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../../../../components/group'
-
-import type { Conversation } from '#/views/im/home/types'
-
-import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { isOpenableUrl } from '../../../../../../../packages/utils/src'
-
-import { Button, DropdownButton, Menu, message, Tooltip } from 'ant-design-vue'
-
-import { uploadFile } from '#/api/infra/file'
-import {
- ensureMediaSizeWithinLimit,
- useMediaUploader
-} from '#/views/im/home/composables/useMediaUploader'
-import { useMessageSender } from '#/views/im/home/composables/useMessageSender'
-import { useMuteOverlay } from '#/views/im/home/composables/useMuteOverlay'
-import { useConversationStore } from '#/views/im/home/store/conversationStore'
-import { useFriendStore } from '#/views/im/home/store/friendStore'
-import { useGroupStore } from '#/views/im/home/store/groupStore'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { DANGEROUS_FILE_EXTENSIONS, MESSAGE_GROUP_READ_ENABLED } from '#/views/im/utils/config'
-import { ImContentType, ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants'
-import { getConversationKey } from '#/views/im/utils/conversation'
-import {
- type FaceMessage,
- type QuoteMessage,
- serializeMessage,
- withQuotePayload
-} from '#/views/im/utils/message'
-import { getMemberDisplayName } from '#/views/im/utils/user'
-
-import { ReplyPreview } from '../message'
-import FacePicker from './face-picker.vue'
-import MentionPicker from './mention-picker.vue'
-import VoiceRecorder from './voice-recorder.vue'
-
-defineOptions({ name: 'ImMessageInput' })
-
-const conversationStore = useConversationStore()
-const groupStore = useGroupStore()
-const friendStore = useFriendStore()
-const { send, sendRaw } = useMessageSender()
-const {
- uploadAndSendMedia,
- insertMediaPlaceholder,
- markMediaFailed,
- commitMediaPlaceholder,
- createUploadProgressHandler,
- verifyMediaUploadStillAllowed,
- requireMediaHandler
-} = useMediaUploader()
-const muteOverlay = useMuteOverlay() // 绂佽█ / 灏佺瑕嗙洊灞�
-
-const editorRef = useTemplateRef<HTMLDivElement>('editorRef')
-const imageInputRef = useTemplateRef<HTMLInputElement>('imageInputRef')
-const fileInputRef = useTemplateRef<HTMLInputElement>('fileInputRef')
-const videoInputRef = useTemplateRef<HTMLInputElement>('videoInputRef')
-const mentionRef = useTemplateRef<InstanceType<typeof MentionPicker>>('mentionRef')
-
-// ==================== 鏂囨湰 / 鍙戦�� ====================
-const canSend = ref(false) // editor 鏄惁鏈夊彲鍙戦�佸唴瀹癸紱contenteditable 娌� v-model锛岄潬 input 浜嬩欢涓诲姩鍚屾
-
-/** 缁存姢 canSend + data-empty锛堟拺璧� placeholder锛夛紱涓嶅啓鑽夌锛宺estoreDraftToEditor 澶嶇敤閬垮厤鍥炴祦 */
-function applyEditorUiState(editor: HTMLDivElement) {
- const raw = editor.textContent || ''
- // canSend 鎸� trim 鍚庡垽鏂紙绌烘牸 / 鎹㈣涓嶇畻鍙彂閫佸唴瀹癸級锛涚瑷�鎬佺洿鎺ョ鐢�
- canSend.value = !!raw.trim() && !!conversationStore.activeConversation && !muteOverlay.value
- // data-empty 鎸夊師濮嬪唴瀹瑰垽鏂細鐢ㄦ埛鏁蹭竴涓┖鏍间篃瑕佽 placeholder 闅愯棌锛岄伩鍏嶈瑙夊彔鍔�
- // 鐢ㄥ睘鎬�"瀛樺湪 / 缂哄け"鑰岄潪 'true'/'false' 瀛楃涓诧細CSS [data-empty]::before 鍛戒腑鍗冲彲锛�
- // 姣� [data-empty='true'] 鐩磋锛涙祻瑙堝櫒鍒犵┖鍚庣暀 <br> 鈫� :empty 涓嶅懡涓紝鎵�浠ュ繀椤� JS 缁存姢
- if (raw) {
- delete editor.dataset.empty
- } else {
- editor.dataset.empty = ''
- }
-}
-
-/** 鐢ㄦ埛缂栬緫鍏ュ彛鐨勭粺涓�鏀跺熬锛歎I 鐘舵�佸悓姝� + 鑽夌鍐欏洖 store锛堝垪琛ㄧ珛鍗冲嚭 [鑽夌] 鍓嶇紑锛� */
-function syncEditorState() {
- const editor = editorRef.value
- if (!editor) {
- return
- }
- applyEditorUiState(editor)
- syncDraftToStore(editor)
-}
-
-/** 绂佽█鐘舵�佸彉鍖栨椂鍚屾鍙戦�佹寜閽� */
-watch(muteOverlay, () => {
- const editor = editorRef.value
- if (editor) {
- applyEditorUiState(editor)
- }
-})
-
-/** 鎶� editor 褰撳墠鍐呭鍐欏埌浼氳瘽鑽夌锛沺lain 鐢� collectFromEditor 鎷匡紝涓庡彂閫佹椂鍚屾簮閬垮厤鍒楄〃涓庡疄鍙戜笉涓�鑷� */
-function syncDraftToStore(editor: HTMLDivElement) {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- // collectFromEditor 宸� trim锛宲lain 涓虹┖鏃� store 鍐呴儴鎸� clearConversationDraft 澶勭悊
- // reply 閫忎紶褰撳墠蹇収锛歴etConversationDraft 鏄暣瀵硅薄鏇挎崲锛屼笉璇绘棫 reply 浼氳鐢ㄦ埛姣忔暡涓�涓敭灏辨妸寮曠敤鏉℃摝鎺�
- const { text } = collectFromEditor(editor)
- const existing = conversationStore.getConversationDraft(conversation)
- conversationStore.setConversationDraft(conversation, {
- html: editor.innerHTML,
- plain: text,
- reply: existing?.reply
- })
-}
-
-/** 鍒囦細璇濇椂鎶� store 閲岀殑鑽夌杩樺師鍒� editor锛涘彧鏇� UI 涓嶅洖鍐欒崏绋匡紝閬垮厤 store鈫抏ditor鈫抯tore 鍥炴祦 */
-function restoreDraftToEditor() {
- const editor = editorRef.value
- if (!editor) {
- return
- }
- const conversation = conversationStore.activeConversation
- const draft = conversation ? conversationStore.getConversationDraft(conversation) : undefined
- editor.innerHTML = draft?.html || ''
- applyEditorUiState(editor)
- // 鎶婂厜鏍囩Щ鍒版湯灏撅紝璁╃敤鎴锋帴鐫�杈撳叆锛涚┖鍐呭鐩存帴 focus 鍗冲彲
- if (draft?.html) {
- placeCaretAtEnd(editor)
- }
-}
-
-/** 鎶婂厜鏍囨斁鍒� contenteditable 鍏冪礌鐨勬湯灏锯�斺�斿垏鍥炴湁鑽夌鐨勪細璇濇椂鍏夋爣鑷劧钀藉湪灏鹃儴锛屽榻愬井淇� */
-function placeCaretAtEnd(el: HTMLElement) {
- const range = document.createRange()
- range.selectNodeContents(el)
- range.collapse(false)
- const sel = window.getSelection()
- if (!sel) {
- return
- }
- sel.removeAllRanges()
- sel.addRange(range)
-}
-
-/**
- * 璧� DOM 鎶� editor 鍐呭鎷煎洖 text + atUserIds
- *
- * 鑺傜偣鍒嗘敮锛�
- * 1. text 鑺傜偣锛氱洿鎺ユ嫾 textContent锛涜繃绋嬩腑婊ゆ帀 ZWSP锛坱oken 棣栦綅閿氱偣鐢紝涓嶈繘鍙戦�佸唴瀹癸級
- * 2. br锛氭嫾 \n锛圫hift+Enter 璧� execCommand('insertLineBreak') 浜х墿锛�
- * 3. span[data-id]锛氭嫾 token 鏄剧ず鏂囨湰 + 鎶� dataset.id 鏀跺埌 atUserIds锛堜笉閫掑綊 span 鍐呴儴锛�
- * 4. div锛氭祻瑙堝櫒鍦� contenteditable 閲岄粯璁ゆ崲琛屽鍣紝鍓嶇疆 \n 鍚庨�掑綊瀛愯妭鐐�
- * 5. 鍏朵粬鍏冪礌锛氶�忎紶锛岄�掑綊瀛愯妭鐐�
- *
- * atUserIds 璧� Set 鍘婚噸锛氱敤鎴� @ 寮犱笁涓ゆ鏃� atUserIds 鍙嚭鐜颁竴娆★紱trim 鏈熬绌虹櫧
- */
-function collectFromEditor(root: HTMLElement): { atUserIds: number[]; text: string; } {
- const userIds: number[] = []
- let text = ''
-
- function walk(node: Node) {
- if (node.nodeType === Node.TEXT_NODE) {
- text += (node.textContent || '').replaceAll('鈥�', '')
- return
- }
- if (node.nodeType !== Node.ELEMENT_NODE) {
- return
- }
- const el = node as HTMLElement
- const tag = el.tagName.toLowerCase()
- if (tag === 'br') {
- text += '\n'
- return
- }
- if (tag === 'span' && el.dataset.id) {
- text += el.textContent || ''
- const id = Number(el.dataset.id)
- if (!Number.isNaN(id)) {
- userIds.push(id)
- }
- return
- }
- if (tag === 'div') {
- if (text && !text.endsWith('\n')) {
- text += '\n'
- }
- el.childNodes.forEach((child) => walk(child))
- return
- }
- el.childNodes.forEach((child) => walk(child))
- }
-
- // 鐩存帴浠� root.childNodes 寮�濮嬶紝閬垮厤鎶� root 鏈韩涔熷綋鍏冪礌澶勭悊锛堣櫧鐒剁洰鍓嶆病鏈夌壒娈婃牱寮忥紝浣嗕互闃叉湭鏉ユ敼鍔級
- root.childNodes.forEach((child) => walk(child))
- return {
- text: text.trim(),
- atUserIds: [...new Set(userIds)]
- }
-}
-
-/**
- * 鍙戦�侊細浠� DOM 鏀� text + atUserIds 鈫� 娓呯┖缂栬緫鍣� 鈫� 璧� useMessageSender.send
- *
- * 1. 闃插尽锛歝anSend 鏄� false锛坱rim 鍚庣┖ / 娌℃縺娲讳細璇濓級鎴� editor 娌� mount 鈫� 鐩存帴 return
- * 2. 鏀堕泦锛欴OM walk 鎷垮埌瑕佸彂閫佺殑鏂囨湰 + atUserIds
- * 3. 浜屾闃插尽锛歝ollectFromEditor 璧� trim锛屽彲鑳芥瘮 syncEditorState 鏇翠弗鏍硷紙渚嬪鍏� ZWSP锛夛紝浠嶇┖灏� return
- * 4. 娓呯┖ + 鍚屾鐘舵�侊細鍏堟竻 innerHTML 鍐� syncEditorState 璁� placeholder / canSend 涓�璧峰洖褰�
- * 锛堥『搴忓緢閲嶈锛氬厛娓呭悗 sync锛屽惁鍒� sync 鐪嬪埌鏃у唴瀹逛細璇垽锛�
- * 5. 涓婇�侊細atUserIds 闈炵┖鎵嶄紶锛岄伩鍏嶅彂绌烘暟缁勶紱quote 鐢� clearConversationDraft 鍓嶆姄鍙栵紝纭繚寮曠敤鏉$珛鍗虫秷澶�
- */
-async function handleSend(options?: { receipt?: boolean }) {
- const editor = editorRef.value
- if (!canSend.value || !editor || muteOverlay.value) {
- return
- }
- const { text, atUserIds } = collectFromEditor(editor)
- if (!text) {
- return
- }
- // 1. 鎶� quote 鍚庢竻绌� editor + 褰撳墠浼氳瘽鑽夌锛堝寘鍚� reply锛夛紱syncEditorState 鍚� / reply 閮戒负绌�
- // store 鍐呴儴浼氳嚜鍔ㄦ竻鐞嗭紝浣嗘樉寮� clearConversationDraft 鑳界珛鍗冲悓姝ャ�佷笉渚濊禆 debounce 鏃跺簭锛屽垪琛ㄤ笂鐨� [鑽夌] 绔嬪嵆娑堝け
- const replyQuote = replyTarget.value
- editor.innerHTML = ''
- if (conversationStore.activeConversation) {
- conversationStore.clearConversationDraft(conversationStore.activeConversation)
- }
- syncEditorState()
- // 2. 鍙戦��
- await send(text, {
- atUserIds: atUserIds.length > 0 ? atUserIds : undefined,
- receipt: options?.receipt,
- quote: replyQuote
- })
-}
-
-/** 鍙戦�佹寜閽笅鎷夎彍鍗曟搷浣� */
-function handleSendMenuClick({ key }: { key: number | string }) {
- if (key === 'receipt') {
- handleSend({ receipt: true })
- }
-}
-
-// ==================== 閫夊尯 / 鎻掑叆 ====================
-/**
- * 涓婃钀藉湪 editor 鍐呯殑 selection锛堢劍鐐硅琛ㄦ儏闈㈡澘绛夊ず璧版椂鐢ㄦ潵鍥炲埌鍘熸彃鍏ョ偣锛�
- *
- * 鐩戝惉 document.selectionchange 姣� editor.@blur 鏇村彲闈狅細blur 鏃� selection 宸茬粡绉昏蛋
- */
-let savedRange: null | Range = null
-
-/**
- * 璧� native execCommand锛屼繚鐣欐祻瑙堝櫒鍘熺敓 undo 鏍�
- *
- * execCommand 鍦� lib.dom 閲岃鏍� @deprecated锛圛DE 鏄剧ず鍒犻櫎绾匡級锛屼絾 'insertText' /
- * 'insertLineBreak' 娌℃湁绛変环鐨� W3C 鏍囧噯鏇夸唬鈥斺�擱ange/Selection 鑷繁鎷� DOM 浼氳 Ctrl+Z 澶辨晥銆�
- * 闆嗕腑鍒拌繖閲岃皟鐢紝璋冪敤鐐逛笉蹇呮暎钀� ts-expect-error锛涗笉寮曞叆 eslint disable锛堥」鐩綋鍓�
- * @typescript-eslint v7 娌℃湁 no-deprecated 瑙勫垯锛屽姞浜嗕篃鏃犳晥锛屽弽鑰岃 lint 鎶�"瑙勫垯涓嶅瓨鍦�"锛�
- */
-function nativeExec(command: 'insertLineBreak' | 'insertText', value?: string) {
- document.execCommand(command, false, value)
-}
-
-/**
- * document selectionchange 鐩戝惉锛氭妸钀藉湪 editor 鍐呯殑 selection 缂撳瓨鍒� savedRange锛�
- * insertText 鍦ㄧ劍鐐硅鍋疯蛋鍚庣敤瀹冩妸鍏夋爣鎭㈠鍒板師鎻掑叆鐐�
- */
-function onSelectionChange() {
- const editor = editorRef.value
- const sel = window.getSelection()
- if (!editor || !sel || sel.rangeCount === 0) {
- return
- }
- const range = sel.getRangeAt(0)
- if (editor.contains(range.startContainer)) {
- savedRange = range.cloneRange()
- }
-}
-
-/**
- * 鐐瑰嚮 editor / picker 澶栭儴鏃跺叧鎺夋诞灞傦紝閬垮厤杈撳叆 @keyword 鍚庣敤鎴风偣鍒娴眰涓嶆秷澶�
- *
- * 鐢� mousedown 鑰岄潪 click锛歝lick 鍦ㄦ煇浜涙祻瑙堝櫒閲� picker 鍏冪礌娑堝け鍚庡洖涓嶅埌鍘� target锛屼細琚悶鎺�
- */
-function onDocMousedown(e: MouseEvent) {
- if (!mentionVisible.value) {
- return
- }
- const target = e.target as Node | null
- if (!target) {
- return
- }
- if (editorRef.value?.contains(target)) {
- return
- }
- // picker 鏄� fixed 瀹氫綅鐨勫厔寮熻妭鐐癸紝涓嶅湪 editor 瀛愭爲閲岋紱鐢ㄧ被鍚嶅畾浣�
- const pickerEl = document.querySelector('.message-input__mention-picker')
- if (pickerEl?.contains(target)) {
- return
- }
- closeMention()
-}
-
-onMounted(() => {
- document.addEventListener('selectionchange', onSelectionChange)
- document.addEventListener('mousedown', onDocMousedown)
- restoreDraftToEditor()
-})
-
-onBeforeUnmount(() => {
- document.removeEventListener('selectionchange', onSelectionChange)
- document.removeEventListener('mousedown', onDocMousedown)
-})
-
-/**
- * 鍒囦細璇濇椂杩樺師瀵规柟鐨勮崏绋垮埌 editor
- *
- * 鍚屾鍏� @ / 琛ㄦ儏 / 璇煶娴眰骞舵竻 savedRange锛�
- * - mentionRange / savedRange 鏃у紩鐢ㄨ繕鎸囧悜涓婁竴浼氳瘽鐨� DOM 鑺傜偣锛屼笉娓呬笅娆℃彃 token 浼氳惤閿欎綅缃�
- * - 璇煶褰曞埗寮圭獥淇濈暀鏃讹紝褰曞畬瑙﹀彂鐨� onVoiceSend 浼氳褰撳墠 activeConversation锛屾妸璇煶鍙戝埌鏂颁細璇�
- */
-watch(
- () =>
- conversationStore.activeConversation
- ? getConversationKey(conversationStore.activeConversation)
- : null,
- () => {
- closeMention()
- emojiVisible.value = false
- voiceVisible.value = false
- savedRange = null
- restoreDraftToEditor()
- }
-)
-
-/**
- * 鎶婂瓧绗︿覆鎻掑叆鍏夋爣澶勶紙emoji 闈㈡澘绛夊満鏅皟鐢級
- *
- * 1. editor 娌℃寕鐩存帴杩斿洖
- * 2. 鐒︾偣鍥炲埌 editor + 鎶� savedRange 鎭㈠鎴愬綋鍓� selection锛坋moji 闈㈡澘鍋风劍鐐瑰悗杩樿兘鍥炲師浣嶏級
- * 3. nativeExec 鎻掓枃鏈紝淇濈暀娴忚鍣ㄥ師鐢� undo 鏍�
- * 4. 鍚屾 canSend / placeholder
- */
-function insertText(str: string) {
- const editor = editorRef.value
- if (!editor) {
- return
- }
- editor.focus()
- if (savedRange) {
- const sel = window.getSelection()
- if (sel) {
- sel.removeAllRanges()
- sel.addRange(savedRange)
- }
- }
- // 1. nativeExec 鎻掓枃鏈紝淇濈暀娴忚鍣ㄥ師鐢� undo 鏍�
- nativeExec('insertText', str)
- // 2. 鍚屾 canSend / placeholder
- syncEditorState()
-}
-
-/**
- * 绮樿创澶勭悊
- *
- * 1. 浼樺厛鎵� clipboardData.items 鎵炬枃浠剁被鍨嬫潯鐩紙鎴浘銆佹嫋鍏ョ殑鍥剧墖 / 鏂囦欢绛夛級
- * - image/* 鈫� 璧� IMAGE 涓婁紶鍙戦��
- * - 鍏跺畠 file 鈫� 璧� FILE 涓婁紶鍙戦��
- * - 涓�娆$矘璐村彧澶勭悊绗竴涓枃浠讹紝閬垮厤涓�娆$矘璐村彂鍑哄鏉℃秷鎭�
- * 2. 娌℃枃浠跺啀璧� text锛氬墺鎺夊閮ㄦ牱寮� / 鑴氭湰锛岄伩鍏嶅绔� inline style 姹℃煋 editor
- * 锛坈ontenteditable 榛樿绮樿创浼氬甫 HTML锛屾墍浠ユā鏉夸笂 @paste.prevent 鎷︽埅锛�
- */
-function onPaste(e: ClipboardEvent) {
- // 1. 浼樺厛鎵� clipboardData.items 鎵炬枃浠剁被鍨嬫潯鐩紙鎴浘銆佹嫋鍏ョ殑鍥剧墖 / 鏂囦欢绛夛級
- const items = e.clipboardData?.items
- if (items?.length) {
- for (const item of items) {
- if (!item) {
- continue
- }
- if (item.kind !== 'file') {
- continue
- }
- const file = item.getAsFile()
- if (!file) {
- continue
- }
- if (item.type.startsWith('image/')) {
- void uploadAndSendImage(file)
- } else {
- void uploadAndSendFile(file)
- }
- return
- }
- }
- // 2. 娌℃枃浠跺啀璧� text锛氬墺鎺夊閮ㄦ牱寮� / 鑴氭湰锛岄伩鍏嶅绔� inline style 姹℃煋 editor
- const text = e.clipboardData?.getData('text/plain') || ''
- if (text) {
- nativeExec('insertText', text)
- // @paste.prevent 闃绘柇浜嗘祻瑙堝櫒榛樿 input 浜嬩欢锛岄渶鎵嬪姩鍚屾鑽夌 / canSend锛屼笌 insertText() 璺緞涓�鑷�
- syncEditorState()
- }
-}
-
-/** 缂栬緫鍣ㄥ唴瀹瑰彉鍖栫殑缁熶竴鍏ュ彛锛氬厛鍚屾 canSend / placeholder锛屽啀鍒� @ 娴眰鏄惁瑕佸睍寮� */
-function onInput() {
- syncEditorState()
- detectAtMention()
-}
-
-// ==================== 寮曠敤 / 鍥炲 ====================
-
-/** 褰撳墠浼氳瘽鐨勩�屾鍦ㄥ洖澶嶃�嶅璞★紝浠庝細璇濊崏绋挎淳鐢� */
-const replyTarget = computed<QuoteMessage | undefined>(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return undefined
- }
- return conversationStore.getConversationDraft(conversation)?.reply
-})
-
-/** 娓呮帀褰撳墠 reply 浣嗕繚鐣欐鏂囪崏绋匡細鐐� 脳 鍏抽棴 / 鍙戦�佸嵆灏嗚繘琛屾椂璋� */
-function clearConversationReplyDraft() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- conversationStore.clearConversationReplyDraft(conversation)
-}
-
-/** 鍙栬蛋褰撳墠 reply 蹇収锛堟姄涓�娆℃竻涓�娆★級锛屽獟浣撲笂浼犻摼璺湪鍔ㄦ墜鍓嶇粺涓�璋冨畠鎷� quote */
-function consumeReply(): QuoteMessage | undefined {
- const quote = replyTarget.value
- if (quote) {
- clearConversationReplyDraft()
- }
- return quote
-}
-
-// ==================== 琛ㄦ儏 ====================
-const emojiVisible = ref(false)
-/** 鍒囨崲琛ㄦ儏闈㈡澘锛涙墦寮�鏃朵簰鏂ュ叧鎺夎闊抽潰鏉� */
-function toggleEmoji() {
- emojiVisible.value = !emojiVisible.value
- if (emojiVisible.value) {
- voiceVisible.value = false
- }
-}
-
-/** 閫変腑琛ㄦ儏璐村浘锛氭嫾 FaceMessage payload 鐩存帴璧� sendRaw 鍙戦�侊紙quote 澶嶇敤褰撳墠 reply 蹇収锛� */
-async function onSelectFace(face: { height: number; name?: string; url: string; width: number; }) {
- if (muteOverlay.value) {
- return
- }
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- const replyQuote = consumeReply()
- const payload = withQuotePayload<FaceMessage>(
- { url: face.url, width: face.width, height: face.height, name: face.name },
- replyQuote
- )
- await sendRaw(ImContentType.FACE, serializeMessage(payload), { conversation })
-}
-
-// ==================== @ 鎴愬憳閫夋嫨锛堢兢鑱婏級 ====================
-const isGroup = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.GROUP
-)
-
-/** 浠� groupStore 璇诲綋鍓嶆縺娲荤兢鐨勬垚鍛橈紙鍒囦細璇濇椂鐢� MessagePanel 棰勬媺锛� */
-const groupMembers = computed<GroupMemberLite[]>(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return []
- }
- const group = groupStore.getGroup(conversation.targetId)
- return (group?.members || []).map((member) => {
- const friend = friendStore.getFriend(member.userId)
- return {
- userId: member.userId,
- showName: getMemberDisplayName(member, friend),
- nickname: member.nickname,
- avatar: member.avatar,
- status: member.status
- }
- })
-})
-
-/** 褰撳墠鐢ㄦ埛鏄惁鑳� @ 鍏ㄥ憳锛涚兢涓� + 绠$悊鍛橀兘鍏佽锛堝榻愬井淇� PC锛歛dmin 涔熻兘 @ 鎵�鏈変汉锛� */
-const canAtAll = computed<boolean>(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return false
- }
- const group = groupStore.getGroup(conversation.targetId)
- if (!group) {
- return false
- }
- const myId = getCurrentUserId()
- if (!myId) {
- return false
- }
- if (group.ownerUserId === myId) {
- return true
- }
- return group.members?.find((member) => member.userId === myId)?.role === ImGroupMemberRole.ADMIN
-})
-
-const mentionVisible = ref(false)
-const mentionSearchText = ref('')
-/** 娴眰瀹氫綅锛歺 鏄乏杈硅窛锛泃op / bottom 浜岄�変竴鈥斺�� bottom 閿氬畾锛坧icker 涓嬫部璐� @锛夋槸榛樿锛�
- * 涓婃柟鏀句笉涓嬫椂閫�鍖栦负 top 閿氬畾锛坧icker 涓婃部璐� @ 涓嬫柟锛� */
-const mentionPosition = ref<{ bottom?: number; top?: number; x: number; }>({ x: 0, bottom: 0 })
-
-/** MentionPicker 鐨勫鍣ㄥ搴︼紙涓庣粍浠堕噷鐨� w-50 瀵归綈锛夛紝鐢ㄤ簬瑙嗗彛鍙虫部鍥炲脊锛�
- * 楂樺害涓嶅啀鐢ㄥ父閲忕畻浣嶇疆鈥斺�攂ottom 閿氬畾鍚� picker 鍐呭澶氬閮戒笉褰卞搷涓嬫部浣嶇疆锛岃嚜鐒惰创 @ */
-const MENTION_WIDTH = 200
-const MENTION_MIN_FIT_ABOVE = 120 // 涓婃柟鍓╀綑绌洪棿鑷冲皯杩欎箞澶氭墠鏀句笂鏂癸紝鍚﹀垯缈诲埌涓嬫柟锛堥伩鍏� picker 琚鍙i《 / 椤堕儴 chat header 鍒囨帀锛�
-
-/** 褰撳墠 @ 鍏抽敭璇嶅湪 editor 閲岀殑鑼冨洿锛沷nMentionSelect 鐢ㄥ畠瀹氫綅鍒犻櫎 + 鎻掑叆 token */
-let mentionRange: null | Range = null
-
-/** 鍏抽棴娴眰 + 娓呮帀 range锛岄伩鍏嶄笂娆℃畫鐣欑殑 range 琚笅涓�娆� onMentionSelect 璇敤 */
-function closeMention() {
- mentionVisible.value = false
- mentionRange = null
-}
-
-/** 鍦ㄥ厜鏍囧綋鍓嶆枃鏈妭鐐归噷鍚戝墠鎵� @keyword锛屽懡涓垯灞曞紑娴眰 */
-function detectAtMention() {
- if (!isGroup.value) {
- closeMention()
- return
- }
- const sel = window.getSelection()
- if (!sel || sel.rangeCount === 0) {
- closeMention()
- return
- }
- const range = sel.getRangeAt(0)
- if (!range.collapsed || range.startContainer.nodeType !== Node.TEXT_NODE) {
- closeMention()
- return
- }
- const node = range.startContainer
- const offset = range.startOffset
- const before = (node.textContent || '').slice(0, offset)
- // 鐩存帴鎵炬渶杩戠殑 @锛氫笉闄愬埗鍓嶇疆瀛楃锛屽榻愬井淇�"涓枃绱ц创 @ 涔熻兘鑱旀兂"锛堜綘濂紷寮犱笁銆侊紝@寮犱笁 閮借Е鍙戯級锛�
- // 浠d环鏄� email-like "test@example.com" 涔熶細瑙﹀彂锛屼絾鑱婂ぉ杈撳叆閲岀矘 email 鐨勬鐜囦綆锛�
- // 涓旂敤鎴锋寜 Esc 鍗冲彲鍏虫诞灞傦紱鍏滃簳鐢� [^\s@] 淇濊瘉涓�鏃﹁緭鍏ョ┖鏍� / 绗簩涓� @ 灏卞仠涓�
- const match = before.match(/@([^\s@]*)$/)
- if (!match) {
- closeMention()
- return
- }
- const keyword = match[1] ?? ''
- const atOffset = offset - keyword.length - 1
- mentionRange = document.createRange()
- mentionRange.setStart(node, atOffset)
- mentionRange.setEnd(node, offset)
- mentionSearchText.value = keyword
- // 閿氬畾鍦� @ 绗﹀彿鏈韩锛岃�岄潪褰撳墠 caret鈥斺�斿惁鍒欑敤鎴锋瘡澶氭暡涓�涓瓧娴眰灏辫窡鐫�鍙崇Щ锛�"椋�"锛�
- positionMention(node, atOffset)
- mentionVisible.value = true
-}
-
-/**
- * 娴眰浣嶇疆锛氶粯璁� bottom 閿氬畾锛坧icker 涓嬫部璐� @ 涓婃柟 8px锛夛紝涓婃柟涓嶅鎵嶇炕鎴� top 閿氬畾
- *
- * 1. 璁$畻 @ 瀛楃灞忓箷鍧愭爣 rect
- * 2. 妯悜锛歱icker 宸﹁竟瀵归綈 @锛岃秺杩囪鍙e彸娌垮垯宸︽帹锛涜嚦灏戠暀 8px 鐣欑櫧
- * 3. 绾靛悜锛氫笂鏂瑰墿浣� 鈮� MENTION_MIN_FIT_ABOVE 璧� bottom 閿氬畾锛堜笉渚濊禆 picker 瀹為檯楂樺害锛�
- * 鏃犺 1 椤硅繕鏄� N 椤� picker 涓嬫部閮借创 @锛夛紱涓嶅鍒欑炕鍒� @ 涓嬫柟璧� top 閿氬畾
- */
-function positionMention(node: Node, atOffset: number) {
- // 1. 璁$畻 @ 瀛楃灞忓箷鍧愭爣 rect
- const anchor = document.createRange()
- anchor.setStart(node, atOffset)
- anchor.collapse(true)
- const rect = anchor.getBoundingClientRect()
- // 2. 妯悜锛歱icker 宸﹁竟瀵归綈 @锛岃秺杩囪鍙e彸娌垮垯宸︽帹锛涜嚦灏戠暀 8px 鐣欑櫧
- const left = Math.max(8, Math.min(rect.left, window.innerWidth - MENTION_WIDTH - 8))
- // 3. 绾靛悜锛氫笂鏂瑰墿浣� 鈮� MENTION_MIN_FIT_ABOVE 璧� bottom 閿氬畾
- mentionPosition.value = rect.top >= MENTION_MIN_FIT_ABOVE ? { x: left, bottom: window.innerHeight - rect.top + 8 } : { x: left, top: rect.bottom + 8 };
-}
-
-/** editor 鍐呴儴婊氬姩鏃跺悓姝ユ诞灞備綅缃紙澶氳 + 瑙﹀彂婊氬姩鏉″満鏅級 */
-function onEditorScroll() {
- if (!mentionVisible.value || !mentionRange) {
- return
- }
- positionMention(mentionRange.startContainer, mentionRange.startOffset)
-}
-
-function onMentionSelect(member: GroupMemberLite) {
- const editor = editorRef.value
- if (!editor || !mentionRange) {
- return
- }
- // 鍒� @keyword锛屾彃鍏� contenteditable=false 鐨� token锛�
- // 鍒犻櫎鏃舵暣娈垫秷闄� + 涓嶄細琚厜鏍囨媶绌匡紱data-id 鏄悗缁� collectFromEditor 鏀� atUserIds 鐨勯挬瀛�
- // token 鏂囨湰鍥哄畾璧扮湡瀹炴樀绉帮細缇ら噷鎵�鏈夋垚鍛樼湅鍒扮殑瀛楅潰閲忎竴鑷达紝閬垮厤鎴戜晶鐨勫ソ鍙嬪娉� / 缇ゆ樀绉版薄鏌撳彂閫佹枃鏈�
- mentionRange.deleteContents()
- const span = document.createElement('span')
- span.className = 'mention-token'
- span.dataset.id = String(member.userId)
- span.contentEditable = 'false'
- span.textContent = `@${member.nickname || member.showName}`
- mentionRange.insertNode(span)
- // token 鍦� editor 棣栦綅鏃讹紝contenteditable=false 杈圭紭浼氳鍏夋爣鏃犳硶鎸埌 token 鍓�
- // 琛ヤ竴涓浂瀹界┖鏍煎綋閿氱偣锛汥OM walk 鏃朵細琚护鎺夛紝涓嶈繘鍏ュ彂閫佸唴瀹�
- const prev = span.previousSibling
- if (!prev || (prev.nodeType === Node.TEXT_NODE && !prev.textContent)) {
- span.parentNode?.insertBefore(document.createTextNode('\u200B'), span)
- }
- // 鍦� token 鍚庤ˉ涓�涓� NBSP锛岃鍏夋爣鍙互缁х画杈撳叆锛汵BSP 姣旀櫘閫氱┖鏍兼洿绋筹紝閬垮厤琚祻瑙堝櫒鎶樺彔
- const space = document.createTextNode('\u00A0')
- span.parentNode?.insertBefore(space, span.nextSibling)
- // 鍏夋爣绉诲埌 NBSP 涔嬪悗
- const sel = window.getSelection()
- if (sel) {
- const newRange = document.createRange()
- newRange.setStartAfter(space)
- newRange.collapse(true)
- sel.removeAllRanges()
- sel.addRange(newRange)
- }
- closeMention()
- editor.focus()
- syncEditorState()
-}
-
-/**
- * 閿洏浜嬩欢鍒嗗彂
- *
- * 1. mention 娴眰鎵撳紑鏃�
- * 1.1 鈫�/鈫� 绉诲姩楂樹寒
- * 1.2 Enter 鏈夊�欓�� 鈫� 閫変腑锛涙棤鍊欓�� fall through 鍒颁笅闈㈢殑鍙戦�佸垎鏀紝閬垮厤鎸� Enter 娌″弽搴�
- * 1.3 Esc 鍏虫诞灞�
- * 2. Shift+Enter 鎹㈣锛氬己鍒惰蛋 br锛堟祻瑙堝櫒榛樿浼氭彃 div锛孌OM walk 鎷兼帴鏇村鏉傦級
- * 3. 鏅�� Enter 鍙戦�侊紙IME composition 鏈熼棿涓嶈Е鍙戯紝閬垮厤閫夎瘝琚鍙戯級
- */
-function onKeydown(e: KeyboardEvent) {
- // 1. mention 娴眰鎵撳紑鏃�
- if (mentionVisible.value) {
- if (e.key === 'ArrowUp') {
- e.preventDefault()
- mentionRef.value?.moveUp()
- return
- }
- if (e.key === 'ArrowDown') {
- e.preventDefault()
- mentionRef.value?.moveDown()
- return
- }
- if (e.key === 'Enter' && !e.isComposing && mentionRef.value?.hasCandidates()) {
- e.preventDefault()
- mentionRef.value?.pickActive()
- return
- }
- if (e.key === 'Escape') {
- closeMention()
- return
- }
- }
- // 2. Shift+Enter 鎹㈣锛氬己鍒惰蛋 br锛堟祻瑙堝櫒榛樿浼氭彃 div锛孌OM walk 鎷兼帴鏇村鏉傦級
- if (e.key === 'Enter' && e.shiftKey && !e.isComposing) {
- e.preventDefault()
- nativeExec('insertLineBreak')
- syncEditorState()
- return
- }
- // 3. 鏅�� Enter 鍙戦�侊紙IME composition 鏈熼棿涓嶈Е鍙戯紝閬垮厤閫夎瘝琚鍙戯級
- if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.isComposing) {
- e.preventDefault()
- handleSend()
- }
-}
-
-// ==================== 鍥剧墖 / 鏂囦欢 / 璇煶 涓婁紶 ====================
-
-/** 涓婁紶鍓嶇殑缁熶竴鎷︽埅锛氱瑷�鎬� / 鏃犳縺娲讳細璇濈洿鎺ユ斁寮冿紱杩斿洖褰撳墠 conversation 涓庢姄璧扮殑 quote */
-function prepareMediaUpload(): undefined | { conversation: Conversation; quote?: QuoteMessage } {
- if (muteOverlay.value) {
- return undefined
- }
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return undefined
- }
- return { conversation, quote: consumeReply() }
-}
-
-/** 涓婁紶骞跺彂閫� IMAGE 娑堟伅锛堝崰浣� + 杩涘害 + 鐪熷疄 url ack 鐢� useMediaUploader 澶勭悊锛� */
-async function uploadAndSendImage(file: File) {
- const context = prepareMediaUpload()
- if (!context) {
- return
- }
- await uploadAndSendMedia({
- file,
- type: ImContentType.IMAGE,
- quote: context.quote,
- conversation: context.conversation
- })
-}
-
-/** 涓婁紶骞跺彂閫� FILE 娑堟伅锛沺ayload 鐢� mediaTypeHandlers[FILE] 鑷姩鎷� url + name + size */
-async function uploadAndSendFile(file: File) {
- // 鏂囦欢鍚嶅彇鏈�鍚庝竴涓� "." 涔嬪悗鐨勯儴鍒嗭紱鏃犲悗缂� / 绌哄瓧绗︿覆閮芥寜"鏈煡"鏀捐
- const extension = file.name.split('.').pop()?.toLowerCase() ?? ''
- if (extension && DANGEROUS_FILE_EXTENSIONS.includes(extension)) {
- message.warning(`涓嶅厑璁稿彂閫� .${extension} 绫诲瀷鏂囦欢`)
- return
- }
- const context = prepareMediaUpload()
- if (!context) {
- return
- }
- await uploadAndSendMedia({
- file,
- type: ImContentType.FILE,
- quote: context.quote,
- conversation: context.conversation
- })
-}
-
-/** 鍥剧墖閫夊畬鍗充笂浼� + 鍙戦�� IMAGE 娑堟伅锛堜笉鏀惧叆 editor锛岀敱 useMediaUploader 鎺ョ鍗犱綅 / 杩涘害 / ack锛� */
-async function onImagePicked(e: Event) {
- const input = e.target as HTMLInputElement
- const file = input.files?.[0]
- input.value = ''
- if (file) {
- await uploadAndSendImage(file)
- }
-}
-
-/** 鏂囦欢閫夊畬鍗充笂浼� + 鍙戦�� FILE 娑堟伅锛堟惡甯﹀師濮� name / size 鍏冩暟鎹級 */
-async function onFilePicked(e: Event) {
- const input = e.target as HTMLInputElement
- const file = input.files?.[0]
- input.value = ''
- if (file) {
- await uploadAndSendFile(file)
- }
-}
-
-// ==================== 璇煶 ====================
-const voiceVisible = ref(false)
-/** 鎵撳紑璇煶褰曞埗闈㈡澘锛涗簰鏂ュ叧鎺夎〃鎯呴潰鏉� */
-function openVoice() {
- voiceVisible.value = true
- emojiVisible.value = false
-}
-/** 鍙戦�佸綍闊虫秷鎭� */
-async function onVoiceSend(payload: {
- blob: Blob
- duration: number
- extension: string
- mimeType: string
-}) {
- const context = prepareMediaUpload()
- if (!context) {
- return
- }
- const file = new File([payload.blob], `voice-${Date.now()}.${payload.extension}`, {
- type: payload.mimeType || payload.blob.type
- })
- await uploadAndSendMedia({
- file,
- type: ImContentType.VOICE,
- quote: context.quote,
- conversation: context.conversation,
- context: { voiceDuration: payload.duration }
- })
-}
-
-// ==================== 瑙嗛 ====================
-type VideoProbe = {
- cover?: Blob
- duration?: number
- height?: number
- width?: number
-}
-
-const VIDEO_COVER_MAX_DIM = 720 // 灏侀潰鏈�闀胯竟 cap锛氳亰澶╁垪琛ㄩ噷鐨勮棰戝皝闈㈡病蹇呰鍘熻棰戝垎杈ㄧ巼锛�4K 鍘熷昂瀵� jpeg 1-3MB 澶氮璐�
-
-/**
- * 鍔犺浇瑙嗛鏈湴棰勮锛屼竴娆℃�ф嬁鍒� metadata锛坉uration / 瀹介珮锛�+ 棣栧抚灏侀潰 blob
- *
- * 涓�涓� video 鍏冪礌涓蹭袱浠朵簨鏄负浜嗛伩鍏嶉噸澶� decode锛歮etadata 瑙e畬鍚庣洿鎺� seek 棣栧抚鍐嶆埅鍥俱��
- * 鎴浘澶辫触涓嶆姏寮傚父锛屽彧璁� cover 缂哄け锛屼繚璇佷富娴佺▼浠嶈兘涓婁紶瑙嗛鏈綋銆�
- *
- * finally 閲屾樉寮忔柇寮曠敤鏄洜涓猴細浠� revokeObjectURL 涓嶈冻浠ヨ video decoder 绔嬪嵆閲婃斁锛�
- * 閮ㄥ垎娴忚鍣ㄧ増鏈笂 4K 瑙嗛瑙g爜 buffer 鍙粸鐣欐暟鍗� MB 鍑犵鍒板崄鍑犵锛岃繛鍙戝嚑鏉′細绱鏀惧ぇ銆�
- */
-async function probeVideoFile(file: File): Promise<VideoProbe> {
- // 1. 鍑嗗绂诲睆 video
- // 1.1 muted + preload=metadata锛氬彧涓嬭浇鏂囦欢澶达紝涓嶉鍔犺浇鏁存潯娴�
- const objectUrl = URL.createObjectURL(file)
- const video = document.createElement('video')
- video.preload = 'metadata'
- video.muted = true
- video.src = objectUrl
- try {
- // 1.2 绛� metadata 鍔犺浇锛氳В鍑� duration / 瀹介珮鎵嶆湁 seek + 鎴浘鐨勪緷鎹�
- await new Promise<void>((resolve, reject) => {
- video.addEventListener('loadedmetadata', () => resolve(), { once: true })
- video.addEventListener('error', () => reject(new Error('video metadata load error')), {
- once: true
- })
- })
- // 1.3 鎶藉厓淇℃伅锛歞uration 鍋舵湁 NaN锛堟瀬灏戞暟鎹熷潖鏂囦欢锛夛紝杞鐞嗕负 undefined
- const meta = {
- duration: Number.isFinite(video.duration) ? Math.round(video.duration) : undefined,
- width: video.videoWidth || undefined,
- height: video.videoHeight || undefined
- }
-
- // 2. 鎴甯у皝闈紙鐙珛 try锛氬け璐ヤ粎闄嶇骇 cover 涓虹┖锛屼笉褰卞搷 meta锛�
- let cover: Blob | undefined
- try {
- // 2.1 绠� seek 鏃堕棿锛�0.1s 閬垮紑甯歌鐨勭函榛戦甯э紱鏃堕暱 < 0.2s 鐨勬瀬鐭棰戦��鍖栦负 0
- const seekTo = video.duration > 0.2 ? 0.1 : 0
- // 2.2 seek + 3s 瓒呮椂锛歝urrentTime 璁句负褰撳墠鍊硷紙璀宸茬粡鏄� 0 鐨勬瀬鐭棰戯級
- // 閮ㄥ垎娴忚鍣ㄤ笉瑙﹀彂 onseeked锛宲romise 浼氫竴鐩� pending 鍗℃鏁存潯閾捐矾
- await new Promise<void>((resolve, reject) => {
- const timer = setTimeout(() => reject(new Error('video seek timeout')), 3000)
- video.addEventListener('seeked', () => {
- clearTimeout(timer)
- resolve()
- }, { once: true })
- video.addEventListener(
- 'error',
- () => {
- clearTimeout(timer)
- reject(new Error('video seek error'))
- },
- { once: true }
- )
- video.currentTime = seekTo
- })
- // 2.3 绂诲睆 canvas 绛夋瘮缂╂斁锛氶暱杈� cap 720锛圴IDEO_COVER_MAX_DIM锛�
- const canvas = document.createElement('canvas')
- const ratio = Math.min(1, VIDEO_COVER_MAX_DIM / Math.max(video.videoWidth, video.videoHeight))
- canvas.width = Math.round(video.videoWidth * ratio)
- canvas.height = Math.round(video.videoHeight * ratio)
- const context = canvas.getContext('2d')
- if (context && canvas.width && canvas.height) {
- // 2.4 褰撳墠甯х粯鍒� canvas 鈫� toBlob 鎷� jpeg锛�0.8 璐ㄩ噺鏄亰澶╁皝闈㈠父鐢ㄧ敎鐐�
- context.drawImage(video, 0, 0, canvas.width, canvas.height)
- cover =
- (await new Promise<Blob | null>((resolve) =>
- canvas.toBlob((b) => resolve(b), 'image/jpeg', 0.8)
- )) ?? undefined
- // 2.5 鎻愬墠閲婃斁 canvas backing store锛�4K 鍘熷昂瀵� 33MB锛夛紝鍒瓑 GC
- canvas.width = 0
- canvas.height = 0
- }
- } catch (error) {
- console.warn('[IM] 瑙嗛灏侀潰鎴彇澶辫触', error)
- }
- return { ...meta, cover }
- } finally {
- // 3. 鏄惧紡閲婃斁 video 璧勬簮
- // 3.1 revoke 鏈湴 objectUrl
- URL.revokeObjectURL(objectUrl)
- // 3.2 瑙﹀彂 unload锛岃 decoder buffer 绔嬪嵆閲婃斁
- video.removeAttribute('src')
- video.load()
- }
-}
-
-/**
- * 涓婁紶骞跺彂閫� VIDEO 娑堟伅
- *
- * 1. 绔嬪嵆鍗犱綅锛歜lob URL 鏃㈠綋 video src 涔熷綋 poster锛屾祻瑙堝櫒浼氭嬁棣栧抚缁樺埗 cover锛泂tatus=SENDING + 杩涘害鏉�
- * 2. probe 涓庤棰戜笂浼犲悓姝ヨ捣璺戯紱灏侀潰涓婁紶绛� probe 鍑� cover 鍚庝笌瑙嗛涓婁紶绔為��
- * 锛坧robe 瑙g爜 + 灏侀潰涓婁紶閫氬父琚棰戜笂浼犳椂闀垮畬鍏ㄩ伄钄斤紝浣撴劅鑺傜渷鍑犵櫨 ms 璧锋锛�
- * 3. 瑙嗛鏈綋涓婁紶蹇呴』鎴愬姛锛屾嬁涓嶅埌 url 灏辨妸鍗犱綅缃� FAILED锛涘皝闈㈡槸閿︿笂娣昏姳锛屽け璐ヤ粎鏃ュ織
- * 4. 瑙嗛閾捐矾鑰楁椂闀匡紝涓婁紶鏈熼棿鐢ㄦ埛鍒囦細璇濆垯鏀惧純鍙戦�侊紙閬垮厤钀藉埌閿欒浼氳瘽閲岋級锛涘垏璧板啀鍒囧洖鏉ヤ笉绠楀彉鍖栵紙key 浠嶇浉绛夛級
- */
-async function uploadAndSendVideo(file: File) {
- if (!ensureMediaSizeWithinLimit(file, ImContentType.VIDEO, message.warning)) {
- return
- }
- const context = prepareMediaUpload()
- if (!context) {
- return
- }
- const { conversation } = context
- const replyQuote = context.quote
- const startKey = getConversationKey(conversation)
-
- // 1. 绔嬪嵆鍗犱綅锛歶rl 璧� blob 璁� <video src> 鎷夐瀛楄妭娓叉煋锛沜overUrl 涓嶈 blob
- // 锛�<video poster> 鏈熷緟鍥剧墖璧勬簮锛屼紶 video blob 鍦ㄩ儴鍒嗘祻瑙堝櫒浼氶��鍖栨垚榛戝簳锛屼笉鏄ǔ瀹氳涓猴級
- // cover 绛� probe 寮傛鍑虹湡瀹� URL 鍚庣敱 commit 闃舵涓�璧� patch锛沖localFile 鐣� file 渚涘け璐ラ噸璇�
- // payload 鎷艰璧� mediaTypeHandlers[VIDEO].build 涓� commit 闃舵鍏变韩鍚屼竴浠介�昏緫
- const videoHandler = requireMediaHandler(ImContentType.VIDEO)
- const buildPlaceholderContent = (blobUrl: string): string =>
- serializeMessage(withQuotePayload(videoHandler.build(file, blobUrl, {}), replyQuote))
- const { clientMessageId } = insertMediaPlaceholder({
- file,
- type: ImContentType.VIDEO,
- conversation,
- buildContent: buildPlaceholderContent
- })
-
- // 2. 涓夎矾骞惰璧疯窇锛坧robe 涓庝袱鏉′笂浼犳棤渚濊禆锛屽皝闈笂浼犵瓑 probe 鍑� cover 鍚庣珛鍗虫帴鍔涳級
- // 2.1 瑙嗛鏈綋涓婁紶锛歛sync IIFE 鍖呬竴灞傝 await 鏄惧紡鍙锛坙int 涓嶅啀璇垽 floating promise锛夛紝
- // 澶辫触鍏滃簳涓� url=undefined锛岀敱 step 3 鎷夸笉鍒� url 鏃舵敹灏�
- const videoUploadPromise: Promise<string | undefined> = (async () => {
- try {
- return await uploadFile(
- { file },
- createUploadProgressHandler(conversation, clientMessageId)
- )
- } catch (error) {
- console.warn('[IM] 瑙嗛鏈綋涓婁紶澶辫触', error)
- return undefined
- }
- })()
- // 2.2 probe 鎷垮厓淇℃伅 + 灏侀潰 blob锛氳В鐮佸け璐ラ檷绾т负绌� probe锛屼笉闃绘柇瑙嗛涓婁紶
- const probePromise = probeVideoFile(file).catch((error): VideoProbe => {
- console.warn('[IM] 瑙嗛鍏冧俊鎭姞杞藉け璐ワ紝闄嶇骇涓轰粎 url + size', error)
- return {}
- })
- // 2.3 灏侀潰涓婁紶锛氱瓑 probe.cover 鍑烘潵鍚庢帴鍔涜捣璺戯紝涓庤棰戜笂浼犵珵閫燂紱澶辫触闄嶇骇 coverUrl 涓虹┖
- const coverUploadPromise = probePromise.then(async (probe) => {
- if (!probe.cover) {
- return { probe, coverUrl: undefined as string | undefined }
- }
- try {
- const coverUrl = await uploadFile({
- file: new File([probe.cover], `cover-${Date.now()}.jpg`, { type: 'image/jpeg' })
- })
- return { probe, coverUrl }
- } catch (error) {
- console.warn('[IM] 瑙嗛灏侀潰涓婁紶澶辫触', error)
- return { probe, coverUrl: undefined as string | undefined }
- }
- })
-
- // 3. 鏀跺彛鏍¢獙
- // 3.1 绛変袱鏉′笂浼犻摼璺眹鍚�
- const [videoRes, { probe, coverUrl }] = await Promise.all([
- videoUploadPromise,
- coverUploadPromise
- ])
- // 3.2 瑙嗛鏈綋娌� url锛氬崰浣嶇疆 FAILED锛岃鐢ㄦ埛鍐冲畾閲嶈瘯 / 鍒犻櫎锛坃localFile 鍦ㄥ唴瀛橀噷锛�
- const url = videoRes
- if (!url) {
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return
- }
- if (!isOpenableUrl(url)) {
- console.warn('[IM] 瑙嗛涓婁紶杩斿洖浜嗕笉鏀寔鎵撳紑鐨� URL', { url })
- message.warning('涓婁紶杩斿洖鐨勮棰戝湴鍧�涓嶆敮鎸佹墦寮�')
- markMediaFailed(conversation.type, conversation.targetId, clientMessageId)
- return
- }
- const safeCoverUrl = coverUrl && isOpenableUrl(coverUrl) ? coverUrl : undefined
- if (coverUrl && !safeCoverUrl) {
- console.warn('[IM] 瑙嗛灏侀潰涓婁紶杩斿洖浜嗕笉鏀寔鎵撳紑鐨� URL', { coverUrl })
- }
- // 3.3 涓婁紶鍚庝細璇濇牎楠� + muteOverlay 澶嶆煡锛堜笌 useMediaUploader.uploadAndSendMedia 鍚屼竴閬擄級
- if (
- !verifyMediaUploadStillAllowed(conversation, startKey, ImContentType.VIDEO, clientMessageId)
- ) {
- return
- }
-
- // 4. 鎷肩湡瀹� VideoMessage payload锛宲atch 杩涘崰浣� + 璧� sendRaw 澶嶇敤鍗犱綅鍙戦��
- const realContent = serializeMessage(
- withQuotePayload(
- videoHandler.build(file, url, {
- videoProbe: { duration: probe.duration, width: probe.width, height: probe.height },
- videoCoverUrl: safeCoverUrl
- }),
- replyQuote
- )
- )
- await commitMediaPlaceholder({
- type: ImContentType.VIDEO,
- conversation,
- clientMessageId,
- realContent
- })
-}
-
-/** 瑙嗛閫夊畬鍗充笂浼� + 鍙戦�� VIDEO 娑堟伅锛堜笉鏀惧叆 editor锛岀嫭绔嬮摼璺細probe + 鍙屼笂浼狅紝鏈�缁堣蛋 commitMediaPlaceholder 鏀跺熬锛� */
-async function onVideoPicked(e: Event) {
- const input = e.target as HTMLInputElement
- const file = input.files?.[0]
- input.value = ''
- if (file) {
- await uploadAndSendVideo(file)
- }
-}
-</script>
-
-<template>
- <!--
- 澶栧眰搴曡壊涓庢秷鎭祦锛坆g-color-page锛変繚鎸佷竴鑷达紝璁�"娑堟伅 鈫� 杈撳叆"鏃犺壊宸繃娓★紱
- padding 缁欏唴灞傜櫧鍗$墖鍛煎惛绌洪棿锛屽崱鐗囪嚜甯﹁竟妗嗗氨澶熷尯鍒嗚緭鍏ュ尯锛屼笉鍐嶉渶瑕佷竴鏉� border-t
- -->
- <div class="relative bg-[var(--ant-color-bg-layout)] px-3 pt-2 pb-3">
- <!-- 绂佽█ / 灏佺瑕嗙洊灞傦細浼樺厛绾� 灏佺 > 鍏ㄧ兢绂佽█ > 鎴愬憳绂佽█ -->
- <div
- v-if="muteOverlay"
- class="message-input__mute-overlay absolute top-2 right-3 bottom-3 left-3 z-20 flex items-center justify-center gap-2 rounded-lg border border-solid text-sm"
- :class="
- muteOverlay.icon === 'ant-design:stop-outlined'
- ? 'message-input__mute-overlay--error'
- : 'message-input__mute-overlay--warning'
- "
- >
- <Icon :icon="muteOverlay.icon" :size="18" />
- <span>{{ muteOverlay.text }}</span>
- </div>
- <!-- 鍐呭眰鐧借壊鍦嗚鍗$墖 = editor + 宸ュ叿鏍忥紱border + rounded 妯℃嫙寰俊銆岃緭鍏ユ銆嶈竟鐣� -->
- <div
- class="relative flex flex-col bg-[var(--ant-color-bg-container)] rounded-lg border border-solid border-[var(--im-border-color-lighter)]"
- >
- <!--
- 杈撳叆鍖哄湪涓婏細contenteditable div锛堝彇浠� textarea锛屽榻愬井淇� PC锛氳緭鍏ュ尯鍦ㄤ笂锛屾搷浣滃湪涓嬶級
- - 璁� @ 娴眰鑳芥嬁鍒扮湡瀹炲厜鏍� rect锛坱extarea 鎷夸笉鍒帮級
- - 璁� @ 鎴愬憳浠� <span data-id> token 鑺傜偣瀛樺湪锛屽垹 token 鍗冲垹 id锛岄伩鍏� stale atUserIds
- - placeholder 閫氳繃 data-empty + ::before 妯℃嫙锛坈ontenteditable 娌℃湁鍘熺敓 placeholder锛�
- -->
- <div
- ref="editorRef"
- class="message-input__editor relative min-h-[120px] max-h-[200px] overflow-y-auto py-3.5 px-4 text-sm leading-normal outline-none whitespace-pre-wrap break-words"
- contenteditable="true"
- data-placeholder="鎸� Enter 鍙戦�侊紝Shift+Enter 鎹㈣"
- data-empty=""
- role="textbox"
- @keydown="onKeydown"
- @input="onInput"
- @scroll.passive="onEditorScroll"
- @paste.prevent="onPaste"
- ></div>
-
- <!-- 寮曠敤棰勮鏉� -->
- <ReplyPreview
- v-if="replyTarget"
- :quote="replyTarget"
- closable
- class="mx-3 mb-1.5"
- @close="clearConversationReplyDraft"
- />
-
- <!--
- 搴曢儴宸ュ叿鏍忥細宸︿晶鎿嶄綔鍥炬爣 + 鍙充晶鍙戦�佹寜閽紙瀵归綈寰俊 PC锛氭搷浣滃浘鏍囩粺涓�鏀惧簳閮級
- - relative 缁� FacePicker 鎻愪緵 absolute 閿氱偣锛宲icker 鐢� bottom-full 鍚戜笂寮瑰嚭
- - 鍥炬爣缁熶竴 30脳30 鐐瑰嚮鍖猴紙18px icon + p-1.5锛夛紝gap-1 璁╅棿璺濊创鍚堝井淇¤鎰�
- - border-t 鍦ㄧ紪杈戝尯涓庡伐鍏锋爮涔嬮棿鐢讳竴鏉′笌 card 杈规鍚岃壊鐨勭粏绾�
- -->
- <div
- class="relative flex items-center justify-between gap-2 px-3 py-2 border-t border-t-solid border-[var(--im-border-color-lighter)]"
- >
- <div class="flex items-center gap-1">
- <!--
- 鎵�鏈� icon 缁熶竴璧� Iconify锛坅nt-design outlined 绯诲垪锛夛細
- - 瑙嗚椋庢牸鏇存帴杩戝井淇� PC锛堢嚎鎬с�佸渾瑙掞紝姣� Element Plus 鍐呯疆鐨勬洿杞婚噺锛�
- - 绗戣劯 / 鍥剧墖 / 鏂囦欢澶� / 楹﹀厠椋� 鍚屾簮锛岄伩鍏嶄竴涓蛋 ep 涓�涓蛋 antd 瑙嗚鍓茶
- - 澶栧眰 span 澶嶇敤 .message-input__tool 鐨� padding / hover 鏍峰紡锛宻coped CSS 鐨� :deep(svg) 浠嶈兘鍛戒腑
- -->
- <Tooltip title="琛ㄦ儏" placement="top">
- <span
- class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill)]"
- @click.stop="toggleEmoji"
- >
- <Icon icon="ant-design:smile-outlined" :size="18" />
- </span>
- </Tooltip>
- <Tooltip title="鍙戦�佸浘鐗�" placement="top">
- <span
- class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill)]"
- @click="imageInputRef?.click()"
- >
- <Icon icon="ant-design:picture-outlined" :size="18" />
- </span>
- </Tooltip>
- <Tooltip title="鍙戦�佹枃浠�" placement="top">
- <span
- class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill)]"
- @click="fileInputRef?.click()"
- >
- <Icon icon="ant-design:folder-outlined" :size="18" />
- </span>
- </Tooltip>
- <Tooltip title="璇煶娑堟伅" placement="top">
- <span
- class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill)]"
- @click.stop="openVoice"
- >
- <Icon icon="ant-design:audio-outlined" :size="18" />
- </span>
- </Tooltip>
- <Tooltip title="鍙戦�佽棰�" placement="top">
- <span
- class="message-input__tool inline-flex items-center justify-center box-content p-1.5 cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill)]"
- @click="videoInputRef?.click()"
- >
- <Icon icon="ant-design:video-camera-outlined" :size="18" />
- </span>
- </Tooltip>
- </div>
-
- <!-- 缇よ亰 + 缇ゅ凡璇诲紑鍚細鍙戦�佹寜閽� + 鈻� 涓嬫媺鑿滃崟锛堢偣涓绘寜閽櫘閫氬彂閫� / 鐐� 鈻� 閫夈�屽彂閫佸洖鎵ф秷鎭�嶏級锛屽榻愬井淇� PC -->
- <!-- 娉ㄦ剰锛歛nt-design-vue 鐨勫垎瑁傛寜閽槸 DropdownButton锛堥潪 Dropdown 鐨� split-button 灞炴�э紝閭f槸 Element Plus 鍐欐硶锛� -->
- <DropdownButton
- v-if="isGroup && MESSAGE_GROUP_READ_ENABLED"
- type="primary"
- :disabled="!canSend"
- @click="handleSend()"
- >
- 鍙� 閫�
- <template #overlay>
- <Menu @click="handleSendMenuClick">
- <Menu.Item key="receipt">鍙戦�佸洖鎵ф秷鎭�</Menu.Item>
- </Menu>
- </template>
- </DropdownButton>
- <!-- 绉佽亰鎴栫兢宸茶鍏抽棴锛氭櫘閫氬彂閫佹寜閽紙鏃犵兢鍥炴墽鍏ュ彛锛� -->
- <Button v-else type="primary" :disabled="!canSend" @click="handleSend()">
- 鍙� 閫�
- </Button>
-
- <!-- 琛ㄦ儏闈㈡澘锛歜ottom-full 璁� picker 涓嬫部璐村伐鍏锋爮椤堕儴锛屽悜涓婂脊鍑猴紙瀵归綈宸ュ叿鏍忓乏渚ч鍥炬爣锛� -->
- <FacePicker
- v-model:visible="emojiVisible"
- class="bottom-full left-3 mb-2"
- @select-emoji="insertText"
- @select-face="onSelectFace"
- />
-
- <!-- 璇煶褰曞埗闈㈡澘锛氫笌琛ㄦ儏闈㈡澘鍚屽宸ュ叿鏍忥紝bottom-full 鍚戜笂寮瑰嚭锛岄伩鍏嶇瑙﹀彂鐨勯害鍏嬮鍥炬爣杩囪繙 -->
- <VoiceRecorder v-model="voiceVisible" class="bottom-full left-3 mb-2" @send="onVoiceSend" />
- </div>
- </div>
-
- <!-- @ 閫夋嫨娴眰锛氱兢鑱婃墠鍚敤 -->
- <MentionPicker
- ref="mentionRef"
- v-model:visible="mentionVisible"
- :position="mentionPosition"
- :members="groupMembers"
- :search-text="mentionSearchText"
- :can-at-all="canAtAll"
- @select="onMentionSelect"
- />
-
- <!-- 闅愯棌鐨勬枃浠堕�夋嫨鍣� -->
- <input ref="imageInputRef" type="file" accept="image/*" hidden @change="onImagePicked" />
- <input ref="fileInputRef" type="file" hidden @change="onFilePicked" />
- <input ref="videoInputRef" type="file" accept="video/*" hidden @change="onVideoPicked" />
- </div>
-</template>
-
-<style scoped>
-/* el-icon 鍏ㄥ眬瑙勫垯 .el-icon{color:var(--color,inherit); font-size:inherit; width:1em; height:1em} 浼樺厛绾ф洿楂橈紝
- 鐢ㄥ瓧闈㈤�夋嫨鍣� + !important 閿佹锛涢鑹插彇 Element Plus 涓婚鍙橀噺锛屾殫鑹茶嚜鍔ㄥ垏鍒版祬鐏� */
-.message-input__tool,
-.message-input__tool:deep(svg) {
- font-size: 18px !important;
- color: var(--ant-color-text) !important;
- fill: currentColor !important;
-}
-.message-input__tool:hover,
-.message-input__tool:hover:deep(svg) {
- color: var(--ant-color-primary) !important;
-}
-
-.message-input__mute-overlay--warning {
- color: #d48806;
- background: #fff7e6;
- border-color: #ffd591;
-}
-
-.message-input__mute-overlay--error {
- color: #cf1322;
- background: #fff1f0;
- border-color: #ffa39e;
-}
-
-/* 娉ㄦ剰锛氬繀椤绘妸 .dark 涓庣洰鏍囩被鏁翠綋鏀捐繘 :global()锛屽惁鍒� Vue scoped 缂栬瘧浼氭妸
- `:global(.dark) .xxx` 閿欒濉岀缉鎴愯8 `.dark`锛屽鑷磋繖浜涢鑹茶鐩存帴鍒峰埌 <html> 涓�
- 锛堟殫鑹蹭笅鏁撮〉鍙戠孩/鍙戞殫榛勶級銆侭EM 绫诲悕鍞竴锛屾暣浣� global 涓嶄細璇激鍏跺畠鍏冪礌銆� */
-:global(.dark .message-input__mute-overlay--warning) {
- color: #ffd666;
- background: rgb(77 56 21 / 88%);
- border-color: #ad6800;
-}
-
-:global(.dark .message-input__mute-overlay--error) {
- color: #ff7875;
- background: rgb(91 33 33 / 88%);
- border-color: #a8071a;
-}
-
-/* 鐢� data-empty 鑰岄潪 :empty锛氭祻瑙堝櫒鍦ㄥ垹绌哄悗浼氱暀涓� <br>锛�:empty 涓嶅懡涓紱data-empty 鐢� syncEditorState 缁存姢 */
-.message-input__editor[data-empty]::before {
- content: attr(data-placeholder);
- color: var(--ant-color-text-placeholder);
- pointer-events: none;
- position: absolute;
-}
-
-/* @ token 璧颁富鑹查珮浜紱contenteditable=false 璁� backspace 鏁存鍒犺�屼笉鏄�愬瓧绗� */
-.message-input__editor :deep(.mention-token) {
- color: var(--ant-color-primary);
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/input/message-multi-select-bar.vue b/src/views/im/home/pages/conversation/components/input/message-multi-select-bar.vue
deleted file mode 100644
index 77f6158..0000000
--- a/src/views/im/home/pages/conversation/components/input/message-multi-select-bar.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-<script lang="ts" setup>
-import type { Message } from '#/views/im/home/types'
-
-import { computed, inject } from 'vue'
-
-import { confirm } from '../../../../../../../packages/effects/common-ui/src'
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { useMessageMultiSelect } from '#/views/im/home/composables/useMessageMultiSelect'
-import { useConversationStore } from '#/views/im/home/store/conversationStore'
-import { useMessageStore } from '#/views/im/home/store/messageStore'
-import { ImForwardMode, isNormalMessage } from '#/views/im/utils/constants'
-import { getClientConversationId } from '#/views/im/utils/db'
-
-import { IM_FORWARD_DIALOG_KEY } from '../message/forward/keys'
-
-defineOptions({ name: 'ImMessageMultiSelectBar' })
-
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY)
-const multiSelect = useMessageMultiSelect()
-
-/** 閫変腑鏉℃暟 */
-const selectedCount = computed(() => multiSelect.state.selectedClientMessageIds.length)
-
-/** 褰撳墠浼氳瘽鍐呭凡閫夋秷鎭� */
-function getSelectedMessages(): Message[] {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return []
- }
- const ids = multiSelect.selectedIdSet.value
- return messageStore
- .getMessages(getClientConversationId(conversation.type, conversation.targetId))
- .filter((message) => ids.has(message.clientMessageId) && isNormalMessage(message.type))
-}
-
-/** 閫愭潯杞彂锛氬紑 ForwardDialog 鍗曟潯妯″紡 */
-function handleForwardOneByOne() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- const messages = getSelectedMessages()
- if (messages.length === 0) {
- return
- }
- openForwardDialog?.({
- mode: ImForwardMode.SINGLE,
- messages,
- sourceConversation: conversation
- })
-}
-
-/** 鍚堝苟杞彂锛氬紑 ForwardDialog 鍚堝苟妯″紡 */
-function handleForwardMerged() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- const messages = getSelectedMessages()
- if (messages.length === 0) {
- return
- }
- openForwardDialog?.({
- mode: ImForwardMode.MERGE,
- messages,
- sourceConversation: conversation
- })
-}
-
-/** 鍒犻櫎锛氬脊纭妗� 鈫� 鏈湴鎵归噺绉婚櫎锛堜笉鍚屾鍚庣锛屽榻愬井淇°�屽垹闄ゃ�嶈涔夛級 */
-async function handleDelete() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- const messages = getSelectedMessages()
- if (messages.length === 0) {
- return
- }
- try {
- await confirm(`纭鍒犻櫎閫変腑鐨� ${messages.length} 鏉℃秷鎭紵`, {
- cancelText: '鍙栨秷',
- confirmText: '纭畾',
- icon: 'warning',
- title: '鍒犻櫎纭'
- })
- } catch {
- return
- }
- for (const m of messages) {
- messageStore.removeMessage(conversation.type, conversation.targetId, {
- id: m.id,
- clientMessageId: m.clientMessageId
- })
- }
- multiSelect.exit()
-}
-
-/** 鍙栨秷澶氶�� */
-function handleCancel() {
- multiSelect.exit()
-}
-</script>
-
-<template>
- <div
- v-if="multiSelect.state.active"
- class="flex items-center justify-center gap-12 px-5 w-full h-full border-t border-t-solid bg-[var(--ant-color-bg-container)] border-[var(--im-border-color-lighter)]"
- >
- <span
- class="absolute left-5 top-1/2 -translate-y-1/2 text-12px text-[var(--ant-color-text-secondary)]"
- >
- 宸查�� {{ selectedCount }} 鏉�
- </span>
-
- <button
- class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:text-[var(--ant-color-primary)] hover:bg-[var(--ant-color-fill)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
- :disabled="selectedCount === 0"
- @click="handleForwardOneByOne"
- >
- <Icon icon="ant-design:share-alt-outlined" :size="22" />
- <span>閫愭潯杞彂</span>
- </button>
-
- <button
- class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:text-[var(--ant-color-primary)] hover:bg-[var(--ant-color-fill)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
- :disabled="selectedCount === 0"
- @click="handleForwardMerged"
- >
- <Icon icon="tabler:arrow-merge" :size="22" />
- <span>鍚堝苟杞彂</span>
- </button>
-
- <button
- class="inline-flex flex-col items-center gap-1 px-3 py-1 text-12px rounded-md border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill)] hover:text-[var(--ant-color-error)] disabled:text-[var(--ant-color-text-disabled)] disabled:cursor-not-allowed disabled:bg-transparent"
- :disabled="selectedCount === 0"
- @click="handleDelete"
- >
- <Icon icon="ant-design:delete-outlined" :size="22" />
- <span>鍒犻櫎</span>
- </button>
-
- <button
- class="absolute right-5 top-1/2 -translate-y-1/2 inline-flex items-center justify-center w-7 h-7 rounded-full border-0 bg-transparent cursor-pointer transition-colors text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill)]"
- @click="handleCancel"
- >
- <Icon icon="ant-design:close-outlined" :size="20" />
- </button>
- </div>
-</template>
diff --git a/src/views/im/home/pages/conversation/components/input/voice-recorder.vue b/src/views/im/home/pages/conversation/components/input/voice-recorder.vue
deleted file mode 100644
index 0598afa..0000000
--- a/src/views/im/home/pages/conversation/components/input/voice-recorder.vue
+++ /dev/null
@@ -1,340 +0,0 @@
-<script lang="ts" setup>
-import {
- computed,
- onBeforeUnmount,
- onMounted,
- onUnmounted,
- ref,
- useTemplateRef,
- watch
-} from 'vue'
-
-import { Button, message } from 'ant-design-vue'
-
-import { formatSeconds } from '#/views/im/utils/time'
-
-defineOptions({ name: 'ImVoiceRecorder' })
-
-const props = withDefaults(
- defineProps<{
- maxDuration?: number // 鏈�闀垮綍鍒剁鏁�
- modelValue: boolean // 鏄惁鏄剧ず
- }>(),
- {
- maxDuration: 60
- }
-)
-
-const emit = defineEmits<{
- send: [payload: { blob: Blob; duration: number; extension: string; mimeType: string }] // 褰曞埗瀹屾垚鏁版嵁
- 'update:modelValue': [value: boolean]
-}>()
-
-const VOICE_MIME_TYPE_OPTIONS = [
- { extension: 'webm', mimeType: 'audio/webm;codecs=opus' },
- { extension: 'webm', mimeType: 'audio/webm' },
- { extension: 'm4a', mimeType: 'audio/mp4' },
- { extension: 'ogg', mimeType: 'audio/ogg;codecs=opus' },
- { extension: 'ogg', mimeType: 'audio/ogg' }
-]
-
-const visible = computed({
- get: () => props.modelValue,
- set: (v) => emit('update:modelValue', v)
-})
-
-const rootRef = useTemplateRef<HTMLDivElement>('rootRef')
-
-/** 褰曞埗鐘舵�� */
-type Status = 'idle' | 'preview' | 'recording'
-const status = ref<Status>('idle')
-const duration = ref(0)
-const previewUrl = ref('')
-
-let mediaRecorder: MediaRecorder | null = null
-let audioChunks: Blob[] = []
-let mediaStream: MediaStream | null = null
-let timer: null | ReturnType<typeof setInterval> = null
-let recordedBlob: Blob | null = null
-let discarding = false
-let recordingMimeType = ''
-let recordingExtension = ''
-let recordedMimeType = ''
-let recordedExtension = ''
-
-/** 璁℃椂鍣ㄦ枃妗� */
-const timerText = computed(() => formatSeconds(duration.value))
-
-/** 鐩戝惉闈㈡澘鏄剧ず鐘舵�� */
-watch(visible, (v) => {
- if (v) {
- document.addEventListener('click', handleDocumentClick)
- } else {
- document.removeEventListener('click', handleDocumentClick)
- resetAll()
- }
-})
-
-/** 澶勭悊澶栭儴鐐瑰嚮 */
-function handleDocumentClick(e: MouseEvent) {
- if (!props.modelValue || !rootRef.value) {
- return
- }
- if (rootRef.value.contains(e.target as Node)) {
- return
- }
- if (status.value !== 'idle') {
- return
- }
- visible.value = false
-}
-
-/** 鑾峰彇鏀寔鐨勫綍闊虫牸寮� */
-function getSupportedVoiceMimeType() {
- if (typeof MediaRecorder === 'undefined' || !MediaRecorder.isTypeSupported) {
- return undefined
- }
- return VOICE_MIME_TYPE_OPTIONS.find((item) => MediaRecorder.isTypeSupported(item.mimeType))
-}
-
-/** 鑾峰彇褰曢煶鏂囦欢鍚庣紑 */
-function getVoiceExtension(mimeType: string) {
- const normalizedMimeType = mimeType.split(';')[0]
- if (normalizedMimeType === 'audio/mp4') {
- return 'm4a'
- }
- if (normalizedMimeType === 'audio/ogg') {
- return 'ogg'
- }
- return 'webm'
-}
-
-/** 鍒涘缓褰曢煶鍣� */
-function createVoiceRecorder(stream: MediaStream) {
- const supportedMimeType = getSupportedVoiceMimeType()
- const recorder = supportedMimeType
- ? new MediaRecorder(stream, { mimeType: supportedMimeType.mimeType })
- : new MediaRecorder(stream)
- recordingMimeType = recorder.mimeType || supportedMimeType?.mimeType || ''
- recordingExtension = supportedMimeType?.extension || (recordingMimeType ? getVoiceExtension(recordingMimeType) : '')
- return recorder
-}
-
-/** 寮�濮嬪綍鍒� */
-async function startRecord() {
- if (typeof MediaRecorder === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
- message.error('褰撳墠娴忚鍣ㄤ笉鏀寔褰曢煶锛堥渶瑕� HTTPS 鎴� localhost锛�')
- return
- }
- try {
- mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })
- } catch {
- message.error('鏃犳硶鑾峰彇楹﹀厠椋庢潈闄�')
- return
- }
- audioChunks = []
- discarding = false
- try {
- mediaRecorder = createVoiceRecorder(mediaStream)
- } catch {
- cleanupStream()
- message.error('褰撳墠娴忚鍣ㄤ笉鏀寔褰曢煶鏍煎紡')
- return
- }
- mediaRecorder.addEventListener('dataavailable', (event: BlobEvent) => {
- if (event.data.size > 0) {
- audioChunks.push(event.data)
- }
- })
- mediaRecorder.addEventListener('stop', () => {
- if (discarding) {
- return
- }
- recordedMimeType = recordingMimeType || mediaRecorder?.mimeType || audioChunks[0]?.type || 'audio/webm'
- recordedExtension = recordingExtension || getVoiceExtension(recordedMimeType)
- recordedBlob = new Blob(audioChunks, { type: recordedMimeType })
- previewUrl.value = URL.createObjectURL(recordedBlob)
- status.value = 'preview'
- })
- mediaRecorder.start()
- status.value = 'recording'
- duration.value = 0
- timer = setInterval(() => {
- duration.value++
- if (duration.value >= props.maxDuration) {
- stopRecord()
- }
- }, 1000)
-}
-
-/** 鍋滄褰曞埗 */
-function stopRecord() {
- if (duration.value < 1) {
- message.warning('褰曢煶鏃堕棿澶煭')
- resetAll()
- return
- }
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
- mediaRecorder.stop()
- }
- cleanupStream()
- if (timer) {
- clearInterval(timer)
- timer = null
- }
-}
-
-/** 閲嶆柊褰曞埗 */
-function restart() {
- clearPreview()
- duration.value = 0
- status.value = 'idle'
-}
-
-/** 鍙戦�佸綍闊� */
-function handleSend() {
- if (!recordedBlob) {
- return
- }
- emit('send', {
- blob: recordedBlob,
- duration: duration.value,
- extension: recordedExtension,
- mimeType: recordedMimeType || recordedBlob.type
- })
- visible.value = false
-}
-
-/** 鍙栨秷褰曞埗 */
-function handleCancel() {
- visible.value = false
-}
-
-/** 閲嶇疆褰曞埗璧勬簮 */
-function resetAll() {
- discarding = true
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
- mediaRecorder.stop()
- }
- cleanupStream()
- if (timer) {
- clearInterval(timer)
- timer = null
- }
- audioChunks = []
- duration.value = 0
- status.value = 'idle'
- recordingMimeType = ''
- recordingExtension = ''
- recordedMimeType = ''
- recordedExtension = ''
- clearPreview()
-}
-
-/** 閲婃斁棰勮闊抽 */
-function clearPreview() {
- recordedBlob = null
- if (previewUrl.value) {
- URL.revokeObjectURL(previewUrl.value)
- previewUrl.value = ''
- }
-}
-
-/** 鍏抽棴楹﹀厠椋庨噰闆� */
-function cleanupStream() {
- mediaStream?.getTracks().forEach((t) => t.stop())
- mediaStream = null
-}
-
-onMounted(() => {
- if (props.modelValue) {
- document.addEventListener('click', handleDocumentClick)
- }
-})
-
-onBeforeUnmount(resetAll)
-
-onUnmounted(() => {
- document.removeEventListener('click', handleDocumentClick)
-})
-</script>
-
-<template>
- <!-- 璇煶褰曞埗闈㈡澘 -->
- <div
- v-if="visible"
- ref="rootRef"
- class="im-popover-arrow absolute z-100 w-80 p-4 rounded-md bg-[var(--ant-color-bg-container)] shadow-[0_4px_16px_rgba(0,0,0,0.12)]"
- @click.stop
- >
- <div class="flex flex-col items-center gap-3">
- <!-- 褰曞埗鏃堕暱 -->
- <div class="text-[28px] font-medium tabular-nums text-[var(--ant-color-text)]">
- {{ timerText }}
- </div>
-
- <!-- 鐘舵�佹枃妗� -->
- <div class="text-13px text-[var(--ant-color-text-secondary)]">
- <span v-if="status === 'idle'">鐐瑰嚮涓嬫柟鎸夐挳寮�濮嬪綍鍒�</span>
- <span v-else-if="status === 'recording'">褰曞埗涓紝鏈�闀� {{ maxDuration }} 绉�</span>
- <span v-else>褰曞埗瀹屾垚锛屽彲璇曞惉鍚庡彂閫�</span>
- </div>
-
- <!-- 褰曞埗鐘舵�� -->
- <div
- v-if="status !== 'preview'"
- class="w-12 h-12 rounded-full bg-[var(--ant-color-border)]"
- :class="{ 'im-voice-recorder__pulse bg-[#f56c6c]': status === 'recording' }"
- ></div>
- <audio v-else :src="previewUrl" controls class="w-full"></audio>
- </div>
-
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="flex justify-end gap-2 mt-3">
- <template v-if="status === 'idle'">
- <Button size="small" @click="handleCancel">鍙栨秷</Button>
- <Button size="small" type="primary" @click="startRecord">寮�濮嬪綍鍒�</Button>
- </template>
- <template v-else-if="status === 'recording'">
- <Button size="small" @click="handleCancel">鍙栨秷</Button>
- <Button size="small" type="primary" @click="stopRecord">鍋滄褰曞埗</Button>
- </template>
- <template v-else>
- <Button size="small" @click="handleCancel">鍙栨秷</Button>
- <Button size="small" @click="restart">閲嶆柊褰曞埗</Button>
- <Button size="small" type="primary" @click="handleSend">鍙戦��</Button>
- </template>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 搴曢儴灏忎笁瑙� */
-.im-popover-arrow::after {
- content: '';
- position: absolute;
- top: calc(100% - 1px);
- left: 110px;
- border-style: solid;
- border-width: 6px 6px 0 6px;
- border-color: var(--ant-color-bg-container) transparent transparent transparent;
- filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.08));
-}
-
-/* 褰曢煶涓殑鑴夊啿鍔ㄧ敾 */
-.im-voice-recorder__pulse {
- animation: im-voice-pulse 1s infinite;
-}
-
-@keyframes im-voice-pulse {
- 0% {
- box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.6);
- }
- 70% {
- box-shadow: 0 0 0 20px rgba(245, 108, 108, 0);
- }
- 100% {
- box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
- }
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/forward/index.ts b/src/views/im/home/pages/conversation/components/message/forward/index.ts
deleted file mode 100644
index 5e83f77..0000000
--- a/src/views/im/home/pages/conversation/components/message/forward/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as MessageForwardDialog } from './message-forward-dialog.vue';
-export { default as MessageMergeDetailDialog } from './message-merge-detail-dialog.vue';
diff --git a/src/views/im/home/pages/conversation/components/message/forward/keys.ts b/src/views/im/home/pages/conversation/components/message/forward/keys.ts
deleted file mode 100644
index f76c191..0000000
--- a/src/views/im/home/pages/conversation/components/message/forward/keys.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { InjectionKey } from 'vue'
-
-import type { Conversation, Message } from '#/views/im/home/types'
-import type { ImForwardModeValue } from '#/views/im/utils/constants'
-
-/** 鎵撳紑杞彂寮圭獥 */
-export type OpenForwardDialog = (opts: {
- messages: Message[]
- mode: ImForwardModeValue
- sourceConversation: Conversation
-}) => void
-
-/** 鎵撳紑鍚堝苟娑堟伅璇︽儏寮圭獥 */
-export type OpenMergeDetailDialog = (content: string) => void
-
-/** 閲嶆嫧 RTC 閫氳瘽锛涚偣绉佽亰 RTC_CALL_END 姘旀场瑙﹀彂 */
-export type RtcRedial = (mediaType: number) => void
-
-/** MessagePanel 閫氳繃 provide 鏆撮湶缁欏瓙鏍� */
-export const IM_FORWARD_DIALOG_KEY: InjectionKey<OpenForwardDialog> = Symbol('IM_FORWARD_DIALOG')
-export const IM_MERGE_DETAIL_DIALOG_KEY: InjectionKey<OpenMergeDetailDialog> = Symbol(
- 'IM_MERGE_DETAIL_DIALOG'
-)
-export const IM_RTC_REDIAL_KEY: InjectionKey<RtcRedial> = Symbol('IM_RTC_REDIAL')
diff --git a/src/views/im/home/pages/conversation/components/message/forward/message-forward-dialog.vue b/src/views/im/home/pages/conversation/components/message/forward/message-forward-dialog.vue
deleted file mode 100644
index 80698bd..0000000
--- a/src/views/im/home/pages/conversation/components/message/forward/message-forward-dialog.vue
+++ /dev/null
@@ -1,446 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation, FriendLite, Message } from '#/views/im/home/types'
-
-import { computed, reactive, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../../packages/icons/src'
-
-import { Button, Input, message, Modal } from 'ant-design-vue'
-
-import { createGroup } from '#/api/im/group'
-import { ConversationPickerPanel } from '#/views/im/home/components/picker'
-import { FriendPickerPanel } from '#/views/im/home/components/picker'
-import { useMessageMultiSelect } from '#/views/im/home/composables/useMessageMultiSelect'
-import { useMessageSender } from '#/views/im/home/composables/useMessageSender'
-import { useConversationStore } from '#/views/im/home/store/conversationStore'
-import { useFriendStore } from '#/views/im/home/store/friendStore'
-import { useGroupStore } from '#/views/im/home/store/groupStore'
-import { MESSAGE_MERGE_PREVIEW_LINES } from '#/views/im/utils/config'
-import {
- ImContentType,
- ImConversationType,
- ImForwardMode,
- type ImForwardModeValue
-} from '#/views/im/utils/constants'
-import { getConversationKey, summarizeMessageContent } from '#/views/im/utils/conversation'
-import { buildDefaultGroupName } from '#/views/im/utils/group'
-import {
- buildMergeMessagePayload,
- removeQuotePayload,
- serializeMessage
-} from '#/views/im/utils/message'
-import { getGroupDisplayName, isGroupQuit } from '#/views/im/utils/user'
-
-import { FacePicker } from '../../input'
-
-defineOptions({ name: 'ImMessageForwardDialog' })
-
-const conversationStore = useConversationStore()
-const friendStore = useFriendStore()
-const groupStore = useGroupStore()
-const { sendRaw, send } = useMessageSender()
-const multiSelect = useMessageMultiSelect()
-
-const state = reactive({
- mode: ImForwardMode.SINGLE as ImForwardModeValue,
- messages: [] as Message[],
- sourceConversation: null as Conversation | null
-})
-const visible = ref(false)
-const view = ref<'contact' | 'conversation'>('conversation') // 褰撳墠瑙嗗浘锛氶粯璁や細璇濋�夋嫨锛屻�屽垱寤鸿亰澶┿�嶅叆鍙e垏鍒板ソ鍙嬮�夋嫨
-const selectedKeys = ref<string[]>([])
-const selectedFriendIds = ref<number[]>([])
-const leaveMessage = ref('')
-const sending = ref(false)
-const emojiVisible = ref(false) // emoji picker 鏄鹃殣锛氬彸渚х瑧鑴告寜閽垏鎹�
-
-defineExpose({
- /** 鎵撳紑杞彂寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true */
- open(opts: { messages: Message[]; mode: ImForwardModeValue; sourceConversation: Conversation }) {
- state.mode = opts.mode
- state.messages = opts.messages
- state.sourceConversation = opts.sourceConversation
- view.value = 'conversation'
- selectedKeys.value = []
- selectedFriendIds.value = []
- leaveMessage.value = ''
- emojiVisible.value = false
- sending.value = false
- visible.value = true
- }
-})
-
-/** 寮圭獥鏍囬锛氫細璇濊鍥炬寜 mode 鍖哄垎銆岄�愭潯 / 鍚堝苟杞彂銆嶏紱濂藉弸瑙嗗浘鍥哄畾涓恒�岄�夋嫨濂藉弸銆� */
-const headerTitle = computed(() => {
- if (view.value === 'contact') {
- return '閫夋嫨濂藉弸'
- }
- return state.mode === ImForwardMode.MERGE ? '鍚堝苟杞彂' : '閫愭潯杞彂'
-})
-
-/** 纭鎸夐挳鏂囨锛氬崟閫夈�屽彂閫併�嶃�佸閫夈�屽垎鍒彂閫�(n)銆� */
-const confirmButtonText = computed(() =>
- selectedKeys.value.length > 1 ? `鍒嗗埆鍙戦�侊紙${selectedKeys.value.length}锛塦 : '鍙戦��'
-)
-
-/** 鍊欓�変細璇濓細浠� store 鎷挎帓搴忓悗鐨勫垪琛紙杞彂鍥炲師浼氳瘽涔熷厑璁革紝涓庡井淇′竴鑷达級锛涘叕浼楀彿 / 棰戦亾鍗曞悜娑堟伅涓嶆帴鍙楄浆鍙戯紝浠庡�欓�夐噷鍓旈櫎 */
-const candidateConversations = computed<Conversation[]>(() =>
- conversationStore.getSortedConversationList.filter(
- (conversation) =>
- conversation.type !== ImConversationType.CHANNEL &&
- // 鍘嗗彶閫�缇ょ兢涓嶅彲琚浆鍙戦�変腑锛堥�変簡鍚庣涔熶細鎷掞級
- !(
- conversation.type === ImConversationType.GROUP &&
- isGroupQuit(groupStore.getGroup(conversation.targetId))
- )
- )
-)
-
-/** 濂藉弸瑙嗗浘鍊欓�夊垪琛細鐩存帴澶嶇敤 friendStore Lite 瑙嗗浘 */
-const friends = computed<FriendLite[]>(() => friendStore.getActiveFriendLiteList)
-
-/** 鍒囧埌濂藉弸瑙嗗浘锛氭竻鎺変箣鍓嶅湪浼氳瘽瑙嗗浘杈撳叆鐨勭暀瑷�锛岄伩鍏嶅湪涓嶅彲瑙佽緭鍏ユ閲屾妸鐣欒█闈欓粯鍙戝埌鏂扮兢 */
-function handleSwitchToContact() {
- view.value = 'contact'
- leaveMessage.value = ''
- emojiVisible.value = false
-}
-
-/** 閫変腑 emoji锛氭嫾鍒扮暀瑷�鏈熬锛汧acePicker 鑷韩璐熻矗鍏抽棴闈㈡澘 */
-function handleEmojiSelect(emoji: string) {
- leaveMessage.value = `${leaveMessage.value}${emoji}`
-}
-
-/** 鍚堝苟 payload + 搴忓垪鍖� content锛沵erge 妯″紡涓嬩竴娆℃瀯閫狅紝棰勮 / 鍙戦�佸叡鐢� */
-const mergeBundle = computed(() => {
- if (
- state.mode !== ImForwardMode.MERGE ||
- !state.sourceConversation ||
- state.messages.length === 0
- ) {
- return null
- }
- const payload = buildMergeMessagePayload(state.messages, state.sourceConversation)
- return { payload, content: serializeMessage(payload) }
-})
-
-/** 鍚堝苟妯″紡棰勮锛氫粠 messages 鍓� N 鏉℃淳鐢熴�寋鏄电О}锛歿鎽樿}銆� */
-const mergePreview = computed(() => {
- const payload = mergeBundle.value?.payload
- if (!payload) {
- return null
- }
- const lines = payload.messages
- .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
- .map((item) => `${item.senderNickname}锛�${summarizeMessageContent(item)}`)
- return { title: payload.title, lines }
-})
-
-/** 閫愭潯妯″紡棰勮锛氬彇鍓� N 鏉℃憳瑕� */
-const singlePreviewLines = computed(() =>
- state.messages.slice(0, MESSAGE_MERGE_PREVIEW_LINES).map((m) => summarizeMessageContent(m))
-)
-
-/** 寰呭彂閫佺殑閫愭潯娑堟伅锛氬墺绂� quote 涓�娆★紝鍙戦�佸鐩爣鏃跺鐢� */
-const cleanedSinglePayloads = computed(() =>
- state.messages.map((m) => ({ type: m.type, content: removeQuotePayload(m.content) }))
-)
-
-/**
- * 缁欏崟涓洰鏍囧彂閫佽浆鍙戞秷鎭細merge 涓�娆� sendRaw銆乻ingle 鎸夋椂闂村簭閫愭潯 sendRaw
- *
- * 浠讳竴鍘熸秷鎭け璐ュ嵆鍋滄鍚庣画锛岃繑鍥� false 璁╀笂灞傝烦杩囩暀瑷�
- */
-async function forwardToTarget(target: Conversation): Promise<boolean> {
- if (state.mode === ImForwardMode.MERGE) {
- const content = mergeBundle.value?.content
- if (!content) {
- return false
- }
- return sendRaw(ImContentType.MERGE, content, { conversation: target })
- }
- for (const payload of cleanedSinglePayloads.value) {
- const ok = await sendRaw(payload.type, payload.content, { conversation: target })
- if (!ok) {
- return false
- }
- }
- return true
-}
-
-/**
- * 纭鍙戦�侊細鍗曟潯妯″紡閫愭潯寰幆锛涘悎骞舵ā寮忓彂涓�鏉� MergeMessage
- *
- * 鏂囨鑱氬悎锛氬叏閮ㄦ垚鍔熴�屽凡杞彂銆嶃�佸叏閮ㄥけ璐ャ�岃浆鍙戝け璐ワ細A銆丅銆嶃�侀儴鍒嗗け璐ャ�屽凡杞彂锛屼絾 X銆乊 澶辫触銆嶏紱
- * 鐣欒█鍙湪璇ョ洰鏍囩殑杞彂娑堟伅鍏ㄩ儴鎴愬姛鍚庢墠杩藉姞锛岄伩鍏嶉敊搴�
- */
-async function handleSend() {
- if (selectedKeys.value.length === 0) {
- return
- }
- if (state.messages.length === 0) {
- message.warning('娌℃湁鍙浆鍙戠殑娑堟伅')
- return
- }
- // 鍙嶆煡宸查�� conversation 瀵硅薄锛堟寜 selectedKeys 鏁扮粍椤哄簭锛屽嵆鐐瑰嚮椤哄簭锛�
- const candidates = candidateConversations.value
- const byKey = new Map(candidates.map((c) => [getConversationKey(c), c]))
- const targets = selectedKeys.value
- .map((key) => byKey.get(key))
- .filter((c): c is Conversation => c != null)
- if (targets.length === 0) {
- return
- }
- const leaveText = leaveMessage.value.trim()
- sending.value = true
- try {
- const tasks = targets.map(async (target) => {
- const forwardOk = await forwardToTarget(target)
- if (!forwardOk) {
- return { target, ok: false }
- }
- const ok = leaveText ? await send(leaveText, { conversation: target }) : true
- return { target, ok }
- })
- const results = await Promise.all(tasks)
- const failedNames = results.filter((r) => !r.ok).map((r) => r.target.name || '鏈懡鍚嶄細璇�')
- // 鍛戒腑鐨勭洰鏍囩粺涓�鎺ㄥ埌鏈�杩戣浆鍙戝垪琛紙閮ㄥ垎澶辫触涔熸帹锛氱敤鎴风殑"鎰忓浘"宸茶〃杈撅級
- conversationStore.pushRecentForwardConversationKeyList(targets.map((c) => getConversationKey(c)))
- if (failedNames.length === 0) {
- message.success('宸茶浆鍙�')
- } else if (failedNames.length === targets.length) {
- message.error(`杞彂澶辫触锛�${failedNames.join('銆�')}`)
- } else {
- message.warning(`宸茶浆鍙戯紝浣� ${failedNames.join('銆�')} 澶辫触`)
- }
- if (multiSelect.state.active) {
- multiSelect.exit()
- }
- visible.value = false
- } finally {
- sending.value = false
- }
-}
-
-/**
- * 濂藉弸瑙嗗浘鍙戦�侊細鍏堝缓缇わ紙鍚屾椂閭�璇锋墍閫夊ソ鍙嬶級鈫� 缁欐柊缇ゅ鐢� forwardToTarget 杞彂 鈫� 鍙戠暀瑷� 鈫� 鍏冲脊绐�
- *
- * 璺熶細璇濊鍥剧殑宸埆锛氬厛瑕� createGroup 鎷垮埌 groupId锛屼箣鍚庢瀯閫� GROUP 涓存椂 conversation 鍠傜粰宸叉湁鐨� forwardToTarget
- * 锛坰endRaw 鍐呴儴浼氳嚜鍔� insertMessage 鎶婃柊缇ょ櫥璁拌繘 store锛屾渶杩戣浆鍙戝垪琛ㄤ篃鑳芥甯告帹锛�
- */
-async function handleCreateGroupAndSend() {
- if (selectedFriendIds.value.length === 0) {
- return
- }
- if (state.messages.length === 0) {
- message.warning('娌℃湁鍙浆鍙戠殑娑堟伅')
- return
- }
- const byId = new Map(friends.value.map((f) => [f.id, f]))
- const members = selectedFriendIds.value
- .map((id) => byId.get(id))
- .filter((f): f is FriendLite => f != null)
- if (members.length === 0) {
- return
- }
- sending.value = true
- try {
- const memberUserIds = members.map((m) => m.id)
- const name = buildDefaultGroupName(members)
- const group = await createGroup({ name, memberUserIds, joinApproval: false })
- if (!group?.id) {
- throw new Error('鍒涘缓缇ゅけ璐ワ細鏈繑鍥炵兢缂栧彿')
- }
- // upsert 杩� groupStore锛岀渷涓�娆� fetchGroupList
- groupStore.upsertGroup({
- id: group.id,
- name: group.name,
- avatar: group.avatar,
- notice: group.notice,
- ownerUserId: group.ownerUserId
- })
- // 缁欐柊缇ゆ瀯閫犱竴涓复鏃� conversation 瀵硅薄缁� forwardToTarget 鐢紱sendRaw 鍐呴儴浼氳嚜鍔� insertMessage 鐧昏
- const newConversation: Conversation = {
- type: ImConversationType.GROUP,
- targetId: group.id,
- name: getGroupDisplayName(group) || name,
- avatar: group.avatar || '',
- unreadCount: 0,
- lastContent: '',
- lastSendTime: 0
- }
- const forwardOk = await forwardToTarget(newConversation)
- if (forwardOk) {
- const leaveText = leaveMessage.value.trim()
- if (leaveText) {
- await send(leaveText, { conversation: newConversation })
- }
- conversationStore.pushRecentForwardConversationKeyList([getConversationKey(newConversation)])
- message.success('宸插垱寤虹兢鑱婂苟杞彂')
- } else {
- message.warning('缇ゅ凡鍒涘缓锛屼絾娑堟伅杞彂澶辫触锛岃绋嶅悗鍦ㄧ兢閲岄噸璇�')
- }
- // 缁熶竴閫�澶氶�� + 鍏冲脊绐楋細鎴愬姛 / 澶辫触閮借閫�婧愪細璇濈殑澶氶�夋�侊紝閬垮厤閬楃暀
- if (multiSelect.state.active) {
- multiSelect.exit()
- }
- visible.value = false
- } finally {
- sending.value = false
- }
-}
-</script>
-
-<template>
- <!--
- 杞彂娑堟伅锛堥�愭潯 / 鍚堝苟锛夛細閫夌洰鏍囦細璇� + 鐣欒█鍚庢壒閲忓彂閫�
- - dialog 澹虫湰缁勪欢鎸佹湁锛涢�夋嫨 UI 濮旀墭 ConversationPickerPanel / FriendPickerPanel
- - view='conversation'锛氶�夊凡鏈変細璇濆彂閫侊紙榛樿瑙嗗浘锛�
- - view='contact'锛氫粠銆屽垱寤鸿亰澶┿�嶅叆鍙h繘鍏ワ紝閫夊ソ鍙嬪缓缇ゅ啀杞彂锛屼笟鍔″3灞傚垏瑙嗗浘
- - footer slot 濉為瑙堝崱锛堝悎骞� / 閫愭潯涓嶅悓瑙嗚锛�+ 鐣欒█ + 鎻愪氦鎸夐挳
- - 瀵瑰鎺ュ彛娌跨敤锛歳ef + open({ mode, messages, sourceConversation })
- -->
- <Modal
- v-model:open="visible"
- width="720px"
- :mask-closable="false"
- :footer="view === 'conversation' ? null : undefined"
- class="im-picker-dialog im-forward-dialog"
- >
- <template #header>
- <div class="flex gap-2 items-center">
- <Icon
- v-if="view === 'contact'"
- icon="ant-design:arrow-left-outlined"
- :size="16"
- class="cursor-pointer im-forward-dialog__back"
- @click="view = 'conversation'"
- />
- <span class="text-base text-[var(--ant-color-text)]">
- {{ headerTitle }}
- </span>
- </div>
- </template>
-
- <div class="h-[480px]">
- <!-- 浼氳瘽瑙嗗浘锛氶�夊凡鏈変細璇濊浆鍙� -->
- <ConversationPickerPanel
- v-if="view === 'conversation'"
- v-model:selected-keys="selectedKeys"
- :conversations="candidateConversations"
- :recent-forward-conversation-keys="conversationStore.recentForwardConversationKeys"
- :show-create-chat="true"
- @create-chat="handleSwitchToContact"
- @remove-recent="conversationStore.removeRecentForwardConversationKey"
- >
- <template #footer>
- <div class="flex flex-col gap-3 px-4 py-3">
- <!-- 鍚堝苟妯″紡棰勮锛氥�孾鑱婂ぉ璁板綍] 鏍囬 + 鎽樿鍒楄〃銆嶉瑙堝崱 -->
- <div
- v-if="state.mode === ImForwardMode.MERGE && mergePreview"
- class="flex flex-col w-full overflow-hidden rounded-md bg-[var(--ant-color-bg-container)] border border-solid border-[var(--ant-color-border-secondary)]"
- >
- <div
- class="px-3 py-2 text-sm font-medium truncate text-[var(--ant-color-text)]"
- >
- {{ mergePreview.title }}
- </div>
- <div class="flex flex-col px-3 pb-2 gap-0.5">
- <div
- v-for="(line, idx) in mergePreview.lines"
- :key="idx"
- class="text-12px text-[var(--ant-color-text-secondary)] truncate"
- >
- {{ line }}
- </div>
- </div>
- <div
- class="px-3 py-1 text-12px border-t border-t-solid text-[var(--ant-color-text-placeholder)] border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-tertiary)]"
- >
- 鑱婂ぉ璁板綍
- </div>
- </div>
-
- <!-- 閫愭潯妯″紡棰勮锛氭秷鎭暟 + 棣栨潯鎽樿 -->
- <div
- v-else-if="state.mode === ImForwardMode.SINGLE && singlePreviewLines.length > 0"
- class="flex flex-col w-full overflow-hidden rounded-md bg-[var(--ant-color-bg-container)] border border-solid border-[var(--ant-color-border-secondary)]"
- >
- <div class="flex flex-col px-3 py-2 gap-0.5">
- <div
- v-for="(line, idx) in singlePreviewLines"
- :key="idx"
- class="text-13px text-[var(--ant-color-text)] truncate"
- >
- {{ line }}
- </div>
- </div>
- <div
- class="px-3 py-1 text-12px border-t border-t-solid text-[var(--ant-color-text-placeholder)] border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-tertiary)]"
- >
- 鍏� {{ state.messages.length }} 鏉℃秷鎭�
- </div>
- </div>
-
- <!-- 鐣欒█锛堝崟琛岋級锛氬彸渚ц〃鎯呮寜閽Е鍙� FacePicker锛涢�変腑 emoji 鎷煎埌鏈熬 -->
- <div class="relative">
- <Input v-model:value="leaveMessage" :maxlength="100" placeholder="缁欐湅鍙嬬暀瑷�">
- <template #suffix>
- <Icon
- icon="ant-design:smile-outlined"
- :size="18"
- class="cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-primary)]"
- @click.stop="emojiVisible = !emojiVisible"
- />
- </template>
- </Input>
- <FacePicker
- v-model:visible="emojiVisible"
- mode="emoji"
- class="bottom-full right-0 mb-2"
- @select-emoji="handleEmojiSelect"
- />
- </div>
-
- <div class="flex gap-2 justify-end">
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="sending"
- :disabled="selectedKeys.length === 0"
- @click="handleSend"
- >
- {{ confirmButtonText }}
- </Button>
- </div>
- </div>
- </template>
- </ConversationPickerPanel>
-
- <!-- 濂藉弸瑙嗗浘锛氶�夊ソ鍙嬪缓缇ゅ悗杞彂 -->
- <FriendPickerPanel v-else v-model:selected-ids="selectedFriendIds" :friends="friends" />
- </div>
-
- <!-- 濂藉弸瑙嗗浘鐨� dialog footer锛氬缓缇ゅ苟杞彂 -->
- <template v-if="view === 'contact'" #footer>
- <Button @click="visible = false">鍙栨秷</Button>
- <Button
- type="primary"
- :loading="sending"
- :disabled="selectedFriendIds.length === 0"
- @click="handleCreateGroupAndSend"
- >
- 鍒涘缓缇よ亰骞跺彂閫�
- </Button>
- </template>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-/* 澶嶇敤閫夋嫨绫诲脊绐楃殑鍏叡 mixin锛� 鍐呴儴鏄� :deep 绌块�� el-dialog 鍐呴儴 header / body 鐨勬牱寮忥紝鏃犳硶鏇挎崲涓哄伐鍏风被 */
-@use '#/views/im/home/components/picker/picker-dialog' as picker;
-
-.im-picker-dialog {
- @include picker.styles;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/forward/message-merge-detail-dialog.vue b/src/views/im/home/pages/conversation/components/message/forward/message-merge-detail-dialog.vue
deleted file mode 100644
index 509f653..0000000
--- a/src/views/im/home/pages/conversation/components/message/forward/message-merge-detail-dialog.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../../packages/icons/src'
-
-import { Modal } from 'ant-design-vue'
-
-import { UserAvatar } from '#/views/im/home/components/user'
-import { useVoicePlayer } from '#/views/im/home/composables/useVoicePlayer'
-import { type MergeMessage, parseMessage } from '#/views/im/utils/message'
-import { formatMergeItemTime } from '#/views/im/utils/time'
-
-import { MessageBubble } from '..'
-
-defineOptions({ name: 'ImMessageMergeDetailDialog' })
-
-const voicePlayer = useVoicePlayer()
-const visible = ref(false)
-
-const stack = ref<MergeMessage[]>([]) // 宓屽灞傜骇鏍堬紝瀛� parsed payload 閬垮厤鍒囧眰閲� parse
-
-defineExpose({
- /** 鎵撳紑璇︽儏寮圭獥锛屼紶鍏ラ《灞傚悎骞舵秷鎭� content */
- open(content: string) {
- const payload = parseMessage<MergeMessage>(content)
- stack.value = payload ? [payload] : []
- visible.value = true
- }
-})
-
-/** 褰撳墠灞� payload */
-const currentPayload = computed<MergeMessage | null>(
- () => stack.value[stack.value.length - 1] ?? null
-)
-
-/** 宓屽鍚堝苟姘旀场鐐瑰嚮锛氳В鏋� content 鍚庡帇鏍堣繘鍏ヤ笅涓�灞� */
-function handleNestedOpen(content: string) {
- const payload = parseMessage<MergeMessage>(content)
- if (payload) {
- stack.value.push(payload)
- }
-}
-
-/** 椤堕儴杩斿洖绠ご鐐瑰嚮锛氬脊鍑烘爤椤跺洖鍒颁笂涓�灞� */
-function handleBack() {
- if (stack.value.length > 1) {
- stack.value.pop()
- }
-}
-
-/** 寮圭獥鍏抽棴锛氭竻鏍� + 鍋滆闊筹紝涓嬫鎵撳紑浠庨《灞傞噸鏂板紑濮� */
-function handleClose() {
- stack.value = []
- voicePlayer.stop()
-}
-
-</script>
-
-<template>
- <Modal
- v-model:open="visible"
- width="560px"
- :footer="null"
- :mask-closable="false"
- class="im-merge-detail-dialog"
- :closable="true"
- @close="handleClose"
- >
- <template #header>
- <div class="flex items-center gap-2">
- <Icon
- v-if="stack.length > 1"
- icon="ant-design:left-outlined"
- class="cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[var(--ant-color-text)]"
- @click="handleBack"
- />
- <span class="text-base font-medium truncate">{{ currentPayload?.title || '鑱婂ぉ璁板綍' }}</span>
- </div>
- </template>
-
- <div v-if="currentPayload" class="flex flex-col h-[480px]">
- <div
- class="px-4 py-2 text-12px text-[var(--ant-color-text-secondary)] border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- 浠ヤ笅鏄� {{ currentPayload.messages.length }} 鏉℃秷鎭�
- </div>
- <!-- overflow-y-auto锛氬悎骞舵秷鎭鏃跺湪瀹氶珮寮圭獥鍐呭彲婊氬姩锛屽榻� Vue3 鐨� el-scrollbar -->
- <div class="flex-1 overflow-y-auto">
- <div
- v-for="(item, idx) in currentPayload.messages"
- :key="idx"
- class="flex gap-3 items-start px-4 py-3 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <UserAvatar
- :url="item.senderAvatar"
- :name="item.senderNickname"
- :size="36"
- :clickable="false"
- />
- <div class="flex-1 min-w-0">
- <div class="flex gap-2 items-baseline">
- <span class="text-13px font-medium text-[var(--ant-color-text)] truncate">
- {{ item.senderNickname }}
- </span>
- <span class="text-12px text-[var(--ant-color-text-secondary)] flex-shrink-0">
- {{ formatMergeItemTime(item.sendTime) }}
- </span>
- </div>
- <!-- 澶嶇敤涓昏亰澶╂皵娉℃覆鏌擄紱MERGE 宓屽鐐瑰嚮 push 褰撳墠灞� -->
- <div class="mt-1.5">
- <MessageBubble
- :type="item.type"
- :content="item.content"
- @open-merge="handleNestedOpen($event)"
- />
- </div>
- </div>
- </div>
- </div>
- </div>
- </Modal>
-</template>
-
-<style scoped>
-/* :deep 绌块�� el-dialog 鍐呴儴 body锛屽幓鎺夐粯璁ゅ唴杈硅窛锛岃婊氬姩鍖鸿创杈� */
-.im-merge-detail-dialog :deep(.el-dialog__body) {
- padding: 0;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/group-pinned-message.vue b/src/views/im/home/pages/conversation/components/message/group-pinned-message.vue
deleted file mode 100644
index 216ebb7..0000000
--- a/src/views/im/home/pages/conversation/components/message/group-pinned-message.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-<script lang="ts" setup>
-import type { Message } from '../../../../types'
-
-import { computed, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { Button, message } from 'ant-design-vue'
-
-import { unpinGroupMessage as apiUnpinGroupMessage } from '#/api/im/group'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants'
-import { resolveConversationLastContent } from '#/views/im/utils/conversation'
-import { getSenderDisplayName, isGroupQuit } from '#/views/im/utils/user'
-
-import { useGroupStore } from '../../../../store/groupStore'
-
-defineOptions({ name: 'ImGroupPinnedMessage' })
-
-const props = defineProps<{
- /** 褰撳墠缇ょ紪鍙凤紙鑷浠� groupStore 鎷垮畬鏁� Group锛岃窡闅忓搷搴斿紡锛� */
- groupId: number
-}>()
-
-const emit = defineEmits<{
- /** 鐐瑰嚮缃《娑堟伅 鈫� 鐖剁骇 MessagePanel 婊氬姩瀹氫綅鍒板師娑堟伅浣嶇疆 */
- locate: [messageId: number]
-}>()
-
-const groupStore = useGroupStore()
-
-/** 褰撳墠缇わ紙鍚� pinnedMessages锛� */
-const group = computed(() => groupStore.getGroup(props.groupId))
-
-const expanded = ref(false)
-const removingId = ref<null | number>(null)
-
-// 鍒囩兢鏃堕噸缃睍寮� / 绉婚櫎涓姸鎬侊細鏈湴 ref 涓嶈窡闅� groupId锛屽惁鍒欎笂涓�缇�"灞曞紑"鎴�"绉婚櫎涓�"浼氬甫鍒版柊缇�
-watch(
- () => props.groupId,
- () => {
- expanded.value = false
- removingId.value = null
- }
-)
-
-/** 褰撳墠缇ょ疆椤舵秷鎭垪琛紙鐩存帴璧� group.value锛岃窡闅忓搷搴斿紡锛� */
-const pinnedMessages = computed<Message[]>(() => group.value?.pinnedMessages ?? [])
-
-/** 椤堕儴鑳跺泭灞曠ず鐨勬渶鏂颁竴鏉★紙鍗冲垪琛ㄦ渶鍚庝竴鏉★紝pin 椤哄簭杩藉姞锛� */
-const latest = computed<Message | null>(
- () => pinnedMessages.value[pinnedMessages.value.length - 1] ?? null
-)
-
-/** 褰撳墠鐢ㄦ埛鏄惁缇や富 / 绠$悊鍛橈紙鍐冲畾鏄惁鏄剧ず銆岀Щ闄ゃ�嶅叆鍙o級 */
-const canManage = computed(() => {
- // 鍘嗗彶閫�缇ょ兢锛氭湰鍦扮紦瀛樻畫鐣欐椂涔熶笉缁欍�岀Щ闄ゃ�嶅叆鍙�
- if (isGroupQuit(group.value)) {
- return false
- }
- const myId = getCurrentUserId()
- const role = group.value?.members?.find((m) => m.userId === myId)?.role
- return role === ImGroupMemberRole.OWNER || role === ImGroupMemberRole.ADMIN
-})
-
-/** 椤堕儴鑳跺泭鐐瑰嚮锛氬崟鏉$洿鎺ヨ烦杞師娑堟伅浣嶇疆锛涘鏉″垏鎹㈠睍寮� / 鎶樺彔 */
-function handleTopClick() {
- if (!latest.value) {
- return
- }
- if (pinnedMessages.value.length === 1) {
- handleLocate(latest.value)
- return
- }
- expanded.value = !expanded.value
-}
-
-/** 鐐瑰嚮缃《娑堟伅琛� 鈫� 瑙﹀彂璺宠浆 + 鏀惰捣寮瑰嚭灞� */
-function handleLocate(message: Message) {
- if (!message.id) {
- return
- }
- emit('locate', message.id)
- expanded.value = false
-}
-
-/** 缃《娑堟伅鍙戦�佷汉鏄剧ず鍚� */
-function getSenderName(message: Message): string {
- return group.value
- ? getSenderDisplayName(message.senderId, ImConversationType.GROUP, group.value.id)
- : ''
-}
-
-/** 缃《娑堟伅棰勮鏂囨湰锛氬鐢ㄤ細璇濇渶鍚庝竴鏉℃憳瑕侀�昏緫锛圼鍥剧墖] / [鏂囦欢] / 鏂囨湰绛夛級 */
-function getPreview(message: Message): string {
- return group.value
- ? resolveConversationLastContent(message, ImConversationType.GROUP, group.value.id)
- : ''
-}
-
-/** 绉婚櫎缃《锛氳皟鍚庣 API锛宭oading 鏈熼棿绂佹閲嶅鐐癸紱鍚庣骞挎挱 GROUP_MESSAGE_UNPIN 鐢� dispatcher 鑷姩鍚屾鏈湴 */
-async function handleRemove(pinnedMessage: Message) {
- if (!group.value || !pinnedMessage.id || removingId.value !== null) {
- return
- }
- removingId.value = pinnedMessage.id
- try {
- await apiUnpinGroupMessage({ id: group.value.id, messageId: pinnedMessage.id })
- message.success('宸插彇娑堢疆椤�')
- } finally {
- removingId.value = null
- }
-}
-</script>
-
-<template>
- <!-- 缇よ亰缃《娑堟伅锛氫粎缇よ亰 + 鏈夌疆椤舵椂鏄剧ず锛屾偓鎸傚湪缇よ亰澶撮儴涓嬫柟宸︿笂瑙掞紱涓嶅崰鏁磋锛堝榻愬井淇� PC锛� -->
- <div
- v-if="latest"
- class="im-group-pinned-message relative flex flex-shrink-0 flex-col items-start px-4 pt-1.5 pb-2 bg-[var(--ant-color-fill-secondary)]"
- >
- <!-- 椤堕儴鑳跺泭锛氬崟鏉$偣鍑昏烦杞紱澶氭潯鎶樺彔鐐瑰嚮灞曞紑锛涘鏉″睍寮�鐐瑰嚮鎶樺彔 -->
- <div
- class="flex items-center gap-1.5 w-[360px] px-3 py-1.5 rounded-[10px] text-13px text-[var(--ant-color-text)] bg-[var(--ant-color-bg-container)] shadow-[0_1px_2px_rgba(0,0,0,0.04)] cursor-pointer hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleTopClick"
- >
- <Icon
- icon="ant-design:pushpin-outlined"
- :size="14"
- class="flex-shrink-0 text-[var(--ant-color-warning)]"
- />
- <span class="flex-shrink-0 text-[var(--ant-color-text-secondary)]">{{ getSenderName(latest) }}锛�</span>
- <span class="flex-1 min-w-0 truncate">{{ getPreview(latest) }}</span>
- <!-- 鍗曟潯锛氱Щ闄ゆ寜閽紱澶氭潯鎶樺彔锛氬叡 N 鏉★紱澶氭潯灞曞紑锛氭敹璧风澶� -->
- <Button
- v-if="pinnedMessages.length === 1 && canManage"
- type="link"
- size="small"
- :loading="removingId === latest.id"
- class="flex-shrink-0 !h-auto !p-0 text-13px"
- @click.stop="handleRemove(latest)"
- >
- 绉婚櫎
- </Button>
- <template v-else-if="pinnedMessages.length > 1">
- <span class="flex-shrink-0 text-[var(--ant-color-text-secondary)] text-12px">
- 鍏� {{ pinnedMessages.length }} 鏉�
- </span>
- <Icon
- :icon="expanded ? 'ant-design:up-outlined' : 'ant-design:down-outlined'"
- :size="11"
- class="flex-shrink-0 text-[var(--ant-color-text-placeholder)]"
- />
- </template>
- </div>
-
- <!-- 澶氭潯灞曞紑锛氭祬鑹查潰鏉垮寘瑁瑰畬鏁村垪琛紝姣忔潯鐙珛鑳跺泭锛涚偣鍑昏烦杞埌瀵瑰簲娑堟伅浣嶇疆 -->
- <div
- v-if="pinnedMessages.length > 1 && expanded"
- class="im-group-pinned-message__list absolute top-full left-1.5 z-10 flex flex-col gap-2.5 w-[380px] p-3 rounded-xl bg-[var(--ant-color-bg-container)] shadow-[0_6px_16px_rgba(0,0,0,0.12)]"
- style="margin-top: -1px"
- >
- <div
- v-for="msg in pinnedMessages"
- :key="msg.id"
- class="flex items-center gap-1.5 w-full px-3 py-1.5 rounded-[10px] text-13px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-secondary)] cursor-pointer hover:bg-[var(--ant-color-bg-container)]"
- @click="handleLocate(msg)"
- >
- <Icon
- icon="ant-design:pushpin-outlined"
- :size="14"
- class="flex-shrink-0 text-[var(--ant-color-warning)]"
- />
- <span class="flex-shrink-0 text-[var(--ant-color-text-secondary)]">
- {{ getSenderName(msg) }}锛�
- </span>
- <span class="flex-1 min-w-0 truncate">{{ getPreview(msg) }}</span>
- <Button
- v-if="canManage"
- type="link"
- size="small"
- :loading="removingId === msg.id"
- class="flex-shrink-0 !h-auto !p-0 text-13px"
- @click.stop="handleRemove(msg)"
- >
- 绉婚櫎
- </Button>
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 寮瑰嚭灞傛湞涓婄殑涓夎绠ご锛涜蛋 ::before + 4 杈� border 閰嶈壊鐢伙紝棰滆壊璺熷脊鍑哄眰 background 涓�鑷� */
-.im-group-pinned-message__list::before {
- content: '';
- position: absolute;
- top: -8px;
- left: 184px;
- width: 0;
- height: 0;
- border-left: 8px solid transparent;
- border-right: 8px solid transparent;
- border-bottom: 8px solid var(--ant-color-bg-container);
- filter: drop-shadow(0 -2px 1px rgba(0, 0, 0, 0.04));
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/group-request-pending.vue b/src/views/im/home/pages/conversation/components/message/group-request-pending.vue
deleted file mode 100644
index 1b3f292..0000000
--- a/src/views/im/home/pages/conversation/components/message/group-request-pending.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { ImGroupMemberRole } from '#/views/im/utils/constants'
-
-import { GroupRequestListDialog } from '../../../../components/group'
-import { useGroupRequestStore } from '../../../../store/groupRequestStore'
-import { useGroupStore } from '../../../../store/groupStore'
-
-defineOptions({ name: 'ImGroupRequestPending' })
-
-const props = defineProps<{
- groupId: number
-}>()
-
-const groupStore = useGroupStore()
-const groupRequestStore = useGroupRequestStore()
-
-const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>() // 鐢宠鍒楄〃寮圭獥 ref锛歨andleOpen 璋� open({ groupId }) 瑙﹀彂
-
-/** 鎵撳紑褰撳墠缇ょ殑杩涚兢鐢宠鍒楄〃 */
-function handleOpen() {
- requestListDialogRef.value?.open({ groupId: props.groupId })
-}
-
-/** 褰撳墠缇わ紙鍚� ownerUserId / members锛� */
-const group = computed(() => groupStore.getGroup(props.groupId))
-
-/** 褰撳墠鐢ㄦ埛鍦ㄧ兢閲岀殑瑙掕壊锛涗紭鍏堢敤 group.members锛屾噿鍔犺浇鏈埌鏃跺洖閫�鍒� ownerUserId 鐩村垽 */
-const myRole = computed(() => {
- const myId = getCurrentUserId()
- if (group.value?.ownerUserId === myId) {
- return ImGroupMemberRole.OWNER
- }
- return group.value?.members?.find((m) => m.userId === myId)?.role
-})
-
-/** 浠呯兢涓� / 绠$悊鍛樺彲瑙� */
-const canManage = computed(
- () => myRole.value === ImGroupMemberRole.OWNER || myRole.value === ImGroupMemberRole.ADMIN
-)
-
-/** 褰撳墠缇ゆ湭澶勭悊鐢宠鏁帮紱浠� store 娲剧敓 */
-const pendingCount = computed(() => groupRequestStore.getUnhandledGroupRequestCount(props.groupId))
-</script>
-
-<template>
- <!--
- 缇ら《閮ㄣ�屽緟澶勭悊鍔犵兢鐢宠銆嶆í骞�
- - 浠呭綋鐧诲綍鐢ㄦ埛鏄缇� owner / admin 涓旇缇や笅鏈鐞嗙敵璇锋暟 > 0 鏃舵樉绀�
- - count 浠� groupRequestStore 娲剧敓锛堝叏灞�瀛橈級锛涙湰绔鐞� / WS 閫氱煡鍒拌揪鍚� store 鑷姩鏇存柊
- - 鐐瑰嚮妯箙鎵撳紑 GroupRequestListDialog锛堝惈鍘嗗彶宸插鐞嗚褰曪級锛屼笉鍐嶅氨鍦板睍寮�
- -->
- <div
- v-if="canManage && pendingCount > 0"
- class="flex flex-shrink-0 flex-col items-start px-4 pt-1.5 pb-2 bg-[var(--ant-color-fill-secondary)]"
- >
- <div
- class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-[10px] text-13px text-[var(--ant-color-text)] bg-[var(--ant-color-bg-container)] cursor-pointer shadow-[0_1px_2px_rgba(0,0,0,0.04)] transition-colors hover:bg-[var(--ant-color-fill-tertiary)]"
- @click="handleOpen"
- >
- <Icon
- icon="ant-design:user-add-outlined"
- :size="14"
- class="im-group-request-pending__icon flex-shrink-0 text-[var(--ant-color-success)]"
- />
- <span class="flex-1 min-w-0 truncate"> 鏂拌繘缇ょ敵璇凤紙{{ pendingCount }}锛� </span>
- <Icon
- icon="ant-design:right-outlined"
- :size="11"
- class="flex-shrink-0 text-[var(--ant-color-text-placeholder)]"
- />
- </div>
-
- <!-- 鐢宠鍒楄〃 dialog锛氬鐢ㄥ悓涓�缁勪欢锛岄伩鍏嶇兢绠$悊闈㈡澘涓庝細璇濋《閮ㄥ悇鍐欎竴浠� -->
- <GroupRequestListDialog ref="requestListDialogRef" />
- </div>
-</template>
-
-<style scoped>
-/* :deep 绌块�� Icon 瀛愮粍浠� DOM锛涘己鍒� svg 璧� currentColor 搴斿鏆楄壊妯″紡 el-icon 鍏ㄥ眬鑹茶鐩� */
-.im-group-request-pending__icon :deep(svg) {
- fill: currentColor !important;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/index.ts b/src/views/im/home/pages/conversation/components/message/index.ts
deleted file mode 100644
index 025a74c..0000000
--- a/src/views/im/home/pages/conversation/components/message/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export { default as GroupPinnedMessage } from './group-pinned-message.vue';
-export { default as GroupRequestPending } from './group-request-pending.vue';
-export { default as MaterialBubble } from './material-bubble.vue';
-export { default as MessageBubble } from './message-bubble.vue';
-export { default as MessageHistory } from './message-history.vue';
-export { default as MessageItem } from './message-item.vue';
-export { default as MessagePanel } from './message-panel.vue';
-export { default as MessageReadStatus } from './message-read-status.vue';
-export { default as ReplyPreview } from './reply-preview.vue';
-export { default as TipSegments } from './tip-segments.vue';
diff --git a/src/views/im/home/pages/conversation/components/message/material-bubble.vue b/src/views/im/home/pages/conversation/components/message/material-bubble.vue
deleted file mode 100644
index 7964b0b..0000000
--- a/src/views/im/home/pages/conversation/components/message/material-bubble.vue
+++ /dev/null
@@ -1,170 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { openSafeUrl } from '../../../../../../../packages/utils/src'
-
-import { Modal, Spin } from 'ant-design-vue'
-
-import { getChannelMaterial } from '#/api/im/channel/material'
-import { useChannelStore } from '#/views/im/home/store/channelStore'
-import { useConversationStore } from '#/views/im/home/store/conversationStore'
-import { ImConversationType } from '#/views/im/utils/constants'
-import { type MaterialMessage, parseMessage } from '#/views/im/utils/message'
-
-const props = defineProps<{
- content: string
-}>()
-
-const conversationStore = useConversationStore()
-const channelStore = useChannelStore()
-
-/** 褰撳墠鏄惁鍦ㄥ叕浼楀彿 / 棰戦亾浼氳瘽閲岋細鍐冲畾璧板ぇ鍗$墖杩樻槸绱у噾杞彂鍗$墖 */
-const isChannelView = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
-)
-
-/** 鍙嶅簭鍒楀寲 content JSON 涓� payload 瀵硅薄 */
-const payload = computed<MaterialMessage>(
- () => parseMessage<MaterialMessage>(props.content) ?? {}
-)
-
-/** 鏉ユ簮棰戦亾锛涚揣鍑戝崱搴曢儴娓叉煋澶村儚 + 鍚嶇О */
-const sourceChannel = computed(() =>
- payload.value.channelId ? channelStore.getChannel(payload.value.channelId) : undefined
-)
-
-const detailVisible = ref(false)
-const detailLoading = ref(false)
-const detailHtml = ref('')
-
-// 鐐瑰嚮琛屼负锛歶rl 闈炵┖璺冲閾撅紱涓虹┖鍒欐寜 payload.materialId 鎷夊瘜鏂囨湰姝f枃锛屽叏灞� dialog 娓叉煋
-const onClick = async () => {
- if (payload.value.url) {
- openSafeUrl(payload.value.url)
- return
- }
- if (!payload.value.materialId) {
- return
- }
- detailVisible.value = true
- detailLoading.value = true
- detailHtml.value = ''
- try {
- const material = await getChannelMaterial(payload.value.materialId)
- detailHtml.value = material?.content ?? ''
- } catch (error) {
- console.error('[Material] 鎷夊彇姝f枃澶辫触', error)
- } finally {
- detailLoading.value = false
- }
-}
-</script>
-
-<template>
- <!-- 鍏紬鍙蜂細璇濆唴澶у崱鐗囷細瀵归綈寰俊鍏紬鍙峰崟鍥炬枃鍗★紙灏侀潰 9:5 + 涓嬫柟鐧藉簳鍔犵矖鏍囬鏉★級 -->
- <div
- v-if="isChannelView"
- class="material-card cursor-pointer w-full overflow-hidden rounded-lg bg-[var(--ant-color-bg-container)] border border-solid border-[var(--im-border-color-lighter)]"
- @click="onClick"
- >
- <img v-if="payload.coverUrl" class="block w-full h-[200px] object-cover" :src="payload.coverUrl" />
- <div class="px-3.5 py-3 text-15px font-600 leading-[1.4] text-[var(--ant-color-text)] line-clamp-2">
- {{ payload.title || '(鏃犳爣棰�)' }}
- </div>
- </div>
-
- <!-- 绉佽亰 / 缇よ亰閲岃杞彂鐨勭礌鏉愮揣鍑戝崱鐗囷細鏍囬 + 鎽樿鍦ㄥ乏銆佸皬灏侀潰鍦ㄥ彸銆佸簳閮ㄩ閬撳ご鍍� + 鍚嶇О锛堝榻愬井淇″叕浼楀彿杞彂鍗★級 -->
- <div
- v-else
- class="material-card cursor-pointer flex flex-col w-[260px] px-3.5 pt-3 pb-2.5 rounded-lg bg-[var(--ant-color-bg-container)] border border-solid border-[var(--im-border-color-lighter)]"
- @click="onClick"
- >
- <div class="flex gap-2.5 items-start">
- <div class="flex flex-1 flex-col gap-1.5 min-w-0">
- <div class="text-15px font-600 leading-[1.4] text-[var(--ant-color-text)] line-clamp-2 break-all">
- {{ payload.title || '(鏃犳爣棰�)' }}
- </div>
- <div
- v-if="payload.summary"
- class="text-12px leading-[1.5] text-[var(--ant-color-text-secondary)] line-clamp-2 break-all"
- >
- {{ payload.summary }}
- </div>
- </div>
- <img
- v-if="payload.coverUrl"
- class="flex-shrink-0 w-[60px] h-[60px] object-cover rounded bg-[var(--ant-color-fill-secondary)]"
- :src="payload.coverUrl"
- />
- </div>
- <div class="flex items-center gap-1.5 mt-2.5 pt-2 border-t border-t-solid border-[var(--im-border-color-lighter)] text-12px text-[var(--ant-color-text-secondary)]">
- <img
- v-if="sourceChannel?.avatar"
- class="w-4 h-4 rounded-full object-cover flex-shrink-0"
- :src="sourceChannel.avatar"
- />
- <Icon v-else icon="ep:promotion" :size="14" />
- <span class="truncate">{{ sourceChannel?.name || '棰戦亾娑堟伅' }}</span>
- </div>
- </div>
-
- <!-- 瀵屾枃鏈鎯咃細鍏ㄥ睆寮圭獥鎸夐渶鎸傝浇锛屽叧闂悗閲婃斁瀵屾枃鏈� DOM -->
- <Modal
- v-model:open="detailVisible"
- :title="payload.title || '璇︽儏'"
- :footer="null"
- width="100vw"
- wrap-class-name="im-material-detail-modal"
- destroy-on-close
- >
- <Spin :spinning="detailLoading" wrapper-class-name="w-full">
- <div class="material-detail-body max-w-[720px] mx-auto px-5 pt-6 pb-20 min-h-[60vh]">
- <div class="text-[22px] font-600 leading-[1.4] text-[var(--ant-color-text)] mb-5">
- {{ payload.title || '' }}
- </div>
- <div
- v-if="detailHtml"
- class="article-content text-15px leading-[1.75] text-[var(--ant-color-text)]"
- v-dompurify-html="detailHtml"
- ></div>
- <div
- v-else-if="!detailLoading"
- class="text-center text-[var(--ant-color-text-secondary)] mt-20"
- >
- 鏆傛棤姝f枃
- </div>
- </div>
- </Spin>
- </Modal>
-</template>
-
-<style scoped lang="scss">
-/* hover 闃村奖 + transition锛氬悎鍐欎竴澶勶紝鍐欐硶鏇寸揣鍑� */
-.material-card {
- transition: box-shadow 0.15s ease;
-
- &:hover {
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
- }
-}
-
-/* :deep 绌块�� v-dompurify-html 娓叉煋鐨勫唴宓� DOM锛涚粺涓�鎺у埗瀵屾枃鏈噷鐨� img / p / hN 鎺掔増 */
-.article-content {
- :deep(img) {
- max-width: 100%;
- height: auto;
- }
-
- :deep(p) {
- margin: 12px 0;
- }
-
- :deep(h1),
- :deep(h2),
- :deep(h3) {
- margin: 20px 0 12px;
- font-weight: 600;
- }
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/message-bubble.vue b/src/views/im/home/pages/conversation/components/message/message-bubble.vue
deleted file mode 100644
index 9f2692f..0000000
--- a/src/views/im/home/pages/conversation/components/message/message-bubble.vue
+++ /dev/null
@@ -1,414 +0,0 @@
-<script lang="ts" setup>
-import { computed, onBeforeUnmount } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { formatFileSize, openSafeUrl } from '../../../../../../../packages/utils/src'
-
-import { Image } from 'ant-design-vue'
-
-import { CardBubble } from '#/views/im/home/components/card'
-import { useVoicePlayer } from '#/views/im/home/composables/useVoicePlayer'
-import { MESSAGE_MERGE_PREVIEW_LINES } from '#/views/im/utils/config'
-import { ImContentType } from '#/views/im/utils/constants'
-import { summarizeMessageContent } from '#/views/im/utils/conversation'
-import {
- type AudioMessage,
- type CardMessage,
- type FaceMessage,
- type FileMessage,
- getFileIconInfo,
- type ImageMessage,
- type MentionCandidate,
- type MergeMessage,
- parseMessage,
- parseTextSegments,
- type TextMessage,
- type VideoMessage
-} from '#/views/im/utils/message'
-import { formatSeconds } from '#/views/im/utils/time'
-
-import MaterialBubble from './material-bubble.vue'
-import TipSegments from './tip-segments.vue'
-
-defineOptions({ name: 'ImMessageBubble' })
-
-const props = defineProps<{
- /** 娑堟伅 content锛圝SON 瀛楃涓诧級 */
- content: string
- /** TEXT 姘旀场鐨� @ mention 鍊欓�夊悕瀛楋紱涓嶄紶鍒欐枃鏈噷鐨� @xxx 閫�鍖栦负鏅�氭枃鏈� */
- mentions?: MentionCandidate[]
- /** 鏄惁鑷繁鍙戦�侊紝褰卞搷姘旀场閰嶈壊锛堢豢搴� vs 鐏板簳锛� */
- selfSend?: boolean
- /** 鍐呭绫诲瀷锛屽榻� ImContentType */
- type: number
- /** 濯掍綋涓婁紶杩涘害锛�0-100锛夛紱闈� null 鍗宠涓轰笂浼犱腑锛屾覆鏌撻伄缃� / 杩涘害鏉� */
- uploadProgress?: null | number
-}>()
-
-const emit = defineEmits<{
- /** 鍚嶇墖鐐瑰嚮锛氳皟鐢ㄦ柟鍐冲畾寮瑰崱鐗� / 璺崇兢绛夎涓� */
- clickCard: [card: CardMessage, e: MouseEvent]
- /** 鍚堝苟娑堟伅姘旀场鐐瑰嚮锛氳皟鐢ㄦ柟鍐冲畾寮� dialog 鎴栨爤鍐� push */
- openMerge: [content: string]
-}>()
-
-/** 鍚� type 鍒ゅ畾 */
-const isText = computed(() => props.type === ImContentType.TEXT)
-const isImage = computed(() => props.type === ImContentType.IMAGE)
-const isFile = computed(() => props.type === ImContentType.FILE)
-const isVoice = computed(() => props.type === ImContentType.VOICE)
-const isVideo = computed(() => props.type === ImContentType.VIDEO)
-const isFace = computed(() => props.type === ImContentType.FACE)
-const isCard = computed(() => props.type === ImContentType.CARD)
-const isMerge = computed(() => props.type === ImContentType.MERGE)
-const isMaterial = computed(() => props.type === ImContentType.MATERIAL)
-
-/** 濯掍綋涓婁紶涓細uploadProgress 闈� null 鍗宠涓轰笂浼犱腑 */
-const isUploading = computed(() => props.uploadProgress != null)
-const uploadProgress = computed(() => props.uploadProgress ?? 0)
-const uploadProgressText = computed(() => `${uploadProgress.value}%`)
-
-/**
- * 鍗曚竴 parse 鍏ュ彛锛歝ontent 涓�鍙樺彧 parse 涓�娆★紝鎸� type 鍒嗗彂鍒颁笅闈� 7 涓� payload
- *
- * 鍚勭被鍨� payload 鍏辩敤鍚屼竴妫� JSON 鏍戯紝閬垮厤 7 涓� computed 鍚勮嚜閲� parse 鍚屼竴浠� content
- */
-const parsedContent = computed<unknown>(() => parseMessage(props.content))
-
-const textPayload = computed(() =>
- isText.value ? (parsedContent.value as null | TextMessage) : null
-)
-
-/** 鏂囨湰姘旀场 segment 鏁扮粍锛歮ention 楂樹寒 + URL 鑷姩璇嗗埆 + 鏅�氭枃鏈笁娈垫嫾鎺� */
-const textSegments = computed(() => {
- const content = textPayload.value?.content
- if (!content) {
- return []
- }
- return parseTextSegments(content, props.mentions || [])
-})
-const imagePayload = computed(() =>
- isImage.value ? (parsedContent.value as ImageMessage | null) : null
-)
-const filePayload = computed(() =>
- isFile.value ? (parsedContent.value as FileMessage | null) : null
-)
-const voicePayload = computed(() =>
- isVoice.value ? (parsedContent.value as AudioMessage | null) : null
-)
-const videoPayload = computed(() =>
- isVideo.value ? (parsedContent.value as null | VideoMessage) : null
-)
-const cardPayload = computed(() =>
- isCard.value ? (parsedContent.value as CardMessage | null) : null
-)
-const mergePayload = computed(() =>
- isMerge.value ? (parsedContent.value as MergeMessage | null) : null
-)
-
-/** 鍚堝苟娑堟伅鍐呭祵鍓� N 鏉℃淳鐢熴�寋鏄电О}锛歿鎽樿}銆� */
-const mergePreviewLines = computed(() => {
- if (!mergePayload.value) {
- return []
- }
- return mergePayload.value.messages
- .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
- .map((item) => `${item.senderNickname}锛�${summarizeMessageContent(item)}`)
-})
-
-const FACE_DIMENSION_MAX = 2048 // 琛ㄦ儏 payload锛氶潪娉曞楂樻淳鐢熸垚 undefined锛岃 <img> 璧� CSS max-w / max-h 鍏滃簳
-const facePayload = computed(() => {
- if (!isFace.value) {
- return null
- }
- const raw = parsedContent.value as FaceMessage | null
- if (!raw) {
- return null
- }
- const sanitize = (v: number | undefined) =>
- v && v > 0 && v <= FACE_DIMENSION_MAX ? v : undefined
- return { ...raw, width: sanitize(raw.width), height: sanitize(raw.height) }
-})
-
-/** 鏂囦欢鍥炬爣 + 閰嶈壊锛氭寜鎵╁睍鍚嶅垎鍙� */
-const fileIconInfo = computed(() => getFileIconInfo(filePayload.value?.name))
-
-/** 鏂囨湰 / 鏂囦欢 / 璇煶姘旀场鐨勬暣浣� class锛堝惈 selfSend 閰嶈壊 + ::before 涓夎鐨� side class锛� */
-function bubbleClass(variant: 'file' | 'text' | 'voice'): string[] {
- const isSelf = props.selfSend
- const side = isSelf ? 'message-bubble--self' : 'message-bubble--other'
- switch (variant) {
- case 'file': {
- return [
- side,
- 'message-bubble--file',
- isSelf
- ? 'bg-[#95ec69] border-[var(--ant-color-border-secondary)]'
- : 'bg-[var(--ant-color-bg-container)] border-[var(--ant-color-border-secondary)] hover:border-[#409eff]'
- ]
- }
- case 'text': {
- return [
- side,
- 'message-bubble--text',
- isSelf
- ? 'text-black bg-[#95ec69]'
- : 'text-[var(--ant-color-text)]'
- ]
- }
- case 'voice': {
- return [side, 'message-bubble--voice', isSelf ? 'bg-[#95ec69]' : '']
- }
- }
-}
-
-/** 鏂囦欢鐐瑰嚮 鈫� 鏂扮獥鍙d笅杞斤紱涓婁紶涓烦杩� */
-function handleFileClick() {
- if (isUploading.value || !filePayload.value?.url) {
- return
- }
- openSafeUrl(filePayload.value.url)
-}
-
-const voicePlayer = useVoicePlayer() // 璇煶鐐瑰嚮锛氭墭绠$粰 useVoicePlayer 鍏ㄥ眬浜掓枼鎾斁锛屾柊鐐圭殑璇煶浼氬仠鎺夋棫鐨�
-/**
- * 瀹炰緥绾у敮涓�鎾斁 key锛氭瘡涓� MessageBubble 瀹炰緥鐙珛涓�浠�
- *
- * 涓嶇敤 url 褰� key 鏄负浜嗛伩鍏嶃�屼富闈㈡澘 / 鍘嗗彶鎶藉眽 / 鍚堝苟璇︽儏鍚屼竴鏉¤闊炽�嶅叡浜韩浠斤細閭f牱涓夊姘旀场浼�
- * 鍚屾椂鏄剧ず鎾斁鎬侊紝涓斾换浣曚竴澶勫嵏杞介兘浼� stop 鎺夊埆澶勪粛鍙鐨勬挱鏀�
- */
-const voiceKey = Symbol('im-message-bubble-voice')
-const voicePlaying = computed(() => voicePlayer.isPlaying(voiceKey))
-function handleVoiceClick() {
- const url = voicePayload.value?.url
- if (!url) {
- return
- }
- voicePlayer.play(voiceKey, url)
-}
-
-/** 姘旀场鍗歌浇鍏滃簳锛氫紶 key 璁� stop 鑷繁鍒ゅ埆銆屾槸涓嶆槸鎴戙�嶏紝涓嶄細璇激鍒汉鐨勬挱鏀� */
-onBeforeUnmount(() => {
- voicePlayer.stop(voiceKey)
-})
-</script>
-
-<template>
- <!-- 鏂囨湰锛氭寜 segment 娓叉煋锛宮ention 楂樹寒鍙偣鍑汇�乁RL 鑷姩璇嗗埆鎴愬彲鐐瑰嚮閾炬帴 -->
- <div
- v-if="isText && textPayload"
- class="relative px-3.5 py-2.5 text-sm leading-normal break-words whitespace-pre-wrap rounded-lg"
- :class="bubbleClass('text')"
- >
- <TipSegments :segments="textSegments" />
- </div>
-
- <!-- 鍥剧墖锛歟l-image 鍐呯疆棰勮锛涗笂浼犱腑鍗婇�忔槑閬僵 -->
- <div v-else-if="isImage && imagePayload" class="relative inline-block">
- <Image
- class="max-w-[220px] rounded cursor-zoom-in"
- :src="imagePayload.thumbnailUrl || imagePayload.url"
- :preview="isUploading ? false : { src: imagePayload.url }"
- />
- <div
- v-if="isUploading"
- class="absolute inset-0 flex items-center justify-center text-sm text-white bg-black bg-opacity-45 rounded pointer-events-none"
- >
- {{ uploadProgressText }}
- </div>
- </div>
-
- <!-- 鏂囦欢锛氭枃浠跺悕 + 澶у皬闈犲乏銆佸僵鑹插ぇ鍥炬爣璐村彸锛涗笂浼犱腑鎻掍竴鏉¤繘搴︽潯 -->
- <div
- v-else-if="isFile && filePayload"
- class="relative flex gap-3 items-center min-w-[260px] max-w-[340px] px-3.5 py-3 border border-solid rounded transition-colors"
- :class="[bubbleClass('file'), isUploading ? 'cursor-default' : 'cursor-pointer']"
- @click="handleFileClick"
- >
- <div class="flex-1 min-w-0">
- <div class="overflow-hidden text-sm font-medium truncate text-[var(--ant-color-text)]">
- {{ filePayload.name }}
- </div>
- <div class="mt-1 text-12px text-[var(--ant-color-text-secondary)]">
- {{ formatFileSize(filePayload.size) }}
- </div>
- <div v-if="isUploading" class="flex gap-2 items-center mt-1.5">
- <div class="overflow-hidden flex-1 h-1 rounded bg-[var(--ant-color-fill-dark)]">
- <div
- class="h-full bg-[var(--ant-color-primary)] transition-[width] duration-150"
- :style="{ width: `${uploadProgress }%` }"
- ></div>
- </div>
- <span class="text-11px text-[var(--ant-color-text-secondary)] tabular-nums">
- {{ uploadProgressText }}
- </span>
- </div>
- </div>
- <Icon :icon="fileIconInfo.icon" :color="fileIconInfo.color" :size="40" class="flex-shrink-0" />
- </div>
-
- <!-- 璇煶 -->
- <div
- v-else-if="isVoice && voicePayload"
- class="relative flex gap-2 items-center min-w-[120px] px-3.5 py-2.5 rounded-lg cursor-pointer"
- :class="bubbleClass('voice')"
- @click="handleVoiceClick"
- >
- <Icon
- icon="ant-design:audio-outlined"
- :size="18"
- class="message-bubble__voice-icon"
- :class="{ 'im-voice-playing': voicePlaying }"
- />
- <span class="text-13px text-[var(--ant-color-text)]">
- {{ formatSeconds(voicePayload.duration) }}
- </span>
- </div>
-
- <!-- 瑙嗛锛氬師鐢� controls 鍐呭祵鎾斁锛宲oster 璧板悗绔皝闈紱涓婁紶涓崐閫忔槑閬僵 -->
- <div v-else-if="isVideo && videoPayload?.url" class="relative inline-block">
- <video
- class="max-w-[280px] max-h-[320px] rounded bg-black"
- :src="videoPayload.url"
- :poster="videoPayload.coverUrl"
- :controls="!isUploading"
- preload="metadata"
- ></video>
- <div
- v-if="isUploading"
- class="absolute inset-0 flex items-center justify-center text-sm text-white bg-black bg-opacity-45 rounded pointer-events-none"
- >
- {{ uploadProgressText }}
- </div>
- </div>
- <!-- 瑙嗛浣� payload 瑙f瀽澶辫触 / 娌� url锛氶檷绾у睍绀� -->
- <div
- v-else-if="isVideo"
- class="px-3.5 py-2.5 text-sm italic rounded-lg text-[var(--ant-color-text-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- [瑙嗛娑堟伅]
- </div>
-
- <!-- 琛ㄦ儏璐村浘锛氳8 <img> 涓嶅姘旀场 -->
- <div v-else-if="isFace && facePayload" class="inline-block">
- <img
- :src="facePayload.url"
- :alt="facePayload.name || '琛ㄦ儏'"
- :title="facePayload.name || ''"
- :width="facePayload.width"
- :height="facePayload.height"
- class="block max-w-[160px] max-h-[160px] object-contain"
- draggable="false"
- />
- </div>
-
- <!-- 鍚嶇墖 -->
- <CardBubble
- v-else-if="isCard && cardPayload"
- :card="cardPayload"
- clickable
- @click="(e: MouseEvent) => emit('clickCard', cardPayload!, e)"
- />
-
- <!-- 鍚堝苟杞彂姘旀场锛歵itle + 鎽樿棰勮 + 搴曢儴銆岃亰澶╄褰曘�嶆爣绛� -->
- <div
- v-else-if="isMerge && mergePayload"
- class="flex flex-col w-[260px] rounded-md overflow-hidden cursor-pointer bg-[var(--ant-color-bg-container)] border border-solid border-[var(--ant-color-border)] hover:border-[#409eff]"
- @click="emit('openMerge', content)"
- >
- <div class="px-3 py-2 text-sm font-medium text-[var(--ant-color-text)] truncate">
- {{ mergePayload.title }}
- </div>
- <div class="flex flex-col gap-0.5 px-3 pb-2">
- <div
- v-for="(line, idx) in mergePreviewLines"
- :key="idx"
- class="text-12px text-[var(--ant-color-text-secondary)] truncate"
- >
- {{ line }}
- </div>
- </div>
- <div
- class="px-3 py-1 text-12px border-t border-t-solid text-[var(--ant-color-text-placeholder)] border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-tertiary)]"
- >
- 鑱婂ぉ璁板綍
- </div>
- </div>
- <!-- 鍚堝苟娑堟伅瑙f瀽澶辫触鍏滃簳 -->
- <div
- v-else-if="isMerge"
- class="px-3.5 py-2.5 text-sm italic rounded-lg text-[var(--ant-color-text-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- [鑱婂ぉ璁板綍]
- </div>
-
- <!-- 棰戦亾绱犳潗锛氬浘鏂囧崱鐗囷紝鐐瑰嚮鎷夊瘜鏂囨湰 / 璺冲閾� -->
- <MaterialBubble v-else-if="isMaterial" :content="props.content" />
-
- <!-- 鏈煡绫诲瀷闄嶇骇 -->
- <div
- v-else
- class="px-3.5 py-2.5 text-sm italic rounded-lg text-[var(--ant-color-text-secondary)] bg-[var(--ant-color-fill-secondary)]"
- >
- [涓嶆敮鎸佺殑鍐呭绫诲瀷]
- </div>
-</template>
-
-<style scoped>
-/* 姘旀场灏惧反灏忎笁瑙掓寚鍚戝搴斿ご鍍忥紙瀵规柟鍦ㄥ乏銆佽嚜宸卞湪鍙筹級锛涜蛋 ::before + 4 杈� border 閰嶈壊鐢伙細閫忔槑 3 杈� + 瀹炶壊 1 杈癸紝
- 棰滆壊涓庢皵娉¤儗鏅搴旓紝鐣� 1px 瑙嗚鍚冭繘鍘伙紝鐪佷竴寮犲浘鐗� */
-.message-bubble--other::before,
-.message-bubble--self::before {
- content: '';
- position: absolute;
- top: 12px;
- width: 0;
- height: 0;
- border-style: solid;
-}
-.message-bubble--other {
- --im-message-bubble-other-bg: #f5f5f5;
-}
-.message-bubble--other.message-bubble--text,
-.message-bubble--other.message-bubble--voice {
- background-color: var(--im-message-bubble-other-bg);
-}
-.message-bubble--other::before {
- left: -5px;
- border-width: 5px 6px 5px 0;
- border-color: transparent var(--im-message-bubble-other-bg) transparent transparent;
-}
-.message-bubble--self::before {
- right: -5px;
- border-width: 5px 0 5px 6px;
- border-color: transparent transparent transparent #95ec69;
-}
-
-/* 鏁翠綋鏀捐繘 :global()锛岄伩鍏� Vue scoped 鎶� `:global(.dark) .xxx` 濉岀缉鎴愯8 `.dark` 鑰屾妸鍙橀噺鍒峰埌 <html> */
-:global(.dark .message-bubble--other) {
- --im-message-bubble-other-bg: rgb(255 255 255 / 12%);
-}
-
-/* :deep 绌块�� scoped 瀛愮粍浠� DOM锛沞l-icon 鍦ㄦ殫鑹叉ā寮忎笅鍏ㄥ眬 color 琚� .el-icon{color:var(--color)} 骞叉壈锛屾妸 voice 鍥炬爣 fill 閿佹 */
-.message-bubble__voice-icon :deep(svg) {
- fill: #606266 !important;
-}
-.message-bubble__voice-icon.im-voice-playing :deep(svg) {
- fill: #409eff !important;
-}
-
-/* @keyframes 闇�瑕� SCSS 澹版槑锛涙挱鏀句腑鐨勮剦鍐插姩鐢� */
-.im-voice-playing {
- animation: im-voice-icon-pulse 0.8s infinite;
-}
-
-@keyframes im-voice-icon-pulse {
- 0%,
- 100% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.15);
- }
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/message-history.vue b/src/views/im/home/pages/conversation/components/message/message-history.vue
deleted file mode 100644
index 1374db7..0000000
--- a/src/views/im/home/pages/conversation/components/message/message-history.vue
+++ /dev/null
@@ -1,807 +0,0 @@
-<script lang="ts" setup>
-import type { Message } from '#/views/im/home/types'
-
-import { computed, inject, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { useUserStore } from '../../../../../../../packages/stores/src'
-
-import { Button, Calendar, Input, Modal, Popover, Tag } from 'ant-design-vue'
-import dayjs from 'dayjs'
-
-import { getGroupMessageList as apiGetGroupMessageList } from '#/api/im/message/group'
-import { getPrivateMessageList as apiGetPrivateMessageList } from '#/api/im/message/private'
-import { useMessagePuller } from '#/views/im/home/composables/useMessagePuller'
-import { useVoicePlayer } from '#/views/im/home/composables/useVoicePlayer'
-import {
- ImContentType,
- ImConversationType,
- isFriendChatTip,
- isGroupNotification
-} from '#/views/im/utils/constants'
-import {
- buildFacePreviewText,
- buildRecallTip,
- buildRecallTipSegments,
- getConversationKey
-} from '#/views/im/utils/conversation'
-import { getClientConversationId } from '#/views/im/utils/db'
-import {
- resolveFriendNotificationSegments,
- resolveFriendNotificationText,
- resolveGroupNotificationSegments
-} from '#/views/im/utils/message'
-import {
- type CardMessage,
- type FaceMessage,
- type FileMessage,
- getCardLabelInfo,
- type MergeMessage,
- parseMessage,
- type TextMessage
-} from '#/views/im/utils/message'
-import { formatHistoryTime } from '#/views/im/utils/time'
-import {
- getMemberDisplayName,
- getSenderDisplayName,
- getSenderRealNickname
-} from '#/views/im/utils/user'
-
-import { GroupMember, type GroupMemberLite } from '../../../../components/group'
-import { UserAvatar } from '../../../../components/user'
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useFriendStore } from '../../../../store/friendStore'
-import { useGroupStore } from '../../../../store/groupStore'
-import { useMessageStore } from '../../../../store/messageStore'
-import { IM_MERGE_DETAIL_DIALOG_KEY } from './forward/keys'
-import MessageBubble from './message-bubble.vue'
-import TipSegments from './tip-segments.vue'
-
-defineOptions({ name: 'ImMessageHistory' })
-
-const emit = defineEmits<{
- // 鍘嗗彶娑堟伅琛屼笂鐨�"瀹氫綅"鎸夐挳锛氶�氱煡鐖剁粍浠� MessagePanel 婊氬埌瀵瑰簲娑堟伅浣嶇疆 + 鍏虫帀鑷繁
- locate: [messageId: number]
-}>()
-
-const userStore = useUserStore()
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-const groupStore = useGroupStore()
-const friendStore = useFriendStore()
-const openMergeDetail = inject(IM_MERGE_DETAIL_DIALOG_KEY)
-const voicePlayer = useVoicePlayer()
-const { convertPrivateMessage, convertGroupMessage } = useMessagePuller()
-
-const visible = ref(false)
-
-defineExpose({
- /** 鎵撳紑鍘嗗彶娑堟伅鎶藉眽 */
- open() {
- visible.value = true
- }
-})
-
-const conversation = computed(() => conversationStore.activeConversation)
-const isGroup = computed(() => conversation.value?.type === ImConversationType.GROUP)
-const allMessages = computed<Message[]>(() =>
- conversation.value
- ? messageStore.getMessages(
- getClientConversationId(conversation.value.type, conversation.value.targetId)
- )
- : []
-)
-
-/** 鍗曟潯娑堟伅鐨勫彂閫佷汉鏄剧ず鍚嶏細娓叉煋鏃舵寜 conversation 涓婁笅鏂囪蛋 WeChat 浼樺厛绾у疄鏃剁畻 */
-function senderDisplayNameOf(message: Message): string {
- return getSenderDisplayName(
- message.senderId,
- conversation.value?.type ?? 0,
- conversation.value?.targetId ?? 0
- )
-}
-
-/** 缇ゅ箍鎾簨浠� segments 鐨勬垚鍛樺悕瑙f瀽鍣紱鎸夊綋鍓嶄細璇� targetId 璧� getSenderDisplayName */
-function resolveGroupMemberName(message: Message): (userId: number) => string {
- return (id: number) => getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0)
-}
-
-/** 鍗曟潯娑堟伅鐨勫彂閫佷汉鐪熷疄鏄电О锛氱粰 UserAvatar 鑹插崱 / alt 鐢紝姘歌繙鏄� nickname 涓嶆幒澶囨敞 */
-function senderRealNicknameOf(message: Message): string {
- return getSenderRealNickname(
- message.senderId,
- conversation.value?.type ?? 0,
- conversation.value?.targetId ?? 0
- )
-}
-
-/** 鍗曟潯鎾ゅ洖娑堟伅鐨� tip 鏂囨锛歜uildRecallTip 鍐呴儴鎸� conversation 涓婁笅鏂囧疄鏃剁畻 sender 鍚� */
-function recallTipOf(message: Message): string {
- return buildRecallTip(
- message.senderId,
- message.selfSend,
- conversation.value?.type ?? 0,
- conversation.value?.targetId ?? 0
- )
-}
-
-/** 鍗曟潯鎾ゅ洖娑堟伅鐨� tip segments锛歴ender 鍚嶆鎸傚彲鐐瑰嚮 mention */
-function recallTipSegmentsOf(message: Message) {
- return buildRecallTipSegments(
- message.senderId,
- message.selfSend,
- conversation.value?.type ?? 0,
- conversation.value?.targetId ?? 0
- )
-}
-
-// ==================== 鏍囬 ====================
-
-/**
- * 寮圭獥鏍囬锛氬弬鑰冨井淇� PC
- * - 绉佽亰锛氫笌"X"鐨勮亰澶╄褰�
- * - 缇よ亰锛�"X 缇�"鐨勮亰澶╄褰�(N)
- */
-const title = computed(() => {
- if (!conversation.value) {
- return '鑱婂ぉ璁板綍'
- }
- const name = conversation.value.name
- if (isGroup.value) {
- return `"${name}"鐨勮亰澶╄褰�(${allMessages.value.length})`
- }
- return `涓�"${name}"鐨勮亰澶╄褰昤
-})
-
-// ==================== 鎼滅储 + 绛涢�� chip ====================
-
-/** 褰撳墠婵�娲荤殑绛涢�夋潯浠� 鈥斺�� 鍗� chip 妯″紡锛堝悓鏃跺彧 1 涓級锛岀偣鍑� tab 钀芥柊鍊硷紝脳 娓呯┖ */
-type ActiveFilter =
- | { day: string; kind: 'date'; } // YYYY-MM-DD
- | { kind: 'file' }
- | { kind: 'image' }
- | { kind: 'member'; nickname: string; userId: number; }
- | { kind: 'voice' }
-
-const keyword = ref('')
-const activeFilter = ref<ActiveFilter | null>(null)
-
-/** chip 鏂囨锛氭棩鏈� / 缇ゆ垚鍛� 澶氬甫鍊硷紝鍏朵粬鐩存帴 tab 鍚� */
-const filterChipLabel = computed(() => {
- if (!activeFilter.value) {
- return ''
- }
- switch (activeFilter.value.kind) {
- case 'date': {
- return `鏃ユ湡锛�${activeFilter.value.day}`
- }
- case 'file': {
- return '鏂囦欢'
- }
- case 'image': {
- return '鍥剧墖'
- }
- case 'member': {
- return `@${activeFilter.value.nickname}`
- }
- case 'voice': {
- return '璇煶'
- }
- default: {
- return ''
- }
- }
-})
-
-/** 鐐� tab 钀界瓫閫夛紱鍚� kind 閲嶅鐐瑰嚮 鈫� 褰� toggle 鍏虫帀锛堥伩鍏嶈糠鎯戯級 */
-function setFilter(filter: ActiveFilter) {
- if (
- activeFilter.value?.kind === filter.kind &&
- filter.kind !== 'date' &&
- filter.kind !== 'member'
- ) {
- activeFilter.value = null
- return
- }
- activeFilter.value = filter
-}
-
-/** chip 脳 鍏抽棴 / 閲嶇疆 */
-function clearFilter() {
- activeFilter.value = null
-}
-
-// ==================== 鏃ユ湡 popover ====================
-
-const datePopoverVisible = ref(false)
-const datePickerValue = ref(dayjs())
-
-/** 鏃ユ湡 popover 纭畾锛氭妸 Date 鈫� YYYY-MM-DD 钀藉埌 activeFilter锛屽叧 popover */
-function onDateConfirm() {
- if (!datePickerValue.value) {
- datePopoverVisible.value = false
- return
- }
- const day = datePickerValue.value.format('YYYY-MM-DD')
- activeFilter.value = { kind: 'date', day }
- datePopoverVisible.value = false
-}
-
-// ==================== 缇ゆ垚鍛� popover ====================
-
-const memberPopoverVisible = ref(false)
-const memberSearchKeyword = ref('')
-
-/** 缇ゆ垚鍛� picker 鍒楄〃锛氫粠 groupStore 鎷� + 閫傞厤 GroupMemberLite + 鍏抽敭瀛楄繃婊� */
-const filteredMembersForPicker = computed<GroupMemberLite[]>(() => {
- if (!isGroup.value || !conversation.value) {
- return []
- }
- const group = groupStore.getGroup(conversation.value.targetId)
- const all = (group?.members || []).map((member) => {
- const friend = friendStore.getFriend(member.userId)
- return {
- userId: member.userId,
- showName: getMemberDisplayName(member, friend),
- nickname: member.nickname,
- avatar: member.avatar,
- status: member.status
- }
- })
- const trimmedKeyword = memberSearchKeyword.value.trim()
- if (!trimmedKeyword) {
- return all
- }
- return all.filter((member) => member.showName.includes(trimmedKeyword))
-})
-
-/** 缇ゆ垚鍛� picker 閫夋嫨锛氳惤 activeFilter + 鍏� popover + 娓呮悳绱㈣瘝 */
-function onMemberSelect(member: GroupMemberLite) {
- activeFilter.value = {
- kind: 'member',
- userId: member.userId,
- nickname: member.showName
- }
- memberPopoverVisible.value = false
- memberSearchKeyword.value = ''
-}
-
-// ==================== 鍒楄〃杩囨护 ====================
-
-/** activeFilter 鍛戒腑锛氶粯璁ゆ棤绛涢�夋椂鍏ㄩ儴鍛戒腑 */
-function matchesActiveFilter(message: Message): boolean {
- if (!activeFilter.value) {
- return true
- }
- switch (activeFilter.value.kind) {
- case 'date': {
- return dayjs(message.sendTime).format('YYYY-MM-DD') === activeFilter.value.day
- }
- case 'file': {
- return message.type === ImContentType.FILE
- }
- case 'image': {
- return message.type === ImContentType.IMAGE
- }
- case 'member': {
- return message.senderId === activeFilter.value.userId
- }
- case 'voice': {
- return message.type === ImContentType.VOICE
- }
- default: {
- return true
- }
- }
-}
-
-/**
- * 褰撳墠鍒楄〃锛氬厛 activeFilter 杩囨护銆佸啀 keyword 妯$硦鍛戒腑銆佹渶鍚� reverse锛堟渶鏂板湪鍓嶏級
- *
- * 鍏抽敭瀛楀懡涓蛋 textSnippetOf 鈥斺�� 鏂囨湰鎷垮師鏂囥�佸獟浣撴嬁"[鍥剧墖]"绛夊崰浣嶈瘝銆佹枃浠舵嬁鏂囦欢鍚�
- */
-const currentList = computed<Message[]>(() => {
- const trimmedKeyword = keyword.value.trim()
- let list = allMessages.value.filter((message) => matchesActiveFilter(message))
- if (trimmedKeyword) {
- list = list.filter((message) => textSnippetOf(message).includes(trimmedKeyword))
- }
- return list.toReversed()
-})
-
-// ==================== 鍔犺浇鏇存棭娑堟伅 ====================
-
-const HISTORY_PAGE_SIZE = 50
-const loadingMore = ref(false)
-const hasMore = ref(true)
-
-/**
- * 鍔犺浇鏇存棭娑堟伅锛氭嬁褰撳墠鏈�鏃╀竴鏉� id 浣� maxId锛堜笉鍚級锛岃皟 list 鎺ュ彛鎷変竴椤� + convert + prepend
- *
- * - 鏈鎺� list 鎺ュ彛鐨� type / keyword / sender 杩囨护鍙傛暟锛氬悗绔彧鏀寔 maxId + limit 娓告爣鍒嗛〉锛�
- * tab 绛涢�夊湪鍓嶇鍋氾紙鏁版嵁鏉ュ洖鍒版湰鍦板悗杩囨护锛�
- * - 鏈湴鍗犱綅璺宠繃锛氬悗绔病娉曟寜 messageId 鏌�
- * - 杩斿洖鏁伴噺 < limit 瑙嗕负鍒伴《
- */
-async function loadEarlier() {
- // 閲嶅叆 / 鍒伴《 / 鏃犱細璇� 鏃╅��锛氶伩鍏嶉噸澶嶈姹傛垨鍦� conversation 鍒囨崲闂撮殭瑙﹀彂
- if (loadingMore.value || !hasMore.value || !conversation.value) {
- return
- }
- // 浠� PRIVATE / GROUP 璧板垎椤垫帴鍙o紱CHANNEL 鍗曞悜骞挎挱銆佹病鏈� list 鎺ュ彛锛岃惤鍒� else 浼氳璋冪鑱婃帴鍙o紙receiverId 浼� channelId锛�
- const requestedType = conversation.value.type
- if (requestedType !== ImConversationType.PRIVATE && requestedType !== ImConversationType.GROUP) {
- return
- }
- // 蹇収褰撳墠浼氳瘽涓婚敭锛歛wait 鏈熼棿鐢ㄦ埛鍒囪蛋 / 鍏抽棴闈㈡澘鏃朵涪寮冨搷搴旓紝閬垮厤鏃т細璇濆巻鍙茶 prepend 鍒版柊浼氳瘽閫犳垚涓插彿
- const requestedKey = getConversationKey(conversation.value)
- const requestedTargetId = conversation.value.targetId
- const requestedIsGroup = requestedType === ImConversationType.GROUP
-
- loadingMore.value = true
- try {
- // 绠� maxId锛堜笉鍚紝浣滀负鍚庣娓告爣锛夛細鍙栧綋鍓嶄細璇濇湰鍦扮紦瀛橀噷鏈�鏃╀竴鏉℃湇鍔$ id锛�
- // 鏈湴涔愯鍗犱綅娑堟伅娌℃湁鏈嶅姟绔� id锛岃鍓旈櫎
- // 鍏ㄦ槸鍗犱綅 / 鍒楄〃涓虹┖鏃� reduce 涓嶆洿鏂板垵鍊硷紙POSITIVE_INFINITY锛夛紝杞垚 undefined 鈫� 鍚庣浠庢渶鏂版媺
- let earliestId = Number.POSITIVE_INFINITY
- for (const message of allMessages.value) {
- if (message.id && message.id > 0) {
- earliestId = Math.min(earliestId, message.id)
- }
- }
- const maxId = Number.isFinite(earliestId) ? earliestId : undefined
-
- // 璋冨悗绔� list 鎺ュ彛锛氱鑱� / 缇よ亰鎺ュ彛绛惧悕涓嶅悓锛屽垎鏀皟搴︼紱杩斿洖缁撴灉鐢� useMessagePuller
- // 鏆撮湶鐨� convert 鍑芥暟杞垚鏈湴 Message锛堜笌 puller 鍚屼竴浠藉瓧娈垫槧灏勶紝閬垮厤鍒嗘锛�
- let earlier: Message[] = []
- let pageLength = 0
- if (requestedIsGroup) {
- const list = await apiGetGroupMessageList({
- groupId: requestedTargetId,
- maxId,
- limit: HISTORY_PAGE_SIZE
- })
- earlier = (list || []).map((message) => convertGroupMessage(message))
- pageLength = list?.length ?? 0
- } else {
- const list = await apiGetPrivateMessageList({
- receiverId: requestedTargetId,
- maxId,
- limit: HISTORY_PAGE_SIZE
- })
- earlier = (list || []).map((message) => convertPrivateMessage(message))
- pageLength = list?.length ?? 0
- }
-
- // await 鏈熼棿 active 鍙兘琚閮ㄧ疆 null / 鎹富閿細鐩存帴涓㈠純鍝嶅簲锛涗笉鏇存柊 hasMore锛堟棫浼氳瘽鍒伴《涓嶄唬琛ㄦ柊浼氳瘽鍒伴《锛変篃涓� prepend
- if (!conversation.value || getConversationKey(conversation.value) !== requestedKey) {
- return
- }
-
- // 杩斿洖鏁伴噺 < limit 瑙嗕负鍒伴《 鈥斺�� 鍏抽棴"鍔犺浇鏇存棭"鎸夐挳锛岄伩鍏嶅悗缁偣鍑荤┖璺戞帴鍙�
- if (pageLength < HISTORY_PAGE_SIZE) {
- hasMore.value = false
- }
- // 鍚堝苟鍒� messageStore锛歱rependMessageList 鍐呴儴鍘婚噸 + 鍗囧簭鍚堝苟 + 钀� IndexedDB锛�
- // 涓昏亰澶╅潰鏉跨殑 messages 鏄悓涓�浠藉紩鐢紝鑰佹秷鎭篃浼氫竴璧峰嚭鐜板湪涓婚潰鏉块噷锛堢鍚堥鏈燂級
- messageStore.prependMessageList(requestedType, requestedTargetId, earlier)
- } finally {
- loadingMore.value = false
- }
-}
-
-// ==================== 寮圭獥鎵撳紑 reset ====================
-
-/** 寮圭獥鎵撳紑鏃舵妸涓婃鐨� chip / 鎼滅储 / 鍔犺浇鐘舵�侀兘娓呭共鍑�锛岄伩鍏嶄笂娆$殑鐘舵�佹畫鐣欒糠鎯� */
-function onDialogOpen() {
- activeFilter.value = null
- keyword.value = ''
- hasMore.value = true
- datePopoverVisible.value = false
- memberPopoverVisible.value = false
- memberSearchKeyword.value = ''
- datePickerValue.value = dayjs()
- // 鏈湴鏃犳秷鎭椂绔嬪嵆鎷変竴娆★紙maxId=undefined 浠庢渶鏂板紑濮嬶級锛岄伩鍏嶆柊璁惧 / 缂撳瓨娓呭悗寮圭獥鍙樉绀�"鏆傛棤娑堟伅"
- if (allMessages.value.length === 0 && hasMore.value && conversation.value) {
- void loadEarlier()
- }
-}
-
-/** 鎶藉眽鍏抽棴鏃跺浣� + 鍋滆闊� */
-watch(visible, (value) => {
- if (!value) {
- activeFilter.value = null
- keyword.value = ''
- voicePlayer.stop()
- }
-})
-
-/**
- * 鎶藉眽寮�鐫�鏃跺閮ㄥ垏浜� active conversation锛歞ialog 鐨� title / 鍒楄〃 / isGroup 鍏ㄩ儴璺熺潃鏂� conversation 璧帮紝
- * 杩欓噷鎶婂垎椤垫�佷竴骞堕噸缃紱鍚﹀垯鏃т細璇濇畫鐣欑殑 loadingMore=true / hasMore=false 浼氳鏂颁細璇�"鍔犺浇鏇存棭"鎸夐挳澶辨晥
- */
-watch(conversation, () => {
- loadingMore.value = false
- hasMore.value = true
-})
-
-// ==================== helper ====================
-
-/** 鍙栧ご鍍� url锛氳嚜宸辩敤 userStore锛岀兢閲屾煡 groupStore 鎴愬憳锛岀鑱婄敤 conversation.avatar */
-function getAvatar(message: Message): string {
- if (message.selfSend) {
- return userStore.userInfo?.avatar || ''
- }
- if (!conversation.value) {
- return ''
- }
- if (isGroup.value) {
- const group = groupStore.getGroup(conversation.value.targetId)
- return group?.members?.find((member) => member.userId === message.senderId)?.avatar || ''
- }
- return conversation.value.avatar || ''
-}
-
-/** 鍏抽敭瀛楀懡涓枃鏈細鏂囨湰绫昏繑鍥炲師鏂囥�佹枃浠惰繑鍥炴枃浠跺悕锛堝埄浜庢寜鏂囦欢鍚嶆悳锛夈�佸叾浠栬繑鍥炲崰浣嶈瘝 */
-function textSnippetOf(message: Message): string {
- if (isFriendChatTip(message.type)) {
- return resolveFriendNotificationText(message)
- }
- switch (message.type) {
- case ImContentType.CARD: {
- const card = parseMessage<CardMessage>(message.content)
- return `[${getCardLabelInfo(card).label}] ${card?.name ?? ''}`
- }
- case ImContentType.FACE: {
- return buildFacePreviewText(parseMessage<FaceMessage>(message.content))
- }
- case ImContentType.FILE: {
- return parseMessage<FileMessage>(message.content)?.name ?? '[鏂囦欢]'
- }
- case ImContentType.IMAGE: {
- return '[鍥剧墖]'
- }
- case ImContentType.MERGE: {
- const merge = parseMessage<MergeMessage>(message.content)
- return merge?.title ?? '[鑱婂ぉ璁板綍]'
- }
- case ImContentType.RECALL: {
- return recallTipOf(message)
- }
- case ImContentType.TEXT: {
- return parseMessage<TextMessage>(message.content)?.content ?? ''
- }
- case ImContentType.VIDEO: {
- return '[瑙嗛]'
- }
- case ImContentType.VOICE: {
- return '[璇煶]'
- }
- default: {
- return ''
- }
- }
-}
-
-/**
- * 瀹氫綅鍒拌亰澶╀綅缃細emit 缁� MessagePanel 璧� scrollIntoView + 鐭殏楂樹寒锛屽啀鍏虫帀鑷繁
- * messageId === 0锛堟湰鍦板崰浣嶆秷鎭級璺宠繃鈥斺�旇繕娌℃嬁鍒扮湡瀹� id锛孌OM 涓婃病娉� querySelector
- */
-function locateMessage(messageId: number) {
- if (!messageId) {
- return
- }
- emit('locate', messageId)
- visible.value = false
-}
-</script>
-
-<template>
- <!--
- 鍘嗗彶娑堟伅寮圭獥锛堝榻愬井淇� PC锛�
- - 灞呬腑 dialog锛宼itle "涓庛�孹銆嶈亰澶╄褰�" / "銆孹 缇ゃ�嶈亰澶╄褰�(N)"
- - 榛樿鏃犵瓫閫夛紝鍏ㄩ噺鍊掑簭灞曠ず锛涚偣鍑� tab 钀界瓫閫� 鈫� 鎼滅储妗嗗乏渚у嚭 chip 脳 鍙竻
- - 鏂囦欢 / 鍥剧墖 / 閾炬帴锛氱洿鎺ヨ惤绛涢�夛紱鏃ユ湡 / 缇ゆ垚鍛橈細寮� popover 浜屾閫夋嫨鍐嶈惤
- - "鍔犺浇鏇存棭娑堟伅"锛氬垪琛ㄥ簳閮ㄦ寜閽紝鐐瑰嚮璋� /im/message/{private,group}/list 鎷夸竴椤� + prepend
- -->
- <Modal
- v-model:open="visible"
- :title="title"
- width="640px"
- :footer="null"
- class="im-message-history__dialog"
- @after-open-change="(open: boolean) => open && onDialogOpen()"
- >
- <div class="flex flex-col gap-3 h-[520px]">
- <!-- 鎼滅储鍖猴細activeFilter 瀛樺湪鏃跺乏渚у嚭 chip 脳 鍙竻锛屽惁鍒欑函鎼滅储妗� -->
- <div
- class="im-message-history__searchbar flex items-center gap-2 px-3 py-1.5 rounded-md bg-[var(--ant-color-fill-secondary)]"
- >
- <Tag
- v-if="activeFilter"
- closable
- size="small"
- class="im-message-history__chip flex-shrink-0"
- @close="clearFilter"
- >
- {{ filterChipLabel }}
- </Tag>
- <Icon
- v-else
- icon="ant-design:search-outlined"
- class="text-[var(--ant-color-text-secondary)]"
- />
- <input
- v-model="keyword"
- type="text"
- placeholder="鎼滅储"
- class="im-message-history__search-input flex-1 min-w-0 bg-transparent border-none outline-none text-sm text-[var(--ant-color-text)]"
- />
- </div>
-
- <!-- Tab 琛岋細鏂囦欢 / 鍥剧墖 / 璇煶 / 鏃ユ湡(popover) / 缇ゆ垚鍛�(popover, 浠呯兢鑱�)锛涘簳閮ㄤ竴鏉″垎鍓茬嚎鎶� tab 鍖鸿窡娑堟伅鍒楄〃鍒嗗紑锛屽榻愬井淇¤鎰� -->
- <div
- class="flex gap-5 px-2 pb-2 text-sm flex-shrink-0 text-[#1989fa] border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <span
- class="im-message-history__tab cursor-pointer"
- :class="{ 'im-message-history__tab--active': activeFilter?.kind === 'file' }"
- @click="setFilter({ kind: 'file' })"
- >
- 鏂囦欢
- </span>
- <span
- class="im-message-history__tab cursor-pointer"
- :class="{ 'im-message-history__tab--active': activeFilter?.kind === 'image' }"
- @click="setFilter({ kind: 'image' })"
- >
- 鍥剧墖
- </span>
- <span
- class="im-message-history__tab cursor-pointer"
- :class="{ 'im-message-history__tab--active': activeFilter?.kind === 'voice' }"
- @click="setFilter({ kind: 'voice' })"
- >
- 璇煶
- </span>
- <!-- 鏃ユ湡锛歟l-popover 鍖� el-calendar锛岀‘璁ゅ悗鎵嶈惤绛涢�� -->
- <Popover
- v-model:open="datePopoverVisible"
- trigger="click"
- placement="bottom"
- :overlay-style="{ width: '320px' }"
- >
- <span
- class="im-message-history__tab cursor-pointer"
- :class="{ 'im-message-history__tab--active': activeFilter?.kind === 'date' }"
- >
- 鏃ユ湡
- </span>
- <template #content>
- <div class="im-message-history__date-panel">
- <div class="px-2 pt-1 pb-2 text-13px font-medium text-[var(--ant-color-text)]">
- 閫夋嫨鍙戦�佹棩鏈�
- </div>
- <Calendar
- v-model:value="datePickerValue"
- :fullscreen="false"
- class="im-message-history__calendar"
- />
- <div class="flex gap-2 justify-end px-2 pt-2">
- <Button size="small" @click="datePopoverVisible = false">鍙栨秷</Button>
- <Button size="small" type="primary" @click="onDateConfirm">纭畾</Button>
- </div>
- </div>
- </template>
- </Popover>
- <!-- 缇ゆ垚鍛橈細浠呯兢鑱婏紱popover 鍐呰嚜甯︽悳绱� + GroupMember 鍒楄〃 -->
- <Popover
- v-if="isGroup"
- v-model:open="memberPopoverVisible"
- trigger="click"
- placement="bottom"
- :overlay-style="{ width: '320px' }"
- >
- <span
- class="im-message-history__tab cursor-pointer"
- :class="{ 'im-message-history__tab--active': activeFilter?.kind === 'member' }"
- >
- 缇ゆ垚鍛�
- </span>
- <template #content>
- <div>
- <Input v-model:value="memberSearchKeyword" placeholder="鎼滅储缇ゆ垚鍛�" size="small">
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- <div class="max-h-[360px] overflow-y-auto mt-2">
- <GroupMember
- v-for="member in filteredMembersForPicker"
- :key="member.userId"
- :member="member"
- :height="44"
- :clickable="false"
- class="cursor-pointer hover:bg-[var(--ant-color-fill)]"
- @click="onMemberSelect(member)"
- />
- <div
- v-if="filteredMembersForPicker.length === 0"
- class="py-6 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- 娌℃湁鍖归厤鐨勬垚鍛�
- </div>
- </div>
- </div>
- </template>
- </Popover>
- </div>
-
- <!-- 娑堟伅鍒楄〃 -->
- <div class="flex-1 overflow-y-auto">
- <template v-for="message in currentList" :key="message.id || message.clientMessageId">
- <!-- 濂藉弸浼氳瘽浜嬩欢锛團RIEND_ADD / FRIEND_DELETE锛夛細灞呬腑鐏拌壊锛屼笉鎸傚ご鍍� / sender锛�
- 璺熶富鑱婂ぉ闈㈡澘閲� MessageItem 鐨勬覆鏌撹涔夊榻� -->
- <div
- v-if="isFriendChatTip(message.type)"
- class="px-4 py-3 text-12px text-center italic text-[var(--ant-color-text-secondary)] border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <TipSegments :segments="resolveFriendNotificationSegments(message)" />
- </div>
-
- <!-- 缇ゅ箍鎾簨浠讹細璺熷ソ鍙嬩簨浠跺悓鐏拌壊鏍峰紡锛宮ention 娈垫寕鐐瑰嚮寮� UserInfoCard -->
- <div
- v-else-if="isGroupNotification(message.type)"
- class="px-4 py-3 text-12px text-center italic text-[var(--ant-color-text-secondary)] border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <TipSegments
- :segments="resolveGroupNotificationSegments(message, resolveGroupMemberName(message))"
- />
- </div>
-
- <!-- 鏅�氭秷鎭 -->
- <div
- v-else
- class="im-message-history__row flex gap-3 items-start px-1 py-3 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
- >
- <UserAvatar
- :url="getAvatar(message)"
- :name="senderRealNicknameOf(message)"
- :size="36"
- :clickable="false"
- />
- <div class="flex-1 min-w-0">
- <div
- class="flex justify-between items-start text-12px text-[var(--ant-color-text-secondary)]"
- >
- <span class="font-medium text-[var(--ant-color-text)]">
- {{ senderDisplayNameOf(message) }}
- </span>
- <span class="im-message-history__meta relative flex-shrink-0">
- <span class="block text-right">{{ formatHistoryTime(message.sendTime) }}</span>
- <!-- 瀹氫綅鍒拌亰澶╀綅缃細absolute 娴湪鏃堕棿涓嬫柟锛岃 hover 鎵嶆樉绀猴紝
- 涓嶅弬涓庡彸渚ф爮 flex 鎺掔増锛堥伩鍏嶉殣钘忔椂鍗犱綅璁�"鎴�"鍜屽唴瀹逛箣闂寸暀绌猴級锛�
- 浠呮湁鐪熷疄 id 鐨勬秷鎭墠鏀寔锛堟湰鍦板崰浣嶆秷鎭笉琛岋級 -->
- <span
- v-if="message.id && message.id > 0"
- class="im-message-history__locate"
- @click="locateMessage(message.id!)"
- >
- 瀹氫綅鍒拌亰澶╀綅缃�
- </span>
- </span>
- </div>
-
- <div class="mt-1.5">
- <!-- 鎾ゅ洖锛氬崟鐙蛋鐏拌壊 tip锛宻ender 鍚嶆鍙偣鍑� -->
- <div
- v-if="message.type === ImContentType.RECALL"
- class="text-sm italic text-[var(--ant-color-text-secondary)]"
- >
- <TipSegments :segments="recallTipSegmentsOf(message)" />
- </div>
- <!-- 鍏跺畠绫诲瀷璧� MessageBubble 澶嶇敤涓昏亰澶╂皵娉� -->
- <MessageBubble
- v-else
- :type="message.type"
- :content="message.content"
- @open-merge="openMergeDetail?.($event)"
- />
- </div>
- </div>
- </div>
- </template>
-
- <div
- v-if="currentList.length === 0"
- class="py-10 text-center text-13px text-[var(--ant-color-text-disabled)]"
- >
- {{ keyword || activeFilter ? '娌℃湁鍖归厤鐨勬秷鎭�' : '鏆傛棤娑堟伅' }}
- </div>
-
- <!-- 鍔犺浇鏇存棭娑堟伅锛氬垪琛ㄥ簳閮� trigger锛坮everse 鍚庢渶鏃╃殑鍦ㄦ渶涓嬶紝鎸夐挳鏀惧簳閮ㄦ洿鑷劧锛夛紱
- filter 鍛戒腑 0 鏉℃椂浠嶄繚鐣� 鈥斺�� 鍔犺浇鏇存棭鍙兘甯﹀洖鍖归厤鍐呭 -->
- <div
- v-if="hasMore && allMessages.length > 0"
- class="py-3 text-center border-t border-t-solid border-[var(--ant-color-border-secondary)]"
- >
- <Button :loading="loadingMore" type="link" @click="loadEarlier">
- 鍔犺浇鏇存棭娑堟伅
- </Button>
- </div>
- <!-- "娌℃湁鏇存棭" 鍙湪宸叉湁鍖归厤椤规椂闇插嚭锛岄伩鍏嶅拰涓婇潰"娌℃湁鍖归厤鐨勬秷鎭�"绌烘�佹枃妗堥噸鍙� -->
- <div
- v-else-if="!hasMore && currentList.length > 0"
- class="py-3 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- 娌℃湁鏇存棭鐨勬秷鎭簡
- </div>
- </div>
- </div>
- </Modal>
-</template>
-
-<style scoped>
-/* :deep 绌块�� el-tag 瀛愮粍浠� DOM锛涙悳绱㈠尯 chip 绂佹帀 hover 棰滆壊杩囨浮 / 脳 鍥炬爣鍔ㄦ晥锛岄伩鍏嶅湪鎼滅储鍖洪噷鏈夋姈鍔ㄦ劅 */
-.im-message-history__chip,
-.im-message-history__chip :deep(.el-tag__close) {
- transition: none !important;
- animation: none !important;
-}
-
-/* 銆屽畾浣嶅埌鑱婂ぉ浣嶇疆銆嶆寜閽細鐖惰 hover 鎵嶆樉绀猴紝璧� .parent:hover .child 璺ㄥ厓绱犵姸鎬佽仈鍔� */
-.im-message-history__locate {
- position: absolute;
- top: 100%;
- right: 0;
- margin-top: 4px;
- display: none;
- white-space: nowrap;
- color: #1989fa;
- cursor: pointer;
- transition: color 0.15s;
-}
-.im-message-history__row:hover .im-message-history__locate {
- display: block;
-}
-.im-message-history__locate:hover {
- color: #146fc7;
-}
-
-/* ::after 浼厓绱犵敾婵�娲讳笅鍒掔嚎锛泃ab 榛樿钃濊壊琛屽唴鏂囧瓧锛屾縺娲绘�佸姞涓嬪垝绾� */
-.im-message-history__tab {
- position: relative;
- padding: 4px 2px;
- color: #1989fa;
- transition: color 0.15s;
-}
-.im-message-history__tab:hover {
- color: #2f81d4;
-}
-.im-message-history__tab--active::after {
- content: '';
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
- height: 2px;
- background: #1989fa;
- border-radius: 1px;
-}
-
-/* :deep 绌块�� antd Calendar锛坒ullscreen=false 鍗$墖妯″紡锛夊瓙 DOM锛屽帇涓�鍘嬪杩� 320 popover */
-.im-message-history__calendar :deep(.ant-picker-calendar-header) {
- padding: 4px 8px;
-}
-.im-message-history__calendar :deep(.ant-picker-content) {
- font-size: 12px;
-}
-.im-message-history__calendar :deep(.ant-picker-cell) {
- padding: 1px 0;
-}
-.im-message-history__calendar :deep(.ant-picker-cell .ant-picker-calendar-date) {
- height: 28px;
- margin: 0 2px;
- padding: 2px 4px 0;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/message-item.vue b/src/views/im/home/pages/conversation/components/message/message-item.vue
deleted file mode 100644
index b28be0b..0000000
--- a/src/views/im/home/pages/conversation/components/message/message-item.vue
+++ /dev/null
@@ -1,1055 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../../../../components/group'
-import type { Message } from '../../../../types'
-
-import { computed, inject } from 'vue'
-
-import { confirm } from '../../../../../../../packages/effects/common-ui/src'
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { useUserStore } from '../../../../../../../packages/stores/src'
-
-import { useClipboard } from '@vueuse/core'
-import { message as antdMessage, Tag } from 'ant-design-vue'
-
-import { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '#/api/im/group'
-import { removeGroupMember } from '#/api/im/group/member'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import {
- MESSAGE_GROUP_READ_ENABLED,
- MESSAGE_PRIVATE_READ_ENABLED,
- MESSAGE_RECALL_WINDOW_MS,
- MESSAGE_TIME_TIP_GAP_MS
-} from '#/views/im/utils/config'
-import {
- ImContentType,
- ImConversationType,
- ImForwardMode,
- ImFriendAddSource,
- ImGroupMemberRole,
- ImMessageReceiptStatus,
- ImMessageStatus,
- isFriendChatTip,
- isGroupNotification,
- isMediaMessageType,
- isNormalMessage,
- isRtcCallTip
-} from '#/views/im/utils/constants'
-import { buildRecallTipSegments } from '#/views/im/utils/conversation'
-import {
- buildQuoteFromMessage,
- type CardMessage,
- extractAddableFace,
- getQuoteFromMessage,
- type MentionCandidate,
- parseMessage,
- type TextMessage
-} from '#/views/im/utils/message'
-import {
- parseRtcCallPayload,
- resolveFriendNotificationSegments,
- resolveGroupNotificationSegments,
- resolveRtcCallPrivateBubbleText,
- resolveRtcCallTipSegments
-} from '#/views/im/utils/message'
-import { formatTimeTip } from '#/views/im/utils/time'
-import {
- getMemberDisplayName,
- getMentionCandidates,
- getSenderDisplayName,
- getSenderRealNickname,
- isGroupQuit
-} from '#/views/im/utils/user'
-
-import { UserAvatar } from '../../../../components/user'
-import { mediaTypeHandlers, useMediaUploader } from '../../../../composables/useMediaUploader'
-import { useMessageMultiSelect } from '../../../../composables/useMessageMultiSelect'
-import { useMessageSender } from '../../../../composables/useMessageSender'
-import { useMuteOverlay } from '../../../../composables/useMuteOverlay'
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useFaceStore } from '../../../../store/faceStore'
-import { useFriendStore } from '../../../../store/friendStore'
-import { useGroupStore } from '../../../../store/groupStore'
-import { useMessageStore } from '../../../../store/messageStore'
-import { useImUiStore } from '../../../../store/uiStore'
-import {
- IM_FORWARD_DIALOG_KEY,
- IM_MERGE_DETAIL_DIALOG_KEY,
- IM_RTC_REDIAL_KEY
-} from './forward/keys'
-import MessageBubble from './message-bubble.vue'
-import MessageReadStatus from './message-read-status.vue'
-import ReplyPreview from './reply-preview.vue'
-import TipSegments from './tip-segments.vue'
-
-defineOptions({ name: 'ImMessageItem' })
-
-const props = defineProps<{
- message: Message
- /** 鍒楄〃涓殑涓婁竴鏉℃秷鎭紝鐢ㄤ簬鍒ゆ柇鏄惁瑕佸湪褰撳墠娑堟伅涓婃柟娓叉煋鏃堕棿鍒嗛殧鏉★紱涓嶄紶鎸夊垪琛ㄧ涓�鏉″鐞� */
- prevMessage?: Message
-}>()
-
-const emit = defineEmits<{
- /** 寮曠敤鍧楃偣鍑� 鈫� MessagePanel 婊氬畾浣� + 楂樹寒 */
- locate: [messageId: number]
- /** 绂佽█锛氶渶瑕佺埗缁勪欢鎵撳紑鏃堕暱閫夋嫨寮圭獥 */
- mute: [groupId: number, userId: number, displayName: string]
- /** 鏁版嵁鍙樻洿鍚庡埛鏂扮兢淇℃伅 */
- reload: []
-}>()
-
-// ==================== Stores / Hooks ====================
-
-const userStore = useUserStore()
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-const groupStore = useGroupStore()
-const friendStore = useFriendStore()
-const faceStore = useFaceStore()
-const uiStore = useImUiStore()
-const { recall, sendRaw } = useMessageSender()
-const { uploadAndSendMedia } = useMediaUploader()
-const muteOverlay = useMuteOverlay()
-// legacy:true 鍏煎 HTTP 鐜锛屾病鏈� navigator.clipboard 鏃堕檷绾у埌 execCommand
-const { copy: copyToClipboard } = useClipboard({ legacy: true })
-
-// ==================== 鍐呭绫诲瀷鍒ゆ柇 ====================
-
-/** 鏄惁鍦ㄥ綋鍓嶆秷鎭笂鏂规覆鏌撴椂闂村垎闅旀潯锛氬垪琛ㄧ涓�鏉� / 璺濅笂涓�鏉¤秴杩囬槇鍊硷紱缂� sendTime 涓嶆覆鏌擄紱棰戦亾绱犳潗姣忔潯閮芥樉绀� */
-const shouldShowTimeTip = computed(() => {
- if (!props.message.sendTime) {
- return false
- }
- if (isMaterial.value) {
- return true
- }
- if (!props.prevMessage?.sendTime) {
- return true
- }
- return props.message.sendTime - props.prevMessage.sendTime > MESSAGE_TIME_TIP_GAP_MS
-})
-
-/** 浠� MessageItem 鑷韩浠嶈鐢ㄥ埌鐨� type 鍒ゅ畾锛堝叾瀹冨垎鏀凡涓嬫矇鍒� MessageBubble锛� */
-const isVoice = computed(() => props.message.type === ImContentType.VOICE)
-const isMerge = computed(() => props.message.type === ImContentType.MERGE)
-/**
- * 棰戦亾绱犳潗鍦ㄣ�岄閬撲細璇濄�嶅唴浠垮井淇″叕浼楀彿鏍峰紡锛堝眳涓� + 鏃犲ご鍍忥級锛�
- * 绉佽亰 / 缇よ亰閲岃杞彂杩囨潵鐨勭礌鏉愭寜 selfSend 璧版爣鍑嗘皵娉″竷灞�锛堣嚜宸卞彸銆佸鏂瑰乏銆佸甫澶村儚锛�
- */
-const isMaterial = computed(
- () =>
- props.message.type === ImContentType.MATERIAL &&
- conversationStore.activeConversation?.type === ImConversationType.CHANNEL
-)
-
-/** 褰撳墠鏄惁鍦ㄥ叕浼楀彿 / 棰戦亾浼氳瘽鍐咃細闄愬埗鍙抽敭鑿滃崟鍙睍绀鸿浆鍙� / 鍒犻櫎 */
-const isChannelConversation = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
-)
-
-// ==================== 浜嬩欢娑堟伅锛堟挙鍥� / 濂藉弸 / 缇ゅ箍鎾級 ====================
-// 杩欎笁绫讳笉璧版櫘閫氭皵娉★紝娓叉煋鎴愬眳涓伆鑹� tip锛涘垽鏂� + 鏂囨閰嶅鏀句竴璧凤紝鏂板绗洓绫讳簨浠跺彧闇�鍦ㄦ湰鍧楁敼瀹�
-
-/** 鏄惁宸叉挙鍥烇細pull / WS 涓よ矾閮戒細璋� recallMessage 鎶婂師娑堟伅鏇存柊涓� type=RECALL锛屾覆鏌撳彧闇�璇嗗埆 type */
-const isRecall = computed(() => props.message.type === ImContentType.RECALL)
-
-/** 鎾ゅ洖鎻愮ず segments锛氫緷璧� activeConversation 瀹炴椂绠� sender 鍚� */
-const recallTipSegments = computed(() => {
- const conversation = conversationStore.activeConversation
- return buildRecallTipSegments(
- props.message.senderId,
- props.message.selfSend,
- conversation?.type ?? 0,
- conversation?.targetId ?? 0
- )
-})
-
-/** 鏄惁浼氳瘽鍐呭ソ鍙嬩簨浠舵皵娉★紙FRIEND_ADD / FRIEND_DELETE锛� */
-const isFriendChatTipMessage = computed(() => isFriendChatTip(props.message.type))
-
-/** 濂藉弸浜嬩欢 segments */
-const friendChatTipSegments = computed(() => resolveFriendNotificationSegments(props.message))
-
-/** 鏄惁缇ゅ箍鎾簨浠讹紙GROUP_CREATE..GROUP_BANNED 娈典綅锛屾帓闄� GROUP_MEMBER_SETTING_UPDATE 涓汉淇″彿锛� */
-const isGroupNotificationMessage = computed(() => isGroupNotification(props.message.type))
-
-/** 缇ゅ箍鎾簨浠� segments */
-const groupNotificationSegments = computed(() =>
- resolveGroupNotificationSegments(props.message, (id: number) =>
- getSenderDisplayName(id, ImConversationType.GROUP, props.message.targetId ?? 0)
- )
-)
-
-/** 绉佽亰 RTC_CALL_END 璧般�屽噯姘旀场銆嶏紙宸﹀彸鍒嗗竷 + 鐢佃瘽鍥炬爣 + 鏂囨锛夛紱闈炵鑱婂満鏅负 null */
-const rtcCallEndPrivatePayload = computed(() => {
- if (props.message.type !== ImContentType.RTC_CALL_END) {
- return null
- }
- const payload = parseRtcCallPayload(props.message.content)
- return payload?.conversationType === ImConversationType.PRIVATE ? payload : null
-})
-
-/** 鏄惁绉佽亰閫氳瘽姘旀场 */
-const isRtcCallPrivateBubbleMessage = computed(() => rtcCallEndPrivatePayload.value !== null)
-
-/** 绉佽亰閫氳瘽姘旀场鏂囨锛堟寜 operatorUserId 鏄惁褰撳墠鐢ㄦ埛鍖哄垎锛涘榻愬井淇′袱绔笉鍚岃瑙掞級 */
-const rtcCallPrivateBubbleText = computed(() =>
- resolveRtcCallPrivateBubbleText(rtcCallEndPrivatePayload.value)
-)
-
-/** 鏄惁浼氳瘽鍐呯兢閫氳瘽浜嬩欢灞呬腑 tip */
-const isRtcCallTipMessage = computed(() => {
- if (!isRtcCallTip(props.message.type)) {
- return false
- }
- const payload = parseRtcCallPayload(props.message.content)
- if (payload?.conversationType !== ImConversationType.GROUP) {
- return false
- }
- return !isRtcCallPrivateBubbleMessage.value
-})
-
-/** 閫氳瘽浜嬩欢 segments锛涗粎缇よ亰 tip 鐢� */
-const rtcCallTipSegments = computed(() => resolveRtcCallTipSegments(props.message))
-
-// ==================== 娑堟伅鍐呭瑙f瀽 / payload ====================
-
-/** 寮曠敤瀵硅薄锛氭皵娉″唴宓屽叆灞曠ず锛涢潪寮曠敤娑堟伅杩斿洖 null锛屾ā鏉� v-if 涓嶆覆鏌� */
-const quote = computed(() => getQuoteFromMessage(props.message.content))
-
-const openForwardDialog = inject(IM_FORWARD_DIALOG_KEY) // MessagePanel 娉ㄥ叆鐨勫脊绐楄Е鍙戝嚱鏁�
-const openMergeDetail = inject(IM_MERGE_DETAIL_DIALOG_KEY)
-const redialRtcCall = inject(IM_RTC_REDIAL_KEY)
-
-/** 绉佽亰 RTC_CALL_END 姘旀场鐐瑰嚮锛氱敤鍚屾 mediaType 閲嶆嫧 */
-function handleRtcCallBubbleClick() {
- const mediaType = rtcCallEndPrivatePayload.value?.mediaType
- if (mediaType == null) {
- return
- }
- redialRtcCall?.(mediaType)
-}
-
-const multiSelect = useMessageMultiSelect() // 澶氶�夋ā寮忥細妯″潡绾у崟渚� composable
-
-/** 鍚堝苟娑堟伅姘旀场鐐瑰嚮锛氭墦寮�璇︽儏寮圭獥锛堝祵濂楀悎骞剁敱寮圭獥鍐呴儴 push 鏍堬級 */
-function handleMergeOpen(content: string) {
- openMergeDetail?.(content)
-}
-
-/** 鏂囨湰姘旀场 @ mention 鍊欓�夊悕瀛楋細浠呯兢娑堟伅鏈夋晥锛屾寜 atUserIds 鍙嶆煡缇ゆ垚鍛樼湡瀹炴樀绉帮紱闈� TEXT 涓嶈蛋 store 璇伙紝璁� getMentionCandidates 鐩存帴杩斿洖绋冲畾绌烘暟缁� */
-const textMentions = computed<MentionCandidate[]>(() => {
- if (props.message.type !== ImContentType.TEXT) {
- return getMentionCandidates(undefined, null)
- }
- return getMentionCandidates(props.message.atUserIds, conversationStore.activeConversation)
-})
-
-/** 鍚嶇墖鐐瑰嚮锛氱敤鎴峰悕鐗囧脊 UserInfoCard锛岀兢鍚嶇墖寮� GroupInfoCard锛涘叾瀹� targetType锛堝惈鏀瑰寘鑴忔暟鎹級蹇界暐 */
-function handleCardClick(card: CardMessage, e: MouseEvent) {
- if (!card?.targetId) {
- return
- }
- if (card.targetType === ImConversationType.PRIVATE) {
- uiStore.openUserInfoCardAtEvent(
- { id: card.targetId, nickname: card.name, avatar: card.avatar },
- e,
- ImFriendAddSource.CARD
- )
- return
- }
- if (card.targetType === ImConversationType.GROUP) {
- uiStore.openGroupInfoCardAtEvent(
- {
- id: card.targetId,
- name: card.name,
- showImage: card.avatar,
- memberCount: card.memberCount
- },
- e
- )
- }
-}
-
-/** 濯掍綋涓婁紶涓細MessageBubble 鑷覆鏌撻伄缃� / 杩涘害鏉★紱澶栧眰鍙敤浣� showSendingLoading 鍒ゅ畾 */
-const isUploading = computed(() => props.message.uploadProgress != null)
-
-/**
- * 鏄惁鍦ㄦ皵娉″熬閮ㄦ樉绀恒�屽彂閫佷腑銆峫oading 杞湀
- *
- * 鍥剧墖 / 瑙嗛 / 鏂囦欢姘旀场鍐呭祵宸叉湁杩涘害鍙嶉锛堥伄缃� / 杩涘害鏉★級锛屽灞� loading 涓嶅啀鍙犲姞锛�
- * 璇煶姘旀场鍙湁楹﹀厠椋� + 鏃堕暱锛屾棤鍐呭祵杩涘害锛屽繀椤讳繚鐣欏灞� loading 璁╃敤鎴锋劅鐭ユ鍦ㄥ彂閫�
- */
-const showSendingLoading = computed(
- () => props.message.status === ImMessageStatus.SENDING && (!isUploading.value || isVoice.value)
-)
-
-// ==================== 鍙戦�佷汉 / 宸茶 / @ ====================
-
-/** 缇よ亰 + 瀵规柟娑堟伅 鏃讹紝鍦ㄦ皵娉′笂鏂规樉绀哄彂閫佽�呮樀绉� */
-const showSenderName = computed(() => {
- if (props.message.selfSend) {
- return false
- }
- return conversationStore.activeConversation?.type === ImConversationType.GROUP
-})
-
-/** 鍙戦�佽�呭ご鍍忥紱绉佽亰鐨� conversation.avatar 灏辨槸瀵规柟澶村儚锛坥penConversation 鍏ュ弬绾﹀畾锛� */
-const senderAvatar = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || props.message.selfSend) {
- return ''
- }
- if (conversation.type === ImConversationType.GROUP) {
- const group = groupStore.getGroup(conversation.targetId)
- return group?.members?.find((member) => member.userId === props.message.senderId)?.avatar || ''
- }
- return conversation.avatar || ''
-})
-
-/** 澶村儚鑹插崱 fallback 鏂囨湰锛氭案杩滄槸鐪熷疄鏄电О锛屼笉鎺哄娉� */
-const senderRealNickname = computed(() => {
- const conversation = conversationStore.activeConversation
- return getSenderRealNickname(
- props.message.senderId,
- conversation?.type ?? 0,
- conversation?.targetId ?? 0
- )
-})
-
-/** 姘旀场涓婃柟鍙戦�佷汉鏄剧ず鍚嶏紙浠呯兢鑱婂鏂规秷鎭樉绀猴級锛氬ソ鍙嬪娉� > 缇ゅ娉� > 鐪熷疄鏄电О */
-const senderDisplayName = computed(() => {
- const conversation = conversationStore.activeConversation
- return getSenderDisplayName(
- props.message.senderId,
- conversation?.type ?? 0,
- conversation?.targetId ?? 0
- )
-})
-
-/** 绉佽亰銆屽凡璇� / 鏈銆嶆�侊紙浠呭鑷繁鍙戦�佺殑绉佽亰娑堟伅灞曠ず锛涚鑱婂凡璇诲叏灞�鍏抽棴鏃朵笉鍐嶅睍绀猴級 */
-const privateReadLabel = computed(() => {
- if (!MESSAGE_PRIVATE_READ_ENABLED) {
- return ''
- }
- if (!props.message.selfSend) {
- return ''
- }
- if (conversationStore.activeConversation?.type !== ImConversationType.PRIVATE) {
- return ''
- }
- if (props.message.receiptStatus === ImMessageReceiptStatus.DONE) {
- return '宸茶'
- }
- if (props.message.receiptStatus === ImMessageReceiptStatus.PENDING) {
- return '鏈'
- }
- return ''
-})
-
-/** 鏄惁闇�瑕佹樉绀虹兢鍥炴墽 popover锛氳嚜宸卞彂鐨勭兢娑堟伅涓斿悗绔紑鍚簡鍥炴墽锛圢O_RECEIPT 琛ㄧず鍙戦�佹椂鏈姹傚洖鎵э紝涓嶆覆鏌擄紱缇ゅ凡璇诲叏灞�鍏抽棴鏃剁粺涓�涓嶅睍绀猴級 */
-const showGroupReadStatus = computed(() => {
- if (!MESSAGE_GROUP_READ_ENABLED) {
- return false
- }
- if (!props.message.selfSend) {
- return false
- }
- if (conversationStore.activeConversation?.type !== ImConversationType.GROUP) {
- return false
- }
- const status = props.message.receiptStatus
- if (status === undefined || status === null) {
- return false
- }
- return status !== ImMessageReceiptStatus.NO_RECEIPT
-})
-
-/** 褰撳墠缇ゆ垚鍛橈紙渚� MessageReadStatus 璁$畻鏈鍚嶅崟锛涙湭鍔犺浇瀹屾椂鍏滃簳绌烘暟缁勪笉娓叉煋锛� */
-const groupMembersForReadStatus = computed<GroupMemberLite[]>(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return []
- }
- const group = groupStore.getGroup(conversation.targetId)
- return (group?.members || []).map((member) => {
- const friend = friendStore.getFriend(member.userId)
- return {
- userId: member.userId,
- showName: getMemberDisplayName(member, friend),
- nickname: member.nickname,
- avatar: member.avatar,
- status: member.status
- }
- })
-})
-
-/** 鏄惁 @鎴戯紙缇ゆ秷鎭睍绀哄皬寰芥爣锛� */
-const isAtMe = computed(() => {
- const myId = getCurrentUserId()
- if (!myId) {
- return false
- }
- return (props.message.atUserIds || []).includes(myId)
-})
-
-// ==================== 鍙抽敭鑿滃崟 / 鎿嶄綔 ====================
-
-// 鍙抽敭鑿滃崟 key 甯搁噺锛沺ush 绔拰鍒嗗彂绔粠鍚屼竴澶勫彇锛宼ypo 缂栬瘧鏈熷氨鑳芥姄
-const MENU_KEYS = {
- COPY: 'COPY',
- REPLY: 'REPLY',
- FORWARD: 'FORWARD',
- MULTI_SELECT: 'MULTI_SELECT',
- PIN: 'PIN',
- ADD_TO_FACE: 'ADD_TO_FACE',
- MUTE: 'MUTE',
- UNMUTE: 'UNMUTE',
- KICK: 'KICK',
- RECALL: 'RECALL',
- DELETE: 'DELETE'
-} as const
-type MenuKey = (typeof MENU_KEYS)[keyof typeof MENU_KEYS]
-
-/**
- * 鍙抽敭鑿滃崟椤癸細
- * - 寮曠敤锛氬凡钀藉簱 + 鏈挙鍥炵殑娑堟伅鍙紩鐢紝寮曠敤鍧楀啓鍏ヤ細璇濊崏绋�
- * - 鎾ゅ洖 / 鍒犻櫎锛氫簰鏂ワ紱鑷繁鍙戦�� + 宸茶惤搴� + 鏈挙鍥� + 2 鍒嗛挓鍐呮樉绀恒�屾挙鍥炪�嶏紙鎺ㄦ湇鍔″櫒锛夛紝鍏跺畠鏄剧ず銆屽垹闄ゃ�嶏紙浠呮湰鍦版竻锛�
- *
- * 濂藉弸浜嬩欢姘旀场鎬佷笉寮硅彍鍗�
- */
-async function handleContextMenu(e: MouseEvent) {
- if (isFriendChatTipMessage.value) {
- return
- }
- // 澶氶�夋ā寮忎笅涓嶅脊鑿滃崟锛氬彸閿偣鍑诲氨褰撳垏鎹㈠嬀閫夛紝涓庡崟鍑昏涓轰竴鑷�
- if (isInMultiSelect.value) {
- if (canForward.value) {
- multiSelect.toggle(props.message)
- }
- return
- }
-
- type MenuItem = {
- danger?: boolean
- disabled?: boolean
- divided?: boolean
- icon?: string
- key: MenuKey
- name: string
- }
- // 棰戦亾锛堝叕浼楀彿锛変細璇濆崟鍚戞秷鎭細鍙繚鐣欒浆鍙� + 鏈湴鍒犻櫎锛涗笉鏀寔澶嶅埗 / 寮曠敤 / 澶氶�� / 鎾ゅ洖 / 缇ょ鐞嗘搷浣�
- if (isChannelConversation.value) {
- const channelItems: MenuItem[] = []
- if (canForward.value) {
- channelItems.push({
- key: MENU_KEYS.FORWARD,
- name: '杞彂',
- icon: 'ant-design:share-alt-outlined'
- })
- }
- channelItems.push({
- key: MENU_KEYS.DELETE,
- name: '鍒犻櫎',
- icon: 'ant-design:delete-outlined',
- divided: true,
- danger: true
- })
- uiStore.openContextMenu({ x: e.clientX, y: e.clientY }, channelItems, async (item) => {
- if (item.key === MENU_KEYS.FORWARD) {
- handleForward()
- } else if (item.key === MENU_KEYS.DELETE) {
- handleDelete()
- }
- })
- return
- }
-
- const items: MenuItem[] = []
- // 銆屽鍒躲�嶏細浠呮枃鏈秷鎭敮鎸侊紱鏀惧湪绗竴椤癸紝瀵归綈寰俊妗岄潰鍙抽敭涔犳儻
- if (props.message.type === ImContentType.TEXT) {
- items.push({
- key: MENU_KEYS.COPY,
- name: '澶嶅埗',
- icon: 'ant-design:copy-outlined'
- })
- }
- // 銆屽紩鐢ㄣ�嶏細宸茶惤搴� + 鏈挙鍥� + 闈炲悎骞惰浆鍙戯紱MERGE 鍐呭祵蹇収鍦ㄥ紩鐢ㄩ瑙堥噷鏃犳硶闄嶇骇灞曠ず
- if (!!props.message.id && !isRecall.value && !isMerge.value) {
- items.push({
- key: MENU_KEYS.REPLY,
- name: '寮曠敤',
- icon: 'bxs:quote-alt-left'
- })
- }
- // 銆岃浆鍙戙�嶃�屽閫夈�嶏細宸茶惤搴� + 鏅�氭秷鎭� + 鏈挙鍥烇紱瑙﹀彂 ForwardDialog / 杩涘叆澶氶�夋ā寮�
- if (canForward.value) {
- items.push({
- key: MENU_KEYS.FORWARD,
- name: '杞彂',
- icon: 'ant-design:share-alt-outlined'
- }, {
- key: MENU_KEYS.MULTI_SELECT,
- name: '澶氶��',
- icon: 'ant-design:check-square-outlined'
- })
- }
- // 銆岀疆椤躲�嶏細浠呯兢鑱� + 鏅�氭秷鎭� + 宸茶惤搴� + 鏈挙鍥� + 缇や富鎴栫鐞嗗憳锛涘凡缃《涓嶅啀灞曠ず锛岀敱缃《闈㈡澘鐨勩�岀Щ闄ゃ�嶅叆鍙f壙鎺�
- // 涓嶆湰鍦伴鍒や笂闄愶紝璁╁悗绔牎楠岋紝瓒呴檺鏃堕�氳繃 error toast 鍙嶉
- if (canPin.value) {
- items.push({
- key: MENU_KEYS.PIN,
- name: '缃《',
- icon: 'ant-design:pushpin-outlined'
- })
- }
- // 銆屾坊鍔犲埌琛ㄦ儏銆嶏細FACE / IMAGE 娑堟伅 + 宸茶惤搴� + 鏈挙鍥烇紱鍐欏叆涓汉琛ㄦ儏鍖咃紝瀵圭収寰俊銆屾坊鍔犲埌琛ㄦ儏銆�
- if (canAddToFace.value) {
- items.push({
- key: MENU_KEYS.ADD_TO_FACE,
- name: '娣诲姞鍒拌〃鎯�',
- icon: 'ant-design:smile-outlined'
- })
- }
- // 銆岀瑷� / 瑙g / 绉婚櫎銆嶏細缇よ亰 + 闈炶嚜宸辨秷鎭� + 鎴戞槸缇や富鎴栫鐞嗗憳
- if (currentGroup.value && !props.message.selfSend && canManageSender.value) {
- const senderMember = currentGroup.value.members?.find(
- (m) => m.userId === props.message.senderId
- )
- const isMuted = senderMember?.muteEndTime && new Date(senderMember.muteEndTime) > new Date()
- if (isMuted) {
- items.push({
- key: MENU_KEYS.UNMUTE,
- name: '瑙i櫎绂佽█',
- icon: 'ant-design:audio-outlined',
- divided: true
- })
- } else {
- items.push({
- key: MENU_KEYS.MUTE,
- name: '绂佽█',
- icon: 'ant-design:audio-muted-outlined',
- divided: true
- })
- }
- items.push({
- key: MENU_KEYS.KICK,
- name: '绉婚櫎',
- icon: 'ant-design:user-delete-outlined',
- danger: true
- })
- }
- // 銆屾挙鍥� / 鍒犻櫎銆嶄簩閫変竴锛�
- // - 鑷繁鍙戦�� + 宸茶惤搴� + 鏈挙鍥� + 鍦ㄦ挙鍥炵獥鍙e唴 鈫� 鎾ゅ洖锛堟帹鏈嶅姟鍣ㄦ妸娑堟伅鎬佺疆 RECALL锛�
- // - 鍏跺畠锛堝鏂规秷鎭� / 宸叉挙鍥� / 瓒呭嚭鎾ゅ洖绐楀彛锛夆啋 鍒犻櫎锛堜粎鏈湴娓咃紝涓嶅姩鍚庣锛�
- // divided 鎶婅繖涓�椤瑰拰涓婇潰鐨勩�屽紩鐢ㄣ�嶉殧寮�锛宒anger 鏄剧孩瀵归綈寰俊
- const canRecall =
- props.message.selfSend &&
- !!props.message.id &&
- !isRecall.value &&
- Date.now() - props.message.sendTime <= MESSAGE_RECALL_WINDOW_MS
- if (canRecall) {
- items.push({
- key: MENU_KEYS.RECALL,
- name: '鎾ゅ洖',
- icon: 'ant-design:undo-outlined',
- divided: true,
- danger: true
- })
- } else {
- items.push({
- key: MENU_KEYS.DELETE,
- name: '鍒犻櫎',
- icon: 'ant-design:delete-outlined',
- divided: true,
- danger: true
- })
- }
-
- // 鎶婅彍鍗曟覆鏌撲氦缁欏叏灞� uiStore锛堝崟渚嬶紝閬垮厤姣忔潯娑堟伅閮芥寕涓�浠借彍鍗� DOM锛�
- const menuHandlers: Record<MenuKey, () => Promise<void> | void> = {
- [MENU_KEYS.COPY]: handleCopy,
- [MENU_KEYS.REPLY]: handleReply,
- [MENU_KEYS.FORWARD]: handleForward,
- [MENU_KEYS.MULTI_SELECT]: handleEnterMultiSelect,
- [MENU_KEYS.PIN]: handlePin,
- [MENU_KEYS.ADD_TO_FACE]: handleAddToFace,
- [MENU_KEYS.MUTE]: handleMute,
- [MENU_KEYS.UNMUTE]: handleUnmute,
- [MENU_KEYS.KICK]: handleKick,
- [MENU_KEYS.RECALL]: handleRecall,
- [MENU_KEYS.DELETE]: handleDelete
- }
- uiStore.openContextMenu({ x: e.clientX, y: e.clientY }, items, async (item) => {
- await menuHandlers[item.key as MenuKey]?.()
- })
-}
-
-/** 鏄惁鍙�屾坊鍔犲埌琛ㄦ儏銆嶏細FACE / IMAGE 娑堟伅 + 宸茶惤搴� + 鏈挙鍥烇紙GIF / 闈欏浘閮藉厑璁革級 */
-const canAddToFace = computed(() => {
- if (isRecall.value || !props.message.id) {
- return false
- }
- return extractAddableFace(props.message) !== null
-})
-
-/** 娣诲姞鍒颁釜浜鸿〃鎯咃細浠� message 鎶� url + 灏哄 + name 鍐欏叆涓汉琛ㄦ儏搴� */
-async function handleAddToFace() {
- const payload = extractAddableFace(props.message)
- if (!payload) {
- return
- }
- const data = await faceStore.addFaceUserItem(payload)
- if (data) {
- antdMessage.success('宸叉坊鍔犲埌琛ㄦ儏')
- }
-}
-
-/** 褰撳墠婵�娲讳細璇濆搴旂殑缇わ紙绉佽亰鍦烘櫙涓� undefined锛� */
-const currentGroup = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return undefined
- }
- return groupStore.getGroup(conversation.targetId)
-})
-
-/** 褰撳墠鐢ㄦ埛鍦ㄨ缇ら噷鐨勮鑹诧紱绉佽亰鎴栭潪缇ゆ垚鍛� 鈫� undefined */
-const myGroupRole = computed(() => {
- const myId = getCurrentUserId()
- return currentGroup.value?.members?.find((m) => m.userId === myId)?.role
-})
-
-/** 鏄惁鍙鐞嗚娑堟伅鍙戦�佷汉锛氭垜鐨勮鑹查珮浜庣洰鏍囪鑹诧紙缇や富 > 绠$悊鍛� > 鏅�氭垚鍛橈級锛涚洰鏍囪鑹叉湭鐭ユ椂涓嶅睍绀� */
-const canManageSender = computed(() => {
- // 鍘嗗彶閫�缇ょ兢锛氭湰鍦板彲鑳芥畫鐣欐垚鍛樼紦瀛橈紝鏄惧紡鎺掗櫎锛岄伩鍏嶅彸閿粛鍑恒�岀瑷� / 绉婚櫎銆�
- if (!currentGroup.value || isGroupQuit(currentGroup.value) || !myGroupRole.value) {
- return false
- }
- const senderMember = currentGroup.value.members?.find((m) => m.userId === props.message.senderId)
- // 鎴愬憳缂撳瓨涓嶅畬鏁存椂涓嶅睍绀虹鐞嗘搷浣滐紝绛夋垚鍛樻暟鎹ˉ榻愬悗鍐嶅垽鏂�
- if (!senderMember?.role) {
- return false
- }
- // 瑙掕壊鏁板�硷細1=缇や富 2=绠$悊鍛� 3=鏅�氭垚鍛橈紱鏁板�艰秺灏忔潈闄愯秺楂�
- return myGroupRole.value < senderMember.role
-})
-
-/** 鏄惁鍏佽杞彂 / 澶氶�夛細鏅�氭秷鎭� + 宸茶惤搴� + 鏈挙鍥烇紱鏈湴鍗犱綅 / 鎾ゅ洖 / 浜嬩欢娑堟伅涓�寰嬩笉鍙� */
-const canForward = computed(
- () => isNormalMessage(props.message.type) && !!props.message.id && !isRecall.value
-)
-
-/** 澶氶�夋ā寮忔槸鍚︽縺娲伙紱鍒囨崲浼氳瘽鐢� index.vue 涓诲姩 exitMultiSelect */
-const isInMultiSelect = computed(() => multiSelect.state.active)
-
-/** 褰撳墠娑堟伅鏄惁宸插嬀閫� */
-const isMessageChecked = computed(() => {
- if (!isInMultiSelect.value) {
- return false
- }
- return multiSelect.selectedIdSet.value.has(props.message.clientMessageId)
-})
-
-/** 澶氶�夋ā寮忎笅鐐瑰嚮姘旀场锛氬彲杞彂鐨勬秷鎭垏鎹㈠嬀閫夛紱浜嬩欢 / 鎾ゅ洖 / 鍗犱綅绛変笉鍝嶅簲锛堢洿鎺ュ悆鎺夌偣鍑婚伩鍏嶈Е鍙戝浘鐗囬瑙堢瓑锛� */
-function handleMultiSelectClick(e: MouseEvent) {
- if (!isInMultiSelect.value) {
- return
- }
- e.preventDefault()
- e.stopPropagation()
- if (!canForward.value) {
- return
- }
- multiSelect.toggle(props.message)
-}
-
-/** 鏄惁鍏佽缃《锛堝凡缃《娑堟伅涓嶅啀灞曠ず鑿滃崟椤癸紝鐢辩疆椤堕潰鏉跨殑銆岀Щ闄ゃ�嶅叆鍙f壙鎺ワ級锛氱兢鑱� + 鏅�氭秷鎭� + 宸茶惤搴� + 鏈挙鍥� + 缇や富鎴栫鐞嗗憳 + 鏈疆椤� */
-const canPin = computed(
- () =>
- !!currentGroup.value &&
- !isGroupQuit(currentGroup.value) &&
- isNormalMessage(props.message.type) &&
- !!props.message.id &&
- !isRecall.value &&
- (myGroupRole.value === ImGroupMemberRole.OWNER ||
- myGroupRole.value === ImGroupMemberRole.ADMIN) &&
- !currentGroup.value.pinnedMessages?.some((m) => m.id === props.message.id)
-)
-
-/** 缃《娑堟伅锛氫簩娆$‘璁� 鈫� 璋冨悗绔� pin-message锛涘悗绔箍鎾� GROUP_MESSAGE_PIN锛屾湰绔� dispatcher 鎷夋渶鏂� pinnedMessages */
-async function handlePin() {
- const group = currentGroup.value
- if (!group || !props.message.id) {
- return
- }
- try {
- await confirm('灏嗗湪褰撳墠缇ゆ垚鍛樼殑鑱婂ぉ涓疆椤�', '缃《娑堟伅')
- await apiPinGroupMessage({ id: group.id, messageId: props.message.id })
- antdMessage.success('宸茬疆椤�')
- } catch {}
-}
-
-/** 澶嶅埗鏂囨湰娑堟伅锛氳В鍑� content 瀛楁鍐欏叆鍓创鏉匡紝鎻愮ず銆屽唴瀹瑰凡澶嶅埗鍒板壀璐存澘銆� */
-async function handleCopy() {
- const text = parseMessage<TextMessage>(props.message.content)?.content
- if (!text) {
- return
- }
- await copyToClipboard(text)
- antdMessage.success('鍐呭宸插鍒跺埌鍓创鏉�')
-}
-
-/** 杩涘叆寮曠敤妯″紡锛氭妸褰撳墠娑堟伅鏋勯�犳垚 QuoteMessage 鍐欏叆浼氳瘽鑽夌 */
-function handleReply() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- conversationStore.setConversationReplyDraft(conversation, buildQuoteFromMessage(props.message))
-}
-
-/** 杞彂褰撳墠娑堟伅锛氭墦寮� ForwardDialog锛堝崟鏉℃ā寮忥紱mode=single 鍗冲師鏍疯浆锛� */
-function handleForward() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- openForwardDialog?.({
- mode: ImForwardMode.SINGLE,
- messages: [props.message],
- sourceConversation: conversation
- })
-}
-
-/** 杩涘叆澶氶�夋ā寮忥紝鍒濆鍕鹃�夊綋鍓嶆秷鎭� */
-function handleEnterMultiSelect() {
- multiSelect.enter(props.message)
-}
-
-/**
- * 鎾ゅ洖娑堟伅锛氬脊纭妗� 鈫� 璋� useMessageSender.recall 鈫� 鍚庣閫氳繃 WS RECALL 浜嬩欢鎺ㄥ洖锛�
- * websocketStore 鎶婂搴� message 鐨� type 鏀规垚 RECALL锛孶I 鑷姩鍒囧埌"XX 鎾ゅ洖浜嗕竴鏉℃秷鎭�"
- *
- * 涓嶅仛涔愯鎾ゅ洖锛氬け璐� / 瓒呮椂 / 鍚庣鎷掔粷鏃舵湰绔姸鎬佸彲鑳戒笌鏈嶅姟绔紓绉伙紝缁熶竴璁� WS 鍥炴帹鏈�绋�
- */
-async function handleRecall() {
- try {
- await confirm('纭畾瑕佹挙鍥炶繖鏉℃秷鎭悧锛�', '鎾ゅ洖娑堟伅')
- await recall(props.message)
- } catch {}
-}
-
-/**
- * 澶辫触娑堟伅鐐瑰嚮閲嶈瘯
- *
- * - 濯掍綋娑堟伅锛坕mage / file / voice / video锛夛細_localFile 鍦ㄥ唴瀛樺氨閲嶈蛋 uploadAndSendMedia锛堥噸鏂颁笂浼� + 鍗犱綅 + 杩涘害锛�
- * - 鏂囨湰娑堟伅锛氬鐢ㄥ師 clientMessageId + status 鍥炴粴鍒� SENDING锛岃蛋 existingClientMessageId 璺緞璁╂湇鍔$鎸� cmid 骞傜瓑
- *
- * 濯掍綋绫诲瀷鑻� _localFile 宸蹭涪锛堢悊璁轰笂 IDB 鎭㈠闃舵灏辫 drop锛岃繘涓嶅埌杩欓噷锛涗繚闄╄捣瑙佷粛璧版枃鏈厹搴曪級鍒欐寜鏂囨湰璺緞閲嶅彂锛�
- * 鍚庣鎷掔粷澶辨晥 blob URL 鏃跺啀娆� FAILED锛岀敤鎴峰彲鍙抽敭鍒犻櫎
- *
- * 涓嶈繕鍘熷師 receipt锛氱兢鍥炴墽鏄彂閫佹椂鐨勬墿灞曢�夐」銆佷笉浼氭寔涔呭寲鍒� message锛屽己琛岀寽娴嬪彲鑳戒笌鍘熸剰涓嶇锛�
- * 榛樿鎸�"鏃犲洖鎵�"閲嶅彂锛岀粷澶у鏁板満鏅鍚堥鏈燂紝瑕佸洖鎵у氨閲嶆柊鍙戜竴娆℃洿鐩磋
- */
-async function handleResend() {
- if (props.message.status !== ImMessageStatus.FAILED) {
- return
- }
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- // 绂佽█ / 灏佺鏃舵嫤鎴細閬垮厤閲嶈瘯缁曡繃 MessageInput 鐨� muteOverlay 鍙堣蛋涓�娆� sendRaw 璁╁悗绔嫆
- if (muteOverlay.value) {
- return
- }
- const message = props.message
- const file = message._localFile
-
- // 濯掍綋绫诲瀷 + _localFile 鍦� 鈫� 閲嶈蛋 uploadAndSendMedia锛泃ype 鍒嗗彂 + 鏃у厓鏁版嵁澶嶇敤缁熶竴鍦� mediaTypeHandlers 琛ㄩ噷
- if (isMediaMessageType(message.type) && file) {
- const handler = mediaTypeHandlers[message.type]
- if (handler) {
- const oldQuote = getQuoteFromMessage(message.content) ?? undefined
- const context = handler.extractResendContext(message.content)
- await uploadAndSendMedia({
- file,
- type: message.type,
- quote: oldQuote,
- conversation,
- context,
- existingClientMessageId: message.clientMessageId
- })
- return
- }
- }
-
- // 鏂囨湰绫诲瀷 / 濯掍綋绫诲瀷浣� _localFile 宸蹭涪锛氭妸 FAILED 鍗犱綅鍥炴粴鍒� SENDING锛屽鐢� clientMessageId 璁╂湇鍔$鎸� cmid 骞傜瓑鍘婚噸
- messageStore.patchMessage(conversation.type, conversation.targetId, message.clientMessageId, {
- status: ImMessageStatus.SENDING
- })
- await sendRaw(message.type, message.content, {
- atUserIds: message.atUserIds,
- existingClientMessageId: message.clientMessageId
- })
-}
-
-/**
- * 鍒犻櫎娑堟伅锛氭湰鍦拌蒋鍒狅紝浠呬粠 messageStore 绉婚櫎锛屼笉璋冨悗绔�
- * 鍖哄埆浜�"鎾ゅ洖"锛氭湇鍔$娌″姩锛屽绔櫥褰曟椂鍏跺畠瀹㈡埛绔� / 缇ら噷鍏朵粬浜轰緷鐒惰兘鐪嬪埌杩欐潯
- */
-/** 绂佽█锛歟mit 缁欑埗缁勪欢鎵撳紑鏃堕暱閫夋嫨寮圭獥锛堥伩鍏� MessageItem 杩囬噸锛� */
-function handleMute() {
- const group = currentGroup.value
- if (!group) {
- return
- }
- const name = senderDisplayName.value || '璇ユ垚鍛�'
- emit('mute', group.id, props.message.senderId, name)
-}
-
-/** 瑙i櫎绂佽█ */
-async function handleUnmute() {
- const group = currentGroup.value
- if (!group) {
- return
- }
- try {
- await confirm('纭畾瑙i櫎璇ユ垚鍛樼殑绂佽█鍚楋紵', '瑙i櫎绂佽█')
- await cancelMuteMember({ id: group.id, userId: props.message.senderId })
- antdMessage.success('宸茶В闄ょ瑷�')
- emit('reload')
- } catch {}
-}
-
-/** 绉婚櫎缇ゆ垚鍛� */
-async function handleKick() {
- const group = currentGroup.value
- if (!group) {
- return
- }
- const name = senderDisplayName.value || '璇ユ垚鍛�'
- try {
- await confirm(`纭畾灏嗐��${name}銆嶇Щ鍑虹兢鑱婂悧锛焋, '绉婚櫎鎴愬憳')
- await removeGroupMember({ groupId: group.id, memberUserIds: [props.message.senderId] })
- antdMessage.success('宸茬Щ闄�')
- emit('reload')
- } catch {}
-}
-
-function handleDelete() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- messageStore.removeMessage(conversation.type, conversation.targetId, {
- id: props.message.id,
- clientMessageId: props.message.clientMessageId
- })
-}
-</script>
-
-<template>
- <!-- 鏃堕棿鍒嗛殧鏉★細鍒楄〃绗竴鏉� / 璺濅笂涓�鏉¤秴杩囬槇鍊兼椂灞呬腑鏄剧ず鐏拌壊鏃堕棿 -->
- <div
- v-if="shouldShowTimeTip"
- class="flex items-center justify-center px-4 py-2 text-12px text-[var(--ant-color-text-disabled)]"
- >
- {{ formatTimeTip(message.sendTime) }}
- </div>
-
- <!-- 濂藉弸浼氳瘽浜嬩欢锛團RIEND_ADD / FRIEND_DELETE锛夛細璺熺兢骞挎挱浜嬩欢鍚岀伆鑹叉牱寮忥紝鏂囨鍥哄畾 -->
- <div
- v-if="isFriendChatTipMessage"
- class="flex items-center justify-center px-4 py-2 text-12px text-[var(--ant-color-text-secondary)]"
- >
- <TipSegments :segments="friendChatTipSegments" />
- </div>
-
- <!-- 缇ゅ箍鎾簨浠讹細璺熷ソ鍙嬩簨浠跺悓鐏拌壊鏍峰紡锛宮ention 娈垫寕鐐瑰嚮寮� UserInfoCard -->
- <div
- v-else-if="isGroupNotificationMessage"
- class="flex items-center justify-center px-4 py-2 text-12px text-[var(--ant-color-text-secondary)]"
- >
- <TipSegments :segments="groupNotificationSegments" />
- </div>
-
- <!-- 缇ら�氳瘽浜嬩欢锛圧TC_CALL_START / RTC_CALL_END锛夛細灞呬腑鐏拌壊 tip 涓ゆ寮� -->
- <div
- v-else-if="isRtcCallTipMessage"
- class="flex items-center justify-center px-4 py-2 text-12px text-[var(--ant-color-text-secondary)]"
- >
- <TipSegments :segments="rtcCallTipSegments" />
- </div>
-
- <!-- 绉佽亰閫氳瘽缁撴潫锛圧TC_CALL_END锛夛細浠垮井淇°�屽噯姘旀场銆嶏紱鎸� selfSend 宸﹀彸鍒嗗竷锛涚數璇濆浘鏍� + 鏂囨 -->
- <div v-else-if="isRtcCallPrivateBubbleMessage" class="flex gap-2 items-start px-4 py-2">
- <div
- class="flex flex-1 min-w-0 gap-2 items-start"
- :class="{ 'flex-row-reverse': message.selfSend }"
- >
- <UserAvatar
- :id="message.selfSend ? getCurrentUserId() : message.senderId"
- :name="senderRealNickname"
- :url="message.selfSend ? userStore.userInfo?.avatar : senderAvatar"
- :size="36"
- />
- <div
- class="flex gap-2 items-center px-3.5 py-2 text-sm rounded-lg cursor-pointer"
- :class="
- message.selfSend
- ? 'text-black bg-[#95ec69]'
- : 'text-[var(--ant-color-text)] bg-[var(--ant-color-fill-secondary)]'
- "
- @click="handleRtcCallBubbleClick"
- >
- <Icon icon="ant-design:phone-outlined" :size="16" class="rotate-[135deg] flex-shrink-0" />
- <span class="whitespace-nowrap">{{ rtcCallPrivateBubbleText }}</span>
- </div>
- </div>
- </div>
-
- <!-- 鎾ゅ洖娑堟伅锛氭暣琛岀伆鑹� tip锛宻ender 鍚嶆鍙偣鍑� -->
- <div
- v-else-if="isRecall"
- class="flex items-center justify-center px-4 py-2 text-12px text-[var(--ant-color-text-secondary)]"
- >
- <TipSegments :segments="recallTipSegments" />
- </div>
-
- <!-- 鏅�氭秷鎭皵娉� -->
- <div
- v-else
- class="flex gap-2 items-start px-4 py-2"
- :class="{ 'cursor-pointer': isInMultiSelect }"
- @contextmenu.prevent="handleContextMenu"
- @click.capture="handleMultiSelectClick"
- >
- <!-- 澶氶�夋寚绀猴細姘歌繙鍦ㄦ暣琛屾渶宸︿晶锛堣嚜宸� / 瀵规柟娑堟伅涓�鑷达級锛屼笉鍙備笌 selfSend reverse -->
- <span
- v-if="isInMultiSelect"
- class="flex flex-shrink-0 items-center justify-center mt-3 w-5 h-5 rounded-full transition-colors"
- :class="
- isMessageChecked
- ? 'bg-[#07c160]'
- : 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
- "
- >
- <Icon v-if="isMessageChecked" icon="ant-design:check-outlined" :size="12" color="#fff" />
- </span>
-
- <!-- 娑堟伅琛岋細澶村儚 + 姘旀场鍐呴儴鎸� selfSend reverse锛涢閬撶礌鏉愪豢鍏紬鍙锋牱寮忓眳涓笖涓嶆樉绀哄ご鍍� -->
- <div
- class="flex flex-1 min-w-0 gap-2 items-start"
- :class="{ 'flex-row-reverse': message.selfSend, 'justify-center': isMaterial }"
- >
- <!-- 澶村儚锛氱偣鍑诲脊 UserInfoCard 鐢� UserAvatar 鍐呴儴鎵挎帴锛涢閬撶礌鏉愭秷鎭笉鏄剧ず澶村儚 -->
- <UserAvatar
- v-if="!isMaterial"
- :id="message.selfSend ? getCurrentUserId() : message.senderId"
- :name="senderRealNickname"
- :url="message.selfSend ? userStore.userInfo?.avatar : senderAvatar"
- :size="36"
- />
-
- <div
- class="flex flex-col gap-0.5"
- :class="[
- message.selfSend ? 'items-end' : '',
- // 鍏紬鍙蜂細璇濆唴绱犳潗锛氬浐瀹� 360 瀹斤紙瀵归綈寰俊鍏紬鍙峰崱鐗囷級锛涘叾瀹冿紙鍚鑱� / 缇よ亰杞彂鐨勭礌鏉愶級锛�70% 涓婇檺锛屾皵娉¤嚜宸辨拺瀹藉害
- isMaterial ? 'w-[360px]' : 'max-w-[70%]'
- ]"
- >
- <!-- 缇よ亰瀵规柟娑堟伅锛氭皵娉′笂鏂规樉绀哄彂閫佽�呮樀绉� -->
- <div
- v-if="showSenderName"
- class="mb-0.5 text-12px text-[var(--ant-color-text-secondary)] leading-tight"
- >
- {{ senderDisplayName }}
- </div>
- <div class="flex gap-1.5 items-center" :class="{ 'flex-row-reverse': message.selfSend }">
- <!-- 娑堟伅鍐呭锛氭寜 type 璧� 9 绫绘皵娉★紝缁熶竴鐢� MessageBubble 娓叉煋 -->
- <MessageBubble
- :type="message.type"
- :content="message.content"
- :self-send="message.selfSend"
- :upload-progress="message.uploadProgress"
- :mentions="textMentions"
- @click-card="handleCardClick"
- @open-merge="handleMergeOpen"
- />
-
- <!-- 鐘舵�佸尯锛氳嚜宸辨秷鎭睍绀哄彂閫佺姸鎬� + 宸茶/缇ゅ洖鎵э紱瀵规柟娑堟伅 + @鑷繁鏃跺睍绀� @寰芥爣 -->
- <div class="flex gap-1.5 items-center text-base">
- <template v-if="message.selfSend">
- <Icon
- v-if="showSendingLoading"
- icon="ant-design:loading-outlined"
- class="im-loading-spin"
- />
- <Icon
- v-else-if="message.status === ImMessageStatus.FAILED"
- icon="ant-design:warning-filled"
- color="#f56c6c"
- class="cursor-pointer"
- title="鍙戦�佸け璐ワ紝鐐瑰嚮閲嶈瘯"
- @click="handleResend"
- />
- <!-- 宸茶鎬侊紙绉佽亰锛� -->
- <span
- v-else-if="privateReadLabel"
- class="text-12px whitespace-nowrap"
- :class="
- message.receiptStatus === ImMessageReceiptStatus.DONE
- ? 'text-[#409eff]'
- : 'text-[var(--ant-color-text-secondary)]'
- "
- >
- {{ privateReadLabel }}
- </span>
- <!-- 缇ゅ洖鎵э細鐐瑰嚮寮� popover 灞曠ず宸茶 / 鏈鎴愬憳鍒楄〃 -->
- <MessageReadStatus
- v-else-if="showGroupReadStatus"
- :message="message"
- :group-id="conversationStore.activeConversation?.targetId || 0"
- :group-members="groupMembersForReadStatus"
- class="text-12px whitespace-nowrap text-[var(--ant-color-text-secondary)]"
- />
- </template>
- <!-- 缇� @ 鎴戞彁绀� -->
- <Tag
- v-if="!message.selfSend && isAtMe"
- danger
- size="small"
- class="ml-1"
- >
- @鎴�
- </Tag>
- </div>
- </div>
- <!-- 寮曠敤鍧楋細姘旀场涓嬫柟锛宻elfSend 鏃剁珫绾垮湪鍙充晶 -->
- <ReplyPreview
- v-if="quote"
- :quote="quote"
- clickable
- :mirrored="message.selfSend"
- class="max-w-[280px]"
- @locate="emit('locate', $event)"
- />
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* @keyframes 闇�瑕� SCSS 澹版槑锛汼ENDING 鐘舵�佺殑杞湀鍔ㄧ敾 */
-.im-loading-spin {
- animation: im-loading-spin 1s linear infinite;
-}
-@keyframes im-loading-spin {
- to {
- transform: rotate(360deg);
- }
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/message-panel.vue b/src/views/im/home/pages/conversation/components/message/message-panel.vue
deleted file mode 100644
index 3043ec5..0000000
--- a/src/views/im/home/pages/conversation/components/message/message-panel.vue
+++ /dev/null
@@ -1,805 +0,0 @@
-<script lang="ts" setup>
-import type { GroupMemberLite } from '../../../../components/group'
-import type { GroupLite } from '../../../../types'
-
-import { computed, nextTick, provide, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-
-import { message, Popover, Tooltip } from 'ant-design-vue'
-
-import { createCall } from '#/api/im/rtc'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { ImConversationType, ImRtcCallMediaType, ImRtcCallStatus } from '#/views/im/utils/constants'
-import { getClientConversationId } from '#/views/im/utils/db'
-import { resolveCallEndReasonText } from '#/views/im/utils/message'
-import { getGroupDisplayName, getMemberDisplayName, isGroupQuit } from '#/views/im/utils/user'
-
-import { GroupMuteMemberDialog } from '../../../../components/group'
-import {
- RtcCallMemberPickerDialog,
- RtcGroupCallBanner
-} from '../../../../components/rtc'
-import { useMessageMultiSelect } from '../../../../composables/useMessageMultiSelect'
-import { useVoicePlayer } from '../../../../composables/useVoicePlayer'
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useFriendStore } from '../../../../store/friendStore'
-import { useGroupStore } from '../../../../store/groupStore'
-import { useMessageStore } from '../../../../store/messageStore'
-import { useRtcStore } from '../../../../store/rtcStore'
-import { useImUiStore } from '../../../../store/uiStore'
-import { ConversationGroupSide } from '../conversation'
-import { ConversationPrivateSide } from '../conversation'
-import { MessageInput } from '../input'
-import { MessageMultiSelectBar } from '../input'
-import { MessageForwardDialog } from './forward'
-import { MessageMergeDetailDialog } from './forward'
-import {
- IM_FORWARD_DIALOG_KEY,
- IM_MERGE_DETAIL_DIALOG_KEY,
- IM_RTC_REDIAL_KEY
-} from './forward/keys'
-import GroupPinnedMessage from './group-pinned-message.vue'
-import GroupRequestPending from './group-request-pending.vue'
-import MessageHistory from './message-history.vue'
-import MessageItem from './message-item.vue'
-
-defineOptions({ name: 'ImMessagePanel' })
-
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-const friendStore = useFriendStore()
-const uiStore = useImUiStore()
-const groupStore = useGroupStore()
-const rtcStore = useRtcStore()
-const listRef = ref<HTMLElement>()
-
-// ==================== 杞彂 / 鍚堝苟娑堟伅璇︽儏锛氭湰鍦� dialog 娴眰 ====================
-// MessageItem / MessageMultiSelectBar / MessageHistory 閫氳繃 inject 瑙﹀彂锛涗笉鎸傚叏灞� store
-
-const forwardDialogRef = ref<InstanceType<typeof MessageForwardDialog>>()
-const mergeDetailDialogRef = ref<InstanceType<typeof MessageMergeDetailDialog>>()
-
-provide(IM_FORWARD_DIALOG_KEY, (opts) => forwardDialogRef.value?.open(opts))
-provide(IM_MERGE_DETAIL_DIALOG_KEY, (content) => mergeDetailDialogRef.value?.open(content))
-provide(IM_RTC_REDIAL_KEY, (mediaType: number) => {
- if (isPrivate.value) {
- void startPrivateCall(mediaType)
- }
-}) // 绉佽亰 RTC_CALL_END 姘旀场鐐瑰嚮閲嶆嫧锛汳essageItem 娉ㄥ叆鍚庤皟鐢�
-
-// ==================== 澶氶�夋ā寮� ====================
-// 妯″潡绾у崟渚� state锛坈omposable锛夛紱鏈粍浠朵粎鍋氬垏浼氳瘽閫�鍑� + template 鏄鹃殣鍒ゅ畾
-
-const multiSelect = useMessageMultiSelect()
-const voicePlayer = useVoicePlayer()
-
-/** 鍒囦細璇濋��鍑哄閫� + 鍋滆闊筹紱閬垮厤涓婁竴浼氳瘽鐨勫嬀閫� / 鎾斁鎬佹硠婕忓埌鏂颁細璇濓紙type+targetId 涓�璧风洃鍚紝绉佽亰涓庣兢鑱� id 鍚屽彿鏃朵篃鑳借Е鍙戯級 */
-watch(
- () => [
- conversationStore.activeConversation?.type,
- conversationStore.activeConversation?.targetId
- ],
- () => {
- multiSelect.exit()
- voicePlayer.stop()
- }
-)
-
-const messages = computed(() => {
- const conversation = conversationStore.activeConversation
- return conversation
- ? messageStore.getMessages(getClientConversationId(conversation.type, conversation.targetId))
- : []
-})
-const isGroup = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.GROUP
-)
-const isPrivate = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.PRIVATE
-)
-const isChannel = computed(
- () => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
-)
-
-/** 褰撳墠婵�娲讳細璇濇槸鍚﹀巻鍙查��缇ょ兢锛氱缇ら�氳瘽銆侀殣钘忕兢鐢宠妯箙绛夋搷浣滃叆鍙o紱鑱婂ぉ鍘嗗彶銆佺兢鍚嶅ご鍍忕収甯稿睍绀� */
-const isQuitGroup = computed(() => {
- const conversation = conversationStore.activeConversation
- return (
- conversation?.type === ImConversationType.GROUP &&
- isGroupQuit(groupStore.getGroup(conversation.targetId))
- )
-})
-
-/** 绉佽亰浼氳瘽涓斿绔笉鏄湁鏁堝ソ鍙嬶紙鏈 friend 璁板綍缂哄け鎴� DISABLE锛夛紱鍗曡竟鍒犻櫎璇箟涓嬨�岃瀵规柟鍒犻櫎銆嶄笉瑙﹀彂鏈妯箙 */
-const showNotFriendBanner = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.PRIVATE) {
- return false
- }
- return !friendStore.isActiveFriend(conversation.targetId)
-})
-
-/** 鐐瑰嚮銆屽鏂硅繕涓嶆槸浣犵殑鏈嬪弸銆嶈兌鍥婏細鎵撳紑 UserInfoCard锛屽紩瀵肩敤鎴烽噸鏂版坊鍔� */
-function handleNotFriendClick(event: MouseEvent) {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
- uiStore.openUserInfoCard(
- {
- id: conversation.targetId,
- nickname: conversation.name,
- avatar: conversation.avatar
- },
- { x: rect.left, y: rect.bottom + 4 }
- )
-}
-
-/**
- * 缇よ亰 header 鏄剧ず鐨勪汉鏁帮細浼樺厛 groupStore.memberCount锛堟棤闇�绛夋垚鍛樺垪琛級锛屾棤鍊煎啀鍥為�� members.length
- *
- * 涔嬫墍浠ヤ笉鐩存帴鐢� groupMembers.value.length锛氭垚鍛樺垪琛ㄦ槸鎸夐渶鎳掑姞杞界殑锛屽垰鍒囧埌缇ゆ椂鏈姞杞藉畬锛�
- * 鑰� groupInfo.memberCount 璺熺兢淇℃伅涓�璧锋潵锛岃兘鏇存棭鏄剧ず浜烘暟閬垮厤"鍏堢┖鍐嶈功"
- */
-const headerMemberCount = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return 0
- }
- const group = groupStore.getGroup(conversation.targetId)
- return group?.memberCount ?? group?.members?.length ?? 0
-})
-
-/** 椤堕儴鍓爣棰橈細浠呭綋缇ゅ娉� 鈮� 鍘熺兢鍚嶆椂鏄剧ず鍘熺兢鍚嶏紙瀵归綈寰俊 PC 鍙岃 header锛� */
-const headerSubtitle = computed(() => {
- const remark = groupInfo.value?.groupRemark
- const name = groupInfo.value?.name
- return remark && name && remark !== name ? name : ''
-})
-
-const BOTTOM_THRESHOLD = 80 // "鏄惁鍋滅暀鍦ㄥ簳閮�"鐨勯槇鍊硷細璺濈搴曢儴 < 80px 瑙嗕负搴曢儴
-const showJumpToBottom = ref(false) // 褰撳墠鏄惁宸蹭笉鍦ㄥ簳閮紙鏄剧ず"鍥炲埌搴曢儴"鎸夐挳锛�
-const newMessageCount = ref(0) // 涓嶅湪搴曢儴鏈熼棿绱鐨勬柊娑堟伅鏁�
-
-/**
- * 褰撳墠婵�娲荤殑缇よ鎯咃細浼樺厛 groupStore锛堝甫璇︾粏瀛楁锛夛紝鏈姞杞藉畬鏃剁敤 activeConversation 鍏滃簳
- *
- * groupStore 鏄寜闇�鎳掑姞杞界殑锛屽垵娆¤繘缇ゆ椂 ensureGroupData 瑙﹀彂鍚庢墠浼氭湁瀹屾暣鏁版嵁锛�
- * 鍏滃簳瀛楁锛坣ame / avatar锛変繚璇� header 涓嶄細"闂┖"锛宯otice / ownerId / memberCount
- * 蹇呴』绛� store 灏变綅鎵嶆湁鍊硷紙杩欎簺瀛楁鍦� conversation 閲屾病鏈夛級
- */
-const groupInfo = computed<
- | (GroupLite & {
- groupRemark?: string
- notice?: string
- ownerId?: number
- remarkNickName?: string
- })
- | undefined
->(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return undefined
- }
- const group = groupStore.getGroup(conversation.targetId)
- const selfMember = group?.members?.find((member) => member.userId === getCurrentUserId())
- const showGroupName = group ? getGroupDisplayName(group) : conversation.name
- return {
- id: conversation.targetId,
- name: group?.name || conversation.name,
- showGroupName,
- showImage: group?.avatar || conversation.avatar,
- notice: group?.notice,
- remarkNickName: selfMember?.displayUserName,
- groupRemark: group?.groupRemark,
- ownerId: group?.ownerUserId,
- memberCount: group?.memberCount,
- joinApproval: group?.joinApproval
- }
-})
-
-/** 缇ゆ垚鍛樺垪琛細鐩存帴鍙� groupStore 缂撳瓨锛宮ap 鎴� GroupMemberLite 缁欎笅娓告秷璐癸紙@-mention / 閭�璇风瓑锛� */
-const groupMembers = computed<GroupMemberLite[]>(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return []
- }
- const group = groupStore.getGroup(conversation.targetId)
- return (group?.members || []).map((member) => {
- // 鏄剧ず鍚嶈蛋銆屽ソ鍙嬪娉� > 缇ゅ娉� > 鐪熷疄鏄电О銆嶄笁绾э紱澶村儚璧� nickname 淇濈ǔ瀹�
- const friend = friendStore.getFriend(member.userId)
- return {
- userId: member.userId,
- showName: getMemberDisplayName(member, friend),
- nickname: member.nickname,
- avatar: member.avatar,
- status: member.status,
- role: member.role
- }
- })
-})
-
-/** 鍒囨崲鍒扮兢浼氳瘽鏃跺悓姝ョ兢淇℃伅 + 鎴愬憳 */
-async function ensureGroupData(groupId: number) {
- // 杩滅▼鎷夌兢淇℃伅锛堢兢鍚� / 鍏憡 / 缇や富绛夊厓鏁版嵁锛�
- await groupStore.fetchGroupInfo(groupId).catch((error) => {
- console.warn('[IM MessagePanel] fetchGroupInfo 澶辫触', { groupId }, error)
- })
- if (isGroupQuit(groupStore.getGroup(groupId))) {
- return
- }
-
- // 鍏堜粠 IDB 鍚屾鍔犺浇缇ゆ垚鍛橈紝璁╅甯х珛鍗冲嚭鎴愬憳鍚� / 澶村儚
- await groupStore.loadGroupMemberList(groupId).catch((error) => {
- console.warn('[IM MessagePanel] loadGroupMemberList 澶辫触', { groupId }, error)
- return null
- })
- const group = groupStore.getGroup(groupId)
- if (!group?.membersLoaded || group.membersExpired) {
- groupStore.fetchGroupMemberList(groupId, true).catch((error) => {
- console.warn('[IM MessagePanel] fetchGroupMemberList 澶辫触', { groupId }, error)
- })
- }
-}
-
-/** 缇や俊鎭娊灞夐噷鐐�"鍒锋柊"锛氬己鎷変竴娆℃渶鏂扮兢鍏冩暟鎹� + 缇ゆ垚鍛� */
-function reloadGroupData() {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return
- }
- groupStore.fetchGroupInfo(conversation.targetId, true)
- groupStore.fetchGroupMemberList(conversation.targetId, true)
-}
-
-const historyDialogRef = ref<InstanceType<typeof MessageHistory>>() // 鍘嗗彶娑堟伅鎶藉眽 ref锛氥�岃亰澶╁巻鍙层�峣con / 鎶藉眽銆屾煡鎵捐亰澶╁唴瀹广�嶅叆鍙i兘璋� open() 瑙﹀彂
-const sideVisible = ref(false) // 淇℃伅鎶藉眽寮�鍏筹細缇よ亰 / 绉佽亰鍏辩敤涓�涓� ref
-const muteMemberDialogRef = ref<InstanceType<typeof GroupMuteMemberDialog>>()
-const callMemberPickerRef = ref<InstanceType<typeof RtcCallMemberPickerDialog>>()
-const pendingMediaType = ref<null | number>(null) // 缇ら�氳瘽鍙戣捣锛氭垚鍛橀�夋嫨寮圭獥鎵撳紑鏈熼棿涓存椂鎸佹湁鐨� mediaType
-
-/** 娑堟伅鍙抽敭鑿滃崟銆岀瑷�銆嶁啋 鎵撳紑鏃堕暱閫夋嫨寮圭獥 */
-function handleMuteMember(groupId: number, userId: number, displayName: string) {
- muteMemberDialogRef.value?.open(groupId, userId, displayName)
-}
-
-/** 淇℃伅鎶藉眽鐨� toggle锛氳窡 header 涓� 3 鐐瑰浘鏍囨寜閽叡鐢� */
-function toggleSide() {
- sideVisible.value = !sideVisible.value
-}
-
-const callPopoverVisible = ref(false) // 绉佽亰閫氳瘽鍏ュ彛锛歱opover 瑙﹀彂锛涚偣 璇煶 / 瑙嗛 鐩存帴鍙戣捣
-const callInviting = ref(false) // 閫氳瘽鍙戣捣涓�
-async function startPrivateCall(mediaType: number) {
- callPopoverVisible.value = false
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- await doInvite({
- conversationType: ImConversationType.PRIVATE,
- mediaType,
- inviteeIds: [conversation.targetId]
- })
-}
-
-/** 缇ら�氳瘽鍏ュ彛锛氶粯璁よ闊崇洿鎺ュ脊閫変汉锛涗笌寰俊缇ら�氳瘽涓�鑷达紝杩涢�氳瘽鍚庣敤鎴锋寜闇�寮�鎽勫儚澶� */
-function handleGroupCall() {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return
- }
- pendingMediaType.value = ImRtcCallMediaType.VOICE
- callMemberPickerRef.value?.open({ groupId: conversation.targetId, mode: 'invite' })
-}
-
-/** 閫変汉寮圭獥纭锛涘甫閫変腑 ID 鍙戣捣缇ら�氳瘽 */
-async function onCallMemberPicked(selectedIds: number[]) {
- const conversation = conversationStore.activeConversation
- const mediaType = pendingMediaType.value
- pendingMediaType.value = null
- if (!conversation || mediaType == null || selectedIds.length === 0) {
- return
- }
- await doInvite({
- conversationType: ImConversationType.GROUP,
- mediaType,
- groupId: conversation.targetId,
- inviteeIds: selectedIds
- })
-}
-
-/** 瀹為檯璋� create 鎺ュ彛锛涚粺涓�澶勭悊鎴愬姛 / ENDED锛堝蹇欑嚎绔嬪嵆缁撴潫锛� */
-async function doInvite(reqVO: {
- conversationType: number
- groupId?: number
- inviteeIds: number[]
- mediaType: number
-}) {
- if (callInviting.value) {
- return
- }
- if (rtcStore.isActive) {
- message.warning('褰撳墠宸叉湁閫氳瘽')
- return
- }
- callInviting.value = true
- try {
- const data = await createCall(reqVO)
- // 鍚庣宸� INSERT + 绔嬪嵆 end锛堝蹇欑嚎锛夛細toast 鎻愮ず锛屼笉杩� INVITING 闃舵锛沜hat tip 鐢� RTC_CALL_END 鎺ㄩ�佸啓鍏ユ秷鎭祦
- if (data.status === ImRtcCallStatus.ENDED) {
- message.warning(resolveCallEndReasonText(data.endReason))
- return
- }
- // 姝e父杩涘叆 INVITING 闃舵锛氳蛋 store 閫昏緫鍙戣捣閫氳瘽锛屽悗缁姸鎬佹洿鏂� / 娑堟伅娴佹洿鏂扮敱 RTC 妯″潡鐩戝惉鎺ㄩ�佸鐞�
- rtcStore.startInviting(data)
- } finally {
- callInviting.value = false
- }
-}
-
-/** 褰撳墠绉佽亰瀵瑰簲鐨勫ソ鍙嬶紙鎶藉眽澶撮儴灞曠ず鐢級 */
-const privateFriend = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || conversation.type !== ImConversationType.PRIVATE) {
- return undefined
- }
- return friendStore.getFriend(conversation.targetId)
-})
-
-/** 璁$畻璺濈搴曢儴鐨勫儚绱� */
-function distanceFromBottom(): number {
- const el = listRef.value
- if (!el) {
- return 0
- }
- return el.scrollHeight - el.scrollTop - el.clientHeight
-}
-
-/**
- * 娑堟伅鍒楄〃婊氬姩浜嬩欢锛氬埛鏂�"鏄惁鍦ㄥ簳閮�"鐘舵��
- * - 鍦ㄥ簳閮細闅愯棌"鍥炲埌搴曢儴"娴獥 + 娓呮帀"鏈鏂版秷鎭�"璁℃暟
- * - 涓嶅湪搴曢儴锛氭樉绀�"鍥炲埌搴曢儴"娴獥锛屾柊娑堟伅浼氱疮璁″埌 newMessageCount
- */
-function handleScroll() {
- const dist = distanceFromBottom()
- const atBottom = dist <= BOTTOM_THRESHOLD
- showJumpToBottom.value = !atBottom
- if (atBottom) {
- newMessageCount.value = 0
- }
-}
-
-/**
- * 婊氬埌搴曢儴锛氬垏浼氳瘽 / 鏀跺埌鏂版秷鎭紙涓斿綋鍓嶅湪搴曢儴锛�/ 鐢ㄦ埛涓诲姩鐐广�屽洖鍒板簳閮ㄣ�� 閮借蛋杩欓噷
- *
- * smooth=true 璧板钩婊戝姩鐢伙紝閫傚悎鐢ㄦ埛涓诲姩鐐瑰嚮锛涘垵濮� / 鑷姩婊氬姩鐢� auto锛岄伩鍏嶇敤鎴锋劅鐭ュ埌鍔ㄧ敾鎷栨嫿
- */
-async function scrollToBottom(smooth = false) {
- // 1. 婊氬埌褰撳墠 scrollHeight 鐨勫簳閮紙鍥剧墖 / 瑙嗛杩樺湪鍔犺浇鏃跺彧鏄ぇ鑷村埌搴曪級
- // 1.1 绛� v-for 鎶婃柊娑堟伅鐪熸娓叉煋杩� DOM 鍚庡啀绠� scrollHeight锛屽惁鍒欏樊鏈�鍚庝竴鏉$殑浣嶇疆
- await nextTick()
- if (!listRef.value) {
- return
- }
- // 1.2 瑙﹀彂婊氬姩锛泂mooth 浠� user 涓诲姩鐐广�屽洖鍒板簳閮ㄣ�嶇敤锛屽垵濮� / 鑷姩婊氳蛋 auto 閬垮厤鍔ㄧ敾鎷栨嫿鎰�
- listRef.value.scrollTo({
- top: listRef.value.scrollHeight,
- behavior: smooth ? 'smooth' : 'auto'
- })
- newMessageCount.value = 0
- showJumpToBottom.value = false
- // 1.3 璁颁笅銆屾湡鏈涘仠涓嬬殑 scrollTop銆嶏紱鍥剧墖 / 瑙嗛鍔犺浇瀹屽簳閮ㄤ細涓婄Щ锛宻crollTop 娌″姩灏辫鏄庣敤鎴锋病鎵嬪姩婊氳蛋
- // 涓嶈兘鐢� distanceFromBottom 鍒ゆ柇锛氬簳閮ㄤ笂绉讳細璁� distance 鍙樺ぇ锛岃璇垽涓恒�岀敤鎴锋粴璧颁簡銆嶇洿鎺ユ斁寮冭ˉ婊�
- const expectedScrollTop = listRef.value.scrollHeight - listRef.value.clientHeight
-
- // 2. 绛夊獟浣撳姞杞藉畬鍚庤ˉ婊氬埌鐪熷疄搴曢儴
- // 2.1 绛夊鍣ㄥ唴鏈姞杞藉畬鐨勫浘鐗� / 瑙嗛鍏冩暟鎹紱鍔犺浇瀹屽悗 scrollHeight 浼氬闀垮埌鐪熷疄搴曢儴
- await waitMediaSettled()
- if (!listRef.value) {
- return
- }
- // 2.2 浠呭湪鐢ㄦ埛娌℃墜鍔ㄦ粴璧版椂锛坰crollTop 浠嶈创杩� expectedScrollTop锛夋墠琛ユ粴锛岄伩鍏嶇瓑寰呮湡闂寸敤鎴蜂笂缈昏鎵撴柇
- if (Math.abs(listRef.value.scrollTop - expectedScrollTop) > BOTTOM_THRESHOLD) {
- return
- }
- // 2.3 琛ユ粴鍒版柊搴曢儴
- listRef.value.scrollTo({ top: listRef.value.scrollHeight, behavior: 'auto' })
-}
-
-/**
- * 绛夊緟瀹瑰櫒鍐呮湭鍔犺浇瀹岀殑鍥剧墖 / 瑙嗛锛涙渶澶氱瓑 2s 闃叉瓒呭ぇ璧勬簮鎶婃暣涓粴鍔ㄨ窡杩涘崱浣�
- *
- * 浠呭叧蹇冨厓鏁版嵁锛坙oadedmetadata / img.complete锛夛紝涓嶇瓑鐪熸瑙g爜锛屽洜涓哄昂瀵稿绠� scrollHeight 灏辫
- */
-function waitMediaSettled(): Promise<void> {
- // 1. 鏀堕泦瀹瑰櫒鍐呮湭鍔犺浇瀹岀殑鍥剧墖 / 瑙嗛
- if (!listRef.value) {
- return Promise.resolve()
- }
- // 1.1 涓�娆℃壂 img + video锛屾寜 element 绫诲瀷鍒嗗埆鐪嬨�宑omplete / readyState銆嶈繃婊� pending
- const pendingMedia = [...listRef.value.querySelectorAll<HTMLImageElement | HTMLVideoElement>('img, video')].filter((el) => (el instanceof HTMLImageElement ? !el.complete : el.readyState < 1))
- // 1.2 娌℃湁 pending 鐩存帴杩斿洖锛岀渷鎺� Promise.race / setTimeout 闂寘鏋勯��
- if (pendingMedia.length === 0) {
- return Promise.resolve()
- }
-
- // 2. 绛夋墍鏈� pending 璧勬簮 load / error锛屾渶闀� 2s 鍏滃簳
- // 2.1 姣忎釜 element 閮界洃鍚搴� loadedEvent + error锛屼换涓�瑙﹀彂鍗� resolve锛堜笉璁╁崟鏉″け璐ュ崱 race锛�
- const loadAll = Promise.all(
- pendingMedia.map(
- (el) =>
- new Promise<void>((resolve) => {
- const loadedEvent = el instanceof HTMLImageElement ? 'load' : 'loadedmetadata'
- el.addEventListener(loadedEvent, () => resolve(), { once: true })
- el.addEventListener('error', () => resolve(), { once: true })
- })
- )
- ).then(() => undefined)
- // 2.2 2s 瓒呮椂鍏滃簳锛岄槻姝㈣秴澶ц祫婧� / 缃戠粶鎸傝捣鎶婃暣涓粴鍔ㄨ窡杩涙案涔呭崱浣�
- const timeout = new Promise<void>((resolve) => setTimeout(resolve, 2000))
- return Promise.race([loadAll, timeout])
-}
-
-/**
- * 瀹氫綅鍒拌亰澶╀綅缃細MessageHistory 琛屼笂"瀹氫綅"鎸夐挳 / 姘旀场鍐呭紩鐢ㄥ潡鐐瑰嚮瑙﹀彂
- *
- * 1. 鍏堝叧鎺夊巻鍙插脊绐楋紙閬垮厤 scroll 鏃堕伄鎸� + dialog 鍏抽棴鍚庤鑱婂ぉ闈㈡澘鎷垮洖鐒︾偣锛�
- * 2. nextTick 绛夊脊绐� leave 鍔ㄧ敾 / 鍒楄〃娓叉煋绋冲畾鍚庡啀鏌� DOM
- * 3. 鎸� data-message-id 鎵� wrapper锛宻crollIntoView({ block: center }) 璁╂秷鎭惤鍒拌鍙d腑閮�
- * 4. 鍔� --highlight class 鐭殏楂樹寒锛屾彁绀虹敤鎴�"灏辨槸杩欐潯"
- * 5. 鎵句笉鍒� wrapper(鍘熸秷鎭凡鍒嗛〉鍑哄幓)鏃跺脊 warning 鎻愮ず,涓庡井淇�"娑堟伅宸蹭笉鍦ㄧ獥鍙�"瑙傛劅涓�鑷�
- */
-async function handleLocate(messageId: number) {
- if (!messageId) {
- return
- }
- await nextTick()
- if (!listRef.value) {
- return
- }
- const target = listRef.value.querySelector<HTMLElement>(`[data-message-id="${messageId}"]`)
- if (!target) {
- message.warning('鍘熸秷鎭笉鍦ㄨ閲�')
- return
- }
- target.scrollIntoView({ behavior: 'smooth', block: 'center' })
- target.classList.add('message-panel__message-anchor--highlight')
- setTimeout(() => {
- target.classList.remove('message-panel__message-anchor--highlight')
- }, 1600)
-}
-
-/**
- * 娑堟伅鏁伴噺鍙樺寲鏃剁殑婊氬姩璺熻繘绛栫暐
- * - 褰撳墠鍦ㄥ簳閮� 鈫� 鑷姩婊氬埌鏂板簳閮紙鐢ㄦ埛鍦�"瀹炴椂璺熻"锛屼綋楠屼笂璺熷井淇′竴鑷达級
- * - 涓嶅湪搴曢儴 鈫� 绱 newMessageCount锛岃 sticky 娴獥鏄剧ず"X 鏉℃柊娑堟伅"锛岃鐢ㄦ埛涓诲姩鐐�
- */
-watch(
- () => messages.value.length,
- (newLen, oldLen) => {
- // 浠呭鐞嗘柊澧烇紙delta > 0锛夛紱鍒犻櫎 / 鎾ゅ洖璁� length 鍑忓皯鏃朵笉鍔ㄦ粴鍔ㄧ姸鎬�
- const delta = (newLen || 0) - (oldLen || 0)
- if (delta <= 0) {
- return
- }
- // 鐢� BOTTOM_THRESHOLD锛�80px锛夊仛瀹瑰樊锛氱敤鎴风◢寰線涓婄炕鍑犺灏辫浣�"涓嶅湪搴曢儴"锛�
- // 鍚﹀垯涓�鐩� auto-scroll 浼氭妸浜烘鍦ㄨ鐨勫唴瀹归《璧帮紝浣撻獙绯熺硶
- const dist = distanceFromBottom()
- if (dist <= BOTTOM_THRESHOLD) {
- scrollToBottom()
- } else {
- newMessageCount.value += delta
- showJumpToBottom.value = true
- }
- }
-)
-
-/**
- * 鍒囨崲浼氳瘽锛氭竻绌�"鍦ㄤ笉鍦ㄥ簳閮�"鐩稿叧鐘舵�併�佸己鍒舵粴鍒板簳閮ㄣ�佺兢浼氳瘽棰勬媺璧勬枡
- *
- * type+targetId 涓�璧风洃鍚細绉佽亰涓庣兢鑱� id 鍚屽彿鏃跺垏鎹篃鑳借Е鍙戯紱immediate:true 璁╅娆¤繘鍏ラ〉闈㈠氨鑳藉垵濮嬪寲
- */
-watch(
- () => [
- conversationStore.activeConversation?.type,
- conversationStore.activeConversation?.targetId
- ],
- ([type, targetId]) => {
- // 鍒囦細璇濇椂涓婁竴浼氳瘽鐨勩�屾湭璇荤疮璁� + 娴獥鏄剧ず銆嶅繀椤绘竻鎺夛紝鍚﹀垯浼氬甫鍒版柊浼氳瘽閲岀湅璧锋潵寰堢獊鍏�
- newMessageCount.value = 0
- showJumpToBottom.value = false
- // 鎶藉眽閲屽睍绀虹殑缇や俊鎭� / 濂藉弸淇℃伅灞炰簬涓婁竴浼氳瘽锛屽垏浼氳瘽鏃剁粺涓�鍏虫帀
- sideVisible.value = false
- scrollToBottom()
- // 浠呯兢鑱婇鎷夎鎯� / 鎴愬憳锛堢鑱婂绔湪棣栧睆 fetchFriendList 鏃跺氨鎷変簡锛�
- if (targetId && type === ImConversationType.GROUP) {
- ensureGroupData(targetId)
- }
- },
- { immediate: true }
-)
-</script>
-
-<template>
- <div class="flex flex-1 flex-col min-w-0 bg-[var(--ant-color-fill-secondary)]">
- <template v-if="conversationStore.activeConversation">
- <!-- 椤堕儴 header锛氱涓�琛岀兢鍚� + 鍙充晶鍥炬爣锛岀浜岃宓屽叆缃《姘旀场锛堜粎缇よ亰 + 鏈夌疆椤讹級 -->
- <div
- class="flex flex-shrink-0 flex-col bg-[var(--ant-color-fill-secondary)] border-b border-b-solid border-[var(--im-border-color-lighter)]"
- >
- <div class="flex items-center justify-between h-14 px-5">
- <span class="flex flex-col min-w-0">
- <span class="flex items-baseline gap-1.5 min-w-0">
- <span
- class="overflow-hidden text-base font-medium truncate text-[var(--ant-color-text)]"
- >
- {{ conversationStore.activeConversation?.name || '' }}
- </span>
- <span
- v-if="isGroup && headerMemberCount > 0"
- class="flex-shrink-0 text-sm text-[var(--ant-color-text-secondary)]"
- >
- ({{ headerMemberCount }})
- </span>
- </span>
- <!-- 鍓爣棰橈細澶囨敞 鈮� 缇ゅ悕鏃跺睍绀哄師缇ゅ悕锛屾彁绀虹敤鎴峰綋鍓嶇湅鍒扮殑涓诲悕鏄嚜宸辫鐨勫娉� -->
- <span
- v-if="headerSubtitle"
- class="overflow-hidden text-xs truncate text-[var(--ant-color-text-secondary)]"
- >
- {{ headerSubtitle }}
- </span>
- </span>
- <div class="flex gap-2 items-center">
- <!-- 鑱婂ぉ鍘嗗彶 -->
- <Tooltip title="鑱婂ぉ鍘嗗彶" placement="bottom">
- <Icon
- icon="ep:chat-dot-round"
- class="message-panel__header-icon cursor-pointer"
- @click="historyDialogRef?.open()"
- />
- </Tooltip>
- <!-- 閫氳瘽鍏ュ彛锛氱鑱婂脊銆岃闊� / 瑙嗛銆峱opover锛涚兢鑱婄洿鎺ヨ繘閫変汉寮圭獥 -->
- <Popover
- v-if="isPrivate"
- v-model:open="callPopoverVisible"
- placement="bottomRight"
- :overlay-style="{ width: '140px' }"
- trigger="click"
- overlay-class-name="message-panel__call-popover"
- >
- <Icon
- icon="ant-design:phone-outlined"
- class="message-panel__header-icon cursor-pointer"
- />
- <template #content>
- <div class="flex flex-col gap-0.5">
- <div
- class="flex items-center gap-2.5 px-3 py-2 rounded cursor-pointer text-sm text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill-secondary)]"
- @click="startPrivateCall(ImRtcCallMediaType.VOICE)"
- >
- <Icon icon="ant-design:phone-outlined" :size="16" />
- <span>璇煶閫氳瘽</span>
- </div>
- <div
- class="flex items-center gap-2.5 px-3 py-2 rounded cursor-pointer text-sm text-[var(--ant-color-text)] hover:bg-[var(--ant-color-fill-secondary)]"
- @click="startPrivateCall(ImRtcCallMediaType.VIDEO)"
- >
- <Icon icon="ant-design:video-camera-outlined" :size="16" />
- <span>瑙嗛閫氳瘽</span>
- </div>
- </div>
- </template>
- </Popover>
- <Tooltip v-else-if="!isQuitGroup" title="閫氳瘽" placement="bottom">
- <Icon
- icon="ant-design:phone-outlined"
- class="message-panel__header-icon cursor-pointer"
- @click="handleGroupCall"
- />
- </Tooltip>
- <!-- 淇℃伅鎶藉眽鍏ュ彛锛氱兢鑱婃湁锛岀鑱婁粎鍦ㄥ鏂规槸濂藉弸鏃舵樉绀猴紙闈炲ソ鍙嬩細璇濇娊灞夊唴瀹逛负绌猴紝娌℃剰涔夛級 -->
- <Tooltip
- v-if="isGroup || (isPrivate && !showNotFriendBanner)"
- :title="isGroup ? '缇よ亰淇℃伅' : '鑱婂ぉ淇℃伅'"
- placement="bottom"
- >
- <Icon
- icon="ant-design:ellipsis-outlined"
- class="message-panel__header-icon cursor-pointer"
- @click="toggleSide"
- />
- </Tooltip>
- </div>
- </div>
-
- <!-- 缇ら�氳瘽鑳跺泭鏉★細浠呯兢鑱� + 璇ョ兢鏈夋椿璺冮�氳瘽鏃舵樉绀猴紱鐐瑰嚮灞曞紑鐪嬫垚鍛� + 鍔犲叆鎸夐挳 -->
- <RtcGroupCallBanner
- v-if="isGroup && !isQuitGroup && conversationStore.activeConversation"
- :group-id="conversationStore.activeConversation.targetId"
- />
-
- <!-- 缇ょ疆椤舵秷鎭細绗簩琛屽祵鍏� header锛涗粎缇よ亰 + 鏈夌疆椤舵椂鏄剧ず -->
- <GroupPinnedMessage
- v-if="isGroup && conversationStore.activeConversation"
- :group-id="conversationStore.activeConversation.targetId"
- @locate="handleLocate"
- />
- <!-- 缇ら《閮ㄣ�屽緟澶勭悊鍔犵兢鐢宠銆嶆í骞咃細浠呯兢鑱� + owner / admin + count > 0 鏃舵樉绀� -->
- <GroupRequestPending
- v-if="isGroup && !isQuitGroup && conversationStore.activeConversation"
- :group-id="conversationStore.activeConversation.targetId"
- />
- <!-- 绉佽亰锛氬鏂逛笉鍐嶆槸鏈夋晥濂藉弸锛堟垜鍒犱簡瀵规柟 / 浠庢湭鍔犺繃锛涘崟杈硅璁′笅銆岃瀵规柟鍒犻櫎銆嶆湰绔� friendStore 涓嶆洿鏂版晠涓嶄細瑙﹀彂锛夛紱鑳跺泭宓屽湪 header 鍐咃紙璺熺兢缃《鍚岀骇锛夛紝鐐瑰嚮寮� UserInfoCard -->
- <div
- v-if="showNotFriendBanner"
- class="flex flex-shrink-0 items-start px-4 pb-2 bg-[var(--ant-color-fill-secondary)]"
- >
- <div
- class="inline-flex items-center gap-2 px-2.5 py-1 rounded-full text-13px cursor-pointer text-[var(--ant-color-text)] bg-[var(--ant-color-warning-bg)] transition-colors hover:bg-[var(--ant-color-warning-bg-hover)]"
- @click="handleNotFriendClick"
- >
- <span
- class="inline-flex items-center justify-center w-4 h-4 rounded-full text-white bg-[var(--ant-color-warning)] flex-shrink-0"
- >
- <Icon icon="ant-design:user-outlined" :size="11" />
- </span>
- <span>瀵规柟杩樹笉鏄綘鐨勬湅鍙�</span>
- <Icon icon="ep:arrow-right" :size="12" class="text-[var(--ant-color-text-secondary)]" />
- </div>
- </div>
- </div>
-
- <!-- 涓棿锛氭秷鎭垪琛� -->
- <div
- ref="listRef"
- class="relative flex-1 py-2 overflow-y-auto bg-[var(--ant-color-bg-layout)]"
- @scroll="handleScroll"
- >
- <div
- v-if="messages.length === 0"
- class="flex items-center justify-center h-full text-sm text-[var(--ant-color-text-secondary)]"
- >
- 鏆傛棤娑堟伅
- </div>
- <!-- data-message-id 缁� MessageHistory "瀹氫綅鍒拌亰澶╀綅缃�" 鐢細鐖剁骇閫氳繃 querySelector
- 鎵惧埌杩欏眰 wrapper锛宻crollIntoView + 鍔犻珮浜� class锛涙湰鍦板崰浣嶆秷鎭烦杩� -->
- <div
- v-for="(msg, index) in messages"
- :key="msg.id || msg.clientMessageId"
- :data-message-id="msg.id || ''"
- class="message-panel__message-anchor"
- >
- <MessageItem
- :message="msg"
- :prev-message="messages[index - 1]"
- @locate="handleLocate"
- @mute="handleMuteMember"
- @reload="reloadGroupData"
- />
- </div>
-
- <!-- 鍥炲埌搴曢儴娴姩鎸夐挳锛堟粴鍔ㄤ笉鍦ㄥ簳閮ㄦ椂鏄剧ず锛� -->
- <transition name="message-panel__jump-fade">
- <div
- v-if="showJumpToBottom"
- class="message-panel__jump-bottom sticky bottom-3 left-1/2 inline-flex gap-1.5 items-center w-fit mx-auto px-3.5 py-1.5 text-xs text-[#409eff] bg-[var(--ant-color-bg-elevated)] rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.12)] cursor-pointer hover:text-white hover:bg-[#409eff]"
- @click="scrollToBottom(true)"
- >
- <Icon icon="ant-design:down-outlined" :size="14" />
- <span v-if="newMessageCount > 0" class="font-medium">
- {{ newMessageCount > 99 ? '99+' : newMessageCount }} 鏉℃柊娑堟伅
- </span>
- <span v-else>鍥炲埌搴曢儴</span>
- </div>
- </transition>
- </div>
-
- <!-- 搴曢儴锛氳緭鍏ユ锛堥閬撳崟鍚戞秷鎭棤闇�杈撳叆妗嗭級锛涘閫夋ā寮忓簳鏍忎綔涓烘诞灞傜洊鍦ㄤ笂闈紝淇濇寔涓嬫柟杈撳叆妗嗗昂瀵镐笉鍙� -->
- <div v-if="!isChannel" class="relative">
- <MessageInput />
- <MessageMultiSelectBar v-if="multiSelect.state.active" class="absolute inset-0 z-10" />
- </div>
-
- <!-- 鍙充晶淇℃伅鎶藉眽锛氱兢鑱� / 绉佽亰鍚勮嚜涓�浠� -->
- <ConversationGroupSide
- v-if="isGroup"
- v-model="sideVisible"
- :group="groupInfo"
- :conversation="conversationStore.activeConversation"
- :members="groupMembers"
- @reload="reloadGroupData"
- @open-history="historyDialogRef?.open()"
- />
- <ConversationPrivateSide
- v-else
- v-model="sideVisible"
- :conversation="conversationStore.activeConversation"
- :friend="privateFriend"
- @open-history="historyDialogRef?.open()"
- />
-
- <!-- 鍘嗗彶娑堟伅鎶藉眽 -->
- <MessageHistory ref="historyDialogRef" @locate="handleLocate" />
-
- <!-- 杞彂寮圭獥 / 鍚堝苟娑堟伅璇︽儏锛氬湪 MessagePanel 瀛愭爲鍐呮寕杞斤紝瀛愮粍浠堕�氳繃 inject 瑙﹀彂 -->
- <MessageForwardDialog ref="forwardDialogRef" />
- <MessageMergeDetailDialog ref="mergeDetailDialogRef" />
-
- <!-- 绂佽█鏃堕暱閫夋嫨寮圭獥 -->
- <GroupMuteMemberDialog ref="muteMemberDialogRef" @success="reloadGroupData" />
-
- <!-- 缇ら�氳瘽鎴愬憳閫夋嫨寮圭獥 -->
- <RtcCallMemberPickerDialog ref="callMemberPickerRef" @success="onCallMemberPicked" />
- </template>
- <div
- v-else
- class="flex items-center justify-center h-full text-sm text-[var(--ant-color-text-secondary)]"
- >
- <span>閫夋嫨涓�涓細璇濆紑濮嬭亰澶�</span>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* :deep 绌块�� el-icon 瀛愮粍浠� svg锛沞l-icon 鍏ㄥ眬瑙勫垯 .el-icon{color:var(--color,inherit)} 浼樺厛绾ф洿楂橈紝
- 鐢� :deep + !important 閿佽壊锛涢鑹插彇 Element Plus 涓婚鍙橀噺锛屾殫鑹叉ā寮忚嚜鍔ㄥ垏鍒版洿浜殑鐏� */
-.message-panel__header-icon,
-.message-panel__header-icon :deep(svg) {
- width: 20px;
- height: 20px;
- color: var(--ant-color-text) !important;
- fill: currentColor !important;
- transition: color 0.15s;
-}
-
-.message-panel__header-icon {
- box-sizing: content-box;
- padding: 4px;
-}
-.message-panel__header-icon:hover,
-.message-panel__header-icon:hover :deep(svg) {
- color: var(--ant-color-primary) !important;
-}
-
-/* sticky + translate 灞呬腑锛歠it-content 瀹藉害涓嶄細鎾戞弧锛宼ransform 姘村钩 -50% 鍋忕Щ锛涘悓鏃� transition opacity 鍜� transform 涓や釜灞炴�� */
-.message-panel__jump-bottom {
- transform: translateX(-50%);
- transition:
- opacity 0.2s,
- transform 0.2s;
-}
-
-/* JS 鍒囨崲 highlight class + background-color transition 鑱斿姩锛汳essageHistory銆屽畾浣嶃�嶈烦杩囨潵鏃剁煭鏆傞珮浜� 1.6s */
-.message-panel__message-anchor {
- transition: background-color 0.6s ease;
-}
-.message-panel__message-anchor--highlight {
- background-color: var(--ant-color-warning-bg);
-}
-
-/* Vue <transition> 閽╁瓙绫诲悕鍥哄畾锛屽繀椤� SCSS 澹版槑锛涘洖鍒板簳閮ㄦ寜閽贰鍏ユ贰鍑� */
-.message-panel__jump-fade-enter-active,
-.message-panel__jump-fade-leave-active {
- transition:
- opacity 0.2s,
- transform 0.2s;
-}
-
-.message-panel__jump-fade-enter-from,
-.message-panel__jump-fade-leave-to {
- opacity: 0;
- transform: translate(-50%, 20px);
-}
-</style>
-
-<style>
-/* el-popover 鍏ㄥ眬鏍峰紡锛氱揣璐磋彍鍗曠殑灏� padding锛涢潪 scoped 鎵嶈兘鍛戒腑 popper-class */
-.message-panel__call-popover.el-popover.el-popper {
- min-width: auto;
- padding: 6px;
-}
-</style>
diff --git a/src/views/im/home/pages/conversation/components/message/message-read-status.vue b/src/views/im/home/pages/conversation/components/message/message-read-status.vue
deleted file mode 100644
index 3fd526b..0000000
--- a/src/views/im/home/pages/conversation/components/message/message-read-status.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-<script lang="ts" setup>
-import type { Message } from '../../../../types'
-
-import { computed, ref } from 'vue'
-
-import { CommonStatusEnum } from '../../../../../../../packages/constants/src'
-
-import { Popover, TabPane, Tabs } from 'ant-design-vue'
-
-import { getGroupReadUsers as apiGetGroupReadUsers } from '#/api/im/message/group'
-
-import { ImConversationType, ImMessageReceiptStatus } from '../../../../../utils/constants'
-import { PagedScroller } from '../../../../components'
-import { GroupMember, type GroupMemberLite } from '../../../../components/group'
-import { useMessageStore } from '../../../../store/messageStore'
-
-defineOptions({ name: 'ImMessageReadStatus' })
-
-const props = defineProps<{
- // 褰撳墠缇ょ紪鍙凤紱渚� loadReadUsers 浣滀负 /im/message/group/get-read-users 鐨勫叆鍙�
- groupId: number
- // 褰撳墠缇ゆ墍鏈夋垚鍛橈紙澶栭儴 MessagePanel.groupMembers 浼犲叆锛涙病鏈夊氨浼犵┖鏁扮粍锛屾湭璇诲垪琛ㄤ細鍙樼┖浣嗕笉鎶ラ敊锛�
- groupMembers: GroupMemberLite[]
- message: Message
-}>()
-
-const messageStore = useMessageStore()
-
-// popover 寮�鍏筹細show 鏃舵媺宸茶鍚嶅崟锛屽叧闂悗淇濈暀 readUserIds 缂撳瓨锛堥噸寮�鍚屼竴鏉℃秷鎭笉鍐嶈姹傦級
-const popVisible = ref(false)
-const activeTab = ref<'read' | 'unread'>('read')
-// 鏈嶅姟绔繑鍥炵殑"宸茶杩欐潯娑堟伅鐨� userId 鍒楄〃"锛屾湭璇婚潬 visibleMembers 鍑忓幓杩欎唤寰楀埌
-const readUserIds = ref<number[]>([])
-
-/**
- * 鏍囩鏂囨锛�
- * - receiptStatus === DONE锛氭湇鍔$纭鍏ㄥ憳宸茶 鈫� 鐩存帴鏄剧ず"鍏ㄩ儴宸茶"锛宺eadCount 涓嶅啀浣跨敤
- * - readCount > 0锛氭樉绀�"N 浜哄凡璇�"
- * - 鍏朵粬锛坮eadCount = 0 鎴� undefined锛屼笖鏈埌 DONE锛夛細鏄剧ず"鏈"
- */
-const label = computed(() => {
- if (props.message.receiptStatus === ImMessageReceiptStatus.DONE) {
- return '鍏ㄩ儴宸茶'
- }
- const readCount = props.message.readCount || 0
- return readCount > 0 ? `${readCount} 浜哄凡璇籤 : '鏈'
-})
-
-/**
- * 杩欐潯娑堟伅"搴旇琚皝鐪嬪埌"鐨勫彲瑙佹垚鍛橀泦鍚堬紙宸茶 / 鏈涓や釜 tab 鍏辩敤鍩哄簳锛�
- *
- * 涓夊眰杩囨护锛�
- * 1. 瀹氬悜缇ゆ秷鎭細receiverUserIds 闈炵┖鏃讹紝鍙畻鍚嶅崟鍐呯殑鎴愬憳銆傚皯浜嗚繖灞傦紝鏈湪鎶曢�掕寖鍥撮噷鐨勪汉
- * 浼氳閿欑畻鎴�"鏈"锛坓etGroupReadUsers 鍚庣宸叉寜鍙鎬ц繃婊わ紝鍓嶇蹇呴』閰嶅锛�
- * 2. 鍙戦�佽�呮湰浜猴細鑷繁鍙戠殑娑堟伅绠椾笉绠�"鑷繁宸茶"璇箟妯$硦锛岀储鎬т袱涓� tab 閮戒笉灞曠ず
- * 3. 宸查��缇わ紙status === DISABLE锛夛細浠栬娌¤宸茬粡涓嶅叧蹇冿紝UI 涓嶅睍绀�
- */
-const visibleMembers = computed<GroupMemberLite[]>(() => {
- const receiverUserIds = props.message.receiverUserIds
- const isDirected = !!receiverUserIds && receiverUserIds.length > 0
- return props.groupMembers.filter(
- (member) =>
- member.status !== CommonStatusEnum.DISABLE &&
- member.userId !== props.message.senderId &&
- (!isDirected || receiverUserIds.includes(member.userId))
- )
-})
-
-/** 宸茶 = 鍙鎴愬憳 鈭� readUserIds */
-const readMembers = computed(() =>
- visibleMembers.value.filter((member) => readUserIds.value.includes(member.userId))
-)
-
-/** 鏈 = 鍙鎴愬憳 鈭� readUserIds */
-const unreadMembers = computed(() =>
- visibleMembers.value.filter((member) => !readUserIds.value.includes(member.userId))
-)
-
-/**
- * 鎷夊彇宸茶鐢ㄦ埛 id 鍒楄〃
- *
- * 瑙﹀彂锛歟l-popover @show锛堟寜闇�鎳掑姞杞斤紝閬垮厤姣忔潯缇ゆ秷鎭兘棰勬媺锛�
- * 璺宠繃锛氭湰鍦板崰浣嶆秷鎭紙id = 0锛岃繕娌℃嬁鍒版湇鍔$ id锛夛紝鍚庣娌℃硶鎸� messageId 鏌�
- * 澶辫触锛氫粎鎺у埗鍙板憡璀︼紝readUserIds 淇濇寔绌烘暟缁� 鈫� label 璧� readCount 鍏滃簳锛屼笉闃诲 UI
- *
- * 鎷夊埌鍚嶅崟鍚庨『鎵嬫妸 readCount / receiptStatus 鍥炲啓鍒� messageStore锛岃 popover 澶栭潰鐨�
- * label 涔熻窡鐫�璧版渶鏂版暟锛氱绾� / 婕忔敹 RECEIPT 浜嬩欢鏃舵湰鍦� readCount 浼氬亸鏃э紝寮瑰眰閲岀湅鍒�"宸茶 5"
- * 浣嗗闈粛鏄�"鏈"鎴栨棫浜烘暟锛涜繖閲屼互鏈嶅姟绔繑鍥炰负鍑嗙煫姝e洖鍘�
- */
-async function loadReadUsers() {
- if (!props.message.id) {
- return
- }
- try {
- const userIds = await apiGetGroupReadUsers({
- groupId: props.groupId,
- messageId: props.message.id
- })
- readUserIds.value = userIds || []
- const readCount = readUserIds.value.length
- // 鍏ㄥ彲瑙佹垚鍛橀兘宸茶 鈫� 鏇存柊涓� DONE锛岃澶栭潰 label 鐩存帴鍛戒腑銆屽叏閮ㄥ凡璇汇�嶅垎鏀紱
- // 鍚﹀垯鍙洿鏂� readCount锛宺eceiptStatus 缁存寔涓嶅彉锛圥ENDING锛�
- const allRead = readCount > 0 && readCount >= visibleMembers.value.length
- messageStore.applyMessageReadReceipt({
- conversationType: ImConversationType.GROUP,
- targetId: props.groupId,
- groupMessageId: props.message.id,
- readCount,
- receiptStatus: allRead ? ImMessageReceiptStatus.DONE : undefined
- })
- } catch (error) {
- console.error('[IM] 鎷夊彇缇ゅ凡璇诲垪琛ㄥけ璐�:', error)
- }
-}
-</script>
-
-<template>
- <!--
- 缇ゆ秷鎭凡璇荤姸鎬�
- - 鏍囩褰㈡�侊細灞曠ず銆孨 宸茶銆嶆垨銆屽叏閮ㄥ凡璇汇�嶏紱鐐瑰嚮寮瑰嚭 popover 鐢� tab 鍒楀嚭宸茶 / 鏈鎴愬憳
- - 娓叉煋鏉′欢锛氫粎鍦ㄧ兢鑱娿�佽嚜宸卞彂閫併�乺eceiptStatus !== NO_RECEIPT 鏃舵寕鍑猴紙鐢� MessageItem 鎶婂叧锛�
- - 宸茶鍚嶅崟鎸夐渶鎷夛細popover 瑙﹀彂 show 鎵嶆墦 /im/message/group/get-read-users锛岄伩鍏嶆瘡鏉$兢娑堟伅閮介鎷�
- - 鏈鍚嶅崟 = 褰撳墠缇ゆ垚鍛� 鈭� 鑷繁 鈭� 宸查��缇� 鈭� 宸茶闆嗗悎锛堝墠绔仛鍚堬紝鍚庣鍙洖宸茶 userId锛�
- -->
- <Popover
- v-model:open="popVisible"
- placement="left"
- trigger="click"
- :overlay-style="{ width: '320px' }"
- @open-change="(open) => open && loadReadUsers()"
- >
- <span
- class="text-12px whitespace-nowrap cursor-pointer text-[var(--ant-color-text-secondary)] hover:text-[#409eff]"
- >
- {{ label }}
- </span>
-
- <template #content>
- <Tabs v-model:active-key="activeTab" centered>
- <TabPane :tab="`宸茶(${readMembers.length})`" key="read">
- <PagedScroller :items="readMembers" :page-size="20" item-key="userId" class="h-75">
- <template #default="{ item }">
- <GroupMember :member="item as GroupMemberLite" :height="40" :clickable="false" />
- </template>
- </PagedScroller>
- <div
- v-if="readMembers.length === 0"
- class="py-5 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ groupMembers.length === 0 ? '缇ゆ垚鍛樻湭鍔犺浇' : '鏆傛棤宸茶' }}
- </div>
- </TabPane>
- <TabPane :tab="`鏈(${unreadMembers.length})`" key="unread">
- <PagedScroller :items="unreadMembers" :page-size="20" item-key="userId" class="h-75">
- <template #default="{ item }">
- <GroupMember :member="item as GroupMemberLite" :height="40" :clickable="false" />
- </template>
- </PagedScroller>
- <div
- v-if="unreadMembers.length === 0"
- class="py-5 text-12px text-center text-[var(--ant-color-text-disabled)]"
- >
- {{ groupMembers.length === 0 ? '缇ゆ垚鍛樻湭鍔犺浇' : '鍏ㄩ儴宸茶' }}
- </div>
- </TabPane>
- </Tabs>
- </template>
- </Popover>
-</template>
diff --git a/src/views/im/home/pages/conversation/components/message/reply-preview.vue b/src/views/im/home/pages/conversation/components/message/reply-preview.vue
deleted file mode 100644
index ccf65f8..0000000
--- a/src/views/im/home/pages/conversation/components/message/reply-preview.vue
+++ /dev/null
@@ -1,234 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../../../packages/icons/src'
-import { formatFileSize } from '../../../../../../../packages/utils/src'
-
-import { CardLineLabel } from '#/views/im/home/components/card'
-import { ImContentType } from '#/views/im/utils/constants'
-import { getClientConversationId } from '#/views/im/utils/db'
-import {
- type AudioMessage,
- type CardMessage,
- type FaceMessage,
- type FileMessage,
- getFileIconInfo,
- type ImageMessage,
- type MaterialMessage,
- parseMessage,
- type QuoteMessage,
- type TextMessage,
- type VideoMessage
-} from '#/views/im/utils/message'
-import { formatSeconds } from '#/views/im/utils/time'
-import { getSenderDisplayName } from '#/views/im/utils/user'
-
-import { useConversationStore } from '../../../../store/conversationStore'
-import { useMessageStore } from '../../../../store/messageStore'
-
-defineOptions({ name: 'ImReplyPreview' })
-
-const props = withDefaults(
- defineProps<{
- /** 姘旀场鍐呬负 true 鏀寔鐐瑰嚮璺宠浆,杈撳叆鏉′负 false */
- clickable?: boolean
- /** 杈撳叆鏉′负 true 鏄剧ず 脳 鍏抽棴鎸夐挳 */
- closable?: boolean
- /** 鑷繁鍙戦�佺殑姘旀场涓� true锛屾妸绔栫嚎闀滃儚鍒板彸渚э紝涓庢皵娉″悓渚� */
- mirrored?: boolean
- quote: QuoteMessage
- }>(),
- {
- clickable: false,
- closable: false,
- mirrored: false
- }
-)
-
-const emit = defineEmits<{
- close: []
- locate: [messageId: number]
-}>()
-
-const MAX_TEXT_PREVIEW_LEN = 60 // 鏂囨湰鎽樿鍦ㄥ紩鐢ㄥ潡閲屽睍绀虹殑鏈�澶у瓧绗︽暟
-
-const conversationStore = useConversationStore()
-const messageStore = useMessageStore()
-
-/** 鍦ㄥ綋鍓嶄細璇濇秷鎭垪琛ㄩ噷鏌ユ壘鍘熸秷鎭�,浠呯敤浜庡疄鏃跺垽鏂槸鍚﹀凡鎾ゅ洖;鎽樿 / 缂╃暐鍥鹃兘浠� quote.content 鐩存帴娲剧敓 */
-const liveMessage = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation || !props.quote.messageId) {
- return undefined
- }
- return messageStore
- .getMessages(getClientConversationId(conversation.type, conversation.targetId))
- .find((message) => message.id === props.quote.messageId)
-})
-
-/** 鍛戒腑鏈湴缂撳瓨涓� type === RECALL 鎵嶅垽瀹氫负宸叉挙鍥�;涓嶅湪缂撳瓨鐨勫綋蹇収浠嶆湁鏁� */
-const isRecalled = computed(() => liveMessage.value?.type === ImContentType.RECALL)
-
-/** 娓叉煋鏃跺疄鏃剁畻,涓庢皵娉′笂鏂规樉绀哄悕璧板悓涓�濂楄鍒�,閬垮厤澶囨敞鍙樻洿鍚庡紩鐢ㄥ潡闄堟棫 */
-const senderName = computed(() => {
- const conversation = conversationStore.activeConversation
- if (!conversation) {
- return ''
- }
- return getSenderDisplayName(props.quote.senderId, conversation.type, conversation.targetId)
-})
-
-/** quote.content 瑙f瀽涓�娆$紦瀛橈紝璁╁涓� computed 澶嶇敤锛岄暱浼氳瘽姣忔潯寮曠敤姘旀场灏戜竴娆� JSON.parse */
-type AnyQuotePayload = Partial<
- AudioMessage &
- CardMessage &
- FaceMessage &
- FileMessage &
- ImageMessage &
- MaterialMessage &
- TextMessage &
- VideoMessage
->
-const parsedPayload = computed(() => parseMessage<AnyQuotePayload>(props.quote.content))
-
-const isText = computed(() => props.quote.type === ImContentType.TEXT)
-const isFile = computed(() => props.quote.type === ImContentType.FILE)
-const isVoice = computed(() => props.quote.type === ImContentType.VOICE)
-const isCard = computed(() => props.quote.type === ImContentType.CARD)
-const isFace = computed(() => props.quote.type === ImContentType.FACE)
-const isMaterial = computed(() => props.quote.type === ImContentType.MATERIAL)
-
-/** 鏂囨湰瓒呰繃 MAX_TEXT_PREVIEW_LEN 鎴柇锛岄暱鍐呭涓嶆拺鐖嗗紩鐢ㄥ潡 */
-const textPreview = computed(() => {
- const text = parsedPayload.value?.content ?? ''
- return text.length <= MAX_TEXT_PREVIEW_LEN ? text : `${text.slice(0, Math.max(0, MAX_TEXT_PREVIEW_LEN))}鈥
-})
-
-/** 鏂囦欢 icon锛氭寜鎵╁睍鍚嶆寫鑹诧紝璺熶富姘旀场娓叉煋鍚屾簮 */
-const fileIcon = computed(() => getFileIconInfo(parsedPayload.value?.name))
-
-/** 缂╃暐鍥� URL锛氬浘鐗� / 瑙嗛 / 琛ㄦ儏璐村浘 / 棰戦亾绱犳潗灏侀潰浠� quote.content 鐩存帴鍙栵紝涓嶄緷璧栨湰鍦扮紦瀛� */
-const thumbnailUrl = computed<string | undefined>(() => {
- if (isRecalled.value) {
- return undefined
- }
- const { type } = props.quote
- if (type === ImContentType.IMAGE) {
- return parsedPayload.value?.thumbnailUrl || parsedPayload.value?.url
- }
- if (type === ImContentType.VIDEO || type === ImContentType.MATERIAL) {
- return parsedPayload.value?.coverUrl
- }
- if (type === ImContentType.FACE) {
- return parsedPayload.value?.url
- }
- return undefined
-})
-
-/** 浠� clickable 涓旀湭鎾ゅ洖鏃惰Е鍙戣烦杞� */
-function onClick() {
- if (!props.clickable || isRecalled.value) {
- return
- }
- emit('locate', props.quote.messageId)
-}
-</script>
-
-<template>
- <!--
- 寮曠敤棰勮鍧楋紙瀵归綈寰俊 PC锛夛細2px 绔栫嚎浣滀负銆屽紩鐢ㄣ�嶈瑙夋爣璇� + 绱у噾鍐呭棰勮
- - clickable=true锛堟皵娉″唴锛夛細鐐瑰嚮瑙﹀彂 locate emit锛涙挙鍥炴�佺鐢ㄨ烦杞�
- - closable=true锛堣緭鍏ユ潯锛夛細灏鹃儴 脳 鍏抽棴鎸夐挳
- - mirrored=true锛堣嚜宸卞彂閫佺殑姘旀场锛夛細绔栫嚎闀滃儚鍒板彸渚э紝涓庢皵娉″悓渚�
- - 鍐呭棰勮锛堜笌涓绘皵娉″悓婧愶紝涓嶇缉鍐欐垚銆孾鏂囦欢]/[璇煶]銆嶏級锛�
- - 鏂囨湰锛氭埅鏂悗绾枃瀛�
- - 鍥剧墖 / 瑙嗛锛氱缉鐣ュ浘锛圛MAGE 鐢� thumbnailUrl/url锛孷IDEO 鐢� coverUrl锛�
- - 鏂囦欢锛歠ile icon + 鏂囦欢鍚� + 澶у皬
- - 璇煶锛歛udio icon + 鏃堕暱
- - 鎾ゅ洖锛堝懡涓湰鍦扮紦瀛樹笖 type === RECALL锛夛細銆屽師娑堟伅宸叉挙鍥炪�嶆枩浣撶伆瀛�
- -->
- <div
- class="flex w-fit gap-1.5 items-center min-w-0 py-0.5 text-12px text-[var(--ant-color-text-secondary)] rounded transition-colors"
- :class="[
- mirrored
- ? 'pl-1 pr-2 border-r-2 border-r-solid border-r-[var(--ant-color-border)]'
- : 'pl-2 pr-1 border-l-2 border-l-solid border-l-[var(--ant-color-border)]',
- {
- 'cursor-pointer hover:text-[var(--ant-color-text)]': clickable && !isRecalled,
- 'hover:bg-[var(--ant-color-fill-secondary)]': (clickable && !isRecalled) || closable
- }
- ]"
- @click="onClick"
- >
- <span class="flex-shrink-0">{{ senderName }}:</span>
-
- <!-- 鎾ゅ洖闄嶇骇 -->
- <span v-if="isRecalled" class="italic">鍘熸秷鎭凡鎾ゅ洖</span>
-
- <!-- 鏂囨湰 -->
- <span v-else-if="isText" class="min-w-0 line-clamp-2 break-words">{{ textPreview }}</span>
-
- <!-- 鏂囦欢锛歩con + 鏂囦欢鍚� + 澶у皬 -->
- <template v-else-if="isFile">
- <Icon :icon="fileIcon.icon" :color="fileIcon.color" :size="14" class="flex-shrink-0" />
- <span v-if="parsedPayload?.name" class="min-w-0 line-clamp-2 break-words">
- {{ parsedPayload.name }}
- </span>
- <span
- v-if="parsedPayload?.size"
- class="flex-shrink-0 text-[var(--ant-color-text-placeholder)]"
- >
- {{ formatFileSize(parsedPayload.size) }}
- </span>
- </template>
-
- <!-- 璇煶锛歛udio icon + 鏃堕暱 -->
- <template v-else-if="isVoice">
- <Icon icon="ant-design:audio-outlined" :size="14" class="flex-shrink-0" />
- <span v-if="parsedPayload?.duration" class="flex-shrink-0">
- {{ formatSeconds(parsedPayload.duration) }}
- </span>
- </template>
-
- <!-- 鍚嶇墖 -->
- <CardLineLabel
- v-else-if="isCard"
- :card="parsedPayload"
- class="min-w-0 line-clamp-2 break-words"
- />
-
- <!-- 琛ㄦ儏璐村浘锛氱缉鐣ュ浘 + name锛堟棤 name 浠呮樉绀� [琛ㄦ儏]锛� -->
- <template v-else-if="isFace">
- <span class="flex-shrink-0">[琛ㄦ儏]</span>
- <span v-if="parsedPayload?.name" class="min-w-0 line-clamp-2 break-words">
- {{ parsedPayload.name }}
- </span>
- </template>
-
- <!-- 棰戦亾绱犳潗锛歔棰戦亾] + 鏍囬 + 灏侀潰缂╃暐鍥� -->
- <template v-else-if="isMaterial">
- <span class="flex-shrink-0">[棰戦亾]</span>
- <span v-if="parsedPayload?.title" class="min-w-0 line-clamp-2 break-words">
- {{ parsedPayload.title }}
- </span>
- </template>
-
- <!-- 鍥剧墖 / 瑙嗛 / 琛ㄦ儏璐村浘 / 棰戦亾绱犳潗缂╃暐鍥� -->
- <img
- v-if="thumbnailUrl"
- :src="thumbnailUrl"
- class="flex-shrink-0 object-cover w-6 h-6 rounded"
- alt=""
- />
-
- <!-- 鍏抽棴鎸夐挳 -->
- <button
- v-if="closable"
- type="button"
- class="flex-shrink-0 inline-flex items-center justify-center w-4 h-4 cursor-pointer rounded-full bg-transparent border-none text-[var(--ant-color-text-secondary)] hover:bg-[var(--ant-color-fill)] hover:text-[var(--ant-color-text)]"
- @click.stop="emit('close')"
- >
- <Icon icon="ant-design:close-outlined" :size="10" />
- </button>
- </div>
-</template>
diff --git a/src/views/im/home/pages/conversation/components/message/tip-segments.vue b/src/views/im/home/pages/conversation/components/message/tip-segments.vue
deleted file mode 100644
index 19b7be2..0000000
--- a/src/views/im/home/pages/conversation/components/message/tip-segments.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<!--
- 娑堟伅鏂囨湰鐗囨娓叉煋锛歵ip 鏂囨 + TEXT 姘旀场鍏辩敤
- - mention 娈垫寕鐐瑰嚮 鈫� openMentionUserInfoCardAtEvent
- - link 娈垫覆鏌� <a target="_blank">锛屾祻瑙堝櫒榛樿琛屼负鎵撳紑鏂版爣绛�
- - text 娈靛師鏍疯緭鍑�
--->
-<script lang="ts" setup>
-import type { TipSegment } from '#/views/im/utils/message'
-
-import { IM_AT_ALL_USER_ID } from '#/views/im/utils/constants'
-import { openMentionUserInfoCardAtEvent } from '#/views/im/utils/user'
-
-defineOptions({ name: 'ImTipSegments' })
-
-defineProps<{
- segments: TipSegment[]
-}>()
-
-/** @鍏ㄤ綋鎴愬憳鏄箍鎾� mention锛屼粎楂樹寒閰嶈壊锛屼笉鎸傚彲鐐瑰嚮浜や簰 */
-function isClickableMention(segment: { userId: number }): boolean {
- return segment.userId !== IM_AT_ALL_USER_ID
-}
-
-/** mention 娈电偣鍑伙細fallbackName 鍙� segment 鏂囨湰锛岄伩鍏� friend / member 閮芥煡涓嶅埌鏃跺脊绌� */
-function handleMentionClick(
- segment: { text: string; type: 'mention'; userId: number; },
- event: MouseEvent
-) {
- if (!isClickableMention(segment)) {
- return
- }
- openMentionUserInfoCardAtEvent(segment.userId, event, segment.text)
-}
-</script>
-
-<template>
- <template v-for="(segment, _index) in segments" :key="_index">
- <span
- v-if="segment.type === 'mention'"
- class="text-[#576b95]"
- :class="{ 'cursor-pointer hover:underline': isClickableMention(segment) }"
- @click.stop="handleMentionClick(segment, $event)"
- >{{ segment.text }}</span>
- <a
- v-else-if="segment.type === 'link'"
- :href="segment.href"
- target="_blank"
- rel="noopener noreferrer"
- class="text-[#576b95] hover:underline break-all"
- @click.stop
- >{{ segment.text }}</a>
- <span v-else>{{ segment.text }}</span>
- </template>
-</template>
diff --git a/src/views/im/home/pages/conversation/index.vue b/src/views/im/home/pages/conversation/index.vue
deleted file mode 100644
index f5177ee..0000000
--- a/src/views/im/home/pages/conversation/index.vue
+++ /dev/null
@@ -1,281 +0,0 @@
-<script lang="ts" setup>
-import type { Conversation } from '../../types'
-
-import { computed, nextTick, ref, watch } from 'vue'
-
-import { IconifyIcon as Icon } from '../../../../../packages/icons/src'
-
-import { Button, Dropdown, Input, Menu } from 'ant-design-vue'
-
-import { ImConversationType } from '../../../utils/constants'
-import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
-import { StorageKeys } from '../../../utils/db'
-import { getGroupDisplayName } from '../../../utils/user'
-import { ResizableAside } from '../../components'
-import { FriendAddDialog } from '../../components/friend'
-import { GroupCreateDialog } from '../../components/group'
-import { useConversationStore } from '../../store/conversationStore'
-import { useGroupStore } from '../../store/groupStore'
-import { useImUiStore } from '../../store/uiStore'
-import { ConversationItem, MessagePanel } from './components'
-
-defineOptions({ name: 'ImMessagePage' })
-
-const conversationStore = useConversationStore()
-const groupStore = useGroupStore()
-const uiStore = useImUiStore()
-
-// ==================== 浼氳瘽鍒楄〃 ====================
-
-const keyword = ref('')
-
-const sortedConversations = computed(() => conversationStore.getSortedConversationList)
-
-/** 椤堕儴鎼滅储妗嗚繃婊や細璇濓細鍙寜 name 妯$硦鍖归厤锛岄伩鍏嶅懡涓� lastContent 绛夋瑕佸瓧娈靛共鎵� */
-const filteredConversations = computed(() =>
- filterConversationsByKeyword(sortedConversations.value, keyword.value)
-)
-
-// ==================== 缃《鐩稿叧 ====================
-
-const PINNED_FOLD_THRESHOLD = 3 // 缃《瓒呰繃璇ユ暟閲忔椂鏄剧ず鎶樺彔鍏ュ彛锛涗互涓嬫暟閲忕洿鎺ラ摵寮�锛堥伩鍏嶅崟鏉$疆椤跺氨鍑烘姌鍙犲ご瑙嗚澶噸锛�
-
-// 缃《鎶樺彔灞曞紑鎬侊細localStorage 鎸佷箙鍖栵紝鍒锋柊鍚庝繚鐣欑敤鎴蜂笂娆$殑閫夋嫨锛屽榻愬井淇�
-const pinnedExpanded = ref(
- localStorage.getItem(StorageKeys.localStorage.conversationPinnedExpanded) === 'true'
-)
-
-/** toggle + 鍐欑洏 */
-function togglePinnedExpanded() {
- pinnedExpanded.value = !pinnedExpanded.value
- localStorage.setItem(
- StorageKeys.localStorage.conversationPinnedExpanded,
- String(pinnedExpanded.value)
- )
-}
-
-/** 缃《浼氳瘽锛氬崟鐙垏鐗囷紝缁欐姌鍙犲ご璁℃暟 + 鎶樺彔鍖烘覆鏌撶敤 */
-const pinnedConversations = computed(() => filteredConversations.value.filter((c) => c.top))
-
-/** 闈炵疆椤朵細璇濓細鎶樺彔鎬佷笅濮嬬粓閾哄紑鍦ㄦ姌鍙犲ご涔嬩笅 */
-const normalConversations = computed(() => filteredConversations.value.filter((c) => !c.top))
-
-/**
- * 缃《鍒嗕袱鍫嗭細visible锛堟姌鍙犲ご涔嬩笂 = 鏈 + 褰撳墠婵�娲伙級/ foldable锛堟姌鍙犲ご涔嬩笅锛夛紱涓�娆� partition 瀹屾垚
- *
- * 褰撳墠婵�娲讳細璇濅篃"閽�"鍦� visible锛氶伩鍏嶇偣寮�鏈缃《 鈫� 绔嬪埢琚 鈫� 鍒楄〃涓�闂噸鎺掑洖鎶樺彔鐨勪綋楠�
- */
-const pinnedGroups = computed(() => {
- const visible: Conversation[] = []
- const foldable: Conversation[] = []
- for (const conversation of pinnedConversations.value) {
- if (isActiveConversation(conversation) || hasUnreadBadge(conversation)) {
- visible.push(conversation)
- } else {
- foldable.push(conversation)
- }
- }
- return { visible, foldable }
-})
-
-/** 鎶樺彔鏃跺彧娓� visible锛堟湭璇� / 婵�娲荤┛閫忥級锛涘睍寮�鏃舵覆鍏ㄩ儴 鈥斺�� 灞曞紑鍚庝笉鍒嗙粍锛岄伩鍏嶇偣鍑绘姌鍙犲尯璺ㄧ粍涓婅烦 */
-const renderedPinnedConversations = computed(() =>
- pinnedExpanded.value ? pinnedConversations.value : pinnedGroups.value.visible
-)
-
-/** 缃《鎶樺彔鏃舵槸鍚︿笂娴埌鎶樺彔澶翠箣涓婏細浠呬互鏁板瓧寰芥爣涓哄噯锛涘厤鎵撴壈鍗充究鏈夋湭璇讳篃鍙睍绀哄皬绾㈢偣锛屼笉鍙備笌涓婃诞 */
-function hasUnreadBadge(conversation: Conversation): boolean {
- return !conversation.silent && (conversation.unreadCount || 0) > 0
-}
-
-/** 鏄惁涓哄綋鍓嶆縺娲讳細璇� */
-function isActiveConversation(conversation: Conversation): boolean {
- const active = conversationStore.activeConversation
- return !!active && getConversationKey(active) === getConversationKey(conversation)
-}
-
-/**
- * 鏄惁杩涘叆"鍒嗙粍妯″紡"锛堢嫭绔嬫祬搴� + 鍙兘鏈夋姌鍙犲ご锛夛細鎼滅储鏃朵笉鍒嗙粍锛堢敤鎴峰湪鎵句汉锛屽埆鍐嶈鎶樺彔鎸′綇锛夛紱
- * 缃《鏁� < 闃堝�间篃涓嶅垎缁勶紝閬垮厤鍗曟潯缃《灏卞嚭鎶樺彔澶磋瑙夊お閲�
- */
-const showPinnedSection = computed(
- () => !keyword.value.trim() && pinnedConversations.value.length >= PINNED_FOLD_THRESHOLD
-)
-
-// ==================== 娣诲姞鏈嬪弸 ====================
-
-const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 娣诲姞鏈嬪弸寮圭獥 ref锛氬彸涓婅 +-涓嬫媺銆屾坊鍔犳湅鍙嬨�嶅叆鍙h皟 open() 瑙﹀彂
-
-// ==================== 寤虹兢鐩稿叧 ====================
-
-const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // 鍙戣捣缇よ亰寮圭獥 ref锛歨andleOpenCreateGroup 璋� open() 鎵撳紑
-
-/** 鎵撳紑鍙戣捣缇よ亰寮圭獥锛氭棤閿佸畾椤圭殑鍏ㄥ眬鍏ュ彛 */
-function handleOpenCreateGroup() {
- createGroupDialogRef.value?.open()
-}
-
-/** 澶勭悊寤虹兢鎴愬姛 */
-function handleGroupCreated(groupId: number) {
- // GroupCreateDialog 宸茬粡 upsertGroup 鎶婃柊缇ゅ啓杩� store锛岃繖閲屽彧 get + 鎵撳紑浼氳瘽
- const group = groupStore.getGroup(groupId)
- if (!group) {
- return
- }
- conversationStore.openConversation(
- groupId,
- ImConversationType.GROUP,
- getGroupDisplayName(group),
- group.avatar || '',
- { silent: !!group.silent }
- )
-}
-
-// ==================== 婊氬姩鍒颁笅涓�涓湭璇� ====================
-// 宸ュ叿鏍忋�屾秷鎭�峊ab 浜屾鐐瑰嚮 鈫� uiStore.nextUnreadJumpNonce 閫掑 鈫� 鏈〉 watch 婊氬姩鍒颁笅涓�鏉℃湭璇�
-// 鍚厤鎵撴壈浼氳瘽锛堝皬绾㈢偣涔熺畻鏈锛夛紱閫氳繃缁存姢 lastJumpedConversationKey 璁╄繛缁偣鍑婚『搴忕┛杩囨暣涓湭璇诲垪琛�
-
-/** 涓婃鍛戒腑鐨勬湭璇讳細璇� key锛涗负绌烘椂浠庢湭璇诲垪琛ㄥご寮�濮� */
-let lastJumpedConversationKey: null | string = null
-
-/** 婊氬姩鍒颁笅涓�涓湭璇讳細璇濓紙鍚厤鎵撴壈锛夛紱鐩爣琚悳绱㈡杩囨护鎴栬棌鍦ㄧ疆椤舵姌鍙犲尯鏃跺厛瑙i櫎鎷︽埅鍐嶆粴 */
-async function jumpToNextUnread() {
- // 鍚厤鎵撴壈鐨勫叏閲忔湭璇讳細璇濓紱绌哄垯鐩存帴杩斿洖
- const unreadList = sortedConversations.value.filter((c) => (c.unreadCount || 0) > 0)
- if (unreadList.length === 0) {
- return
- }
- // 浠庝笂娆″懡涓偅鏉″線鍚庢帹涓�浣嶏紱棣栨鎴栦笂娆$洰鏍囧凡璇诲畬涓嶅湪鍒楄〃閲屾椂锛宺efIndex=-1锛屼粠澶村紑濮�
- const refIndex = lastJumpedConversationKey
- ? unreadList.findIndex((c) => getConversationKey(c) === lastJumpedConversationKey)
- : -1
- const target = unreadList[(refIndex + 1) % unreadList.length]
- if (!target) {
- return
- }
- const key = getConversationKey(target)
- lastJumpedConversationKey = key
-
- // 鐩爣琚悳绱㈠叧閿瓧杩囨护鎺夛細娓呯┖ keyword 璁╁畠閲嶆柊杩涘叆鍙鍒楄〃
- if (keyword.value && !filteredConversations.value.some((c) => getConversationKey(c) === key)) {
- keyword.value = ''
- }
- // 鐩爣钘忓湪缃《鎶樺彔鍖猴細涓存椂灞曞紑锛涗笉鍐� localStorage锛屽埛鏂板悗杩樺師鎶樺彔鎬�
- const inFoldable = pinnedGroups.value.foldable.some((c) => getConversationKey(c) === key)
- if (inFoldable && !pinnedExpanded.value) {
- pinnedExpanded.value = true
- }
-
- // 绛� keyword / pinnedExpanded 鍙樺寲鍙嶅簲鍒� DOM 鍚庡啀鍘诲彇鐩爣鍏冪礌
- await nextTick()
- const el = document.querySelector(`[data-conversation-key="${key}"]`) as HTMLElement | null
- if (!el) {
- return
- }
- // block: 'start' 鎶婄洰鏍囦細璇濋《鍒板垪琛ㄥ彲瑙嗗尯绗竴琛岋紱瀵归綈寰俊"鍒囧埌涓嬩竴涓湭璇�=鍒楄〃鐨勭涓�鏉�"
- el.scrollIntoView({ behavior: 'smooth', block: 'start' })
-}
-
-watch(() => uiStore.nextUnreadJumpNonce, jumpToNextUnread)
-</script>
-
-<template>
- <!-- 娑堟伅 Tab锛氬乏渚т細璇濆垪琛� + 鍙充晶鑱婂ぉ闈㈡澘 -->
- <div class="flex flex-1 min-w-0 h-full">
- <!-- 宸︿晶浼氳瘽鍒楄〃锛堝彲鎷栨嫿瀹藉害锛� -->
- <ResizableAside :default-width="260" :storage-key="StorageKeys.localStorage.asideWidth">
- <!-- 椤堕儴锛氭悳绱㈡ + "+" 鍙蜂笅鎷夛紙瀵归綈寰俊 PC锛氬彂璧风兢鑱� / 娣诲姞鏈嬪弸锛夛紱h-14 涓庡彸渚� MessagePanel 澶撮儴瀵归綈 -->
- <div
- class="flex flex-shrink-0 gap-2 items-center h-14 px-4 border-b border-b-solid border-[var(--im-border-color-lighter)]"
- >
- <Input v-model:value="keyword" placeholder="鎼滅储" allow-clear class="flex-1">
- <template #prefix>
- <Icon icon="ant-design:search-outlined" />
- </template>
- </Input>
- <Dropdown :trigger="['click']" placement="bottom">
- <Button size="small" shape="circle">
- <Icon icon="ant-design:plus-outlined" />
- </Button>
- <template #overlay>
- <Menu>
- <Menu.Item @click="handleOpenCreateGroup">
- <span class="inline-flex items-center gap-2">
- <Icon icon="ant-design:message-outlined" :size="16" />
- <span>鍙戣捣缇よ亰</span>
- </span>
- </Menu.Item>
- <Menu.Item @click="friendAddDialogRef?.open()">
- <span class="inline-flex items-center gap-2">
- <Icon icon="ant-design:user-add-outlined" :size="16" />
- <span>娣诲姞鏈嬪弸</span>
- </span>
- </Menu.Item>
- </Menu>
- </template>
- </Dropdown>
- </div>
-
- <!-- 浼氳瘽鍒楄〃涓讳綋 -->
- <div class="flex-1 overflow-y-auto">
- <!-- 缃《浼氳瘽锛氭暟閲� < 闃堝�兼垨鎼滅储涓洿鎺ラ摵寮�锛涘惁鍒欒繘鍏ュ垎缁勬ā寮忥紙鐙珛娴呭簳 + 鍙�夋姌鍙犲ご锛� -->
- <template v-if="!showPinnedSection">
- <ConversationItem
- v-for="conversation in pinnedConversations"
- :key="getConversationKey(conversation)"
- :conversation="conversation"
- />
- </template>
- <div v-else class="bg-[var(--ant-color-fill-secondary)]">
- <ConversationItem
- v-for="conversation in renderedPinnedConversations"
- :key="getConversationKey(conversation)"
- :conversation="conversation"
- />
-
- <!-- 鎶樺彔澶达細鏀惧湪缃《鍖哄簳閮ㄥ榻� WeChat mac锛涘睍寮� / 鎶樺彔鎬佸叡鐢紝浠呭湪杩樻湁"鍙姌鍙�"鍐呭銆佹垨褰撳墠宸插睍寮�鏃跺嚭鐜� -->
- <div
- v-if="pinnedGroups.foldable.length > 0 || pinnedExpanded"
- class="flex items-center justify-between px-4 py-2.5 cursor-pointer transition-colors text-13px text-[var(--ant-color-text)] border-y border-y-solid border-[var(--im-border-color-lighter)] hover:bg-[var(--ant-color-fill)]"
- @click="togglePinnedExpanded"
- >
- <span class="flex items-center gap-1.5">
- <Icon icon="ant-design:menu-outlined" :size="14" />
- {{
- pinnedExpanded
- ? '鎶樺彔缃《鑱婂ぉ'
- : `${pinnedGroups.foldable.length} 涓疆椤惰亰澶ー
- }}
- </span>
- <Icon
- :icon="pinnedExpanded ? 'ant-design:up-outlined' : 'ant-design:down-outlined'"
- :size="11"
- class="text-[var(--ant-color-text-placeholder)]"
- />
- </div>
- </div>
-
- <!-- 鏅�氫細璇� -->
- <ConversationItem
- v-for="conversation in normalConversations"
- :key="getConversationKey(conversation)"
- :conversation="conversation"
- />
-
- <div
- v-if="filteredConversations.length === 0"
- class="flex items-center justify-center py-10 text-sm text-[var(--ant-color-text-secondary)]"
- >
- {{ keyword ? '娌℃湁婊¤冻鏉′欢鐨勪細璇�' : '鏆傛棤浼氳瘽' }}
- </div>
- </div>
- </ResizableAside>
-
- <!-- 鍙充晶鑱婂ぉ闈㈡澘 -->
- <MessagePanel />
-
- <!-- 娣诲姞鏈嬪弸 / 鍙戣捣缇よ亰寮圭獥 -->
- <FriendAddDialog ref="friendAddDialogRef" />
- <GroupCreateDialog ref="createGroupDialogRef" @created="handleGroupCreated" />
- </div>
-</template>
diff --git a/src/views/im/home/store/channelStore.ts b/src/views/im/home/store/channelStore.ts
deleted file mode 100644
index 3f059ca..0000000
--- a/src/views/im/home/store/channelStore.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import type { ImManagerChannelApi } from '#/api/im/manager/channel'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { getSimpleChannelList } from '#/api/im/manager/channel'
-
-import { ImConversationType } from '../../utils/constants'
-import { getDb } from '../../utils/db'
-import { useConversationStore } from './conversationStore'
-
-/**
- * IM 棰戦亾 Store
- *
- * 璐熻矗锛氱紦瀛樺綋鍓嶇敤鎴疯兘鐪嬪埌鐨勯閬撶簿绠�鍒楄〃锛堝惈 id / code / name / avatar锛夛紝
- * 渚涗細璇濆垪琛� / 鍗$墖娓叉煋鏃跺弽鏌ラ閬撳悕绉� + 澶村儚锛岄伩鍏嶆樉绀恒�岄閬� 1銆嶈繖绉嶅崰浣�
- */
-export const useChannelStore = defineStore('imChannelStore', {
- state: () => ({
- channels: [] as ImManagerChannelApi.Channel[],
- loaded: false
- }),
-
- getters: {
- getChannel(state): (id: number) => ImManagerChannelApi.Channel | undefined {
- return (id: number) => state.channels.find((c) => c.id === id)
- }
- },
-
- actions: {
- // ==================== 鏈湴缂撳瓨 ====================
-
- /** 浠� IndexedDB 鎭㈠棰戦亾鍒楄〃 */
- async loadChannelList(): Promise<boolean> {
- try {
- const cached = await getDb().getAll<ImManagerChannelApi.Channel>('channels')
- if (!cached || cached.length === 0) {
- return false
- }
- this.channels = cached
- return true
- } catch (error) {
- console.warn('[IM channelStore] 鏈湴棰戦亾缂撳瓨璇诲彇澶辫触', error)
- return false
- }
- },
-
- /** 淇濆瓨棰戦亾鍒楄〃 */
- saveChannelList(): void {
- void getDb()
- .transaction(['channels'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.clearStore('channels', tx)
- for (const channel of this.channels) {
- await db.put('channels', channel, tx)
- }
- })
- .catch((error) => console.warn('[IM channelStore] 鏈湴棰戦亾缂撳瓨鍐欏叆澶辫触', error))
- },
-
- // ==================== 杩滅鎷夊彇 ====================
-
- /** 鎷夊彇鍚敤鐨勯閬撶簿绠�鍒楄〃锛涙垚鍔熷悗鍥炲~浼氳瘽鍒楄〃宸叉湁鐨勯閬� name / avatar锛岃鐩� IDB 鏃у崰浣� */
- async fetchChannelList(force = false) {
- if (this.loaded && !force) {
- return
- }
- try {
- this.channels = (await getSimpleChannelList()) || []
- this.loaded = true
- this.syncChannelConversationMetadata()
- this.saveChannelList()
- } catch (error) {
- console.warn('[IM channelStore] fetchChannelList 澶辫触', error)
- }
- },
-
- /** 鐢ㄦ渶鏂扮殑棰戦亾淇℃伅瑕嗙洊宸叉湁 CHANNEL 浼氳瘽鐨� name / avatar */
- syncChannelConversationMetadata() {
- const conversationStore = useConversationStore()
- const indexed = new Map(this.channels.map((c) => [c.id, c]))
- conversationStore.conversations.forEach((conversation) => {
- if (conversation.type !== ImConversationType.CHANNEL) {
- return
- }
- const channel = indexed.get(conversation.targetId)
- if (!channel) {
- return
- }
- conversationStore.updateConversation(ImConversationType.CHANNEL, conversation.targetId, {
- name: channel.name,
- avatar: channel.avatar
- })
- })
- },
-
- /** 娓呯┖棰戦亾鍐呭瓨 */
- clear() {
- this.channels = []
- this.loaded = false
- }
- }
-})
-
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useChannelStore, import.meta.hot))
-}
-
-export const useChannelStoreWithOut = () => useChannelStore()
diff --git a/src/views/im/home/store/conversationStore.ts b/src/views/im/home/store/conversationStore.ts
deleted file mode 100644
index a799ffc..0000000
--- a/src/views/im/home/store/conversationStore.ts
+++ /dev/null
@@ -1,910 +0,0 @@
-import type {
- Conversation,
- ConversationDO,
- ConversationRead,
- ConversationReadDO,
- MessageDO
-} from '../types'
-
-import type { ImConversationReadApi } from '#/api/im/conversation/read'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { pullMyConversationReadList as apiPullMyConversationReadList } from '#/api/im/conversation/read'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { CONVERSATION_RECENT_FORWARD_MAX } from '../../utils/config'
-import {
- ImConversationType,
- ImMessageReceiptStatus,
- ImMessageStatus,
- isNormalMessage
-} from '../../utils/constants'
-import { type DbTransaction, getClientConversationId, getDb, StorageKeys } from '../../utils/db'
-import { runIncrementalPull } from '../../utils/pull'
-import { useMessageStore } from './messageStore'
-
-const PERSIST_DRAFT_DEBOUNCE_MS = 500
-const pendingDraftConversations = new Set<Conversation>()
-
-/** 鍒涘缓浼氳瘽璇讳綅缃褰� */
-function createConversationRead(
- type: number,
- targetId: number,
- messageId: number
-): ConversationRead {
- return {
- conversationType: type,
- targetId,
- messageId,
- updateTime: Date.now()
- }
-}
-
-/** 鍒涘缓鑽夌淇濆瓨闃叉姈鍑芥暟 */
-function createDraftDebounce(fn: () => void, wait: number) {
- let timer: ReturnType<typeof setTimeout> | undefined
-
- const run = () => {
- if (timer) {
- clearTimeout(timer)
- timer = undefined
- }
- fn()
- }
- const debounced = () => {
- if (timer) {
- clearTimeout(timer)
- }
- timer = setTimeout(run, wait)
- }
- debounced.cancel = () => {
- if (timer) {
- clearTimeout(timer)
- timer = undefined
- }
- }
- debounced.flush = run
- return debounced
-}
-
-/** 浼氳瘽杞� IndexedDB 璁板綍 */
-function toConversationDO(conversation: Conversation): ConversationDO {
- const draft = conversation.draft
- return {
- targetId: conversation.targetId,
- type: conversation.type,
- name: conversation.name,
- avatar: conversation.avatar,
- unreadCount: conversation.unreadCount,
- lastContent: conversation.lastContent,
- lastSendTime: conversation.lastSendTime,
- lastSenderId: conversation.lastSenderId,
- lastMessageType: conversation.lastMessageType,
- lastMessageId: conversation.lastMessageId,
- lastClientMessageId: conversation.lastClientMessageId,
- lastMessageStatus: conversation.lastMessageStatus,
- lastReceiptStatus: conversation.lastReceiptStatus,
- lastSelfSend: conversation.lastSelfSend,
- lastSenderDisplayName: conversation.lastSenderDisplayName,
- reportedReadMessageId: conversation.reportedReadMessageId,
- deleted: conversation.deleted,
- top: conversation.top,
- silent: conversation.silent,
- atMe: conversation.atMe,
- atAll: conversation.atAll,
- draft: draft ? { ...draft, reply: draft.reply ? { ...draft.reply } : undefined } : undefined,
- clientConversationId: getClientConversationId(conversation.type, conversation.targetId)
- }
-}
-
-/** IndexedDB 璁板綍杞細璇� */
-function fromConversationDO(conversation: ConversationDO): Conversation {
- const {
- clientConversationId: _clientConversationId,
- ...rest
- } = conversation
- return rest
-}
-
-/** 浼氳瘽璇讳綅缃浆 IndexedDB 璁板綍 */
-function toConversationReadDO(record: ConversationRead): ConversationReadDO {
- return {
- conversationType: record.conversationType,
- targetId: record.targetId,
- messageId: record.messageId,
- updateTime: record.updateTime,
- clientConversationId: getClientConversationId(record.conversationType, record.targetId)
- }
-}
-
-/** IndexedDB 璁板綍杞細璇濊浣嶇疆 */
-function fromConversationReadDO(record: ConversationReadDO): ConversationRead {
- const { clientConversationId: _clientConversationId, ...rest } = record
- return rest
-}
-
-/** 鏄惁涓烘湁鏁堜細璇濊浣嶇疆 */
-function isValidConversationReadRecord(record: ImConversationReadApi.ConversationReadRespVO): boolean {
- return !!record.conversationType && !!record.targetId && !!record.messageId
-}
-
-/** 鑾峰彇瀵规柟鏅�氭秷鎭渶澶х紪鍙� */
-function getMaxIncomingNormalMessageId(
- messages: Array<Pick<MessageDO, 'id' | 'selfSend' | 'status' | 'type'>>
-): number {
- let maxMessageId = 0
- for (const message of messages) {
- if (
- message.id &&
- !message.selfSend &&
- isNormalMessage(message.type) &&
- message.status !== ImMessageStatus.RECALL &&
- message.id > maxMessageId
- ) {
- maxMessageId = message.id
- }
- }
- return maxMessageId
-}
-
-export const useConversationStore = defineStore('imConversationStore', {
- state: () => ({
- conversations: [] as Conversation[], // 鍏ㄩ噺浼氳瘽鍒楄〃锛堢鑱� + 缇よ亰 + 棰戦亾锛�
- conversationReads: {} as Record<string, ConversationRead>, // 浼氳瘽璇讳綅缃�
- activeConversation: null as Conversation | null, // 褰撳墠婵�娲荤殑浼氳瘽
- loading: false, // 鏄惁姝e湪鎵归噺鍔犺浇
- recentForwardConversationKeys: [] as string[] // 鏈�杩戣浆鍙戜細璇� key 鍒楄〃
- }),
-
- getters: {
- /** 鎺掑簭鍚庣殑浼氳瘽鍒楄〃 */
- getSortedConversationList(state): Conversation[] {
- return state.conversations
- .filter((conversation) => !conversation.deleted)
- .toSorted((a, b) => {
- const aTop = a.top ? 1 : 0
- const bTop = b.top ? 1 : 0
- if (aTop !== bTop) {
- return bTop - aTop
- }
- return (b.lastSendTime || 0) - (a.lastSendTime || 0)
- })
- },
-
- /** 鏈鎬绘暟 */
- getTotalUnreadCount(state): number {
- return state.conversations
- .filter((conversation) => !conversation.deleted && !conversation.silent)
- .reduce((sum, conversation) => sum + (conversation.unreadCount || 0), 0)
- },
-
- /** 鏌ユ壘浼氳瘽 */
- getConversation:
- (state) =>
- (type: number, targetId: number): Conversation | undefined =>
- state.conversations.find(
- (conversation) => conversation.type === type && conversation.targetId === targetId
- ),
-
- /** 鏌ユ壘浼氳瘽璇讳綅缃� */
- getConversationRead:
- (state) =>
- (type: number, targetId: number): ConversationRead | undefined =>
- state.conversationReads[getClientConversationId(type, targetId)]
- },
-
- actions: {
- /** 鍔犺浇浼氳瘽 */
- async loadConversationList() {
- // 1. 娓呯悊鏃ц处鍙峰唴瀛�
- const userId = getCurrentUserId()
- if (!userId) {
- this.clear()
- return
- }
- const previousActiveKey = this.activeConversation
- ? getClientConversationId(this.activeConversation.type, this.activeConversation.targetId)
- : null
- this.clear()
- // 2. 浠� IndexedDB 璇诲彇浼氳瘽鍜岃交閲忚缃�
- const db = getDb()
- const [conversations, conversationReads, recent] = await Promise.all([
- db.getAll<ConversationDO>('conversations'),
- db.getAll<ConversationReadDO>('conversationReads'),
- db.getSetting<string[]>(StorageKeys.settings.recentForwardConversationKeys)
- ])
- const nextConversationReads: Record<string, ConversationRead> = {}
- for (const record of conversationReads) {
- const item = fromConversationReadDO(record)
- nextConversationReads[getClientConversationId(item.conversationType, item.targetId)] = item
- }
- const nextConversations = conversations.map((conversation) => fromConversationDO(conversation))
- this.conversationReads = nextConversationReads
- await this.applyLocalConversationReads(nextConversations)
- this.conversations = nextConversations
- if (Array.isArray(recent)) {
- this.recentForwardConversationKeys = recent.slice(0, CONVERSATION_RECENT_FORWARD_MAX)
- }
- // 3. 鎭㈠褰撳墠婵�娲讳細璇�
- if (previousActiveKey) {
- this.activeConversation =
- this.conversations.find(
- (conversation) =>
- !conversation.deleted &&
- getClientConversationId(conversation.type, conversation.targetId) ===
- previousActiveKey
- ) ?? null
- }
- },
-
- /** 娓呯┖浼氳瘽鍐呭瓨 */
- clear() {
- saveDraftConversationListDebounced.cancel()
- pendingDraftConversations.clear()
- this.conversations = []
- this.conversationReads = {}
- this.activeConversation = null
- this.recentForwardConversationKeys = []
- },
-
- /** 鎸佷箙鍖栦細璇濊浣嶇疆 */
- async saveConversationReadRecord(
- target: ConversationRead | ConversationRead[] | null | undefined,
- tx?: DbTransaction
- ): Promise<void> {
- let targets: ConversationRead[] = []
- if (Array.isArray(target)) {
- targets = target
- } else if (target) {
- targets = [target]
- }
- const records = targets.map((record) => toConversationReadDO(record))
- if (records.length === 0) {
- return
- }
- const db = getDb()
- if (tx) {
- for (const record of records) {
- await db.put('conversationReads', record, tx)
- }
- return
- }
- await db.transaction(['conversationReads'], 'readwrite', async (tx) => {
- for (const record of records) {
- await db.put('conversationReads', record, tx)
- }
- })
- },
-
- /** 搴旂敤鏈湴浼氳瘽璇讳綅缃� */
- async applyLocalConversationReads(conversations?: Conversation[]) {
- const targetConversations = conversations || this.conversations
- const changedConversations: Conversation[] = []
- for (const conversation of targetConversations) {
- const record = this.getConversationRead(conversation.type, conversation.targetId)
- if (!record) {
- continue
- }
- if (this.applyReadToConversation(conversation, record.messageId)) {
- changedConversations.push(conversation)
- continue
- }
- if (conversation.unreadCount === 0 && !conversation.atMe && !conversation.atAll) {
- continue
- }
- const messages = await getDb().getAllByIndex<MessageDO>(
- 'messages',
- 'clientConversationId',
- getClientConversationId(conversation.type, conversation.targetId)
- )
- const maxIncomingMessageId = getMaxIncomingNormalMessageId(messages)
- if (maxIncomingMessageId > 0 && maxIncomingMessageId <= record.messageId) {
- conversation.unreadCount = 0
- conversation.atMe = false
- conversation.atAll = false
- changedConversations.push(conversation)
- }
- }
- if (changedConversations.length > 0) {
- await this.saveConversationRecord(changedConversations)
- }
- },
-
- /** 鍒ゆ柇娑堟伅鏄惁宸茶浼氳瘽璇讳綅缃鐩� */
- isMessageCoveredByReadPosition(
- conversation: Pick<Conversation, 'targetId' | 'type'>,
- message?: null | { id?: number }
- ): boolean {
- if (!message?.id) {
- return false
- }
- const record = this.getConversationRead(conversation.type, conversation.targetId)
- return !!record && message.id <= record.messageId
- },
-
- /** 鍒ゆ柇浼氳瘽璇讳綅缃槸鍚﹁鐩栨秷鎭紪鍙� */
- isReadPositionCovered(type: number, targetId: number, messageId?: number): boolean {
- if (!messageId) {
- return false
- }
- const record = this.getConversationRead(type, targetId)
- return !!record && record.messageId >= messageId
- },
-
- /** 鍒ゆ柇鏈嶅姟绔凡璇讳綅缃槸鍚﹁鐩栨秷鎭紪鍙� */
- isReportedReadPositionCovered(type: number, targetId: number, messageId?: number): boolean {
- if (!messageId) {
- return false
- }
- const conversation = this.getConversation(type, targetId)
- return (conversation?.reportedReadMessageId || 0) >= messageId
- },
-
- /** 搴旂敤璇讳綅缃埌浼氳瘽 */
- applyReadToConversation(conversation: Conversation, messageId: number): boolean {
- if (!conversation.lastMessageId || conversation.lastMessageId > messageId) {
- return false
- }
- if (conversation.unreadCount === 0 && !conversation.atMe && !conversation.atAll) {
- return false
- }
- conversation.unreadCount = 0
- conversation.atMe = false
- conversation.atAll = false
- return true
- },
-
- /** 搴旂敤浼氳瘽璇讳綅缃� */
- async applyConversationReadList(
- records: ImConversationReadApi.ConversationReadRespVO[],
- isActive?: () => boolean
- ): Promise<void> {
- if (records.length === 0) {
- return
- }
- const changedReads = new Map<string, ConversationRead>()
- const changedConversations = new Map<string, Conversation>()
- const changedMessages = new Map<string, MessageDO>()
- const db = getDb()
- const messageStore = useMessageStore()
-
- // 1. 鎸夎浣嶇疆鏇存柊浼氳瘽鏈鍜岄閬撳凡璇绘��
- for (const record of records) {
- if (isActive && !isActive()) {
- return
- }
- if (!isValidConversationReadRecord(record)) {
- continue
- }
- const clientConversationId = getClientConversationId(
- record.conversationType,
- record.targetId
- )
- let storedMessages: MessageDO[] | undefined
- const getStoredMessages = async () => {
- if (!storedMessages) {
- storedMessages = await db.getAllByIndex<MessageDO>(
- 'messages',
- 'clientConversationId',
- clientConversationId
- )
- }
- return storedMessages
- }
- const current = this.conversationReads[clientConversationId]
- const messageId = Math.max(record.messageId, current?.messageId || 0)
- const conversation = this.getConversation(record.conversationType, record.targetId)
- if (conversation && record.messageId > (conversation.reportedReadMessageId || 0)) {
- conversation.reportedReadMessageId = record.messageId
- changedConversations.set(clientConversationId, conversation)
- }
- if (!current || messageId > current.messageId) {
- const next = {
- conversationType: record.conversationType,
- targetId: record.targetId,
- messageId,
- updateTime: record.updateTime
- }
- this.conversationReads[clientConversationId] = next
- changedReads.set(clientConversationId, next)
- }
-
- if (conversation && this.applyReadToConversation(conversation, messageId)) {
- changedConversations.set(clientConversationId, conversation)
- } else if (conversation) {
- const maxIncomingMessageId = getMaxIncomingNormalMessageId(await getStoredMessages())
- if (maxIncomingMessageId > 0 && maxIncomingMessageId <= messageId) {
- conversation.unreadCount = 0
- conversation.atMe = false
- conversation.atAll = false
- changedConversations.set(clientConversationId, conversation)
- }
- }
- if (record.conversationType !== ImConversationType.CHANNEL) {
- continue
- }
- const memoryMessages = messageStore.getMessages(clientConversationId)
- for (const message of memoryMessages) {
- if (
- message.id &&
- message.id <= messageId &&
- message.receiptStatus !== ImMessageReceiptStatus.DONE
- ) {
- message.receiptStatus = ImMessageReceiptStatus.DONE
- }
- }
- for (const message of await getStoredMessages()) {
- if (
- message.id &&
- message.id <= messageId &&
- message.receiptStatus !== ImMessageReceiptStatus.DONE
- ) {
- message.receiptStatus = ImMessageReceiptStatus.DONE
- changedMessages.set(message.messageKey, message)
- }
- }
- }
-
- // 2. 鎸佷箙鍖栨湰杞彉鏇�
- if (
- changedReads.size === 0 &&
- changedConversations.size === 0 &&
- changedMessages.size === 0
- ) {
- return
- }
- if (isActive && !isActive()) {
- return
- }
- const stores: Array<'conversationReads' | 'conversations' | 'messages'> = []
- if (changedReads.size > 0) {
- stores.push('conversationReads')
- }
- if (changedConversations.size > 0) {
- stores.push('conversations')
- }
- if (changedMessages.size > 0) {
- stores.push('messages')
- }
- await db.transaction(stores, 'readwrite', async (tx) => {
- if (changedReads.size > 0) {
- await this.saveConversationReadRecord([...changedReads.values()], tx)
- }
- if (changedConversations.size > 0) {
- await this.saveConversationRecord([...changedConversations.values()], tx)
- }
- for (const message of changedMessages.values()) {
- await db.put('messages', message, tx)
- }
- })
- },
-
- /** 澧為噺鎷夊彇浼氳瘽璇讳綅缃� */
- async pullConversationReads(isActive?: () => boolean): Promise<void> {
- await runIncrementalPull(
- StorageKeys.settings.conversationReadPullCursor,
- apiPullMyConversationReadList,
- async (records) => {
- if (isActive && !isActive()) {
- return false
- }
- await this.applyConversationReadList(records, isActive)
- if (isActive && !isActive()) {
- return false
- }
- return true
- },
- isActive
- )
- },
-
- /** 鎵ц浼氳瘽璁板綍鎸佷箙鍖� */
- async saveConversationRecord(
- target: Conversation | Conversation[] | null | undefined,
- tx?: DbTransaction
- ): Promise<void> {
- const db = getDb()
- const conversations = (Array.isArray(target) ? target : (target ? [target] : [])).map(
- (conversation) => toConversationDO(conversation)
- )
- if (conversations.length === 0) {
- return
- }
- if (tx) {
- for (const conversation of conversations) {
- await db.put('conversations', conversation, tx)
- }
- return
- }
- await db.transaction(['conversations'], 'readwrite', async (tx) => {
- for (const conversation of conversations) {
- await db.put('conversations', conversation, tx)
- }
- })
- },
-
- /** 鎸佷箙鍖栧崟涓細璇� */
- saveConversation(conversation: Conversation | null | undefined, tx?: DbTransaction): void {
- if (!conversation) {
- return
- }
- void this.saveConversationRecord(conversation, tx).catch((error) =>
- console.warn('[IM conversationStore] 浼氳瘽鍐欏叆澶辫触', error)
- )
- },
-
- /** 鎸佷箙鍖栦細璇濆垪琛� */
- saveConversationList(conversations?: Conversation[] | null, tx?: DbTransaction): void {
- if (this.loading && !tx) {
- return
- }
- void this.saveConversationRecord(conversations || this.conversations, tx).catch((error) =>
- console.warn('[IM conversationStore] 浼氳瘽鍐欏叆澶辫触', error)
- )
- },
-
- /** 纭繚浼氳瘽瀛樺湪 */
- ensureConversation(info: {
- avatar: string
- name: string
- silent?: boolean
- targetId: number
- type: number
- }): Conversation {
- // 1. 鍒涘缓涓嶅瓨鍦ㄧ殑浼氳瘽
- let conversation = this.getConversation(info.type, info.targetId)
- if (!conversation) {
- conversation = this.createEmptyConversation(
- info.type,
- info.targetId,
- info.name,
- info.avatar,
- info.silent
- )
- this.conversations.unshift(conversation)
- } else if (conversation.deleted) {
- // 2. 鎭㈠杞垹闄や細璇�
- conversation.deleted = false
- conversation.name = info.name || conversation.name
- conversation.avatar = info.avatar || conversation.avatar
- if (info.silent !== undefined) {
- conversation.silent = info.silent
- }
- } else {
- // 3. 鍚屾浼氳瘽灞曠ず鍏冩暟鎹�
- if (info.name) {
- conversation.name = info.name
- }
- if (info.avatar) {
- conversation.avatar = info.avatar
- }
- if (info.silent !== undefined) {
- conversation.silent = info.silent
- }
- }
- return conversation
- },
-
- /** 鎵撳紑鎴栧垱寤轰細璇� */
- openConversation(
- targetId: number,
- type: number,
- name: string,
- avatar: string,
- options?: { silent?: boolean }
- ): Conversation {
- // 1. 纭繚浼氳瘽鍦ㄥ垪琛ㄤ腑
- const conversation = this.ensureConversation({
- type,
- targetId,
- name,
- avatar,
- silent: options?.silent
- })
- // 2. 婵�娲讳細璇濆苟淇濆瓨
- this.setActiveConversation(conversation)
- this.saveConversation(conversation)
- return conversation
- },
-
- /** 璁剧疆褰撳墠浼氳瘽 */
- setActiveConversation(conversation: Conversation | null) {
- this.activeConversation = conversation
- if (!conversation) {
- return
- }
- // 鎳掑姞杞芥秷鎭苟淇濆瓨浼氳瘽鎽樿
- void useMessageStore().ensureConversationMessageListLoaded(conversation)
- this.saveConversation(conversation)
- },
-
- /** 鍒涘缓绌轰細璇� */
- createEmptyConversation(
- type: number,
- targetId: number,
- name: string,
- avatar: string,
- silent = false
- ): Conversation {
- return {
- targetId,
- type,
- name,
- avatar,
- lastContent: '',
- lastSendTime: 0,
- unreadCount: 0,
- deleted: false,
- top: false,
- silent,
- atMe: false,
- atAll: false
- }
- },
-
- /** 璁剧疆缃《 */
- setConversationTop(type: number, targetId: number, top: boolean) {
- const conversation = this.getConversation(type, targetId)
- if (!conversation) {
- return
- }
- conversation.top = top
- this.saveConversation(conversation)
- },
-
- /** 璁剧疆鍏嶆墦鎵� */
- setConversationSilent(type: number, targetId: number, silent: boolean) {
- const conversation = this.getConversation(type, targetId)
- if (!conversation) {
- return
- }
- conversation.silent = silent
- this.saveConversation(conversation)
- },
-
- /** 鍒犻櫎浼氳瘽 */
- removeConversation(type: number, targetId: number) {
- // 1. 鏍囪浼氳瘽鍒犻櫎
- const conversation = this.getConversation(type, targetId)
- if (!conversation) {
- return
- }
- if (this.activeConversation === conversation) {
- this.activeConversation = null
- }
- conversation.deleted = true
- // 2. 鍒犻櫎浼氳瘽鍏宠仈鐨勬秷鎭拰鑽夌
- useMessageStore().deleteConversationMessageList(type, targetId)
- this.clearConversationDraft(conversation)
- this.saveConversation(conversation)
- },
-
- /** 鍒犻櫎绉佽亰浼氳瘽 */
- removePrivateConversation(friendId: number) {
- this.removeConversation(ImConversationType.PRIVATE, friendId)
- },
-
- /** 鍒犻櫎缇よ亰浼氳瘽 */
- removeGroupConversation(groupId: number) {
- this.removeConversation(ImConversationType.GROUP, groupId)
- },
-
- /** 鏍囪浼氳瘽宸茶 */
- markConversationRead(type: number, targetId: number, messageId?: number): void {
- const conversation = this.getConversation(type, targetId)
- if (!conversation) {
- return
- }
- const key = getClientConversationId(type, targetId)
- const current = this.conversationReads[key]
- const readMessageIdAdvanced = !!messageId && messageId > (current?.messageId || 0)
- if (
- conversation.unreadCount === 0 &&
- !conversation.atMe &&
- !conversation.atAll &&
- !readMessageIdAdvanced
- ) {
- return
- }
- conversation.unreadCount = 0
- conversation.atMe = false
- conversation.atAll = false
- if (readMessageIdAdvanced) {
- const record = createConversationRead(type, targetId, messageId)
- this.conversationReads[key] = record
- void getDb()
- .transaction(['conversations', 'conversationReads'], 'readwrite', async (tx) => {
- await this.saveConversationRecord(conversation, tx)
- await this.saveConversationReadRecord(record, tx)
- })
- .catch((error) =>
- console.warn(
- '[IM conversationStore] 浼氳瘽宸茶鍐欏叆澶辫触',
- {
- conversationType: type,
- targetId,
- messageId,
- conversationKey: key
- },
- error
- )
- )
- return
- }
- this.saveConversation(conversation)
- },
-
- /** 鏍囪浼氳瘽宸蹭笂鎶ユ湇鍔$璇讳綅缃� */
- markConversationReadReported(type: number, targetId: number, messageId?: number): void {
- if (!messageId) {
- return
- }
- const conversation = this.getConversation(type, targetId)
- if (!conversation || messageId <= (conversation.reportedReadMessageId || 0)) {
- return
- }
- conversation.reportedReadMessageId = messageId
- this.saveConversation(conversation)
- },
-
- // ==================== 鏈�杩戣浆鍙� ====================
-
- /** 鎺ㄩ�佹渶杩戣浆鍙戜細璇� */
- pushRecentForwardConversationKeyList(keys: string[]) {
- if (!keys || keys.length === 0) {
- return
- }
- const merged = [...keys, ...this.recentForwardConversationKeys]
- this.recentForwardConversationKeys = [...new Set(merged)].slice(
- 0,
- CONVERSATION_RECENT_FORWARD_MAX
- )
- this.saveRecentForwardConversationKeyList()
- },
-
- /** 绉婚櫎鏈�杩戣浆鍙戜細璇� */
- removeRecentForwardConversationKey(key: string) {
- const index = this.recentForwardConversationKeys.indexOf(key)
- if (index === -1) {
- return
- }
- this.recentForwardConversationKeys.splice(index, 1)
- this.saveRecentForwardConversationKeyList()
- },
-
- /** 淇濆瓨鏈�杩戣浆鍙戜細璇� */
- saveRecentForwardConversationKeyList() {
- void getDb()
- .setSetting(
- StorageKeys.settings.recentForwardConversationKeys,
- this.recentForwardConversationKeys.slice(0, CONVERSATION_RECENT_FORWARD_MAX)
- )
- .catch((error) => console.warn('[IM conversationStore] 鏈�杩戣浆鍙戝垪琛ㄥ啓鍏ュけ璐�', error))
- },
-
- // ==================== 浼氳瘽缁存姢 ====================
-
- /** 閲嶆帓浼氳瘽 */
- sortConversationList() {
- this.conversations.sort((a, b) => (b.lastSendTime || 0) - (a.lastSendTime || 0))
- this.saveConversationList(this.conversations)
- },
-
- /** 鍚屾浼氳瘽灞曠ず鍏冩暟鎹� */
- updateConversation(
- type: number,
- targetId: number,
- info: { avatar?: string; name?: string; silent?: boolean }
- ) {
- const conversation = this.getConversation(type, targetId)
- if (!conversation) {
- return
- }
- let changed = false
- if (info.name && conversation.name !== info.name) {
- conversation.name = info.name
- changed = true
- }
- if (info.avatar !== undefined && conversation.avatar !== info.avatar) {
- conversation.avatar = info.avatar || ''
- changed = true
- }
- if (info.silent !== undefined && conversation.silent !== info.silent) {
- conversation.silent = info.silent
- changed = true
- }
- if (changed) {
- this.saveConversation(conversation)
- }
- },
-
- // ==================== 鑽夌 ====================
-
- /** 鑾峰彇鑽夌 */
- getConversationDraft(conversation: { targetId: number; type: number; }): Conversation['draft'] | undefined {
- return this.getConversation(conversation.type, conversation.targetId)?.draft
- },
-
- /** 璁剧疆鑽夌 */
- setConversationDraft(
- conversation: { targetId: number; type: number; },
- snapshot: NonNullable<Conversation['draft']>
- ): void {
- if (!snapshot.plain.trim() && !snapshot.reply) {
- this.clearConversationDraft(conversation)
- return
- }
- const target = this.getConversation(conversation.type, conversation.targetId)
- if (!target) {
- return
- }
- target.draft = snapshot
- this.scheduleConversationDraftSave(target)
- },
-
- /** 娓呴櫎鑽夌 */
- clearConversationDraft(conversation: { targetId: number; type: number; }): void {
- const target = this.getConversation(conversation.type, conversation.targetId)
- if (!target?.draft) {
- return
- }
- target.draft = undefined
- this.scheduleConversationDraftSave(target)
- },
-
- /** 璁剧疆鍥炲鑽夌 */
- setConversationReplyDraft(
- conversation: { targetId: number; type: number; },
- quote: NonNullable<Conversation['draft']>['reply']
- ) {
- if (!quote) {
- return
- }
- const existing = this.getConversationDraft(conversation)
- this.setConversationDraft(conversation, {
- html: existing?.html ?? '',
- plain: existing?.plain ?? '',
- reply: quote
- })
- },
-
- /** 娓呴櫎鍥炲鑽夌 */
- clearConversationReplyDraft(conversation: { targetId: number; type: number; }): void {
- const existing = this.getConversationDraft(conversation)
- if (!existing?.reply) {
- return
- }
- this.setConversationDraft(conversation, { ...existing, reply: undefined })
- },
-
- /** 璋冨害鑽夌淇濆瓨 */
- scheduleConversationDraftSave(conversation: Conversation): void {
- pendingDraftConversations.add(conversation)
- saveDraftConversationListDebounced()
- },
-
- /** 绔嬪嵆淇濆瓨鑽夌 */
- flushConversationDraftSave(): void {
- saveDraftConversationListDebounced.flush()
- }
- }
-})
-
-export const useConversationStoreWithOut = () => useConversationStore()
-
-/** 鍚堝苟鑽夌鍐欏叆 */
-const saveDraftConversationListDebounced = createDraftDebounce(() => {
- const conversations = [...pendingDraftConversations]
- pendingDraftConversations.clear()
- if (conversations.length === 0) {
- return
- }
- void useConversationStoreWithOut()
- .saveConversationRecord(conversations)
- .catch((error) => console.warn('[IM conversationStore] 鑽夌鍐欏叆澶辫触', error))
-}, PERSIST_DRAFT_DEBOUNCE_MS)
-
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useConversationStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/faceStore.ts b/src/views/im/home/store/faceStore.ts
deleted file mode 100644
index 8122443..0000000
--- a/src/views/im/home/store/faceStore.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import type { ImFacePackApi } from '#/api/im/face/pack'
-import type { ImFaceUserItemApi } from '#/api/im/face/userItem'
-
-import { ref } from 'vue'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { getFacePackList as apiGetFacePackList } from '#/api/im/face/pack'
-import { createFaceUserItem as apiCreateFaceUserItem, deleteFaceUserItem as apiDeleteFaceUserItem, getFaceUserItemList as apiGetFaceUserItemList } from '#/api/im/face/userItem'
-
-/**
- * IM 琛ㄦ儏闈㈡澘鏁版嵁 store锛堢郴缁熻〃鎯呭寘 + 涓汉琛ㄦ儏锛�
- *
- * 涓嶆寔涔呭寲锛氭暟鎹皬銆佷綆棰戯紱姣忔杩� IM 绗竴娆℃墦寮�琛ㄦ儏闈㈡澘鏃舵寜闇�鎷夛紝鍏� tab 鍗充涪寮�
- * - 绯荤粺鍖咃細IM 涓婚〉 onMounted 鍚庡彴棰勬媺锛堜笉闃诲棣栧睆锛夛紝娑堥櫎闈㈡澘棣栨灞曞紑鐨勭櫧灞�
- * - 涓汉琛ㄦ儏锛氬垏鍒般�屾敹钘忋�峵ab / 闀挎寜娑堟伅銆屾坊鍔犲埌琛ㄦ儏銆嶆椂鎸夐渶鎷�
- */
-export const useFaceStore = defineStore('imFace', () => {
-
- /** 绯荤粺琛ㄦ儏鍖呭垪琛紙鍚瘡涓寘鐨� items锛夛紱杩愯惀绠$悊鍚庡彴缁存姢 */
- const facePacks = ref<ImFacePackApi.FacePackUser[]>([])
- /** 涓汉琛ㄦ儏鍖呭垪琛紙鐢ㄦ埛闀挎寜銆屾坊鍔犲埌琛ㄦ儏銆�/ 涓婁紶浜х敓锛� */
- const faceUserItems = ref<ImFaceUserItemApi.FaceUserItem[]>([])
-
- /** clear() 鏃堕�掑锛涙棫璐﹀彿璇锋眰杩斿洖鍚庝笉鍐欏叆鏂拌处鍙峰唴瀛� */
- let storeEpoch = 0
-
- /**
- * 绯荤粺琛ㄦ儏鍖呮媺鍙� promise锛沞nsureFacePackList 鍐� cache锛�
- * - null = 杩樻病鎷夎繃锛屼笅娆¤皟鐢ㄧ湡鍙戣姹�
- * - resolve 鍚庝繚鐣欏璞� = 鍚庣画璋冪敤 await 绔嬪嵆杩斿洖锛屼笉鍐嶅彂璇锋眰
- * - reject 鍚庣疆鍥� null锛岃璋冪敤鏂逛笅娆¢噸璇�
- */
- let facePacksPromise: null | Promise<void> = null
- /** 鎸夐渶鎷夊彇绯荤粺琛ㄦ儏鍖咃紙宸叉媺杩囧垯鐩存帴澶嶇敤 cached promise锛� */
- async function ensureFacePackList(): Promise<void> {
- if (!facePacksPromise) {
- const requestEpoch = storeEpoch
- facePacksPromise = apiGetFacePackList()
- .then((data) => {
- if (requestEpoch !== storeEpoch) {
- return
- }
- facePacks.value = data || []
- })
- .catch((error) => {
- console.warn('[IM] 鎷夊彇琛ㄦ儏鍖呭け璐�', error)
- if (requestEpoch === storeEpoch) {
- facePacksPromise = null
- }
- throw error
- })
- }
- return facePacksPromise
- }
-
- /** 涓汉琛ㄦ儏鎷夊彇 promise锛涜涔夊悓涓� */
- let faceUserItemsPromise: null | Promise<void> = null
- /** 鎸夐渶鎷夊彇涓汉琛ㄦ儏锛堝凡鎷夎繃鍒欑洿鎺ュ鐢� cached promise锛� */
- async function ensureFaceUserItemList(): Promise<void> {
- if (!faceUserItemsPromise) {
- const requestEpoch = storeEpoch
- faceUserItemsPromise = apiGetFaceUserItemList()
- .then((data) => {
- if (requestEpoch !== storeEpoch) {
- return
- }
- faceUserItems.value = data || []
- })
- .catch((error) => {
- console.warn('[IM] 鎷夊彇涓汉琛ㄦ儏澶辫触', error)
- if (requestEpoch === storeEpoch) {
- faceUserItemsPromise = null
- }
- throw error
- })
- }
- return faceUserItemsPromise
- }
-
- /**
- * 娣诲姞涓汉琛ㄦ儏锛涙湇鍔$瀵瑰悓 URL 鎶� FACE_USER_ITEM_DUPLICATED 閿欒
- *
- * 鏉ユ簮锛�1. 鐢ㄦ埛鍦ㄨ〃鎯呴潰鏉裤��+銆嶄笂浼犲浘鐗� 2. 闀挎寜娑堟伅銆屾坊鍔犲埌琛ㄦ儏銆�
- */
- async function addFaceUserItem(reqVO: ImFaceUserItemApi.FaceUserItemSaveReqVO): Promise<boolean> {
- const requestEpoch = storeEpoch
- const id = await apiCreateFaceUserItem(reqVO)
- if (!id) {
- return false
- }
- // 宸插垏璐﹀彿鏃惰烦杩囨棫璇锋眰缁撴灉
- if (requestEpoch !== storeEpoch) {
- return false
- }
- // id 涓嶅湪缂撳瓨閲屾墠鎻掑叆锛涙湇鍔$鍞竴绾︽潫鍏滃簳浜� race锛屾湰鍦扮悊璁轰笂涓嶄細鎷垮埌閲嶅 id
- if (!faceUserItems.value.some((item) => item.id === id)) {
- faceUserItems.value.unshift({
- id,
- url: reqVO.url,
- name: reqVO.name,
- width: reqVO.width,
- height: reqVO.height
- })
- }
- return true
- }
-
- /** 鍒犻櫎涓汉琛ㄦ儏锛涙湰鍦扮珛鍗崇Щ闄� */
- async function removeFaceUserItem(id: number): Promise<boolean> {
- const requestEpoch = storeEpoch
- try {
- await apiDeleteFaceUserItem(id)
- // 宸插垏璐﹀彿鏃惰烦杩囨棫璇锋眰缁撴灉
- if (requestEpoch !== storeEpoch) {
- return false
- }
- faceUserItems.value = faceUserItems.value.filter((item) => item.id !== id)
- return true
- } catch (error) {
- console.warn('[IM] 鍒犻櫎涓汉琛ㄦ儏澶辫触', { id }, error)
- return false
- }
- }
-
- /** 娓呯┖琛ㄦ儏缂撳瓨 */
- function clear(): void {
- facePacks.value = []
- faceUserItems.value = []
- facePacksPromise = null
- faceUserItemsPromise = null
- storeEpoch++
- }
-
- return {
- facePacks,
- faceUserItems,
- ensureFacePackList,
- ensureFaceUserItemList,
- addFaceUserItem,
- removeFaceUserItem,
- clear
- }
-})
-
-/** 鍦� setup 澶栵紙璺敱瀹堝崼绛夛級鍙� store 瀹炰緥鐨勫伐鍏锋柟娉� */
-export const useFaceStoreWithOut = () => useFaceStore()
-
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useFaceStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/friendStore.ts b/src/views/im/home/store/friendStore.ts
deleted file mode 100644
index eeaebf9..0000000
--- a/src/views/im/home/store/friendStore.ts
+++ /dev/null
@@ -1,849 +0,0 @@
-import type { Friend, FriendLite, FriendRequest } from '../types'
-
-import type { ImFriendApi } from '#/api/im/friend'
-import type { ImFriendRequestApi } from '#/api/im/friend/request'
-
-import { CommonStatusEnum } from '../../../../packages/constants/src'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { blockFriend as apiBlockFriend, deleteFriend as apiDeleteFriend, getFriend as apiGetFriend, getMyFriendList as apiGetMyFriendList, pullMyFriendList as apiPullMyFriendList, unblockFriend as apiUnblockFriend, updateFriend as apiUpdateFriend } from '#/api/im/friend'
-import { agreeFriendRequest as apiAgreeFriendRequest, applyFriendRequest as apiApplyFriendRequest, getMyFriendRequest as apiGetMyFriendRequest, getMyFriendRequestList as apiGetMyFriendRequestList, pullMyFriendRequestList as apiPullMyFriendRequestList, refuseFriendRequest as apiRefuseFriendRequest } from '#/api/im/friend/request'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { FRIEND_REQUEST_PAGE_SIZE } from '../../utils/config'
-import { ImConversationType, ImFriendRequestHandleResult } from '../../utils/constants'
-import { getDb, StorageKeys } from '../../utils/db'
-import { runIncrementalPull } from '../../utils/pull'
-import { getFriendDisplayName } from '../../utils/user'
-import { useConversationStore } from './conversationStore'
-
-type PendingRequest = { epoch: number; promise: Promise<void>; userId: number; }
-
-/** 褰撳墠姝e湪杩涜鐨勫ソ鍙嬪垪琛ㄦ媺鍙栵紱澶� dispatcher 鍚屾椂瑙﹀彂鏃跺鐢ㄥ悓涓� Promise锛岄伩鍏嶉洩宕╅噸鎷� */
-let pendingFetchFriends: null | PendingRequest = null
-/** 褰撳墠姝e湪杩涜鐨勫ソ鍙嬬敵璇峰垪琛ㄦ媺鍙栵紱澶氱杩炵画澶氭潯鐢宠鍒拌揪鏃跺鐢ㄥ悓涓� Promise锛岄伩鍏嶉洩宕╅噸鎷� */
-let pendingFetchRequests: null | PendingRequest = null
-/** 褰撳墠姝e湪杩涜鐨勩�屽姞杞芥洿澶氱敵璇枫�嶈姹� */
-let pendingLoadMoreRequests: null | PendingRequest = null
-/** 褰撳墠姝e湪杩涜鐨勫ソ鍙嬭鎯呰姹� */
-const pendingFetchFriendInfos = new Map<string, Promise<void>>()
-
-/** clear() 鏃堕�掑锛涙棫璐﹀彿閭f杩樻病杩斿洖鐨勮姹� resolve 鍚庢瘮瀵逛竴鑷存墠鍐� store锛岄槻璺ㄨ处鍙锋暟鎹硠婕� */
-let storeEpoch = 0
-
-/** 鏋勫缓濂藉弸璇︽儏璇锋眰鍘婚噸 key */
-function getPendingFriendInfoKey(userId: number, friendUserId: number): string {
- return `${userId}:${friendUserId}`
-}
-
-/** 濂藉弸閫氱煡 payload锛堝榻愬悗绔� BaseFriendNotification + 瀛愮被瑁佸噺鍚庣殑瀛楁锛� */
-export interface FriendNotificationPayload {
- operatorUserId: number
- friendUserId: number
- // FRIEND_REQUEST_* 绯诲垪锛氱敵璇疯褰曠殑鏍稿績瀛楁锛堥伩鍏� payload 鎼哄甫瀹屾暣 DO锛�
- requestId?: number
- applyContent?: string
- handleContent?: string
- addSource?: number
- // FRIEND_REQUEST_RECEIVED锛氱敵璇锋柟鑱氬悎瀛楁锛屼緵鍓嶇鐩存帹 push 杩涘垪琛紝鏃犻渶鍥炴媺
- fromNickname?: string
- fromAvatar?: string
- // FRIEND_UPDATE锛氬崟杈瑰睘鎬у彉鏇�
- displayName?: string
- silent?: boolean
- pinned?: boolean
- // FRIEND_DELETE锛氭槸鍚︾骇鑱旀竻鐞嗘湰绔浉鍏虫暟鎹紙濡傜鑱婁細璇濓級
- clear?: boolean
-}
-
-/**
- * IM 濂藉弸 Store
- *
- * 璐熻矗锛�
- * - 鎷夊彇 / 缂撳瓨褰撳墠鐧诲綍鐢ㄦ埛鐨勫ソ鍙嬪垪琛� + 鐢宠鍒楄〃
- * - 鐢宠-瀹℃壒娴佺▼锛坅pply / agree / refuse锛�+ 澶囨敞 / 鍏嶆墦鎵� / 鑱旂郴浜虹疆椤� / 鎷夐粦
- * - 鎺ユ敹 WebSocket 1201-1210 娈典綅閫氱煡锛屾寜浜嬩欢鍒嗗彂鍒� friendStore 鍐呴儴鍚� dispatcher
- */
-export const useFriendStore = defineStore('imFriendStore', {
- state: () => ({
- friends: [] as Friend[],
- // 浠� fetchFriendList 鎴愬姛鍚庣疆浣嶏紱loadFriendData锛圛DB锛変笉缃綅锛屽惁鍒欏悗鍙� SWR 鍒锋柊浼氳缂撳瓨鍛戒腑璺宠繃
- loaded: false,
- /** 鎴戠浉鍏崇殑濂藉弸鐢宠鍒楄〃锛堝惈鎴戝彂璧风殑 + 鍒汉鍔犳垜鐨勶紱鍚庣鎸� id 鍊掑簭娓告爣鍒嗛〉锛� */
- friendRequests: [] as FriendRequest[],
- /** 鏄惁杩樻湁鏇存棭鐨勭敵璇疯褰曞彲鍔犺浇锛涜繑鍥炰笉婊� page size 鍗崇疆 false */
- hasMoreFriendRequests: true
- }),
-
- getters: {
- /**
- * friendUserId 鈫� Friend 鐨� O(1) 绱㈠紩锛涗粠 friends 鏁扮粍娲剧敓锛孭inia 鍦� friends 鍙樺寲鏃惰嚜鍔ㄩ噸绠�
- *
- * 娑堟伅娓叉煋闇�瑕佹寜 senderId 鍙嶆煡鍙戦�佷汉澶村儚 / 澶囨敞锛屾瘡鏉℃秷鎭覆鏌撻兘浼� getFriend锛�
- * 鐩存帴 find 鏃� N 鏉℃秷鎭� 脳 M 濂藉弸 = O(N脳M)锛涘缓绱㈠紩鍚庡崟娆¤ O(1)锛岄噸寤哄彧鍦ㄥ啓濂藉弸锛坒etchFriendList / upsertFriend 绛夛級鏃跺彂鐢�
- */
- getFriendMap: (state): Map<number, Friend> => {
- const map = new Map<number, Friend>()
- for (const friend of state.friends) {
- map.set(friend.friendUserId, friend)
- }
- return map
- },
- /** 鎸� friendUserId 鎵惧ソ鍙嬶紙鍚凡杞垹鐨� DISABLE 璁板綍锛岃皟鐢ㄦ柟鑷鍒ゅ畾锛� */
- getFriend(): (friendUserId: number) => Friend | undefined {
- return (friendUserId: number) => this.getFriendMap.get(friendUserId)
- },
- /** 褰撳墠鐢熸晥鐨勫ソ鍙嬪垪琛紙杩囨护鎺� DISABLE 杞垹璁板綍锛� */
- getActiveFriendList: (state): Friend[] => {
- return state.friends.filter((friend) => friend.status !== CommonStatusEnum.DISABLE)
- },
- /** 褰撳墠鐢熸晥濂藉弸鐨� Lite 瑙嗗浘锛圥ickerPanel / 閫変汉寮圭獥鍏辩敤锛岃嚜甯︽嫾闊冲瓧娈典緵鍒嗘《 / 鎼滅储锛� */
- getActiveFriendLiteList(): FriendLite[] {
- return this.getActiveFriendList.map((friend: Friend) => ({
- id: friend.friendUserId,
- nickname: friend.nickname,
- nicknamePinyin: friend.nicknamePinyin,
- avatar: friend.avatar,
- displayName: friend.displayName,
- displayNamePinyin: friend.displayNamePinyin
- }))
- },
- /** 鍒ゆ柇瀵规柟鏄惁鏄綋鍓嶇敤鎴风殑鏈夋晥濂藉弸锛堝瓨鍦� + 闈� DISABLE锛� */
- isActiveFriend() {
- return (friendUserId: number): boolean => {
- const entry = this.getFriend(friendUserId)
- return !!entry && entry.status !== CommonStatusEnum.DISABLE
- }
- },
- /** 鎴戠殑榛戝悕鍗曪紙blocked=true 涓� ENABLE锛� */
- getBlockedFriendList: (state): Friend[] => {
- return state.friends.filter(
- (friend) => friend.status !== CommonStatusEnum.DISABLE && friend.blocked === true
- )
- },
- /** 鏈鐞嗙敵璇锋暟锛堟帴鏀舵柟=鎴戯級鈥斺�� 瀹炴椂娲剧敓锛屻�屾柊鐨勬湅鍙嬨�嶇孩鐐圭敤 */
- getUnhandledRequestCount: (state): number => {
- const currentUserId = getCurrentUserId()
- return state.friendRequests.filter(
- (request) =>
- request.handleResult === ImFriendRequestHandleResult.UNHANDLED &&
- request.toUserId === currentUserId
- ).length
- }
- },
-
- actions: {
- // ==================== 鏈湴缂撳瓨 ====================
-
- /** 浠� IndexedDB 鎭㈠濂藉弸鍜屽ソ鍙嬬敵璇� */
- async loadFriendData(): Promise<boolean> {
- try {
- const [friends, friendRequests] = await Promise.all([
- getDb().getAll<Friend>('friends'),
- getDb().getAll<FriendRequest>('friendRequests')
- ])
- if (friends.length > 0) {
- this.friends = friends
- }
- if (friendRequests.length > 0) {
- this.friendRequests = friendRequests.toSorted(
- (requestA, requestB) => requestB.id - requestA.id
- )
- this.hasMoreFriendRequests = friendRequests.length >= FRIEND_REQUEST_PAGE_SIZE
- }
- return friends.length > 0
- } catch (error) {
- console.warn('[IM friendStore] 鏈湴濂藉弸缂撳瓨璇诲彇澶辫触', error)
- return false
- }
- },
-
- /** 淇濆瓨濂藉弸鍒楄〃 */
- saveFriendList(): void {
- void getDb()
- .transaction(['friends'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.clearStore('friends', tx)
- for (const friend of this.friends) {
- if (friend.id) {
- await db.put('friends', friend, tx)
- }
- }
- })
- .catch((error) => console.warn('[IM friendStore] 鏈湴濂藉弸缂撳瓨鍐欏叆澶辫触', error))
- },
-
- /** 淇濆瓨鍗曚釜濂藉弸 */
- async saveFriendRecord(friend: Friend | undefined): Promise<void> {
- if (!friend?.id) {
- return
- }
- await getDb().put('friends', friend)
- },
-
- /** 淇濆瓨鍗曚釜濂藉弸 */
- saveFriend(friend: Friend | undefined): void {
- void this.saveFriendRecord(friend).catch((error) =>
- console.warn('[IM friendStore] 鏈湴濂藉弸鍐欏叆澶辫触', error)
- )
- },
-
- /** 淇濆瓨濂藉弸鐢宠鍒楄〃 */
- saveFriendRequestList(): void {
- void getDb()
- .transaction(['friendRequests'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.clearStore('friendRequests', tx)
- for (const request of this.friendRequests) {
- await db.put('friendRequests', request, tx)
- }
- })
- .catch((error) => console.warn('[IM friendStore] 鏈湴濂藉弸鐢宠缂撳瓨鍐欏叆澶辫触', error))
- },
-
- /** 淇濆瓨鍗曟潯濂藉弸鐢宠 */
- async saveFriendRequestRecord(request: FriendRequest | undefined): Promise<void> {
- if (!request) {
- return
- }
- await getDb().put('friendRequests', request)
- },
-
- /** 淇濆瓨鍗曟潯濂藉弸鐢宠 */
- saveFriendRequest(request: FriendRequest | undefined): void {
- void this.saveFriendRequestRecord(request).catch((error) =>
- console.warn('[IM friendStore] 鏈湴濂藉弸鐢宠鍐欏叆澶辫触', error)
- )
- },
-
- // ==================== 杩滅鎷夊彇 ====================
-
- /** 浠庡悗绔媺鍙栧苟瑕嗙洊鏈湴鍒楄〃锛堝惈 DISABLE 鍘嗗彶濂藉弸缁欏凡鍒犲璇濆厹搴曪級锛涘彧鍚屾 ENABLE 鐨勪細璇濅俊鎭紝DISABLE 鐨勪笉鍔� 鈥斺�� cascade 娓呬細璇濈敱 WS dispatcher 鎸� payload.clear 澶勭悊锛岄伩鍏� fetchFriendList 瑕嗙洊鐢ㄦ埛銆屼笉娓呯┖鑱婂ぉ璁板綍銆嶇殑閫夋嫨 */
- async fetchFriendList(force = false) {
- if (this.loaded && !force) {
- return
- }
- // 蹇収 epoch锛沜lear() 涔嬪悗鍒� .then 涔嬮棿瑙﹀彂鐨� epoch++ 琛ㄧず璐﹀彿宸插垏锛屾棫缁撴灉涓嶈兘鍐欏叆鏂� store
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- if (
- pendingFetchFriends?.epoch === requestEpoch &&
- pendingFetchFriends.userId === requestUserId
- ) {
- return pendingFetchFriends.promise
- }
- const promise = apiGetMyFriendList()
- .then((list) => {
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.friends = (list || []).map((friend) => convertFriend(friend))
- this.loaded = true
- const conversationStore = useConversationStore()
- for (const friend of this.friends) {
- if (friend.status === CommonStatusEnum.DISABLE) {
- continue
- }
- conversationStore.updateConversation(ImConversationType.PRIVATE, friend.friendUserId, {
- name: getFriendDisplayName(friend),
- avatar: friend.avatar,
- silent: friend.silent
- })
- }
- this.saveFriendList()
- })
- .finally(() => {
- if (
- pendingFetchFriends?.epoch === requestEpoch &&
- pendingFetchFriends.userId === requestUserId
- ) {
- pendingFetchFriends = null
- }
- })
- pendingFetchFriends = { epoch: requestEpoch, userId: requestUserId, promise }
- return promise
- },
-
- /**
- * 澧為噺鎷夊彇濂藉弸鍙樻洿骞跺悎骞讹細杩涘叆 IM 棣栧睆涓绘暟鎹仮澶嶏紙绌烘父鏍� = 棣栨鍏ㄩ噺澧為噺鎷夛級+ 閲嶈繛 / 绂荤嚎琛ュ伩
- *
- * 鍚凡鍒犻櫎濂藉弸锛屾寜 status 璧� upsert
- */
- async pullFriends() {
- // 蹇収 epoch锛涜处鍙峰湪鎷夊彇閫斾腑鍒囨崲锛坈lear() 鈫� epoch++锛夋椂涓㈠純鏃ц处鍙烽偅鍑犻〉缁撴灉锛岄槻璺ㄨ处鍙锋暟鎹硠婕�
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const isActive = () => requestEpoch === storeEpoch && getCurrentUserId() === requestUserId
- await runIncrementalPull(
- StorageKeys.settings.friendPullCursor,
- apiPullMyFriendList,
- async (records) => {
- if (!isActive()) {
- return false
- }
- await Promise.all(records.map((vo) => this.upsertFriendForPull(convertFriend(vo))))
- return true
- },
- isActive
- )
- // 缃� loaded锛屼緵閫氳褰曢〉 fetchFriendList(force=false) 澶嶇敤缂撳瓨鑰岄潪閲嶅鍏ㄩ噺鎷�
- if (isActive()) {
- this.loaded = true
- }
- },
-
- /** 鎸� friendUserId 鑾峰彇璇︽儏骞跺悎骞跺埌鏈湴锛堜繚璇� nickname / avatar 鏈�鏂帮級 */
- async fetchFriendInfo(friendUserId: number) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- if (!requestUserId) {
- return
- }
- const key = getPendingFriendInfoKey(requestUserId, friendUserId)
- const inflight = pendingFetchFriendInfos.get(key)
- if (inflight) {
- return inflight
- }
- const promise = (async () => {
- try {
- const data = await apiGetFriend(friendUserId)
- if (!data) {
- return
- }
- // clear() 宸插垏璐﹀彿锛氭棫璇锋眰鐨勫ソ鍙嬭鎯呬笉鑳藉啀 upsert 杩涙柊璐﹀彿鐨� friends
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.upsertFriend(convertFriend(data))
- } catch (error) {
- console.warn('[IM friendStore] fetchFriendInfo 澶辫触', error)
- }
- })().finally(() => {
- if (pendingFetchFriendInfos.get(key) === promise) {
- pendingFetchFriendInfos.delete(key)
- }
- })
- pendingFetchFriendInfos.set(key, promise)
- return promise
- },
-
- // ==================== 鐢宠-瀹℃壒 ====================
-
- /** 鍙戣捣濂藉弸鐢宠锛氭垚鍔熷悗绛夊緟瀵规柟鍚屾剰锛堜笉鐩存帴钀藉湴涓哄ソ鍙嬶級 */
- async applyFriendRequest(reqVO: ImFriendRequestApi.FriendRequestApplyReqVO): Promise<null | number> {
- return await apiApplyFriendRequest(reqVO)
- },
-
- /** 鍚屾剰涓�鏉″ソ鍙嬬敵璇凤紱鍚庣浼氬弻鍚戣惤搴� + 鎺� FRIEND_ADD锛屾湰绔瓑閫氱煡鍒拌揪鍐� upsertFriend */
- async agreeFriendRequest(requestId: number) {
- await apiAgreeFriendRequest(requestId)
- await this.applyHandleResult(requestId, ImFriendRequestHandleResult.AGREED)
- },
-
- /** 鎷掔粷涓�鏉″ソ鍙嬬敵璇� */
- async refuseFriendRequest(requestId: number, handleContent?: string) {
- await apiRefuseFriendRequest(requestId, handleContent)
- await this.applyHandleResult(requestId, ImFriendRequestHandleResult.REFUSED, handleContent)
- },
-
- /** 鎶� handleResult 搴旂敤鍒版湰鍦扮敵璇疯褰曪紱鎵句笉鍒板氨鎸� id 鍗曟煡鍏滃簳 upsert锛岄伩鍏嶇牬鍧� id 鍊掑簭 */
- async applyHandleResult(
- requestId: number,
- result: number,
- handleContent?: string
- ): Promise<void> {
- const request = this.getFriendRequest(requestId)
- if (request) {
- request.handleResult = result
- if (handleContent !== undefined) {
- request.handleContent = handleContent
- }
- request.handleTime = Date.now()
- this.saveFriendRequest(request)
- return
- }
- await this.fetchFriendRequest(requestId)
- },
-
- /** 鎷夊彇銆屾垜鐩稿叧銆嶇殑濂藉弸鐢宠鍒楄〃棣栭〉锛堥〉闈㈡墦寮� / 鏀跺埌 FRIEND_REQUEST_RECEIVED 鏃跺埛鏂帮級锛沺ending 鏈熼棿澶嶇敤鍚屼竴 Promise */
- async fetchFriendRequestList() {
- if (pendingFetchRequests) {
- const currentUserId = getCurrentUserId()
- if (
- pendingFetchRequests.epoch === storeEpoch &&
- pendingFetchRequests.userId === currentUserId
- ) {
- return pendingFetchRequests.promise
- }
- }
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const promise = apiGetMyFriendRequestList(FRIEND_REQUEST_PAGE_SIZE)
- .then((list) => {
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const items = (list || []).map((request) => convertFriendRequest(request))
- this.friendRequests = items
- // 涓嶈冻涓�椤靛嵆娌℃湁鏇村锛涙弧椤靛彲鑳借繕鏈夛紝绛� loadMore 鎷夊埌 0 鏉″啀纭畾
- this.hasMoreFriendRequests = items.length >= FRIEND_REQUEST_PAGE_SIZE
- this.saveFriendRequestList()
- })
- .finally(() => {
- if (
- pendingFetchRequests?.epoch === requestEpoch &&
- pendingFetchRequests.userId === requestUserId
- ) {
- pendingFetchRequests = null
- }
- })
- pendingFetchRequests = { epoch: requestEpoch, userId: requestUserId, promise }
- return promise
- },
-
- /** 鍔犺浇鏇村鐢宠锛堟寜鏈湴鏈�鏃� requestId 娓告爣鍒嗛〉锛夛紱鏃犳洿澶� / pending 涓洿鎺ヨ繑鍥� */
- async loadMoreFriendRequestList() {
- const requestUserId = getCurrentUserId()
- const hasSameFetchPending =
- pendingFetchRequests?.epoch === storeEpoch && pendingFetchRequests.userId === requestUserId
- if (!this.hasMoreFriendRequests || hasSameFetchPending) {
- return
- }
- if (
- pendingLoadMoreRequests?.epoch === storeEpoch &&
- pendingLoadMoreRequests.userId === requestUserId
- ) {
- return pendingLoadMoreRequests.promise
- }
- const oldest = this.friendRequests[this.friendRequests.length - 1]
- if (!oldest) {
- return this.fetchFriendRequestList()
- }
- const requestEpoch = storeEpoch
- const promise = apiGetMyFriendRequestList(FRIEND_REQUEST_PAGE_SIZE, oldest.id)
- .then((list) => {
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const items = (list || []).map((request) => convertFriendRequest(request))
- this.friendRequests.push(...items)
- this.hasMoreFriendRequests = items.length >= FRIEND_REQUEST_PAGE_SIZE
- this.saveFriendRequestList()
- })
- .finally(() => {
- if (
- pendingLoadMoreRequests?.epoch === requestEpoch &&
- pendingLoadMoreRequests.userId === requestUserId
- ) {
- pendingLoadMoreRequests = null
- }
- })
- pendingLoadMoreRequests = { epoch: requestEpoch, userId: requestUserId, promise }
- return promise
- },
-
- /** 鎸� id 鏌ョ敵璇疯褰曪紱鍒楄〃鏄寜 id 鍊掑簭鐨勫皬鍒楄〃锛孫(n) find 鍗冲彲锛屼笉鍐嶇淮鎶� Map 绱㈠紩 */
- getFriendRequest(requestId: number): FriendRequest | undefined {
- return this.friendRequests.find((request) => request.id === requestId)
- },
-
- /** 鎸� id 浠庡悗绔崟鏌ュ苟 upsert 鍒版湰鍦帮紙dispatcher 鍏滃簳鐢紝閬垮厤鍏ㄩ噺閲嶆媺锛夛紱鍚庣甯﹁秺鏉冭繃婊� */
- async fetchFriendRequest(requestId: number) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const data = await apiGetMyFriendRequest(requestId)
- if (!data) {
- return
- }
- // clear() 宸插垏璐﹀彿锛氭棫璇锋眰鐨勭敵璇疯褰曚笉鑳藉啀鍐欒繘鏂拌处鍙风殑 friendRequests
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.upsertFriendRequest(convertFriendRequest(data))
- },
-
- /** 鍚堝苟鍗曟潯濂藉弸鐢宠锛氬凡鏈夊垯鎸� id 瑕嗙洊锛涙柊璁板綍鎸� id 鍊掑簭鎻掑叆锛堟瘮鏈湴鏈�鏃ц繕鑰佸垯璺宠繃锛岀暀缁� loadMore 甯﹀洖锛� */
- upsertFriendRequest(next: FriendRequest) {
- void this.upsertFriendRequestForPull(next).catch((error) =>
- console.warn('[IM friendStore] 鏈湴濂藉弸鐢宠鍐欏叆澶辫触', error)
- )
- },
-
- /** 鍚堝苟鍗曟潯濂藉弸鐢宠 */
- async upsertFriendRequestForPull(next: FriendRequest): Promise<void> {
- const existing = this.getFriendRequest(next.id)
- if (existing) {
- Object.assign(existing, next)
- await this.saveFriendRequestRecord(existing)
- return
- }
- // 姣旀湰鍦版渶鏃� id 杩樿�侊細涓嶅叆鍒楄〃锛岃 loadMore 鑷劧甯﹀洖锛岄伩鍏嶇牬鍧� id 鍊掑簭 / 鍚庣画 loadMore 閲嶅 push
- const oldest = this.friendRequests[this.friendRequests.length - 1]
- if (oldest && next.id < oldest.id) {
- return
- }
- // 鎸� id 鍊掑簭鎵鹃涓瘮鑷繁灏忕殑浣嶇疆鎻掑叆锛涙壘涓嶅埌鍒欒拷鍔犳湯灏�
- const insertIndex = this.friendRequests.findIndex((request) => request.id < next.id)
- if (insertIndex === -1) {
- this.friendRequests.push(next)
- } else {
- this.friendRequests.splice(insertIndex, 0, next)
- }
- await this.saveFriendRequestRecord(next)
- },
-
- /** 澧為噺鎷夊彇濂藉弸鐢宠鍙樻洿骞跺悎骞讹紙閲嶈繛 / 绂荤嚎琛ュ伩锛夛紱鎸� update_time + id 娓告爣锛屽凡澶勭悊鐨勬寜 handleResult 瑕嗙洊 */
- async pullFriendRequests() {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const isActive = () => requestEpoch === storeEpoch && getCurrentUserId() === requestUserId
- await runIncrementalPull(
- StorageKeys.settings.friendRequestPullCursor,
- apiPullMyFriendRequestList,
- async (records) => {
- if (!isActive()) {
- return false
- }
- await Promise.all(
- records.map((vo) => this.upsertFriendRequestForPull(convertFriendRequest(vo)))
- )
- return true
- },
- isActive
- )
- },
-
- // ==================== 濂藉弸鍏崇郴鎿嶄綔 ====================
-
- /** 鍒犻櫎濂藉弸锛堝崟鍚戣蒋鍒狅紝鏈缃� DISABLE锛夛紱clear=true 鏃剁骇鑱旀竻鐞嗘湰鍦扮浉鍏虫暟鎹紙濡傜鑱婁細璇濓級锛屽苟閫忎紶鍚庣缁欏绔悓姝� */
- async deleteFriend(friendUserId: number, clear: boolean = true) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- await apiDeleteFriend(friendUserId, clear)
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.removeFriend(friendUserId, clear)
- },
-
- /** 鍒囨崲鍏嶆墦鎵帮細鍚屾浼氳瘽鐨� silent 瀛楁锛岄伩鍏嶄細璇濆垪琛� silent 鍥炬爣绛� 1210 鎺ㄥ埌鎵嶆洿鏂� */
- async setFriendSilent(friendUserId: number, silent: boolean) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- await apiUpdateFriend({ friendUserId, silent })
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const friend = this.getFriend(friendUserId)
- if (friend) {
- friend.silent = silent
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.PRIVATE, friendUserId, { silent })
- this.saveFriend(friend)
- }
- },
-
- /** 鍒囨崲鑱旂郴浜虹疆椤� */
- async setFriendPinned(friendUserId: number, pinned: boolean) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- await apiUpdateFriend({ friendUserId, pinned })
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const friend = this.getFriend(friendUserId)
- if (friend) {
- friend.pinned = pinned
- this.saveFriend(friend)
- }
- },
-
- /** 鎷夐粦濂藉弸锛氭湰绔箰瑙傛洿鏂� + 璋冩帴鍙o紱鍚庣 FRIEND_BLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍏滃簳鍚屾澶氱 */
- async blockFriend(friendUserId: number) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- await apiBlockFriend(friendUserId)
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const friend = this.getFriend(friendUserId)
- if (friend) {
- friend.blocked = true
- this.saveFriend(friend)
- }
- },
-
- /** 绉诲嚭榛戝悕鍗曪細鏈涔愯鏇存柊 + 璋冩帴鍙o紱鍚庣 FRIEND_UNBLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍏滃簳鍚屾澶氱 */
- async unblockFriend(friendUserId: number) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- await apiUnblockFriend(friendUserId)
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const friend = this.getFriend(friendUserId)
- if (friend) {
- friend.blocked = false
- this.saveFriend(friend)
- }
- },
-
- /** 淇敼濂藉弸灞曠ず澶囨敞锛堜粎鑷繁鍙锛� */
- async setFriendDisplayName(friendUserId: number, displayName: string) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const value = displayName.trim()
- // 鍚庣 displayName 璇箟锛歯ull/undefined = 涓嶆敼锛�"" = 娓呯┖锛屾墍浠ヨ繖閲岀洿鎺ヤ紶 value锛堝彲鑳芥槸绌轰覆锛�
- await apiUpdateFriend({ friendUserId, displayName: value })
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const friend = this.getFriend(friendUserId)
- if (friend) {
- friend.displayName = value
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.PRIVATE, friendUserId, {
- name: getFriendDisplayName(friend)
- })
- this.saveFriend(friend)
- }
- },
-
- /** 鏈湴鍚堝苟 / 鏂板鏌愪釜濂藉弸锛圵ebSocket 浜嬩欢 & 鎵嬪姩鍒锋柊閮界敤锛� */
- upsertFriend(friend: Friend) {
- void this.upsertFriendForPull(friend).catch((error) =>
- console.warn('[IM friendStore] 鏈湴濂藉弸鍐欏叆澶辫触', error)
- )
- },
-
- /** 鏈湴鍚堝苟 / 鏂板鏌愪釜濂藉弸 */
- async upsertFriendForPull(friend: Friend): Promise<void> {
- const index = this.friends.findIndex(
- (existing) => existing.friendUserId === friend.friendUserId
- )
- if (index === -1) {
- this.friends.push({
- ...friend,
- status: friend.status ?? CommonStatusEnum.ENABLE
- })
- } else {
- this.friends[index] = {
- ...this.friends[index],
- ...friend,
- status: friend.status ?? CommonStatusEnum.ENABLE
- }
- }
- const conversationStore = useConversationStore()
- const merged = this.getFriend(friend.friendUserId)
- conversationStore.updateConversation(ImConversationType.PRIVATE, friend.friendUserId, {
- name: merged ? getFriendDisplayName(merged) : friend.nickname,
- avatar: friend.avatar,
- silent: friend.silent
- })
- await this.saveFriendRecord(merged)
- },
-
- /** 鏈湴鏍囪鍒犻櫎锛圵ebSocket FRIEND_DELETE 浜嬩欢瑙﹀彂锛沜lear=true 鏃剁骇鑱旀竻鐩稿叧鏁版嵁濡傜鑱婁細璇濓級 */
- removeFriend(friendUserId: number, clear: boolean = true) {
- const friend = this.getFriend(friendUserId)
- if (friend) {
- // blocked 涓嶅姩锛岃窡鍚庣 deleteFriend0銆屽垹濂藉弸鏈熼棿淇濈暀鎷夐粦鐘舵�併�嶅榻�
- friend.status = CommonStatusEnum.DISABLE
- friend.deleteTime = Date.now()
- }
- if (clear) {
- const conversationStore = useConversationStore()
- conversationStore.removePrivateConversation(friendUserId)
- }
- this.saveFriend(friend)
- },
-
- // ==================== WebSocket 浜嬩欢 dispatcher锛�1201-1210 娈碉級 ====================
-
- /** FRIEND_REQUEST_RECEIVED(1203)锛氭敹鍒版柊鐢宠锛沺ayload 宸插甫鐢宠鏂规樀绉� / 澶村儚锛屾寜 requestId 鐩存帹 push 杩涘垪琛� */
- applyFriendRequestReceivedNotification(payload: FriendNotificationPayload) {
- if (!payload.requestId) {
- return
- }
- const currentUserId = getCurrentUserId()
- const existingIndex = this.friendRequests.findIndex((item) => item.id === payload.requestId)
- if (existingIndex !== -1) {
- const existing = this.friendRequests.splice(existingIndex, 1)[0]
- if (!existing) {
- return
- }
- const next = {
- ...existing,
- fromUserId: payload.operatorUserId,
- toUserId: currentUserId,
- handleResult: ImFriendRequestHandleResult.UNHANDLED,
- applyContent: payload.applyContent,
- addSource: payload.addSource,
- createTime: Date.now(),
- fromNickname: payload.fromNickname,
- fromAvatar: payload.fromAvatar
- }
- this.friendRequests.unshift(next)
- this.saveFriendRequest(next)
- return
- }
- const next = {
- id: payload.requestId,
- fromUserId: payload.operatorUserId,
- toUserId: currentUserId,
- handleResult: ImFriendRequestHandleResult.UNHANDLED,
- applyContent: payload.applyContent,
- addSource: payload.addSource,
- createTime: Date.now(),
- fromNickname: payload.fromNickname,
- fromAvatar: payload.fromAvatar
- }
- this.friendRequests.unshift(next)
- this.saveFriendRequest(next)
- },
-
- /** FRIEND_REQUEST_APPROVED(1201)锛氭垜鐨勭敵璇疯鍚屾剰锛涙寜 requestId 鏇存柊鐘舵�侊紙FRIEND_ADD 浼氬彟澶栨帹锛� */
- applyFriendRequestApprovedNotification(payload: FriendNotificationPayload) {
- if (!payload.requestId) {
- return
- }
- void this.applyHandleResult(payload.requestId, ImFriendRequestHandleResult.AGREED)
- },
-
- /** FRIEND_REQUEST_REJECTED(1202)锛氭垜鐨勭敵璇疯鎷掔粷锛涙寜 requestId 鏇存柊鐘舵�� */
- applyFriendRequestRejectedNotification(payload: FriendNotificationPayload) {
- if (!payload.requestId) {
- return
- }
- void this.applyHandleResult(
- payload.requestId,
- ImFriendRequestHandleResult.REFUSED,
- payload.handleContent
- )
- },
-
- /**
- * FRIEND_ADD(1204)锛氭柊澧炲ソ鍙嬶紱鏈鎷夊彇濂藉弸璇︽儏骞跺叆搴�
- * peerUserId 鐢� websocketStore 鎸夊抚 sender / receiver 绠楀ソ浼犲叆锛歜ecomeFriends 鍗曟潯鍏ュ簱鍚庡弻鏂规敹鍒板悓涓�浠� payload锛�
- * 鏈鐪熸鐨勩�屽绔�嶆槸甯т笂鐨勫彟涓�涓敤鎴凤紝涓嶆槸 payload.friendUserId锛坧ayload 閲屽浐瀹氭槸 toUserId锛夈��
- */
- applyFriendAddNotification(_payload: FriendNotificationPayload, peerUserId: number) {
- if (this.isActiveFriend(peerUserId)) {
- return
- }
- void this.fetchFriendInfo(peerUserId)
- },
-
- /**
- * FRIEND_DELETE(1205)锛氬ソ鍙嬭鍒犻櫎锛涙湰绔竻鐞� + 鎸� payload.clear 鍐冲畾鏄惁绾ц仈娓呬細璇濓紙澶氱璺熶富鎿嶄綔绔竴鑷达級
- * peerUserId 鐢� websocketStore 鎸夊抚 sender / receiver 绠楀ソ浼犲叆锛涗笌 FRIEND_ADD 淇濇寔涓�鑷寸殑 peer 鎺ㄦ柇
- */
- applyFriendDeleteNotification(payload: FriendNotificationPayload, peerUserId: number) {
- this.removeFriend(peerUserId, payload.clear !== false)
- },
-
- /** FRIEND_BLOCK(1207)锛氭媺榛戯紱澶氱鍚屾 */
- applyFriendBlockNotification(payload: FriendNotificationPayload) {
- const friend = this.getFriend(payload.friendUserId)
- if (friend) {
- friend.blocked = true
- this.saveFriend(friend)
- }
- },
-
- /** FRIEND_UNBLOCK(1208)锛氱Щ鍑洪粦鍚嶅崟锛涘绔悓姝� */
- applyFriendUnblockNotification(payload: FriendNotificationPayload) {
- const friend = this.getFriend(payload.friendUserId)
- if (friend) {
- friend.blocked = false
- this.saveFriend(friend)
- }
- },
-
- /** FRIEND_INFO_UPDATED(1209)锛氬ソ鍙嬭祫鏂欏彉鏇达紙鏄电О / 澶村儚锛夛紱閲嶆媺璇︽儏 */
- applyFriendInfoUpdatedNotification(payload: FriendNotificationPayload) {
- void this.fetchFriendInfo(payload.friendUserId)
- },
-
- /** FRIEND_UPDATE(1210)锛氭壒閲忔洿鏂帮紙澶囨敞 / 鍏嶆墦鎵� / 鑱旂郴浜虹疆椤讹級锛涘绔悓姝� */
- applyFriendUpdateNotification(payload: FriendNotificationPayload) {
- const friend = this.getFriend(payload.friendUserId)
- if (!friend) {
- return
- }
- if (payload.displayName != null) {
- friend.displayName = payload.displayName
- }
- if (payload.silent != null) {
- friend.silent = payload.silent
- }
- if (payload.pinned != null) {
- friend.pinned = payload.pinned
- }
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.PRIVATE, payload.friendUserId, {
- name: getFriendDisplayName(friend),
- silent: friend.silent
- })
- this.saveFriend(friend)
- },
-
- /** 娓呯┖濂藉弸鍐呭瓨鐘舵�侊紝骞跺簾寮冩湭杩斿洖璇锋眰锛坧ending Promise 缃┖ + storeEpoch++锛� */
- clear() {
- this.friends = []
- this.friendRequests = []
- this.loaded = false
- this.hasMoreFriendRequests = true
- pendingFetchFriends = null
- pendingFetchRequests = null
- pendingLoadMoreRequests = null
- pendingFetchFriendInfos.clear()
- storeEpoch++
- }
- }
-})
-
-function convertFriend(vo: ImFriendApi.FriendRespVO): Friend {
- return {
- id: vo.id,
- friendUserId: vo.friendUserId,
- nickname: vo.nickname || String(vo.friendUserId),
- nicknamePinyin: vo.nicknamePinyin,
- avatar: vo.avatar,
- silent: !!vo.silent,
- displayName: vo.displayName || '',
- displayNamePinyin: vo.displayNamePinyin,
- addSource: vo.addSource,
- pinned: !!vo.pinned,
- blocked: !!vo.blocked,
- status: vo.status,
- addTime: vo.addTime ? new Date(vo.addTime).getTime() : undefined,
- deleteTime: vo.deleteTime ? new Date(vo.deleteTime).getTime() : undefined
- }
-}
-
-function convertFriendRequest(vo: ImFriendRequestApi.FriendRequestRespVO): FriendRequest {
- return {
- id: vo.id,
- fromUserId: vo.fromUserId,
- toUserId: vo.toUserId,
- handleResult: vo.handleResult,
- applyContent: vo.applyContent,
- handleContent: vo.handleContent,
- addSource: vo.addSource,
- handleTime: vo.handleTime ? new Date(vo.handleTime).getTime() : undefined,
- createTime: vo.createTime ? new Date(vo.createTime).getTime() : 0,
- fromNickname: vo.fromNickname,
- fromAvatar: vo.fromAvatar,
- toNickname: vo.toNickname,
- toAvatar: vo.toAvatar
- }
-}
-
-export const useFriendStoreWithOut = () => useFriendStore()
-
-// dev: 璁� Pinia 鐨� actions / state 鏀瑰姩鏀寔 HMR锛岄伩鍏嶆瘡娆℃敼 store 閮藉緱纭埛
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useFriendStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/groupRequestStore.ts b/src/views/im/home/store/groupRequestStore.ts
deleted file mode 100644
index 24ea5da..0000000
--- a/src/views/im/home/store/groupRequestStore.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-import type { ImGroupRequestApi } from '#/api/im/group/request'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { agreeGroupRequest as apiAgreeGroupRequest, getMyGroupRequest as apiGetMyGroupRequest, getUnhandledRequestList as apiGetUnhandledRequestList, pullMyGroupRequestList as apiPullMyGroupRequestList, refuseGroupRequest as apiRefuseGroupRequest } from '#/api/im/group/request'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
-
-import { getDb, StorageKeys } from '../../utils/db'
-import { runIncrementalPull } from '../../utils/pull'
-
-type PendingRequest = { epoch: number; promise: Promise<void>; userId: number; }
-
-/** clear() 鏃堕�掑锛涙棫璐﹀彿 in-flight 鐨� pullGroupRequests 缁撴灉 resolve 鍚庢瘮瀵逛竴鑷存墠鍐� store锛岄槻璺ㄨ处鍙风孩鐐规薄鏌擄紙涓� friendStore 鍚屽彛寰勶級 */
-let storeEpoch = 0
-let pendingUnhandledFetch: null | PendingRequest = null
-
-/**
- * IM 鍔犵兢鐢宠 Store
- *
- * 浠呯淮鎶ゃ�屾垜绠$悊鐨勬墍鏈夌兢銆嶄笅鏈鐞嗙殑鐢宠鍒楄〃锛坲nhandledList锛夛紱
- * 妯箙 / Drawer 閮戒粠杩欓噷娲剧敓 count 鍜屽垎缁勫垪琛紝閬垮厤缁� ImGroupApi.GroupRespVO 鎸� pendingRequestCount 瀛楁
- *
- * 鏁版嵁鐢熷懡鍛ㄦ湡锛�
- * - 杩� IM 鍚庤皟涓�娆� fetchUnhandledGroupRequestList 鎷夐椤靛叏閲�
- * - WebSocket 1503 鈫� 璋� addGroupRequestById(requestId) 鎷夊崟鏉� + push 鍒� unhandledList 澶撮儴
- * - WebSocket 1505 / 1506 鈫� 鎸� requestId 浠� unhandledList 绉婚櫎
- * - WebSocket 1517 GROUP_ADMIN_ADD锛堣嚜宸辫鍔犱负 admin锛夆啋 閲嶆柊璋� fetchUnhandledGroupRequestList
- * - 鏈 agree / refuse 澶勭悊鍚� 鈫� 鏈湴鎸� requestId 绉婚櫎
- */
-export const useGroupRequestStore = defineStore('imGroupRequestStore', {
- state: () => ({
- /** 鎴戠鐞嗙殑鎵�鏈夌兢涓嬫湭澶勭悊鐢宠鍒楄〃锛堟寜 id 鍊掑簭锛� */
- unhandledList: [] as ImGroupRequestApi.GroupRequestRespVO[],
- /** fetchUnhandledGroupRequestList 鏄惁鎴愬姛鎵ц杩囷紱閬垮厤妯箙鏄剧ず 0 鐒跺悗璺虫暟瀛楃殑闂儊 */
- loaded: false
- }),
-
- getters: {
- /**
- * 鍚勭兢涓嬫湭澶勭悊鐢宠鏁扮殑 Map锛汷(N) 鎵竴娆$紦瀛樹緵 ConversationItem 绛� N 澶勫鐢紝閬垮厤 N脳M 閲嶅 filter
- */
- getUnhandledGroupRequestCountMap(state): Map<number, number> {
- const map = new Map<number, number>()
- for (const request of state.unhandledList) {
- map.set(request.groupId, (map.get(request.groupId) ?? 0) + 1)
- }
- return map
- },
- /** 鎸囧畾缇や笅鐨勬湭澶勭悊鐢宠鏁� */
- getUnhandledGroupRequestCount(): (groupId: number) => number {
- return (groupId: number) => this.getUnhandledGroupRequestCountMap.get(groupId) ?? 0
- },
- /** 鎸囧畾缇や笅鐨勬湭澶勭悊鐢宠鍒楄〃 */
- getUnhandledGroupRequestListByGroupId:
- (state) =>
- (groupId: number): ImGroupRequestApi.GroupRequestRespVO[] =>
- state.unhandledList.filter((r) => r.groupId === groupId)
- },
-
- actions: {
- /** 浠� IndexedDB 鎭㈠鍔犵兢鐢宠 */
- async loadGroupRequestList(): Promise<boolean> {
- try {
- const cached = await getDb().getAll<ImGroupRequestApi.GroupRequestRespVO>('groupRequests')
- if (!cached || cached.length === 0) {
- return false
- }
- this.unhandledList = cached
- .filter((request) => request.handleResult === ImGroupRequestHandleResult.UNHANDLED)
- .toSorted((requestA, requestB) => requestB.id - requestA.id)
- return true
- } catch (error) {
- console.warn('[IM groupRequestStore] 鏈湴鍔犵兢鐢宠缂撳瓨璇诲彇澶辫触', error)
- return false
- }
- },
-
- /** 淇濆瓨鍔犵兢鐢宠鍒楄〃 */
- saveGroupRequestList(): void {
- void getDb()
- .transaction(['groupRequests'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.clearStore('groupRequests', tx)
- for (const request of this.unhandledList) {
- await db.put('groupRequests', request, tx)
- }
- })
- .catch((error) => console.warn('[IM groupRequestStore] 鏈湴鍔犵兢鐢宠缂撳瓨鍐欏叆澶辫触', error))
- },
-
- /** 淇濆瓨鍗曟潯鍔犵兢鐢宠 */
- async saveGroupRequestRecord(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
- await getDb().put('groupRequests', request)
- },
-
- /** 淇濆瓨鍗曟潯鍔犵兢鐢宠 */
- saveGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO): void {
- void this.saveGroupRequestRecord(request).catch((error) =>
- console.warn('[IM groupRequestStore] 鏈湴鍔犵兢鐢宠鍐欏叆澶辫触', error)
- )
- },
-
- /** 鎷夊彇鎴戠鐞嗙殑鎵�鏈夌兢涓嬫湭澶勭悊鐢宠锛涜繘 IM 鍚� / 鍗囩骇 admin 鍚� / WS 鎺ㄩ�佹湁鍐茬獊鏃惰皟鐢� */
- async fetchUnhandledGroupRequestList() {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- if (
- pendingUnhandledFetch?.epoch === requestEpoch &&
- pendingUnhandledFetch.userId === requestUserId
- ) {
- return pendingUnhandledFetch.promise
- }
- const promise = (async () => {
- const list = await apiGetUnhandledRequestList()
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.unhandledList = list || []
- this.loaded = true
- this.saveGroupRequestList()
- })().finally(() => {
- if (
- pendingUnhandledFetch?.epoch === requestEpoch &&
- pendingUnhandledFetch.userId === requestUserId
- ) {
- pendingUnhandledFetch = null
- }
- })
- pendingUnhandledFetch = { epoch: requestEpoch, userId: requestUserId, promise }
- return promise
- },
-
- /**
- * WS 鏀跺埌 1503锛氭媺鏈�鏂板唴瀹瑰苟缃《
- *
- * 鍚屼竴瀵� group_id, user_id 澶嶇敤璁板綍鏃� requestId 涓嶅彉浣� applyContent / inviterUserId 浼氬埛鏂帮紝鎵�浠ユ棤鏉′欢 fetch + 鎺掑埌澶撮儴
- */
- async addGroupRequestById(requestId: number) {
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const request = await apiGetMyGroupRequest(requestId)
- if (!request) {
- return
- }
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- this.upsertGroupRequest(request)
- },
-
- /**
- * 鏈湴鍚堝苟 / 鏂板鍗曟潯鍔犵兢鐢宠锛圵S 鎺ㄩ�� & 澧為噺鎷夊彇鍏辩敤锛�
- *
- * 鏈鐞嗙殑鎸� id 鍘婚噸鍚庣疆椤讹紱宸插鐞嗙殑浠庢湭澶勭悊鍒楄〃绉婚櫎锛岄伩鍏嶈ˉ鍋挎椂鎶婂凡鍚屾剰 / 鎷掔粷鐨勮褰曞鍥炵孩鐐�
- */
- upsertGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO) {
- void this.upsertGroupRequestForPull(request).catch((error) =>
- console.warn('[IM groupRequestStore] 鏈湴鍔犵兢鐢宠鍐欏叆澶辫触', error)
- )
- },
-
- /** 鏈湴鍚堝苟 / 鏂板鍗曟潯鍔犵兢鐢宠 */
- async upsertGroupRequestForPull(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
- if (request.handleResult !== ImGroupRequestHandleResult.UNHANDLED) {
- await this.removeGroupRequestByIdForPull(request.id)
- return
- }
- this.unhandledList = [request, ...this.unhandledList.filter((r) => r.id !== request.id)]
- await this.saveGroupRequestRecord(request)
- },
-
- /**
- * 澧為噺鎷夊彇鍔犵兢鐢宠鍙樻洿骞跺悎骞讹紱鍚凡澶勭悊鐨勬寜 handleResult 璧扮Щ闄わ紙宸插鐞� 鈫� 浠庣孩鐐瑰垪琛ㄥ墧闄わ級
- *
- * 鍙仛閲嶈繛 / 鍚庣画绂荤嚎琛ュ伩锛屼笉璐熻矗棣栫櫥锛氶鐧荤孩鐐硅蛋 fetchUnhandledGroupRequestList锛堟湇鍔$鐩存帴杩囨护鏈鐞嗭紝璇箟鏇寸簿鍑嗐�佸惎鍔ㄦ洿杞伙級銆�
- * 鏁呴娆¢噸杩炴椂娓告爣涓虹┖ = 涓�娆℃�у叏閲忚蛋涓�閬嶏紙宸插鐞嗚褰曞懡涓� removeGroupRequestById 涓� no-op锛岀孩鐐逛笉鍙楀奖鍝嶏級锛屼箣鍚庡閲忋��
- */
- async pullGroupRequests() {
- // 蹇収 epoch锛涜处鍙峰湪鎷夊彇閫斾腑鍒囨崲锛坈lear() 鈫� epoch++锛夋椂涓㈠純鏃ц处鍙烽偅鍑犻〉缁撴灉锛岄槻璺ㄨ处鍙风孩鐐规薄鏌�
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- const isActive = () => requestEpoch === storeEpoch && getCurrentUserId() === requestUserId
- await runIncrementalPull(
- StorageKeys.settings.groupRequestPullCursor,
- apiPullMyGroupRequestList,
- async (records) => {
- if (!isActive()) {
- return false
- }
- await Promise.all(records.map((vo) => this.upsertGroupRequestForPull(vo)))
- return true
- },
- isActive
- )
- if (isActive()) {
- this.loaded = true
- }
- },
-
- /** WS 鏀跺埌 1505 / 1506 鎴栨湰绔鐞嗗畬涓�鏉★細鎸� requestId 浠庡垪琛ㄧЩ闄� */
- removeGroupRequestById(requestId: number) {
- this.unhandledList = this.unhandledList.filter((r) => r.id !== requestId)
- void getDb()
- .delete('groupRequests', requestId)
- .catch((error) => console.warn('[IM groupRequestStore] 鏈湴鍔犵兢鐢宠鍒犻櫎澶辫触', error))
- },
-
- /** 鍒犻櫎鍗曟潯鍔犵兢鐢宠 */
- async removeGroupRequestByIdForPull(requestId: number): Promise<void> {
- this.unhandledList = this.unhandledList.filter((r) => r.id !== requestId)
- await getDb().delete('groupRequests', requestId)
- },
-
- /** 鍚屾剰鐢宠锛涙湰绔鐞嗗悗绔嬪嵆浠庡垪琛ㄧЩ闄わ紝閬垮厤琚弽澶嶇偣鍑� */
- async agreeGroupRequest(requestId: number) {
- await apiAgreeGroupRequest(requestId)
- this.removeGroupRequestById(requestId)
- },
-
- /** 鎷掔粷鐢宠 */
- async refuseGroupRequest(requestId: number, handleContent?: string) {
- await apiRefuseGroupRequest(requestId, handleContent)
- this.removeGroupRequestById(requestId)
- },
-
- /** 娓呯┖鍔犵兢鐢宠鍐呭瓨 */
- clear() {
- this.unhandledList = []
- this.loaded = false
- // 璐﹀彿鍒囨崲锛氶�掑 epoch 搴熷純鏃ц处鍙� in-flight 鐨� pullGroupRequests 缁撴灉锛岄伩鍏嶅啓杩涙柊璐﹀彿绾㈢偣鍒楄〃
- storeEpoch++
- pendingUnhandledFetch = null
- }
- }
-})
-
-export const useGroupRequestStoreWithOut = () => useGroupRequestStore()
-
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useGroupRequestStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/groupStore.ts b/src/views/im/home/store/groupStore.ts
deleted file mode 100644
index d916d35..0000000
--- a/src/views/im/home/store/groupStore.ts
+++ /dev/null
@@ -1,1017 +0,0 @@
-import type { Group, GroupDO, GroupMember, Message } from '../types'
-
-import type { ImGroupApi } from '#/api/im/group'
-import type { ImGroupMemberApi } from '#/api/im/group/member'
-
-import { CommonStatusEnum } from '../../../../packages/constants/src'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { getGroup as apiGetGroup, getMyGroupList as apiGetMyGroupList } from '#/api/im/group'
-import { getGroupMember as apiGetGroupMember, getGroupMemberList as apiGetGroupMemberList, updateGroupMember as apiUpdateGroupMember } from '#/api/im/group/member'
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import {
- ImContentType,
- ImConversationType,
- ImGroupMemberRole,
- ImMessageStatus
-} from '../../utils/constants'
-import { getDb } from '../../utils/db'
-import { type GroupNotificationPayload } from '../../utils/message'
-import { getGroupDisplayName } from '../../utils/user'
-import { useConversationStore } from './conversationStore'
-import { useGroupRequestStore } from './groupRequestStore'
-
-/** clear() 鏃堕�掑锛涙棫璐﹀彿 in-flight 鐨勬垚鍛樿姹傝繑鍥炲悗姣斿涓�鑷存墠鍐� store */
-let storeEpoch = 0
-
-/**
- * fetchGroupMemberList 骞跺彂鍘婚噸琛細鍚� groupId 鍚屾椂杩涚殑璇锋眰鍏辩敤涓�涓� Promise
- *
- * key 蹇呴』甯� userId鈥斺�旇处鍙峰垏鎹㈡椂 A 鐨勮姹備笉鑳借 B 澶嶇敤锛屽惁鍒� IIFE 鍐呴儴鐨� saveGroupMemberList 浼氭妸 A 鐨勬垚鍛樻暟鎹啓杩� B 鐨� IDB 妗�
- */
-const pendingMemberFetches = new Map<string, Promise<GroupMember[]>>()
-const pendingMemberKey = (userId: number, groupId: number) => `${userId}:${groupId}`
-
-/**
- * fetchGroupMember 鍗曟垚鍛樺苟鍙戝幓閲嶈〃锛氬悓 (groupId, memberUserId) 鍚屾椂杩涚殑璇锋眰鍏辩敤涓�涓� Promise
- *
- * 璺熸暣缇よ〃鍒嗗紑锛氬崟鎴愬憳 fetch 璺熸暣缇� fetch 璇箟涓嶅悓锛堝崟鎴愬憳涓嶅洖濉� me 鐨� silent锛夛紝涓嶈兘浜掔浉浠f浛
- */
-const pendingSingleMemberFetches = new Map<string, Promise<GroupMember | null>>()
-
-const pendingSingleMemberKey = (userId: number, groupId: number, memberUserId: number) =>
- `${userId}:${groupId}:${memberUserId}`
-
-/** 鏋勫缓缇� IndexedDB 璁板綍 */
-function buildGroupDO(group: Group): GroupDO {
- const {
- activeCallExpired: _activeCallExpired,
- activeCallLoaded: _activeCallLoaded,
- infoLoaded: _infoLoaded,
- members: _members,
- membersLoaded: _membersLoaded,
- membersExpired: _membersExpired,
- ...record
- } = group
- return record
-}
-
-/** 鍒ゆ柇褰撳墠鐢ㄦ埛鏄惁鍦� payload.memberUserIds 閲岋紙GROUP_CREATE / INVITE / KICK 鑷垽鐢級 */
-function isSelfInPayloadMembers(payload: GroupNotificationPayload): boolean {
- const selfUserId = getCurrentUserId()
- return !!selfUserId && (payload.memberUserIds || []).includes(selfUserId)
-}
-
-/** 鍒锋柊鎴戠鐞嗙殑缇ょ敵璇风孩鐐� */
-function refreshUnhandledGroupRequests(): void {
- useGroupRequestStore()
- .fetchUnhandledGroupRequestList()
- .catch(() => undefined)
-}
-
-/**
- * IM 缇� Store
- *
- * 璐熻矗锛�
- * - 鎷夊彇 / 缂撳瓨褰撳墠鐧诲綍鐢ㄦ埛鍔犲叆鐨勭兢鍒楄〃
- * - 鎸� groupId 鎳掑姞杞界兢鎴愬憳锛堜緵 ConversationGroupSide / MentionPicker / MessageReadStatus 娑堣垂锛�
- * - 鎴愬憳"宸茶 / 鏈"绛夎仛鍚堟煡璇㈢敱 MessageReadStatus 鍙﹁缁勫悎
- */
-export const useGroupStore = defineStore('imGroupStore', {
- state: () => ({
- groups: [] as Group[],
- loaded: false, // 浠� fetchGroupList 鎴愬姛鍚庣疆浣嶏紱loadGroupList锛圛DB锛変笉缃綅锛屽惁鍒欏悗鍙� SWR 鍒锋柊浼氳缂撳瓨鍛戒腑璺宠繃
- groupMembersExpired: false // 杩涘叆 IM / 閲嶈繛鍚庣疆浣嶏紱IDB 閲岀殑鎴愬憳妗跺欢杩熷姞杞藉埌鍐呭瓨鏃讹紝涔熻鎸夎繃鏈熷鐞�
- }),
-
- getters: {
- getGroup:
- (state) =>
- (id: number): Group | undefined => {
- return state.groups.find((g) => g.id === id)
- },
- /** 缇ゆ垚鍛� userId 鈫� GroupMember 绱㈠紩锛涜皟鐢ㄦ柟鎸� userId 鍙嶆煡鏄电О / 澶村儚绛夊厓淇℃伅 */
- getGroupMemberMap:
- (state) =>
- (id: number): Map<number, GroupMember> => {
- const group = state.groups.find((g) => g.id === id)
- return new Map((group?.members || []).map((m) => [m.userId, m]))
- }
- },
-
- actions: {
- // ==================== 鏈湴缂撳瓨 ====================
-
- /** 浠� IndexedDB 鎭㈠缇ゅ垪琛� */
- async loadGroupList(): Promise<boolean> {
- try {
- const cached = await getDb().getAll<GroupDO>('groups')
- if (!cached || cached.length === 0) {
- return false
- }
- this.groups = cached
- return true
- } catch (error) {
- console.warn('[IM groupStore] 鏈湴缇ょ紦瀛樿鍙栧け璐�', error)
- return false
- }
- },
-
- /** 淇濆瓨缇ゅ垪琛� */
- saveGroupList(): void {
- void getDb()
- .transaction(['groups'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.clearStore('groups', tx)
- for (const group of this.groups) {
- await db.put('groups', buildGroupDO(group), tx)
- }
- })
- .catch((error) => console.warn('[IM groupStore] 鏈湴缇ょ紦瀛樺啓鍏ュけ璐�', error))
- },
-
- /** 淇濆瓨鍗曚釜缇� */
- async saveGroupRecord(group: Group | undefined): Promise<void> {
- if (!group) {
- return
- }
- await getDb().put('groups', buildGroupDO(group))
- },
-
- /** 淇濆瓨鍗曚釜缇� */
- saveGroup(group: Group | undefined): void {
- void this.saveGroupRecord(group).catch((error) =>
- console.warn('[IM groupStore] 鏈湴缇ゅ啓鍏ュけ璐�', error)
- )
- },
-
- /** 浠� IndexedDB 鎭㈠鎸囧畾缇ゆ垚鍛� */
- async loadGroupMemberList(groupId: number): Promise<GroupMember[] | null> {
- // in-memory 宸�"瀹屾暣"鍔犺浇锛坒etchGroupMemberList 璺戣繃鎴栦笂娆″喎鍚姩浠� IDB 鏁存《鎭㈠杩囷級锛氱洿鎺ュ鐢紱
- // 鍗曟垚鍛樿ˉ榻愶紙fetchGroupMember锛夊啓杩涚殑 partial members 涓嶅湪姝よ繑鍥炵紦瀛樷�斺�斿叾 membersLoaded=false
- const cachedGroup = this.getGroup(groupId)
- if (cachedGroup?.members && cachedGroup.membersLoaded) {
- return cachedGroup.members
- }
- try {
- const cached = await getDb().getAllByIndex<GroupMember>(
- 'groupMembers',
- 'groupId',
- groupId
- )
- if (!cached || cached.length === 0) {
- return null
- }
- // 鎶� IDB 鎷垮埌鐨勬垚鍛樿惤鍒板搴� group
- const group = this.getGroup(groupId)
- if (group) {
- group.members = cached
- group.memberCount = cached.length
- group.membersLoaded = true
- group.membersExpired = this.groupMembersExpired
- } else {
- // group 杩樻病灏变綅锛氫粎 in-memory 鍗犱綅锛坣ame='' 琛ㄧず鏈煡锛夛紝涓嶈皟 upsertGroup 鈥斺�� 閬垮厤鎶婂亣鍚嶇亴杩� conversation.name + groups IDB 妗讹紱
- // 鍚庣画锛岀瓑 fetchGroupList 娴呭悎骞舵椂锛岃鐪熷悕瑕嗙洊
- this.groups.push({
- id: groupId,
- name: '',
- members: cached,
- memberCount: cached.length,
- membersLoaded: true,
- membersExpired: this.groupMembersExpired
- })
- }
- return cached
- } catch (error) {
- console.warn('[IM groupStore] 鏈湴缇ゆ垚鍛樼紦瀛樿鍙栧け璐�', { groupId }, error)
- return null
- }
- },
-
- /** 淇濆瓨鎸囧畾缇ゆ垚鍛� */
- saveGroupMemberList(groupId: number): void {
- const members = this.getGroup(groupId)?.members
- if (!members) {
- return
- }
- void getDb()
- .transaction(['groupMembers'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.deleteByIndex('groupMembers', 'groupId', groupId, tx)
- for (const member of members) {
- if (member.id) {
- await db.put('groupMembers', member, tx)
- }
- }
- })
- .catch((error) =>
- console.warn(`[IM groupStore] 鏈湴缇ゆ垚鍛樼紦瀛樺啓鍏ュけ璐� (groupId=${groupId})`, error)
- )
- },
-
- // ==================== 杩滅鎷夊彇 ====================
-
- /** 鎷夊彇缇ゅ垪琛紱鍚屾鍒锋柊瀵瑰簲缇よ亰浼氳瘽鐨勫睍绀哄悕 / 澶村儚 + 钀� IDB */
- async fetchGroupList(force = false) {
- if (this.loaded && !force) {
- return
- }
- const requestEpoch = storeEpoch
- const requestUserId = getCurrentUserId()
- // 鎷夊彇褰撳墠鐧诲綍鐢ㄦ埛鍔犲叆鐨勬墍鏈夌兢锛堜笉甯︽垚鍛橈紱鎴愬憳鎸夐渶鍐嶈蛋 fetchGroupMemberList锛�
- const list = await apiGetMyGroupList()
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return
- }
- const fresh = (list || []).map((group) => convertGroup(group))
- // 鍚堝苟鑰岄潪鍏ㄩ噺鏇挎崲锛氭垚鍛樼紦瀛樺彧鍦ㄦ垚鍛樺垪琛ㄦ帴鍙g淮鎶わ紝缇や釜浜鸿缃互缇ゅ垪琛ㄦ帴鍙d负鍑�
- const groupMap = new Map(this.groups.map((group) => [group.id, group]))
- this.groups = fresh.map((group) => {
- const existing = groupMap.get(group.id)
- if (!existing) {
- return { ...group, activeCallExpired: true, infoLoaded: true }
- }
- return {
- ...group,
- infoLoaded: true,
- activeCallExpired: existing.activeCallExpired,
- activeCallLoaded: existing.activeCallLoaded,
- members: existing.members,
- memberCount: existing.memberCount ?? group.memberCount,
- membersLoaded: existing.membersLoaded,
- membersExpired: existing.membersExpired
- }
- })
- this.loaded = true
- const conversationStore = useConversationStore()
- for (const group of this.groups) {
- conversationStore.updateConversation(ImConversationType.GROUP, group.id, {
- name: getGroupDisplayName(group),
- avatar: group.avatar,
- silent: group.silent
- })
- }
- this.saveGroupList()
- this.preloadMembersForEmptyAvatarGroups()
- },
-
- /** 澶辨晥鍏ㄩ儴缇よ鎯呯紦瀛� */
- markAllGroupInfoExpired() {
- for (const group of this.groups) {
- group.infoLoaded = false
- }
- },
-
- /** 棰勫姞杞界┖缇ゅご鍍忕殑鎴愬憳鍒楄〃锛屼緵 GroupAvatar 寮傛鍚堟垚缇ゅご鍍� */
- preloadMembersForEmptyAvatarGroups() {
- for (const group of this.groups) {
- if (
- group.avatar ||
- group.joinStatus === CommonStatusEnum.DISABLE ||
- (group.membersLoaded && !group.membersExpired && group.members?.length)
- ) {
- continue
- }
- const force = !!group.membersLoaded && !group.membersExpired && !group.members?.length
- this.fetchGroupMemberList(group.id, force).catch((error) => {
- console.warn('[IM groupStore] 棰勫姞杞界兢澶村儚鎴愬憳澶辫触', { groupId: group.id }, error)
- })
- }
- },
-
- /** 澶辨晥鍏ㄩ儴缇ゆ垚鍛樼紦瀛� */
- markAllGroupMembersExpired() {
- this.groupMembersExpired = true
- for (const group of this.groups) {
- if (group.membersLoaded) {
- group.membersExpired = true
- }
- }
- },
-
- /** 澶辨晥鍏ㄩ儴缇ら�氳瘽鎺㈡祴缂撳瓨 */
- markAllGroupActiveCallsExpired() {
- for (const group of this.groups) {
- group.activeCallExpired = true
- }
- },
-
- /** 鏍囪缇ら�氳瘽鎺㈡祴宸插姞杞� */
- markGroupActiveCallLoaded(groupId: number) {
- const group = this.getGroup(groupId)
- if (!group) {
- return
- }
- group.activeCallLoaded = true
- group.activeCallExpired = false
- },
-
- /** 鍒ゆ柇缇ら�氳瘽鏄惁闇�瑕侀噸鏂版帰娴� */
- isGroupActiveCallExpired(groupId: number): boolean {
- const group = this.getGroup(groupId)
- return !group?.activeCallLoaded || !!group.activeCallExpired
- },
-
- /** 澶辨晥鎸囧畾缇ゆ垚鍛樼紦瀛� */
- markGroupMembersExpired(groupId: number) {
- const group = this.getGroup(groupId)
- if (group?.membersLoaded) {
- group.membersExpired = true
- }
- },
-
- /** 鍗曠兢鍒锋柊锛氱敤 /im/group/get 鎷変竴浠芥渶鏂板厓鏁版嵁鍐� upsert锛屽父鐢ㄤ簬 GROUP_UPDATE 鎺ㄩ�佸悗鎴栨墜鍔� reload */
- async fetchGroupInfo(groupId: number, force = false) {
- const cached = this.getGroup(groupId)
- if (cached?.infoLoaded && !force) {
- return
- }
- try {
- const data = await apiGetGroup(groupId)
- if (!data) {
- return
- }
- this.upsertGroup({ ...convertGroup(data), infoLoaded: true })
- } catch (error) {
- console.warn('[IM groupStore] fetchGroupInfo 澶辫触', error)
- }
- },
-
- /** 鎸夌兢鎷夊彇鎴愬憳锛坕n-memory 缂撳瓨 + 骞跺彂鍘婚噸锛宖orce=true 寮哄埛锛�+ 钀� IDB */
- fetchGroupMemberList(groupId: number, force = false): Promise<GroupMember[]> {
- // in-memory "瀹屾暣"鍔犺浇杩囨墠鍛戒腑鈥斺�斿崟鎴愬憳琛ラ綈鍐欏叆鐨� partial members 涓嶅湪姝よ繑鍥烇紙membersLoaded=false锛�
- const cached = this.getGroup(groupId)
- if (cached && cached.members && cached.membersLoaded && !cached.membersExpired && !force) {
- return Promise.resolve(cached.members)
- }
- // 鏈櫥褰曪細涓嶅彂璧疯姹備篃涓嶇櫥璁� in-flight锛岄伩鍏嶆薄鏌撳崟椋炶〃
- const requestUserId = getCurrentUserId()
- if (!requestUserId) {
- return Promise.resolve([])
- }
- const requestEpoch = storeEpoch
- // 鍚� (userId, groupId) 宸茬粡鏈夋鍦ㄩ鐨勮姹傦細鐩存帴澶嶇敤锛岄伩鍏嶉噸澶嶆墦鎺ュ彛
- const key = pendingMemberKey(requestUserId, groupId)
- const inflight = pendingMemberFetches.get(key)
- if (inflight) {
- return inflight
- }
- const promise = (async () => {
- // 鎷夋帴鍙� + 鍗� pass 杞崲锛氬悓鏃舵崟鑾� me 鐨勫師濮� VO锛岀粰涓嬮潰鍥炲~ user-per-group 瀛楁锛坰ilent / groupRemark锛夌敤
- const list = await apiGetGroupMemberList(groupId)
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return []
- }
- let meRaw: ImGroupMemberApi.GroupMemberRespVO | undefined
- const members = (list || []).map((member) => {
- if (member.userId === requestUserId) {
- meRaw = member
- }
- return convertGroupMember(member, groupId)
- })
- const silent = !!meRaw?.silent
- const groupRemark = meRaw?.groupRemark || ''
-
- // 蹇呴』 await 涔嬪悗閲嶆柊 getGroup锛岄伩鍏� fetchGroupList 宸插苟鍙戝啓鍏ョ湡瀹� group 鐨� race
- const group = this.getGroup(groupId)
- const isPlaceholder = !group
- let groupFieldsChanged = false
- if (group) {
- group.members = members
- group.memberCount = members.length
- group.membersLoaded = true
- group.membersExpired = false
- // silent / groupRemark 浠讳竴鍙樺寲鎵嶅悓姝ュ埌 conversation 鍜� IDB锛沢roupRemark 鍙樺寲瑕侀『甯﹀埛浼氳瘽鍚�
- if (group.silent !== silent || group.groupRemark !== groupRemark) {
- group.silent = silent
- group.groupRemark = groupRemark
- groupFieldsChanged = true
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.GROUP, groupId, {
- name: getGroupDisplayName(group),
- silent
- })
- }
- } else {
- // group 杩樻病灏变綅锛氫粎 in-memory push 鍗犱綅锛坣ame='' 琛ㄧず鏈煡锛夛紝涓嶈皟 upsertGroup鈥斺�旈伩鍏嶆妸鍋囧悕鐏岃繘 conversation.name + groups IDB 妗讹紱
- // 鍚庣画锛岀瓑 fetchGroupList 娴呭悎骞舵椂锛岃鐪熷悕瑕嗙洊
- this.groups.push({
- id: groupId,
- name: '',
- members,
- memberCount: members.length,
- silent,
- groupRemark,
- membersLoaded: true,
- membersExpired: false
- })
- }
-
- // groups 妗朵粎鍦� user-per-group 瀛楁瀹為檯鍙樺寲鏃跺啓鈥斺�旈伩鍏嶄竴娆℃壒閲忚繘缇ゅ紩鍙戝娆℃暣妗堕噸鍐�
- this.saveGroupMemberList(groupId)
- if (!isPlaceholder && groupFieldsChanged) {
- this.saveGroup(group)
- }
- return members
- // 鏃犺鎴愬姛 / 澶辫触閮借浠庡崟椋炶〃娓呮帀锛屽惁鍒欏悗缁悓 group 璇锋眰姘歌繙鎷垮埌杩欎釜 stale Promise
- })().finally(() => pendingMemberFetches.delete(key))
-
- // 鎶� Promise 鐧昏杩涘崟椋炶〃锛岃姝ゅ悗鐭椂闂村唴鐨勫悓 (userId, groupId) 璇锋眰澶嶇敤
- pendingMemberFetches.set(key, promise)
- return promise
- },
-
- /**
- * 鎸� (groupId, memberUserId) 鍗曟垚鍛樿ˉ榻愨�斺�攄eriveLastSenderDisplayName 鍏滃簳鍦烘櫙鐢�
- *
- * 璺� fetchGroupMemberList 鍖哄埆锛氬彧鎷夎繖涓�涓垚鍛橈紝涓嶅姩 me 鐨� silent / groupRemark锛堜笉鏄� me 鐨勮瘽鎷夸笉鍒帮級锛�
- * 鍛戒腑鏃舵妸鎴愬憳 upsert 杩� group.members 鏁扮粍骞惰惤 IDB锛岃鍚庣画娓叉煋鑳界敤 displayUserName
- */
- fetchGroupMember(groupId: number, memberUserId: number): Promise<GroupMember | null> {
- // in-memory 鍛戒腑鐩存帴杩斿洖锛屼笉鎵撴帴鍙�
- const cached = this.getGroup(groupId)?.members?.find((m) => m.userId === memberUserId)
- if (cached) {
- return Promise.resolve(cached)
- }
- // 鏈櫥褰曪細涓嶅彂璧疯姹備篃涓嶇櫥璁� in-flight锛岄伩鍏嶆薄鏌撳崟椋炶〃
- const requestUserId = getCurrentUserId()
- if (!requestUserId) {
- return Promise.resolve(null)
- }
- const requestEpoch = storeEpoch
- // 鍚� (userId, groupId, memberUserId) 宸茬粡鏈夋鍦ㄩ鐨勮姹傦細鐩存帴澶嶇敤
- const key = pendingSingleMemberKey(requestUserId, groupId, memberUserId)
- const inflight = pendingSingleMemberFetches.get(key)
- if (inflight) {
- return inflight
- }
- const promise = (async () => {
- const data = await apiGetGroupMember(groupId, memberUserId)
- if (!data) {
- return null
- }
- if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
- return null
- }
- const member = convertGroupMember(data, groupId)
- // 鎶婅繖涓�鏉� upsert 杩� group.members 浠呬緵 in-memory 娓叉煋鍏滃簳锛沢roup 杩樻病灏变綅鍒欑敤 placeholder
- // 娉ㄦ剰锛氫笉鍐� IDB鈥斺�旀垚鍛樻《璇箟鏄�"鍏ㄩ噺"锛屽瓨"1 浜烘《"浼氭薄鏌撲笅娆″喎鍚姩鐨� loadGroupMemberList
- const group = this.getGroup(groupId)
- if (group) {
- const memberList = group.members ?? []
- const index = memberList.findIndex((m) => m.userId === memberUserId)
- if (index === -1) {
- memberList.push(member)
- } else {
- memberList[index] = member
- }
- group.members = memberList
- } else {
- // memberCount 涓嶈锛氬悗缁� fetchGroupList 鍚堝苟 `existing.memberCount ?? fresh.memberCount` 鏃讹紝
- // 鍗犱綅鍊间細椤舵浛鐪熷疄鍊硷紙fresh 涓嶅甫 memberCount锛夛紝绛� fetchGroupMemberList 璺戣繃鎵嶈兘鎷垮埌鐪熷疄鏁�
- this.groups.push({
- id: groupId,
- name: '',
- members: [member]
- })
- }
- return member
- })().finally(() => pendingSingleMemberFetches.delete(key))
- pendingSingleMemberFetches.set(key, promise)
- return promise
- },
-
- /** 鎸� id 鎻掑叆鎴栧悎骞剁兢锛堝懡涓垯娴呭悎骞朵繚鐣欐棫瀛楁锛屾湭鍛戒腑鍒欒拷鍔狅級锛屽悓姝ユ妸灞曠ず鍚� / 澶村儚 / 鍏嶆墦鎵版帹鍒板搴斾細璇� */
- upsertGroup(group: Group) {
- void this.upsertGroupAndSave(group).catch((error) =>
- console.warn('[IM groupStore] 鏈湴缇ゅ啓鍏ュけ璐�', error)
- )
- },
-
- /** 鎸� id 鎻掑叆鎴栧悎骞剁兢 */
- async upsertGroupAndSave(group: Group): Promise<void> {
- const index = this.groups.findIndex((g) => g.id === group.id)
- if (index === -1) {
- this.groups.push(group)
- } else {
- this.groups[index] = { ...this.groups[index], ...group }
- }
- // 鍚屾鎺ㄥ埌 conversation锛氱兢鍚� / 澶村儚 / 鍏嶆墦鎵版槸浼氳瘽鍒楄〃灞曠ず鐢ㄧ殑锛屽繀椤荤揣闅� group 鍙樻洿
- const merged = this.getGroup(group.id) ?? group
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.GROUP, group.id, {
- name: getGroupDisplayName(merged),
- avatar: merged.avatar,
- silent: merged.silent
- })
- // 鎸佷箙鍖栧埌 IDB锛坒ire-and-forget锛�
- await this.saveGroupRecord(merged)
- },
-
- /** 鏈湴绉婚櫎缇ょ紦瀛樺拰缇や細璇濓紱缇よВ鏁o紙GROUP_DEL锛夈�侀��缇ゃ�佽韪㈤兘澶嶇敤 */
- removeGroup(id: number) {
- // 鏈湴纭垹锛堝尯鍒簬濂藉弸鍒犻櫎鐨勮蒋鍒犱繚鐣欒褰曪級锛涚骇鑱旀竻缇よ亰浼氳瘽閬垮厤鍒楄〃閲岀暀姝荤兢
- this.groups = this.groups.filter((g) => g.id !== id)
- const conversationStore = useConversationStore()
- conversationStore.removeGroupConversation(id)
- void getDb()
- .transaction(['groups', 'groupMembers'], 'readwrite', async (tx) => {
- const db = getDb()
- await db.delete('groups', id, tx)
- await db.deleteByIndex('groupMembers', 'groupId', id, tx)
- })
- .catch((error) => console.warn(`[IM groupStore] 缇ょ紦瀛樺垹闄ゅけ璐� (groupId=${id})`, error))
- },
-
- /** 鍒囨崲鍏嶆墦鎵帮細鎺ㄥ悗绔� + 钀芥湰鍦� + 鍚屾浼氳瘽鍒楄〃鐨� silent锛岄伩鍏� silent 鍥炬爣 / 鎬绘湭璇� / 鎻愮ず闊冲垽鏂笌璁剧疆婕傜Щ锛涘拰 friendStore.setFriendSilent 瀵归綈 */
- async setGroupSilent(id: number, silent: boolean) {
- await apiUpdateGroupMember({ groupId: id, silent })
- const group = this.getGroup(id)
- if (!group) {
- return
- }
- group.silent = silent
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.GROUP, id, { silent })
- this.saveGroup(group)
- },
-
- /** 鎵归噺鏇存柊缇ゆ垚鍛樿鑹诧紱鏈湴涓嶅懡涓垯蹇界暐锛岀瓑 fetchGroupMemberList 鍏滃簳 */
- updateGroupMemberRoleList(groupId: number, userIds: number[], role: number) {
- const group = this.getGroup(groupId)
- if (!group?.members?.length) {
- return
- }
- // 鍛戒腑鐩爣涓旇鑹插凡鍙樺寲鎵嶆爣璁� changed锛岄伩鍏嶆棤鍙樺寲鏃舵暣鏁扮粍閲嶅缓瑙﹀彂鍝嶅簲寮�
- const idSet = new Set(userIds)
- let changed = false
- const newMembers = group.members.map((member) => {
- if (!idSet.has(member.userId) || member.role === role) {
- return member
- }
- changed = true
- return { ...member, role }
- })
- // 鏈夊彉鍖栨墠鏁寸粍鏇挎崲锛岃鍝嶅簲寮忓彧鍦ㄧ湡鏈夋洿鏂版椂閫氱煡涓嬫父
- if (changed) {
- group.members = newMembers
- this.saveGroupMemberList(groupId)
- }
- },
-
- /** 缇や富杞锛氱兢琛� ownerUserId 鏀逛负鏂板�硷紱鏃х兢涓� role 鈫� NORMAL锛涙柊缇や富 role 鈫� OWNER */
- transferGroupOwner(groupId: number, oldOwnerId: number, newOwnerId: number) {
- const group = this.getGroup(groupId)
- if (!group) {
- return
- }
- if (group.ownerUserId !== newOwnerId) {
- group.ownerUserId = newOwnerId
- }
- this.updateGroupMemberRoleList(groupId, [oldOwnerId], ImGroupMemberRole.NORMAL)
- this.updateGroupMemberRoleList(groupId, [newOwnerId], ImGroupMemberRole.OWNER)
- this.saveGroup(group)
- },
-
- /** 鏈湴鍓旈櫎缇ゆ垚鍛橈紙GROUP_MEMBER_QUIT / KICK 浜嬩欢锛夛紱涓嶅懡涓垯绛� fetchGroupMemberList 鍏滃簳 */
- removeLocalGroupMemberList(groupId: number, userIds: number[]) {
- const group = this.getGroup(groupId)
- if (!group?.members?.length || userIds.length === 0) {
- return
- }
- const idSet = new Set(userIds)
- const next = group.members.filter((member) => !idSet.has(member.userId))
- if (next.length === group.members.length) {
- return
- }
- group.members = next
- group.memberCount = next.length
- this.saveGroupMemberList(groupId)
- },
-
- /** 鏈湴鏇存柊缇ゆ垚鍛樼殑 status锛堣嚜宸遍��缇� / 琚涪鐨勬湰鍦伴缃紱璁� isMember 绔嬪嵆鏀舵暃鍒� stranger锛屼笉渚濊禆 removeGroup 鐨勬暣缇ょЩ闄わ級 */
- updateGroupMemberStatus(groupId: number, userId: number, status: number) {
- const group = this.getGroup(groupId)
- const member = group?.members?.find((m) => m.userId === userId)
- if (!member || member.status === status) {
- return
- }
- member.status = status
- this.saveGroupMemberList(groupId)
- },
-
- /** 鏈湴鏇存柊缇ゆ垚鍛樼殑 displayUserName锛圙ROUP_MEMBER_NICKNAME_UPDATE 浜嬩欢锛夛紱涓嶅懡涓垯绛� fetchGroupMemberList 鍏滃簳 */
- updateGroupMemberDisplayUserName(groupId: number, userId: number, displayUserName: string) {
- const group = this.getGroup(groupId)
- const member = group?.members?.find((m) => m.userId === userId)
- if (!member || member.displayUserName === displayUserName) {
- return
- }
- member.displayUserName = displayUserName
- this.saveGroupMemberList(groupId)
- },
-
- /** 灞�閮ㄦ洿鏂扮兢瀛楁锛坣ame / notice / avatar 绛夛級锛涙湭鍛戒腑鏈湴缂撳瓨鏃堕潤榛樺拷鐣ワ紝绛� fetchGroupList 鍏滃簳锛涙柊鍊艰窡鏃у�奸兘鐩稿悓鏃惰烦杩囧搷搴斿紡 + IDB 鍐� */
- updateGroupFields(groupId: number, fields: Partial<Group>) {
- const group = this.getGroup(groupId)
- if (!group) {
- return
- }
- const changed = (Object.keys(fields) as (keyof Group)[]).some((k) => group[k] !== fields[k])
- if (!changed) {
- return
- }
- Object.assign(group, fields)
- const conversationStore = useConversationStore()
- conversationStore.updateConversation(ImConversationType.GROUP, groupId, {
- name: getGroupDisplayName(group),
- avatar: group.avatar,
- silent: group.silent
- })
- this.saveGroup(group)
- },
-
- /**
- * 鎺ユ敹 GROUP_* 缇ゅ箍鎾簨浠讹紝鎸� type 鍒嗗彂鍒板搴旂鏈� action
- *
- * WebSocket 瀹炴椂鏀惰蛋 messageStore.insertMessage 鏃佽矾璋冪敤
- * store 閲屾病缂撳瓨鐨勭兢闈欓粯蹇界暐锛岀瓑 fetchGroupList 鍏滃簳
- */
- applyGroupNotification(groupId: number, type: number, content?: string) {
- if (!groupId) {
- return
- }
- let payload: GroupNotificationPayload
- try {
- payload = content ? JSON.parse(content) : {}
- } catch (error) {
- console.warn(
- '[IM groupStore] applyGroupNotification 瑙f瀽 content 澶辫触',
- { groupId, type, contentLength: content?.length ?? 0 },
- error
- )
- return
- }
- switch (type) {
- case ImContentType.GROUP_ADMIN_ADD: {
- this.updateGroupMemberRoleList(
- groupId,
- payload.memberUserIds || [],
- ImGroupMemberRole.ADMIN
- )
- this.markGroupMembersExpired(groupId)
- // 鑷繁琚姞涓虹鐞嗗憳锛屽師鏈湅涓嶅埌鐨勭兢涓嬫湭澶勭悊鐢宠鐜板湪鍙樺彲瑙侊紝閲嶆柊鎷変竴娆� unhandledList
- if (isSelfInPayloadMembers(payload)) {
- refreshUnhandledGroupRequests()
- }
- break
- }
- case ImContentType.GROUP_ADMIN_REMOVE: {
- this.updateGroupMemberRoleList(
- groupId,
- payload.memberUserIds || [],
- ImGroupMemberRole.NORMAL
- )
- this.markGroupMembersExpired(groupId)
- if (isSelfInPayloadMembers(payload)) {
- refreshUnhandledGroupRequests()
- }
- break
- }
- case ImContentType.GROUP_BANNED: {
- this.updateGroupFields(groupId, { banned: !!payload.banned })
- break
- }
- case ImContentType.GROUP_CANCEL_MUTED: {
- this.updateGroupFields(groupId, { mutedAll: false })
- break
- }
- case ImContentType.GROUP_CREATE: {
- this.applyGroupCreateNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_DISSOLVE: {
- this.removeGroup(groupId)
- break
- }
- case ImContentType.GROUP_INFO_UPDATE: {
- this.applyGroupInfoUpdateNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_CANCEL_MUTED: {
- this.applyGroupMemberCancelMutedNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_ENTER: {
- this.applyGroupMemberEnterNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_INVITE: {
- this.applyGroupMemberInviteNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_KICK: {
- this.applyGroupMemberKickNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_MUTED: {
- this.applyGroupMemberMutedNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_NICKNAME_UPDATE: {
- this.applyGroupMemberNicknameUpdateNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MEMBER_QUIT: {
- this.applyGroupMemberQuitNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MESSAGE_PIN: {
- this.applyGroupMessagePinNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MESSAGE_UNPIN: {
- this.applyGroupMessageUnpinNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_MUTED: {
- this.updateGroupFields(groupId, { mutedAll: true })
- break
- }
- case ImContentType.GROUP_NAME_UPDATE: {
- this.applyGroupNameUpdateNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_NOTICE_UPDATE: {
- this.applyGroupNoticeUpdateNotification(groupId, payload)
- break
- }
- case ImContentType.GROUP_OWNER_TRANSFER: {
- this.applyGroupOwnerTransferNotification(groupId, payload)
- break
- }
- }
- },
-
- /** 鍒涘缓缇ゅ箍鎾細缇ゆ湭灏变綅鏃舵媺缇よ鎯� */
- async applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) {
- if (!isSelfInPayloadMembers(payload)) {
- return
- }
- const selfUserId = getCurrentUserId()
- const selfIsOperator = !!selfUserId && payload.operatorUserId === selfUserId
- if (selfIsOperator && this.getGroup(groupId)) {
- return
- }
- await this.fetchGroupInfo(groupId, true)
- },
-
- /** 缇ゅ悕鍙樻洿锛氭寜 newName 灞�閮ㄦ洿鏂版湰鍦扮兢鍚� */
- applyGroupNameUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
- if (payload.newName) {
- this.updateGroupFields(groupId, { name: payload.newName })
- }
- },
-
- /** 缇ゅ叕鍛婂彉鏇达細鎸� newNotice 灞�閮ㄦ洿鏂帮紙鍏佽绌轰覆浣滀负銆屾竻绌哄叕鍛娿�嶏級 */
- applyGroupNoticeUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
- this.updateGroupFields(groupId, { notice: payload.newNotice ?? '' })
- },
-
- /** 缇や俊鎭彉鏇达細鍚屾澶村儚銆佽繘缇ゅ鎵� */
- applyGroupInfoUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
- const fields: Partial<Group> = {}
- if (payload.newAvatar) {
- fields.avatar = payload.newAvatar
- }
- if (payload.newJoinApproval != null) {
- fields.joinApproval = payload.newJoinApproval
- }
- if (Object.keys(fields).length > 0) {
- this.updateGroupFields(groupId, fields)
- }
- },
-
- /** 鎴愬憳鍔犲叆锛氳閭�璇疯�呮湰绔� group 鏈氨浣嶅厛 fetchGroupInfo 鍒濇鎷夊彇锛涙墍鏈変汉閮藉埛鎴愬憳鍒楄〃锛堟柊鎴愬憳 nickname / avatar 涓嶅湪 payload锛� */
- async applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) {
- // 鑷繁鍒氳鎷夎繘鏉ワ細蹇呴』 await fetchGroupInfo 璁╃兢鍏� state.groups锛屽惁鍒� fetchGroupMemberList 鐨� guard 浼氬厹绌�
- if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) {
- await this.fetchGroupInfo(groupId, true)
- }
- this.markGroupMembersExpired(groupId)
- this.fetchGroupMemberList(groupId, true).catch(() => undefined)
- },
-
- /** 鑷敱杩涚兢锛氳繘缇よ�呮湰绔� group 鏈氨浣嶅厛 fetchGroupInfo 鍒濇鎷夊彇锛涙墍鏈変汉閮藉埛鎴愬憳鍒楄〃 */
- async applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) {
- const selfUserId = getCurrentUserId()
- // 鑷繁鑷敱杩涚兢锛氬繀椤� await fetchGroupInfo 璁╃兢鍏� state.groups锛屽惁鍒� fetchGroupMemberList 鐨� guard 浼氬厹绌�
- if (selfUserId && payload.entrantUserId === selfUserId && !this.getGroup(groupId)) {
- await this.fetchGroupInfo(groupId, true)
- }
- this.markGroupMembersExpired(groupId)
- this.fetchGroupMemberList(groupId, true).catch(() => undefined)
- },
-
- /** 鎴愬憳閫�缇わ細閫�缇よ�呮湰浜哄厛鎶� self.status 缃� DISABLE 鍐� removeGroup锛堜繚鐣欑姸鎬佽涔� + 缁存寔 groups 鍒楄〃骞插噣锛夛紱鍏朵粬鎴愬憳浠庢湰鍦板垪琛ㄧЩ闄� quitter */
- applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) {
- const selfUserId = getCurrentUserId()
- if (selfUserId && payload.operatorUserId === selfUserId) {
- this.updateGroupMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE)
- this.removeGroup(groupId)
- } else if (payload.operatorUserId) {
- this.removeLocalGroupMemberList(groupId, [payload.operatorUserId])
- this.markGroupMembersExpired(groupId)
- }
- },
-
- /** 鎴愬憳琚Щ鍑猴細琚涪鑰呮湰浜哄厛鎶� self.status 缃� DISABLE 鍐� removeGroup锛涘叾浠栨垚鍛樹粠鏈湴鍒楄〃绉婚櫎琚涪鑰� */
- applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) {
- const memberIds = payload.memberUserIds || []
- const selfUserId = getCurrentUserId()
- if (isSelfInPayloadMembers(payload)) {
- if (selfUserId) {
- this.updateGroupMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE)
- }
- this.removeGroup(groupId)
- } else if (memberIds.length > 0) {
- this.removeLocalGroupMemberList(groupId, memberIds)
- this.markGroupMembersExpired(groupId)
- }
- },
-
- /** 鎴愬憳鏄电О鍙樻洿锛氭寜 operatorUserId 灞�閮ㄦ洿鏂板搴� member.displayUserName */
- applyGroupMemberNicknameUpdateNotification(groupId: number, payload: GroupNotificationPayload) {
- if (payload.operatorUserId) {
- this.updateGroupMemberDisplayUserName(
- groupId,
- payload.operatorUserId,
- payload.displayUserName ?? ''
- )
- this.markGroupMembersExpired(groupId)
- }
- },
-
- /** 缇や富杞锛氭棫缇や富 鈫� NORMAL锛屾柊缇や富 鈫� OWNER锛涙柊缇や富鑷繁渚ч噸鏂版媺鐢宠鍒楄〃 */
- applyGroupOwnerTransferNotification(groupId: number, payload: GroupNotificationPayload) {
- if (payload.operatorUserId && payload.newOwnerUserId) {
- this.transferGroupOwner(groupId, payload.operatorUserId, payload.newOwnerUserId)
- this.markGroupMembersExpired(groupId)
- }
- // 鑷繁鎺ョ缇や富锛氬師鏈湅涓嶅埌鐨勭兢涓嬫湭澶勭悊鐢宠鐜板湪鍙樺彲瑙侊紝閲嶆柊鎷変竴娆� unhandledList
- const selfUserId = getCurrentUserId()
- if (selfUserId && payload.newOwnerUserId === selfUserId) {
- refreshUnhandledGroupRequests()
- } else if (selfUserId && payload.operatorUserId === selfUserId) {
- refreshUnhandledGroupRequests()
- }
- },
-
- /** 缇ゆ秷鎭疆椤讹細浠� payload 鍙栨秷鎭睍绀烘暟鎹姞鍏ョ疆椤跺垪琛� */
- applyGroupMessagePinNotification(groupId: number, payload: GroupNotificationPayload) {
- const message = payload.message
- if (!message) {
- return
- }
- const group = this.getGroup(groupId)
- if (!group) {
- return
- }
- // 骞傜瓑锛氬凡瀛樺湪鍚� messageId 涓嶉噸澶� push
- const existing = group.pinnedMessages || []
- if (existing.some((msg) => msg.id === message.id)) {
- return
- }
- group.pinnedMessages = [
- ...existing,
- {
- id: message.id,
- clientMessageId: '',
- senderId: message.senderId,
- type: message.type,
- content: message.content,
- status: ImMessageStatus.NORMAL,
- sendTime: new Date(message.sendTime).getTime(),
- targetId: message.groupId || groupId,
- selfSend: message.senderId === getCurrentUserId(),
- atUserIds: message.atUserIds ? [...message.atUserIds] : [],
- receiverUserIds: message.receiverUserIds ? [...message.receiverUserIds] : []
- }
- ]
- this.saveGroup(group)
- },
-
- /** 缇ゆ秷鎭彇娑堢疆椤讹細鎸� messageId 浠庢湰鍦扮疆椤跺垪琛ㄤ腑绉婚櫎 */
- applyGroupMessageUnpinNotification(groupId: number, payload: GroupNotificationPayload) {
- if (!payload.messageId) {
- return
- }
- const group = this.getGroup(groupId)
- if (!group?.pinnedMessages?.length) {
- return
- }
- const newPinnedMessages = group.pinnedMessages.filter((m) => m.id !== payload.messageId)
- if (newPinnedMessages.length === group.pinnedMessages.length) {
- return
- }
- group.pinnedMessages = newPinnedMessages
- this.saveGroup(group)
- },
-
- /** 鍗曟垚鍛樼瑷�锛氭洿鏂扮洰鏍囨垚鍛樼殑 muteEndTime */
- applyGroupMemberMutedNotification(groupId: number, payload: GroupNotificationPayload) {
- const group = this.getGroup(groupId)
- const member = group?.members?.find((m) => m.userId === payload.mutedUserId)
- if (member && payload.muteEndTime) {
- member.muteEndTime = payload.muteEndTime
- this.saveGroupMemberList(groupId)
- this.markGroupMembersExpired(groupId)
- }
- },
-
- /** 鍗曟垚鍛樺彇娑堢瑷�锛氭竻绌虹洰鏍囨垚鍛樼殑 muteEndTime */
- applyGroupMemberCancelMutedNotification(groupId: number, payload: GroupNotificationPayload) {
- const group = this.getGroup(groupId)
- const member = group?.members?.find((m) => m.userId === payload.mutedUserId)
- if (member) {
- member.muteEndTime = undefined
- this.saveGroupMemberList(groupId)
- this.markGroupMembersExpired(groupId)
- }
- },
-
- /** 鍒囪处鍙锋椂浠呮竻 in-memory锛孖DB 鎸� userId 鍒嗘《澶╃劧闅旂锛屽洖鍒囩寮� */
- clear() {
- this.groups = []
- this.loaded = false
- this.groupMembersExpired = false
- // 璐﹀彿鍒囨崲锛氶�掑 epoch 搴熷純鏃ц处鍙� in-flight 鐨勬垚鍛樿姹�
- storeEpoch++
- // 鍗曢琛ㄨ窡 in-memory state 涓�璧烽噸缃紱鏃ц处鍙� in-flight 鐨勮姹� finally 涔熶細鑷繁 delete key锛屾彁鍓嶆竻绌哄彧鏄洿骞茶剢
- pendingMemberFetches.clear()
- pendingSingleMemberFetches.clear()
- }
- }
-})
-
-function convertGroup(group: ImGroupApi.GroupRespVO): Group {
- return {
- id: group.id,
- name: group.name,
- avatar: group.avatar,
- notice: group.notice,
- ownerUserId: group.ownerUserId,
- pinnedMessages: group.pinnedMessages?.map((message) => convertGroupMessageVO(message)),
- mutedAll: group.mutedAll,
- banned: group.banned,
- joinApproval: group.joinApproval,
- joinStatus: group.joinStatus,
- groupRemark: group.groupRemark,
- silent: group.silent
- }
-}
-
-/** 鍚庣 ImGroupMessageApi.GroupMessageRespVO -> 鍓嶇 Message锛氳ˉ targetId / selfSend / sendTime 绛夋淳鐢熷瓧娈� */
-function convertGroupMessageVO(
- message: NonNullable<ImGroupApi.GroupRespVO['pinnedMessages']>[number]
-): Message {
- const currentUserId = getCurrentUserId()
- return {
- id: message.id,
- clientMessageId: message.clientMessageId || '',
- type: message.type,
- content: message.content,
- status: message.status,
- sendTime: new Date(message.sendTime).getTime(),
- senderId: message.senderId,
- targetId: message.groupId,
- selfSend: !!currentUserId && message.senderId === currentUserId,
- atUserIds: message.atUserIds || [],
- receiverUserIds: message.receiverUserIds || [],
- receiptStatus: message.receiptStatus,
- readCount: message.readCount
- }
-}
-
-function convertGroupMember(member: ImGroupMemberApi.GroupMemberRespVO, groupId: number): GroupMember {
- return {
- id: member.id,
- userId: member.userId,
- groupId,
- nickname: member.nickname || String(member.userId),
- avatar: member.avatar,
- displayUserName: member.displayUserName,
- status: member.status,
- role: member.role,
- muteEndTime: member.muteEndTime
- }
-}
-
-export const useGroupStoreWithOut = () => useGroupStore()
-
-// dev: 璁� Pinia 鐨� actions 鏀瑰姩鏀寔 HMR锛屽厤鍘绘瘡娆℃敼 store 閮借纭埛
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useGroupStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/messageStore.ts b/src/views/im/home/store/messageStore.ts
deleted file mode 100644
index fa17b67..0000000
--- a/src/views/im/home/store/messageStore.ts
+++ /dev/null
@@ -1,953 +0,0 @@
-import type { Conversation, Message, MessageDO } from '../types'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import {
- IM_AT_ALL_USER_ID,
- ImContentType,
- ImConversationType,
- ImMessageReceiptStatus,
- ImMessageStatus,
- isGroupNotification,
- isNormalMessage
-} from '../../utils/constants'
-import { resolveConversationLastContent } from '../../utils/conversation'
-import {
- type DbTransaction,
- getClientConversationId,
- getClientMessageKey,
- getDb,
- getServerMessageKey,
- parseClientConversationId,
- setMessageMaxId,
- StorageKeys
-} from '../../utils/db'
-import {
- generateClientMessageId,
- parseRecallMessageId,
- revokeBlobUrlsInContent
-} from '../../utils/message'
-import { isGroupQuit, tryGetSenderDisplayName } from '../../utils/user'
-import { useConversationStore } from './conversationStore'
-import { useGroupStore } from './groupStore'
-
-const MESSAGE_CACHE_RECENT_CONVERSATION_LIMIT = 5
-const MESSAGE_CACHE_RETAIN_CONVERSATION_LIMIT = MESSAGE_CACHE_RECENT_CONVERSATION_LIMIT + 1
-const ackMergingPromises = new Map<string, Promise<void>>()
-
-interface MessageConversationInfo {
- type: number
- targetId: number
- name: string
- avatar: string
- silent?: boolean
-}
-
-interface PersistMessageRecordOptions {
- mergeClientRecord?: boolean
-}
-
-/** 鎷夊彇娑堟伅鎵归噺澶勭悊椤� */
-export type PulledMessage =
- | {
- conversationInfo: MessageConversationInfo
- kind: 'insert'
- message: Message
- }
- | {
- conversationType: number
- kind: 'recall'
- recallSignalContent: string
- targetId: number
- }
-
-/** 鑾峰彇浼氳瘽鐨勬秷鎭紦瀛� key */
-function getMessageCacheKey(type: number, targetId: number): string {
- return getClientConversationId(type, targetId)
-}
-
-/** 鐢熸垚娑堟伅鏈湴涓婚敭 */
-function getMessageKey(
- message: Pick<Message, 'clientMessageId' | 'id'>,
- conversationType: number
-): string {
- return message.id
- ? getServerMessageKey(conversationType, message.id)
- : getClientMessageKey(message.clientMessageId)
-}
-
-/** 琛ラ綈瀹㈡埛绔秷鎭紪鍙� */
-function ensureClientMessageId(message: Message): Message {
- if (!message.clientMessageId) {
- message.clientMessageId = generateClientMessageId()
- }
- if (!message.id) {
- message.id = undefined
- }
- return message
-}
-
-/** 杞崲涓� IndexedDB 娑堟伅璁板綍 */
-function buildMessageDO(message: Message, conversationType: number): MessageDO {
- return {
- id: message.id,
- clientMessageId: message.clientMessageId,
- type: message.type,
- content: message.content,
- status: message.status,
- sendTime: message.sendTime,
- senderId: message.senderId,
- atUserIds: message.atUserIds ? [...message.atUserIds] : undefined,
- receiverUserIds: message.receiverUserIds ? [...message.receiverUserIds] : undefined,
- receiptStatus: message.receiptStatus,
- readCount: message.readCount,
- materialId: message.materialId,
- targetId: message.targetId,
- selfSend: message.selfSend,
- messageKey: getMessageKey(message, conversationType),
- conversationType,
- clientConversationId: getClientConversationId(conversationType, message.targetId)
- }
-}
-
-/** IndexedDB 娑堟伅璁板綍杞墠绔秷鎭� */
-function buildMessageFromDO(message: MessageDO): Message {
- const {
- messageKey: _messageKey,
- conversationType: _conversationType,
- clientConversationId: _clientConversationId,
- ...rest
- } = message
- return rest
-}
-
-/** 绠楀嚭鏈潯娑堟伅鐨勫彂閫佷汉蹇収 */
-function deriveLastSenderDisplayName(
- conversation: Conversation,
- senderId: number
-): string | undefined {
- // 1. 浼樺厛浣跨敤褰撳墠鍐呭瓨涓殑濂藉弸 / 缇ゆ垚鍛樹俊鎭�
- const liveSenderName = tryGetSenderDisplayName(senderId, conversation.type, conversation.targetId)
- if (liveSenderName) {
- return liveSenderName
- }
- // 2. 缇ゆ垚鍛樼紦瀛樼己澶辨椂寮傛琛ラ綈
- if (conversation.type === ImConversationType.GROUP) {
- const groupStore = useGroupStore()
- const group = groupStore.getGroup(conversation.targetId)
- if (!group || isGroupQuit(group)) {
- return conversation.lastSenderId === senderId ? conversation.lastSenderDisplayName : undefined
- }
- const fetchPromise =
- group?.membersLoaded && !group.membersExpired
- ? groupStore.fetchGroupMember(conversation.targetId, senderId)
- : groupStore.fetchGroupMemberList(conversation.targetId)
- fetchPromise.catch((error) =>
- console.warn(
- '[IM messageStore] 鍏滃簳鎷夌兢鎴愬憳澶辫触',
- { groupId: conversation.targetId, senderId, fullFetch: !group?.membersLoaded },
- error
- )
- )
- }
- return conversation.lastSenderId === senderId ? conversation.lastSenderDisplayName : undefined
-}
-
-/** 鎸夋秷鎭洿鏂颁細璇濇憳瑕� */
-function applyConversationSummary(conversation: Conversation, message: Message): void {
- const senderDisplayName = deriveLastSenderDisplayName(conversation, message.senderId)
- conversation.lastContent = resolveConversationLastContent(
- message,
- conversation.type,
- conversation.targetId,
- senderDisplayName
- )
- conversation.lastSendTime = message.sendTime || Date.now()
- conversation.lastSenderId = message.senderId
- conversation.lastMessageType = message.type
- conversation.lastMessageId = message.id
- conversation.lastClientMessageId = message.clientMessageId
- conversation.lastMessageStatus = message.status
- conversation.lastReceiptStatus = message.receiptStatus
- conversation.lastSelfSend = message.selfSend
- conversation.lastSenderDisplayName = senderDisplayName
-}
-
-/** 鎸夋湯鏉℃秷鎭噸绠椾細璇濇憳瑕� */
-function recomputeConversationLast(conversation: Conversation, messages: Message[]): void {
- const last = messages[messages.length - 1]
- if (last) {
- applyConversationSummary(conversation, last)
- return
- }
- conversation.lastContent = ''
- conversation.lastSendTime = 0
- conversation.lastSenderId = undefined
- conversation.lastMessageType = undefined
- conversation.lastMessageId = undefined
- conversation.lastClientMessageId = undefined
- conversation.lastMessageStatus = undefined
- conversation.lastReceiptStatus = undefined
- conversation.lastSelfSend = undefined
- conversation.lastSenderDisplayName = undefined
-}
-
-/** 鍚屾缇� @ 鐘舵�� */
-function syncConversationAtFlags(conversation: Conversation, message: Message): void {
- if (
- message.selfSend ||
- conversation.type !== ImConversationType.GROUP ||
- !message.atUserIds ||
- message.atUserIds.length === 0
- ) {
- return
- }
- const currentUserId = getCurrentUserId()
- if (currentUserId && message.atUserIds.includes(currentUserId)) {
- conversation.atMe = true
- }
- if (message.atUserIds.includes(IM_AT_ALL_USER_ID)) {
- conversation.atAll = true
- }
-}
-
-/** 搴旂敤鏈嶅姟绔秷鎭洿鏂� */
-function applyServerMessageUpdate(message: Message, updates: Partial<Message>): void {
- if (updates.content && updates.content !== message.content) {
- revokeBlobUrlsInContent(message.content)
- }
- Object.assign(message, updates)
- if (updates.id === 0) {
- message.id = undefined
- }
- if (updates.status !== undefined && updates.status !== ImMessageStatus.SENDING) {
- message.uploadProgress = undefined
- if (updates.status !== ImMessageStatus.FAILED) {
- message._localFile = undefined
- }
- }
-}
-
-/** 鍒ゆ柇鏄惁涓哄悓涓�鏉℃秷鎭� */
-function isSameMessage(left: Message, right: Message): boolean {
- if (left.id && right.id && left.id === right.id) {
- return true
- }
- return !!left.clientMessageId && left.clientMessageId === right.clientMessageId
-}
-
-export const useMessageStore = defineStore('imMessageStore', {
- state: () => ({
- messagesByConversation: {} as Record<string, Message[]>,
- loadedConversationKeys: [] as string[],
- privateReadMaxIds: {} as Partial<Record<number, number>>,
- privateMessageMaxId: 0,
- groupMessageMaxId: 0,
- channelMessageMaxId: 0
- }),
-
- getters: {
- /** 鑾峰彇浼氳瘽宸插姞杞芥秷鎭� */
- getMessages:
- (state) =>
- (clientConversationId: string): Message[] =>
- state.messagesByConversation[clientConversationId] || []
- },
-
- actions: {
- /** 娓呯┖娑堟伅鍐呭瓨 */
- clear() {
- Object.values(this.messagesByConversation).forEach((messages) => {
- messages.forEach((message) => {
- revokeBlobUrlsInContent(message.content)
- message._localFile = undefined
- })
- })
- this.messagesByConversation = {}
- this.loadedConversationKeys = []
- this.privateReadMaxIds = {}
- this.privateMessageMaxId = 0
- this.groupMessageMaxId = 0
- this.channelMessageMaxId = 0
- ackMergingPromises.clear()
- },
-
- /** 浠� settings 鍔犺浇娑堟伅娓告爣 */
- async loadMessageCursorList() {
- const db = getDb()
- const [privateMaxId, groupMaxId, channelMaxId] = await Promise.all([
- db.getSetting<number>(StorageKeys.settings.privateMessageMaxId),
- db.getSetting<number>(StorageKeys.settings.groupMessageMaxId),
- db.getSetting<number>(StorageKeys.settings.channelMessageMaxId)
- ])
- this.privateMessageMaxId = privateMaxId || 0
- this.groupMessageMaxId = groupMaxId || 0
- this.channelMessageMaxId = channelMaxId || 0
- },
-
- /** 鏇存柊鍐呭瓨娓告爣 */
- updateMessageCursor(conversationType: number, messageId?: number) {
- if (!messageId) {
- return
- }
- if (conversationType === ImConversationType.PRIVATE && messageId > this.privateMessageMaxId) {
- this.privateMessageMaxId = messageId
- } else if (
- conversationType === ImConversationType.GROUP &&
- messageId > this.groupMessageMaxId
- ) {
- this.groupMessageMaxId = messageId
- } else if (
- conversationType === ImConversationType.CHANNEL &&
- messageId > this.channelMessageMaxId
- ) {
- this.channelMessageMaxId = messageId
- }
- },
-
- /** 鑾峰彇绉佽亰瀵规柟宸茶浣嶇疆缂撳瓨 */
- getPrivateReadMaxId(peerId: number): number | undefined {
- return this.privateReadMaxIds[peerId]
- },
-
- /** 鏇存柊绉佽亰瀵规柟宸茶浣嶇疆缂撳瓨 */
- updatePrivateReadMaxId(peerId: number, maxReadId: null | number = 0): number {
- if (!peerId) {
- return 0
- }
- const nextMaxReadId = maxReadId || 0
- const current = this.getPrivateReadMaxId(peerId)
- if (current !== undefined && nextMaxReadId <= current) {
- return current
- }
- this.privateReadMaxIds = { ...this.privateReadMaxIds, [peerId]: nextMaxReadId }
- return nextMaxReadId
- },
-
- /** 娓呯┖绉佽亰瀵规柟宸茶浣嶇疆缂撳瓨 */
- clearPrivateReadMaxIdCache(): void {
- this.privateReadMaxIds = {}
- },
-
- /** 鏍囪浼氳瘽杩戞湡浣跨敤 */
- touchConversationMessageCache(clientConversationId: string) {
- this.loadedConversationKeys = [
- clientConversationId,
- ...this.loadedConversationKeys.filter((key) => key !== clientConversationId)
- ]
- // 淇濈暀褰撳墠娲昏穬浼氳瘽 + 鏈�杩戞墦寮�杩囩殑浼氳瘽
- const retained = this.loadedConversationKeys.slice(0, MESSAGE_CACHE_RETAIN_CONVERSATION_LIMIT)
- const removed = this.loadedConversationKeys.slice(MESSAGE_CACHE_RETAIN_CONVERSATION_LIMIT)
- this.loadedConversationKeys = retained
- removed.forEach((key) => {
- Reflect.deleteProperty(this.messagesByConversation, key)
- })
- },
-
- /** 鍔犺浇褰撳墠浼氳瘽鏈�杩戞秷鎭� */
- async loadMoreMessageList(
- clientConversationId: string,
- beforeSendTime?: number,
- limit = 50
- ): Promise<Message[]> {
- // 1. 浠� IndexedDB 鍊掑簭璇诲彇涓�椤碉紝杩斿洖鍓嶅凡鎸夋椂闂村崌搴忔帓鍒�
- const list = await getDb().getMessageListByConversation(clientConversationId, {
- beforeSendTime,
- limit
- })
- // 2. 鍚堝苟鍒板唴瀛樼紦瀛橈紝杩囨护宸插瓨鍦ㄧ殑娑堟伅
- const parsed = parseClientConversationId(clientConversationId)
- if (!parsed) {
- return []
- }
- const messages = list.map((message) => buildMessageFromDO(message))
- const existing = this.messagesByConversation[clientConversationId] || []
- const existingKeys = new Set(existing.map((message) => getMessageKey(message, parsed.type)))
- const fresh = messages.filter(
- (message) => !existingKeys.has(getMessageKey(message, parsed.type))
- )
- this.messagesByConversation[clientConversationId] = [...fresh, ...existing].toSorted(
- (messageA, messageB) => (messageA.sendTime || 0) - (messageB.sendTime || 0)
- )
- this.touchConversationMessageCache(clientConversationId)
- return fresh
- },
-
- /** 纭繚浼氳瘽娑堟伅宸插姞杞� */
- async ensureConversationMessageListLoaded(conversation: Conversation) {
- const key = getMessageCacheKey(conversation.type, conversation.targetId)
- if (this.messagesByConversation[key]) {
- this.touchConversationMessageCache(key)
- return
- }
- await this.loadMoreMessageList(key)
- },
-
- /** 鑾峰彇鍐呭瓨娑堟伅鏁扮粍 */
- getMessageList(conversationType: number, targetId: number): Message[] {
- const key = getMessageCacheKey(conversationType, targetId)
- if (!this.messagesByConversation[key]) {
- this.messagesByConversation[key] = []
- }
- this.touchConversationMessageCache(key)
- return this.messagesByConversation[key]
- },
-
- /** 鎸佷箙鍖栨秷鎭褰� */
- async saveMessageRecord(
- message: Message,
- conversationType: number,
- tx?: DbTransaction,
- options?: PersistMessageRecordOptions
- ) {
- const db = getDb()
- const next = buildMessageDO(message, conversationType)
- // 鏈嶅姟绔� key 鏇挎崲 client key
- if (options?.mergeClientRecord && message.id && message.clientMessageId) {
- const existing = await db.getByIndex<MessageDO>(
- 'messages',
- 'clientMessageId',
- message.clientMessageId,
- tx
- )
- if (existing && existing.messageKey !== next.messageKey) {
- await db.delete('messages', existing.messageKey, tx)
- }
- }
- await db.put('messages', next, tx)
- },
-
- /** 淇濆瓨娑堟伅娓告爣 */
- async saveMessageCursor(conversationType: number, messageId?: number, tx?: DbTransaction) {
- await setMessageMaxId(conversationType, messageId, tx)
- this.updateMessageCursor(conversationType, messageId)
- },
-
- /** 搴旂敤鎾ゅ洖鍒板唴瀛� */
- applyRecallMessageInMemory(
- conversationType: number,
- targetId: number,
- recallSignalContent: string
- ) {
- // 1. 瀹氫綅琚挙鍥炵殑鍘熸秷鎭�
- const messageId = parseRecallMessageId(recallSignalContent)
- if (!messageId) {
- return null
- }
- const conversationStore = useConversationStore()
- const conversation = conversationStore.getConversation(conversationType, targetId)
- if (!conversation) {
- return null
- }
- const messages = this.getMessageList(conversationType, targetId)
- const message = messages.find((item) => item.id === messageId)
- if (!message) {
- return null
- }
- // 2. 鏇存柊娑堟伅鍜屼細璇濇憳瑕�
- message.type = ImContentType.RECALL
- message.status = ImMessageStatus.RECALL
- message.content = ''
- if (messages[messages.length - 1]?.id === messageId) {
- recomputeConversationLast(conversation, messages)
- }
- return { conversation, message }
- },
-
- /** 鎵归噺鍐欏叆鎷夊彇娑堟伅 */
- async applyPulledMessageList(
- pulledMessages: PulledMessage[],
- conversationType: number,
- maxMessageId?: number
- ) {
- if (pulledMessages.length === 0) {
- // 1. 绌烘壒娆″彧鎺ㄨ繘娓告爣
- await this.saveMessageCursor(conversationType, maxMessageId)
- return
- }
- const conversationStore = useConversationStore()
- const persistedMessages = new Map<
- string,
- { conversationType: number; mergeClientRecord?: boolean; message: Message; }
- >()
- const changedConversations = new Map<string, Conversation>()
-
- const addChanged = (
- conversation: Conversation,
- message: Message,
- options?: PersistMessageRecordOptions
- ) => {
- const clientConversationId = getClientConversationId(
- conversation.type,
- conversation.targetId
- )
- changedConversations.set(clientConversationId, conversation)
- persistedMessages.set(getMessageKey(message, conversation.type), {
- message,
- conversationType: conversation.type,
- mergeClientRecord: options?.mergeClientRecord
- })
- }
-
- // 1. 鍏堟洿鏂板唴瀛橈紝鏀堕泦闇�瑕佹寔涔呭寲鐨勬秷鎭拰浼氳瘽
- for (const pulledMessage of pulledMessages) {
- if (pulledMessage.kind === 'recall') {
- // 1.1 鎾ゅ洖淇″彿鏇存柊鍘熸秷鎭�
- const changed = this.applyRecallMessageInMemory(
- pulledMessage.conversationType,
- pulledMessage.targetId,
- pulledMessage.recallSignalContent
- )
- if (changed) {
- addChanged(changed.conversation, changed.message)
- }
- continue
- }
-
- const { conversationInfo } = pulledMessage
- const hasServerClientMessageId = !!pulledMessage.message.clientMessageId
- const message = ensureClientMessageId(pulledMessage.message)
- // 1.2 纭繚浼氳瘽鍜屾秷鎭紦瀛樺瓨鍦�
- const conversation = conversationStore.ensureConversation(conversationInfo)
- const messages = this.getMessageList(conversationInfo.type, conversationInfo.targetId)
- const existingIndex = messages.findIndex((existing) => isSameMessage(existing, message))
- if (existingIndex !== -1) {
- const existing = messages[existingIndex]
- if (!existing) {
- continue
- }
- // 1.3 宸插瓨鍦ㄦ秷鎭悎骞舵湇鍔$鐘舵��
- applyServerMessageUpdate(existing, message)
- if (existingIndex === messages.length - 1) {
- recomputeConversationLast(conversation, messages)
- syncConversationAtFlags(conversation, message)
- }
- addChanged(conversation, existing, {
- mergeClientRecord: hasServerClientMessageId
- })
- continue
- }
-
- // 1.4 鏂版秷鎭洿鏂颁細璇濇憳瑕佸拰鏈鐘舵��
- applyConversationSummary(conversation, message)
- syncConversationAtFlags(conversation, message)
- const isActive =
- conversationStore.activeConversation?.type === conversationInfo.type &&
- conversationStore.activeConversation?.targetId === conversationInfo.targetId
- if (
- !message.selfSend &&
- !isActive &&
- !conversationStore.isMessageCoveredByReadPosition(conversation, message) &&
- isNormalMessage(message.type) &&
- message.status !== ImMessageStatus.RECALL
- ) {
- conversation.unreadCount++
- }
-
- // 1.5 鏂版秷鎭寜鏈嶅姟绔� id 鎻掑叆鍐呭瓨鍒楄〃
- let insertIndex = messages.length
- if (message.id) {
- for (const [index, existing] of messages.entries()) {
- if (existing.id && message.id < existing.id) {
- insertIndex = index
- break
- }
- }
- }
- messages.splice(insertIndex, 0, message)
- addChanged(conversation, message, {
- mergeClientRecord: hasServerClientMessageId && !!message.id
- })
- }
-
- // 2. 鍗曚簨鍔″啓鍏ユ秷鎭�佷細璇濇憳瑕佸拰娓告爣
- await getDb().transaction(
- ['messages', 'conversations', 'settings'],
- 'readwrite',
- async (tx) => {
- // 2.1 鍐欏叆鏈壒鍙樻洿娑堟伅
- for (const item of persistedMessages.values()) {
- await this.saveMessageRecord(item.message, item.conversationType, tx, {
- mergeClientRecord: item.mergeClientRecord
- })
- }
- // 2.2 鍐欏叆鏈壒鍙樻洿浼氳瘽
- await conversationStore.saveConversationRecord([...changedConversations.values()], tx)
- // 2.3 鍐欏叆鏈壒娓告爣
- await setMessageMaxId(conversationType, maxMessageId, tx)
- }
- )
- // 3. 鎸佷箙鍖栨垚鍔熷悗鎺ㄨ繘鍐呭瓨娓告爣
- this.updateMessageCursor(conversationType, maxMessageId)
- for (const item of persistedMessages.values()) {
- this.updateMessageCursor(item.conversationType, item.message.id)
- }
- },
-
- /** 鎻掑叆娑堟伅 */
- insertMessage(
- conversationInfo: MessageConversationInfo,
- messageInfo: Message,
- options?: { saveMaxId?: boolean }
- ): Promise<void> {
- const conversationStore = useConversationStore()
- const hasIncomingClientMessageId = !!messageInfo.clientMessageId
- const message = ensureClientMessageId(messageInfo)
- // 1. 鍏堝鐞嗘秷鎭甫鏉ョ殑缇よ祫鏂欏彉鏇�
- if (conversationInfo.type === ImConversationType.GROUP && isGroupNotification(message.type)) {
- useGroupStore().applyGroupNotification(
- conversationInfo.targetId,
- message.type,
- message.content
- )
- }
-
- // 2. 纭繚浼氳瘽鍜屾秷鎭紦瀛樺瓨鍦�
- const conversation = conversationStore.ensureConversation(conversationInfo)
- const messages = this.getMessageList(conversationInfo.type, conversationInfo.targetId)
- const existingIndex = messages.findIndex((item) => isSameMessage(item, message))
- // 3. 宸插瓨鍦ㄦ秷鎭蛋瑕嗙洊鏇存柊
- if (existingIndex !== -1) {
- const existing = messages[existingIndex]
- if (!existing) {
- return Promise.resolve()
- }
- applyServerMessageUpdate(existing, message)
- if (existingIndex === messages.length - 1) {
- recomputeConversationLast(conversation, messages)
- syncConversationAtFlags(conversation, message)
- }
- return getDb()
- .transaction(['messages', 'conversations', 'settings'], 'readwrite', async (tx) => {
- await this.saveMessageRecord(existing, conversationInfo.type, tx, {
- mergeClientRecord: hasIncomingClientMessageId
- })
- await conversationStore.saveConversationRecord(conversation, tx)
- if (options?.saveMaxId !== false) {
- await setMessageMaxId(conversationInfo.type, message.id, tx)
- }
- })
- .catch((error) => {
- console.error('[IM messageStore] 娑堟伅鍐欏叆澶辫触', error)
- throw error
- })
- .then(() => {
- this.updateMessageCursor(conversationInfo.type, message.id)
- })
- }
-
- // 4. 鏂版秷鎭洿鏂颁細璇濇憳瑕佸拰鏈鐘舵��
- applyConversationSummary(conversation, message)
- syncConversationAtFlags(conversation, message)
-
- const isActive =
- conversationStore.activeConversation?.type === conversationInfo.type &&
- conversationStore.activeConversation?.targetId === conversationInfo.targetId
- if (
- !message.selfSend &&
- !isActive &&
- !conversationStore.isMessageCoveredByReadPosition(conversation, message) &&
- isNormalMessage(message.type) &&
- message.status !== ImMessageStatus.RECALL
- ) {
- conversation.unreadCount++
- }
-
- // 5. 鏂版秷鎭寜 id 鎻掑叆鍒板唴瀛樻暟缁�
- let insertIndex = messages.length
- if (message.id) {
- for (const [index, existing] of messages.entries()) {
- if (existing.id && message.id < existing.id) {
- insertIndex = index
- break
- }
- }
- }
- messages.splice(insertIndex, 0, message)
- // 6. 鍗曚簨鍔″啓鍏ユ秷鎭�佷細璇濇憳瑕佸拰娓告爣
- return getDb()
- .transaction(['messages', 'conversations', 'settings'], 'readwrite', async (tx) => {
- await this.saveMessageRecord(message, conversationInfo.type, tx, {
- mergeClientRecord: hasIncomingClientMessageId && !!message.id
- })
- await conversationStore.saveConversationRecord(conversation, tx)
- if (options?.saveMaxId !== false) {
- await setMessageMaxId(conversationInfo.type, message.id, tx)
- }
- })
- .catch((error) => {
- console.error('[IM messageStore] 娑堟伅鍐欏叆澶辫触', error)
- throw error
- })
- .then(() => {
- this.updateMessageCursor(conversationInfo.type, message.id)
- })
- },
-
- /** ack 鍚堝苟 */
- ackMessage(
- conversationType: number,
- targetId: number,
- clientMessageId: string,
- updates: Partial<Message>
- ) {
- const mergeKey = `${conversationType}:${targetId}:${clientMessageId}`
- const existingPromise = ackMergingPromises.get(mergeKey)
- if (existingPromise) {
- return existingPromise
- }
- const promise = this.doAckMessage(
- conversationType,
- targetId,
- clientMessageId,
- updates
- ).finally(() => {
- ackMergingPromises.delete(mergeKey)
- })
- ackMergingPromises.set(mergeKey, promise)
- return promise
- },
-
- /** 鎵ц ack 鍚堝苟 */
- async doAckMessage(
- conversationType: number,
- targetId: number,
- clientMessageId: string,
- updates: Partial<Message>
- ) {
- // 1. 瀹氫綅寰呭悎骞舵秷鎭�
- const conversationStore = useConversationStore()
- const conversation = conversationStore.getConversation(conversationType, targetId)
- if (!conversation) {
- return
- }
- const messages = this.getMessageList(conversationType, targetId)
- const message = messages.find((item) => item.clientMessageId === clientMessageId)
- if (!message) {
- return
- }
- message._ackMerging = true
- try {
- // 2. 鍚堝苟鏈嶅姟绔� ack 鍒板唴瀛�
- applyServerMessageUpdate(message, updates)
- if (messages[messages.length - 1] === message) {
- recomputeConversationLast(conversation, messages)
- }
- // 3. 鍗曚簨鍔″啓鍏ユ秷鎭�佷細璇濇憳瑕佸拰娓告爣
- await getDb()
- .transaction(['messages', 'conversations', 'settings'], 'readwrite', async (tx) => {
- await this.saveMessageRecord(message, conversationType, tx, {
- mergeClientRecord: true
- })
- await conversationStore.saveConversationRecord(conversation, tx)
- await setMessageMaxId(conversationType, message.id, tx)
- })
- .catch((error) => {
- console.error('[IM messageStore] ack 鍐欏叆澶辫触', error)
- throw error
- })
- this.updateMessageCursor(conversationType, message.id)
- } finally {
- // 4. 娓呯悊鍚堝苟鏍囪
- message._ackMerging = false
- }
- },
-
- /** 灞�閮ㄦ洿鏂版秷鎭� */
- patchMessage(
- conversationType: number,
- targetId: number,
- clientMessageId: string,
- patch: Partial<Message>
- ) {
- const message = this.getMessageList(conversationType, targetId).find(
- (item) => item.clientMessageId === clientMessageId
- )
- if (!message) {
- return
- }
- let changed = false
- for (const key in patch) {
- if (
- Object.prototype.hasOwnProperty.call(patch, key) &&
- (patch as Record<string, unknown>)[key] !==
- (message as unknown as Record<string, unknown>)[key]
- ) {
- changed = true
- break
- }
- }
- if (changed) {
- applyServerMessageUpdate(message, patch)
- }
- },
-
- /** 鎾ゅ洖娑堟伅 */
- async recallMessage(
- conversationType: number,
- targetId: number,
- recallSignalContent: string
- ): Promise<void> {
- const conversationStore = useConversationStore()
- const changed = this.applyRecallMessageInMemory(
- conversationType,
- targetId,
- recallSignalContent
- )
- if (!changed) {
- return
- }
- await getDb()
- .transaction(['messages', 'conversations'], 'readwrite', async (tx) => {
- await this.saveMessageRecord(changed.message, conversationType, tx)
- await conversationStore.saveConversationRecord(changed.conversation, tx)
- })
- .catch((error) => {
- console.error('[IM messageStore] 鎾ゅ洖娑堟伅鍐欏叆澶辫触', error)
- throw error
- })
- },
-
- /** 搴旂敤宸茶鍥炴墽 */
- applyMessageReadReceipt(options: {
- conversationType: number
- groupMessageId?: number
- privateReadMaxId?: number
- readCount?: number
- receiptStatus?: number
- targetId: number
- }) {
- const messages = this.getMessageList(options.conversationType, options.targetId)
- const changed: Message[] = []
- // 1. 绉佽亰鍥炴墽鎵归噺鏇存柊鑷繁鍙戦�佺殑娑堟伅
- if (options.conversationType === ImConversationType.PRIVATE && options.privateReadMaxId) {
- this.updatePrivateReadMaxId(options.targetId, options.privateReadMaxId)
- const privateReadMaxId = options.privateReadMaxId
- messages.forEach((message) => {
- if (
- message.selfSend &&
- message.id &&
- message.id <= privateReadMaxId &&
- message.receiptStatus === ImMessageReceiptStatus.PENDING
- ) {
- message.receiptStatus = ImMessageReceiptStatus.DONE
- changed.push(message)
- }
- })
- } else if (options.conversationType === ImConversationType.GROUP && options.groupMessageId) {
- // 2. 缇よ亰鍥炴墽鏇存柊鍗曟潯娑堟伅
- const message = messages.find((item) => item.id === options.groupMessageId)
- if (message) {
- if (options.readCount !== undefined) {
- message.readCount = options.readCount
- }
- if (options.receiptStatus !== undefined) {
- message.receiptStatus = options.receiptStatus
- }
- changed.push(message)
- }
- }
- if (changed.length === 0) {
- return
- }
- // 3. 鍗曚簨鍔″啓鍏ュ彉鏇存秷鎭�
- void getDb()
- .transaction(['messages'], 'readwrite', async (tx) => {
- for (const message of changed) {
- await this.saveMessageRecord(message, options.conversationType, tx)
- }
- })
- .catch((error) => console.warn('[IM messageStore] 鍥炴墽鍐欏叆澶辫触', error))
- },
-
- /** 鍓嶇疆鍘嗗彶娑堟伅 */
- prependMessageList(conversationType: number, targetId: number, earlierMessages: Message[]) {
- if (earlierMessages.length === 0) {
- return
- }
- const messages = this.getMessageList(conversationType, targetId)
- const existingIds = new Set(messages.map((message) => message.id).filter(Boolean))
- const fresh = earlierMessages
- .map((message) => ensureClientMessageId(message))
- .filter((message) => message.id && !existingIds.has(message.id))
- .toSorted((messageA, messageB) => (messageA.id || 0) - (messageB.id || 0))
- if (fresh.length === 0) {
- return
- }
- const key = getMessageCacheKey(conversationType, targetId)
- this.messagesByConversation[key] = [...fresh, ...messages]
- void getDb()
- .transaction(['messages'], 'readwrite', async (tx) => {
- for (const message of fresh) {
- await this.saveMessageRecord(message, conversationType, tx)
- }
- })
- .catch((error) => console.warn('[IM messageStore] 鍘嗗彶娑堟伅鍐欏叆澶辫触', error))
- },
-
- /** 鍒犻櫎鍗曟潯娑堟伅 */
- removeMessage(
- conversationType: number,
- targetId: number,
- key: { clientMessageId?: string; id?: number; }
- ) {
- // 1. 瀹氫綅浼氳瘽鍜屾秷鎭�
- const conversationStore = useConversationStore()
- const conversation = conversationStore.getConversation(conversationType, targetId)
- if (!conversation) {
- return
- }
- const messages = this.getMessageList(conversationType, targetId)
- const index = messages.findIndex((message) => {
- if (key.id && message.id && message.id === key.id) {
- return true
- }
- return !!key.clientMessageId && message.clientMessageId === key.clientMessageId
- })
- if (index === -1) {
- return
- }
- // 2. 浠庡唴瀛樼Щ闄ゆ秷鎭�
- const [removed] = messages.splice(index, 1)
- if (!removed) {
- return
- }
- revokeBlobUrlsInContent(removed.content)
- if (index === messages.length) {
- recomputeConversationLast(conversation, messages)
- }
- // 3. 鍒犻櫎鏈湴璁板綍骞朵繚瀛樹細璇濇憳瑕�
- getDb()
- .delete('messages', getMessageKey(removed, conversationType))
- .catch((error) => console.warn('[IM messageStore] 娑堟伅鍒犻櫎澶辫触', error))
- conversationStore.saveConversation(conversation)
- },
-
- /** 鍒犻櫎浼氳瘽鍏ㄩ儴娑堟伅 */
- deleteConversationMessageList(conversationType: number, targetId: number) {
- // 1. 娓呯悊鍐呭瓨娑堟伅鍜屽獟浣撹祫婧�
- const clientConversationId = getClientConversationId(conversationType, targetId)
- const messages = this.messagesByConversation[clientConversationId] || []
- messages.forEach((message) => {
- revokeBlobUrlsInContent(message.content)
- message._localFile = undefined
- })
- Reflect.deleteProperty(this.messagesByConversation, clientConversationId)
- this.loadedConversationKeys = this.loadedConversationKeys.filter(
- (key) => key !== clientConversationId
- )
- // 2. 鍒犻櫎 IndexedDB 娑堟伅
- getDb()
- .deleteByIndex('messages', 'clientConversationId', clientConversationId)
- .catch((error) => console.warn('[IM messageStore] 浼氳瘽娑堟伅鍒犻櫎澶辫触', error))
- }
- }
-})
-
-export const useMessageStoreWithOut = () => useMessageStore()
-
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useMessageStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/rtcStore.ts b/src/views/im/home/store/rtcStore.ts
deleted file mode 100644
index 46a98b9..0000000
--- a/src/views/im/home/store/rtcStore.ts
+++ /dev/null
@@ -1,450 +0,0 @@
-import type { ImRtcApi } from '#/api/im/rtc'
-
-import { computed, ref } from 'vue'
-
-import { defineStore } from 'pinia'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import {
- ImConversationType,
- type ImRtcCallEndReasonValue,
- ImRtcCallStage,
- type ImRtcCallStageValue,
- ImRtcCallStatus,
- type ImRtcParticipantStatusValue
-} from '../../utils/constants'
-import { useFriendStore } from './friendStore'
-import { useGroupStore } from './groupStore'
-
-type GroupActiveCallCache = {
- participantsLoaded?: boolean // 鏄惁宸叉媺鍙栧畬鏁村弬涓庤�呭垪琛�
-} & ImRtcApi.RtcGroupCallRespVO
-
-// RTC_CALL 閫氳瘽淇′护杞借嵎锛涙寜 status 鍖哄垎瀛愮被鍨嬭涔�
-export interface ImRtcCallNotification {
- status: ImRtcParticipantStatusValue
- room: string
- conversationType: number
- mediaType: number
- groupId?: number
- // INVITE 涓撳睘锛氳鍙帴閫氶渶瑕佺殑 LiveKit 杩炴帴鍙傛暟 + 涓诲彨灞曠ず淇℃伅
- livekitUrl?: string
- token?: string
- inviterUserId?: number
- inviterNickname?: string
- inviterAvatar?: string
- // INVITE 涓撳睘锛氭湰娆¤閭�璇蜂汉鍒楄〃锛涘寘鍚敹浠朵汉鑷韩锛屽墠绔潵鐢靛皬鏉℃寜闇�杩囨护灞曠ず銆岄個璇风殑鍏朵粬浜恒��
- inviteeIds?: number[]
- // REJECT 涓撳睘锛氭搷浣滆�呭睍绀轰俊鎭紙鍏跺畠瀛愮被鍨嬭蛋 RTC_CALL_END锛�
- operatorUserId?: number
- operatorNickname?: string
- operatorAvatar?: string
-}
-
-// RTC_PARTICIPANT_CONNECTED 閫氳瘽鍙備笌鑰呭姞鍏ヨ浇鑽凤紙LiveKit webhook 杞帹锛�
-export interface ImRtcParticipantConnectedNotification {
- room: string
- userId: number
- conversationType: number
- groupId?: number
- // 缇よ亰鍦烘櫙闈為個璇锋垚鍛橀娆″~鍏呰兌鍥婃潯鐢�
- mediaType?: number
- inviterUserId?: number
-}
-
-// RTC_PARTICIPANT_DISCONNECTED 閫氳瘽鍙備笌鑰呯寮�杞借嵎锛圠iveKit webhook 杞帹锛�
-export interface ImRtcParticipantDisconnectedNotification {
- room: string
- userId: number
- conversationType: number
- groupId?: number
-}
-
-// RTC_CALL_END 閫氳瘽缁撴潫杞借嵎锛堝叆娑堟伅娴侊紱绉佽亰娓叉煋娑堟伅姘旀场锛岀兢鑱婃覆鏌撶郴缁熸彁绀鸿锛�
-export interface ImRtcCallEndNotification {
- room: string
- conversationType: number
- mediaType: number
- endReason: ImRtcCallEndReasonValue
- durationSeconds?: number
- // 鎿嶄綔鑰呰仛鍚堝瓧娈碉細HANGUP/CANCEL/REJECT 瑙﹀彂浜猴紱webhook 鍏滃簳涓� null
- operatorUserId?: number
- operatorNickname?: string
- operatorAvatar?: string
-}
-
-export const useRtcStore = defineStore('imRtc', () => {
- /** 褰撳墠闃舵 */
- const stage = ref<ImRtcCallStageValue>(ImRtcCallStage.IDLE)
- /** 褰撳墠閫氳瘽锛沬nvite / accept / refreshToken 鎷垮埌鐨勫畬鏁翠俊鎭� */
- const call = ref<ImRtcApi.RtcCallRespVO | null>(null)
- /** 鏉ョ數杞借嵎锛涗粎 INCOMING 闃舵浣跨敤锛泂tatus 鍥哄畾 INVITING锛屽叾瀹冨瓧娈� INVITE 涓撳睘 */
- const incomingPayload = ref<ImRtcCallNotification | null>(null)
- /** 杩涘叆 RUNNING 鐨勬椂闂存埑锛涚敤浜庨�氳瘽鏃堕暱灞曠ず锛況eset 鏃舵竻闆� */
- const startedAt = ref(0)
-
- /** 鏄惁澶勪簬閫氳瘽鐩稿叧闃舵 */
- const isActive = computed(() => stage.value !== ImRtcCallStage.IDLE)
-
- /**
- * 瀵圭灞曠ず鍚嶏紱鎸夐樁娈� + 浼氳瘽绫诲瀷鍒嗘敮锛�
- * INCOMING 鍙栨潵鐢佃浇鑽风殑 inviterNickname锛涚兢閫氳瘽鍙栫兢鍚嶏紱绉佽亰鏌� friendStore 鍙嶆煡瀵圭 userId
- */
- const peerNickname = computed<string>(() => {
- if (stage.value === ImRtcCallStage.INCOMING) {
- return incomingPayload.value?.inviterNickname || ''
- }
- const c = call.value
- if (!c) return ''
- if (c.conversationType === ImConversationType.GROUP) {
- return useGroupStore().getGroup(c.groupId ?? 0)?.name || ''
- }
- const peerUserId = resolvePrivatePeerUserId(c)
- return (peerUserId && useFriendStore().getFriend(peerUserId)?.nickname) || ''
- })
-
- /** 瀵圭澶村儚锛涚瓥鐣ュ悓 peerNickname */
- const peerAvatar = computed<string>(() => {
- if (stage.value === ImRtcCallStage.INCOMING) {
- return incomingPayload.value?.inviterAvatar || ''
- }
- const c = call.value
- if (!c) return ''
- if (c.conversationType === ImConversationType.GROUP) {
- return useGroupStore().getGroup(c.groupId ?? 0)?.avatar || ''
- }
- const peerUserId = resolvePrivatePeerUserId(c)
- return (peerUserId && useFriendStore().getFriend(peerUserId)?.avatar) || ''
- })
-
- /** 绉佽亰鍦烘櫙瀵圭 userId锛氳嚜宸辨槸涓诲彨鍒欏彇棣栦釜 invitee锛屽惁鍒欏彇 inviter */
- function resolvePrivatePeerUserId(c: ImRtcApi.RtcCallRespVO): number | undefined {
- const myId = getCurrentUserId()
- return c.inviterId === myId ? c.inviteeIds?.[0] : c.inviterId
- }
-
- /** 缇ゆ椿璺冮�氳瘽绱㈠紩锛沢roupId -> 缇ら�氳瘽鎽樿锛涚敤浜庣兢鑱婇《閮ㄨ兌鍥婃潯 */
- const groupActiveCalls = ref<Map<number, GroupActiveCallCache>>(new Map())
-
- /**
- * 宸查��鍑� / 宸叉嫆缁濈殑鐢ㄦ埛缂栧彿闆嗗悎锛涚兢閫氳瘽鍦烘櫙鍐� pending 鍗犱綅娓叉煋鏃舵帓闄わ紱
- * 鏉ユ簮锛氬弬涓庤�呯寮�閫氱煡 + 缇ら�氳瘽鍗曚汉鎷掔粷鐨� operatorUserId锛涢�氳瘽缁撴潫锛坮eset锛夋椂娓呯┖
- */
- const leftUserIds = ref<Set<number>>(new Set())
-
- /** 鏄惁宸茶褰曟煇 userId 宸查��鍑� / 鎷掔粷 */
- function isUserLeft(userId: number): boolean {
- return leftUserIds.value.has(userId)
- }
-
- /** 鏍囪鏌愪釜 userId 宸查��鍑� / 鎷掔粷锛涚敤浜� pending 鍗犱綅娓叉煋鏃舵帓闄� */
- function markUserLeft(userId: number) {
- if (!userId || leftUserIds.value.has(userId)) {
- return
- }
- leftUserIds.value = new Set([...leftUserIds.value, userId])
- }
-
- /**
- * 涓诲彨鍙戣捣閫氳瘽锛涙寜浼氳瘽绫诲瀷 + status 鍐冲畾 stage锛�
- * 缇ら�氳瘽锛氬彂璧蜂汉鐩存帴杩� RUNNING 澶氫汉鍗$墖瑙嗗浘锛屾埧鍐呭彲鑳藉彧鏈夎嚜宸憋紝绛夊叾浠栦汉闄嗙画鍔犲叆锛�
- * 绉佽亰锛氭寜 status 璧帮紱RUNNING锛堝凡鍔犲叆宸叉湁閫氳瘽鍦烘櫙锛夆啋 RUNNING锛汣REATED 鈫� INVITING 绛夎鍙帴閫�
- */
- function startInviting(data: ImRtcApi.RtcCallRespVO) {
- call.value = data
- // 缇ら�氳瘽鍦烘櫙鍐欏叆鏈湴鑳跺泭鏉$紦瀛�
- syncGroupActiveCall(data)
- // 鏇存柊 stage 鐘舵��
- if (data.conversationType === ImConversationType.GROUP) {
- stage.value = ImRtcCallStage.RUNNING
- startedAt.value = Date.now()
- return
- }
- const running = data.status === ImRtcCallStatus.RUNNING
- stage.value = running ? ImRtcCallStage.RUNNING : ImRtcCallStage.INVITING
- if (running) {
- startedAt.value = Date.now()
- }
- }
-
- /** 琚彨鏀跺埌鏉ョ數锛涘垏鍒� INCOMING锛涙帴鏀� RTC_CALL(INVITE) payload */
- function showIncoming(payload: ImRtcCallNotification) {
- if (isActive.value) {
- return
- }
- incomingPayload.value = payload
- stage.value = ImRtcCallStage.INCOMING
- // 鎸� inviter 鍏滃簳棣栨濉厖鑳跺泭鏉�
- syncGroupActiveCall({
- conversationType: payload.conversationType,
- room: payload.room,
- groupId: payload.groupId,
- mediaType: payload.mediaType,
- inviterId: payload.inviterUserId ?? 0,
- joinedUserIds: payload.inviterUserId ? [payload.inviterUserId] : [],
- inviteeIds: payload.inviteeIds
- })
- }
-
- /** 杩涘叆閫氳瘽涓樁娈� */
- function enterRunning(data: ImRtcApi.RtcCallRespVO) {
- call.value = data
- // 绂诲紑 INCOMING 闃舵锛涙竻绌烘潵鐢佃浇鑽�
- incomingPayload.value = null
- stage.value = ImRtcCallStage.RUNNING
- startedAt.value = Date.now()
- // 鎺ラ�氬悗鐢� RespVO 瀹屾暣瑕嗙洊鑳跺泭鏉�
- syncGroupActiveCall(data)
- }
-
- /**
- * 缇ら�氳瘽鍦烘櫙鍚屾鏈湴 groupActiveCalls 缂撳瓨锛涢潪缇ら�氳瘽鎴栫己 groupId 鐩存帴杩斿洖锛�
- * 涓嶄緷璧栧悗绔� webhook 鎺ㄩ�佺殑 RTC_PARTICIPANT_CONNECTED 棣栨濉厖锛岄伩鍏嶈兌鍥婃潯鍑虹幇寤惰繜锛�
- * 琚彨鍦烘櫙閫氱煡杞借嵎鏃� joinedUserIds锛岃皟鐢ㄦ柟鎸変富鍙汉鍏滃簳锛屽悗缁� getActiveCall / 鍙備笌鑰呬簨浠跺埛鏂版垚瀹屾暣鍒楄〃
- */
- function syncGroupActiveCall(input: {
- conversationType: number
- groupId?: number
- inviteeIds?: number[]
- inviterId: number
- joinedUserIds?: number[]
- mediaType: number
- room: string
- }) {
- if (input.conversationType !== ImConversationType.GROUP || !input.groupId) {
- return
- }
- // 鍐欏叆鎴栨洿鏂扮兢娲昏穬閫氳瘽缂撳瓨
- setGroupCall({
- room: input.room,
- groupId: input.groupId,
- mediaType: input.mediaType,
- inviterId: input.inviterId,
- joinedUserIds: input.joinedUserIds ?? [],
- inviteeIds: input.inviteeIds ?? []
- })
- }
-
- /** 閲嶇疆锛涢�氳瘽缁撴潫缁熶竴璋冪敤 */
- function reset() {
- stage.value = ImRtcCallStage.IDLE
- call.value = null
- incomingPayload.value = null
- startedAt.value = 0
- leftUserIds.value = new Set()
- }
-
- /** 閫氳瘽涓拷鍔犺閭�璇蜂汉锛涜 participants 缃戞牸鍑虹幇 pending 鍗犱綅銆佽兌鍥婃潯鍚屾鏇存柊 */
- function appendInvitees(userIds: number[]) {
- if (!call.value || userIds.length === 0) {
- return
- }
- const existing = call.value.inviteeIds ?? []
- const merged = [...new Set([...existing, ...userIds])]
- if (merged.length === existing.length) {
- return
- }
- call.value = { ...call.value, inviteeIds: merged }
- syncGroupActiveCall(call.value)
- }
-
- // ==================== 缇ら�氳瘽鑳跺泭鏉$姸鎬� ====================
-
- /**
- * 缇ら�氳瘽寮�濮� / 鐘舵�佸埛鏂帮細鍐欏叆 / 鏇存柊 groupActiveCalls锛涘睍绀虹敤銆岃兌鍥婃潯銆嶏紱
- * 鎴垮唴鎴愬憳鍚屾浜ょ粰 LiveKit 瀹㈡埛绔簨浠讹紙ParticipantConnected / Disconnected锛夛紱
- * 鑳跺泭鏉′笉瀹炴椂鍒锋柊 joinedUserIds / inviteeIds锛屽睍寮� / 鍔犲叆鏃跺啀璧� getActiveCall 鎺ュ彛鎷夋渶鏂�
- */
- function setGroupCall(payload: ImRtcApi.RtcGroupCallRespVO, participantsLoaded?: boolean) {
- if (!payload?.groupId) {
- return
- }
- useGroupStore().markGroupActiveCallLoaded(payload.groupId)
- // 娴呮瘮杈冿細room / mediaType / joinedUserIds / inviteeIds 閮芥病鍙樺氨璺宠繃锛岄伩鍏嶄笅娓� watcher 鏃犳剰涔夐噸绠�
- const existing = groupActiveCalls.value.get(payload.groupId)
- const nextParticipantsLoaded =
- participantsLoaded ?? (existing?.room === payload.room && !!existing.participantsLoaded)
- if (
- existing &&
- isSameGroupCall(existing, payload) &&
- !!existing.participantsLoaded === nextParticipantsLoaded
- ) {
- return
- }
- const newGroupActiveCalls = new Map(groupActiveCalls.value)
- newGroupActiveCalls.set(payload.groupId, {
- ...payload,
- participantsLoaded: nextParticipantsLoaded
- })
- groupActiveCalls.value = newGroupActiveCalls
- }
-
- /** 娓呯┖鎸囧畾缇ょ殑閫氳瘽缂撳瓨 */
- function clearGroupCallCache(groupId?: number) {
- if (!groupId) {
- groupActiveCalls.value = new Map()
- return
- }
- const next = new Map(groupActiveCalls.value)
- next.delete(groupId)
- groupActiveCalls.value = next
- }
-
- /** 鍒ゆ柇缇ら�氳瘽鏄惁宸茶ˉ榻� */
- function isGroupCallParticipantsLoaded(groupId: number, room?: string): boolean {
- const call = groupActiveCalls.value.get(groupId)
- return (
- !!groupId &&
- !!room &&
- !!call &&
- call.room === room &&
- !!call.participantsLoaded
- )
- }
-
- /** 涓ゆ潯缇ら�氳瘽鎽樿鍐呭鐩哥瓑锛坮oom / mediaType / inviterId / 涓や釜 userId 鏁扮粍閫愰」鐩哥瓑锛� */
- function isSameGroupCall(a: ImRtcApi.RtcGroupCallRespVO, b: ImRtcApi.RtcGroupCallRespVO): boolean {
- if (a.room !== b.room || a.mediaType !== b.mediaType || a.inviterId !== b.inviterId) {
- return false
- }
- return isSameNumberList(a.joinedUserIds ?? [], b.joinedUserIds ?? []) &&
- isSameNumberList(a.inviteeIds ?? [], b.inviteeIds ?? [])
- }
-
- /** 鍒ゆ柇涓や釜鐢ㄦ埛缂栧彿鍒楄〃鏄惁閫愰」鐩哥瓑 */
- function isSameNumberList(a: number[], b: number[]): boolean {
- if (a.length !== b.length) {
- return false
- }
- return a.every((item, index) => item === b[index])
- }
-
- /** 缇ら�氳瘽缁撴潫锛氫粠 groupActiveCalls 绉婚櫎锛涜兌鍥婃潯娑堝け */
- function removeGroupCall(groupId: number, room?: string) {
- if (!groupId) {
- return
- }
- const existing = groupActiveCalls.value.get(groupId)
- if (room && existing?.room !== room) {
- return
- }
- clearGroupCallCache(groupId)
- useGroupStore().markGroupActiveCallLoaded(groupId)
- }
-
- /** 鑾峰彇缇ゅ綋鍓嶆椿璺冮�氳瘽锛涚敤浜庤兌鍥婃潯鎸� groupId 鏌ヨ */
- function getGroupCall(groupId: number): ImRtcApi.RtcGroupCallRespVO | undefined {
- return groupActiveCalls.value.get(groupId)
- }
-
- /** 閫氳瘽鍙備笌鑰呭姞鍏ワ細鎶� userId 鍔犺繘 joinedUserIds锛涚兢鑱婂満鏅棤娲昏穬璁板綍鏃堕娆″~鍏呰兌鍥婃潯 */
- function applyParticipantConnected(payload: ImRtcParticipantConnectedNotification) {
- const isGroup = payload.conversationType === ImConversationType.GROUP
- if (!isGroup || !payload.groupId) {
- return
- }
- // 鑳跺泭鏉℃噿濉厖锛氭湰绔彲鑳藉湪閫氳瘽寮�濮嬪悗鎵嶆墦寮�璇ョ兢浼氳瘽锛屾病鏀跺埌杩� setGroupCall锛�
- // 姝ゅ鐢ㄥ姞鍏ラ�氱煡寤轰竴鏉℃渶灏忚褰曪紝inviteeIds 鐣欑┖锛屽睍寮� popover / 鍔犲叆鏃跺啀璧� getActiveCall 琛�
- const existing = groupActiveCalls.value.get(payload.groupId)
- if (!existing) {
- setGroupCall({
- room: payload.room,
- groupId: payload.groupId,
- mediaType: payload.mediaType ?? 0,
- inviterId: payload.inviterUserId ?? 0,
- joinedUserIds: [payload.userId],
- inviteeIds: []
- })
- return
- }
- if (existing.room !== payload.room) {
- return
- }
- const joined = existing.joinedUserIds ?? []
- if (joined.includes(payload.userId)) {
- return
- }
- setGroupCall({ ...existing, joinedUserIds: [...joined, payload.userId] })
- }
-
- /** 閫氳瘽鍙備笌鑰呯寮�锛氫粠 joinedUserIds 绉婚櫎锛涘悓鏃舵爣璁� leftUserIds锛坧ending 鍗犱綅娓叉煋鎺掗櫎锛� */
- function applyParticipantDisconnected(payload: ImRtcParticipantDisconnectedNotification) {
- markUserLeft(payload.userId)
- const isGroup = payload.conversationType === ImConversationType.GROUP
- if (!isGroup || !payload.groupId) {
- return
- }
- dropFromGroupActiveCall(payload.groupId, payload.room, payload.userId)
- }
-
- /** 缇ら�氳瘽鍗曚汉鎷掔粷閭�璇凤細鏍囪 leftUserIds + 浠庤兌鍥婃潯 inviteeIds 绉婚櫎锛堢鑱婃嫆缁濊蛋 RTC_CALL_END锛屼笉鍏ユ湰閫氶亾锛� */
- function applyParticipantRejected(
- payload: Pick<ImRtcCallNotification, 'conversationType' | 'groupId' | 'operatorUserId' | 'room'>
- ) {
- if (!payload.operatorUserId) {
- return
- }
- markUserLeft(payload.operatorUserId)
- if (payload.conversationType === ImConversationType.GROUP && payload.groupId) {
- dropFromGroupActiveCall(payload.groupId, payload.room, payload.operatorUserId)
- }
- }
-
- /** 缇ら�氳瘽鍗曚汉鎸搩瓒呮椂锛涘 banner 鐨勫鐞嗕笌鎷掓帴涓�鑷达紙璇箟鐙珛銆佸疄鐜板叡浜級 */
- function applyParticipantNoAnswer(
- payload: Pick<ImRtcCallNotification, 'conversationType' | 'groupId' | 'operatorUserId' | 'room'>
- ) {
- applyParticipantRejected(payload)
- }
-
- /** 浠庢寚瀹氱兢娲昏穬閫氳瘽鐨� joined / pending 鍒楄〃閲屽悓姝ョЩ闄ゆ煇鐢ㄦ埛锛涚敤浜� disconnect / reject 璁╄兌鍥婃潯涓嶅啀灞曠ず */
- function dropFromGroupActiveCall(groupId: number, room: string, userId: number) {
- const existing = groupActiveCalls.value.get(groupId)
- if (!existing || existing.room !== room) {
- return
- }
- const joined = existing.joinedUserIds ?? []
- const invitee = existing.inviteeIds ?? []
- const nextJoined = joined.filter((id) => id !== userId)
- const nextInvitee = invitee.filter((id) => id !== userId)
- if (nextJoined.length === joined.length && nextInvitee.length === invitee.length) {
- return
- }
- if (nextJoined.length === 0 && nextInvitee.length === 0) {
- removeGroupCall(groupId, room)
- return
- }
- setGroupCall({
- ...existing,
- joinedUserIds: nextJoined,
- inviteeIds: nextInvitee
- })
- }
-
- return {
- stage,
- call,
- incomingPayload,
- peerNickname,
- peerAvatar,
- startedAt,
- isActive,
- startInviting,
- showIncoming,
- enterRunning,
- reset,
- appendInvitees,
- markUserLeft,
- isUserLeft,
- setGroupCall,
- isGroupCallParticipantsLoaded,
- clearGroupCallCache,
- removeGroupCall,
- getGroupCall,
- applyParticipantConnected,
- applyParticipantDisconnected,
- applyParticipantRejected,
- applyParticipantNoAnswer
- }
-})
diff --git a/src/views/im/home/store/uiStore.ts b/src/views/im/home/store/uiStore.ts
deleted file mode 100644
index 93d0b6e..0000000
--- a/src/views/im/home/store/uiStore.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import type { GroupLite, User } from '../types'
-
-import { reactive, ref } from 'vue'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { ImFriendAddSource } from '../../utils/constants'
-
-/**
- * IM 鍏ㄥ眬 UI store
- *
- * 鏀剁撼鏍囧噯锛氳Е鍙戠偣 N 涓�佹寕杞界偣鎯充繚鎸� 1 涓殑娴眰鐘舵�併��
- * 浠绘剰浣嶇疆閮藉彲鑳� open锛屼絾 DOM 涓婂彧鎯崇暀涓�浠藉疄渚� 鈫� 璧� store 娲惧彂锛�
- * 鐢� `index.vue` 鎸備竴涓闃呯粍浠剁粺涓�娓叉煋銆�
- */
-export const useImUiStore = defineStore('imUiStore', () => {
- // ==================== 鐢ㄦ埛鍚嶇墖 UserInfoCard ====================
- // 鐢ㄦ埛鍚嶇墖鎮诞鍗�
-
- const userInfoCard = reactive({
- show: false,
- user: null as null | User,
- position: { x: 0, y: 0 },
- // addSource / addSourceExtra 璺熼殢瑙﹀彂鐐瑰甫鍏ャ�屽姞濂藉弸銆嶆潵婧愶紙缇ゆ垚鍛樺叆鍙� = GROUP + 缇ゅ悕锛涘叾浣欓粯璁ゆ悳绱級
- addSource: ImFriendAddSource.SEARCH as number,
- addSourceExtra: '' as string
- })
-
- /** 鎵撳紑鐢ㄦ埛鍚嶇墖 */
- function openUserInfoCard(
- user: User,
- position: { x: number; y: number },
- addSource: number = ImFriendAddSource.SEARCH,
- addSourceExtra: string = ''
- ) {
- const viewportWidth = document.documentElement.clientWidth
- const viewportHeight = document.documentElement.clientHeight
- userInfoCard.user = user
- userInfoCard.position.x = Math.min(position.x, viewportWidth - 350)
- userInfoCard.position.y = Math.min(position.y, viewportHeight - 220)
- userInfoCard.addSource = addSource
- userInfoCard.addSourceExtra = addSourceExtra
- userInfoCard.show = true
- }
-
- /** 榧犳爣鐐瑰嚮浣嶇疆 + 20px 妯悜鍋忕Щ鎵撳紑鍚嶇墖锛氶伩鍏嶅悕鐗囩洿鎺ヨ鐩栬Е鍙戝厓绱狅紝瀵归綈澶村儚 / 鍚嶇墖娑堟伅绛夌偣鍑讳氦浜掔殑缁熶竴瑙傛劅 */
- function openUserInfoCardAtEvent(
- user: User,
- e: MouseEvent,
- addSource: number = ImFriendAddSource.SEARCH,
- addSourceExtra: string = ''
- ) {
- openUserInfoCard(user, { x: e.clientX + 20, y: e.clientY }, addSource, addSourceExtra)
- }
-
- /** 鍏抽棴鐢ㄦ埛鍚嶇墖 */
- function closeUserInfoCard() {
- userInfoCard.show = false
- }
-
- // ==================== 缇ゅ悕鐗� GroupInfoCard ====================
-
- const groupInfoCard = reactive({
- show: false,
- group: null as GroupLite | null,
- position: { x: 0, y: 0 }
- })
-
- /** 榧犳爣鐐瑰嚮浣嶇疆 + 20px 妯悜鍋忕Щ鎵撳紑缇ゅ悕鐗囷紝瀵归綈 UserInfoCard 鐨勭粺涓�瑙傛劅 */
- function openGroupInfoCardAtEvent(group: GroupLite, e: MouseEvent) {
- const viewportWidth = document.documentElement.clientWidth
- const viewportHeight = document.documentElement.clientHeight
- groupInfoCard.group = group
- groupInfoCard.position.x = Math.min(e.clientX + 20, viewportWidth - 350)
- groupInfoCard.position.y = Math.min(e.clientY, viewportHeight - 220)
- groupInfoCard.show = true
- }
-
- /** 鍏抽棴缇ゅ悕鐗� */
- function closeGroupInfoCard() {
- groupInfoCard.show = false
- }
-
- // ==================== 鍙抽敭鑿滃崟 ContextMenu ====================
- // 鍙抽敭鑿滃崟铏界劧鏄竴涓粍浠舵寕鍦ㄤ富澹充笂锛屼絾鍏惰Е鍙戞椂鏈哄垎鏁e湪鍚勫垪琛�
- interface ContextMenuItem {
- key: string
- name: string
- disabled?: boolean
- divided?: boolean // 鏄惁鍦ㄨ椤逛笂鏂规樉绀哄垎鍓茬嚎锛堢敤浜庢妸"鍒犻櫎"绛夊嵄闄╂搷浣滀笌涓婇潰鐨勫父瑙勯」闅斿紑锛�
- danger?: boolean // 鏄惁璧板嵄闄╂搷浣滄牱寮忥紙绾㈣壊鏂囧瓧锛�
- icon?: string // 鍙�� iconify 鍥炬爣鍚嶏紙濡� ant-design:delete-outlined锛夛紱涓嶄紶鍒欎笉娓叉煋鍓嶇疆鍥炬爣
- }
-
- const contextMenu = reactive({
- show: false,
- position: { x: 0, y: 0 },
- items: [] as ContextMenuItem[],
- /** 閫変腑鍥炶皟锛氭瘡娆� open 鏃剁敱璋冪敤鏂逛紶鍏� */
- onSelect: null as ((item: ContextMenuItem) => void) | null
- })
-
- /** 鎵撳紑鍙抽敭鑿滃崟 */
- function openContextMenu(
- position: { x: number; y: number },
- items: ContextMenuItem[],
- onSelect: (item: ContextMenuItem) => void
- ) {
- contextMenu.position = position
- contextMenu.items = items
- contextMenu.onSelect = onSelect
- contextMenu.show = true
- }
-
- /** 鍏抽棴鍙抽敭鑿滃崟 */
- function closeContextMenu() {
- contextMenu.show = false
- contextMenu.onSelect = null
- }
-
- // ==================== 娑堟伅 Tab 璺宠浆涓嬩竴鏈 ====================
- // 鍦� ImHomeConversation 椤甸潰鍐嶆鐐瑰嚮宸ュ叿鏍忋�屾秷鎭�嶆椂瑙﹀彂锛�
- // 閫氳繃閫掑 nonce 璁� conversation/index.vue 鐨� watch 鎰熺煡鍚庢墽琛屾粴鍔� + 楂樹寒
-
- const nextUnreadJumpNonce = ref(0)
-
- /** 璇锋眰婊氬姩鍒颁笅涓�涓湭璇讳細璇濓紙鍚厤鎵撴壈锛� */
- function requestNextUnreadJump() {
- nextUnreadJumpNonce.value++
- }
-
- return {
- userInfoCard,
- openUserInfoCard,
- openUserInfoCardAtEvent,
- closeUserInfoCard,
-
- groupInfoCard,
- openGroupInfoCardAtEvent,
- closeGroupInfoCard,
-
- contextMenu,
- openContextMenu,
- closeContextMenu,
-
- nextUnreadJumpNonce,
- requestNextUnreadJump
- }
-})
-
-// dev: 璁� Pinia 鐨� actions / state 鏀瑰姩鏀寔 HMR锛岄伩鍏嶆瘡娆℃敼 store 閮藉緱纭埛
-// 鍚﹀垯 Vite 鎶婃柊妯″潡鎺ㄤ笅鏉ュ悗锛岃�� store 瀹炰緥鐨� action 闂寘浠嶆寚鍚戞棫鍑芥暟浣�
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useImUiStore, import.meta.hot))
-}
diff --git a/src/views/im/home/store/websocketStore.ts b/src/views/im/home/store/websocketStore.ts
deleted file mode 100644
index f948bcd..0000000
--- a/src/views/im/home/store/websocketStore.ts
+++ /dev/null
@@ -1,1259 +0,0 @@
-import type {
- Group,
- ImGroupMessageNotification,
- ImMessageReadNotification,
- ImMessageReceiptNotification,
- ImNoConversationNotification,
- ImNotificationWebSocketDTO,
- ImPrivateMessageNotification,
- Message,
- WebSocketFrame
-} from '../types'
-
-import type { ImChannelMessageApi } from '#/api/im/message/channel'
-
-import { acceptHMRUpdate, defineStore } from 'pinia'
-
-import { readChannelMessages as apiReadChannelMessages } from '#/api/im/message/channel'
-import { readGroupMessages as apiReadGroupMessages } from '#/api/im/message/group'
-import { readPrivateMessages as apiReadPrivateMessages } from '#/api/im/message/private'
-import { getCurrentUserId, getRefreshToken } from '#/views/im/utils/auth'
-
-import { buildChannelConversationStub } from '../../utils/channel'
-import {
- MESSAGE_GROUP_READ_ENABLED,
- MESSAGE_PRIVATE_READ_ENABLED,
- WS_RECONNECT_BASE_MS,
- WS_RECONNECT_JITTER_MS,
- WS_RECONNECT_MAX_MS
-} from '../../utils/config'
-import {
- ImContentType,
- ImConversationType,
- ImMessageReceiptStatus,
- ImMessageStatus,
- ImRtcCallMediaType,
- ImRtcParticipantStatus,
- ImWebSocketMessageType,
- isFriendChatTip,
- isFriendNotification,
- isGroupRequestNotification,
- isNormalMessage
-} from '../../utils/constants'
-import {
- getPrivateMessagePeerId,
- parseRtcCallPayload,
- playAudioTip,
- resolveCallEndReasonText
-} from '../../utils/message'
-import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
-import { useConversationStore } from './conversationStore'
-import { type FriendNotificationPayload, useFriendStore } from './friendStore'
-import { useGroupRequestStore } from './groupRequestStore'
-import { useGroupStore } from './groupStore'
-import { useMessageStore } from './messageStore'
-import {
- type ImRtcCallEndNotification,
- type ImRtcCallNotification,
- type ImRtcParticipantConnectedNotification,
- type ImRtcParticipantDisconnectedNotification,
- useRtcStore
-} from './rtcStore'
-
-/** FRIEND_DELETE 甯� payload 鏄惁甯� clear=true锛歝lear 璇箟鏄竻浼氳瘽鏈韩锛岃烦杩囨皵娉℃覆鏌� */
-const isFriendDeleteWithClear = (frame: ImPrivateMessageNotification): boolean => {
- if (frame.type !== ImContentType.FRIEND_DELETE) {
- return false
- }
- try {
- const payload = JSON.parse(frame.content || '{}') as { clear?: boolean }
- return payload.clear === true
- } catch {
- return false
- }
-}
-
-/** 浠庣鑱婃秷鎭抚瑙f瀽濂藉弸閫氱煡 payload */
-const parseFriendNotificationPayload = (
- frame: ImPrivateMessageNotification
-): FriendNotificationPayload => JSON.parse(frame.content || '{}') as FriendNotificationPayload
-
-/** 绉佽亰娑堟伅甯ф槸鍚﹀彲鎺ㄦ柇濂藉弸瀵圭 */
-const isPrivateMessageNotification = (
- frame: ImNoConversationNotification | ImPrivateMessageNotification
-): frame is ImPrivateMessageNotification => 'senderId' in frame && 'receiverId' in frame
-
-const RTC_LIVEKIT_PROTOCOLS = new Set(['http:', 'https:', 'ws:', 'wss:'])
-const RTC_MEDIA_TYPES = new Set<number>(Object.values(ImRtcCallMediaType))
-
-/** 蹇界暐鏅�氬疄鏃跺抚鎸佷箙鍖栧け璐� */
-function ignoreRealtimePersistError(promise: Promise<void>): void {
- void promise.catch(() => undefined)
-}
-
-interface WebSocketListenerSet {
- close: (event: CloseEvent) => void
- error: (event: Event) => void
- message: (event: MessageEvent) => void
- open: (event: Event) => void
-}
-
-const websocketListenerMap = new WeakMap<WebSocket, WebSocketListenerSet>()
-
-/** 缁戝畾 WebSocket 浜嬩欢 */
-function bindWebSocketListeners(socket: WebSocket, listeners: WebSocketListenerSet): void {
- websocketListenerMap.set(socket, listeners)
- socket.addEventListener('open', listeners.open)
- socket.addEventListener('message', listeners.message)
- socket.addEventListener('close', listeners.close)
- socket.addEventListener('error', listeners.error)
-}
-
-/** 瑙g粦 WebSocket 浜嬩欢 */
-function unbindWebSocketListeners(socket: WebSocket): void {
- const listeners = websocketListenerMap.get(socket)
- if (!listeners) {
- return
- }
- socket.removeEventListener('open', listeners.open)
- socket.removeEventListener('message', listeners.message)
- socket.removeEventListener('close', listeners.close)
- socket.removeEventListener('error', listeners.error)
- websocketListenerMap.delete(socket)
-}
-
-/** 鏍¢獙 LiveKit 杩炴帴鍦板潃 */
-function isValidLiveKitUrl(url?: string): boolean {
- if (!url) {
- return false
- }
- try {
- return RTC_LIVEKIT_PROTOCOLS.has(new URL(url).protocol)
- } catch {
- return false
- }
-}
-
-/** 鏍¢獙鏉ョ數淇′护杞借嵎 */
-function isValidRtcInvitePayload(payload: ImRtcCallNotification): boolean {
- if (!payload.room || !payload.token || !isValidLiveKitUrl(payload.livekitUrl)) {
- return false
- }
- if (!RTC_MEDIA_TYPES.has(payload.mediaType) || !payload.inviterUserId) {
- return false
- }
- if (payload.conversationType === ImConversationType.PRIVATE) {
- return true
- }
- return payload.conversationType === ImConversationType.GROUP && !!payload.groupId
-}
-
-/**
- * WebSocket 绉佽亰 DTO -> 鍓嶇 Message锛泃argetId 鏄細璇濅富閿紙瀵圭 userId锛�
- * 涓嶅啓鍙戦�佷汉鍚嶅瓧娈碉細娓叉煋灞傝蛋 utils/user 瀹炴椂绠楋紙澶囨敞 / 缇ゆ樀绉板彉鏇村悗鍘嗗彶娑堟伅鑷姩鍒锋柊锛�
- */
-const convertPrivateMessage = (
- websocketMessage: ImPrivateMessageNotification,
- currentUserId: number
-): Message => ({
- id: websocketMessage.id,
- clientMessageId: websocketMessage.clientMessageId,
- type: websocketMessage.type,
- content: websocketMessage.content,
- status: websocketMessage.status,
- receiptStatus: websocketMessage.receiptStatus,
- sendTime: new Date(websocketMessage.sendTime).getTime(),
- senderId: websocketMessage.senderId,
- targetId: getPrivateMessagePeerId(websocketMessage, currentUserId),
- selfSend: websocketMessage.senderId === currentUserId
-})
-
-/**
- * WebSocket 缇よ亰 DTO -> 鍓嶇 Message
- * 甯� atUserIds / receiverUserIds 缁� @ 鏍囪鍜屽畾鍚戞帴鏀剁敤锛�
- * receiptStatus / readCount 璁╁绔悓姝ユ敹鍒拌嚜宸卞彂鐨勭兢娑堟伅鏃跺洖鎵� UI 绔嬪埢灏辨湁鏁版嵁
- */
-const convertGroupMessage = (
- websocketMessage: ImGroupMessageNotification,
- currentUserId: number
-): Message => ({
- id: websocketMessage.id,
- clientMessageId: websocketMessage.clientMessageId,
- type: websocketMessage.type,
- content: websocketMessage.content,
- status: websocketMessage.status,
- sendTime: new Date(websocketMessage.sendTime).getTime(),
- senderId: websocketMessage.senderId,
- targetId: websocketMessage.groupId,
- selfSend: websocketMessage.senderId === currentUserId,
- atUserIds: websocketMessage.atUserIds || [],
- receiverUserIds: websocketMessage.receiverUserIds || [],
- receiptStatus: websocketMessage.receiptStatus,
- readCount: websocketMessage.readCount
-})
-
-/**
- * IM WebSocket Store
- *
- * 鑱岃矗锛堜笉鍙槸杩為�氫俊锛屼篃鏄悗绔� IM 浜嬩欢鐨勭粺涓�鍏ュ彛 鈫� 鑱斿姩 conversationStore / friendStore / groupStore锛夛細
- *
- * 1. 閾捐矾绠$悊锛氬缓杩� / 鏂繛 / 蹇冭烦淇濇椿 / 鑷姩閲嶈繛
- * 2. 甯у垎鍙戯細dispatchFrame 鈫� dispatchPrivateFrame / dispatchGroupFrame锛屾寜浼氳瘽鍜屽唴瀹圭被鍨嬪垎娴�
- * 3. 缂撳啿锛氬垵濮嬪寲鍔犺浇鏈燂紙conversationStore.loading=true锛夋殏瀛樻秷鎭紝绛� pull 瀹屾垚鍚庣敱 useMessagePuller 璋� flushBuffer 鍥炴斁
- * 4. 浜嬩欢澶勭悊锛堟寜绫诲瀷鍒嗗彂鍒板搴� handle*锛岃仈鍔� conversation / friend / group store锛夛細
- * - 鏅�氭秷鎭紙TEXT / IMAGE / FILE / VOICE / VIDEO锛夛細鍏ュ簱 + 褰撳墠浼氳瘽鑷姩宸茶 / 鎻愮ず闊�
- * - 宸茶 / 鍥炴墽锛圧EAD / RECEIPT锛夛細澶氱宸茶鍚屾銆佸鏂硅鍚庡洖鎵�
- * - 濂藉弸鍙樻洿锛團RIEND_*锛夛細鍚屾 friendStore + 绾ц仈鍒锋柊绉佽亰浼氳瘽锛汧RIEND_ADD / FRIEND_DELETE 棰濆鎻掑叆浼氳瘽姘旀场
- * - 缇や釜浜轰俊鍙凤紙GROUP_MEMBER_SETTING_UPDATE锛夛細鍚屾 groupStore + 绾ц仈鍒锋柊缇よ亰浼氳瘽
- * - 缇ゆ垚鍛樻樀绉板彉鏇达紙GROUP_MEMBER_NICKNAME_UPDATE锛夛細鍚屾 groupStore锛屼笉鎻掑叆娑堟伅鍒楄〃
- * - 缇ゅ箍鎾簨浠讹紙GROUP_*锛夛細璧� handleGroupMessage + applyGroupNotification 鏃佽矾锛堝惈 DISSOLVE / QUIT / KICK 鑷垽娓呯兢锛�
- */
-export const useImWebSocketStore = defineStore('imWebSocketStore', {
- state: () => ({
- socket: null as null | WebSocket,
- isConnected: false,
- reconnectTimer: null as null | ReturnType<typeof setTimeout>,
- /** 杩炵画閲嶈繛澶辫触娆℃暟锛沷nopen 鎴愬姛 / disconnect 涓诲姩鏂紑鍚庢竻闆讹紝鐢ㄤ簬鎸囨暟閫�閬� */
- reconnectAttempts: 0,
- heartbeatTimer: null as null | ReturnType<typeof setInterval>,
- messageBuffer: [] as Array<
- | { conversationType: typeof ImConversationType.CHANNEL; payload: ImChannelMessageApi.ChannelMessageRespVO }
- | {
- conversationType: typeof ImConversationType.GROUP
- payload: ImGroupMessageNotification
- }
- | {
- conversationType: typeof ImConversationType.PRIVATE
- payload: ImPrivateMessageNotification
- }
- > // 鍒濆鍖栧姞杞芥湡鍐咃紝鍏堟妸鏅�氭秷鎭涪杩涚紦鍐插尯锛宲ull 瀹屾垚鍚庡啀涓�娆℃�у洖鏀�
- }),
-
- actions: {
- /**
- * 鍙栧嚭缂撳啿鍖烘秷鎭苟娓呯┖锛堢敱 useMessagePuller 鍦� pull 瀹屾垚鍚庤皟鐢紝缁熶竴鍥炴斁缁� conversationStore锛�
- * 閰嶅悎 messageBuffer 瀹炵幇锛氬湪 conversationStore.loading 鏈熼棿鏀跺埌鐨� WS 娑堟伅鍏堟殏瀛橈紝閬垮厤鍜� pull 鐨� minId 娓告爣鎵撴灦
- */
- flushBuffer() {
- const msgs = [...this.messageBuffer]
- this.messageBuffer = []
- return msgs
- },
-
- /** 鐩存帴涓㈠純缂撳啿甯т笉鍥炴斁锛坈ancelPull / 绂诲紑 IM 璋冪敤锛岄槻姝笅娆¤繘 IM 鎶婃棫 session 甯у洖鏀捐繘鏂� store锛� */
- discardBuffer() {
- this.messageBuffer = []
- },
-
- /**
- * 杩炴帴 WebSocket
- * 澶嶇敤 yudao 鍐呯疆 /infra/ws 閫氶亾锛屽悗绔�氳繃 sendObject(type, content) 涓嬪彂
- *
- * 璋冪敤濂戠害锛氬垏璐﹀彿 / token 鍒锋柊鍓嶅繀椤诲厛 `disconnect()` 鍐� `connect()`锛�
- * 鏈柟娉曚笉鎰熺煡 token 鍙樺寲锛屾棫 socket 鍦� CONNECTING / OPEN 鐘舵�佷細鐩存帴澶嶇敤鏃� token锛屽彲鑳芥嬁鍒伴敊璇韩浠�
- */
- connect() {
- // 閴存潈鐢� refreshToken锛堢敓鍛藉懆鏈熸洿闀匡紱access token 杩囨湡鍚庢湇鍔$浼氶�氳繃 frame 閫氱煡閲嶇櫥锛�
- const refreshToken = getRefreshToken()
- if (!refreshToken) {
- console.warn('[IM WS] refreshToken 涓虹┖锛岃烦杩囪繛鎺�')
- return
- }
- // 鏃� socket 杩樺湪 CONNECTING / OPEN 鐩存帴澶嶇敤锛岄伩鍏嶅彔鍔犲浠� onmessage 鐩戝惉瀵艰嚧閲嶅娑堟伅 / 鎻愮ず闊� / 宸茶涓婃姤
- const existingSocket = this.socket
- if (
- existingSocket &&
- (existingSocket.readyState === WebSocket.OPEN ||
- existingSocket.readyState === WebSocket.CONNECTING)
- ) {
- return
- }
- // 鏃� socket 宸� CLOSING / CLOSED锛氳В缁戝洖璋� + 娓呭紩鐢ㄥ啀 new锛岄伩鍏嶈�� handler 浠嶆寔鏈� store 寮曠敤闃荤 GC
- if (existingSocket) {
- unbindWebSocketListeners(existingSocket)
- this.socket = null
- }
- const url = `${this.buildWsUrl()}/infra/ws?token=${refreshToken}`
- const socket = new WebSocket(url)
- this.socket = socket
-
- bindWebSocketListeners(socket, {
- // 杩炴帴寤虹珛锛氭爣璁颁笂绾� + 鍚姩蹇冭烦淇濇椿锛涢噸杩為��閬胯鏁板綊闆�
- open: () => {
- this.isConnected = true
- this.reconnectAttempts = 0
- console.log('[IM WS] connected')
- this.startHeartbeat()
- },
-
- // 鏀跺埌甯э細'pong' 鏄績璺冲簲绛旂洿鎺ュ悶鎺夛紱鍏朵綑鎸� WebSocketFrame 瑙f瀽鍚庝氦缁� dispatchFrame 鍒嗘祦
- message: (event) => {
- if (event.data === 'pong') {
- return
- }
- try {
- const frame = JSON.parse(event.data) as WebSocketFrame
- this.dispatchFrame(frame)
- } catch (error) {
- console.error('[IM WS] message parse error:', error)
- }
- },
-
- // 鏈嶅姟绔叧闂� / 缃戠粶鏂細鏍囪涓嬬嚎锛屾寜鎸囨暟閫�閬胯嚜鍔ㄩ噸杩�
- close: () => {
- this.isConnected = false
- console.log('[IM WS] disconnected')
- this.reconnect()
- },
-
- // 寮傚父鏃朵笉涓诲姩 reconnect锛屼富鍔� close() 璁� close 鎴愪负鍞竴閲嶈繛鍏ュ彛
- error: (error) => {
- console.error('[IM WS] error:', error)
- this.isConnected = false
- this.socket?.close()
- }
- })
- },
-
- /** 鎷兼帴 WebSocket 鍩虹鍦板潃 */
- buildWsUrl(): string {
- // VITE_BASE_URL 鍙兘鏄� http:// 鎴� https:// 寮�澶达紝鏇挎崲鎴� ws:// 鎴� wss://锛涘鏋滄病閰嶇疆锛屽氨鐢ㄥ綋鍓嶉〉闈㈢殑鍗忚 + host
- const baseUrl = (import.meta as any).env?.VITE_BASE_URL as string | undefined
- if (baseUrl && baseUrl.length > 0) {
- return baseUrl.replace(/^http/, 'ws')
- }
- // 褰撳墠椤甸潰鍗忚 + host锛堝 http://localhost:8080锛夛紝鏇挎崲鎴� ws://localhost:8080
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
- const host = window.location.host
- return `${protocol}//${host}`
- },
-
- /**
- * 鎸� IM 閫氱煡甯у垎鍙�
- */
- dispatchFrame(frame: WebSocketFrame) {
- if (frame.type !== ImWebSocketMessageType.NOTIFICATION) {
- console.debug('[IM WS] 鏈瘑鍒簨浠�', frame)
- return
- }
-
- const notification = this.safeParse(frame.content) as ImNotificationWebSocketDTO | null
- if (!notification?.payload || !notification.contentType) {
- return
- }
- const payload = {
- ...notification.payload,
- type: notification.contentType
- }
- switch (notification.conversationType) {
- case ImConversationType.CHANNEL: {
- this.dispatchChannelFrame(payload as ImChannelMessageApi.ChannelMessageRespVO)
- break
- }
- case ImConversationType.GROUP: {
- this.dispatchGroupFrame(payload as ImGroupMessageNotification)
- break
- }
- case ImConversationType.NONE: {
- this.dispatchNoConversationFrame(payload as ImNoConversationNotification)
- break
- }
- case ImConversationType.PRIVATE: {
- this.dispatchPrivateFrame(payload as ImPrivateMessageNotification)
- break
- }
- default: {
- console.debug('[IM WS] 鏈瘑鍒�氱煡', notification)
- }
- }
- },
-
- /**
- * 鏃犱細璇濋�氱煡鍒嗗彂
- */
- dispatchNoConversationFrame(websocketMessage: ImNoConversationNotification) {
- if (isFriendNotification(websocketMessage.type)) {
- this.handleFriendNotification(websocketMessage)
- return
- }
- if (isGroupRequestNotification(websocketMessage.type)) {
- this.handleGroupRequestNotification(websocketMessage)
- return
- }
- switch (websocketMessage.type) {
- case ImContentType.RTC_CALL:
- case ImContentType.RTC_PARTICIPANT_CONNECTED:
- case ImContentType.RTC_PARTICIPANT_DISCONNECTED: {
- this.handleRtcSignaling(websocketMessage)
- break
- }
- default: {
- console.debug('[IM WS] 鏈瘑鍒棤浼氳瘽閫氱煡', websocketMessage)
- }
- }
- },
-
- /**
- * 棰戦亾甯у垎鍙戯細鎸� payload.type 鍒嗗埌 READ锛堝绔凡璇诲悓姝ワ級鎴栨櫘閫氱礌鏉愭帹閫�
- */
- dispatchChannelFrame(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO) {
- if (websocketMessage.type === ImContentType.READ) {
- this.handleChannelRead(websocketMessage)
- return
- }
- ignoreRealtimePersistError(this.handleChannelMessage(websocketMessage))
- },
-
- /** 棰戦亾 READ锛氳嚜宸卞叾瀹冪粓绔湪鏌愰閬撻噷鏍囦负宸茶锛屾湰绔悓姝ユ竻闆惰棰戦亾鏈 */
- handleChannelRead(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO) {
- void useConversationStore()
- .applyConversationReadList([
- {
- id: websocketMessage.id,
- conversationType: ImConversationType.CHANNEL,
- targetId: websocketMessage.channelId,
- messageId: websocketMessage.id
- }
- ])
- .catch((error) => console.warn('[IM WS] 棰戦亾宸茶鍚屾澶辫触', error))
- },
-
- /**
- * 棰戦亾娑堟伅瀹炴椂鍏ヤ細璇濓紱棰戦亾娑堟伅鍗曞悜 + 鏃犵姸鎬佹満锛岀洿鎺� insertMessage 鍗冲彲
- * pull 涓� WS 鎷垮埌鍚屼竴鏉� id 鏃讹紝messageStore.insertMessage 鍐呴儴鎸� id 鍘婚噸锛屼笉浼氶噸澶�
- */
- handleChannelMessage(websocketMessage: ImChannelMessageApi.ChannelMessageRespVO): Promise<void> {
- const conversationStore = useConversationStore()
- const messageStore = useMessageStore()
- // 绂荤嚎鍔犺浇鏈熼棿鍏堢紦鍐诧紝绛� pull 瀹屾垚鍚庡啀缁熶竴鍥炴斁锛岄伩鍏嶉噸澶嶆垨椤哄簭閿欎贡
- if (conversationStore.loading) {
- this.messageBuffer.push({
- conversationType: ImConversationType.CHANNEL,
- payload: websocketMessage
- })
- return Promise.resolve()
- }
- const sendTimeMs =
- typeof websocketMessage.sendTime === 'number'
- ? websocketMessage.sendTime
- : new Date(websocketMessage.sendTime).getTime()
- const conversation = conversationStore.getConversation(
- ImConversationType.CHANNEL,
- websocketMessage.channelId
- )
- const isActive =
- conversationStore.activeConversation?.type === ImConversationType.CHANNEL &&
- conversationStore.activeConversation?.targetId === websocketMessage.channelId
- // 棰戦亾鍗曞悜璁㈤槄锛宺eceiptStatus 琛ㄨ揪銆屾垜鏄惁宸茶杩欐潯銆嶏細浼氳瘽鎵撳紑鍗冲凡璇� DONE锛屽惁鍒� PENDING锛堜笌 pull 鍙e緞涓�鑷达級
- const persistPromise = messageStore.insertMessage(
- buildChannelConversationStub(websocketMessage.channelId),
- {
- id: websocketMessage.id,
- clientMessageId: '',
- type: websocketMessage.type,
- content: websocketMessage.content,
- status: ImMessageStatus.NORMAL,
- receiptStatus: isActive ? ImMessageReceiptStatus.DONE : ImMessageReceiptStatus.PENDING,
- sendTime: sendTimeMs,
- senderId: 0,
- targetId: websocketMessage.channelId,
- selfSend: false,
- materialId: websocketMessage.materialId
- }
- )
- if (isActive) {
- // 绐楀彛鎵撳紑 = 宸茶锛氭湰绔竻鏈 + 涓婃姤鏈嶅姟绔浣嶇疆锛岄伩鍏嶈浣嶇疆婊炲悗
- const readReported = conversationStore.isReportedReadPositionCovered(
- ImConversationType.CHANNEL,
- websocketMessage.channelId,
- websocketMessage.id
- )
- conversationStore.markConversationRead(
- ImConversationType.CHANNEL,
- websocketMessage.channelId,
- websocketMessage.id
- )
- if (!readReported) {
- apiReadChannelMessages(websocketMessage.channelId, websocketMessage.id)
- .then(() =>
- conversationStore.markConversationReadReported(
- ImConversationType.CHANNEL,
- websocketMessage.channelId,
- websocketMessage.id
- )
- )
- .catch((error) => {
- console.warn(
- '[IM WS] 棰戦亾鑷姩宸茶涓婃姤澶辫触',
- {
- conversationType: ImConversationType.CHANNEL,
- channelId: websocketMessage.channelId,
- messageId: websocketMessage.id
- },
- error
- )
- })
- }
- } else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
- // 闈炲綋鍓嶄細璇濅笖鏈厤鎵撴壈锛氬搷涓�涓嬫彁绀洪煶
- playAudioTip()
- }
- return persistPromise
- },
-
- /** content 鏃㈠彲鑳藉凡鏄璞′篃鍙兘鏄� JSON 瀛楃涓诧紙鍚庣鐢� Map 搴忓垪鍖栦笅鍙戯級 */
- safeParse(raw: unknown): null | Record<string, any> {
- if (!raw) {
- return null
- }
- if (typeof raw === 'object') {
- return raw as Record<string, any>
- }
- try {
- return JSON.parse(raw as string)
- } catch (error) {
- console.error('[IM WS] content 瑙f瀽澶辫触', error)
- return null
- }
- },
-
- // ==================== 鏅�氭秷鎭� ====================
-
- /**
- * 绉佽亰缁熶竴甯у垎鍙戯細鎸� payload.type锛圛mContentType锛夊垎鍒板凡璇� / 鍥炴墽 / 濂藉弸閫氱煡 / 鏅�氭秷鎭�
- *
- * 娑堟伅閫氱煡銆佸凡璇婚�氱煡銆佸洖鎵ч�氱煡鐢卞灞� contentType 缁熶竴鍒嗗彂
- */
- dispatchPrivateFrame(websocketMessage: ImPrivateMessageNotification) {
- try {
- switch (websocketMessage.type) {
- case ImContentType.READ: {
- this.handlePrivateRead(websocketMessage as ImMessageReadNotification)
- break
- }
- case ImContentType.RECEIPT: {
- this.handlePrivateReceipt(websocketMessage as ImMessageReceiptNotification)
- break
- }
- case ImContentType.RTC_CALL_END: {
- // 鍏ュ簱 + 鍏抽棴閫氳瘽绐� + 娓叉煋鑱婂ぉ tip锛堢鑱婂満鏅級
- this.handleRtcCallEnd(websocketMessage)
- ignoreRealtimePersistError(this.handlePrivateMessage(websocketMessage))
- break
- }
- default: {
- if (isFriendChatTip(websocketMessage.type)) {
- this.handleFriendNotification(websocketMessage)
- // FRIEND_DELETE 鐨� clear=true 璇箟鏄竻浼氳瘽鏈韩锛岃烦杩囨皵娉¢伩鍏嶅湪宸叉竻浼氳瘽閲屽啓鍏ヨ櫄鎷熸秷鎭�
- if (!isFriendDeleteWithClear(websocketMessage)) {
- ignoreRealtimePersistError(this.handlePrivateMessage(websocketMessage))
- }
- } else {
- // TEXT / IMAGE / FILE / VOICE / VIDEO 绛夋櫘閫氭秷鎭�
- ignoreRealtimePersistError(this.handlePrivateMessage(websocketMessage))
- }
- }
- }
- } catch (error) {
- // 鍗曟潯甯х殑澶勭悊寮傚父涓嶅簲闃绘柇鍚庣画甯э紱鎵撳嵃瀹屾暣 websocketMessage 渚夸簬鎺掓煡
- console.warn('[IM WS] dispatchPrivateFrame 澶勭悊澶辫触', websocketMessage, error)
- }
- },
-
- /**
- * 缇よ亰缁熶竴甯у垎鍙戯細鎸� payload.type锛圛mContentType锛夊垎鍒板凡璇� / 鍥炴墽 / 缇や釜浜轰俊鍙� / 鏅�氭秷鎭�
- *
- * GROUP_MEMBER_SETTING_UPDATE / GROUP_MEMBER_NICKNAME_UPDATE 鏄垚鍛樿祫鏂欏悓姝ヤ俊鍙凤紱鍏跺畠缇ゅ箍鎾簨浠惰蛋 handleGroupMessage 鍏ュ簱 + 瑙﹀彂 applyGroupNotification 鏃佽矾
- */
- dispatchGroupFrame(websocketMessage: ImGroupMessageNotification) {
- try {
- switch (websocketMessage.type) {
- case ImContentType.GROUP_MEMBER_NICKNAME_UPDATE: {
- this.handleGroupMemberNicknameUpdate(websocketMessage)
- break
- }
- case ImContentType.GROUP_MEMBER_SETTING_UPDATE: {
- this.handleGroupMemberSettingUpdate(websocketMessage)
- break
- }
- case ImContentType.READ: {
- this.handleGroupRead(websocketMessage as ImMessageReadNotification)
- break
- }
- case ImContentType.RECEIPT: {
- this.handleGroupReceipt(websocketMessage as ImMessageReceiptNotification)
- break
- }
- case ImContentType.RTC_CALL_END: {
- // 鍏ュ簱 + 绉婚櫎鑳跺泭鏉� + 鍏抽棴閫氳瘽绐楋紙濡傛灉褰撳墠鍦ㄨ缇ら�氳瘽鍐咃級
- this.handleRtcCallEnd(websocketMessage)
- ignoreRealtimePersistError(this.handleGroupMessage(websocketMessage))
- break
- }
- case ImContentType.RTC_CALL_START: {
- // 鍏ュ簱 + 娓叉煋鑱婂ぉ tip锛涘悓鏃剁敤 START payload 鍏堢敓鎴愭渶灏忚兌鍥婃潯锛屽悗缁� getActiveCall / 鍙備笌鑰呬簨浠跺啀琛ラ綈鎴愬憳
- this.handleRtcCallStart(websocketMessage)
- ignoreRealtimePersistError(this.handleGroupMessage(websocketMessage))
- break
- }
- default: {
- // TEXT / IMAGE / FILE / VOICE / VIDEO + GROUP_* 缇ゅ箍鎾簨浠�
- ignoreRealtimePersistError(this.handleGroupMessage(websocketMessage))
- }
- }
- } catch (error) {
- // 鍗曟潯甯х殑澶勭悊寮傚父涓嶅簲闃绘柇鍚庣画甯э紱鎵撳嵃瀹屾暣 websocketMessage 渚夸簬鎺掓煡
- console.warn('[IM WS] dispatchGroupFrame 澶勭悊澶辫触', websocketMessage, error)
- }
- },
-
- /**
- * 绉佽亰鏅�氭秷鎭紙TEXT / IMAGE / FILE / VOICE / VIDEO锛夊叆搴� + 鑷姩宸茶
- *
- * 娴佺▼锛�
- * 1. 绂荤嚎鍔犺浇鏈熺紦鍐诧紙閬垮紑涓� pull 鍥炲~鐨勭珵鎬侊級
- * 2. 璁$畻 selfSend / peerId 缁村害锛屾媺濂藉弸淇℃伅鍥炲~灞曠ず瀛楁
- * 3. 鎾ゅ洖 TIP 鐩存帴杞蛋 recallMessage锛屼笉杩涙秷鎭垪琛�
- * 4. 鏋勯�犲墠绔� Message锛屾彃鍏ュ埌瀵瑰簲绉佽亰浼氳瘽
- * 5. 褰撳墠浼氳瘽婵�娲绘椂鑷姩涓婃姤宸茶锛涘惁鍒欓潪鍏嶆墦鎵板搷鎻愮ず闊�
- */
- handlePrivateMessage(websocketMessage: ImPrivateMessageNotification): Promise<void> {
- const conversationStore = useConversationStore()
- const friendStore = useFriendStore()
- const currentUserId = getCurrentUserId()
-
- // 0. 闃插尽灞傦細senderId / receiverId 鍧囦笉鍚綋鍓嶇敤鎴风殑绉佽亰甯х洿鎺ヤ涪寮冿紝閬垮厤鍚庣璺敱 / 澶氱涓插彿姹℃煋浼氳瘽
- // 锛團RIEND_* 绛夌郴缁熼�氱煡涔熻蛋杩欐潯閫氶亾锛屼絾 fromUserId=senderId銆乼oUserId=receiverId 浠嶆槸褰撳墠鐢ㄦ埛瑙嗚锛�
- if (
- currentUserId &&
- websocketMessage.senderId !== currentUserId &&
- websocketMessage.receiverId !== currentUserId
- ) {
- console.warn('[IM WS] 涓㈠純涓嶅睘浜庡綋鍓嶇敤鎴风殑绉佽亰甯�', websocketMessage)
- return Promise.resolve()
- }
-
- // 1. 绂荤嚎鍔犺浇鏈熼棿鍏堢紦鍐诧紝绛� pull 瀹屾垚鍚庡啀缁熶竴鍥炴斁锛岄伩鍏嶉噸澶嶆垨椤哄簭閿欎贡
- if (conversationStore.loading) {
- this.messageBuffer.push({
- conversationType: ImConversationType.PRIVATE,
- payload: websocketMessage
- })
- return Promise.resolve()
- }
-
- // 2. selfSend / peerId锛氳嚜宸卞彂鐨勬秷鎭睘浜庛�屽彂缁� receiverId 鐨勪細璇濄�嶏紝鍒汉鍙戠殑灞炰簬銆屽彂閫佽�呯殑浼氳瘽銆�
- const selfSend = websocketMessage.senderId === currentUserId
- const peerId = getPrivateMessagePeerId(websocketMessage, currentUserId)
- // 鏈煡瀵圭锛堥檶鐢熶汉鍔犲ソ鍙嬪墠鍏堟敹鍒版秷鎭瓑鍦烘櫙锛夛細寮傛琛ユ媺涓�娆★紝涓嬫鍐嶆覆鏌撳氨鏈� name/avatar
- const friend = friendStore.getFriend(peerId)
- if (!friend) {
- friendStore.fetchFriendInfo(peerId).catch(() => undefined)
- }
- // 浼氳瘽鏍囬姘歌繙璺熴�屽绔�嶈蛋锛堜笉绠¤皝鍙戠殑娑堟伅锛夛紱杩欓噷鍙畻涓�娆$粰 insertMessage 鐢�
- const peerDisplayName = friend ? getFriendDisplayName(friend) : ''
-
- // 3. 鍚庣鎾ゅ洖锛氫笅鍙戜竴鏉� RECALL 娑堟伅锛宑ontent 涓� `{"messageId": xxx}`锛堝榻� ImContentTypeEnum.RECALL 鈫� RecallMessage锛�
- // 杩欓噷鎷︽埅涓嬫潵鏀硅蛋 recallMessage锛堟妸鍘熸秷鎭洿鏂颁负 RECALL 鎬侊級锛屼笉璁╁畠浣滀负鏂版秷鎭繘鍒楄〃
- if (websocketMessage.type === ImContentType.RECALL) {
- return useMessageStore().recallMessage(
- ImConversationType.PRIVATE,
- peerId,
- websocketMessage.content
- )
- }
-
- // 4. 鍚庣 DTO 鈫� 鍓嶇 Message锛氬彂閫佷汉鍚嶆覆鏌撴椂瀹炴椂绠楋紝涓嶅啓鍏ユ秷鎭瓧娈�
- const message = convertPrivateMessage(websocketMessage, currentUserId)
- const persistPromise = useMessageStore().insertMessage(
- {
- type: ImConversationType.PRIVATE,
- targetId: peerId,
- name: peerDisplayName || String(peerId),
- avatar: friend?.avatar || '',
- silent: friend?.silent
- },
- message
- )
-
- // 5. 浠呭鏂规秷鎭墠璧般�岃嚜鍔ㄥ凡璇� / 鎻愮ず闊炽�嶅垎鏀細鑷繁鍙戠殑涓嶄細瑙﹀彂
- if (!selfSend) {
- const conversation = conversationStore.getConversation(ImConversationType.PRIVATE, peerId)
- const isActive =
- conversationStore.activeConversation?.type === ImConversationType.PRIVATE &&
- conversationStore.activeConversation?.targetId === peerId
- if (isActive) {
- // 鑱婂ぉ绐楀彛鎵撳紑 = 瀹為檯鐪嬪埌浜嗭細鏈娓呮湭璇伙紱绉佽亰宸茶寮�鍚椂鍐嶄笂鎶ュ悗绔紝璁╁鏂� UI 绔嬪埢鍒囧埌"宸茶"
- // 宸茶浣嶇疆鐩存帴鐢ㄥ垰鍒扮殑娑堟伅 id锛堣繖鏉″氨鏄綋鍓嶄細璇濇渶澶� id锛�
- const readReported = conversationStore.isReportedReadPositionCovered(
- ImConversationType.PRIVATE,
- peerId,
- websocketMessage.id
- )
- conversationStore.markConversationRead(
- ImConversationType.PRIVATE,
- peerId,
- websocketMessage.id
- )
- if (MESSAGE_PRIVATE_READ_ENABLED && !readReported) {
- apiReadPrivateMessages(peerId, websocketMessage.id)
- .then(() =>
- conversationStore.markConversationReadReported(
- ImConversationType.PRIVATE,
- peerId,
- websocketMessage.id
- )
- )
- .catch((error) => {
- console.warn(
- '[IM WS] 绉佽亰鑷姩宸茶涓婃姤澶辫触',
- {
- conversationType: ImConversationType.PRIVATE,
- peerId,
- messageId: websocketMessage.id
- },
- error
- )
- })
- }
- } else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
- // 闈炲綋鍓嶄細璇濅笖鏈厤鎵撴壈锛氬搷涓�涓嬫彁绀洪煶锛堝甫鑺傛祦锛岃瑙� playAudioTip锛夛紱FRIEND_* 绛夌郴缁熶簨浠朵笉鍝�
- playAudioTip()
- }
- }
- return persistPromise
- },
-
- /** 绉佽亰 READ 浜嬩欢锛氳嚜宸辩殑鍏跺畠缁堢鍦ㄥ鏂逛細璇濋噷鏍囦负宸茶锛屾湰绔悓姝ユ竻闆舵湭璇伙紱绉佽亰宸茶鍏抽棴鏃跺厹搴曞拷鐣� */
- handlePrivateRead(websocketMessage: ImMessageReadNotification) {
- if (!MESSAGE_PRIVATE_READ_ENABLED) {
- return
- }
- if (!websocketMessage.id || !websocketMessage.receiverId) {
- return
- }
- void useConversationStore()
- .applyConversationReadList([
- {
- id: websocketMessage.id,
- conversationType: ImConversationType.PRIVATE,
- targetId: websocketMessage.receiverId,
- messageId: websocketMessage.id
- }
- ])
- .catch((error) => console.warn('[IM WS] 绉佽亰宸茶鍚屾澶辫触', error))
- },
-
- /**
- * 绉佽亰 RECEIPT 浜嬩欢锛氬鏂硅浜嗘垜鐨勬秷鎭紝鎶婂拰瀵规柟浼氳瘽閲岃嚜宸卞彂鐨勬秷鎭爣涓哄凡璇�
- * 鍚庣灏� maxReadId 缂栫爜鍦ㄩ�氱煡鐨� id 瀛楁锛�
- * 杩欓噷鎹鍗¤竟鐣岋紝閬垮厤鎶�"鍥炴墽鍦ㄨ矾涓婃椂鍒氬彂鐨勬秷鎭�"璇爣涓哄凡璇伙紱绉佽亰宸茶鍏抽棴鏃跺厹搴曞拷鐣�
- */
- handlePrivateReceipt(websocketMessage: ImMessageReceiptNotification) {
- if (!MESSAGE_PRIVATE_READ_ENABLED) {
- return
- }
- if (!websocketMessage.id) {
- return
- }
- if (!websocketMessage.senderId) {
- return
- }
- useMessageStore().applyMessageReadReceipt({
- conversationType: ImConversationType.PRIVATE,
- targetId: websocketMessage.senderId,
- privateReadMaxId: websocketMessage.id
- })
- },
-
- /**
- * 缇よ亰鏅�氭秷鎭叆搴� + 鑷姩宸茶锛堢粨鏋勪笌 handlePrivateMessage 瀵圭О锛�
- *
- * 娴佺▼锛�
- * 1. 绂荤嚎鍔犺浇鏈熺紦鍐�
- * 2. 鏈煡缇ゆ椂鎷夌兢璇︽儏鍏滃簳
- * 3. 鎾ゅ洖 TIP 鐩存帴杞蛋
- * 4. 鏋勯�� Message + at 瀛楁锛屾彃鍏ュ埌瀵瑰簲缇よ亰浼氳瘽锛堝彂閫佷汉鍚嶆覆鏌撴椂瀹炴椂绠楋級
- * 5. 褰撳墠浼氳瘽婵�娲绘椂鑷姩涓婃姤宸茶锛堝甫 lastMessageId锛夛紱鍚﹀垯闈炲厤鎵撴壈鍝嶆彁绀洪煶
- */
- handleGroupMessage(websocketMessage: ImGroupMessageNotification): Promise<void> {
- const conversationStore = useConversationStore()
- const groupStore = useGroupStore()
- const currentUserId = getCurrentUserId()
- const selfSend = websocketMessage.senderId === currentUserId
-
- // 0. 闃插尽灞傦細瀹氬悜缇ゆ秷鎭� receiverUserIds 闈炵┖涓旀湭鍖呭惈褰撳墠鐢ㄦ埛鏃朵涪寮�
- // 鑷繁鍙戠殑锛坰elfSend锛夊缁堥�氳繃锛涘叏鍛樺彲瑙侊紙receiverUserIds 涓虹┖ / 缂哄け锛変篃閫氳繃
- const receiverUserIds = websocketMessage.receiverUserIds
- if (
- currentUserId &&
- !selfSend &&
- Array.isArray(receiverUserIds) &&
- receiverUserIds.length > 0 &&
- !receiverUserIds.includes(currentUserId)
- ) {
- console.warn('[IM WS] 涓㈠純涓嶅睘浜庡綋鍓嶇敤鎴风殑瀹氬悜缇ゆ秷鎭�', websocketMessage)
- return Promise.resolve()
- }
-
- // 1. 绂荤嚎鍔犺浇鏈熺紦鍐诧紙涓庣鑱婂绉帮級
- if (conversationStore.loading) {
- this.messageBuffer.push({
- conversationType: ImConversationType.GROUP,
- payload: websocketMessage
- })
- return Promise.resolve()
- }
-
- // 2. 鏈煡缇ゆ椂鑷姩鎷夌兢璇︽儏 + 鎴愬憳锛堣鎷夊叆缇や絾杩樻病鏀跺埌 GROUP_CREATE 鏃剁殑鍏滃簳锛�
- const group = groupStore.getGroup(websocketMessage.groupId)
- if (!group) {
- groupStore.fetchGroupInfo(websocketMessage.groupId).catch(() => undefined)
- }
-
- // 3. 鍚庣鎾ゅ洖锛氫笅鍙戜竴鏉� RECALL 娑堟伅锛宑ontent 涓� `{"messageId": xxx}`
- // 杩欓噷鎷︽埅涓嬫潵鏀硅蛋 recallMessage锛堟妸鍘熸秷鎭洿鏂颁负 RECALL 鎬侊級
- if (websocketMessage.type === ImContentType.RECALL) {
- return useMessageStore().recallMessage(
- ImConversationType.GROUP,
- websocketMessage.groupId,
- websocketMessage.content
- )
- }
-
- // 4. 鍚庣 DTO 鈫� 鍓嶇 Message锛氬彂閫佷汉鍚嶆覆鏌撴椂瀹炴椂绠楋紝涓嶅啓鍏ユ秷鎭瓧娈�
- const message = convertGroupMessage(websocketMessage, currentUserId)
- const persistPromise = useMessageStore().insertMessage(
- {
- type: ImConversationType.GROUP,
- targetId: websocketMessage.groupId,
- name: group ? getGroupDisplayName(group) : String(websocketMessage.groupId),
- avatar: group?.avatar || '',
- silent: group?.silent
- },
- message
- )
-
- // 5. 浠呭鏂规秷鎭墠璧般�岃嚜鍔ㄥ凡璇� / 鎻愮ず闊炽�嶏紙涓庣鑱婂绉帮級
- if (!selfSend) {
- const conversation = conversationStore.getConversation(
- ImConversationType.GROUP,
- websocketMessage.groupId
- )
- const isActive =
- conversationStore.activeConversation?.type === ImConversationType.GROUP &&
- conversationStore.activeConversation?.targetId === websocketMessage.groupId
- if (isActive) {
- // 缇ゅ凡璇讳笂鎶ラ渶瑕佸甫 messageId锛堢兢娑堟伅浠�"璇诲埌绗嚑鏉�"鐨勬父鏍囦负鍑嗭紝鍖哄埆浜庣鑱婂彧鏍� receiverId锛夛紱缇ゅ凡璇诲叧闂椂浠呮湰鍦版竻闆�
- const readReported = conversationStore.isReportedReadPositionCovered(
- ImConversationType.GROUP,
- websocketMessage.groupId,
- websocketMessage.id
- )
- conversationStore.markConversationRead(
- ImConversationType.GROUP,
- websocketMessage.groupId,
- websocketMessage.id
- )
- if (MESSAGE_GROUP_READ_ENABLED && !readReported) {
- apiReadGroupMessages(websocketMessage.groupId, websocketMessage.id)
- .then(() =>
- conversationStore.markConversationReadReported(
- ImConversationType.GROUP,
- websocketMessage.groupId,
- websocketMessage.id
- )
- )
- .catch((error) => {
- console.warn(
- '[IM WS] 缇よ亰鑷姩宸茶涓婃姤澶辫触',
- {
- conversationType: ImConversationType.GROUP,
- groupId: websocketMessage.groupId,
- messageId: websocketMessage.id
- },
- error
- )
- })
- }
- } else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
- // GROUP_* 缇ゅ箍鎾簨浠剁瓑绯荤粺娑堟伅涓嶅搷鎻愮ず闊�
- playAudioTip()
- }
- }
- return persistPromise
- },
-
- // ==================== 缇よ亰宸茶 / 鍥炴墽 ====================
-
- /** 缇よ亰 READ锛氳嚜宸卞叾瀹冪粓绔湪鏌愮兢閲屾爣涓哄凡璇伙紝鏈鍚屾娓呴浂璇ョ兢鏈 + @ 绾㈠瓧锛涚兢宸茶鍏抽棴鏃跺厹搴曞拷鐣� */
- handleGroupRead(websocketMessage: ImMessageReadNotification) {
- if (!MESSAGE_GROUP_READ_ENABLED) {
- return
- }
- const readMessageId = websocketMessage.readId || websocketMessage.id
- if (!readMessageId || !websocketMessage.groupId) {
- return
- }
- void useConversationStore()
- .applyConversationReadList([
- {
- id: readMessageId,
- conversationType: ImConversationType.GROUP,
- targetId: websocketMessage.groupId,
- messageId: readMessageId
- }
- ])
- .catch((error) => console.warn('[IM WS] 缇よ亰宸茶鍚屾澶辫触', error))
- },
-
- /** 缇よ亰 RECEIPT锛氭洿鏂版煇鏉$兢娑堟伅鐨� readCount / receiptStatus锛涚兢宸茶鍏抽棴鏃跺厹搴曞拷鐣� */
- handleGroupReceipt(websocketMessage: ImMessageReceiptNotification) {
- if (!MESSAGE_GROUP_READ_ENABLED) {
- return
- }
- if (!websocketMessage.id || !websocketMessage.groupId) {
- return
- }
- useMessageStore().applyMessageReadReceipt({
- conversationType: ImConversationType.GROUP,
- targetId: websocketMessage.groupId,
- groupMessageId: websocketMessage.id,
- readCount: websocketMessage.readCount,
- receiptStatus: websocketMessage.receiptStatus
- })
- },
-
- // ==================== 濂藉弸閫氱煡锛�1201-1210 娈典綅锛� ====================
-
- /**
- * 绠� FRIEND_ADD / FRIEND_DELETE 甯х殑銆屽绔� userId銆嶏細
- * becomeFriends 鍗曟潯鍏ュ簱鍚庡弻鏂规敹鍒板悓涓�浠� payload锛宲ayload.friendUserId 鍥哄畾鏄� toUserId锛屾湰绔湡姝g殑瀵圭瑕佷粠甯� sender / receiver 鍙嶆帹
- */
- computeFriendPeerId(frame: ImPrivateMessageNotification): number {
- const currentUserId = getCurrentUserId()
- return getPrivateMessagePeerId(frame, currentUserId)
- },
-
- /**
- * 濂藉弸閫氱煡缁熶竴鍏ュ彛锛氭寜 type 鍒嗗彂鍒� friendStore 鍐呴儴 dispatcher
- */
- handleFriendNotification(
- websocketMessage: ImNoConversationNotification | ImPrivateMessageNotification
- ) {
- const payload = isPrivateMessageNotification(websocketMessage)
- ? parseFriendNotificationPayload(websocketMessage)
- : (websocketMessage as unknown as FriendNotificationPayload)
- const friendStore = useFriendStore()
- switch (websocketMessage.type) {
- case ImContentType.FRIEND_ADD: {
- friendStore.applyFriendAddNotification(
- payload,
- isPrivateMessageNotification(websocketMessage)
- ? this.computeFriendPeerId(websocketMessage)
- : payload.friendUserId
- )
- break
- }
- case ImContentType.FRIEND_BLOCK: {
- friendStore.applyFriendBlockNotification(payload)
- break
- }
- case ImContentType.FRIEND_DELETE: {
- friendStore.applyFriendDeleteNotification(
- payload,
- isPrivateMessageNotification(websocketMessage)
- ? this.computeFriendPeerId(websocketMessage)
- : payload.friendUserId
- )
- break
- }
- case ImContentType.FRIEND_INFO_UPDATED: {
- friendStore.applyFriendInfoUpdatedNotification(payload)
- break
- }
- case ImContentType.FRIEND_REQUEST_APPROVED: {
- friendStore.applyFriendRequestApprovedNotification(payload)
- break
- }
- case ImContentType.FRIEND_REQUEST_RECEIVED: {
- friendStore.applyFriendRequestReceivedNotification(payload)
- break
- }
- case ImContentType.FRIEND_REQUEST_REJECTED: {
- friendStore.applyFriendRequestRejectedNotification(payload)
- break
- }
- case ImContentType.FRIEND_UNBLOCK: {
- friendStore.applyFriendUnblockNotification(payload)
- break
- }
- case ImContentType.FRIEND_UPDATE: {
- friendStore.applyFriendUpdateNotification(payload)
- break
- }
- default: {
- console.debug('[IM WS] 鏈瘑鍒ソ鍙嬮�氱煡', websocketMessage)
- }
- }
- },
-
- // ==================== 鍔犵兢鐢宠閫氱煡锛�1503 / 1505 / 1506锛� ====================
-
- /**
- * 鍔犵兢鐢宠閫氱煡缁熶竴鍏ュ彛锛氬垎鍙戝埌 groupRequestStore锛岄┍鍔ㄦí骞� + Drawer 鍚屾
- */
- handleGroupRequestNotification(websocketMessage: ImNoConversationNotification) {
- const payload = websocketMessage as { requestId?: number }
- if (!payload.requestId) {
- return
- }
- const groupRequestStore = useGroupRequestStore()
- switch (websocketMessage.type) {
- case ImContentType.GROUP_REQUEST_APPROVED:
- case ImContentType.GROUP_REQUEST_REJECTED: {
- groupRequestStore.removeGroupRequestById(payload.requestId)
- break
- }
- case ImContentType.GROUP_REQUEST_RECEIVED: {
- groupRequestStore.addGroupRequestById(payload.requestId).catch(() => undefined)
- break
- }
- default: {
- break
- }
- }
- },
-
- // ==================== 缇ゅ叧绯讳簨浠讹紙鎵胯浇浜庣兢鑱婇�氶亾锛屾寜 inner type 鍒嗘祦锛� ====================
-
- /**
- * GROUP_MEMBER_SETTING_UPDATE锛氬绔悓姝ユ垚鍛樹釜浜鸿缃彉鏇达紙silent / groupRemark锛�
- *
- * payload 鎼哄甫鍙樻洿瀛楁锛屾寜闈� null 瀛楁鐩存帴灞�閮ㄦ洿鏂帮紱鐪佷竴娆� fetchGroupMemberList 鎺ュ彛
- */
- handleGroupMemberSettingUpdate(websocketMessage: ImGroupMessageNotification) {
- // content 瑙f瀽澶辫触鐢卞灞� dispatchGroupFrame 鐨� try-catch 鍏滃簳锛堝惈 websocketMessage 鎵撳嵃锛夛紝涓嶉噸澶� catch
- const payload: { groupRemark?: string; silent?: boolean; } = JSON.parse(
- websocketMessage.content || '{}'
- )
- const groupStore = useGroupStore()
- const group = groupStore.getGroup(websocketMessage.groupId)
- if (!group) {
- return
- }
- const fields: Partial<Group> = {}
- if (payload.silent != null) {
- fields.silent = payload.silent
- }
- if (payload.groupRemark != null) {
- fields.groupRemark = payload.groupRemark
- }
- if (Object.keys(fields).length > 0) {
- groupStore.updateGroupFields(websocketMessage.groupId, fields)
- }
- },
-
- /** GROUP_MEMBER_NICKNAME_UPDATE锛氬悓姝ユ垚鍛樺湪缇ら噷鐨勬樀绉� */
- handleGroupMemberNicknameUpdate(websocketMessage: ImGroupMessageNotification) {
- useGroupStore().applyGroupNotification(
- websocketMessage.groupId,
- websocketMessage.type,
- websocketMessage.content
- )
- },
-
- // ==================== 蹇冭烦 / 閲嶈繛 ====================
-
- /** 蹇冭烦鍖咃細绾枃鏈� 'ping'锛屽搴旀湇鍔$ 'pong'锛堝悗绔繖灞傜敤绾瓧绗︿覆绾﹀畾锛岄伩鍏� JSON 瑙f瀽寮�閿�锛� */
- sendHeartBeat() {
- if (this.socket && this.isConnected) {
- this.socket.send('ping')
- }
- },
-
- /** 涓诲姩鏂紑锛堝垏鎹㈢敤鎴� / 閫�鍑虹櫥褰曟椂鐢級锛氬叧 socket + 鍋滃績璺� + 鍙栨秷寰呴噸杩� */
- disconnect() {
- if (this.socket) {
- // close() 寮傛瑙﹀彂 onclose / onerror锛屽洖璋冮噷浼氭棤鏉′欢 reconnect锛�
- // 涓诲姩鍏抽棴璺緞蹇呴』鍏堝叏閮ㄨВ缁戯紝鍚﹀垯 onclose 浼氬紩鍙戣嚜鍔ㄩ噸杩烇紝CONNECTING 鏈熼棿鐨� in-flight message 涔熷彲鑳借鑰� onmessage 鎶曢�掑埌 stale 涓婁笅鏂�
- unbindWebSocketListeners(this.socket)
- this.socket.close()
- this.socket = null
- }
- // onclose 宸茶瑙g粦锛屼笉浼氬啀甯垜浠 isConnected=false锛岃繖閲屾墜鍔ㄥ浣�
- this.isConnected = false
- this.stopHeartbeat()
- if (this.reconnectTimer) {
- clearTimeout(this.reconnectTimer)
- this.reconnectTimer = null
- }
- // 涓诲姩鏂紑锛堝垏璐﹀彿 / 閫�鍑猴級锛氭竻闆堕��閬胯鏁帮紝涓嬫 connect 閲嶆柊浠庢渶鐭棿闅旇捣绠�
- this.reconnectAttempts = 0
- },
-
- /**
- * 鑷姩閲嶈繛锛氭寚鏁伴��閬� base * 2^attempt锛堝皝椤� max锛�+ 0~jitter ms 闅忔満鍋忕Щ
- *
- * onclose 鏄敮涓�鍏ュ彛锛沷nerror 涓嶅啀璋冩湰鏂规硶锛堟祻瑙堝櫒瑙勮寖涓よ�呭繀鍚屾椂瑙﹀彂锛岄伩鍏嶈鏁� +2锛�
- * 涓嶈娆℃暟涓婇檺锛岄鐜囧皝椤跺湪 WS_RECONNECT_MAX_MS锛堢害 30s锛夋寔缁噸璇曪紝鐩村埌閾捐矾鎭㈠鎴栦富鍔� disconnect
- */
- reconnect() {
- this.stopHeartbeat()
- if (this.reconnectTimer) {
- clearTimeout(this.reconnectTimer)
- this.reconnectTimer = null
- }
- const backoff = Math.min(
- WS_RECONNECT_BASE_MS * 2 ** this.reconnectAttempts,
- WS_RECONNECT_MAX_MS
- )
- const delay = backoff + Math.floor(Math.random() * WS_RECONNECT_JITTER_MS)
- this.reconnectAttempts++
- console.log(`[IM WS] reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`)
- this.reconnectTimer = setTimeout(() => {
- this.connect()
- }, delay)
- },
-
- /** 蹇冭烦 5 绉掍竴娆★紝淇濇椿 + 鎺㈡椿锛堥摼璺柇浜� onclose 浼氳Е鍙戯紝鐢� reconnect 鍏滃簳锛� */
- startHeartbeat() {
- if (this.heartbeatTimer) clearInterval(this.heartbeatTimer)
- this.heartbeatTimer = setInterval(() => {
- if (this.socket && this.isConnected) {
- this.sendHeartBeat()
- }
- }, 5000)
- },
-
- /** 鍋滃績璺筹細disconnect / 閲嶈繛鍓嶈皟锛岄伩鍏嶈�� timer 鍦ㄦ柊 socket 涓婄户缁Е鍙� sendHeartBeat */
- stopHeartbeat() {
- if (this.heartbeatTimer) {
- clearInterval(this.heartbeatTimer)
- this.heartbeatTimer = null
- }
- },
-
- // ==================== 瀹炴椂閫氳瘽淇′护鍒嗗彂 ====================
-
- /**
- * 閫氳瘽淇′护鍒嗗彂锛�1601 RTC_CALL锛堟寜 status 鍖哄垎 INVITING / JOINED / REJECTED / NO_ANSWER / LEFT锛�+ 1602 / 1603 鍙備笌鑰呭姞鍏� / 绂诲紑
- * <p>
- * 鍗曚竴 dispatcher锛屾寜 type 鍒嗗彂鍒� rtcStore
- */
- handleRtcSignaling(websocketMessage: ImNoConversationNotification) {
- const rtcStore = useRtcStore()
- switch (websocketMessage.type) {
- case ImContentType.RTC_CALL: {
- const payload = websocketMessage as unknown as ImRtcCallNotification
- switch (payload.status) {
- case ImRtcParticipantStatus.INVITING: {
- if (!isValidRtcInvitePayload(payload)) {
- console.warn('[IM WS] RTC_CALL invite payload 涓嶅悎娉�', payload)
- return
- }
- // 褰撳墠宸插湪閫氳瘽涓細蹇界暐鏂版潵鐢碉紱鍚庣灞傞潰涔熶細鎷掔粷锛岃繖閲屾槸鍏滃簳
- if (!rtcStore.isActive) {
- rtcStore.showIncoming(payload)
- }
- break
- }
- case ImRtcParticipantStatus.JOINED:
- case ImRtcParticipantStatus.LEFT: {
- // ACCEPT / HUNGUP 鏆備笉闇�瑕佹湰绔澶栧搷搴旓紱rtcStore 鐘舵�佺敱 1602/1603 + END 缁存姢
- break
- }
- case ImRtcParticipantStatus.NO_ANSWER: {
- // 缇ら�氳瘽鍗曚汉鎸搩瓒呮椂锛涗俊浠ょ嫭绔嬩繚鐣欒涔夛紝澶勭悊涓� REJECTED 涓�鑷�
- rtcStore.applyParticipantNoAnswer(payload)
- break
- }
- case ImRtcParticipantStatus.REJECTED: {
- rtcStore.applyParticipantRejected(payload)
- break
- }
- default: {
- console.warn('[IM WS] 鏈瘑鍒殑 RTC_CALL status', payload)
- }
- }
- return
- }
- case ImContentType.RTC_PARTICIPANT_CONNECTED: {
- const payload = websocketMessage as unknown as ImRtcParticipantConnectedNotification
- if (payload?.room && payload.userId) {
- rtcStore.applyParticipantConnected(payload)
- }
- return
- }
- case ImContentType.RTC_PARTICIPANT_DISCONNECTED: {
- const payload = websocketMessage as unknown as ImRtcParticipantDisconnectedNotification
- if (payload?.room && payload.userId) {
- rtcStore.applyParticipantDisconnected(payload)
- }
- }
- }
- },
-
- /** RTC_CALL_START 閫氳瘽寮�濮� */
- handleRtcCallStart(websocketMessage: ImGroupMessageNotification) {
- const payload = parseRtcCallPayload(websocketMessage.content)
- if (!payload?.room || !payload.mediaType || !payload.inviterUserId) {
- console.warn('[IM WS] RTC_CALL_START payload 涓嶅悎娉�', {
- groupId: websocketMessage.groupId,
- messageId: websocketMessage.id,
- contentLength: websocketMessage.content?.length ?? 0
- })
- return
- }
- useRtcStore().setGroupCall({
- room: payload.room,
- groupId: websocketMessage.groupId,
- mediaType: payload.mediaType,
- inviterId: payload.inviterUserId,
- joinedUserIds: [payload.inviterUserId],
- inviteeIds: []
- })
- },
-
- /**
- * RTC_CALL_END 閫氳瘽缁撴潫锛涚鑱� + 缇よ亰閮借蛋杩欎竴鏉★紱payload 鎼哄甫 conversationType 鍖哄垎
- * <p>
- * 绉佽亰锛氬叧闂綋鍓嶉�氳瘽绐�
- * 缇よ亰锛氱Щ闄よ兌鍥婃潯锛涘鏈鍦ㄨ缇ら�氳瘽鍐呭垯鍏抽棴閫氳瘽绐�
- */
- handleRtcCallEnd(
- websocketMessage: ImGroupMessageNotification | ImPrivateMessageNotification
- ) {
- const payload = this.safeParse(websocketMessage.content) as ImRtcCallEndNotification | null
- if (!payload?.room) {
- return
- }
- const rtcStore = useRtcStore()
- const isGroup = payload.conversationType === ImConversationType.GROUP
- // 缇ら�氳瘽锛氱Щ闄ゅ搴旀埧闂寸殑鑳跺泭鏉�
- const groupId = (websocketMessage as ImGroupMessageNotification).groupId
- if (isGroup && groupId) {
- rtcStore.removeGroupCall(groupId, payload.room)
- }
- // 閫氳瘽绐� / 鏉ョ數绐楁寚鍚戝悓涓� room 鏃跺叧闂細
- // RUNNING / INVITING 闃舵瀵规瘮 call.room锛汭NCOMING 闃舵瀵规瘮 incomingPayload.room
- const matchCall = rtcStore.call?.room === payload.room
- const matchIncoming = rtcStore.incomingPayload?.room === payload.room
- if (rtcStore.isActive && (matchCall || matchIncoming)) {
- const reasonText = resolveCallEndReasonText(payload.endReason)
- console.info('[Call] end:', reasonText)
- rtcStore.reset()
- }
- }
- }
-})
-
-export const useImWebSocketStoreWithOut = () => {
- return useImWebSocketStore()
-}
-
-// dev: 璁� Pinia 鐨� actions / state 鏀瑰姩鏀寔 HMR锛岄伩鍏嶆瘡娆℃敼 store 閮藉緱纭埛
-// 鍚﹀垯 Vite 鎶婃柊妯″潡鎺ㄤ笅鏉ュ悗锛岃�� store 瀹炰緥鐨� action 闂寘浠嶆寚鍚戞棫鍑芥暟浣�
-if (import.meta.hot) {
- import.meta.hot.accept(acceptHMRUpdate(useImWebSocketStore, import.meta.hot))
-}
diff --git a/src/views/im/home/types/index.ts b/src/views/im/home/types/index.ts
deleted file mode 100644
index aaa2011..0000000
--- a/src/views/im/home/types/index.ts
+++ /dev/null
@@ -1,318 +0,0 @@
-// ==================== WebSocket 甯� / 浜嬩欢 ====================
-
-// 鍚庣 WebSocket 缁熶竴甯х粨鏋勶細{ type, content }
-export interface WebSocketFrame {
- type: string // 甯х被鍨嬶紝瀵归綈 ImWebSocketMessageType
- content: string // 甯у唴瀹癸紙JSON 瀛楃涓诧級
-}
-
-// IM WebSocket 閫氱煡 DTO锛堝榻愬悗绔� ImNotificationWebSocketDTO锛�
-export interface ImNotificationWebSocketDTO {
- conversationType: number // 浼氳瘽绫诲瀷
- contentType: number // 鍐呭绫诲瀷
- payload: Record<string, any> // 璐熻浇鏁版嵁
-}
-
-// 鏃犱細璇濆湪绾块�氱煡锛堝榻愬悗绔� conversationType = NONE 鐨勭嫭绔� payload锛�
-export interface ImNoConversationNotification {
- type: number // 鍐呭绫诲瀷
- [key: string]: any
-}
-
-// 绉佽亰娑堟伅 DTO锛堝榻愬悗绔� ImPrivateMessageNotification锛�
-export interface ImPrivateMessageNotification {
- id: number // 娑堟伅缂栧彿
- clientMessageId: string // 瀹㈡埛绔秷鎭紪鍙�
- senderId: number // 鍙戦�佷汉缂栧彿
- receiverId: number // 鎺ユ敹浜虹紪鍙�
- type: number // 鍐呭绫诲瀷
- content: string // 娑堟伅鍐呭
- status: number // 娑堟伅鐘舵��
- receiptStatus?: number // 鍥炴墽鐘舵�侊紙涓嶉渶瑕� / 寰呭畬鎴� / 宸插畬鎴愶級
- sendTime: string // 鍙戦�佹椂闂�
-}
-
-// 缇よ亰娑堟伅 DTO锛堝榻愬悗绔� ImGroupMessageNotification锛�
-export interface ImGroupMessageNotification {
- id: number // 娑堟伅缂栧彿
- clientMessageId: string // 瀹㈡埛绔秷鎭紪鍙�
- senderId: number // 鍙戦�佷汉缂栧彿
- groupId: number // 缇ょ紪鍙�
- type: number // 鍐呭绫诲瀷
- content: string // 娑堟伅鍐呭
- status: number // 娑堟伅鐘舵��
- sendTime: string // 鍙戦�佹椂闂�
- atUserIds?: number[] // 缇� @ 鐩爣鐢ㄦ埛鍒楄〃
- receiverUserIds?: number[] // 缇ゅ畾鍚戞帴鏀剁敤鎴峰垪琛�
- readCount?: number // 缇ゅ洖鎵у凡璇讳汉鏁帮紙type = RECEIPT 鏃朵娇鐢級
- receiptStatus?: number // 缇ゅ洖鎵х姸鎬侊紙type = RECEIPT 鏃朵娇鐢級
- readId?: number // 宸茶浣嶇疆
-}
-
-// 娑堟伅宸茶鍚屾閫氱煡锛堝榻愬悗绔� ImMessageReadNotification锛�
-export interface ImMessageReadNotification {
- id: number // 宸茶浣嶇疆
- type: number // 鍐呭绫诲瀷
- senderId?: number // 鍙戦�佷汉缂栧彿
- receiverId?: number // 绉佽亰鎺ユ敹浜虹紪鍙�
- groupId?: number // 缇ょ紪鍙�
- channelId?: number // 棰戦亾缂栧彿
- readId?: number // 宸茶浣嶇疆
-}
-
-// 娑堟伅鍥炴墽閫氱煡锛堝榻愬悗绔� ImMessageReceiptNotification锛�
-export interface ImMessageReceiptNotification {
- id: number // 娑堟伅缂栧彿
- type: number // 鍐呭绫诲瀷
- senderId?: number // 宸茶鏂圭敤鎴风紪鍙�
- receiverId?: number // 绉佽亰鎺ユ敹浜虹紪鍙�
- groupId?: number // 缇ょ紪鍙�
- readCount?: number // 缇ゅ洖鎵у凡璇讳汉鏁�
- receiptStatus?: number // 缇ゅ洖鎵х姸鎬�
-}
-
-// ==================== 鏈湴浼氳瘽 / 娑堟伅缁撴瀯 ====================
-
-/** 寮曠敤娑堟伅 */
-export interface QuoteMessage {
- messageId: number // 寮曠敤娑堟伅缂栧彿
- senderId: number // 寮曠敤娑堟伅鍙戦�佷汉缂栧彿
- type: number // 寮曠敤鍐呭绫诲瀷
- content: string // 寮曠敤娑堟伅鍐呭
-}
-
-// 浼氳瘽鏁版嵁缁撴瀯锛堝墠绔嚜鏈夌粨鏋勶紝鍚庣鏃犲搴斿疄浣擄級
-export interface Conversation {
- // ========== 鏍稿績鏍囪瘑 ==========
- targetId: number // 浼氳瘽鐩爣缂栧彿锛氱鑱�=瀵规柟 userId锛涚兢鑱�=groupId
- type: number // 浼氳瘽绫诲瀷锛屽榻� ImConversationType
-
- // ========== 灞曠ず瀛楁 ==========
- name: string // 灞曠ず鍚嶇О锛堢鑱�=濂藉弸鏄电О锛涚兢鑱�=缇ゅ悕锛�
- avatar: string // 澶村儚
- unreadCount: number // 鏈鏁�
-
- // ========== 鏈�鍚庝竴鏉℃秷鎭簨瀹炵储寮� ==========
- lastContent: string // 浼氳瘽鍒楄〃灞曠ず鐨勬渶鍚庝竴鏉℃秷鎭憳瑕�
- lastSendTime: number // 鏈�鍚庝竴鏉℃秷鎭椂闂达紝鐢ㄤ簬鎺掑簭
- lastSenderId?: number // 鍙戦�佷汉缂栧彿
- lastMessageType?: number // 鍐呭绫诲瀷锛屽榻� ImContentType
- lastMessageId?: number // 鏈�鍚庝竴鏉℃湇鍔$娑堟伅缂栧彿
- lastClientMessageId?: string // 鏈�鍚庝竴鏉″鎴风娑堟伅缂栧彿
- lastMessageStatus?: number // 鏈�鍚庝竴鏉℃秷鎭姸鎬�
- lastReceiptStatus?: number // 鏈�鍚庝竴鏉$兢鍥炴墽鐘舵��
- lastSelfSend?: boolean // 鏄惁鑷繁鍙戠殑
- lastSenderDisplayName?: string // 鍙戦�佷汉鏄剧ず鍚嶅揩鐓р�斺�斾粎浣� utils/user.getSenderDisplayName 瀹炴椂绠椾笉鍑虹湡鍚嶆椂鐨� fallback
-
- // ========== UI 鐘舵�� ==========
- deleted?: boolean // 鏄惁宸插垹闄わ紙杞垹鏍囪锛屾寔涔呭寲鏃惰繃婊わ級
- top?: boolean // 鏄惁缃《锛堟帓搴忔椂浼樺厛锛�
- silent?: boolean // 鏄惁鍏嶆墦鎵帮紙涓嶅睍绀烘湭璇诲窘鏍� + 涓嶅搷鎻愮ず闊筹級
- atMe?: boolean // 缇よ亰锛氭槸鍚︽湁浜� @鎴�
- atAll?: boolean // 缇よ亰锛氭槸鍚︽湁浜� @鍏ㄤ綋鎴愬憳
- reportedReadMessageId?: number // 宸蹭笂鎶ュ埌鏈嶅姟绔殑鏈�澶у凡璇绘秷鎭紪鍙�
- draft?: {
- html: string // 杈撳叆妗� HTML
- plain: string // 杈撳叆妗嗙函鏂囨湰
- reply?: QuoteMessage // 寮曠敤娑堟伅
- } // 杈撳叆妗嗚崏绋�
-}
-
-// 娑堟伅鏁版嵁缁撴瀯
-export interface Message {
- // ========== 鍚庣瀛楁锛堝榻� ImPrivateMessageNotification / ImGroupMessageNotification锛� ==========
- id?: number // 鏈嶅姟绔秷鎭紪鍙凤紝鍙戦�佷腑涓虹┖
- clientMessageId: string // 瀹㈡埛绔秷鎭紪鍙凤紝鏈湴鐢熸垚鐢ㄤ簬鍚堝苟鍘婚噸
- type: number // 鍐呭绫诲瀷锛屽榻� ImContentType
- content: string // 娑堟伅鍐呭锛孞SON 瀛楃涓�
- status: number // 娑堟伅鐘舵�侊紝瀵归綈 ImMessageStatus
- sendTime: number // 鍙戦�佹椂闂达紙鍓嶇杞绉掓椂闂存埑锛涘悗绔负 LocalDateTime 瀛楃涓诧級
- senderId: number // 鍙戦�佷汉缂栧彿
- atUserIds?: number[] // 缇� @ 鐩爣鐢ㄦ埛鍒楄〃
- receiverUserIds?: number[] // 缇ゅ畾鍚戞帴鏀剁敤鎴峰垪琛�
- receiptStatus?: number // 鍥炴墽鐘舵�侊紝瀵归綈 ImMessageReceiptStatus锛堢鑱� / 缇� / 棰戦亾閫氱敤锛�
- readCount?: number // 缇ゅ洖鎵у凡璇讳汉鏁帮紙浠呯兢娑堟伅锛�
- materialId?: number // 鍏宠仈棰戦亾绱犳潗缂栧彿锛堜粎棰戦亾娑堟伅 type=MATERIAL锛�
-
- // ========== 鍓嶇鎵╁睍瀛楁 ==========
- // 鍙戦�佷汉鏄剧ず鍚嶄竴寰嬫覆鏌撴椂瀹炴椂绠楋細utils/user.getSenderDisplayName / getSenderRealNickname
- // 涓嶅湪 Message 涓婂瓨浠讳綍鍚嶅瓧蹇収锛岄伩鍏嶅娉� / 缇ゆ樀绉板彉鏇村悗鍘嗗彶娑堟伅鏄剧ず闄堟棫
- targetId: number // 浼氳瘽鐩爣缂栧彿锛堢鑱�=瀵圭 userId / 缇よ亰=groupId锛夛紝涓� Conversation.targetId 涓�鑷�
- selfSend: boolean // 鏄惁鑷繁鍙戦�侊紙鍓嶇鎸� senderId 璁$畻锛�
- uploadProgress?: number // 濯掍綋娑堟伅涓婁紶杩涘害锛�0-100锛夛紱status=SENDING 鏈熼棿鎸佺画鏇存柊锛沘ck 鍚庣疆 undefined
- // 濯掍綋娑堟伅鍐呭瓨涓繚鐣欑殑鍘熷 File锛涗笅鍒掔嚎鍓嶇紑琛ㄧず涓嶈繘 JSON / 涓嶆寔涔呭寲锛圛DB 鎭㈠鍚庡繀涓� undefined锛�
- // 澶辫触閲嶈瘯鏃舵寜瀹冮噸璧颁笂浼狅紱椤甸潰鍒锋柊鍚庤瀛楁涓㈠け锛屾仮澶嶉樁娈电洿鎺� drop 鏁存潯娑堟伅
- _localFile?: File
- _ackMerging?: boolean // ack 鍚堝苟涓爣璁帮紝涓嶆寔涔呭寲
-}
-
-// ==================== IndexedDB 鏈湴瀛樺偍缁撴瀯 ====================
-
-/** 浼氳瘽 IndexedDB 瀛樺偍缁撴瀯 */
-export interface ConversationDO extends Conversation {
- clientConversationId: string // `${type}:${targetId}`
-}
-
-export interface ConversationRead {
- conversationType: number // 浼氳瘽绫诲瀷锛屽榻� ImConversationType
- targetId: number // 浼氳瘽鐩爣缂栧彿
- messageId: number // 褰撳墠鐢ㄦ埛宸茶鍒扮殑鏈�澶ф秷鎭紪鍙�
- updateTime?: number // 鏇存柊鏃堕棿
-}
-
-/** 浼氳瘽璇讳綅缃� IndexedDB 瀛樺偍缁撴瀯 */
-export interface ConversationReadDO extends ConversationRead {
- clientConversationId: string // `${conversationType}:${targetId}`
-}
-
-/** 娑堟伅 IndexedDB 瀛樺偍缁撴瀯 */
-export interface MessageDO extends Omit<Message, '_ackMerging' | '_localFile' | 'uploadProgress'> {
- messageKey: string // `${conversationType}:${id}` 鎴� `client:${clientMessageId}`
- conversationType: number // 浼氳瘽绫诲瀷锛屽榻� ImConversationType
- clientConversationId: string // ConversationDO.clientConversationId
-}
-
-/** 璁剧疆 IndexedDB 瀛樺偍缁撴瀯 */
-export interface SettingDO<T = unknown> {
- key: string
- value: T
- updateTime?: number
-}
-
-// ==================== 缇� / 缇ゆ垚鍛� ====================
-
-// 缇ゅ疄浣擄紙鍓嶇鍐呴儴缁撴瀯锛�
-export interface Group {
- // ========== 鍚庣瀛楁锛堝榻� ImGroupApi.GroupRespVO锛� ==========
- id: number // 缇ょ紪鍙�
- name: string // 缇ゅ悕绉�
- avatar?: string // 缇ゅご鍍�
- notice?: string // 缇ゅ叕鍛�
- ownerUserId?: number // 缇や富鐢ㄦ埛缂栧彿
- pinnedMessages?: Message[] // 缇ょ疆椤舵秷鎭垪琛�
- mutedAll?: boolean // 鏄惁鍏ㄧ兢绂佽█
- banned?: boolean // 鏄惁琚鐞嗗憳灏佺
- joinApproval?: boolean // 杩涚兢鏄惁闇�缇や富 / 绠$悊鍛樺鎵�
- joinStatus?: number // 褰撳墠鐧诲綍鐢ㄦ埛鍦ㄨ缇ょ殑鎴愬憳鐘舵�侊紙鍙傝 CommonStatusEnum锛�0 鍦ㄧ兢 / 1 宸查��缇わ級锛涘巻鍙查��缇ょ兢浠嶈繑鍥烇紝渚涘睍绀哄巻鍙叉秷鎭殑缇ゅ悕 / 澶村儚
-
- // ========== 鍓嶇鎵╁睍瀛楁锛坲ser-per-group 缁村害锛� ==========
- silent?: boolean // 鏄惁鍏嶆墦鎵般�備粠褰撳墠鐢ㄦ埛鐨� GroupMember 鍥炲~
- groupRemark?: string // 缇ゅ娉ㄣ�備粠褰撳墠鐢ㄦ埛鐨� GroupMember 鍥炲~锛堝綋鍓嶇敤鎴峰璇ョ兢鐨勮嚜瀹氫箟鍚嶏級
- members?: GroupMember[] // 缇ゆ垚鍛樼紦瀛橈紙鎸夐渶鎳掑姞杞斤級
- infoLoaded?: boolean // 缇よ鎯呮槸鍚﹀凡鍔犺浇锛屾湰杞細璇濆唴瀛樻爣璁帮紝涓嶆寔涔呭寲
- activeCallLoaded?: boolean // 缇ゆ椿璺冮�氳瘽鏄惁宸叉帰娴嬶紝鏈疆浼氳瘽鍐呭瓨鏍囪锛屼笉鎸佷箙鍖�
- activeCallExpired?: boolean // 缇ゆ椿璺冮�氳瘽鎺㈡祴鏄惁宸茶繃鏈�
- membersLoaded?: boolean // members 鏄惁"瀹屾暣鍔犺浇"鈥斺�斿彧鏈夋暣缇� loadGroupMemberList / fetchGroupMemberList 鍛戒腑鏃朵负 true锛沠etchGroupMember 鍗曟垚鍛樿ˉ榻愪笉缃綅锛岄伩鍏� fetchGroupMemberList(force=false) 鍛戒腑缂撳瓨鏃惰鍒ゆ暣缇ゅ凡鍔犺浇
- membersExpired?: boolean // 缇ゆ垚鍛樼紦瀛樻槸鍚﹀凡杩囨湡锛涢噸杩� / 閲嶆柊杩涘叆 IM 鍚庡彧鏍囪涓嶅垹闄わ紝涓嬫杩涘叆缇や細璇濆啀鍒锋柊
- memberCount?: number // 鎴愬憳鎬绘暟
-}
-
-/** 缇� IndexedDB 瀛樺偍缁撴瀯 */
-export type GroupDO = Omit<
- Group,
- 'activeCallExpired' | 'activeCallLoaded' | 'infoLoaded' | 'members' | 'membersExpired' | 'membersLoaded'
->
-
-// 缇ゆ垚鍛樺疄浣擄紙鍓嶇鍐呴儴缁撴瀯锛�
-export interface GroupMember {
- // ========== 鍚庣瀛楁锛堝榻� ImGroupMemberApi.GroupMemberRespVO锛� ==========
- id?: number // 缇ゆ垚鍛樺叧绯昏褰曠紪鍙�
- groupId: number // 缇ょ紪鍙�
- userId: number // 鐢ㄦ埛缂栧彿
- avatar?: string // 澶村儚
- nickname: string // 鐢ㄦ埛鏄电О
- displayUserName?: string // 璇ユ垚鍛樺湪缇ゅ唴鑷畾涔夋樀绉帮紙姣忎釜 member 涓�浠斤紱涓嶄笌 nickname 鍚堝苟锛岀敱娑堣垂鏂规寜闇�鍙栬垗锛�
- status?: number // 鍦ㄧ兢 / 閫�缇ょ姸鎬侊紝瀵归綈 CommonStatusEnum
- role?: number // 鎴愬憳瑙掕壊锛屽弬瑙� ImGroupMemberRole 鏋氫妇锛�1=缇や富 2=绠$悊鍛� 3=鏅�氭垚鍛�
- muteEndTime?: string // 绂佽█鍒版湡鏃堕棿锛圛SO 瀛楃涓诧級
-
- // ========== 鍓嶇鎵╁睍瀛楁 ==========
- isOwner?: boolean // 鏄惁缇や富锛堝墠绔粠 Group.ownerUserId 璁$畻锛�
-}
-
-// ==================== 濂藉弸 ====================
-
-// 濂藉弸瀹炰綋锛堝墠绔唴閮ㄧ粨鏋勶級
-export interface Friend {
- // ========== 鍚庣瀛楁锛堝榻� ImFriendApi.FriendRespVO锛� ==========
- id?: number // 濂藉弸鍏崇郴璁板綍缂栧彿锛堟湰鍦颁箰瑙傛柊澧炴椂鍙兘鏆傜己锛�
- friendUserId: number // 濂藉弸鐢ㄦ埛缂栧彿锛堜笌 Conversation.targetId 瀵归綈锛�
- nickname: string // 濂藉弸鏄电О锛堝鏂圭湡瀹炴樀绉帮紝姘歌繙涓嶈澶囨敞瑕嗙洊锛沀I 鏄剧ず璧� displayName || nickname锛�
- nicknamePinyin?: string // 鏄电О鐨勬嫾闊筹紙鍚庣鐢� Pinyin4j 绠楀ソ鍥炲~锛屽皬鍐欐棤绌烘牸锛�
- avatar?: string // 濂藉弸澶村儚
- silent?: boolean // 鏄惁鍏嶆墦鎵帮紙涓嶅睍绀烘湭璇诲窘鏍� + 涓嶅搷鎻愮ず闊筹級
- displayName?: string // 濂藉弸灞曠ず澶囨敞锛氫粎鑷繁鍙鐨勫埆鍚嶏紙鍗曞瓧娈典笉姝т箟锛屼笉甯� Friend 鍓嶇紑锛�
- displayNamePinyin?: string // 澶囨敞鐨勬嫾闊筹紙鍚庣鐢� Pinyin4j 绠楀ソ鍥炲~锛屽皬鍐欐棤绌烘牸锛�
- status?: number // 濂藉弸鐘舵�侊紝瀵归綈 CommonStatusEnum锛圖ISABLE = 宸插垹闄わ紝杞垹淇濈暀璁板綍锛�
- addSource?: number // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum
- pinned?: boolean // 鏄惁缃《鑱旂郴浜�
- blocked?: boolean // 鏄惁鎷夐粦锛堜粎鑷繁鍙锛屽崟杈瑰睆钄藉鏂圭鑱婃秷鎭級
- addTime?: number // 娣诲姞濂藉弸鏃堕棿锛堟绉掓椂闂存埑锛涘悗绔负 LocalDateTime 瀛楃涓诧紝鍦� convertFriend 杞崲锛�
- deleteTime?: number // 鍒犻櫎濂藉弸鏃堕棿锛堟绉掓椂闂存埑锛涘悗绔负 LocalDateTime 瀛楃涓诧紝鍦� convertFriend 杞崲锛�
-}
-
-/**
- * 濂藉弸鐢宠璁板綍锛堝墠绔唴閮ㄧ粨鏋勶紝瀵归綈鍚庣 ImFriendRequestApi.FriendRequestRespVO锛�
- */
-export interface FriendRequest {
- // ========== 鍚庣瀛楁锛堝榻� ImFriendRequestApi.FriendRequestRespVO锛� ==========
- id: number // 鐢宠缂栧彿
- fromUserId: number // 鍙戣捣鏂圭敤鎴风紪鍙�
- toUserId: number // 鎺ユ敹鏂圭敤鎴风紪鍙�
- handleResult: number // 澶勭悊缁撴灉锛�0=鏈鐞嗭紱1=鍚屾剰锛�2=鎷掔粷
- applyContent?: string // 鐢宠鐞嗙敱锛堝彂璧锋柟濉啓锛�
- handleContent?: string // 澶勭悊鐞嗙敱锛堟帴鏀舵柟鎷掔粷鏃跺彲閫夊~锛�
- addSource?: number // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum
- handleTime?: number // 澶勭悊鏃堕棿锛堟绉掓椂闂存埑锛�
- createTime: number // 鐢宠鍒涘缓鏃堕棿锛堟绉掓椂闂存埑锛�
-
- // ========== 鑱氬悎瀛楁锛堣嚜 AdminUser锛屼粎灞曠ず鐢級 ==========
- fromNickname?: string // 鍙戣捣鏂规樀绉�
- fromAvatar?: string // 鍙戣捣鏂瑰ご鍍�
- toNickname?: string // 鎺ユ敹鏂规樀绉�
- toAvatar?: string // 鎺ユ敹鏂瑰ご鍍�
-}
-
-// ==================== 鐢ㄦ埛鍚嶇墖 ====================
-
-// 鐢ㄦ埛绮剧畝淇℃伅锛堝榻愬悗绔� UserSimpleRespVO锛屽悕鐗� / 澶村儚 hover 绛夊満鏅叡鐢級
-export interface User {
- id: number
- nickname?: string
- avatar?: string
- sex?: number
- deptId?: number
- deptName?: string
-}
-
-// ==================== 鍒楄〃琛屽睍绀虹敤 Lite 绫诲瀷 ====================
-
-/**
- * 濂藉弸鍒楄〃琛岋細浠� Friend 娲剧敓鐨勫睍绀哄揩鐓�
- * - id 鐢� friendUserId锛堜笌鍒楄〃 click / 閫変腑姣斿涓�鑷达級锛屼笉鏄� Friend.id锛堝叧绯昏褰曚富閿級
- * - 杞垹锛坰tatus === DISABLE锛夌敱涓婃父 friendStore.getActiveFriendList / getActiveFriendLiteList 缁熶竴杩囨护鎺�
- */
-export interface FriendLite {
- id: number
- nickname: string
- nicknamePinyin?: string // 鏄电О鎷奸煶锛堢敤浜庡瓧姣嶅垎妗� / 鎷奸煶鎼滅储锛�
- avatar?: string
- displayName?: string
- displayNamePinyin?: string // 澶囨敞鎷奸煶锛堜紭鍏堜簬 nicknamePinyin 鍙備笌鍒嗘《锛�
-}
-
-/**
- * 缇ゅ垪琛ㄨ锛氫粠 Group 娲剧敓鐨勫睍绀哄揩鐓�
- * - showGroupName / showImage锛氳皟鐢ㄦ柟鍐冲畾甯︿笉甯﹀娉紙濡備釜浜哄娉ㄧ兢鍚嶏級锛涘睍绀烘寜 show* > 鍘熷�煎厹搴�
- * - showImageThumb锛氶珮棰戝垪琛ㄧ敤缂╃暐鍥撅紝閬垮厤鎷夊師鍥鹃樆濉炴粴鍔�
- */
-export interface GroupLite {
- id: number
- name?: string
- showGroupName?: string
- showImage?: string
- showImageThumb?: string
- memberCount?: number
- ownerId?: number
- joinApproval?: boolean // 杩涚兢鏄惁闇�缇や富 / 绠$悊鍛樺鎵�
-}
diff --git a/src/views/im/manager/channel/list/components/index.ts b/src/views/im/manager/channel/list/components/index.ts
deleted file mode 100644
index da6d660..0000000
--- a/src/views/im/manager/channel/list/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as ChannelSelect } from './select.vue';
diff --git a/src/views/im/manager/channel/list/components/select.vue b/src/views/im/manager/channel/list/components/select.vue
deleted file mode 100644
index 6195d1c..0000000
--- a/src/views/im/manager/channel/list/components/select.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerChannelApi } from '#/api/im/manager/channel';
-
-import { computed, onMounted, ref } from 'vue';
-
-import { Select } from 'ant-design-vue';
-
-import { getSimpleChannelList } from '#/api/im/manager/channel';
-
-defineOptions({ name: 'ImManagerChannelSelect' });
-
-const props = withDefaults(
- defineProps<{
- allowClear?: boolean
- disabled?: boolean
- modelValue?: number
- placeholder?: string
- }>(),
- {
- allowClear: true,
- disabled: false,
- modelValue: undefined,
- placeholder: '璇烽�夋嫨棰戦亾',
- },
-);
-
-const emit = defineEmits<{
- 'update:modelValue': [value: number | undefined];
-}>();
-
-const loading = ref(false);
-const channelList = ref<ImManagerChannelApi.Channel[]>([]);
-
-const value = computed({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const options = computed(() =>
- channelList.value.map((item) => ({ label: item.name, value: item.id })),
-);
-
-/** 鍔犺浇棰戦亾鍒楄〃 */
-async function loadChannelList() {
- loading.value = true;
- try {
- channelList.value = await getSimpleChannelList();
- } finally {
- loading.value = false;
- }
-}
-
-onMounted(loadChannelList);
-</script>
-
-<template>
- <Select
- v-model:value="value"
- :allow-clear="allowClear"
- :disabled="disabled"
- :loading="loading"
- :options="options"
- :placeholder="placeholder"
- class="w-full"
- />
-</template>
diff --git a/src/views/im/manager/channel/list/data.ts b/src/views/im/manager/channel/list/data.ts
deleted file mode 100644
index 733a71a..0000000
--- a/src/views/im/manager/channel/list/data.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form'
-import type { VxeTableGridOptions } from '#/adapter/vxe-table'
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src'
-import { getDictOptions } from '../../../../../packages/effects/hooks/src'
-
-import { z } from '#/adapter/form'
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false
- }
- },
- {
- fieldName: 'code',
- label: '棰戦亾缂栫爜',
- component: 'Input',
- componentProps: {
- placeholder: '濡� system_notice'
- },
- rules: z
- .string({ message: '棰戦亾缂栫爜涓嶈兘涓虹┖' })
- .regex(/^[a-z][a-z0-9_]*$/, '鍙兘鐢卞皬鍐欏瓧姣� / 鏁板瓧 / 涓嬪垝绾跨粍鎴愶紝涓斾互瀛楁瘝寮�澶�')
- },
- {
- fieldName: 'name',
- label: '棰戦亾鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '濡� 绯荤粺鍏憡'
- },
- rules: 'required'
- },
- {
- fieldName: 'avatar',
- label: '棰戦亾澶村儚',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 1
- },
- rules: 'required'
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- min: 0
- },
- rules: z.number().default(0)
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button'
- },
- rules: z.number().default(CommonStatusEnum.ENABLE)
- }
- ]
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'code',
- label: '缂栫爜',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '棰戦亾涓氬姟鐮�'
- }
- },
- {
- fieldName: 'name',
- label: '鍚嶇О',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '棰戦亾鍚嶇О'
- }
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��'
- }
- }
- ]
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 80
- },
- {
- field: 'avatar',
- title: '澶村儚',
- width: 80,
- slots: { default: 'avatar' }
- },
- {
- field: 'code',
- title: '缂栫爜',
- minWidth: 160
- },
- {
- field: 'name',
- title: '鍚嶇О',
- minWidth: 140
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- width: 80
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS }
- }
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime'
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' }
- }
- ]
-}
diff --git a/src/views/im/manager/channel/list/index.vue b/src/views/im/manager/channel/list/index.vue
deleted file mode 100644
index c5f3feb..0000000
--- a/src/views/im/manager/channel/list/index.vue
+++ /dev/null
@@ -1,130 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table'
-import type { ImManagerChannelApi } from '#/api/im/manager/channel'
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src'
-
-import { Image, message } from 'ant-design-vue'
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'
-import { deleteManagerChannel, getManagerChannelPage } from '#/api/im/manager/channel'
-import { $t } from '#/locales'
-
-import { useGridColumns, useGridFormSchema } from './data'
-import Form from './modules/form.vue'
-
-defineOptions({ name: 'ImManagerChannel' })
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true
-})
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query()
-}
-
-/** 鍒涘缓棰戦亾 */
-function handleCreate() {
- formModalApi.setData(null).open()
-}
-
-/** 缂栬緫棰戦亾 */
-function handleEdit(row: ImManagerChannelApi.Channel) {
- formModalApi.setData(row).open()
-}
-
-/** 鍒犻櫎棰戦亾 */
-async function handleDelete(row: ImManagerChannelApi.Channel) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0
- })
- try {
- await deleteManagerChannel(row.id)
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]))
- handleRefresh()
- } finally {
- hideLoading()
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema()
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerChannelPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues
- })
- }
- }
- },
- rowConfig: {
- keyField: 'id',
- isHover: true
- },
- toolbarConfig: {
- refresh: true,
- search: true
- }
- } as VxeTableGridOptions<ImManagerChannelApi.Channel>
-})
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="棰戦亾鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['棰戦亾']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:channel:create'],
- onClick: handleCreate
- }
- ]"
- />
- </template>
- <template #avatar="{ row }">
- <Image v-if="row.avatar" :src="row.avatar" :width="32" :height="32" />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['im:manager:channel:update'],
- onClick: handleEdit.bind(null, row)
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:channel:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row)
- }
- }
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/channel/list/modules/form.vue b/src/views/im/manager/channel/list/modules/form.vue
deleted file mode 100644
index 5358a36..0000000
--- a/src/views/im/manager/channel/list/modules/form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerChannelApi } from '#/api/im/manager/channel'
-
-import { computed, ref } from 'vue'
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src'
-
-import { message } from 'ant-design-vue'
-
-import { useVbenForm } from '#/adapter/form'
-import {
- createManagerChannel,
- getManagerChannel,
- updateManagerChannel
-} from '#/api/im/manager/channel'
-import { $t } from '#/locales'
-
-import { useFormSchema } from '../data'
-
-const emit = defineEmits(['success'])
-const formData = ref<ImManagerChannelApi.Channel>()
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['棰戦亾'])
- : $t('ui.actionTitle.create', ['棰戦亾'])
-})
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full'
- },
- formItemClass: 'col-span-2',
- labelWidth: 100
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false
-})
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate()
- if (!valid) {
- return
- }
- modalApi.lock()
- const data = (await formApi.getValues()) as ImManagerChannelApi.Channel
- try {
- await (formData.value?.id ? updateManagerChannel(data) : createManagerChannel(data))
- await modalApi.close()
- emit('success')
- message.success($t('ui.actionMessage.operationSuccess'))
- } finally {
- modalApi.unlock()
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined
- return
- }
- const data = modalApi.getData<ImManagerChannelApi.Channel>()
- if (!data || !data.id) {
- return
- }
- modalApi.lock()
- try {
- formData.value = await getManagerChannel(data.id)
- await formApi.setValues(formData.value)
- } finally {
- modalApi.unlock()
- }
- }
-})
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/im/manager/channel/material/components/index.ts b/src/views/im/manager/channel/material/components/index.ts
deleted file mode 100644
index a0d10a8..0000000
--- a/src/views/im/manager/channel/material/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as MaterialSelect } from './select.vue';
diff --git a/src/views/im/manager/channel/material/components/select.vue b/src/views/im/manager/channel/material/components/select.vue
deleted file mode 100644
index c5e47b8..0000000
--- a/src/views/im/manager/channel/material/components/select.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
-
-import { computed, ref, watch } from 'vue';
-
-import { Select } from 'ant-design-vue';
-
-import { getSimpleManagerChannelMaterialList } from '#/api/im/manager/channel/material';
-
-defineOptions({ name: 'ImManagerMaterialSelect' });
-
-const props = withDefaults(
- defineProps<{
- allowClear?: boolean
- channelId?: number
- modelValue?: number
- placeholder?: string
- }>(),
- {
- allowClear: true,
- channelId: undefined,
- modelValue: undefined,
- placeholder: '璇烽�夋嫨绱犳潗',
- },
-);
-
-const emit = defineEmits<{
- 'update:modelValue': [value: number | undefined];
-}>();
-
-const loading = ref(false);
-const materialList = ref<ImManagerChannelMaterialApi.Material[]>([]);
-
-const value = computed({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const options = computed(() =>
- materialList.value.map((item) => ({ label: item.title, value: item.id })),
-);
-
-/** 鎸夐閬撳姞杞界礌鏉� */
-async function loadMaterialList(channelId?: number) {
- emit('update:modelValue', undefined);
- materialList.value = [];
- if (!channelId) {
- return;
- }
- loading.value = true;
- try {
- materialList.value = await getSimpleManagerChannelMaterialList(channelId);
- } finally {
- loading.value = false;
- }
-}
-
-watch(() => props.channelId, loadMaterialList, { immediate: true });
-</script>
-
-<template>
- <Select
- v-model:value="value"
- :allow-clear="allowClear"
- :disabled="!channelId"
- :loading="loading"
- :options="options"
- :placeholder="placeholder"
- class="w-full"
- />
-</template>
diff --git a/src/views/im/manager/channel/material/data.ts b/src/views/im/manager/channel/material/data.ts
deleted file mode 100644
index 8b3f6c5..0000000
--- a/src/views/im/manager/channel/material/data.ts
+++ /dev/null
@@ -1,235 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-import { ChannelSelect } from '#/views/im/manager/channel/list/components';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'channelId',
- label: '鎵�灞為閬�',
- component: markRaw(ChannelSelect),
- rules: 'selectRequired',
- },
- {
- fieldName: 'type',
- label: '鍐呭绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IM_CHANNEL_MATERIAL_TYPE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(1),
- },
- {
- fieldName: 'title',
- label: '鏍囬',
- component: 'Input',
- componentProps: {
- maxlength: 128,
- placeholder: '璇疯緭鍏ユ爣棰�',
- },
- rules: 'required',
- },
- {
- fieldName: 'coverUrl',
- label: '灏侀潰鍥�',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 1,
- },
- },
- {
- fieldName: 'summary',
- label: '鎽樿',
- component: 'Textarea',
- componentProps: {
- maxlength: 255,
- placeholder: '璇疯緭鍏ユ憳瑕�',
- rows: 2,
- },
- },
- {
- fieldName: 'content',
- label: '姝f枃',
- component: 'RichTextarea',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 1,
- },
- },
- {
- fieldName: 'url',
- label: '璺宠浆閾炬帴',
- component: 'Input',
- componentProps: {
- placeholder: 'https://example.com/...',
- },
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type !== 1,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'channelId',
- label: '棰戦亾',
- component: markRaw(ChannelSelect),
- componentProps: {
- placeholder: '鍏ㄩ儴',
- },
- },
- {
- fieldName: 'title',
- label: '鏍囬',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '鏍囬鍏抽敭瀛�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 80,
- },
- {
- field: 'coverUrl',
- title: '灏侀潰',
- width: 80,
- slots: { default: 'cover' },
- },
- {
- field: 'channelName',
- title: '棰戦亾',
- minWidth: 120,
- },
- {
- field: 'type',
- title: '鍐呭绫诲瀷',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_CHANNEL_MATERIAL_TYPE },
- },
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 180,
- },
- {
- field: 'summary',
- title: '鎽樿',
- minWidth: 180,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 棰戦亾娑堟伅鎼滅储琛ㄥ崟 */
-export function useMessageGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'channelId',
- label: '棰戦亾',
- component: markRaw(ChannelSelect),
- componentProps: {
- placeholder: '鍏ㄩ儴',
- },
- },
- {
- fieldName: 'sendTime',
- label: '鍙戦�佹椂闂�',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 棰戦亾娑堟伅瀛楁 */
-export function useMessageGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 80,
- },
- {
- field: 'materialCoverUrl',
- title: '灏侀潰',
- width: 80,
- slots: { default: 'cover' },
- },
- {
- field: 'channelName',
- title: '棰戦亾',
- minWidth: 120,
- },
- {
- field: 'materialTitle',
- title: '绱犳潗鏍囬',
- minWidth: 180,
- },
- {
- field: 'receiverUserIds',
- title: '鎺ユ敹浜�',
- width: 120,
- slots: { default: 'receivers' },
- },
- {
- field: 'sendTime',
- title: '鍙戦�佹椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/im/manager/channel/material/index.vue b/src/views/im/manager/channel/material/index.vue
deleted file mode 100644
index eeddf5d..0000000
--- a/src/views/im/manager/channel/material/index.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Image, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerChannelMaterial,
- getManagerChannelMaterialPage,
-} from '#/api/im/manager/channel/material';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'ImManagerChannelMaterial' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓绱犳潗 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫绱犳潗 */
-function handleEdit(row: ImManagerChannelMaterialApi.Material) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎绱犳潗 */
-async function handleDelete(row: ImManagerChannelMaterialApi.Material) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.title]),
- duration: 0,
- });
- try {
- await deleteManagerChannelMaterial(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerChannelMaterialPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerChannelMaterialApi.Material>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="绱犳潗鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绱犳潗']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:channel-material:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #cover="{ row }">
- <Image
- v-if="row.coverUrl"
- :height="40"
- :src="row.coverUrl"
- :width="40"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['im:manager:channel-material:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:channel-material:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.title]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/channel/material/modules/form.vue b/src/views/im/manager/channel/material/modules/form.vue
deleted file mode 100644
index 1622eeb..0000000
--- a/src/views/im/manager/channel/material/modules/form.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerChannelMaterialApi } from '#/api/im/manager/channel/material';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createManagerChannelMaterial,
- getManagerChannelMaterial,
- updateManagerChannelMaterial,
-} from '#/api/im/manager/channel/material';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<ImManagerChannelMaterialApi.Material>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绱犳潗'])
- : $t('ui.actionTitle.create', ['绱犳潗']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- const data =
- (await formApi.getValues()) as ImManagerChannelMaterialApi.Material;
- try {
- await (formData.value?.id
- ? updateManagerChannelMaterial(data)
- : createManagerChannelMaterial(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- await formApi.resetForm();
- return;
- }
- const data = modalApi.getData<ImManagerChannelMaterialApi.Material>();
- await formApi.setValues({ type: 1 });
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getManagerChannelMaterial(data.id);
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/im/manager/channel/message/index.vue b/src/views/im/manager/channel/message/index.vue
deleted file mode 100644
index 48aa126..0000000
--- a/src/views/im/manager/channel/message/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerChannelMessageApi } from '#/api/im/manager/channel/message';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Image, message, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerChannelMessage,
- getManagerChannelMessagePage,
-} from '#/api/im/manager/channel/message';
-import { $t } from '#/locales';
-
-import { useMessageGridColumns, useMessageGridFormSchema } from '../material/data';
-import SendForm from './modules/send-form.vue';
-
-defineOptions({ name: 'ImManagerChannelMessage' });
-
-const [SendModal, sendModalApi] = useVbenModal({
- connectedComponent: SendForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鎵撳紑鍙戦�佸脊绐� */
-function handleSend() {
- sendModalApi.open();
-}
-
-/** 鍒犻櫎棰戦亾娑堟伅 */
-async function handleDelete(row: ImManagerChannelMessageApi.ChannelMessage) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.id]),
- duration: 0,
- });
- try {
- await deleteManagerChannelMessage(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useMessageGridFormSchema(),
- },
- gridOptions: {
- columns: useMessageGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerChannelMessagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerChannelMessageApi.ChannelMessage>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <SendModal @success="handleRefresh" />
- <Grid table-title="棰戦亾娑堟伅鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '绔嬪嵆鎺ㄩ��',
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:channel-message:send'],
- onClick: handleSend,
- },
- ]"
- />
- </template>
- <template #cover="{ row }">
- <Image
- v-if="row.materialCoverUrl"
- :height="40"
- :src="row.materialCoverUrl"
- :width="40"
- />
- </template>
- <template #receivers="{ row }">
- <Tag v-if="!row.receiverUserIds || row.receiverUserIds.length === 0" color="warning">
- 鍏ㄥ憳
- </Tag>
- <span v-else>{{ row.receiverUserIds.length }} 浜�</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:channel-message:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.id]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/channel/message/modules/send-form.vue b/src/views/im/manager/channel/message/modules/send-form.vue
deleted file mode 100644
index 9963e9d..0000000
--- a/src/views/im/manager/channel/message/modules/send-form.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { Form, FormItem, message, Radio, RadioGroup } from 'ant-design-vue';
-
-import { sendManagerChannelMessage } from '#/api/im/manager/channel/message';
-import { ChannelSelect } from '#/views/im/manager/channel/list/components';
-import { MaterialSelect } from '#/views/im/manager/channel/material/components';
-import { UserSelect } from '#/views/system/user/components';
-
-const emit = defineEmits(['success']);
-
-const formData = ref({
- channelId: undefined as number | undefined,
- materialId: undefined as number | undefined,
- receiverUserType: 'all' as 'all' | 'users',
- receiverUserIds: [] as number[],
-});
-
-/** 閲嶇疆琛ㄥ崟 */
-function resetForm() {
- formData.value = {
- channelId: undefined,
- materialId: undefined,
- receiverUserType: 'all',
- receiverUserIds: [],
- };
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- if (!formData.value.channelId || !formData.value.materialId) {
- message.warning('璇烽�夋嫨棰戦亾鍜岀礌鏉�');
- return;
- }
- if (
- formData.value.receiverUserType === 'users' &&
- formData.value.receiverUserIds.length === 0
- ) {
- message.warning('璇疯嚦灏戦�夋嫨涓�涓帴鏀剁敤鎴�');
- return;
- }
- modalApi.lock();
- try {
- await sendManagerChannelMessage({
- materialId: formData.value.materialId,
- receiverUserIds:
- formData.value.receiverUserType === 'users'
- ? formData.value.receiverUserIds
- : undefined,
- });
- await modalApi.close();
- emit('success');
- message.success('鎺ㄩ�佹垚鍔�');
- } finally {
- modalApi.unlock();
- }
- },
- onOpenChange(isOpen: boolean) {
- if (isOpen) {
- resetForm();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" title="绔嬪嵆鎺ㄩ�侀閬撴秷鎭�">
- <Form :label-col="{ style: { width: '110px' } }">
- <FormItem label="鎵�灞為閬�" required>
- <ChannelSelect
- v-model="formData.channelId"
- placeholder="璇烽�夋嫨棰戦亾锛堢敤浜庡姞杞界礌鏉愶級"
- />
- </FormItem>
- <FormItem label="绱犳潗" required>
- <MaterialSelect
- v-model="formData.materialId"
- :channel-id="formData.channelId"
- placeholder="璇烽�夋嫨绱犳潗"
- />
- </FormItem>
- <FormItem label="鍙椾紬">
- <RadioGroup v-model:value="formData.receiverUserType">
- <Radio value="all">鍏ㄥ憳</Radio>
- <Radio value="users">鎸囧畾鐢ㄦ埛</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem v-if="formData.receiverUserType === 'users'" label="鎺ユ敹鐢ㄦ埛" required>
- <UserSelect
- v-model="formData.receiverUserIds"
- multiple
- placeholder="璇烽�夋嫨鎺ユ敹鐢ㄦ埛"
- />
- </FormItem>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/im/manager/face/pack/data.ts b/src/views/im/manager/face/pack/data.ts
deleted file mode 100644
index c26615f..0000000
--- a/src/views/im/manager/face/pack/data.ts
+++ /dev/null
@@ -1,375 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 琛ㄦ儏鍖呰〃鍗� */
-export function usePackFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'icon',
- label: '灏侀潰',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 1,
- },
- },
- {
- fieldName: 'name',
- label: '鍚嶇О',
- component: 'Input',
- componentProps: {
- maxlength: 64,
- placeholder: '璇疯緭鍏ヨ〃鎯呭寘鍚嶇О',
- },
- rules: 'required',
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- min: 0,
- max: 9999,
- },
- rules: z.number().default(0),
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 琛ㄦ儏鍖呮悳绱㈣〃鍗� */
-export function usePackGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '琛ㄦ儏鍖�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ヨ〃鎯呭寘鍚嶇О',
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ儏鍖呭瓧娈� */
-export function usePackGridColumns(): VxeTableGridOptions['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- field: 'icon',
- title: '灏侀潰',
- width: 80,
- slots: { default: 'icon' },
- },
- {
- field: 'name',
- title: '鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- width: 80,
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 240,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 琛ㄦ儏琛ㄥ崟 */
-export function useItemFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'packId',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'url',
- label: '琛ㄦ儏鍥�',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 1,
- },
- rules: 'required',
- },
- {
- fieldName: 'name',
- label: '琛ㄦ儏鍚�',
- component: 'Input',
- componentProps: {
- maxlength: 64,
- placeholder: '璇疯緭鍏ヨ〃鎯呭悕',
- },
- },
- {
- fieldName: 'width',
- label: '瀹藉害',
- component: 'InputNumber',
- componentProps: {
- min: 1,
- max: 2048,
- },
- rules: 'required',
- },
- {
- fieldName: 'height',
- label: '楂樺害',
- component: 'InputNumber',
- componentProps: {
- min: 1,
- max: 2048,
- },
- rules: 'required',
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- min: 0,
- max: 9999,
- },
- rules: z.number().default(0),
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 琛ㄦ儏鎼滅储琛ㄥ崟 */
-export function useItemGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '琛ㄦ儏鍚�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ヨ〃鎯呭悕',
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- },
- },
- ];
-}
-
-/** 琛ㄦ儏瀛楁 */
-export function useItemGridColumns(): VxeTableGridOptions['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'id',
- title: '缂栧彿',
- width: 80,
- },
- {
- field: 'url',
- title: '琛ㄦ儏鍥�',
- width: 80,
- slots: { default: 'image' },
- },
- {
- field: 'name',
- title: '琛ㄦ儏鍚�',
- minWidth: 140,
- },
- {
- title: '灏哄',
- width: 120,
- slots: { default: 'size' },
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- width: 80,
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鐢ㄦ埛琛ㄦ儏鎼滅储琛ㄥ崟 */
-export function useUserItemGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛缂栧彿',
- component: 'InputNumber',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ョ敤鎴风紪鍙�',
- },
- },
- {
- fieldName: 'name',
- label: '琛ㄦ儏鍚�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ヨ〃鎯呭悕',
- },
- },
- {
- fieldName: 'createTime',
- label: '娣诲姞鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鐢ㄦ埛琛ㄦ儏瀛楁 */
-export function useUserItemGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- field: 'url',
- title: '琛ㄦ儏鍥�',
- width: 80,
- slots: { default: 'image' },
- },
- {
- field: 'name',
- title: '琛ㄦ儏鍚�',
- minWidth: 140,
- },
- {
- title: '鎵�灞炵敤鎴�',
- minWidth: 180,
- slots: { default: 'user' },
- },
- {
- title: '灏哄',
- width: 120,
- slots: { default: 'size' },
- },
- {
- field: 'createTime',
- title: '娣诲姞鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/im/manager/face/pack/index.vue b/src/views/im/manager/face/pack/index.vue
deleted file mode 100644
index 3bf6105..0000000
--- a/src/views/im/manager/face/pack/index.vue
+++ /dev/null
@@ -1,198 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
-
-import { ref } from 'vue';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { isEmpty } from '../../../../../packages/utils/src';
-
-import { Image, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerFacePack,
- deleteManagerFacePackList,
- getManagerFacePackPage,
-} from '#/api/im/manager/face/pack';
-import { $t } from '#/locales';
-
-import {
- usePackGridColumns,
- usePackGridFormSchema,
-} from './data';
-import Form from './modules/form.vue';
-import ItemDrawer from './modules/item-drawer.vue';
-
-defineOptions({ name: 'ImManagerFacePack' });
-
-const itemDrawerRef = ref<InstanceType<typeof ItemDrawer>>();
-const checkedIds = ref<number[]>([]);
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓琛ㄦ儏鍖� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫琛ㄦ儏鍖� */
-function handleEdit(row: ImManagerFacePackApi.FacePack) {
- formModalApi.setData(row).open();
-}
-
-/** 鎵撳紑琛ㄦ儏绠$悊 */
-function handleItems(row: ImManagerFacePackApi.FacePack) {
- itemDrawerRef.value?.open(row);
-}
-
-/** 鍒犻櫎琛ㄦ儏鍖� */
-async function handleDelete(row: ImManagerFacePackApi.FacePack) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteManagerFacePack(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎵归噺鍒犻櫎琛ㄦ儏鍖� */
-async function handleDeleteBatch() {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deletingBatch'),
- duration: 0,
- });
- try {
- await deleteManagerFacePackList(checkedIds.value);
- checkedIds.value = [];
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 琛ㄦ牸閫変腑鍙樺寲 */
-function handleRowCheckboxChange({
- records,
-}: {
- records: ImManagerFacePackApi.FacePack[];
-}) {
- checkedIds.value = records.map((item) => item.id);
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: usePackGridFormSchema(),
- },
- gridOptions: {
- columns: usePackGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerFacePackPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerFacePackApi.FacePack>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <ItemDrawer ref="itemDrawerRef" />
- <Grid table-title="琛ㄦ儏鍖呭垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['琛ㄦ儏鍖�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:face-pack:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.deleteBatch'),
- type: 'primary',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:face-pack:delete'],
- disabled: isEmpty(checkedIds),
- popConfirm: {
- title: $t('ui.actionMessage.deleteBatchConfirm'),
- confirm: handleDeleteBatch,
- },
- },
- ]"
- />
- </template>
- <template #icon="{ row }">
- <Image v-if="row.icon" :height="32" :src="row.icon" :width="32" />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '绠$悊琛ㄦ儏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['im:manager:face-pack-item:query'],
- onClick: handleItems.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['im:manager:face-pack:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:face-pack:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/face/pack/modules/form.vue b/src/views/im/manager/face/pack/modules/form.vue
deleted file mode 100644
index cfc186e..0000000
--- a/src/views/im/manager/face/pack/modules/form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createManagerFacePack,
- getManagerFacePack,
- updateManagerFacePack,
-} from '#/api/im/manager/face/pack';
-import { $t } from '#/locales';
-
-import { usePackFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<ImManagerFacePackApi.FacePack>();
-const getTitle = computed(() => {
- return formData.value?.id ? '淇敼琛ㄦ儏鍖�' : '鏂板琛ㄦ儏鍖�';
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: usePackFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- const data = (await formApi.getValues()) as ImManagerFacePackApi.FacePack;
- try {
- await (formData.value?.id
- ? updateManagerFacePack(data)
- : createManagerFacePack(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- await formApi.resetForm();
- return;
- }
- const data = modalApi.getData<ImManagerFacePackApi.FacePack>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getManagerFacePack(data.id);
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/im/manager/face/pack/modules/item-drawer.vue b/src/views/im/manager/face/pack/modules/item-drawer.vue
deleted file mode 100644
index 72feed4..0000000
--- a/src/views/im/manager/face/pack/modules/item-drawer.vue
+++ /dev/null
@@ -1,217 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerFacePackItemApi } from '#/api/im/manager/face/item';
-import type { ImManagerFacePackApi } from '#/api/im/manager/face/pack';
-
-import { computed, nextTick, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { Drawer, Image, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerFacePackItem,
- deleteManagerFacePackItemList,
- getManagerFacePackItemPage,
-} from '#/api/im/manager/face/item';
-import { $t } from '#/locales';
-
-import { useItemGridColumns, useItemGridFormSchema } from '../data';
-import ItemForm from './item-form.vue';
-
-const visible = ref(false);
-const currentPack = ref<ImManagerFacePackApi.FacePack>();
-const checkedIds = ref<number[]>([]);
-const title = computed(() =>
- currentPack.value ? `銆�${currentPack.value.name}銆嶈〃鎯呯鐞哷 : '琛ㄦ儏绠$悊',
-);
-
-const [ItemFormModal, itemFormModalApi] = useVbenModal({
- connectedComponent: ItemForm,
- destroyOnClose: true,
-});
-
-/** 鎵撳紑鎶藉眽 */
-function open(pack: ImManagerFacePackApi.FacePack) {
- currentPack.value = pack;
- visible.value = true;
- checkedIds.value = [];
-}
-
-defineExpose({ open });
-
-/** 鎶藉眽鎵撳紑鍚庢煡璇㈣〃鎯呭垪琛� */
-async function handleOpenChange(open: boolean) {
- if (!open) {
- return;
- }
- await nextTick();
- gridApi.query();
-}
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓琛ㄦ儏 */
-function handleCreate() {
- itemFormModalApi.setData({ packId: currentPack.value?.id }).open();
-}
-
-/** 缂栬緫琛ㄦ儏 */
-function handleEdit(row: ImManagerFacePackItemApi.FacePackItem) {
- itemFormModalApi
- .setData({ id: row.id, packId: currentPack.value?.id })
- .open();
-}
-
-/** 鍒犻櫎琛ㄦ儏 */
-async function handleDelete(row: ImManagerFacePackItemApi.FacePackItem) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name || row.id]),
- duration: 0,
- });
- try {
- await deleteManagerFacePackItem(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name || row.id]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎵归噺鍒犻櫎琛ㄦ儏 */
-async function handleDeleteBatch() {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deletingBatch'),
- duration: 0,
- });
- try {
- await deleteManagerFacePackItemList(checkedIds.value);
- checkedIds.value = [];
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 琛ㄦ牸閫変腑鍙樺寲 */
-function handleRowCheckboxChange({
- records,
-}: {
- records: ImManagerFacePackItemApi.FacePackItem[];
-}) {
- checkedIds.value = records.map((item) => item.id);
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useItemGridFormSchema(),
- },
- gridOptions: {
- columns: useItemGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerFacePackItemPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- packId: currentPack.value?.id,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerFacePackItemApi.FacePackItem>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-</script>
-
-<template>
- <Drawer
- v-model:open="visible"
- :title="title"
- destroy-on-close
- width="65%"
- @after-open-change="handleOpenChange"
- >
- <ItemFormModal @success="handleRefresh" />
- <Grid table-title="琛ㄦ儏鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['琛ㄦ儏']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:face-pack-item:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.deleteBatch'),
- type: 'primary',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:face-pack-item:delete'],
- disabled: isEmpty(checkedIds),
- popConfirm: {
- title: $t('ui.actionMessage.deleteBatchConfirm'),
- confirm: handleDeleteBatch,
- },
- },
- ]"
- />
- </template>
- <template #image="{ row }">
- <Image v-if="row.url" :height="40" :src="row.url" :width="40" />
- </template>
- <template #size="{ row }">
- <span v-if="row.width || row.height">
- {{ row.width || '?' }} 脳 {{ row.height || '?' }}
- </span>
- <span v-else>-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['im:manager:face-pack-item:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:face-pack-item:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name || row.id]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Drawer>
-</template>
diff --git a/src/views/im/manager/face/pack/modules/item-form.vue b/src/views/im/manager/face/pack/modules/item-form.vue
deleted file mode 100644
index 92663fd..0000000
--- a/src/views/im/manager/face/pack/modules/item-form.vue
+++ /dev/null
@@ -1,119 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerFacePackItemApi } from '#/api/im/manager/face/item';
-
-import { computed, ref, watch } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createManagerFacePackItem,
- getManagerFacePackItem,
- updateManagerFacePackItem,
-} from '#/api/im/manager/face/item';
-import { $t } from '#/locales';
-import { probeImageSize } from '#/views/im/manager/utils/format';
-
-import { useItemFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<ImManagerFacePackItemApi.FacePackItem>();
-const packId = ref(0);
-const lastUrl = ref('');
-const getTitle = computed(() => {
- return formData.value?.id ? '淇敼琛ㄦ儏' : '鏂板琛ㄦ儏';
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useItemFormSchema(),
- showDefaultActions: false,
-});
-
-/** 鍥炲~鍥剧墖灏哄 */
-async function applyImageSize(url?: string) {
- if (!url) {
- lastUrl.value = '';
- await formApi.setFieldValue('width', undefined);
- await formApi.setFieldValue('height', undefined);
- return;
- }
- if (url === lastUrl.value) {
- return;
- }
- lastUrl.value = url;
- try {
- const size = await probeImageSize(url);
- await formApi.setFieldValue('width', size.width);
- await formApi.setFieldValue('height', size.height);
- } catch {
- message.warning('鍥剧墖灏哄鎺㈡祴澶辫触锛岃鎵嬪姩濉啓瀹介珮');
- }
-}
-
-watch(
- () => formApi.form.values?.url,
- (url) => {
- void applyImageSize(url as string | undefined);
- },
-);
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- const data = (await formApi.getValues()) as ImManagerFacePackItemApi.FacePackItem;
- try {
- data.packId = data.packId || packId.value;
- await (formData.value?.id
- ? updateManagerFacePackItem(data)
- : createManagerFacePackItem(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- lastUrl.value = '';
- await formApi.resetForm();
- return;
- }
- const data = modalApi.getData<{ id?: number; packId: number }>();
- packId.value = data?.packId || 0;
- await formApi.setValues({ packId: packId.value, sort: 0 });
- if (!data?.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getManagerFacePackItem(data.id);
- lastUrl.value = formData.value.url;
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/im/manager/face/userItem/index.vue b/src/views/im/manager/face/userItem/index.vue
deleted file mode 100644
index cc0b377..0000000
--- a/src/views/im/manager/face/userItem/index.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerFaceUserItemApi } from '#/api/im/manager/face/userItem';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { Image, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerFaceUserItem,
- getManagerFaceUserItemPage,
-} from '#/api/im/manager/face/userItem';
-import { $t } from '#/locales';
-import { formatUserLabel } from '#/views/im/manager/utils/format';
-
-import { useUserItemGridColumns, useUserItemGridFormSchema } from '../pack/data';
-
-defineOptions({ name: 'ImManagerFaceUserItem' });
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒犻櫎鐢ㄦ埛琛ㄦ儏 */
-async function handleDelete(row: ImManagerFaceUserItemApi.FaceUserItem) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name || row.id]),
- duration: 0,
- });
- try {
- await deleteManagerFaceUserItem(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name || row.id]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useUserItemGridFormSchema(),
- },
- gridOptions: {
- columns: useUserItemGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerFaceUserItemPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerFaceUserItemApi.FaceUserItem>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="鐢ㄦ埛琛ㄦ儏鍒楄〃">
- <template #image="{ row }">
- <Image v-if="row.url" :height="40" :src="row.url" :width="40" />
- </template>
- <template #user="{ row }">
- {{ formatUserLabel(row.userNickname, row.userId) }}
- </template>
- <template #size="{ row }">
- <span v-if="row.width || row.height">
- {{ row.width || '?' }} 脳 {{ row.height || '?' }}
- </span>
- <span v-else>-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:face-user-item:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name || row.id]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/friend/data.ts b/src/views/im/manager/friend/data.ts
deleted file mode 100644
index a3becbb..0000000
--- a/src/views/im/manager/friend/data.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-import { UserSelect } from '#/views/system/user/components';
-
-/** 濂藉弸鍏崇郴鎼滅储琛ㄥ崟 */
-export function useFriendGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛',
- component: markRaw(UserSelect),
- },
- {
- fieldName: 'friendUserId',
- label: '濂藉弸',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨濂藉弸',
- },
- },
- {
- fieldName: 'status',
- label: '濂藉弸鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_FRIEND_STATUS, 'number'),
- placeholder: '璇烽�夋嫨濂藉弸鐘舵��',
- },
- },
- {
- fieldName: 'silent',
- label: '鍏嶆墦鎵�',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- placeholder: '璇烽�夋嫨鍏嶆墦鎵扮姸鎬�',
- },
- },
- {
- fieldName: 'addTime',
- label: '娣诲姞鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 濂藉弸鍏崇郴鍒楄〃瀛楁 */
-export function useFriendGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '鐢ㄦ埛',
- minWidth: 180,
- slots: { default: 'user' },
- },
- {
- title: '濂藉弸',
- minWidth: 180,
- slots: { default: 'friend' },
- },
- {
- field: 'displayName',
- title: '澶囨敞',
- minWidth: 120,
- },
- {
- field: 'addSource',
- title: '娣诲姞鏉ユ簮',
- width: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_FRIEND_ADD_SOURCE },
- },
- },
- {
- field: 'silent',
- title: '鍏嶆墦鎵�',
- width: 90,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'pinned',
- title: '缃《',
- width: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'blocked',
- title: '鎷夐粦',
- width: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_FRIEND_STATUS },
- },
- },
- {
- field: 'addTime',
- title: '娣诲姞鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'deleteTime',
- title: '鍒犻櫎鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 濂藉弸鐢宠鎼滅储琛ㄥ崟 */
-export function useFriendRequestGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'fromUserId',
- label: '鍙戣捣鏂�',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鍙戣捣鏂�',
- },
- },
- {
- fieldName: 'toUserId',
- label: '鎺ユ敹鏂�',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鎺ユ敹鏂�',
- },
- },
- {
- fieldName: 'handleResult',
- label: '澶勭悊缁撴灉',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_FRIEND_REQUEST_HANDLE_RESULT, 'number'),
- placeholder: '璇烽�夋嫨澶勭悊缁撴灉',
- },
- },
- {
- fieldName: 'addSource',
- label: '娣诲姞鏉ユ簮',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_FRIEND_ADD_SOURCE, 'number'),
- placeholder: '璇烽�夋嫨娣诲姞鏉ユ簮',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 濂藉弸鐢宠鍒楄〃瀛楁 */
-export function useFriendRequestGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '鍙戣捣鏂�',
- minWidth: 180,
- slots: { default: 'fromUser' },
- },
- {
- title: '鎺ユ敹鏂�',
- minWidth: 180,
- slots: { default: 'toUser' },
- },
- {
- field: 'applyContent',
- title: '鐢宠鐞嗙敱',
- minWidth: 160,
- },
- {
- field: 'displayName',
- title: '澶囨敞',
- minWidth: 120,
- },
- {
- field: 'addSource',
- title: '娣诲姞鏉ユ簮',
- width: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_FRIEND_ADD_SOURCE },
- },
- },
- {
- field: 'handleResult',
- title: '澶勭悊缁撴灉',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_FRIEND_REQUEST_HANDLE_RESULT },
- },
- },
- {
- field: 'handleContent',
- title: '澶勭悊鐞嗙敱',
- minWidth: 140,
- },
- {
- field: 'handleTime',
- title: '澶勭悊鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/im/manager/friend/index.vue b/src/views/im/manager/friend/index.vue
deleted file mode 100644
index aa5976d..0000000
--- a/src/views/im/manager/friend/index.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerFriendApi } from '#/api/im/manager/friend';
-
-import { useRouter } from 'vue-router';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerFriendPage } from '#/api/im/manager/friend';
-import { formatUserLabel } from '#/views/im/manager/utils/format';
-
-import { useFriendGridColumns, useFriendGridFormSchema } from './data';
-
-defineOptions({ name: 'ImManagerFriend' });
-
-const router = useRouter();
-
-/** 鏌ョ湅绉佽亰娑堟伅 */
-function handleConversation(row: ImManagerFriendApi.Friend) {
- router.push({
- name: 'ImPrivateMessage',
- query: {
- senderId: row.userId,
- receiverId: row.friendUserId,
- },
- });
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useFriendGridFormSchema(),
- },
- gridOptions: {
- columns: useFriendGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerFriendPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerFriendApi.Friend>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="濂藉弸鍏崇郴鍒楄〃">
- <template #user="{ row }">
- {{ formatUserLabel(row.userNickname, row.userId) }}
- </template>
- <template #friend="{ row }">
- {{ formatUserLabel(row.friendNickname, row.friendUserId) }}
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鏌ョ湅瀵硅瘽',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleConversation.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/friend/request/index.vue b/src/views/im/manager/friend/request/index.vue
deleted file mode 100644
index 130ce37..0000000
--- a/src/views/im/manager/friend/request/index.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerFriendRequestApi } from '#/api/im/manager/friend/request';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerFriendRequestPage } from '#/api/im/manager/friend/request';
-import { formatUserLabel } from '#/views/im/manager/utils/format';
-
-import {
- useFriendRequestGridColumns,
- useFriendRequestGridFormSchema,
-} from '../data';
-
-defineOptions({ name: 'ImManagerFriendRequest' });
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useFriendRequestGridFormSchema(),
- },
- gridOptions: {
- columns: useFriendRequestGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerFriendRequestPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerFriendRequestApi.FriendRequest>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="濂藉弸鐢宠鍒楄〃">
- <template #fromUser="{ row }">
- {{ formatUserLabel(row.fromNickname, row.fromUserId) }}
- </template>
- <template #toUser="{ row }">
- {{ formatUserLabel(row.toNickname, row.toUserId) }}
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/group/components/index.ts b/src/views/im/manager/group/components/index.ts
deleted file mode 100644
index 4bbf84f..0000000
--- a/src/views/im/manager/group/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as GroupSelectDialog } from './select-dialog.vue';
-export { default as GroupSelect } from './select.vue';
diff --git a/src/views/im/manager/group/components/select-dialog.vue b/src/views/im/manager/group/components/select-dialog.vue
deleted file mode 100644
index 64a2aba..0000000
--- a/src/views/im/manager/group/components/select-dialog.vue
+++ /dev/null
@@ -1,271 +0,0 @@
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerGroupApi } from '#/api/im/manager/group';
-
-import { nextTick, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { Button, message, Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- getManagerGroup,
- getManagerGroupPage,
-} from '#/api/im/manager/group';
-
-const emit = defineEmits<{
- selected: [rows: ImManagerGroupApi.Group[]];
-}>();
-
-const open = ref(false); // 寮圭獥鏄惁鎵撳紑
-const multiple = ref(false); // 鏄惁澶氶��
-const selectedRows = ref<ImManagerGroupApi.Group[]>([]); // 宸查�夌兢鍒楄〃
-const preSelectedIds = ref<number[]>([]); // 棰勯�夌兢缂栧彿鍒楄〃
-
-/** 缇ら�夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
-function useGroupSelectGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '缇ゅ悕绉�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ョ兢鍚嶇О',
- },
- },
- {
- fieldName: 'status',
- label: '缇ょ姸鎬�',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_GROUP_STATUS, 'number'),
- placeholder: '璇烽�夋嫨缇ょ姸鎬�',
- },
- },
- ];
-}
-
-/** 缇ら�夋嫨寮圭獥鐨勫瓧娈� */
-function useGroupSelectGridColumns(
- isMultiple = false,
-): VxeTableGridOptions<ImManagerGroupApi.Group>['columns'] {
- return [
- {
- type: isMultiple ? 'checkbox' : 'radio',
- width: 50,
- },
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- field: 'name',
- title: '缇ゅ悕绉�',
- minWidth: 160,
- },
- {
- field: 'ownerNickname',
- title: '缇や富',
- minWidth: 160,
- },
- {
- field: 'memberCount',
- title: '鎴愬憳鏁�',
- width: 90,
- },
- {
- field: 'status',
- title: '缇ょ姸鎬�',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_GROUP_STATUS },
- },
- },
- ];
-}
-
-/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
-function getMultipleSelectedRows() {
- const selectedMap = new Map<number, ImManagerGroupApi.Group>();
- const records = [
- ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
- ...(gridApi.grid.getCheckboxRecords?.() ?? []),
- ] as ImManagerGroupApi.Group[];
- records.forEach((row) => {
- selectedMap.set(row.id, row);
- });
- return [...selectedMap.values()];
-}
-
-/** 澶勭悊澶氶�夊嬀閫夊彉鍖� */
-function handleCheckboxSelectChange() {
- selectedRows.value = getMultipleSelectedRows();
-}
-
-/** 澶勭悊鍗曢�夊垏鎹� */
-function handleRadioChange(row: ImManagerGroupApi.Group) {
- selectedRows.value = [row];
-}
-
-/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
-async function toggleMultipleRow(row: ImManagerGroupApi.Group) {
- const selected = gridApi.grid.isCheckedByCheckboxRow(row);
- await gridApi.grid.setCheckboxRow(row, !selected);
- selectedRows.value = getMultipleSelectedRows();
-}
-
-/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紝澶氶�夊垏鎹㈠嬀閫� */
-async function handleCellDblclick({ row }: { row: ImManagerGroupApi.Group }) {
- if (multiple.value) {
- await toggleMultipleRow(row);
- return;
- }
- selectedRows.value = [row];
- await gridApi.grid.setRadioRow(row);
- handleConfirm();
-}
-
-/** 鍥炴樉棰勯�夌兢 */
-async function applyPreSelection() {
- if (preSelectedIds.value.length === 0) {
- return;
- }
- const rows = gridApi.grid.getData() as ImManagerGroupApi.Group[];
- for (const row of rows) {
- if (!preSelectedIds.value.includes(row.id)) {
- continue;
- }
- if (multiple.value) {
- await gridApi.grid.setCheckboxRow(row, true);
- } else {
- await gridApi.grid.setRadioRow(row);
- selectedRows.value = [row];
- return;
- }
- }
- if (multiple.value) {
- selectedRows.value = getMultipleSelectedRows();
- return;
- }
- const targetId = preSelectedIds.value[0];
- if (targetId) {
- selectedRows.value = [await getManagerGroup(targetId)];
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGroupSelectGridFormSchema(),
- },
- gridOptions: {
- columns: useGroupSelectGridColumns(false),
- height: 520,
- keepSource: true,
- checkboxConfig: {
- highlight: true,
- range: true,
- reserve: true,
- },
- radioConfig: {
- highlight: true,
- trigger: 'row',
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerGroupPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerGroupApi.Group>,
- gridEvents: {
- cellDblclick: handleCellDblclick,
- checkboxAll: handleCheckboxSelectChange,
- checkboxChange: handleCheckboxSelectChange,
- radioChange: ({ row }: { row: ImManagerGroupApi.Group }) => {
- handleRadioChange(row);
- },
- },
-});
-
-/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
-async function resetQueryState() {
- selectedRows.value = [];
- await gridApi.grid.clearCheckboxRow();
- await gridApi.grid.clearCheckboxReserve();
- await gridApi.grid.clearRadioRow();
- await gridApi.formApi.resetForm();
-}
-
-/** 鎵撳紑缇ら�夋嫨寮圭獥 */
-async function openModal(
- selectedIds?: number[],
- options?: { multiple?: boolean },
-) {
- open.value = true;
- multiple.value = options?.multiple ?? false;
- preSelectedIds.value = selectedIds || [];
- await nextTick();
- gridApi.setGridOptions({
- columns: useGroupSelectGridColumns(multiple.value),
- });
- await resetQueryState();
- await gridApi.query();
- await nextTick();
- await applyPreSelection();
-}
-
-/** 鍏抽棴缇ら�夋嫨寮圭獥 */
-function closeModal() {
- open.value = false;
-}
-
-/** 纭閫夋嫨缇� */
-function handleConfirm() {
- const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
- if (rows.length === 0) {
- message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
- return;
- }
- emit('selected', multiple.value ? rows : [rows[0]!]);
- open.value = false;
-}
-
-defineExpose({ open: openModal });
-</script>
-
-<template>
- <Modal
- v-model:open="open"
- title="缇ら�夋嫨"
- width="70%"
- :destroy-on-close="true"
- @ok="handleConfirm"
- @cancel="closeModal"
- >
- <Grid table-title="缇ゅ垪琛�" />
- <template #footer>
- <Button @click="closeModal">鍙栨秷</Button>
- <Button type="primary" @click="handleConfirm">纭畾</Button>
- </template>
- </Modal>
-</template>
diff --git a/src/views/im/manager/group/components/select.vue b/src/views/im/manager/group/components/select.vue
deleted file mode 100644
index 00d1d2c..0000000
--- a/src/views/im/manager/group/components/select.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerGroupApi } from '#/api/im/manager/group';
-
-import { computed, ref, useAttrs, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { Input, Tooltip } from 'ant-design-vue';
-
-import { getManagerGroup } from '#/api/im/manager/group';
-
-import GroupSelectDialog from './select-dialog.vue';
-
-defineOptions({ name: 'ImManagerGroupSelect', inheritAttrs: false });
-
-const props = withDefaults(
- defineProps<{
- allowClear?: boolean;
- disabled?: boolean;
- modelValue?: number;
- placeholder?: string;
- }>(),
- {
- allowClear: true,
- disabled: false,
- modelValue: undefined,
- placeholder: '璇烽�夋嫨缇�',
- },
-);
-
-const emit = defineEmits<{
- change: [item: ImManagerGroupApi.Group | undefined];
- 'update:modelValue': [value: number | undefined];
-}>();
-
-const attrs = useAttrs();
-const dialogRef = ref<InstanceType<typeof GroupSelectDialog>>(); // 缇ら�夋嫨寮圭獥
-const hovering = ref(false); // 榧犳爣鏄惁鎮仠
-const selectedItem = ref<ImManagerGroupApi.Group>(); // 宸查�夌兢
-
-/** 杈撳叆妗嗘樉绀烘枃鏈� */
-const displayLabel = computed(() => selectedItem.value?.name ?? '');
-
-/** 鏄惁鏄剧ず娓呴櫎鍥炬爣 */
-const showClear = computed(() => {
- return props.allowClear && !props.disabled && hovering.value && props.modelValue != null;
-});
-
-/** 鏍规嵁缂栧彿鏌ヨ缇や俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
-async function resolveItemById(id: number | undefined) {
- if (id == null) {
- selectedItem.value = undefined;
- return;
- }
- if (selectedItem.value?.id === id) {
- return;
- }
- selectedItem.value = await getManagerGroup(id);
-}
-
-watch(() => props.modelValue, resolveItemById, { immediate: true });
-
-/** 娓呯┖宸查�夌兢 */
-function clearSelected() {
- selectedItem.value = undefined;
- emit('update:modelValue', undefined);
- emit('change', undefined);
-}
-
-/** 鎵撳紑缇ら�夋嫨寮圭獥 */
-function handleClick(event: MouseEvent) {
- if (props.disabled) {
- return;
- }
- const target = event.target as HTMLElement;
- if (showClear.value && target.closest('.ant-input-suffix')) {
- event.stopPropagation();
- clearSelected();
- return;
- }
- dialogRef.value?.open(props.modelValue == null ? [] : [props.modelValue]);
-}
-
-/** 寮圭獥閫変腑鍥炶皟 */
-function handleSelected(rows: ImManagerGroupApi.Group[]) {
- const item = rows[0];
- if (!item) {
- return;
- }
- selectedItem.value = item;
- emit('update:modelValue', item.id);
- emit('change', item);
-}
-</script>
-
-<template>
- <div
- v-bind="attrs"
- class="w-full"
- :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
- @click="handleClick"
- @mouseenter="hovering = true"
- @mouseleave="hovering = false"
- >
- <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
- <template #title>
- <div v-if="selectedItem" class="leading-6">
- <div>缇ゅ悕绉帮細{{ selectedItem.name || '-' }}</div>
- <div>缇や富锛歿{ selectedItem.ownerNickname || '-' }}</div>
- <div>鎴愬憳鏁帮細{{ selectedItem.memberCount ?? '-' }}</div>
- </div>
- </template>
- <Input
- :disabled="disabled"
- :placeholder="placeholder"
- :value="displayLabel"
- readonly
- >
- <template #suffix>
- <IconifyIcon
- class="size-4"
- :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
- />
- </template>
- </Input>
- </Tooltip>
- </div>
- <GroupSelectDialog ref="dialogRef" @selected="handleSelected" />
-</template>
diff --git a/src/views/im/manager/group/data.ts b/src/views/im/manager/group/data.ts
deleted file mode 100644
index d50d72e..0000000
--- a/src/views/im/manager/group/data.ts
+++ /dev/null
@@ -1,255 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-import { GroupSelect } from '#/views/im/manager/group/components';
-import { UserSelect } from '#/views/system/user/components';
-
-/** 缇ゆ悳绱㈣〃鍗� */
-export function useGroupGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '缇ゅ悕绉�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ョ兢鍚嶇О',
- },
- },
- {
- fieldName: 'ownerUserId',
- label: '缇や富',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨缇や富',
- },
- },
- {
- fieldName: 'status',
- label: '缇ょ姸鎬�',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_GROUP_STATUS, 'number'),
- placeholder: '璇烽�夋嫨缇ょ姸鎬�',
- },
- },
- {
- fieldName: 'banned',
- label: '鏄惁灏佺',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- placeholder: '璇烽�夋嫨灏佺鐘舵��',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 缇ゅ垪琛ㄥ瓧娈� */
-export function useGroupGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- field: 'avatar',
- title: '澶村儚',
- width: 80,
- slots: { default: 'avatar' },
- },
- {
- field: 'name',
- title: '缇ゅ悕绉�',
- minWidth: 160,
- },
- {
- title: '缇や富',
- minWidth: 180,
- slots: { default: 'owner' },
- },
- {
- field: 'memberCount',
- title: '鎴愬憳鏁�',
- width: 90,
- },
- {
- field: 'status',
- title: '缇ょ姸鎬�',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_GROUP_STATUS },
- },
- },
- {
- field: 'banned',
- title: '灏佺鐘舵��',
- width: 120,
- slots: { default: 'banned' },
- },
- {
- field: 'mutedAll',
- title: '鍏ㄧ兢绂佽█',
- width: 100,
- slots: { default: 'mutedAll' },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 320,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍔犵兢鐢宠鎼滅储琛ㄥ崟 */
-export function useGroupRequestGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'groupId',
- label: '缇�',
- component: markRaw(GroupSelect),
- },
- {
- fieldName: 'userId',
- label: '鐢宠浜�',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鐢宠浜�',
- },
- },
- {
- fieldName: 'inviterUserId',
- label: '閭�璇蜂汉',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨閭�璇蜂汉',
- },
- },
- {
- fieldName: 'handleResult',
- label: '澶勭悊缁撴灉',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_GROUP_REQUEST_HANDLE_RESULT, 'number'),
- placeholder: '璇烽�夋嫨澶勭悊缁撴灉',
- },
- },
- {
- fieldName: 'addSource',
- label: '鍔犲叆鏉ユ簮',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_GROUP_ADD_SOURCE, 'number'),
- placeholder: '璇烽�夋嫨鍔犲叆鏉ユ簮',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍔犵兢鐢宠鍒楄〃瀛楁 */
-export function useGroupRequestGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '缇�',
- minWidth: 180,
- slots: { default: 'group' },
- },
- {
- title: '鐢宠浜� / 琚個璇蜂汉',
- minWidth: 200,
- slots: { default: 'user' },
- },
- {
- title: '閭�璇蜂汉',
- minWidth: 180,
- slots: { default: 'inviter' },
- },
- {
- field: 'applyContent',
- title: '鐢宠鐞嗙敱',
- minWidth: 160,
- },
- {
- field: 'addSource',
- title: '鍔犲叆鏉ユ簮',
- width: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_GROUP_ADD_SOURCE },
- },
- },
- {
- field: 'handleResult',
- title: '澶勭悊缁撴灉',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_GROUP_REQUEST_HANDLE_RESULT },
- },
- },
- {
- title: '澶勭悊浜�',
- minWidth: 180,
- slots: { default: 'handler' },
- },
- {
- field: 'handleContent',
- title: '澶勭悊鐞嗙敱',
- minWidth: 140,
- },
- {
- field: 'handleTime',
- title: '澶勭悊鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/im/manager/group/index.vue b/src/views/im/manager/group/index.vue
deleted file mode 100644
index 4e24dad..0000000
--- a/src/views/im/manager/group/index.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerGroupApi } from '#/api/im/manager/group';
-
-import { ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-
-import { Avatar, message, Tag, Tooltip } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- dissolveManagerGroup,
- getManagerGroupPage,
- unbanManagerGroup,
-} from '#/api/im/manager/group';
-import { DictTag } from '#/components/dict-tag';
-import { formatUserLabel } from '#/views/im/manager/utils/format';
-
-import { useGroupGridColumns, useGroupGridFormSchema } from './data';
-import BanForm from './modules/ban-form.vue';
-import Detail from './modules/detail.vue';
-
-defineOptions({ name: 'ImManagerGroup' });
-
-const router = useRouter();
-const detailRef = ref<InstanceType<typeof Detail>>();
-
-const [BanModal, banModalApi] = useVbenModal({
- connectedComponent: BanForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鎵撳紑璇︽儏 */
-function handleDetail(row: ImManagerGroupApi.Group) {
- detailRef.value?.open(row);
-}
-
-/** 鏌ョ湅缇よ亰娑堟伅 */
-function handleConversation(row: ImManagerGroupApi.Group) {
- router.push({
- name: 'ImGroupMessage',
- query: { groupId: row.id },
- });
-}
-
-/** 鎵撳紑灏佺寮圭獥 */
-function handleBan(row: ImManagerGroupApi.Group) {
- banModalApi.setData(row).open();
-}
-
-/** 瑙e皝缇� */
-async function handleUnban(row: ImManagerGroupApi.Group) {
- await confirm(`纭瑙e皝缇ゃ��${row.name}銆嶅悧锛焋);
- await unbanManagerGroup(row.id);
- message.success('瑙e皝鎴愬姛');
- handleRefresh();
-}
-
-/** 瑙f暎缇� */
-async function handleDissolve(row: ImManagerGroupApi.Group) {
- await confirm(`纭瑙f暎缇ゃ��${row.name}銆嶅悧锛焋);
- await dissolveManagerGroup(row.id);
- message.success('瑙f暎鎴愬姛');
- handleRefresh();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGroupGridFormSchema(),
- },
- gridOptions: {
- columns: useGroupGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerGroupPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerGroupApi.Group>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <BanModal @success="handleRefresh" />
- <Detail ref="detailRef" />
- <Grid table-title="缇ゅ垪琛�">
- <template #avatar="{ row }">
- <Avatar :src="row.avatar" :size="40">
- {{ row.name?.charAt(0) || '?' }}
- </Avatar>
- </template>
- <template #owner="{ row }">
- {{ formatUserLabel(row.ownerNickname, row.ownerUserId) }}
- </template>
- <template #banned="{ row }">
- <Tooltip v-if="row.banned" :title="row.bannedReason">
- <DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.banned" />
- </Tooltip>
- <DictTag v-else :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.banned" />
- </template>
- <template #mutedAll="{ row }">
- <Tag v-if="row.mutedAll" color="error">宸茬瑷�</Tag>
- <Tag v-else>鏈瑷�</Tag>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '璇︽儏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['im:manager:group:query'],
- onClick: handleDetail.bind(null, row),
- },
- {
- label: '鏌ョ湅瀵硅瘽',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleConversation.bind(null, row),
- },
- {
- label: row.banned ? '瑙e皝' : '灏佺',
- type: 'link',
- danger: !row.banned,
- icon: ACTION_ICON.CLOSE,
- auth: ['im:manager:group:ban'],
- onClick: row.banned ? handleUnban.bind(null, row) : handleBan.bind(null, row),
- },
- {
- label: '瑙f暎',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:group:dissolve'],
- ifShow: row.status === CommonStatusEnum.ENABLE,
- onClick: handleDissolve.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/group/modules/ban-form.vue b/src/views/im/manager/group/modules/ban-form.vue
deleted file mode 100644
index 8f2f779..0000000
--- a/src/views/im/manager/group/modules/ban-form.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerGroupApi } from '#/api/im/manager/group';
-
-import { reactive } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Form, FormItem, Input, message } from 'ant-design-vue';
-
-import { banManagerGroup } from '#/api/im/manager/group';
-
-const emit = defineEmits(['success']);
-
-const formData = reactive({
- id: 0,
- groupName: '',
- reason: '',
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- if (!formData.reason.trim()) {
- message.warning('璇疯緭鍏ュ皝绂佸師鍥�');
- return;
- }
- modalApi.lock();
- try {
- await banManagerGroup({ id: formData.id, reason: formData.reason });
- await modalApi.close();
- emit('success');
- message.success('灏佺鎴愬姛');
- } finally {
- modalApi.unlock();
- }
- },
- onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- const data = modalApi.getData<ImManagerGroupApi.Group>();
- formData.id = data.id;
- formData.groupName = data.name;
- formData.reason = '';
- },
-});
-</script>
-
-<template>
- <Modal class="w-1/3" title="灏佺缇�">
- <Form :label-col="{ style: { width: '80px' } }">
- <FormItem label="缇ゅ悕绉�">
- {{ formData.groupName }}
- </FormItem>
- <FormItem label="灏佺鍘熷洜" required>
- <Input.TextArea
- v-model:value="formData.reason"
- :maxlength="200"
- :rows="3"
- placeholder="璇疯緭鍏ュ皝绂佸師鍥�"
- show-count
- />
- </FormItem>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/im/manager/group/modules/detail.vue b/src/views/im/manager/group/modules/detail.vue
deleted file mode 100644
index 0e66801..0000000
--- a/src/views/im/manager/group/modules/detail.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerGroupApi } from '#/api/im/manager/group';
-
-import { computed, ref } from 'vue';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-
-import {
- Avatar,
- Checkbox,
- Descriptions,
- DescriptionsItem,
- Drawer,
- Table,
- Tag,
-} from 'ant-design-vue';
-
-import { getManagerGroupMemberList } from '#/api/im/manager/group';
-import { DictTag } from '#/components/dict-tag';
-import {
- formatDateTimeText,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-
-const visible = ref(false);
-const detail = ref<ImManagerGroupApi.Group>({} as ImManagerGroupApi.Group);
-const loading = ref(false);
-const activeOnly = ref(true);
-const memberList = ref<ImManagerGroupApi.GroupMember[]>([]);
-
-const filteredMembers = computed(() =>
- activeOnly.value
- ? memberList.value.filter((item) => item.status === CommonStatusEnum.ENABLE)
- : memberList.value,
-);
-
-const columns = [
- { title: '澶村儚', dataIndex: 'avatar', width: 80 },
- { title: '鐢ㄦ埛缂栧彿', dataIndex: 'userId', width: 100 },
- { title: '瑙掕壊', dataIndex: 'role', width: 100 },
- { title: '鏄电О', dataIndex: 'nickname' },
- { title: '缁勫唴鏄剧ず鍚�', dataIndex: 'displayUserName' },
- { title: '缇ゅ娉�', dataIndex: 'groupRemark' },
- { title: '鍏嶆墦鎵�', dataIndex: 'silent', width: 90 },
- { title: '鐘舵��', dataIndex: 'status', width: 100 },
- { title: '鍏ョ兢鏃堕棿', dataIndex: 'joinTime', width: 180 },
- { title: '閫�缇ゆ椂闂�', dataIndex: 'quitTime', width: 180 },
- { title: '绂佽█鐘舵��', dataIndex: 'muteEndTime', width: 180 },
-];
-
-/** 鎵撳紑璇︽儏 */
-async function open(row: ImManagerGroupApi.Group) {
- detail.value = row;
- visible.value = true;
- activeOnly.value = true;
- loading.value = true;
- try {
- memberList.value = await getManagerGroupMemberList(row.id);
- } finally {
- loading.value = false;
- }
-}
-
-defineExpose({ open });
-</script>
-
-<template>
- <Drawer v-model:open="visible" destroy-on-close title="缇よ鎯�" width="900">
- <Descriptions bordered :column="2">
- <DescriptionsItem label="缇ょ紪鍙�">{{ detail.id }}</DescriptionsItem>
- <DescriptionsItem label="缇ゅ悕绉�">{{ detail.name }}</DescriptionsItem>
- <DescriptionsItem label="澶村儚">
- <Avatar :src="detail.avatar" :size="36">
- {{ detail.name?.charAt(0) || '?' }}
- </Avatar>
- </DescriptionsItem>
- <DescriptionsItem label="缇や富">
- {{ formatUserLabel(detail.ownerNickname, detail.ownerUserId) }}
- </DescriptionsItem>
- <DescriptionsItem label="鎴愬憳鏁�">{{ detail.memberCount || 0 }}</DescriptionsItem>
- <DescriptionsItem label="缇ょ姸鎬�">
- <DictTag :type="DICT_TYPE.IM_GROUP_STATUS" :value="detail.status" />
- </DescriptionsItem>
- <DescriptionsItem label="灏佺鐘舵��">
- <DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detail.banned" />
- <span v-if="detail.banned" class="ml-2 text-gray-400">
- {{ detail.bannedReason }}
- </span>
- </DescriptionsItem>
- <DescriptionsItem label="鍏ㄧ兢绂佽█">
- <Tag v-if="detail.mutedAll" color="error">宸茬瑷�</Tag>
- <Tag v-else>鏈瑷�</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="缇ゅ叕鍛�" :span="2">
- {{ detail.notice || '-' }}
- </DescriptionsItem>
- <DescriptionsItem label="鍒涘缓鏃堕棿" :span="2">
- {{ formatDateTimeText(detail.createTime) }}
- </DescriptionsItem>
- </Descriptions>
-
- <div class="mb-4 mt-5 flex items-center justify-between">
- <span class="font-bold">缇ゆ垚鍛�</span>
- <Checkbox v-model:checked="activeOnly">浠呭睍绀哄綋鍓嶇兢鍐呯殑鎴愬憳</Checkbox>
- </div>
- <Table
- :columns="columns"
- :data-source="filteredMembers"
- :loading="loading"
- :pagination="false"
- bordered
- row-key="userId"
- size="small"
- >
- <template #bodyCell="{ column, record, text }">
- <template v-if="column.dataIndex === 'avatar'">
- <Avatar :src="record.avatar" :size="40">
- {{ record.nickname?.charAt(0) || '?' }}
- </Avatar>
- </template>
- <template v-else-if="column.dataIndex === 'role'">
- <DictTag :type="DICT_TYPE.IM_GROUP_MEMBER_ROLE" :value="record.role" />
- </template>
- <template v-else-if="column.dataIndex === 'silent'">
- <DictTag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="record.silent" />
- </template>
- <template v-else-if="column.dataIndex === 'status'">
- <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="record.status" />
- </template>
- <template v-else-if="column.dataIndex === 'joinTime'">
- {{ formatDateTimeText(record.joinTime) }}
- </template>
- <template v-else-if="column.dataIndex === 'quitTime'">
- {{ formatDateTimeText(record.quitTime) }}
- </template>
- <template v-else-if="column.dataIndex === 'muteEndTime'">
- <template v-if="record.muteEndTime && new Date(record.muteEndTime) > new Date()">
- <Tag color="error">绂佽█涓�</Tag>
- <div class="mt-1 text-xs text-gray-400">
- {{ formatDateTimeText(record.muteEndTime) }}
- </div>
- </template>
- <span v-else>-</span>
- </template>
- <template v-else>
- {{ text || '-' }}
- </template>
- </template>
- </Table>
- </Drawer>
-</template>
diff --git a/src/views/im/manager/group/request/index.vue b/src/views/im/manager/group/request/index.vue
deleted file mode 100644
index e2440ee..0000000
--- a/src/views/im/manager/group/request/index.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerGroupRequestApi } from '#/api/im/manager/group/request';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerGroupRequestPage } from '#/api/im/manager/group/request';
-import {
- formatGroupLabel,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-
-import {
- useGroupRequestGridColumns,
- useGroupRequestGridFormSchema,
-} from '../data';
-
-defineOptions({ name: 'ImManagerGroupRequest' });
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGroupRequestGridFormSchema(),
- },
- gridOptions: {
- columns: useGroupRequestGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerGroupRequestPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerGroupRequestApi.GroupRequest>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="鍔犵兢鐢宠鍒楄〃">
- <template #group="{ row }">
- {{ formatGroupLabel(row.groupName, row.groupId) }}
- </template>
- <template #user="{ row }">
- {{ formatUserLabel(row.userNickname, row.userId) }}
- </template>
- <template #inviter="{ row }">
- <template v-if="row.inviterUserId">
- {{ formatUserLabel(row.inviterNickname, row.inviterUserId) }}
- </template>
- <span v-else>涓诲姩鐢宠</span>
- </template>
- <template #handler="{ row }">
- <template v-if="row.handleUserId">
- {{ formatUserLabel(row.handleNickname, row.handleUserId) }}
- </template>
- <span v-else>-</span>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/message/content-preview.vue b/src/views/im/manager/message/content-preview.vue
deleted file mode 100644
index 71f371c..0000000
--- a/src/views/im/manager/message/content-preview.vue
+++ /dev/null
@@ -1,254 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue';
-
-import { getDictObj } from '../../../../packages/effects/hooks/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { formatFileSize, openSafeUrl } from '../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { CardLineLabel } from '#/views/im/home/components/card';
-import { MESSAGE_MERGE_PREVIEW_LINES } from '#/views/im/utils/config';
-import {
- ImContentType,
- ImRtcCallEndReason,
- ImRtcCallMediaType,
- isFriendChatTip,
- isGroupNotification,
- isRtcCallTip,
-} from '#/views/im/utils/constants';
-import {
- buildFacePreviewText,
- summarizeMessageContent,
-} from '#/views/im/utils/conversation';
-import {
- getFileIconInfo,
- parseMessage,
- parseRtcCallPayload,
- resolveFriendNotificationText,
- resolveGroupNotificationText,
-} from '#/views/im/utils/message';
-import { formatSeconds } from '#/views/im/utils/time';
-
-defineOptions({ name: 'ImManagerMessageContentPreview' });
-
-const props = defineProps<{
- content?: string;
- senderNickname?: string;
- type?: number;
-}>();
-
-const payload = computed<null | Record<string, any>>(() =>
- parseMessage<Record<string, any>>(props.content ?? ''),
-);
-
-const textContent = computed(() => payload.value?.content || '');
-
-const fileIconInfo = computed(() => getFileIconInfo(payload.value?.name));
-
-const cardPayload = computed(() => {
- if (props.type !== ImContentType.CARD || !payload.value) {
- return undefined;
- }
- return {
- ...payload.value,
- name: payload.value.name || payload.value.nickname || payload.value.userId || '',
- };
-});
-
-const mergePreviewLines = computed(() => {
- const messages = payload.value?.messages;
- if (!Array.isArray(messages)) {
- return [];
- }
- return messages
- .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
- .map((item) => `${item.senderNickname}锛�${summarizeMessageContent(item)}`);
-});
-
-const fallbackText = computed(() => {
- const raw = props.content || '';
- if (!raw) {
- return '';
- }
- try {
- const parsed = JSON.parse(raw);
- if (parsed && typeof parsed === 'object' && parsed.content) {
- return String(parsed.content);
- }
- } catch {}
- return raw;
-});
-
-const friendTipText = computed(() =>
- resolveFriendNotificationText({ type: props.type }),
-);
-
-const groupTipText = computed(() =>
- resolveGroupNotificationText(
- { type: props.type, content: props.content },
- (id) => `鐢ㄦ埛(${id})`,
- props.senderNickname,
- ),
-);
-
-const rtcCallTipText = computed(() => {
- const rtcPayload = parseRtcCallPayload(props.content);
- if (!rtcPayload) {
- return '';
- }
- const mediaLabel =
- rtcPayload.mediaType === ImRtcCallMediaType.VIDEO ? '瑙嗛' : '璇煶';
- if (props.type === ImContentType.RTC_CALL_START) {
- const inviter =
- rtcPayload.inviterNickname?.trim() ||
- `鐢ㄦ埛(${rtcPayload.inviterUserId ?? ''})`;
- return `${inviter} 鍙戣捣浜�${mediaLabel}閫氳瘽`;
- }
- const segments = [`${mediaLabel}閫氳瘽宸茬粨鏉焋];
- if (
- rtcPayload.endReason &&
- rtcPayload.endReason !== ImRtcCallEndReason.HANGUP
- ) {
- const reason = getDictObj(
- 'im_rtc_call_end_reason',
- String(rtcPayload.endReason),
- )?.label;
- if (reason) {
- segments.push(reason);
- }
- }
- const duration = rtcPayload.durationSeconds ?? 0;
- if (duration > 0) {
- segments.push(`鏃堕暱 ${formatSeconds(duration)}`);
- }
- return segments.join('锛�');
-});
-
-/** 鎵撳紑瑙嗛 */
-function openVideo() {
- openSafeUrl(payload.value?.url);
-}
-</script>
-
-<template>
- <span v-if="type === ImContentType.TEXT" class="whitespace-pre-wrap break-all">
- {{ textContent }}
- </span>
-
- <Image
- v-else-if="type === ImContentType.IMAGE && payload"
- :height="60"
- :src="payload.thumbnailUrl || payload.url"
- :width="60"
- />
-
- <span
- v-else-if="type === ImContentType.FILE && payload"
- class="inline-flex items-center gap-1.5"
- >
- <IconifyIcon
- :color="fileIconInfo.color"
- :icon="fileIconInfo.icon"
- class="size-4"
- />
- <span class="max-w-52 truncate">{{ payload.name }}</span>
- <span v-if="payload.size > 0" class="text-xs text-gray-400">
- {{ formatFileSize(payload.size) }}
- </span>
- </span>
-
- <span
- v-else-if="type === ImContentType.VOICE && payload"
- class="inline-flex items-center gap-1.5"
- >
- <IconifyIcon class="size-4" icon="ant-design:audio-outlined" />
- <span>{{ formatSeconds(payload.duration || 0) }}</span>
- </span>
-
- <span
- v-else-if="type === ImContentType.VIDEO && payload"
- class="inline-flex items-center gap-1.5"
- >
- <button
- v-if="payload.coverUrl"
- class="relative size-[60px] overflow-hidden rounded border-0 p-0"
- type="button"
- @click="openVideo"
- >
- <img :src="payload.coverUrl" class="size-full object-cover" />
- <IconifyIcon
- class="absolute inset-0 m-auto size-6 text-white drop-shadow"
- icon="ant-design:play-circle-filled"
- />
- </button>
- <span v-else class="inline-flex items-center gap-1.5">
- <IconifyIcon class="size-4" icon="ant-design:video-camera-filled" />
- <span>[瑙嗛]</span>
- </span>
- <span v-if="payload.duration" class="text-xs text-gray-400">
- {{ formatSeconds(payload.duration) }}
- </span>
- <span v-if="payload.size > 0" class="text-xs text-gray-400">
- {{ formatFileSize(payload.size) }}
- </span>
- </span>
-
- <CardLineLabel
- v-else-if="type === ImContentType.CARD && cardPayload"
- :card="cardPayload"
- :icon-size="16"
- />
-
- <span
- v-else-if="type === ImContentType.FACE && payload"
- class="inline-flex items-center gap-1.5"
- >
- <img
- v-if="payload.url"
- :alt="payload.name || '琛ㄦ儏'"
- :src="payload.url"
- class="size-[30px] rounded object-contain"
- draggable="false"
- />
- <span>{{ buildFacePreviewText(payload) }}</span>
- </span>
-
- <span
- v-else-if="type === ImContentType.MERGE && payload"
- class="inline-flex flex-col gap-0.5 align-middle"
- >
- <span>[鑱婂ぉ璁板綍] {{ payload.title }}</span>
- <span
- v-for="(line, index) in mergePreviewLines"
- :key="index"
- class="truncate text-xs text-gray-400"
- >
- {{ line }}
- </span>
- </span>
-
- <span v-else-if="type === ImContentType.RECALL" class="text-xs text-gray-400">
- [娑堟伅宸叉挙鍥瀅
- </span>
- <span v-else-if="type === ImContentType.READ" class="text-xs text-gray-400">
- [宸茶鍥炴墽]
- </span>
- <span v-else-if="type === ImContentType.RECEIPT" class="text-xs text-gray-400">
- [鍥炴墽]
- </span>
- <span v-else-if="isGroupNotification(type ?? -1)" class="text-xs text-gray-400">
- {{ groupTipText }}
- </span>
- <span v-else-if="isFriendChatTip(type ?? -1)" class="text-xs text-gray-400">
- {{ friendTipText }}
- </span>
- <span
- v-else-if="isRtcCallTip(type ?? -1)"
- class="inline-flex items-center gap-1.5 text-xs text-gray-400"
- >
- <IconifyIcon class="size-4 rotate-[135deg]" icon="ant-design:phone-outlined" />
- <span>{{ rtcCallTipText }}</span>
- </span>
- <span v-else class="whitespace-pre-wrap break-all">{{ fallbackText }}</span>
-</template>
diff --git a/src/views/im/manager/message/data.ts b/src/views/im/manager/message/data.ts
deleted file mode 100644
index ce6daf7..0000000
--- a/src/views/im/manager/message/data.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-import { GroupSelect } from '#/views/im/manager/group/components';
-import { UserSelect } from '#/views/system/user/components';
-
-/** 绉佽亰娑堟伅鎼滅储琛ㄥ崟 */
-export function usePrivateGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'senderId',
- label: '鍙戦�佷汉',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鍙戦�佷汉',
- },
- },
- {
- fieldName: 'receiverId',
- label: '鎺ユ敹浜�',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鎺ユ敹浜�',
- },
- },
- {
- fieldName: 'type',
- label: '鍐呭绫诲瀷',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_CONTENT_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鍐呭绫诲瀷',
- },
- },
- {
- fieldName: 'content',
- label: '娑堟伅鍐呭',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ秷鎭唴瀹�',
- },
- },
- {
- fieldName: 'sendTime',
- label: '鍙戦�佹椂闂�',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 绉佽亰娑堟伅瀛楁 */
-export function usePrivateGridColumns(showReadColumns: boolean): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '鍙戦�佷汉',
- minWidth: 160,
- slots: { default: 'sender' },
- },
- {
- title: '鎺ユ敹浜�',
- minWidth: 160,
- slots: { default: 'receiver' },
- },
- {
- field: 'type',
- title: '绫诲瀷',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_CONTENT_TYPE },
- },
- },
- {
- title: '鍐呭棰勮',
- minWidth: 260,
- align: 'left',
- slots: { default: 'content' },
- },
- ...(showReadColumns
- ? [
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_MESSAGE_STATUS },
- },
- },
- {
- field: 'receiptStatus',
- title: '鍥炴墽',
- width: 110,
- cellRender: {
- name: 'CellDict',
- // 鍥炴墽鐘舵�侊紙绉佽亰 / 缇よ亰鍏辩敤 im_message_receipt_status锛夛紝涓庢簮绔�屽洖鎵с�嶅垪瀵归綈
- props: { type: DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS },
- },
- },
- ]
- : []),
- {
- field: 'sendTime',
- title: '鍙戦�佹椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 缇よ亰娑堟伅鎼滅储琛ㄥ崟 */
-export function useGroupGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'groupId',
- label: '缇�',
- component: markRaw(GroupSelect),
- },
- {
- fieldName: 'senderId',
- label: '鍙戦�佷汉',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鍙戦�佷汉',
- },
- },
- {
- fieldName: 'type',
- label: '鍐呭绫诲瀷',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_CONTENT_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鍐呭绫诲瀷',
- },
- },
- {
- fieldName: 'content',
- label: '娑堟伅鍐呭',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ秷鎭唴瀹�',
- },
- },
- {
- fieldName: 'sendTime',
- label: '鍙戦�佹椂闂�',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 缇よ亰娑堟伅瀛楁 */
-export function useGroupGridColumns(showReadColumns: boolean): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '缇�',
- minWidth: 160,
- slots: { default: 'group' },
- },
- {
- title: '鍙戦�佷汉',
- minWidth: 160,
- slots: { default: 'sender' },
- },
- {
- field: 'type',
- title: '绫诲瀷',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_CONTENT_TYPE },
- },
- },
- {
- title: '@鐢ㄦ埛',
- minWidth: 180,
- slots: { default: 'atUsers' },
- },
- {
- title: '鍐呭棰勮',
- minWidth: 260,
- align: 'left',
- slots: { default: 'content' },
- },
- ...(showReadColumns
- ? [
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_MESSAGE_STATUS },
- },
- },
- {
- field: 'receiptStatus',
- title: '鍥炴墽',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS },
- },
- },
- ]
- : []),
- {
- field: 'sendTime',
- title: '鍙戦�佹椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/im/manager/message/group/index.vue b/src/views/im/manager/message/group/index.vue
deleted file mode 100644
index 5a2050f..0000000
--- a/src/views/im/manager/message/group/index.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerGroupMessageApi } from '#/api/im/manager/message/group';
-
-import { ref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerGroupMessagePage } from '#/api/im/manager/message/group';
-import {
- formatGroupLabel,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-import { MESSAGE_GROUP_READ_ENABLED } from '#/views/im/utils/config';
-import {
- IM_AT_ALL_NICKNAME,
- IM_AT_ALL_USER_ID,
-} from '#/views/im/utils/constants';
-
-import { MessageContentPreview } from '..';
-import { useGroupGridColumns, useGroupGridFormSchema } from '../data';
-import Detail from './modules/detail.vue';
-
-defineOptions({ name: 'ImGroupMessage' });
-
-const route = useRoute();
-const detailRef = ref<InstanceType<typeof Detail>>();
-
-/** 鎵撳紑璇︽儏 */
-function handleDetail(row: ImManagerGroupMessageApi.GroupMessage) {
- detailRef.value?.open(row);
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGroupGridFormSchema(),
- },
- gridOptions: {
- columns: useGroupGridColumns(MESSAGE_GROUP_READ_ENABLED),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerGroupMessagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- groupId: route.query.groupId ? Number(route.query.groupId) : undefined,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerGroupMessageApi.GroupMessage>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Detail ref="detailRef" />
- <Grid table-title="缇よ亰娑堟伅鍒楄〃">
- <template #group="{ row }">
- {{ formatGroupLabel(row.groupName, row.groupId) }}
- </template>
- <template #sender="{ row }">
- {{ formatUserLabel(row.senderNickname, row.senderId) }}
- </template>
- <template #atUsers="{ row }">
- <template v-if="row.atUserIds?.length">
- <span v-for="(userId, index) in row.atUserIds" :key="userId">
- <span v-if="Number(index) > 0">銆�</span>
- <template v-if="userId === IM_AT_ALL_USER_ID">@{{ IM_AT_ALL_NICKNAME }}</template>
- <template v-else>@{{ row.atUserNicknames?.[index] || userId }}</template>
- </span>
- </template>
- <span v-else>-</span>
- </template>
- <template #content="{ row }">
- <MessageContentPreview
- :content="row.content"
- :sender-nickname="row.senderNickname"
- :type="row.type"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '璇︽儏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['im:manager:message:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/message/group/modules/detail.vue b/src/views/im/manager/message/group/modules/detail.vue
deleted file mode 100644
index dfcc144..0000000
--- a/src/views/im/manager/message/group/modules/detail.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerGroupMessageApi } from '#/api/im/manager/message/group';
-
-import { ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-
-import { Descriptions, DescriptionsItem, Modal } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-import {
- formatDateTimeText,
- formatGroupLabel,
- formatJsonText,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-import { MESSAGE_GROUP_READ_ENABLED } from '#/views/im/utils/config';
-import {
- IM_AT_ALL_NICKNAME,
- IM_AT_ALL_USER_ID,
-} from '#/views/im/utils/constants';
-
-import { MessageContentPreview } from '../..';
-
-const visible = ref(false);
-const detail = ref<ImManagerGroupMessageApi.GroupMessage>({} as ImManagerGroupMessageApi.GroupMessage);
-
-/** 鎵撳紑璇︽儏 */
-function open(row: ImManagerGroupMessageApi.GroupMessage) {
- detail.value = row;
- visible.value = true;
-}
-
-defineExpose({ open });
-</script>
-
-<template>
- <Modal v-model:open="visible" :footer="null" title="缇よ亰娑堟伅璇︽儏" width="700px">
- <Descriptions bordered :column="2">
- <DescriptionsItem label="缂栧彿">{{ detail.id }}</DescriptionsItem>
- <DescriptionsItem label="瀹㈡埛绔紪鍙�">
- {{ detail.clientMessageId || '-' }}
- </DescriptionsItem>
- <DescriptionsItem label="缇�">
- {{ formatGroupLabel(detail.groupName, detail.groupId) }}
- </DescriptionsItem>
- <DescriptionsItem label="鍙戦�佷汉">
- {{ formatUserLabel(detail.senderNickname, detail.senderId) }}
- </DescriptionsItem>
- <DescriptionsItem label="绫诲瀷">
- <DictTag :type="DICT_TYPE.IM_CONTENT_TYPE" :value="detail.type" />
- </DescriptionsItem>
- <DescriptionsItem label="鐘舵��">
- <DictTag :type="DICT_TYPE.IM_MESSAGE_STATUS" :value="detail.status" />
- </DescriptionsItem>
- <DescriptionsItem v-if="MESSAGE_GROUP_READ_ENABLED" label="鍥炴墽" :span="2">
- <DictTag
- :type="DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS"
- :value="detail.receiptStatus"
- />
- </DescriptionsItem>
- <DescriptionsItem label="@鐢ㄦ埛" :span="2">
- <template v-if="detail.atUserIds?.length">
- <span v-for="(userId, index) in detail.atUserIds" :key="userId">
- <span v-if="Number(index) > 0">銆�</span>
- <template v-if="userId === IM_AT_ALL_USER_ID">
- @{{ IM_AT_ALL_NICKNAME }}
- </template>
- <template v-else>
- @{{ detail.atUserNicknames?.[index] || userId }}
- <span class="text-gray-400">({{ userId }})</span>
- </template>
- </span>
- </template>
- <span v-else>-</span>
- </DescriptionsItem>
- <DescriptionsItem label="鍙戦�佹椂闂�" :span="2">
- {{ formatDateTimeText(detail.sendTime) }}
- </DescriptionsItem>
- <DescriptionsItem label="娑堟伅鍐呭" :span="2">
- <MessageContentPreview
- :content="detail.content"
- :sender-nickname="detail.senderNickname"
- :type="detail.type"
- />
- </DescriptionsItem>
- <DescriptionsItem label="鍘熷 JSON" :span="2">
- <pre class="m-0 whitespace-pre-wrap break-all rounded bg-gray-100 p-2 font-mono text-xs">{{ formatJsonText(detail.content) }}</pre>
- </DescriptionsItem>
- </Descriptions>
- </Modal>
-</template>
diff --git a/src/views/im/manager/message/index.ts b/src/views/im/manager/message/index.ts
deleted file mode 100644
index 63c961a..0000000
--- a/src/views/im/manager/message/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as MessageContentPreview } from './content-preview.vue';
diff --git a/src/views/im/manager/message/private/index.vue b/src/views/im/manager/message/private/index.vue
deleted file mode 100644
index 2e58563..0000000
--- a/src/views/im/manager/message/private/index.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerPrivateMessageApi } from '#/api/im/manager/message/private';
-
-import { ref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerPrivateMessagePage } from '#/api/im/manager/message/private';
-import { formatUserLabel } from '#/views/im/manager/utils/format';
-import { MESSAGE_PRIVATE_READ_ENABLED } from '#/views/im/utils/config';
-
-import { MessageContentPreview } from '..';
-import {
- usePrivateGridColumns,
- usePrivateGridFormSchema,
-} from '../data';
-import Detail from './modules/detail.vue';
-
-defineOptions({ name: 'ImPrivateMessage' });
-
-const route = useRoute();
-const detailRef = ref<InstanceType<typeof Detail>>();
-
-/** 鎵撳紑璇︽儏 */
-function handleDetail(row: ImManagerPrivateMessageApi.PrivateMessage) {
- detailRef.value?.open(row);
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: usePrivateGridFormSchema(),
- },
- gridOptions: {
- columns: usePrivateGridColumns(MESSAGE_PRIVATE_READ_ENABLED),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerPrivateMessagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- senderId: route.query.senderId
- ? Number(route.query.senderId)
- : undefined,
- receiverId: route.query.receiverId
- ? Number(route.query.receiverId)
- : undefined,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerPrivateMessageApi.PrivateMessage>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Detail ref="detailRef" />
- <Grid table-title="绉佽亰娑堟伅鍒楄〃">
- <template #sender="{ row }">
- {{ formatUserLabel(row.senderNickname, row.senderId) }}
- </template>
- <template #receiver="{ row }">
- {{ formatUserLabel(row.receiverNickname, row.receiverId) }}
- </template>
- <template #content="{ row }">
- <MessageContentPreview
- :content="row.content"
- :sender-nickname="row.senderNickname"
- :type="row.type"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '璇︽儏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['im:manager:message:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/message/private/modules/detail.vue b/src/views/im/manager/message/private/modules/detail.vue
deleted file mode 100644
index 36431a5..0000000
--- a/src/views/im/manager/message/private/modules/detail.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerPrivateMessageApi } from '#/api/im/manager/message/private';
-
-import { ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-
-import { Descriptions, DescriptionsItem, Modal } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-import {
- formatDateTimeText,
- formatJsonText,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-import { MESSAGE_PRIVATE_READ_ENABLED } from '#/views/im/utils/config';
-
-import { MessageContentPreview } from '../..';
-
-const visible = ref(false);
-const detail = ref<ImManagerPrivateMessageApi.PrivateMessage>(
- {} as ImManagerPrivateMessageApi.PrivateMessage,
-);
-
-/** 鎵撳紑璇︽儏 */
-function open(row: ImManagerPrivateMessageApi.PrivateMessage) {
- detail.value = row;
- visible.value = true;
-}
-
-defineExpose({ open });
-</script>
-
-<template>
- <Modal v-model:open="visible" :footer="null" title="绉佽亰娑堟伅璇︽儏" width="700px">
- <Descriptions bordered :column="2">
- <DescriptionsItem label="缂栧彿">{{ detail.id }}</DescriptionsItem>
- <DescriptionsItem label="瀹㈡埛绔紪鍙�">
- {{ detail.clientMessageId || '-' }}
- </DescriptionsItem>
- <DescriptionsItem label="鍙戦�佷汉">
- {{ formatUserLabel(detail.senderNickname, detail.senderId) }}
- </DescriptionsItem>
- <DescriptionsItem label="鎺ユ敹浜�">
- {{ formatUserLabel(detail.receiverNickname, detail.receiverId) }}
- </DescriptionsItem>
- <DescriptionsItem label="绫诲瀷">
- <DictTag :type="DICT_TYPE.IM_CONTENT_TYPE" :value="detail.type" />
- </DescriptionsItem>
- <DescriptionsItem label="鐘舵��">
- <DictTag :type="DICT_TYPE.IM_MESSAGE_STATUS" :value="detail.status" />
- </DescriptionsItem>
- <DescriptionsItem v-if="MESSAGE_PRIVATE_READ_ENABLED" label="鍥炴墽">
- <!-- 鍥炴墽鐘舵�侊紙绉佽亰 / 缇よ亰鍏辩敤 im_message_receipt_status锛夛紝涓庢簮绔鑱婅鎯呫�屽洖鎵с�嶅榻� -->
- <DictTag
- :type="DICT_TYPE.IM_MESSAGE_RECEIPT_STATUS"
- :value="detail.receiptStatus"
- />
- </DescriptionsItem>
- <DescriptionsItem label="鍙戦�佹椂闂�" :span="2">
- {{ formatDateTimeText(detail.sendTime) }}
- </DescriptionsItem>
- <DescriptionsItem label="娑堟伅鍐呭" :span="2">
- <MessageContentPreview
- :content="detail.content"
- :sender-nickname="detail.senderNickname"
- :type="detail.type"
- />
- </DescriptionsItem>
- <DescriptionsItem label="鍘熷 JSON" :span="2">
- <pre class="m-0 whitespace-pre-wrap break-all rounded bg-gray-100 p-2 font-mono text-xs">{{ formatJsonText(detail.content) }}</pre>
- </DescriptionsItem>
- </Descriptions>
- </Modal>
-</template>
diff --git a/src/views/im/manager/rtc/data.ts b/src/views/im/manager/rtc/data.ts
deleted file mode 100644
index 994d4a1..0000000
--- a/src/views/im/manager/rtc/data.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-import { UserSelect } from '#/views/system/user/components';
-
-/** 閫氳瘽璁板綍鎼滅储琛ㄥ崟 */
-export function useRtcGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'inviterUserId',
- label: '鍙戣捣浜�',
- component: markRaw(UserSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨鍙戣捣浜�',
- },
- },
- {
- fieldName: 'conversationType',
- label: '浼氳瘽绫诲瀷',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_RTC_CALL_CONVERSATION_TYPE, 'number'),
- placeholder: '璇烽�夋嫨浼氳瘽绫诲瀷',
- },
- },
- {
- fieldName: 'mediaType',
- label: '濯掍綋绫诲瀷',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE, 'number'),
- placeholder: '璇烽�夋嫨濯掍綋绫诲瀷',
- },
- },
- {
- fieldName: 'status',
- label: '閫氳瘽鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_RTC_CALL_STATUS, 'number'),
- placeholder: '璇烽�夋嫨閫氳瘽鐘舵��',
- },
- },
- {
- fieldName: 'endReason',
- label: '缁撴潫鍘熷洜',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.IM_RTC_CALL_END_REASON, 'number'),
- placeholder: '璇烽�夋嫨缁撴潫鍘熷洜',
- },
- },
- {
- fieldName: 'startTime',
- label: '鍙戣捣鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 閫氳瘽璁板綍瀛楁 */
-export function useRtcGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- title: '鍙戣捣浜�',
- minWidth: 160,
- slots: { default: 'inviter' },
- },
- {
- field: 'conversationType',
- title: '浼氳瘽绫诲瀷',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_RTC_CALL_CONVERSATION_TYPE },
- },
- },
- {
- title: '缇�',
- minWidth: 160,
- slots: { default: 'group' },
- },
- {
- field: 'mediaType',
- title: '濯掍綋绫诲瀷',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE },
- },
- },
- {
- field: 'status',
- title: '閫氳瘽鐘舵��',
- width: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IM_RTC_CALL_STATUS },
- },
- },
- {
- field: 'endReason',
- title: '缁撴潫鍘熷洜',
- width: 120,
- slots: { default: 'endReason' },
- },
- {
- title: '閫氳瘽鏃堕暱',
- width: 120,
- slots: { default: 'duration' },
- },
- {
- field: 'startTime',
- title: '鍙戣捣鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/im/manager/rtc/index.vue b/src/views/im/manager/rtc/index.vue
deleted file mode 100644
index c03f50f..0000000
--- a/src/views/im/manager/rtc/index.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerRtcApi } from '#/api/im/manager/rtc';
-
-import { ref } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getManagerRtcCallPage } from '#/api/im/manager/rtc';
-import { DictTag } from '#/components/dict-tag';
-import {
- formatGroupLabel,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-import { resolveCallDuration } from '#/views/im/utils/time';
-
-import { useRtcGridColumns, useRtcGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-defineOptions({ name: 'ImManagerRtcCall' });
-
-const detailRef = ref<InstanceType<typeof Detail>>();
-
-/** 鎵撳紑璇︽儏 */
-function handleDetail(row: ImManagerRtcApi.RtcCall) {
- detailRef.value?.open(row);
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useRtcGridFormSchema(),
- },
- gridOptions: {
- columns: useRtcGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerRtcCallPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerRtcApi.RtcCall>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Detail ref="detailRef" />
- <Grid table-title="閫氳瘽璁板綍鍒楄〃">
- <template #inviter="{ row }">
- {{ formatUserLabel(row.inviterNickname, row.inviterUserId) }}
- </template>
- <template #group="{ row }">
- {{ formatGroupLabel(row.groupName, row.groupId) }}
- </template>
- <template #endReason="{ row }">
- <DictTag
- v-if="row.endReason"
- :type="DICT_TYPE.IM_RTC_CALL_END_REASON"
- :value="row.endReason"
- />
- <span v-else>-</span>
- </template>
- <template #duration="{ row }">
- {{ resolveCallDuration(row.acceptTime, row.endTime) }}
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '璇︽儏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['im:manager:rtc:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/rtc/modules/detail.vue b/src/views/im/manager/rtc/modules/detail.vue
deleted file mode 100644
index 8798276..0000000
--- a/src/views/im/manager/rtc/modules/detail.vue
+++ /dev/null
@@ -1,119 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerRtcApi } from '#/api/im/manager/rtc';
-
-import { computed, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-
-import { Descriptions, DescriptionsItem, Drawer, Table } from 'ant-design-vue';
-
-import { getManagerRtcCallParticipantList } from '#/api/im/manager/rtc';
-import { DictTag } from '#/components/dict-tag';
-import {
- formatDateTimeText,
- formatGroupLabel,
- formatUserLabel,
-} from '#/views/im/manager/utils/format';
-import { resolveCallDuration } from '#/views/im/utils/time';
-
-const visible = ref(false);
-const detail = ref<ImManagerRtcApi.RtcCall>({} as ImManagerRtcApi.RtcCall);
-const loading = ref(false);
-const participants = ref<ImManagerRtcApi.RtcParticipant[]>([]);
-
-const duration = computed(() =>
- resolveCallDuration(detail.value.acceptTime, detail.value.endTime),
-);
-
-const columns = [
- { title: '鐢ㄦ埛缂栧彿', dataIndex: 'userId', width: 120 },
- { title: '鏄电О', dataIndex: 'userNickname' },
- { title: '鍙備笌瑙掕壊', dataIndex: 'role', width: 120 },
- { title: '鍙備笌鐘舵��', dataIndex: 'status', width: 120 },
- { title: '琚個璇锋椂闂�', dataIndex: 'inviteTime', width: 180 },
- { title: '鎺ュ惉鏃堕棿', dataIndex: 'acceptTime', width: 180 },
- { title: '绂诲紑鏃堕棿', dataIndex: 'leaveTime', width: 180 },
-];
-
-/** 鎵撳紑璇︽儏 */
-async function open(row: ImManagerRtcApi.RtcCall) {
- detail.value = row;
- visible.value = true;
- loading.value = true;
- try {
- participants.value = await getManagerRtcCallParticipantList(row.id);
- } finally {
- loading.value = false;
- }
-}
-
-defineExpose({ open });
-</script>
-
-<template>
- <Drawer v-model:open="visible" destroy-on-close title="閫氳瘽璁板綍璇︽儏" width="900">
- <Descriptions bordered :column="2">
- <DescriptionsItem label="缂栧彿">{{ detail.id }}</DescriptionsItem>
- <DescriptionsItem label="涓氬姟閫氳瘽缂栧彿">{{ detail.room }}</DescriptionsItem>
- <DescriptionsItem label="鍙戣捣浜�">
- {{ formatUserLabel(detail.inviterNickname, detail.inviterUserId) }}
- </DescriptionsItem>
- <DescriptionsItem label="浼氳瘽绫诲瀷">
- <DictTag :type="DICT_TYPE.IM_RTC_CALL_CONVERSATION_TYPE" :value="detail.conversationType" />
- </DescriptionsItem>
- <DescriptionsItem label="缇�">
- {{ formatGroupLabel(detail.groupName, detail.groupId) }}
- </DescriptionsItem>
- <DescriptionsItem label="濯掍綋绫诲瀷">
- <DictTag :type="DICT_TYPE.IM_RTC_CALL_MEDIA_TYPE" :value="detail.mediaType" />
- </DescriptionsItem>
- <DescriptionsItem label="閫氳瘽鐘舵��">
- <DictTag :type="DICT_TYPE.IM_RTC_CALL_STATUS" :value="detail.status" />
- </DescriptionsItem>
- <DescriptionsItem label="缁撴潫鍘熷洜">
- <DictTag
- v-if="detail.endReason"
- :type="DICT_TYPE.IM_RTC_CALL_END_REASON"
- :value="detail.endReason"
- />
- <span v-else>-</span>
- </DescriptionsItem>
- <DescriptionsItem label="鍙戣捣鏃堕棿">
- {{ formatDateTimeText(detail.startTime) }}
- </DescriptionsItem>
- <DescriptionsItem label="鎺ラ�氭椂闂�">
- {{ formatDateTimeText(detail.acceptTime) }}
- </DescriptionsItem>
- <DescriptionsItem label="缁撴潫鏃堕棿">
- {{ formatDateTimeText(detail.endTime) }}
- </DescriptionsItem>
- <DescriptionsItem label="閫氳瘽鏃堕暱">{{ duration }}</DescriptionsItem>
- </Descriptions>
-
- <div class="mb-4 mt-5 font-bold">鍙備笌鑰呭垪琛�</div>
- <Table
- :columns="columns"
- :data-source="participants"
- :loading="loading"
- :pagination="false"
- bordered
- row-key="id"
- size="small"
- >
- <template #bodyCell="{ column, record, text }">
- <template v-if="column.dataIndex === 'role'">
- <DictTag :type="DICT_TYPE.IM_RTC_PARTICIPANT_ROLE" :value="record.role" />
- </template>
- <template v-else-if="column.dataIndex === 'status'">
- <DictTag :type="DICT_TYPE.IM_RTC_PARTICIPANT_STATUS" :value="record.status" />
- </template>
- <template v-else-if="['inviteTime', 'acceptTime', 'leaveTime'].includes(column.dataIndex as string)">
- {{ formatDateTimeText(text) }}
- </template>
- <template v-else>
- {{ text || '-' }}
- </template>
- </template>
- </Table>
- </Drawer>
-</template>
diff --git a/src/views/im/manager/sensitiveword/data.ts b/src/views/im/manager/sensitiveword/data.ts
deleted file mode 100644
index 20fdc5e..0000000
--- a/src/views/im/manager/sensitiveword/data.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'word',
- label: '鏁忔劅璇�',
- component: 'Input',
- componentProps: {
- maxlength: 64,
- placeholder: '璇疯緭鍏ユ晱鎰熻瘝',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'word',
- label: '鏁忔劅璇�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ晱鎰熻瘝',
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'id',
- title: '缂栧彿',
- width: 100,
- },
- {
- field: 'word',
- title: '鏁忔劅璇�',
- minWidth: 180,
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'creatorName',
- title: '鍒涘缓浜�',
- minWidth: 120,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/im/manager/sensitiveword/index.vue b/src/views/im/manager/sensitiveword/index.vue
deleted file mode 100644
index c198ab8..0000000
--- a/src/views/im/manager/sensitiveword/index.vue
+++ /dev/null
@@ -1,175 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ImManagerSensitiveWordApi } from '#/api/im/manager/sensitiveword';
-
-import { ref } from 'vue';
-
-import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { isEmpty } from '../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteManagerSensitiveWord,
- deleteManagerSensitiveWordList,
- getManagerSensitiveWordPage,
-} from '#/api/im/manager/sensitiveword';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'ImManagerSensitiveWord' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鏁忔劅璇� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鏁忔劅璇� */
-function handleEdit(row: ImManagerSensitiveWordApi.SensitiveWord) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鏁忔劅璇� */
-async function handleDelete(row: ImManagerSensitiveWordApi.SensitiveWord) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.word]),
- duration: 0,
- });
- try {
- await deleteManagerSensitiveWord(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.word]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const checkedIds = ref<number[]>([]);
-
-/** 琛ㄦ牸閫変腑鍙樺寲 */
-function handleRowCheckboxChange({
- records,
-}: {
- records: ImManagerSensitiveWordApi.SensitiveWord[];
-}) {
- checkedIds.value = records.map((item) => item.id);
-}
-
-/** 鎵归噺鍒犻櫎鏁忔劅璇� */
-async function handleDeleteBatch() {
- await confirm($t('ui.actionMessage.deleteBatchConfirm'));
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deletingBatch'),
- duration: 0,
- });
- try {
- await deleteManagerSensitiveWordList(checkedIds.value);
- checkedIds.value = [];
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getManagerSensitiveWordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ImManagerSensitiveWordApi.SensitiveWord>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鏁忔劅璇嶅垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鏁忔劅璇�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['im:manager:sensitive-word:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.deleteBatch'),
- type: 'primary',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:sensitive-word:delete'],
- disabled: isEmpty(checkedIds),
- onClick: handleDeleteBatch,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['im:manager:sensitive-word:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['im:manager:sensitive-word:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.word]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/im/manager/sensitiveword/modules/form.vue b/src/views/im/manager/sensitiveword/modules/form.vue
deleted file mode 100644
index 2d92119..0000000
--- a/src/views/im/manager/sensitiveword/modules/form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerSensitiveWordApi } from '#/api/im/manager/sensitiveword';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createManagerSensitiveWord,
- getManagerSensitiveWord,
- updateManagerSensitiveWord,
-} from '#/api/im/manager/sensitiveword';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<ImManagerSensitiveWordApi.SensitiveWord>();
-const getTitle = computed(() => {
- return formData.value?.id ? '淇敼鏁忔劅璇�' : '鏂板鏁忔劅璇�';
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- const data = (await formApi.getValues()) as ImManagerSensitiveWordApi.SensitiveWord;
- try {
- await (formData.value?.id
- ? updateManagerSensitiveWord(data)
- : createManagerSensitiveWord(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- await formApi.resetForm();
- return;
- }
- const data = modalApi.getData<ImManagerSensitiveWordApi.SensitiveWord>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getManagerSensitiveWord(data.id);
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/im/manager/statistics/components/distribution-chart.vue b/src/views/im/manager/statistics/components/distribution-chart.vue
deleted file mode 100644
index ca70371..0000000
--- a/src/views/im/manager/statistics/components/distribution-chart.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { nextTick, onMounted, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictObj } from '../../../../../packages/effects/hooks/src';
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { Card } from 'ant-design-vue';
-
-import {
- getGroupSizeDistribution,
- getMessageTypeDistribution,
- getTopSenders,
-} from '#/api/im/manager/statistics';
-
-const props = defineProps<{
- type: 'groupSize' | 'messageType' | 'topSenders';
-}>();
-
-const titleMap = {
- groupSize: '缇よ妯″垎甯�',
- messageType: '娑堟伅绫诲瀷鍒嗗竷',
- topSenders: '娑堟伅鍙戦�� TOP 10',
-};
-
-const loading = ref(false);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 鍔犺浇鍥捐〃鏁版嵁 */
-async function loadData() {
- loading.value = true;
- try {
- await nextTick();
- if (props.type === 'groupSize') {
- const data = await getGroupSizeDistribution();
- await renderEcharts({
- tooltip: { trigger: 'axis' },
- grid: { left: 24, right: 24, bottom: 16, top: 28, containLabel: true },
- xAxis: { type: 'category', data: data.map((item) => item.range) },
- yAxis: { type: 'value', name: '缇ょ粍鏁�' },
- series: [
- {
- type: 'bar',
- data: data.map((item) => item.count),
- itemStyle: { color: '#18a058', borderRadius: [4, 4, 0, 0] },
- barMaxWidth: 48,
- },
- ],
- });
- return;
- }
- if (props.type === 'messageType') {
- const data = await getMessageTypeDistribution();
- const items = data.map((item) => ({
- name:
- getDictObj(DICT_TYPE.IM_CONTENT_TYPE, String(item.type))?.label ||
- `鏈煡(${item.type})`,
- value: item.value,
- }));
- await renderEcharts({
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
- legend: { orient: 'vertical', right: 8, top: 'middle' },
- series: [
- {
- type: 'pie',
- radius: ['42%', '70%'],
- data: items,
- itemStyle: { borderColor: '#fff', borderRadius: 4, borderWidth: 2 },
- label: { show: false },
- },
- ],
- });
- return;
- }
- const data = await getTopSenders();
- const sorted = data.toSorted((a, b) => a.messageCount - b.messageCount);
- await renderEcharts({
- tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
- grid: { left: 24, right: 24, bottom: 16, top: 20, containLabel: true },
- xAxis: { type: 'value', name: '娑堟伅鏁�' },
- yAxis: {
- type: 'category',
- data: sorted.map((item) => `${item.nickname || item.userId}(${item.userId})`),
- axisLabel: { overflow: 'truncate', width: 110 },
- },
- series: [
- {
- type: 'bar',
- data: sorted.map((item) => item.messageCount),
- itemStyle: { color: '#2f7df6', borderRadius: [0, 4, 4, 0] },
- barMaxWidth: 18,
- },
- ],
- });
- } finally {
- loading.value = false;
- }
-}
-
-onMounted(loadData);
-</script>
-
-<template>
- <Card :body-style="{ padding: '12px 16px 16px' }" :title="titleMap[type]">
- <div class="relative min-h-[320px]">
- <EchartsUI ref="chartRef" height="320px" />
- <div
- v-if="loading"
- class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
- >
- 鍔犺浇涓�
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/im/manager/statistics/components/index.ts b/src/views/im/manager/statistics/components/index.ts
deleted file mode 100644
index c7228a3..0000000
--- a/src/views/im/manager/statistics/components/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as DistributionChart } from './distribution-chart.vue';
-export { default as OverviewCards } from './overview-cards.vue';
-export { default as TrendChart } from './trend-chart.vue';
diff --git a/src/views/im/manager/statistics/components/overview-cards.vue b/src/views/im/manager/statistics/components/overview-cards.vue
deleted file mode 100644
index 58a8181..0000000
--- a/src/views/im/manager/statistics/components/overview-cards.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerStatisticsApi } from '#/api/im/manager/statistics';
-
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { Card } from 'ant-design-vue';
-
-defineOptions({ name: 'ImManagerStatisticsOverviewCards' });
-
-const props = defineProps<{
- overview: ImManagerStatisticsApi.Overview;
-}>();
-
-/** 璁$畻鐜瘮 */
-function calcRatio(today: number, yesterday: number) {
- if (!yesterday) {
- return { label: '鏃犳槰鏃ユ暟鎹�', className: 'text-gray-400' };
- }
- const diff = ((today - yesterday) / yesterday) * 100;
- const sign = diff >= 0 ? '+' : '';
- return {
- label: `${sign}${diff.toFixed(1)}%`,
- className: diff >= 0 ? 'text-green-500' : 'text-red-500',
- };
-}
-
-const cards = computed(() => {
- const overview = props.overview;
- const totalMsgToday =
- (overview.privateMessageToday || 0) + (overview.groupMessageToday || 0);
- const totalMsgYesterday =
- (overview.privateMessageYesterday || 0) +
- (overview.groupMessageYesterday || 0);
- const msgRatio = calcRatio(totalMsgToday, totalMsgYesterday);
- return [
- {
- title: '鎬荤敤鎴�',
- value: overview.totalUser || 0,
- icon: 'lucide:user',
- color: '#2f7df6',
- metaLabel: '浠婃棩鏂板',
- metaValue: `+${overview.newUserToday || 0}`,
- metaClass: 'text-green-500',
- },
- {
- title: '鎬荤兢缁�',
- value: overview.totalGroup || 0,
- icon: 'lucide:messages-square',
- color: '#18a058',
- metaLabel: '浠婃棩鏂板',
- metaValue: `+${overview.newGroupToday || 0}`,
- metaClass: 'text-green-500',
- },
- {
- title: '鏃ユ椿鐢ㄦ埛',
- value: overview.activeUserDaily || 0,
- icon: 'lucide:timer',
- color: '#f59e0b',
- metaLabel: '鍛�/鏈堟椿',
- metaValue: `${overview.activeUserWeekly || 0} / ${overview.activeUserMonthly || 0}`,
- metaClass: 'text-gray-500',
- },
- {
- title: '浠婃棩娑堟伅',
- value: totalMsgToday,
- suffix: `锛圥 ${overview.privateMessageToday}/G ${overview.groupMessageToday}锛塦,
- icon: 'lucide:message-circle',
- color: '#64748b',
- metaLabel: '鐜瘮鏄ㄦ棩',
- metaValue: msgRatio.label,
- metaClass: msgRatio.className,
- },
- ];
-});
-</script>
-
-<template>
- <div class="grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-md:grid-cols-1">
- <Card v-for="card in cards" :key="card.title" :body-style="{ padding: '16px' }">
- <div class="flex items-center">
- <div
- class="mr-3 flex size-12 shrink-0 items-center justify-center rounded"
- :style="{ backgroundColor: card.color }"
- >
- <IconifyIcon :icon="card.icon" class="size-6 text-white" />
- </div>
- <div class="min-w-0 flex-1">
- <div class="mb-1 text-sm text-muted-foreground">{{ card.title }}</div>
- <div class="truncate text-2xl font-semibold leading-none">
- {{ card.value }}
- <span v-if="card.suffix" class="ml-1 text-xs font-normal text-gray-400">
- {{ card.suffix }}
- </span>
- </div>
- <div class="mt-2 text-xs text-gray-400">
- {{ card.metaLabel }}锛�
- <span :class="card.metaClass">{{ card.metaValue }}</span>
- </div>
- </div>
- </div>
- </Card>
- </div>
-</template>
diff --git a/src/views/im/manager/statistics/components/trend-chart.vue b/src/views/im/manager/statistics/components/trend-chart.vue
deleted file mode 100644
index 7ee50ed..0000000
--- a/src/views/im/manager/statistics/components/trend-chart.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<script lang="ts" setup>
-import type { EChartsOption, EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { nextTick, onMounted, ref } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Select } from 'ant-design-vue';
-
-import {
- getMessageTrend,
- getUserTrend,
-} from '#/api/im/manager/statistics';
-
-const props = defineProps<{
- type: 'message' | 'user';
-}>();
-
-const title =
- props.type === 'message'
- ? '娑堟伅瓒嬪娍锛堢鑱� + 缇よ亰锛�'
- : '鐢ㄦ埛瓒嬪娍锛堟柊澧炴敞鍐� + 鏃ユ椿锛�';
-
-const days = ref(7);
-const loading = ref(false);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 鑾峰彇鍥捐〃閰嶇疆 */
-function buildOptions(
- dates: string[],
- series: Record<string, number[]>,
-): EChartsOption {
- if (props.type === 'message') {
- return {
- tooltip: { trigger: 'axis' },
- legend: { data: ['绉佽亰娑堟伅', '缇よ亰娑堟伅'], top: 0 },
- grid: { left: 24, right: 24, bottom: 16, top: 42, containLabel: true },
- xAxis: {
- type: 'category',
- data: dates,
- axisLabel: { formatter: (value: string) => value.slice(5, 10) },
- },
- yAxis: { type: 'value', name: '娑堟伅閲�' },
- series: [
- {
- name: '绉佽亰娑堟伅',
- type: 'line',
- smooth: true,
- data: series.private || [],
- itemStyle: { color: '#2f7df6' },
- areaStyle: { color: 'rgba(47,125,246,0.12)' },
- },
- {
- name: '缇よ亰娑堟伅',
- type: 'line',
- smooth: true,
- data: series.group || [],
- itemStyle: { color: '#18a058' },
- areaStyle: { color: 'rgba(24,160,88,0.12)' },
- },
- ],
- };
- }
- return {
- tooltip: { trigger: 'axis' },
- legend: { data: ['鏂板娉ㄥ唽', '鏃ユ椿'], top: 0 },
- grid: { left: 24, right: 32, bottom: 16, top: 42, containLabel: true },
- xAxis: {
- type: 'category',
- data: dates,
- axisLabel: { formatter: (value: string) => value.slice(5, 10) },
- },
- yAxis: [
- { type: 'value', name: '鏂板娉ㄥ唽', position: 'left' },
- { type: 'value', name: '鏃ユ椿', position: 'right' },
- ],
- series: [
- {
- name: '鏂板娉ㄥ唽',
- type: 'bar',
- yAxisIndex: 0,
- data: series.register || [],
- itemStyle: { color: '#f59e0b' },
- barMaxWidth: 24,
- },
- {
- name: '鏃ユ椿',
- type: 'line',
- yAxisIndex: 1,
- smooth: true,
- data: series.active || [],
- itemStyle: { color: '#ef4444' },
- },
- ],
- };
-}
-
-/** 鍔犺浇鍥捐〃鏁版嵁 */
-async function loadData() {
- loading.value = true;
- try {
- const data =
- props.type === 'message'
- ? await getMessageTrend(days.value)
- : await getUserTrend(days.value);
- await nextTick();
- await renderEcharts(buildOptions(data.dates, data.series));
- } finally {
- loading.value = false;
- }
-}
-
-onMounted(loadData);
-</script>
-
-<template>
- <Card :body-style="{ padding: '12px 16px 16px' }">
- <template #title>
- <div class="flex items-center justify-between">
- <span>{{ title }}</span>
- <Select
- v-model:value="days"
- :options="[
- { label: '杩� 7 澶�', value: 7 },
- { label: '杩� 15 澶�', value: 15 },
- { label: '杩� 30 澶�', value: 30 },
- ]"
- class="w-28"
- size="small"
- @change="loadData"
- />
- </div>
- </template>
- <div class="relative min-h-[320px]">
- <EchartsUI ref="chartRef" height="320px" />
- <div
- v-if="loading"
- class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
- >
- 鍔犺浇涓�
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/im/manager/statistics/index.vue b/src/views/im/manager/statistics/index.vue
deleted file mode 100644
index dd15c1c..0000000
--- a/src/views/im/manager/statistics/index.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script lang="ts" setup>
-import type { ImManagerStatisticsApi } from '#/api/im/manager/statistics';
-
-import { onMounted, ref } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-
-import { getStatisticsOverview } from '#/api/im/manager/statistics';
-
-import {
- DistributionChart,
- OverviewCards,
- TrendChart,
-} from './components';
-
-defineOptions({ name: 'ImManagerStatistics' });
-
-const overview = ref<ImManagerStatisticsApi.Overview>();
-
-/** 鍔犺浇姒傝鏁版嵁 */
-async function loadOverview() {
- overview.value = await getStatisticsOverview();
-}
-
-onMounted(loadOverview);
-</script>
-
-<template>
- <Page auto-content-height>
- <div class="space-y-4">
- <OverviewCards v-if="overview" :overview="overview" />
-
- <div class="grid grid-cols-2 gap-4 max-xl:grid-cols-1">
- <TrendChart type="message" />
- <TrendChart type="user" />
- </div>
-
- <div class="grid grid-cols-3 gap-4 max-2xl:grid-cols-2 max-xl:grid-cols-1">
- <DistributionChart type="messageType" />
- <DistributionChart type="groupSize" />
- <DistributionChart type="topSenders" />
- </div>
- </div>
- </Page>
-</template>
diff --git a/src/views/im/manager/utils/format.ts b/src/views/im/manager/utils/format.ts
deleted file mode 100644
index e922964..0000000
--- a/src/views/im/manager/utils/format.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { formatDateTime } from '../../../../packages/utils/src';
-
-/** 灞曠ず鐢ㄦ埛鍚嶇О */
-export function formatUserLabel(
- nickname: string | undefined,
- userId: number | undefined,
-) {
- if (!userId) {
- return '-';
- }
- return `${nickname || '-'} (${userId})`;
-}
-
-/** 灞曠ず缇ゅ悕绉� */
-export function formatGroupLabel(
- name: string | undefined,
- groupId: number | undefined,
-) {
- if (!groupId) {
- return '-';
- }
- return `${name || '-'} (${groupId})`;
-}
-
-/** 灞曠ず鏃ユ湡鏃堕棿 */
-export function formatDateTimeText(value?: Date | number | string) {
- return value ? (formatDateTime(value) as string) : '-';
-}
-
-/** 鏍煎紡鍖� JSON */
-export function formatJsonText(content?: string) {
- if (!content) {
- return '';
- }
- try {
- return JSON.stringify(JSON.parse(content), null, 2);
- } catch {
- return content;
- }
-}
-
-/** 鎺㈡祴鍥剧墖灏哄 */
-export function probeImageSize(url: string) {
- return new Promise<{ height: number; width: number }>((resolve, reject) => {
- const img = new Image();
- img.addEventListener('load', () => {
- resolve({
- width: img.naturalWidth || img.width,
- height: img.naturalHeight || img.height,
- });
- });
- img.addEventListener('error', reject);
- img.src = url;
- });
-}
diff --git a/src/views/im/utils/auth.ts b/src/views/im/utils/auth.ts
deleted file mode 100644
index 450a31b..0000000
--- a/src/views/im/utils/auth.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useAccessStore, useUserStore } from '../../../packages/stores/src'
-
-// TODO DONE @AI锛氬凡浣跨敤 Vben 鐨� useUserStore / useAccessStore 鑾峰彇鐧诲綍淇℃伅锛�
-
-/** 鑾峰彇褰撳墠鐢ㄦ埛缂栧彿 */
-export function getCurrentUserId(): number {
- const userInfo = useUserStore().userInfo
- return Number(userInfo?.id ?? userInfo?.userId ?? 0)
-}
-
-/** 鑾峰彇鍒锋柊浠ょ墝 */
-export function getRefreshToken(): null | string {
- return useAccessStore().refreshToken
-}
diff --git a/src/views/im/utils/channel.ts b/src/views/im/utils/channel.ts
deleted file mode 100644
index 5276fb2..0000000
--- a/src/views/im/utils/channel.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useChannelStore } from '../home/store/channelStore'
-import { ImConversationType } from './constants'
-
-/**
- * 鏋勫缓棰戦亾浼氳瘽鎻忚堪锛坱ype / targetId / name / avatar锛�
- *
- * 浼樺厛鐢� channelStore 鎷夊埌鐨勭湡瀹炲悕绉� / 澶村儚锛涙湰鍦扮紦瀛樿繕娌℃媺鍒版椂閫�鍖栦负銆岄閬� N銆嶅崰浣嶆枃妗堛��
- * 鎶藉埌 utils/channel.ts 鍚庯紝useMessagePuller / websocketStore 鍏辩敤鍚屼竴浠藉崰浣嶉�昏緫锛岄伩鍏嶅澶勫鍒讹紱绫讳技 utils/user.ts 鍙栨樀绉扮殑宸ュ叿闆嗐��
- */
-export const buildChannelConversationStub = (channelId: number) => {
- const channel = useChannelStore().getChannel(channelId)
- return {
- type: ImConversationType.CHANNEL,
- targetId: channelId,
- name: channel?.name || `棰戦亾 ${channelId}`,
- avatar: channel?.avatar || ''
- }
-}
diff --git a/src/views/im/utils/config.ts b/src/views/im/utils/config.ts
deleted file mode 100644
index 6d153f0..0000000
--- a/src/views/im/utils/config.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * IM 鍓嶇杩愯鍙傛暟 / UI 闃堝��
- *
- * 涓� constants.ts 鐨勫垎宸ワ細
- * - constants.ts锛氬崗璁灇涓撅紙涓庡悗绔� enum 涓�涓�瀵瑰簲锛�+ 鍗忚濂戠害鍊硷紙濡� IM_AT_ALL_USER_ID锛�
- * - config.ts锛氬彲璋冭妭鐨勭瓥鐣ユ暟鍊硷紝鍖呮嫭闀滃儚鍚庣 ImProperties 鐨勯粯璁ゅ�间笌绾墠绔� UI 闃堝��
- *
- * 鏍囨敞銆屽悗绔暅鍍忋�嶇殑甯搁噺涓� yudao.im.* 閰嶇疆榛樿鍊间竴鑷达紱璋冩暣鍚庣閰嶇疆鏃堕渶鍚屾淇敼杩欓噷
- */
-
-// ==================== 鍚庣闀滃儚锛堜笌 ImProperties 榛樿鍊煎榻愶級 ====================
-
-/** 缇ゆ渶澶ф垚鍛樹汉鏁帮紙瀵归綈 yudao.im.group.max-member锛� */
-export const GROUP_MAX_MEMBER = 500
-
-/** 鍗曠兢绠$悊鍛樹汉鏁颁笂闄愶紙瀵归綈 yudao.im.group.admin-max-count锛� */
-export const GROUP_ADMIN_MAX_COUNT = 3
-
-/** 鍗曠兢缃《娑堟伅鏉℃暟涓婇檺锛堝榻� yudao.im.group.pin-max-count锛� */
-export const GROUP_PIN_MAX_COUNT = 5
-
-/**
- * 鏄惁鍚敤绉佽亰宸茶鍔熻兘锛堝榻� yudao.im.message.private-read-enabled锛�
- *
- * 鍏抽棴鍚庯細杩涘叆绉佽亰浼氳瘽涓嶅啀涓婃姤宸茶浣嶇疆锛涙皵娉$殑銆屽凡璇� / 鏈銆嶆爣绛鹃殣钘忥紱
- * 绠$悊鍚庡彴绉佽亰娑堟伅鍒楄〃鐨勩�岀姸鎬併�嶅垪涓庤鎯呬腑鐨勭姸鎬佸瓧娈甸殣钘�
- */
-export const MESSAGE_PRIVATE_READ_ENABLED = true
-
-/**
- * 鏄惁鍚敤缇よ亰宸茶鍔熻兘锛堝惈缇ゆ秷鎭洖鎵э紝瀵归綈 yudao.im.message.group-read-enabled锛�
- *
- * 鍏抽棴鍚庯細杩涘叆缇や細璇濅笉鍐嶄笂鎶ュ凡璇讳綅缃紱杈撳叆妗嗙殑銆屽彂閫佸洖鎵ф秷鎭�嶅叆鍙i殣钘忥紱
- * 缇ゆ秷鎭皵娉′笂鐨勩�孨 浜哄凡璇汇�峱opover 闅愯棌锛涚鐞嗗悗鍙扮兢娑堟伅鍒楄〃鐨勩�岀姸鎬併�嶃�屽洖鎵с�嶅垪涓庤鎯呬腑瀵瑰簲瀛楁闅愯棌
- */
-export const MESSAGE_GROUP_READ_ENABLED = true
-
-/** 娑堟伅鎾ゅ洖鏃堕棿闄愬埗锛堝垎閽燂紝瀵归綈 yudao.im.message.recall-timeout-minutes锛� */
-export const MESSAGE_RECALL_TIMEOUT_MINUTES = 5
-
-/** 绉佽亰绂荤嚎娑堟伅鏈�澶ф媺鍙栧ぉ鏁帮紙瀵归綈 yudao.im.message.private-pull-max-days锛� */
-export const MESSAGE_PRIVATE_PULL_MAX_DAYS = 30
-
-/** 缇よ亰绂荤嚎娑堟伅鏈�澶ф媺鍙栧ぉ鏁帮紙瀵归綈 yudao.im.message.group-pull-max-days锛� */
-export const MESSAGE_GROUP_PULL_MAX_DAYS = 30
-
-// ==================== 鍓嶇鐙湁锛氭媺鍙� / 鍒嗛〉 ====================
-
-/** 姣忔鎷夊彇绉佽亰娑堟伅鐨勬渶澶ф潯鏁帮紙鍚庣涓婇檺 1000锛屽墠绔彇淇濆畧鍊� 100锛� */
-export const MESSAGE_PRIVATE_PULL_SIZE = 100
-
-/** 姣忔鎷夊彇缇よ亰娑堟伅鐨勬渶澶ф潯鏁帮紙鍚庣涓婇檺 1000锛屽墠绔彇淇濆畧鍊� 100锛� */
-export const MESSAGE_GROUP_PULL_SIZE = 100
-
-/** 銆屾垜鐩稿叧銆嶅ソ鍙嬬敵璇峰垪琛ㄧ殑鍗曟鎷夊彇鏉℃暟锛堟父鏍囧垎椤� page size锛� */
-export const FRIEND_REQUEST_PAGE_SIZE = 100
-
-/** 銆屾垜鐩稿叧銆嶅姞缇ょ敵璇峰垪琛ㄧ殑鍗曟鎷夊彇鏉℃暟 */
-export const GROUP_REQUEST_PAGE_SIZE = 100
-
-// ==================== 涓婁紶瀹夊叏绛栫暐 ====================
-// 鏁板�间笌鍚庣 Spring multipart 閰嶇疆瀵归綈锛坄spring.servlet.multipart.max-file-size = 16MB`锛�
-// 鍚庣璋冨ぇ鍚庤繖閲屽悓姝ヨ皟锛涗笉瑕佸崟杈规斁瀹斤紝鍚﹀垯鐢ㄦ埛涓婁紶瀹屽埌鍚庣 413
-
-/** 鍥剧墖 / 瑙嗛 / 鏅�氭枃浠跺崟鏂囦欢涓婇檺锛圡B锛夛紝瀵归綈鍚庣 max-file-size */
-export const MESSAGE_IMAGE_MAX_MB = 16
-export const MESSAGE_VIDEO_MAX_MB = 16
-export const MESSAGE_FILE_MAX_MB = 16
-/** 璇煶鍗曟枃浠朵笂闄愶紙MB锛夛細60s @ 64kbps 绾� 0.5MB锛�5MB 宸茶繙瓒呭綍鍒跺彲鑳戒骇鍑虹殑澶у皬 */
-export const MESSAGE_VOICE_MAX_MB = 5
-
-/** 鍙墽琛� / 鑴氭湰绫绘墿灞曞悕榛戝悕鍗曪紱鎺ユ敹绔偣鍑讳笅杞藉悗鏈湴鍙屽嚮灏辫窇锛宧tml 鏈湴鎵撳紑杩樿兘鎵ц鑴氭湰 */
-export const DANGEROUS_FILE_EXTENSIONS = [
- 'exe',
- 'bat',
- 'cmd',
- 'com',
- 'msi',
- 'scr',
- 'pif',
- 'vbs',
- 'vbe',
- 'wsf',
- 'ws',
- 'js',
- 'jse',
- 'jar',
- 'sh',
- 'app',
- 'ps1',
- 'reg',
- 'html',
- 'htm'
-]
-
-// ==================== 鍓嶇鐙湁锛歎I 闃堝�� ====================
-
-/** 娑堟伅涔嬮棿娓叉煋銆屾椂闂村垎闅旀潯銆嶇殑闃堝�硷細10 鍒嗛挓 */
-export const MESSAGE_TIME_TIP_GAP_MS = 10 * 60 * 1000
-
-/**
- * 鎾ゅ洖鑿滃崟鍙鐨勬椂闂寸獥锛氳嚜宸卞彂閫佺殑娑堟伅瓒呰繃杩欎釜鏃堕暱灏变笉鑳藉啀鎾ゅ洖锛岃彍鍗曞洖閫�涓恒�屽垹闄ゃ��
- *
- * 姣斿悗绔� MESSAGE_RECALL_TIMEOUT_MINUTES锛�5 鍒嗛挓锛変弗鏍硷紝瀵归綈寰俊 PC 鐨� 2 鍒嗛挓
- */
-export const MESSAGE_RECALL_WINDOW_MS = 2 * 60 * 1000
-
-/** 鍚堝苟杞彂娑堟伅锛圡ERGE 绫诲瀷锛夋皵娉″唴棰勮鐨勬渶澶ц鏁帮紙瀵归綈寰俊銆岃亰澶╄褰曘�嶆皵娉★級 */
-export const MESSAGE_MERGE_PREVIEW_LINES = 3
-
-/** 鏈�杩戣浆鍙戜細璇� key 鍒楄〃鐨勬渶澶т繚鐣欐暟閲忥紙瀵归綈寰俊 PC 妯悜澶村儚鍖哄彲瑙佸閲忥級 */
-export const CONVERSATION_RECENT_FORWARD_MAX = 12
-
-// ==================== 鍓嶇鐙湁锛歊TC 閫氳瘽鍏滃簳 ====================
-
-/**
- * 鎸搩瓒呮椂鍏滃簳锛涢�氳瘽瀛樻椿鏈熼棿 timer 璋冪敤 noAnswerCallCheck 鎺ュ彛鐨勯棿闅旓紱
- * 瀹為檯瓒呮椂闃堝�肩敱鍚庣 yudao.im.rtc.invite-timeout-minutes 鍐冲畾锛屽墠绔粎鍐冲畾瑙﹀彂棰戠巼
- */
-export const RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS = 60 * 1000
-
-// ==================== 鍓嶇鐙湁锛歐ebSocket 鑷姩閲嶈繛 ====================
-// 鎸囨暟閫�閬匡細base * 2^attempt锛屼笂闄愬皝椤讹紱姣忔鍐嶅彔鍔� 0~jitter ms 闅忔満鍋忕Щ
-// 閬垮厤鏈嶅姟绔噸鍚椂鍏ㄩ噺瀹㈡埛绔湪鍚屼竴绉掓墦杩囨潵褰㈡垚銆屾儕缇ゃ�嶏紱涓嶈娆℃暟涓婇檺锛屾寔缁噸杩炵洿鍒伴摼璺仮澶�
-
-/** 棣栨閲嶈繛绛夊緟锛屽崟浣� ms */
-export const WS_RECONNECT_BASE_MS = 1000
-
-/** 閫�閬夸笂闄愶紝鍗曚綅 ms锛涜繛缁け璐� 5 娆″悗绋冲畾鍦� 30s 涓嶅啀澧為暱 */
-export const WS_RECONNECT_MAX_MS = 30 * 1000
-
-/** 閫�閬垮彔鍔犵殑闅忔満鎶栧姩涓婇檺锛屽崟浣� ms */
-export const WS_RECONNECT_JITTER_MS = 1000
diff --git a/src/views/im/utils/constants.ts b/src/views/im/utils/constants.ts
deleted file mode 100644
index 895571e..0000000
--- a/src/views/im/utils/constants.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-/** IM 鍐呭绫诲瀷鏋氫妇锛堝榻愬悗绔� ImContentTypeEnum锛� */
-export const ImContentType = {
- // ========== 鐢ㄦ埛鑱婂ぉ娑堟伅锛�101-105 鐩存帴澶嶇敤 OpenIM 娈典綅缂栧彿锛� ==========
- TEXT: 101, // 鏂囨湰锛堝搴� OpenIM Text=101锛�
- IMAGE: 102, // 鍥剧墖锛堝搴� OpenIM Picture=102锛�
- VOICE: 103, // 璇煶锛堝搴� OpenIM Sound=103锛�
- VIDEO: 104, // 瑙嗛锛堝搴� OpenIM Video=104锛�
- FILE: 105, // 鏂囦欢锛堝搴� OpenIM File=105锛�
- MERGE: 107, // 鍚堝苟杞彂锛堝搴� OpenIM Merger=107锛沺ayload 鍐呭祵瀹屾暣蹇収锛�
- CARD: 108, // 鍚嶇墖锛堝搴� OpenIM Card=108锛�
- FACE: 115, // 琛ㄦ儏璐村浘锛堝搴� OpenIM Face=115锛沀nicode emoji 浠嶈蛋 TEXT锛�
- // ========== 棰戦亾娑堟伅鎵╁睍娈碉紙125+锛汷penIM 122 涔嬪悗鏈崰鐢紝浣滀负棰戦亾 / 鍏紬鍙风被娑堟伅鎵╁睍锛� ==========
- MATERIAL: 125, // 棰戦亾绱犳潗锛堝浘鏂囧崱鐗囷細title + coverUrl + summary + url锛涜鎯呮媺 get-content锛�
- // ========== 淇″彿绫伙紙2101 / 2200 鐩存帴澶嶇敤 OpenIM 娈典綅缂栧彿锛�2201 鑷湁鎵╁睍锛� ==========
- RECALL: 2101, // 鎾ゅ洖锛堝搴� OpenIM RevokeNotification=2101锛�
- RECEIPT: 2200, // 鍥炴墽锛堝搴� OpenIM HasReadReceipt=2200锛�
- READ: 2201, // 宸茶锛堝绔悓姝ワ紝OpenIM 鏃犲搴旓紱鑷湁鎵╁睍锛�
- // ========== 瀹炴椂閫氳瘽淇′护锛�1601-1605 娈典綅涓� OpenIM 瀵归綈锛�1610+ 鑷湁鎵╁睍锛� ==========
- RTC_CALL: 1601, // 閫氳瘽淇′护缁熶竴鍏ュ彛锛堝搴� OpenIM SignalingNotification=1601锛�
- RTC_PARTICIPANT_CONNECTED: 1602, // 閫氳瘽鍙備笌鑰呭姞鍏ワ紙瀵瑰簲 OpenIM RoomParticipantsConnectedNotification=1602锛�
- RTC_PARTICIPANT_DISCONNECTED: 1603, // 閫氳瘽鍙備笌鑰呯寮�锛堝搴� OpenIM RoomParticipantsDisconnectedNotification=1603锛�
- // 1604-1609 OpenIM 宸茬敤 / 鐣欎綔鎵╁睍锛屾湰绯荤粺鏆備笉浣跨敤
- RTC_CALL_START: 1610, // 閫氳瘽寮�濮嬶紙鑷湁鎵╁睍锛孫penIM 鏃狅紱浠呯兢鑱婏紱涓� END 涓ゆ寮忛厤瀵癸級
- RTC_CALL_END: 1611, // 閫氳瘽缁撴潫锛堣嚜鏈夋墿灞曪紝OpenIM 鏃狅紱绉佽亰 / 缇よ亰锛�
- // ========== 濂藉弸閫氱煡锛�1201-1210 鐩存帴澶嶇敤 OpenIM 娈典綅缂栧彿锛� ==========
- FRIEND_REQUEST_APPROVED: 1201, // 濂藉弸鐢宠琚悓鎰�
- FRIEND_REQUEST_REJECTED: 1202, // 濂藉弸鐢宠琚嫆缁�
- FRIEND_REQUEST_RECEIVED: 1203, // 鏀跺埌鏂扮殑濂藉弸鐢宠
- FRIEND_ADD: 1204, // 鏂板濂藉弸锛堝弻鏂瑰缓绔嬪叧绯伙級
- FRIEND_DELETE: 1205, // 濂藉弸琚垹闄�
- // 1206 瀵瑰簲 OpenIM FriendRemarkSetNotification锛涙湰绯荤粺骞跺叆 FRIEND_UPDATE(1210) 缁熶竴鎺ㄩ�侊紝鍗曚竴瀛楁鍙樻洿涓嶅啀鐙珛閫氶亾
- FRIEND_BLOCK: 1207, // 鍔犲叆榛戝悕鍗�
- FRIEND_UNBLOCK: 1208, // 绉诲嚭榛戝悕鍗�
- FRIEND_INFO_UPDATED: 1209, // 濂藉弸璧勬枡鍙樻洿锛堟樀绉� / 澶村儚锛�
- FRIEND_UPDATE: 1210, // 濂藉弸淇℃伅鎵归噺鏇存柊锛坰ilent / pinned锛�
- // ========== 缇や簨浠讹紙1501-1520 鐩存帴澶嶇敤 OpenIM 娈典綅缂栧彿锛�1530+ 鑷湁鎵╁睍娈碉級 ==========
- GROUP_CREATE: 1501, // 缇ゅ垱寤�
- GROUP_INFO_UPDATE: 1502, // 缇や俊鎭彉鏇达紙NAME / NOTICE 涔嬪瀛楁鍏滃簳锛�
- GROUP_REQUEST_RECEIVED: 1503, // 鏀跺埌鏂扮殑鍏ョ兢鐢宠锛堢鑱婂畾鍚戞帹閫佺粰缇や富 + 鍏ㄩ儴绠$悊鍛橈級
- GROUP_MEMBER_QUIT: 1504, // 鎴愬憳閫�缇�
- GROUP_REQUEST_APPROVED: 1505, // 鍏ョ兢鐢宠琚悓鎰忥紙绉佽亰鎺ㄧ粰鐢宠浜� + 缇や富 + 鍏ㄩ儴绠$悊鍛橈級
- GROUP_REQUEST_REJECTED: 1506, // 鍏ョ兢鐢宠琚嫆缁濓紙鍚屼笂锛�
- GROUP_OWNER_TRANSFER: 1507, // 缇や富杞
- GROUP_MEMBER_KICK: 1508, // 鎴愬憳琚Щ鍑�
- GROUP_MEMBER_INVITE: 1509, // 鎴愬憳鍔犲叆
- GROUP_MEMBER_ENTER: 1510, // 鑷敱杩涚兢锛團REE 妯″紡鎴栫敵璇烽�氳繃鍚庯紱鍏ㄥ憳骞挎挱锛�
- GROUP_DISSOLVE: 1511, // 缇よВ鏁�
- GROUP_MEMBER_MUTED: 1512, // 鍗曟垚鍛樼瑷�
- GROUP_MEMBER_CANCEL_MUTED: 1513, // 鍗曟垚鍛樺彇娑堢瑷�
- GROUP_MUTED: 1514, // 鍏ㄧ兢绂佽█
- GROUP_CANCEL_MUTED: 1515, // 鍏ㄧ兢鍙栨秷绂佽█
- GROUP_MEMBER_NICKNAME_UPDATE: 1516, // 鎴愬憳鏄电О鍙樻洿锛堢獎鍖栧埌 displayUserName锛�
- GROUP_ADMIN_ADD: 1517, // 娣诲姞绠$悊鍛�
- GROUP_ADMIN_REMOVE: 1518, // 鎾ら攢绠$悊鍛�
- GROUP_NOTICE_UPDATE: 1519, // 缇ゅ叕鍛婂彉鏇�
- GROUP_NAME_UPDATE: 1520, // 缇ゅ悕鍙樻洿
- // ========== 鑷湁鎵╁睍娈碉紙1530+锛孫penIM 1500-1520 娈典綅鏃犲搴旂墿锛� ==========
- GROUP_MEMBER_SETTING_UPDATE: 1530, // 缇ゆ垚鍛樹釜浜鸿缃彉鏇达細silent / groupRemark 涓汉澶氱鍚屾
- GROUP_MESSAGE_PIN: 1531, // 缇ゆ秷鎭疆椤讹紙鑷湁鎵╁睍锛孫penIM 鏃狅級
- GROUP_MESSAGE_UNPIN: 1532, // 缇ゆ秷鎭彇娑堢疆椤讹紙鑷湁鎵╁睍锛孫penIM 鏃狅級
- GROUP_BANNED: 1533 // 缇ゅ皝绂佸彉鏇达紙鑷湁鎵╁睍锛孫penIM 鏃狅級
-} as const
-
-/** 鍒ゆ柇鏄惁銆岀兢骞挎挱浜嬩欢銆嶏細[GROUP_CREATE, GROUP_BANNED] 娈典綅閮界畻锛屼粎 GROUP_MEMBER_SETTING_UPDATE 鏄釜浜轰俊鍙锋帓闄� */
-export function isGroupNotification(type: number): boolean {
- return (
- type >= ImContentType.GROUP_CREATE &&
- type <= ImContentType.GROUP_BANNED &&
- type !== ImContentType.GROUP_MEMBER_SETTING_UPDATE
- )
-}
-
-/** 鍒ゆ柇鏄惁銆屽ソ鍙嬮�氱煡浜嬩欢銆嶏細1201-1210 娈典綅 */
-export function isFriendNotification(type: number): boolean {
- return type >= ImContentType.FRIEND_REQUEST_APPROVED && type <= ImContentType.FRIEND_UPDATE
-}
-
-/** 鍒ゆ柇鏄惁銆屽姞缇ょ敵璇烽�氱煡浜嬩欢銆嶏細1503/1505/1506 */
-export function isGroupRequestNotification(type: number): boolean {
- return (
- type === ImContentType.GROUP_REQUEST_RECEIVED ||
- type === ImContentType.GROUP_REQUEST_APPROVED ||
- type === ImContentType.GROUP_REQUEST_REJECTED
- )
-}
-
-/** 鍒ゆ柇鏄惁銆屼細璇濆唴鐨勫ソ鍙嬩簨浠舵皵娉°�嶏細FRIEND_ADD / FRIEND_DELETE 鐩存帴娓叉煋鎴愮伆鑹叉彁绀猴紝涓庣兢浜嬩欢鍚屽鐞� */
-export function isFriendChatTip(type: number): boolean {
- return type === ImContentType.FRIEND_ADD || type === ImContentType.FRIEND_DELETE
-}
-
-/** 鍒ゆ柇鏄惁銆屼細璇濆唴鐨勯�氳瘽浜嬩欢姘旀场銆嶏細RTC_CALL_START / RTC_CALL_END 娓叉煋鎴愮伆鑹叉彁绀� */
-export function isRtcCallTip(type: number): boolean {
- return type === ImContentType.RTC_CALL_START || type === ImContentType.RTC_CALL_END
-}
-
-/**
- * IM 鏅�氬唴瀹圭被鍨嬮泦鍚堬紙normal vs event 浜屽垎锛涗笌鍚庣 ImContentTypeEnum.normal 瀛楁璇箟涓�鑷达級
- *
- * 杩欎釜闆嗗悎鍦ㄥ澶勮澶嶇敤锛屾柊澧炵被鍨嬪墠鍏堢‘璁ゆ墍鏈夊壇浣滅敤閮界鍚堥鏈燂細
- * 1. 鍚庣鍙戦�佸叆鍙f牎楠岋紙Im{Private,Group}MessageSendReqVO.isNormalType锛夆�斺�� 鐢ㄦ埛鍙戦�佺殑鍐呭绫诲瀷蹇呴』 normal=true
- * 2. 鍓嶇鎺ユ敹渚ф湭璇� / 鎻愮ず闊筹紙websocketStore锛夆�斺�� normal 娑堟伅璁″叆浼氳瘽鏈鏁� + 瑙﹀彂澹伴煶
- * 3. 鍓嶇浼氳瘽鍒楄〃 lastType / @ 鏍囩锛圕onversationItem锛夆�斺�� 鍙湁 normal 鎵嶇畻銆屾渶鍚庝竴鏉¤亰澶╂秷鎭��
- * 4. 鍓嶇缇ゆ秷鎭疆椤惰彍鍗曪紙message-item.vue 鐨� canPin锛夆�斺�� normal 鎵嶅厑璁哥兢涓� / 绠$悊鍛樼疆椤�
- *
- * 鍚嶇墖锛圕ARD锛�/ 琛ㄦ儏锛團ACE锛夐兘鏄�岀敤鎴蜂富鍔ㄥ彂鐨勮亰澶╂秷鎭�嶏紝1/2/3 閮界鍚堥鏈燂紱4 鍚屾椂鏀惧紑 = 缇や富鍙疆椤讹紝璇箟鍚堢悊
- */
-const ImContentTypeNormals = new Set<number>([
- ImContentType.CARD,
- ImContentType.FACE,
- ImContentType.FILE,
- ImContentType.IMAGE,
- ImContentType.MATERIAL, // 棰戦亾绱犳潗璁″叆鏈鏁� + 杩涗細璇濆垪琛�
- ImContentType.MERGE,
- ImContentType.TEXT,
- ImContentType.VIDEO,
- ImContentType.VOICE
-])
-
-/** 鍒ゆ柇鏄惁"鏅�氭秷鎭�" */
-export function isNormalMessage(type: number): boolean {
- return ImContentTypeNormals.has(type)
-}
-
-/** IM 濯掍綋鍐呭绫诲瀷闆嗗悎锛氬彂閫佷緷璧栨湰鍦� File 涓婁紶锛屽埛鏂板悗 _localFile 涓㈠け鍗充笉鍙仮澶� */
-const ImContentTypeMedia = new Set<number>([
- ImContentType.FILE,
- ImContentType.IMAGE,
- ImContentType.VIDEO,
- ImContentType.VOICE
-])
-
-/** 鍒ゆ柇鏄惁銆屽獟浣撴秷鎭�嶏細鍥剧墖 / 鏂囦欢 / 璇煶 / 瑙嗛 */
-export function isMediaMessageType(type: number): boolean {
- return ImContentTypeMedia.has(type)
-}
-
-/**
- * IM 娑堟伅鐘舵�佹灇涓撅紙瀵归綈鍚庣 ImMessageStatusEnum锛屽墠绔墿灞� SENDING + FAILED锛�
- *
- * 鐢熷懡鍛ㄦ湡锛欶AILED(-2) 鈫� SENDING(-1) 鈫� NORMAL(0) 鈫� RECALL(2)
- */
-export const ImMessageStatus = {
- FAILED: -2, // 鍙戦�佸け璐ワ紙鍓嶇鐙湁锛�
- SENDING: -1, // 鍙戦�佷腑锛堝墠绔嫭鏈夛級
- NORMAL: 0, // 姝e父
- RECALL: 2 // 宸叉挙鍥�
-} as const
-
-/** IM 浼氳瘽绫诲瀷鏋氫妇 */
-export const ImConversationType = {
- NONE: 0, // 鏃犱細璇�
- PRIVATE: 1, // 绉佽亰
- GROUP: 2, // 缇よ亰
- CHANNEL: 3 // 棰戦亾 / 鍏紬鍙�
-} as const
-
-/** ImConversationType 鍙栧�硷紙鐢ㄤ簬娑堟伅 payload 瀛楁绫诲瀷鏀剁獎锛� */
-export type ImConversationTypeValue = (typeof ImConversationType)[keyof typeof ImConversationType]
-
-/** 鏄惁绉佽亰浼氳瘽锛涘悓鏃舵敹绐勭被鍨� */
-export function isPrivateConversation(type: number | undefined): boolean {
- return type === ImConversationType.PRIVATE
-}
-
-/** 鏄惁缇よ亰浼氳瘽锛涘悓鏃舵敹绐勭被鍨� */
-export function isGroupConversation(type: number | undefined): boolean {
- return type === ImConversationType.GROUP
-}
-
-/** 鏄惁棰戦亾浼氳瘽锛涘悓鏃舵敹绐勭被鍨� */
-export function isChannelConversation(type: number | undefined): boolean {
- return type === ImConversationType.CHANNEL
-}
-
-/** IM 閫氳瘽濯掍綋绫诲瀷锛堝榻愬悗绔� ImRtcCallMediaTypeEnum锛� */
-export const ImRtcCallMediaType = {
- VOICE: 1,
- VIDEO: 2
-} as const
-
-/** IM 閫氳瘽鐘舵�侊紙瀵归綈鍚庣 ImRtcCallStatusEnum锛� */
-export const ImRtcCallStatus = {
- CREATED: 10, // 鍒涘缓锛氱鑱婄瓑琚彨鎺ュ惉锛涚兢鑱婂彂璧蜂汉宸茶繘鎴跨瓑鍏朵粬浜哄姞鍏�
- RUNNING: 20, // 杩涜涓細绗竴涓潪鍙戣捣浜烘帴閫氬悗杩涘叆
- ENDED: 30 // 宸茬粨鏉�
-} as const
-
-/** IM 閫氳瘽缁撴潫鍘熷洜锛堝榻愬悗绔� ImRtcCallEndReasonEnum锛� */
-export const ImRtcCallEndReason = {
- HANGUP: 1, // 鎺ラ�氬悗浠讳竴鏂逛富鍔ㄦ寕鏂�
- REJECT: 2, // 琚彨鎺ラ�氬墠鐐规嫆鎺�
- CANCEL: 3, // 涓诲彨鎺ラ�氬墠涓诲姩鍙栨秷
- NO_ANSWER: 4, // 鎸搩瓒呮椂鏈帴閫�
- BUSY: 5, // 绉佽亰鍛煎彨鏃跺鏂规蹇�
- ERROR: 9 // 缃戠粶涓柇 / 璁惧澶辫触
-} as const
-
-/** ImRtcCallEndReason 鍙栧�肩被鍨� */
-export type ImRtcCallEndReasonValue = (typeof ImRtcCallEndReason)[keyof typeof ImRtcCallEndReason]
-
-/** IM 閫氳瘽鍙備笌鑰呯姸鎬侊紙瀵归綈鍚庣 ImRtcParticipantStatusEnum锛夛紱鍚屾椂浣滀负 RTC_CALL 淇′护 status 瀛楁鍙栧�� */
-export const ImRtcParticipantStatus = {
- INVITING: 10, // 鏉ョ數閭�璇�
- JOINED: 20, // 鎺ュ惉 / 宸插姞鍏�
- REJECTED: 30, // 鎷掓帴
- NO_ANSWER: 40, // 涓诲彨鍙栨秷锛岃閭�璇锋柟鏈簲绛�
- LEFT: 50 // 鎸傛柇绂诲紑
-} as const
-
-/** ImRtcParticipantStatus 鍙栧�肩被鍨� */
-export type ImRtcParticipantStatusValue =
- (typeof ImRtcParticipantStatus)[keyof typeof ImRtcParticipantStatus]
-
-/**
- * IM 閫氳瘽 UI 闃舵锛涘墠绔嫭鏈夛紝鐢ㄤ簬椹卞姩 inviting / incoming / running 涓夌寮圭獥鍒囨崲锛�
- * 璺熷悗绔� ImRtcCallStatus 涓嶆槸 1:1 鏄犲皠锛宻tage 澶氫簡銆岃嚜宸辨槸涓诲彨杩樻槸琚彨銆嶇殑瑙掕壊缁村害
- */
-export const ImRtcCallStage = {
- IDLE: 'idle', // 绌洪棽锛涘悗绔棤瀵瑰簲锛堟湰绔棤娲昏穬閫氳瘽锛�
- INVITING: 'inviting', // 涓诲彨绛夊緟瀵规柟鎺ュ彈锛涘搴斿悗绔� ImRtcCallStatus.CREATED锛堣嚜宸辨槸涓诲彨锛�
- INCOMING: 'incoming', // 琚彨鏉ョ數鍝嶉搩锛涘搴斿悗绔� ImRtcCallStatus.CREATED锛堣嚜宸辨槸琚彨锛�
- RUNNING: 'running' // 閫氳瘽涓紱瀵瑰簲鍚庣 ImRtcCallStatus.RUNNING
-} as const
-
-/** ImRtcCallStage 鍙栧�肩被鍨� */
-export type ImRtcCallStageValue = (typeof ImRtcCallStage)[keyof typeof ImRtcCallStage]
-
-/** IM WebSocket 澶栧眰甯х被鍨� */
-export const ImWebSocketMessageType = {
- NOTIFICATION: 'im-notification' // IM 閫氱煡
-} as const
-
-/** IM 娑堟伅鍥炴墽鐘舵�佹灇涓撅紙瀵归綈鍚庣 ImMessageReceiptStatusEnum锛� */
-export const ImMessageReceiptStatus = {
- NO_RECEIPT: 0, // 涓嶉渶瑕佸洖鎵�
- PENDING: 1, // 寰呭畬鎴�
- DONE: 2 // 宸插畬鎴�
-} as const
-
-/** 缇ゆ垚鍛樿鑹诧紙瀵归綈鍚庣 ImGroupMemberRoleEnum锛� */
-export const ImGroupMemberRole = {
- OWNER: 1, // 缇や富
- ADMIN: 2, // 绠$悊鍛�
- NORMAL: 3 // 鏅�氭垚鍛�
-} as const
-
-/** 鍔犵兢鏉ユ簮锛堝榻愬悗绔� ImGroupAddSourceEnum锛� */
-export const ImGroupAddSource = {
- SEARCH: 1, // 鎼滅储
- INVITE: 2, // 閭�璇�
- QR_CODE: 3, // 鎵爜
- SHARE_LINK: 4 // 鍒嗕韩閾炬帴
-} as const
-
-/** 鍔犵兢鐢宠澶勭悊缁撴灉锛堝榻愬悗绔� ImGroupRequestHandleResultEnum锛� */
-export const ImGroupRequestHandleResult = {
- UNHANDLED: 0, // 鏈鐞�
- AGREED: 1, // 鍚屾剰
- REFUSED: 2 // 鎷掔粷
-} as const
-
-/** 濂藉弸娣诲姞鏉ユ簮锛堝榻愬悗绔� ImFriendAddSourceEnum锛� */
-export const ImFriendAddSource = {
- SEARCH: 1, // 鎼滅储
- GROUP: 2, // 缇よ亰
- QR_CODE: 3, // 鎵爜
- CARD: 4 // 鍚嶇墖
-} as const
-
-/** 濂藉弸鐢宠澶勭悊缁撴灉锛堝榻愬悗绔� ImFriendRequestHandleResultEnum锛� */
-export const ImFriendRequestHandleResult = {
- UNHANDLED: 0, // 鏈鐞�
- AGREED: 1, // 鍚屾剰
- REFUSED: 2 // 鎷掔粷
-} as const
-
-/**
- * @鍏ㄤ綋鎴愬憳 鐨勭壒娈� userId 鏍囪瘑锛歛tUserIds 涓寘鍚� -1 琛ㄧず @ 鍏ㄤ綋鎴愬憳
- *
- * 涓庡悗绔害瀹氾細缇ゆ秷鎭� atUserIds 鏁扮粍閲屽嚭鐜� -1 鏃讹紝鎵�鏈夋垚鍛橀兘鏀跺埌鎻愰啋
- * MentionPicker 娓叉煋铏氭嫙椤� + conversationStore.applyAt 鍒ゅ畾 atAll 閮介潬杩欎釜鍊�
- */
-export const IM_AT_ALL_USER_ID = -1
-
-/** @鍏ㄤ綋鎴愬憳 鐨勫睍绀哄悕锛堝榻愬井淇� PC锛� */
-export const IM_AT_ALL_NICKNAME = '鎵�鏈変汉'
-
-/** 杞彂妯″紡锛歋INGLE 閫愭潯鍘熸牱杞� / MERGE 鎵撳寘鎴� MergeMessage */
-export const ImForwardMode = {
- SINGLE: 'single',
- MERGE: 'merge'
-} as const
-
-/** ImForwardMode 鍙栧�肩被鍨� */
-export type ImForwardModeValue = (typeof ImForwardMode)[keyof typeof ImForwardMode]
diff --git a/src/views/im/utils/conversation.ts b/src/views/im/utils/conversation.ts
deleted file mode 100644
index f846705..0000000
--- a/src/views/im/utils/conversation.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-// ====================================================================
-// IM 浼氳瘽 / 鎾ゅ洖灞曠ず utility
-// ====================================================================
-// 鍩轰簬 conversation 涓婁笅鏂� + sender 淇℃伅瀹炴椂绠�"灞曠ず鏂囨"銆�
-// 1. 鍙戦�佷汉鍚嶄竴寰嬬敱 utils/user.getSenderDisplayName 瀹炴椂绠楋紝鏈枃浠跺彧璐熻矗鎷�"鎾ゅ洖 tip / 鎽樿"绛夋枃妗堛��
-// 2. fallbackName 鐢辫皟鐢ㄦ柟浼犲叆锛堝吀鍨嬫潵婧愶細Conversation.lastSenderDisplayName 蹇収锛夛紝閫忎紶鍒� getSenderDisplayName 鍐呴儴锛岀畻涓嶅嚭鐪熷悕鏃跺厹搴�
-// ====================================================================
-
-import type { Message } from '../home/types'
-
-import { ImContentType, ImConversationType, isFriendChatTip, isGroupNotification, isRtcCallTip } from './constants'
-import {
- type CardMessage,
- type FaceMessage,
- type FileMessage,
- getCardLabelInfo,
- type MaterialMessage,
- parseMessage,
- segmentsToText,
- type TextMessage,
- tipMention,
- type TipSegment,
- tipText
-} from './message'
-import { resolveFriendNotificationText, resolveGroupNotificationText, resolveRtcCallLastContent } from './message'
-import { getSenderDisplayName } from './user'
-
-/** 浼氳瘽涓婚敭锛歚type-targetId` 鎷兼垚绋冲畾瀛楃涓诧紝缁� v-for :key銆乤ctive 姣斿銆乵ap key 绛夊満鏅叡鐢� */
-export function getConversationKey(conversation: { targetId: number; type: number; }): string {
- return `${conversation.type}-${conversation.targetId}`
-}
-
-/** 鎸夋樀绉版ā绯婅繃婊や細璇濆垪琛細绌� keyword 鍘熸牱杩斿洖锛屽懡涓蛋 toLowerCase 涓嶅尯鍒嗗ぇ灏忓啓 */
-export function filterConversationsByKeyword<T extends { name?: string }>(
- list: T[],
- keyword: string
-): T[] {
- const trimmed = keyword.trim().toLowerCase()
- if (!trimmed) {
- return list
- }
- return list.filter((c) => (c.name || '').toLowerCase().includes(trimmed))
-}
-
-/**
- * 琛ㄦ儏娑堟伅鐨勭粺涓�鏂囨湰棰勮锛氭憳瑕� / 鍘嗗彶鎼滅储 / 寮曠敤鍧� / 绠$悊鍚庡彴棰勮鍏辩敤涓�浠�
- * 鏈� name 鏃惰蛋 `[琛ㄦ儏] name`锛堢郴缁熷寘锛夛紝鏃� name 鏃惰蛋 `[琛ㄦ儏]`锛堜釜浜鸿〃鎯呴�氬父鏃� name锛�
- */
-export function buildFacePreviewText(facePayload: null | undefined | { name?: string }): string {
- return facePayload?.name ? `[琛ㄦ儏] ${facePayload.name}` : '[琛ㄦ儏]'
-}
-
-/**
- * 鎾ゅ洖鎻愮ず segments
- *
- * senderId 缂哄け鏃朵笉鎸� mention 娈碉紝閬垮厤鐐瑰嚭閿欏彿锛涚畻涓嶅嚭鐪熷悕闄嶇骇涓恒�屽鏂广�嶇函鏂囨湰
- */
-export function buildRecallTipSegments(
- senderId: number,
- selfSend: boolean,
- conversationType: number,
- conversationTargetId: number,
- fallbackName?: string
-): TipSegment[] {
- if (selfSend) {
- return [tipText('浣犳挙鍥炰簡涓�鏉℃秷鎭�')]
- }
- const senderDisplayName = getSenderDisplayName(
- senderId,
- conversationType,
- conversationTargetId,
- fallbackName
- )
- if (!senderId) {
- return [tipText(`${senderDisplayName || '瀵规柟'} 鎾ゅ洖浜嗕竴鏉℃秷鎭痐)]
- }
- return [
- tipMention(senderId, senderDisplayName || '瀵规柟'),
- tipText(' 鎾ゅ洖浜嗕竴鏉℃秷鎭�')
- ]
-}
-
-/** 鎾ゅ洖鎻愮ず鏂囨锛氳嚜宸辨挙鍥炲浐瀹氭枃妗堬紝瀵规柟鎾ゅ洖甯� sender 鍚嶏紙瀹炴椂绠� + fallbackName 鍏滃簳锛� */
-export function buildRecallTip(
- senderId: number,
- selfSend: boolean,
- conversationType: number,
- conversationTargetId: number,
- fallbackName?: string
-): string {
- return segmentsToText(
- buildRecallTipSegments(senderId, selfSend, conversationType, conversationTargetId, fallbackName)
- )
-}
-
-/**
- * 鍗曟潯娑堟伅鐨勭函鏂囨湰鎽樿锛涗笉渚濊禆 conversation 涓婁笅鏂�
- *
- * - withFileName=true 鏃舵枃浠舵秷鎭檮甯︽枃浠跺悕锛堝悎骞惰浆鍙戣鎯呯敤锛�
- * - 涓嶅鐞� RECALL / 缇ゅ箍鎾� / 濂藉弸閫氱煡锛岀敱璋冪敤鏂瑰寘涓�灞�
- */
-export function summarizeMessageContent(
- message: Pick<Message, 'content' | 'type'>,
- opts?: { withFileName?: boolean }
-): string {
- switch (message.type) {
- case ImContentType.CARD: {
- return `[${getCardLabelInfo(parseMessage<CardMessage>(message.content)).label}]`
- }
- case ImContentType.FACE: {
- return buildFacePreviewText(parseMessage<FaceMessage>(message.content))
- }
- case ImContentType.FILE: {
- if (opts?.withFileName) {
- const file = parseMessage<FileMessage>(message.content)
- return file?.name ? `[鏂囦欢] ${file.name}` : '[鏂囦欢]'
- }
- return '[鏂囦欢]'
- }
- case ImContentType.IMAGE: {
- return '[鍥剧墖]'
- }
- case ImContentType.MATERIAL: {
- const material = parseMessage<MaterialMessage>(message.content)
- return material?.title ? `[棰戦亾] ${material.title}` : '[棰戦亾]'
- }
- case ImContentType.MERGE: {
- return '[鑱婂ぉ璁板綍]'
- }
- case ImContentType.RTC_CALL_END:
- case ImContentType.RTC_CALL_START: {
- return '[璇煶閫氳瘽]'
- }
- case ImContentType.TEXT: {
- return parseMessage<TextMessage>(message.content)?.content ?? ''
- }
- case ImContentType.VIDEO: {
- return '[瑙嗛]'
- }
- case ImContentType.VOICE: {
- return '[璇煶]'
- }
- default: {
- return ''
- }
- }
-}
-
-/** 浼氳瘽鍒楄〃鏈�鍚庝竴鏉℃憳瑕侊細RECALL 璧� buildRecallTip + fallbackName锛涘叾瀹冩寜鍐呭绫诲瀷娲剧敓 */
-export function resolveConversationLastContent(
- message: Message,
- conversationType: number,
- conversationTargetId: number,
- fallbackName?: string
-): string {
- if (message.type === ImContentType.RECALL) {
- return buildRecallTip(
- message.senderId,
- message.selfSend,
- conversationType,
- conversationTargetId,
- fallbackName
- )
- }
- if (isFriendChatTip(message.type)) {
- return resolveFriendNotificationText(message)
- }
- if (isGroupNotification(message.type)) {
- return resolveGroupNotificationText(message, (id) =>
- getSenderDisplayName(id, ImConversationType.GROUP, message.targetId ?? 0)
- )
- }
- if (isRtcCallTip(message.type)) {
- return resolveRtcCallLastContent(message, conversationType)
- }
- return summarizeMessageContent(message)
-}
diff --git a/src/views/im/utils/db.ts b/src/views/im/utils/db.ts
deleted file mode 100644
index aabf4e9..0000000
--- a/src/views/im/utils/db.ts
+++ /dev/null
@@ -1,541 +0,0 @@
-import type { MessageDO, SettingDO } from '../home/types'
-
-import { toRaw } from 'vue'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { ImConversationType } from './constants'
-
-export const DB_SCHEMA_VERSION = 2
-
-export type DbStoreName =
- | 'channels'
- | 'conversationReads'
- | 'conversations'
- | 'friendRequests'
- | 'friends'
- | 'groupMembers'
- | 'groupRequests'
- | 'groups'
- | 'messages'
- | 'settings'
-
-export type DbTransaction = IDBTransaction
-
-/** IM 鏈湴瀛樺偍 key */
-export const StorageKeys = {
- localStorage: {
- /** 渚ц竟鏍忓搴︼紝涓変釜 Tab 鍏辩敤涓�浠借蹇� */
- asideWidth: 'im:aside',
- /** 浼氳瘽鍒楄〃缃《鎶樺彔灞曞紑鎬� */
- conversationPinnedExpanded: 'im:conversation:pinnedExpanded'
- },
- settings: {
- /** 绉佽亰娑堟伅鎷夊彇娓告爣 */
- privateMessageMaxId: 'privateMessageMaxId',
- /** 缇よ亰娑堟伅鎷夊彇娓告爣 */
- groupMessageMaxId: 'groupMessageMaxId',
- /** 棰戦亾娑堟伅鎷夊彇娓告爣 */
- channelMessageMaxId: 'channelMessageMaxId',
- /** 鏈�杩戣浆鍙戜細璇� key 鍒楄〃 */
- recentForwardConversationKeys: 'recentForwardConversationKeys',
- // 鐘舵�佷簨浠惰ˉ鍋垮閲忔媺鍙栨父鏍囷細涓庝笂闈㈡秷鎭� maxId 娓告爣鍏辩敤鍚屼竴 settings keyspace锛岀粺涓�鐧昏鍦ㄦ閬垮厤鎾� key锛�
- // 璧� update_time + id 澶嶅悎娓告爣锛堥潪鍗曟潯 maxId锛夛紝鏁呯敤 PullCursor 鍚庣紑鍖哄垎璇箟
- /** 濂藉弸鍏崇郴澧為噺鎷夊彇娓告爣 */
- friendPullCursor: 'friendPullCursor',
- /** 濂藉弸鐢宠澧為噺鎷夊彇娓告爣 */
- friendRequestPullCursor: 'friendRequestPullCursor',
- /** 鍔犵兢鐢宠澧為噺鎷夊彇娓告爣 */
- groupRequestPullCursor: 'groupRequestPullCursor',
- /** 浼氳瘽璇讳綅缃閲忔媺鍙栨父鏍� */
- conversationReadPullCursor: 'conversationReadPullCursor'
- }
-} as const
-
-let currentDb: IDBDatabase | null = null
-let currentUserId: null | number = null
-let currentSession = 0
-
-/** 鏍¢獙褰撳墠 IM IndexedDB session 浠嶆湁鏁� */
-export function isCurrentDbSession(session: number): boolean {
- return session === currentSession
-}
-
-/** 鑾峰彇褰撳墠 IM IndexedDB session */
-export function getDbSession(): number {
- return currentSession
-}
-
-/** 鎷兼帴褰撳墠鐢ㄦ埛 IM DB 鍚嶇О */
-function getDbName(userId: number): string {
- return `im:${userId}`
-}
-
-/** 鍖呰 IndexedDB request */
-function requestToPromise<T = unknown>(request: IDBRequest<T>): Promise<T> {
- return new Promise((resolve, reject) => {
- request.addEventListener('success', () => resolve(request.result))
- request.addEventListener('error', () => reject(request.error))
- })
-}
-
-/** 绛夊緟浜嬪姟瀹屾垚 */
-function transactionDone(transaction: DbTransaction): Promise<void> {
- return new Promise((resolve, reject) => {
- transaction.addEventListener('complete', () => resolve())
- transaction.addEventListener('error', () => reject(transaction.error))
- transaction.addEventListener('abort', () => reject(transaction.error))
- })
-}
-
-/** 鍒涘缓绱㈠紩 */
-function createIndex(
- store: IDBObjectStore,
- name: string,
- keyPath: string | string[],
- options?: IDBIndexParameters
-) {
- if (!store.indexNames.contains(name)) {
- store.createIndex(name, keyPath, options)
- }
-}
-
-/** 鍒濆鍖� schema */
-function upgradeSchema(db: IDBDatabase) {
- if (!db.objectStoreNames.contains('conversations')) {
- const store = db.createObjectStore('conversations', { keyPath: 'clientConversationId' })
- createIndex(store, 'lastSendTime', 'lastSendTime')
- }
- if (!db.objectStoreNames.contains('conversationReads')) {
- const store = db.createObjectStore('conversationReads', { keyPath: 'clientConversationId' })
- createIndex(store, 'conversationType+targetId', ['conversationType', 'targetId'], {
- unique: true
- })
- }
- if (!db.objectStoreNames.contains('messages')) {
- const store = db.createObjectStore('messages', { keyPath: 'messageKey' })
- createIndex(store, 'clientConversationId', 'clientConversationId')
- createIndex(store, 'clientConversationId+sendTime', ['clientConversationId', 'sendTime'])
- createIndex(store, 'clientMessageId', 'clientMessageId', { unique: true })
- }
- if (!db.objectStoreNames.contains('friends')) {
- const store = db.createObjectStore('friends', { keyPath: 'id' })
- createIndex(store, 'friendUserId', 'friendUserId', { unique: true })
- createIndex(store, 'status', 'status')
- }
- if (!db.objectStoreNames.contains('friendRequests')) {
- const store = db.createObjectStore('friendRequests', { keyPath: 'id' })
- createIndex(store, 'status', 'status')
- createIndex(store, 'createTime', 'createTime')
- }
- if (!db.objectStoreNames.contains('groups')) {
- const store = db.createObjectStore('groups', { keyPath: 'id' })
- createIndex(store, 'name', 'name')
- createIndex(store, 'status', 'status')
- }
- if (!db.objectStoreNames.contains('groupMembers')) {
- const store = db.createObjectStore('groupMembers', { keyPath: 'id' })
- createIndex(store, 'groupId', 'groupId')
- createIndex(store, 'groupId+userId', ['groupId', 'userId'], { unique: true })
- }
- if (!db.objectStoreNames.contains('groupRequests')) {
- const store = db.createObjectStore('groupRequests', { keyPath: 'id' })
- createIndex(store, 'status', 'status')
- createIndex(store, 'createTime', 'createTime')
- }
- if (!db.objectStoreNames.contains('channels')) {
- const store = db.createObjectStore('channels', { keyPath: 'id' })
- createIndex(store, 'status', 'status')
- createIndex(store, 'sort', 'sort')
- }
- if (!db.objectStoreNames.contains('settings')) {
- db.createObjectStore('settings', { keyPath: 'key' })
- }
-}
-
-/** 鎵撳紑 IM IndexedDB */
-function openDb(name: string): Promise<IDBDatabase> {
- return new Promise((resolve, reject) => {
- const request = indexedDB.open(name, DB_SCHEMA_VERSION)
- // 鍒涘缓鎴栧崌绾у璞′粨搴�
- request.addEventListener('upgradeneeded', () => upgradeSchema(request.result))
- // 杩斿洖鍙鐢ㄨ繛鎺�
- request.addEventListener('success', () => resolve(request.result))
- request.addEventListener('error', () => reject(request.error))
- })
-}
-
-/** 鍒濆鍖栧綋鍓嶇敤鎴� IM DB */
-export async function initDb(): Promise<void> {
- const userId = getCurrentUserId()
- if (!Number.isFinite(userId) || userId <= 0) {
- throw new Error('褰撳墠鐢ㄦ埛涓嶅瓨鍦紝鏃犳硶鍒濆鍖� IM DB')
- }
- if (currentDb && currentUserId === userId) {
- return
- }
- currentDb?.close()
- currentSession++
- currentUserId = userId
- currentDb = await openDb(getDbName(userId))
-}
-
-/** 鍏抽棴褰撳墠 IM DB 杩炴帴 */
-function closeDbConnection() {
- currentDb?.close()
- currentDb = null
- currentUserId = null
-}
-
-/** 鑾峰彇褰撳墠 IM DB */
-function getRawDb(): IDBDatabase {
- if (!currentDb) {
- throw new Error('IM DB 鏈垵濮嬪寲')
- }
- return currentDb
-}
-
-/** 鏍¢獙鍗曟鍐欏叆 session */
-function guardSession(session: number) {
- if (!isCurrentDbSession(session)) {
- throw new Error('IM DB session 宸插け鏁�')
- }
-}
-
-/** 鍏嬮殕鍙叆搴撳璞� */
-function toDbValue<T>(value: T): T {
- return cloneDbValue(value) as T
-}
-
-/** 杞崲涓� IndexedDB 鍙厠闅嗗璞� */
-function cloneDbValue(value: unknown): unknown {
- const raw = toRaw(value)
- if (Array.isArray(raw)) {
- return raw.map((item) => cloneDbValue(item))
- }
- if (!raw || typeof raw !== 'object') {
- return raw
- }
- const prototype = Object.getPrototypeOf(raw)
- if (prototype !== Object.prototype && prototype !== null) {
- return raw
- }
- return Object.fromEntries(
- Object.entries(raw as Record<string, unknown>).map(([key, item]) => [key, cloneDbValue(item)])
- )
-}
-
-class DbClient {
- /** 娓呯┖ store 璁板綍 */
- async clearStore(storeName: DbStoreName, tx?: DbTransaction): Promise<void> {
- if (tx) {
- await requestToPromise(tx.objectStore(storeName).clear())
- return
- }
- await this.transaction([storeName], 'readwrite', (tx) => this.clearStore(storeName, tx))
- }
-
- /** 鍒犻櫎璁板綍 */
- async delete(storeName: DbStoreName, key: IDBValidKey, tx?: DbTransaction): Promise<void> {
- if (tx) {
- await requestToPromise(tx.objectStore(storeName).delete(key))
- return
- }
- await this.transaction([storeName], 'readwrite', (tx) => this.delete(storeName, key, tx))
- }
-
- /** 鎸夌储寮曞垹闄よ褰� */
- async deleteByIndex(
- storeName: DbStoreName,
- indexName: string,
- query: IDBKeyRange | IDBValidKey,
- tx?: DbTransaction
- ): Promise<void> {
- if (!tx) {
- await this.transaction([storeName], 'readwrite', (tx) =>
- this.deleteByIndex(storeName, indexName, query, tx)
- )
- return
- }
- const index = tx.objectStore(storeName).index(indexName)
- await new Promise<void>((resolve, reject) => {
- const request = index.openCursor(query)
- request.addEventListener('error', () => reject(request.error))
- request.addEventListener('success', () => {
- const cursor = request.result
- if (!cursor) {
- resolve()
- return
- }
- cursor.delete()
- cursor.continue()
- })
- })
- }
-
- /** 鑾峰彇鍗曟潯璁板綍 */
- async get<T>(
- storeName: DbStoreName,
- key: IDBValidKey,
- tx?: DbTransaction
- ): Promise<T | undefined> {
- if (tx) {
- return requestToPromise<T | undefined>(tx.objectStore(storeName).get(key))
- }
- return this.transaction<T | undefined>([storeName], 'readonly', (tx) =>
- this.get<T>(storeName, key, tx)
- )
- }
-
- /** 鑾峰彇 store 鍏ㄩ噺璁板綍 */
- async getAll<T>(storeName: DbStoreName, tx?: DbTransaction): Promise<T[]> {
- if (tx) {
- return requestToPromise<T[]>(tx.objectStore(storeName).getAll())
- }
- return this.transaction<T[]>([storeName], 'readonly', (tx) => this.getAll<T>(storeName, tx))
- }
-
- /** 鎸夌储寮曡幏鍙栬褰曞垪琛� */
- async getAllByIndex<T>(
- storeName: DbStoreName,
- indexName: string,
- query?: IDBKeyRange | IDBValidKey,
- tx?: DbTransaction
- ): Promise<T[]> {
- if (tx) {
- return requestToPromise<T[]>(tx.objectStore(storeName).index(indexName).getAll(query))
- }
- return this.transaction<T[]>([storeName], 'readonly', (tx) =>
- this.getAllByIndex<T>(storeName, indexName, query, tx)
- )
- }
-
- /** 鎸夊敮涓�绱㈠紩鑾峰彇鍗曟潯璁板綍 */
- async getByIndex<T>(
- storeName: DbStoreName,
- indexName: string,
- query: IDBKeyRange | IDBValidKey,
- tx?: DbTransaction
- ): Promise<T | undefined> {
- if (tx) {
- return requestToPromise<T | undefined>(tx.objectStore(storeName).index(indexName).get(query))
- }
- return this.transaction<T | undefined>([storeName], 'readonly', (tx) =>
- this.getByIndex<T>(storeName, indexName, query, tx)
- )
- }
-
- /** 鎸変細璇濆垎椤佃幏鍙栨秷鎭� */
- async getMessageListByConversation(
- clientConversationId: string,
- options?: { beforeSendTime?: number; limit?: number },
- tx?: DbTransaction
- ): Promise<MessageDO[]> {
- const limit = options?.limit ?? 50
- const upper = options?.beforeSendTime ?? Number.MAX_SAFE_INTEGER
- const range = IDBKeyRange.bound(
- [clientConversationId, 0],
- [clientConversationId, upper],
- false,
- true
- )
- const read = async (tx: DbTransaction): Promise<MessageDO[]> => {
- const index = tx.objectStore('messages').index('clientConversationId+sendTime')
- const out: MessageDO[] = []
- await new Promise<void>((resolve, reject) => {
- // 浠庢柊鍒版棫璇诲彇涓�椤�
- const request = index.openCursor(range, 'prev')
- request.addEventListener('error', () => reject(request.error))
- request.addEventListener('success', () => {
- const cursor = request.result
- if (!cursor || out.length >= limit) {
- resolve()
- return
- }
- out.push(cursor.value as MessageDO)
- cursor.continue()
- })
- })
- // 姘旀场娓叉煋闇�瑕佹寜鏃堕棿鍗囧簭
- return out.toReversed()
- }
- if (tx) {
- return read(tx)
- }
- return this.transaction<MessageDO[]>(['messages'], 'readonly', read)
- }
-
- /** 璇诲彇璁剧疆 */
- async getSetting<T>(key: string, tx?: DbTransaction): Promise<T | undefined> {
- const item = await this.get<SettingDO<T>>('settings', key, tx)
- return item?.value
- }
-
- /** 鍐欏叆璁板綍 */
- async put<T>(storeName: DbStoreName, value: T, tx?: DbTransaction): Promise<void> {
- if (tx) {
- await requestToPromise(tx.objectStore(storeName).put(toDbValue(value)))
- return
- }
- await this.transaction([storeName], 'readwrite', (tx) => this.put(storeName, value, tx))
- }
-
- /** 鍐欏叆璁剧疆 */
- async setSetting<T>(key: string, value: T, tx?: DbTransaction): Promise<void> {
- await this.put<SettingDO<T>>('settings', { key, value, updateTime: Date.now() }, tx)
- }
-
- /** 鎵ц浜嬪姟 */
- async transaction<T>(
- storeNames: DbStoreName[],
- mode: IDBTransactionMode,
- runner: (tx: DbTransaction) => Promise<T>
- ): Promise<T> {
- // 寮�鍚簨鍔″墠鏍¢獙 session
- const session = getDbSession()
- guardSession(session)
- const tx = getRawDb().transaction(storeNames, mode)
- const done = transactionDone(tx)
- let result: T
- try {
- // 浜嬪姟鍐呭彧鎵ц IndexedDB request 閾�
- result = await runner(tx)
- } catch (error) {
- try {
- tx.abort()
- } catch {}
- await done.catch(() => undefined)
- throw error
- }
- // commit 鍚庡啀娆℃牎楠� session
- await done
- guardSession(session)
- return result
- }
-}
-
-const dbClient = new DbClient()
-
-/** 鑾峰彇褰撳墠 IM DB client */
-export function getDb(): DbClient {
- return dbClient
-}
-
-/** 褰撳墠鐢ㄦ埛浼氳瘽涓婚敭 */
-export function getClientConversationId(type: number, targetId: number): string {
- return `${type}:${targetId}`
-}
-
-/** 瑙f瀽褰撳墠鐢ㄦ埛浼氳瘽涓婚敭 */
-export function parseClientConversationId(
- clientConversationId: string
-): null | { targetId: number; type: number; } {
- const [typeText, targetIdText] = clientConversationId.split(':')
- const type = Number(typeText)
- const targetId = Number(targetIdText)
- if (!Number.isFinite(type) || !Number.isFinite(targetId) || targetId <= 0) {
- return null
- }
- return { type, targetId }
-}
-
-/** 鏈嶅姟绔秷鎭富閿� */
-export function getServerMessageKey(conversationType: number, id: number): string {
- return `${conversationType}:${id}`
-}
-
-/** 瀹㈡埛绔复鏃舵秷鎭富閿� */
-export function getClientMessageKey(clientMessageId: string): string {
- return `client:${clientMessageId}`
-}
-
-/** 瑙f瀽鏈湴娑堟伅涓婚敭 */
-export function parseMessageKey(
- messageKey: string
-):
- | null
- | { clientMessageId: string; kind: 'client'; }
- | { conversationType: number; id: number; kind: 'server'; } {
- if (!messageKey) {
- return null
- }
- if (messageKey.startsWith('client:')) {
- const clientMessageId = messageKey.slice('client:'.length)
- return clientMessageId ? { kind: 'client', clientMessageId } : null
- }
- const [conversationTypeText, idText] = messageKey.split(':')
- const conversationType = Number(conversationTypeText)
- const id = Number(idText)
- if (!Number.isFinite(conversationType) || !Number.isFinite(id) || id <= 0) {
- return null
- }
- return { kind: 'server', conversationType, id }
-}
-
-/** 鏇存柊娑堟伅鎷夊彇娓告爣 */
-export async function setMessageMaxId(
- conversationType: number,
- maxId: number | undefined,
- tx?: DbTransaction
-): Promise<void> {
- if (!maxId) {
- return
- }
- let key: string
- switch (conversationType) {
- case ImConversationType.CHANNEL: {
- key = StorageKeys.settings.channelMessageMaxId
- break
- }
- case ImConversationType.GROUP: {
- key = StorageKeys.settings.groupMessageMaxId
- break
- }
- case ImConversationType.PRIVATE: {
- key = StorageKeys.settings.privateMessageMaxId
- break
- }
- default: {
- throw new Error(`鏈煡 IM 浼氳瘽绫诲瀷锛�${conversationType}`)
- }
- }
- const db = getDb()
- const current = (await db.getSetting<number>(key, tx)) || 0
- if (maxId > current) {
- await db.setSetting(key, maxId, tx)
- }
-}
-
-/** 鍋滄褰撳墠 IM DB session */
-export async function stopRequests(): Promise<void> {
- currentSession++
- const [
- { useMessageStoreWithOut },
- { useConversationStoreWithOut },
- { useFriendStoreWithOut },
- { useGroupStoreWithOut },
- { useChannelStoreWithOut },
- { useGroupRequestStoreWithOut },
- { useFaceStoreWithOut },
- { useRtcStore }
- ] = await Promise.all([
- import('../home/store/messageStore'),
- import('../home/store/conversationStore'),
- import('../home/store/friendStore'),
- import('../home/store/groupStore'),
- import('../home/store/channelStore'),
- import('../home/store/groupRequestStore'),
- import('../home/store/faceStore'),
- import('../home/store/rtcStore')
- ])
- useMessageStoreWithOut().clear()
- useConversationStoreWithOut().clear()
- useFriendStoreWithOut().clear()
- useGroupStoreWithOut().clear()
- useChannelStoreWithOut().clear()
- useGroupRequestStoreWithOut().clear()
- useFaceStoreWithOut().clear()
- useRtcStore().reset()
- useRtcStore().clearGroupCallCache()
- closeDbConnection()
-}
diff --git a/src/views/im/utils/emoji.ts b/src/views/im/utils/emoji.ts
deleted file mode 100644
index 2a25315..0000000
--- a/src/views/im/utils/emoji.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * IM 甯哥敤 Unicode emoji 鍒楄〃
- *
- * 鎵�瑙佸嵆鎵�寰楋紙涓嶄緷璧栧浘鐗囪祫婧愶級锛岀敱琛ㄦ儏闈㈡澘锛團acePicker 鐨� emoji tab锛変娇鐢紱
- * Unicode emoji 閫変腑鍚庣敱璋冪敤鏂规彃鍏ュ埌杈撳叆妗嗚蛋 TEXT 閫氶亾锛屼笉璧� FACE 鍐呭绫诲瀷
- */
-export const IM_EMOJI_LIST: string[] = [
- '馃榾', '馃榿', '馃槀', '馃ぃ', '馃槂', '馃槃', '馃槄', '馃槅', '馃槈', '馃槉',
- '馃構', '馃槑', '馃槏', '馃槝', '馃槜', '馃槞', '馃槡', '馃檪', '馃', '馃ぉ',
- '馃', '馃え', '馃槓', '馃槕', '馃樁', '馃檮', '馃槒', '馃槪', '馃槬', '馃槷',
- '馃', '馃槸', '馃槳', '馃槴', '馃槾', '馃槍', '馃槢', '馃槣', '馃槤', '馃い',
- '馃槖', '馃槗', '馃様', '馃槙', '馃檭', '馃', '馃槻', '鈽癸笍', '馃檨', '馃槚',
- '馃槥', '馃槦', '馃槫', '馃槩', '馃槶', '馃槮', '馃槯', '馃槰', '馃槱', '馃く',
- '馃槵', '馃槹', '馃槺', '馃サ', '馃ザ', '馃槼', '馃お', '馃樀', '馃槨', '馃槧',
- '馃が', '馃樂', '馃', '馃', '馃あ', '馃ぎ', '馃ぇ', '馃槆', '馃', '馃コ',
- '馃憤', '馃憥', '馃憣', '鉁岋笍', '馃', '馃', '馃', '馃', '馃憟', '馃憠',
- '馃憜', '馃憞', '鉁�', '馃', '馃枑', '馃枛', '馃憢', '馃', '馃檹', '馃挭',
- '鉂わ笍', '馃А', '馃挍', '馃挌', '馃挋', '馃挏', '馃枻', '馃挃', '馃挄', '馃挅',
- '馃帀', '馃帄', '馃巵', '馃巶', '馃嵃', '馃尮', '馃尫', '馃尭', '馃幍', '馃幎',
- '猸�', '馃専', '鉁�', '馃挮', '馃敟', '馃挴', '鉁�', '鉂�', '鈿狅笍', '鉂�'
-]
diff --git a/src/views/im/utils/group.ts b/src/views/im/utils/group.ts
deleted file mode 100644
index ddc53fa..0000000
--- a/src/views/im/utils/group.ts
+++ /dev/null
@@ -1,289 +0,0 @@
-import type { FriendLite } from '../home/types'
-
-import { loadImage } from './image'
-import { getAvatarBgColor, getAvatarText } from './user'
-
-/** 榛樿缇ゅ悕鐢熸垚锛氭墍閫夊ソ鍙嬪墠 4 涓悕瀛楁嫾鎺ワ紝瓒呰繃琛ャ�岀瓑 N 浜恒�嶏紱涓虹┖鍏滃簳銆岀兢鑱娿�� */
-export function buildDefaultGroupName(members: FriendLite[]): string {
- if (members.length === 0) {
- return '缇よ亰'
- }
- const names = members.slice(0, 4).map((m) => m.displayName || m.nickname || '')
- const head = names.filter(Boolean).join('銆�')
- if (members.length > 4) {
- // members 鍙惈琚�夊ソ鍙嬶紝+1 鎶婂垱寤鸿�呬篃璁″叆瀹為檯鎴愬憳鏁�
- return `${head}绛�${members.length + 1}浜篳
- }
- return head || '缇よ亰'
-}
-
-/** 缇ゅご鍍忓崟鏍肩殑杈撳叆锛氬ご鍍� URL + 鍚嶅瓧 锛岃嚦灏戠粰涓�涓紝浜岃�呴兘缂烘椂璧扮伆搴曠┖鏍� */
-export interface GroupAvatarMember {
- /** 澶村儚 URL锛涚己澶辨垨鍔犺浇澶辫触鏃舵寜 name 鐢昏壊鍗� */
- avatar?: string
- /** 鏄剧ず鍚嶏紙鏄电О / 澶囨敞锛夛紱鑹插崱鏂囧瓧 + 搴曡壊 hash 鏉ユ簮 */
- name?: string
-}
-
-/** 缇ゅご鍍忔嫾鎺ョ殑鍙�夊弬鏁� */
-export interface BuildGroupAvatarOptions {
- /** 杈撳嚭鐢诲竷杈归暱锛堝儚绱狅級锛涢粯璁� 64 */
- targetSize?: number
- /** 鍗曟牸涔嬮棿鐨勯棿闅旓紙鍍忕礌锛夛紱榛樿 1 */
- divider?: number
- /** 鐢诲竷搴曡壊锛涢粯璁ら�忔槑锛岃涓婁笅鐣欑櫧閫忓嚭瀹夸富瀹瑰櫒搴曡壊 */
- background?: string
-}
-
-/** 鍗曟牸鍦ㄧ敾甯冧笂鐨勪綅缃� + 灏哄 */
-interface CellRect {
- x: number
- y: number
- w: number
- h: number
-}
-
-/** 鍗曟牸鐨勬渶缁堢粯鍒跺唴瀹� */
-type Cell =
- | { bg: string; kind: 'color'; text: string; }
- | { img: HTMLImageElement; kind: 'image'; }
-
-/**
- * 鎶婄兢鎴愬憳澶村儚鎷兼垚涓�寮犳柟褰㈢兢澶村儚 dataURL锛屾寜 1 ~ 9 寮犺蛋涔濆鏍煎彉浣撳竷灞�
- *
- * - 浠呭彇鍓� 9 涓垚鍛�
- * - 鍗曟牸 avatar 涓虹┖ / 鍔犺浇澶辫触锛氭寜 name 鍦� canvas 涓婄敾鑹插崱鍏滃簳
- * - 璺ㄥ煙瑕佹眰鍥剧墖婧愬紑鍚� CORS锛坕mg.crossOrigin = 'anonymous'锛�
- */
-export async function buildGroupAvatar(
- members: GroupAvatarMember[],
- options: BuildGroupAvatarOptions = {}
-): Promise<string> {
- const targetSize = options.targetSize ?? 64
- const divider = options.divider ?? 1
- const background = options.background
-
- const top = members.slice(0, 9)
- if (top.length === 0) {
- return ''
- }
-
- const cells = await Promise.all(top.map((m) => resolveCell(m)))
- const rects = computeCellRects(cells.length, targetSize, divider)
-
- const canvas = document.createElement('canvas')
- canvas.width = targetSize
- canvas.height = targetSize
- const ctx = canvas.getContext('2d')
- if (!ctx) {
- return ''
- }
- if (background) {
- ctx.fillStyle = background
- ctx.fillRect(0, 0, targetSize, targetSize)
- }
- cells.forEach((cell, idx) => {
- const rect = rects[idx]
- if (!rect) {
- return
- }
- drawCell(ctx, rect, cell)
- })
- return canvas.toDataURL('image/png')
-}
-
-/** 缇ゅご鍍� dataURL 缂撳瓨涓婇檺锛氭瘡鏉$害 5 ~ 30KB锛�200 鏉¤蒋灏侀《绾� 5MB 甯搁┗ */
-const MERGED_AVATAR_CACHE_MAX = 200
-const mergedAvatarCache = new Map<string, string>()
-
-/** 鍙栫兢澶村儚 dataURL 缂撳瓨锛涘懡涓椂鎸� LRU 鎻愬埌鏈熬 */
-export function getCachedGroupAvatar(key: string): string | undefined {
- const cached = mergedAvatarCache.get(key)
- if (cached === undefined) {
- return undefined
- }
- mergedAvatarCache.delete(key)
- mergedAvatarCache.set(key, cached)
- return cached
-}
-
-/** 鍐欑兢澶村儚 dataURL 缂撳瓨锛涜秴涓婇檺鏃朵涪寮冩渶鏃╀竴鏉★紙Map 杩唬椤哄簭 = 鎻掑叆椤哄簭锛岄厤鍚� get 鐨勬彁鍗囧嵆 LRU锛� */
-export function setCachedGroupAvatar(key: string, value: string): void {
- if (mergedAvatarCache.has(key)) {
- mergedAvatarCache.delete(key)
- } else if (mergedAvatarCache.size >= MERGED_AVATAR_CACHE_MAX) {
- const oldest = mergedAvatarCache.keys().next().value
- if (oldest !== undefined) {
- mergedAvatarCache.delete(oldest)
- }
- }
- mergedAvatarCache.set(key, value)
-}
-
-/** 鍗曟垚鍛� 鈫� Cell锛氭湁 avatar 涓斿姞杞芥垚鍔熻蛋 image锛屽惁鍒欒蛋 color */
-async function resolveCell(member: GroupAvatarMember): Promise<Cell> {
- if (member.avatar) {
- const img = await loadImage(member.avatar)
- if (img) {
- return { kind: 'image', img }
- }
- }
- return {
- kind: 'color',
- text: getAvatarText(member.name),
- bg: getAvatarBgColor(member.name)
- }
-}
-
-/** 鎶婂崟涓� Cell 鐢诲埌 ctx 涓婃寚瀹氭牸瀛愰噷锛沬mage 璧� cover 瑁佸壀淇濇瘮渚嬶紝color 璧� fillRect + 灞呬腑鏂囧瓧 */
-function drawCell(
- ctx: CanvasRenderingContext2D,
- rect: CellRect,
- cell: Cell
-): void {
- const { x, y, w, h } = rect
- if (cell.kind === 'image') {
- drawImageCover(ctx, cell.img, x, y, w, h)
- return
- }
- ctx.fillStyle = cell.bg
- ctx.fillRect(x, y, w, h)
- if (!cell.text) {
- return
- }
- // 瀛楀彿鎸夌煭杈圭畻锛涘崟瀛� 0.42銆佸弻瀛� 0.34
- const baseSize = Math.min(w, h)
- const fontSize = Math.floor(baseSize * (cell.text.length > 1 ? 0.34 : 0.42))
- ctx.fillStyle = '#FFFFFF'
- ctx.font = `500 ${fontSize}px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif`
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.fillText(cell.text, x + w / 2, y + h / 2)
-}
-
-/** cover 瑁佸壀锛氫粠婧愬浘涓績瑁佸嚭涓庣洰鏍囩煩褰㈠悓姣斾緥鐨勫瓙鍖哄煙锛岀敾鍒扮洰鏍囩煩褰紙淇濇寔姣斾緥涓嶅彉褰級 */
-function drawImageCover(
- ctx: CanvasRenderingContext2D,
- img: HTMLImageElement,
- dx: number,
- dy: number,
- dw: number,
- dh: number
-): void {
- const srcAspect = img.width / img.height
- const dstAspect = dw / dh
- let sx = 0
- let sy = 0
- let sw = img.width
- let sh = img.height
- if (srcAspect > dstAspect) {
- sw = sh * dstAspect
- sx = (img.width - sw) / 2
- } else if (srcAspect < dstAspect) {
- sh = sw / dstAspect
- sy = (img.height - sh) / 2
- }
- ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)
-}
-
-/**
- * 鎸夊紶鏁拌绠楁瘡鏍肩殑浣嶇疆 + 灏哄
- *
- * - 1 寮狅細鍗犳弧鐢诲竷
- * - 2~4 寮狅細2 鍒� 2 琛屾鏂瑰舰鍗曟牸
- * - 5~6 寮狅細3 鍒� 2 琛屾鏂瑰舰鍗曟牸 + 鍨傜洿灞呬腑
- * - 7~9 寮狅細3 鍒� 3 琛屾鏂瑰舰鍗曟牸
- */
-function computeCellRects(count: number, target: number, divider: number): CellRect[] {
- if (count <= 0) {
- return []
- }
- if (count === 1) {
- return [{ x: 0, y: 0, w: target, h: target }]
- }
- if (count <= 4) {
- return computeCellRectsSmall(count, target, divider)
- }
- if (count <= 6) {
- return computeCellRectsMedium(count, target, divider)
- }
- return computeCellRectsLarge(count, target, divider)
-}
-
-/** 2~4 寮狅細2 鍒� 2 琛屾鏂瑰舰 */
-function computeCellRectsSmall(count: number, target: number, divider: number): CellRect[] {
- const s = (target - 3 * divider) / 2
- const step = s + divider
- const rects: CellRect[] = []
- switch (count) {
- case 2: {
- // 灞呬腑 1 琛岋紙鐣欎笂涓嬬┖鐧斤級
- const y = target / 4
- rects.push({ x: divider, y, w: s, h: s }, { x: divider + step, y, w: s, h: s })
- break
- }
- case 3: {
- // 涓� 1 灞呬腑 + 涓� 2
- rects.push({ x: target / 4, y: divider, w: s, h: s })
- const y1 = divider + step
- rects.push({ x: divider, y: y1, w: s, h: s }, { x: divider + step, y: y1, w: s, h: s })
- break
- }
- case 4: {
- // 2脳2 婊¢摵
- rects.push({ x: divider, y: divider, w: s, h: s }, { x: divider + step, y: divider, w: s, h: s })
- const y1 = divider + step
- rects.push({ x: divider, y: y1, w: s, h: s }, { x: divider + step, y: y1, w: s, h: s })
- break
- }
- }
- return rects
-}
-
-/** 5~6 寮狅細3 鍒� 2 琛屾鏂瑰舰鍗曟牸 + 鍨傜洿灞呬腑锛涗笂涓嬬暀鐧界敱閫忔槑鑳屾櫙閫忓嚭 GroupAvatar 瀹瑰櫒搴曡壊 */
-function computeCellRectsMedium(count: number, target: number, divider: number): CellRect[] {
- const s = (target - 4 * divider) / 3
- const step = s + divider
- const xs = [divider, divider + step, divider + 2 * step] as const
- // 2 琛岄珮 + 1 琛岄棿 div
- const totalH = 2 * s + divider
- const y0 = (target - totalH) / 2
- const y1 = y0 + step
- const rects: CellRect[] = []
- if (count === 5) {
- // 涓� 2 灞呬腑锛堝乏鍙冲绉扮暀鐧斤級 + 涓� 3 宸﹀彸璐磋竟
- const upX0 = (target - 2 * s - divider) / 2
- rects.push({ x: upX0, y: y0, w: s, h: s }, { x: upX0 + step, y: y0, w: s, h: s }, { x: xs[0], y: y1, w: s, h: s }, { x: xs[1], y: y1, w: s, h: s }, { x: xs[2], y: y1, w: s, h: s })
- return rects
- }
- // count === 6锛氫笂 3 + 涓� 3 婊¢摵
- rects.push({ x: xs[0], y: y0, w: s, h: s }, { x: xs[1], y: y0, w: s, h: s }, { x: xs[2], y: y0, w: s, h: s }, { x: xs[0], y: y1, w: s, h: s }, { x: xs[1], y: y1, w: s, h: s }, { x: xs[2], y: y1, w: s, h: s })
- return rects
-}
-
-/** 7~9 寮狅細3 鍒� 3 琛屾鏂瑰舰鍗曟牸 */
-function computeCellRectsLarge(count: number, target: number, divider: number): CellRect[] {
- const s = (target - 4 * divider) / 3
- const step = s + divider
- const xs = [divider, divider + step, divider + 2 * step] as const
- const ys = [divider, divider + step, divider + 2 * step] as const
- const rects: CellRect[] = []
- if (count === 7) {
- // 涓� 1 灞呬腑 + 涓� 3 + 涓� 3
- rects.push({ x: xs[1], y: ys[0], w: s, h: s }, { x: xs[0], y: ys[1], w: s, h: s }, { x: xs[1], y: ys[1], w: s, h: s }, { x: xs[2], y: ys[1], w: s, h: s }, { x: xs[0], y: ys[2], w: s, h: s }, { x: xs[1], y: ys[2], w: s, h: s }, { x: xs[2], y: ys[2], w: s, h: s })
- return rects
- }
- if (count === 8) {
- // 涓� 2 灞呬腑 + 涓� 3 + 涓� 3
- const upX0 = (target - 2 * s - divider) / 2
- rects.push({ x: upX0, y: ys[0], w: s, h: s }, { x: upX0 + step, y: ys[0], w: s, h: s }, { x: xs[0], y: ys[1], w: s, h: s }, { x: xs[1], y: ys[1], w: s, h: s }, { x: xs[2], y: ys[1], w: s, h: s }, { x: xs[0], y: ys[2], w: s, h: s }, { x: xs[1], y: ys[2], w: s, h: s }, { x: xs[2], y: ys[2], w: s, h: s })
- return rects
- }
- // count === 9锛�3脳3 婊¢摵
- for (const y of ys) {
- for (const x of xs) {
- rects.push({ x, y, w: s, h: s })
- }
- }
- return rects
-}
diff --git a/src/views/im/utils/image.ts b/src/views/im/utils/image.ts
deleted file mode 100644
index 207947d..0000000
--- a/src/views/im/utils/image.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// ====================================================================
-// IM 鍥剧墖宸ュ叿
-// ====================================================================
-// - probeImageSize锛氬姞杞芥湰鍦� File 鎴栬繙绋� URL锛岃鍑� naturalWidth / naturalHeight
-// - loadImage锛氬姞杞借繙绋� URL 鍒� HTMLImageElement锛堝け璐ヨ繑鍥� null锛屼笉鎶涢敊锛夛紝渚� canvas 缁樺埗浣跨敤
-// ====================================================================
-
-/** 榛樿鍗犱綅灏哄锛歱robe 澶辫触 / 瑙g爜寮傚父鏃跺厹搴曪紝閬垮厤 width/height 涓� 0 璁╂秷鎭覆鏌撳鎺� */
-const DEFAULT_FALLBACK_SIZE = { width: 200, height: 200 } as const
-
-/** 鍔犺浇杩滅▼ URL 鍒� HTMLImageElement锛屽け璐ヨ繑鍥� null锛沜anvas 缁樺埗瑕佹眰 crossOrigin=anonymous锛堥粯璁ゅ紑锛� */
-export function loadImage(
- src: string,
- options: { crossOrigin?: 'anonymous' | 'use-credentials' | null } = {}
-): Promise<HTMLImageElement | null> {
- const crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin
- return new Promise((resolve) => {
- const img = new Image()
- if (crossOrigin) {
- img.crossOrigin = crossOrigin
- }
- img.addEventListener('load', () => resolve(img))
- img.addEventListener('error', () => resolve(null))
- img.src = src
- })
-}
-
-/**
- * 鍔犺浇鏈湴 File 鎴栬繙绋� URL锛岃В鍑� naturalWidth / naturalHeight
- *
- * - File锛氬唴閮� createObjectURL + revokeObjectURL锛岄伩鍏嶅唴瀛樼疮绉�
- * - 杩滅▼ URL锛氱洿鎺ヨ蛋 <img>.src 瑙﹀彂娴忚鍣ㄥ姞杞斤紙鍙� CORS 褰卞搷锛涘彧璇诲昂瀵镐笉闇�瑕佺敾 canvas锛岃法鍩熶篃鑳芥嬁鍒帮級
- * - 瑙g爜澶辫触 / 涓嶆槸鍥剧墖锛氳繑鍥� 200脳200 鍏滃簳
- */
-export function probeImageSize(source: File | string): Promise<{ height: number; width: number; }> {
- const isFile = source instanceof File
- const src = isFile ? URL.createObjectURL(source) : source
- return new Promise((resolve) => {
- const img = new Image()
- img.addEventListener('load', () => {
- if (isFile) {
- URL.revokeObjectURL(src)
- }
- resolve({
- width: img.naturalWidth || DEFAULT_FALLBACK_SIZE.width,
- height: img.naturalHeight || DEFAULT_FALLBACK_SIZE.height
- })
- })
- img.addEventListener('error', () => {
- if (isFile) {
- URL.revokeObjectURL(src)
- }
- resolve({ ...DEFAULT_FALLBACK_SIZE })
- })
- img.src = src
- })
-}
diff --git a/src/views/im/utils/message.ts b/src/views/im/utils/message.ts
deleted file mode 100644
index 70aeda3..0000000
--- a/src/views/im/utils/message.ts
+++ /dev/null
@@ -1,1035 +0,0 @@
-import type { Conversation, GroupLite, Message, QuoteMessage, User } from '../home/types'
-
-import { useUserStore } from '../../../packages/stores/src'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { useFriendStore } from '../home/store/friendStore'
-import { useGroupStore } from '../home/store/groupStore'
-import {
- ImContentType,
- ImConversationType,
- type ImConversationTypeValue,
- ImRtcCallEndReason
-} from './constants'
-import { formatCallDuration } from './time'
-
-export type { QuoteMessage } from '../home/types'
-
-// ====================================================================
-// IM 娑堟伅 content 缂栬В鐮� & 灞曠ず宸ュ叿
-// ====================================================================
-// 绾﹀畾锛氭秷鎭殑 content 瀛楁缁熶竴瀛� JSON 瀛楃涓诧紝瀛楁鍚嶃�佺粨鏋勫榻愬悗绔�
-// cn.iocoder.yudao.module.im.service.websocket.notification.message.* 涓嬬殑 DTO銆�
-// 鍚勭被娑堟伅 payload interface 瀛楁瀵归綈鍚庣锛涜В鏋愮粺涓�鐢� parseMessage<T>锛�
-// 搴忓垪鍖栫洿鎺� JSON.stringify(payload)銆�
-// ====================================================================
-
-// ==================== 瀹㈡埛绔� ID ====================
-
-/** 鐢熸垚瀹㈡埛绔秷鎭� ID锛堢函 UUID锛夛紝鐢ㄤ簬鍓嶇鍘婚噸 & ACK 鍥炲啓 */
-export const generateClientMessageId = (): string => {
- if (globalThis.crypto?.randomUUID) {
- return globalThis.crypto.randomUUID()
- }
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replaceAll(/[xy]/g, (char) => {
- const random = Math.trunc(Math.random() * 16)
- const value = char === 'x' ? random : (random & 0x3) | 0x8
- return value.toString(16)
- })
-}
-
-// ==================== 绉佽亰瀵圭 userId ====================
-
-/**
- * 绉佽亰娑堟伅 / DTO 鐨勫绔� userId锛氳嚜宸卞彂鐨勫绔槸 receiver锛屽埆浜哄彂鐨勫绔槸 sender
- *
- * 鏀跺彛 4 澶勬棫 inline锛坵ebsocketStore.convertPrivateMessage / handlePrivateMessage / computeFriendPeerId
- * 鍜� useMessagePuller.getPrivatePeerId锛夛紝缁撴瀯绫诲瀷鍙 senderId / receiverId 涓や釜瀛楁锛孯EST 涓� WS DTO 閮芥弧瓒�
- */
-export function getPrivateMessagePeerId(
- message: { receiverId: number; senderId: number; },
- currentUserId: number
-): number {
- return message.senderId === currentUserId ? message.receiverId : message.senderId
-}
-
-// ==================== 鏂囨湰鐗囨锛坱ip 鏂囨 + TEXT 姘旀场鍏辩敤锛� ====================
-// 鏃㈢敤浜庣伆鏉� tip锛�"XX 閭�璇� YY 鍔犲叆缇よ亰"锛夛紝涔熺敤浜� TEXT 姘旀场姝f枃锛園xxx 楂樹寒 + URL 鑷姩璇嗗埆锛夈��
-// mention 娈垫惡甯� userId 鐢ㄤ簬鎸傜偣鍑诲脊 UserInfoCard锛沴ink 娈垫惡甯� href 鐢ㄤ簬 <a> 璺宠浆锛�
-// text 娈垫槸绾枃鏈厹搴曘�傛覆鏌撳眰锛坱ip-segments.vue锛夋寜 type 鍒嗗彂缁熶竴澶勭悊銆�
-
-export type TipSegment =
- | { href: string; text: string; type: 'link'; }
- | { text: string; type: 'mention'; userId: number; }
- | { text: string; type: 'text'; }
-
-export const tipText = (text: string): TipSegment => ({ type: 'text', text })
-
-export const tipMention = (userId: number, text: string): TipSegment => ({
- type: 'mention',
- userId,
- text
-})
-
-export const tipLink = (href: string, text: string): TipSegment => ({
- type: 'link',
- href,
- text
-})
-
-export const segmentsToText = (segments: TipSegment[]): string =>
- segments.map((s) => s.text).join('')
-
-/** 澶氫釜 userId 鐢ㄥ悓涓�涓垎闅旂鎻掑�兼垚 segments锛屾瘡涓� user 鍗曠嫭鎴� mention 娈� */
-export function joinMentionSegments(
- userIds: number[],
- separator: string,
- resolveName: (userId: number) => string
-): TipSegment[] {
- return userIds.flatMap((id, index) =>
- index === 0
- ? [tipMention(id, resolveName(id))]
- : [tipText(separator), tipMention(id, resolveName(id))]
- )
-}
-
-/** mention 鍊欓�夛紱name 鐢ㄤ簬鍖归厤鏂囨湰瀛楅潰閲忥紝displayName 鐢ㄤ簬瑕嗙洊娓叉煋锛堜笉浼犲垯鎸� name 娓叉煋锛� */
-export interface MentionCandidate {
- userId: number
- /** 鐢ㄦ潵鍦� content 涓墠缂�鍖归厤鐨勫瓧闈㈤噺锛堜笉鍚� @锛� */
- name: string
- /**
- * 娓叉煋鏃跺己鍒朵娇鐢ㄧ殑鏄剧ず鍚嶏紙涓嶅惈 @锛�
- *
- * 鐢ㄤ簬銆屽巻鍙叉秷鎭� content 閲屽啓鐨勬槸澶囨敞鍚� / 缇ゆ樀绉帮紝浣嗘帴鏀剁瑕佹寜鐪熷疄鏄电О娓叉煋銆嶇殑鍦烘櫙锛�
- * 鍊欓�夊彲浠ユ惡甯﹀绉嶅瓧闈㈤噺锛坢atch 鐢級锛屼絾 displayName 缁熶竴鎸囧悜 nickname锛�
- * 璁╂墍鏈夊巻鍙� / 鏂版秷鎭殑 @ 閮芥敹鏁涘埌涓�鑷寸殑灞曠ず
- */
- displayName?: string
- /**
- * 姝т箟鏍囪锛氬悓 name 瀵瑰簲澶氫釜 userId 鏃朵负 true
- *
- * parser 浠嶄細鎸� name 鍛戒腑锛堟渶闀夸紭鍏堬級锛屼絾鍛戒腑鍚庢暣娈靛悆鎴愭櫘閫氭枃鏈�斺��
- * 閬垮厤銆孈寮犱笁銆嶈鍓旈櫎鍚庯紝鐭墠缂�鍊欓�夈�孈寮犮�嶆姠鍖归厤閿欑粦
- */
- ambiguous?: boolean
-}
-
-/** URL 閿氬畾姝e垯锛涚粓姝簬绌虹櫧銆佷腑鏂囥�丂锛堥伩鍏嶅悶鎺変笅涓�涓� mention锛夈��< > " '锛堥槻 HTML / 寮曞彿涓插叆锛夛紱y 鏍囧織璧� sticky 鍖归厤锛岀渷 text.slice */
-const URL_STICKY_REGEX = /(https?:\/\/[^\s涓�-榫<>"']+|www\.[^\s涓�-榫<>"']+)/iy
-
-/** URL 鏈熬甯歌鏍囩偣鍓旈櫎锛岄伩鍏嶅悶鎺夊彞鏈腑鑻辨枃鏍囩偣 */
-const URL_TRAILING_PUNCTUATION = /[.,!?;:)\]銆侊紝銆傦紒锛燂紱锛氾級銆慮+$/
-
-/** 鏈�鐭� URL锛歚www.ab` / `http:/` 杩欑瀛ゆ涓嶇畻閾炬帴 */
-const URL_MIN_LENGTH = 6
-
-/**
- * 鏂囨湰姘旀场 content 鎷嗘锛歮ention 娈垫寜鍊欓�夋渶闀垮墠缂�鍖归厤锛孶RL 娈佃蛋閿氬畾姝e垯锛屽墿浣欏綊 text
- *
- * mentions 涓嶄紶鎴栧尮閰嶄笉涓婃椂锛孈xxx 閫�鍖栦负鏅�� text锛涜В鏋愪笌娓叉煋瑙h�︼紝宸ュ叿鍑芥暟鏈韩涓嶄緷璧� store
- */
-export function parseTextSegments(text: string, mentions: MentionCandidate[] = []): TipSegment[] {
- if (!text) {
- return []
- }
- // 銆孈寮犱笁涓般�嶄笉鑳借銆孈寮犱笁銆嶆埅鑳★紝鍊欓�夋寜 name 闀垮害鍊掑簭
- const sortedMentions =
- mentions.length > 1 ? mentions.toSorted((a, b) => b.name.length - a.name.length) : mentions
- const out: TipSegment[] = []
- let buffer = ''
-
- const flush = () => {
- if (buffer) {
- out.push(tipText(buffer))
- buffer = ''
- }
- }
-
- let i = 0
- while (i < text.length) {
- if (text[i] === '@' && sortedMentions.length > 0) {
- const matched = sortedMentions.find((m) => m.name && text.startsWith(m.name, i + 1))
- if (matched) {
- if (matched.ambiguous) {
- // 鍚屽瓧闈㈤噺瀵瑰簲澶� userId锛屾棤娉曞垽瀹氭剰鍥撅紱鏁存绱叆 buffer 璁╃煭鍓嶇紑鍊欓�夋病鏈轰細鍐嶆壂杩欐
- buffer += '@' + matched.name
- } else {
- flush()
- // 娓叉煋缁熶竴璧� displayName锛堝嵆鐪熷疄鏄电О锛夛紝璁╁巻鍙� content 閲屾畫鐣欑殑澶囨敞 / 缇ゆ樀绉颁篃鏀舵暃鍒� nickname
- out.push(tipMention(matched.userId, '@' + (matched.displayName || matched.name)))
- }
- i += 1 + matched.name.length
- continue
- }
- }
- const head = text[i]
- if (head === 'h' || head === 'H' || head === 'w' || head === 'W') {
- URL_STICKY_REGEX.lastIndex = i
- const linkMatch = URL_STICKY_REGEX.exec(text)
- if (linkMatch) {
- const urlText = linkMatch[0].replace(URL_TRAILING_PUNCTUATION, '')
- if (urlText.length >= URL_MIN_LENGTH) {
- flush()
- const href = /^https?:\/\//i.test(urlText) ? urlText : `https://${urlText}`
- out.push(tipLink(href, urlText))
- i += urlText.length
- continue
- }
- }
- }
- buffer += text[i]
- i += 1
- }
- flush()
- return out
-}
-
-// ==================== 寮曠敤娑堟伅 ====================
-
-/** 寮曠敤瀹瑰櫒锛�5 绉嶆櫘閫氭秷鎭�(TEXT / IMAGE / FILE / VOICE / VIDEO)閮藉彲鎼哄甫 quote */
-interface Quotable {
- quote?: QuoteMessage
-}
-
-// ==================== 娑堟伅 payload ====================
-
-/** 鏂囨湰娑堟伅 payload锛堝榻愬悗绔� TextMessage锛� */
-export interface TextMessage extends Quotable {
- content: string
-}
-
-/** 鍥剧墖娑堟伅 payload锛堝榻愬悗绔� ImageMessage锛� */
-export interface ImageMessage extends Quotable {
- url: string
- /** 缂╃暐鍥� URL */
- thumbnailUrl?: string
- /** 鍥剧墖瀹藉害 */
- width?: number
- /** 鍥剧墖楂樺害 */
- height?: number
- /** 鏂囦欢澶у皬锛堝瓧鑺傦級 */
- size?: number
-}
-
-/** 璇煶娑堟伅 payload锛堝榻愬悗绔� AudioMessage锛汭mContentType 淇濈暀 VOICE 鍛藉悕锛� */
-export interface AudioMessage extends Quotable {
- url: string
- /** 鏃堕暱锛堢锛� */
- duration: number
- /** 鏂囦欢澶у皬锛堝瓧鑺傦級 */
- size?: number
-}
-
-/** 鏂囦欢娑堟伅 payload锛堝榻愬悗绔� FileMessage锛� */
-export interface FileMessage extends Quotable {
- url: string
- name: string
- size: number
- /** MIME 绫诲瀷 */
- type?: string
-}
-
-/** 瑙嗛娑堟伅 payload锛堝榻愬悗绔� VideoMessage锛涙殏鏈帴鍏ユ覆鏌擄級 */
-export interface VideoMessage extends Quotable {
- url: string
- /** 灏侀潰 URL */
- coverUrl?: string
- /** 鏃堕暱锛堢锛� */
- duration?: number
- width?: number
- height?: number
- /** 鏂囦欢澶у皬锛堝瓧鑺傦級 */
- size?: number
-}
-
-/**
- * 鍚嶇墖娑堟伅 payload锛堝榻愬悗绔� CardMessage锛�
- *
- * 鎸� targetType 鍖哄垎鐢ㄦ埛鍚嶇墖 / 缇ゅ悕鐗囷細
- * - PRIVATE锛歵argetId = 鐢ㄦ埛缂栧彿锛宯ame = 鐢ㄦ埛鏄电О锛堝彇鐪熷疄鏄电О锛岄潪澶囨敞锛�
- * - GROUP锛歵argetId = 缇ょ紪鍙凤紝name = 缇ゅ悕锛屽彲甯� memberCount
- */
-export interface CardMessage extends Quotable {
- /** 鍚嶇墖瀵硅薄绫诲瀷 */
- targetType: ImConversationTypeValue
- /** 鐩爣瀵硅薄缂栧彿锛歅RIVATE 鏃� = userId锛汫ROUP 鏃� = groupId */
- targetId: number
- /** 鏄剧ず鍚嶅揩鐓э細PRIVATE 鏃� = 鐢ㄦ埛鏄电О锛汫ROUP 鏃� = 缇ゅ悕 */
- name: string
- /** 澶村儚锛堝揩鐓э級 */
- avatar?: string
- /** 缇ゆ垚鍛樻暟锛堜粎 targetType = GROUP锛涙帴鏀剁灞曠ず銆孨 浜虹兢鑱娿�嶏級 */
- memberCount?: number
-}
-
-/**
- * 鍚嶇墖杞彂鐨勬簮瀵硅薄锛圧ecommendCardDialog 鍏ュ弬锛夛紱瀛楁涓� CardMessage 1:1锛屾棤 quote
- */
-export type CardTarget = Omit<CardMessage, 'quote'>
-
-/** 鐢ㄦ埛瀵硅薄 鈫� 鐢ㄦ埛鍚嶇墖婧愶紙PRIVATE锛夛紱缂� id 杩斿洖 null 璁╄皟鐢ㄦ柟 v-bind 缁戝畾涓轰笉娓叉煋 */
-export function toUserCardTarget(user: null | undefined | User): CardTarget | null {
- if (!user?.id) {
- return null
- }
- return {
- targetType: ImConversationType.PRIVATE,
- targetId: user.id,
- name: user.nickname || '',
- avatar: user.avatar
- }
-}
-
-/** 缇ゅ璞� 鈫� 缇ゅ悕鐗囨簮锛圙ROUP锛夛紱缂� id 杩斿洖 null */
-export function toGroupCardTarget(group: GroupLite | null | undefined): CardTarget | null {
- if (!group?.id) {
- return null
- }
- return {
- targetType: ImConversationType.GROUP,
- targetId: group.id,
- name: group.name || '',
- avatar: group.showImage || group.showImageThumb,
- memberCount: group.memberCount
- }
-}
-
-/**
- * 鍚嶇墖鏍囩 + 鍥炬爣锛堟寜 targetType 浜屽垎锛夛紝缁熶竴鑱婂ぉ姘旀场 / 寮曠敤棰勮 / 鍘嗗彶鎽樿 / 鍚庡彴棰勮鐨勬枃妗堜笌鍥炬爣
- *
- * 缂哄け / 闈炴硶 targetType 璧般�屼釜浜哄悕鐗囥�嶅厹搴曪紝閬垮厤鑰佹秷鎭垨鑴忔暟鎹鑷� UI 绌虹櫧
- */
-export function getCardLabelInfo(card: null | undefined | { targetType?: number }): {
- icon: string
- label: string
-} {
- if (card?.targetType === ImConversationType.GROUP) {
- return { label: '缇ゅ悕鐗�', icon: 'ant-design:usergroup-outlined' }
- }
- return { label: '涓汉鍚嶇墖', icon: 'ant-design:user-outlined' }
-}
-
-/** 琛ㄦ儏娑堟伅 payload锛堝榻愬悗绔� FaceMessage锛沀nicode emoji 浠嶈蛋 TEXT锛屾湰绫诲瀷鍙壙杞借创鍥� / 鑷畾涔夎〃鎯呭寘锛� */
-export interface FaceMessage extends Quotable {
- /** 琛ㄦ儏鍥� URL */
- url: string
- /** 娓叉煋瀹藉害锛堝儚绱狅級锛岄伩鍏嶅竷灞�鎶栧姩锛涘彲閫夛紝缂哄け鏃惰皟鐢ㄦ柟璧� CSS max-w 鍏滃簳 */
- width?: number
- /** 娓叉煋楂樺害锛堝儚绱狅級锛屽彲閫� */
- height?: number
- /** 琛ㄦ儏鍚嶏紙绯荤粺鍖呴�氬父鏈夛紝涓汉琛ㄦ儏鍖呴�氬父鏃狅級 */
- name?: string
-}
-
-/** 鍚堝苟杞彂鐨勫崟鏉″唴宓屾秷鎭揩鐓э紙瀵归綈鍚庣 MergeMessage.Item锛� */
-export interface MergeMessageItem {
- /** 鍘熸秷鎭紪鍙凤紱浠呭仛婧簮鏍囪瘑 */
- messageId: number
- /** 鍙戦�佷汉缂栧彿 */
- senderId: number
- /** 鍙戦�佷汉鏄电О蹇収锛涘绔彲鑳戒笉鍦ㄥ師浼氳瘽閲岋紝鏃犳硶瀹炴椂鏌ュ埌 */
- senderNickname: string
- /** 鍙戦�佷汉澶村儚蹇収 */
- senderAvatar?: string
- /** 鍐呭绫诲瀷锛屽榻� ImContentType */
- type: number
- /** 鍘熸秷鎭� content锛圝SON锛夛紱宓屽鍚堝苟娑堟伅鏃朵粛鎸夋湰缁撴瀯灞傚眰灞曞紑 */
- content: string
- /** 鍙戦�佹椂闂存埑锛堟绉掞級 */
- sendTime: number
-}
-
-/** 鍚堝苟杞彂娑堟伅 payload锛堝榻愬悗绔� MergeMessage锛� */
-export interface MergeMessage {
- title: string // 鍚堝苟鏍囬锛涗緥锛氥�屽紶涓夊拰鏉庡洓鐨勮亰澶╄褰曘�嶃�岀兢鑱婄殑鑱婂ぉ璁板綍銆�
- messages: MergeMessageItem[] // 鍐呭祵鐨勫畬鏁存秷鎭揩鐓�
-}
-
-/** 棰戦亾绱犳潗娑堟伅 payload锛堝榻愬悗绔� MaterialMessage锛� */
-export interface MaterialMessage {
- materialId?: number
- channelId?: number // 棰戦亾缂栧彿锛涜浆鍙戝悗娓叉煋鍗$墖搴曢儴鐨勯閬撳ご鍍� + 鍚嶇О
- title?: string
- coverUrl?: string
- summary?: string
- url?: string // 璺宠浆閾炬帴锛涗负绌烘椂鐐瑰嚮鍦ㄥ鎴风鍐呯疆璇︽儏椤垫寜 materialId 鎷� content 娓叉煋锛涢潪绌哄垯璺� url
-}
-
-// ==================== 鍚堝苟杞彂 payload 鏋勯�� ====================
-
-/** 鍗曚釜鍙戦�佷汉鐨勫揩鐓ф樀绉� / 澶村儚 */
-interface SenderSnapshot {
- nickname: string
- avatar: string
-}
-
-/**
- * 涓�娆℃�ф瀯閫� senderId 鈫� SenderSnapshot 鏄犲皠锛涢伩鍏� N 鏉℃秷鎭�愭潯 find 缇ゆ垚鍛�
- *
- * 缇よ亰浠� group.members 涓�娆℃�� indexBy锛涚鑱婂彧闇� self + friend锛涘鍔犲叏浣� senderId 涓婂仛 friend 鍏滃簳
- */
-function buildSenderSnapshotMap(
- senderIds: number[],
- conversation: Conversation
-): Map<number, SenderSnapshot> {
- const userStore = useUserStore()
- const friendStore = useFriendStore()
- const selfUserId = getCurrentUserId()
- const result = new Map<number, SenderSnapshot>()
-
- let groupMembers: Map<number, { avatar?: string; nickname: string; }> | null = null
- if (conversation.type === ImConversationType.GROUP) {
- const group = useGroupStore().getGroup(conversation.targetId)
- groupMembers = new Map((group?.members || []).map((m) => [m.userId, m]))
- }
-
- for (const senderId of senderIds) {
- if (result.has(senderId)) {
- continue
- }
- if (senderId === selfUserId) {
- result.set(senderId, {
- nickname: userStore.userInfo?.nickname || String(senderId),
- avatar: userStore.userInfo?.avatar || ''
- })
- continue
- }
- const member = groupMembers?.get(senderId)
- if (member?.nickname) {
- result.set(senderId, { nickname: member.nickname, avatar: member.avatar || '' })
- continue
- }
- const friend = friendStore.getFriend(senderId)
- result.set(senderId, {
- nickname: friend?.nickname || String(senderId),
- avatar: friend?.avatar || ''
- })
- }
- return result
-}
-
-/** 鎶婂崟鏉� Message 杞垚 MergeMessageItem 蹇収锛涘墺绂� quote 闃插紩鐢ㄧ┛閫� */
-function mapMessageToMergeItem(
- message: Message,
- senderSnapshots: Map<number, SenderSnapshot>
-): MergeMessageItem {
- const snapshot = senderSnapshots.get(message.senderId)
- return {
- messageId: message.id || 0,
- senderId: message.senderId,
- senderNickname: snapshot?.nickname ?? String(message.senderId),
- senderAvatar: snapshot?.avatar ?? '',
- type: message.type,
- content: removeQuotePayload(message.content),
- sendTime: message.sendTime
- }
-}
-
-/** 鍚堝苟杞彂鏍囬锛氱鑱娿�寋瀵规柟} 鍜� {鑷繁} 鐨勮亰澶╄褰曘�嶏紱缇よ亰銆寋缇ゅ悕} 鐨勮亰澶╄褰曘�� */
-export function buildMergeTitle(conversation: Conversation): string {
- if (conversation.type === ImConversationType.GROUP) {
- return `${conversation.name || '缇よ亰'} 鐨勮亰澶╄褰昤
- }
- const myName = useUserStore().userInfo?.nickname || '鎴�'
- return `${conversation.name || '瀵规柟'} 鍜� ${myName} 鐨勮亰澶╄褰昤
-}
-
-/** 鎶婁竴缁� Message 鎵撳寘鎴� MergeMessage payload锛涜皟鐢ㄦ柟璐熻矗鎸� sendTime 鎺掑簭鍚庝紶鍏� */
-export function buildMergeMessagePayload(
- messages: Message[],
- conversation: Conversation
-): MergeMessage {
- const senderIds = messages.map((m) => m.senderId)
- const senderSnapshots = buildSenderSnapshotMap(senderIds, conversation)
- return {
- title: buildMergeTitle(conversation),
- messages: messages.map((m) => mapMessageToMergeItem(m, senderSnapshots))
- }
-}
-
-/** 銆屾坊鍔犲埌琛ㄦ儏銆嶇殑鍙彂璧锋簮锛欶ACE / IMAGE 閮藉厑璁革紙GIF 鍥剧墖涔熷父琚敹钘忥級 */
-export interface AddableFacePayload {
- url: string
- width: number
- height: number
- name?: string
-}
-
-/**
- * 浠庢秷鎭娊鍙栥�屾坊鍔犲埌琛ㄦ儏銆嶇殑 payload锛涘綋鍓嶅唴瀹圭被鍨嬩笉鍙坊鍔犺繑鍥� null
- *
- * 璋冪敤鏂癸紙MessageItem 鐨勫彸閿彍鍗曪級鎸� nullable 鍐冲畾鏄惁灞曠ず銆屾坊鍔犲埌琛ㄦ儏銆嶅叆鍙�
- */
-export function extractAddableFace(message: Message): AddableFacePayload | null {
- if (message.type === ImContentType.FACE) {
- const face = parseMessage<FaceMessage>(message.content)
- if (!face?.url) {
- return null
- }
- return { url: face.url, width: face.width || 200, height: face.height || 200, name: face.name }
- }
- if (message.type === ImContentType.IMAGE) {
- const image = parseMessage<ImageMessage>(message.content)
- if (!image?.url) {
- return null
- }
- return { url: image.url, width: image.width || 200, height: image.height || 200 }
- }
- return null
-}
-
-/** 瑙f瀽娑堟伅 content锛圝SON 瀛楃涓诧級涓烘寚瀹� payload锛岄潪娉� JSON 杩斿洖 null */
-export const parseMessage = <T>(content: string): null | T => {
- try {
- return JSON.parse(content) as T
- } catch {
- return null
- }
-}
-
-/** 搴忓垪鍖栨秷鎭� payload 涓� content JSON 瀛楃涓诧紱涓� parseMessage 瀵圭О */
-export const serializeMessage = <T>(payload: T): string => JSON.stringify(payload)
-
-/** `URL.createObjectURL(file)` 鐢熸垚鐨� URL 鍓嶇紑锛涘崰浣� / revoke / 閲嶄紶鏃у�艰瘑鍒叡鐢� */
-export const BLOB_URL_PREFIX = 'blob:'
-
-/**
- * 濯掍綋 payload 閲屽彲鑳藉寘鍚� blob URL 鐨勫瓧娈碉紙鍥剧墖/鏂囦欢/瑙嗛/璇煶閮藉榻愯繖濂� url 瀛楁鍛藉悕锛�
- *
- * 璺熼殢 ImageMessage / VideoMessage / FileMessage / AudioMessage interface 瀹氫箟鍚屾锛�
- * - url锛氫富浣撹祫婧愶紙鍗犱綅鏃舵槸 blob URL锛宎ck 鍚庢槸鐪熷疄 URL锛�
- * - coverUrl锛氳棰戝皝闈紙commit 鍚庢槸鐪熷疄 URL锛涘崰浣嶉樁娈典笉璁句互閬垮厤浼� blob 褰� poster 鍦ㄩ儴鍒嗘祻瑙堝櫒閫�鍖栵級
- * - thumbnailUrl锛氬浘鐗囩缉鐣ュ浘锛堝綋鍓嶆湭鍗犱綅鏃朵娇鐢� blob锛岄鐣欙級
- */
-const MEDIA_BLOB_URL_FIELDS = ['url', 'coverUrl', 'thumbnailUrl'] as const
-
-/**
- * 閲婃斁 content 涓獟浣� payload 瀛楁涓婄殑 blob URL 鍐呭瓨鏄犲皠
- *
- * 浠呮壂鎻� url / coverUrl / thumbnailUrl 涓変釜宸茬煡瀛楁锛岄伩鍏� regex 鍏ㄦ枃 grep 璇激 quote.content 閲屽祵濂楃殑鍚屽悕 blob URL銆�
- * 浠呭褰撳墠 document 鍐呭垱寤虹殑 blob URL 鏈夋晥锛汭ndexedDB 鎭㈠鍑烘潵鐨勬棫 blob URL 宸查殢鏃� document 澶辨晥锛岃皟瀹冩棤瀹充絾鏃犳剰涔�
- */
-export const revokeBlobUrlsInContent = (content: string): void => {
- if (!content || !content.includes(BLOB_URL_PREFIX)) {
- return
- }
- const payload = parseMessage<Record<string, unknown>>(content)
- if (!payload) {
- return
- }
- for (const field of MEDIA_BLOB_URL_FIELDS) {
- const value = payload[field]
- if (typeof value === 'string' && value.startsWith(BLOB_URL_PREFIX)) {
- URL.revokeObjectURL(value)
- }
- }
-}
-
-// ==================== 寮曠敤娑堟伅 helper ====================
-
-/** 鎶� quote 鍚堣繘 payload(搴忓垪鍖栧墠璋冪敤);quote 缂哄け鏃跺師鏍疯繑鍥� */
-export const withQuotePayload = <T extends Quotable>(payload: T, quote?: QuoteMessage): T => {
- if (!quote) {
- return payload
- }
- return { ...payload, quote }
-}
-
-/**
- * 浠� content JSON 瀛楃涓查噷娓呮帀 quote 瀛楁
- *
- * 瀹㈡埛绔箰瑙傛覆鏌撴瀯閫� quote 鏃惰皟鐢�,閬垮厤"鍥炲涓�鏉″甫寮曠敤鐨勬秷鎭�"閫犳垚 quote 宓屽婊氶洩鐞�;
- * 涓庡悗绔� ImMessageUtils.removeQuote 瀵归綈
- */
-export const removeQuotePayload = (content: string): string => {
- if (!content || !content.includes('"quote"')) {
- return content
- }
- const parsed = parseMessage<Record<string, unknown>>(content)
- if (!parsed || !('quote' in parsed)) {
- return content
- }
- delete parsed.quote
- return JSON.stringify(parsed)
-}
-
-/** 鐢� Message 娲剧敓 QuoteMessage 鐢ㄤ簬涔愯娓叉煋;ack 鍚庝細琚湇鍔$鏉冨▉鐗堟湰瑕嗙洊 */
-export const buildQuoteFromMessage = (message: Message): QuoteMessage => {
- return {
- messageId: message.id || 0,
- senderId: message.senderId,
- type: message.type,
- content: removeQuotePayload(message.content)
- }
-}
-
-/** 浠庡凡搴忓垪鍖� message.content 涓В鍑� quote;闈� JSON / 鏃� quote 杩斿洖 null */
-export const getQuoteFromMessage = (content: string): null | QuoteMessage => {
- // 闀夸細璇濇瘡鏉℃秷鎭覆鏌撻兘璧� quote computed,闈炲紩鐢ㄦ秷鎭瓧绗︿覆棰勬壂鐩存帴杩斿洖,鍏嶄竴娆� JSON.parse
- if (!content || !content.includes('"quote"')) {
- return null
- }
- const parsed = parseMessage<Quotable>(content)
- return parsed?.quote ?? null
-}
-
-// ==================== 鎾ゅ洖 ====================
-
-/**
- * 浠庡悗绔笅鍙戠殑鎾ゅ洖 RecallMessage content 涓В鏋愬嚭琚挙鍥炵殑鍘熸秷鎭� id
- * content 褰㈠ `{"messageId": 123}`锛岃嫢涓嶅惈 messageId 鍒欒繑鍥� 0锛堣〃绀鸿繖鏉′笉鏄挙鍥炴秷鎭級
- */
-export const parseRecallMessageId = (content: string): number => {
- try {
- const parsed = JSON.parse(content)
- return parsed?.messageId == null ? 0 : Number(parsed.messageId)
- } catch {
- return 0
- }
-}
-
-// ==================== 鏂版秷鎭彁绀洪煶 ====================
-
-import tipAudioUrl from '#/assets/audio/im/message-tip.mp3'
-
-/**
- * 鏂版秷鎭彁绀洪煶锛屽甫 1 绉掕妭娴侊細鐭椂闂村唴澶氭潯娑堟伅鍙搷涓�娆★紝閬垮厤鐤插姵杞扮偢銆�
- *
- * 娴忚鍣ㄨ嚜鍔ㄦ挱鏀剧瓥鐣ヨ姹傞〉闈㈡湁杩囩敤鎴蜂氦浜掞紱鑻ユ棤娉曟挱鏀句細闈欓粯澶辫触銆�
- */
-let __lastPlayAudioTipAt = 0
-let __tipAudio: HTMLAudioElement | null = null
-export const playAudioTip = () => {
- const now = Date.now()
- if (now - __lastPlayAudioTipAt < 1000) {
- return
- }
- __lastPlayAudioTipAt = now
- try {
- if (!__tipAudio) {
- __tipAudio = new Audio(tipAudioUrl)
- __tipAudio.preload = 'auto'
- }
- __tipAudio.currentTime = 0
- const playPromise = __tipAudio.play()
- if (playPromise && typeof playPromise.catch === 'function') {
- playPromise.catch((error) => console.debug('[IM] playAudioTip 澶辫触', error))
- }
- } catch (error) {
- console.debug('[IM] playAudioTip 澶辫触', error)
- }
-}
-
-// ==================== 鏂囦欢鍥炬爣 ====================
-
-/**
- * 鎸夋枃浠舵墿灞曞悕鎸戞枃浠跺浘鏍� + 棰滆壊锛屽榻愬井淇¤鎰�
- *
- * message-item.vue锛堜富姘旀场锛夊拰 reply-preview.vue锛堝紩鐢ㄩ瑙堬級鍏辩敤鍚屼竴浠芥槧灏勶紝閬垮厤瑙嗚涓ゅ涓嶄竴鑷�
- */
-export function getFileIconInfo(filename: string | undefined): { color: string; icon: string; } {
- const ext = (filename || '').split('.').pop()?.toLowerCase() || ''
- if (ext === 'pdf') {
- return { icon: 'ant-design:file-pdf-filled', color: '#ed5757' }
- }
- if (['doc', 'docx'].includes(ext)) {
- return { icon: 'ant-design:file-word-filled', color: '#2b7cd3' }
- }
- if (['xls', 'xlsx'].includes(ext)) {
- return { icon: 'ant-design:file-excel-filled', color: '#1f7244' }
- }
- if (['ppt', 'pptx'].includes(ext)) {
- return { icon: 'ant-design:file-ppt-filled', color: '#d24726' }
- }
- if (['7z', 'gz', 'rar', 'tar', 'zip'].includes(ext)) {
- return { icon: 'ant-design:file-zip-filled', color: '#f0ad4e' }
- }
- if (['bmp', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp'].includes(ext)) {
- return { icon: 'ant-design:file-image-filled', color: '#9c27b0' }
- }
- if (['avi', 'flv', 'mkv', 'mov', 'mp4', 'wmv'].includes(ext)) {
- return { icon: 'ant-design:video-camera-filled', color: '#9c27b0' }
- }
- if (['aac', 'flac', 'mp3', 'ogg', 'wav'].includes(ext)) {
- return { icon: 'ant-design:audio-filled', color: '#9c27b0' }
- }
- if (['json', 'log', 'md', 'txt', 'xml'].includes(ext)) {
- return { icon: 'ant-design:file-text-filled', color: '#909399' }
- }
- return { icon: 'ant-design:file-filled', color: '#909399' }
-}
-
-// ==================== 绠$悊鍚庡彴灞曠ず宸ュ叿 ====================
-
-/** 璇︽儏寮圭獥閲屾妸 content JSON 缇庡寲鎴� 2 缂╄繘 */
-export const formatJson = (content?: string): string => {
- if (!content) return ''
- try {
- return JSON.stringify(JSON.parse(content), null, 2)
- } catch {
- return content
- }
-}
-
-// ==================== 缇ゅ箍鎾簨浠� payload + 鏂囨 ====================
-
-// 缇ゅ箍鎾簨浠� payload锛涘榻愬悗绔� GroupNotificationMessage 瀛愮被鑱氬悎瀛楁
-export type GroupNotificationPayload = {
- addSource?: number
- // 鍏ㄧ兢灏佺
- banned?: boolean
- displayUserName?: string
- // 鑷敱杩涚兢浜嬩欢
- entrantUserId?: number
- memberUserIds?: number[]
- // PIN 浜嬩欢鎼哄甫鐨勮缃《娑堟伅灞曠ず鏁版嵁
- message?: {
- atUserIds?: number[]
- content: string
- groupId: number
- id: number
- receiverUserIds?: number[]
- senderId: number
- sendTime: string
- type: number
- }
- messageId?: number
- // 绂佽█浜嬩欢
- mutedUserId?: number
- muteEndTime?: string
- newAvatar?: string
- newJoinApproval?: boolean
- newName?: string
- newNotice?: string
- newOwnerUserId?: number
- oldAvatar?: string
- oldJoinApproval?: boolean
- oldName?: string
- oldNotice?: string
- operatorUserId?: number
-}
-
-/**
- * 缇ゅ箍鎾簨浠� segments
- *
- * resolveName 鐢辫皟鐢ㄦ柟娉ㄥ叆锛堥粯璁ゅ満鏅紶 getSenderDisplayName锛夛紱
- * operatorNameOverride 浠呰鐩� operator 娈垫枃妗堬紝mention userId 浠嶇敤 payload.operatorUserId
- */
-export function resolveGroupNotificationSegments(
- message: { content?: string; targetId?: number; type?: number; },
- resolveName: (userId: number) => string,
- operatorNameOverride?: string
-): TipSegment[] {
- let payload: GroupNotificationPayload
- try {
- payload = JSON.parse(message.content || '{}')
- } catch {
- return []
- }
- // ENTER 涓昏鏄� entrant 鑰岄潪 operator锛岀嫭绔嬪鐞嗭紱鍏跺畠 case 閮戒互 operatorUserId 涓轰富璇�
- if (message.type === ImContentType.GROUP_MEMBER_ENTER) {
- const entrantId = payload.entrantUserId ?? payload.operatorUserId
- return entrantId ? [tipMention(entrantId, resolveName(entrantId)), tipText(' 鍔犲叆浜嗙兢鑱�')] : []
- }
- if (!payload.operatorUserId) {
- return []
- }
- const operatorSegment = tipMention(
- payload.operatorUserId,
- operatorNameOverride ?? resolveName(payload.operatorUserId)
- )
- const memberSegments = joinMentionSegments(payload.memberUserIds || [], '銆�', resolveName)
-
- switch (message.type) {
- case ImContentType.GROUP_ADMIN_ADD: {
- return [operatorSegment, tipText(' 灏� '), ...memberSegments, tipText(' 璁句负绠$悊鍛�')]
- }
- case ImContentType.GROUP_ADMIN_REMOVE: {
- return [operatorSegment, tipText(' 鎾ら攢浜� '), ...memberSegments, tipText(' 鐨勭鐞嗗憳韬唤')]
- }
- case ImContentType.GROUP_BANNED: {
- return [operatorSegment, tipText(payload.banned ? ' 灏佺浜嗚缇�' : ' 瑙e皝浜嗚缇�')]
- }
- case ImContentType.GROUP_CANCEL_MUTED: {
- return [operatorSegment, tipText(' 鍏抽棴浜嗗叏缇ょ瑷�')]
- }
- case ImContentType.GROUP_CREATE: {
- return [operatorSegment, tipText(' 鍒涘缓浜嗙兢鑱�')]
- }
- case ImContentType.GROUP_DISSOLVE: {
- return [operatorSegment, tipText(' 瑙f暎浜嗙兢鑱�')]
- }
- case ImContentType.GROUP_INFO_UPDATE: {
- return payload.newAvatar
- ? [operatorSegment, tipText(' 鏇存崲浜嗙兢澶村儚')]
- : [operatorSegment, tipText(' 鏇存柊浜嗙兢淇℃伅')]
- }
- case ImContentType.GROUP_MEMBER_CANCEL_MUTED: {
- return payload.mutedUserId
- ? [
- operatorSegment,
- tipText(' 瑙i櫎浜� '),
- tipMention(payload.mutedUserId, resolveName(payload.mutedUserId)),
- tipText(' 鐨勭瑷�')
- ]
- : []
- }
- case ImContentType.GROUP_MEMBER_INVITE: {
- return [operatorSegment, tipText(' 閭�璇� '), ...memberSegments, tipText(' 鍔犲叆缇よ亰')]
- }
- case ImContentType.GROUP_MEMBER_KICK: {
- return [operatorSegment, tipText(' 绉诲嚭浜� '), ...memberSegments]
- }
- case ImContentType.GROUP_MEMBER_MUTED: {
- return payload.mutedUserId
- ? [
- operatorSegment,
- tipText(' 灏� '),
- tipMention(payload.mutedUserId, resolveName(payload.mutedUserId)),
- tipText(' 绂佽█')
- ]
- : []
- }
- case ImContentType.GROUP_MEMBER_NICKNAME_UPDATE: {
- return [operatorSegment, tipText(` 淇敼缇ゆ樀绉颁负 "${payload.displayUserName ?? ''}"`)]
- }
- case ImContentType.GROUP_MEMBER_QUIT: {
- return [operatorSegment, tipText(' 閫�鍑轰簡缇よ亰')]
- }
- case ImContentType.GROUP_MESSAGE_PIN: {
- return [operatorSegment, tipText(' 缃《浜嗕竴鏉℃秷鎭�')]
- }
- case ImContentType.GROUP_MESSAGE_UNPIN: {
- return [operatorSegment, tipText(' 鍙栨秷浜嗕竴鏉$疆椤舵秷鎭�')]
- }
- case ImContentType.GROUP_MUTED: {
- return [operatorSegment, tipText(' 寮�鍚簡鍏ㄧ兢绂佽█')]
- }
- case ImContentType.GROUP_NAME_UPDATE: {
- return [operatorSegment, tipText(` 灏嗙兢鍚嶄慨鏀逛负 "${payload.newName ?? ''}"`)]
- }
- case ImContentType.GROUP_NOTICE_UPDATE: {
- return [operatorSegment, tipText(' 鏇存柊浜嗙兢鍏憡')]
- }
- case ImContentType.GROUP_OWNER_TRANSFER: {
- return payload.newOwnerUserId
- ? [
- operatorSegment,
- tipText(' 宸插皢缇や富杞缁� '),
- tipMention(payload.newOwnerUserId, resolveName(payload.newOwnerUserId))
- ]
- : []
- }
- default: {
- return []
- }
- }
-}
-
-/** 缇ゅ箍鎾簨浠朵腑鏂囨枃妗� */
-export function resolveGroupNotificationText(
- message: { content?: string; targetId?: number; type?: number; },
- resolveName: (userId: number) => string,
- operatorNameOverride?: string
-): string {
- return segmentsToText(
- resolveGroupNotificationSegments(message, resolveName, operatorNameOverride)
- )
-}
-
-// ==================== 濂藉弸浜嬩欢 ====================
-
-/** 浼氳瘽鍐呭ソ鍙嬩簨浠� segments */
-export function resolveFriendNotificationSegments(message: { type?: number }): TipSegment[] {
- switch (message.type) {
- case ImContentType.FRIEND_ADD: {
- return [tipText('浣犱滑宸茬粡鏄ソ鍙嬩簡锛屽紑濮嬭亰澶╁惂')]
- }
- case ImContentType.FRIEND_DELETE: {
- return [tipText('浣犲凡鍒犻櫎濂藉弸')]
- }
- default: {
- return []
- }
- }
-}
-
-/** 浼氳瘽鍐呭ソ鍙嬩簨浠舵枃妗堬細FRIEND_ADD / FRIEND_DELETE 娓叉煋鎴愮伆鑹叉彁绀烘皵娉★紝鏂囨鍥哄畾涓嶄緷璧� payload */
-export function resolveFriendNotificationText(message: { type?: number }): string {
- return segmentsToText(resolveFriendNotificationSegments(message))
-}
-
-// ==================== RTC 閫氳瘽浜嬩欢 ====================
-
-// RTC_CALL_START payload锛涗粎缇よ亰锛涚敤浜庤亰澶� tip 鏂囨銆寋inviter} 鍙戣捣浜唟voice/video}閫氳瘽銆�
-export type RtcCallStartPayload = {
- conversationType?: number
- inviterAvatar?: string
- inviterNickname?: string
- inviterUserId?: number
- mediaType?: number
- room?: string
-}
-
-// RTC_CALL_END payload锛涚鑱婂噯姘旀场 + 缇よ亰銆岄�氳瘽宸茬粨鏉� [鏃堕暱 X]銆嶅叡鐢�
-export type RtcCallEndPayload = {
- conversationType?: number
- durationSeconds?: number
- endReason?: number
- mediaType?: number
- operatorAvatar?: string
- operatorNickname?: string
- operatorUserId?: number
- room?: string
-}
-
-/** 瑙f瀽 RTC_CALL_START / RTC_CALL_END 娑堟伅 content锛涜В鏋愬け璐ヨ繑鍥� null */
-export function parseRtcCallPayload(
- content?: string
-): null | (RtcCallEndPayload & RtcCallStartPayload) {
- return content ? parseMessage<RtcCallEndPayload & RtcCallStartPayload>(content) : null
-}
-
-/**
- * 浼氳瘽鍐呴�氳瘽浜嬩欢 segments锛圧TC_CALL_START / RTC_CALL_END锛�
- * <p>
- * 缇よ亰涓ゆ寮忥細START銆寋inviter} 鍙戣捣浜嗚闊抽�氳瘽銆�+ END銆岃闊抽�氳瘽宸茬粡缁撴潫銆�
- * <p>
- * 绉佽亰姘旀场璧� {@link resolveRtcCallPrivateBubbleText}
- */
-export function resolveRtcCallTipSegments(message: {
- content?: string
- selfSend?: boolean
- type?: number
-}): TipSegment[] {
- const payload = parseRtcCallPayload(message.content)
- if (!payload) {
- return []
- }
- if (message.type === ImContentType.RTC_CALL_START) {
- return payload.inviterUserId
- ? [
- tipMention(payload.inviterUserId, resolveRtcInviterLabel(payload)),
- tipText(' 鍙戣捣浜嗚闊抽�氳瘽')
- ]
- : []
- }
- if (message.type === ImContentType.RTC_CALL_END) {
- return [tipText('璇煶閫氳瘽宸茬粡缁撴潫')]
- }
- return []
-}
-
-/** 鍙� RTC 閫氳瘽鍙戣捣浜哄睍绀哄悕锛涙樀绉颁负绌烘椂鍥為��銆岀敤鎴� {id}銆� */
-function resolveRtcInviterLabel(payload: RtcCallStartPayload): string {
- return payload.inviterNickname?.trim() || `鐢ㄦ埛 ${payload.inviterUserId ?? ''}`
-}
-
-/**
- * 浼氳瘽鍒楄〃鏈�鍚庝竴鏉¢瑙堟枃妗堬紙RTC_CALL_START / RTC_CALL_END锛�
- * <p>
- * 绉佽亰锛歋TART / END 閮藉睍绀恒�孾璇煶閫氳瘽]銆嶏紝瀵归綈 [鍥剧墖] / [璇煶] 椋庢牸
- * <p>
- * 缇よ亰锛歋TART銆寋inviter} 鍙戣捣浜嗚闊抽�氳瘽銆嶃�丒ND銆岃闊抽�氳瘽宸茬粡缁撴潫銆嶏紝涓庤亰澶� tip 鍚屾簮
- */
-export function resolveRtcCallLastContent(
- message: { content?: string; type?: number; },
- conversationType: number
-): string {
- if (conversationType === ImConversationType.PRIVATE) {
- return '[璇煶閫氳瘽]'
- }
- if (message.type === ImContentType.RTC_CALL_END) {
- return '璇煶閫氳瘽宸茬粡缁撴潫'
- }
- if (message.type === ImContentType.RTC_CALL_START) {
- const payload = parseRtcCallPayload(message.content)
- if (payload) {
- return `${resolveRtcInviterLabel(payload)} 鍙戣捣浜嗚闊抽�氳瘽`
- }
- }
- return ''
-}
-
-/**
- * 绉佽亰 RTC_CALL_END 姘旀场鍐呮枃妗堬紱鎸� operatorUserId 鏄笉鏄嚜宸辨覆鏌撲袱绔笉鍚屾枃妗堬紙瀵归綈寰俊锛�
- * <p>
- * 鏂囨瑙勫垯锛�
- * HANGUP duration > 0 鈫� 銆岄�氳瘽鏃堕暱 N銆嶏紙鍙屾柟涓�鑷达級
- * HANGUP duration 鈮� 0 鈫� 銆岄�氳瘽涓柇銆嶏紙鏈帴閫氱殑鍏滃簳锛�
- * CANCEL 鈫� 鎿嶄綔鑰呫�屽凡鍙栨秷銆�/ 鍙︿竴鏂广�屽鏂瑰凡鍙栨秷銆�
- * REJECT 鈫� 鎿嶄綔鑰呫�屽凡鎷掔粷銆�/ 鍙︿竴鏂广�屽鏂瑰凡鎷掔粷銆�
- * BUSY 鈫� 鎿嶄綔鑰呫�屽繖绾挎湭鎺ュ惉銆�/ 鍙︿竴鏂广�屽鏂瑰繖绾夸腑銆�
- * NO_ANSWER 鈫� 鎿嶄綔鑰呫�屾湭鎺ュ惉銆�/ 鍙︿竴鏂广�屽鏂规湭鎺ュ惉銆嶏紙鎸搩瓒呮椂 Job 瑙﹀彂锛�
- * ERROR 鈫� 銆岄�氳瘽涓柇 [N]銆嶏紙鎺ラ�氬悗寮傚父鏂紑锛沝uration > 0 鏃跺甫鏃堕暱锛�
- */
-export function resolveRtcCallPrivateBubbleText(payload: null | RtcCallEndPayload): string {
- if (!payload) {
- return '閫氳瘽宸茬粨鏉�'
- }
- const duration = payload.durationSeconds ?? 0
- const hasDuration = duration > 0
- const isOperator = payload.operatorUserId === getCurrentUserId()
- switch (payload.endReason) {
- case ImRtcCallEndReason.BUSY: {
- return isOperator ? '蹇欑嚎鏈帴鍚�' : '瀵规柟蹇欑嚎涓�'
- }
- case ImRtcCallEndReason.CANCEL: {
- return isOperator ? '宸插彇娑�' : '瀵规柟宸插彇娑�'
- }
- case ImRtcCallEndReason.ERROR: {
- return hasDuration ? `閫氳瘽涓柇 ${formatCallDuration(duration)}` : '閫氳瘽涓柇'
- }
- case ImRtcCallEndReason.HANGUP: {
- return hasDuration ? `閫氳瘽鏃堕暱 ${formatCallDuration(duration)}` : '閫氳瘽涓柇'
- }
- case ImRtcCallEndReason.NO_ANSWER: {
- return isOperator ? '鏈帴鍚�' : '瀵规柟鏈帴鍚�'
- }
- case ImRtcCallEndReason.REJECT: {
- return isOperator ? '宸叉嫆缁�' : '瀵规柟宸叉嫆缁�'
- }
- default: {
- return hasDuration ? `閫氳瘽鏃堕暱 ${formatCallDuration(duration)}` : '閫氳瘽宸茬粨鏉�'
- }
- }
-}
-
-/** 浼氳瘽鍐呴�氳瘽浜嬩欢鏂囨 */
-export function resolveRtcCallTipText(message: {
- content?: string
- selfSend?: boolean
- type?: number
-}): string {
- return segmentsToText(resolveRtcCallTipSegments(message))
-}
-
-/**
- * RTC_CALL_END 缁撴潫鍘熷洜鍏滃簳鏂囨锛涘墠绔� toast / console 鍏滃簳鐢�
- * <p>
- * 缂� operator 淇℃伅锛堝悓姝ュ搷搴� + WS push 鍏滃簳鍦烘櫙锛夋椂鐨勯�氱敤鏂囨锛涚粏鍒嗘枃妗堬紙鎸夊彂閫佹柟瑙嗚锛夎蛋 {@link resolveRtcCallPrivateBubbleText}
- */
-export function resolveCallEndReasonText(reason: number | undefined): string {
- switch (reason) {
- case ImRtcCallEndReason.BUSY: {
- return '瀵规柟蹇欑嚎涓�'
- }
- case ImRtcCallEndReason.CANCEL: {
- return '瀵规柟宸插彇娑�'
- }
- case ImRtcCallEndReason.ERROR: {
- return '閫氳瘽寮傚父'
- }
- case ImRtcCallEndReason.HANGUP: {
- return '閫氳瘽宸茬粨鏉�'
- }
- case ImRtcCallEndReason.REJECT: {
- return '瀵规柟宸叉嫆缁�'
- }
- default: {
- return '閫氳瘽宸叉柇寮�'
- }
- }
-}
diff --git a/src/views/im/utils/pull.ts b/src/views/im/utils/pull.ts
deleted file mode 100644
index 4e3ec11..0000000
--- a/src/views/im/utils/pull.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { getDb } from './db'
-
-/**
- * IM 鐘舵�佷簨浠惰ˉ鍋匡紙澧為噺鎷夊彇锛夐�氱敤缂栨帓
- *
- * 鍚勪笟鍔℃ā鍧楋紙濂藉弸銆佺兢銆佺敵璇枫�佽浣嶇疆锛夊叡鐢ㄤ竴濂� update_time + id 姝e悜娓告爣锛�
- * 浠庢寔涔呭寲娓告爣鍑哄彂寰幆鎷夊彇鍙樻洿骞跺悎骞惰繘鏈湴 store锛岀洿鍒版煇椤典笉婊★紙娌℃湁鏇村锛夛紝
- * 鐢ㄤ簬 WebSocket 婕忔帹鍚庣殑鍏滃簳琛ュ伩锛堣繘鍏� IM銆佹柇绾块噸杩炴椂瑙﹀彂锛�
- */
-
-/** 澧為噺鎷夊彇娓告爣锛氫笂娆℃媺鍒扮殑浣嶇疆 */
-export interface PullCursor {
- lastUpdateTime?: number
- lastId?: number
-}
-
-/** 鍙綔涓烘父鏍囩殑鎷夊彇璁板綍锛氭湇鍔$鎸� update_time + id 杩斿洖锛屽鎴风鍙栨渶鍚庝竴鏉℃帹杩涙父鏍� */
-interface PullRecord {
- id: number
- updateTime?: number
-}
-
-/** 鍗曟鎷夊彇鏉℃暟锛堜笌鍚庣 limit 涓婇檺瀵归綈锛� */
-const PULL_PAGE_SIZE = 100
-/** 鍗曡疆鏈�澶氱炕椤垫暟锛屽厹搴曢槻寰″紓甯告父鏍囧鑷寸殑姝诲惊鐜� */
-const PULL_MAX_PAGES = 100
-/** 鐘舵�佷簨浠舵媺鍙栧洖鎵獥鍙o紝瑕嗙洊鍚岀鍐呮棫琛屾洿鏂板拰瀹㈡埛绔� / 鏈嶅姟绔椂閽熺簿搴﹀樊 */
-const PULL_OVERLAP_MS = 5000
-/** 娑堟伅绫� minId 鎷夊彇鐨勫崟杞炕椤典笂闄愶紝鍏滃簳闃插尽寮傚父娓告爣姝荤炕锛涙秷鎭噺鍙兘杩滃ぇ浜庣姸鎬佷簨浠讹紝鏀惧鍒� 1000 */
-const MIN_ID_PULL_MAX_PAGES = 1000
-
-/** 璇诲彇鏌愭ā鍧楃殑鎷夊彇娓告爣锛涙棤鍒欒繑鍥炵┖娓告爣锛堥娆℃媺鍏ㄩ噺锛� */
-export async function getPullCursor(key: string): Promise<PullCursor> {
- return (await getDb().getSetting<PullCursor>(key)) ?? {}
-}
-
-/**
- * 閫氱敤澧為噺鎷夊彇锛氫粠鎸佷箙鍖栨父鏍囧嚭鍙戯紝寰幆鎷夊彇骞跺簲鐢ㄥ彉鏇达紝鐩村埌鏌愰〉涓嶆弧
- *
- * @param cursorKey 娓告爣鍦� settings 閲岀殑 key锛堟瘡涓ā鍧椾竴浠斤級
- * @param fetchPage 鎸夋父鏍囨媺涓�椤碉紙璋冨搴� pull 鎺ュ彛锛岃繑鍥� VO 鍒楄〃锛�
- * @param apply 鎶婁竴椤靛彉鏇村悎骞惰繘鏈湴 store锛涜繑鍥� false 琛ㄧず鏈〉鏈畬鍏ㄨ惤鍦帮紙濡傝处鍙峰凡鍒囥�佷緷璧栬祫婧愯ˉ鎷夊け璐ワ級锛�
- * 姝ゆ椂涓嶆帹杩涙父鏍囧苟缁堟鏈疆锛岄伩鍏嶆父鏍囪秺杩囨湭钀藉湴鐨勮褰曘�佸鑷村悗缁閲忔案涔呮紡鎷夈�傚彲杩斿洖 Promise
- */
-export async function runIncrementalPull<T extends PullRecord>(
- cursorKey: string,
- fetchPage: (params: { lastId?: number; lastUpdateTime?: number; limit: number }) => Promise<T[]>,
- apply: (records: T[]) => boolean | Promise<boolean>,
- isActive?: () => boolean
-): Promise<void> {
- const storedCursor = await getPullCursor(cursorKey)
- const highWater = { ...storedCursor }
- let cursor =
- storedCursor.lastUpdateTime == null
- ? {}
- : { lastUpdateTime: Math.max(0, storedCursor.lastUpdateTime - PULL_OVERLAP_MS), lastId: 0 }
- for (let page = 0; page < PULL_MAX_PAGES; page++) {
- if (isActive && !isActive()) {
- return
- }
- const list = await fetchPage({
- lastUpdateTime: cursor.lastUpdateTime,
- lastId: cursor.lastId,
- limit: PULL_PAGE_SIZE
- })
- if (isActive && !isActive()) {
- return
- }
- if (list.length > 0) {
- // apply 鏈畬鍏ㄨ惤鍦帮紙杩斿洖 false锛夋椂鐩存帴缁堟锛氭父鏍囧彧鑳借窡鐫�宸茶惤鍦扮殑鏁版嵁璧帮紝鍚﹀垯浼氳烦杩囨湰椤佃褰�
- if ((await apply(list)) === false) {
- return
- }
- if (isActive && !isActive()) {
- return
- }
- // 鎺ㄨ繘娓告爣鍒版湰椤垫渶鍚庝竴鏉″苟鎸佷箙鍖栵細涓嬫浠庤繖閲屾帴鐫�鎷�
- const last = list[list.length - 1]
- if (!last) {
- return
- }
- if (last.updateTime == null) {
- return
- }
- cursor = { lastUpdateTime: last.updateTime, lastId: last.id }
- if (
- highWater.lastUpdateTime == null ||
- cursor.lastUpdateTime > highWater.lastUpdateTime ||
- (cursor.lastUpdateTime === highWater.lastUpdateTime &&
- cursor.lastId > (highWater.lastId ?? 0))
- ) {
- highWater.lastUpdateTime = cursor.lastUpdateTime
- highWater.lastId = cursor.lastId
- await getDb().setSetting(cursorKey, highWater)
- }
- }
- // 涓嶆弧涓�椤� = 娌℃湁鏇村鍙樻洿
- if (list.length < PULL_PAGE_SIZE) {
- return
- }
- }
- console.warn(`[IM pull] ${cursorKey} 杈惧埌鍗曡疆缈婚〉涓婇檺锛屾彁鍓嶇粨鏉熸湰杞ˉ鍋縛)
-}
-
-/**
- * 娑堟伅绫诲閲忔媺鍙栭�氱敤缂栨帓锛氭寜鍗曡皟 minId 娓告爣寰幆缈婚〉锛岀洿鍒扮┖椤垫垨娓告爣涓嶅啀鍓嶈繘
- *
- * 涓� runIncrementalPull 鐨勫尯鍒細娑堟伅娓告爣鏄崟璋冩秷鎭� id锛堥潪 update_time + id 澶嶅悎娓告爣锛夛紝涓� messageMaxId 鐨勬寔涔呭寲
- * 璺熼殢娑堟伅鍏ュ簱鍦� applyPage 鍐呭畬鎴愶紙messageStore锛夛紝鏁呮湰鍑芥暟鍙礋璐c�屽惊鐜炕椤垫満鍒躲�嶏紝涓嶇 settings锛屼篃涓嶆幒娑堟伅涓氬姟銆�
- *
- * @param initialMinId 璧峰娓告爣锛氫笂娆″叆搴撶殑鏈�澶ф秷鎭� id
- * @param pageSize 鍗曢〉鏉℃暟
- * @param fetchPage 鎸� minId 鎷変竴椤�
- * @param applyPage 澶勭悊鏈〉锛堝缓浼氳瘽 / 鍒嗗彂 / 鍏ュ簱 + 鎺ㄨ繘 messageMaxId锛夛紱杩斿洖 false 琛ㄧず鏈〉鏈惤鍦帮紝鍋滄涓斾笉鎺ㄨ繘娓告爣
- * @param isActive 姣忔 await 鍓嶅悗鑷锛氬彇娑� / 鍒囪处鍙锋椂杩斿洖 false锛屼涪寮冩湰鎵逛笉鍏ュ簱銆佷笉鍐嶇炕椤�
- * @param maxPages 鍗曡疆缈婚〉涓婇檺
- */
-export async function runMinIdPull<T extends { id?: number }>(options: {
- applyPage: (records: T[], nextMinId?: number) => Promise<boolean> | Promise<void>
- fetchPage: (params: { minId: number; size: number }) => Promise<T[]>
- initialMinId: number
- isActive?: () => boolean
- maxPages?: number
- pageSize: number
-}): Promise<void> {
- const { initialMinId, pageSize, fetchPage, applyPage, isActive } = options
- const maxPages = options.maxPages ?? MIN_ID_PULL_MAX_PAGES
- let minId = initialMinId || 0
- for (let page = 0; page < maxPages; page++) {
- if (isActive && !isActive()) {
- return
- }
- const list = await fetchPage({ minId, size: pageSize })
- // 鎷夊彇鏈熼棿鍙栨秷 / 鍒囪处鍙凤細涓㈠純鏈壒涓嶅叆搴擄紝涔熶笉鍐嶇炕椤�
- if (isActive && !isActive()) {
- return
- }
- if (!list || list.length === 0) {
- return
- }
- // 鏈壒鏈�澶ф秷鎭� id 浣滀负涓嬫娓告爣锛涙棤鏈夋晥 id 鍒欐湰鎵� apply 鍚庡仠锛堟棤娉曠户缁炕椤碉級
- const validIds = list.map((record) => record.id).filter((id): id is number => id != null)
- const nextMinId = validIds.length > 0 ? Math.max(...validIds) : undefined
- // applyPage 杩斿洖 false锛氭湰椤垫湭钀藉湴锛堝鍏ュ簱澶辫触锛夛紝涓嶆帹杩涙父鏍囧苟缁堟锛岄伩鍏嶆紡娑堟伅
- if ((await applyPage(list, nextMinId)) === false) {
- return
- }
- // 鏃犳湁鏁� id锛屾垨娓告爣娌″墠杩涳紙鍚庣濂戠害鏄� id > minId锛岀悊璁轰笉浼氬嚭鐜帮級锛氬仠锛岄槻寰℃缈�
- if (nextMinId == null || nextMinId <= minId) {
- return
- }
- minId = nextMinId
- }
- console.warn('[IM pull] runMinIdPull 杈惧埌鍗曡疆缈婚〉涓婇檺锛屾彁鍓嶇粨鏉熸湰杞�')
-}
diff --git a/src/views/im/utils/time.ts b/src/views/im/utils/time.ts
deleted file mode 100644
index 7c5b6e8..0000000
--- a/src/views/im/utils/time.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import dayjs from 'dayjs'
-
-// ====================================================================
-// IM 鏃堕棿鏍煎紡鍖�
-// ====================================================================
-// 4 绫诲満鏅紝鏂囨瑙勫垯鍚勬湁宸紓锛涚粺涓�璧� dayjs锛岄伩鍏� raw Date 鏁g偣鍐欐硶
-
-const WEEKDAY_NAMES_FULL = ['鏄熸湡鏃�', '鏄熸湡涓�', '鏄熸湡浜�', '鏄熸湡涓�', '鏄熸湡鍥�', '鏄熸湡浜�', '鏄熸湡鍏�']
-const WEEKDAY_NAMES_SHORT = ['鍛ㄦ棩', '鍛ㄤ竴', '鍛ㄤ簩', '鍛ㄤ笁', '鍛ㄥ洓', '鍛ㄤ簲', '鍛ㄥ叚']
-
-/**
- * 娑堟伅鍒楄〃鐨勬椂闂村垎闅旀潯
- *
- * - 浠婂ぉ锛欻H:mm
- * - 鏄ㄥぉ锛氭槰澶� HH:mm
- * - 涓�鍛ㄥ唴锛氬懆X HH:mm
- * - 瓒呰繃涓�鍛細MM-DD HH:mm
- */
-export function formatTimeTip(timestamp: number): string {
- if (!timestamp) {
- return ''
- }
- const target = dayjs(timestamp)
- const now = dayjs()
- const time = target.format('HH:mm')
- if (target.isSame(now, 'day')) {
- return time
- }
- if (target.isSame(now.subtract(1, 'day'), 'day')) {
- return `鏄ㄥぉ ${time}`
- }
- const diffDays = now.startOf('day').diff(target.startOf('day'), 'day')
- if (diffDays >= 2 && diffDays <= 6) {
- return `${WEEKDAY_NAMES_SHORT[target.day()]} ${time}`
- }
- return target.format('MM-DD HH:mm')
-}
-
-/**
- * 浼氳瘽鍒楄〃閲岀殑鏃堕棿锛堟棤 HH:mm 鍚庣紑锛屽彧灞曠ず鏃ユ湡绮掑害锛�
- *
- * - 浠婂ぉ锛欻H:mm
- * - 鏄ㄥぉ锛氭槰澶� HH:mm
- * - 鏈懆鍐咃紙2-6 澶╁墠锛夛細鏄熸湡X
- * - 鍚屽勾鍏朵粬锛歁M/DD
- * - 璺ㄥ勾锛歒YYY/MM/DD
- */
-export function formatConversationTime(timestamp: number): string {
- if (!timestamp) {
- return ''
- }
- const target = dayjs(timestamp)
- const now = dayjs()
- if (target.isSame(now, 'day')) {
- return target.format('HH:mm')
- }
- if (target.isSame(now.subtract(1, 'day'), 'day')) {
- return `鏄ㄥぉ ${target.format('HH:mm')}`
- }
- // 鐢� startOf('day') 鍏滃簳璺ㄦ椂闂寸偣鐨勫樊鍊硷紝閬垮厤鎺ヨ繎鍑屾櫒鏃跺彇鍒� 1.x 鈫� diff 绠楁垚 1 婕忔帀鏄ㄥぉ鍒嗘敮
- const diffDays = now.startOf('day').diff(target.startOf('day'), 'day')
- if (diffDays >= 2 && diffDays <= 6) {
- return WEEKDAY_NAMES_FULL[target.day()] || ''
- }
- return target.year() === now.year() ? target.format('MM/DD') : target.format('YYYY/MM/DD')
-}
-
-/**
- * 鍘嗗彶娑堟伅鎼滅储鍒楄〃鐨勬椂闂达細灞曠ず缁濆鏃ユ湡
- *
- * - 鍚屽勾锛歁鏈圖鏃� HH:mm
- * - 璺ㄥ勾锛歒YYY骞碝鏈圖鏃� HH:mm
- */
-export function formatHistoryTime(timestamp: number): string {
- if (!timestamp) {
- return ''
- }
- const target = dayjs(timestamp)
- return target.year() === dayjs().year()
- ? target.format('M鏈圖鏃� HH:mm')
- : target.format('YYYY骞碝鏈圖鏃� HH:mm')
-}
-
-/** 鍚堝苟娑堟伅璇︽儏鍒楄〃閲屾瘡琛岀殑鏃堕棿锛歁M-DD HH:mm */
-export function formatMergeItemTime(timestamp: number): string {
- if (!timestamp) {
- return ''
- }
- return dayjs(timestamp).format('MM-DD HH:mm')
-}
-
-/** RTC 閫氳瘽鏃堕暱锛堢锛夆啋 "00:06" / "1:23:45" */
-export function formatCallDuration(seconds: number | undefined): string {
- const total = Math.max(0, Math.floor(seconds || 0))
- const h = Math.floor(total / 3600)
- const m = Math.floor((total % 3600) / 60)
- const s = total % 60
- const pad = (n: number) => String(n).padStart(2, '0')
- return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`
-}
-
-/** 绉掓暟鏍煎紡鍖栦负 mm:ss */
-export function formatSeconds(seconds?: number): string {
- const value = Math.max(0, Number(seconds || 0))
- const minute = Math.floor(value / 60)
- const second = Math.floor(value % 60)
- return `${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`
-}
-
-/** 鏍煎紡鍖栨棩鏈� */
-export function formatDate(
- date?: Date | number | string,
- format = 'YYYY-MM-DD HH:mm:ss'
-) {
- return dayjs(date).format(format)
-}
-
-/** 鎺ラ�氬埌缁撴潫鐨勯�氳瘽鏃堕暱锛涗换涓�鏃堕棿缂哄け杩斿洖 '-' */
-export function resolveCallDuration(
- acceptTime: Date | string | undefined,
- endTime: Date | string | undefined
-): string {
- if (!acceptTime || !endTime) {
- return '-'
- }
- const seconds = Math.floor((new Date(endTime).getTime() - new Date(acceptTime).getTime()) / 1000)
- return seconds > 0 ? formatCallDuration(seconds) : '-'
-}
diff --git a/src/views/im/utils/user.ts b/src/views/im/utils/user.ts
deleted file mode 100644
index 4760144..0000000
--- a/src/views/im/utils/user.ts
+++ /dev/null
@@ -1,367 +0,0 @@
-// ====================================================================
-// IM 鐢ㄦ埛灞曠ず utility
-// ====================================================================
-// 鑱岃矗锛氱粺涓�鍥炵瓟"鏌愪釜鐢ㄦ埛鍦� UI 涓婂簲璇ュ浣曞睍绀�"锛屽寘鍚細
-// 1. 鏄剧ず鍚嶏紙getFriendDisplayName / getMemberDisplayName / getGroupDisplayName / getSenderDisplayName 绛夛級
-// 2. 涓婁笅鏂囨劅鐭ュ悕锛坱ryGetSenderDisplayName / getSenderRealNickname锛夛細娓叉煋鏃舵寜 conversation 涓婁笅鏂囧疄鏃舵煡 friendStore / groupStore / userStore锛岃澶囨敞 / 缇ゆ樀绉� / 鐪熷疄鏄电О鍙樻洿鍚庢墍鏈夊巻鍙叉秷鎭珛鍗冲搷搴斿紡鍒锋柊
-// 3. 鎬у埆鍥炬爣 / 棰滆壊锛坓etGenderIcon / getGenderColor锛夛細鐢疯摑銆佸コ绮夛紝鏈煡涓嶅睍绀猴紝鎵�鏈� UserInfo 鍗$墖 / 鍒楄〃琛屽叡鐢�
-//
-// 鍛藉悕绾﹀畾锛氭樉绀哄悕鐩稿叧鍑芥暟涓�寰嬩娇鐢� displayName锛屼笌 friend.displayName / member.displayUserName 瀛楁瀵归綈
-// ====================================================================
-
-import type { Conversation, Friend, Group, User } from '../home/types'
-
-import { CommonStatusEnum } from '../../../packages/constants/src'
-import { useUserStore } from '../../../packages/stores/src'
-
-import { getCurrentUserId } from '#/views/im/utils/auth'
-
-import { useConversationStore } from '../home/store/conversationStore'
-import { useFriendStore } from '../home/store/friendStore'
-import { useGroupStore } from '../home/store/groupStore'
-import { useImUiStore } from '../home/store/uiStore'
-import {
- IM_AT_ALL_NICKNAME,
- IM_AT_ALL_USER_ID,
- ImConversationType,
- ImFriendAddSource
-} from './constants'
-import { type MentionCandidate } from './message'
-
-const SystemUserSexEnum = {
- FEMALE: 2,
- MALE: 1,
- UNKNOWN: 0
-} as const
-
-// 鍊欓�夌己澶卞満鏅殑绋冲畾绌烘暟缁勶紱璁� textMentions computed 鍦ㄩ潪 TEXT / 闈炵兢鑱� / 鏃� @ 鏃惰繑鍥炲悓涓�寮曠敤锛�
-// MessageBubble 鐨� textSegments 鎵嶄笉浼氳窡鐫�鏃犺皳閲嶇畻
-const EMPTY_MENTIONS: MentionCandidate[] = []
-
-/**
- * 鏄惁鍘嗗彶閫�缇ょ兢锛歫oinStatus 涓� DISABLE锛堝凡閫�缇� / 琚Щ闄わ級
- *
- * /im/group/list 浼氬甫鍥炲巻鍙查��缇ょ兢锛堜緵灞曠ず鍘嗗彶娑堟伅鐨勭兢鍚� / 澶村儚锛夛紱杩欑被缇ゅ簲绂佹鍙戦�併�侀殣钘忕兢鎿嶄綔鍏ュ彛銆佷笉鍙杞彂 / 鎺ㄨ崘閫変腑
- */
-export function isGroupQuit(group?: Group | null): boolean {
- return group?.joinStatus === CommonStatusEnum.DISABLE
-}
-
-/**
- * 绉佽亰濂藉弸鏄剧ず鍚嶏細澶囨敞 > 鐪熷疄鏄电О
- *
- * displayName 鏄�屾垜瀵硅繖涓汉鐨勭浜虹О鍛笺�嶅睘浜庢垜鐨勬暟鎹紝鍒犲ソ鍙嬶紙DISABLE锛変篃淇濈暀锛涘垹浜嗗啀鍔犲洖鏉ユ椂澶囨敞鑷劧寤剁画锛屽巻鍙叉秷鎭噷浠嶄互澶囨敞杈ㄨ瘑
- */
-export function getFriendDisplayName(friend: Pick<Friend, 'displayName' | 'nickname'>): string {
- return friend.displayName || friend.nickname
-}
-
-/**
- * 缇ゆ垚鍛樻樉绀哄悕锛氬ソ鍙嬪娉� > 鐢ㄦ埛缇ゅ娉紙displayUserName锛� > 鐪熷疄鏄电О
- *
- * WeChat 浼樺厛绾э細濂藉弸澶囨敞鏄�"鎴�"瀵硅鎴愬憳鐨勭浜虹О鍛硷紝鏈�楂樹紭鍏堬紱鍏舵鏄� ta 鍦ㄧ兢鍐呰嚜瀹氫箟鏄电О锛涙渶鍚庣湡瀹炴樀绉板厹搴曪紱璋冪敤鏂规嬁鍒� friend 鎵嶄紶鍏ワ紝娌℃嬁鍒帮紙闄岀敓浜猴級灏卞彧鐢� member 瀛楁闄嶇骇
- */
-export function getMemberDisplayName(
- member: { displayUserName?: string; nickname: string },
- friend?: null | Pick<Friend, 'displayName'>
-): string {
- return friend?.displayName || member.displayUserName || member.nickname
-}
-
-/** 缇ゆ樉绀哄悕锛氬綋鍓嶇敤鎴峰璇ョ兢鐨勫娉紙groupRemark锛� > 缇ゅ悕锛坣ame锛� */
-export function getGroupDisplayName(group: Pick<Group, 'groupRemark' | 'name'>): string {
- return group.groupRemark || group.name
-}
-
-/**
- * 娑堟伅鍙戦�佽�呮樉绀哄悕锛堜弗鏍肩増锛夛細绠椾笉鍑虹湡鍚嶈繑鍥� undefined
- *
- * 缁欓渶瑕�"鏄惁鐪熷悕"淇″彿鐨勮皟鐢ㄦ柟鐢ㄢ�斺�旀瘮濡� conversationStore 鍐冲畾瑕佷笉瑕佸啓 lastSenderDisplayName 蹇収銆佽涓嶈瑙﹀彂 fetchGroupMemberList 鍏滃簳鎷夋垚鍛�
- *
- * GROUP 鍦烘櫙涓� member 宸插氨浣嶄紭鍏� displayUserName / 濂藉弸澶囨敞 / 鐪熷疄鏄电О锛沵ember 缂哄け鏃跺尯鍒嗕袱绉� sender锛�
- * - self 鈫� 鐩存帴鎷� userStore.nickname 鍏滃簳锛堟湰绔案杩滅煡閬撹嚜宸辩殑鏄电О锛屼笉闇�瑕� fetch锛涘悓鏃堕伩鍏� self 閫�缇ゅ悗 GROUP_MEMBER_QUIT 閫氱煡瑙﹀彂鍏滃簳鎷夋垚鍛� 鈫� 403锛�
- * - 鍏朵粬浜� 鈫� 杩斿洖 undefined锛岃 deriveLastSenderDisplayName 璧板厹搴曟媺鎴愬憳
- */
-export function tryGetSenderDisplayName(
- senderId: number,
- conversationType: number,
- conversationTargetId: number
-): string | undefined {
- if (conversationType === ImConversationType.GROUP) {
- const group = useGroupStore().getGroup(conversationTargetId)
- const member = group?.members?.find((m) => m.userId === senderId)
- if (member) {
- const friend = useFriendStore().getFriend(senderId)
- return getMemberDisplayName(member, friend)
- }
- if (senderId === getCurrentUserId()) {
- return useUserStore().userInfo?.nickname || undefined
- }
- return undefined
- }
-
- // PRIVATE / 鏈煡浼氳瘽绫诲瀷锛歴elf 璧� userStore锛屽鏂硅蛋 friend
- if (senderId === getCurrentUserId()) {
- return useUserStore().userInfo?.nickname || undefined
- }
- if (conversationType === ImConversationType.PRIVATE) {
- const friend = useFriendStore().getFriend(senderId)
- return friend ? getFriendDisplayName(friend) : undefined
- }
- return undefined
-}
-
-/**
- * 娑堟伅鍙戦�佽�呮樉绀哄悕锛氭覆鏌撴椂瀹炴椂绠楋紝鎸� WeChat 浼樺厛绾�
- *
- * - 鑷繁锛歶serStore.nickname
- * - 绉佽亰瀵规柟锛氬ソ鍙嬪娉� > 鐪熷疄鏄电О
- * - 缇よ亰瀵规柟锛氬ソ鍙嬪娉� > 缇ゅ娉紙displayUserName锛� > 鐪熷疄鏄电О
- * - 鏌ヤ笉鍒帮細fallbackName || (self 璧� userStore.nickname) || String(senderId)
- */
-export function getSenderDisplayName(
- senderId: number,
- conversationType: number,
- conversationTargetId: number,
- fallbackName?: string
-): string {
- const real = tryGetSenderDisplayName(senderId, conversationType, conversationTargetId)
- if (real) {
- return real
- }
- if (fallbackName) {
- return fallbackName
- }
- // self 鍦� GROUP members 娌″姞杞芥椂锛岃嚦灏戠敤鐪熷疄鏄电О鍏滃簳娓叉煋锛堟瘮 String(senderId) 鍙嬪ソ锛夛紱鍏滃簳鎷夋垚鍛樼敱 conversationStore 瑙﹀彂锛屽洖鏉ュ悗 try 鐗堟湰鑳藉懡涓湡鍚嶈嚜鐒跺埛鏂�
- const userStore = useUserStore()
- if (senderId === getCurrentUserId()) {
- return userStore.userInfo?.nickname || String(senderId)
- }
- return String(senderId)
-}
-
-/**
- * 娑堟伅鍙戦�佽�呫�岀湡瀹炴樀绉般�嶏細姘歌繙鏄� nickname锛屼笉鎺哄娉�
- *
- * - 鑷繁锛歶serStore.nickname
- * - 绉佽亰瀵规柟锛歠riend.nickname
- * - 缇よ亰瀵规柟锛歮ember.nickname
- *
- * 涓撶粰 UserAvatar 鐨� :name 鐢ㄢ�斺�旇壊鍗¢瀛楁瘝 / alt 鏂囨湰瑕佷繚璇佸悓涓�涓汉鍦ㄦ墍鏈夌晫闈竴鑷达紝涓嶈窡澶囨敞鍙�
- */
-export function getSenderRealNickname(
- senderId: number,
- conversationType: number,
- conversationTargetId: number
-): string {
- const userStore = useUserStore()
- const selfUserId = getCurrentUserId()
-
- // 缇よ亰鍏堣蛋 member.nickname锛坰elf 涔熸槸 member锛夛紝寮傚父鏃跺啀璧� self / senderId 鍏滃簳
- if (conversationType === ImConversationType.GROUP) {
- const group = useGroupStore().getGroup(conversationTargetId)
- const member = group?.members?.find((m) => m.userId === senderId)
- if (member?.nickname) {
- return member.nickname
- }
- if (senderId === selfUserId) {
- return userStore.userInfo?.nickname || String(senderId)
- }
- return String(senderId)
- }
-
- if (conversationType === ImConversationType.PRIVATE) {
- if (senderId === selfUserId) {
- return userStore.userInfo?.nickname || String(senderId)
- }
- const friend = useFriendStore().getFriend(senderId)
- return friend?.nickname || String(senderId)
- }
-
- if (senderId === selfUserId) {
- return userStore.userInfo?.nickname || String(senderId)
- }
- return String(senderId)
-}
-
-/**
- * 娑堟伅鍙戦�佽�呭ご鍍忥紱鎸� conversation 涓婁笅鏂囧疄鏃舵煡 group.members / friend / userStore
- *
- * - 鑷繁锛歶serStore.avatar
- * - 绉佽亰瀵规柟锛歠riend.avatar
- * - 缇よ亰瀵规柟锛氬厛 member.avatar锛岀己鍒欓檷绾� friend.avatar
- * - 鏌ヤ笉鍒帮細杩斿洖绌轰覆锛岃皟鐢ㄦ柟璧� UserAvatar 鑹插崱鍏滃簳
- */
-export function getSenderAvatar(
- senderId: number,
- conversationType: number,
- conversationTargetId: number
-): string {
- const userStore = useUserStore()
- if (senderId === getCurrentUserId()) {
- return userStore.userInfo?.avatar || ''
- }
- if (conversationType === ImConversationType.GROUP) {
- const group = useGroupStore().getGroup(conversationTargetId)
- const member = group?.members?.find((m) => m.userId === senderId)
- if (member?.avatar) {
- return member.avatar
- }
- }
- return useFriendStore().getFriend(senderId)?.avatar || ''
-}
-
-/**
- * 缇ゆ秷鎭� @ mention 鍊欓�夛細鎸� atUserIds 鍙嶆煡缇ゆ垚鍛橈紝name 鏀堕泦鎵�鏈夊彲鑳藉瓧闈㈤噺锛堝惈濂藉弸澶囨敞 / 缇よ嚜瀹氫箟鏄电О锛夛紝displayName 缁熶竴鎸囧悜鐪熷疄鏄电О
- *
- * 鍚屽瓧闈㈤噺琚 userId 鍏变韩鏃舵爣璁� ambiguous锛岀敱 parser 鏁存鍚冩垚鏅�氭枃鏈紱
- * 鐩存帴鍓旈櫎浼氳鐭墠缂�鍊欓�夛紙濡傘�孈寮犮�嶏級鎶㈠悆銆孈寮犱笁銆嶇殑鍓嶇紑锛岄敊缁戝埌鍞竴鐨勩�屽紶銆嶇敤鎴�
- */
-export function getMentionCandidates(
- atUserIds: number[] | undefined,
- conversation: null | Pick<Conversation, 'targetId' | 'type'> | undefined
-): MentionCandidate[] {
- if (!atUserIds || atUserIds.length === 0) {
- return EMPTY_MENTIONS
- }
- if (!conversation || conversation.type !== ImConversationType.GROUP) {
- return EMPTY_MENTIONS
- }
- // 缇ゆ垚鍛橀寤� Map锛岄伩鍏嶆瘡涓� atUserId 璧颁竴娆� array find锛園鍏ㄤ綋鎴愬憳鍦烘櫙涓嬫垚鍛樻暟 脳 atUserIds 鏄� N虏锛�
- const members = useGroupStore().getGroup(conversation.targetId)?.members || []
- const memberById = new Map(members.map((m) => [m.userId, m]))
- const friendStore = useFriendStore()
- const candidates: MentionCandidate[] = []
- const seen = new Set<string>()
- for (const userId of atUserIds) {
- // @鍏ㄤ綋鎴愬憳鏄櫄鎷熶吉鎴愬憳锛寀serId = -1 鍦� group.members 閲屾煡涓嶅埌锛屾敞鍏ュ瓧闈㈤噺銆屾墍鏈変汉銆嶅�欓��
- if (userId === IM_AT_ALL_USER_ID) {
- const key = `${IM_AT_ALL_USER_ID}#${IM_AT_ALL_NICKNAME}`
- if (!seen.has(key)) {
- seen.add(key)
- candidates.push({
- userId: IM_AT_ALL_USER_ID,
- name: IM_AT_ALL_NICKNAME,
- displayName: IM_AT_ALL_NICKNAME
- })
- }
- continue
- }
- const member = memberById.get(userId)
- const friend = friendStore.getFriend(userId)
- const nickname = (member?.nickname || friend?.nickname || '').trim()
- if (!nickname) {
- continue
- }
- for (const literal of [nickname, friend?.displayName, member?.displayUserName]) {
- const trimmed = (literal || '').trim()
- if (!trimmed) {
- continue
- }
- const key = `${userId}#${trimmed}`
- if (seen.has(key)) {
- continue
- }
- seen.add(key)
- candidates.push({ userId, name: trimmed, displayName: nickname })
- }
- }
- const nameCount = new Map<string, number>()
- for (const candidate of candidates) {
- nameCount.set(candidate.name, (nameCount.get(candidate.name) ?? 0) + 1)
- }
- for (const candidate of candidates) {
- if ((nameCount.get(candidate.name) ?? 0) > 1) {
- candidate.ambiguous = true
- }
- }
- return candidates
-}
-
-/**
- * 鏂囨湰 / tip 閲� mention 娈电偣鍑荤殑缁熶竴鍏ュ彛锛氭淳鐢� User 寮� UserInfoCard
- *
- * 澶村儚璧� getSenderAvatar锛涙樀绉板彇鐪熷疄鏄电О锛宖riend / member 閮芥煡涓嶅埌鏃剁敤 fallbackName 鍏滃簳锛堝鐏版潯 tip 娈电殑瀛楅潰閲忔枃鏈級锛�
- * addSource 缇よ亰甯� GROUP + 缇ゅ悕锛堢敤浜庛�屽姞涓哄ソ鍙嬨�嶇殑鏉ユ簮 + 澶囨敞寮规枃妗堬級锛岀鑱婇檷绾� SEARCH锛�
- * @鍏ㄤ綋鎴愬憳 鏄箍鎾� mention 娌℃湁鍏蜂綋鐢ㄦ埛瀹炰綋锛屽榻愬井淇� PC 涓嶅脊鍗$墖
- */
-export function openMentionUserInfoCardAtEvent(
- userId: number,
- event: MouseEvent,
- fallbackName?: string
-): void {
- if (userId === IM_AT_ALL_USER_ID) {
- return
- }
- const conversation = useConversationStore().activeConversation
- const isGroup = conversation?.type === ImConversationType.GROUP
- const group = isGroup && conversation ? useGroupStore().getGroup(conversation.targetId) : undefined
- const member = group?.members?.find((m) => m.userId === userId)
- const friend = useFriendStore().getFriend(userId)
- const user: User = {
- id: userId,
- nickname: friend?.nickname || member?.nickname || fallbackName || String(userId),
- avatar: getSenderAvatar(userId, conversation?.type ?? 0, conversation?.targetId ?? 0)
- }
- useImUiStore().openUserInfoCardAtEvent(
- user,
- event,
- isGroup ? ImFriendAddSource.GROUP : ImFriendAddSource.SEARCH,
- isGroup ? group?.name || '' : ''
- )
-}
-
-/** 鎬у埆鍥炬爣锛沀NKNOWN / null / undefined 涓�寰嬩笉灞曠ず锛屽榻愬井淇$暀鐧� */
-export function getGenderIcon(sex?: number): string {
- if (sex === SystemUserSexEnum.MALE) {
- return 'mdi:human-male'
- }
- if (sex === SystemUserSexEnum.FEMALE) {
- return 'mdi:human-female'
- }
- return ''
-}
-
-/** 鎬у埆鍥炬爣涓婚鑹诧細鐢疯摑銆佸コ绮� */
-export function getGenderColor(sex?: number): string {
- if (sex === SystemUserSexEnum.MALE) {
- return '#5b97f5'
- }
- if (sex === SystemUserSexEnum.FEMALE) {
- return '#f56c92'
- }
- return ''
-}
-
-/** 澶村儚鑹插崱搴曡壊璋冭壊鏉匡紙鍙傝�冨井淇★級 */
-const AVATAR_BG_COLORS = ['#07C160', '#1A95FF', '#FA9D3B', '#9163E0', '#F76760', '#1ABC9C']
-
-/** 澶村儚鑹插崱鏂囧瓧锛氫腑鏂囧彇棣栧瓧銆佽嫳鏂囧彇鍓� 2 瀛楁瘝澶у啓銆佸叾浠栧彇棣栧瓧澶у啓銆佺┖鍚嶈繑鍥炵┖涓� */
-export function getAvatarText(name?: string): string {
- const trimmed = name?.trim()
- if (!trimmed) {
- return ''
- }
- const first = trimmed.charAt(0)
- const code = first.codePointAt(0) ?? 0
- if (code >= 0x4E_00 && code <= 0x9F_A5) {
- return first
- }
- const letters = trimmed.match(/[A-Za-z]/g)
- if (!letters || letters.length === 0) {
- return first.toUpperCase()
- }
- return letters.slice(0, 2).join('').toUpperCase()
-}
-
-/** 澶村儚鑹插崱搴曡壊锛氭寜 name charCode 涔嬪拰鍙栬皟鑹叉澘鑹诧紝绌哄悕璧伴粯璁ょ伆 */
-export function getAvatarBgColor(name?: string): string {
- if (!name) {
- return '#909399'
- }
- let hash = 0
- for (let i = 0; i < name.length; i++) {
- hash += name.codePointAt(i) ?? 0
- }
- return AVATAR_BG_COLORS[hash % AVATAR_BG_COLORS.length] || '#909399'
-}
diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue
index 9d1e53e..64b8d0f 100644
--- a/src/views/infra/apiAccessLog/index.vue
+++ b/src/views/infra/apiAccessLog/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiAccessLogApi } from '#/api/infra/api-access-log';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -68,12 +68,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绯荤粺鏃ュ織" url="https://doc.iocoder.cn/system-log/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="API 璁块棶鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue
index 8a027da..434a5bd 100644
--- a/src/views/infra/apiErrorLog/index.vue
+++ b/src/views/infra/apiErrorLog/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraApiErrorLogApi } from '#/api/infra/api-error-log';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { InfraApiErrorLogProcessStatusEnum } from '../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
@@ -90,12 +90,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绯荤粺鏃ュ織" url="https://doc.iocoder.cn/system-log/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="API 閿欒鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue
index 968e9b4..548e9a4 100644
--- a/src/views/infra/codegen/index.vue
+++ b/src/views/infra/codegen/index.vue
@@ -6,7 +6,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -193,24 +193,7 @@
initDataSourceConfig();
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浠g爜鐢熸垚锛堝崟琛級"
- url="https://doc.iocoder.cn/new-feature/"
- />
- <DocAlert
- title="浠g爜鐢熸垚锛堟爲琛級"
- url="https://doc.iocoder.cn/new-feature/tree/"
- />
- <DocAlert
- title="浠g爜鐢熸垚锛堜富瀛愯〃锛�"
- url="https://doc.iocoder.cn/new-feature/master-sub/"
- />
- <DocAlert title="鍗曞厓娴嬭瘯" url="https://doc.iocoder.cn/unit-test/" />
- </template>
-
- <ImportModal @success="handleRefresh" />
+ <Page auto-content-height><ImportModal @success="handleRefresh" />
<PreviewModal />
<Grid table-title="浠g爜鐢熸垚鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/infra/druid/index.vue b/src/views/infra/druid/index.vue
index ede1a5c..c2d7efb 100644
--- a/src/views/infra/druid/index.vue
+++ b/src/views/infra/druid/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { getConfigKey } from '#/api/infra/config';
@@ -22,15 +22,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鏁版嵁搴� MyBatis" url="https://doc.iocoder.cn/mybatis/" />
- <DocAlert
- title="澶氭暟鎹簮锛堣鍐欏垎绂伙級"
- url="https://doc.iocoder.cn/dynamic-datasource/"
- />
- </template>
-
- <IFrame v-if="!loading" v-loading="loading" :src="src" />
+ <Page auto-content-height><IFrame v-if="!loading" v-loading="loading" :src="src" />
</Page>
</template>
diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue
index feab932..a6717fa 100644
--- a/src/views/infra/job/index.vue
+++ b/src/views/infra/job/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { InfraJobStatusEnum } from '../../../packages/constants/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../packages/utils/src';
@@ -199,14 +199,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="瀹氭椂浠诲姟" url="https://doc.iocoder.cn/job/" />
- <DocAlert title="寮傛浠诲姟" url="https://doc.iocoder.cn/async-task/" />
- <DocAlert title="娑堟伅闃熷垪" url="https://doc.iocoder.cn/message-queue/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<DetailModal />
<Grid table-title="瀹氭椂浠诲姟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/infra/job/logger/index.vue b/src/views/infra/job/logger/index.vue
index 59b6214..ecf5c85 100644
--- a/src/views/infra/job/logger/index.vue
+++ b/src/views/infra/job/logger/index.vue
@@ -4,7 +4,7 @@
import { useRoute } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -65,14 +65,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="瀹氭椂浠诲姟" url="https://doc.iocoder.cn/job/" />
- <DocAlert title="寮傛浠诲姟" url="https://doc.iocoder.cn/async-task/" />
- <DocAlert title="娑堟伅闃熷垪" url="https://doc.iocoder.cn/message-queue/" />
- </template>
-
- <DetailModal />
+ <Page auto-content-height><DetailModal />
<Grid table-title="浠诲姟鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/infra/redis/index.vue b/src/views/infra/redis/index.vue
index 38ae92e..d6a91c4 100644
--- a/src/views/infra/redis/index.vue
+++ b/src/views/infra/redis/index.vue
@@ -3,7 +3,7 @@
import { onMounted, ref } from 'vue';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import { Card } from 'ant-design-vue';
@@ -30,13 +30,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="Redis 缂撳瓨" url="https://doc.iocoder.cn/redis-cache/" />
- <DocAlert title="鏈湴缂撳瓨" url="https://doc.iocoder.cn/local-cache/" />
- </template>
-
- <Card title="Redis 姒傝">
+ <Page auto-content-height><Card title="Redis 姒傝">
<Info :redis-data="redisData" />
</Card>
diff --git a/src/views/infra/server/index.vue b/src/views/infra/server/index.vue
index 063a749..7c2d491 100644
--- a/src/views/infra/server/index.vue
+++ b/src/views/infra/server/index.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { getConfigKey } from '#/api/infra/config';
@@ -25,11 +25,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鏈嶅姟鐩戞帶" url="https://doc.iocoder.cn/server-monitor/" />
- </template>
-
- <IFrame v-if="!loading" v-loading="loading" :src="src" />
+ <Page auto-content-height><IFrame v-if="!loading" v-loading="loading" :src="src" />
</Page>
</template>
diff --git a/src/views/infra/skywalking/index.vue b/src/views/infra/skywalking/index.vue
index 180450f..74542d7 100644
--- a/src/views/infra/skywalking/index.vue
+++ b/src/views/infra/skywalking/index.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { getConfigKey } from '#/api/infra/config';
@@ -22,11 +22,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鏈嶅姟鐩戞帶" url="https://doc.iocoder.cn/server-monitor/" />
- </template>
-
- <IFrame v-if="!loading" v-loading="loading" :src="src" />
+ <Page auto-content-height><IFrame v-if="!loading" v-loading="loading" :src="src" />
</Page>
</template>
diff --git a/src/views/infra/swagger/index.vue b/src/views/infra/swagger/index.vue
index eb05e76..4547503 100644
--- a/src/views/infra/swagger/index.vue
+++ b/src/views/infra/swagger/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { getConfigKey } from '#/api/infra/config';
@@ -23,11 +23,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鎺ュ彛鏂囨。" url="https://doc.iocoder.cn/api-doc/" />
- </template>
-
- <IFrame v-if="!loading" :src="src" />
+ <Page auto-content-height><IFrame v-if="!loading" :src="src" />
</Page>
</template>
diff --git a/src/views/infra/webSocket/index.vue b/src/views/infra/webSocket/index.vue
index 13aca5a..4b5b481 100644
--- a/src/views/infra/webSocket/index.vue
+++ b/src/views/infra/webSocket/index.vue
@@ -3,7 +3,7 @@
import { computed, onMounted, ref, watchEffect } from 'vue';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import { IconifyIcon } from '../../../packages/icons/src';
import { useAccessStore } from '../../../packages/stores/src';
import { formatDate } from '../../../packages/utils/src';
@@ -174,15 +174,7 @@
</script>
<template>
- <Page>
- <template #doc>
- <DocAlert
- title="WebSocket 瀹炴椂閫氫俊"
- url="https://doc.iocoder.cn/websocket/"
- />
- </template>
-
- <div class="mt-4 flex flex-col gap-4 md:flex-row">
+ <Page><div class="mt-4 flex flex-col gap-4 md:flex-row">
<!-- 宸︿晶锛氬缓绔嬭繛鎺ャ�佸彂閫佹秷鎭� -->
<Card :bordered="false" class="w-full md:w-1/2">
<template #title>
diff --git a/src/views/iot/alert/config/data.ts b/src/views/iot/alert/config/data.ts
deleted file mode 100644
index 0b70243..0000000
--- a/src/views/iot/alert/config/data.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { AlertConfigApi } from '#/api/iot/alert/config';
-
-import { markRaw } from 'vue';
-
-import {
- CommonStatusEnum,
- DICT_TYPE,
- IotAlertReceiveTypeEnum,
-} from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getSimpleRuleSceneList } from '#/api/iot/rule/scene';
-import { getSimpleUserList } from '#/api/system/user';
-import { getRangePickerDefaultProps } from '#/utils';
-import { MailTemplateSelect } from '#/views/system/mail/template/components';
-import { NotifyTemplateSelect } from '#/views/system/notify/template/components';
-import { SmsTemplateSelect } from '#/views/system/sms/template/components';
-
-function hasReceiveType(values: Partial<Record<string, any>>, type: number) {
- return (
- Array.isArray(values.receiveTypes) && values.receiveTypes.includes(type)
- );
-}
-
-/** 鏂板/淇敼鍛婅閰嶇疆鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '閰嶇疆鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ厤缃悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '閰嶇疆鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ラ厤缃弿杩�',
- rows: 3,
- },
- },
- {
- fieldName: 'level',
- label: '鍛婅绾у埆',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'),
- placeholder: '璇烽�夋嫨鍛婅绾у埆',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '閰嶇疆鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: CommonStatusEnum.ENABLE,
- rules: 'required',
- },
- {
- fieldName: 'sceneRuleIds',
- label: '鍏宠仈鍦烘櫙鑱斿姩瑙勫垯',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleRuleSceneList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨鍏宠仈鐨勫満鏅仈鍔ㄨ鍒�',
- },
- defaultValue: [],
- rules: 'required',
- },
- {
- fieldName: 'receiveUserIds',
- label: '鎺ユ敹鐨勭敤鎴�',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleUserList,
- labelField: 'nickname',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨鎺ユ敹鐨勭敤鎴�',
- },
- defaultValue: [],
- rules: 'required',
- },
- {
- fieldName: 'receiveTypes',
- label: '鎺ユ敹绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_ALERT_RECEIVE_TYPE, 'number'),
- mode: 'multiple',
- placeholder: '璇烽�夋嫨鎺ユ敹绫诲瀷',
- },
- defaultValue: [],
- rules: 'required',
- },
- {
- fieldName: 'smsTemplateCode',
- label: '鐭俊妯℃澘',
- component: markRaw(SmsTemplateSelect),
- dependencies: {
- triggerFields: ['receiveTypes'],
- show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.SMS),
- trigger: async (values, formApi) => {
- if (
- !hasReceiveType(values, IotAlertReceiveTypeEnum.SMS) &&
- values.smsTemplateCode
- ) {
- await formApi.setFieldValue('smsTemplateCode', undefined);
- }
- },
- },
- rules: 'selectRequired',
- },
- {
- fieldName: 'mailTemplateCode',
- label: '閭欢妯℃澘',
- component: markRaw(MailTemplateSelect),
- dependencies: {
- triggerFields: ['receiveTypes'],
- show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL),
- trigger: async (values, formApi) => {
- if (
- !hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL) &&
- values.mailTemplateCode
- ) {
- await formApi.setFieldValue('mailTemplateCode', undefined);
- }
- },
- },
- rules: 'selectRequired',
- },
- {
- fieldName: 'notifyTemplateCode',
- label: '绔欏唴淇℃ā鏉�',
- component: markRaw(NotifyTemplateSelect),
- dependencies: {
- triggerFields: ['receiveTypes'],
- show: (values) =>
- hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY),
- trigger: async (values, formApi) => {
- if (
- !hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY) &&
- values.notifyTemplateCode
- ) {
- await formApi.setFieldValue('notifyTemplateCode', undefined);
- }
- },
- },
- rules: 'selectRequired',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '閰嶇疆鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ厤缃悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '閰嶇疆鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨閰嶇疆鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<AlertConfigApi.AlertConfig>['columns'] {
- return [
- {
- field: 'id',
- title: '閰嶇疆缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '閰嶇疆鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'description',
- title: '閰嶇疆鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'level',
- title: '鍛婅绾у埆',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_ALERT_LEVEL },
- },
- },
- {
- field: 'status',
- title: '閰嶇疆鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'sceneRuleIds',
- title: '鍏宠仈鍦烘櫙鑱斿姩瑙勫垯',
- minWidth: 150,
- formatter: ({ cellValue }) => `${cellValue?.length || 0} 鏉,
- },
- {
- field: 'receiveUserNames',
- title: '鎺ユ敹浜�',
- minWidth: 150,
- },
- {
- field: 'receiveTypes',
- title: '鎺ユ敹绫诲瀷',
- minWidth: 150,
- slots: { default: 'receiveTypes' },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/alert/config/index.vue b/src/views/iot/alert/config/index.vue
deleted file mode 100644
index 4d619ad..0000000
--- a/src/views/iot/alert/config/index.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { AlertConfigApi } from '#/api/iot/alert/config';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteAlertConfig, getAlertConfigPage } from '#/api/iot/alert/config';
-import { DictTag } from '#/components/dict-tag';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍛婅閰嶇疆 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍛婅閰嶇疆 */
-function handleEdit(row: AlertConfigApi.AlertConfig) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍛婅閰嶇疆 */
-async function handleDelete(row: AlertConfigApi.AlertConfig) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteAlertConfig(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAlertConfigPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<AlertConfigApi.AlertConfig>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍛婅閰嶇疆鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍛婅閰嶇疆']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:alert-config:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #receiveTypes="{ row }">
- <DictTag
- v-for="(type, index) in row.receiveTypes"
- :key="index"
- :type="DICT_TYPE.IOT_ALERT_RECEIVE_TYPE"
- :value="type"
- class="mr-1"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:alert-config:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:alert-config:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/alert/config/modules/form.vue b/src/views/iot/alert/config/modules/form.vue
deleted file mode 100644
index d8a3830..0000000
--- a/src/views/iot/alert/config/modules/form.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script lang="ts" setup>
-import type { AlertConfigApi } from '#/api/iot/alert/config';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createAlertConfig,
- getAlertConfig,
- updateAlertConfig,
-} from '#/api/iot/alert/config';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<AlertConfigApi.AlertConfig>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍛婅閰嶇疆'])
- : $t('ui.actionTitle.create', ['鍛婅閰嶇疆']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 140,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as AlertConfigApi.AlertConfig;
- try {
- await (formData.value?.id
- ? updateAlertConfig(data)
- : createAlertConfig(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<AlertConfigApi.AlertConfig>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getAlertConfig(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-3/5" :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/alert/record/data.ts b/src/views/iot/alert/record/data.ts
deleted file mode 100644
index f7bca0b..0000000
--- a/src/views/iot/alert/record/data.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { AlertRecordApi } from '#/api/iot/alert/record';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
-import { getSimpleDeviceList } from '#/api/iot/device/device';
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-let productList: IotProductApi.Product[] = [];
-let deviceList: IotDeviceApi.Device[] = [];
-getSimpleProductList().then((data) => (productList = data));
-getSimpleDeviceList().then((data) => (deviceList = data));
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'configId',
- label: '鍛婅閰嶇疆',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleAlertConfigList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鍛婅閰嶇疆',
- allowClear: true,
- showSearch: true,
- },
- },
- {
- fieldName: 'configLevel',
- label: '鍛婅绾у埆',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'),
- placeholder: '璇烽�夋嫨鍛婅绾у埆',
- allowClear: true,
- },
- },
- {
- fieldName: 'productId',
- label: '浜у搧',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- allowClear: true,
- showSearch: true,
- },
- },
- {
- fieldName: 'deviceId',
- label: '璁惧',
- component: 'ApiSelect',
- componentProps: {
- api: (params?: { productId?: number }) =>
- getSimpleDeviceList(undefined, params?.productId),
- labelField: 'deviceName',
- valueField: 'id',
- placeholder: '璇烽�夋嫨璁惧',
- allowClear: true,
- showSearch: true,
- },
- dependencies: {
- triggerFields: ['productId'],
- componentProps: (values) => {
- return {
- params: { productId: values.productId },
- };
- },
- trigger: (values, formApi) => {
- if (values.deviceId !== undefined) {
- void formApi.setFieldValue('deviceId', undefined);
- }
- },
- },
- },
- {
- fieldName: 'processStatus',
- label: '鏄惁澶勭悊',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- placeholder: '璇烽�夋嫨鏄惁澶勭悊',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<AlertRecordApi.AlertRecord>['columns'] {
- return [
- {
- field: 'id',
- title: '璁板綍缂栧彿',
- minWidth: 80,
- },
- {
- field: 'configName',
- title: '鍛婅鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'configLevel',
- title: '鍛婅绾у埆',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_ALERT_LEVEL },
- },
- },
- {
- field: 'productId',
- title: '浜у搧鍚嶇О',
- minWidth: 120,
- formatter: ({ cellValue }) =>
- productList.find((product) => product.id === cellValue)?.name || '-',
- },
- {
- field: 'deviceId',
- title: '璁惧鍚嶇О',
- minWidth: 120,
- formatter: ({ cellValue }) =>
- deviceList.find((device) => device.id === cellValue)?.deviceName || '-',
- },
- {
- field: 'deviceMessage',
- title: '瑙﹀彂鐨勮澶囨秷鎭�',
- minWidth: 150,
- slots: { default: 'deviceMessage' },
- },
- {
- field: 'processStatus',
- title: '鏄惁澶勭悊',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'processRemark',
- title: '澶勭悊缁撴灉',
- minWidth: 150,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/alert/record/index.vue b/src/views/iot/alert/record/index.vue
deleted file mode 100644
index ee45966..0000000
--- a/src/views/iot/alert/record/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { AlertRecordApi } from '#/api/iot/alert/record';
-
-import { h, ref } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Button, Input, message, Modal, Popover } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getAlertRecordPage, processAlertRecord } from '#/api/iot/alert/record';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-/** 鎶婅澶囨秷鎭簭鍒楀寲鎴愬彲璇诲瓧绗︿覆 */
-function stringifyDeviceMessage(deviceMessage: any) {
- if (!deviceMessage) {
- return '';
- }
- return typeof deviceMessage === 'object'
- ? JSON.stringify(deviceMessage, null, 2)
- : String(deviceMessage);
-}
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 澶勭悊鍛婅璁板綍 */
-function handleProcess(row: AlertRecordApi.AlertRecord) {
- const processRemark = ref('');
- Modal.confirm({
- title: '澶勭悊鍛婅璁板綍',
- content: () =>
- h('div', { class: 'space-y-2' }, [
- h('p', '璇疯緭鍏ュ鐞嗗師鍥狅細'),
- h(Input.TextArea, {
- value: processRemark.value,
- 'onUpdate:value': (val: string) => (processRemark.value = val),
- rows: 3,
- placeholder: '璇疯緭鍏ュ鐞嗗師鍥�',
- }),
- ]),
- async onOk() {
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊...',
- duration: 0,
- });
- try {
- await processAlertRecord(row.id!, processRemark.value);
- message.success('澶勭悊鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
- },
- });
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAlertRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<AlertRecordApi.AlertRecord>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="鍛婅璁板綍鍒楄〃">
- <template #deviceMessage="{ row }">
- <Popover
- v-if="row.deviceMessage"
- placement="topLeft"
- trigger="hover"
- :overlay-style="{ maxWidth: '600px' }"
- >
- <template #content>
- <pre class="text-xs">{{
- stringifyDeviceMessage(row.deviceMessage)
- }}</pre>
- </template>
- <Button size="small" type="link">
- <IconifyIcon icon="ant-design:eye-outlined" class="mr-1" />
- 鏌ョ湅娑堟伅
- </Button>
- </Popover>
- <span v-else class="text-gray-400">-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '澶勭悊',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:alert-record:process'],
- onClick: handleProcess.bind(null, row),
- ifShow: !row.processStatus,
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/data.ts b/src/views/iot/device/device/data.ts
deleted file mode 100644
index bd5689b..0000000
--- a/src/views/iot/device/device/data.ts
+++ /dev/null
@@ -1,342 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getSimpleDeviceGroupList } from '#/api/iot/device/group';
-import { getSimpleProductList } from '#/api/iot/product/product';
-
-/** 鍩虹琛ㄥ崟瀛楁 */
-export function useBasicFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'productId',
- label: '浜у搧',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- },
- dependencies: {
- triggerFields: ['id'],
- disabled: (values: any) => !!values?.id,
- },
- rules: 'required',
- },
- {
- component: 'Input',
- fieldName: 'deviceType',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'deviceName',
- label: 'DeviceName',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� DeviceName',
- },
- dependencies: {
- triggerFields: ['id'],
- disabled: (values: any) => !!values?.id,
- },
- rules: z
- .string()
- .min(4, 'DeviceName 闀垮害涓嶈兘灏戜簬 4 涓瓧绗�')
- .max(32, 'DeviceName 闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�')
- .regex(
- /^[\w.\-:@]{4,32}$/,
- '鏀寔鑻辨枃瀛楁瘝銆佹暟瀛椼�佷笅鍒掔嚎锛坃锛夈�佷腑鍒掔嚎锛�-锛夈�佺偣鍙凤紙.锛夈�佸崐瑙掑啋鍙凤紙:锛夊拰鐗规畩瀛楃@',
- ),
- },
- ];
-}
-
-/** 楂樼骇璁剧疆琛ㄥ崟瀛楁锛堟洿澶氳缃級 */
-export function useAdvancedFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'nickname',
- label: '澶囨敞鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉ㄥ悕绉�',
- },
- rules: z
- .string()
- .refine((value) => {
- const length = value.replaceAll(
- /[\u4E00-\u9FA5\u3040-\u30FF]/g,
- 'aa',
- ).length;
- return length >= 4 && length <= 64;
- }, '澶囨敞鍚嶇О闀垮害闄愬埗涓� 4~64 涓瓧绗︼紝涓枃鍙婃棩鏂囩畻 2 涓瓧绗�')
- .refine(
- (value) => /^[\u4E00-\u9FA5\u3040-\u30FF\w]+$/.test(value),
- '澶囨敞鍚嶇О鍙兘鍖呭惈涓枃銆佽嫳鏂囧瓧姣嶃�佹棩鏂囥�佹暟瀛楀拰涓嬪垝绾匡紙_锛�',
- )
- .optional()
- .or(z.literal('')),
- },
- {
- fieldName: 'picUrl',
- label: '璁惧鍥剧墖',
- component: 'ImageUpload',
- },
- {
- fieldName: 'groupIds',
- label: '璁惧鍒嗙粍',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeviceGroupList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨璁惧鍒嗙粍',
- },
- },
- {
- fieldName: 'serialNumber',
- label: '璁惧搴忓垪鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ澶囧簭鍒楀彿',
- },
- rules: z
- .string()
- .regex(/^[\w-]+$/, '搴忓垪鍙峰彧鑳藉寘鍚瓧姣嶃�佹暟瀛椼�佷腑鍒掔嚎鍜屼笅鍒掔嚎')
- .optional()
- .or(z.literal('')),
- },
- {
- fieldName: 'longitude',
- label: '璁惧缁忓害',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヨ澶囩粡搴�',
- min: -180,
- max: 180,
- precision: 6,
- },
- rules: z
- .number()
- .min(-180, '缁忓害鑼冨洿涓� -180 鍒� 180')
- .max(180, '缁忓害鑼冨洿涓� -180 鍒� 180')
- .optional()
- .nullable(),
- },
- {
- fieldName: 'latitude',
- label: '璁惧绾害',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヨ澶囩含搴�',
- min: -90,
- max: 90,
- precision: 6,
- },
- rules: z
- .number()
- .min(-90, '绾害鑼冨洿涓� -90 鍒� 90')
- .max(90, '绾害鑼冨洿涓� -90 鍒� 90')
- .optional()
- .nullable(),
- },
- ];
-}
-
-/** 璁惧鍒嗙粍琛ㄥ崟 */
-export function useGroupFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'groupIds',
- label: '璁惧鍒嗙粍',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeviceGroupList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨璁惧鍒嗙粍',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 璁惧瀵煎叆琛ㄥ崟 */
-export function useImportFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'file',
- label: '璁惧鏁版嵁',
- component: 'Upload',
- rules: 'required',
- help: '浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢',
- },
- {
- fieldName: 'updateSupport',
- label: '鏄惁瑕嗙洊',
- component: 'Switch',
- componentProps: {
- checkedChildren: '鏄�',
- unCheckedChildren: '鍚�',
- },
- rules: z.boolean().default(false),
- help: '鏄惁鏇存柊宸茬粡瀛樺湪鐨勮澶囨暟鎹�',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'productId',
- label: '浜у搧',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- allowClear: true,
- },
- },
- {
- fieldName: 'deviceName',
- label: 'DeviceName',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� DeviceName',
- allowClear: true,
- },
- },
- {
- fieldName: 'nickname',
- label: '澶囨敞鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'deviceType',
- label: '璁惧绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'),
- placeholder: '璇烽�夋嫨璁惧绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '璁惧鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number'),
- placeholder: '璇烽�夋嫨璁惧鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'groupId',
- label: '璁惧鍒嗙粍',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeviceGroupList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨璁惧鍒嗙粍',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<IotDeviceApi.Device>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'deviceName',
- title: 'DeviceName',
- minWidth: 150,
- slots: { default: 'deviceName' },
- },
- {
- field: 'nickname',
- title: '澶囨敞鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'picUrl',
- title: '璁惧鍥剧墖',
- width: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'productId',
- title: '鎵�灞炰骇鍝�',
- minWidth: 120,
- slots: { default: 'product' },
- },
- {
- field: 'deviceType',
- title: '璁惧绫诲瀷',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE },
- },
- },
- {
- field: 'groupIds',
- title: '鎵�灞炲垎缁�',
- minWidth: 150,
- slots: { default: 'groups' },
- },
- {
- field: 'state',
- title: '璁惧鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_DEVICE_STATE },
- },
- },
- {
- field: 'onlineTime',
- title: '鏈�鍚庝笂绾挎椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/device/device/detail/index.vue b/src/views/iot/device/device/detail/index.vue
deleted file mode 100644
index 9fcce5f..0000000
--- a/src/views/iot/device/device/detail/index.vue
+++ /dev/null
@@ -1,162 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-import { DeviceTypeEnum } from '../../../../../packages/constants/src';
-
-import { message, Tabs } from 'ant-design-vue';
-
-import { getDevice } from '#/api/iot/device/device';
-import { getProduct, ProtocolTypeEnum } from '#/api/iot/product/product';
-import { getThingModelListByProductId } from '#/api/iot/thingmodel';
-
-import DeviceDetailConfig from './modules/config.vue';
-import DeviceDetailsHeader from './modules/header.vue';
-import DeviceDetailsInfo from './modules/info.vue';
-import DeviceDetailsMessage from './modules/message.vue';
-import DeviceModbusConfig from './modules/modbus-config.vue';
-import DeviceDetailsSimulator from './modules/simulator.vue';
-import DeviceDetailsSubDevice from './modules/sub-device.vue';
-import DeviceDetailsThingModel from './modules/thing-model.vue';
-
-const route = useRoute();
-const router = useRouter();
-
-const id = Number(route.params.id);
-const loading = ref(true);
-const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
-const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device);
-const activeTab = ref('info');
-const thingModelList = ref<ThingModelApi.ThingModel[]>([]);
-
-/** 鑾峰彇璁惧璇︽儏 */
-async function getDeviceData(deviceId: number) {
- loading.value = true;
- try {
- device.value = await getDevice(deviceId);
- await getProductData(device.value.productId);
- await getThingModelList(device.value.productId);
- } catch {
- message.error('鑾峰彇璁惧璇︽儏澶辫触');
- } finally {
- loading.value = false;
- }
-}
-
-/** 鑾峰彇浜у搧璇︽儏 */
-async function getProductData(productId: number) {
- try {
- product.value = await getProduct(productId);
- } catch {
- message.error('鑾峰彇浜у搧璇︽儏澶辫触');
- }
-}
-
-/** 鑾峰彇鐗╂ā鍨嬪垪琛� */
-async function getThingModelList(productId: number) {
- try {
- const data = await getThingModelListByProductId(productId);
- thingModelList.value = data || [];
- } catch {
- message.error('鑾峰彇鐗╂ā鍨嬪垪琛ㄥけ璐�');
- thingModelList.value = [];
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- if (!id) {
- message.warning('鍙傛暟閿欒锛岃澶囦笉鑳戒负绌猴紒');
- router.back();
- return;
- }
-
- await getDeviceData(id);
-
- // 澶勭悊 tab 鍙傛暟
- const { tab } = route.query;
- if (tab) {
- activeTab.value = tab as string;
- }
-});
-</script>
-<template>
- <Page>
- <DeviceDetailsHeader
- :device="device"
- :loading="loading"
- :product="product"
- @refresh="() => getDeviceData(id)"
- />
-
- <Tabs v-model:active-key="activeTab" class="mt-4">
- <Tabs.TabPane key="info" tab="璁惧淇℃伅">
- <DeviceDetailsInfo
- v-if="activeTab === 'info' && device.id"
- :device="device"
- :product="product"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="model" tab="鐗╂ā鍨嬫暟鎹�">
- <DeviceDetailsThingModel
- v-if="activeTab === 'model' && device.id"
- :device-id="device.id"
- :thing-model-list="thingModelList"
- />
- </Tabs.TabPane>
- <Tabs.TabPane
- v-if="product.deviceType === DeviceTypeEnum.GATEWAY"
- key="subDevice"
- tab="瀛愯澶囩鐞�"
- >
- <DeviceDetailsSubDevice
- v-if="activeTab === 'subDevice' && device.id"
- :device-id="device.id"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="log" tab="璁惧娑堟伅">
- <DeviceDetailsMessage
- v-if="activeTab === 'log' && device.id"
- :device-id="device.id"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="simulator" tab="妯℃嫙璁惧">
- <DeviceDetailsSimulator
- v-if="activeTab === 'simulator' && device.id"
- :device="device"
- :product="product"
- :thing-model-list="thingModelList"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="config" tab="璁惧閰嶇疆">
- <DeviceDetailConfig
- v-if="activeTab === 'config' && device.id"
- :device="device"
- @success="() => getDeviceData(id)"
- />
- </Tabs.TabPane>
- <Tabs.TabPane
- v-if="
- [
- ProtocolTypeEnum.MODBUS_TCP_CLIENT,
- ProtocolTypeEnum.MODBUS_TCP_SERVER,
- ].includes(product.protocolType as ProtocolTypeEnum)
- "
- key="modbus"
- tab="Modbus 閰嶇疆"
- >
- <DeviceModbusConfig
- v-if="activeTab === 'modbus' && device.id"
- :device="device"
- :product="product"
- :thing-model-list="thingModelList"
- />
- </Tabs.TabPane>
- </Tabs>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/config.vue b/src/views/iot/device/device/detail/modules/config.vue
deleted file mode 100644
index dd800e5..0000000
--- a/src/views/iot/device/device/detail/modules/config.vue
+++ /dev/null
@@ -1,173 +0,0 @@
-<!-- 璁惧閰嶇疆 -->
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { computed, ref, watchEffect } from 'vue';
-
-import { IotDeviceMessageMethodEnum } from '../../../../../../packages/constants/src';
-
-import { Alert, Button, message, Popconfirm, Textarea } from 'ant-design-vue';
-
-import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device';
-
-const props = defineProps<{
- device: IotDeviceApi.Device;
-}>();
-
-const emit = defineEmits<{
- (e: 'success'): void; // 瀹氫箟 success 浜嬩欢锛屼笉闇�瑕佸弬鏁�
-}>();
-
-const loading = ref(false); // 鍔犺浇涓�
-const pushLoading = ref(false); // 鎺ㄩ�佸姞杞戒腑
-const saveLoading = ref(false); // 淇濆瓨鍔犺浇涓�
-const config = ref<any>({}); // 鍙瓨鍌� config 瀛楁
-const configString = ref(''); // 鐢ㄤ簬缂栬緫鍣ㄧ殑瀛楃涓叉牸寮�
-
-/** 鐩戝惉 props.device 鐨勫彉鍖栵紝鍙洿鏂� config 瀛楁 */
-watchEffect(() => {
- try {
- config.value = props.device.config ? JSON.parse(props.device.config) : {};
- // 灏嗗璞¤浆鎹负鏍煎紡鍖栫殑 JSON 瀛楃涓�
- configString.value = JSON.stringify(config.value, null, 2);
- } catch {
- config.value = {};
- configString.value = '{}';
- }
-});
-
-const isEditing = ref(false); // 缂栬緫鐘舵��
-
-/** 鏍煎紡鍖栫殑閰嶇疆鐢ㄤ簬鍙灞曠ず */
-const formattedConfig = computed(() => {
- try {
- if (typeof config.value === 'string') {
- return JSON.stringify(JSON.parse(config.value), null, 2);
- }
- return JSON.stringify(config.value, null, 2);
- } catch {
- return JSON.stringify(config.value, null, 2);
- }
-});
-
-/** 鍚敤缂栬緫妯″紡鐨勫嚱鏁� */
-function handleEdit() {
- isEditing.value = true;
- // 閲嶆柊鍚屾缂栬緫鍣ㄥ唴瀹�
- configString.value = JSON.stringify(config.value, null, 2);
-}
-
-/** 鍙栨秷缂栬緫鐨勫嚱鏁� */
-function handleCancelEdit() {
- try {
- config.value = props.device.config ? JSON.parse(props.device.config) : {};
- configString.value = JSON.stringify(config.value, null, 2);
- } catch {
- config.value = {};
- configString.value = '{}';
- }
- isEditing.value = false;
-}
-
-/** 淇濆瓨閰嶇疆鐨勫嚱鏁� */
-async function saveConfig() {
- // 楠岃瘉 JSON 鏍煎紡
- try {
- config.value = JSON.parse(configString.value);
- } catch (error) {
- console.error('JSON鏍煎紡閿欒:', error);
- message.error('JSON鏍煎紡閿欒锛岃淇鍚庡啀鎻愪氦锛�');
- return;
- }
- saveLoading.value = true;
- try {
- await updateDeviceConfig();
- isEditing.value = false;
- } finally {
- saveLoading.value = false;
- }
-}
-
-/** 閰嶇疆鎺ㄩ�佸鐞嗗嚱鏁� */
-async function handleConfigPush() {
- pushLoading.value = true;
- try {
- // 璋冪敤閰嶇疆鎺ㄩ�佹帴鍙�
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.CONFIG_PUSH.method,
- params: config.value,
- });
- // 鎻愮ず鎴愬姛
- message.success('閰嶇疆鎺ㄩ�佹垚鍔燂紒');
- } finally {
- pushLoading.value = false;
- }
-}
-
-/** 鏇存柊璁惧閰嶇疆 */
-async function updateDeviceConfig() {
- try {
- // 鎻愪氦璇锋眰
- loading.value = true;
- await updateDevice({
- id: props.device.id,
- config: JSON.stringify(config.value),
- } as IotDeviceApi.Device);
- message.success('鏇存柊鎴愬姛锛�');
- // 瑙﹀彂 success 浜嬩欢
- emit('success');
- } finally {
- loading.value = false;
- }
-}
-</script>
-
-<template>
- <div>
- <!-- 浣跨敤璇存槑鎻愮ず -->
- <Alert
- class="!mb-4"
- description="濡傞渶缂栬緫鏂囦欢锛岃鐐瑰嚮涓嬫柟缂栬緫鎸夐挳"
- message="鏀寔杩滅▼鏇存柊璁惧鐨勯厤缃枃浠�(JSON 鏍煎紡)锛屽彲浠ュ湪涓嬫柟缂栬緫閰嶇疆妯℃澘锛屽璁惧鐨勭郴缁熷弬鏁般�佺綉缁滃弬鏁扮瓑杩涜杩滅▼閰嶇疆銆傞厤缃畬鎴愬悗锛岄渶鐐瑰嚮銆岄厤缃帹閫併�嶆寜閽紝璁惧鍗冲彲杩涜杩滅▼閰嶇疆銆�"
- show-icon
- type="info"
- />
-
- <!-- 浠g爜瑙嗗浘 - 鍙灞曠ず -->
- <pre
- v-if="!isEditing"
- class="m-0 h-[460px] overflow-y-auto whitespace-pre-wrap break-words rounded border border-[#d9d9d9] bg-[#f5f5f5] p-3 font-mono text-[13px] leading-normal text-[#333] dark:border-[#3a3a3a] dark:bg-[#1a1a1a] dark:text-gray-300"
- v-text="formattedConfig"
- ></pre>
-
- <!-- 缂栬緫鍣ㄨ鍥� - 鍙紪杈� -->
- <Textarea
- v-else
- v-model:value="configString"
- class="!h-[460px] font-mono text-[13px]"
- placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勯厤缃俊鎭�"
- />
-
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="mt-4 flex justify-center gap-2">
- <Button v-if="isEditing" @click="handleCancelEdit">鍙栨秷</Button>
- <Button
- v-if="isEditing"
- :loading="saveLoading"
- type="primary"
- @click="saveConfig"
- >
- 淇濆瓨
- </Button>
- <Button v-else @click="handleEdit">缂栬緫</Button>
- <Popconfirm
- v-if="!isEditing"
- title="纭畾瑕佹帹閫侀厤缃埌璁惧鍚楋紵姝ゆ搷浣滃皢杩滅▼鏇存柊璁惧閰嶇疆銆�"
- @confirm="handleConfigPush"
- >
- <Button :loading="pushLoading" type="primary"> 閰嶇疆鎺ㄩ�� </Button>
- </Popconfirm>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/header.vue b/src/views/iot/device/device/detail/modules/header.vue
deleted file mode 100644
index ebe2c1c..0000000
--- a/src/views/iot/device/device/detail/modules/header.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { useRouter } from 'vue-router';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { Button, Card, Descriptions, message } from 'ant-design-vue';
-
-import DeviceForm from '../../modules/form.vue';
-
-interface Props {
- product: IotProductApi.Product;
- device: IotDeviceApi.Device;
- loading?: boolean;
-}
-
-withDefaults(defineProps<Props>(), {
- loading: false,
-});
-
-const emit = defineEmits<{
- refresh: [];
-}>();
-
-const router = useRouter();
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: DeviceForm,
- destroyOnClose: true,
-});
-
-/** 澶嶅埗鍒板壀璐存澘 */
-async function copyToClipboard(text: string | undefined) {
- if (!text) return;
- try {
- await navigator.clipboard.writeText(text);
- message.success('澶嶅埗鎴愬姛');
- } catch {
- message.error('澶嶅埗澶辫触');
- }
-}
-
-/** 璺宠浆鍒颁骇鍝佽鎯呴〉闈� */
-function goToProductDetail(productId: number | undefined) {
- if (productId) {
- router.push({ name: 'IoTProductDetail', params: { id: productId } });
- }
-}
-
-/** 鎵撳紑缂栬緫琛ㄥ崟 */
-function openEditForm(row: IotDeviceApi.Device) {
- formModalApi.setData(row).open();
-}
-</script>
-<template>
- <div class="mb-4">
- <FormModal @success="emit('refresh')" />
-
- <div class="flex items-start justify-between">
- <div>
- <h2 class="text-xl font-bold">{{ device.deviceName }}</h2>
- </div>
- <div class="flex gap-2">
- <Button
- v-if="product.status === 0"
- v-access:code="['iot:device:update']"
- @click="openEditForm(device)"
- >
- 缂栬緫
- </Button>
- </div>
- </div>
-
- <Card class="mt-4">
- <Descriptions :column="2">
- <Descriptions.Item label="浜у搧">
- <a
- class="cursor-pointer text-blue-600"
- @click="goToProductDetail(product.id)"
- >
- {{ product.name }}
- </a>
- </Descriptions.Item>
- <Descriptions.Item label="ProductKey">
- {{ product.productKey }}
- <Button
- class="ml-2"
- size="small"
- @click="copyToClipboard(product.productKey)"
- >
- 澶嶅埗
- </Button>
- </Descriptions.Item>
- </Descriptions>
- </Card>
- </div>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/info.vue b/src/views/iot/device/device/detail/modules/info.vue
deleted file mode 100644
index 456ecec..0000000
--- a/src/views/iot/device/device/detail/modules/info.vue
+++ /dev/null
@@ -1,195 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { computed, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDateTime } from '../../../../../../packages/utils/src';
-
-import {
- Button,
- Card,
- Descriptions,
- Form,
- Input,
- message,
- Modal,
-} from 'ant-design-vue';
-
-import { getDeviceAuthInfo } from '#/api/iot/device/device';
-import { DictTag } from '#/components/dict-tag';
-import { MapDialog } from '#/components/map';
-
-interface Props {
- device: IotDeviceApi.Device;
- product: IotProductApi.Product;
-}
-
-const props = defineProps<Props>();
-
-const authDialogVisible = ref(false);
-const authPasswordVisible = ref(false);
-const authInfo = ref<IotDeviceApi.DeviceAuthInfoRespVO>(
- {} as IotDeviceApi.DeviceAuthInfoRespVO,
-);
-const mapDialogRef = ref<InstanceType<typeof MapDialog>>();
-
-/** 鏄惁鏈変綅缃俊鎭紙鍚堟硶缁忕含搴� 0 涓嶅簲瑙嗕负绌猴級 */
-const hasLocation = computed(() => {
- return props.device.longitude !== null && props.device.latitude !== null;
-});
-
-/** 鎵撳紑鍦板浘寮圭獥 */
-function openMapDialog() {
- mapDialogRef.value?.open(props.device.longitude, props.device.latitude);
-}
-
-/** 澶嶅埗鍒板壀璐存澘 */
-async function copyToClipboard(text: string) {
- try {
- await navigator.clipboard.writeText(text);
- message.success('澶嶅埗鎴愬姛');
- } catch {
- message.error('澶嶅埗澶辫触');
- }
-}
-
-/** 鎵撳紑璁惧璁よ瘉淇℃伅寮规 */
-async function handleAuthInfoDialogOpen() {
- if (!props.device.id) return;
- try {
- authInfo.value = await getDeviceAuthInfo(props.device.id);
- authDialogVisible.value = true;
- } catch {
- message.error('鑾峰彇璁惧璁よ瘉淇℃伅澶辫触锛岃妫�鏌ョ綉缁滆繛鎺ユ垨鑱旂郴绠$悊鍛�');
- }
-}
-
-/** 鍏抽棴璁惧璁よ瘉淇℃伅寮规 */
-function handleAuthInfoDialogClose() {
- authDialogVisible.value = false;
-}
-</script>
-
-<template>
- <div>
- <Card title="璁惧淇℃伅">
- <Descriptions :column="3" bordered size="small">
- <Descriptions.Item label="浜у搧鍚嶇О">
- {{ product.name }}
- </Descriptions.Item>
- <Descriptions.Item label="ProductKey">
- {{ product.productKey }}
- </Descriptions.Item>
- <Descriptions.Item label="璁惧绫诲瀷">
- <DictTag
- :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
- :value="product.deviceType"
- />
- </Descriptions.Item>
- <Descriptions.Item label="DeviceName">
- {{ device.deviceName }}
- </Descriptions.Item>
- <Descriptions.Item label="澶囨敞鍚嶇О">
- {{ device.nickname || '--' }}
- </Descriptions.Item>
- <Descriptions.Item label="褰撳墠鐘舵��">
- <DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
- </Descriptions.Item>
- <Descriptions.Item label="鍒涘缓鏃堕棿">
- {{ formatDateTime(device.createTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="婵�娲绘椂闂�">
- {{ formatDateTime(device.activeTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="鏈�鍚庝笂绾挎椂闂�">
- {{ formatDateTime(device.onlineTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="鏈�鍚庣绾挎椂闂�">
- {{ formatDateTime(device.offlineTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="璁惧浣嶇疆">
- <template v-if="hasLocation">
- <span class="mr-2">
- {{ device.longitude }}, {{ device.latitude }}
- </span>
- <Button type="link" size="small" @click="openMapDialog">
- <IconifyIcon icon="lucide:map-pin" class="mr-1" />
- 鏌ョ湅鍦板浘
- </Button>
- </template>
- <span v-else class="text-gray-400">鏆傛棤浣嶇疆淇℃伅</span>
- </Descriptions.Item>
- <Descriptions.Item label="MQTT 杩炴帴鍙傛暟">
- <Button size="small" type="link" @click="handleAuthInfoDialogOpen">
- 鏌ョ湅
- </Button>
- </Descriptions.Item>
- </Descriptions>
- </Card>
-
- <!-- 璁よ瘉淇℃伅寮规 -->
- <Modal
- v-model:open="authDialogVisible"
- :footer="null"
- title="MQTT 杩炴帴鍙傛暟"
- width="640px"
- >
- <Form :label-col="{ span: 6 }">
- <Form.Item label="clientId">
- <Input.Group compact>
- <Input
- v-model:value="authInfo.clientId"
- readonly
- style="width: calc(100% - 80px)"
- />
- <Button type="primary" @click="copyToClipboard(authInfo.clientId)">
- <IconifyIcon icon="lucide:copy" />
- </Button>
- </Input.Group>
- </Form.Item>
- <Form.Item label="username">
- <Input.Group compact>
- <Input
- v-model:value="authInfo.username"
- readonly
- style="width: calc(100% - 80px)"
- />
- <Button type="primary" @click="copyToClipboard(authInfo.username)">
- <IconifyIcon icon="lucide:copy" />
- </Button>
- </Input.Group>
- </Form.Item>
- <Form.Item label="password">
- <Input.Group compact>
- <Input
- v-model:value="authInfo.password"
- :type="authPasswordVisible ? 'text' : 'password'"
- readonly
- style="width: calc(100% - 160px)"
- />
- <Button
- type="primary"
- @click="authPasswordVisible = !authPasswordVisible"
- >
- <IconifyIcon
- :icon="authPasswordVisible ? 'lucide:eye-off' : 'lucide:eye'"
- />
- </Button>
- <Button type="primary" @click="copyToClipboard(authInfo.password)">
- <IconifyIcon icon="lucide:copy" />
- </Button>
- </Input.Group>
- </Form.Item>
- </Form>
- <div class="mt-4 text-right">
- <Button @click="handleAuthInfoDialogClose">鍏抽棴</Button>
- </div>
- </Modal>
-
- <!-- 鍦板浘寮圭獥 -->
- <MapDialog ref="mapDialogRef" />
- </div>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/message.vue b/src/views/iot/device/device/detail/modules/message.vue
deleted file mode 100644
index bf2960d..0000000
--- a/src/views/iot/device/device/detail/modules/message.vue
+++ /dev/null
@@ -1,250 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import {
- computed,
- onBeforeUnmount,
- onMounted,
- reactive,
- ref,
- watch,
-} from 'vue';
-
-import { Page } from '../../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE, IotDeviceMessageMethodEnum } from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDateTime } from '../../../../../../packages/utils/src';
-
-import { Button, Select, Space, Switch, Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getDeviceMessagePage } from '#/api/iot/device/device';
-
-const props = defineProps<{
- deviceId: number;
-}>();
-
-const queryParams = reactive({
- method: undefined,
- upstream: undefined,
-}); // 鏌ヨ鍙傛暟
-
-const autoRefresh = ref(false); // 鑷姩鍒锋柊寮�鍏�
-let autoRefreshTimer: any = null; // 鑷姩鍒锋柊瀹氭椂鍣�
-let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 寤惰繜鍒锋柊瀹氭椂鍣�
-
-/** 娑堟伅鏂规硶閫夐」 */
-const methodOptions = computed(() => {
- return Object.values(IotDeviceMessageMethodEnum).map((item) => ({
- label: item.name,
- value: item.method,
- }));
-});
-
-/** Grid 鍒楀畾涔� */
-function useGridColumns(): VxeTableGridOptions<Record<string, any>>['columns'] {
- return [
- {
- field: 'ts',
- title: '鏃堕棿',
- width: 160,
- slots: { default: 'ts' },
- },
- {
- field: 'upstream',
- title: '涓婅/涓嬭',
- width: 100,
- slots: { default: 'upstream' },
- },
- {
- field: 'reply',
- title: '鏄惁鍥炲',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'requestId',
- title: '璇锋眰缂栧彿',
- width: 280,
- showOverflow: 'tooltip',
- },
- {
- field: 'method',
- title: '璇锋眰鏂规硶',
- width: 120,
- slots: { default: 'method' },
- },
- {
- field: 'params',
- title: '璇锋眰/鍝嶅簲鏁版嵁',
- minWidth: 200,
- showOverflow: 'tooltip',
- slots: { default: 'params' },
- },
- ];
-}
-
-/** 鍒涘缓 Grid 瀹炰緥 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- if (!props.deviceId) {
- return { list: [], total: 0 };
- }
- return await getDeviceMessagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- deviceId: props.deviceId,
- method: queryParams.method,
- upstream: queryParams.upstream,
- });
- },
- },
- },
- toolbarConfig: {
- refresh: false,
- search: false,
- },
- pagerConfig: {
- enabled: true,
- },
- } as VxeTableGridOptions,
-});
-
-/** 鎼滅储鎿嶄綔 */
-function handleQuery() {
- gridApi.query();
-}
-
-/** 鐩戝惉鑷姩鍒锋柊 */
-watch(autoRefresh, (newValue) => {
- if (newValue) {
- autoRefreshTimer = setInterval(() => {
- gridApi.query();
- }, 5000);
- } else {
- clearInterval(autoRefreshTimer);
- autoRefreshTimer = null;
- }
-});
-
-/** 鐩戝惉璁惧鏍囪瘑鍙樺寲 */
-watch(
- () => props.deviceId,
- (newValue) => {
- if (newValue) {
- handleQuery();
- }
- },
-);
-
-/** 缁勪欢鍗歌浇鏃舵竻闄ゅ畾鏃跺櫒 */
-onBeforeUnmount(() => {
- if (autoRefreshTimer) {
- clearInterval(autoRefreshTimer);
- autoRefreshTimer = null;
- }
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- if (props.deviceId) {
- handleQuery();
- }
-});
-
-/** 鍒锋柊娑堟伅鍒楄〃 */
-function refresh(delay = 0) {
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
- if (delay > 0) {
- refreshTimer = setTimeout(() => {
- gridApi.query();
- refreshTimer = undefined;
- }, delay);
- } else {
- gridApi.query();
- }
-}
-
-/** 鏆撮湶鏂规硶缁欑埗缁勪欢 */
-defineExpose({
- refresh,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <!-- 鎼滅储鍖哄煙 -->
- <div class="mb-4 flex flex-wrap items-center gap-3">
- <Select
- v-model:value="queryParams.method"
- allow-clear
- placeholder="鎵�鏈夋柟娉�"
- style="width: 160px"
- >
- <Select.Option
- v-for="item in methodOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- >
- {{ item.label }}
- </Select.Option>
- </Select>
- <Select
- v-model:value="queryParams.upstream"
- allow-clear
- placeholder="涓婅/涓嬭"
- style="width: 160px"
- >
- <Select.Option label="涓婅" value="true">涓婅</Select.Option>
- <Select.Option label="涓嬭" value="false">涓嬭</Select.Option>
- </Select>
- <Space>
- <Button type="primary" @click="handleQuery">
- <IconifyIcon icon="ep:search" class="mr-[5px]" /> 鎼滅储
- </Button>
- <Switch
- v-model:checked="autoRefresh"
- checked-children="瀹氭椂鍒锋柊"
- un-checked-children="瀹氭椂鍒锋柊"
- />
- </Space>
- </div>
-
- <!-- 娑堟伅鍒楄〃 -->
- <Grid>
- <template #ts="{ row }">
- {{ formatDateTime(row.ts) }}
- </template>
- <template #upstream="{ row }">
- <Tag :color="row.upstream ? 'blue' : 'green'">
- {{ row.upstream ? '涓婅' : '涓嬭' }}
- </Tag>
- </template>
- <template #method="{ row }">
- {{ methodOptions.find((item) => item.value === row.method)?.label }}
- </template>
- <template #params="{ row }">
- <span v-if="row.reply">
- {{ `{"code":${row.code},"msg":"${row.msg}","data":${row.data}\}` }}
- </span>
- <span v-else>{{ row.params }}</span>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/modbus-config-form.vue b/src/views/iot/device/device/detail/modules/modbus-config-form.vue
deleted file mode 100644
index 93e0207..0000000
--- a/src/views/iot/device/device/detail/modules/modbus-config-form.vue
+++ /dev/null
@@ -1,236 +0,0 @@
-<!-- Modbus 杩炴帴閰嶇疆寮圭獥 -->
-<script lang="ts" setup>
-import type { IotDeviceModbusConfigApi } from '#/api/iot/device/modbus/config';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import {
- CommonStatusEnum,
- DICT_TYPE,
- ModbusFrameFormatEnum,
- ModbusModeEnum,
-} from '../../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../../packages/effects/hooks/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm, z } from '#/adapter/form';
-import { saveModbusConfig } from '#/api/iot/device/modbus/config';
-import { ProtocolTypeEnum } from '#/api/iot/product/product';
-import { $t } from '#/locales';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<IotDeviceModbusConfigApi.ModbusConfig>();
-const deviceId = ref<number>(0);
-const protocolType = ref<string>('');
-
-const isClient = computed(
- () => protocolType.value === ProtocolTypeEnum.MODBUS_TCP_CLIENT,
-); // 鏄惁涓� Client 妯″紡
-const isServer = computed(
- () => protocolType.value === ProtocolTypeEnum.MODBUS_TCP_SERVER,
-); // 鏄惁涓� Server 妯″紡
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'ip',
- label: 'IP 鍦板潃',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� Modbus 鏈嶅姟鍣� IP 鍦板潃',
- },
- // Client 妯″紡涓撴湁瀛楁锛氬繀濉紱Server 妯″紡涓嶆樉绀轰篃涓嶆牎楠�
- dependencies: {
- triggerFields: [''],
- show: () => isClient.value,
- rules: () =>
- isClient.value ? z.string().min(1, '璇疯緭鍏� IP 鍦板潃') : null,
- },
- },
- {
- fieldName: 'port',
- label: '绔彛',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ョ鍙�',
- min: 1,
- max: 65_535,
- },
- dependencies: {
- triggerFields: [''],
- show: () => isClient.value,
- rules: () =>
- isClient.value
- ? z.number({ message: '璇疯緭鍏ョ鍙�' }).min(1).max(65_535)
- : null,
- },
- defaultValue: 502,
- },
- {
- fieldName: 'slaveId',
- label: '浠庣珯鍦板潃',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヤ粠绔欏湴鍧�锛岃寖鍥� 1-247',
- min: 1,
- max: 247,
- },
- rules: z.number().min(1, '璇疯緭鍏ヤ粠绔欏湴鍧�').max(247),
- defaultValue: 1,
- },
- {
- fieldName: 'timeout',
- label: '杩炴帴瓒呮椂(ms)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヨ繛鎺ヨ秴鏃舵椂闂�',
- min: 1000,
- step: 1000,
- },
- dependencies: {
- triggerFields: [''],
- show: () => isClient.value,
- rules: () =>
- isClient.value
- ? z.number({ message: '璇疯緭鍏ヨ繛鎺ヨ秴鏃舵椂闂�' }).min(1000)
- : null,
- },
- defaultValue: 3000,
- },
- {
- fieldName: 'retryInterval',
- label: '閲嶈瘯闂撮殧(ms)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ラ噸璇曢棿闅�',
- min: 1000,
- step: 1000,
- },
- dependencies: {
- triggerFields: [''],
- show: () => isClient.value,
- rules: () =>
- isClient.value
- ? z.number({ message: '璇疯緭鍏ラ噸璇曢棿闅�' }).min(1000)
- : null,
- },
- defaultValue: 10_000,
- },
- {
- fieldName: 'mode',
- label: '宸ヤ綔妯″紡',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_MODBUS_MODE, 'number'),
- },
- dependencies: {
- triggerFields: [''],
- show: () => isServer.value, // Server 妯″紡涓撴湁瀛楁锛氬伐浣滄ā寮�
- },
- rules: 'required',
- defaultValue: ModbusModeEnum.POLLING,
- },
- {
- fieldName: 'frameFormat',
- label: '甯ф牸寮�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_MODBUS_FRAME_FORMAT, 'number'),
- },
- dependencies: {
- triggerFields: [''],
- show: () => isServer.value, // Server 妯″紡涓撴湁瀛楁锛氬抚鏍煎紡
- },
- rules: 'required',
- defaultValue: ModbusFrameFormatEnum.MODBUS_TCP,
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ],
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as IotDeviceModbusConfigApi.ModbusConfig;
- try {
- data.deviceId = deviceId.value;
- await saveModbusConfig(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{
- config?: IotDeviceModbusConfigApi.ModbusConfig;
- deviceId: number;
- protocolType: string;
- }>();
- if (!data) {
- return;
- }
- deviceId.value = data.deviceId;
- protocolType.value = data.protocolType;
- if (!data.config) {
- return;
- }
- // 璁剧疆鍒� values
- formData.value = { ...data.config };
- await formApi.setValues(formData.value);
- },
-});
-</script>
-
-<template>
- <Modal title="缂栬緫 Modbus 杩炴帴閰嶇疆" class="w-[600px]">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/modbus-config.vue b/src/views/iot/device/device/detail/modules/modbus-config.vue
deleted file mode 100644
index 606a772..0000000
--- a/src/views/iot/device/device/detail/modules/modbus-config.vue
+++ /dev/null
@@ -1,368 +0,0 @@
-<!-- Modbus 閰嶇疆 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotDeviceModbusConfigApi } from '#/api/iot/device/modbus/config';
-import type { IotDeviceModbusPointApi } from '#/api/iot/device/modbus/point';
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { computed, h, onMounted, ref } from 'vue';
-
-import { confirm, useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE, ModbusFunctionCodeOptions } from '../../../../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getModbusConfig } from '#/api/iot/device/modbus/config';
-import {
- deleteModbusPoint,
- getModbusPointPage,
-} from '#/api/iot/device/modbus/point';
-import { ProtocolTypeEnum } from '#/api/iot/product/product';
-import { useDescription } from '#/components/description';
-import { DictTag } from '#/components/dict-tag';
-
-import DeviceModbusConfigForm from './modbus-config-form.vue';
-import DeviceModbusPointForm from './modbus-point-form.vue';
-
-const props = defineProps<{
- device: IotDeviceApi.Device;
- product: IotProductApi.Product;
- thingModelList: ThingModelApi.ThingModel[];
-}>();
-
-// ======================= 杩炴帴閰嶇疆 =======================
-
-const isClient = computed(
- () => props.product.protocolType === ProtocolTypeEnum.MODBUS_TCP_CLIENT,
-); // 鏄惁涓� Client 妯″紡
-const isServer = computed(
- () => props.product.protocolType === ProtocolTypeEnum.MODBUS_TCP_SERVER,
-); // 鏄惁涓� Server 妯″紡
-const modbusConfig = ref<IotDeviceModbusConfigApi.ModbusConfig>(
- {} as IotDeviceModbusConfigApi.ModbusConfig,
-); // 杩炴帴閰嶇疆
-
-/** 杩炴帴閰嶇疆 Description Schema */
-function useConfigDescriptionSchema(): DescriptionItemSchema[] {
- return [
- // Client 妯″紡涓撴湁瀛楁
- {
- field: 'ip',
- label: 'IP 鍦板潃',
- show: () => isClient.value,
- },
- {
- field: 'port',
- label: '绔彛',
- show: () => isClient.value,
- },
- // 鍏叡瀛楁
- {
- field: 'slaveId',
- label: '浠庣珯鍦板潃',
- },
- // Client 妯″紡涓撴湁瀛楁
- {
- field: 'timeout',
- label: '杩炴帴瓒呮椂',
- show: () => isClient.value,
- render: (val) => (val ? `${val} ms` : '-'),
- },
- {
- field: 'retryInterval',
- label: '閲嶈瘯闂撮殧',
- show: () => isClient.value,
- render: (val) => (val ? `${val} ms` : '-'),
- },
- // Server 妯″紡涓撴湁瀛楁
- {
- field: 'mode',
- label: '宸ヤ綔妯″紡',
- show: () => isServer.value,
- render: (val) =>
- h(DictTag, { type: DICT_TYPE.IOT_MODBUS_MODE, value: val }),
- },
- {
- field: 'frameFormat',
- label: '甯ф牸寮�',
- show: () => isServer.value,
- render: (val) =>
- h(DictTag, { type: DICT_TYPE.IOT_MODBUS_FRAME_FORMAT, value: val }),
- },
- // 鍏叡瀛楁
- {
- field: 'status',
- label: '鐘舵��',
- render: (val) =>
- h(DictTag, { type: DICT_TYPE.COMMON_STATUS, value: val }),
- },
- ];
-}
-
-const [ConfigDescriptions] = useDescription({
- title: '杩炴帴閰嶇疆',
- column: 3,
- bordered: true,
- schema: useConfigDescriptionSchema(),
-});
-
-/** 鑾峰彇杩炴帴閰嶇疆 */
-async function loadModbusConfig() {
- modbusConfig.value = await getModbusConfig(props.device.id!);
-}
-
-/** 缂栬緫杩炴帴閰嶇疆 - 浣跨敤 useVbenModal */
-const [ConfigFormModal, configFormModalApi] = useVbenModal({
- connectedComponent: DeviceModbusConfigForm,
- destroyOnClose: true,
-});
-
-/** 鎵撳紑缂栬緫杩炴帴閰嶇疆寮圭獥 */
-function handleEditConfig() {
- configFormModalApi
- .setData({
- config: modbusConfig.value,
- deviceId: props.device.id!,
- protocolType: props.product.protocolType!,
- })
- .open();
-}
-
-// ======================= 鐐逛綅閰嶇疆 =======================
-
-/** 鏍煎紡鍖栧姛鑳界爜 */
-function formatFunctionCode(code: number) {
- const option = ModbusFunctionCodeOptions.find((item) => item.value === code);
- return option ? option.label : `${code}`;
-}
-
-/** 鏍煎紡鍖栧瘎瀛樺櫒鍦板潃涓哄崄鍏繘鍒� */
-function formatRegisterAddress(address: number) {
- return `0x${address.toString(16).toUpperCase().padStart(4, '0')}`;
-}
-
-/** 鐐逛綅鎼滅储琛ㄥ崟 Schema */
-function usePointFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '灞炴�у悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ睘鎬у悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'identifier',
- label: '鏍囪瘑绗�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ爣璇嗙',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鐐逛綅鍒楄〃鍒楅厤缃� */
-function usePointColumns(): VxeTableGridOptions<IotDeviceModbusPointApi.ModbusPoint>['columns'] {
- return [
- { field: 'name', title: '灞炴�у悕绉�', minWidth: 100 },
- {
- field: 'identifier',
- title: '鏍囪瘑绗�',
- minWidth: 100,
- cellRender: { name: 'CellTag', props: { color: 'blue' } },
- },
- {
- field: 'functionCode',
- title: '鍔熻兘鐮�',
- minWidth: 140,
- formatter: ({ cellValue }) => formatFunctionCode(cellValue),
- },
- {
- field: 'registerAddress',
- title: '瀵勫瓨鍣ㄥ湴鍧�',
- minWidth: 100,
- formatter: ({ cellValue }) => formatRegisterAddress(cellValue),
- },
- { field: 'registerCount', title: '瀵勫瓨鍣ㄦ暟閲�', minWidth: 90 },
- {
- field: 'rawDataType',
- title: '鏁版嵁绫诲瀷',
- minWidth: 90,
- cellRender: { name: 'CellTag' },
- },
- { field: 'byteOrder', title: '瀛楄妭搴�', minWidth: 80 },
- { field: 'scale', title: '缂╂斁鍥犲瓙', minWidth: 80 },
- {
- field: 'pollInterval',
- title: '杞闂撮殧',
- minWidth: 90,
- formatter: ({ cellValue }) => `${cellValue} ms`,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: usePointFormSchema(),
- submitOnChange: true,
- },
- gridOptions: {
- columns: usePointColumns(),
- height: 'auto',
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getModbusPointPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- deviceId: props.device.id,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotDeviceModbusPointApi.ModbusPoint>,
-});
-
-/** 鏂板鐐逛綅 - 浣跨敤 useVbenModal */
-const [PointFormModal, pointFormModalApi] = useVbenModal({
- connectedComponent: DeviceModbusPointForm,
- destroyOnClose: true,
-});
-
-/** 鎵撳紑鏂板鐐逛綅寮圭獥 */
-function handleAddPoint() {
- pointFormModalApi
- .setData({
- deviceId: props.device.id!,
- thingModelList: props.thingModelList,
- })
- .open();
-}
-
-/** 缂栬緫鐐逛綅 */
-function handleEditPoint(row: IotDeviceModbusPointApi.ModbusPoint) {
- pointFormModalApi
- .setData({
- id: row.id,
- deviceId: props.device.id!,
- thingModelList: props.thingModelList,
- })
- .open();
-}
-
-/** 鍒犻櫎鐐逛綅 */
-async function handleDeletePoint(row: IotDeviceModbusPointApi.ModbusPoint) {
- await confirm(`纭畾瑕佸垹闄ょ偣浣嶃��${row.name}銆戝悧锛焋);
- await deleteModbusPoint(row.id!);
- message.success('鍒犻櫎鎴愬姛');
- await gridApi.query();
-}
-
-/** 鍒锋柊鐐逛綅鍒楄〃 */
-function handlePointSuccess() {
- gridApi.query();
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- await loadModbusConfig();
-});
-</script>
-
-<template>
- <div>
- <!-- 杩炴帴閰嶇疆鍖哄煙 -->
- <ConfigDescriptions :data="modbusConfig" class="mb-4">
- <template #extra>
- <TableAction
- :actions="[
- {
- label: '缂栬緫',
- type: 'primary',
- auth: ['iot:device:create'],
- onClick: handleEditConfig,
- },
- ]"
- />
- </template>
- </ConfigDescriptions>
-
- <!-- 鐐逛綅閰嶇疆鍖哄煙 -->
- <Grid table-title="鐐逛綅閰嶇疆" class="h-[600px] overflow-auto">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鏂板鐐逛綅',
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:device:create'],
- onClick: handleAddPoint,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '缂栬緫',
- type: 'link',
- auth: ['iot:device:update'],
- onClick: () => handleEditPoint(row),
- },
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- auth: ['iot:device:delete'],
- popConfirm: {
- title: `纭畾瑕佸垹闄ょ偣浣嶃��${row.name}銆戝悧锛焋,
- confirm: () => handleDeletePoint(row),
- },
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 杩炴帴閰嶇疆寮圭獥 -->
- <ConfigFormModal @success="loadModbusConfig" />
-
- <!-- 鐐逛綅琛ㄥ崟寮圭獥 -->
- <PointFormModal @success="handlePointSuccess" />
- </div>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/modbus-point-form.vue b/src/views/iot/device/device/detail/modules/modbus-point-form.vue
deleted file mode 100644
index 529c67a..0000000
--- a/src/views/iot/device/device/detail/modules/modbus-point-form.vue
+++ /dev/null
@@ -1,327 +0,0 @@
-<!-- Modbus 鐐逛綅琛ㄥ崟寮圭獥 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { IotDeviceModbusPointApi } from '#/api/iot/device/modbus/point';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, h, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import {
- CommonStatusEnum,
- DICT_TYPE,
- getByteOrderOptions,
- IoTThingModelTypeEnum,
- ModbusFunctionCodeOptions,
- ModbusRawDataTypeOptions,
-} from '../../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../../packages/effects/hooks/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm, z } from '#/adapter/form';
-import {
- createModbusPoint,
- getModbusPoint,
- updateModbusPoint,
-} from '#/api/iot/device/modbus/point';
-import { $t } from '#/locales';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<IotDeviceModbusPointApi.ModbusPoint>();
-const getTitle = computed(() => {
- return formData.value?.id ? '缂栬緫鐐逛綅' : '鏂板鐐逛綅';
-});
-const deviceId = ref<number>(0);
-const thingModelList = ref<ThingModelApi.ThingModel[]>([]);
-
-/** 绛涢�夊睘鎬х被鍨嬬殑鐗╂ā鍨� */
-const propertyList = computed(() => {
- return thingModelList.value.filter(
- (item) => Number(item.type) === IoTThingModelTypeEnum.PROPERTY,
- );
-});
-
-/** 琛ㄥ崟 Schema */
-function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'identifier',
- dependencies: {
- triggerFields: [''], // 闅愯棌瀛楁锛歩dentifier锛堢敱鐗╂ā鍨嬪睘鎬ч�夋嫨鑷姩濉厖锛�
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'name',
- dependencies: {
- triggerFields: [''], // 闅愯棌瀛楁锛歯ame锛堢敱鐗╂ā鍨嬪睘鎬ч�夋嫨鑷姩濉厖锛�
- show: () => false,
- },
- },
- {
- fieldName: 'thingModelId',
- label: '鐗╂ā鍨嬪睘鎬�',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐗╂ā鍨嬪睘鎬�',
- showSearch: true,
- filterOption(input: string, option: any) {
- return option.label.toLowerCase().includes(input.toLowerCase());
- },
- },
- dependencies: {
- triggerFields: [''],
- componentProps: () => ({
- options: propertyList.value.map((item) => ({
- value: item.id,
- label: `${item.name} (${item.identifier})`,
- })),
- }),
- },
- rules: 'required',
- },
- {
- fieldName: 'functionCode',
- label: '鍔熻兘鐮�',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鍔熻兘鐮�',
- options: ModbusFunctionCodeOptions.map((item) => ({
- value: item.value,
- label: item.label,
- })),
- },
- rules: 'required',
- },
- {
- fieldName: 'registerAddress',
- label: '瀵勫瓨鍣ㄥ湴鍧�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ュ瘎瀛樺櫒鍦板潃',
- min: 0,
- max: 65_535,
- },
- rules: 'required',
- suffix: () => {
- const addr = formApi.form.values?.registerAddress;
- if (addr === null || addr === undefined) {
- return '';
- }
- return h(
- 'span',
- { class: 'text-gray-400' },
- `0x${Number(addr).toString(16).toUpperCase().padStart(4, '0')}`,
- );
- },
- },
- {
- fieldName: 'registerCount',
- label: '瀵勫瓨鍣ㄦ暟閲�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ュ瘎瀛樺櫒鏁伴噺',
- min: 1,
- max: 125,
- },
- rules: 'required',
- },
- {
- fieldName: 'rawDataType',
- label: '鍘熷鏁版嵁绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鏁版嵁绫诲瀷',
- options: ModbusRawDataTypeOptions.map((item) => ({
- value: item.value,
- label: `${item.label} - ${item.description}`,
- })),
- },
- rules: 'required',
- },
- {
- fieldName: 'byteOrder',
- label: '瀛楄妭搴�',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨瀛楄妭搴�',
- },
- dependencies: {
- triggerFields: ['rawDataType'],
- componentProps: (values) => ({
- options: values.rawDataType
- ? getByteOrderOptions(values.rawDataType).map((item) => ({
- value: item.value,
- label: `${item.label} - ${item.description}`,
- }))
- : [],
- }),
- },
- rules: 'required',
- },
- {
- fieldName: 'scale',
- label: '缂╂斁鍥犲瓙',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ョ缉鏀惧洜瀛�',
- precision: 6,
- step: 0.1,
- },
- defaultValue: 1,
- },
- {
- fieldName: 'pollInterval',
- label: '杞闂撮殧(ms)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヨ疆璇㈤棿闅�',
- min: 100,
- step: 1000,
- },
- rules: z.number().min(100, '璇疯緭鍏ヨ疆璇㈤棿闅�'),
- defaultValue: 5000,
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
- handleValuesChange: async (values, changedFields) => {
- // 鐗╂ā鍨嬪睘鎬у彉鍖栵細鑷姩濉厖 identifier 鍜� name
- if (changedFields.includes('thingModelId')) {
- const thingModelId = values.thingModelId;
- const thingModel = thingModelList.value.find(
- (item) => item.id === thingModelId,
- );
- if (thingModel) {
- await formApi.setFieldValue('identifier', thingModel.identifier);
- await formApi.setFieldValue('name', thingModel.name);
- }
- }
- // 鏁版嵁绫诲瀷鍙樺寲锛氳嚜鍔ㄨ缃瘎瀛樺櫒鏁伴噺鍜屽瓧鑺傚簭
- if (changedFields.includes('rawDataType')) {
- const rawDataType = values.rawDataType;
- if (rawDataType) {
- // 鏍规嵁鏁版嵁绫诲瀷鑷姩璁剧疆瀵勫瓨鍣ㄦ暟閲�
- const option = ModbusRawDataTypeOptions.find(
- (item) => item.value === rawDataType,
- );
- if (option && option.registerCount > 0) {
- await formApi.setFieldValue('registerCount', option.registerCount);
- }
- // 瀛楄妭搴忥細浠呭湪褰撳墠鍊间负绌烘垨涓嶅睘浜庢柊 rawDataType 鏃舵墠閲嶇疆涓虹涓�涓�夐」锛�
- // 缂栬緫鍥炲~鏃� setValues 浼氳Е鍙戞湰鍥炶皟锛屾棤鏉′欢閲嶇疆浼氳鐩栧凡淇濆瓨瀛楄妭搴�
- const byteOrderOptions = getByteOrderOptions(rawDataType);
- if (byteOrderOptions.length > 0) {
- const currentByteOrder = values.byteOrder;
- const isCurrentValid =
- !!currentByteOrder &&
- byteOrderOptions.some((opt) => opt.value === currentByteOrder);
- if (!isCurrentValid) {
- await formApi.setFieldValue(
- 'byteOrder',
- byteOrderOptions[0]!.value,
- );
- }
- }
- }
- }
- },
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as IotDeviceModbusPointApi.ModbusPoint;
- try {
- data.deviceId = deviceId.value;
- await (formData.value?.id
- ? updateModbusPoint(data)
- : createModbusPoint(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{
- deviceId: number;
- id?: number;
- thingModelList: ThingModelApi.ThingModel[];
- }>();
- if (!data) {
- return;
- }
- deviceId.value = data.deviceId;
- thingModelList.value = data.thingModelList || [];
- if (!data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getModbusPoint(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-[600px]">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/simulator.vue b/src/views/iot/device/device/detail/modules/simulator.vue
deleted file mode 100644
index ea8e3ab..0000000
--- a/src/views/iot/device/device/detail/modules/simulator.vue
+++ /dev/null
@@ -1,768 +0,0 @@
-<!-- 妯℃嫙璁惧 -->
-<script lang="ts" setup>
-import type { TableColumnType } from 'ant-design-vue';
-
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, ref, watch } from 'vue';
-
-import { ContentWrap } from '../../../../../../packages/effects/common-ui/src';
-import {
- DeviceStateEnum,
- IoTDataSpecsDataTypeEnum,
- IotDeviceMessageMethodEnum,
- IoTThingModelTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import {
- Button,
- Card,
- Col,
- Input,
- InputNumber,
- message,
- Row,
- Select,
- Table,
- Tabs,
- Textarea,
-} from 'ant-design-vue';
-
-import { sendDeviceMessage } from '#/api/iot/device/device';
-
-import DataDefinition from '../../../../thingmodel/modules/components/data-definition.vue';
-import DeviceDetailsMessage from './message.vue';
-
-const props = defineProps<{
- device: IotDeviceApi.Device;
- product: IotProductApi.Product;
- thingModelList: ThingModelApi.ThingModel[];
-}>();
-
-// 娑堟伅寮圭獥
-const activeTab = ref('upstream'); // 涓婅upstream銆佷笅琛宒ownstream
-const upstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_POST.method); // 涓婅瀛愭爣绛�
-const downstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_SET.method); // 涓嬭瀛愭爣绛�
-const deviceMessageRef = ref(); // 璁惧娑堟伅缁勪欢寮曠敤
-const deviceMessageRefreshDelay = 2000; // 寤惰繜 N 绉掞紝淇濊瘉妯℃嫙涓婅鐨勬秷鎭澶勭悊
-
-// 鎶樺彔鐘舵��
-const debugCollapsed = ref(false); // 鎸囦护璋冭瘯鍖哄煙鎶樺彔鐘舵��
-const messageCollapsed = ref(false); // 璁惧娑堟伅鍖哄煙鎶樺彔鐘舵��
-
-// 琛ㄥ崟鏁版嵁锛氬瓨鍌ㄧ敤鎴疯緭鍏ョ殑妯℃嫙鍊�
-const formData = ref<Record<string, any>>({});
-
-// 鏍规嵁绫诲瀷杩囨护鐗╂ā鍨嬫暟鎹�
-const getFilteredThingModelList = (type: number) => {
- return props.thingModelList.filter(
- (item) => String(item.type) === String(type),
- );
-};
-
-// 璁$畻灞炴�э細灞炴�у垪琛�
-const propertyList = computed(() =>
- getFilteredThingModelList(IoTThingModelTypeEnum.PROPERTY),
-);
-
-// 璁$畻灞炴�э細浜嬩欢鍒楄〃
-const eventList = computed(() =>
- getFilteredThingModelList(IoTThingModelTypeEnum.EVENT),
-);
-
-// 璁$畻灞炴�э細鏈嶅姟鍒楄〃
-const serviceList = computed(() =>
- getFilteredThingModelList(IoTThingModelTypeEnum.SERVICE),
-);
-
-// 灞炴�ц〃鏍煎垪瀹氫箟
-const propertyColumns: TableColumnType[] = [
- {
- title: '鍔熻兘鍚嶇О',
- dataIndex: 'name',
- key: 'name',
- width: 100,
- fixed: 'left' as any,
- },
- {
- title: '鏍囪瘑绗�',
- dataIndex: 'identifier',
- key: 'identifier',
- width: 120,
- fixed: 'left' as any,
- },
- {
- title: '鏁版嵁绫诲瀷',
- key: 'dataType',
- width: 90,
- },
- {
- title: '鏁版嵁瀹氫箟',
- key: 'dataDefinition',
- minWidth: 150,
- },
- {
- title: '鍊�',
- key: 'value',
- width: 180,
- fixed: 'right' as any,
- },
-];
-
-// 浜嬩欢琛ㄦ牸鍒楀畾涔�
-const eventColumns = [
- {
- title: '鍔熻兘鍚嶇О',
- dataIndex: 'name',
- key: 'name',
- width: 100,
- fixed: 'left' as any,
- },
- {
- title: '鏍囪瘑绗�',
- dataIndex: 'identifier',
- key: 'identifier',
- width: 120,
- fixed: 'left' as any,
- },
- {
- title: '鏁版嵁绫诲瀷',
- key: 'dataType',
- width: 90,
- },
- {
- title: '鏁版嵁瀹氫箟',
- key: 'dataDefinition',
- minWidth: 150,
- },
- {
- title: '鍊�',
- key: 'value',
- width: 180,
- },
- {
- title: '鎿嶄綔',
- key: 'action',
- width: 100,
- fixed: 'right' as any,
- },
-];
-
-// 鏈嶅姟琛ㄦ牸鍒楀畾涔�
-const serviceColumns = [
- {
- title: '鏈嶅姟鍚嶇О',
- dataIndex: 'name',
- key: 'name',
- width: 100,
- fixed: 'left' as any,
- },
- {
- title: '鏍囪瘑绗�',
- dataIndex: 'identifier',
- key: 'identifier',
- width: 120,
- fixed: 'left' as any,
- },
- {
- title: '杈撳叆鍙傛暟',
- key: 'dataDefinition',
- minWidth: 150,
- },
- {
- title: '鍙傛暟鍊�',
- key: 'value',
- width: 180,
- },
- {
- title: '鎿嶄綔',
- key: 'action',
- width: 100,
- fixed: 'right' as any,
- },
-];
-
-// 鑾峰彇琛ㄥ崟鍊�
-function getFormValue(identifier: string) {
- return formData.value[identifier] ?? '';
-}
-
-// 璁剧疆琛ㄥ崟鍊�
-function setFormValue(identifier: string, value: any) {
- formData.value[identifier] = value;
-}
-
-/** 鑾峰彇灞炴�ф暟鎹被鍨� */
-function getPropertyDataType(row: ThingModelApi.ThingModel) {
- return row.property?.dataType;
-}
-
-/** 鍒ゆ柇灞炴�ф槸鍚︿负鏁板�肩被鍨� */
-function isNumberProperty(row: ThingModelApi.ThingModel) {
- return [
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.INT,
- ].includes(getPropertyDataType(row) as any);
-}
-
-/** 鍒ゆ柇灞炴�ф槸鍚︿娇鐢ㄤ笅鎷夐�夐」 */
-function isSelectProperty(row: ThingModelApi.ThingModel) {
- return [
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
- ].includes(getPropertyDataType(row) as any);
-}
-
-/** 鑾峰彇灞炴�ч�夐」 */
-function getPropertyOptions(row: ThingModelApi.ThingModel) {
- const list = row.property?.dataSpecsList || [];
- if (list.length > 0) {
- return list.map((item: any) => ({
- label: item.name || item.label || String(item.value),
- value: String(item.value),
- }));
- }
- if (getPropertyDataType(row) === IoTDataSpecsDataTypeEnum.BOOL) {
- return [
- { label: '鐪� (true)', value: 'true' },
- { label: '鍋� (false)', value: 'false' },
- ];
- }
- return [];
-}
-
-/** 鑾峰彇鐗╂ā鍨嬮�夐」鍘熷鍊� */
-function getMatchedPropertyOption(row: ThingModelApi.ThingModel, value: any) {
- return row.property?.dataSpecsList?.find(
- (item: any) => String(item.value) === String(value),
- );
-}
-
-/** 鎸夌墿妯″瀷鏁版嵁绫诲瀷杞崲灞炴�у�� */
-function normalizePropertyValue(row: ThingModelApi.ThingModel, value: any) {
- if (value === undefined || value === null || value === '') {
- return undefined;
- }
- const dataType = getPropertyDataType(row);
- if (isNumberProperty(row)) {
- return Number(value);
- }
- if (
- [IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.ENUM].includes(
- dataType as any,
- )
- ) {
- const option = getMatchedPropertyOption(row, value);
- if (option) {
- return option.value;
- }
- }
- if (dataType === IoTDataSpecsDataTypeEnum.BOOL) {
- if (String(value) === 'true') {
- return true;
- }
- if (String(value) === 'false') {
- return false;
- }
- }
- return value;
-}
-
-// 灞炴�т笂鎶�
-async function handlePropertyPost() {
- try {
- const params: Record<string, any> = {};
- propertyList.value.forEach((item) => {
- const value = normalizePropertyValue(
- item,
- formData.value[item.identifier!],
- );
- if (value !== undefined) {
- params[item.identifier!] = value;
- }
- });
-
- if (Object.keys(params).length === 0) {
- message.warning('璇疯嚦灏戣緭鍏ヤ竴涓睘鎬у��');
- return;
- }
-
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.PROPERTY_POST.method,
- params,
- });
-
- message.success('灞炴�т笂鎶ユ垚鍔�');
- // 寤惰繜鍒锋柊璁惧娑堟伅鍒楄〃
- deviceMessageRef.value?.refresh(deviceMessageRefreshDelay);
- } catch (error) {
- message.error('灞炴�т笂鎶ュけ璐�');
- console.error(error);
- }
-}
-
-// 浜嬩欢涓婃姤
-async function handleEventPost(row: ThingModelApi.ThingModel) {
- try {
- const valueStr = formData.value[row.identifier!];
- let eventValue: any;
-
- if (valueStr === undefined || valueStr === null || valueStr === '') {
- message.warning('璇疯緭鍏ヤ簨浠跺弬鏁�');
- return;
- }
- try {
- eventValue = JSON.parse(valueStr);
- } catch {
- message.error('浜嬩欢鍙傛暟鏍煎紡閿欒锛岃杈撳叆鏈夋晥鐨凧SON鏍煎紡');
- return;
- }
-
- // 涓庡悗绔� IotDeviceEventPostReqDTO 瀵归綈 锛歿 identifier, value, time }
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.EVENT_POST.method,
- params: {
- identifier: row.identifier,
- value: eventValue,
- time: Date.now(),
- },
- });
-
- message.success('浜嬩欢涓婃姤鎴愬姛');
- // 寤惰繜鍒锋柊璁惧娑堟伅鍒楄〃
- deviceMessageRef.value?.refresh(deviceMessageRefreshDelay);
- } catch (error) {
- message.error('浜嬩欢涓婃姤澶辫触');
- console.error(error);
- }
-}
-
-// 鐘舵�佸彉鏇�
-async function handleDeviceState(state: number) {
- try {
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.STATE_UPDATE.method,
- params: { state },
- });
-
- message.success('鐘舵�佸彉鏇存垚鍔�');
- // 寤惰繜鍒锋柊璁惧娑堟伅鍒楄〃
- deviceMessageRef.value?.refresh(deviceMessageRefreshDelay);
- } catch (error) {
- message.error('鐘舵�佸彉鏇村け璐�');
- console.error(error);
- }
-}
-
-// 灞炴�ц缃�
-async function handlePropertySet() {
- try {
- const params: Record<string, any> = {};
- propertyList.value.forEach((item) => {
- const value = normalizePropertyValue(
- item,
- formData.value[item.identifier!],
- );
- if (value !== undefined) {
- params[item.identifier!] = value;
- }
- });
-
- if (Object.keys(params).length === 0) {
- message.warning('璇疯嚦灏戣緭鍏ヤ竴涓睘鎬у��');
- return;
- }
-
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.PROPERTY_SET.method,
- params,
- });
-
- message.success('灞炴�ц缃垚鍔�');
- // 寤惰繜鍒锋柊璁惧娑堟伅鍒楄〃
- deviceMessageRef.value?.refresh(deviceMessageRefreshDelay);
- } catch (error) {
- message.error('灞炴�ц缃け璐�');
- console.error(error);
- }
-}
-
-// 鏈嶅姟璋冪敤
-async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
- try {
- const valueStr = formData.value[row.identifier!];
- let inputParams: any = {};
-
- if (valueStr === undefined || valueStr === null || valueStr === '') {
- message.warning('璇疯緭鍏ユ湇鍔″弬鏁�');
- return;
- }
- try {
- inputParams = JSON.parse(valueStr);
- } catch {
- message.error('鏈嶅姟鍙傛暟鏍煎紡閿欒锛岃杈撳叆鏈夋晥鐨凧SON鏍煎紡');
- return;
- }
- if (
- typeof inputParams !== 'object' ||
- inputParams === null ||
- Array.isArray(inputParams)
- ) {
- message.error('鏈嶅姟鍙傛暟蹇呴』鏄� JSON 瀵硅薄');
- return;
- }
-
- // 涓庡悗绔� IotDeviceServiceInvokeReqDTO 瀵归綈 锛歿 identifier, inputParams }
- await sendDeviceMessage({
- deviceId: props.device.id!,
- method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method,
- params: {
- identifier: row.identifier,
- inputParams,
- },
- });
-
- message.success('鏈嶅姟璋冪敤鎴愬姛');
- // 寤惰繜鍒锋柊璁惧娑堟伅鍒楄〃
- deviceMessageRef.value?.refresh(deviceMessageRefreshDelay);
- } catch (error) {
- message.error('鏈嶅姟璋冪敤澶辫触');
- console.error(error);
- }
-}
-
-/** 鍒囨崲璋冭瘯鏂规硶鏃舵竻绌鸿緭鍏ワ紝閬垮厤涓嶅悓鏂规硶涔嬮棿涓插彴鎻愪氦 */
-watch([activeTab, upstreamTab, downstreamTab], () => {
- formData.value = {};
-});
-</script>
-
-<template>
- <ContentWrap>
- <Row :gutter="16">
- <!-- 宸︿晶锛氭寚浠よ皟璇曞尯鍩� -->
- <Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24">
- <Card class="simulator-tabs h-full">
- <template #title>
- <div class="flex items-center justify-between">
- <span>鎸囦护璋冭瘯</span>
- <Button
- size="small"
- type="text"
- @click="debugCollapsed = !debugCollapsed"
- >
- <IconifyIcon v-if="!debugCollapsed" icon="lucide:chevron-up" />
- <IconifyIcon v-if="debugCollapsed" icon="lucide:chevron-down" />
- </Button>
- </div>
- </template>
- <div v-show="!debugCollapsed">
- <Tabs v-model:active-key="activeTab" size="small">
- <!-- 涓婅鎸囦护璋冭瘯 -->
- <Tabs.TabPane key="upstream" tab="涓婅鎸囦护璋冭瘯">
- <Tabs
- v-if="activeTab === 'upstream'"
- v-model:active-key="upstreamTab"
- size="small"
- >
- <!-- 灞炴�т笂鎶� -->
- <Tabs.TabPane
- :key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
- tab="灞炴�т笂鎶�"
- >
- <ContentWrap>
- <Table
- :columns="propertyColumns"
- :data-source="propertyList"
- :pagination="false"
- :scroll="{ x: 'max-content', y: 300 }"
- align="center"
- size="small"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'dataType'">
- {{ record.property?.dataType ?? '-' }}
- </template>
- <template v-else-if="column.key === 'dataDefinition'">
- <DataDefinition :data="record" />
- </template>
- <template v-else-if="column.key === 'value'">
- <InputNumber
- v-if="isNumberProperty(record)"
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆鍊�"
- size="small"
- class="w-full"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- <Select
- v-else-if="isSelectProperty(record)"
- :value="getFormValue(record.identifier)"
- :options="getPropertyOptions(record)"
- placeholder="璇烽�夋嫨鍊�"
- size="small"
- class="w-full"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- <Input
- v-else
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆鍊�"
- size="small"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- </template>
- </template>
- </Table>
- <div class="mt-4 flex items-center justify-between">
- <span class="text-sm text-gray-600">
- 璁剧疆灞炴�у�煎悗锛岀偣鍑汇�屽彂閫佸睘鎬т笂鎶ャ�嶆寜閽�
- </span>
- <Button type="primary" @click="handlePropertyPost">
- 鍙戦�佸睘鎬т笂鎶�
- </Button>
- </div>
- </ContentWrap>
- </Tabs.TabPane>
-
- <!-- 浜嬩欢涓婃姤 -->
- <Tabs.TabPane
- :key="IotDeviceMessageMethodEnum.EVENT_POST.method"
- tab="浜嬩欢涓婃姤"
- >
- <ContentWrap>
- <Table
- :columns="eventColumns"
- :data-source="eventList"
- :pagination="false"
- :scroll="{ x: 'max-content', y: 300 }"
- align="center"
- size="small"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'dataType'">
- {{ record.event?.dataType ?? '-' }}
- </template>
- <template v-else-if="column.key === 'dataDefinition'">
- <DataDefinition :data="record" />
- </template>
- <template v-else-if="column.key === 'value'">
- <Textarea
- :rows="3"
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆浜嬩欢鍙傛暟锛圝SON鏍煎紡锛�"
- size="small"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- </template>
- <template v-else-if="column.key === 'action'">
- <Button
- size="small"
- type="primary"
- @click="handleEventPost(record)"
- >
- 涓婃姤浜嬩欢
- </Button>
- </template>
- </template>
- </Table>
- </ContentWrap>
- </Tabs.TabPane>
-
- <!-- 鐘舵�佸彉鏇� -->
- <Tabs.TabPane
- :key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
- tab="鐘舵�佸彉鏇�"
- >
- <ContentWrap>
- <div class="flex gap-4">
- <Button
- type="primary"
- @click="handleDeviceState(DeviceStateEnum.ONLINE)"
- >
- 璁惧涓婄嚎
- </Button>
- <Button
- danger
- @click="handleDeviceState(DeviceStateEnum.OFFLINE)"
- >
- 璁惧涓嬬嚎
- </Button>
- </div>
- </ContentWrap>
- </Tabs.TabPane>
- </Tabs>
- </Tabs.TabPane>
-
- <!-- 涓嬭鎸囦护璋冭瘯 -->
- <Tabs.TabPane key="downstream" tab="涓嬭鎸囦护璋冭瘯">
- <Tabs
- v-if="activeTab === 'downstream'"
- v-model:active-key="downstreamTab"
- size="small"
- >
- <!-- 灞炴�ц皟璇� -->
- <Tabs.TabPane
- :key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
- tab="灞炴�ц缃�"
- >
- <ContentWrap>
- <Table
- :columns="propertyColumns"
- :data-source="propertyList"
- :pagination="false"
- :scroll="{ x: 'max-content', y: 300 }"
- align="center"
- size="small"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'dataType'">
- {{ record.property?.dataType ?? '-' }}
- </template>
- <template v-else-if="column.key === 'dataDefinition'">
- <DataDefinition :data="record" />
- </template>
- <template v-else-if="column.key === 'value'">
- <InputNumber
- v-if="isNumberProperty(record)"
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆鍊�"
- size="small"
- class="w-full"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- <Select
- v-else-if="isSelectProperty(record)"
- :value="getFormValue(record.identifier)"
- :options="getPropertyOptions(record)"
- placeholder="璇烽�夋嫨鍊�"
- size="small"
- class="w-full"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- <Input
- v-else
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆鍊�"
- size="small"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- </template>
- </template>
- </Table>
- <div class="mt-4 flex items-center justify-between">
- <span class="text-sm text-gray-600">
- 璁剧疆灞炴�у�煎悗锛岀偣鍑汇�屽彂閫佸睘鎬ц缃�嶆寜閽�
- </span>
- <Button type="primary" @click="handlePropertySet">
- 鍙戦�佸睘鎬ц缃�
- </Button>
- </div>
- </ContentWrap>
- </Tabs.TabPane>
-
- <!-- 鏈嶅姟璋冪敤 -->
- <Tabs.TabPane
- :key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
- tab="璁惧鏈嶅姟璋冪敤"
- >
- <ContentWrap>
- <Table
- :columns="serviceColumns"
- :data-source="serviceList"
- :pagination="false"
- :scroll="{ x: 'max-content', y: 300 }"
- align="center"
- size="small"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'dataDefinition'">
- <DataDefinition :data="record" />
- </template>
- <template v-else-if="column.key === 'value'">
- <Textarea
- :rows="3"
- :value="getFormValue(record.identifier)"
- placeholder="杈撳叆鏈嶅姟鍙傛暟锛圝SON鏍煎紡锛�"
- size="small"
- @update:value="
- setFormValue(record.identifier, $event)
- "
- />
- </template>
- <template v-else-if="column.key === 'action'">
- <Button
- size="small"
- type="primary"
- @click="handleServiceInvoke(record)"
- >
- 鏈嶅姟璋冪敤
- </Button>
- </template>
- </template>
- </Table>
- </ContentWrap>
- </Tabs.TabPane>
- </Tabs>
- </Tabs.TabPane>
- </Tabs>
- </div>
- </Card>
- </Col>
-
- <!-- 鍙充晶锛氳澶囨秷鎭尯鍩� -->
- <Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24">
- <Card class="h-full">
- <template #title>
- <div class="flex items-center justify-between">
- <span>璁惧娑堟伅</span>
- <Button
- size="small"
- type="text"
- @click="messageCollapsed = !messageCollapsed"
- >
- <IconifyIcon
- v-if="!messageCollapsed"
- icon="lucide:chevron-up"
- />
- <IconifyIcon
- v-if="messageCollapsed"
- icon="lucide:chevron-down"
- />
- </Button>
- </div>
- </template>
- <div v-show="!messageCollapsed">
- <DeviceDetailsMessage
- v-if="device.id"
- ref="deviceMessageRef"
- :device-id="device.id"
- />
- </div>
- </Card>
- </Col>
- </Row>
- </ContentWrap>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/sub-device.vue b/src/views/iot/device/device/detail/modules/sub-device.vue
deleted file mode 100644
index 5d2db63..0000000
--- a/src/views/iot/device/device/detail/modules/sub-device.vue
+++ /dev/null
@@ -1,358 +0,0 @@
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { ref, watch } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { confirm, Page, useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { DeviceTypeEnum, DICT_TYPE } from '../../../../../../packages/constants/src';
-import { formatDateTime, isEmpty } from '../../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- bindDeviceGateway,
- getSubDeviceList,
- getUnboundSubDevicePage,
- unbindDeviceGateway,
-} from '#/api/iot/device/device';
-import { getSimpleProductList } from '#/api/iot/product/product';
-
-interface Props {
- deviceId: number;
-}
-
-const props = defineProps<Props>();
-const router = useRouter();
-
-/** 瀛愯澶囧垪琛ㄨ〃鏍煎垪閰嶇疆 */
-function useGridColumns(): VxeTableGridOptions<IotDeviceApi.Device>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'deviceName',
- title: 'DeviceName',
- minWidth: 150,
- },
- {
- field: 'nickname',
- title: '澶囨敞鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'productName',
- title: '浜у搧鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'state',
- title: '璁惧鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_DEVICE_STATE },
- },
- },
- {
- field: 'onlineTime',
- title: '鏈�鍚庝笂绾挎椂闂�',
- minWidth: 160,
- formatter: ({ cellValue }) => formatDateTime(cellValue),
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async () => {
- if (!props.deviceId) {
- return [];
- }
- return await getSubDeviceList(props.deviceId);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- pagerConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<IotDeviceApi.Device>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-
-/** 鑾峰彇瀛愯澶囧垪琛� */
-function getList() {
- gridApi.query();
-}
-
-/** 鎵撳紑璁惧璇︽儏 */
-function openDeviceDetail(id: number) {
- router.push({ name: 'IoTDeviceDetail', params: { id } });
-}
-
-/** 澶氶�夋閫変腑鏁版嵁 */
-const checkedIds = ref<number[]>([]);
-function handleRowCheckboxChange({
- records,
-}: {
- records: IotDeviceApi.Device[];
-}) {
- checkedIds.value = records.map((item) => item.id!);
-}
-
-/** 瑙g粦鍗曚釜璁惧 */
-async function handleUnbind(row: IotDeviceApi.Device) {
- await confirm(`纭畾瑕佽В缁戝瓙璁惧銆�${row.deviceName}銆戝悧锛焋);
- const hideLoading = message.loading({
- content: `姝e湪瑙g粦銆�${row.deviceName}銆�...`,
- duration: 0,
- });
- try {
- await unbindDeviceGateway(props.deviceId, [row.id!]);
- message.success('瑙g粦鎴愬姛');
- getList();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎵归噺瑙g粦 */
-async function handleUnbindBatch() {
- await confirm(`纭畾瑕佽В缁戦�変腑鐨� ${checkedIds.value.length} 涓瓙璁惧鍚楋紵`);
- const hideLoading = message.loading({
- content: '姝e湪鎵归噺瑙g粦...',
- duration: 0,
- });
- try {
- await unbindDeviceGateway(props.deviceId, checkedIds.value);
- checkedIds.value = [];
- message.success('鎵归噺瑙g粦鎴愬姛');
- getList();
- } finally {
- hideLoading();
- }
-}
-
-// ===================== 娣诲姞瀛愯澶囧脊绐� =====================
-
-const addSelectedRowKeys = ref<number[]>([]);
-
-/** 娣诲姞寮圭獥鎼滅储琛ㄥ崟 schema */
-function useAddGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'productId',
- label: '浜у搧',
- component: 'ApiSelect',
- componentProps: {
- api: () => getSimpleProductList(DeviceTypeEnum.GATEWAY_SUB),
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- allowClear: true,
- },
- },
- {
- fieldName: 'deviceName',
- label: 'DeviceName',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� DeviceName',
- allowClear: true,
- },
- },
- ];
-}
-
-function useAddGridColumns(): VxeTableGridOptions<IotDeviceApi.Device>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'deviceName',
- title: 'DeviceName',
- minWidth: 150,
- },
- {
- field: 'nickname',
- title: '澶囨敞鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'productName',
- title: '浜у搧鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'state',
- title: '璁惧鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_DEVICE_STATE },
- },
- },
- ];
-}
-
-const [AddGrid, addGridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useAddGridFormSchema(),
- submitOnChange: true,
- },
- gridOptions: {
- columns: useAddGridColumns(),
- height: 400,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getUnboundSubDevicePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotDeviceApi.Device>,
- gridEvents: {
- checkboxAll: handleAddSelectionChange,
- checkboxChange: handleAddSelectionChange,
- },
-});
-
-/** 澶勭悊娣诲姞寮圭獥琛ㄦ牸閫夋嫨鍙樺寲 */
-function handleAddSelectionChange() {
- const records = addGridApi.grid?.getCheckboxRecords() || [];
- addSelectedRowKeys.value = records.map(
- (record: IotDeviceApi.Device) => record.id!,
- );
-}
-
-const [AddModal, addModalApi] = useVbenModal({
- async onConfirm() {
- if (addSelectedRowKeys.value.length === 0) {
- message.warning('璇峰厛閫夋嫨瑕佹坊鍔犵殑瀛愯澶�');
- return;
- }
- addModalApi.lock();
- try {
- await bindDeviceGateway(props.deviceId, addSelectedRowKeys.value);
- message.success('缁戝畾鎴愬姛');
- await addModalApi.close();
- addSelectedRowKeys.value = [];
- getList();
- } finally {
- addModalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (isOpen) {
- addSelectedRowKeys.value = [];
- await addGridApi.formApi?.resetForm();
- await addGridApi.query();
- }
- },
-});
-
-/** 鎵撳紑娣诲姞瀛愯澶囧脊绐� */
-function openAddModal() {
- addModalApi.open();
-}
-
-/** 鐩戝惉 deviceId 鍙樺寲 */
-watch(
- () => props.deviceId,
- (newVal) => {
- if (newVal) {
- getList();
- }
- },
- { immediate: true },
-);
-</script>
-
-<template>
- <Page auto-content-height>
- <!-- 瀛愯澶囧垪琛� -->
- <Grid table-title="瀛愯澶囧垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '娣诲姞瀛愯澶�',
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:device:update'],
- onClick: openAddModal,
- },
- {
- label: '鎵归噺瑙g粦',
- type: 'primary',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:device:update'],
- disabled: isEmpty(checkedIds),
- onClick: handleUnbindBatch,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鏌ョ湅',
- type: 'link',
- onClick: () => openDeviceDetail(row.id!),
- },
- {
- label: '瑙g粦',
- type: 'link',
- danger: true,
- auth: ['iot:device:update'],
- onClick: () => handleUnbind(row),
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 娣诲姞瀛愯澶囧脊绐� -->
- <AddModal title="娣诲姞瀛愯澶�" class="w-3/5">
- <AddGrid />
- </AddModal>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/thing-model-event.vue b/src/views/iot/device/device/detail/modules/thing-model-event.vue
deleted file mode 100644
index efceb70..0000000
--- a/src/views/iot/device/device/detail/modules/thing-model-event.vue
+++ /dev/null
@@ -1,272 +0,0 @@
-<!-- 璁惧浜嬩欢绠$悊 -->
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
-
-import { Page } from '../../../../../../packages/effects/common-ui/src';
-import {
- getEventTypeLabel,
- IotDeviceMessageMethodEnum,
- IoTThingModelTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDateTime } from '../../../../../../packages/utils/src';
-
-import { Button, RangePicker, Select, Space, Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getDeviceMessagePairPage } from '#/api/iot/device/device';
-
-const props = defineProps<{
- deviceId: number;
- thingModelList: ThingModelApi.ThingModel[];
-}>();
-
-/** 鏌ヨ鍙傛暟 */
-const queryParams = reactive({
- identifier: '',
- times: undefined as [string, string] | undefined,
-});
-let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 寤惰繜鍒锋柊瀹氭椂鍣�
-
-/** 浜嬩欢绫诲瀷鐨勭墿妯″瀷鏁版嵁 */
-const eventThingModels = computed(() => {
- return props.thingModelList.filter(
- (item: ThingModelApi.ThingModel) =>
- String(item.type) === String(IoTThingModelTypeEnum.EVENT),
- );
-});
-
-/** Grid 鍒楀畾涔� */
-function useGridColumns(): VxeTableGridOptions<Record<string, any>>['columns'] {
- return [
- {
- field: 'reportTime',
- title: '涓婃姤鏃堕棿',
- width: 180,
- slots: { default: 'reportTime' },
- },
- {
- field: 'identifier',
- title: '鏍囪瘑绗�',
- width: 160,
- slots: { default: 'identifier' },
- },
- {
- field: 'eventName',
- title: '浜嬩欢鍚嶇О',
- width: 160,
- slots: { default: 'eventName' },
- },
- {
- field: 'eventType',
- title: '浜嬩欢绫诲瀷',
- width: 100,
- slots: { default: 'eventType' },
- },
- {
- field: 'params',
- title: '杈撳叆鍙傛暟',
- minWidth: 200,
- showOverflow: 'tooltip',
- slots: { default: 'params' },
- },
- ];
-}
-
-/** 鍒涘缓 Grid 瀹炰緥 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- if (!props.deviceId) {
- return { list: [], total: 0 };
- }
- return await getDeviceMessagePairPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- deviceId: props.deviceId,
- method: IotDeviceMessageMethodEnum.EVENT_POST.method,
- identifier: queryParams.identifier || undefined,
- times: queryParams.times,
- });
- },
- },
- },
- toolbarConfig: {
- refresh: false,
- search: false,
- },
- pagerConfig: {
- enabled: true,
- },
- } as VxeTableGridOptions,
-});
-
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-function handleQuery() {
- gridApi.query();
-}
-
-/** 閲嶇疆鎸夐挳鎿嶄綔 */
-function resetQuery() {
- queryParams.identifier = '';
- queryParams.times = undefined;
- handleQuery();
-}
-
-/** 鑾峰彇浜嬩欢鍚嶇О */
-function getEventName(identifier: string | undefined) {
- if (!identifier) return '-';
- const event = eventThingModels.value.find(
- (item: ThingModelApi.ThingModel) => item.identifier === identifier,
- );
- return event?.name || identifier;
-}
-
-/** 鑾峰彇浜嬩欢绫诲瀷 */
-function getEventType(identifier: string | undefined) {
- if (!identifier) return '-';
- const event = eventThingModels.value.find(
- (item: ThingModelApi.ThingModel) => item.identifier === identifier,
- );
- if (!event?.event?.type) return '-';
- return getEventTypeLabel(event.event.type) || '-';
-}
-
-/** 瑙f瀽鍙傛暟 */
-function parseParams(params: string) {
- try {
- const parsed = JSON.parse(params);
- if (parsed.params) {
- return parsed.params;
- }
- return parsed;
- } catch {
- return {};
- }
-}
-
-/** 鍒锋柊鍒楄〃 */
-function refresh(delay = 0) {
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
- if (delay > 0) {
- refreshTimer = setTimeout(() => {
- gridApi.query();
- refreshTimer = undefined;
- }, delay);
- } else {
- gridApi.query();
- }
-}
-
-/** 鐩戝惉璁惧鏍囪瘑鍙樺寲 */
-watch(
- () => props.deviceId,
- (newValue) => {
- if (newValue) {
- handleQuery();
- }
- },
-);
-
-/** 鍒濆鍖� */
-onMounted(() => {
- if (props.deviceId) {
- handleQuery();
- }
-});
-
-/** 缁勪欢鍗歌浇鏃舵竻闄ゅ欢杩熷埛鏂板畾鏃跺櫒 */
-onBeforeUnmount(() => {
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
-});
-
-/** 鏆撮湶鏂规硶缁欑埗缁勪欢 */
-defineExpose({
- refresh,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <!-- 鎼滅储鍖哄煙 -->
- <div class="mb-4 flex flex-wrap items-center gap-3">
- <div class="flex items-center gap-2">
- <span>鏍囪瘑绗︼細</span>
- <Select
- v-model:value="queryParams.identifier"
- allow-clear
- placeholder="璇烽�夋嫨浜嬩欢鏍囪瘑绗�"
- style="width: 240px"
- >
- <Select.Option
- v-for="event in eventThingModels"
- :key="event.identifier"
- :value="event.identifier!"
- >
- {{ event.name }}({{ event.identifier }})
- </Select.Option>
- </Select>
- </div>
- <div class="flex items-center gap-2">
- <span>鏃堕棿鑼冨洿锛�</span>
- <RangePicker
- v-model:value="queryParams.times"
- format="YYYY-MM-DD HH:mm:ss"
- show-time
- style="width: 360px"
- value-format="YYYY-MM-DD HH:mm:ss"
- />
- </div>
- <Space>
- <Button type="primary" @click="handleQuery">
- <template #icon>
- <IconifyIcon icon="ep:search" />
- </template>
- 鎼滅储
- </Button>
- <Button @click="resetQuery">
- <template #icon>
- <IconifyIcon icon="ep:refresh" />
- </template>
- 閲嶇疆
- </Button>
- </Space>
- </div>
-
- <!-- 浜嬩欢鍒楄〃 -->
- <Grid>
- <template #reportTime="{ row }">
- {{
- row.request?.reportTime ? formatDateTime(row.request.reportTime) : '-'
- }}
- </template>
- <template #identifier="{ row }">
- <Tag color="blue" size="small">
- {{ row.request?.identifier }}
- </Tag>
- </template>
- <template #eventName="{ row }">
- {{ getEventName(row.request?.identifier) }}
- </template>
- <template #eventType="{ row }">
- {{ getEventType(row.request?.identifier) }}
- </template>
- <template #params="{ row }">
- {{ parseParams(row.request?.params) }}
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/thing-model-property-history.vue b/src/views/iot/device/device/detail/modules/thing-model-property-history.vue
deleted file mode 100644
index cd888d0..0000000
--- a/src/views/iot/device/device/detail/modules/thing-model-property-history.vue
+++ /dev/null
@@ -1,478 +0,0 @@
-<!-- 璁惧鐗╂ā鍨� -> 杩愯鐘舵�� -> 鏌ョ湅鏁版嵁锛堣澶囩殑灞炴�у�煎巻鍙诧級-->
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { EchartsUIType } from '../../../../../../packages/effects/plugins/src/echarts';
-
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { computed, nextTick, reactive, ref, watch } from 'vue';
-
-import { IoTDataSpecsDataTypeEnum } from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { EchartsUI, useEcharts } from '../../../../../../packages/effects/plugins/src/echarts';
-import { formatDate } from '../../../../../../packages/utils/src';
-
-import {
- Button,
- Empty,
- message,
- Modal,
- Space,
- Spin,
- Table,
- Tag,
-} from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import { getHistoryDevicePropertyList } from '#/api/iot/device/device';
-import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
-
-defineProps<{ deviceId: number }>();
-
-const dialogVisible = ref(false); // 寮圭獥鐨勬槸鍚﹀睍绀�
-const loading = ref(false);
-const viewMode = ref<'chart' | 'list'>('chart'); // 瑙嗗浘妯″紡鐘舵��
-const list = ref<Array<IotDeviceApi.DeviceProperty & { _rowKey: string }>>([]); // 鍒楄〃鐨勬暟鎹�
-const total = ref(0); // 鎬绘暟鎹噺
-const thingModelDataType = ref<string>(''); // 鐗╂ā鍨嬫暟鎹被鍨�
-const propertyIdentifier = ref<string>(''); // 灞炴�ф爣璇嗙
-
-const dateRange = ref<[string, string]>([
- dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
- dayjs().format('YYYY-MM-DD'),
-]); // 鏃堕棿鑼冨洿锛堜粎鏃ユ湡锛屼笉鍖呭惈鏃跺垎绉掞級
-
-const queryParams = reactive({
- deviceId: -1,
- identifier: '',
- times: formatDateRangeWithTime(dateRange.value),
-});
-
-// Echarts 鐩稿叧
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 涓嶆敮鎸佸浘琛ㄥ睍绀虹殑鏁版嵁绫诲瀷鍒楄〃 */
-const CHART_DISABLED_DATA_TYPES = [
- IoTDataSpecsDataTypeEnum.ARRAY, // 鏁扮粍
- IoTDataSpecsDataTypeEnum.STRUCT, // 缁撴瀯浣�
- IoTDataSpecsDataTypeEnum.TEXT, // 鏂囨湰鍨�
- IoTDataSpecsDataTypeEnum.BOOL, // 甯冨皵鍨�
- IoTDataSpecsDataTypeEnum.ENUM, // 鏋氫妇鍨�
- IoTDataSpecsDataTypeEnum.DATE, // 鏃堕棿鍨�
-] as const;
-
-/** 鍒ゆ柇鏄惁鏀寔鍥捐〃灞曠ず锛堜粎鏁板�肩被鍨嬫敮鎸侊細int銆乫loat銆乨ouble锛� */
-const canShowChart = computed(() => {
- if (!thingModelDataType.value) return false;
- return !CHART_DISABLED_DATA_TYPES.includes(
- thingModelDataType.value as (typeof CHART_DISABLED_DATA_TYPES)[number],
- );
-});
-
-/** 鍒ゆ柇鏄惁涓哄鏉傛暟鎹被鍨嬶紙鐢ㄤ簬鏍煎紡鍖栨樉绀猴級 */
-const isComplexDataType = computed(() => {
- if (!thingModelDataType.value) return false;
- return (
- thingModelDataType.value === IoTDataSpecsDataTypeEnum.ARRAY ||
- thingModelDataType.value === IoTDataSpecsDataTypeEnum.STRUCT
- );
-});
-
-/** 鏈�澶у�肩粺璁� */
-const maxValue = computed(() => {
- if (!canShowChart.value || list.value.length === 0) return '-';
- const values = list.value
- .map((item) => Number(item.value))
- .filter((value) => !Number.isNaN(value));
- return values.length > 0 ? Math.max(...values).toFixed(2) : '-';
-});
-
-/** 鏈�灏忓�肩粺璁� */
-const minValue = computed(() => {
- if (!canShowChart.value || list.value.length === 0) return '-';
- const values = list.value
- .map((item) => Number(item.value))
- .filter((value) => !Number.isNaN(value));
- return values.length > 0 ? Math.min(...values).toFixed(2) : '-';
-});
-
-/** 骞冲潎鍊肩粺璁� */
-const avgValue = computed(() => {
- if (!canShowChart.value || list.value.length === 0) return '-';
- const values = list.value
- .map((item) => Number(item.value))
- .filter((value) => !Number.isNaN(value));
- if (values.length === 0) return '-';
- const sum = values.reduce((total, value) => total + value, 0);
- return (sum / values.length).toFixed(2);
-});
-
-/** 灏嗘棩鏈熻寖鍥磋浆鎹负甯︽椂鍒嗙鐨勬牸寮� */
-function formatDateRangeWithTime(dates: [string, string]): [string, string] {
- return [`${dates[0]} 00:00:00`, `${dates[1]} 23:59:59`];
-}
-
-const tableColumns = computed(() => [
- {
- title: '搴忓彿',
- key: 'index',
- width: 80,
- align: 'center' as const,
- customRender: ({ index }: { index: number }) => index + 1,
- },
- {
- title: '鏃堕棿',
- key: 'updateTime',
- dataIndex: 'updateTime',
- width: 200,
- align: 'center' as const,
- },
- {
- title: '灞炴�у��',
- key: 'value',
- dataIndex: 'value',
- align: 'center' as const,
- },
-]); // 琛ㄦ牸鍒楅厤缃�
-
-/** 鑾峰緱璁惧鍘嗗彶鏁版嵁 */
-async function getList() {
- loading.value = true;
- try {
- const data = await getHistoryDevicePropertyList(queryParams);
- list.value = (data || []).map((item, idx) => ({
- ...item,
- _rowKey: `${item.updateTime ?? ''}-${idx}`, // 鍚庣鐩存帴杩斿洖鏁扮粍锛屼粎鍚� value/updateTime锛岀粰姣忚琛� _rowKey 淇濊瘉鍞竴
- }));
- total.value = list.value.length;
-
- // 濡傛灉鏄浘琛ㄦā寮忎笖鏀寔鍥捐〃灞曠ず锛岀瓑寰呮覆鏌撳浘琛�
- if (
- viewMode.value === 'chart' &&
- canShowChart.value &&
- list.value.length > 0
- ) {
- await renderChartWhenReady();
- }
- } catch {
- message.error('鑾峰彇鏁版嵁澶辫触');
- list.value = [];
- total.value = 0;
- } finally {
- loading.value = false;
- }
-}
-
-/** 纭繚鍥捐〃瀹瑰櫒宸茬粡鍙鍚庡啀娓叉煋 */
-async function renderChartWhenReady() {
- if (!list.value || list.value.length === 0) {
- return;
- }
- // 绛夊緟 Modal銆丆ard loading 鐘舵�併�乿-show 绛� DOM 鏇存柊瀹屾垚
- await nextTick();
- await nextTick();
- renderChart();
-}
-
-/** 娓叉煋鍥捐〃 */
-function renderChart() {
- if (!list.value || list.value.length === 0) {
- return;
- }
-
- const times = list.value.map((item) =>
- formatDate(new Date(item.updateTime), 'YYYY-MM-DD HH:mm:ss'),
- );
- const values = list.value.map((item) => Number(item.value));
-
- renderEcharts({
- title: {
- text: '灞炴�у�艰秼鍔�',
- left: 'center',
- textStyle: {
- fontSize: 16,
- fontWeight: 'normal',
- },
- },
- grid: {
- left: 60,
- right: 60,
- bottom: 100,
- top: 80,
- containLabel: true,
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- },
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- name: '鏃堕棿',
- nameTextStyle: {
- padding: [10, 0, 0, 0],
- },
- data: times,
- },
- yAxis: {
- type: 'value',
- name: '灞炴�у��',
- nameTextStyle: {
- padding: [0, 0, 10, 0],
- },
- },
- series: [
- {
- name: '灞炴�у��',
- type: 'line',
- smooth: true,
- symbol: 'circle',
- symbolSize: 6,
- lineStyle: {
- width: 2,
- color: '#1890FF',
- },
- itemStyle: {
- color: '#1890FF',
- },
- areaStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- {
- offset: 0,
- color: 'rgba(24, 144, 255, 0.3)',
- },
- {
- offset: 1,
- color: 'rgba(24, 144, 255, 0.05)',
- },
- ],
- },
- },
- data: values,
- },
- ],
- dataZoom: [
- {
- type: 'inside',
- start: 0,
- end: 100,
- },
- {
- type: 'slider',
- height: 30,
- bottom: 20,
- },
- ],
- });
-}
-
-/** 鎵撳紑寮圭獥 */
-async function open(deviceId: number, identifier: string, dataType: string) {
- dialogVisible.value = true;
- queryParams.deviceId = deviceId;
- queryParams.identifier = identifier;
- propertyIdentifier.value = identifier;
- thingModelDataType.value = dataType;
-
- // 閲嶇疆鏃堕棿鑼冨洿涓烘渶杩�7澶�
- dateRange.value = [
- dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
- dayjs().format('YYYY-MM-DD'),
- ];
-
- // 鏇存柊鏌ヨ鍙傛暟鐨勬椂闂�
- queryParams.times = formatDateRangeWithTime(dateRange.value);
-
- // 濡傛灉涓嶆敮鎸佸浘琛ㄥ睍绀猴紝榛樿浣跨敤鍒楄〃妯″紡
- viewMode.value = canShowChart.value ? 'chart' : 'list';
-
- // 绛夊緟寮圭獥瀹屽叏娓叉煋鍚庡啀鑾峰彇鏁版嵁
- await nextTick();
- await nextTick(); // 鍙岄噸 nextTick 纭繚 Modal 瀹屽叏娓叉煋
- await getList();
-}
-
-/** 澶勭悊鏃堕棿鑼冨洿鍙樺寲 */
-function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
- if (!times || times.length !== 2) {
- return;
- }
- dateRange.value = [
- dayjs(times[0]).format('YYYY-MM-DD'),
- dayjs(times[1]).format('YYYY-MM-DD'),
- ];
- // 灏嗛�夋嫨鐨勬棩鏈熻浆鎹负甯︽椂鍒嗙鐨勬牸寮忥紙寮�濮嬫棩鏈� 00:00:00锛岀粨鏉熸棩鏈� 23:59:59锛�
- queryParams.times = formatDateRangeWithTime(dateRange.value);
- getList();
-}
-
-/** 鍒锋柊鏁版嵁 */
-function handleRefresh() {
- getList();
-}
-
-/** 鍏抽棴寮圭獥 */
-function handleClose() {
- dialogVisible.value = false;
- list.value = [];
- total.value = 0;
-}
-
-/** 鏍煎紡鍖栧鏉傛暟鎹被鍨� */
-function formatComplexValue(value: any) {
- if (typeof value === 'object') {
- return JSON.stringify(value);
- }
- return String(value);
-}
-
-/** 鐩戝惉瑙嗗浘妯″紡鍙樺寲锛岄噸鏂版覆鏌撳浘琛� */
-watch(viewMode, async (newMode) => {
- if (newMode === 'chart' && canShowChart.value && list.value.length > 0) {
- await renderChartWhenReady();
- }
-});
-
-defineExpose({ open }); // 鎻愪緵 open 鏂规硶锛岀敤浜庢墦寮�寮圭獥
-</script>
-<template>
- <Modal
- v-model:open="dialogVisible"
- title="鏌ョ湅鏁版嵁"
- width="1200px"
- @cancel="handleClose"
- >
- <div class="property-history-container">
- <!-- 宸ュ叿鏍� -->
- <div class="toolbar-wrapper mb-4">
- <Space :size="12" class="w-full" wrap>
- <!-- 鏃堕棿閫夋嫨 -->
- <div class="flex items-center gap-3">
- <span class="whitespace-nowrap text-sm text-gray-500">
- 鏃堕棿鑼冨洿
- </span>
- <ShortcutDateRangePicker @change="handleDateRangeChange" />
- </div>
-
- <!-- 鍒锋柊鎸夐挳 -->
- <Button :loading="loading" @click="handleRefresh">
- <template #icon>
- <IconifyIcon icon="ant-design:reload-outlined" />
- </template>
- 鍒锋柊
- </Button>
-
- <!-- 瑙嗗浘鍒囨崲 -->
- <Button.Group class="ml-auto">
- <Button
- :disabled="!canShowChart"
- :type="viewMode === 'chart' ? 'primary' : 'default'"
- @click="viewMode = 'chart'"
- >
- <template #icon>
- <IconifyIcon icon="ant-design:line-chart-outlined" />
- </template>
- 鍥捐〃
- </Button>
- <Button
- :type="viewMode === 'list' ? 'primary' : 'default'"
- @click="viewMode = 'list'"
- >
- <template #icon>
- <IconifyIcon icon="ant-design:table-outlined" />
- </template>
- 鍒楄〃
- </Button>
- </Button.Group>
- </Space>
-
- <!-- 鏁版嵁缁熻淇℃伅 -->
- <div v-if="list.length > 0" class="mt-3 text-sm text-gray-600">
- <Space :size="16">
- <span>鍏� {{ total }} 鏉℃暟鎹�</span>
- <span v-if="viewMode === 'chart' && canShowChart">
- 鏈�澶у��: {{ maxValue }} | 鏈�灏忓��: {{ minValue }} | 骞冲潎鍊�:
- {{ avgValue }}
- </span>
- </Space>
- </div>
- </div>
-
- <!-- 鏁版嵁灞曠ず鍖哄煙 -->
- <Spin :delay="200" :spinning="loading">
- <!-- 鍥捐〃妯″紡 - 浣跨敤 v-show 纭繚鍥捐〃缁勪欢濮嬬粓鎸傝浇 -->
- <div v-show="viewMode === 'chart'" class="chart-container">
- <Empty
- v-if="list.length === 0"
- :image="Empty.PRESENTED_IMAGE_SIMPLE"
- class="py-20"
- :description="$t('common.noData')"
- />
- <div v-show="list.length > 0">
- <EchartsUI ref="chartRef" height="500px" />
- </div>
- </div>
-
- <!-- 琛ㄦ牸妯″紡 -->
- <div v-show="viewMode === 'list'" class="table-container">
- <Table
- :columns="tableColumns"
- :data-source="list"
- :pagination="false"
- :scroll="{ y: 500 }"
- row-key="_rowKey"
- size="small"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'updateTime'">
- {{ formatDate(new Date(record.updateTime)) }}
- </template>
- <template v-else-if="column.key === 'value'">
- <Tag v-if="isComplexDataType" color="processing">
- {{ formatComplexValue(record.value) }}
- </Tag>
- <span v-else class="font-medium">{{ record.value }}</span>
- </template>
- </template>
- </Table>
- </div>
- </Spin>
- </div>
-
- <template #footer>
- <Button @click="handleClose">鍏抽棴</Button>
- </template>
- </Modal>
-</template>
-
-<style lang="scss" scoped>
-.property-history-container {
- max-height: 70vh;
- overflow: auto;
-
- .toolbar-wrapper {
- padding: 16px;
- background-color: hsl(var(--card) / 90%);
- border: 1px solid hsl(var(--border) / 60%);
- border-radius: 8px;
- }
-
- .chart-container,
- .table-container {
- padding: 16px;
- background-color: hsl(var(--card) / 100%);
- border: 1px solid hsl(var(--border) / 60%);
- border-radius: 8px;
- }
-}
-</style>
diff --git a/src/views/iot/device/device/detail/modules/thing-model-property.vue b/src/views/iot/device/device/detail/modules/thing-model-property.vue
deleted file mode 100644
index 94f62bf..0000000
--- a/src/views/iot/device/device/detail/modules/thing-model-property.vue
+++ /dev/null
@@ -1,427 +0,0 @@
-<!-- 璁惧灞炴�х鐞� -->
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import {
- nextTick,
- onBeforeUnmount,
- onMounted,
- reactive,
- ref,
- watch,
-} from 'vue';
-
-import { Page } from '../../../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDateTime } from '../../../../../../packages/utils/src';
-
-import {
- Button,
- Card,
- Col,
- Divider,
- Input,
- Row,
- Switch,
- Tag,
-} from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getLatestDeviceProperties } from '#/api/iot/device/device';
-
-import DeviceDetailsThingModelPropertyHistory from './thing-model-property-history.vue';
-
-const props = defineProps<{ deviceId: number }>();
-
-const loading = ref(true); // 鍒楄〃鐨勫姞杞戒腑
-const list = ref<IotDeviceApi.DevicePropertyDetail[]>([]); // 鏄剧ず鐨勫垪琛ㄦ暟鎹�
-const filterList = ref<IotDeviceApi.DevicePropertyDetail[]>([]); // 瀹屾暣鐨勬暟鎹垪琛�
-const queryParams = reactive({
- keyword: '' as string,
-}); // 鏌ヨ鍙傛暟
-const autoRefresh = ref(false); // 鑷姩鍒锋柊寮�鍏�
-let autoRefreshTimer: any = null; // 瀹氭椂鍣�
-const viewMode = ref<'card' | 'list'>('card'); // 瑙嗗浘妯″紡鐘舵��
-
-/** Grid 鍒楀畾涔� */
-function useGridColumns(): VxeTableGridOptions<IotDeviceApi.DevicePropertyDetail>['columns'] {
- return [
- {
- field: 'identifier',
- title: '灞炴�ф爣璇嗙',
- },
- {
- field: 'name',
- title: '灞炴�у悕绉�',
- },
- {
- field: 'dataType',
- title: '鏁版嵁绫诲瀷',
- },
- {
- field: 'value',
- title: '灞炴�у��',
- slots: { default: 'value' },
- },
- {
- field: 'updateTime',
- title: '鏇存柊鏃堕棿',
- width: 180,
- slots: { default: 'updateTime' },
- },
- {
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍒涘缓 Grid 瀹炰緥 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- rowConfig: {
- keyField: 'identifier',
- isHover: true,
- },
- proxyConfig: {
- ajax: {
- query: async () => {
- if (!props.deviceId) {
- return { list: [], total: 0 };
- }
- const data = await getLatestDeviceProperties({
- deviceId: props.deviceId,
- identifier: undefined,
- name: undefined,
- });
- // 绛涢�夋暟鎹�
- let filteredData = data;
- if (queryParams.keyword.trim()) {
- const keyword = queryParams.keyword.toLowerCase();
- filteredData = data.filter(
- (item: IotDeviceApi.DevicePropertyDetail) =>
- item.identifier?.toLowerCase().includes(keyword) ||
- item.name?.toLowerCase().includes(keyword),
- );
- }
- // 鏇存柊鏈湴鍒楄〃鐢ㄤ簬鍗$墖瑙嗗浘
- filterList.value = data;
- list.value = filteredData;
- return {
- list: filteredData,
- total: filteredData.length,
- };
- },
- },
- },
- toolbarConfig: {
- refresh: false,
- search: false,
- },
- pagerConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<IotDeviceApi.DevicePropertyDetail>,
-});
-
-// 鍖呰 gridApi.query() 鏂规硶锛岀粺涓�鍒楄〃瑙嗗浘鍜屽崱鐗囪鍥剧殑鏌ヨ鎺ュ彛
-gridApi.query = async () => {
- if (viewMode.value === 'list') {
- // 鍒楄〃瑙嗗浘锛氭墜鍔ㄨ幏鍙栨暟鎹苟鍔犺浇鍒� Grid
- if (!props.deviceId) {
- return;
- }
- const data = await getLatestDeviceProperties({
- deviceId: props.deviceId,
- identifier: undefined,
- name: undefined,
- });
- const dataArray = Array.isArray(data) ? data : [];
- let filteredData = dataArray;
- if (queryParams.keyword.trim()) {
- const keyword = queryParams.keyword.toLowerCase();
- filteredData = dataArray.filter(
- (item: IotDeviceApi.DevicePropertyDetail) =>
- item.identifier?.toLowerCase().includes(keyword) ||
- item.name?.toLowerCase().includes(keyword),
- );
- }
- filterList.value = dataArray;
- list.value = filteredData;
- // 鐩存帴鍔犺浇鏁版嵁鍒� Grid
- if (gridApi.grid) {
- gridApi.grid.loadData(filteredData);
- }
- } else {
- // 鍗$墖瑙嗗浘锛氳皟鐢� getList 鏂规硶
- await getList();
- }
-};
-
-/** 鏌ヨ鍒楄〃 */
-async function getList() {
- loading.value = true;
- try {
- if (viewMode.value === 'list') {
- await gridApi.query();
- } else {
- // 鍗$墖瑙嗗浘锛氭墜鍔ㄨ幏鍙栨暟鎹�
- const params = {
- deviceId: props.deviceId,
- identifier: undefined as string | undefined,
- name: undefined as string | undefined,
- };
- filterList.value = await getLatestDeviceProperties(params);
- handleFilter();
- }
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍓嶇绛涢�夋暟鎹� */
-function handleFilter() {
- if (queryParams.keyword.trim()) {
- const keyword = queryParams.keyword.toLowerCase();
- list.value = filterList.value.filter(
- (item: IotDeviceApi.DevicePropertyDetail) =>
- item.identifier?.toLowerCase().includes(keyword) ||
- item.name?.toLowerCase().includes(keyword),
- );
- } else {
- list.value = filterList.value;
- }
-}
-
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-function handleQuery() {
- if (viewMode.value === 'list') {
- gridApi.query();
- } else {
- handleFilter();
- }
-}
-
-/** 鎼滅储鍏抽敭璇嶅彉鍖� */
-function handleKeywordChange(event: Event) {
- if (!(event.target as HTMLInputElement).value) {
- handleQuery();
- }
-}
-
-/** 瑙嗗浘鍒囨崲 */
-async function handleViewModeChange(mode: 'card' | 'list') {
- if (viewMode.value === mode) {
- return;
- }
- viewMode.value = mode;
- await nextTick();
- gridApi.query();
-}
-
-/** 鍘嗗彶鎿嶄綔 */
-const historyRef = ref();
-function openHistory(deviceId: number, identifier: string, dataType: string) {
- historyRef.value.open(deviceId, identifier, dataType);
-}
-
-/** 鏍煎紡鍖栧睘鎬у�煎拰鍗曚綅 */
-function formatValueWithUnit(item: IotDeviceApi.DevicePropertyDetail) {
- if (item.value === null || item.value === undefined || item.value === '') {
- return '-';
- }
- const unitName = item.dataSpecs?.unitName;
- return unitName ? `${item.value} ${unitName}` : item.value;
-}
-
-/** 鐩戝惉鑷姩鍒锋柊 */
-watch(autoRefresh, (newValue) => {
- if (newValue) {
- autoRefreshTimer = setInterval(() => {
- gridApi.query();
- }, 5000);
- } else {
- clearInterval(autoRefreshTimer);
- autoRefreshTimer = null;
- }
-});
-
-/** 鐩戝惉璁惧鏍囪瘑鍙樺寲 */
-watch(
- () => props.deviceId,
- (newValue) => {
- if (newValue) {
- gridApi.query();
- }
- },
-);
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- if (props.deviceId) {
- await nextTick();
- gridApi.query();
- }
-});
-
-/** 缁勪欢鍗歌浇鏃舵竻闄ゅ畾鏃跺櫒 */
-onBeforeUnmount(() => {
- if (autoRefreshTimer) {
- clearInterval(autoRefreshTimer);
- autoRefreshTimer = null;
- }
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <!-- 鎼滅储宸ヤ綔鏍� -->
- <div class="flex items-center justify-between" style="margin-bottom: 16px">
- <div class="flex items-center" style="gap: 16px">
- <Input
- v-model:value="queryParams.keyword"
- allow-clear
- placeholder="璇疯緭鍏ュ睘鎬у悕绉般�佹爣璇嗙"
- style="width: 240px"
- @change="handleKeywordChange"
- @press-enter="handleQuery"
- />
- <Switch
- v-model:checked="autoRefresh"
- checked-children="瀹氭椂鍒锋柊"
- class="ml-[20px]"
- un-checked-children="瀹氭椂鍒锋柊"
- />
- </div>
- <Button.Group>
- <Button
- :type="viewMode === 'card' ? 'primary' : 'default'"
- @click="handleViewModeChange('card')"
- >
- <IconifyIcon icon="ep:grid" />
- </Button>
- <Button
- :type="viewMode === 'list' ? 'primary' : 'default'"
- @click="handleViewModeChange('list')"
- >
- <IconifyIcon icon="ep:list" />
- </Button>
- </Button.Group>
- </div>
-
- <!-- 鍒嗛殧绾� -->
- <Divider style="margin: 16px 0" />
-
- <!-- 鍗$墖瑙嗗浘 -->
- <template v-if="viewMode === 'card'">
- <Row v-loading="loading" :gutter="16">
- <Col
- v-for="item in list"
- :key="item.identifier"
- :lg="6"
- :md="12"
- :sm="12"
- :xs="24"
- class="mb-4"
- >
- <Card
- :body-style="{ padding: '0' }"
- class="relative h-full overflow-hidden transition-colors"
- >
- <!-- 娣诲姞娓愬彉鑳屾櫙灞� -->
- <div
- class="pointer-events-none absolute left-0 right-0 top-0 h-12 bg-gradient-to-b from-muted to-transparent"
- ></div>
- <div class="relative p-4">
- <!-- 鏍囬鍖哄煙 -->
- <div class="mb-3 flex items-center">
- <div class="mr-2.5 flex items-center">
- <IconifyIcon class="text-lg text-primary" icon="ep:cpu" />
- </div>
- <div class="flex-1 text-base font-bold">{{ item.name }}</div>
- <!-- 鏍囪瘑绗� -->
- <div class="mr-2 inline-flex items-center">
- <Tag color="blue" size="small">
- {{ item.identifier }}
- </Tag>
- </div>
- <!-- 鏁版嵁绫诲瀷鏍囩 -->
- <div class="mr-2 inline-flex items-center">
- <Tag size="small">
- {{ item.dataType }}
- </Tag>
- </div>
- <!-- 鏁版嵁鍥炬爣 - 鍙偣鍑� -->
- <div
- class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full transition-colors hover:bg-blue-50"
- @click="
- openHistory(props.deviceId, item.identifier, item.dataType)
- "
- >
- <IconifyIcon
- class="text-lg text-primary"
- icon="ep:data-line"
- />
- </div>
- </div>
-
- <!-- 淇℃伅鍖哄煙 -->
- <div class="text-sm">
- <div class="mb-2.5 last:mb-0">
- <span class="mr-2.5 text-muted-foreground">灞炴�у��</span>
- <span class="font-bold text-foreground">
- {{ formatValueWithUnit(item) }}
- </span>
- </div>
- <div class="mb-2.5 last:mb-0">
- <span class="mr-2.5 text-muted-foreground">鏇存柊鏃堕棿</span>
- <span class="text-sm text-foreground">
- {{
- item.updateTime ? formatDateTime(item.updateTime) : '-'
- }}
- </span>
- </div>
- </div>
- </div>
- </Card>
- </Col>
- </Row>
- </template>
-
- <!-- 鍒楄〃瑙嗗浘 -->
- <Grid v-show="viewMode === 'list'">
- <template #value="{ row }">
- {{ formatValueWithUnit(row) }}
- </template>
- <template #updateTime="{ row }">
- {{ row.updateTime ? formatDateTime(row.updateTime) : '-' }}
- </template>
- <template #actions="{ row }">
- <Button
- type="link"
- @click="openHistory(props.deviceId, row.identifier, row.dataType)"
- >
- 鏌ョ湅鏁版嵁
- </Button>
- </template>
- </Grid>
-
- <!-- 琛ㄥ崟寮圭獥锛氭坊鍔�/淇敼 -->
- <DeviceDetailsThingModelPropertyHistory
- ref="historyRef"
- :device-id="props.deviceId"
- />
- </Page>
-</template>
-<style scoped>
-/* 绉婚櫎 a-row 鐨勯澶栬竟璺� */
-:deep(.ant-row) {
- margin-right: -8px !important;
- margin-left: -8px !important;
-}
-</style>
diff --git a/src/views/iot/device/device/detail/modules/thing-model-service.vue b/src/views/iot/device/device/detail/modules/thing-model-service.vue
deleted file mode 100644
index 84bbdc7..0000000
--- a/src/views/iot/device/device/detail/modules/thing-model-service.vue
+++ /dev/null
@@ -1,297 +0,0 @@
-<!-- 璁惧鏈嶅姟璋冪敤 -->
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
-
-import { Page } from '../../../../../../packages/effects/common-ui/src';
-import {
- getThingModelServiceCallTypeLabel,
- IotDeviceMessageMethodEnum,
- IoTThingModelTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDateTime } from '../../../../../../packages/utils/src';
-
-import { Button, RangePicker, Select, Space, Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getDeviceMessagePairPage } from '#/api/iot/device/device';
-
-const props = defineProps<{
- deviceId: number;
- thingModelList: ThingModelApi.ThingModel[];
-}>();
-
-/** 鏌ヨ鍙傛暟 */
-const queryParams = reactive({
- identifier: '',
- times: undefined as [string, string] | undefined,
-});
-let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 寤惰繜鍒锋柊瀹氭椂鍣�
-
-/** 鏈嶅姟绫诲瀷鐨勭墿妯″瀷鏁版嵁 */
-const serviceThingModels = computed(() => {
- return props.thingModelList.filter(
- (item: ThingModelApi.ThingModel) =>
- String(item.type) === String(IoTThingModelTypeEnum.SERVICE),
- );
-});
-
-/** Grid 鍒楀畾涔� */
-function useGridColumns(): VxeTableGridOptions<Record<string, any>>['columns'] {
- return [
- {
- field: 'requestTime',
- title: '璋冪敤鏃堕棿',
- width: 180,
- slots: { default: 'requestTime' },
- },
- {
- field: 'responseTime',
- title: '鍝嶅簲鏃堕棿',
- width: 180,
- slots: { default: 'responseTime' },
- },
- {
- field: 'identifier',
- title: '鏍囪瘑绗�',
- width: 160,
- slots: { default: 'identifier' },
- },
- {
- field: 'serviceName',
- title: '鏈嶅姟鍚嶇О',
- width: 160,
- slots: { default: 'serviceName' },
- },
- {
- field: 'callType',
- title: '璋冪敤鏂瑰紡',
- width: 100,
- slots: { default: 'callType' },
- },
- {
- field: 'inputParams',
- title: '杈撳叆鍙傛暟',
- minWidth: 200,
- showOverflow: 'tooltip',
- slots: { default: 'inputParams' },
- },
- {
- field: 'outputParams',
- title: '杈撳嚭鍙傛暟',
- minWidth: 200,
- showOverflow: 'tooltip',
- slots: { default: 'outputParams' },
- },
- ];
-}
-
-/** 鍒涘缓 Grid 瀹炰緥 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- if (!props.deviceId) {
- return { list: [], total: 0 };
- }
- return await getDeviceMessagePairPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- deviceId: props.deviceId,
- method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method,
- identifier: queryParams.identifier || undefined,
- times: queryParams.times,
- });
- },
- },
- },
- toolbarConfig: {
- refresh: false,
- search: false,
- },
- pagerConfig: {
- enabled: true,
- },
- } as VxeTableGridOptions,
-});
-
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-function handleQuery() {
- gridApi.query();
-}
-
-/** 閲嶇疆鎸夐挳鎿嶄綔 */
-function resetQuery() {
- queryParams.identifier = '';
- queryParams.times = undefined;
- handleQuery();
-}
-
-/** 鑾峰彇鏈嶅姟鍚嶇О */
-function getServiceName(identifier: string | undefined) {
- if (!identifier) return '-';
- const service = serviceThingModels.value.find(
- (item: ThingModelApi.ThingModel) => item.identifier === identifier,
- );
- return service?.name || identifier;
-}
-
-/** 鑾峰彇璋冪敤鏂瑰紡 */
-function getCallType(identifier: string | undefined) {
- if (!identifier) return '-';
- const service = serviceThingModels.value.find(
- (item: ThingModelApi.ThingModel) => item.identifier === identifier,
- );
- if (!service?.service?.callType) return '-';
- return getThingModelServiceCallTypeLabel(service.service.callType) || '-';
-}
-
-/** 瑙f瀽鍙傛暟 */
-function parseParams(params: string) {
- if (!params) return '-';
- try {
- const parsed = JSON.parse(params);
- if (parsed.params) {
- return JSON.stringify(parsed.params, null, 2);
- }
- return JSON.stringify(parsed, null, 2);
- } catch {
- return params;
- }
-}
-
-/** 鍒锋柊鍒楄〃 */
-function refresh(delay = 0) {
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
- if (delay > 0) {
- refreshTimer = setTimeout(() => {
- gridApi.query();
- refreshTimer = undefined;
- }, delay);
- } else {
- gridApi.query();
- }
-}
-
-/** 鐩戝惉璁惧鏍囪瘑鍙樺寲 */
-watch(
- () => props.deviceId,
- (newValue) => {
- if (newValue) {
- handleQuery();
- }
- },
-);
-
-/** 鍒濆鍖� */
-onMounted(() => {
- if (props.deviceId) {
- handleQuery();
- }
-});
-
-/** 缁勪欢鍗歌浇鏃舵竻闄ゅ欢杩熷埛鏂板畾鏃跺櫒 */
-onBeforeUnmount(() => {
- if (refreshTimer) {
- clearTimeout(refreshTimer);
- refreshTimer = undefined;
- }
-});
-
-/** 鏆撮湶鏂规硶缁欑埗缁勪欢 */
-defineExpose({
- refresh,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <!-- 鎼滅储鍖哄煙 -->
- <div class="mb-4 flex flex-wrap items-center gap-3">
- <div class="flex items-center gap-2">
- <span>鏍囪瘑绗︼細</span>
- <Select
- v-model:value="queryParams.identifier"
- allow-clear
- placeholder="璇烽�夋嫨鏈嶅姟鏍囪瘑绗�"
- style="width: 240px"
- >
- <Select.Option
- v-for="service in serviceThingModels"
- :key="service.identifier"
- :value="service.identifier!"
- >
- {{ service.name }}({{ service.identifier }})
- </Select.Option>
- </Select>
- </div>
- <div class="flex items-center gap-2">
- <span>鏃堕棿鑼冨洿锛�</span>
- <RangePicker
- v-model:value="queryParams.times"
- format="YYYY-MM-DD HH:mm:ss"
- show-time
- style="width: 360px"
- value-format="YYYY-MM-DD HH:mm:ss"
- />
- </div>
- <Space>
- <Button type="primary" @click="handleQuery">
- <template #icon>
- <IconifyIcon icon="ep:search" />
- </template>
- 鎼滅储
- </Button>
- <Button @click="resetQuery">
- <template #icon>
- <IconifyIcon icon="ep:refresh" />
- </template>
- 閲嶇疆
- </Button>
- </Space>
- </div>
-
- <!-- 鏈嶅姟璋冪敤鍒楄〃 -->
- <Grid>
- <template #requestTime="{ row }">
- {{
- row.request?.reportTime ? formatDateTime(row.request.reportTime) : '-'
- }}
- </template>
- <template #responseTime="{ row }">
- {{ row.reply?.reportTime ? formatDateTime(row.reply.reportTime) : '-' }}
- </template>
- <template #identifier="{ row }">
- <Tag color="blue" size="small">
- {{ row.request?.identifier }}
- </Tag>
- </template>
- <template #serviceName="{ row }">
- {{ getServiceName(row.request?.identifier) }}
- </template>
- <template #callType="{ row }">
- {{ getCallType(row.request?.identifier) }}
- </template>
- <template #inputParams="{ row }">
- {{ parseParams(row.request?.params) }}
- </template>
- <template #outputParams="{ row }">
- <span v-if="row.reply">
- {{
- `{"code":${row.reply.code},"msg":"${row.reply.msg}","data":${row.reply.data}\}`
- }}
- </span>
- <span v-else>-</span>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/device/device/detail/modules/thing-model.vue b/src/views/iot/device/device/detail/modules/thing-model.vue
deleted file mode 100644
index b94d741..0000000
--- a/src/views/iot/device/device/detail/modules/thing-model.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<!-- 璁惧鐗╂ā鍨嬶細璁惧灞炴�с�佷簨浠剁鐞嗐�佹湇鍔¤皟鐢� -->
-<script lang="ts" setup>
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { ref } from 'vue';
-
-import { ContentWrap } from '../../../../../../packages/effects/common-ui/src';
-
-import { Tabs } from 'ant-design-vue';
-
-import DeviceDetailsThingModelEvent from './thing-model-event.vue';
-import DeviceDetailsThingModelProperty from './thing-model-property.vue';
-import DeviceDetailsThingModelService from './thing-model-service.vue';
-
-const props = defineProps<{
- deviceId: number;
- thingModelList: ThingModelApi.ThingModel[];
-}>();
-
-const activeTab = ref('property'); // 榛樿閫変腑璁惧灞炴��
-</script>
-<template>
- <ContentWrap>
- <Tabs v-model:active-key="activeTab" class="!h-auto !p-0">
- <Tabs.TabPane key="property" tab="璁惧灞炴�э紙杩愯鐘舵�侊級">
- <DeviceDetailsThingModelProperty
- v-if="activeTab === 'property'"
- :device-id="deviceId"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="event" tab="璁惧浜嬩欢涓婃姤">
- <DeviceDetailsThingModelEvent
- v-if="activeTab === 'event'"
- :device-id="props.deviceId"
- :thing-model-list="props.thingModelList"
- />
- </Tabs.TabPane>
- <Tabs.TabPane key="service" tab="璁惧鏈嶅姟璋冪敤">
- <DeviceDetailsThingModelService
- v-if="activeTab === 'service'"
- :device-id="deviceId"
- :thing-model-list="props.thingModelList"
- />
- </Tabs.TabPane>
- </Tabs>
- </ContentWrap>
-</template>
diff --git a/src/views/iot/device/device/index.vue b/src/views/iot/device/device/index.vue
deleted file mode 100644
index c9e6e71..0000000
--- a/src/views/iot/device/device/index.vue
+++ /dev/null
@@ -1,532 +0,0 @@
-<script lang="ts" setup>
-import type { PageParam } from '../../../../packages/effects/request/src';
-
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotDeviceGroupApi } from '#/api/iot/device/group';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { nextTick, onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
-
-import {
- Button,
- Card,
- Input,
- message,
- Select,
- Space,
- Tag,
-} from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteDevice,
- deleteDeviceList,
- exportDeviceExcel,
- getDevicePage,
-} from '#/api/iot/device/device';
-import { getSimpleDeviceGroupList } from '#/api/iot/device/group';
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { $t } from '#/locales';
-
-import { useGridColumns } from './data';
-import DeviceCardView from './modules/card-view.vue';
-import DeviceForm from './modules/form.vue';
-import DeviceGroupForm from './modules/group-form.vue';
-import DeviceImportForm from './modules/import-form.vue';
-
-const route = useRoute();
-const router = useRouter();
-const products = ref<IotProductApi.Product[]>([]);
-const deviceGroups = ref<IotDeviceGroupApi.DeviceGroup[]>([]);
-const viewMode = ref<'card' | 'list'>('card');
-const cardViewRef = ref();
-const checkedIds = ref<number[]>([]);
-
-const [DeviceFormModal, deviceFormModalApi] = useVbenModal({
- connectedComponent: DeviceForm,
- destroyOnClose: true,
-});
-
-const [DeviceGroupFormModal, deviceGroupFormModalApi] = useVbenModal({
- connectedComponent: DeviceGroupForm,
- destroyOnClose: true,
-});
-
-const [DeviceImportFormModal, deviceImportFormModalApi] = useVbenModal({
- connectedComponent: DeviceImportForm,
- destroyOnClose: true,
-});
-
-const queryParams = ref<Partial<PageParam>>({
- deviceName: '',
- nickname: '',
- productId: undefined,
- deviceType: undefined,
- status: undefined,
- groupId: undefined,
-}); // 鎼滅储鍙傛暟
-
-/** 鎼滅储 */
-function handleSearch() {
- if (viewMode.value === 'list') {
- gridApi.formApi.setValues(queryParams.value);
- }
- gridApi.query();
-}
-
-/** 閲嶇疆鎼滅储 */
-function handleReset() {
- queryParams.value.deviceName = '';
- queryParams.value.nickname = '';
- queryParams.value.productId = undefined;
- queryParams.value.deviceType = undefined;
- queryParams.value.status = undefined;
- queryParams.value.groupId = undefined;
- handleSearch();
-}
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瑙嗗浘鍒囨崲 */
-async function handleViewModeChange(mode: 'card' | 'list') {
- if (viewMode.value === mode) {
- return; // 濡傛灉宸茬粡鏄洰鏍囪鍥撅紝涓嶉渶瑕佸垏鎹�
- }
- viewMode.value = mode;
- // 绛夊緟瑙嗗浘鏇存柊鍚庡啀瑙﹀彂鏌ヨ
- await nextTick();
- gridApi.query();
-}
-
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
- const data = await exportDeviceExcel({
- ...queryParams.value,
- pageNo: 1,
- pageSize: 999_999,
- } as PageParam);
- downloadFileFromBlobPart({ fileName: '鐗╄仈缃戣澶�.xls', source: data });
-}
-
-/** 鎵撳紑璁惧璇︽儏 */
-function openDetail(id: number) {
- router.push({ name: 'IoTDeviceDetail', params: { id } });
-}
-
-/** 璺宠浆鍒颁骇鍝佽鎯呴〉闈� */
-function openProductDetail(productId: number) {
- router.push({ name: 'IoTProductDetail', params: { id: productId } });
-}
-
-/** 鎵撳紑鐗╂ā鍨嬫暟鎹� */
-function openModel(id: number) {
- router.push({
- name: 'IoTDeviceDetail',
- params: { id },
- query: { tab: 'model' },
- });
-}
-
-/** 鏂板璁惧 */
-function handleCreate() {
- deviceFormModalApi.setData(null).open();
-}
-
-/** 缂栬緫璁惧 */
-function handleEdit(row: IotDeviceApi.Device) {
- deviceFormModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎璁惧 */
-async function handleDelete(row: IotDeviceApi.Device) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.deviceName]),
- duration: 0,
- });
- try {
- await deleteDevice(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.deviceName]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎵归噺鍒犻櫎璁惧 */
-async function handleDeleteBatch() {
- if (checkedIds.value.length === 0) {
- message.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁惧');
- return;
- }
- await confirm($t('ui.actionMessage.deleteBatchConfirm'));
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deletingBatch'),
- duration: 0,
- });
- try {
- await deleteDeviceList(checkedIds.value);
- message.success($t('ui.actionMessage.deleteSuccess'));
- checkedIds.value = [];
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 娣诲姞鍒板垎缁� */
-function handleAddToGroup() {
- if (checkedIds.value.length === 0) {
- message.warning('璇烽�夋嫨瑕佹坊鍔犲埌鍒嗙粍鐨勮澶�');
- return;
- }
- deviceGroupFormModalApi.setData(checkedIds.value).open();
-}
-
-/** 璁惧瀵煎叆 */
-function handleImport() {
- deviceImportFormModalApi.open();
-}
-
-function handleRowCheckboxChange({
- records,
-}: {
- records: IotDeviceApi.Device[];
-}) {
- checkedIds.value = records.map((item) => item.id!);
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- checkboxConfig: {
- highlight: true,
- reserve: true,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- pageSize: 12,
- },
- proxyConfig: {
- ajax: {
- query: async ({
- page,
- }: {
- page: { currentPage: number; pageSize: number };
- }) => {
- return await getDevicePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...queryParams.value,
- } as PageParam);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotDeviceApi.Device>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-
-/** 鍖呰 gridApi.query() 鏂规硶锛岀粺涓�鍒楄〃瑙嗗浘鍜屽崱鐗囪鍥剧殑鏌ヨ鎺ュ彛 */
-const originalQuery = gridApi.query.bind(gridApi);
-gridApi.query = async (params?: Record<string, any>) => {
- if (viewMode.value === 'list') {
- return await originalQuery(params);
- } else {
- // 鍗$墖瑙嗗浘锛氳皟鐢ㄥ崱鐗囩粍浠剁殑 query 鏂规硶
- cardViewRef.value?.query();
- }
-};
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- // 鑾峰彇浜у搧鍒楄〃
- products.value = await getSimpleProductList();
- // 鑾峰彇鍒嗙粍鍒楄〃
- deviceGroups.value = await getSimpleDeviceGroupList();
-
- // 澶勭悊 productId 鍙傛暟
- const { productId } = route.query;
- if (productId) {
- queryParams.value.productId = Number(productId);
- // 鑷姩瑙﹀彂鎼滅储
- handleSearch();
- }
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <DeviceFormModal @success="handleRefresh" />
- <DeviceGroupFormModal @success="handleRefresh" />
- <DeviceImportFormModal @success="handleRefresh" />
-
- <!-- 缁熶竴鎼滅储宸ュ叿鏍� -->
- <Card :body-style="{ padding: '16px' }" class="!mb-2">
- <!-- 鎼滅储琛ㄥ崟 -->
- <div class="mb-3 flex flex-wrap items-center gap-3">
- <Select
- v-model:value="queryParams.productId"
- placeholder="璇烽�夋嫨浜у搧"
- allow-clear
- style="width: 200px"
- >
- <Select.Option
- v-for="product in products"
- :key="product.id"
- :value="product.id"
- >
- {{ product.name }}
- </Select.Option>
- </Select>
- <Input
- v-model:value="queryParams.deviceName"
- placeholder="璇疯緭鍏� DeviceName"
- allow-clear
- style="width: 200px"
- @press-enter="handleSearch"
- />
- <Input
- v-model:value="queryParams.nickname"
- placeholder="璇疯緭鍏ュ娉ㄥ悕绉�"
- allow-clear
- style="width: 200px"
- @press-enter="handleSearch"
- />
- <Select
- v-model:value="queryParams.deviceType"
- placeholder="璇烽�夋嫨璁惧绫诲瀷"
- allow-clear
- style="width: 200px"
- >
- <Select.Option
- v-for="dict in getDictOptions(
- DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE,
- 'number',
- )"
- :key="dict.value"
- :value="dict.value"
- >
- {{ dict.label }}
- </Select.Option>
- </Select>
- <Select
- v-model:value="queryParams.status"
- placeholder="璇烽�夋嫨璁惧鐘舵��"
- allow-clear
- style="width: 200px"
- >
- <Select.Option
- v-for="dict in getDictOptions(DICT_TYPE.IOT_DEVICE_STATE, 'number')"
- :key="dict.value"
- :value="dict.value"
- >
- {{ dict.label }}
- </Select.Option>
- </Select>
- <Select
- v-model:value="queryParams.groupId"
- placeholder="璇烽�夋嫨璁惧鍒嗙粍"
- allow-clear
- style="width: 200px"
- >
- <Select.Option
- v-for="group in deviceGroups"
- :key="group.id"
- :value="group.id"
- >
- {{ group.name }}
- </Select.Option>
- </Select>
- <Button type="primary" @click="handleSearch">
- <IconifyIcon icon="ant-design:search-outlined" class="mr-1" />
- {{ $t('common.search') }}
- </Button>
- <Button @click="handleReset">
- <IconifyIcon icon="ant-design:reload-outlined" class="mr-1" />
- {{ $t('common.reset') }}
- </Button>
- </div>
-
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="flex items-center justify-between">
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['璁惧']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:device:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.export'),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['iot:device:export'],
- onClick: handleExport,
- },
- {
- label: $t('ui.actionTitle.import'),
- type: 'primary',
- icon: ACTION_ICON.UPLOAD,
- auth: ['iot:device:import'],
- onClick: handleImport,
- },
- {
- label: '娣诲姞鍒板垎缁�',
- type: 'primary',
- icon: 'ant-design:folder-add-outlined',
- auth: ['iot:device:update'],
- disabled: isEmpty(checkedIds),
- onClick: handleAddToGroup,
- },
- {
- label: $t('ui.actionTitle.deleteBatch'),
- type: 'primary',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:device:delete'],
- disabled: isEmpty(checkedIds),
- onClick: handleDeleteBatch,
- },
- ]"
- />
- <!-- 瑙嗗浘鍒囨崲 -->
- <Space :size="4">
- <Button
- :type="viewMode === 'card' ? 'primary' : 'default'"
- @click="handleViewModeChange('card')"
- >
- <IconifyIcon icon="ant-design:appstore-outlined" />
- </Button>
- <Button
- :type="viewMode === 'list' ? 'primary' : 'default'"
- @click="handleViewModeChange('list')"
- >
- <IconifyIcon icon="ant-design:unordered-list-outlined" />
- </Button>
- </Space>
- </div>
- </Card>
-
- <!-- 鍒楄〃瑙嗗浘 -->
- <Grid table-title="璁惧鍒楄〃" v-show="viewMode === 'list'">
- <template #deviceName="{ row }">
- <a class="cursor-pointer text-primary" @click="openDetail(row.id!)">
- {{ row.deviceName }}
- </a>
- </template>
- <template #product="{ row }">
- <a
- class="cursor-pointer text-primary"
- @click="openProductDetail(row.productId)"
- >
- {{
- products.find((product) => product.id === row.productId)?.name ||
- '-'
- }}
- </a>
- </template>
- <template #groups="{ row }">
- <template v-if="row.groupIds?.length">
- <Tag
- v-for="groupId in row.groupIds"
- :key="groupId"
- size="small"
- class="mr-1"
- >
- {{ deviceGroups.find((group) => group.id === groupId)?.name }}
- </Tag>
- </template>
- <span v-else>-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- auth: ['iot:device:query'],
- onClick: openDetail.bind(null, row.id!),
- },
- {
- label: '鏃ュ織',
- type: 'link',
- auth: ['iot:device:query'],
- onClick: openModel.bind(null, row.id!),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:device:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:device:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.deviceName]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 鍗$墖瑙嗗浘 -->
- <DeviceCardView
- v-show="viewMode === 'card'"
- ref="cardViewRef"
- :products="products"
- :device-groups="deviceGroups"
- :search-params="{
- deviceName: queryParams.deviceName || '',
- nickname: queryParams.nickname || '',
- productId: queryParams.productId,
- deviceType: queryParams.deviceType,
- status: queryParams.status,
- groupId: queryParams.groupId,
- }"
- @create="handleCreate"
- @edit="handleEdit"
- @delete="handleDelete"
- @detail="openDetail"
- @model="openModel"
- @product-detail="openProductDetail"
- />
- </Page>
-</template>
-
-<style scoped>
-/* 闅愯棌 VxeGrid 鑷甫鐨勬悳绱㈣〃鍗曞尯鍩� */
-:deep(.vxe-grid--form-wrapper) {
- display: none !important;
-}
-</style>
diff --git a/src/views/iot/device/device/modules/card-view.vue b/src/views/iot/device/device/modules/card-view.vue
deleted file mode 100644
index bd32991..0000000
--- a/src/views/iot/device/device/modules/card-view.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-<script lang="ts" setup>
-import type { PageParam } from '../../../../../packages/effects/request/src';
-
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { onMounted, ref } from 'vue';
-
-import { useAccess } from '../../../../../packages/effects/access/src';
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import {
- Button,
- Card,
- Col,
- Empty,
- Image,
- Pagination,
- Popconfirm,
- Row,
- Tooltip,
-} from 'ant-design-vue';
-
-import { getDevicePage } from '#/api/iot/device/device';
-import { DictTag } from '#/components/dict-tag';
-
-interface Props {
- products: any[];
- deviceGroups: any[];
- searchParams?: {
- deviceName: string;
- deviceType?: number;
- groupId?: number;
- nickname: string;
- productId?: number;
- status?: number;
- };
-}
-
-const props = defineProps<Props>();
-
-const emit = defineEmits<{
- create: [];
- delete: [row: any];
- detail: [id: number];
- edit: [row: any];
- model: [id: number];
- productDetail: [productId: number];
-}>();
-
-const { hasAccessByCodes } = useAccess();
-
-const loading = ref(false);
-const list = ref<IotDeviceApi.Device[]>([]);
-const total = ref(0);
-const queryParams = ref<Partial<PageParam>>({
- pageNo: 1,
- pageSize: 12,
-});
-
-/** 鑾峰彇浜у搧鍚嶇О */
-function getProductName(productId: number) {
- const product = props.products.find((p: any) => p.id === productId);
- return product?.name || '-';
-}
-
-/** 鑾峰彇璁惧鍒楄〃 */
-async function getList() {
- loading.value = true;
- try {
- const data = await getDevicePage({
- ...queryParams.value,
- ...props.searchParams,
- } as PageParam);
- list.value = data.list || [];
- total.value = data.total || 0;
- } finally {
- loading.value = false;
- }
-}
-
-/** 澶勭悊椤电爜鍙樺寲 */
-function handlePageChange(page: number, pageSize: number) {
- queryParams.value.pageNo = page;
- queryParams.value.pageSize = pageSize;
- getList();
-}
-
-defineExpose({
- reload: getList,
- search: () => {
- queryParams.value.pageNo = 1;
- getList();
- },
- query: () => {
- queryParams.value.pageNo = 1;
- getList();
- },
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- getList();
-});
-</script>
-
-<template>
- <div>
- <!-- 璁惧鍗$墖鍒楄〃 -->
- <div v-loading="loading" class="min-h-96">
- <Row v-if="list.length > 0" :gutter="[16, 16]">
- <Col
- v-for="item in list"
- :key="item.id"
- :xs="24"
- :sm="12"
- :md="12"
- :lg="6"
- >
- <Card
- :body-style="{
- padding: '16px',
- display: 'flex',
- flexDirection: 'column',
- height: '100%',
- }"
- class="h-full overflow-hidden rounded-lg transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
- >
- <!-- 椤堕儴鏍囬鍖哄煙 -->
- <div class="mb-3 flex items-center">
- <div
- class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-[#40a9ff] to-[#1890ff] text-white"
- >
- <IconifyIcon icon="mdi:chip" class="text-xl" />
- </div>
- <div class="ml-3 min-w-0 flex-1">
- <div
- class="truncate text-[15px] font-semibold leading-9 dark:text-white/85"
- >
- {{ item.deviceName }}
- </div>
- </div>
- <DictTag
- :type="DICT_TYPE.IOT_DEVICE_STATE"
- :value="item.state"
- class="text-xs"
- />
- </div>
- <!-- 鍐呭鍖哄煙 -->
- <div class="mb-3 flex items-start">
- <div class="flex-1">
- <div class="mb-2 flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 鎵�灞炰骇鍝�
- </span>
- <a
- class="cursor-pointer truncate font-medium text-primary"
- @click="
- (e) => {
- e.stopPropagation();
- emit('productDetail', item.productId);
- }
- "
- >
- {{ getProductName(item.productId) }}
- </a>
- </div>
- <div class="mb-2 flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 璁惧绫诲瀷
- </span>
- <DictTag
- :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
- :value="item.deviceType"
- class="m-0 text-xs"
- />
- </div>
- <div class="flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 澶囨敞鍚嶇О
- </span>
- <Tooltip
- :title="item.nickname || item.deviceName"
- placement="top"
- >
- <span
- class="inline-block max-w-[150px] cursor-pointer truncate align-middle text-xs opacity-85 dark:text-white/75"
- >
- {{ item.nickname || item.deviceName }}
- </span>
- </Tooltip>
- </div>
- </div>
- <!-- 璁惧鍥剧墖 -->
- <div
- class="flex size-20 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-[#40a9ff15] to-[#1890ff15] text-[#1890ff] dark:from-[#40a9ff25] dark:to-[#1890ff25] dark:text-[#69c0ff]"
- >
- <Image
- v-if="item.picUrl"
- :src="item.picUrl"
- :preview="true"
- class="size-full rounded object-cover"
- />
- <IconifyIcon
- v-else
- icon="lucide:image"
- class="text-2xl opacity-50"
- />
- </div>
- </div>
- <!-- 鎸夐挳缁� -->
- <div class="mt-auto flex gap-2 border-t border-border pt-3">
- <Button
- v-if="hasAccessByCodes(['iot:device:update'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
- @click="emit('edit', item)"
- >
- <IconifyIcon icon="lucide:edit" class="mr-1" />
- 缂栬緫
- </Button>
- <Button
- v-if="hasAccessByCodes(['iot:device:query'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
- @click="emit('detail', item.id!)"
- >
- <IconifyIcon icon="lucide:eye" class="mr-1" />
- 璇︽儏
- </Button>
- <Button
- v-if="hasAccessByCodes(['iot:device:query'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
- @click="emit('model', item.id!)"
- >
- <IconifyIcon icon="lucide:database" class="mr-1" />
- 鏁版嵁
- </Button>
- <template v-if="hasAccessByCodes(['iot:device:delete'])">
- <div
- class="h-5 w-px self-center bg-[#dcdfe6] dark:bg-[#3a3a3a]"
- ></div>
- <Popconfirm
- :title="`纭鍒犻櫎璁惧 ${item.deviceName} 鍚�?`"
- @confirm="emit('delete', item)"
- >
- <Button
- size="small"
- danger
- class="!h-8 rounded-md p-0 text-[13px] transition-all duration-200 !w-8"
- >
- <IconifyIcon icon="lucide:trash-2" class="text-sm" />
- </Button>
- </Popconfirm>
- </template>
- </div>
- </Card>
- </Col>
- </Row>
- <!-- 绌虹姸鎬� -->
- <Empty v-else description="鏆傛棤璁惧鏁版嵁" class="my-20" />
- </div>
-
- <!-- 鍒嗛〉 -->
- <div v-if="list.length > 0" class="mt-3 flex justify-end">
- <Pagination
- v-model:current="queryParams.pageNo"
- v-model:page-size="queryParams.pageSize"
- :total="total"
- :show-total="(total) => `鍏� ${total} 鏉"
- show-quick-jumper
- show-size-changer
- :page-size-options="['12', '24', '36', '48']"
- @change="handlePageChange"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/iot/device/device/modules/form.vue b/src/views/iot/device/device/modules/form.vue
deleted file mode 100644
index f9c7e70..0000000
--- a/src/views/iot/device/device/modules/form.vue
+++ /dev/null
@@ -1,222 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { computed, nextTick, onMounted, ref, watch } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Button, Collapse, message, Space } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createDevice, getDevice, updateDevice } from '#/api/iot/device/device';
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { MapDialog } from '#/components/map';
-import { $t } from '#/locales';
-
-import { useAdvancedFormSchema, useBasicFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<IotDeviceApi.Device>();
-const products = ref<IotProductApi.Product[]>([]);
-const activeKey = ref<string[]>([]);
-const mapDialogRef = ref<InstanceType<typeof MapDialog>>();
-
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['璁惧'])
- : $t('ui.actionTitle.create', ['璁惧']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useBasicFormSchema(),
- showDefaultActions: false,
- handleValuesChange: async (values, changedFields) => {
- // 褰撲骇鍝� ProductId 鍙樺寲鏃讹紝鑷姩璁剧疆璁惧绫诲瀷
- if (changedFields.includes('productId')) {
- const productId = values.productId;
- if (!productId) {
- await formApi.setFieldValue('deviceType', undefined);
- return;
- }
- // 浠庝骇鍝佸垪琛ㄤ腑鏌ユ壘浜у搧
- const product = products.value.find((item) => item.id === productId);
- if (product?.deviceType !== undefined) {
- await formApi.setFieldValue('deviceType', product.deviceType);
- }
- }
- },
-});
-
-const [AdvancedForm, advancedFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useAdvancedFormSchema(),
- showDefaultActions: false,
-});
-
-/** 鑾峰彇楂樼骇琛ㄥ崟鐨勫�硷紙濡傛灉琛ㄥ崟鏈寕杞斤紝鍒欎粠 formData 涓幏鍙栵級 */
-async function getAdvancedFormValues() {
- if (advancedFormApi.isMounted) {
- return await advancedFormApi.getValues();
- }
- // 琛ㄥ崟鏈寕杞斤紙鎶樺彔鐘舵�侊級锛屼粠 formData 涓幏鍙�
- return {
- nickname: formData.value?.nickname,
- picUrl: formData.value?.picUrl,
- groupIds: formData.value?.groupIds,
- serialNumber: formData.value?.serialNumber,
- longitude: formData.value?.longitude,
- latitude: formData.value?.latitude,
- };
-}
-
-/** 鎵撳紑鍦板浘閫夋嫨寮圭獥 */
-async function openMapDialog() {
- // 濡傛灉楂樼骇琛ㄥ崟鏈寕杞斤紝鍏堝睍寮� Collapse
- if (!advancedFormApi.isMounted) {
- activeKey.value = ['advanced'];
- await nextTick();
- await nextTick();
- }
- const values = await advancedFormApi.getValues();
- mapDialogRef.value?.open(
- values.longitude ? Number(values.longitude) : undefined,
- values.latitude ? Number(values.latitude) : undefined,
- );
-}
-
-/** 澶勭悊鍦板浘閫夋嫨纭 */
-async function handleMapConfirm(data: {
- address: string;
- latitude: string;
- longitude: string;
-}) {
- if (advancedFormApi.isMounted) {
- await advancedFormApi.setFieldValue('longitude', Number(data.longitude));
- await advancedFormApi.setFieldValue('latitude', Number(data.latitude));
- }
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- // 楂樼骇琛ㄥ崟锛氬厛鍗曠嫭鏍¢獙锛屽啀鍋氱粡绾害鎴愬濉啓鐨勮法瀛楁妫�鏌�
- if (advancedFormApi.isMounted) {
- const { valid: advancedValid } = await advancedFormApi.validate();
- if (!advancedValid) {
- return;
- }
- const advValues = await advancedFormApi.getValues();
- const hasLongitude =
- advValues.longitude !== undefined &&
- advValues.longitude !== null &&
- advValues.longitude !== '';
- const hasLatitude =
- advValues.latitude !== undefined &&
- advValues.latitude !== null &&
- advValues.latitude !== '';
- if (hasLongitude !== hasLatitude) {
- message.warning(
- hasLongitude ? '璇峰悓鏃跺~鍐欒澶囩含搴�' : '璇峰悓鏃跺~鍐欒澶囩粡搴�',
- );
- return;
- }
- }
- modalApi.lock();
- // 鍚堝苟涓や釜琛ㄥ崟鐨勫�硷紙瀛楁涓嶅啿绐侊紝鍙洿鎺ュ悎骞讹級
- const basicValues = await formApi.getValues();
- const advancedValues = await getAdvancedFormValues();
- const data = {
- ...basicValues,
- ...advancedValues,
- } as IotDeviceApi.Device;
- try {
- await (formData.value?.id ? updateDevice(data) : createDevice(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- activeKey.value = [];
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<IotDeviceApi.Device>();
- if (!data || !data.id) {
- return;
- }
- // 缂栬緫妯″紡锛氬姞杞芥暟鎹�
- modalApi.lock();
- try {
- formData.value = await getDevice(data.id);
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 鐩戝惉 Collapse 灞曞紑锛岃嚜鍔ㄨ缃珮绾ц〃鍗曠殑鍊� */
-watch(
- activeKey,
- async (newKeys) => {
- // 褰撶敤鎴锋墜鍔ㄥ睍寮� Collapse 涓斿瓨鍦ㄨ〃鍗曟暟鎹椂锛岃缃珮绾ц〃鍗曠殑鍊�
- if (newKeys.includes('advanced') && formData.value) {
- // 绛夊緟琛ㄥ崟鎸傝浇
- await nextTick();
- await nextTick();
- if (advancedFormApi.isMounted) {
- await advancedFormApi.setValues(formData.value);
- }
- }
- },
- { immediate: false },
-);
-
-/** 鍒濆鍖栦骇鍝佸垪琛� */
-onMounted(async () => {
- products.value = await getSimpleProductList();
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <div class="mx-4">
- <Form />
- <Collapse v-model:active-key="activeKey" class="mt-4">
- <Collapse.Panel key="advanced" header="鏇村璁剧疆">
- <AdvancedForm />
- <Space class="mt-2">
- <Button type="primary" @click="openMapDialog">鍧愭爣鎷惧彇</Button>
- </Space>
- </Collapse.Panel>
- </Collapse>
- </div>
- </Modal>
- <!-- 鍦板浘閫夋嫨寮圭獥 -->
- <MapDialog ref="mapDialogRef" @confirm="handleMapConfirm" />
-</template>
diff --git a/src/views/iot/device/device/modules/group-form.vue b/src/views/iot/device/device/modules/group-form.vue
deleted file mode 100644
index c289dad..0000000
--- a/src/views/iot/device/device/modules/group-form.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { updateDeviceGroup } from '#/api/iot/device/device';
-import { $t } from '#/locales';
-
-import { useGroupFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const deviceIds = ref<number[]>([]);
-const getTitle = computed(() => '娣诲姞璁惧鍒板垎缁�');
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useGroupFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- await updateDeviceGroup({
- ids: deviceIds.value,
- groupIds: data.groupIds as number[],
- } as IotDeviceApi.DeviceUpdateGroupReqVO);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- deviceIds.value = [];
- return;
- }
- // 鍔犺浇鏁版嵁
- const ids = modalApi.getData<number[]>();
- if (ids) {
- deviceIds.value = ids;
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/device/device/modules/import-form.vue b/src/views/iot/device/device/modules/import-form.vue
deleted file mode 100644
index 3d8abef..0000000
--- a/src/views/iot/device/device/modules/import-form.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script lang="ts" setup>
-import type { FileType } from 'ant-design-vue/es/upload/interface';
-
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { alert, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
-
-import { Button, message, Upload } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { importDevice, importDeviceTemplate } from '#/api/iot/device/device';
-import { $t } from '#/locales';
-
-import { useImportFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useImportFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- const result = await importDevice(data.file, data.updateSupport);
- // 澶勭悊瀵煎叆缁撴灉鎻愮ず
- const importData = result as IotDeviceApi.DeviceImportRespVO;
- if (importData) {
- let text = `涓婁紶鎴愬姛鏁伴噺锛�${importData.createDeviceNames?.length || 0};`;
- if (importData.createDeviceNames?.length) {
- for (const deviceName of importData.createDeviceNames) {
- text += `< ${deviceName} >`;
- }
- }
- text += `鏇存柊鎴愬姛鏁伴噺锛�${importData.updateDeviceNames?.length || 0};`;
- if (importData.updateDeviceNames?.length) {
- for (const deviceName of importData.updateDeviceNames) {
- text += `< ${deviceName} >`;
- }
- }
- text += `鏇存柊澶辫触鏁伴噺锛�${Object.keys(importData.failureDeviceNames || {}).length};`;
- if (importData.failureDeviceNames) {
- for (const deviceName in importData.failureDeviceNames) {
- text += `< ${deviceName}: ${importData.failureDeviceNames[deviceName]} >`;
- }
- }
- await alert(text, '瀵煎叆缁撴灉');
- }
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 涓婁紶鍓� */
-function beforeUpload(file: FileType) {
- formApi.setFieldValue('file', file);
- return false;
-}
-
-/** 涓嬭浇妯$増 */
-async function handleDownload() {
- const data = await importDeviceTemplate();
- downloadFileFromBlobPart({ fileName: '璁惧瀵煎叆妯℃澘.xls', source: data });
-}
-</script>
-
-<template>
- <Modal :title="$t('ui.actionTitle.import', ['璁惧'])" class="w-1/3">
- <Form class="mx-4">
- <template #file>
- <div class="w-full">
- <Upload
- :max-count="1"
- accept=".xls,.xlsx"
- :before-upload="beforeUpload"
- >
- <Button type="primary"> 閫夋嫨 Excel 鏂囦欢 </Button>
- </Upload>
- </div>
- </template>
- </Form>
- <template #prepend-footer>
- <div class="flex flex-auto items-center">
- <Button @click="handleDownload"> 涓嬭浇瀵煎叆妯℃澘 </Button>
- </div>
- </template>
- </Modal>
-</template>
diff --git a/src/views/iot/device/group/data.ts b/src/views/iot/device/group/data.ts
deleted file mode 100644
index f2d8667..0000000
--- a/src/views/iot/device/group/data.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceGroupApi } from '#/api/iot/device/group';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍒嗙粍鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎缁勫悕绉�',
- },
- rules: z.string().min(1, '鍒嗙粍鍚嶇О涓嶈兘涓虹┖'),
- },
- {
- fieldName: 'status',
- label: '鍒嗙粍鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '鍒嗙粍鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎缁勬弿杩�',
- rows: 3,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍒嗙粍鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎缁勫悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<IotDeviceGroupApi.DeviceGroup>['columns'] {
- return [
- {
- field: 'id',
- title: 'ID',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鍒嗙粍鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'description',
- title: '鍒嗙粍鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'deviceCount',
- title: '璁惧鏁伴噺',
- minWidth: 100,
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/device/group/index.vue b/src/views/iot/device/group/index.vue
deleted file mode 100644
index 6ab240b..0000000
--- a/src/views/iot/device/group/index.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotDeviceGroupApi } from '#/api/iot/device/group';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteDeviceGroup, getDeviceGroupPage } from '#/api/iot/device/group';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓璁惧鍒嗙粍 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫璁惧鍒嗙粍 */
-function handleEdit(row: IotDeviceGroupApi.DeviceGroup) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎璁惧鍒嗙粍 */
-async function handleDelete(row: IotDeviceGroupApi.DeviceGroup) {
- if (row.deviceCount && row.deviceCount > 0) {
- message.warning(
- `鍒嗙粍銆�${row.name}銆嶄笅瀛樺湪 ${row.deviceCount} 鍙拌澶囷紝鏃犳硶鍒犻櫎`,
- );
- return;
- }
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDeviceGroup(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDeviceGroupPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotDeviceGroupApi.DeviceGroup>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="璁惧鍒嗙粍鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['璁惧鍒嗙粍']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:device-group:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:device-group:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:device-group:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/device/group/modules/form.vue b/src/views/iot/device/group/modules/form.vue
deleted file mode 100644
index 6d070fd..0000000
--- a/src/views/iot/device/group/modules/form.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script lang="ts" setup>
-import type { IotDeviceGroupApi } from '#/api/iot/device/group';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createDeviceGroup,
- getDeviceGroup,
- updateDeviceGroup,
-} from '#/api/iot/device/group';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<IotDeviceGroupApi.DeviceGroup>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['璁惧鍒嗙粍'])
- : $t('ui.actionTitle.create', ['璁惧鍒嗙粍']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as IotDeviceGroupApi.DeviceGroup;
- try {
- await (formData.value?.id
- ? updateDeviceGroup(data)
- : createDeviceGroup(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<IotDeviceGroupApi.DeviceGroup>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDeviceGroup(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/home/chart-options.ts b/src/views/iot/home/chart-options.ts
deleted file mode 100644
index 8745452..0000000
--- a/src/views/iot/home/chart-options.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-/** 娑堟伅瓒嬪娍鍥捐〃閰嶇疆 */
-export function getMessageTrendChartOptions(
- times: string[],
- upstreamData: number[],
- downstreamData: number[],
-): any {
- return {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- label: {
- backgroundColor: '#6a7985',
- },
- },
- },
- legend: {
- data: ['涓婅娑堟伅閲�', '涓嬭娑堟伅閲�'],
- top: '5%',
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- top: '15%',
- containLabel: true,
- },
- xAxis: [
- {
- type: 'category',
- boundaryGap: false,
- data: times,
- },
- ],
- yAxis: [
- {
- type: 'value',
- name: '娑堟伅鏁伴噺',
- },
- ],
- series: [
- {
- name: '涓婅娑堟伅閲�',
- type: 'line',
- smooth: true,
- areaStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
- { offset: 1, color: 'rgba(24, 144, 255, 0)' },
- ],
- },
- },
- emphasis: {
- focus: 'series',
- },
- data: upstreamData,
- itemStyle: {
- color: '#1890ff',
- },
- },
- {
- name: '涓嬭娑堟伅閲�',
- type: 'line',
- smooth: true,
- areaStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- { offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
- { offset: 1, color: 'rgba(82, 196, 26, 0)' },
- ],
- },
- },
- emphasis: {
- focus: 'series',
- },
- data: downstreamData,
- itemStyle: {
- color: '#52c41a',
- },
- },
- ],
- };
-}
-
-/** 璁惧鐘舵�佷华琛ㄧ洏鍥捐〃閰嶇疆 */
-export function getDeviceStateGaugeChartOptions(
- value: number,
- max: number,
- color: string,
- title: string,
-): any {
- return {
- series: [
- {
- type: 'gauge',
- startAngle: 360,
- endAngle: 0,
- min: 0,
- max,
- center: ['50%', '50%'],
- radius: '80%',
- progress: {
- show: true,
- width: 12,
- itemStyle: {
- color,
- },
- },
- axisLine: {
- lineStyle: {
- width: 12,
- color: [[1, '#E5E7EB']] as [number, string][],
- },
- },
- axisTick: { show: false },
- splitLine: { show: false },
- axisLabel: { show: false },
- pointer: { show: false },
- title: {
- show: true,
- offsetCenter: [0, '80%'],
- fontSize: 14,
- color: '#666',
- },
- detail: {
- valueAnimation: true,
- fontSize: 32,
- fontWeight: 'bold',
- color,
- offsetCenter: [0, '10%'],
- formatter: (val: number) => `${val} 涓猔,
- },
- data: [{ value, name: title }],
- },
- ],
- };
-}
-
-/** 璁惧鏁伴噺楗煎浘閰嶇疆 */
-export function getDeviceCountPieChartOptions(
- data: Array<{ name: string; value: number }>,
-): any {
- return {
- tooltip: {
- trigger: 'item',
- formatter: '{b}: {c} 涓� ({d}%)',
- },
- legend: {
- type: 'scroll',
- orient: 'horizontal',
- bottom: '10px',
- left: 'center',
- icon: 'circle',
- itemWidth: 10,
- itemHeight: 10,
- itemGap: 12,
- textStyle: {
- fontSize: 12,
- },
- pageButtonPosition: 'end',
- pageIconSize: 12,
- pageTextStyle: {
- fontSize: 12,
- },
- pageFormatter: '{current}/{total}',
- },
- series: [
- {
- name: '璁惧鏁伴噺',
- type: 'pie',
- radius: ['35%', '55%'],
- center: ['50%', '40%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 8,
- borderColor: '#fff',
- borderWidth: 2,
- },
- label: {
- show: false,
- },
- emphasis: {
- label: {
- show: true,
- fontSize: 16,
- fontWeight: 'bold',
- },
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)',
- },
- },
- labelLine: {
- show: false,
- },
- data,
- },
- ],
- };
-}
diff --git a/src/views/iot/home/data.ts b/src/views/iot/home/data.ts
deleted file mode 100644
index eaa9159..0000000
--- a/src/views/iot/home/data.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { IotStatisticsApi } from '#/api/iot/statistics';
-
-/** 缁熻鏁版嵁 */
-export type StatsData = IotStatisticsApi.StatisticsSummaryRespVO;
-
-/** 榛樿缁熻鏁版嵁锛涚敤 -1 浣滀负銆屾湭鍔犺浇銆嶅摠鍏碉紝閬垮厤涓庛�岀湡 0 璁惧銆嶆贩娣� */
-export const defaultStatsData: StatsData = {
- productCategoryCount: -1,
- productCount: -1,
- deviceCount: -1,
- deviceMessageCount: -1,
- productCategoryTodayCount: -1,
- productTodayCount: -1,
- deviceTodayCount: -1,
- deviceMessageTodayCount: -1,
- deviceOnlineCount: -1,
- deviceOfflineCount: -1,
- deviceInactiveCount: -1,
- productCategoryDeviceCounts: {},
-};
diff --git a/src/views/iot/home/index.vue b/src/views/iot/home/index.vue
deleted file mode 100644
index a9c9b52..0000000
--- a/src/views/iot/home/index.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<script lang="ts" setup>
-import type { StatsData } from './data';
-
-import { onMounted, ref } from 'vue';
-
-import { ComparisonCard, Page } from '../../../packages/effects/common-ui/src';
-
-import { Col, Row } from 'ant-design-vue';
-
-import { getStatisticsSummary } from '#/api/iot/statistics';
-
-import { defaultStatsData } from './data';
-import DeviceCountCard from './modules/device-count-card.vue';
-import DeviceMapCard from './modules/device-map-card.vue';
-import DeviceStateCountCard from './modules/device-state-count-card.vue';
-import MessageTrendCard from './modules/message-trend-card.vue';
-
-const loading = ref(true);
-const statsData = ref<StatsData>(defaultStatsData);
-
-/** 鍔犺浇鏁版嵁 */
-async function loadData() {
- loading.value = true;
- try {
- statsData.value = await getStatisticsSummary();
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadData();
-});
-</script>
-
-<template>
- <Page>
- <!-- 绗竴琛岋細缁熻鍗$墖 -->
- <Row :gutter="16" class="mb-4">
- <Col :span="6">
- <ComparisonCard
- title="鍒嗙被鏁伴噺"
- :value="statsData.productCategoryCount"
- :today-count="statsData.productCategoryTodayCount"
- icon="menu"
- icon-color="text-blue-500"
- :loading="loading"
- />
- </Col>
- <Col :span="6">
- <ComparisonCard
- title="浜у搧鏁伴噺"
- :value="statsData.productCount"
- :today-count="statsData.productTodayCount"
- icon="box"
- icon-color="text-orange-500"
- :loading="loading"
- />
- </Col>
- <Col :span="6">
- <ComparisonCard
- title="璁惧鏁伴噺"
- :value="statsData.deviceCount"
- :today-count="statsData.deviceTodayCount"
- icon="cpu"
- icon-color="text-purple-500"
- :loading="loading"
- />
- </Col>
- <Col :span="6">
- <ComparisonCard
- title="璁惧娑堟伅鏁�"
- :value="statsData.deviceMessageCount"
- :today-count="statsData.deviceMessageTodayCount"
- icon="message"
- icon-color="text-teal-500"
- :loading="loading"
- />
- </Col>
- </Row>
-
- <!-- 绗簩琛岋細鍥捐〃 -->
- <Row :gutter="16" class="mb-4">
- <Col :span="12">
- <DeviceCountCard :stats-data="statsData" :loading="loading" />
- </Col>
- <Col :span="12">
- <DeviceStateCountCard :stats-data="statsData" :loading="loading" />
- </Col>
- </Row>
-
- <!-- 绗笁琛岋細娑堟伅缁熻 -->
- <Row :gutter="16" class="mb-4">
- <Col :span="24">
- <MessageTrendCard />
- </Col>
- </Row>
-
- <!-- 绗洓琛岋細璁惧鍒嗗竷鍦板浘 -->
- <Row :gutter="16">
- <Col :span="24">
- <DeviceMapCard />
- </Col>
- </Row>
- </Page>
-</template>
diff --git a/src/views/iot/home/modules/device-count-card.vue b/src/views/iot/home/modules/device-count-card.vue
deleted file mode 100644
index a29af09..0000000
--- a/src/views/iot/home/modules/device-count-card.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<script lang="ts" setup>
-import type { IotStatisticsApi } from '#/api/iot/statistics';
-
-import { computed, nextTick, onMounted, ref, watch } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Empty } from 'ant-design-vue';
-
-import { getDeviceCountPieChartOptions } from '../chart-options';
-
-defineOptions({ name: 'DeviceCountCard' });
-
-const props = defineProps<{
- loading?: boolean;
- statsData: IotStatisticsApi.StatisticsSummaryRespVO;
-}>();
-
-const deviceCountChartRef = ref();
-const { renderEcharts } = useEcharts(deviceCountChartRef);
-
-/** 鏄惁鏈夋暟鎹� */
-const hasData = computed(() => {
- if (!props.statsData) {
- return false;
- }
- const categories = Object.entries(
- props.statsData.productCategoryDeviceCounts || {},
- );
- return categories.length > 0 && props.statsData.deviceCount !== -1;
-});
-
-/** 鍒濆鍖栧浘琛� */
-async function initChart() {
- if (!hasData.value) {
- return;
- }
-
- await nextTick();
- const data = Object.entries(props.statsData.productCategoryDeviceCounts).map(
- ([name, value]) => ({ name, value }),
- );
- await renderEcharts(getDeviceCountPieChartOptions(data));
-}
-
-/** 鐩戝惉鏁版嵁鍙樺寲 */
-watch(
- () => props.statsData,
- () => {
- initChart();
- },
- { deep: true },
-);
-
-/** 缁勪欢鎸傝浇鏃跺垵濮嬪寲鍥捐〃 */
-onMounted(() => {
- initChart();
-});
-</script>
-
-<template>
- <Card title="璁惧鏁伴噺缁熻" :loading="loading" class="h-full">
- <div
- v-if="loading && !hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鍔犺浇涓�..." />
- </div>
- <div
- v-else-if="!hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鏆傛棤鏁版嵁" />
- </div>
- <div v-else>
- <EchartsUI ref="deviceCountChartRef" class="h-[400px] w-full" />
- </div>
- </Card>
-</template>
diff --git a/src/views/iot/home/modules/device-map-card.vue b/src/views/iot/home/modules/device-map-card.vue
deleted file mode 100644
index fc66e10..0000000
--- a/src/views/iot/home/modules/device-map-card.vue
+++ /dev/null
@@ -1,215 +0,0 @@
-<script lang="ts" setup>
-import type { NumberDictDataType } from '../../../../packages/effects/hooks/src';
-
-import type { IotDeviceApi } from '#/api/iot/device/device';
-
-import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { DeviceStateEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictLabel, getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { Card, Empty, Spin } from 'ant-design-vue';
-
-import { getDeviceLocationList } from '#/api/iot/device/device';
-import { loadBaiduMapSdk } from '#/components/map';
-
-defineOptions({ name: 'DeviceMapCard' });
-
-const router = useRouter();
-const mapContainerRef = ref<HTMLElement>(); // 鍦板浘瀹瑰櫒鑺傜偣
-let mapInstance: any = null; // 鐧惧害鍦板浘瀹炰緥
-const loading = ref(true); // 鍔犺浇鐘舵��
-const deviceList = ref<IotDeviceApi.Device[]>([]); // 璁惧鍒嗗竷鍒楄〃
-
-const hasData = computed(() => deviceList.value.length > 0); // 鏄惁鏈夋暟鎹�
-
-const stateOptions = computed(
- () =>
- getDictOptions(
- DICT_TYPE.IOT_DEVICE_STATE,
- 'number',
- ) as NumberDictDataType[],
-); // 鐘舵�佸浘渚嬪垪琛紙浠庡瓧鍏歌幏鍙栵級
-
-const stateColorMap: Record<number, string> = {
- [DeviceStateEnum.INACTIVE]: '#EAB308', // 寰呮縺娲� - 榛勮壊
- [DeviceStateEnum.ONLINE]: '#22C55E', // 鍦ㄧ嚎 - 缁胯壊
- [DeviceStateEnum.OFFLINE]: '#9CA3AF', // 绂荤嚎 - 鐏拌壊
-}; // 璁惧鐘舵�侀鑹叉槧灏�
-
-/** 鑾峰彇璁惧鐘舵�侀厤缃紱鍚嶇О璧板瓧鍏革紝棰滆壊鐢ㄦ湰鍦版槧灏� */
-function getStateConfig(state: number): { color: string; name: string } {
- return {
- name: getDictLabel(DICT_TYPE.IOT_DEVICE_STATE, state) || '鏈煡',
- color: stateColorMap[state] || '#909399',
- };
-}
-
-/** 鍒涘缓鑷畾涔夋爣璁扮偣鍥炬爣 */
-function createMarkerIcon(color: string, isOnline: boolean) {
- const size = isOnline ? 24 : 20;
- const svg = `
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24">
- <circle cx="12" cy="12" r="8" fill="${color}" stroke="white" stroke-width="2"/>
- ${isOnline ? `<circle cx="12" cy="12" r="10" fill="none" stroke="${color}" stroke-width="2" opacity="0.5"/>` : ''}
- </svg>
- `;
- const blob = new Blob([svg], { type: 'image/svg+xml' });
- const url = URL.createObjectURL(blob);
- return new window.BMapGL.Icon(url, new window.BMapGL.Size(size, size), {
- anchor: new window.BMapGL.Size(size / 2, size / 2),
- });
-}
-
-/** 鍒濆鍖栧湴鍥� */
-function initMap() {
- if (!mapContainerRef.value || !window.BMapGL) {
- return;
- }
-
- // 閿�姣佹棫瀹炰緥
- if (mapInstance) {
- mapInstance.destroy?.();
- mapInstance = null;
- }
-
- // 鍒涘缓鍦板浘瀹炰緥锛岄粯璁や互涓浗涓轰腑蹇�
- mapInstance = new window.BMapGL.Map(mapContainerRef.value);
- mapInstance.centerAndZoom(new window.BMapGL.Point(106, 37.5), 5);
- mapInstance.enableScrollWheelZoom();
-
- // 娣诲姞鎺т欢
- mapInstance.addControl(new window.BMapGL.ScaleControl());
- mapInstance.addControl(new window.BMapGL.ZoomControl());
-
- // 娣诲姞璁惧鏍囪鐐�
- deviceList.value.forEach((device) => {
- const config = getStateConfig(device.state!);
- const isOnline = device.state === DeviceStateEnum.ONLINE;
- const point = new window.BMapGL.Point(device.longitude, device.latitude);
-
- // 鍒涘缓鏍囪
- const marker = new window.BMapGL.Marker(point, {
- icon: createMarkerIcon(config.color, isOnline),
- });
-
- // 鍒涘缓淇℃伅绐楀彛鍐呭
- const infoContent = `
- <div style="padding: 8px; min-width: 180px;">
- <div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">${device.nickname || device.deviceName}</div>
- <div style="color: #666; font-size: 12px; line-height: 1.8;">
- <div>浜у搧: ${device.productName || '-'}</div>
- <div>鐘舵��: <span style="color: ${config.color}; font-weight: 500;">${config.name}</span></div>
- </div>
- <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #eee;">
- <a href="javascript:void(0)" class="device-link" data-id="${device.id}" style="color: #1890ff; font-size: 12px; text-decoration: none;">鐐瑰嚮鏌ョ湅璇︽儏 鈫�</a>
- </div>
- </div>
- `;
-
- // 鐐瑰嚮鏍囪鏄剧ず淇℃伅绐楀彛
- marker.addEventListener('click', () => {
- const infoWindow = new window.BMapGL.InfoWindow(infoContent, {
- width: 220,
- height: 140,
- title: '',
- });
-
- // 淇℃伅绐楀彛鎵撳紑鍚庣粦瀹氶摼鎺ョ偣鍑讳簨浠�
- infoWindow.addEventListener('open', () => {
- setTimeout(() => {
- const link = document.querySelector(
- '.BMap_bubble_content .device-link',
- );
- if (!link) {
- return;
- }
- link.addEventListener('click', (e) => {
- e.preventDefault();
- if (device.id === undefined || device.id === null) {
- return;
- }
- router.push({
- name: 'IoTDeviceDetail',
- params: { id: device.id },
- });
- });
- }, 100);
- });
-
- mapInstance.openInfoWindow(infoWindow, point);
- });
-
- mapInstance.addOverlay(marker);
- });
-}
-
-/** 鍔犺浇璁惧鏁版嵁 */
-async function loadDeviceData() {
- loading.value = true;
- try {
- deviceList.value = await getDeviceLocationList();
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-async function init() {
- await loadDeviceData();
- if (!hasData.value) {
- return;
- }
- await loadBaiduMapSdk();
- // 绛夊緟 v-show 瀹瑰櫒娓叉煋瀹屾垚锛汼DK 缂撳瓨鍛戒腑鏃朵笂涓�琛屼細鍚屾 resolve锛孌OM 鏉ヤ笉鍙婂垏鎹�
- await nextTick();
- initMap();
-}
-
-/** 缁勪欢鎸傝浇鏃跺垵濮嬪寲 */
-onMounted(() => {
- init();
-});
-
-/** 缁勪欢鍗歌浇鏃堕攢姣佸湴鍥惧疄渚� */
-onUnmounted(() => {
- if (mapInstance) {
- mapInstance.destroy?.();
- mapInstance = null;
- }
-});
-</script>
-
-<template>
- <Card class="h-full" title="璁惧鍒嗗竷鍦板浘">
- <template #extra>
- <div class="flex items-center gap-4 text-sm">
- <span
- v-for="item in stateOptions"
- :key="item.value"
- class="flex items-center gap-1"
- >
- <span
- class="inline-block h-3 w-3 rounded-full"
- :style="{
- backgroundColor: stateColorMap[item.value],
- }"
- ></span>
- <span class="text-gray-500">{{ item.label }}</span>
- </span>
- </div>
- </template>
- <Spin v-if="loading" class="flex h-[500px] items-center justify-center" />
- <Empty
- v-else-if="!hasData"
- class="h-[500px]"
- description="鏆傛棤璁惧浣嶇疆鏁版嵁"
- />
- <div
- v-show="hasData && !loading"
- ref="mapContainerRef"
- class="h-[500px] w-full"
- ></div>
- </Card>
-</template>
diff --git a/src/views/iot/home/modules/device-state-count-card.vue b/src/views/iot/home/modules/device-state-count-card.vue
deleted file mode 100644
index 88b36d1..0000000
--- a/src/views/iot/home/modules/device-state-count-card.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<script lang="ts" setup>
-import type { IotStatisticsApi } from '#/api/iot/statistics';
-
-import { computed, nextTick, onMounted, ref, watch } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Col, Empty, Row } from 'ant-design-vue';
-
-import { getDeviceStateGaugeChartOptions } from '../chart-options';
-
-defineOptions({ name: 'DeviceStateCountCard' });
-
-const props = defineProps<{
- loading?: boolean;
- statsData: IotStatisticsApi.StatisticsSummaryRespVO;
-}>();
-
-const deviceOnlineChartRef = ref();
-const deviceOfflineChartRef = ref();
-const deviceInactiveChartRef = ref();
-
-const { renderEcharts: renderOnlineChart } = useEcharts(deviceOnlineChartRef);
-const { renderEcharts: renderOfflineChart } = useEcharts(deviceOfflineChartRef);
-const { renderEcharts: renderInactiveChart } = useEcharts(
- deviceInactiveChartRef,
-);
-
-/** 鏄惁鏈夋暟鎹� */
-const hasData = computed(() => {
- if (!props.statsData) return false;
- return props.statsData.deviceCount !== -1;
-});
-
-/** 鍒濆鍖栧浘琛� */
-async function initCharts() {
- if (!hasData.value) {
- return;
- }
-
- await nextTick();
- const max = props.statsData.deviceCount || 100;
- // 鍦ㄧ嚎璁惧
- await renderOnlineChart(
- getDeviceStateGaugeChartOptions(
- props.statsData.deviceOnlineCount,
- max,
- '#52c41a',
- '鍦ㄧ嚎璁惧',
- ),
- );
- // 绂荤嚎璁惧
- await renderOfflineChart(
- getDeviceStateGaugeChartOptions(
- props.statsData.deviceOfflineCount,
- max,
- '#ff4d4f',
- '绂荤嚎璁惧',
- ),
- );
- // 寰呮縺娲昏澶�
- await renderInactiveChart(
- getDeviceStateGaugeChartOptions(
- props.statsData.deviceInactiveCount,
- max,
- '#1890ff',
- '寰呮縺娲昏澶�',
- ),
- );
-}
-
-/** 鐩戝惉鏁版嵁鍙樺寲 */
-watch(
- () => props.statsData,
- () => {
- initCharts();
- },
- { deep: true },
-);
-
-/** 缁勪欢鎸傝浇鏃跺垵濮嬪寲鍥捐〃 */
-onMounted(() => {
- initCharts();
-});
-</script>
-
-<template>
- <Card title="璁惧鐘舵�佺粺璁�" :loading="loading" class="h-full">
- <div
- v-if="loading && !hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鍔犺浇涓�..." />
- </div>
- <div
- v-else-if="!hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鏆傛棤鏁版嵁" />
- </div>
- <Row v-else class="h-[280px]">
- <Col :span="8" class="flex items-center justify-center">
- <EchartsUI ref="deviceOnlineChartRef" class="h-[250px] w-full" />
- </Col>
- <Col :span="8" class="flex items-center justify-center">
- <EchartsUI ref="deviceOfflineChartRef" class="h-[250px] w-full" />
- </Col>
- <Col :span="8" class="flex items-center justify-center">
- <EchartsUI ref="deviceInactiveChartRef" class="h-[250px] w-full" />
- </Col>
- </Row>
- </Card>
-</template>
diff --git a/src/views/iot/home/modules/message-trend-card.vue b/src/views/iot/home/modules/message-trend-card.vue
deleted file mode 100644
index 22fe346..0000000
--- a/src/views/iot/home/modules/message-trend-card.vue
+++ /dev/null
@@ -1,187 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { IotStatisticsApi } from '#/api/iot/statistics';
-
-import { computed, nextTick, onMounted, reactive, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { EchartsUI, useEcharts } from '../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Empty, Select } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import { getDeviceMessageSummaryByDate } from '#/api/iot/statistics';
-import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
-
-import { getMessageTrendChartOptions } from '../chart-options';
-
-defineOptions({ name: 'MessageTrendCard' });
-
-const messageChartRef = ref();
-const { renderEcharts } = useEcharts(messageChartRef);
-
-const loading = ref(false); // 鍔犺浇鐘舵��
-const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
- [],
-); // 娑堟伅瓒嬪娍鏁版嵁
-const isFirstMount = ref(true); // 棣栨鎸傝浇鏍囪锛岀敤浜庤烦杩囧瓙缁勪欢 mount 鏃剁殑榛樿 emit
-
-/** 鏃堕棿鑼冨洿锛堜粎鏃ユ湡锛屼笉鍖呭惈鏃跺垎绉掞級 */
-const dateRange = ref<[string, string]>([
- // 榛樿鏄剧ず鏈�杩戜竴鍛ㄧ殑鏁版嵁锛堟埅鑷虫槰澶╋級
- dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
- dayjs().subtract(1, 'day').format('YYYY-MM-DD'),
-]);
-
-/** 灏嗘棩鏈熻寖鍥磋浆鎹负甯︽椂鍒嗙鐨勬牸寮� */
-function formatDateRangeWithTime(dates: [string, string]): [string, string] {
- return [`${dates[0]} 00:00:00`, `${dates[1]} 23:59:59`];
-}
-
-/** 鏌ヨ鍙傛暟 */
-const queryParams = reactive<IotStatisticsApi.DeviceMessageReqVO>({
- interval: 1, // 榛樿鎸夊ぉ
- times: formatDateRangeWithTime(dateRange.value),
-});
-
-/** 鏄惁鏈夋暟鎹� */
-const hasData = computed(() => {
- return messageData.value && messageData.value.length > 0;
-});
-
-/** 鏃堕棿闂撮殧瀛楀吀閫夐」 */
-const intervalOptions = computed(() =>
- getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number').map((item) => ({
- label: item.label,
- value: item.value as number,
- })),
-);
-
-/** 澶勭悊鏌ヨ鎿嶄綔 */
-function handleQuery() {
- fetchMessageData();
-}
-
-/** 澶勭悊鏃堕棿鑼冨洿鍙樺寲 */
-function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
- if (!times || times.length !== 2) {
- return;
- }
- dateRange.value = [
- dayjs(times[0]).format('YYYY-MM-DD'),
- dayjs(times[1]).format('YYYY-MM-DD'),
- ];
- // 灏嗛�夋嫨鐨勬棩鏈熻浆鎹负甯︽椂鍒嗙鐨勬牸寮忥紙寮�濮嬫棩鏈� 00:00:00锛岀粨鏉熸棩鏈� 23:59:59锛�
- queryParams.times = formatDateRangeWithTime(dateRange.value);
- if (isFirstMount.value) {
- // 瀛愮粍浠� ShortcutDateRangePicker mount 鏃朵細 emit 涓�娆¢粯璁ゆ棩鏈熻寖鍥达紝璺宠繃 fetch锛�
- // 棣栨璇锋眰缁熶竴鐢辩埗缁勪欢 onMounted 鍙戣捣锛岄伩鍏嶅弻璇锋眰
- return;
- }
- handleQuery();
-}
-
-/** 澶勭悊鏃堕棿闂撮殧鍙樺寲 */
-function handleIntervalChange() {
- handleQuery();
-}
-
-/** 鑾峰彇娑堟伅缁熻鏁版嵁 */
-async function fetchMessageData() {
- if (!queryParams.times || queryParams.times.length !== 2) {
- return;
- }
-
- loading.value = true;
- try {
- messageData.value = await getDeviceMessageSummaryByDate(queryParams);
- } catch {
- messageData.value = [];
- } finally {
- loading.value = false;
- await renderChartWhenReady();
- }
-}
-
-/** 鍒濆鍖栧浘琛� */
-function initChart() {
- // 妫�鏌ユ暟鎹槸鍚﹀瓨鍦�
- if (!hasData.value) {
- return;
- }
-
- const times = messageData.value.map((item) => item.time);
- const upstreamData = messageData.value.map((item) => item.upstreamCount);
- const downstreamData = messageData.value.map((item) => item.downstreamCount);
- renderEcharts(
- getMessageTrendChartOptions(times, upstreamData, downstreamData),
- );
-}
-
-/** 纭繚鍥捐〃瀹瑰櫒宸茬粡鍙鍚庡啀娓叉煋 */
-async function renderChartWhenReady() {
- if (!hasData.value) {
- return;
- }
- // 绛夊緟 Card loading 鐘舵�併�乿-show 绛� DOM 鏇存柊瀹屾垚
- await nextTick();
- await nextTick();
- initChart();
-}
-// 鐖剁粍浠舵寕杞藉悗缁熶竴鍙戣捣棣栨璇锋眰锛�
-// 鍘熷洜锛氬瓙缁勪欢 ShortcutDateRangePicker 鏃╂湡 emit 瑙﹀彂鐨勮姹傝惤鍦� useEcharts isActiveRef = false 闃舵锛屼細琚� renderEcharts 闈欓粯涓㈠純锛�
-// 閫氳繃 handleDateRangeChange 鍦� isFirstMount=true 鏃惰烦杩� fetch锛岀敱杩欓噷缁熶竴鍙戣捣涓�娆★紝閬垮厤鍙岃姹�
-onMounted(() => {
- fetchMessageData();
- isFirstMount.value = false;
-});
-</script>
-
-<template>
- <Card class="h-full">
- <template #title>
- <div class="flex flex-wrap items-center justify-between gap-4">
- <span class="text-base font-medium text-gray-600">娑堟伅閲忕粺璁�</span>
- <div class="flex flex-wrap items-center gap-4">
- <div class="flex items-center gap-3">
- <span class="whitespace-nowrap text-sm text-gray-500">
- 鏃堕棿鑼冨洿
- </span>
- <ShortcutDateRangePicker @change="handleDateRangeChange" />
- </div>
- <div class="flex items-center gap-2">
- <span class="text-sm text-gray-500">鏃堕棿闂撮殧</span>
- <Select
- v-model:value="queryParams.interval"
- :options="intervalOptions"
- placeholder="闂撮殧绫诲瀷"
- :style="{ width: '80px' }"
- @change="handleIntervalChange"
- />
- </div>
- </div>
- </div>
- </template>
-
- <!-- 鍔犺浇涓姸鎬� -->
- <div
- v-show="loading && !hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鍔犺浇涓�..." />
- </div>
- <!-- 鏃犳暟鎹姸鎬� -->
- <div
- v-show="!loading && !hasData"
- class="flex h-[300px] items-center justify-center"
- >
- <Empty description="鏆傛棤鏁版嵁" />
- </div>
- <!-- 鍥捐〃瀹瑰櫒 - 浣跨敤 v-show 鑰岄潪 v-if锛岀‘淇濈粍浠跺缁堟寕杞� -->
- <div v-show="hasData">
- <EchartsUI ref="messageChartRef" class="h-[300px] w-full" />
- </div>
- </Card>
-</template>
diff --git a/src/views/iot/ota/firmware/data.ts b/src/views/iot/ota/firmware/data.ts
deleted file mode 100644
index ddce3db..0000000
--- a/src/views/iot/ota/firmware/data.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { formatDateTime } from '../../../../packages/utils/src';
-
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-let productList: IotProductApi.Product[] = [];
-getSimpleProductList().then((data) => (productList = data));
-
-/** 鏍规嵁浜у搧 ID 鍙栦骇鍝佸悕绉� */
-export function getProductName(productId?: number): string {
- if (!productId) {
- return '-';
- }
- return productList.find((product) => product.id === productId)?.name || '-';
-}
-
-/** 鍥轰欢璇︽儏鐨勬弿杩板瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- { field: 'name', label: '鍥轰欢鍚嶇О' },
- {
- field: 'productName',
- label: '鎵�灞炰骇鍝�',
- render: (val) => val || '-',
- },
- { field: 'version', label: '鍥轰欢鐗堟湰' },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => (val ? (formatDateTime(val) as string) : '-'),
- },
- { field: 'description', label: '鍥轰欢鎻忚堪', span: 2 },
- ];
-}
-
-/** 鏂板/淇敼鍥轰欢鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍥轰欢鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ浐浠跺悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'productId',
- label: '鎵�灞炰骇鍝�',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- },
- dependencies: {
- triggerFields: ['id'],
- componentProps: (values) => ({
- disabled: !!values.id,
- }),
- rules: (values) => (values.id ? null : 'required'),
- },
- },
- {
- fieldName: 'version',
- label: '鐗堟湰鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ増鏈彿',
- },
- dependencies: {
- triggerFields: ['id'],
- componentProps: (values) => ({
- disabled: !!values.id,
- }),
- rules: (values) => (values.id ? null : 'required'),
- },
- },
- {
- fieldName: 'description',
- label: '鍥轰欢鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ浐浠舵弿杩�',
- rows: 3,
- },
- },
- {
- fieldName: 'fileUrl',
- label: '鍥轰欢鏂囦欢',
- component: 'FileUpload',
- componentProps: {
- maxNumber: 1,
- accept: ['bin', 'zip', 'pdf'],
- maxSize: 50,
- helpText: '鏀寔涓婁紶 .bin銆�.zip銆�.pdf 鏍煎紡鐨勫浐浠舵枃浠讹紝鏈�澶� 50MB',
- },
- dependencies: {
- triggerFields: ['id'],
- componentProps: (values) => ({
- disabled: !!values.id,
- }),
- rules: (values) => (values.id ? null : 'required'),
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍥轰欢鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ浐浠跺悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'productId',
- label: '浜у搧',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鍥轰欢缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '鍥轰欢鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'version',
- title: '鐗堟湰鍙�',
- minWidth: 120,
- },
- {
- field: 'description',
- title: '鍥轰欢鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'productId',
- title: '鎵�灞炰骇鍝�',
- minWidth: 150,
- slots: { default: 'productName' },
- },
- {
- field: 'fileUrl',
- title: '鍥轰欢鏂囦欢',
- minWidth: 120,
- slots: { default: 'fileUrl' },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/ota/firmware/detail/index.vue b/src/views/iot/ota/firmware/detail/index.vue
deleted file mode 100644
index 0b94650..0000000
--- a/src/views/iot/ota/firmware/detail/index.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
-
-import { onMounted, ref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-
-import { getOtaFirmware } from '#/api/iot/ota/firmware';
-import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
-
-import OtaTaskList from '../../task/modules/list.vue';
-import UpgradeStatistics from '../../task/modules/statistics.vue';
-import FirmwareInfo from './modules/info.vue';
-
-const route = useRoute();
-
-const firmwareId = ref(Number(route.params.id));
-const firmwareLoading = ref(false);
-const firmware = ref<IoTOtaFirmwareApi.Firmware>();
-
-const firmwareStatisticsLoading = ref(false);
-const firmwareStatistics = ref<Record<string, number>>({});
-
-/** 鑾峰彇鍥轰欢淇℃伅 */
-async function getFirmwareInfo() {
- firmwareLoading.value = true;
- try {
- firmware.value = await getOtaFirmware(firmwareId.value);
- } finally {
- firmwareLoading.value = false;
- }
-}
-
-/** 鑾峰彇鍗囩骇缁熻 */
-async function getStatistics() {
- firmwareStatisticsLoading.value = true;
- try {
- firmwareStatistics.value = await getOtaTaskRecordStatusStatistics(
- firmwareId.value,
- );
- } finally {
- firmwareStatisticsLoading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- getFirmwareInfo();
- getStatistics();
-});
-</script>
-
-<template>
- <Page>
- <!-- 鍥轰欢淇℃伅 -->
- <FirmwareInfo :firmware="firmware" :loading="firmwareLoading" />
- <!-- 鍗囩骇璁惧缁熻 -->
- <div class="mt-4">
- <UpgradeStatistics
- :loading="firmwareStatisticsLoading"
- :statistics="firmwareStatistics"
- />
- </div>
- <!-- 浠诲姟绠$悊 -->
- <div v-if="firmware?.productId" class="mt-4">
- <OtaTaskList
- :firmware-id="firmwareId"
- :product-id="firmware.productId"
- @success="getStatistics"
- />
- </div>
- </Page>
-</template>
diff --git a/src/views/iot/ota/firmware/detail/modules/info.vue b/src/views/iot/ota/firmware/detail/modules/info.vue
deleted file mode 100644
index f039587..0000000
--- a/src/views/iot/ota/firmware/detail/modules/info.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
-
-import { Card } from 'ant-design-vue';
-
-import { useDescription } from '#/components/description';
-
-import { useDetailSchema } from '../../data';
-
-defineProps<{
- firmware?: IoTOtaFirmwareApi.Firmware;
- loading?: boolean;
-}>();
-
-const [Description] = useDescription({
- bordered: true,
- column: 3,
- schema: useDetailSchema(),
-});
-</script>
-
-<template>
- <Card title="鍥轰欢淇℃伅" :loading="loading">
- <Description :data="firmware" />
- </Card>
-</template>
diff --git a/src/views/iot/ota/firmware/index.vue b/src/views/iot/ota/firmware/index.vue
deleted file mode 100644
index 7a35e07..0000000
--- a/src/views/iot/ota/firmware/index.vue
+++ /dev/null
@@ -1,181 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
-
-import { useRouter } from 'vue-router';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
-import { $t } from '#/locales';
-
-import { getProductName, useGridColumns, useGridFormSchema } from './data';
-import OtaFirmwareForm from './modules/form.vue';
-
-const { push } = useRouter();
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: OtaFirmwareForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍥轰欢 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍥轰欢 */
-function handleEdit(row: IoTOtaFirmwareApi.Firmware) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍒犻櫎鍥轰欢 */
-async function handleDelete(row: IoTOtaFirmwareApi.Firmware) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteOtaFirmware(row.id!);
- message.success({
- content: $t('ui.actionMessage.deleteSuccess', [row.name]),
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏌ョ湅鍥轰欢璇︽儏 */
-function handleDetail(row: IoTOtaFirmwareApi.Firmware) {
- push({ name: 'IoTOtaFirmwareDetail', params: { id: row.id } });
-}
-
-/** 璺宠浆鍒颁骇鍝佽鎯� */
-function handleOpenProductDetail(productId: number) {
- push({ name: 'IoTProductDetail', params: { id: productId } });
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getOtaFirmwarePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IoTOtaFirmwareApi.Firmware>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍥轰欢鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍥轰欢']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:ota-firmware:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <!-- 鎵�灞炰骇鍝佸垪锛氱偣鍑昏烦浜у搧璇︽儏 -->
- <template #productName="{ row }">
- <a
- v-if="row.productId && getProductName(row.productId) !== '-'"
- class="cursor-pointer text-primary hover:underline"
- @click="handleOpenProductDetail(row.productId)"
- >
- {{ getProductName(row.productId) }}
- </a>
- <span v-else class="text-gray-400">-</span>
- </template>
- <!-- 鍥轰欢鏂囦欢鍒� -->
- <template #fileUrl="{ row }">
- <div
- v-if="row.fileUrl"
- class="inline-flex items-center gap-1.5 align-middle leading-none"
- >
- <IconifyIcon
- icon="ant-design:download-outlined"
- class="shrink-0 align-middle text-base text-primary"
- />
- <a
- :href="row.fileUrl"
- target="_blank"
- download
- class="cursor-pointer align-middle text-primary hover:underline"
- >
- 涓嬭浇鍥轰欢
- </a>
- </div>
- <span v-else class="text-gray-400">鏃犳枃浠�</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['iot:ota-firmware:query'],
- onClick: handleDetail.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:ota-firmware:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:ota-firmware:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/ota/firmware/modules/form.vue b/src/views/iot/ota/firmware/modules/form.vue
deleted file mode 100644
index e8eed55..0000000
--- a/src/views/iot/ota/firmware/modules/form.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createOtaFirmware,
- getOtaFirmware,
- updateOtaFirmware,
-} from '#/api/iot/ota/firmware';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<IoTOtaFirmwareApi.Firmware>();
-
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍥轰欢'])
- : $t('ui.actionTitle.create', ['鍥轰欢']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟锛氱紪杈戞�佸彧鎻愪氦 id / name / description锛屽叾瀹冨瓧娈靛浐瀹氫笉鍙�
- const values = (await formApi.getValues()) as IoTOtaFirmwareApi.Firmware;
- const data: IoTOtaFirmwareApi.Firmware = formData.value?.id
- ? {
- id: formData.value.id,
- name: values.name,
- description: values.description,
- }
- : values;
- try {
- await (formData.value?.id
- ? updateOtaFirmware(data)
- : createOtaFirmware(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<IoTOtaFirmwareApi.Firmware>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getOtaFirmware(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/ota/task/data.ts b/src/views/iot/ota/task/data.ts
deleted file mode 100644
index 929e6be..0000000
--- a/src/views/iot/ota/task/data.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { DICT_TYPE, IoTOtaTaskDeviceScopeEnum } from '../../../../packages/constants/src';
-import { getDictLabel, getDictOptions } from '../../../../packages/effects/hooks/src';
-import { formatDateTime } from '../../../../packages/utils/src';
-
-/** 浠诲姟璇︽儏鐨勬弿杩板瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- { field: 'id', label: '浠诲姟缂栧彿' },
- { field: 'name', label: '浠诲姟鍚嶇О' },
- {
- field: 'deviceScope',
- label: '鍗囩骇鑼冨洿',
- render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, val),
- },
- {
- field: 'status',
- label: '浠诲姟鐘舵��',
- render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_STATUS, val),
- },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => (val ? (formatDateTime(val) as string) : '-'),
- },
- { field: 'description', label: '浠诲姟鎻忚堪', span: 3 },
- ];
-}
-
-/** 鏂板鍗囩骇浠诲姟鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'firmwareId',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '浠诲姟鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ换鍔″悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '浠诲姟鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ换鍔℃弿杩�',
- rows: 3,
- },
- },
- {
- fieldName: 'deviceScope',
- label: '鍗囩骇鑼冨洿',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, 'number'),
- placeholder: '璇烽�夋嫨鍗囩骇鑼冨洿',
- },
- defaultValue: IoTOtaTaskDeviceScopeEnum.ALL.value,
- rules: 'required',
- },
- {
- fieldName: 'deviceIds',
- label: '閫夋嫨璁惧',
- component: 'Select',
- componentProps: {
- mode: 'multiple',
- placeholder: '璇烽�夋嫨璁惧',
- showSearch: true,
- filterOption: true,
- optionFilterProp: 'label',
- },
- defaultValue: [],
- dependencies: {
- triggerFields: ['deviceScope'],
- show: (values) =>
- values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value,
- rules: (values) =>
- values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value
- ? 'required'
- : null,
- },
- },
- ];
-}
-
-/** 浠诲姟鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '浠诲姟缂栧彿',
- width: 80,
- align: 'center',
- },
- {
- field: 'name',
- title: '浠诲姟鍚嶇О',
- minWidth: 150,
- align: 'center',
- },
- {
- field: 'deviceScope',
- title: '鍗囩骇鑼冨洿',
- width: 110,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE },
- },
- },
- {
- field: 'progress',
- title: '鍗囩骇杩涘害',
- width: 110,
- align: 'center',
- formatter: ({ row }) =>
- `${row.deviceSuccessCount || 0}/${row.deviceTotalCount || 0}`,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- align: 'center',
- formatter: 'formatDateTime',
- },
- {
- field: 'description',
- title: '浠诲姟鎻忚堪',
- minWidth: 150,
- align: 'center',
- showOverflow: 'tooltip',
- },
- {
- field: 'status',
- title: '浠诲姟鐘舵��',
- width: 110,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_OTA_TASK_STATUS },
- },
- },
- {
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- align: 'center',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/ota/task/modules/detail.vue b/src/views/iot/ota/task/modules/detail.vue
deleted file mode 100644
index 79f3821..0000000
--- a/src/views/iot/ota/task/modules/detail.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { getOtaTask } from '#/api/iot/ota/task';
-import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
-
-import OtaTaskRecordList from '../record/modules/list.vue';
-import TaskInfo from './info.vue';
-import UpgradeStatistics from './statistics.vue';
-
-const emit = defineEmits(['success']);
-
-const taskId = ref<number>();
-const taskLoading = ref(false);
-const task = ref<IoTOtaTaskApi.Task>();
-
-const taskStatisticsLoading = ref(false);
-const taskStatistics = ref<Record<string, number>>({});
-
-/** 鑾峰彇浠诲姟璇︽儏 */
-async function getTaskInfo() {
- if (!taskId.value) {
- return;
- }
- taskLoading.value = true;
- try {
- task.value = await getOtaTask(taskId.value);
- } finally {
- taskLoading.value = false;
- }
-}
-
-/** 鑾峰彇缁熻鏁版嵁 */
-async function getStatistics() {
- if (!taskId.value) {
- return;
- }
- taskStatisticsLoading.value = true;
- try {
- taskStatistics.value = await getOtaTaskRecordStatusStatistics(
- undefined,
- taskId.value,
- );
- } finally {
- taskStatisticsLoading.value = false;
- }
-}
-
-/** 鍗曟潯璁板綍鍙栨秷鍚庯紝鍒锋柊浠诲姟淇℃伅鍜岀粺璁� */
-async function handleRecordSuccess() {
- await getStatistics();
- await getTaskInfo();
- emit('success');
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- const data = modalApi.getData<{ id: number }>();
- if (!data?.id) {
- return;
- }
- taskId.value = data.id;
- await Promise.all([getTaskInfo(), getStatistics()]);
- },
-});
-</script>
-
-<template>
- <Modal
- title="鍗囩骇浠诲姟璇︽儏"
- class="w-5/6"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <!-- 浠诲姟淇℃伅 -->
- <TaskInfo :task="task" :loading="taskLoading" />
- <!-- 鍗囩骇璁惧缁熻 -->
- <div class="mt-4">
- <UpgradeStatistics
- :loading="taskStatisticsLoading"
- :statistics="taskStatistics"
- />
- </div>
- <!-- 鍗囩骇璁惧璁板綍 -->
- <div class="mt-4">
- <OtaTaskRecordList :task-id="taskId" @success="handleRecordSuccess" />
- </div>
- </Modal>
-</template>
diff --git a/src/views/iot/ota/task/modules/form.vue b/src/views/iot/ota/task/modules/form.vue
deleted file mode 100644
index 8d06ef9..0000000
--- a/src/views/iot/ota/task/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getDeviceListByProductId } from '#/api/iot/device/device';
-import { createOtaTask } from '#/api/iot/ota/task';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as IoTOtaTaskApi.Task;
- try {
- await createOtaTask(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{ firmwareId: number; productId: number }>();
- if (!data?.firmwareId || !data?.productId) {
- return;
- }
- modalApi.lock();
- try {
- // 璁剧疆 firmwareId
- await formApi.setValues({ firmwareId: data.firmwareId });
- // 鍔犺浇浜у搧涓嬬殑璁惧鍒楄〃
- const devices = (await getDeviceListByProductId(data.productId)) || [];
- // 娉ㄥ叆鍒� deviceIds 瀛楁鐨� options
- formApi.updateSchema([
- {
- fieldName: 'deviceIds',
- componentProps: {
- options: devices.map((device) => ({
- label: device.nickname
- ? `${device.deviceName} (${device.nickname})`
- : device.deviceName,
- value: device.id,
- })),
- },
- },
- ]);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-3/5" :title="$t('ui.actionTitle.create', ['鍗囩骇浠诲姟'])">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/ota/task/modules/info.vue b/src/views/iot/ota/task/modules/info.vue
deleted file mode 100644
index a671ca7..0000000
--- a/src/views/iot/ota/task/modules/info.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script lang="ts" setup>
-import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
-
-import { Card } from 'ant-design-vue';
-
-import { useDescription } from '#/components/description';
-
-import { useDetailSchema } from '../data';
-
-defineProps<{
- loading?: boolean;
- task?: IoTOtaTaskApi.Task;
-}>();
-
-const [Description] = useDescription({
- bordered: true,
- column: 3,
- schema: useDetailSchema(),
-});
-</script>
-
-<template>
- <Card title="浠诲姟淇℃伅" :loading="loading">
- <Description :data="task" />
- </Card>
-</template>
diff --git a/src/views/iot/ota/task/modules/list.vue b/src/views/iot/ota/task/modules/list.vue
deleted file mode 100644
index 18256ea..0000000
--- a/src/views/iot/ota/task/modules/list.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { IoTOtaTaskStatusEnum } from '../../../../../packages/constants/src';
-
-import { Input, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { cancelOtaTask, getOtaTaskPage } from '#/api/iot/ota/task';
-import { $t } from '#/locales';
-
-import { useGridColumns } from '../data';
-import OtaTaskDetail from './detail.vue';
-import OtaTaskForm from './form.vue';
-
-const props = defineProps<{
- firmwareId: number;
- productId: number;
-}>();
-
-const emit = defineEmits(['success']);
-
-const searchName = ref('');
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: OtaTaskForm,
- destroyOnClose: true,
-});
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: OtaTaskDetail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-async function handleRefresh() {
- await gridApi.query();
- emit('success');
-}
-
-/** 鎸変换鍔″悕绉版悳绱� */
-async function handleSearch() {
- await gridApi.reload();
-}
-
-/** 鏂板浠诲姟 */
-function handleCreate() {
- formModalApi
- .setData({ firmwareId: props.firmwareId, productId: props.productId })
- .open();
-}
-
-/** 鏌ョ湅浠诲姟璇︽儏 */
-function handleDetail(row: IoTOtaTaskApi.Task) {
- detailModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍙栨秷浠诲姟 */
-async function handleCancel(row: IoTOtaTaskApi.Task) {
- await cancelOtaTask(row.id!);
- message.success('鍙栨秷鎴愬姛');
- await handleRefresh();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- maxHeight: 500,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- return await getOtaTaskPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- firmwareId: props.firmwareId,
- name: searchName.value || undefined,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- } as VxeTableGridOptions<IoTOtaTaskApi.Task>,
-});
-</script>
-
-<template>
- <div>
- <FormModal @success="handleRefresh" />
- <DetailModal @success="handleRefresh" />
-
- <Grid table-title="鍗囩骇浠诲姟绠$悊">
- <template #toolbar-tools>
- <div class="flex items-center gap-2">
- <Input
- v-model:value="searchName"
- placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
- allow-clear
- style="width: 200px"
- @press-enter="handleSearch"
- @clear="handleSearch"
- />
- <TableAction
- :actions="[
- {
- label: $t('common.search'),
- type: 'default',
- icon: 'ant-design:search-outlined',
- onClick: handleSearch,
- },
- {
- label: $t('ui.actionTitle.create', ['鍗囩骇浠诲姟']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:ota-task:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </div>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['iot:ota-task:query'],
- onClick: handleDetail.bind(null, row),
- },
- {
- label: $t('common.cancel'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:ota-task:cancel'],
- ifShow: row.status === IoTOtaTaskStatusEnum.IN_PROGRESS.value,
- popConfirm: {
- title: '纭瑕佸彇娑堣鍗囩骇浠诲姟鍚楋紵',
- confirm: handleCancel.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </div>
-</template>
diff --git a/src/views/iot/ota/task/modules/statistics.vue b/src/views/iot/ota/task/modules/statistics.vue
deleted file mode 100644
index 70fafc8..0000000
--- a/src/views/iot/ota/task/modules/statistics.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue';
-
-import { DICT_TYPE, IoTOtaTaskRecordStatusEnum } from '../../../../../packages/constants/src';
-import { getDictLabel } from '../../../../../packages/effects/hooks/src';
-
-import { Card, Col, Row } from 'ant-design-vue';
-
-const props = defineProps<{
- loading?: boolean;
- statistics: Record<string, number>;
-}>();
-
-/** 鍙栧瓧鍏告爣绛撅紙鍚屾锛� */
-function dictLabel(value: number) {
- return getDictLabel(DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS, value);
-}
-
-/** 缁熻椤归厤缃� */
-const items = computed(() => [
- {
- label: '鍗囩骇璁惧鎬绘暟',
- span: 6,
- color: 'text-blue-500',
- value: Object.values(props.statistics).reduce(
- (sum, count) => sum + (count || 0),
- 0,
- ),
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.PENDING.value),
- span: 3,
- color: 'text-gray-400',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.PENDING.value] || 0,
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.PUSHED.value),
- span: 3,
- color: 'text-blue-400',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0,
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.UPGRADING.value),
- span: 3,
- color: 'text-yellow-500',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.UPGRADING.value] || 0,
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.SUCCESS.value),
- span: 3,
- color: 'text-green-500',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] || 0,
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.FAILURE.value),
- span: 3,
- color: 'text-red-500',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] || 0,
- },
- {
- label: dictLabel(IoTOtaTaskRecordStatusEnum.CANCELED.value),
- span: 3,
- color: 'text-gray-400',
- value: props.statistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0,
- },
-]);
-</script>
-
-<template>
- <Card title="鍗囩骇璁惧缁熻" :loading="loading">
- <Row :gutter="20" class="py-5">
- <Col v-for="item in items" :key="item.label" :span="item.span">
- <div
- class="rounded border border-solid border-gray-200 bg-gray-50 p-5 text-center"
- >
- <div class="mb-2 text-3xl font-bold" :class="item.color">
- {{ item.value }}
- </div>
- <div class="text-sm text-gray-600">{{ item.label }}</div>
- </div>
- </Col>
- </Row>
- </Card>
-</template>
diff --git a/src/views/iot/ota/task/record/data.ts b/src/views/iot/ota/task/record/data.ts
deleted file mode 100644
index 33e0db7..0000000
--- a/src/views/iot/ota/task/record/data.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-
-/** 鍗囩骇璁板綍鐨勫垪琛ㄥ瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'deviceName',
- title: '璁惧鍚嶇О',
- minWidth: 150,
- align: 'center',
- },
- {
- field: 'fromFirmwareVersion',
- title: '褰撳墠鐗堟湰',
- width: 120,
- align: 'center',
- },
- {
- field: 'status',
- title: '鍗囩骇鐘舵��',
- width: 120,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS },
- },
- },
- {
- field: 'progress',
- title: '鍗囩骇杩涘害',
- width: 120,
- align: 'center',
- formatter: ({ row }) => `${row.progress || 0}%`,
- },
- {
- field: 'description',
- title: '鐘舵�佹弿杩�',
- minWidth: 150,
- align: 'center',
- showOverflow: 'tooltip',
- },
- {
- field: 'updateTime',
- title: '鏇存柊鏃堕棿',
- width: 180,
- align: 'center',
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- align: 'center',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/ota/task/record/modules/list.vue b/src/views/iot/ota/task/record/modules/list.vue
deleted file mode 100644
index 9dcd689..0000000
--- a/src/views/iot/ota/task/record/modules/list.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IoTOtaTaskRecordApi } from '#/api/iot/ota/task/record';
-
-import { computed, ref, watch } from 'vue';
-
-import { IoTOtaTaskRecordStatusEnum } from '../../../../../../packages/constants/src';
-
-import { Card, message, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- cancelOtaTaskRecord,
- getOtaTaskRecordPage,
-} from '#/api/iot/ota/task/record';
-import { $t } from '#/locales';
-
-import { useGridColumns } from '../data';
-
-const props = defineProps<{
- taskId: number | undefined;
-}>();
-
-const emit = defineEmits(['success']);
-
-const activeTab = ref('');
-
-/** 鐘舵�佹爣绛鹃厤缃� */
-const statusTabs = computed(() => {
- const tabs = [{ key: '', label: '鍏ㄩ儴璁惧' }];
- Object.values(IoTOtaTaskRecordStatusEnum).forEach((status) => {
- tabs.push({
- key: status.value.toString(),
- label: status.label,
- });
- });
- return tabs;
-});
-
-/** 鍒囨崲鏍囩 */
-async function handleTabChange(tabKey: number | string) {
- activeTab.value = String(tabKey);
- await gridApi.reload();
-}
-
-/** 鍙栨秷鍗曟潯璁板綍鐨勫崌绾� */
-async function handleCancelUpgrade(record: IoTOtaTaskRecordApi.TaskRecord) {
- await cancelOtaTaskRecord(record.id!);
- message.success('鍙栨秷鎴愬姛');
- await gridApi.query();
- emit('success');
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 400,
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- pagerConfig: {
- enabled: true,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- if (!props.taskId) {
- return { list: [], total: 0 };
- }
- return await getOtaTaskRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- taskId: props.taskId,
- status:
- activeTab.value === '' ? undefined : Number(activeTab.value),
- });
- },
- },
- },
- toolbarConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<IoTOtaTaskRecordApi.TaskRecord>,
-});
-
-/** taskId 鍙樺寲鏃堕噸鏂版煡璇� */
-watch(
- () => props.taskId,
- async (val) => {
- if (val) {
- activeTab.value = '';
- await gridApi.reload();
- }
- },
-);
-</script>
-
-<template>
- <Card title="鍗囩骇璁惧璁板綍">
- <Tabs v-model:active-key="activeTab" @change="handleTabChange" class="mb-4">
- <Tabs.TabPane v-for="tab in statusTabs" :key="tab.key" :tab="tab.label" />
- </Tabs>
- <Grid>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.cancel'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:ota-task-record:cancel'],
- ifShow: [
- IoTOtaTaskRecordStatusEnum.PENDING.value,
- IoTOtaTaskRecordStatusEnum.PUSHED.value,
- IoTOtaTaskRecordStatusEnum.UPGRADING.value,
- ].includes(row.status!),
- popConfirm: {
- title: '纭瑕佸彇娑堣璁惧鐨勫崌绾т换鍔″悧锛�',
- confirm: handleCancelUpgrade.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Card>
-</template>
diff --git a/src/views/iot/product/category/data.ts b/src/views/iot/product/category/data.ts
deleted file mode 100644
index 5a84863..0000000
--- a/src/views/iot/product/category/data.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductCategoryApi } from '#/api/iot/product/category';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶅瓧',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕瀛�',
- },
- rules: z
- .string()
- .min(1, '鍒嗙被鍚嶅瓧涓嶈兘涓虹┖')
- .max(64, '鍒嗙被鍚嶅瓧闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�'),
- },
- {
- fieldName: 'sort',
- label: '鍒嗙被鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ュ垎绫绘帓搴�',
- min: 0,
- precision: 0,
- },
- defaultValue: 0,
- rules: z.number().min(0, '鍒嗙被鎺掑簭涓嶈兘灏忎簬 0'),
- },
- {
- fieldName: 'status',
- label: '鍒嗙被鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'description',
- label: '鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫绘弿杩�',
- rows: 3,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶅瓧',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕瀛�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<IotProductCategoryApi.ProductCategory>['columns'] {
- return [
- {
- field: 'id',
- title: 'ID',
- width: 80,
- },
- {
- field: 'name',
- title: '鍚嶅瓧',
- minWidth: 200,
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- width: 100,
- },
- {
- field: 'status',
- title: '鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'description',
- title: '鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/product/category/index.vue b/src/views/iot/product/category/index.vue
deleted file mode 100644
index 0d66ebb..0000000
--- a/src/views/iot/product/category/index.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductCategoryApi } from '#/api/iot/product/category';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteProductCategory,
- getProductCategoryPage,
-} from '#/api/iot/product/category';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍒嗙被 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍒嗙被 */
-function handleEdit(row: IotProductCategoryApi.ProductCategory) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍒嗙被 */
-async function handleDelete(row: IotProductCategoryApi.ProductCategory) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteProductCategory(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getProductCategoryPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotProductCategoryApi.ProductCategory>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="浜у搧鍒嗙被鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍒嗙被']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:product-category:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:product-category:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:product-category:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/product/category/modules/form.vue b/src/views/iot/product/category/modules/form.vue
deleted file mode 100644
index a801f9c..0000000
--- a/src/views/iot/product/category/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductCategoryApi } from '#/api/iot/product/category';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createProductCategory,
- getProductCategory,
- updateProductCategory,
-} from '#/api/iot/product/category';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<IotProductCategoryApi.ProductCategory>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['浜у搧鍒嗙被'])
- : $t('ui.actionTitle.create', ['浜у搧鍒嗙被']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as IotProductCategoryApi.ProductCategory;
- try {
- await (formData.value?.id
- ? updateProductCategory(data)
- : createProductCategory(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<IotProductCategoryApi.ProductCategory>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getProductCategory(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/iot/product/product/components/index.ts b/src/views/iot/product/product/components/index.ts
deleted file mode 100644
index 5ddac0c..0000000
--- a/src/views/iot/product/product/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as ProductSelect } from './select.vue';
diff --git a/src/views/iot/product/product/components/select.vue b/src/views/iot/product/product/components/select.vue
deleted file mode 100644
index b73a8b9..0000000
--- a/src/views/iot/product/product/components/select.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { onMounted, ref } from 'vue';
-
-import { Select } from 'ant-design-vue';
-
-import { getSimpleProductList } from '#/api/iot/product/product';
-
-/** 浜у搧涓嬫媺閫夋嫨鍣ㄧ粍浠� */
-defineOptions({ name: 'ProductSelect' });
-
-const props = defineProps<{
- deviceType?: number; // 璁惧绫诲瀷杩囨护
- modelValue?: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value?: number): void;
- (e: 'change', value?: number): void;
-}>();
-
-const loading = ref(false);
-const productList = ref<IotProductApi.Product[]>([]);
-
-/** 澶勭悊閫夋嫨鍙樺寲 */
-function handleChange(value: any) {
- emit('update:modelValue', value as number | undefined);
- emit('change', value as number | undefined);
-}
-
-/** 鑾峰彇浜у搧鍒楄〃 */
-async function getProductList() {
- try {
- loading.value = true;
- productList.value = (await getSimpleProductList(props.deviceType)) || [];
- } finally {
- loading.value = false;
- }
-}
-
-onMounted(() => {
- getProductList();
-});
-</script>
-
-<template>
- <Select
- :value="modelValue"
- :options="
- productList.map((product) => ({
- label: product.name,
- value: product.id,
- }))
- "
- :loading="loading"
- placeholder="璇烽�夋嫨浜у搧"
- allow-clear
- class="w-full"
- option-filter-prop="label"
- show-search
- @change="handleChange"
- />
-</template>
diff --git a/src/views/iot/product/product/data.ts b/src/views/iot/product/product/data.ts
deleted file mode 100644
index c353ff4..0000000
--- a/src/views/iot/product/product/data.ts
+++ /dev/null
@@ -1,259 +0,0 @@
-import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { h } from 'vue';
-
-import { DeviceTypeEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { Button } from 'ant-design-vue';
-
-import { z } from '#/adapter/form';
-import { getSimpleProductCategoryList } from '#/api/iot/product/category';
-
-/** 鍩虹琛ㄥ崟瀛楁锛堜笉鍚浘鏍囥�佸浘鐗囥�佹弿杩帮級 */
-export function useBasicFormSchema(
- formApi?: VbenFormApi,
- generateProductKey?: () => string,
-): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'productKey',
- label: 'ProductKey',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� ProductKey',
- },
- dependencies: {
- triggerFields: ['id'],
- if(values) {
- return !values.id;
- },
- },
- rules: z
- .string()
- .min(1, 'ProductKey 涓嶈兘涓虹┖')
- .max(32, 'ProductKey 闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�'),
- suffix: () => {
- return h(
- Button,
- {
- type: 'default',
- onClick: () => {
- if (generateProductKey) {
- formApi?.setFieldValue('productKey', generateProductKey());
- }
- },
- },
- { default: () => '閲嶆柊鐢熸垚' },
- );
- },
- },
- {
- fieldName: 'productKey',
- label: 'ProductKey',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� ProductKey',
- disabled: true,
- },
- dependencies: {
- triggerFields: ['id'],
- if(values) {
- return !!values.id;
- },
- },
- rules: z
- .string()
- .min(1, 'ProductKey 涓嶈兘涓虹┖')
- .max(32, 'ProductKey 闀垮害涓嶈兘瓒呰繃 32 涓瓧绗�'),
- },
- {
- fieldName: 'name',
- label: '浜у搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ骇鍝佸悕绉�',
- },
- rules: z
- .string()
- .min(1, '浜у搧鍚嶇О涓嶈兘涓虹┖')
- .max(64, '浜у搧鍚嶇О闀垮害涓嶈兘瓒呰繃 64 涓瓧绗�'),
- },
- {
- fieldName: 'categoryId',
- label: '浜у搧鍒嗙被',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductCategoryList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨浜у搧鍒嗙被',
- },
- rules: 'required',
- },
- {
- fieldName: 'deviceType',
- label: '璁惧绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- dependencies: {
- triggerFields: ['id'],
- componentProps: (values) => ({
- // 缂栬緫鏃惰澶囩被鍨嬩笉鍙敼
- disabled: !!values.id,
- }),
- },
- rules: 'required',
- },
- {
- fieldName: 'netType',
- label: '鑱旂綉鏂瑰紡',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鑱旂綉鏂瑰紡',
- },
- // 缃戝叧瀛愯澶囪蛋缃戝叧鑱旂綉锛屼笉闇�瑕佽仈缃戞柟寮�
- dependencies: {
- triggerFields: ['deviceType'],
- show: (values) =>
- [DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY].includes(
- values.deviceType,
- ),
- },
- rules: 'required',
- },
- {
- fieldName: 'protocolType',
- label: '鍗忚绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'string'),
- placeholder: '璇烽�夋嫨鍗忚绫诲瀷',
- },
- defaultValue: 'mqtt',
- rules: 'required',
- },
- {
- fieldName: 'serializeType',
- label: '搴忓垪鍖栫被鍨�',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_SERIALIZE_TYPE, 'string'),
- placeholder: '璇烽�夋嫨搴忓垪鍖栫被鍨�',
- },
- defaultValue: 'json',
- help: 'iot-gateway-server 榛樿鏍规嵁鎺ュ叆鐨勫崗璁被鍨嬬‘瀹氭暟鎹牸寮忥紝浠� MQTT銆丒MQX 鍗忚鏀寔鑷畾涔夊簭鍒楀寲绫诲瀷',
- rules: 'required',
- },
- ];
-}
-
-/** 楂樼骇璁剧疆琛ㄥ崟瀛楁锛堝浘鏍囥�佸浘鐗囥�佷骇鍝佹弿杩般�佸姩鎬佹敞鍐岋級 */
-export function useAdvancedFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'registerEnabled',
- label: '鍔ㄦ�佹敞鍐�',
- component: 'Switch',
- componentProps: {
- checkedChildren: '寮�',
- unCheckedChildren: '鍏�',
- },
- defaultValue: false,
- help: '璁惧鍔ㄦ�佹敞鍐屾棤闇�涓�涓�鐑у綍璁惧璇佷功锛圖eviceSecret锛夛紝姣忓彴璁惧鐑у綍鐩稿悓鐨勪骇鍝佽瘉涔︼紝鍗� ProductKey 鍜� ProductSecret 锛屼簯绔壌鏉冮�氳繃鍚庝笅鍙戣澶囪瘉涔︼紝鎮ㄥ彲浠ユ牴鎹渶瑕佸紑鍚垨鍏抽棴鍔ㄦ�佹敞鍐岋紝淇濋殰瀹夊叏鎬с��',
- },
- {
- fieldName: 'icon',
- label: '浜у搧鍥炬爣',
- component: 'ImageUpload',
- },
- {
- fieldName: 'picUrl',
- label: '浜у搧鍥剧墖',
- component: 'ImageUpload',
- },
- {
- fieldName: 'description',
- label: '浜у搧鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ骇鍝佹弿杩�',
- rows: 3,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<IotProductApi.Product>['columns'] {
- return [
- {
- field: 'id',
- title: 'ID',
- width: 80,
- },
- {
- field: 'productKey',
- title: 'ProductKey',
- minWidth: 150,
- },
- {
- field: 'categoryName',
- title: '鍝佺被',
- minWidth: 120,
- formatter: ({ row }) => row.categoryName || '鏈垎绫�',
- },
- {
- field: 'deviceType',
- title: '璁惧绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE },
- },
- },
- {
- field: 'icon',
- title: '浜у搧鍥炬爣',
- width: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'picUrl',
- title: '浜у搧鍥剧墖',
- width: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 220,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/product/product/detail/index.vue b/src/views/iot/product/product/detail/index.vue
deleted file mode 100644
index 5e3c4e1..0000000
--- a/src/views/iot/product/product/detail/index.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { onMounted, provide, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { Page } from '../../../../../packages/effects/common-ui/src';
-import { IOT_PROVIDE_KEY } from '../../../../../packages/constants/src';
-
-import { message, Tabs } from 'ant-design-vue';
-
-import { getDeviceCount } from '#/api/iot/device/device';
-import { getProduct } from '#/api/iot/product/product';
-import IoTProductThingModel from '#/views/iot/thingmodel/index.vue';
-
-import ProductDetailsHeader from './modules/header.vue';
-import ProductDetailsInfo from './modules/info.vue';
-
-const route = useRoute();
-const router = useRouter();
-
-const id = Number(route.params.id);
-const loading = ref(true);
-const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
-const activeTab = ref('info');
-
-/** 鍚戝瓙缁勪欢鎻愪緵浜у搧淇℃伅 */
-provide(IOT_PROVIDE_KEY.PRODUCT, product);
-
-/** 鑾峰彇浜у搧璇︽儏 */
-async function getProductData(productId: number) {
- loading.value = true;
- try {
- product.value = await getProduct(productId);
- } catch {
- message.error('鑾峰彇浜у搧璇︽儏澶辫触');
- } finally {
- loading.value = false;
- }
-}
-
-/** 鏌ヨ璁惧鏁伴噺 */
-async function getDeviceCountData(productId: number) {
- try {
- return await getDeviceCount(productId);
- } catch {
- message.error('鑾峰彇璁惧鏁伴噺澶辫触');
- return 0;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- if (!id) {
- message.warning('鍙傛暟閿欒锛屼骇鍝佷笉鑳戒负绌猴紒');
- router.back();
- return;
- }
- await getProductData(id);
-
- // 澶勭悊 tab 鍙傛暟
- const { tab } = route.query;
- if (tab) {
- activeTab.value = tab as string;
- }
- // 鏌ヨ璁惧鏁伴噺
- if (product.value.id) {
- product.value.deviceCount = await getDeviceCountData(product.value.id);
- }
-});
-</script>
-
-<template>
- <Page>
- <ProductDetailsHeader
- :loading="loading"
- :product="product"
- @refresh="() => getProductData(id)"
- />
- <Tabs v-model:active-key="activeTab" class="mt-4">
- <Tabs.TabPane key="info" tab="浜у搧淇℃伅">
- <ProductDetailsInfo v-if="activeTab === 'info'" :product="product" />
- </Tabs.TabPane>
- <Tabs.TabPane key="thingModel" tab="鐗╂ā鍨嬶紙鍔熻兘瀹氫箟锛�">
- <IoTProductThingModel v-if="activeTab === 'thingModel'" />
- </Tabs.TabPane>
- </Tabs>
- </Page>
-</template>
diff --git a/src/views/iot/product/product/detail/modules/header.vue b/src/views/iot/product/product/detail/modules/header.vue
deleted file mode 100644
index 13387e2..0000000
--- a/src/views/iot/product/product/detail/modules/header.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { useRouter } from 'vue-router';
-
-import { useAccess } from '../../../../../../packages/effects/access/src';
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { ProductStatusEnum } from '../../../../../../packages/constants/src';
-
-import {
- Button,
- Card,
- Descriptions,
- message,
- Popconfirm,
-} from 'ant-design-vue';
-
-import {
- syncProductPropertyTable,
- updateProductStatus,
-} from '#/api/iot/product/product';
-
-import Form from '../../modules/form.vue';
-
-interface Props {
- product: IotProductApi.Product;
- loading?: boolean;
-}
-
-withDefaults(defineProps<Props>(), {
- loading: false,
-});
-
-const emit = defineEmits<{
- refresh: [];
-}>();
-
-const router = useRouter();
-const { hasAccessByCodes } = useAccess();
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 澶嶅埗鍒板壀璐存澘 */
-async function copyToClipboard(text: string) {
- try {
- await navigator.clipboard.writeText(text);
- message.success('澶嶅埗鎴愬姛');
- } catch {
- message.error('澶嶅埗澶辫触');
- }
-}
-
-/** 璺宠浆鍒拌澶囩鐞� */
-function goToDeviceList(productId: number) {
- router.push({
- name: 'IoTDevice',
- query: { productId: String(productId) },
- });
-}
-
-/** 鎵撳紑缂栬緫琛ㄥ崟 */
-function openEditForm(row: IotProductApi.Product) {
- formModalApi.setData(row).open();
-}
-
-/** 鍙戝竷浜у搧 */
-async function handlePublish(product: IotProductApi.Product) {
- await updateProductStatus(product.id!, ProductStatusEnum.PUBLISHED);
- message.success('鍙戝竷鎴愬姛');
- emit('refresh');
-}
-
-/** 鎾ら攢鍙戝竷 */
-async function handleUnpublish(product: IotProductApi.Product) {
- await updateProductStatus(product.id!, ProductStatusEnum.UNPUBLISHED);
- message.success('鎾ら攢鍙戝竷鎴愬姛');
- emit('refresh');
-}
-
-/** 鍚屾鐗╂ā鍨嬭秴绾ц〃缁撴瀯 */
-async function handleSyncPropertyTable(product: IotProductApi.Product) {
- await syncProductPropertyTable(product.id!);
- message.success('鍚屾鎴愬姛');
-}
-</script>
-
-<template>
- <div class="mb-4">
- <FormModal @success="emit('refresh')" />
-
- <div class="flex items-start justify-between">
- <div>
- <h2 class="text-xl font-bold">{{ product.name }}</h2>
- </div>
- <div class="flex gap-2">
- <Button
- v-if="hasAccessByCodes(['iot:product:update'])"
- :disabled="product.status === ProductStatusEnum.PUBLISHED"
- @click="openEditForm(product)"
- >
- 缂栬緫
- </Button>
- <Popconfirm
- v-if="
- product.status === ProductStatusEnum.UNPUBLISHED &&
- hasAccessByCodes(['iot:product:update'])
- "
- :title="`纭瑕佸彂甯冧骇鍝併��${product.name}銆嶅悧锛焋"
- @confirm="handlePublish(product)"
- >
- <Button type="primary">鍙戝竷</Button>
- </Popconfirm>
- <Popconfirm
- v-if="
- product.status === ProductStatusEnum.PUBLISHED &&
- hasAccessByCodes(['iot:product:update'])
- "
- :title="`纭瑕佹挙閿�鍙戝竷浜у搧銆�${product.name}銆嶅悧锛焋"
- @confirm="handleUnpublish(product)"
- >
- <Button danger>鎾ら攢鍙戝竷</Button>
- </Popconfirm>
- <Popconfirm
- v-if="hasAccessByCodes(['iot:product:update'])"
- :title="`纭瑕佸悓姝ヤ骇鍝併��${product.name}銆嶇殑鐗╂ā鍨嬭秴绾ц〃缁撴瀯鍚楋紵`"
- @confirm="handleSyncPropertyTable(product)"
- >
- <Button>鍚屾鐗╂ā鍨嬭〃缁撴瀯</Button>
- </Popconfirm>
- </div>
- </div>
-
- <Card class="mt-4">
- <Descriptions :column="1">
- <Descriptions.Item label="ProductKey">
- {{ product.productKey }}
- <Button
- class="ml-2"
- size="small"
- @click="copyToClipboard(product.productKey || '')"
- >
- 澶嶅埗
- </Button>
- </Descriptions.Item>
- <Descriptions.Item label="璁惧鎬绘暟">
- <span class="ml-5 mr-2">
- {{ product.deviceCount ?? '鍔犺浇涓�...' }}
- </span>
- <Button size="small" @click="goToDeviceList(product.id!)">
- 鍓嶅線绠$悊
- </Button>
- </Descriptions.Item>
- </Descriptions>
- </Card>
- </div>
-</template>
diff --git a/src/views/iot/product/product/detail/modules/info.vue b/src/views/iot/product/product/detail/modules/info.vue
deleted file mode 100644
index cc45604..0000000
--- a/src/views/iot/product/product/detail/modules/info.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { ref } from 'vue';
-
-import { DeviceTypeEnum, DICT_TYPE } from '../../../../../../packages/constants/src';
-
-import { Button, Card, Descriptions, message } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-
-interface Props {
- product: IotProductApi.Product;
-}
-
-defineProps<Props>();
-
-const showProductSecret = ref(false); // 鏄惁鏄剧ず浜у搧瀵嗛挜
-
-/** 鏍煎紡鍖栨棩鏈� */
-function formatDate(date?: Date | string) {
- if (!date) {
- return '-';
- }
- return new Date(date).toLocaleString('zh-CN');
-}
-
-/** 鍒囨崲浜у搧瀵嗛挜鏄剧ず鐘舵�� */
-function toggleProductSecretVisible() {
- showProductSecret.value = !showProductSecret.value;
-}
-
-/** 澶嶅埗鍒板壀璐存澘 */
-async function copyToClipboard(text: string) {
- try {
- await navigator.clipboard.writeText(text);
- message.success('澶嶅埗鎴愬姛');
- } catch {
- message.error('澶嶅埗澶辫触');
- }
-}
-</script>
-
-<template>
- <Card title="浜у搧淇℃伅">
- <Descriptions :column="3" bordered size="small">
- <Descriptions.Item label="浜у搧鍚嶇О">
- {{ product.name }}
- </Descriptions.Item>
- <Descriptions.Item label="鎵�灞炲垎绫�">
- {{ product.categoryName || '-' }}
- </Descriptions.Item>
- <Descriptions.Item label="璁惧绫诲瀷">
- <DictTag
- :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
- :value="product.deviceType"
- />
- </Descriptions.Item>
- <Descriptions.Item label="鍒涘缓鏃堕棿">
- {{ formatDate(product.createTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="鍗忚绫诲瀷">
- <DictTag
- :type="DICT_TYPE.IOT_PROTOCOL_TYPE"
- :value="product.protocolType"
- />
- </Descriptions.Item>
- <Descriptions.Item label="搴忓垪鍖栫被鍨�">
- <DictTag
- :type="DICT_TYPE.IOT_SERIALIZE_TYPE"
- :value="product.serializeType"
- />
- </Descriptions.Item>
- <Descriptions.Item label="浜у搧鐘舵��">
- <DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" />
- </Descriptions.Item>
- <Descriptions.Item
- v-if="
- (
- [DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY] as number[]
- ).includes(product.deviceType!)
- "
- label="鑱旂綉鏂瑰紡"
- >
- <DictTag :type="DICT_TYPE.IOT_NET_TYPE" :value="product.netType" />
- </Descriptions.Item>
- <Descriptions.Item label="浜у搧瀵嗛挜">
- <span v-if="showProductSecret">{{ product.productSecret }}</span>
- <span v-else>********</span>
- <Button class="ml-2" size="small" @click="toggleProductSecretVisible">
- {{ showProductSecret ? '闅愯棌' : '鏄剧ず' }}
- </Button>
- <Button
- v-if="showProductSecret && product.productSecret"
- class="ml-2"
- size="small"
- @click="copyToClipboard(product.productSecret || '')"
- >
- 澶嶅埗
- </Button>
- </Descriptions.Item>
- <Descriptions.Item label="鍔ㄦ�佹敞鍐�">
- {{ product.registerEnabled ? '宸插紑鍚�' : '鏈紑鍚�' }}
- </Descriptions.Item>
- <Descriptions.Item :span="3" label="浜у搧鎻忚堪">
- {{ product.description || '-' }}
- </Descriptions.Item>
- </Descriptions>
- </Card>
-</template>
diff --git a/src/views/iot/product/product/index.vue b/src/views/iot/product/product/index.vue
deleted file mode 100644
index ae7c9a4..0000000
--- a/src/views/iot/product/product/index.vue
+++ /dev/null
@@ -1,301 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductCategoryApi } from '#/api/iot/product/category';
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { nextTick, onMounted, ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { ProductStatusEnum } from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
-
-import { Button, Card, Input, message, Space } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSimpleProductCategoryList } from '#/api/iot/product/category';
-import {
- deleteProduct,
- exportProduct,
- getProductPage,
-} from '#/api/iot/product/product';
-import { $t } from '#/locales';
-
-import { useGridColumns } from './data';
-import ProductCardView from './modules/card-view.vue';
-import Form from './modules/form.vue';
-
-const router = useRouter();
-const categoryList = ref<IotProductCategoryApi.ProductCategory[]>([]);
-const viewMode = ref<'card' | 'list'>('card');
-const cardViewRef = ref();
-const queryParams = ref({
- name: '',
- productKey: '',
-}); // 鎼滅储鍙傛暟
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍔犺浇浜у搧鍒嗙被鍒楄〃 */
-async function loadCategories() {
- categoryList.value = await getSimpleProductCategoryList();
-}
-
-/** 鎼滅储浜у搧 */
-function handleSearch() {
- if (viewMode.value === 'list') {
- gridApi.formApi.setValues(queryParams.value);
- }
- gridApi.query();
-}
-
-/** 閲嶇疆鎼滅储 */
-function handleReset() {
- queryParams.value.name = '';
- queryParams.value.productKey = '';
- handleSearch();
-}
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瑙嗗浘鍒囨崲 */
-async function handleViewModeChange(mode: 'card' | 'list') {
- if (viewMode.value === mode) {
- return; // 濡傛灉宸茬粡鏄洰鏍囪鍥撅紝涓嶉渶瑕佸垏鎹�
- }
- viewMode.value = mode;
- // 绛夊緟瑙嗗浘鏇存柊鍚庡啀瑙﹀彂鏌ヨ
- await nextTick();
- gridApi.query();
-}
-
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
- const data = await exportProduct(queryParams.value);
- downloadFileFromBlobPart({ fileName: '鐗╄仈缃戜骇鍝�.xls', source: data });
-}
-
-/** 鎵撳紑浜у搧璇︽儏 */
-function openProductDetail(productId: number) {
- router.push({
- name: 'IoTProductDetail',
- params: { id: productId },
- });
-}
-
-/** 鎵撳紑鐗╂ā鍨嬬鐞� */
-function openThingModel(productId: number) {
- router.push({
- name: 'IoTProductDetail',
- params: { id: productId },
- query: { tab: 'thingModel' },
- });
-}
-
-/** 鏂板浜у搧 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫浜у搧 */
-function handleEdit(row: IotProductApi.Product) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎浜у搧 */
-async function handleDelete(row: IotProductApi.Product) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteProduct(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- return await getProductPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...queryParams.value,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<IotProductApi.Product>,
-});
-
-/** 鍖呰 gridApi.query() 鏂规硶锛岀粺涓�鍒楄〃瑙嗗浘鍜屽崱鐗囪鍥剧殑鏌ヨ鎺ュ彛 */
-const originalQuery = gridApi.query.bind(gridApi);
-gridApi.query = async (params?: Record<string, any>) => {
- if (viewMode.value === 'list') {
- return await originalQuery(params);
- } else {
- // 鍗$墖瑙嗗浘锛氳皟鐢ㄥ崱鐗囩粍浠剁殑 query 鏂规硶
- cardViewRef.value?.query();
- }
-};
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadCategories();
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
-
- <!-- 缁熶竴鎼滅储宸ュ叿鏍� -->
- <Card :body-style="{ padding: '16px' }" class="!mb-2">
- <!-- 鎼滅储琛ㄥ崟 -->
- <div class="mb-3 flex items-center gap-3">
- <Input
- v-model:value="queryParams.name"
- allow-clear
- class="w-[220px]"
- placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
- @press-enter="handleSearch"
- >
- <template #prefix>
- <span class="text-gray-400">浜у搧鍚嶇О</span>
- </template>
- </Input>
- <Input
- v-model:value="queryParams.productKey"
- allow-clear
- class="w-[220px]"
- placeholder="璇疯緭鍏ヤ骇鍝佹爣璇�"
- @press-enter="handleSearch"
- >
- <template #prefix>
- <span class="text-gray-400">ProductKey</span>
- </template>
- </Input>
- <Button type="primary" @click="handleSearch">
- <IconifyIcon class="mr-1" icon="ant-design:search-outlined" />
- 鎼滅储
- </Button>
- <Button @click="handleReset">
- <IconifyIcon class="mr-1" icon="ant-design:reload-outlined" />
- 閲嶇疆
- </Button>
- </div>
- <!-- 鎿嶄綔鎸夐挳 -->
- <div class="flex items-center justify-between">
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['浜у搧']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:product:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.export'),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['iot:product:export'],
- onClick: handleExport,
- },
- ]"
- />
- <!-- 瑙嗗浘鍒囨崲 -->
- <Space :size="4">
- <Button
- :type="viewMode === 'card' ? 'primary' : 'default'"
- @click="handleViewModeChange('card')"
- >
- <IconifyIcon icon="ant-design:appstore-outlined" />
- </Button>
- <Button
- :type="viewMode === 'list' ? 'primary' : 'default'"
- @click="handleViewModeChange('list')"
- >
- <IconifyIcon icon="ant-design:unordered-list-outlined" />
- </Button>
- </Space>
- </div>
- </Card>
-
- <Grid v-show="viewMode === 'list'" table-title="浜у搧鍒楄〃">
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- auth: ['iot:product:query'],
- onClick: openProductDetail.bind(null, row.id!),
- },
- {
- label: '鐗╂ā鍨�',
- type: 'link',
- auth: ['iot:thing-model:query'],
- onClick: openThingModel.bind(null, row.id!),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:product:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:product:delete'],
- disabled: row.status === ProductStatusEnum.PUBLISHED,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 鍗$墖瑙嗗浘 -->
- <ProductCardView
- v-show="viewMode === 'card'"
- ref="cardViewRef"
- :category-list="categoryList"
- :search-params="queryParams"
- @create="handleCreate"
- @delete="handleDelete"
- @detail="openProductDetail"
- @edit="handleEdit"
- @thing-model="openThingModel"
- />
- </Page>
-</template>
diff --git a/src/views/iot/product/product/modules/card-view.vue b/src/views/iot/product/product/modules/card-view.vue
deleted file mode 100644
index 6a1ea9e..0000000
--- a/src/views/iot/product/product/modules/card-view.vue
+++ /dev/null
@@ -1,286 +0,0 @@
-<script lang="ts" setup>
-import { onMounted, ref } from 'vue';
-
-import { useAccess } from '../../../../../packages/effects/access/src';
-import { DICT_TYPE, ProductStatusEnum } from '../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { isHttpUrl } from '../../../../../packages/utils/src';
-
-import {
- Button,
- Card,
- Col,
- Empty,
- Image,
- Pagination,
- Popconfirm,
- Row,
- Tooltip,
-} from 'ant-design-vue';
-
-import { getProductPage } from '#/api/iot/product/product';
-import defaultPicUrl from '#/assets/imgs/iot/device.png';
-import defaultIconUrl from '#/assets/svgs/iot/cube.svg';
-import { DictTag } from '#/components/dict-tag';
-
-interface Props {
- categoryList: any[];
- searchParams?: {
- name: string;
- productKey: string;
- };
-}
-
-const props = defineProps<Props>();
-
-const emit = defineEmits<{
- create: [];
- delete: [row: any];
- detail: [productId: number];
- edit: [row: any];
- thingModel: [productId: number];
-}>();
-
-const { hasAccessByCodes } = useAccess();
-
-const loading = ref(false);
-const list = ref<any[]>([]);
-const total = ref(0);
-const queryParams = ref({
- pageNo: 1,
- pageSize: 12,
-});
-
-/** 鑾峰彇鍒嗙被鍚嶇О */
-function getCategoryName(item: any) {
- const category = props.categoryList.find(
- (c: any) => c.id === item.categoryId,
- );
- return item.categoryName || category?.name || '鏈垎绫�';
-}
-
-/** 鏄惁鎸夊浘鐗� URL 娓叉煋浜у搧鍥炬爣 */
-function isImageIcon(icon?: string) {
- if (!icon) {
- return true;
- }
- return isHttpUrl(icon);
-}
-
-/** 浜у搧鍥炬爣 fallback */
-function getProductIcon(icon?: string) {
- return icon || defaultIconUrl;
-}
-
-/** 浜у搧鍥剧墖 fallback */
-function getProductPic(picUrl?: string) {
- return picUrl || defaultPicUrl;
-}
-
-/** 鑾峰彇浜у搧鍒楄〃 */
-async function getList() {
- loading.value = true;
- try {
- const data = await getProductPage({
- ...queryParams.value,
- ...props.searchParams,
- });
- list.value = data.list || [];
- total.value = data.total || 0;
- } finally {
- loading.value = false;
- }
-}
-
-/** 澶勭悊椤电爜鍙樺寲 */
-function handlePageChange(page: number, pageSize: number) {
- queryParams.value.pageNo = page;
- queryParams.value.pageSize = pageSize;
- getList();
-}
-
-defineExpose({
- reload: getList,
- query: () => {
- queryParams.value.pageNo = 1;
- getList();
- },
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- getList();
-});
-</script>
-
-<template>
- <div>
- <!-- 浜у搧鍗$墖鍒楄〃 -->
- <div v-loading="loading" class="min-h-96">
- <Row v-if="list.length > 0" :gutter="[16, 16]">
- <Col
- v-for="item in list"
- :key="item.id"
- :xs="24"
- :sm="12"
- :md="12"
- :lg="6"
- >
- <Card
- :body-style="{
- padding: '16px',
- display: 'flex',
- flexDirection: 'column',
- height: '100%',
- }"
- class="h-full overflow-hidden rounded-lg transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
- >
- <!-- 椤堕儴鏍囬鍖哄煙 -->
- <div class="mb-3 flex items-center">
- <div
- class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-[#40a9ff] to-[#1890ff] text-white"
- >
- <img
- v-if="isImageIcon(item.icon)"
- :src="getProductIcon(item.icon)"
- alt=""
- class="size-6 object-contain"
- />
- <IconifyIcon v-else :icon="item.icon" class="text-xl" />
- </div>
- <div class="ml-3 min-w-0 flex-1">
- <div
- class="truncate text-[15px] font-semibold leading-9 dark:text-white/85"
- >
- {{ item.name }}
- </div>
- </div>
- </div>
- <!-- 鍐呭鍖哄煙 -->
- <div class="mb-3 flex items-start">
- <div class="flex-1">
- <div class="mb-2 flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 浜у搧鍒嗙被
- </span>
- <span class="truncate font-medium text-primary">
- {{ getCategoryName(item) }}
- </span>
- </div>
- <div class="mb-2 flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 浜у搧绫诲瀷
- </span>
- <DictTag
- :type="DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE"
- :value="item.deviceType"
- class="m-0 text-xs"
- />
- </div>
- <div class="flex items-center text-[13px]">
- <span class="mr-2 shrink-0 opacity-65 dark:text-white/65">
- 浜у搧鏍囪瘑
- </span>
- <Tooltip :title="item.productKey || item.id" placement="top">
- <span
- class="inline-block max-w-[150px] cursor-pointer truncate align-middle font-mono text-xs opacity-85 dark:text-white/75"
- >
- {{ item.productKey || item.id }}
- </span>
- </Tooltip>
- </div>
- </div>
- <!-- 浜у搧鍥剧墖 -->
- <div
- class="flex size-20 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-[#40a9ff15] to-[#1890ff15] text-[#1890ff] dark:from-[#40a9ff25] dark:to-[#1890ff25] dark:text-[#69c0ff]"
- >
- <Image
- :src="getProductPic(item.picUrl)"
- :preview="true"
- class="size-full rounded object-cover"
- />
- </div>
- </div>
- <!-- 鎸夐挳缁� -->
- <div class="mt-auto flex gap-2 border-t border-border pt-3">
- <Button
- v-if="hasAccessByCodes(['iot:product:update'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
- @click="emit('edit', item)"
- >
- <IconifyIcon icon="lucide:edit" class="mr-1" />
- 缂栬緫
- </Button>
- <Button
- v-if="hasAccessByCodes(['iot:product:query'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
- @click="emit('detail', item.id)"
- >
- <IconifyIcon icon="lucide:eye" class="mr-1" />
- 璇︽儏
- </Button>
- <Button
- v-if="hasAccessByCodes(['iot:thing-model:query'])"
- size="small"
- class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
- @click="emit('thingModel', item.id)"
- >
- <IconifyIcon icon="lucide:git-branch" class="mr-1" />
- 鐗╂ā鍨�
- </Button>
- <template v-if="hasAccessByCodes(['iot:product:delete'])">
- <div
- class="h-5 w-px self-center bg-[#dcdfe6] dark:bg-[#3a3a3a]"
- ></div>
- <Tooltip
- v-if="item.status === ProductStatusEnum.PUBLISHED"
- title="宸插彂甯冪殑浜у搧涓嶈兘鍒犻櫎"
- >
- <Button
- size="small"
- danger
- disabled
- class="!h-8 rounded-md p-0 text-[13px] transition-all duration-200 !w-8"
- >
- <IconifyIcon icon="lucide:trash-2" class="text-sm" />
- </Button>
- </Tooltip>
- <Popconfirm
- v-else
- :title="`纭鍒犻櫎浜у搧 ${item.name} 鍚�?`"
- @confirm="emit('delete', item)"
- >
- <Button
- size="small"
- danger
- class="!h-8 rounded-md p-0 text-[13px] transition-all duration-200 !w-8"
- >
- <IconifyIcon icon="lucide:trash-2" class="text-sm" />
- </Button>
- </Popconfirm>
- </template>
- </div>
- </Card>
- </Col>
- </Row>
- <!-- 绌虹姸鎬� -->
- <Empty v-else description="鏆傛棤浜у搧鏁版嵁" class="my-20" />
- </div>
-
- <!-- 鍒嗛〉 -->
- <div v-if="list.length > 0" class="mt-3 flex justify-end">
- <Pagination
- v-model:current="queryParams.pageNo"
- v-model:page-size="queryParams.pageSize"
- :total="total"
- :show-total="(total) => `鍏� ${total} 鏉"
- show-quick-jumper
- show-size-changer
- :page-size-options="['12', '24', '36', '48']"
- @change="handlePageChange"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/iot/product/product/modules/form.vue b/src/views/iot/product/product/modules/form.vue
deleted file mode 100644
index 6bcc8d9..0000000
--- a/src/views/iot/product/product/modules/form.vue
+++ /dev/null
@@ -1,157 +0,0 @@
-<script lang="ts" setup>
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { computed, nextTick, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Collapse, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createProduct,
- getProduct,
- updateProduct,
-} from '#/api/iot/product/product';
-import { $t } from '#/locales';
-
-import { useAdvancedFormSchema, useBasicFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-/** 鐢熸垚 ProductKey锛堝寘鍚ぇ灏忓啓瀛楁瘝鍜屾暟瀛楋級 */
-function generateProductKey(): string {
- const chars =
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- let result = '';
- for (let i = 0; i < 16; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length));
- }
- return result;
-}
-
-const formData = ref<IotProductApi.Product>();
-const activeKey = ref<string[]>([]);
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['浜у搧'])
- : $t('ui.actionTitle.create', ['浜у搧']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: { class: 'w-full' },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: [],
- showDefaultActions: false,
-});
-
-const [AdvancedForm, advancedFormApi] = useVbenForm({
- commonConfig: {
- componentProps: { class: 'w-full' },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useAdvancedFormSchema(),
- showDefaultActions: false,
-});
-
-/** 鑾峰彇楂樼骇琛ㄥ崟鐨勫�硷紙濡傛灉琛ㄥ崟鏈寕杞斤紝鍒欎粠 formData 涓幏鍙栵級 */
-async function getAdvancedFormValues() {
- if (advancedFormApi.isMounted) {
- return await advancedFormApi.getValues();
- }
- // 琛ㄥ崟鏈寕杞斤紙鎶樺彔鐘舵�侊級锛屼粠 formData 涓幏鍙�
- return {
- registerEnabled: formData.value?.registerEnabled,
- icon: formData.value?.icon,
- picUrl: formData.value?.picUrl,
- description: formData.value?.description,
- };
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鍚堝苟涓や釜琛ㄥ崟鐨勫�硷紙瀛楁涓嶅啿绐侊紝鍙洿鎺ュ悎骞讹級
- const basicValues = await formApi.getValues();
- const advancedValues = await getAdvancedFormValues();
- const data = {
- ...basicValues,
- ...advancedValues,
- } as IotProductApi.Product;
- try {
- await (formData.value?.id ? updateProduct(data) : createProduct(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- activeKey.value = [];
- return;
- }
- formApi.setState({
- schema: useBasicFormSchema(formApi, generateProductKey),
- });
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<IotProductApi.Product>();
- if (!data || !data.id) {
- // 鏂板锛氱‘淇� Collapse 鎶樺彔锛屽苟璁剧疆榛樿鍊�
- activeKey.value = [];
- await formApi.setValues({
- productKey: generateProductKey(),
- });
- return;
- }
- // 缂栬緫锛氬姞杞芥暟鎹�
- modalApi.lock();
- try {
- formData.value = await getProduct(data.id);
- await formApi.setValues(formData.value);
- // 濡傛灉瀛樺湪楂樼骇瀛楁鏁版嵁锛岃嚜鍔ㄥ睍寮� Collapse
- if (
- formData.value?.registerEnabled ||
- formData.value?.icon ||
- formData.value?.picUrl ||
- formData.value?.description
- ) {
- activeKey.value = ['advanced'];
- // 绛夊緟 Collapse 灞曞紑鍚庤〃鍗曟寕杞�
- await nextTick();
- await nextTick();
- if (advancedFormApi.isMounted) {
- await advancedFormApi.setValues(formData.value);
- }
- }
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <div class="mx-4">
- <Form />
- <Collapse v-model:active-key="activeKey" class="mt-4">
- <Collapse.Panel key="advanced" header="鏇村璁剧疆">
- <AdvancedForm />
- </Collapse.Panel>
- </Collapse>
- </div>
- </Modal>
-</template>
diff --git a/src/views/iot/rule/data/index.vue b/src/views/iot/rule/data/index.vue
deleted file mode 100644
index 1aa4473..0000000
--- a/src/views/iot/rule/data/index.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-
-import { Tabs } from 'ant-design-vue';
-
-import DataRuleList from './rule/index.vue';
-import DataSinkList from './sink/index.vue';
-
-const activeTabName = ref('rule');
-</script>
-
-<template>
- <Page auto-content-height>
- <Tabs v-model:active-key="activeTabName">
- <Tabs.TabPane key="rule" tab="瑙勫垯">
- <DataRuleList />
- </Tabs.TabPane>
- <Tabs.TabPane key="sink" tab="鐩殑">
- <DataSinkList />
- </Tabs.TabPane>
- </Tabs>
- </Page>
-</template>
diff --git a/src/views/iot/rule/data/rule/data.ts b/src/views/iot/rule/data/rule/data.ts
deleted file mode 100644
index 042153f..0000000
--- a/src/views/iot/rule/data/rule/data.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DataRuleApi } from '#/api/iot/rule/data/rule';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { getDataSinkSimpleList } from '#/api/iot/rule/data/sink';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '瑙勫垯鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍒欏悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '瑙勫垯鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 瑙勫垯琛ㄥ崟 Schema */
-export function useRuleFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '瑙勫垯鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍒欏悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '瑙勫垯鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍒欐弿杩�',
- rows: 3,
- },
- },
- {
- fieldName: 'status',
- label: '瑙勫垯鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- defaultValue: 0,
- rules: 'required',
- },
- {
- fieldName: 'sinkIds',
- label: '鏁版嵁鐩殑',
- component: 'ApiSelect',
- componentProps: {
- api: getDataSinkSimpleList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- allowClear: true,
- placeholder: '璇烽�夋嫨鏁版嵁鐩殑',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鏁版嵁婧愰厤缃紙琛岀紪杈戣〃锛夌殑瀛楁 */
-export function useSourceConfigColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'productId',
- title: '浜у搧',
- minWidth: 200,
- slots: { default: 'productId' },
- },
- {
- field: 'deviceId',
- title: '璁惧',
- minWidth: 200,
- slots: { default: 'deviceId' },
- },
- {
- field: 'method',
- title: '娑堟伅',
- minWidth: 200,
- slots: { default: 'method' },
- },
- {
- field: 'identifier',
- title: '鏍囪瘑绗�',
- minWidth: 250,
- slots: { default: 'identifier' },
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<DataRuleApi.DataRule>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'id',
- title: '瑙勫垯缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '瑙勫垯鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'description',
- title: '瑙勫垯鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'status',
- title: '瑙勫垯鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'sourceConfigs',
- title: '鏁版嵁婧�',
- minWidth: 100,
- formatter: ({ cellValue }) => `${cellValue?.length || 0} 涓猔,
- },
- {
- field: 'sinkIds',
- title: '鏁版嵁鐩殑',
- minWidth: 100,
- formatter: ({ cellValue }) => `${cellValue?.length || 0} 涓猔,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/rule/data/rule/index.vue b/src/views/iot/rule/data/rule/index.vue
deleted file mode 100644
index 6801918..0000000
--- a/src/views/iot/rule/data/rule/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DataRuleApi } from '#/api/iot/rule/data/rule';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteDataRule, getDataRulePage } from '#/api/iot/rule/data/rule';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓瑙勫垯 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫瑙勫垯 */
-function handleEdit(row: DataRuleApi.DataRule) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍒犻櫎瑙勫垯 */
-async function handleDelete(row: DataRuleApi.DataRule) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDataRule(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDataRulePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<DataRuleApi.DataRule>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鏁版嵁瑙勫垯鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['瑙勫垯']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:data-rule:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:data-rule:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:data-rule:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/rule/data/rule/modules/form.vue b/src/views/iot/rule/data/rule/modules/form.vue
deleted file mode 100644
index 445718d..0000000
--- a/src/views/iot/rule/data/rule/modules/form.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<script lang="ts" setup>
-import type { DataRuleApi } from '#/api/iot/rule/data/rule';
-
-import { computed, nextTick, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createDataRule,
- getDataRule,
- updateDataRule,
-} from '#/api/iot/rule/data/rule';
-import { $t } from '#/locales';
-
-import { useRuleFormSchema } from '../data';
-import SourceConfigForm from './source-config-form.vue';
-
-const emit = defineEmits(['success']);
-const formData = ref<DataRuleApi.DataRule>();
-const sourceConfigRef = ref<InstanceType<typeof SourceConfigForm>>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鏁版嵁瑙勫垯'])
- : $t('ui.actionTitle.create', ['鏁版嵁瑙勫垯']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useRuleFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- // 鏍¢獙鏁版嵁婧愰厤缃�
- try {
- await sourceConfigRef.value?.validate();
- } catch {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as DataRuleApi.DataRule;
- data.sourceConfigs = sourceConfigRef.value?.getData() || [];
- try {
- await (formData.value?.id ? updateDataRule(data) : createDataRule(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- sourceConfigRef.value?.setData([]);
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<DataRuleApi.DataRule>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDataRule(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- // 璁剧疆鏁版嵁婧愰厤缃�
- await nextTick();
- sourceConfigRef.value?.setData(formData.value.sourceConfigs || []);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-4/5" :title="getTitle">
- <Form class="mx-4" />
- <div class="mx-4 mt-4">
- <div class="mb-2 text-sm font-medium">鏁版嵁婧愰厤缃�</div>
- <SourceConfigForm ref="sourceConfigRef" />
- </div>
- </Modal>
-</template>
diff --git a/src/views/iot/rule/data/rule/modules/source-config-form.vue b/src/views/iot/rule/data/rule/modules/source-config-form.vue
deleted file mode 100644
index 94b8a72..0000000
--- a/src/views/iot/rule/data/rule/modules/source-config-form.vue
+++ /dev/null
@@ -1,301 +0,0 @@
-<script lang="ts" setup>
-import { computed, nextTick, onMounted, ref } from 'vue';
-
-import {
- IotDeviceMessageMethodEnum,
- IoTThingModelTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { Button, message, Select } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSimpleDeviceList } from '#/api/iot/device/device';
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { getThingModelListByProductId } from '#/api/iot/thingmodel';
-
-import { useSourceConfigColumns } from '../data';
-
-const formData = ref<any[]>([]);
-const productList = ref<any[]>([]); // 浜у搧鍒楄〃
-const deviceList = ref<any[]>([]); // 璁惧鍒楄〃
-const thingModelCache = ref<Map<number, any[]>>(new Map()); // 缂撳瓨鐗╂ā鍨嬫暟鎹紝key 涓� productId
-
-/** 涓婅娑堟伅鏂规硶鍒楄〃 */
-const upstreamMethods = computed(() => {
- return Object.values(IotDeviceMessageMethodEnum).filter(
- (item) => item.upstream,
- );
-});
-
-/** 鏍规嵁浜у搧 ID 杩囨护璁惧 */
-function getFilteredDevices(productId: number) {
- if (!productId) return [];
- return deviceList.value.filter(
- (device: any) => device.productId === productId,
- );
-}
-
-/** 鍒ゆ柇鏄惁闇�瑕佹樉绀烘爣璇嗙閫夋嫨鍣� */
-function shouldShowIdentifierSelect(row: any) {
- return [
- IotDeviceMessageMethodEnum.EVENT_POST.method,
- IotDeviceMessageMethodEnum.PROPERTY_POST.method,
- ].includes(row.method);
-}
-
-/** 鑾峰彇鐗╂ā鍨嬮�夐」 */
-function getThingModelOptions(row: any) {
- if (!row.productId || !shouldShowIdentifierSelect(row)) {
- return [];
- }
- const thingModels: any[] = thingModelCache.value.get(row.productId) || [];
- let filteredModels: any[] = [];
- if (row.method === IotDeviceMessageMethodEnum.EVENT_POST.method) {
- filteredModels = thingModels.filter(
- (item: any) => item.type === IoTThingModelTypeEnum.EVENT,
- );
- } else if (row.method === IotDeviceMessageMethodEnum.PROPERTY_POST.method) {
- filteredModels = thingModels.filter(
- (item: any) => item.type === IoTThingModelTypeEnum.PROPERTY,
- );
- }
- return filteredModels.map((item: any) => ({
- label: `${item.name} (${item.identifier})`,
- value: item.identifier,
- }));
-}
-
-/** 鍔犺浇浜у搧鍒楄〃 */
-async function loadProductList() {
- try {
- productList.value = await getSimpleProductList();
- } catch (error) {
- console.error('鍔犺浇浜у搧鍒楄〃澶辫触:', error);
- }
-}
-
-/** 鍔犺浇璁惧鍒楄〃 */
-async function loadDeviceList() {
- try {
- deviceList.value = await getSimpleDeviceList();
- } catch (error) {
- console.error('鍔犺浇璁惧鍒楄〃澶辫触:', error);
- }
-}
-
-/** 鍔犺浇鐗╂ā鍨嬫暟鎹� */
-async function loadThingModel(productId: number) {
- if (thingModelCache.value.has(productId)) {
- return;
- }
- try {
- const thingModels = await getThingModelListByProductId(productId);
- thingModelCache.value.set(productId, thingModels);
- } catch (error) {
- console.error('鍔犺浇鐗╂ā鍨嬪け璐�:', error);
- }
-}
-
-/** 浜у搧鍙樺寲鏃舵竻绌鸿澶� / 娑堟伅 / 鏍囪瘑绗� */
-function handleProductChange(rowIndex: number) {
- const row = formData.value[rowIndex];
- row.deviceId = 0;
- row.method = undefined;
- row.identifier = undefined;
- row.identifierLoading = false;
-}
-
-/** 娑堟伅鏂规硶鍙樺寲鏃舵竻绌烘爣璇嗙 + 鎸夐渶鍔犺浇鐗╂ā鍨� */
-async function handleMethodChange(rowIndex: number) {
- const row = formData.value[rowIndex];
- row.identifier = undefined;
- if (shouldShowIdentifierSelect(row) && row.productId) {
- row.identifierLoading = true;
- await loadThingModel(row.productId);
- row.identifierLoading = false;
- }
-}
-
-/** 琛ㄦ牸閰嶇疆 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useSourceConfigColumns(),
- data: formData.value,
- minHeight: 160,
- border: true,
- showOverflow: false,
- rowConfig: { isHover: true, height: 64 },
- pagerConfig: { enabled: false },
- toolbarConfig: { enabled: false },
- },
-});
-
-/** 鍚屾 formData 鍒� vxe-grid */
-async function reloadGrid() {
- await nextTick();
- await gridApi.grid?.reloadData(formData.value);
-}
-
-/** 鏂板涓�琛屾暟鎹簮 */
-async function handleAdd() {
- formData.value.push({
- productId: undefined,
- deviceId: undefined,
- method: undefined,
- identifier: undefined,
- identifierLoading: false,
- });
- await reloadGrid();
-}
-
-/** 鍒犻櫎涓�琛屾暟鎹簮 */
-async function handleDelete(rowIndex: number) {
- formData.value.splice(rowIndex, 1);
- await reloadGrid();
-}
-
-/** 鏍¢獙鍏ㄩ儴琛岋紱杩斿洖 Promise锛屽け璐ユ椂 reject 绗竴鏉¢敊璇俊鎭� */
-function validate() {
- if (formData.value.length === 0) {
- message.error('璇疯嚦灏戞坊鍔犱竴鏉℃暟鎹簮閰嶇疆');
- return Promise.reject(new Error('鏁版嵁婧愰厤缃笉鑳戒负绌�'));
- }
- for (let i = 0; i < formData.value.length; i++) {
- const row = formData.value[i];
- if (!row.productId) {
- message.error(`绗� ${i + 1} 琛岋細浜у搧涓嶈兘涓虹┖`);
- return Promise.reject(new Error('浜у搧涓嶈兘涓虹┖'));
- }
- if (row.deviceId === undefined || row.deviceId === null) {
- message.error(`绗� ${i + 1} 琛岋細璁惧涓嶈兘涓虹┖`);
- return Promise.reject(new Error('璁惧涓嶈兘涓虹┖'));
- }
- if (!row.method) {
- message.error(`绗� ${i + 1} 琛岋細娑堟伅鏂规硶涓嶈兘涓虹┖`);
- return Promise.reject(new Error('娑堟伅鏂规硶涓嶈兘涓虹┖'));
- }
- }
- return Promise.resolve();
-}
-
-/** 鍙栧綋鍓嶆墍鏈夎鐨勫�硷紙鍓旈櫎 identifierLoading 绛変粎渚� UI 浣跨敤鐨勪复鏃跺瓧娈碉級 */
-function getData() {
- return formData.value.map(
- ({ identifierLoading: _identifierLoading, ...rest }) => rest,
- );
-}
-
-/** 璁剧疆鍒濆鏁版嵁 */
-async function setData(data: any[]) {
- formData.value = (data || []).map((item) => ({
- ...item,
- identifierLoading: false,
- }));
- // 涓哄凡鏈夋暟鎹鍔犺浇鐗╂ā鍨嬶紱骞惰鍔犺浇锛屼笉闃诲 reloadGrid
- await Promise.all(
- (data || [])
- .filter((item) => item.productId && shouldShowIdentifierSelect(item))
- .map((item) => loadThingModel(item.productId)),
- );
- await reloadGrid();
-}
-
-onMounted(async () => {
- await Promise.all([loadProductList(), loadDeviceList()]);
-});
-
-defineExpose({ validate, getData, setData });
-</script>
-
-<template>
- <div>
- <Grid>
- <template #productId="{ rowIndex }">
- <Select
- v-model:value="formData[rowIndex].productId"
- placeholder="璇烽�夋嫨浜у搧"
- show-search
- allow-clear
- :filter-option="
- (input: string, option: any) =>
- option.label.toLowerCase().includes(input.toLowerCase())
- "
- :options="
- productList.map((p: any) => ({ label: p.name, value: p.id }))
- "
- class="w-full"
- @change="() => handleProductChange(rowIndex)"
- />
- </template>
- <template #deviceId="{ rowIndex }">
- <Select
- v-model:value="formData[rowIndex].deviceId"
- placeholder="璇烽�夋嫨璁惧"
- show-search
- allow-clear
- :filter-option="
- (input: string, option: any) =>
- option.label.toLowerCase().includes(input.toLowerCase())
- "
- :options="[
- { label: '鍏ㄩ儴璁惧', value: 0 },
- ...getFilteredDevices(formData[rowIndex].productId).map(
- (d: any) => ({
- label: d.deviceName,
- value: d.id,
- }),
- ),
- ]"
- class="w-full"
- />
- </template>
- <template #method="{ rowIndex }">
- <Select
- v-model:value="formData[rowIndex].method"
- placeholder="璇烽�夋嫨娑堟伅"
- show-search
- allow-clear
- :filter-option="
- (input: string, option: any) =>
- option.label.toLowerCase().includes(input.toLowerCase())
- "
- :options="
- upstreamMethods.map((m: any) => ({
- label: m.name,
- value: m.method,
- }))
- "
- class="w-full"
- @change="() => handleMethodChange(rowIndex)"
- />
- </template>
- <template #identifier="{ rowIndex }">
- <Select
- v-if="shouldShowIdentifierSelect(formData[rowIndex])"
- v-model:value="formData[rowIndex].identifier"
- placeholder="璇烽�夋嫨鏍囪瘑绗�"
- show-search
- allow-clear
- :loading="formData[rowIndex].identifierLoading"
- :filter-option="
- (input: string, option: any) =>
- option.label.toLowerCase().includes(input.toLowerCase())
- "
- :options="getThingModelOptions(formData[rowIndex])"
- class="w-full"
- />
- <span v-else class="text-xs text-muted-foreground">-</span>
- </template>
- <template #actions="{ rowIndex }">
- <Button danger type="link" @click="handleDelete(rowIndex)">鍒犻櫎</Button>
- </template>
- </Grid>
- <div class="mt-3 text-center">
- <Button type="primary" @click="handleAdd">
- <IconifyIcon icon="ant-design:plus-outlined" class="mr-1" />
- 娣诲姞鏁版嵁婧�
- </Button>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/components/key-value-editor.vue b/src/views/iot/rule/data/sink/config/components/key-value-editor.vue
deleted file mode 100644
index 890da4d..0000000
--- a/src/views/iot/rule/data/sink/config/components/key-value-editor.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script lang="ts" setup>
-import { ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../packages/icons/src';
-import { isEmpty } from '../../../../../../../packages/utils/src';
-
-import { Button, Input } from 'ant-design-vue';
-
-defineOptions({ name: 'KeyValueEditor' });
-
-const props = defineProps<{
- addButtonText: string;
- modelValue: Record<string, string>;
-}>();
-
-const emit = defineEmits(['update:modelValue']);
-
-interface KeyValueItem {
- _uid: number;
- key: string;
- value: string;
-}
-
-let uidCounter = 0;
-const items = ref<KeyValueItem[]>([]); // 鍐呴儴 key-value 椤瑰垪琛�
-
-/** 娣诲姞椤圭洰 */
-function addItem() {
- uidCounter += 1;
- items.value.push({ _uid: uidCounter, key: '', value: '' });
- updateModelValue();
-}
-
-/** 绉婚櫎椤圭洰 */
-function removeItem(index: number) {
- items.value.splice(index, 1);
- updateModelValue();
-}
-
-/** 鏇存柊 modelValue */
-function updateModelValue() {
- const result: Record<string, string> = {};
- items.value.forEach((item) => {
- if (item.key) {
- result[item.key] = item.value;
- }
- });
- emit('update:modelValue', result);
-}
-
-/** 鐩戝惉椤圭洰鍙樺寲 */
-watch(items, updateModelValue, { deep: true });
-watch(
- () => props.modelValue,
- (val) => {
- // 鍒楄〃鏈夊�煎悗浠ュ垪琛ㄤ腑鐨勫�间负鍑�
- if (isEmpty(val) || !isEmpty(items.value)) {
- return;
- }
- items.value = Object.entries(props.modelValue).map(([key, value]) => {
- uidCounter += 1;
- return { _uid: uidCounter, key, value };
- });
- },
-);
-</script>
-
-<template>
- <div v-for="(item, index) in items" :key="item._uid" class="mb-2 flex w-full">
- <Input v-model:value="item.key" class="mr-2" placeholder="閿�" />
- <Input v-model:value="item.value" placeholder="鍊�" />
- <Button class="ml-2" type="link" danger @click="removeItem(index)">
- <IconifyIcon icon="ant-design:delete-outlined" />
- 鍒犻櫎
- </Button>
- </div>
- <Button type="link" @click="addItem">
- <IconifyIcon icon="ant-design:plus-outlined" />
- {{ addButtonText }}
- </Button>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/database-config-form.vue b/src/views/iot/rule/data/sink/config/database-config-form.vue
deleted file mode 100644
index ba2e12c..0000000
--- a/src/views/iot/rule/data/sink/config/database-config-form.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<script lang="ts" setup>
-import { onBeforeUnmount, onMounted, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useClipboard, useVModel } from '@vueuse/core';
-import { Button, Form, Input, message } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const TABLE_SQL = `CREATE TABLE iot_device_message_sink (
- id VARCHAR(64) NOT NULL COMMENT '娑堟伅ID',
- device_id BIGINT NOT NULL COMMENT '璁惧缂栧彿',
- tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '绉熸埛缂栧彿',
- method VARCHAR(128) COMMENT '璇锋眰鏂规硶',
- report_time DATETIME COMMENT '涓婃姤鏃堕棿',
- data TEXT COMMENT '瀹屾暣娑堟伅JSON',
- create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
- PRIMARY KEY (id) USING BTREE,
- INDEX idx_create_time (create_time ASC) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'IoT 璁惧娑堟伅娴佽浆鐩爣琛�';`;
-
-const config = useVModel(props, 'modelValue', emit);
-
-const showSqlTip = ref(false);
-const copied = ref(false);
-const { copy } = useClipboard();
-let copyResetTimer: null | ReturnType<typeof setTimeout> = null;
-
-async function handleCopySql() {
- await copy(TABLE_SQL);
- copied.value = true;
- message.success('寤鸿〃 SQL 宸插鍒跺埌鍓创鏉�');
- if (copyResetTimer) {
- clearTimeout(copyResetTimer);
- }
- copyResetTimer = setTimeout(() => {
- copied.value = false;
- copyResetTimer = null;
- }, 2000);
-}
-
-onBeforeUnmount(() => {
- if (copyResetTimer) {
- clearTimeout(copyResetTimer);
- copyResetTimer = null;
- }
-});
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.DATABASE}`,
- jdbcUrl: '',
- username: '',
- password: '',
- tableName: 'iot_device_message_sink',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'jdbcUrl']"
- :rules="[
- { required: true, message: 'JDBC 杩炴帴鍦板潃涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="JDBC 鍦板潃"
- >
- <Input
- v-model:value="config.jdbcUrl"
- placeholder="璇疯緭鍏� JDBC 杩炴帴鍦板潃锛屽锛歫dbc:mysql://localhost:3306/iot_data"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'username']"
- :rules="[{ required: true, message: '鐢ㄦ埛鍚嶄笉鑳戒负绌�', trigger: 'blur' }]"
- label="鐢ㄦ埛鍚�"
- >
- <Input v-model:value="config.username" placeholder="璇疯緭鍏ユ暟鎹簱鐢ㄦ埛鍚�" />
- </Form.Item>
- <Form.Item
- :name="['config', 'password']"
- :rules="[{ required: true, message: '瀵嗙爜涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="瀵嗙爜"
- >
- <Input.Password
- v-model:value="config.password"
- placeholder="璇疯緭鍏ユ暟鎹簱瀵嗙爜"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'tableName']"
- :rules="[{ required: true, message: '鐩爣琛ㄥ悕涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="鐩爣琛ㄥ悕"
- >
- <div class="flex items-center gap-3">
- <Input
- v-model:value="config.tableName"
- placeholder="鐩爣琛ㄥ悕"
- class="w-[240px]"
- />
- <Button type="link" @click="showSqlTip = !showSqlTip">
- <IconifyIcon
- :icon="showSqlTip ? 'lucide:chevron-up' : 'lucide:file-text'"
- class="mr-1"
- />
- {{ showSqlTip ? '鏀惰捣琛ㄧ粨鏋勬彁绀�' : '鏌ョ湅琛ㄧ粨鏋勬彁绀�' }}
- </Button>
- </div>
- </Form.Item>
- <div
- v-if="showSqlTip"
- class="mt-2 overflow-hidden rounded border border-gray-200 dark:border-gray-700"
- >
- <div
- class="flex items-center justify-between bg-gray-100 px-3 py-2 dark:bg-gray-800"
- >
- <span class="text-xs text-gray-600 dark:text-gray-300">
- 鐩爣鏁版嵁搴撻渶鍖呭惈浠ヤ笅缁撴瀯鐨勮〃锛屾墠鑳芥甯告帴鏀舵暟鎹祦杞殑娑堟伅
- </span>
- <Button size="small" @click="handleCopySql">
- <IconifyIcon
- :icon="copied ? 'lucide:check' : 'lucide:copy'"
- class="mr-1"
- />
- {{ copied ? '宸插鍒�' : '澶嶅埗 SQL' }}
- </Button>
- </div>
- <pre
- class="m-0 overflow-x-auto bg-gray-50 p-3 font-mono text-[12px] leading-normal text-gray-800 dark:bg-gray-900 dark:text-gray-200"
- ><code>{{ TABLE_SQL }}</code></pre>
- </div>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/http-config-form.vue b/src/views/iot/rule/data/sink/config/http-config-form.vue
deleted file mode 100644
index 4939750..0000000
--- a/src/views/iot/rule/data/sink/config/http-config-form.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<!--suppress HttpUrlsUsage -->
-<script lang="ts" setup>
-import { computed, onMounted, ref, watch } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, Select } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-import KeyValueEditor from './components/key-value-editor.vue';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-const urlPrefix = ref<'http://' | 'https://'>('http://');
-const urlPath = ref('');
-const fullUrl = computed(() =>
- urlPath.value ? urlPrefix.value + urlPath.value : '',
-);
-
-function syncUrlFields(url?: string) {
- if (url?.startsWith('https://')) {
- urlPrefix.value = 'https://';
- urlPath.value = url.slice(8);
- } else if (url?.startsWith('http://')) {
- urlPrefix.value = 'http://';
- urlPath.value = url.slice(7);
- } else {
- urlPath.value = url ?? '';
- }
-}
-
-watch([urlPrefix, urlPath], () => {
- config.value.url = fullUrl.value;
-});
-
-watch(
- () => config.value?.url,
- (url) => syncUrlFields(url),
- { immediate: true },
-);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- syncUrlFields(config.value.url);
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.HTTP}`,
- url: '',
- method: 'POST',
- headers: {},
- query: {},
- body: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'url']"
- :rules="[{ required: true, message: '璇锋眰鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="璇锋眰鍦板潃"
- >
- <Input v-model:value="urlPath" placeholder="璇疯緭鍏ヨ姹傚湴鍧�">
- <template #addonBefore>
- <Select v-model:value="urlPrefix" class="w-[100px]">
- <Select.Option value="http://">http://</Select.Option>
- <Select.Option value="https://">https://</Select.Option>
- </Select>
- </template>
- </Input>
- </Form.Item>
- <Form.Item
- :name="['config', 'method']"
- :rules="[
- { required: true, message: '璇锋眰鏂规硶涓嶈兘涓虹┖', trigger: 'change' },
- ]"
- label="璇锋眰鏂规硶"
- >
- <Select v-model:value="config.method" placeholder="璇烽�夋嫨璇锋眰鏂规硶">
- <Select.Option value="GET">GET</Select.Option>
- <Select.Option value="POST">POST</Select.Option>
- <Select.Option value="PUT">PUT</Select.Option>
- <Select.Option value="DELETE">DELETE</Select.Option>
- </Select>
- </Form.Item>
- <Form.Item label="璇锋眰澶�">
- <KeyValueEditor v-model="config.headers" add-button-text="娣诲姞璇锋眰澶�" />
- </Form.Item>
- <Form.Item label="璇锋眰鍙傛暟">
- <KeyValueEditor v-model="config.query" add-button-text="娣诲姞鍙傛暟" />
- </Form.Item>
- <Form.Item label="璇锋眰浣�">
- <Input.TextArea
- v-model:value="config.body"
- placeholder="璇疯緭鍏ュ唴瀹�"
- :rows="4"
- />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/index.ts b/src/views/iot/rule/data/sink/config/index.ts
deleted file mode 100644
index d2967ea..0000000
--- a/src/views/iot/rule/data/sink/config/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export { default as DatabaseConfigForm } from './database-config-form.vue';
-export { default as HttpConfigForm } from './http-config-form.vue';
-export { default as KafkaMqConfigForm } from './kafka-mq-config-form.vue';
-export { default as MqttConfigForm } from './mqtt-config-form.vue';
-export { default as RabbitMqConfigForm } from './rabbit-mq-config-form.vue';
-export { default as RedisStreamConfigForm } from './redis-stream-config-form.vue';
-export { default as RocketMqConfigForm } from './rocket-mq-config-form.vue';
-export { default as TcpConfigForm } from './tcp-config-form.vue';
-export { default as WebSocketConfigForm } from './websocket-config-form.vue';
diff --git a/src/views/iot/rule/data/sink/config/kafka-mq-config-form.vue b/src/views/iot/rule/data/sink/config/kafka-mq-config-form.vue
deleted file mode 100644
index 6abc97b..0000000
--- a/src/views/iot/rule/data/sink/config/kafka-mq-config-form.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, Switch } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.KAFKA}`,
- bootstrapServers: '',
- username: '',
- password: '',
- ssl: false,
- topic: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'bootstrapServers']"
- :rules="[{ required: true, message: '鏈嶅姟鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="鏈嶅姟鍦板潃"
- >
- <Input
- v-model:value="config.bootstrapServers"
- placeholder="璇疯緭鍏ユ湇鍔″湴鍧�锛屽锛歭ocalhost:9092"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'username']"
- :rules="[{ required: true, message: '鐢ㄦ埛鍚嶄笉鑳戒负绌�', trigger: 'blur' }]"
- label="鐢ㄦ埛鍚�"
- >
- <Input v-model:value="config.username" placeholder="璇疯緭鍏ョ敤鎴峰悕" />
- </Form.Item>
- <Form.Item
- :name="['config', 'password']"
- :rules="[{ required: true, message: '瀵嗙爜涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="瀵嗙爜"
- >
- <Input.Password v-model:value="config.password" placeholder="璇疯緭鍏ュ瘑鐮�" />
- </Form.Item>
- <Form.Item :name="['config', 'ssl']" label="鍚敤 SSL">
- <Switch v-model:checked="config.ssl" />
- </Form.Item>
- <Form.Item
- :name="['config', 'topic']"
- :rules="[{ required: true, message: '涓婚涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓婚"
- >
- <Input v-model:value="config.topic" placeholder="璇疯緭鍏ヤ富棰�" />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/mqtt-config-form.vue b/src/views/iot/rule/data/sink/config/mqtt-config-form.vue
deleted file mode 100644
index 2b5b421..0000000
--- a/src/views/iot/rule/data/sink/config/mqtt-config-form.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.MQTT}`,
- url: '',
- username: '',
- password: '',
- clientId: '',
- topic: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'url']"
- :rules="[{ required: true, message: '鏈嶅姟鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="鏈嶅姟鍦板潃"
- >
- <Input
- v-model:value="config.url"
- placeholder="璇疯緭鍏� MQTT 鏈嶅姟鍦板潃锛屽锛歮qtt://localhost:1883"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'username']"
- :rules="[{ required: true, message: '鐢ㄦ埛鍚嶄笉鑳戒负绌�', trigger: 'blur' }]"
- label="鐢ㄦ埛鍚�"
- >
- <Input v-model:value="config.username" placeholder="璇疯緭鍏ョ敤鎴峰悕" />
- </Form.Item>
- <Form.Item
- :name="['config', 'password']"
- :rules="[{ required: true, message: '瀵嗙爜涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="瀵嗙爜"
- >
- <Input.Password v-model:value="config.password" placeholder="璇疯緭鍏ュ瘑鐮�" />
- </Form.Item>
- <Form.Item
- :name="['config', 'clientId']"
- :rules="[
- { required: true, message: '瀹㈡埛绔� ID 涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="瀹㈡埛绔� ID"
- >
- <Input v-model:value="config.clientId" placeholder="璇疯緭鍏ュ鎴风 ID" />
- </Form.Item>
- <Form.Item
- :name="['config', 'topic']"
- :rules="[{ required: true, message: '涓婚涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓婚"
- >
- <Input v-model:value="config.topic" placeholder="璇疯緭鍏ヤ富棰�" />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/rabbit-mq-config-form.vue b/src/views/iot/rule/data/sink/config/rabbit-mq-config-form.vue
deleted file mode 100644
index 7a7ee50..0000000
--- a/src/views/iot/rule/data/sink/config/rabbit-mq-config-form.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, InputNumber } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.RABBITMQ}`,
- host: '',
- port: 5672,
- virtualHost: '/',
- username: '',
- password: '',
- exchange: '',
- routingKey: '',
- queue: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'host']"
- :rules="[{ required: true, message: '涓绘満鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓绘満鍦板潃"
- >
- <Input
- v-model:value="config.host"
- placeholder="璇疯緭鍏ヤ富鏈哄湴鍧�锛屽锛歭ocalhost"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'port']"
- :rules="[
- { required: true, message: '绔彛涓嶈兘涓虹┖', trigger: 'blur' },
- {
- type: 'number',
- min: 1,
- max: 65_535,
- message: '绔彛鍙疯寖鍥� 1-65535',
- trigger: 'blur',
- },
- ]"
- label="绔彛"
- >
- <InputNumber
- v-model:value="config.port"
- :max="65535"
- :min="1"
- placeholder="璇疯緭鍏ョ鍙�"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'virtualHost']"
- :rules="[{ required: true, message: '铏氭嫙涓绘満涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="铏氭嫙涓绘満"
- >
- <Input v-model:value="config.virtualHost" placeholder="璇疯緭鍏ヨ櫄鎷熶富鏈�" />
- </Form.Item>
- <Form.Item
- :name="['config', 'username']"
- :rules="[{ required: true, message: '鐢ㄦ埛鍚嶄笉鑳戒负绌�', trigger: 'blur' }]"
- label="鐢ㄦ埛鍚�"
- >
- <Input v-model:value="config.username" placeholder="璇疯緭鍏ョ敤鎴峰悕" />
- </Form.Item>
- <Form.Item
- :name="['config', 'password']"
- :rules="[{ required: true, message: '瀵嗙爜涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="瀵嗙爜"
- >
- <Input.Password v-model:value="config.password" placeholder="璇疯緭鍏ュ瘑鐮�" />
- </Form.Item>
- <Form.Item
- :name="['config', 'exchange']"
- :rules="[{ required: true, message: '浜ゆ崲鏈轰笉鑳戒负绌�', trigger: 'blur' }]"
- label="浜ゆ崲鏈�"
- >
- <Input v-model:value="config.exchange" placeholder="璇疯緭鍏ヤ氦鎹㈡満" />
- </Form.Item>
- <Form.Item
- :name="['config', 'routingKey']"
- :rules="[{ required: true, message: '璺敱閿笉鑳戒负绌�', trigger: 'blur' }]"
- label="璺敱閿�"
- >
- <Input v-model:value="config.routingKey" placeholder="璇疯緭鍏ヨ矾鐢遍敭" />
- </Form.Item>
- <Form.Item
- :name="['config', 'queue']"
- :rules="[{ required: true, message: '闃熷垪涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="闃熷垪"
- >
- <Input v-model:value="config.queue" placeholder="璇疯緭鍏ラ槦鍒�" />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/redis-stream-config-form.vue b/src/views/iot/rule/data/sink/config/redis-stream-config-form.vue
deleted file mode 100644
index 18ade7d..0000000
--- a/src/views/iot/rule/data/sink/config/redis-stream-config-form.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, InputNumber } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-/** 绉婚櫎褰撳墠 Redis Stream API 绫诲瀷鏈0鏄庣殑鏃ф墿灞曞瓧娈� */
-function removeUnsupportedFields() {
- delete config.value.dataStructure;
- delete config.value.hashField;
- delete config.value.scoreField;
-}
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- removeUnsupportedFields();
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.REDIS_STREAM}`,
- host: '',
- port: 6379,
- password: '',
- database: 0,
- topic: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'host']"
- :rules="[{ required: true, message: '涓绘満鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓绘満鍦板潃"
- >
- <Input
- v-model:value="config.host"
- placeholder="璇疯緭鍏ヤ富鏈哄湴鍧�锛屽锛歭ocalhost"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'port']"
- :rules="[
- { required: true, message: '绔彛涓嶈兘涓虹┖', trigger: 'blur' },
- {
- type: 'number',
- min: 1,
- max: 65_535,
- message: '绔彛鍙疯寖鍥� 1-65535',
- trigger: 'blur',
- },
- ]"
- label="绔彛"
- >
- <InputNumber
- v-model:value="config.port"
- :max="65535"
- :min="1"
- placeholder="璇疯緭鍏ョ鍙�"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'password']"
- :rules="[{ required: true, message: '瀵嗙爜涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="瀵嗙爜"
- >
- <Input.Password v-model:value="config.password" placeholder="璇疯緭鍏ュ瘑鐮�" />
- </Form.Item>
- <Form.Item
- :name="['config', 'database']"
- :rules="[
- { required: true, message: '鏁版嵁搴撶储寮曚笉鑳戒负绌�', trigger: 'blur' },
- {
- type: 'number',
- min: 0,
- message: '鏁版嵁搴撶储寮曞繀椤绘槸闈炶礋鏁存暟',
- trigger: 'blur',
- },
- ]"
- label="鏁版嵁搴�"
- >
- <InputNumber
- v-model:value="config.database"
- :max="15"
- :min="0"
- placeholder="璇疯緭鍏ユ暟鎹簱绱㈠紩"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'topic']"
- :rules="[{ required: true, message: '涓婚涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓婚"
- >
- <Input v-model:value="config.topic" placeholder="璇疯緭鍏ヤ富棰�" />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/rocket-mq-config-form.vue b/src/views/iot/rule/data/sink/config/rocket-mq-config-form.vue
deleted file mode 100644
index d5ca32c..0000000
--- a/src/views/iot/rule/data/sink/config/rocket-mq-config-form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.ROCKETMQ}`,
- nameServer: '',
- accessKey: '',
- secretKey: '',
- group: '',
- topic: '',
- tags: '',
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'nameServer']"
- :rules="[
- { required: true, message: 'NameServer 鍦板潃涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="NameServer"
- >
- <Input
- v-model:value="config.nameServer"
- placeholder="璇疯緭鍏� NameServer 鍦板潃锛屽锛�127.0.0.1:9876"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'accessKey']"
- :rules="[
- { required: true, message: 'AccessKey 涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="AccessKey"
- >
- <Input v-model:value="config.accessKey" placeholder="璇疯緭鍏� AccessKey" />
- </Form.Item>
- <Form.Item
- :name="['config', 'secretKey']"
- :rules="[
- { required: true, message: 'SecretKey 涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="SecretKey"
- >
- <Input.Password
- v-model:value="config.secretKey"
- placeholder="璇疯緭鍏� SecretKey"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'group']"
- :rules="[{ required: true, message: '娑堣垂缁勪笉鑳戒负绌�', trigger: 'blur' }]"
- label="娑堣垂缁�"
- >
- <Input v-model:value="config.group" placeholder="璇疯緭鍏ユ秷璐圭粍" />
- </Form.Item>
- <Form.Item
- :name="['config', 'topic']"
- :rules="[{ required: true, message: '涓婚涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="涓婚"
- >
- <Input v-model:value="config.topic" placeholder="璇疯緭鍏ヤ富棰�" />
- </Form.Item>
- <Form.Item :name="['config', 'tags']" label="鏍囩">
- <Input v-model:value="config.tags" placeholder="璇疯緭鍏ユ爣绛�" />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/tcp-config-form.vue b/src/views/iot/rule/data/sink/config/tcp-config-form.vue
deleted file mode 100644
index b833e2f..0000000
--- a/src/views/iot/rule/data/sink/config/tcp-config-form.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, InputNumber, Select, Switch } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.TCP}`,
- host: '',
- port: 8080,
- connectTimeoutMs: 5000,
- readTimeoutMs: 10_000,
- ssl: false,
- sslCertPath: '',
- dataFormat: 'JSON',
- heartbeatIntervalMs: 30_000,
- reconnectIntervalMs: 5000,
- maxReconnectAttempts: 3,
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'host']"
- :rules="[{ required: true, message: '涓绘満鍦板潃涓嶈兘涓虹┖', trigger: 'blur' }]"
- label="鏈嶅姟鍣ㄥ湴鍧�"
- >
- <Input
- v-model:value="config.host"
- placeholder="璇疯緭鍏� TCP 鏈嶅姟鍣ㄥ湴鍧�锛屽锛歭ocalhost"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'port']"
- :rules="[
- { required: true, message: '绔彛涓嶈兘涓虹┖', trigger: 'blur' },
- {
- type: 'number',
- min: 1,
- max: 65_535,
- message: '绔彛鍙疯寖鍥� 1-65535',
- trigger: 'blur',
- },
- ]"
- label="绔彛"
- >
- <InputNumber
- v-model:value="config.port"
- :max="65535"
- :min="1"
- placeholder="璇疯緭鍏ョ鍙�"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'connectTimeoutMs']"
- :rules="[
- { required: true, message: '杩炴帴瓒呮椂鏃堕棿涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="杩炴帴瓒呮椂(ms)"
- >
- <InputNumber
- v-model:value="config.connectTimeoutMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'readTimeoutMs']"
- :rules="[
- { required: true, message: '璇诲彇瓒呮椂鏃堕棿涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="璇诲彇瓒呮椂(ms)"
- >
- <InputNumber
- v-model:value="config.readTimeoutMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'ssl']" label="鍚敤 SSL">
- <Switch v-model:checked="config.ssl" />
- </Form.Item>
- <Form.Item
- v-if="config.ssl"
- :name="['config', 'sslCertPath']"
- label="SSL 璇佷功璺緞"
- >
- <Input
- v-model:value="config.sslCertPath"
- placeholder="璇疯緭鍏� SSL 璇佷功璺緞"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'dataFormat']"
- :rules="[
- { required: true, message: '鏁版嵁鏍煎紡涓嶈兘涓虹┖', trigger: 'change' },
- ]"
- label="鏁版嵁鏍煎紡"
- >
- <Select v-model:value="config.dataFormat" placeholder="璇烽�夋嫨鏁版嵁鏍煎紡">
- <Select.Option value="JSON">JSON</Select.Option>
- <Select.Option value="BINARY">BINARY</Select.Option>
- </Select>
- </Form.Item>
- <Form.Item :name="['config', 'heartbeatIntervalMs']" label="蹇冭烦闂撮殧(ms)">
- <InputNumber
- v-model:value="config.heartbeatIntervalMs"
- :min="0"
- :step="1000"
- placeholder="0 琛ㄧず涓嶅惎鐢ㄥ績璺�"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'reconnectIntervalMs']" label="閲嶈繛闂撮殧(ms)">
- <InputNumber
- v-model:value="config.reconnectIntervalMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'maxReconnectAttempts']" label="鏈�澶ч噸杩炴鏁�">
- <InputNumber
- v-model:value="config.maxReconnectAttempts"
- :min="0"
- class="w-full"
- />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/config/websocket-config-form.vue b/src/views/iot/rule/data/sink/config/websocket-config-form.vue
deleted file mode 100644
index e53e34f..0000000
--- a/src/views/iot/rule/data/sink/config/websocket-config-form.vue
+++ /dev/null
@@ -1,160 +0,0 @@
-<script lang="ts" setup>
-import { onMounted } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, InputNumber, Select, Switch } from 'ant-design-vue';
-
-import { IotDataSinkTypeEnum } from '#/api/iot/rule/data/sink';
-
-const props = defineProps<{ modelValue: any }>();
-const emit = defineEmits(['update:modelValue']);
-const config = useVModel(props, 'modelValue', emit);
-
-onMounted(() => {
- if (!isEmpty(config.value)) {
- return;
- }
- config.value = {
- type: `${IotDataSinkTypeEnum.WEBSOCKET}`,
- serverUrl: '',
- connectTimeoutMs: 5000,
- sendTimeoutMs: 10_000,
- heartbeatIntervalMs: 30_000,
- heartbeatMessage: '{"type":"heartbeat"}',
- subprotocols: '',
- customHeaders: '',
- verifySslCert: true,
- dataFormat: 'JSON',
- reconnectIntervalMs: 5000,
- maxReconnectAttempts: 3,
- enableCompression: false,
- sendRetryCount: 1,
- sendRetryIntervalMs: 1000,
- };
-});
-</script>
-
-<template>
- <Form.Item
- :name="['config', 'serverUrl']"
- :rules="[
- {
- required: true,
- message: 'WebSocket 鏈嶅姟鍣ㄥ湴鍧�涓嶈兘涓虹┖',
- trigger: 'blur',
- },
- ]"
- label="鏈嶅姟鍣ㄥ湴鍧�"
- >
- <Input
- v-model:value="config.serverUrl"
- placeholder="璇疯緭鍏� WebSocket 鍦板潃锛屽锛歸s://localhost:8080/ws"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'connectTimeoutMs']"
- :rules="[
- { required: true, message: '杩炴帴瓒呮椂鏃堕棿涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="杩炴帴瓒呮椂(ms)"
- >
- <InputNumber
- v-model:value="config.connectTimeoutMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item
- :name="['config', 'sendTimeoutMs']"
- :rules="[
- { required: true, message: '鍙戦�佽秴鏃舵椂闂翠笉鑳戒负绌�', trigger: 'blur' },
- ]"
- label="鍙戦�佽秴鏃�(ms)"
- >
- <InputNumber
- v-model:value="config.sendTimeoutMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'heartbeatIntervalMs']" label="蹇冭烦闂撮殧(ms)">
- <InputNumber
- v-model:value="config.heartbeatIntervalMs"
- :min="0"
- :step="1000"
- placeholder="0 琛ㄧず涓嶅惎鐢ㄥ績璺�"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'heartbeatMessage']" label="蹇冭烦娑堟伅">
- <Input
- v-model:value="config.heartbeatMessage"
- placeholder="璇疯緭鍏ュ績璺虫秷鎭唴瀹癸紙JSON 鏍煎紡锛�"
- />
- </Form.Item>
- <Form.Item :name="['config', 'subprotocols']" label="瀛愬崗璁�">
- <Input
- v-model:value="config.subprotocols"
- placeholder="璇疯緭鍏ュ瓙鍗忚鍒楄〃锛屽涓敤閫楀彿鍒嗛殧"
- />
- </Form.Item>
- <Form.Item :name="['config', 'customHeaders']" label="鑷畾涔夎姹傚ご">
- <Input.TextArea
- v-model:value="config.customHeaders"
- placeholder="璇疯緭鍏ヨ嚜瀹氫箟璇锋眰澶达紙JSON 鏍煎紡锛�"
- :rows="3"
- />
- </Form.Item>
- <Form.Item :name="['config', 'verifySslCert']" label="楠岃瘉 SSL 璇佷功">
- <Switch v-model:checked="config.verifySslCert" />
- </Form.Item>
- <Form.Item
- :name="['config', 'dataFormat']"
- :rules="[
- { required: true, message: '鏁版嵁鏍煎紡涓嶈兘涓虹┖', trigger: 'change' },
- ]"
- label="鏁版嵁鏍煎紡"
- >
- <Select v-model:value="config.dataFormat" placeholder="璇烽�夋嫨鏁版嵁鏍煎紡">
- <Select.Option value="JSON">JSON</Select.Option>
- <Select.Option value="TEXT">TEXT</Select.Option>
- </Select>
- </Form.Item>
- <Form.Item :name="['config', 'reconnectIntervalMs']" label="閲嶈繛闂撮殧(ms)">
- <InputNumber
- v-model:value="config.reconnectIntervalMs"
- :min="1000"
- :step="1000"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'maxReconnectAttempts']" label="鏈�澶ч噸杩炴鏁�">
- <InputNumber
- v-model:value="config.maxReconnectAttempts"
- :min="0"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'enableCompression']" label="鍚敤鍘嬬缉">
- <Switch v-model:checked="config.enableCompression" />
- </Form.Item>
- <Form.Item :name="['config', 'sendRetryCount']" label="鍙戦�侀噸璇曟鏁�">
- <InputNumber
- v-model:value="config.sendRetryCount"
- :min="0"
- class="w-full"
- />
- </Form.Item>
- <Form.Item :name="['config', 'sendRetryIntervalMs']" label="閲嶈瘯闂撮殧(ms)">
- <InputNumber
- v-model:value="config.sendRetryIntervalMs"
- :min="100"
- :step="500"
- class="w-full"
- />
- </Form.Item>
-</template>
diff --git a/src/views/iot/rule/data/sink/data.ts b/src/views/iot/rule/data/sink/data.ts
deleted file mode 100644
index 255e92e..0000000
--- a/src/views/iot/rule/data/sink/data.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DataSinkApi } from '#/api/iot/rule/data/sink';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鐩殑鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ洰鐨勫悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐩殑鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'type',
- label: '鐩殑绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM, 'number'),
- placeholder: '璇烽�夋嫨鐩殑绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<DataSinkApi.DataSink>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'id',
- title: '鐩殑缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '鐩殑鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'description',
- title: '鐩殑鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'status',
- title: '鐩殑鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'type',
- title: '鐩殑绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/rule/data/sink/index.vue b/src/views/iot/rule/data/sink/index.vue
deleted file mode 100644
index 2cd5b61..0000000
--- a/src/views/iot/rule/data/sink/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DataSinkApi } from '#/api/iot/rule/data/sink';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteDataSink, getDataSinkPage } from '#/api/iot/rule/data/sink';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鏁版嵁鐩殑 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鏁版嵁鐩殑 */
-function handleEdit(row: DataSinkApi.DataSink) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍒犻櫎鏁版嵁鐩殑 */
-async function handleDelete(row: DataSinkApi.DataSink) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDataSink(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDataSinkPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<DataSinkApi.DataSink>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鏁版嵁鐩殑鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鏁版嵁鐩殑']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:data-sink:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:data-sink:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:data-sink:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/rule/data/sink/modules/form.vue b/src/views/iot/rule/data/sink/modules/form.vue
deleted file mode 100644
index db7f4c9..0000000
--- a/src/views/iot/rule/data/sink/modules/form.vue
+++ /dev/null
@@ -1,193 +0,0 @@
-<script lang="ts" setup>
-import type { DataSinkApi } from '#/api/iot/rule/data/sink';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../../packages/effects/hooks/src';
-
-import { Form, Input, message, Radio, Select } from 'ant-design-vue';
-
-import {
- createDataSink,
- getDataSink,
- IotDataSinkTypeEnum,
- updateDataSink,
-} from '#/api/iot/rule/data/sink';
-import { $t } from '#/locales';
-
-import {
- DatabaseConfigForm,
- HttpConfigForm,
- KafkaMqConfigForm,
- MqttConfigForm,
- RabbitMqConfigForm,
- RedisStreamConfigForm,
- RocketMqConfigForm,
- TcpConfigForm,
- WebSocketConfigForm,
-} from '../config';
-
-const emit = defineEmits(['success']);
-
-const formRef = ref();
-const formData = ref<DataSinkApi.DataSink>(buildEmptyFormData());
-
-const getTitle = computed(() =>
- formData.value.id
- ? $t('ui.actionTitle.edit', ['鏁版嵁鐩殑'])
- : $t('ui.actionTitle.create', ['鏁版嵁鐩殑']),
-);
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- try {
- await formRef.value?.validate();
- } catch {
- return;
- }
- modalApi.lock();
- try {
- const data = { ...formData.value };
- await (data.id ? updateDataSink(data) : createDataSink(data));
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- formData.value = buildEmptyFormData();
- formRef.value?.clearValidate?.();
- const data = modalApi.getData<{ id?: number }>();
- if (!data?.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDataSink(data.id);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 鏋勯�犵┖鐧借〃鍗曟暟鎹� */
-function buildEmptyFormData(): DataSinkApi.DataSink {
- return {
- status: CommonStatusEnum.ENABLE,
- type: IotDataSinkTypeEnum.HTTP,
- config: {} as any,
- };
-}
-
-/** 绫诲瀷鍒囨崲鏃舵竻绌洪厤缃紝瀛愮粍浠� onMounted 浼氭寜鏂扮被鍨嬮噸鏂板垵濮嬪寲 */
-function handleTypeChange(type: number) {
- formData.value.type = type;
- formData.value.config = {} as any;
-}
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-3/5">
- <Form
- ref="formRef"
- :label-col="{ span: 5 }"
- :model="formData"
- :wrapper-col="{ span: 18 }"
- class="mx-4"
- >
- <Form.Item
- :rules="[
- { required: true, message: '鐩殑鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- ]"
- label="鐩殑鍚嶇О"
- name="name"
- >
- <Input v-model:value="formData.name" placeholder="璇疯緭鍏ョ洰鐨勫悕绉�" />
- </Form.Item>
- <Form.Item label="鐩殑鎻忚堪" name="description">
- <Input.TextArea
- v-model:value="formData.description"
- placeholder="璇疯緭鍏ョ洰鐨勬弿杩�"
- :rows="3"
- />
- </Form.Item>
- <Form.Item
- :rules="[
- { required: true, message: '鐩殑绫诲瀷涓嶈兘涓虹┖', trigger: 'change' },
- ]"
- label="鐩殑绫诲瀷"
- name="type"
- >
- <Select
- :value="formData.type"
- :options="
- getDictOptions(DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM, 'number') as any
- "
- placeholder="璇烽�夋嫨鐩殑绫诲瀷"
- @change="(value: any) => handleTypeChange(value as number)"
- />
- </Form.Item>
- <!-- 閰嶇疆椤癸細鎸夌洰鐨勭被鍨嬪垎鏀� -->
- <HttpConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.HTTP"
- v-model="formData.config"
- />
- <TcpConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.TCP"
- v-model="formData.config"
- />
- <WebSocketConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.WEBSOCKET"
- v-model="formData.config"
- />
- <MqttConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.MQTT"
- v-model="formData.config"
- />
- <DatabaseConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.DATABASE"
- v-model="formData.config"
- />
- <RocketMqConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.ROCKETMQ"
- v-model="formData.config"
- />
- <KafkaMqConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.KAFKA"
- v-model="formData.config"
- />
- <RabbitMqConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.RABBITMQ"
- v-model="formData.config"
- />
- <RedisStreamConfigForm
- v-if="formData.type === IotDataSinkTypeEnum.REDIS_STREAM"
- v-model="formData.config"
- />
- <Form.Item
- :rules="[
- { required: true, message: '鐩殑鐘舵�佷笉鑳戒负绌�', trigger: 'change' },
- ]"
- label="鐩殑鐘舵��"
- name="status"
- >
- <Radio.Group v-model:value="formData.status">
- <Radio
- v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS, 'number')"
- :key="String(dict.value)"
- :value="dict.value"
- >
- {{ dict.label }}
- </Radio>
- </Radio.Group>
- </Form.Item>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/iot/rule/scene/data.ts b/src/views/iot/rule/scene/data.ts
deleted file mode 100644
index 3619055..0000000
--- a/src/views/iot/rule/scene/data.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '瑙勫垯鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍒欏悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '瑙勫垯鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'name',
- title: '瑙勫垯鍚嶇О',
- minWidth: 200,
- align: 'left',
- showOverflow: false,
- slots: { default: 'name' },
- },
- {
- field: 'triggers',
- title: '瑙﹀彂鏉′欢',
- minWidth: 280,
- align: 'left',
- showOverflow: false,
- slots: { default: 'triggers' },
- },
- {
- field: 'actions',
- title: '鎵ц鍔ㄤ綔',
- minWidth: 250,
- align: 'left',
- showOverflow: false,
- slots: { default: 'actionsCol' },
- },
- {
- field: 'lastTriggerTime',
- title: '鏈�杩戣Е鍙�',
- width: 180,
- slots: { default: 'lastTriggerTime' },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 210,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/rule/scene/form/configs/alert-config.vue b/src/views/iot/rule/scene/form/configs/alert-config.vue
deleted file mode 100644
index 1cb57b0..0000000
--- a/src/views/iot/rule/scene/form/configs/alert-config.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<!-- 鍛婅閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import { onMounted, ref } from 'vue';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Select } from 'ant-design-vue';
-
-import { getSimpleAlertConfigList } from '#/api/iot/alert/config';
-
-/** 鍛婅閰嶇疆缁勪欢 */
-defineOptions({ name: 'AlertConfig' });
-
-const props = defineProps<{
- modelValue?: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value?: number): void;
-}>();
-
-const localValue = useVModel(props, 'modelValue', emit);
-
-const loading = ref(false); // 鍔犺浇鐘舵��
-const alertConfigs = ref<any[]>([]); // 鍛婅閰嶇疆鍒楄〃
-
-/** 澶勭悊閫夋嫨鍙樺寲浜嬩欢 */
-function handleChange(value?: any) {
- emit('update:modelValue', value);
-}
-
-/** 鍔犺浇鍛婅閰嶇疆鍒楄〃 */
-async function loadAlertConfigs() {
- loading.value = true;
- try {
- alertConfigs.value = (await getSimpleAlertConfigList()) || [];
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� **/
-onMounted(() => {
- loadAlertConfigs();
-});
-</script>
-
-<template>
- <div class="w-full">
- <Form.Item label="鍛婅閰嶇疆" required>
- <Select
- v-model:value="localValue"
- placeholder="璇烽�夋嫨鍛婅閰嶇疆"
- show-search
- allow-clear
- @change="handleChange"
- class="w-full"
- :loading="loading"
- >
- <Select.Option
- v-for="config in alertConfigs"
- :key="config.id"
- :label="config.name"
- :value="config.id"
- />
- </Select>
- </Form.Item>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/condition-config.vue b/src/views/iot/rule/scene/form/configs/condition-config.vue
deleted file mode 100644
index 88774fb..0000000
--- a/src/views/iot/rule/scene/form/configs/condition-config.vue
+++ /dev/null
@@ -1,336 +0,0 @@
-<!-- 鍗曚釜鏉′欢閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { computed, ref } from 'vue';
-
-import {
- getConditionTypeOptions,
- IoTDeviceStatusEnum,
- IotRuleSceneTriggerConditionParameterOperatorEnum,
- IotRuleSceneTriggerConditionTypeEnum,
-} from '../../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import { Col, Form, Row, Select } from 'ant-design-vue';
-
-import ValueInput from '../inputs/value-input.vue';
-import DeviceSelector from '../selectors/device-selector.vue';
-import OperatorSelector from '../selectors/operator-selector.vue';
-import ProductSelector from '../selectors/product-selector.vue';
-import PropertySelector from '../selectors/property-selector.vue';
-import CurrentTimeConditionConfig from './current-time-condition-config.vue';
-
-/** 鍗曚釜鏉′欢閰嶇疆缁勪欢 */
-defineOptions({ name: 'ConditionConfig' });
-
-const props = defineProps<{
- modelValue: RuleSceneApi.TriggerCondition;
- triggerType: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
-}>();
-
-/** 鑾峰彇璁惧鐘舵�侀�夐」 */
-const deviceStatusOptions = [
- {
- value: IoTDeviceStatusEnum.ONLINE.value,
- label: IoTDeviceStatusEnum.ONLINE.label,
- },
- {
- value: IoTDeviceStatusEnum.OFFLINE.value,
- label: IoTDeviceStatusEnum.OFFLINE.label,
- },
-];
-
-/** 鑾峰彇鐘舵�佹搷浣滅閫夐」 */
-const statusOperatorOptions = [
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name,
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name,
- },
-];
-
-const condition = useVModel(props, 'modelValue', emit);
-
-const propertyType = ref<string>('string'); // 灞炴�х被鍨�
-const propertyConfig = ref<any>(null); // 灞炴�ч厤缃�
-const isDeviceCondition = computed(() => {
- return (
- condition.value.type ===
- IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS ||
- condition.value.type ===
- IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
- );
-}); // 璁$畻灞炴�э細鍒ゆ柇鏄惁涓鸿澶囩浉鍏虫潯浠�
-
-/**
- * 鏇存柊鏉′欢瀛楁
- * @param field 瀛楁鍚�
- * @param value 瀛楁鍊�
- */
-function updateConditionField(field: any, value: any) {
- Object.assign(condition.value, { [field]: value });
- emit('update:modelValue', condition.value);
-}
-
-/**
- * 鏇存柊鏁翠釜鏉′欢瀵硅薄
- * @param newCondition 鏂扮殑鏉′欢瀵硅薄
- */
-function updateCondition(newCondition: RuleSceneApi.TriggerCondition) {
- condition.value = newCondition;
- emit('update:modelValue', condition.value);
-}
-
-/**
- * 澶勭悊鏉′欢绫诲瀷鍙樺寲浜嬩欢
- * @param type 鏉′欢绫诲瀷
- */
-function handleConditionTypeChange(type: any) {
- // 鏍规嵁鏉′欢绫诲瀷娓呯悊瀛楁
- const isCurrentTime =
- type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME;
- const isDeviceStatus =
- type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS;
-
- // 娓呯悊鏍囪瘑绗﹀瓧娈碉紙鏃堕棿鏉′欢鍜岃澶囩姸鎬佹潯浠堕兘涓嶉渶瑕侊級
- if (isCurrentTime || isDeviceStatus) {
- condition.value.identifier = undefined;
- }
-
- // 娓呯悊璁惧鐩稿叧瀛楁锛堜粎鏃堕棿鏉′欢闇�瑕侊級
- if (isCurrentTime) {
- condition.value.productId = undefined;
- condition.value.deviceId = undefined;
- }
-
- // 璁剧疆榛樿鎿嶄綔绗�
- condition.value.operator = isCurrentTime
- ? 'at_time'
- : IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value;
-
- // 娓呯┖鍙傛暟鍊�
- condition.value.param = '';
-}
-
-/** 澶勭悊浜у搧鍙樺寲浜嬩欢 */
-function handleProductChange(_: any) {
- // 浜у搧鍙樺寲鏃舵竻绌鸿澶囧拰灞炴��
- condition.value.deviceId = undefined;
- condition.value.identifier = '';
-}
-
-/** 澶勭悊璁惧鍙樺寲浜嬩欢 */
-function handleDeviceChange(_: any) {
- // 璁惧鍙樺寲鏃舵竻绌哄睘鎬�
- condition.value.identifier = '';
-}
-
-/**
- * 澶勭悊灞炴�у彉鍖栦簨浠�
- * @param propertyInfo.config - 灞炴�ч厤缃�
- * @param propertyInfo.type - 灞炴�х被鍨�
- */
-function handlePropertyChange(propertyInfo: { config: any; type: string }) {
- propertyType.value = propertyInfo.type;
- propertyConfig.value = propertyInfo.config;
-
- // 閲嶇疆鎿嶄綔绗﹀拰鍊�
- condition.value.operator =
- IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value;
- condition.value.param = '';
-}
-
-/** 澶勭悊鎿嶄綔绗﹀彉鍖栦簨浠� */
-function handleOperatorChange() {
- // 閲嶇疆鍊�
- condition.value.param = '';
-}
-</script>
-
-<template>
- <div class="gap-[16px] flex flex-col">
- <!-- 鏉′欢绫诲瀷閫夋嫨 -->
- <Row :gutter="16">
- <Col :span="8">
- <Form.Item label="鏉′欢绫诲瀷" required>
- <Select
- :value="condition.type"
- @change="
- (value: any) => {
- updateConditionField('type', value);
- handleConditionTypeChange(value);
- }
- "
- placeholder="璇烽�夋嫨鏉′欢绫诲瀷"
- class="w-full"
- >
- <Select.Option
- v-for="option in getConditionTypeOptions()"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
- </Row>
-
- <!-- 浜у搧璁惧閫夋嫨 - 璁惧鐩稿叧鏉′欢鐨勫叕鍏遍儴鍒� -->
- <Row v-if="isDeviceCondition" :gutter="16">
- <Col :span="12">
- <Form.Item label="浜у搧" required>
- <ProductSelector
- :model-value="condition.productId"
- @update:model-value="
- (value) => updateConditionField('productId', value)
- "
- @change="handleProductChange"
- />
- </Form.Item>
- </Col>
- <Col :span="12">
- <Form.Item label="璁惧" required>
- <DeviceSelector
- :model-value="condition.deviceId"
- @update:model-value="
- (value) => updateConditionField('deviceId', value)
- "
- :product-id="condition.productId"
- @change="handleDeviceChange"
- />
- </Form.Item>
- </Col>
- </Row>
-
- <!-- 璁惧鐘舵�佹潯浠堕厤缃� -->
- <div
- v-if="
- condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
- "
- class="gap-[16px] flex flex-col"
- >
- <!-- 鐘舵�佸拰鎿嶄綔绗﹂�夋嫨 -->
- <Row :gutter="16">
- <!-- 鎿嶄綔绗﹂�夋嫨 -->
- <Col :span="12">
- <Form.Item label="鎿嶄綔绗�" required>
- <Select
- :value="condition.operator"
- @change="(value: any) => updateConditionField('operator', value)"
- placeholder="璇烽�夋嫨鎿嶄綔绗�"
- class="w-full"
- >
- <Select.Option
- v-for="option in statusOperatorOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
-
- <!-- 鐘舵�侀�夋嫨 -->
- <Col :span="12">
- <Form.Item label="璁惧鐘舵��" required>
- <Select
- :value="condition.param"
- @change="(value: any) => updateConditionField('param', value)"
- placeholder="璇烽�夋嫨璁惧鐘舵��"
- class="w-full"
- >
- <Select.Option
- v-for="option in deviceStatusOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
- </Row>
- </div>
-
- <!-- 璁惧灞炴�ф潯浠堕厤缃� -->
- <div
- v-else-if="
- condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
- "
- class="space-y-[16px]"
- >
- <!-- 灞炴�ч厤缃� -->
- <Row :gutter="16">
- <!-- 灞炴��/浜嬩欢/鏈嶅姟閫夋嫨 -->
- <Col :span="6">
- <Form.Item label="鐩戞帶椤�" required>
- <PropertySelector
- :model-value="condition.identifier"
- @update:model-value="
- (value) => updateConditionField('identifier', value)
- "
- :trigger-type="triggerType"
- :product-id="condition.productId"
- :device-id="condition.deviceId"
- @change="handlePropertyChange"
- />
- </Form.Item>
- </Col>
-
- <!-- 鎿嶄綔绗﹂�夋嫨 -->
- <Col :span="6">
- <Form.Item label="鎿嶄綔绗�" required>
- <OperatorSelector
- :model-value="condition.operator"
- @update:model-value="
- (value) => updateConditionField('operator', value)
- "
- :property-type="propertyType"
- @change="handleOperatorChange"
- />
- </Form.Item>
- </Col>
-
- <!-- 鍊艰緭鍏� -->
- <Col :span="12">
- <Form.Item label="姣旇緝鍊�" required>
- <ValueInput
- :model-value="condition.param"
- @update:model-value="
- (value) => updateConditionField('param', value)
- "
- :property-type="propertyType"
- :operator="condition.operator"
- :property-config="propertyConfig"
- />
- </Form.Item>
- </Col>
- </Row>
- </div>
-
- <!-- 褰撳墠鏃堕棿鏉′欢閰嶇疆 -->
- <CurrentTimeConditionConfig
- v-else-if="
- condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME
- "
- :model-value="condition"
- @update:model-value="updateCondition"
- />
- </div>
-</template>
-
-<style scoped>
-:deep(.ant-form-item) {
- margin-bottom: 0;
-}
-</style>
diff --git a/src/views/iot/rule/scene/form/configs/current-time-condition-config.vue b/src/views/iot/rule/scene/form/configs/current-time-condition-config.vue
deleted file mode 100644
index f066436..0000000
--- a/src/views/iot/rule/scene/form/configs/current-time-condition-config.vue
+++ /dev/null
@@ -1,262 +0,0 @@
-<!-- 褰撳墠鏃堕棿鏉′欢閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import type { Dayjs } from 'dayjs';
-
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { computed, watch } from 'vue';
-
-import { IotRuleSceneTriggerTimeOperatorEnum } from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { formatDayjs } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Col,
- DatePicker,
- Form,
- Row,
- Select,
- Tag,
- TimePicker,
-} from 'ant-design-vue';
-
-/** 褰撳墠鏃堕棿鏉′欢閰嶇疆缁勪欢 */
-defineOptions({ name: 'CurrentTimeConditionConfig' });
-
-const props = defineProps<{
- modelValue: RuleSceneApi.TriggerCondition;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.TriggerCondition): void;
-}>();
-
-const condition = useVModel(props, 'modelValue', emit);
-
-// 鏃堕棿鎿嶄綔绗﹂�夐」
-const timeOperatorOptions = [
- {
- value: IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.value,
- label: IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.name,
- icon: 'ep:arrow-left',
- iconClass: 'text-blue-500',
- tag: 'processing',
- category: '鏃堕棿鐐�',
- },
- {
- value: IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.value,
- label: IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.name,
- icon: 'ep:arrow-right',
- iconClass: 'text-green-500',
- tag: 'success',
- category: '鏃堕棿鐐�',
- },
- {
- value: IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value,
- label: IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.name,
- icon: 'ep:sort',
- iconClass: 'text-orange-500',
- tag: 'warning',
- category: '鏃堕棿娈�',
- },
- {
- value: IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.value,
- label: IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.name,
- icon: 'ep:position',
- iconClass: 'text-purple-500',
- tag: 'default',
- category: '鏃堕棿鐐�',
- },
- {
- value: IotRuleSceneTriggerTimeOperatorEnum.TODAY.value,
- label: IotRuleSceneTriggerTimeOperatorEnum.TODAY.name,
- icon: 'ep:calendar',
- iconClass: 'text-red-500',
- tag: 'error',
- category: '鏃ユ湡',
- },
-];
-
-// 璁$畻灞炴�э細鏄惁闇�瑕佹椂闂磋緭鍏�
-const needsTimeInput = computed(() => {
- const timeOnlyOperators = [
- IotRuleSceneTriggerTimeOperatorEnum.BEFORE_TIME.value,
- IotRuleSceneTriggerTimeOperatorEnum.AFTER_TIME.value,
- IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value,
- IotRuleSceneTriggerTimeOperatorEnum.AT_TIME.value,
- ];
- return timeOnlyOperators.includes(condition.value.operator as any);
-});
-
-/** 鏄惁闇�瑕佹棩鏈熻緭鍏ワ細褰撳墠鍙敮鎸佹椂闂寸淮搴︾殑鍒ゆ柇 */
-const needsDateInput = computed(() => {
- return false;
-});
-
-// 璁$畻灞炴�э細鏄惁闇�瑕佺浜屼釜鏃堕棿杈撳叆
-const needsSecondTimeInput = computed(() => {
- return (
- condition.value.operator ===
- IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
- );
-});
-
-// 璁$畻灞炴�э細浠� param 涓В鏋愭椂闂村��
-const timeValue = computed(() => {
- if (!condition.value.param) return '';
- const params = condition.value.param.split(',');
- return params[0] || '';
-});
-
-// 璁$畻灞炴�э細浠� param 涓В鏋愮浜屼釜鏃堕棿鍊�
-const timeValue2 = computed(() => {
- if (!condition.value.param) return '';
- const params = condition.value.param.split(',');
- return params[1] || '';
-});
-
-/**
- * 鏇存柊鏉′欢瀛楁
- * @param field 瀛楁鍚�
- * @param value 瀛楁鍊�
- */
-function updateConditionField(field: any, value: any) {
- Object.assign(condition.value, { [field]: value });
-}
-
-/**
- * 澶勭悊绗竴涓椂闂村�煎彉鍖�
- * @param value 鏃堕棿鍊�
- */
-function handleTimeValueChange(value: Dayjs | null | string) {
- const normalized = formatDayjs(value);
- const currentParams = condition.value.param
- ? condition.value.param.split(',')
- : [];
- currentParams[0] = normalized;
-
- // 濡傛灉鏄寖鍥存潯浠讹紝淇濈暀绗簩涓�硷紱鍚﹀垯鍙繚鐣欑涓�涓��
- condition.value.param = needsSecondTimeInput.value
- ? currentParams.slice(0, 2).join(',')
- : currentParams[0];
-}
-
-/**
- * 澶勭悊绗簩涓椂闂村�煎彉鍖�
- * @param value 鏃堕棿鍊�
- */
-function handleTimeValue2Change(value: Dayjs | null | string) {
- const normalized = formatDayjs(value);
- const currentParams = condition.value.param
- ? condition.value.param.split(',')
- : [''];
- currentParams[1] = normalized;
- condition.value.param = currentParams.slice(0, 2).join(',');
-}
-
-/** 鐩戝惉鎿嶄綔绗﹀彉鍖栵紝娓呯悊涓嶇浉鍏崇殑鏃堕棿鍊� */
-watch(
- () => condition.value.operator,
- (newOperator) => {
- if (newOperator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {
- // 浠婃棩鏉′欢涓嶉渶瑕佹椂闂村弬鏁�
- condition.value.param = '';
- } else if (!needsSecondTimeInput.value) {
- // 闈炶寖鍥存潯浠跺彧淇濈暀绗竴涓椂闂村��
- const currentParams = condition.value.param
- ? condition.value.param.split(',')
- : [];
- condition.value.param = currentParams[0] || '';
- }
- },
-);
-</script>
-
-<template>
- <div class="flex flex-col gap-4">
- <Row :gutter="16">
- <!-- 鏃堕棿鎿嶄綔绗﹂�夋嫨 -->
- <Col :span="8">
- <Form.Item label="鏃堕棿鏉′欢" required>
- <Select
- :value="condition.operator"
- @update:value="
- (value: any) => updateConditionField('operator', value)
- "
- placeholder="璇烽�夋嫨鏃堕棿鏉′欢"
- class="w-full"
- >
- <Select.Option
- v-for="option in timeOperatorOptions"
- :key="option.value"
- :label="option.label"
- :value="option.value"
- >
- <div class="flex w-full items-center justify-between">
- <div class="flex items-center gap-2">
- <IconifyIcon :icon="option.icon" :class="option.iconClass" />
- <span>{{ option.label }}</span>
- </div>
- <Tag :color="option.tag">
- {{ option.category }}
- </Tag>
- </div>
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
-
- <!-- 鏃堕棿鍊艰緭鍏� -->
- <Col :span="8">
- <Form.Item label="鏃堕棿鍊�" required>
- <TimePicker
- v-if="needsTimeInput"
- :value="timeValue"
- @update:value="handleTimeValueChange"
- placeholder="璇烽�夋嫨鏃堕棿"
- format="HH:mm:ss"
- value-format="HH:mm:ss"
- class="w-full"
- />
- <DatePicker
- v-else-if="needsDateInput"
- :value="timeValue"
- @update:value="handleTimeValueChange"
- type="datetime"
- placeholder="璇烽�夋嫨鏃ユ湡鏃堕棿"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- class="w-full"
- />
- <div v-else class="text-sm text-muted-foreground">鏃犻渶璁剧疆鏃堕棿鍊�</div>
- </Form.Item>
- </Col>
-
- <!-- 绗簩涓椂闂村�硷紙鑼冨洿鏉′欢锛� -->
- <Col :span="8" v-if="needsSecondTimeInput">
- <Form.Item label="缁撴潫鏃堕棿" required>
- <TimePicker
- v-if="needsTimeInput"
- :value="timeValue2"
- @update:value="handleTimeValue2Change"
- placeholder="璇烽�夋嫨缁撴潫鏃堕棿"
- format="HH:mm:ss"
- value-format="HH:mm:ss"
- class="w-full"
- />
- <DatePicker
- v-else
- :value="timeValue2"
- @update:value="handleTimeValue2Change"
- type="datetime"
- placeholder="璇烽�夋嫨缁撴潫鏃ユ湡鏃堕棿"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- class="w-full"
- />
- </Form.Item>
- </Col>
- </Row>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/device-control-config.vue b/src/views/iot/rule/scene/form/configs/device-control-config.vue
deleted file mode 100644
index 6c1d3f3..0000000
--- a/src/views/iot/rule/scene/form/configs/device-control-config.vue
+++ /dev/null
@@ -1,395 +0,0 @@
-<!-- 璁惧鎺у埗閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, onMounted, ref, watch } from 'vue';
-
-import {
- IoTDataSpecsDataTypeEnum,
- IotRuleSceneActionTypeEnum,
- IoTThingModelAccessModeEnum,
-} from '../../../../../../packages/constants/src';
-import { isObject } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Col, Form, Row, Select, Tag } from 'ant-design-vue';
-
-import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
-
-import JsonParamsInput from '../inputs/json-params-input.vue';
-import DeviceSelector from '../selectors/device-selector.vue';
-import ProductSelector from '../selectors/product-selector.vue';
-
-/** 璁惧鎺у埗閰嶇疆缁勪欢 */
-defineOptions({ name: 'DeviceControlConfig' });
-
-const props = defineProps<{
- modelValue: RuleSceneApi.Action;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.Action): void;
-}>();
-
-const action = useVModel(props, 'modelValue', emit);
-
-const thingModelProperties = ref<ThingModelApi.Property[]>([]); // 鐗╂ā鍨嬪睘鎬у垪琛�
-const loadingThingModel = ref(false); // 鐗╂ā鍨嬪姞杞界姸鎬�
-const selectedService = ref<null | ThingModelApi.Service>(null); // 閫変腑鐨勬湇鍔″璞�
-const serviceList = ref<ThingModelApi.Service[]>([]); // 鏈嶅姟鍒楄〃
-const loadingServices = ref(false); // 鏈嶅姟鍔犺浇鐘舵��
-
-// 鍙傛暟鍊肩殑璁$畻灞炴�э紝鐢ㄤ簬鍙屽悜缁戝畾
-const paramsValue = computed({
- get: () => {
- // 濡傛灉 params 鏄璞★紝杞崲涓� JSON 瀛楃涓诧紙鍏煎鏃ф暟鎹級
- if (action.value.params && isObject(action.value.params)) {
- return JSON.stringify(action.value.params, null, 2);
- }
- // 濡傛灉 params 宸茬粡鏄瓧绗︿覆锛岀洿鎺ヨ繑鍥�
- return action.value.params || '';
- },
- set: (value: string) => {
- // 鐩存帴淇濆瓨涓� JSON 瀛楃涓诧紝涓嶈繘琛岃В鏋愯浆鎹�
- action.value.params = (value.trim() || '') as any;
- },
-});
-
-// 璁$畻灞炴�э細鏄惁涓哄睘鎬ц缃被鍨�
-const isPropertySetAction = computed(() => {
- return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET;
-});
-
-// 璁$畻灞炴�э細鏄惁涓烘湇鍔¤皟鐢ㄧ被鍨�
-const isServiceInvokeAction = computed(() => {
- return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE;
-});
-
-/**
- * 澶勭悊浜у搧鍙樺寲浜嬩欢
- * @param productId 浜у搧 ID
- *
- * ProductSelector 鍙湪鐢ㄦ埛涓诲姩鍒囨崲鏃� emit change锛岀紪杈戝洖濉樁娈典笉浼氳Е鍙戙��
- */
-function handleProductChange(productId?: number) {
- action.value.deviceId = undefined;
- action.value.identifier = undefined;
- action.value.params = '' as any;
- selectedService.value = null;
- serviceList.value = [];
-
- if (productId) {
- if (isPropertySetAction.value) {
- loadThingModelProperties(productId);
- } else if (isServiceInvokeAction.value) {
- loadServiceList(productId);
- }
- }
-}
-
-/**
- * 澶勭悊璁惧鍙樺寲浜嬩欢
- * @param deviceId 璁惧 ID
- */
-function handleDeviceChange(_deviceId?: number) {
- action.value.params = '' as any;
-}
-
-/**
- * 澶勭悊鏈嶅姟鍙樺寲浜嬩欢
- * @param serviceIdentifier 鏈嶅姟鏍囪瘑绗�
- */
-function handleServiceChange(serviceIdentifier?: any) {
- // 鏍规嵁鏈嶅姟鏍囪瘑绗︽壘鍒板搴旂殑鏈嶅姟瀵硅薄
- const service =
- serviceList.value.find((item) => item.identifier === serviceIdentifier) ||
- null;
- selectedService.value = service;
-
- // 褰撴湇鍔″彉鍖栨椂锛屾竻绌哄弬鏁伴厤缃�
- action.value.params = '' as any;
-
- // 濡傛灉閫夋嫨浜嗘湇鍔′笖鏈夎緭鍏ュ弬鏁帮紝鐢熸垚榛樿鍙傛暟缁撴瀯
- if (service && service.inputParams && service.inputParams.length > 0) {
- const defaultParams: Record<string, any> = {};
- service.inputParams.forEach((param) => {
- defaultParams[param.identifier!] = getDefaultValueForParam(param);
- });
- // 灏嗛粯璁ゅ弬鏁拌浆鎹负 JSON 瀛楃涓蹭繚瀛�
- action.value.params = JSON.stringify(defaultParams, null, 2) as any;
- }
-}
-
-/** 鑾峰彇鐗╂ā鍨� TSL 鏁版嵁 */
-async function fetchThingModelTSL(productId: number) {
- if (!productId) return null;
- try {
- return await getThingModelTSLByProductId(productId);
- } catch (error) {
- console.error('鑾峰彇鐗╂ā鍨� TSL 鏁版嵁澶辫触锛�', error);
- return null;
- }
-}
-
-/**
- * 鍔犺浇鐗╂ā鍨嬪睘鎬э紙鍙啓灞炴�э級
- * @param productId 浜у搧ID
- */
-async function loadThingModelProperties(productId: number) {
- if (!productId) {
- thingModelProperties.value = [];
- return;
- }
-
- try {
- loadingThingModel.value = true;
- const tslData = await fetchThingModelTSL(productId);
-
- if (!tslData?.properties) {
- thingModelProperties.value = [];
- return;
- }
-
- // 杩囨护鍑哄彲鍐欑殑灞炴�э紙accessMode 鍖呭惈 'w'锛�
- thingModelProperties.value = tslData.properties.filter(
- (property: ThingModelApi.Property) =>
- property.accessMode &&
- (property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value ||
- property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value),
- );
- } catch (error) {
- console.error('鍔犺浇鐗╂ā鍨嬪睘鎬уけ璐� 锛�', error);
- thingModelProperties.value = [];
- } finally {
- loadingThingModel.value = false;
- }
-}
-
-/**
- * 鍔犺浇鏈嶅姟鍒楄〃
- * @param productId 浜у搧ID
- */
-async function loadServiceList(productId: number) {
- if (!productId) {
- serviceList.value = [];
- return;
- }
-
- try {
- loadingServices.value = true;
- const tslData = await fetchThingModelTSL(productId);
-
- if (!tslData?.services) {
- serviceList.value = [];
- return;
- }
-
- serviceList.value = tslData.services;
- } catch (error) {
- console.error('鍔犺浇鏈嶅姟鍒楄〃澶辫触 锛�', error);
- serviceList.value = [];
- } finally {
- loadingServices.value = false;
- }
-}
-
-/**
- * 浠� TSL 鍔犺浇鏈嶅姟淇℃伅锛堢敤浜庣紪杈戞ā寮忓洖鏄撅級
- * @param productId 浜у搧 ID
- * @param serviceIdentifier 鏈嶅姟鏍囪瘑绗�
- */
-async function loadServiceFromTSL(
- productId: number,
- serviceIdentifier: string,
-) {
- // 鍏堝姞杞芥湇鍔″垪琛�
- await loadServiceList(productId);
-
- // 鐒跺悗璁剧疆閫変腑鐨勬湇鍔�
- const service = serviceList.value.find(
- (s: any) => s.identifier === serviceIdentifier,
- );
- if (service) {
- selectedService.value = service;
- }
-}
-
-/**
- * 鏍规嵁鍙傛暟绫诲瀷鑾峰彇榛樿鍊�
- * @param param 鍙傛暟瀵硅薄
- * @returns 榛樿鍊�
- */
-function getDefaultValueForParam(param: any) {
- switch (param.dataType) {
- case IoTDataSpecsDataTypeEnum.BOOL: {
- return false;
- }
- case IoTDataSpecsDataTypeEnum.DOUBLE:
- case IoTDataSpecsDataTypeEnum.FLOAT: {
- return 0;
- }
- case IoTDataSpecsDataTypeEnum.ENUM: {
- // 濡傛灉鏈夋灇涓惧�硷紝浣跨敤绗竴涓�
- if (
- param.dataSpecs?.dataSpecsList &&
- param.dataSpecs.dataSpecsList.length > 0
- ) {
- return param.dataSpecs.dataSpecsList[0].value;
- }
- return '';
- }
- case IoTDataSpecsDataTypeEnum.INT: {
- return 0;
- }
- case IoTDataSpecsDataTypeEnum.TEXT: {
- return '';
- }
- default: {
- return '';
- }
- }
-}
-
-/**
- * 鍒濆鍖栫粍浠舵暟鎹�
- */
-async function initializeComponent() {
- const currentAction = action.value;
- if (!currentAction) return;
-
- // 濡傛灉宸茬粡閫夋嫨浜嗕骇鍝佷笖鏄睘鎬ц缃被鍨嬶紝鍔犺浇鐗╂ā鍨�
- if (currentAction.productId && isPropertySetAction.value) {
- await loadThingModelProperties(currentAction.productId);
- }
-
- // 濡傛灉鏄湇鍔¤皟鐢ㄧ被鍨嬩笖宸叉湁鏍囪瘑绗︼紝鍒濆鍖栨湇鍔¢�夋嫨
- if (
- currentAction.productId &&
- isServiceInvokeAction.value &&
- currentAction.identifier
- ) {
- // 鍔犺浇鐗╂ā鍨婽SL浠ヨ幏鍙栨湇鍔′俊鎭�
- await loadServiceFromTSL(currentAction.productId, currentAction.identifier);
- }
-}
-
-/** 缁勪欢鍒濆鍖� */
-onMounted(() => {
- initializeComponent();
-});
-
-/** 鐩戝惉鍏抽敭瀛楁鐨勫彉鍖栵紝閬垮厤娣卞害鐩戝惉瀵艰嚧鐨勬�ц兘闂 */
-watch(
- () => [action.value.productId, action.value.type, action.value.identifier],
- async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => {
- // 浜у搧鍙樺寲鏃堕噸鏂板姞杞芥暟鎹�
- if (newProductId !== oldProductId) {
- if (newProductId && isPropertySetAction.value) {
- await loadThingModelProperties(newProductId as number);
- } else if (newProductId && isServiceInvokeAction.value) {
- await loadServiceList(newProductId as number);
- }
- }
-
- // 鏈嶅姟鏍囪瘑绗﹀彉鍖栨椂鏇存柊閫変腑鐨勬湇鍔�
- if (
- newIdentifier !== oldIdentifier &&
- newProductId &&
- isServiceInvokeAction.value &&
- newIdentifier
- ) {
- const service = serviceList.value.find(
- (s: any) => s.identifier === newIdentifier,
- );
- if (service) {
- selectedService.value = service;
- }
- }
- },
-);
-</script>
-
-<template>
- <div class="gap-[16px] flex flex-col">
- <!-- 浜у搧鍜岃澶囬�夋嫨 - 涓庤Е鍙戝櫒淇濇寔涓�鑷寸殑鍒嗙寮忛�夋嫨鍣� -->
- <Row :gutter="16">
- <Col :span="12">
- <Form.Item label="浜у搧" required>
- <ProductSelector
- v-model="action.productId"
- @change="handleProductChange"
- />
- </Form.Item>
- </Col>
- <Col :span="12">
- <Form.Item label="璁惧" required>
- <DeviceSelector
- v-model="action.deviceId"
- :product-id="action.productId"
- @change="handleDeviceChange"
- />
- </Form.Item>
- </Col>
- </Row>
-
- <!-- 鏈嶅姟閫夋嫨 - 鏈嶅姟璋冪敤绫诲瀷鏃舵樉绀� -->
- <div
- v-if="action.productId && isServiceInvokeAction"
- class="space-y-[16px]"
- >
- <Form.Item label="鏈嶅姟" required>
- <Select
- v-model:value="action.identifier"
- placeholder="璇烽�夋嫨鏈嶅姟"
- show-search
- allow-clear
- class="w-full"
- :loading="loadingServices"
- @change="handleServiceChange"
- >
- <Select.Option
- v-for="service in serviceList"
- :key="service.identifier"
- :label="service.name"
- :value="service.identifier"
- >
- <div class="flex items-center justify-between">
- <span>{{ service.name }}</span>
- <Tag
- :color="service.callType === 'sync' ? 'processing' : 'success'"
- >
- {{ service.callType === 'sync' ? '鍚屾' : '寮傛' }}
- </Tag>
- </div>
- </Select.Option>
- </Select>
- </Form.Item>
-
- <!-- 鏈嶅姟鍙傛暟閰嶇疆 -->
- <div v-if="action.identifier" class="space-y-[16px]">
- <Form.Item label="鏈嶅姟鍙傛暟" required>
- <JsonParamsInput
- v-model="paramsValue"
- type="service"
- :config="{ service: selectedService } as any"
- placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勬湇鍔″弬鏁�"
- />
- </Form.Item>
- </div>
- </div>
-
- <!-- 鎺у埗鍙傛暟閰嶇疆 - 灞炴�ц缃被鍨嬫椂鏄剧ず -->
- <div v-if="action.productId && isPropertySetAction" class="space-y-[16px]">
- <!-- 鍙傛暟閰嶇疆 -->
- <Form.Item label="鍙傛暟" required>
- <JsonParamsInput
- v-model="paramsValue"
- type="property"
- :config="{ properties: thingModelProperties }"
- placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勬帶鍒跺弬鏁�"
- />
- </Form.Item>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/device-trigger-config.vue b/src/views/iot/rule/scene/form/configs/device-trigger-config.vue
deleted file mode 100644
index 02a8f02..0000000
--- a/src/views/iot/rule/scene/form/configs/device-trigger-config.vue
+++ /dev/null
@@ -1,286 +0,0 @@
-<!-- 璁惧瑙﹀彂閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { nextTick } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { getStableObjectKey } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Tag } from 'ant-design-vue';
-
-import MainConditionInnerConfig from './main-condition-inner-config.vue';
-import SubConditionGroupConfig from './sub-condition-group-config.vue';
-
-/** 璁惧瑙﹀彂閰嶇疆缁勪欢 */
-defineOptions({ name: 'DeviceTriggerConfig' });
-
-const props = defineProps<{
- index: number;
- modelValue: RuleSceneApi.Trigger;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.Trigger): void;
- (e: 'triggerTypeChange', type: number): void;
-}>();
-
-const trigger = useVModel(props, 'modelValue', emit);
-
-const maxSubGroups = 3; // 鏈�澶� 3 涓瓙鏉′欢缁�
-const maxConditionsPerGroup = 3; // 姣忕粍鏈�澶� 3 涓潯浠�
-
-/**
- * 鏇存柊鏉′欢
- * @param condition 鏉′欢瀵硅薄
- */
-function updateCondition(condition: RuleSceneApi.Trigger) {
- trigger.value = condition;
-}
-
-/**
- * 澶勭悊瑙﹀彂鍣ㄧ被鍨嬪彉鍖栦簨浠�
- * @param type 瑙﹀彂鍣ㄧ被鍨�
- */
-function handleTriggerTypeChange(type: number) {
- trigger.value.type = type;
- emit('triggerTypeChange', type);
-}
-
-/** 娣诲姞瀛愭潯浠剁粍 */
-async function addSubGroup() {
- if (!trigger.value.conditionGroups) {
- trigger.value.conditionGroups = [];
- }
-
- // 妫�鏌ユ槸鍚﹁揪鍒版渶澶у瓙缁勬暟閲忛檺鍒�
- if (trigger.value.conditionGroups?.length >= maxSubGroups) {
- return;
- }
-
- // 浣跨敤 nextTick 纭繚鍝嶅簲寮忔洿鏂板畬鎴愬悗鍐嶆坊鍔犳柊鐨勫瓙缁�
- await nextTick();
- if (trigger.value.conditionGroups) {
- trigger.value.conditionGroups.push([] as any);
- }
-}
-
-/**
- * 绉婚櫎瀛愭潯浠剁粍
- * @param index 瀛愭潯浠剁粍绱㈠紩
- */
-function removeSubGroup(index: number) {
- if (trigger.value.conditionGroups) {
- trigger.value.conditionGroups.splice(index, 1);
- }
-}
-
-/**
- * 鏇存柊瀛愭潯浠剁粍
- * @param index 瀛愭潯浠剁粍绱㈠紩
- * @param subGroup 瀛愭潯浠剁粍鏁版嵁
- */
-function updateSubGroup(index: number, subGroup: any) {
- if (trigger.value.conditionGroups) {
- trigger.value.conditionGroups[index] = subGroup;
- }
-}
-
-/** 绉婚櫎鏁翠釜鏉′欢缁� */
-function removeConditionGroup() {
- trigger.value.conditionGroups = undefined;
-}
-</script>
-
-<template>
- <div class="gap-[16px] flex flex-col">
- <!-- 涓绘潯浠堕厤缃� - 榛樿鐩存帴灞曠ず -->
- <div class="space-y-[16px]">
- <!-- 涓绘潯浠堕厤缃� -->
- <div class="gap-[16px] flex flex-col">
- <!-- 涓绘潯浠堕厤缃� -->
- <div class="space-y-[16px]">
- <!-- 涓绘潯浠跺ご閮紙缁胯壊涓婚锛� -->
- <div
- class="px-[16px] py-[10px] rounded-[6px] flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 dark:border-green-900/40 dark:from-green-950/30 dark:to-emerald-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-green-700 dark:text-green-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
- >
- 涓�
- </div>
- <span>涓绘潯浠�</span>
- </div>
- <Tag color="success">蹇呴』婊¤冻</Tag>
- </div>
- </div>
-
- <!-- 涓绘潯浠跺唴瀹归厤缃� -->
- <MainConditionInnerConfig
- :model-value="trigger"
- @update:model-value="updateCondition"
- :trigger-type="trigger.type as number"
- @trigger-type-change="handleTriggerTypeChange"
- />
- </div>
- </div>
- </div>
-
- <!-- 鏉′欢缁勯厤缃� -->
- <div class="space-y-[16px]">
- <!-- 鏉′欢缁勯厤缃� -->
- <div class="gap-[16px] flex flex-col">
- <!-- 鏉′欢缁勫鍣ㄥご閮紙缁胯壊涓婚锛� -->
- <div
- class="px-[16px] py-[10px] rounded-[6px] flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 dark:border-green-900/40 dark:from-green-950/30 dark:to-emerald-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-green-700 dark:text-green-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
- >
- 缁�
- </div>
- <span>闄勫姞鏉′欢缁�</span>
- </div>
- <Tag color="success">涓庛�屼富鏉′欢銆嶄负涓斿叧绯�</Tag>
- <Tag>{{ trigger.conditionGroups?.length || 0 }} 涓瓙鏉′欢缁�</Tag>
- </div>
- <div class="gap-[8px] flex items-center">
- <Button
- type="primary"
- size="small"
- @click="addSubGroup"
- :disabled="(trigger.conditionGroups?.length || 0) >= maxSubGroups"
- >
- <IconifyIcon icon="lucide:plus" />
- 娣诲姞瀛愭潯浠剁粍
- </Button>
- <Button
- danger
- size="small"
- type="link"
- @click="removeConditionGroup"
- >
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎鏉′欢缁�
- </Button>
- </div>
- </div>
-
- <!-- 瀛愭潯浠剁粍鍒楄〃 -->
- <div
- v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
- class="space-y-[16px]"
- >
- <!-- 閫昏緫鍏崇郴璇存槑 -->
- <div class="relative">
- <div
- v-for="(subGroup, subGroupIndex) in trigger.conditionGroups"
- :key="getStableObjectKey(subGroup)"
- class="relative"
- >
- <!-- 瀛愭潯浠剁粍瀹瑰櫒锛堟鑹蹭富棰橈級 -->
- <div
- class="rounded-[8px] border border-orange-200 bg-orange-50/40 shadow-sm transition-shadow hover:shadow-md dark:border-orange-900/40 dark:bg-orange-950/20"
- >
- <div
- class="px-[16px] py-[10px] rounded-t-[8px] flex items-center justify-between border-b border-orange-200 bg-gradient-to-r from-orange-50 to-yellow-50 dark:border-orange-900/40 dark:from-orange-950/30 dark:to-yellow-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-orange-700 dark:text-orange-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-orange-500 font-bold text-white"
- >
- {{ subGroupIndex + 1 }}
- </div>
- <span>瀛愭潯浠剁粍 {{ subGroupIndex + 1 }}</span>
- </div>
- <Tag color="warning" class="font-medium">
- 缁勫唴鏉′欢涓恒�屼笖銆嶅叧绯�
- </Tag>
- <Tag color="default">
- {{ (subGroup as any)?.length || 0 }} 涓潯浠�
- </Tag>
- </div>
- <Button
- danger
- size="small"
- type="link"
- class="hover:bg-red-50"
- @click="removeSubGroup(subGroupIndex)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎缁�
- </Button>
- </div>
-
- <SubConditionGroupConfig
- :model-value="subGroup as any"
- @update:model-value="
- (value) => updateSubGroup(subGroupIndex, value)
- "
- :trigger-type="trigger.type as number"
- :max-conditions="maxConditionsPerGroup"
- />
- </div>
-
- <!-- 瀛愭潯浠剁粍闂寸殑'鎴�'杩炴帴绗� -->
- <div
- v-if="subGroupIndex < trigger.conditionGroups!.length - 1"
- class="py-[12px] flex items-center justify-center"
- >
- <div class="gap-[8px] flex items-center">
- <!-- 杩炴帴绾� -->
- <div
- class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
- ></div>
- <!-- 鎴栨爣绛� -->
- <div
- class="px-[14px] py-[3px] rounded-full border border-orange-300 bg-orange-100 dark:border-orange-800 dark:bg-orange-950/50"
- >
- <span
- class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"
- >
- 鎴�
- </span>
- </div>
- <!-- 杩炴帴绾� -->
- <div
- class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
- ></div>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 绌虹姸鎬侊紙姗欒壊涓婚锛� -->
- <div
- v-else
- class="p-[24px] rounded-[8px] border-2 border-dashed border-orange-200 bg-orange-50/40 text-center dark:border-orange-900/40 dark:bg-orange-950/10"
- >
- <div class="gap-[10px] flex flex-col items-center">
- <IconifyIcon
- icon="lucide:plus"
- class="text-[28px] text-orange-400 dark:text-orange-300"
- />
- <div class="text-orange-600 dark:text-orange-300">
- <p class="text-[13px] font-medium mb-[2px]">鏆傛棤瀛愭潯浠剁粍</p>
- <p class="text-[12px]">鐐瑰嚮涓婃柟銆屾坊鍔犲瓙鏉′欢缁勩�嶆寜閽紑濮嬮厤缃�</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/main-condition-inner-config.vue b/src/views/iot/rule/scene/form/configs/main-condition-inner-config.vue
deleted file mode 100644
index ea33d7f..0000000
--- a/src/views/iot/rule/scene/form/configs/main-condition-inner-config.vue
+++ /dev/null
@@ -1,367 +0,0 @@
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { computed, ref } from 'vue';
-
-import {
- getTriggerTypeLabel,
- IoTDeviceStatusEnum,
- IotRuleSceneTriggerConditionParameterOperatorEnum,
- IotRuleSceneTriggerTypeEnum,
- triggerTypeOptions,
-} from '../../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import { Col, Form, Input, Row, Select } from 'ant-design-vue';
-
-import JsonParamsInput from '../inputs/json-params-input.vue';
-import ValueInput from '../inputs/value-input.vue';
-import DeviceSelector from '../selectors/device-selector.vue';
-import OperatorSelector from '../selectors/operator-selector.vue';
-import ProductSelector from '../selectors/product-selector.vue';
-import PropertySelector from '../selectors/property-selector.vue';
-
-/** 涓绘潯浠跺唴閮ㄩ厤缃粍浠� */
-defineOptions({ name: 'MainConditionInnerConfig' });
-
-const props = defineProps<{
- modelValue: RuleSceneApi.Trigger;
- triggerType: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.Trigger): void;
- (e: 'triggerTypeChange', value: number): void;
-}>();
-
-/** 鑾峰彇璁惧鐘舵�佸彉鏇撮�夐」锛堢敤浜庤Е鍙戝櫒閰嶇疆锛� */
-const deviceStatusChangeOptions = [
- {
- label: IoTDeviceStatusEnum.ONLINE.label,
- value: IoTDeviceStatusEnum.ONLINE.value,
- },
- {
- label: IoTDeviceStatusEnum.OFFLINE.label,
- value: IoTDeviceStatusEnum.OFFLINE.value,
- },
-];
-
-const condition = useVModel(props, 'modelValue', emit);
-const propertyType = ref(''); // 灞炴�х被鍨�
-const propertyConfig = ref<any>(null); // 灞炴�ч厤缃�
-
-// 璁$畻灞炴�э細鏄惁涓鸿澶囧睘鎬цЕ鍙戝櫒
-const isDevicePropertyTrigger = computed(() => {
- return (
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST ||
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
- );
-});
-
-// 璁$畻灞炴�э細鏄惁涓鸿澶囩姸鎬佽Е鍙戝櫒
-const isDeviceStatusTrigger = computed(() => {
- return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE;
-});
-
-// 璁$畻灞炴�э細鏄惁闇�瑕佹搷浣滅閫夋嫨锛堟湇鍔¤皟鐢ㄥ拰浜嬩欢涓婃姤涓嶉渶瑕佹搷浣滅锛�
-const needsOperatorSelector = computed(() => {
- const noOperatorTriggerTypes = [
- IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
- IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
- ] as number[];
- return !noOperatorTriggerTypes.includes(props.triggerType);
-});
-
-// 璁$畻灞炴�э細鏄惁闇�瑕佸鍒楀竷灞�锛堟湇鍔¤皟鐢ㄥ拰浜嬩欢涓婃姤涓嶉渶瑕佹搷浣滅鍒楋紝鎵�浠ュ�艰緭鍏ュ垪鏇村锛�
-const isWideValueColumn = computed(() => {
- const wideColumnTriggerTypes = [
- IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
- IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
- ] as number[];
- return wideColumnTriggerTypes.includes(props.triggerType);
-});
-
-// 璁$畻灞炴�э細鍊艰緭鍏ュ瓧娈电殑鏍囩鏂囨湰
-const valueInputLabel = computed(() => {
- return props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
- ? '鏈嶅姟鍙傛暟'
- : '姣旇緝鍊�';
-});
-
-// 璁$畻灞炴�э細鏈嶅姟閰嶇疆 - 鐢ㄤ簬 JsonParamsInput
-const serviceConfig = computed(() => {
- if (
- propertyConfig.value &&
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
- ) {
- return {
- service: {
- name: propertyConfig.value.name || '鏈嶅姟',
- inputParams: propertyConfig.value.inputParams || [],
- },
- };
- }
- return undefined;
-});
-
-/**
- * 鏇存柊鏉′欢瀛楁
- * @param field 瀛楁鍚�
- * @param value 瀛楁鍊�
- */
-function updateConditionField(field: any, value: any) {
- Object.assign(condition.value, { [field]: value });
-}
-
-/**
- * 澶勭悊瑙﹀彂鍣ㄧ被鍨嬪彉鍖栦簨浠�
- * @param type 瑙﹀彂鍣ㄧ被鍨�
- */
-function handleTriggerTypeChange(type: number) {
- emit('triggerTypeChange', type);
-}
-
-/** 澶勭悊浜у搧鍙樺寲浜嬩欢 */
-function handleProductChange() {
- const trigger = condition.value;
- trigger.deviceId = undefined;
- trigger.identifier = '';
- trigger.operator = undefined;
- // 涓绘潯浠舵瘮杈冨�煎瓧娈垫槸 Trigger.value锛堜笉鏄� TriggerCondition.param锛�
- trigger.value = '';
- propertyType.value = '';
- propertyConfig.value = null;
-}
-
-/** 澶勭悊璁惧鍙樺寲浜嬩欢 */
-function handleDeviceChange() {
- const trigger = condition.value;
- trigger.identifier = '';
- trigger.operator = undefined;
- trigger.value = '';
- propertyType.value = '';
- propertyConfig.value = null;
-}
-
-/**
- * 澶勭悊灞炴�у彉鍖栦簨浠�
- * @param propertyInfo 灞炴�т俊鎭璞�
- */
-function handlePropertyChange(propertyInfo: any) {
- if (propertyInfo) {
- propertyType.value = propertyInfo.type;
- propertyConfig.value = propertyInfo.config;
-
- // 瀵逛簬浜嬩欢涓婃姤鍜屾湇鍔¤皟鐢紝鑷姩璁剧疆鎿嶄綔绗︿负 '='
- if (
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
- props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
- ) {
- condition.value.operator =
- IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value;
- }
- }
-}
-</script>
-
-<template>
- <div class="space-y-4">
- <!-- 瑙﹀彂浜嬩欢绫诲瀷閫夋嫨 -->
- <Form.Item label="瑙﹀彂浜嬩欢绫诲瀷" required>
- <Select
- :value="triggerType"
- @change="(value: any) => handleTriggerTypeChange(value)"
- placeholder="璇烽�夋嫨瑙﹀彂浜嬩欢绫诲瀷"
- class="w-full"
- >
- <Select.Option
- v-for="option in triggerTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
-
- <!-- 璁惧灞炴�ф潯浠堕厤缃� -->
- <div v-if="isDevicePropertyTrigger" class="space-y-4">
- <!-- 浜у搧璁惧閫夋嫨 -->
- <Row :gutter="16">
- <Col :span="12">
- <Form.Item label="浜у搧" required>
- <ProductSelector
- :model-value="condition.productId"
- @update:model-value="
- (value) => updateConditionField('productId', value)
- "
- @change="handleProductChange"
- />
- </Form.Item>
- </Col>
- <Col :span="12">
- <Form.Item label="璁惧" required>
- <DeviceSelector
- :model-value="condition.deviceId"
- @update:model-value="
- (value) => updateConditionField('deviceId', value)
- "
- :product-id="condition.productId"
- @change="handleDeviceChange"
- />
- </Form.Item>
- </Col>
- </Row>
-
- <!-- 灞炴�ч厤缃� -->
- <Row :gutter="16">
- <!-- 灞炴��/浜嬩欢/鏈嶅姟閫夋嫨 -->
- <Col :span="6">
- <Form.Item label="鐩戞帶椤�" required>
- <PropertySelector
- :model-value="condition.identifier"
- @update:model-value="
- (value) => updateConditionField('identifier', value)
- "
- :trigger-type="triggerType"
- :product-id="condition.productId"
- :device-id="condition.deviceId"
- @change="handlePropertyChange"
- />
- </Form.Item>
- </Col>
-
- <!-- 鎿嶄綔绗﹂�夋嫨 - 鏈嶅姟璋冪敤鍜屼簨浠朵笂鎶ヤ笉闇�瑕佹搷浣滅 -->
- <Col v-if="needsOperatorSelector" :span="6">
- <Form.Item label="鎿嶄綔绗�" required>
- <OperatorSelector
- :model-value="condition.operator"
- @update:model-value="
- (value) => updateConditionField('operator', value)
- "
- :property-type="propertyType"
- />
- </Form.Item>
- </Col>
-
- <!-- 鍊艰緭鍏� -->
- <Col :span="isWideValueColumn ? 18 : 12">
- <Form.Item :label="valueInputLabel" required>
- <!-- 鏈嶅姟璋冪敤鍙傛暟閰嶇疆 -->
- <JsonParamsInput
- v-if="
- triggerType ===
- IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE
- "
- v-model="condition.value"
- type="service"
- :config="serviceConfig as any"
- placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勬湇鍔″弬鏁�"
- />
- <!-- 浜嬩欢涓婃姤鍙傛暟閰嶇疆锛氭簮椤圭洰鍏佽鏍囬噺鍊兼垨鐣欑┖琛ㄧず浜嬩欢鍙戠敓鍗冲尮閰� -->
- <Input
- v-else-if="
- triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
- "
- :value="condition.value"
- @update:value="(value) => updateConditionField('value', value)"
- placeholder="鐣欑┖鍒欎簨浠跺彂鐢熷嵆鍖归厤"
- />
- <!-- 鏅�氬�艰緭鍏� -->
- <ValueInput
- v-else
- :model-value="condition.value"
- @update:model-value="
- (value) => updateConditionField('value', value)
- "
- :property-type="propertyType"
- :operator="condition.operator"
- :property-config="propertyConfig"
- />
- </Form.Item>
- </Col>
- </Row>
- </div>
-
- <!-- 璁惧鐘舵�佹潯浠堕厤缃� -->
- <div v-else-if="isDeviceStatusTrigger" class="space-y-4">
- <!-- 璁惧鐘舵�佽Е鍙戝櫒浣跨敤绠�鍖栫殑閰嶇疆 -->
- <Row :gutter="16">
- <Col :span="12">
- <Form.Item label="浜у搧" required>
- <ProductSelector
- :model-value="condition.productId"
- @update:model-value="
- (value) => updateConditionField('productId', value)
- "
- @change="handleProductChange"
- />
- </Form.Item>
- </Col>
- <Col :span="12">
- <Form.Item label="璁惧" required>
- <DeviceSelector
- :model-value="condition.deviceId"
- @update:model-value="
- (value) => updateConditionField('deviceId', value)
- "
- :product-id="condition.productId"
- @change="handleDeviceChange"
- />
- </Form.Item>
- </Col>
- </Row>
- <Row :gutter="16">
- <Col :span="6">
- <Form.Item label="鎿嶄綔绗�" required>
- <Select
- :value="condition.operator"
- @change="(value: any) => updateConditionField('operator', value)"
- placeholder="璇烽�夋嫨鎿嶄綔绗�"
- class="w-full"
- >
- <Select.Option
- :value="
- IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value
- "
- >
- {{
- IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name
- }}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
- <Col :span="6">
- <Form.Item label="鍙傛暟" required>
- <Select
- :value="condition.value"
- @change="(value: any) => updateConditionField('value', value)"
- placeholder="璇烽�夋嫨鎿嶄綔绗�"
- class="w-full"
- >
- <Select.Option
- v-for="option in deviceStatusChangeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
- </Row>
- </div>
-
- <!-- 鍏朵粬瑙﹀彂绫诲瀷鐨勬彁绀� -->
- <div v-else class="py-5 text-center">
- <p class="mb-1 text-sm text-muted-foreground">
- 褰撳墠瑙﹀彂浜嬩欢绫诲瀷锛歿{ getTriggerTypeLabel(triggerType) }}
- </p>
- <p class="text-xs text-muted-foreground">
- 姝よЕ鍙戠被鍨嬫殏涓嶉渶瑕侀厤缃澶栨潯浠�
- </p>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/sub-condition-group-config.vue b/src/views/iot/rule/scene/form/configs/sub-condition-group-config.vue
deleted file mode 100644
index 0eb177c..0000000
--- a/src/views/iot/rule/scene/form/configs/sub-condition-group-config.vue
+++ /dev/null
@@ -1,171 +0,0 @@
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { computed, nextTick } from 'vue';
-
-import {
- IotRuleSceneTriggerConditionParameterOperatorEnum,
- IotRuleSceneTriggerConditionTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { getStableObjectKey } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button } from 'ant-design-vue';
-
-import ConditionConfig from './condition-config.vue';
-
-/** 瀛愭潯浠剁粍閰嶇疆缁勪欢 */
-defineOptions({ name: 'SubConditionGroupConfig' });
-
-const props = defineProps<{
- maxConditions?: number;
- modelValue: RuleSceneApi.TriggerCondition[];
- triggerType: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[]): void;
-}>();
-
-const subGroup = useVModel(props, 'modelValue', emit);
-
-const maxConditions = computed(() => props.maxConditions || 3); // 鏈�澶ф潯浠舵暟閲�
-
-/** 娣诲姞鏉′欢 */
-async function addCondition() {
- // 纭繚 subGroup.value 鏄竴涓暟缁�
- if (!subGroup.value) {
- subGroup.value = [];
- }
-
- // 妫�鏌ユ槸鍚﹁揪鍒版渶澶ф潯浠舵暟閲忛檺鍒�
- if (subGroup.value?.length >= maxConditions.value) {
- return;
- }
-
- const newCondition: RuleSceneApi.TriggerCondition = {
- type: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, // 榛樿涓鸿澶囧睘鎬�
- productId: undefined,
- deviceId: undefined,
- identifier: '',
- operator: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value, // 浣跨敤鏋氫妇榛樿鍊�
- param: '',
- };
-
- // 浣跨敤 nextTick 纭繚鍝嶅簲寮忔洿鏂板畬鎴愬悗鍐嶆坊鍔犳柊鏉′欢
- await nextTick();
- if (subGroup.value) {
- subGroup.value.push(newCondition);
- }
-}
-
-/**
- * 绉婚櫎鏉′欢
- * @param index 鏉′欢绱㈠紩
- */
-function removeCondition(index: number) {
- if (subGroup.value) {
- subGroup.value.splice(index, 1);
- }
-}
-
-/**
- * 鏇存柊鏉′欢
- * @param index 鏉′欢绱㈠紩
- * @param condition 鏉′欢瀵硅薄
- */
-function updateCondition(
- index: number,
- condition: RuleSceneApi.TriggerCondition,
-) {
- if (subGroup.value) {
- subGroup.value[index] = condition;
- }
-}
-</script>
-
-<template>
- <div class="p-4">
- <!-- 绌虹姸鎬� -->
- <div v-if="!subGroup || subGroup.length === 0" class="py-6 text-center">
- <div class="flex flex-col items-center gap-3">
- <IconifyIcon icon="lucide:plus" class="text-8 text-muted-foreground" />
- <div class="text-muted-foreground">
- <p class="mb-1 text-base font-bold">鏆傛棤鏉′欢</p>
- <p class="text-xs">鐐瑰嚮涓嬫柟鎸夐挳娣诲姞绗竴涓潯浠�</p>
- </div>
- <Button type="primary" @click="addCondition">
- <IconifyIcon icon="lucide:plus" />
- 娣诲姞鏉′欢
- </Button>
- </div>
- </div>
-
- <!-- 鏉′欢鍒楄〃 -->
- <div v-else class="space-y-4">
- <div
- v-for="(condition, conditionIndex) in subGroup"
- :key="getStableObjectKey(condition)"
- class="relative"
- >
- <!-- 鏉′欢閰嶇疆 -->
- <div
- class="rounded-[3px] bg-fill-color-blank border border-border shadow-sm"
- >
- <div
- class="rounded-t-1 bg-fill-color-blank flex items-center justify-between border-b border-border p-3"
- >
- <div class="flex items-center gap-2">
- <div
- class="flex size-5 items-center justify-center rounded-full bg-primary text-xs font-bold text-white"
- >
- {{ conditionIndex + 1 }}
- </div>
- <span class="text-base font-bold text-foreground">
- 鏉′欢 {{ conditionIndex + 1 }}
- </span>
- </div>
- <Button
- v-if="subGroup!.length > 1"
- danger
- size="small"
- type="link"
- class="hover:bg-red-50"
- @click="removeCondition(conditionIndex)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- </Button>
- </div>
-
- <div class="p-3">
- <ConditionConfig
- :model-value="condition"
- @update:model-value="
- (value: RuleSceneApi.TriggerCondition) =>
- updateCondition(conditionIndex, value)
- "
- :trigger-type="triggerType"
- />
- </div>
- </div>
- </div>
-
- <!-- 娣诲姞鏉′欢鎸夐挳 -->
- <div
- v-if="
- subGroup && subGroup.length > 0 && subGroup.length < maxConditions
- "
- class="py-4 text-center"
- >
- <Button type="primary" plain @click="addCondition">
- <IconifyIcon icon="lucide:plus" />
- 缁х画娣诲姞鏉′欢
- </Button>
- <span class="mt-2 block text-xs text-muted-foreground">
- 鏈�澶氬彲娣诲姞 {{ maxConditions }} 涓潯浠�
- </span>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/configs/timer-condition-group-config.vue b/src/views/iot/rule/scene/form/configs/timer-condition-group-config.vue
deleted file mode 100644
index 1646577..0000000
--- a/src/views/iot/rule/scene/form/configs/timer-condition-group-config.vue
+++ /dev/null
@@ -1,192 +0,0 @@
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { nextTick } from 'vue';
-
-import { IotRuleSceneTriggerTypeEnum } from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { getStableObjectKey } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Tag } from 'ant-design-vue';
-
-import SubConditionGroupConfig from './sub-condition-group-config.vue';
-
-const props = defineProps<{
- modelValue?: RuleSceneApi.TriggerCondition[][];
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.TriggerCondition[][]): void;
-}>();
-
-const conditionGroups = useVModel(props, 'modelValue', emit);
-
-const maxGroups = 3; // 鏈�澶� 3 涓潯浠剁粍
-const maxConditionsPerGroup = 3; // 姣忕粍鏈�澶� 3 涓潯浠�
-
-/** 娣诲姞鏉′欢缁� */
-async function addConditionGroup() {
- if (!conditionGroups.value) {
- conditionGroups.value = [];
- }
- if (conditionGroups.value.length >= maxGroups) {
- return;
- }
- await nextTick();
- if (conditionGroups.value) {
- conditionGroups.value.push([]);
- }
-}
-
-/** 绉婚櫎鏉′欢缁� */
-function removeConditionGroup(index: number) {
- if (conditionGroups.value) {
- conditionGroups.value.splice(index, 1);
- }
-}
-
-/** 鏇存柊鏉′欢缁� */
-function updateConditionGroup(
- index: number,
- group: RuleSceneApi.TriggerCondition[],
-) {
- if (conditionGroups.value) {
- conditionGroups.value[index] = group;
- }
-}
-</script>
-
-<template>
- <div class="space-y-[16px]">
- <!-- 鏉′欢缁勫鍣ㄥご閮紙钃濊壊涓婚锛� -->
- <div
- class="px-[16px] py-[10px] rounded-[6px] flex items-center justify-between border border-blue-200 bg-gradient-to-r from-blue-50 to-cyan-50 dark:border-blue-900/40 dark:from-blue-950/30 dark:to-cyan-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-blue-700 dark:text-blue-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-blue-500 font-bold text-white"
- >
- 缁�
- </div>
- <span>闄勫姞鏉′欢缁�</span>
- </div>
- <Tag color="processing">瀹氭椂瑙﹀彂鏃堕渶婊¤冻浠ヤ笅鏉′欢</Tag>
- <Tag color="warning">
- {{ conditionGroups?.length || 0 }} 涓瓙鏉′欢缁�
- </Tag>
- </div>
- <Button
- type="primary"
- size="small"
- :disabled="(conditionGroups?.length || 0) >= maxGroups"
- @click="addConditionGroup"
- >
- <IconifyIcon icon="lucide:plus" />
- 娣诲姞鏉′欢缁�
- </Button>
- </div>
-
- <!-- 鏉′欢缁勫垪琛� -->
- <div
- v-if="conditionGroups && conditionGroups.length > 0"
- class="space-y-[16px]"
- >
- <div
- v-for="(group, groupIndex) in conditionGroups"
- :key="getStableObjectKey(group)"
- class="relative"
- >
- <!-- 鏉′欢缁勫鍣紙姗欒壊涓婚锛� -->
- <div
- class="rounded-[8px] border border-orange-200 bg-orange-50/40 shadow-sm transition-shadow hover:shadow-md dark:border-orange-900/40 dark:bg-orange-950/20"
- >
- <div
- class="px-[16px] py-[10px] rounded-t-[8px] flex items-center justify-between border-b border-orange-200 bg-gradient-to-r from-orange-50 to-yellow-50 dark:border-orange-900/40 dark:from-orange-950/30 dark:to-yellow-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-orange-700 dark:text-orange-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-orange-500 font-bold text-white"
- >
- {{ groupIndex + 1 }}
- </div>
- <span>瀛愭潯浠剁粍 {{ groupIndex + 1 }}</span>
- </div>
- <Tag color="warning" class="font-medium">
- 缁勫唴鏉′欢涓恒�屼笖銆嶅叧绯�
- </Tag>
- <Tag color="default"> {{ group?.length || 0 }} 涓潯浠� </Tag>
- </div>
- <Button
- danger
- size="small"
- type="link"
- class="hover:bg-red-50"
- @click="removeConditionGroup(groupIndex)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎缁�
- </Button>
- </div>
-
- <SubConditionGroupConfig
- :model-value="group"
- :trigger-type="IotRuleSceneTriggerTypeEnum.TIMER"
- :max-conditions="maxConditionsPerGroup"
- @update:model-value="
- (value: RuleSceneApi.TriggerCondition[]) =>
- updateConditionGroup(groupIndex, value)
- "
- />
- </div>
-
- <!-- 鏉′欢缁勯棿鐨勩�屾垨銆嶈繛鎺ョ -->
- <div
- v-if="groupIndex < conditionGroups.length - 1"
- class="py-[12px] flex items-center justify-center"
- >
- <div class="gap-[8px] flex items-center">
- <div
- class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
- ></div>
- <div
- class="px-[14px] py-[3px] rounded-full border border-orange-300 bg-orange-100 dark:border-orange-800 dark:bg-orange-950/50"
- >
- <span
- class="text-[13px] font-semibold text-orange-600 dark:text-orange-300"
- >
- 鎴�
- </span>
- </div>
- <div
- class="w-[32px] h-[1px] bg-orange-300 dark:bg-orange-800"
- ></div>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 绌虹姸鎬侊紙钃濊壊涓婚锛� -->
- <div
- v-else
- class="p-[24px] rounded-[8px] border-2 border-dashed border-blue-200 bg-blue-50/40 text-center dark:border-blue-900/40 dark:bg-blue-950/10"
- >
- <div class="gap-[10px] flex flex-col items-center">
- <IconifyIcon
- icon="lucide:plus"
- class="text-[28px] text-blue-400 dark:text-blue-300"
- />
- <div class="text-blue-600 dark:text-blue-300">
- <p class="text-[13px] font-medium mb-[2px]">鏆傛棤闄勫姞鏉′欢</p>
- <p class="text-[12px]">瀹氭椂瑙﹀彂鏃跺皢鐩存帴鎵ц鍔ㄤ綔</p>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/inputs/json-params-input.vue b/src/views/iot/rule/scene/form/inputs/json-params-input.vue
deleted file mode 100644
index f9561bd..0000000
--- a/src/views/iot/rule/scene/form/inputs/json-params-input.vue
+++ /dev/null
@@ -1,582 +0,0 @@
-<!-- JSON鍙傛暟杈撳叆缁勪欢 - 閫氱敤鐗堟湰 -->
-<script setup lang="ts">
-import type { JsonParamsInputType } from '../../../../../../packages/constants/src';
-
-import { computed, nextTick, onMounted, ref, watch } from 'vue';
-
-import {
- IoTDataSpecsDataTypeEnum,
- JSON_PARAMS_EXAMPLE_VALUES,
- JSON_PARAMS_INPUT_CONSTANTS,
- JSON_PARAMS_INPUT_ICONS,
- JsonParamsInputTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { isEmptyVal } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Input, Popover, Tag } from 'ant-design-vue';
-
-/** JSON鍙傛暟杈撳叆缁勪欢 - 閫氱敤鐗堟湰 */
-defineOptions({ name: 'JsonParamsInput' });
-
-const props = withDefaults(defineProps<Props>(), {
- type: JsonParamsInputTypeEnum.SERVICE,
- placeholder: JSON_PARAMS_INPUT_CONSTANTS.PLACEHOLDER,
-});
-
-const emit = defineEmits<Emits>();
-
-interface JsonParamsConfig {
- // 鏈嶅姟閰嶇疆
- service?: {
- inputParams?: any[];
- name: string;
- };
- // 浜嬩欢閰嶇疆
- event?: {
- name: string;
- outputParams?: any[];
- };
- // 灞炴�ч厤缃�
- properties?: any[];
- // 鑷畾涔夐厤缃�
- custom?: {
- name: string;
- params: any[];
- };
-}
-
-interface Props {
- modelValue: string;
- config: JsonParamsConfig;
- type?: JsonParamsInputType;
- placeholder?: string;
-}
-
-interface Emits {
- (e: 'update:modelValue', value: string): void;
-}
-
-const localValue = useVModel(props, 'modelValue', emit, {
- defaultValue: '',
-});
-
-const paramsJson = ref(''); // JSON鍙傛暟瀛楃涓�
-const jsonError = ref(''); // JSON楠岃瘉閿欒淇℃伅
-
-// 璁$畻灞炴�э細鍙傛暟鍒楄〃
-const paramsList = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return props.config?.custom?.params || [];
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return props.config?.event?.outputParams || [];
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return props.config?.properties || [];
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return props.config?.service?.inputParams || [];
- }
- default: {
- return [];
- }
- }
-});
-
-// 璁$畻灞炴�э細鏍囬
-const title = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.CUSTOM(
- props.config?.custom?.name,
- );
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.EVENT(
- props.config?.event?.name,
- );
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.PROPERTY;
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.SERVICE(
- props.config?.service?.name,
- );
- }
- default: {
- return JSON_PARAMS_INPUT_CONSTANTS.TITLES.DEFAULT;
- }
- }
-});
-
-// 璁$畻灞炴�э細鏍囬鍥炬爣
-const titleIcon = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.CUSTOM;
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.EVENT;
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.PROPERTY;
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.SERVICE;
- }
- default: {
- return JSON_PARAMS_INPUT_ICONS.TITLE_ICONS.DEFAULT;
- }
- }
-});
-
-// 璁$畻灞炴�э細鍙傛暟鍥炬爣
-const paramsIcon = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.CUSTOM;
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.EVENT;
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.PROPERTY;
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.SERVICE;
- }
- default: {
- return JSON_PARAMS_INPUT_ICONS.PARAMS_ICONS.DEFAULT;
- }
- }
-});
-
-// 璁$畻灞炴�э細鍙傛暟鏍囩
-const paramsLabel = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.CUSTOM;
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.EVENT;
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.PROPERTY;
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.SERVICE;
- }
- default: {
- return JSON_PARAMS_INPUT_CONSTANTS.PARAMS_LABELS.DEFAULT;
- }
- }
-});
-
-// 璁$畻灞炴�э細绌虹姸鎬佹秷鎭�
-const emptyMessage = computed(() => {
- switch (props.type) {
- case JsonParamsInputTypeEnum.CUSTOM: {
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.CUSTOM;
- }
- case JsonParamsInputTypeEnum.EVENT: {
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.EVENT;
- }
- case JsonParamsInputTypeEnum.PROPERTY: {
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.PROPERTY;
- }
- case JsonParamsInputTypeEnum.SERVICE: {
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.SERVICE;
- }
- default: {
- return JSON_PARAMS_INPUT_CONSTANTS.EMPTY_MESSAGES.DEFAULT;
- }
- }
-});
-
-// 璁$畻灞炴�э細鏃犻厤缃秷鎭�
-// const noConfigMessage = computed(() => {
-// switch (props.type) {
-// case JsonParamsInputTypeEnum.CUSTOM: {
-// return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.CUSTOM;
-// }
-// case JsonParamsInputTypeEnum.EVENT: {
-// return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.EVENT;
-// }
-// case JsonParamsInputTypeEnum.PROPERTY: {
-// return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.PROPERTY;
-// }
-// case JsonParamsInputTypeEnum.SERVICE: {
-// return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.SERVICE;
-// }
-// default: {
-// return JSON_PARAMS_INPUT_CONSTANTS.NO_CONFIG_MESSAGES.DEFAULT;
-// }
-// }
-// });
-
-/**
- * 澶勭悊鍙傛暟鍙樺寲浜嬩欢
- *
- * 娉ㄦ剰锛氬繀椤诲湪鎵�鏈夋牎楠岋紙鍚堟硶 JSON / 蹇呴』鏄璞� / 蹇呭~鍙傛暟锛夐�氳繃鍚庡啀鍥炲啓
- * localValue锛屽惁鍒欑埗琛ㄥ崟浠呮牎楠岄潪绌烘椂浼氬厛鍐欏叆闈炴硶鍊煎啀鎻愮ず閿欒銆�
- */
-function handleParamsChange() {
- try {
- jsonError.value = '';
-
- if (!paramsJson.value.trim()) {
- localValue.value = '';
- return;
- }
-
- const parsed = JSON.parse(paramsJson.value);
-
- // 蹇呴』鏄璞�
- if (typeof parsed !== 'object' || parsed === null) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAMS_MUST_BE_OBJECT;
- return;
- }
-
- // 蹇呭~鍙傛暟鏍¢獙
- for (const param of paramsList.value) {
- const value = parsed[param.identifier];
- if (param.required && isEmptyVal(value)) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.PARAM_REQUIRED_ERROR(
- param.name,
- );
- return;
- }
- }
-
- // 鎵�鏈夋牎楠岄�氳繃鍚庢墠鍥炲啓鍒扮埗琛ㄥ崟
- localValue.value = paramsJson.value;
- } catch (error) {
- jsonError.value = JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_ERROR(
- error instanceof Error
- ? error.message
- : JSON_PARAMS_INPUT_CONSTANTS.UNKNOWN_ERROR,
- );
- }
-}
-
-/**
- * 蹇�熷~鍏呯ず渚嬫暟鎹�
- */
-function fillExampleJson() {
- paramsJson.value = generateExampleJson();
- handleParamsChange();
-}
-
-/**
- * 娓呯┖鍙傛暟
- */
-function clearParams() {
- paramsJson.value = '';
- localValue.value = '';
- jsonError.value = '';
-}
-
-/**
- * 鑾峰彇鍙傛暟绫诲瀷鍚嶇О
- * @param dataType 鏁版嵁绫诲瀷
- * @returns 绫诲瀷鍚嶇О
- */
-function getParamTypeName(dataType: string) {
- // 浣跨敤 constants.ts 涓凡鏈夌殑 getDataTypeName 鍑芥暟閫昏緫
- const typeMap: Record<string, string> = {
- [IoTDataSpecsDataTypeEnum.INT]: '鏁存暟',
- [IoTDataSpecsDataTypeEnum.FLOAT]: '娴偣鏁�',
- [IoTDataSpecsDataTypeEnum.DOUBLE]: '鍙岀簿搴�',
- [IoTDataSpecsDataTypeEnum.TEXT]: '瀛楃涓�',
- [IoTDataSpecsDataTypeEnum.BOOL]: '甯冨皵鍊�',
- [IoTDataSpecsDataTypeEnum.ENUM]: '鏋氫妇',
- [IoTDataSpecsDataTypeEnum.DATE]: '鏃ユ湡',
- [IoTDataSpecsDataTypeEnum.STRUCT]: '缁撴瀯浣�',
- [IoTDataSpecsDataTypeEnum.ARRAY]: '鏁扮粍',
- };
- return typeMap[dataType] || dataType;
-}
-
-/**
- * 鑾峰彇鍙傛暟绫诲瀷鏍囩鏍峰紡
- * @param dataType 鏁版嵁绫诲瀷
- * @returns 鏍囩鏍峰紡
- */
-function getParamTypeTag(dataType: string) {
- const tagMap: Record<string, string> = {
- [IoTDataSpecsDataTypeEnum.INT]: 'processing',
- [IoTDataSpecsDataTypeEnum.FLOAT]: 'success',
- [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success',
- [IoTDataSpecsDataTypeEnum.TEXT]: 'default',
- [IoTDataSpecsDataTypeEnum.BOOL]: 'warning',
- [IoTDataSpecsDataTypeEnum.ENUM]: 'error',
- [IoTDataSpecsDataTypeEnum.DATE]: 'processing',
- [IoTDataSpecsDataTypeEnum.STRUCT]: 'default',
- [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning',
- };
- return tagMap[dataType] || 'default';
-}
-
-/**
- * 鑾峰彇绀轰緥鍊�
- * @param param 鍙傛暟瀵硅薄
- * @returns 绀轰緥鍊�
- */
-function getExampleValue(param: any) {
- const exampleConfig: any =
- JSON_PARAMS_EXAMPLE_VALUES[param.dataType] ||
- JSON_PARAMS_EXAMPLE_VALUES.DEFAULT;
- return exampleConfig.display;
-}
-
-/**
- * 鐢熸垚绀轰緥JSON
- * @returns JSON瀛楃涓�
- */
-function generateExampleJson() {
- if (paramsList.value.length === 0) {
- return '{}';
- }
-
- const example: Record<string, any> = {};
- paramsList.value.forEach((param) => {
- const exampleConfig: any =
- JSON_PARAMS_EXAMPLE_VALUES[param.dataType] ||
- JSON_PARAMS_EXAMPLE_VALUES.DEFAULT;
- example[param.identifier] = exampleConfig.value;
- });
-
- return JSON.stringify(example, null, 2);
-}
-
-/**
- * 澶勭悊鏁版嵁鍥炴樉
- * @param value 鍊煎瓧绗︿覆
- */
-function handleDataDisplay(value: string) {
- if (!value || !value.trim()) {
- paramsJson.value = '';
- jsonError.value = '';
- return;
- }
-
- try {
- // 灏濊瘯瑙f瀽JSON锛屽鏋滄垚鍔熷垯鏍煎紡鍖�
- const parsed = JSON.parse(value);
- paramsJson.value = JSON.stringify(parsed, null, 2);
- jsonError.value = '';
- } catch {
- // 濡傛灉涓嶆槸鏈夋晥鐨凧SON锛岀洿鎺ヤ娇鐢ㄥ師瀛楃涓�
- paramsJson.value = value;
- jsonError.value = '';
- }
-}
-
-// 鐩戝惉澶栭儴鍊煎彉鍖栵紙缂栬緫妯″紡鏁版嵁鍥炴樉锛�
-watch(
- () => localValue.value,
- async (newValue, oldValue) => {
- // 閬垮厤寰幆鏇存柊
- if (newValue === oldValue) return;
-
- // 浣跨敤 nextTick 纭繚鍦ㄤ笅涓�涓� tick 涓鐞嗘暟鎹�
- await nextTick();
- if ((newValue || '') === paramsJson.value) {
- return;
- }
- handleDataDisplay(newValue || '');
- },
- { immediate: true },
-);
-
-// 缁勪欢鎸傝浇鍚庝篃灏濊瘯澶勭悊涓�娆℃暟鎹洖鏄�
-onMounted(async () => {
- await nextTick();
- if (localValue.value) {
- handleDataDisplay(localValue.value);
- }
-});
-
-// 鐩戝惉閰嶇疆鍙樺寲
-watch(
- () => props.config,
- (newConfig, oldConfig) => {
- // 鍙湁鍦ㄩ厤缃湡姝e彉鍖栨椂鎵嶆竻绌烘暟鎹�
- if (
- JSON.stringify(newConfig) !== JSON.stringify(oldConfig) && // 濡傛灉娌℃湁澶栭儴浼犲叆鐨勫�硷紝鎵嶆竻绌烘暟鎹�
- !localValue.value
- ) {
- paramsJson.value = '';
- jsonError.value = '';
- }
- },
-);
-</script>
-
-<template>
- <!-- 鍙傛暟閰嶇疆 -->
- <div class="w-full space-y-3">
- <!-- JSON 杈撳叆妗� -->
- <div class="relative">
- <Input.TextArea
- v-model:value="paramsJson"
- :rows="4"
- :placeholder="placeholder"
- :class="{ 'is-error': jsonError }"
- @input="handleParamsChange"
- />
- <!-- 鏌ョ湅璇︾粏绀轰緥寮瑰嚭灞� -->
- <div class="absolute right-2 top-2">
- <Popover
- placement="leftTop"
- :overlay-style="{ width: '450px' }"
- trigger="click"
- :arrow="true"
- overlay-class-name="json-params-detail-popover"
- >
- <template #content>
- <!-- 寮瑰嚭灞傚唴瀹� -->
- <div class="json-params-detail-content">
- <div class="mb-4 flex items-center gap-2">
- <IconifyIcon :icon="titleIcon" class="text-lg text-primary" />
- <span class="text-base font-bold text-foreground">
- {{ title }}
- </span>
- </div>
-
- <div class="space-y-4">
- <!-- 鍙傛暟鍒楄〃 -->
- <div v-if="paramsList.length > 0">
- <div class="mb-2 flex items-center gap-2">
- <IconifyIcon
- :icon="paramsIcon"
- class="text-base text-primary"
- />
- <span class="text-base font-bold text-foreground">
- {{ paramsLabel }}
- </span>
- </div>
- <div class="ml-6 space-y-2">
- <div
- v-for="param in paramsList"
- :key="param.identifier"
- class="flex items-center justify-between rounded-lg bg-card p-2"
- >
- <div class="flex-1">
- <div class="text-base font-bold text-foreground">
- {{ param.name }}
- <Tag v-if="param.required" color="error" class="ml-1">
- {{ JSON_PARAMS_INPUT_CONSTANTS.REQUIRED_TAG }}
- </Tag>
- </div>
- <div class="text-xs text-muted-foreground">
- {{ param.identifier }}
- </div>
- </div>
- <div class="flex items-center gap-2">
- <Tag :color="getParamTypeTag(param.dataType)">
- {{ getParamTypeName(param.dataType) }}
- </Tag>
- <span class="text-xs text-muted-foreground">
- {{ getExampleValue(param) }}
- </span>
- </div>
- </div>
- </div>
-
- <div class="ml-6 mt-3">
- <div class="mb-1 text-xs text-muted-foreground">
- {{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
- </div>
- <pre
- class="border-l-[3px] overflow-x-auto rounded-lg border-primary bg-card p-3 text-sm text-foreground"
- >
- <code>{{ generateExampleJson() }}</code>
- </pre>
- </div>
- </div>
-
- <!-- 鏃犲弬鏁版彁绀� -->
- <div v-else>
- <div class="py-4 text-center">
- <p class="text-sm text-muted-foreground">
- {{ emptyMessage }}
- </p>
- </div>
- </div>
- </div>
- </div>
- </template>
- <Button
- type="link"
- shape="circle"
- size="small"
- :title="JSON_PARAMS_INPUT_CONSTANTS.VIEW_EXAMPLE_TITLE"
- >
- <IconifyIcon icon="ep:info-filled" />
- </Button>
- </Popover>
- </div>
- </div>
-
- <!-- 楠岃瘉鐘舵�佸拰閿欒鎻愮ず -->
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-2">
- <IconifyIcon
- :icon="
- jsonError
- ? JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.ERROR
- : JSON_PARAMS_INPUT_ICONS.STATUS_ICONS.SUCCESS
- "
- :class="jsonError ? 'text-danger' : 'text-success'"
- class="text-sm"
- />
- <span
- :class="jsonError ? 'text-danger' : 'text-success'"
- class="text-xs"
- >
- {{ jsonError || JSON_PARAMS_INPUT_CONSTANTS.JSON_FORMAT_CORRECT }}
- </span>
- </div>
-
- <!-- 蹇�熷~鍏呮寜閽� -->
- <div v-if="paramsList.length > 0" class="flex items-center gap-2">
- <span class="text-xs text-muted-foreground">
- {{ JSON_PARAMS_INPUT_CONSTANTS.QUICK_FILL_LABEL }}
- </span>
- <Button size="small" type="primary" plain @click="fillExampleJson">
- {{ JSON_PARAMS_INPUT_CONSTANTS.EXAMPLE_DATA_BUTTON }}
- </Button>
- <Button size="small" danger type="primary" @click="clearParams">
- {{ JSON_PARAMS_INPUT_CONSTANTS.CLEAR_BUTTON }}
- </Button>
- </div>
- </div>
- </div>
-</template>
-
-<style scoped>
-/* 寮瑰嚭灞傚唴瀹规牱寮� */
-.json-params-detail-content {
- padding: 4px 0;
-}
-
-/* 寮瑰嚭灞傝嚜瀹氫箟鏍峰紡 */
-:global(.json-params-detail-popover) {
- max-width: 500px !important;
-}
-
-:global(.json-params-detail-popover .ant-popover__content) {
- padding: 16px !important;
-}
-
-/* JSON 浠g爜鍧楁牱寮� */
-.json-params-detail-content pre {
- max-height: 200px;
- overflow-y: auto;
-}
-</style>
diff --git a/src/views/iot/rule/scene/form/inputs/value-input.vue b/src/views/iot/rule/scene/form/inputs/value-input.vue
deleted file mode 100644
index 0cb6ea0..0000000
--- a/src/views/iot/rule/scene/form/inputs/value-input.vue
+++ /dev/null
@@ -1,338 +0,0 @@
-<!-- 鍊艰緭鍏ョ粍浠� -->
-<script setup lang="ts">
-import { computed, ref, watch } from 'vue';
-
-import {
- IoTDataSpecsDataTypeEnum,
- IotRuleSceneTriggerConditionParameterOperatorEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- DatePicker,
- Input,
- InputNumber,
- Select,
- Tag,
- Tooltip,
-} from 'ant-design-vue';
-
-/** 鍊艰緭鍏ョ粍浠� */
-defineOptions({ name: 'ValueInput' });
-
-const props = defineProps<Props>();
-
-const emit = defineEmits<Emits>();
-
-interface Props {
- modelValue?: string;
- propertyType?: string;
- operator?: string;
- propertyConfig?: any;
-}
-
-interface Emits {
- (e: 'update:modelValue', value: string): void;
-}
-
-const localValue = useVModel(props, 'modelValue', emit, {
- defaultValue: '',
-});
-
-const rangeStart = ref(''); // 鑼冨洿寮�濮嬪��
-const rangeEnd = ref(''); // 鑼冨洿缁撴潫鍊�
-const dateValue = ref(''); // 鏃ユ湡鍊�
-const numberValue = ref<number>(); // 鏁板瓧鍊�
-
-/** 璁$畻灞炴�э細鏋氫妇閫夐」 */
-const enumOptions = computed(() => {
- if (props.propertyConfig?.enum) {
- return props.propertyConfig.enum.map((item: any) => ({
- label: item.name || item.label || item.value,
- value: item.value,
- }));
- }
- return [];
-});
-
-/** 璁$畻灞炴�э細鍒楄〃棰勮 */
-const listPreview = computed(() => {
- if (
- props.operator ===
- IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value &&
- localValue.value
- ) {
- return localValue.value
- .split(',')
- .map((item) => item.trim())
- .filter(Boolean);
- }
- return [];
-});
-
-/** 鍒ゆ柇鏄惁涓烘暟瀛楃被鍨� */
-function isNumericType() {
- return [
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.INT,
- ].includes((props.propertyType || '') as any);
-}
-
-/** 鑾峰彇杈撳叆妗嗙被鍨� */
-function getInputType() {
- switch (props.propertyType) {
- case IoTDataSpecsDataTypeEnum.DOUBLE:
- case IoTDataSpecsDataTypeEnum.FLOAT:
- case IoTDataSpecsDataTypeEnum.INT: {
- return 'number';
- }
- default: {
- return 'text';
- }
- }
-}
-
-/** 鑾峰彇鍗犱綅绗︽枃鏈� */
-function getPlaceholder() {
- const typeMap: Record<string, string> = {
- [IoTDataSpecsDataTypeEnum.TEXT]: '璇疯緭鍏ュ瓧绗︿覆',
- [IoTDataSpecsDataTypeEnum.INT]: '璇疯緭鍏ユ暣鏁�',
- [IoTDataSpecsDataTypeEnum.FLOAT]: '璇疯緭鍏ユ诞鐐规暟',
- [IoTDataSpecsDataTypeEnum.DOUBLE]: '璇疯緭鍏ュ弻绮惧害鏁�',
- [IoTDataSpecsDataTypeEnum.STRUCT]: '璇疯緭鍏� JSON 鏍煎紡鏁版嵁',
- [IoTDataSpecsDataTypeEnum.ARRAY]: '璇疯緭鍏ユ暟缁勬牸寮忔暟鎹�',
- };
- return typeMap[props.propertyType || ''] || '璇疯緭鍏ュ��';
-}
-
-/** 鑾峰彇鏁板瓧绮惧害 */
-function getPrecision() {
- return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 0 : 2;
-}
-
-/** 鑾峰彇鏁板瓧姝ラ暱 */
-function getStep() {
- return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 1 : 0.1;
-}
-
-/** 鑾峰彇鏈�灏忓�� */
-function getMin() {
- return props.propertyConfig?.min || undefined;
-}
-
-/** 鑾峰彇鏈�澶у�� */
-function getMax() {
- return props.propertyConfig?.max || undefined;
-}
-
-/** 澶勭悊鑼冨洿鍙樺寲浜嬩欢 */
-function handleRangeChange() {
- localValue.value =
- rangeStart.value && rangeEnd.value
- ? `${rangeStart.value},${rangeEnd.value}`
- : '';
-}
-
-/** 澶勭悊鏃ユ湡鍙樺寲浜嬩欢 */
-function handleDateChange(value: any) {
- localValue.value = value || '';
-}
-
-/** 澶勭悊鏁板瓧鍙樺寲浜嬩欢 锛汭nputNumber 鍥炶皟鍊肩被鍨嬩负 ValueType锛坰tring | number锛� */
-function handleNumberChange(value: any) {
- localValue.value = value === null ? '' : String(value);
-}
-
-/** 鏍规嵁澶栭儴鍊煎悓姝ュ唴閮ㄨ緭鍏ユ�� */
-function syncInternalValue(value = '') {
- const normalized = value;
- if (
- props.operator ===
- IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value
- ) {
- const [start = '', end = ''] = normalized.split(',');
- rangeStart.value = start;
- rangeEnd.value = end;
- numberValue.value = undefined;
- dateValue.value = '';
- return;
- }
- rangeStart.value = '';
- rangeEnd.value = '';
- if (props.propertyType === IoTDataSpecsDataTypeEnum.DATE) {
- dateValue.value = normalized;
- numberValue.value = undefined;
- return;
- }
- if (isNumericType()) {
- const parsed = Number(normalized);
- numberValue.value =
- normalized === '' || Number.isNaN(parsed) ? undefined : parsed;
- dateValue.value = '';
- return;
- }
- numberValue.value = undefined;
- dateValue.value = '';
-}
-
-/** 鐩戝惉鎿嶄綔绗﹀彉鍖� */
-watch(
- () => props.operator,
- (_operator, oldOperator) => {
- if (oldOperator === undefined) {
- syncInternalValue(props.modelValue);
- return;
- }
- localValue.value = '';
- rangeStart.value = '';
- rangeEnd.value = '';
- dateValue.value = '';
- numberValue.value = undefined;
- },
-);
-
-watch(
- () => [props.modelValue, props.propertyType] as const,
- ([value]) => syncInternalValue(value),
- { immediate: true },
-);
-</script>
-
-<template>
- <div class="w-full min-w-0">
- <!-- 甯冨皵鍊奸�夋嫨 -->
- <Select
- v-if="propertyType === IoTDataSpecsDataTypeEnum.BOOL"
- v-model:value="localValue"
- placeholder="璇烽�夋嫨甯冨皵鍊�"
- class="w-full!"
- >
- <Select.Option value="true">鐪� (true)</Select.Option>
- <Select.Option value="false">鍋� (false)</Select.Option>
- </Select>
-
- <!-- 鏋氫妇鍊奸�夋嫨 -->
- <Select
- v-else-if="
- propertyType === IoTDataSpecsDataTypeEnum.ENUM && enumOptions.length > 0
- "
- v-model:value="localValue"
- placeholder="璇烽�夋嫨鏋氫妇鍊�"
- class="w-full!"
- >
- <Select.Option
- v-for="option in enumOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
-
- <!-- 鑼冨洿杈撳叆 (between 鎿嶄綔绗�) -->
- <div
- v-else-if="
- operator ===
- IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value
- "
- class="w-full! flex items-center gap-2"
- >
- <Input
- v-model:value="rangeStart"
- :type="getInputType()"
- placeholder="鏈�灏忓��"
- @input="handleRangeChange"
- class="min-w-0 flex-1"
- style="width: auto !important"
- />
- <span class="whitespace-nowrap text-xs text-muted-foreground"> 鑷� </span>
- <Input
- v-model:value="rangeEnd"
- :type="getInputType()"
- placeholder="鏈�澶у��"
- @input="handleRangeChange"
- class="min-w-0 flex-1"
- />
- </div>
-
- <!-- 鍒楄〃杈撳叆 (in 鎿嶄綔绗�) -->
- <div
- v-else-if="
- operator === IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value
- "
- class="w-full!"
- >
- <Input
- v-model:value="localValue"
- placeholder="璇疯緭鍏ュ�煎垪琛紝鐢ㄩ�楀彿鍒嗛殧"
- class="w-full!"
- >
- <template #suffix>
- <Tooltip content="澶氫釜鍊肩敤閫楀彿鍒嗛殧锛屽锛�1,2,3" placement="top">
- <IconifyIcon
- icon="ep:question-filled"
- class="cursor-help text-gray-400"
- />
- </Tooltip>
- </template>
- </Input>
- <div
- v-if="listPreview.length > 0"
- class="mt-2 flex flex-wrap items-center gap-1"
- >
- <span class="text-xs text-muted-foreground"> 瑙f瀽缁撴灉锛� </span>
- <Tag v-for="(item, index) in listPreview" :key="index" class="m-0">
- {{ item }}
- </Tag>
- </div>
- </div>
-
- <!-- 鏃ユ湡鏃堕棿杈撳叆 -->
- <DatePicker
- v-else-if="propertyType === IoTDataSpecsDataTypeEnum.DATE"
- v-model:value="dateValue"
- type="datetime"
- placeholder="璇烽�夋嫨鏃ユ湡鏃堕棿"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- @change="handleDateChange"
- class="w-full!"
- />
-
- <!-- 鏁板瓧杈撳叆 -->
- <InputNumber
- v-else-if="isNumericType()"
- v-model:value="numberValue"
- :precision="getPrecision()"
- :step="getStep()"
- :min="getMin()"
- :max="getMax()"
- placeholder="璇疯緭鍏ユ暟鍊�"
- @change="handleNumberChange"
- class="w-full!"
- />
-
- <!-- 鏂囨湰杈撳叆 -->
- <Input
- v-else
- v-model:value="localValue"
- :type="getInputType()"
- :placeholder="getPlaceholder()"
- class="w-full!"
- >
- <template #suffix>
- <Tooltip
- v-if="propertyConfig?.unit"
- :content="`鍗曚綅锛�${propertyConfig.unit}`"
- placement="top"
- >
- <span class="px-1 text-xs text-muted-foreground">
- {{ propertyConfig.unit }}
- </span>
- </Tooltip>
- </template>
- </Input>
- </div>
-</template>
diff --git a/src/views/iot/rule/scene/form/sections/action-section.vue b/src/views/iot/rule/scene/form/sections/action-section.vue
deleted file mode 100644
index e4f02da..0000000
--- a/src/views/iot/rule/scene/form/sections/action-section.vue
+++ /dev/null
@@ -1,295 +0,0 @@
-<!-- 鎵ц鍣ㄩ厤缃粍浠� -->
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import {
- getActionTypeLabel,
- getActionTypeOptions,
- IotRuleSceneActionTypeEnum,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { getStableObjectKey } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Card, Empty, Form, Select, Tag } from 'ant-design-vue';
-
-import AlertConfig from '../configs/alert-config.vue';
-import DeviceControlConfig from '../configs/device-control-config.vue';
-
-/** 鎵ц鍣ㄩ厤缃粍浠� */
-defineOptions({ name: 'ActionSection' });
-
-const props = defineProps<{
- actions: RuleSceneApi.Action[];
-}>();
-
-const emit = defineEmits<{
- (e: 'update:actions', value: RuleSceneApi.Action[]): void;
-}>();
-
-const actions = useVModel(props, 'actions', emit);
-
-/** 鑾峰彇鎵ц鍣ㄦ爣绛鹃鑹诧紙antd Tag `color`锛� */
-function getActionTypeColor(
- type: number,
-): 'default' | 'error' | 'processing' | 'success' | 'warning' {
- const actionTypeTags: Record<
- number,
- 'default' | 'error' | 'processing' | 'success' | 'warning'
- > = {
- [IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET]: 'processing',
- [IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE]: 'success',
- [IotRuleSceneActionTypeEnum.ALERT_TRIGGER]: 'error',
- [IotRuleSceneActionTypeEnum.ALERT_RECOVER]: 'warning',
- } as const;
- return actionTypeTags[type] || 'default';
-}
-
-/** 鍒ゆ柇鏄惁涓鸿澶囨墽琛屽櫒绫诲瀷 */
-function isDeviceAction(type: number): boolean {
- const deviceActionTypes = [
- IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET,
- IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE,
- ] as number[];
- return deviceActionTypes.includes(type);
-}
-
-/** 鍒ゆ柇鏄惁涓哄憡璀︽墽琛屽櫒绫诲瀷 */
-function isAlertAction(type: number): boolean {
- const alertActionTypes = [
- IotRuleSceneActionTypeEnum.ALERT_TRIGGER,
- IotRuleSceneActionTypeEnum.ALERT_RECOVER,
- ] as number[];
- return alertActionTypes.includes(type);
-}
-
-/**
- * 鍒涘缓榛樿鐨勬墽琛屽櫒鏁版嵁
- * @returns 榛樿鎵ц鍣ㄥ璞�
- */
-function createDefaultActionData(): RuleSceneApi.Action {
- return {
- type: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, // 榛樿涓鸿澶囧睘鎬ц缃�
- productId: undefined,
- deviceId: undefined,
- identifier: undefined, // 鐗╂ā鍨嬫爣璇嗙锛堟湇鍔¤皟鐢ㄦ椂浣跨敤锛�
- params: undefined,
- alertConfigId: undefined,
- };
-}
-
-/**
- * 娣诲姞鎵ц鍣�
- */
-function addAction() {
- const newAction = createDefaultActionData();
- actions.value.push(newAction);
-}
-
-/**
- * 鍒犻櫎鎵ц鍣�
- * @param index 鎵ц鍣ㄧ储寮�
- */
-function removeAction(index: number) {
- actions.value.splice(index, 1);
-}
-
-/**
- * 鏇存柊鎵ц鍣ㄧ被鍨�
- * @param index 鎵ц鍣ㄧ储寮�
- * @param type 鎵ц鍣ㄧ被鍨�
- */
-function updateActionType(index: number, type: number) {
- const action = actions.value[index] as RuleSceneApi.Action;
- onActionTypeChange(action, type); // 椤诲湪璧嬫柊鍊煎墠璋冪敤 锛屽唴閮ㄤ緷璧� action.type 鏃у��
- action.type = type;
-}
-
-/**
- * 鏇存柊鎵ц鍣�
- * @param index 鎵ц鍣ㄧ储寮�
- * @param action 鎵ц鍣ㄥ璞�
- */
-function updateAction(index: number, action: RuleSceneApi.Action) {
- actions.value[index] = action;
-}
-
-/**
- * 鏇存柊鍛婅閰嶇疆
- * @param index 鎵ц鍣ㄧ储寮�
- * @param alertConfigId 鍛婅閰嶇疆ID
- */
-function updateActionAlertConfig(index: number, alertConfigId?: number) {
- actions.value[index]!.alertConfigId = alertConfigId;
-}
-
-/**
- * 鐩戝惉鎵ц鍣ㄧ被鍨嬪彉鍖�
- * @param action 鎵ц鍣ㄥ璞�
- * @param type 鎵ц鍣ㄧ被鍨�
- */
-function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
- if (isDeviceAction(type)) {
- // 璁惧鎺у埗绫诲瀷锛氭竻鐞嗗憡璀﹂厤缃紝纭繚璁惧鍙傛暟瀛樺湪
- action.alertConfigId = undefined;
- if (!action.params) {
- action.params = '';
- }
- // 鍒囨崲鍒拌澶囨帶鍒剁被鍨嬫椂娓呯┖ identifier锛岃鐢ㄦ埛閲嶆柊閫夋嫨
- if (action.identifier) {
- action.identifier = undefined;
- }
- } else if (isAlertAction(type)) {
- action.productId = undefined;
- action.deviceId = undefined;
- action.identifier = undefined;
- action.params = undefined;
- action.alertConfigId = undefined;
- }
-}
-</script>
-
-<template>
- <Card class="rounded-lg border border-primary" shadow="never">
- <template #title>
- <div class="flex items-center justify-between">
- <div class="gap-[8px] flex items-center">
- <IconifyIcon icon="ep:setting" class="text-[18px] text-primary" />
- <span class="text-[16px] font-semibold text-foreground">
- 鎵ц鍣ㄩ厤缃�
- </span>
- <Tag color="default"> {{ actions.length }} 涓墽琛屽櫒 </Tag>
- </div>
- <div class="gap-[8px] flex items-center">
- <Button type="primary" size="small" @click="addAction">
- <IconifyIcon icon="ep:plus" />
- 娣诲姞鎵ц鍣�
- </Button>
- </div>
- </div>
- </template>
-
- <div class="p-0">
- <!-- 绌虹姸鎬� -->
- <div v-if="actions.length === 0">
- <Empty description="鏆傛棤鎵ц鍣ㄩ厤缃�">
- <Button type="primary" @click="addAction">
- <IconifyIcon icon="ep:plus" />
- 娣诲姞绗竴涓墽琛屽櫒
- </Button>
- </Empty>
- </div>
-
- <!-- 鎵ц鍣ㄥ垪琛� -->
- <div v-else class="space-y-[24px]">
- <div
- v-for="(action, index) in actions"
- :key="getStableObjectKey(action)"
- class="rounded-lg border border-blue-200 bg-blue-50/40 shadow-sm transition-shadow hover:shadow-md dark:border-blue-900/40 dark:bg-blue-950/20"
- >
- <!-- 鎵ц鍣ㄥご閮紙钃濊壊涓婚锛� -->
- <div
- class="flex items-center justify-between rounded-t-lg border-b border-blue-200 bg-gradient-to-r from-blue-50 to-sky-50 px-4 py-[10px] dark:border-blue-900/40 dark:from-blue-950/30 dark:to-sky-950/30"
- >
- <div class="gap-[10px] flex items-center">
- <div
- class="font-semibold flex items-center gap-2 text-sm text-blue-700 dark:text-blue-300"
- >
- <div
- class="flex w-[22px] h-[22px] items-center justify-center rounded-full bg-blue-500 text-xs font-bold text-white"
- >
- {{ index + 1 }}
- </div>
- <span>鎵ц鍣� {{ index + 1 }}</span>
- </div>
- <Tag
- :color="getActionTypeColor(action.type as number)"
- class="font-medium"
- >
- {{ getActionTypeLabel(action.type as number) }}
- </Tag>
- </div>
- <div class="gap-[8px] flex items-center">
- <Button
- v-if="actions.length > 1"
- danger
- size="small"
- type="link"
- class="hover:bg-red-50"
- @click="removeAction(index)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎
- </Button>
- </div>
- </div>
-
- <!-- 鎵ц鍣ㄥ唴瀹瑰尯鍩� -->
- <div class="p-[16px] space-y-[16px]">
- <!-- 鎵ц绫诲瀷閫夋嫨 -->
- <div class="w-full">
- <Form.Item label="鎵ц绫诲瀷" required>
- <Select
- :value="action.type"
- @change="(value) => updateActionType(index, value as number)"
- placeholder="璇烽�夋嫨鎵ц绫诲瀷"
- class="w-full"
- >
- <Select.Option
- v-for="option in getActionTypeOptions()"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </Select.Option>
- </Select>
- </Form.Item>
- </div>
-
- <!-- 璁惧鎺у埗閰嶇疆 -->
- <DeviceControlConfig
- v-if="isDeviceAction(action.type as number)"
- :model-value="action"
- @update:model-value="(value) => updateAction(index, value)"
- />
-
- <!-- 鍛婅閰嶇疆 - 鍙湁鎭㈠鍛婅鏃舵墠鏄剧ず -->
- <AlertConfig
- v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER"
- :model-value="action.alertConfigId"
- @update:model-value="
- (value) => updateActionAlertConfig(index, value)
- "
- />
-
- <!-- 瑙﹀彂鍛婅鎻愮ず - 瑙﹀彂鍛婅鏃舵樉绀� -->
- <div
- v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
- class="bg-fill-color-blank rounded-lg border border-border p-4"
- >
- <div class="mb-2 flex items-center gap-2">
- <IconifyIcon icon="ep:warning" class="text-base text-warning" />
- <span class="font-semibold text-sm text-foreground">
- 瑙﹀彂鍛婅
- </span>
- <Tag color="warning">鑷姩鎵ц</Tag>
- </div>
- <div class="text-xs leading-relaxed text-muted-foreground">
- 褰撹Е鍙戞潯浠舵弧瓒虫椂锛岀郴缁熷皢鑷姩鍙戦�佸憡璀﹂�氱煡锛屽彲鍦ㄨ彍鍗� [鍛婅涓績 ->
- 鍛婅閰嶇疆] 绠$悊銆�
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 娣诲姞鎻愮ず -->
- <div v-if="actions.length > 0" class="py-[16px] text-center">
- <Button type="primary" plain @click="addAction">
- <IconifyIcon icon="ep:plus" />
- 缁х画娣诲姞鎵ц鍣�
- </Button>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/iot/rule/scene/form/sections/basic-info-section.vue b/src/views/iot/rule/scene/form/sections/basic-info-section.vue
deleted file mode 100644
index 9bccb54..0000000
--- a/src/views/iot/rule/scene/form/sections/basic-info-section.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<!-- 鍩虹淇℃伅閰嶇疆缁勪欢 -->
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../../packages/effects/hooks/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import { Card, Col, Form, Input, Radio, Row } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-
-/** 鍩虹淇℃伅閰嶇疆缁勪欢 */
-defineOptions({ name: 'BasicInfoSection' });
-
-const props = defineProps<{
- modelValue: RuleSceneApi.SceneRule;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: RuleSceneApi.SceneRule): void;
-}>();
-
-const formData = useVModel(props, 'modelValue', emit); // 琛ㄥ崟鏁版嵁
-</script>
-
-<template>
- <Card class="rounded-[8px] mb-[10px]! border border-primary" shadow="never">
- <template #title>
- <div class="flex items-center justify-between">
- <div class="gap-[8px] flex items-center">
- <IconifyIcon icon="ep:info-filled" class="text-[18px] text-primary" />
- <span class="text-[16px] font-semibold text-foreground">
- 鍩虹淇℃伅
- </span>
- </div>
- <div class="gap-[8px] flex items-center">
- <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="formData.status" />
- </div>
- </div>
- </template>
-
- <div class="p-0">
- <Row :gutter="24" class="mb-[24px]">
- <Col :span="12">
- <Form.Item label="鍦烘櫙鍚嶇О" name="name" required>
- <Input
- v-model:value="formData.name"
- placeholder="璇疯緭鍏ュ満鏅悕绉�"
- :maxlength="50"
- show-word-limit
- allow-clear
- />
- </Form.Item>
- </Col>
- <Col :span="12">
- <Form.Item label="鍦烘櫙鐘舵��" name="status" required>
- <Radio.Group v-model:value="formData.status">
- <Radio
- v-for="(dict, index) in getDictOptions(
- DICT_TYPE.COMMON_STATUS,
- 'number',
- )"
- :key="index"
- :value="dict.value"
- >
- {{ dict.label }}
- </Radio>
- </Radio.Group>
- </Form.Item>
- </Col>
- </Row>
- <Form.Item label="鍦烘櫙鎻忚堪" name="description">
- <Input.TextArea
- v-model:value="formData.description"
- placeholder="璇疯緭鍏ュ満鏅弿杩帮紙鍙�夛級"
- :rows="3"
- :maxlength="200"
- show-word-limit
- resize="none"
- />
- </Form.Item>
- </div>
- </Card>
-</template>
-
-<style scoped>
-:deep(.ant-form-item) {
- margin-bottom: 20px;
-}
-
-:deep(.ant-form-item:last-child) {
- margin-bottom: 0;
-}
-</style>
diff --git a/src/views/iot/rule/scene/form/sections/trigger-section.vue b/src/views/iot/rule/scene/form/sections/trigger-section.vue
deleted file mode 100644
index 461d3b8..0000000
--- a/src/views/iot/rule/scene/form/sections/trigger-section.vue
+++ /dev/null
@@ -1,248 +0,0 @@
-<script setup lang="ts">
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { onMounted } from 'vue';
-
-import {
- getTriggerTypeLabel,
- IotRuleSceneTriggerTypeEnum,
- isDeviceTrigger,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { getStableObjectKey } from '../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Card, Empty, Form, Tag } from 'ant-design-vue';
-
-import { CronTab } from '#/components/cron-tab';
-
-import DeviceTriggerConfig from '../configs/device-trigger-config.vue';
-import TimerConditionGroupConfig from '../configs/timer-condition-group-config.vue';
-
-/** 瑙﹀彂鍣ㄩ厤缃粍浠� */
-defineOptions({ name: 'TriggerSection' });
-
-const props = defineProps<{
- triggers: RuleSceneApi.Trigger[];
-}>();
-
-const emit = defineEmits<{
- (e: 'update:triggers', value: RuleSceneApi.Trigger[]): void;
-}>();
-
-const triggers = useVModel(props, 'triggers', emit);
-
-/** 鑾峰彇瑙﹀彂鍣ㄦ爣绛鹃鑹诧紙antd Tag `color`锛� */
-function getTriggerTagColor(
- type: number,
-): 'default' | 'error' | 'processing' | 'success' | 'warning' {
- if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
- return 'warning';
- }
- return isDeviceTrigger(type) ? 'success' : 'default';
-}
-
-/** 娣诲姞瑙﹀彂鍣� */
-function addTrigger() {
- const newTrigger: RuleSceneApi.Trigger = {
- type: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE,
- productId: undefined,
- deviceId: undefined,
- identifier: undefined,
- operator: undefined,
- value: undefined,
- cronExpression: undefined,
- conditionGroups: [], // 绌虹殑鏉′欢缁勬暟缁�
- };
- triggers.value.push(newTrigger);
-}
-
-/** 鍒犻櫎瑙﹀彂鍣� */
-function removeTrigger(index: number) {
- if (triggers.value.length > 1) {
- triggers.value.splice(index, 1);
- }
-}
-
-/** 鏇存柊瑙﹀彂鍣ㄧ被鍨� */
-function updateTriggerType(index: number, type: number) {
- triggers.value[index]!.type = type;
- onTriggerTypeChange(index);
-}
-
-/** 鏇存柊瑙﹀彂鍣ㄨ澶囬厤缃� */
-function updateTriggerDeviceConfig(
- index: number,
- newTrigger: RuleSceneApi.Trigger,
-) {
- triggers.value[index] = newTrigger;
-}
-
-/** 鏇存柊瑙﹀彂鍣� CRON 閰嶇疆 */
-function updateTriggerCronConfig(index: number, cronExpression?: string) {
- triggers.value[index]!.cronExpression = cronExpression;
-}
-
-/** 鏇存柊瑙﹀彂鍣ㄦ潯浠剁粍閰嶇疆 */
-function updateTriggerConditionGroups(
- index: number,
- conditionGroups: RuleSceneApi.TriggerCondition[][],
-) {
- triggers.value[index]!.conditionGroups = conditionGroups;
-}
-
-/** 瑙﹀彂鍣ㄧ被鍨嬪垏鎹㈠悗娓呯┖鐩稿叧瀛楁 */
-function onTriggerTypeChange(index: number) {
- const triggerItem = triggers.value[index]!;
- triggerItem.productId = undefined;
- triggerItem.deviceId = undefined;
- triggerItem.identifier = undefined;
- triggerItem.operator = undefined;
- triggerItem.value = undefined;
- triggerItem.cronExpression = undefined;
- triggerItem.conditionGroups = [];
-}
-
-/** 鍒濆鍖栵細纭繚鑷冲皯鏈変竴涓Е鍙戝櫒 */
-onMounted(() => {
- if (triggers.value.length === 0) {
- addTrigger();
- }
-});
-</script>
-
-<template>
- <Card class="rounded-[8px] mb-[10px]! border border-primary" shadow="never">
- <template #title>
- <div class="flex items-center justify-between">
- <div class="gap-[8px] flex items-center">
- <IconifyIcon icon="ep:lightning" class="text-[18px] text-primary" />
- <span class="text-[16px] font-semibold text-foreground">
- 瑙﹀彂鍣ㄩ厤缃�
- </span>
- <Tag color="default"> {{ triggers.length }} 涓Е鍙戝櫒 </Tag>
- </div>
- <Button type="primary" size="small" @click="addTrigger">
- <IconifyIcon icon="lucide:plus" />
- 娣诲姞瑙﹀彂鍣�
- </Button>
- </div>
- </template>
-
- <div class="p-[16px] space-y-[24px]">
- <!-- 瑙﹀彂鍣ㄥ垪琛� -->
- <div v-if="triggers.length > 0" class="space-y-[24px]">
- <div
- v-for="(triggerItem, index) in triggers"
- :key="getStableObjectKey(triggerItem)"
- class="rounded-[8px] border border-green-200 bg-green-50/40 shadow-sm transition-shadow hover:shadow-md dark:border-green-900/40 dark:bg-green-950/20"
- >
- <!-- 瑙﹀彂鍣ㄥご閮紙缁胯壊涓婚锛� -->
- <div
- class="px-[16px] py-[10px] rounded-t-[8px] flex items-center justify-between border-b border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 dark:border-green-900/40 dark:from-green-950/30 dark:to-emerald-950/30"
- >
- <div class="gap-[12px] flex items-center">
- <div
- class="gap-[8px] text-[14px] font-semibold flex items-center text-green-700 dark:text-green-300"
- >
- <div
- class="w-[22px] h-[22px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
- >
- {{ index + 1 }}
- </div>
- <span>瑙﹀彂鍣� {{ index + 1 }}</span>
- </div>
- <Tag
- :color="getTriggerTagColor(triggerItem.type as number)"
- class="font-medium"
- >
- {{ getTriggerTypeLabel(triggerItem.type as number) }}
- </Tag>
- </div>
- <div class="gap-[8px] flex items-center">
- <Button
- v-if="triggers.length > 1"
- danger
- size="small"
- type="link"
- class="hover:bg-red-50"
- @click="removeTrigger(index)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎
- </Button>
- </div>
- </div>
-
- <!-- 瑙﹀彂鍣ㄥ唴瀹瑰尯鍩� -->
- <div class="p-[16px] space-y-[16px]">
- <!-- 璁惧瑙﹀彂閰嶇疆 -->
- <DeviceTriggerConfig
- v-if="isDeviceTrigger(triggerItem.type as number)"
- :model-value="triggerItem"
- :index="index"
- @update:model-value="
- (value) => updateTriggerDeviceConfig(index, value)
- "
- @trigger-type-change="(type) => updateTriggerType(index, type)"
- />
-
- <!-- 瀹氭椂瑙﹀彂閰嶇疆 -->
- <div
- v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
- class="gap-[16px] flex flex-col"
- >
- <div
- class="gap-[8px] p-[12px] px-[16px] rounded-[6px] flex items-center border border-primary bg-background"
- >
- <IconifyIcon
- icon="lucide:timer"
- class="text-[18px] text-danger"
- />
- <span class="text-[14px] font-medium text-foreground">
- 瀹氭椂瑙﹀彂閰嶇疆
- </span>
- </div>
-
- <!-- CRON 琛ㄨ揪寮忛厤缃� -->
- <div
- class="p-[16px] rounded-[6px] border border-primary bg-background"
- >
- <Form.Item label="CRON 琛ㄨ揪寮�" required>
- <CronTab
- :model-value="triggerItem.cronExpression || '0 0 12 * * ?'"
- @update:model-value="
- (value) => updateTriggerCronConfig(index, value)
- "
- />
- </Form.Item>
- </div>
-
- <!-- 闄勫姞鏉′欢缁勯厤缃� -->
- <TimerConditionGroupConfig
- :model-value="triggerItem.conditionGroups"
- @update:model-value="
- (value) => updateTriggerConditionGroups(index, value)
- "
- />
- </div>
- </div>
- </div>
- </div>
-
- <!-- 绌虹姸鎬� -->
- <div v-else class="py-[40px] text-center">
- <Empty description="鏆傛棤瑙﹀彂鍣�">
- <template #description>
- <div class="space-y-[8px]">
- <p class="text-muted-foreground">鏆傛棤瑙﹀彂鍣ㄩ厤缃�</p>
- <p class="text-[12px] text-muted-foreground">
- 璇蜂娇鐢ㄤ笂鏂圭殑"娣诲姞瑙﹀彂鍣�"鎸夐挳鏉ヨ缃Е鍙戣鍒�
- </p>
- </div>
- </template>
- </Empty>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/iot/rule/scene/form/selectors/device-selector.vue b/src/views/iot/rule/scene/form/selectors/device-selector.vue
deleted file mode 100644
index ba2b3bb..0000000
--- a/src/views/iot/rule/scene/form/selectors/device-selector.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-<!-- 璁惧閫夋嫨鍣ㄧ粍浠� -->
-<script setup lang="ts">
-import { ref, watch } from 'vue';
-
-import { DEVICE_SELECTOR_OPTIONS, DICT_TYPE } from '../../../../../../packages/constants/src';
-
-import { Select } from 'ant-design-vue';
-
-import { getDeviceListByProductId } from '#/api/iot/device/device';
-import { DictTag } from '#/components/dict-tag';
-
-/** 璁惧閫夋嫨鍣ㄧ粍浠� */
-defineOptions({ name: 'DeviceSelector' });
-
-const props = defineProps<{
- modelValue?: number;
- productId?: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value?: number): void;
- (e: 'change', value?: number): void;
-}>();
-
-const deviceLoading = ref(false); // 璁惧鍔犺浇鐘舵��
-const deviceList = ref<any[]>([]); // 璁惧鍒楄〃
-
-/** 澶勭悊閫夋嫨鍙樺寲浜嬩欢 */
-function handleChange(value: any) {
- emit('update:modelValue', value as number | undefined);
- emit('change', value as number | undefined);
-}
-
-/**
- * 鑾峰彇璁惧鍒楄〃
- */
-async function getDeviceList() {
- if (!props.productId) {
- deviceList.value = [];
- return;
- }
-
- try {
- deviceLoading.value = true;
- const data = await getDeviceListByProductId(props.productId);
- deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES, ...(data || [])];
- } catch (error) {
- console.error('鑾峰彇璁惧鍒楄〃澶辫触 锛�', error);
- deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES];
- } finally {
- deviceLoading.value = false;
- }
-}
-
-// 鐩戝惉浜у搧鍙樺寲
-watch(
- () => props.productId,
- async (newProductId, oldProductId) => {
- if (!newProductId) {
- deviceList.value = [];
- if (props.modelValue !== undefined && props.modelValue !== null) {
- emit('update:modelValue', undefined);
- emit('change', undefined);
- }
- return;
- }
- await getDeviceList();
- // 鍒囨崲鍒版柊 productId 鏃讹紝鏃� deviceId 涓嶅湪鏂板垪琛ㄩ噷鍒欐竻绌�
- if (
- oldProductId !== undefined &&
- oldProductId !== newProductId &&
- props.modelValue !== undefined &&
- props.modelValue !== null &&
- !deviceList.value.some((d: any) => d.id === props.modelValue)
- ) {
- emit('update:modelValue', undefined);
- emit('change', undefined);
- }
- },
- { immediate: true },
-);
-</script>
-
-<template>
- <Select
- :value="modelValue"
- @change="handleChange"
- placeholder="璇烽�夋嫨璁惧"
- show-search
- allow-clear
- class="w-full"
- option-label-prop="label"
- :loading="deviceLoading"
- :disabled="!productId"
- >
- <Select.Option
- v-for="device in deviceList"
- :key="device.id"
- :label="device.deviceName"
- :value="device.id"
- >
- <div class="py-[4px] flex w-full items-center justify-between">
- <div class="flex-1">
- <div class="text-[14px] font-medium mb-[2px] text-foreground">
- {{ device.deviceName }}
- </div>
- <div class="text-[12px] text-muted-foreground">
- {{ device.deviceKey }}
- </div>
- </div>
- <div class="gap-[4px] flex items-center" v-if="device.id > 0">
- <DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
- </div>
- </div>
- </Select.Option>
- </Select>
-</template>
diff --git a/src/views/iot/rule/scene/form/selectors/operator-selector.vue b/src/views/iot/rule/scene/form/selectors/operator-selector.vue
deleted file mode 100644
index c7677d1..0000000
--- a/src/views/iot/rule/scene/form/selectors/operator-selector.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-<!-- 鎿嶄綔绗﹂�夋嫨鍣ㄧ粍浠� -->
-<script setup lang="ts">
-import { computed, watch } from 'vue';
-
-import {
- IoTDataSpecsDataTypeEnum,
- IotRuleSceneTriggerConditionParameterOperatorEnum,
-} from '../../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import { Select } from 'ant-design-vue';
-
-/** 鎿嶄綔绗﹂�夋嫨鍣ㄧ粍浠� */
-defineOptions({ name: 'OperatorSelector' });
-
-const props = defineProps<{
- modelValue?: string;
- propertyType?: string;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: string): void;
- (e: 'change', value: string): void;
-}>();
-
-const localValue = useVModel(props, 'modelValue', emit);
-
-// 鍩轰簬鏋氫妇鐨勬搷浣滅瀹氫箟
-const allOperators = [
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.name,
- symbol: '=',
- description: '鍊煎畬鍏ㄧ浉绛夋椂瑙﹀彂',
- example: 'temperature = 25',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.TEXT,
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name,
- symbol: '鈮�',
- description: '鍊间笉鐩哥瓑鏃惰Е鍙�',
- example: 'power !== false',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.TEXT,
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN.name,
- symbol: '>',
- description: '鍊煎ぇ浜庢寚瀹氬�兼椂瑙﹀彂',
- example: 'temperature > 30',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value:
- IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS
- .value,
- label:
- IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS
- .name,
- symbol: '鈮�',
- description: '鍊煎ぇ浜庢垨绛変簬鎸囧畾鍊兼椂瑙﹀彂',
- example: 'humidity >= 80',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN.name,
- symbol: '<',
- description: '鍊煎皬浜庢寚瀹氬�兼椂瑙﹀彂',
- example: 'temperature < 10',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value:
- IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS
- .value,
- label:
- IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS
- .name,
- symbol: '鈮�',
- description: '鍊煎皬浜庢垨绛変簬鎸囧畾鍊兼椂瑙﹀彂',
- example: 'battery <= 20',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.IN.name,
- symbol: '鈭�',
- description: '鍊煎湪鎸囧畾鍒楄〃涓椂瑙﹀彂',
- example: 'status in [1,2,3]',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.TEXT,
- IoTDataSpecsDataTypeEnum.ENUM,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN.name,
- symbol: '鈭�',
- description: '鍊间笉鍦ㄦ寚瀹氬垪琛ㄤ腑鏃惰Е鍙�',
- example: 'status not in [1,2,3]',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.TEXT,
- IoTDataSpecsDataTypeEnum.ENUM,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.name,
- symbol: '鈯�',
- description: '鍊煎湪鎸囧畾鑼冨洿鍐呮椂瑙﹀彂',
- example: 'temperature between 20,30',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN.name,
- symbol: '鈯�',
- description: '鍊间笉鍦ㄦ寚瀹氳寖鍥村唴鏃惰Е鍙�',
- example: 'temperature not between 20,30',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.LIKE.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.LIKE.name,
- symbol: '鈮�',
- description: '瀛楃涓插尮閰嶆寚瀹氭ā寮忔椂瑙﹀彂',
- example: 'message like "%error%"',
- supportedTypes: [IoTDataSpecsDataTypeEnum.TEXT],
- },
- {
- value: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value,
- label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.name,
- symbol: '鈮犫垍',
- description: '鍊奸潪绌烘椂瑙﹀彂',
- example: 'data not null',
- supportedTypes: [
- IoTDataSpecsDataTypeEnum.INT,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.TEXT,
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
- IoTDataSpecsDataTypeEnum.DATE,
- ],
- },
-];
-
-// 璁$畻灞炴�э細鍙敤鐨勬搷浣滅
-const availableOperators = computed(() => {
- if (!props.propertyType) {
- return allOperators;
- }
- return allOperators.filter((op) =>
- (op.supportedTypes as any[]).includes(props.propertyType || ''),
- );
-});
-
-// 璁$畻灞炴�э細褰撳墠閫変腑鐨勬搷浣滅
-const selectedOperator = computed(() => {
- return allOperators.find((op) => op.value === localValue.value);
-});
-
-/**
- * 澶勭悊閫夋嫨鍙樺寲浜嬩欢
- * @param value 閫変腑鐨勬搷浣滅鍊�
- */
-function handleChange(value: any) {
- emit('change', value);
-}
-
-/** 鐩戝惉灞炴�х被鍨嬪彉鍖� */
-watch(
- () => props.propertyType,
- () => {
- // 濡傛灉褰撳墠閫夋嫨鐨勬搷浣滅涓嶆敮鎸佹柊鐨勫睘鎬х被鍨嬶紝鍒欐竻绌洪�夋嫨
- if (
- localValue.value &&
- selectedOperator.value &&
- !(selectedOperator.value.supportedTypes as any[]).includes(
- props.propertyType || '',
- )
- ) {
- localValue.value = '';
- }
- },
-);
-</script>
-
-<template>
- <div class="w-full">
- <Select
- v-model:value="localValue"
- placeholder="璇烽�夋嫨鎿嶄綔绗�"
- @change="handleChange"
- class="w-full"
- option-label-prop="label"
- >
- <Select.Option
- v-for="operator in availableOperators"
- :key="operator.value"
- :label="operator.label"
- :value="operator.value"
- >
- <div class="py-[4px] flex w-full items-center justify-between">
- <div class="gap-[8px] flex items-center">
- <div class="text-[14px] font-medium text-foreground">
- {{ operator.label }}
- </div>
- <div
- class="text-[12px] px-[6px] py-[2px] rounded-[4px] bg-primary-light-9 font-mono text-primary"
- >
- {{ operator.symbol }}
- </div>
- </div>
- <div class="text-[12px] text-muted-foreground">
- {{ operator.description }}
- </div>
- </div>
- </Select.Option>
- </Select>
- </div>
-</template>
-
-<style scoped>
-:deep(.ant-select-item-option-content) {
- height: auto;
- padding: 8px 20px;
-}
-</style>
diff --git a/src/views/iot/rule/scene/form/selectors/product-selector.vue b/src/views/iot/rule/scene/form/selectors/product-selector.vue
deleted file mode 100644
index 218f28c..0000000
--- a/src/views/iot/rule/scene/form/selectors/product-selector.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<!-- 浜у搧閫夋嫨鍣ㄧ粍浠� -->
-<script setup lang="ts">
-import { onMounted, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-
-import { Select } from 'ant-design-vue';
-
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { DictTag } from '#/components/dict-tag';
-
-/** 浜у搧閫夋嫨鍣ㄧ粍浠� */
-defineOptions({ name: 'ProductSelector' });
-
-defineProps<{
- modelValue?: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value?: number): void;
- (e: 'change', value?: number): void;
-}>();
-
-const productLoading = ref(false); // 浜у搧鍔犺浇鐘舵��
-const productList = ref<any[]>([]); // 浜у搧鍒楄〃
-
-/**
- * 澶勭悊閫夋嫨鍙樺寲浜嬩欢
- * @param value 閫変腑鐨勪骇鍝� ID
- */
-function handleChange(value?: number) {
- emit('update:modelValue', value);
- emit('change', value);
-}
-
-/** 鑾峰彇浜у搧鍒楄〃 */
-async function getProductList() {
- try {
- productLoading.value = true;
- const res = await getSimpleProductList();
- productList.value = res || [];
- } catch (error) {
- console.error('鑾峰彇浜у搧鍒楄〃澶辫触:', error);
- productList.value = [];
- } finally {
- productLoading.value = false;
- }
-}
-
-/** 缁勪欢鎸傝浇鏃惰幏鍙栦骇鍝佸垪琛� */
-onMounted(() => {
- getProductList();
-});
-</script>
-
-<template>
- <Select
- :value="modelValue"
- @change="(value: any) => handleChange(value)"
- placeholder="璇烽�夋嫨浜у搧"
- show-search
- allow-clear
- class="w-full"
- option-label-prop="label"
- :loading="productLoading"
- >
- <Select.Option
- v-for="product in productList"
- :key="product.id"
- :label="product.name"
- :value="product.id"
- >
- <div class="py-[4px] flex w-full items-center justify-between">
- <div class="flex-1">
- <div class="text-[14px] font-medium mb-[2px] text-foreground">
- {{ product.name }}
- </div>
- <div class="text-[12px] text-muted-foreground">
- {{ product.productKey }}
- </div>
- </div>
- <DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" />
- </div>
- </Select.Option>
- </Select>
-</template>
diff --git a/src/views/iot/rule/scene/form/selectors/property-selector.vue b/src/views/iot/rule/scene/form/selectors/property-selector.vue
deleted file mode 100644
index f2d8a91..0000000
--- a/src/views/iot/rule/scene/form/selectors/property-selector.vue
+++ /dev/null
@@ -1,409 +0,0 @@
-<!-- 灞炴�ч�夋嫨鍣ㄧ粍浠� -->
-<script setup lang="ts">
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, ref, watch } from 'vue';
-
-import {
- getAccessModeLabel,
- getDataTypeName,
- getEventTypeLabel,
- getThingModelServiceCallTypeLabel,
- IotRuleSceneTriggerTypeEnum,
- IoTThingModelTypeEnum,
- THING_MODEL_GROUP_LABELS,
-} from '../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Popover, Select, Tag } from 'ant-design-vue';
-
-import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
-
-/** 灞炴�ч�夋嫨鍣ㄧ粍浠� */
-defineOptions({ name: 'PropertySelector' });
-
-const props = defineProps<{
- deviceId?: number;
- modelValue?: string;
- productId?: number;
- triggerType: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', value: string): void;
- (e: 'change', value: { config: any; type: string }): void;
-}>();
-
-/** 灞炴�ч�夋嫨鍣ㄥ唴閮ㄤ娇鐢ㄧ殑缁熶竴鏁版嵁缁撴瀯 */
-interface PropertySelectorItem {
- identifier: string;
- name: string;
- description?: string;
- dataType: string;
- type: number; // IoTThingModelTypeEnum
- accessMode?: string;
- required?: boolean;
- unit?: string;
- range?: string;
- eventType?: string;
- callType?: string;
- inputParams?: ThingModelApi.Param[];
- outputParams?: ThingModelApi.Param[];
- property?: ThingModelApi.Property;
- event?: ThingModelApi.Event;
- service?: ThingModelApi.Service;
-}
-
-const localValue = useVModel(props, 'modelValue', emit);
-
-const loading = ref(false);
-const propertyList = ref<PropertySelectorItem[]>([]);
-
-/** 瑙﹀彂绫诲瀷 鈫� 鐗╂ā鍨嬬被鍨� + 鍒嗙粍鏍囩 */
-const TRIGGER_TYPE_TO_GROUP: Record<
- number,
- { label: string; modelType: number }
-> = {
- [IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST]: {
- label: THING_MODEL_GROUP_LABELS.PROPERTY,
- modelType: IoTThingModelTypeEnum.PROPERTY,
- },
- [IotRuleSceneTriggerTypeEnum.TIMER]: {
- label: THING_MODEL_GROUP_LABELS.PROPERTY,
- modelType: IoTThingModelTypeEnum.PROPERTY,
- },
- [IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST]: {
- label: THING_MODEL_GROUP_LABELS.EVENT,
- modelType: IoTThingModelTypeEnum.EVENT,
- },
- [IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE]: {
- label: THING_MODEL_GROUP_LABELS.SERVICE,
- modelType: IoTThingModelTypeEnum.SERVICE,
- },
-};
-
-/** 灞炴�у垎缁勶細鎸夎Е鍙戠被鍨嬬瓫閫夊睘鎬� / 浜嬩欢 / 鏈嶅姟 */
-const propertyGroups = computed(() => {
- const config = TRIGGER_TYPE_TO_GROUP[props.triggerType];
- if (!config) return [];
- const options = propertyList.value.filter(
- (item) => item.type === config.modelType,
- );
- return options.length > 0 ? [{ label: config.label, options }] : [];
-});
-
-/** 褰撳墠閫変腑鐨勫睘鎬� */
-const selectedProperty = computed(() =>
- propertyList.value.find(
- (property) => property.identifier === localValue.value,
- ),
-);
-
-/** 澶勭悊閫夋嫨鍙樺寲浜嬩欢 */
-function handleChange(value: any) {
- const property = propertyList.value.find((item) => item.identifier === value);
- if (property) {
- emit('change', { type: property.dataType, config: property });
- }
-}
-
-/** 鑾峰彇鐗╂ā鍨� TSL 鏁版嵁 */
-async function loadThingModelTSL() {
- if (!props.productId) {
- propertyList.value = [];
- return;
- }
- loading.value = true;
- try {
- const tsl = await getThingModelTSLByProductId(props.productId);
- propertyList.value = parseThingModelData(tsl);
- } catch (error) {
- console.error('鑾峰彇鐗╂ā鍨� TSL 澶辫触 锛�', error);
- propertyList.value = [];
- } finally {
- loading.value = false;
- }
-}
-
-/** 鎶� TSL 鏍戝睍骞充负 PropertySelectorItem[] */
-function parseThingModelData(
- tsl?: null | ThingModelApi.ThingModelTSL,
-): PropertySelectorItem[] {
- if (!tsl) return [];
- const properties = (tsl.properties ?? []).map<PropertySelectorItem>(
- (prop) => ({
- identifier: prop.identifier!,
- name: prop.name!,
- description: prop.description,
- dataType: prop.dataType!,
- type: IoTThingModelTypeEnum.PROPERTY,
- accessMode: prop.accessMode,
- required: prop.required,
- unit: prop.dataSpecs?.unit,
- range: getPropertyRange(prop),
- property: prop,
- }),
- );
- const events = (tsl.events ?? []).map<PropertySelectorItem>((event) => ({
- identifier: event.identifier!,
- name: event.name!,
- description: event.description,
- dataType: 'struct',
- type: IoTThingModelTypeEnum.EVENT,
- eventType: event.type,
- required: event.required,
- outputParams: event.outputParams,
- event,
- }));
- const services = (tsl.services ?? []).map<PropertySelectorItem>(
- (service) => ({
- identifier: service.identifier!,
- name: service.name!,
- description: service.description,
- dataType: 'struct',
- type: IoTThingModelTypeEnum.SERVICE,
- callType: service.callType,
- required: service.required,
- inputParams: service.inputParams,
- outputParams: service.outputParams,
- service,
- }),
- );
- return [...properties, ...events, ...services];
-}
-
-/** 鑾峰彇灞炴�у彇鍊艰寖鍥达細鏁板�煎瀷缁� min~max锛涙灇涓� / 甯冨皵缁欓�夐」鍒楄〃 */
-function getPropertyRange(
- property: ThingModelApi.Property,
-): string | undefined {
- const specs = property.dataSpecs;
- if (specs && specs.min !== undefined && specs.max !== undefined) {
- return `${specs.min}~${specs.max}`;
- }
- if (property.dataSpecsList?.length) {
- return property.dataSpecsList
- .map((item: any) => `${item.name}(${item.value})`)
- .join(', ');
- }
- return undefined;
-}
-
-/** 鐩戝惉浜у搧鍙樺寲 */
-watch(
- () => props.productId,
- () => {
- loadThingModelTSL();
- },
- { immediate: true },
-);
-
-/** 鐩戝惉瑙﹀彂绫诲瀷鍙樺寲 */
-watch(
- () => props.triggerType,
- () => {
- localValue.value = '';
- },
-);
-</script>
-
-<template>
- <div class="gap-[8px] flex items-center">
- <Select
- v-model:value="localValue"
- placeholder="璇烽�夋嫨鐩戞帶椤�"
- show-search
- allow-clear
- @change="handleChange"
- class="!w-[150px]"
- option-label-prop="label"
- :loading="loading"
- >
- <Select.OptGroup
- v-for="group in propertyGroups"
- :key="group.label"
- :label="group.label"
- >
- <Select.Option
- v-for="property in group.options"
- :key="property.identifier"
- :label="property.name"
- :value="property.identifier"
- >
- <div class="py-[2px] flex w-full items-center justify-between">
- <span
- class="text-[14px] font-medium flex-1 truncate text-foreground"
- >
- {{ property.name }}
- </span>
- <Tag class="ml-[8px] flex-shrink-0">
- {{ property.identifier }}
- </Tag>
- </div>
- </Select.Option>
- </Select.OptGroup>
- </Select>
-
- <!-- 灞炴�ц鎯呭脊鍑哄眰 -->
- <Popover
- v-if="selectedProperty"
- placement="rightTop"
- :overlay-style="{ width: '350px' }"
- trigger="click"
- :arrow="true"
- overlay-class-name="property-detail-popover"
- >
- <template #content>
- <!-- 寮瑰嚭灞傚唴瀹� -->
- <div class="property-detail-content">
- <div class="gap-[8px] mb-[12px] flex items-center">
- <IconifyIcon icon="ep:info-filled" class="text-[16px] text-info" />
- <span class="text-[14px] font-medium text-foreground">
- {{ selectedProperty.name }}
- </span>
- <Tag>
- {{ getDataTypeName(selectedProperty.dataType) }}
- </Tag>
- </div>
-
- <div class="space-y-[8px] ml-[24px]">
- <div class="gap-[8px] flex items-start">
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 鏍囪瘑绗︼細
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ selectedProperty.identifier }}
- </span>
- </div>
-
- <div
- v-if="selectedProperty.description"
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 鎻忚堪锛�
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ selectedProperty.description }}
- </span>
- </div>
-
- <div
- v-if="selectedProperty.unit"
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 鍗曚綅锛�
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ selectedProperty.unit }}
- </span>
- </div>
-
- <div
- v-if="selectedProperty.range"
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 鍙栧�艰寖鍥达細
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ selectedProperty.range }}
- </span>
- </div>
-
- <!-- 鏍规嵁灞炴�х被鍨嬫樉绀洪澶栦俊鎭� -->
- <div
- v-if="
- selectedProperty.type === IoTThingModelTypeEnum.PROPERTY &&
- selectedProperty.accessMode
- "
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 璁块棶妯″紡锛�
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ getAccessModeLabel(selectedProperty.accessMode) }}
- </span>
- </div>
-
- <div
- v-if="
- selectedProperty.type === IoTThingModelTypeEnum.EVENT &&
- selectedProperty.eventType
- "
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 浜嬩欢绫诲瀷锛�
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{ getEventTypeLabel(selectedProperty.eventType) }}
- </span>
- </div>
-
- <div
- v-if="
- selectedProperty.type === IoTThingModelTypeEnum.SERVICE &&
- selectedProperty.callType
- "
- class="gap-[8px] flex items-start"
- >
- <span
- class="text-[12px] min-w-[60px] flex-shrink-0 text-muted-foreground"
- >
- 璋冪敤绫诲瀷锛�
- </span>
- <span class="text-[12px] flex-1 text-foreground">
- {{
- getThingModelServiceCallTypeLabel(selectedProperty.callType)
- }}
- </span>
- </div>
- </div>
- </div>
- </template>
- <Button
- type="link"
- shape="circle"
- size="small"
- class="flex-shrink-0"
- title="鏌ョ湅灞炴�ц鎯�"
- >
- <IconifyIcon icon="ep:info-filled" />
- </Button>
- </Popover>
- </div>
-</template>
-
-<style scoped>
-:deep(.ant-select-item-option-content) {
- height: auto;
- padding: 6px 20px;
-}
-
-.property-detail-content {
- padding: 4px 0;
-}
-
-:global(.property-detail-popover) {
- max-width: 400px !important;
-}
-
-:global(.property-detail-popover .ant-popover-inner-content) {
- padding: 16px !important;
-}
-</style>
diff --git a/src/views/iot/rule/scene/index.vue b/src/views/iot/rule/scene/index.vue
deleted file mode 100644
index d43a612..0000000
--- a/src/views/iot/rule/scene/index.vue
+++ /dev/null
@@ -1,399 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { ref } from 'vue';
-
-import { Page, useVbenDrawer } from '../../../../packages/effects/common-ui/src';
-import {
- CommonStatusEnum,
- DICT_TYPE,
- getActionTypeLabel,
- getTriggerTypeLabel,
- IotRuleSceneTriggerTypeEnum,
-} from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { CronUtils, formatDateTime } from '../../../../packages/utils/src';
-
-import { Card, Col, message, Row, Tag, Tooltip } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteSceneRule,
- getSceneRulePage,
- updateSceneRuleStatus,
-} from '#/api/iot/rule/scene';
-import { DictTag } from '#/components/dict-tag';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormDrawer, formDrawerApi] = useVbenDrawer({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const statistics = ref({
- total: 0,
- enabled: 0,
- disabled: 0,
- timerRules: 0,
-}); // 缁熻鏁版嵁
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍦烘櫙瑙勫垯 */
-function handleCreate() {
- formDrawerApi.setData(null).open();
-}
-
-/** 缂栬緫鍦烘櫙瑙勫垯 */
-function handleEdit(row: RuleSceneApi.SceneRule) {
- formDrawerApi.setData(row).open();
-}
-
-/** 鍚敤/鍋滅敤鍦烘櫙瑙勫垯 */
-async function handleToggleStatus(row: RuleSceneApi.SceneRule) {
- const newStatus =
- row.status === CommonStatusEnum.ENABLE
- ? CommonStatusEnum.DISABLE
- : CommonStatusEnum.ENABLE;
- const hideLoading = message.loading({
- content:
- newStatus === CommonStatusEnum.ENABLE ? '姝e湪鍚敤...' : '姝e湪鍋滅敤...',
- duration: 0,
- });
- try {
- await updateSceneRuleStatus(row.id as number, newStatus);
- message.success({
- content: newStatus === CommonStatusEnum.ENABLE ? '鍚敤鎴愬姛' : '鍋滅敤鎴愬姛',
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎鍦烘櫙瑙勫垯 */
-async function handleDelete(row: RuleSceneApi.SceneRule) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteSceneRule(row.id as number);
- message.success({
- content: $t('ui.actionMessage.deleteSuccess', [row.name]),
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒ゆ柇瑙勫垯鏄惁鍖呭惈瀹氭椂瑙﹀彂鍣� */
-function hasTimerTrigger(row: RuleSceneApi.SceneRule): boolean {
- return (
- row.triggers?.some(
- (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER,
- ) || false
- );
-}
-
-/** 瑙﹀彂鏉′欢鎽樿鏂囨湰锛堟嫾鎺ユ墍鏈夎Е鍙戝櫒锛� */
-function getTriggerSummary(row: RuleSceneApi.SceneRule): string {
- if (!row.triggers?.length) return '鏃犺Е鍙戝櫒';
- return row.triggers
- .map((trigger) => {
- const type = trigger.type ?? 0;
- let description = getTriggerTypeLabel(type);
- if (
- (type === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST ||
- type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
- type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) &&
- trigger.identifier
- ) {
- description += ` (${trigger.identifier})`;
- } else if (type === IotRuleSceneTriggerTypeEnum.TIMER) {
- description = `${getTriggerTypeLabel(type)} (${CronUtils.format(trigger.cronExpression || '')})`;
- }
- if (trigger.deviceId) {
- description += ` [璁惧 ID: ${trigger.deviceId}]`;
- } else if (trigger.productId) {
- description += ` [浜у搧 ID: ${trigger.productId}]`;
- }
- return description;
- })
- .join(', ');
-}
-
-/** 鎵ц鍔ㄤ綔鎽樿鏂囨湰锛堟嫾鎺ユ墍鏈夊姩浣滐級 */
-function getActionSummary(row: RuleSceneApi.SceneRule): string {
- if (!row.actions?.length) return '鏃犳墽琛屽櫒';
- return row.actions
- .map((action) => {
- let description = getActionTypeLabel(action.type ?? 0);
- if (action.deviceId) {
- description += ` [璁惧 ID: ${action.deviceId}]`;
- } else if (action.productId) {
- description += ` [浜у搧 ID: ${action.productId}]`;
- }
- if (action.alertConfigId) {
- description += ` [鍛婅閰嶇疆 ID: ${action.alertConfigId}]`;
- }
- return description;
- })
- .join(', ');
-}
-
-/** 鍙栧畾鏃惰Е鍙戝櫒鐨� CRON 棰戠巼鎻忚堪 */
-function getCronFrequency(row: RuleSceneApi.SceneRule): string {
- const timerTrigger = row.triggers?.find(
- (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER,
- );
- return timerTrigger?.cronExpression
- ? CronUtils.getFrequencyDescription(timerTrigger.cronExpression)
- : '';
-}
-
-/** 鍙栧畾鏃惰Е鍙戝櫒鍘熷 CRON 琛ㄨ揪寮� */
-function getCronExpression(row: RuleSceneApi.SceneRule): string {
- const timerTrigger = row.triggers?.find(
- (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER,
- );
- return timerTrigger?.cronExpression || '';
-}
-
-/** 鍙栧畾鏃惰Е鍙戝櫒涓嬫鎵ц鏃堕棿 */
-function getNextExecutionTime(row: RuleSceneApi.SceneRule): Date | null {
- const timerTrigger = row.triggers?.find(
- (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER,
- );
- return timerTrigger?.cronExpression
- ? CronUtils.getNextExecutionTime(timerTrigger.cronExpression)
- : null;
-}
-
-/** 鍒锋柊瑙勫垯缁熻鍗$墖鏁版嵁 */
-function updateStatistics(rows: RuleSceneApi.SceneRule[], total?: number) {
- statistics.value = {
- total: total ?? rows.length,
- enabled: rows.filter((item) => item.status === CommonStatusEnum.ENABLE)
- .length,
- disabled: rows.filter((item) => item.status === CommonStatusEnum.DISABLE)
- .length,
- timerRules: rows.filter((item) => hasTimerTrigger(item)).length,
- };
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const result = await getSceneRulePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- updateStatistics(result.list || [], result.total);
- return result;
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<RuleSceneApi.SceneRule>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormDrawer @success="handleRefresh" />
-
- <!-- 缁熻鍗$墖 -->
- <Row :gutter="16" class="mb-4">
- <Col :span="6">
- <Card :body-style="{ padding: '12px 16px' }">
- <div class="flex items-center">
- <div
- class="w-10 h-10 rounded-lg flex items-center justify-center text-xl text-white mr-3 bg-gradient-to-br from-indigo-500 to-purple-600"
- >
- <IconifyIcon icon="ant-design:file-text-outlined" />
- </div>
- <div class="leading-tight">
- <div class="text-xl font-semibold">
- {{ statistics.total }}
- </div>
- <div class="text-xs text-muted-foreground">鎬昏鍒欐暟</div>
- </div>
- </div>
- </Card>
- </Col>
- <Col :span="6">
- <Card :body-style="{ padding: '12px 16px' }">
- <div class="flex items-center">
- <div
- class="w-10 h-10 rounded-lg flex items-center justify-center text-xl text-white mr-3 bg-gradient-to-br from-pink-400 to-red-500"
- >
- <IconifyIcon icon="ant-design:check-outlined" />
- </div>
- <div class="leading-tight">
- <div class="text-xl font-semibold">
- {{ statistics.enabled }}
- </div>
- <div class="text-xs text-muted-foreground">鍚敤瑙勫垯</div>
- </div>
- </div>
- </Card>
- </Col>
- <Col :span="6">
- <Card :body-style="{ padding: '12px 16px' }">
- <div class="flex items-center">
- <div
- class="w-10 h-10 rounded-lg flex items-center justify-center text-xl text-white mr-3 bg-gradient-to-br from-cyan-400 to-blue-500"
- >
- <IconifyIcon icon="ant-design:close-outlined" />
- </div>
- <div class="leading-tight">
- <div class="text-xl font-semibold">
- {{ statistics.disabled }}
- </div>
- <div class="text-xs text-muted-foreground">绂佺敤瑙勫垯</div>
- </div>
- </div>
- </Card>
- </Col>
- <Col :span="6">
- <Card :body-style="{ padding: '12px 16px' }">
- <div class="flex items-center">
- <div
- class="w-10 h-10 rounded-lg flex items-center justify-center text-xl text-white mr-3 bg-gradient-to-br from-green-400 to-teal-400"
- >
- <IconifyIcon icon="lucide:timer" />
- </div>
- <div class="leading-tight">
- <div class="text-xl font-semibold">
- {{ statistics.timerRules }}
- </div>
- <div class="text-xs text-muted-foreground">瀹氭椂瑙勫垯</div>
- </div>
- </div>
- </Card>
- </Col>
- </Row>
-
- <Grid table-title="鍦烘櫙瑙勫垯鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍦烘櫙瑙勫垯']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <!-- 瑙勫垯鍚嶇О鍒楋細鍚嶇О + 鐘舵�� tag inline + 鎻忚堪 -->
- <template #name="{ row }">
- <div class="flex items-center gap-2">
- <span class="font-medium text-foreground">{{ row.name }}</span>
- <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
- </div>
- <Tooltip
- v-if="row.description"
- :title="row.description"
- placement="top"
- >
- <div
- class="mt-1 max-w-[200px] truncate text-xs text-muted-foreground"
- >
- {{ row.description }}
- </div>
- </Tooltip>
- </template>
- <!-- 瑙﹀彂鏉′欢鍒楋細鍗� tag 姹囨�� + 瀹氭椂瑙﹀彂棰濆淇℃伅 -->
- <template #triggers="{ row }">
- <div class="space-y-1">
- <Tag color="processing" class="m-0">{{ getTriggerSummary(row) }}</Tag>
- <Tooltip
- v-if="hasTimerTrigger(row)"
- :title="getCronExpression(row)"
- placement="top"
- >
- <div class="text-xs text-muted-foreground">
- <IconifyIcon icon="lucide:clock" class="mr-1 inline" />
- {{ getCronFrequency(row) }}
- <template v-if="getNextExecutionTime(row)">
- 路 涓嬫 {{ formatDateTime(getNextExecutionTime(row) as Date) }}
- </template>
- </div>
- </Tooltip>
- </div>
- </template>
- <!-- 鎵ц鍔ㄤ綔鍒楋細鍗� tag 姹囨�� -->
- <template #actionsCol="{ row }">
- <Tag color="success" class="m-0">{{ getActionSummary(row) }}</Tag>
- </template>
- <!-- 鏈�杩戣Е鍙戝垪 -->
- <template #lastTriggerTime="{ row }">
- <span v-if="row.lastTriggerTime">
- {{ formatDateTime(row.lastTriggerTime) }}
- </span>
- <span v-else class="text-muted-foreground">鏈Е鍙�</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: row.status === CommonStatusEnum.ENABLE ? '鍋滅敤' : '鍚敤',
- type: 'link',
- icon:
- row.status === CommonStatusEnum.ENABLE
- ? 'ant-design:stop-outlined'
- : 'ant-design:check-circle-outlined',
- popConfirm: {
- title: `纭${row.status === CommonStatusEnum.ENABLE ? '鍋滅敤' : '鍚敤'}鍦烘櫙瑙勫垯銆�${row.name}銆嶅悧锛焋,
- confirm: handleToggleStatus.bind(null, row),
- },
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/rule/scene/modules/form.vue b/src/views/iot/rule/scene/modules/form.vue
deleted file mode 100644
index a35e5b0..0000000
--- a/src/views/iot/rule/scene/modules/form.vue
+++ /dev/null
@@ -1,185 +0,0 @@
-<script lang="ts" setup>
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import { computed, nextTick, reactive, ref } from 'vue';
-
-import { useVbenDrawer } from '../../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum, IotRuleSceneTriggerTypeEnum } from '../../../../../packages/constants/src';
-
-import { Form, message } from 'ant-design-vue';
-
-import {
- createSceneRule,
- getSceneRule,
- updateSceneRule,
-} from '#/api/iot/rule/scene';
-import { $t } from '#/locales';
-import {
- validateSceneRuleActions,
- validateSceneRuleTriggers,
-} from '#/views/iot/utils/scene-rule';
-
-import ActionSection from '../form/sections/action-section.vue';
-import BasicInfoSection from '../form/sections/basic-info-section.vue';
-import TriggerSection from '../form/sections/trigger-section.vue';
-
-defineOptions({ name: 'IoTRuleSceneForm' });
-
-const emit = defineEmits(['success']);
-
-const formRef = ref();
-const formData = ref<RuleSceneApi.SceneRule>(buildEmptyFormData());
-
-const getTitle = computed(() =>
- formData.value.id
- ? $t('ui.actionTitle.edit', ['鍦烘櫙瑙勫垯'])
- : $t('ui.actionTitle.create', ['鍦烘櫙瑙勫垯']),
-);
-
-const [Drawer, drawerApi] = useVbenDrawer({
- async onConfirm() {
- try {
- await formRef.value?.validate();
- } catch {
- return;
- }
- const triggerError = validateSceneRuleTriggers(formData.value.triggers);
- if (triggerError) {
- message.error(triggerError);
- return;
- }
- const actionError = validateSceneRuleActions(formData.value.actions);
- if (actionError) {
- message.error(actionError);
- return;
- }
- drawerApi.lock();
- try {
- const data = { ...formData.value } as RuleSceneApi.SceneRule;
- await (data.id ? updateSceneRule(data) : createSceneRule(data));
- await drawerApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- drawerApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- const data = drawerApi.getData<RuleSceneApi.SceneRule>();
- // 鍏堢敤鍒楄〃浼犲叆鐨勬暟鎹珛鍗冲洖濉紝閬垮厤 await 鏈熼棿琛ㄥ崟鏄剧ず绌虹櫧
- formData.value = data?.id ? normalizeFormData(data) : buildEmptyFormData();
- await nextTick();
- formRef.value?.clearValidate?.();
- // 鍐嶅紓姝ユ媺鍙栬鎯咃紝瑕嗙洊浠ヤ繚璇佸瓧娈垫渶鏂�
- if (data?.id) {
- drawerApi.lock();
- try {
- const fresh = await getSceneRule(data.id);
- formData.value = normalizeFormData(fresh);
- } finally {
- drawerApi.unlock();
- }
- }
- },
-});
-
-/** 鏋勯�犵┖鐧借〃鍗曟暟鎹� */
-function buildEmptyFormData(): RuleSceneApi.SceneRule {
- return {
- name: '',
- description: '',
- status: CommonStatusEnum.ENABLE,
- triggers: [
- {
- type: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
- productId: undefined,
- deviceId: undefined,
- identifier: undefined,
- operator: undefined,
- value: undefined,
- cronExpression: undefined,
- conditionGroups: [],
- },
- ],
- actions: [],
- };
-}
-
-/** 鍥炴樉鏃跺厹搴曪紝淇濊瘉瑙﹀彂鍣�/鎵ц鍣ㄦ暟缁勪笉涓虹┖ */
-function normalizeFormData(result: any): RuleSceneApi.SceneRule {
- const triggers: RuleSceneApi.Trigger[] = result.triggers?.length
- ? result.triggers
- : buildEmptyFormData().triggers!;
- const actions: RuleSceneApi.Action[] = result.actions || [];
- return {
- ...result,
- triggers,
- actions,
- };
-}
-
-/** 瑙﹀彂鍣ㄦ牎楠� */
-function validateTriggers(_rule: any, value: any, callback: any) {
- const error = validateSceneRuleTriggers(value);
- if (error) {
- callback(new Error(error));
- return;
- }
- callback();
-}
-
-/** 鎵ц鍣ㄦ牎楠� */
-function validateActions(_rule: any, value: any, callback: any) {
- const error = validateSceneRuleActions(value);
- if (error) {
- callback(new Error(error));
- return;
- }
- callback();
-}
-
-const formRules = reactive({
- name: [
- { required: true, message: '鍦烘櫙鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
- {
- type: 'string',
- min: 1,
- max: 50,
- message: '鍦烘櫙鍚嶇О闀垮害搴斿湪 1-50 涓瓧绗︿箣闂�',
- trigger: 'blur',
- },
- ],
- status: [{ required: true, message: '鍦烘櫙鐘舵�佷笉鑳戒负绌�', trigger: 'change' }],
- description: [
- {
- type: 'string',
- max: 200,
- message: '鍦烘櫙鎻忚堪涓嶈兘瓒呰繃 200 涓瓧绗�',
- trigger: 'blur',
- },
- ],
- triggers: [
- { required: true, validator: validateTriggers, trigger: 'change' },
- ],
- actions: [{ required: true, validator: validateActions, trigger: 'change' }],
-});
-</script>
-
-<template>
- <Drawer :title="getTitle" class="w-4/5">
- <Form
- ref="formRef"
- :model="formData"
- :rules="formRules as any"
- class="mx-4"
- label-width="110px"
- >
- <BasicInfoSection v-model="formData" />
- <TriggerSection v-model:triggers="formData.triggers as any" />
- <ActionSection v-model:actions="formData.actions as any" />
- </Form>
- </Drawer>
-</template>
diff --git a/src/views/iot/thingmodel/data.ts b/src/views/iot/thingmodel/data.ts
deleted file mode 100644
index 96a7cfb..0000000
--- a/src/views/iot/thingmodel/data.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { DICT_TYPE, getDataTypeOptionsLabel } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'type',
- label: '鍔熻兘绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鍔熻兘绫诲瀷',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<ThingModelApi.ThingModel>['columns'] {
- return [
- { type: 'checkbox', width: 40 },
- {
- field: 'type',
- title: '鍔熻兘绫诲瀷',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_THING_MODEL_TYPE },
- },
- },
- {
- field: 'name',
- title: '鍔熻兘鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'identifier',
- title: '鏍囪瘑绗�',
- minWidth: 120,
- },
- {
- title: '鏁版嵁绫诲瀷',
- minWidth: 100,
- formatter: ({ row }) =>
- getDataTypeOptionsLabel(row.property?.dataType ?? '') || '-',
- },
- {
- title: '鏁版嵁瀹氫箟',
- minWidth: 200,
- slots: { default: 'dataDefinition' },
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/iot/thingmodel/index.vue b/src/views/iot/thingmodel/index.vue
deleted file mode 100644
index f82cc63..0000000
--- a/src/views/iot/thingmodel/index.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, inject } from 'vue';
-
-import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { IOT_PROVIDE_KEY } from '../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import { DataDefinition } from './modules/components';
-import Form from './modules/form.vue';
-import Tsl from './modules/tsl.vue';
-
-const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
-const productId = computed(() => product?.value?.id);
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [TslModal, tslModalApi] = useVbenModal({
- connectedComponent: Tsl,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鏂板鐗╂ā鍨� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鐗╂ā鍨� */
-function handleEdit(row: ThingModelApi.ThingModel) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍒犻櫎鐗╂ā鍨� */
-async function handleDelete(row: ThingModelApi.ThingModel) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteThingModel(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎵撳紑 TSL 寮圭獥 */
-function handleOpenTsl() {
- tslModalApi.open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getThingModelPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- productId: productId.value,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<ThingModelApi.ThingModel>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <TslModal />
-
- <Grid table-title="鐗╂ā鍨嬪垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鐗╂ā鍨�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['iot:thing-model:create'],
- onClick: handleCreate,
- },
- {
- label: 'TSL',
- type: 'primary',
- auth: ['iot:thing-model:query'],
- onClick: handleOpenTsl,
- },
- ]"
- />
- </template>
- <template #dataDefinition="{ row }">
- <DataDefinition :data="row" />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['iot:thing-model:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['iot:thing-model:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/iot/thingmodel/modules/components/data-definition.vue b/src/views/iot/thingmodel/modules/components/data-definition.vue
deleted file mode 100644
index 9105211..0000000
--- a/src/views/iot/thingmodel/modules/components/data-definition.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script lang="ts" setup>
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import {
- getEventTypeLabel,
- getThingModelServiceCallTypeLabel,
- IoTDataSpecsDataTypeEnum,
- IoTThingModelTypeEnum,
-} from '../../../../../packages/constants/src';
-
-defineProps<{ data: ThingModelApi.ThingModel }>();
-const NUMBER_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.INT,
-]);
-const PLACEHOLDER_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.ARRAY,
- IoTDataSpecsDataTypeEnum.DATE,
- IoTDataSpecsDataTypeEnum.STRUCT,
-]);
-const LIST_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
-]);
-</script>
-
-<template>
- <!-- 灞炴�� -->
- <template v-if="data.type === IoTThingModelTypeEnum.PROPERTY">
- <div v-if="NUMBER_TYPES.has(data.property?.dataType as any)">
- 鍙栧�艰寖鍥达細{{
- `${data.property?.dataSpecs?.min}~${data.property?.dataSpecs?.max}`
- }}
- </div>
- <div v-if="data.property?.dataType === IoTDataSpecsDataTypeEnum.TEXT">
- 鏁版嵁闀垮害锛歿{ data.property?.dataSpecs?.length }}
- </div>
- <div v-if="PLACEHOLDER_TYPES.has(data.property?.dataType as any)">-</div>
- <div v-if="LIST_TYPES.has(data.property?.dataType as any)">
- <div>
- {{
- data.property?.dataType === IoTDataSpecsDataTypeEnum.BOOL
- ? '甯冨皵鍊�'
- : '鏋氫妇鍊�'
- }}锛�
- </div>
- <div
- v-for="item in data.property?.dataSpecsList || []"
- :key="String(item.value)"
- >
- {{ item.name }}-{{ item.value }}
- </div>
- </div>
- </template>
- <!-- 鏈嶅姟 -->
- <div v-if="data.type === IoTThingModelTypeEnum.SERVICE">
- 璋冪敤鏂瑰紡锛�
- {{ getThingModelServiceCallTypeLabel(data.service?.callType as any) }}
- </div>
- <!-- 浜嬩欢 -->
- <div v-if="data.type === IoTThingModelTypeEnum.EVENT">
- 浜嬩欢绫诲瀷锛歿{ getEventTypeLabel(data.event?.type as any) }}
- </div>
-</template>
diff --git a/src/views/iot/thingmodel/modules/components/index.ts b/src/views/iot/thingmodel/modules/components/index.ts
deleted file mode 100644
index d6316d6..0000000
--- a/src/views/iot/thingmodel/modules/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DataDefinition } from './data-definition.vue';
diff --git a/src/views/iot/thingmodel/modules/data-specs/array.vue b/src/views/iot/thingmodel/modules/data-specs/array.vue
deleted file mode 100644
index f806cf6..0000000
--- a/src/views/iot/thingmodel/modules/data-specs/array.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-<!-- dataType锛歛rray 鏁扮粍绫诲瀷 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { getDataTypeOptions, IoTDataSpecsDataTypeEnum } from '../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, Radio } from 'ant-design-vue';
-
-import { ThingModelFormRules } from '#/api/iot/thingmodel';
-
-import ThingModelStructDataSpecs from './struct.vue';
-
-const props = defineProps<{ modelValue: any }>();
-const emits = defineEmits(['update:modelValue']);
-/** 鏁扮粍鍏冪礌绂佹閫夋嫨鐨勭被鍨� */
-const EXCLUDED_CHILD_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.ARRAY,
- IoTDataSpecsDataTypeEnum.DATE,
- IoTDataSpecsDataTypeEnum.ENUM,
-]);
-const childDataTypeOptions = getDataTypeOptions().filter(
- (item) => !EXCLUDED_CHILD_TYPES.has(item.value),
-);
-
-const dataSpecs = useVModel(props, 'modelValue', emits) as Ref<any>;
-
-/** 鍏冪礌绫诲瀷鍒囨崲鏃讹紝娓呯悊鏃у瓙绫诲瀷鐨勭粨鏋勪綋灞炴�ч厤缃� */
-function handleChange(e: any) {
- const val = e?.target?.value ?? e;
- dataSpecs.value.dataSpecsList = [];
- dataSpecs.value.childDataType = val;
-}
-</script>
-
-<template>
- <Form.Item
- :name="['property', 'dataSpecs', 'childDataType']"
- :rules="ThingModelFormRules.childDataType"
- label="鍏冪礌绫诲瀷"
- >
- <Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
- <Radio
- v-for="item in childDataTypeOptions"
- :key="item.value"
- :value="item.value"
- class="w-1/3"
- >
- {{ `${item.value}(${item.label})` }}
- </Radio>
- </Radio.Group>
- </Form.Item>
- <Form.Item
- :name="['property', 'dataSpecs', 'size']"
- :rules="ThingModelFormRules.size"
- label="鍏冪礌涓暟"
- >
- <Input
- v-model:value="dataSpecs.size"
- placeholder="璇疯緭鍏ユ暟缁勪腑鐨勫厓绱犱釜鏁�"
- />
- </Form.Item>
- <!-- Struct 鍨嬮厤缃�-->
- <ThingModelStructDataSpecs
- v-if="dataSpecs.childDataType === IoTDataSpecsDataTypeEnum.STRUCT"
- v-model="dataSpecs.dataSpecsList"
- :field-path="['property', 'dataSpecs', 'dataSpecsList']"
- />
-</template>
diff --git a/src/views/iot/thingmodel/modules/data-specs/enum.vue b/src/views/iot/thingmodel/modules/data-specs/enum.vue
deleted file mode 100644
index 29cd0fa..0000000
--- a/src/views/iot/thingmodel/modules/data-specs/enum.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-<!-- dataType锛歟num 鏁扮粍绫诲瀷 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { IoTDataSpecsDataTypeEnum } from '../../../../../packages/constants/src';
-import { isEmpty } from '../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Form, Input, message } from 'ant-design-vue';
-
-import { buildIdentifierLikeNameValidator } from '#/api/iot/thingmodel';
-
-const props = defineProps<{ modelValue: any }>();
-const emits = defineEmits(['update:modelValue']);
-const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
-
-const validateEnumName = buildIdentifierLikeNameValidator('鏋氫妇鎻忚堪');
-
-/** 娣诲姞鏋氫妇椤� */
-function addEnum() {
- dataSpecsList.value.push({
- dataType: IoTDataSpecsDataTypeEnum.ENUM,
- name: '',
- value: '',
- } as any);
-}
-
-/** 鍒犻櫎鏋氫妇椤� */
-function deleteEnum(index: number) {
- if (dataSpecsList.value.length === 1) {
- message.warning('鑷冲皯闇�瑕佷竴涓灇涓鹃」');
- return;
- }
- dataSpecsList.value.splice(index, 1);
-}
-
-/** 鏍¢獙鍗曢」鏋氫妇鍊硷細蹇呭~銆佹暟瀛椼�佷笉閲嶅 */
-function validateEnumValue(_rule: any, value: any, callback: any) {
- if (isEmpty(value)) {
- callback(new Error('鏋氫妇鍊间笉鑳戒负绌�'));
- return;
- }
- if (Number.isNaN(Number(value))) {
- callback(new Error('鏋氫妇鍊煎繀椤绘槸鏁板瓧'));
- return;
- }
- const sameCount = dataSpecsList.value.filter(
- (it) => it.value === value,
- ).length;
- if (sameCount > 1) {
- callback(new Error('鏋氫妇鍊间笉鑳介噸澶�'));
- return;
- }
- callback();
-}
-
-/** 鏍¢獙鏁翠釜鏋氫妇鍒楄〃锛氶潪绌恒�佹棤绌洪」銆佹棤闈炴硶鏁板瓧銆佹棤閲嶅 */
-function validateEnumList(_rule: any, _value: any, callback: any) {
- if (isEmpty(dataSpecsList.value)) {
- callback(new Error('璇疯嚦灏戞坊鍔犱竴涓灇涓鹃」'));
- return;
- }
- const hasEmpty = dataSpecsList.value.some(
- (item) => isEmpty(item.value) || isEmpty(item.name),
- );
- if (hasEmpty) {
- callback(new Error('瀛樺湪鏈~鍐欑殑鏋氫妇鍊兼垨鎻忚堪'));
- return;
- }
- const hasInvalidNumber = dataSpecsList.value.some((item) =>
- Number.isNaN(Number(item.value)),
- );
- if (hasInvalidNumber) {
- callback(new Error('瀛樺湪闈炴暟瀛楃殑鏋氫妇鍊�'));
- return;
- }
- const values = dataSpecsList.value.map((item) => item.value);
- if (new Set(values).size !== values.length) {
- callback(new Error('瀛樺湪閲嶅鐨勬灇涓惧��'));
- return;
- }
- callback();
-}
-</script>
-
-<template>
- <Form.Item
- :rules="[{ validator: validateEnumList, trigger: 'change' }]"
- label="鏋氫妇椤�"
- >
- <div class="flex flex-col">
- <div class="flex items-center">
- <span class="flex-1"> 鍙傛暟鍊� </span>
- <span class="flex-1"> 鍙傛暟鎻忚堪 </span>
- </div>
- <div
- v-for="(item, index) in dataSpecsList"
- :key="index"
- class="mb-[5px] flex items-center justify-between"
- >
- <Form.Item
- :name="['property', 'dataSpecsList', index, 'value']"
- :rules="[
- { required: true, message: '鏋氫妇鍊间笉鑳戒负绌�', trigger: 'blur' },
- { validator: validateEnumValue, trigger: 'blur' },
- ]"
- class="mb-0 flex-1"
- >
- <Input
- v-model:value="item.value"
- placeholder="璇疯緭鍏ユ灇涓惧�硷紝濡傘��0銆�"
- />
- </Form.Item>
- <span class="mx-2">~</span>
- <Form.Item
- :name="['property', 'dataSpecsList', index, 'name']"
- :rules="[
- { required: true, message: '鏋氫妇鎻忚堪涓嶈兘涓虹┖', trigger: 'blur' },
- { validator: validateEnumName, trigger: 'blur' },
- ]"
- class="mb-0 flex-1"
- >
- <Input v-model:value="item.name" placeholder="瀵硅鏋氫妇椤圭殑鎻忚堪" />
- </Form.Item>
- <Button class="ml-2.5" type="link" @click="deleteEnum(index)">
- 鍒犻櫎
- </Button>
- </div>
- <Button type="link" @click="addEnum">+ 娣诲姞鏋氫妇椤�</Button>
- </div>
- </Form.Item>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-form-item) {
- .ant-form-item {
- margin-bottom: 0;
- }
-}
-</style>
diff --git a/src/views/iot/thingmodel/modules/data-specs/index.ts b/src/views/iot/thingmodel/modules/data-specs/index.ts
deleted file mode 100644
index 5b75f0a..0000000
--- a/src/views/iot/thingmodel/modules/data-specs/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export { default as ThingModelArrayDataSpecs } from './array.vue';
-export { default as ThingModelEnumDataSpecs } from './enum.vue';
-export { default as ThingModelNumberDataSpecs } from './number.vue';
-export { default as ThingModelStructDataSpecs } from './struct.vue';
diff --git a/src/views/iot/thingmodel/modules/data-specs/number.vue b/src/views/iot/thingmodel/modules/data-specs/number.vue
deleted file mode 100644
index 77de4b1..0000000
--- a/src/views/iot/thingmodel/modules/data-specs/number.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-<!-- dataType锛歯umber 鏁扮粍绫诲瀷 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, Select } from 'ant-design-vue';
-
-const props = defineProps<{ modelValue: any }>();
-const emits = defineEmits(['update:modelValue']);
-const dataSpecs = useVModel(
- props,
- 'modelValue',
- emits,
-) as Ref<ThingModelApi.DataSpecsNumberData>;
-
-/** 鍗曚綅瀛楀吀閫夐」 */
-const unitOptions = computed(() =>
- getDictOptions(DICT_TYPE.IOT_THING_MODEL_UNIT, 'string'),
-);
-
-/** 鍗曚綅涓嬫媺鍙樺寲鏃讹紝鎷嗗嚭 unitName 涓� unit 鍥炲啓 */
-function unitChange(unitSpecs: any) {
- if (!unitSpecs) {
- return;
- }
- const [unitName, unit] = String(unitSpecs).split('-');
- dataSpecs.value.unitName = unitName;
- dataSpecs.value.unit = unit;
-}
-
-/** 鏍¢獙鏈�灏忓�� */
-function validateMin(_rule: any, _value: any, callback: any) {
- const min = Number(dataSpecs.value.min);
- const max = Number(dataSpecs.value.max);
- if (Number.isNaN(min)) {
- callback(new Error('璇疯緭鍏ユ湁鏁堢殑鏁板��'));
- return;
- }
- if (!Number.isNaN(max) && min >= max) {
- callback(new Error('鏈�灏忓�煎繀椤诲皬浜庢渶澶у��'));
- return;
- }
- callback();
-}
-
-/** 鏍¢獙鏈�澶у�� */
-function validateMax(_rule: any, _value: any, callback: any) {
- const min = Number(dataSpecs.value.min);
- const max = Number(dataSpecs.value.max);
- if (Number.isNaN(max)) {
- callback(new Error('璇疯緭鍏ユ湁鏁堢殑鏁板��'));
- return;
- }
- if (!Number.isNaN(min) && max <= min) {
- callback(new Error('鏈�澶у�煎繀椤诲ぇ浜庢渶灏忓��'));
- return;
- }
- callback();
-}
-
-/** 鏍¢獙姝ラ暱 */
-function validateStep(_rule: any, _value: any, callback: any) {
- const step = Number(dataSpecs.value.step);
- if (Number.isNaN(step)) {
- callback(new Error('璇疯緭鍏ユ湁鏁堢殑鏁板��'));
- return;
- }
- if (step <= 0) {
- callback(new Error('姝ラ暱蹇呴』澶т簬 0'));
- return;
- }
- const min = Number(dataSpecs.value.min);
- const max = Number(dataSpecs.value.max);
- if (!Number.isNaN(min) && !Number.isNaN(max) && step > max - min) {
- callback(new Error('姝ラ暱涓嶈兘澶т簬鏈�澶у�间笌鏈�灏忓�肩殑宸��'));
- return;
- }
- callback();
-}
-</script>
-
-<template>
- <Form.Item label="鍙栧�艰寖鍥�">
- <div class="flex items-center justify-between">
- <Form.Item
- :name="['property', 'dataSpecs', 'min']"
- :rules="[
- { required: true, message: '鏈�灏忓�间笉鑳戒负绌�', trigger: 'blur' },
- { validator: validateMin, trigger: 'blur' },
- ]"
- class="mb-0 flex-1"
- >
- <Input v-model:value="dataSpecs.min" placeholder="璇疯緭鍏ユ渶灏忓��" />
- </Form.Item>
- <span class="mx-2">~</span>
- <Form.Item
- :name="['property', 'dataSpecs', 'max']"
- :rules="[
- { required: true, message: '鏈�澶у�间笉鑳戒负绌�', trigger: 'blur' },
- { validator: validateMax, trigger: 'blur' },
- ]"
- class="mb-0 flex-1"
- >
- <Input v-model:value="dataSpecs.max" placeholder="璇疯緭鍏ユ渶澶у��" />
- </Form.Item>
- </div>
- </Form.Item>
- <Form.Item
- :name="['property', 'dataSpecs', 'step']"
- :rules="[
- { required: true, message: '姝ラ暱涓嶈兘涓虹┖', trigger: 'blur' },
- { validator: validateStep, trigger: 'blur' },
- ]"
- label="姝ラ暱"
- >
- <Input v-model:value="dataSpecs.step" placeholder="璇疯緭鍏ユ闀�" />
- </Form.Item>
- <Form.Item
- :name="['property', 'dataSpecs', 'unit']"
- :rules="[{ required: true, message: '璇烽�夋嫨鍗曚綅', trigger: 'change' }]"
- label="鍗曚綅"
- >
- <Select
- :value="dataSpecs.unit ? `${dataSpecs.unitName}-${dataSpecs.unit}` : ''"
- show-search
- placeholder="璇烽�夋嫨鍗曚綅"
- class="w-full"
- @change="unitChange"
- >
- <Select.Option
- v-for="(item, index) in unitOptions"
- :key="index"
- :value="`${item.label}-${item.value}`"
- >
- {{ `${item.label}-${item.value}` }}
- </Select.Option>
- </Select>
- </Form.Item>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-form-item) {
- .ant-form-item {
- margin-bottom: 0;
- }
-}
-</style>
diff --git a/src/views/iot/thingmodel/modules/data-specs/struct.vue b/src/views/iot/thingmodel/modules/data-specs/struct.vue
deleted file mode 100644
index d9b32ed..0000000
--- a/src/views/iot/thingmodel/modules/data-specs/struct.vue
+++ /dev/null
@@ -1,178 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { onMounted, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { IoTDataSpecsDataTypeEnum } from '../../../../../packages/constants/src';
-import { cloneDeep, isEmpty } from '../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Divider, Form, Input } from 'ant-design-vue';
-
-import { ThingModelFormRules } from '#/api/iot/thingmodel';
-
-import ThingModelProperty from '../property.vue';
-
-const props = withDefaults(
- defineProps<{
- /** 鐖惰〃鍗曚腑 dataSpecsList 鐨� name 璺緞锛岄粯璁� property.dataSpecsList锛�
- * array 宓屽 struct 鏃剁埗绾ч渶浼� ['property', 'dataSpecs', 'dataSpecsList']锛�
- * 鍚﹀垯鐖惰〃鍗� validate() 鏃犳硶瀹氫綅璇ュ瓧娈碉紝闈炵┖鏍¢獙涓嶄細瑙﹀彂銆� */
- fieldPath?: string[];
- modelValue: any;
- }>(),
- {
- fieldPath: () => ['property', 'dataSpecsList'],
- },
-);
-const emits = defineEmits(['update:modelValue']);
-const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
-
-const structFormRef = ref();
-const formData = ref<any>(buildEmptyFormData());
-
-/** 鏍¢獙缁撴瀯浣撳睘鎬у璞¢潪绌� */
-function validateStructSpecsList(_rule: any, _value: any, callback: any) {
- if (isEmpty(dataSpecsList.value)) {
- callback(new Error('璇疯嚦灏戞坊鍔犱竴涓粨鏋勪綋灞炴�у璞�'));
- return;
- }
- callback();
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- try {
- await structFormRef.value?.validate();
- } catch {
- return;
- }
- const data = formData.value;
- const item = {
- identifier: data.identifier,
- name: data.name,
- description: data.description,
- dataType: IoTDataSpecsDataTypeEnum.STRUCT,
- childDataType: data.property.dataType,
- dataSpecs:
- !isEmpty(data.property.dataSpecs) &&
- Object.keys(data.property.dataSpecs).length > 1
- ? data.property.dataSpecs
- : undefined,
- dataSpecsList: isEmpty(data.property.dataSpecsList)
- ? undefined
- : data.property.dataSpecsList,
- };
- const existingIndex = dataSpecsList.value.findIndex(
- (spec) => spec.identifier === data.identifier,
- );
- if (existingIndex === -1) {
- dataSpecsList.value.push(item);
- } else {
- dataSpecsList.value[existingIndex] = item;
- }
- await modalApi.close();
- },
- onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- formData.value = buildEmptyFormData();
- structFormRef.value?.clearValidate?.();
- const data = modalApi.getData<any>();
- if (isEmpty(data)) {
- return;
- }
- // 缂栬緫鍥炴樉鏃� cloneDeep锛岄伩鍏嶅脊绐� v-model 鏀瑰埌鍘熷瀵硅薄锛堢敤鎴峰彇娑堟椂涓嶆薄鏌撳灞� dataSpecsList锛�
- formData.value = {
- identifier: data.identifier ?? '',
- name: data.name ?? '',
- description: data.description ?? '',
- property: {
- dataType: data.childDataType ?? IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: data.dataSpecs ? cloneDeep(data.dataSpecs) : {},
- dataSpecsList: data.dataSpecsList ? cloneDeep(data.dataSpecsList) : [],
- },
- };
- },
-});
-
-/** 鏋勯�犵┖鐧界粨鏋勪綋琛ㄥ崟 */
-function buildEmptyFormData() {
- return {
- identifier: '',
- name: '',
- description: '',
- property: {
- dataType: IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
- dataSpecsList: [],
- },
- };
-}
-
-/** 鎵撳紑缁撴瀯浣撹〃鍗� */
-function openStructForm(val: any) {
- modalApi.setData(val).open();
-}
-
-/** 鍒犻櫎缁撴瀯浣撻」 */
-function deleteStructItem(index: number) {
- dataSpecsList.value.splice(index, 1);
-}
-
-onMounted(() => {
- if (isEmpty(dataSpecsList.value)) {
- dataSpecsList.value = [];
- }
-});
-</script>
-
-<template>
- <Form.Item
- :name="fieldPath"
- :rules="[{ validator: validateStructSpecsList, trigger: 'change' }]"
- label="灞炴�у璞�"
- >
- <div
- v-for="(item, index) in dataSpecsList"
- :key="index"
- class="mb-2.5 flex w-full justify-between bg-gray-100 px-2.5 dark:bg-gray-800"
- >
- <span>鍙傛暟锛歿{ item.name }}</span>
- <div>
- <Button type="link" @click="openStructForm(item)">缂栬緫</Button>
- <Divider type="vertical" />
- <Button danger type="link" @click="deleteStructItem(index)">
- 鍒犻櫎
- </Button>
- </div>
- </div>
- <Button type="link" @click="openStructForm(null)">+ 鏂板鍙傛暟</Button>
- </Form.Item>
-
- <!-- 缁撴瀯浣撳弬鏁拌〃鍗� -->
- <Modal class="w-2/5" title="缁撴瀯浣撳弬鏁�">
- <Form
- ref="structFormRef"
- :label-col="{ span: 6 }"
- :model="formData"
- :wrapper-col="{ span: 18 }"
- class="mx-4"
- >
- <Form.Item :rules="ThingModelFormRules.name" label="鍙傛暟鍚嶇О" name="name">
- <Input v-model:value="formData.name" placeholder="璇疯緭鍏ュ弬鏁板悕绉�" />
- </Form.Item>
- <Form.Item
- :rules="ThingModelFormRules.identifier"
- label="鏍囪瘑绗�"
- name="identifier"
- >
- <Input v-model:value="formData.identifier" placeholder="璇疯緭鍏ユ爣璇嗙" />
- </Form.Item>
- <!-- 灞炴�ч厤缃� -->
- <ThingModelProperty v-model="formData.property" is-struct-data-specs />
- </Form>
- </Modal>
-</template>
diff --git a/src/views/iot/thingmodel/modules/event.vue b/src/views/iot/thingmodel/modules/event.vue
deleted file mode 100644
index d5f0a41..0000000
--- a/src/views/iot/thingmodel/modules/event.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<!-- 浜у搧鐨勭墿妯″瀷琛ㄥ崟锛坋vent 椤癸級 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { watch } from 'vue';
-
-import {
- IoTThingModelEventTypeEnum,
- IoTThingModelParamDirectionEnum,
-} from '../../../../packages/constants/src';
-import { isEmpty } from '../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Radio } from 'ant-design-vue';
-
-import { ThingModelFormRules } from '#/api/iot/thingmodel';
-
-import ThingModelInputOutputParam from './input-output-param.vue';
-
-const props = defineProps<{ modelValue: any }>();
-const emits = defineEmits(['update:modelValue']);
-const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
-
-/** 榛樿閫変腑锛孖NFO 淇℃伅 */
-watch(
- () => thingModelEvent.value.type,
- (val: string | undefined) =>
- isEmpty(val) &&
- (thingModelEvent.value.type = IoTThingModelEventTypeEnum.INFO.value),
- { immediate: true },
-);
-</script>
-
-<template>
- <Form.Item
- :name="['event', 'type']"
- :rules="ThingModelFormRules.eventType"
- label="浜嬩欢绫诲瀷"
- >
- <Radio.Group v-model:value="thingModelEvent.type">
- <Radio
- v-for="eventType in Object.values(IoTThingModelEventTypeEnum)"
- :key="eventType.value"
- :value="eventType.value"
- >
- {{ eventType.label }}
- </Radio>
- </Radio.Group>
- </Form.Item>
- <Form.Item label="杈撳嚭鍙傛暟">
- <ThingModelInputOutputParam
- v-model="thingModelEvent.outputParams"
- :direction="IoTThingModelParamDirectionEnum.OUTPUT"
- />
- </Form.Item>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-form-item) {
- .ant-form-item {
- margin-bottom: 0;
- }
-}
-</style>
diff --git a/src/views/iot/thingmodel/modules/form.vue b/src/views/iot/thingmodel/modules/form.vue
deleted file mode 100644
index d8a1dd9..0000000
--- a/src/views/iot/thingmodel/modules/form.vue
+++ /dev/null
@@ -1,246 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { IotProductApi } from '#/api/iot/product/product';
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, inject, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import {
- DICT_TYPE,
- IOT_PROVIDE_KEY,
- IoTDataSpecsDataTypeEnum,
- IoTThingModelTypeEnum,
-} from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { $t } from '../../../../packages/locales/src';
-import { cloneDeep, isEmpty } from '../../../../packages/utils/src';
-
-import { Form, Input, message, Radio } from 'ant-design-vue';
-
-import {
- createThingModel,
- getThingModel,
- ThingModelFormRules,
- updateThingModel,
-} from '#/api/iot/thingmodel';
-
-import ThingModelEvent from './event.vue';
-import ThingModelProperty from './property.vue';
-import ThingModelService from './service.vue';
-
-const emit = defineEmits(['success']);
-
-const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
-
-const formRef = ref();
-const formData = ref<ThingModelApi.ThingModel>(buildEmptyFormData());
-
-const getTitle = computed(() =>
- formData.value.id
- ? $t('ui.actionTitle.edit', ['鐗╂ā鍨�'])
- : $t('ui.actionTitle.create', ['鐗╂ā鍨�']),
-);
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- try {
- await formRef.value?.validate();
- } catch {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = cloneDeep(formData.value);
- data.productId = product!.value.id;
- data.productKey = product!.value.productKey;
- fillExtraAttributes(data);
- try {
- await (data.id ? updateThingModel(data) : createThingModel(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 姣忔鎵撳紑閮介噸缃紱閬垮厤涓婁竴娆$殑鐘舵�佹畫鐣�
- formData.value = buildEmptyFormData();
- formRef.value?.clearValidate?.();
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{ id?: number }>();
- if (!data?.id) {
- return;
- }
- modalApi.lock();
- try {
- const result = await getThingModel(data.id);
- // 璁剧疆鍒� values
- formData.value = normalizeFormData(result);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 鏋勯�犵┖鐧借〃鍗曟暟鎹� */
-function buildEmptyFormData(): ThingModelApi.ThingModel {
- return {
- type: IoTThingModelTypeEnum.PROPERTY,
- dataType: IoTDataSpecsDataTypeEnum.INT,
- property: {
- dataType: IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: {
- dataType: IoTDataSpecsDataTypeEnum.INT,
- },
- },
- service: {
- inputParams: [],
- outputParams: [],
- },
- event: {
- outputParams: [],
- },
- };
-}
-
-/** 鍥炴樉鏁版嵁鏃讹紝瑙勬暣鍚勫垎鏀瓧娈电‘淇濆瓙琛ㄥ崟鍙粦瀹� */
-function normalizeFormData(
- result: ThingModelApi.ThingModel,
-): ThingModelApi.ThingModel {
- const next: any = { ...result, type: Number(result.type) };
- if (isEmpty(next.property)) {
- next.dataType = IoTDataSpecsDataTypeEnum.INT;
- next.property = {
- dataType: IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
- };
- } else {
- next.property.dataSpecs ??= {};
- next.property.dataSpecsList ??= [];
- next.property.dataType ??= IoTDataSpecsDataTypeEnum.INT;
- }
- if (isEmpty(next.service)) {
- next.service = { inputParams: [], outputParams: [] };
- } else {
- next.service.inputParams ??= [];
- next.service.outputParams ??= [];
- }
- if (isEmpty(next.event)) {
- next.event = { outputParams: [] };
- } else {
- next.event.outputParams ??= [];
- }
- return next;
-}
-
-/** 鎸夊姛鑳界被鍨嬪皢瀛愯〃鍗曟暟鎹洖鍐欏埌椤跺眰锛屽苟娓呯悊鏃犲叧鍒嗘敮 */
-function fillExtraAttributes(data: any) {
- switch (data.type) {
- case IoTThingModelTypeEnum.EVENT: {
- removeDataSpecs(data.event);
- data.dataType = data.event.dataType;
- data.event.identifier = data.identifier;
- data.event.name = data.name;
- delete data.property;
- delete data.service;
-
- break;
- }
- case IoTThingModelTypeEnum.PROPERTY: {
- removeDataSpecs(data.property);
- data.dataType = data.property.dataType;
- data.property.identifier = data.identifier;
- data.property.name = data.name;
- delete data.service;
- delete data.event;
-
- break;
- }
- case IoTThingModelTypeEnum.SERVICE: {
- removeDataSpecs(data.service);
- data.dataType = data.service.dataType;
- data.service.identifier = data.identifier;
- data.service.name = data.name;
- delete data.property;
- delete data.event;
-
- break;
- }
- // No default
- }
-}
-
-/** 娓呯悊绌虹殑 dataSpecs / dataSpecsList */
-function removeDataSpecs(val: any) {
- if (isEmpty(val.dataSpecs)) {
- delete val.dataSpecs;
- }
- if (isEmpty(val.dataSpecsList)) {
- delete val.dataSpecsList;
- }
-}
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form
- ref="formRef"
- :label-col="{ span: 6 }"
- :model="formData"
- :wrapper-col="{ span: 18 }"
- class="mx-4"
- >
- <Form.Item :rules="ThingModelFormRules.type" label="鍔熻兘绫诲瀷" name="type">
- <Radio.Group v-model:value="formData.type">
- <Radio.Button
- v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
- :key="String(dict.value)"
- :value="Number(dict.value)"
- >
- {{ dict.label }}
- </Radio.Button>
- </Radio.Group>
- </Form.Item>
- <Form.Item :rules="ThingModelFormRules.name" label="鍔熻兘鍚嶇О" name="name">
- <Input v-model:value="formData.name" placeholder="璇疯緭鍏ュ姛鑳藉悕绉�" />
- </Form.Item>
- <Form.Item
- :rules="ThingModelFormRules.identifier"
- label="鏍囪瘑绗�"
- name="identifier"
- >
- <Input v-model:value="formData.identifier" placeholder="璇疯緭鍏ユ爣璇嗙" />
- </Form.Item>
- <!-- 灞炴�ч厤缃� -->
- <ThingModelProperty
- v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
- v-model="formData.property"
- />
- <!-- 鏈嶅姟閰嶇疆 -->
- <ThingModelService
- v-if="formData.type === IoTThingModelTypeEnum.SERVICE"
- v-model="formData.service"
- />
- <!-- 浜嬩欢閰嶇疆 -->
- <ThingModelEvent
- v-if="formData.type === IoTThingModelTypeEnum.EVENT"
- v-model="formData.event"
- />
- <Form.Item label="鎻忚堪" name="description">
- <Input.TextArea
- v-model:value="formData.description"
- :maxlength="200"
- :rows="3"
- placeholder="璇疯緭鍏ョ墿妯″瀷鎻忚堪"
- />
- </Form.Item>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/iot/thingmodel/modules/input-output-param.vue b/src/views/iot/thingmodel/modules/input-output-param.vue
deleted file mode 100644
index 53a1567..0000000
--- a/src/views/iot/thingmodel/modules/input-output-param.vue
+++ /dev/null
@@ -1,163 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { IoTDataSpecsDataTypeEnum } from '../../../../packages/constants/src';
-import { cloneDeep, isEmpty } from '../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Divider, Form, Input, message } from 'ant-design-vue';
-
-import { ThingModelFormRules } from '#/api/iot/thingmodel';
-
-import ThingModelProperty from './property.vue';
-
-const props = defineProps<{
- direction: string;
- existingIdentifiers?: string[];
- modelValue: any;
-}>();
-const emits = defineEmits(['update:modelValue']);
-const thingModelParams = useVModel(props, 'modelValue', emits) as Ref<any[]>;
-
-const paramFormRef = ref();
-const formData = ref<any>(buildEmptyFormData());
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- try {
- await paramFormRef.value?.validate();
- } catch {
- return;
- }
- if (!thingModelParams.value) {
- thingModelParams.value = [];
- }
- // 缁勮琛ㄥ崟
- const data = formData.value;
- if (
- data.identifier &&
- props.existingIdentifiers?.includes(data.identifier)
- ) {
- message.warning('杈撳叆鍙傛暟鍜岃緭鍑哄弬鏁版爣璇嗙涓嶈兘閲嶅');
- return;
- }
- const item = {
- identifier: data.identifier,
- name: data.name,
- description: data.description,
- dataType: data.property.dataType,
- paraOrder: 0,
- direction: props.direction,
- dataSpecs:
- !isEmpty(data.property.dataSpecs) &&
- Object.keys(data.property.dataSpecs).length > 1
- ? data.property.dataSpecs
- : undefined,
- dataSpecsList: isEmpty(data.property.dataSpecsList)
- ? undefined
- : data.property.dataSpecsList,
- };
- // 鎸� identifier 鍘婚噸锛涘瓨鍦ㄥ垯鏇存柊锛屽惁鍒欒拷鍔�
- const existingIndex = thingModelParams.value.findIndex(
- (spec) => spec.identifier === data.identifier,
- );
- if (existingIndex === -1) {
- thingModelParams.value.push(item);
- } else {
- thingModelParams.value[existingIndex] = item;
- }
- // 鍏抽棴
- await modalApi.close();
- },
- onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- formData.value = buildEmptyFormData();
- paramFormRef.value?.clearValidate?.();
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<any>();
- if (isEmpty(data)) {
- return;
- }
- // 缂栬緫鍥炴樉鏃� cloneDeep锛岄伩鍏嶅脊绐� v-model 鏀瑰埌鍘熷瀵硅薄锛堢敤鎴峰彇娑堟椂涓嶆薄鏌撳灞� thingModelParams锛�
- formData.value = {
- identifier: data.identifier ?? '',
- name: data.name ?? '',
- description: data.description ?? '',
- property: {
- dataType: data.dataType ?? IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: data.dataSpecs ? cloneDeep(data.dataSpecs) : {},
- dataSpecsList: data.dataSpecsList ? cloneDeep(data.dataSpecsList) : [],
- },
- };
- },
-});
-
-/** 鏋勯�犵┖鐧藉弬鏁拌〃鍗� */
-function buildEmptyFormData() {
- return {
- identifier: '',
- name: '',
- description: '',
- property: {
- dataType: IoTDataSpecsDataTypeEnum.INT,
- dataSpecs: { dataType: IoTDataSpecsDataTypeEnum.INT },
- dataSpecsList: [],
- },
- };
-}
-
-/** 鎵撳紑鍙傛暟琛ㄥ崟锛堟柊澧炴垨缂栬緫锛� */
-function openParamForm(val: any) {
- modalApi.setData(val).open();
-}
-
-/** 鍒犻櫎鍙傛暟椤� */
-function deleteParamItem(index: number) {
- thingModelParams.value.splice(index, 1);
-}
-</script>
-
-<template>
- <div
- v-for="(item, index) in thingModelParams"
- :key="index"
- class="mb-2.5 flex w-full justify-between bg-gray-100 px-2.5 dark:bg-gray-800"
- >
- <span>鍙傛暟鍚嶇О锛歿{ item.name }}</span>
- <div>
- <Button type="link" @click="openParamForm(item)">缂栬緫</Button>
- <Divider type="vertical" />
- <Button danger type="link" @click="deleteParamItem(index)">鍒犻櫎</Button>
- </div>
- </div>
- <Button type="link" @click="openParamForm(null)">+ 鏂板鍙傛暟</Button>
-
- <!-- 鍙傛暟琛ㄥ崟 -->
- <Modal class="w-2/5" title="鍙傛暟閰嶇疆">
- <Form
- ref="paramFormRef"
- :label-col="{ span: 6 }"
- :model="formData"
- :wrapper-col="{ span: 18 }"
- class="mx-4"
- >
- <Form.Item :rules="ThingModelFormRules.name" label="鍙傛暟鍚嶇О" name="name">
- <Input v-model:value="formData.name" placeholder="璇疯緭鍏ュ弬鏁板悕绉�" />
- </Form.Item>
- <Form.Item
- :rules="ThingModelFormRules.identifier"
- label="鏍囪瘑绗�"
- name="identifier"
- >
- <Input v-model:value="formData.identifier" placeholder="璇疯緭鍏ユ爣璇嗙" />
- </Form.Item>
- <!-- 灞炴�ч厤缃� -->
- <ThingModelProperty v-model="formData.property" is-params />
- </Form>
- </Modal>
-</template>
diff --git a/src/views/iot/thingmodel/modules/property.vue b/src/views/iot/thingmodel/modules/property.vue
deleted file mode 100644
index fff3baa..0000000
--- a/src/views/iot/thingmodel/modules/property.vue
+++ /dev/null
@@ -1,222 +0,0 @@
-<!-- 浜у搧鐨勭墿妯″瀷琛ㄥ崟锛坧roperty 椤癸級 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { ThingModelApi } from '#/api/iot/thingmodel';
-
-import { computed, watch } from 'vue';
-
-import {
- getDataTypeOptions,
- IoTDataSpecsDataTypeEnum,
- IoTThingModelAccessModeEnum,
-} from '../../../../packages/constants/src';
-import { isEmpty } from '../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Input, Radio, Select } from 'ant-design-vue';
-
-import { ThingModelFormRules, validateBoolName } from '#/api/iot/thingmodel';
-
-import {
- ThingModelArrayDataSpecs,
- ThingModelEnumDataSpecs,
- ThingModelNumberDataSpecs,
- ThingModelStructDataSpecs,
-} from './data-specs';
-
-const props = defineProps<{
- isParams?: boolean;
- isStructDataSpecs?: boolean;
- modelValue: any;
-}>();
-const emits = defineEmits(['update:modelValue']);
-/** 宓屽鍦ㄧ粨鏋勪綋閲屾椂锛岀姝㈠啀閫夋暟缁� / 缁撴瀯浣擄紙鏈�澶氭敮鎸佷袱灞傚祵濂楋級 */
-const NESTED_EXCLUDED_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.ARRAY,
- IoTDataSpecsDataTypeEnum.STRUCT,
-]);
-const STRUCT_CHILD_OPTIONS = getDataTypeOptions().filter(
- (item) => !NESTED_EXCLUDED_TYPES.has(item.value),
-);
-/** 鏁板�煎瀷鏁版嵁绫诲瀷闆嗗悎 */
-const NUMERIC_TYPES = new Set<string>([
- IoTDataSpecsDataTypeEnum.DOUBLE,
- IoTDataSpecsDataTypeEnum.FLOAT,
- IoTDataSpecsDataTypeEnum.INT,
-]);
-
-const property = useVModel(
- props,
- 'modelValue',
- emits,
-) as Ref<ThingModelApi.Property>;
-
-const dataTypeOptions = computed(() =>
- props.isStructDataSpecs ? STRUCT_CHILD_OPTIONS : getDataTypeOptions(),
-);
-
-/** 鏁版嵁绫诲瀷鍒囨崲鏃讹紝閲嶇疆 dataSpecs / dataSpecsList 骞舵寜鏂扮被鍨嬪垵濮嬪寲 */
-function handleChange(dataType: any) {
- property.value.dataSpecs = {};
- property.value.dataSpecsList = [];
- // 鏁板�� / 鏂囨湰 / 鏃堕棿 / 鏁扮粍鍨嬫妸 dataType 鍚屾鍒� dataSpecs锛涘竷灏� / 鏋氫妇 / 缁撴瀯璧� dataSpecsList
- const listLike = [
- IoTDataSpecsDataTypeEnum.BOOL,
- IoTDataSpecsDataTypeEnum.ENUM,
- IoTDataSpecsDataTypeEnum.STRUCT,
- ];
- if (!listLike.includes(dataType)) {
- property.value.dataSpecs.dataType = dataType;
- }
- if (dataType === IoTDataSpecsDataTypeEnum.BOOL) {
- for (let i = 0; i < 2; i++) {
- property.value.dataSpecsList.push({
- dataType: IoTDataSpecsDataTypeEnum.BOOL,
- name: '', // 甯冨皵鎻忚堪
- value: i, // 甯冨皵鍊�
- });
- }
- } else if (dataType === IoTDataSpecsDataTypeEnum.ENUM) {
- property.value.dataSpecsList.push({
- dataType: IoTDataSpecsDataTypeEnum.ENUM,
- name: '', // 鏋氫妇椤规弿杩�
- value: undefined, // 鏋氫妇鍊�
- });
- }
-}
-
-/** 椤跺眰灞炴�ц〃鍗曢娆¤繘鍏ユ椂锛岄粯璁ら�変腑銆岃鍐欍�� */
-if (!props.isStructDataSpecs && !props.isParams) {
- watch(
- () => property.value.accessMode,
- (val: string | undefined) => {
- if (isEmpty(val)) {
- property.value.accessMode =
- IoTThingModelAccessModeEnum.READ_WRITE.value;
- }
- },
- { immediate: true },
- );
-}
-</script>
-
-<template>
- <Form.Item
- :name="['property', 'dataType']"
- :rules="[{ required: true, message: '璇烽�夋嫨鏁版嵁绫诲瀷', trigger: 'change' }]"
- label="鏁版嵁绫诲瀷"
- >
- <Select
- v-model:value="property.dataType"
- placeholder="璇烽�夋嫨鏁版嵁绫诲瀷"
- @change="handleChange"
- >
- <!-- ARRAY 鍜� STRUCT 绫诲瀷鏁版嵁鐩镐簰宓屽鏃讹紝鏈�澶氭敮鎸侀�掑綊宓屽 2 灞傦紙鐖跺拰瀛愶級 -->
- <Select.Option
- v-for="option in dataTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ `${option.value}(${option.label})` }}
- </Select.Option>
- </Select>
- </Form.Item>
- <!-- 鏁板�煎瀷閰嶇疆 -->
- <ThingModelNumberDataSpecs
- v-if="NUMERIC_TYPES.has(property.dataType || '')"
- v-model="property.dataSpecs"
- />
- <!-- 鏋氫妇鍨嬮厤缃� -->
- <ThingModelEnumDataSpecs
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.ENUM"
- v-model="property.dataSpecsList"
- />
- <!-- 甯冨皵鍨嬮厤缃� -->
- <Form.Item
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL"
- label="甯冨皵鍊�"
- >
- <template v-for="(item, index) in property.dataSpecsList" :key="item.value">
- <div class="mb-[5px] flex w-full items-center justify-start">
- <span>{{ item.value }}</span>
- <span class="mx-2">-</span>
- <Form.Item
- :name="['property', 'dataSpecsList', index, 'name']"
- :rules="[
- { required: true, message: '甯冨皵鎻忚堪涓嶈兘涓虹┖', trigger: 'blur' },
- { validator: validateBoolName, trigger: 'blur' },
- ]"
- class="mb-0 flex-1"
- >
- <Input
- v-model:value="item.name"
- :placeholder="`濡傦細${item.value === 0 ? '鍏�' : '寮�'}`"
- class="!w-[255px]"
- />
- </Form.Item>
- </div>
- </template>
- </Form.Item>
- <!-- 鏂囨湰鍨嬮厤缃� -->
- <Form.Item
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
- :name="['property', 'dataSpecs', 'length']"
- :rules="ThingModelFormRules.length"
- label="鏁版嵁闀垮害"
- >
- <Input
- v-model:value="property.dataSpecs!.length"
- class="!w-[255px]"
- placeholder="璇疯緭鍏ユ枃鏈瓧鑺傞暱搴�"
- >
- <template #addonAfter>瀛楄妭</template>
- </Input>
- </Form.Item>
- <!-- 鏃堕棿鍨嬮厤缃� -->
- <Form.Item
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.DATE"
- label="鏃堕棿鏍煎紡"
- name="date"
- >
- <Input
- class="!w-[255px]"
- disabled
- placeholder="String 绫诲瀷鐨� UTC 鏃堕棿鎴筹紙姣锛�"
- />
- </Form.Item>
- <!-- 鏁扮粍鍨嬮厤缃�-->
- <ThingModelArrayDataSpecs
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.ARRAY"
- v-model="property.dataSpecs"
- />
- <!-- Struct 鍨嬮厤缃�-->
- <ThingModelStructDataSpecs
- v-if="property.dataType === IoTDataSpecsDataTypeEnum.STRUCT"
- v-model="property.dataSpecsList"
- />
- <Form.Item
- v-if="!isStructDataSpecs && !isParams"
- :name="['property', 'accessMode']"
- :rules="ThingModelFormRules.accessMode"
- label="璇诲啓绫诲瀷"
- >
- <Radio.Group v-model:value="property.accessMode">
- <Radio
- v-for="accessMode in Object.values(IoTThingModelAccessModeEnum)"
- :key="accessMode.value"
- :value="accessMode.value"
- >
- {{ accessMode.label }}
- </Radio>
- </Radio.Group>
- </Form.Item>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-form-item) {
- .ant-form-item {
- margin-bottom: 0;
- }
-}
-</style>
diff --git a/src/views/iot/thingmodel/modules/service.vue b/src/views/iot/thingmodel/modules/service.vue
deleted file mode 100644
index 052e280..0000000
--- a/src/views/iot/thingmodel/modules/service.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<!-- 浜у搧鐨勭墿妯″瀷琛ㄥ崟锛坰ervice 椤癸級 -->
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import { watch } from 'vue';
-
-import {
- IoTThingModelParamDirectionEnum,
- IoTThingModelServiceCallTypeEnum,
-} from '../../../../packages/constants/src';
-import { isEmpty } from '../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Form, Radio } from 'ant-design-vue';
-
-import { ThingModelFormRules } from '#/api/iot/thingmodel';
-
-import ThingModelInputOutputParam from './input-output-param.vue';
-
-const props = defineProps<{ modelValue: any }>();
-const emits = defineEmits(['update:modelValue']);
-const service = useVModel(props, 'modelValue', emits) as Ref<any>;
-
-/** 榛樿閫変腑锛孉SYNC 寮傛 */
-watch(
- () => service.value.callType,
- (val: string | undefined) =>
- isEmpty(val) &&
- (service.value.callType = IoTThingModelServiceCallTypeEnum.ASYNC.value),
- { immediate: true },
-);
-
-/** 鎻愬彇鍙傛暟鏍囪瘑绗﹀垪琛紝鐢ㄤ簬杈撳叆 / 杈撳嚭鍙傛暟璺ㄨ〃鍘婚噸 */
-function getParamIdentifiers(params?: any[]) {
- const identifiers: string[] = [];
- for (const item of params || []) {
- if (item.identifier) {
- identifiers.push(item.identifier);
- }
- }
- return identifiers;
-}
-</script>
-
-<template>
- <Form.Item
- :name="['service', 'callType']"
- :rules="ThingModelFormRules.callType"
- label="璋冪敤鏂瑰紡"
- >
- <Radio.Group v-model:value="service.callType">
- <Radio
- v-for="callType in Object.values(IoTThingModelServiceCallTypeEnum)"
- :key="callType.value"
- :value="callType.value"
- >
- {{ callType.label }}
- </Radio>
- </Radio.Group>
- </Form.Item>
- <Form.Item label="杈撳叆鍙傛暟">
- <ThingModelInputOutputParam
- v-model="service.inputParams"
- :direction="IoTThingModelParamDirectionEnum.INPUT"
- :existing-identifiers="getParamIdentifiers(service.outputParams)"
- />
- </Form.Item>
- <Form.Item label="杈撳嚭鍙傛暟">
- <ThingModelInputOutputParam
- v-model="service.outputParams"
- :direction="IoTThingModelParamDirectionEnum.OUTPUT"
- :existing-identifiers="getParamIdentifiers(service.inputParams)"
- />
- </Form.Item>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-form-item) {
- .ant-form-item {
- margin-bottom: 0;
- }
-}
-</style>
diff --git a/src/views/iot/thingmodel/modules/tsl.vue b/src/views/iot/thingmodel/modules/tsl.vue
deleted file mode 100644
index 0c4b8a2..0000000
--- a/src/views/iot/thingmodel/modules/tsl.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { IotProductApi } from '#/api/iot/product/product';
-
-import { computed, inject, ref, watch } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { IOT_PROVIDE_KEY } from '../../../../packages/constants/src';
-
-import { Radio, Textarea } from 'ant-design-vue';
-
-import { getThingModelTSLByProductId } from '#/api/iot/thingmodel';
-
-const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT);
-
-const viewMode = ref<'editor' | 'view'>('view');
-const thingModelTSL = ref<any>({});
-const tslString = ref('');
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- modalApi.lock();
- try {
- // 鍔犺浇鏁版嵁
- thingModelTSL.value = await getThingModelTSLByProductId(
- product?.value?.id || 0,
- );
- // 璁剧疆鍒� values
- tslString.value = JSON.stringify(thingModelTSL.value, null, 2);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 鍙瑙嗗浘涓嬶紝鏍煎紡鍖栧悗鐨� TSL 瀛楃涓� */
-const formattedTSL = computed(() =>
- JSON.stringify(thingModelTSL.value, null, 2),
-);
-
-/** 缂栬緫鍣ㄥ唴瀹瑰彉鍖栨椂锛屽悓姝ュ埌鏁版嵁瀵硅薄锛涚紪杈戣繃绋嬩腑 JSON 鍙兘鏄腑闂存�侊紝瑙f瀽澶辫触淇濈暀鍘熷�� */
-watch(tslString, (newValue) => {
- try {
- thingModelTSL.value = JSON.parse(newValue);
- } catch {
- // 涓棿鎬佸拷鐣�
- }
-});
-</script>
-
-<template>
- <Modal :footer="false" class="w-3/5" title="鐗╂ā鍨� TSL">
- <div class="mx-4">
- <div class="mb-4">
- <Radio.Group v-model:value="viewMode" size="small">
- <Radio.Button value="view">浠g爜瑙嗗浘</Radio.Button>
- <Radio.Button value="editor">缂栬緫鍣ㄨ鍥�</Radio.Button>
- </Radio.Group>
- </div>
- <!-- 浠g爜瑙嗗浘锛氬彧璇诲睍绀猴紙pre / code 蹇呴』绱ц创锛岄伩鍏嶆樉绀哄嚭绌虹櫧锛� -->
- <div
- v-if="viewMode === 'view'"
- class="max-h-[600px] overflow-y-auto rounded border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
- >
- <pre
- class="m-0 whitespace-pre-wrap break-words font-mono text-[13px] leading-normal"
- ><code>{{ formattedTSL }}</code></pre>
- </div>
- <!-- 缂栬緫鍣ㄨ鍥撅細鍙紪杈� -->
- <Textarea
- v-else
- v-model:value="tslString"
- :rows="20"
- class="font-mono text-[13px]"
- placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勭墿妯″瀷 TSL"
- />
- </div>
- </Modal>
-</template>
diff --git a/src/views/iot/utils/scene-rule.ts b/src/views/iot/utils/scene-rule.ts
deleted file mode 100644
index 0577d24..0000000
--- a/src/views/iot/utils/scene-rule.ts
+++ /dev/null
@@ -1,401 +0,0 @@
-import type { RuleSceneApi } from '#/api/iot/rule/scene';
-
-import {
- IotRuleSceneActionTypeEnum,
- IotRuleSceneTriggerConditionTypeEnum,
- IotRuleSceneTriggerTimeOperatorEnum,
- IotRuleSceneTriggerTypeEnum,
- isDeviceTrigger,
-} from '../../../packages/constants/src';
-import { CronUtils, isEmptyVal, isObject } from '../../../packages/utils/src';
-
-/**
- * 鍒ゆ柇鏅�� ID 閫夋嫨鍊兼槸鍚︾己澶便��
- *
- * 浜у搧銆佸憡璀﹂厤缃瓑鏅�氫笟鍔� ID 搴斾负姝f暟锛沗0` 涓嶄唬琛ㄦ湁鏁堜笟鍔℃暟鎹��
- *
- * @param value 鏅�氫笟鍔� ID
- * @returns 鏄惁缂哄け
- */
-function isRequiredIdMissing(value: unknown): boolean {
- return !value;
-}
-
-/**
- * 鍒ゆ柇璁惧 ID 閫夋嫨鍊兼槸鍚︾己澶便��
- *
- * 鍦烘櫙鑱斿姩鐨勮澶囬�夋嫨鍣ㄤ腑锛宍0` 琛ㄧず銆屽叏閮ㄨ澶囥�嶏紝鏄悎娉曞�硷紱鍥犳杩欓噷鍙兘鎶�
- * `undefined`銆乣null`銆佺┖瀛楃涓茶涓烘湭閫夋嫨锛屼笉鑳戒娇鐢ㄦ櫘閫� falsy 鍒ゆ柇銆�
- *
- * @param value 璁惧 ID
- * @returns 鏄惁缂哄け
- */
-function isDeviceIdMissing(value: unknown): boolean {
- return isEmptyVal(value);
-}
-
-/**
- * 鍒ゆ柇鎵ц鍣ㄥ弬鏁版槸鍚︿负绌恒��
- *
- * 鍙傛暟閰嶇疆褰撳墠浠� JSON 瀛楃涓蹭负涓伙紝鍚屾椂鍏煎鍘嗗彶瀵硅薄鍊硷細
- * - 绌哄瓧绗︿覆銆佺┖鐧藉瓧绗︿覆瑙嗕负绌猴紱
- * - 绌哄璞� `{}` 瑙嗕负绌猴紱
- * - 闈炵┖ JSON 瀵硅薄瑙嗕负宸查厤缃紱
- * - 闈炴硶 JSON 涓嶅湪杩欓噷鍒ょ┖锛屼氦缁� JSON 鏍煎紡鏍¢獙杩斿洖鏇村噯纭殑閿欒銆�
- *
- * @param params 鎵ц鍣ㄥ弬鏁�
- * @returns 鏄惁涓虹┖
- */
-export function isActionParamsEmpty(params?: unknown): boolean {
- if (isEmptyVal(params)) {
- return true;
- }
-
- if (typeof params === 'string') {
- if (!params.trim()) {
- return true;
- }
- try {
- const parsed = JSON.parse(params);
- if (isObject(parsed) && !Array.isArray(parsed)) {
- return Object.keys(parsed).length === 0;
- }
- } catch {
- return false;
- }
- return false;
- }
-
- if (isObject(params) && !Array.isArray(params)) {
- return Object.keys(params).length === 0;
- }
-
- return false;
-}
-
-/**
- * 鍒ゆ柇鎵ц鍣ㄥ弬鏁版槸鍚︿负鍚堟硶 JSON銆�
- *
- * 鍙傛暟缂栬緫缁勪欢姝e父浼氫繚瀛� JSON 瀛楃涓诧紱缂栬緫鏃ф暟鎹椂鍙兘浠嶇劧鏄璞★紝瀵硅薄鍙洿鎺ヨ涓哄悎娉曘��
- *
- * @param params 鎵ц鍣ㄥ弬鏁�
- * @returns 鏄惁鍚堟硶
- */
-function isActionParamsJsonValid(params?: unknown): boolean {
- if (isObject(params)) {
- return true;
- }
- try {
- JSON.parse(String(params));
- return true;
- } catch {
- return false;
- }
-}
-
-/**
- * 鏍¢獙鍗曚釜闄勫姞瀛愭潯浠躲��
- *
- * 璇ユ柟娉曠敤浜庢彁浜ゅ墠鍏滃簳鏍¢獙锛岄敊璇彁绀轰細甯︿笂 path锛屾柟渚垮畾浣嶅埌绗嚑涓Е鍙戝櫒銆�
- * 绗嚑涓潯浠剁粍鍜岀鍑犱釜鏉′欢銆�
- *
- * @param condition 瀛愭潯浠堕厤缃�
- * @param path 閿欒鎻愮ず鍓嶇紑
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateTriggerCondition(
- condition: RuleSceneApi.TriggerCondition,
- path: string,
-): null | string {
- if (!condition.type) {
- return `${path}锛氭潯浠剁被鍨嬩笉鑳戒负绌篳;
- }
-
- const isDeviceStatus =
- condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS;
- const isDeviceProperty =
- condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY;
-
- // 璁惧鐘舵�佸拰璁惧灞炴�ч兘蹇呴』鍏堥�夋嫨浜у搧銆佽澶囷紱deviceId = 0 琛ㄧず鍏ㄩ儴璁惧銆�
- if (isDeviceStatus || isDeviceProperty) {
- if (isRequiredIdMissing(condition.productId)) {
- return `${path}锛氫骇鍝佷笉鑳戒负绌篳;
- }
- if (isDeviceIdMissing(condition.deviceId)) {
- return `${path}锛氳澶囦笉鑳戒负绌篳;
- }
- }
-
- // 璁惧鐘舵�佸彧鏍¢獙鎿嶄綔绗﹀拰鐘舵�佹灇涓惧�笺��
- if (isDeviceStatus) {
- if (!condition.operator) {
- return `${path}锛氭搷浣滅涓嶈兘涓虹┖`;
- }
- if (isEmptyVal(condition.param)) {
- return `${path}锛氳澶囩姸鎬佷笉鑳戒负绌篳;
- }
- return null;
- }
-
- // 璁惧灞炴�ч渶瑕佹牎楠岀墿妯″瀷鏍囪瘑绗︺�佹搷浣滅鍜屾瘮杈冨�笺��
- if (isDeviceProperty) {
- if (!condition.identifier) {
- return `${path}锛氱洃鎺ч」涓嶈兘涓虹┖`;
- }
- if (!condition.operator) {
- return `${path}锛氭搷浣滅涓嶈兘涓虹┖`;
- }
- if (isEmptyVal(condition.param)) {
- return `${path}锛氭瘮杈冨�间笉鑳戒负绌篳;
- }
- return null;
- }
-
- // 褰撳墠鏃堕棿鎸夋搷浣滅鍔ㄦ�佸垽鏂� param 鏄惁闇�瑕佸~鍐欍��
- if (condition.type === IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME) {
- if (!condition.operator) {
- return `${path}锛氭椂闂存潯浠朵笉鑳戒负绌篳;
- }
-
- if (
- condition.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value
- ) {
- return null;
- }
-
- if (isEmptyVal(condition.param)) {
- return `${path}锛氭椂闂村�间笉鑳戒负绌篳;
- }
-
- if (
- condition.operator ===
- IotRuleSceneTriggerTimeOperatorEnum.BETWEEN_TIME.value
- ) {
- const parts = String(condition.param).split(',');
- if (!parts[0]?.trim() || !parts[1]?.trim()) {
- return `${path}锛氬紑濮嬪拰缁撴潫鏃堕棿涓嶈兘涓虹┖`;
- }
- }
- }
-
- return null;
-}
-
-/**
- * 鏍¢獙瑙﹀彂鍣ㄧ殑闄勫姞鏉′欢缁勩��
- *
- * 鏉′欢缁勭粨鏋勬槸浜岀淮鏁扮粍锛氬灞傛槸 OR 鍏崇郴鐨勬潯浠剁粍锛屽唴灞傛槸 AND 鍏崇郴鐨勬潯浠跺垪琛ㄣ��
- * 杩欓噷閫愮粍銆侀�愭潯浠惰繑鍥炵涓�鏉¢敊璇紝閬垮厤涓�娆℃�у脊鍑鸿繃澶氭彁绀恒��
- *
- * @param groups 闄勫姞鏉′欢缁�
- * @param triggerIndex 瑙﹀彂鍣ㄥ湪鍒楄〃涓殑涓嬫爣锛岀敤浜庣敓鎴愬彲瀹氫綅鐨勯敊璇彁绀�
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateTriggerConditionGroups(
- groups: RuleSceneApi.TriggerCondition[][] | undefined,
- triggerIndex: number,
-): null | string {
- if (!groups?.length) {
- return null;
- }
-
- for (const [groupIndex, group] of groups.entries()) {
- // 绌烘潯浠剁粍娌℃湁瀹為檯杩囨护鏉′欢锛屾彁浜ゅ悗璇箟涓嶆槑纭紝闇�瑕佹嫤鎴��
- if (!Array.isArray(group) || group.length === 0) {
- return `瑙﹀彂鍣� ${triggerIndex + 1}锛氭潯浠剁粍 ${groupIndex + 1} 涓嶈兘涓虹┖`;
- }
-
- for (const [conditionIndex, condition] of group.entries()) {
- const error = validateTriggerCondition(
- condition,
- `瑙﹀彂鍣� ${triggerIndex + 1} 鏉′欢缁� ${groupIndex + 1} 鏉′欢 ${
- conditionIndex + 1
- }`,
- );
- if (error) {
- return error;
- }
- }
- }
-
- return null;
-}
-
-/**
- * 鏍¢獙鍗曚釜瑙﹀彂鍣ㄩ厤缃��
- *
- * 璇ユ柟娉曠敤浜庡満鏅仈鍔ㄨ〃鍗曟彁浜ゅ墠鍏滃簳鏍¢獙锛岄伩鍏嶈Е鍙戝櫒閰嶇疆娌℃湁鐙珛琛ㄥ崟椤� prop 鏃舵紡鎺夊繀濉」銆�
- * 鏍¢獙閫昏緫闇�瑕佸拰瑙﹀彂鍣ㄤ富鏉′欢 UI 淇濇寔涓�鑷达紝鍚屾椂缁х画鏍¢獙闄勫姞鏉′欢缁勩��
- *
- * @param trigger 瑙﹀彂鍣ㄩ厤缃�
- * @param index 瑙﹀彂鍣ㄥ湪鍒楄〃涓殑涓嬫爣锛岀敤浜庣敓鎴愬彲瀹氫綅鐨勯敊璇彁绀�
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateTriggerItem(
- trigger: RuleSceneApi.Trigger,
- index: number,
-): null | string {
- const prefix = `瑙﹀彂鍣� ${index + 1}`;
-
- if (!trigger.type) {
- return `${prefix}锛氳Е鍙戝櫒绫诲瀷涓嶈兘涓虹┖`;
- }
-
- // 璁惧绫昏Е鍙戝櫒閮芥湁浜у搧銆佽澶囦袱涓熀纭�瀛楁锛沝eviceId = 0 琛ㄧず鍏ㄩ儴璁惧銆�
- if (isDeviceTrigger(trigger.type)) {
- if (isRequiredIdMissing(trigger.productId)) {
- return `${prefix}锛氫骇鍝佷笉鑳戒负绌篳;
- }
- if (isDeviceIdMissing(trigger.deviceId)) {
- return `${prefix}锛氳澶囦笉鑳戒负绌篳;
- }
-
- // 璁惧鐘舵�佸彉鍖栦笉渚濊禆鐗╂ā鍨嬫爣璇嗙锛屽彧鏍¢獙鎿嶄綔绗﹀拰鐘舵�佸�笺��
- if (trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE) {
- if (!trigger.operator) {
- return `${prefix}锛氭搷浣滅涓嶈兘涓虹┖`;
- }
- if (isEmptyVal(trigger.value)) {
- return `${prefix}锛氳澶囩姸鎬佷笉鑳戒负绌篳;
- }
- } else {
- if (!trigger.identifier) {
- return `${prefix}锛氱洃鎺ч」涓嶈兘涓虹┖`;
- }
-
- // 浜嬩欢涓婃姤鍜屾湇鍔¤皟鐢ㄥ彧鐩戝惉鏄惁鍙戠敓锛屼笉闇�瑕侀澶栫殑鎿嶄綔绗﹀拰姣旇緝鍊笺��
- const isEventOrService =
- trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST ||
- trigger.type === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
- if (!isEventOrService) {
- if (!trigger.operator) {
- return `${prefix}锛氭搷浣滅涓嶈兘涓虹┖`;
- }
- if (isEmptyVal(trigger.value)) {
- return `${prefix}锛氬弬鏁板�间笉鑳戒负绌篳;
- }
- }
- }
- }
-
- // 瀹氭椂瑙﹀彂鍣ㄩ渶瑕� CRON 琛ㄨ揪寮忥紝骞剁户缁牎楠� CRON 鏍煎紡銆�
- if (trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) {
- if (!trigger.cronExpression) {
- return `${prefix}锛欳RON 琛ㄨ揪寮忎笉鑳戒负绌篳;
- }
- if (!CronUtils.validate(trigger.cronExpression)) {
- return `${prefix}锛欳RON 琛ㄨ揪寮忔牸寮忎笉姝g‘`;
- }
- }
-
- return validateTriggerConditionGroups(trigger.conditionGroups, index);
-}
-
-/**
- * 鏍¢獙瑙﹀彂鍣ㄥ垪琛ㄣ��
- *
- * 鍦烘櫙鑱斿姩鑷冲皯闇�瑕佷竴涓Е鍙戝櫒锛涘垪琛ㄥ唴閫愭潯杩斿洖绗竴鏉¢敊璇紝閬垮厤涓�娆℃彁浜ゅ嚭鐜板鏉℃彁绀恒��
- *
- * @param triggers 瑙﹀彂鍣ㄥ垪琛�
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateSceneRuleTriggers(
- triggers?: RuleSceneApi.Trigger[],
-): null | string {
- if (!triggers?.length) {
- return '鑷冲皯闇�瑕佷竴涓Е鍙戝櫒';
- }
-
- for (const [index, trigger] of triggers.entries()) {
- const error = validateTriggerItem(trigger, index);
- if (error) {
- return error;
- }
- }
-
- return null;
-}
-
-/**
- * 鏍¢獙鍗曚釜鎵ц鍣ㄩ厤缃��
- *
- * 璇ユ柟娉曠敤浜庡満鏅仈鍔ㄨ〃鍗曟彁浜ゅ墠鍏滃簳鏍¢獙锛岄伩鍏嶆墽琛屽櫒閰嶇疆娌℃湁鐙珛琛ㄥ崟椤� prop 鏃舵紡鎺夊繀濉」銆�
- * 鏍¢獙閫昏緫闇�瑕佸拰鎵ц鍣� UI 淇濇寔涓�鑷淬��
- *
- * @param action 鎵ц鍣ㄩ厤缃�
- * @param index 鎵ц鍣ㄥ湪鍒楄〃涓殑涓嬫爣锛岀敤浜庣敓鎴愬彲瀹氫綅鐨勯敊璇彁绀�
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateActionItem(
- action: RuleSceneApi.Action,
- index: number,
-): null | string {
- const prefix = `鎵ц鍣� ${index + 1}`;
-
- if (!action.type) {
- return `${prefix}锛氭墽琛屽櫒绫诲瀷涓嶈兘涓虹┖`;
- }
-
- // 璁惧灞炴�ц缃拰璁惧鏈嶅姟璋冪敤閮介渶瑕佹寚瀹氳澶囷紝骞跺~鍐欑墿妯″瀷鍙傛暟銆�
- if (
- action.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET ||
- action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
- ) {
- if (isRequiredIdMissing(action.productId)) {
- return `${prefix}锛氫骇鍝佷笉鑳戒负绌篳;
- }
- if (isDeviceIdMissing(action.deviceId)) {
- return `${prefix}锛氳澶囦笉鑳戒负绌篳;
- }
- if (
- action.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE &&
- !action.identifier
- ) {
- return `${prefix}锛氭湇鍔′笉鑳戒负绌篳;
- }
-
- if (isActionParamsEmpty(action.params)) {
- return `${prefix}锛氬弬鏁伴厤缃笉鑳戒负绌篳;
- }
- if (!isActionParamsJsonValid(action.params)) {
- return `${prefix}锛氬弬鏁版牸寮忛』涓哄悎娉� JSON`;
- }
-
- return null;
- }
-
- // 鍛婅鎭㈠鎵ц鍣ㄩ渶瑕佺粦瀹氬叿浣撳憡璀﹂厤缃紱瑙﹀彂鍛婅涓嶉渶瑕侀閫夊憡璀﹂厤缃��
- if (
- action.type === IotRuleSceneActionTypeEnum.ALERT_RECOVER &&
- !action.alertConfigId
- ) {
- return `${prefix}锛氬憡璀﹂厤缃笉鑳戒负绌篳;
- }
-
- return null;
-}
-
-/**
- * 鏍¢獙鎵ц鍣ㄥ垪琛ㄣ��
- *
- * 鍦烘櫙鑱斿姩鑷冲皯闇�瑕佷竴涓墽琛屽櫒锛涘垪琛ㄥ唴閫愭潯杩斿洖绗竴鏉¢敊璇紝閬垮厤涓�娆℃彁浜ゅ嚭鐜板鏉℃彁绀恒��
- *
- * @param actions 鎵ц鍣ㄥ垪琛�
- * @returns 閿欒淇℃伅锛岄�氳繃鍒欒繑鍥� null
- */
-export function validateSceneRuleActions(
- actions?: RuleSceneApi.Action[],
-): null | string {
- if (!actions?.length) {
- return '鑷冲皯闇�瑕佷竴涓墽琛屽櫒';
- }
-
- for (const [index, action] of actions.entries()) {
- const error = validateActionItem(action, index);
- if (error) {
- return error;
- }
- }
-
- return null;
-}
diff --git a/src/views/mall/home/index.vue b/src/views/mall/home/index.vue
deleted file mode 100644
index 56d1556..0000000
--- a/src/views/mall/home/index.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<script lang="ts" setup>
-import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
-import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
-import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
-
-import { onMounted, ref } from 'vue';
-
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
-import { fenToYuan } from '../../../packages/utils/src';
-
-import { Col, Row } from 'ant-design-vue';
-
-import { getUserCountComparison } from '#/api/mall/statistics/member';
-import { getOrderComparison } from '#/api/mall/statistics/trade';
-
-import MemberFunnelCard from '../statistics/member/modules/funnel-card.vue';
-import MemberTerminalCard from '../statistics/member/modules/terminal-card.vue';
-import ComparisonCard from './modules/comparison-card.vue';
-import MemberStatisticsCard from './modules/member-statistics-card.vue';
-import OperationDataCard from './modules/operation-data-card.vue';
-import ShortcutCard from './modules/shortcut-card.vue';
-import TradeTrendCard from './modules/trade-trend-card.vue';
-
-/** 鍟嗗煄棣栭〉 */
-defineOptions({ name: 'MallHome' });
-
-const loading = ref(true); // 鍔犺浇涓�
-const orderComparison =
- ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeOrderSummaryRespVO>>(); // 浜ゆ槗瀵圭収鏁版嵁
-const userComparison =
- ref<DataComparisonRespVO<MallMemberStatisticsApi.MemberCountRespVO>>(); // 鐢ㄦ埛瀵圭収鏁版嵁
-
-/** 鏌ヨ浜ゆ槗瀵圭収鍗$墖鏁版嵁 */
-async function loadOrderComparison() {
- orderComparison.value = await getOrderComparison();
-}
-
-/** 鏌ヨ浼氬憳鐢ㄦ埛鏁伴噺瀵圭収鍗$墖鏁版嵁 */
-async function loadUserCountComparison() {
- userComparison.value = await getUserCountComparison();
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- loading.value = true;
- await Promise.all([loadOrderComparison(), loadUserCountComparison()]);
- loading.value = false;
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鍟嗗煄鎵嬪唽锛堝姛鑳藉紑鍚級"
- url="https://doc.iocoder.cn/mall/build/"
- />
- </template>
-
- <div class="flex flex-col gap-4">
- <!-- 鏁版嵁瀵圭収 -->
- <Row :gutter="16">
- <Col :md="6" :sm="12" :xs="24">
- <ComparisonCard
- tag="浠婃棩"
- title="閿�鍞"
- prefix="锟�"
- :decimals="2"
- :value="fenToYuan(orderComparison?.value?.orderPayPrice || 0)"
- :reference="
- fenToYuan(orderComparison?.reference?.orderPayPrice || 0)
- "
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <ComparisonCard
- tag="浠婃棩"
- title="鐢ㄦ埛璁块棶閲�"
- :value="userComparison?.value?.visitUserCount || 0"
- :reference="userComparison?.reference?.visitUserCount || 0"
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <ComparisonCard
- tag="浠婃棩"
- title="璁㈠崟閲�"
- :value="orderComparison?.value?.orderPayCount || 0"
- :reference="orderComparison?.reference?.orderPayCount || 0"
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <ComparisonCard
- tag="浠婃棩"
- title="鏂板鐢ㄦ埛"
- :value="userComparison?.value?.registerUserCount || 0"
- :reference="userComparison?.reference?.registerUserCount || 0"
- />
- </Col>
- </Row>
- <!-- 蹇嵎鍏ュ彛鍜岃繍钀ユ暟鎹� -->
- <Row :gutter="16">
- <Col :md="12" :xs="24">
- <ShortcutCard />
- </Col>
- <Col :md="12" :xs="24">
- <OperationDataCard />
- </Col>
- </Row>
- <!-- 浼氬憳姒傝鍜屼細鍛樼粓绔� -->
- <Row :gutter="16">
- <Col :md="18" :sm="24" :xs="24">
- <MemberFunnelCard />
- </Col>
- <Col :md="6" :sm="24" :xs="24">
- <MemberTerminalCard />
- </Col>
- </Row>
- <!-- 浜ゆ槗閲忚秼鍔� -->
- <TradeTrendCard />
- <!-- 浼氬憳缁熻 -->
- <MemberStatisticsCard />
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/home/modules/comparison-card.vue b/src/views/mall/home/modules/comparison-card.vue
deleted file mode 100644
index c4ed780..0000000
--- a/src/views/mall/home/modules/comparison-card.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Card, Tag } from 'ant-design-vue';
-
-/** 浜ゆ槗瀵圭収鍗$墖 */
-defineOptions({ name: 'ComparisonCard' });
-
-const props = withDefaults(defineProps<Props>(), {
- title: '',
- tag: '',
- prefix: '',
- value: 0,
- reference: 0,
- decimals: 0,
-});
-
-interface Props {
- title?: string;
- tag?: string;
- prefix?: string;
- value?: number | string;
- reference?: number | string;
- decimals?: number;
-}
-
-/** 璁$畻鐜瘮鐧惧垎姣� */
-const percent = computed(() => {
- const refValue = Number(props.reference);
- const curValue = Number(props.value);
- if (!refValue || refValue === 0) return 0;
- return ((curValue - refValue) / refValue) * 100;
-});
-
-/** 鏍煎紡鍖栦粖鏃ユ暟鎹� */
-const formattedValue = computed(() => {
- const numValue = Number(props.value);
- return numValue.toFixed(props.decimals);
-});
-
-/** 鏍煎紡鍖栨槰鏃ユ暟鎹� */
-const formattedReference = computed(() => {
- const numValue = Number(props.reference);
- return numValue.toFixed(props.decimals);
-});
-</script>
-
-<template>
- <Card :bordered="false" class="h-full">
- <div class="flex flex-col gap-2">
- <div class="flex items-center justify-between text-gray-500">
- <span>{{ title }}</span>
- <Tag v-if="tag">{{ tag }}</Tag>
- </div>
- <div class="flex items-baseline justify-between">
- <div class="text-3xl">{{ prefix }}{{ formattedValue }}</div>
- <span
- :class="percent > 0 ? 'text-red-500' : 'text-green-500'"
- class="flex items-center gap-0.5"
- >
- {{ Math.abs(percent).toFixed(2) }}%
- <IconifyIcon
- :icon="percent > 0 ? 'ep:caret-top' : 'ep:caret-bottom'"
- class="text-sm"
- />
- </span>
- </div>
- <div class="mt-2 border-t border-gray-200 pt-2">
- <div class="flex items-center justify-between text-sm">
- <span class="text-gray-500">鏄ㄦ棩鏁版嵁</span>
- <span>{{ prefix }}{{ formattedReference }}</span>
- </div>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/mall/home/modules/member-statistics-card.vue b/src/views/mall/home/modules/member-statistics-card.vue
deleted file mode 100644
index 10b79e7..0000000
--- a/src/views/mall/home/modules/member-statistics-card.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { EchartsUIType } from '../../../../packages/effects/plugins/src/echarts';
-
-import { onMounted, ref } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Radio, RadioGroup, Spin } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import { getMemberRegisterCountList } from '#/api/mall/statistics/member';
-
-import {
- getMemberStatisticsChartOptions,
- TimeRangeTypeEnum,
-} from './member-statistics-chart-options';
-
-/** 浼氬憳鐢ㄦ埛缁熻鍗$墖 */
-defineOptions({ name: 'MemberStatisticsCard' });
-
-const loading = ref(false);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-const timeRangeConfig = {
- [TimeRangeTypeEnum.DAY30]: {
- name: '30 澶�',
- },
- [TimeRangeTypeEnum.WEEK]: {
- name: '鍛�',
- },
- [TimeRangeTypeEnum.MONTH]: {
- name: '鏈�',
- },
- [TimeRangeTypeEnum.YEAR]: {
- name: '骞�',
- },
-}; // 鏃堕棿鑼冨洿 Map
-const timeRangeType = ref(TimeRangeTypeEnum.DAY30); // 鏃ユ湡蹇嵎閫夋嫨鎸夐挳, 榛樿 30 澶�
-
-/** 鏃堕棿鑼冨洿绫诲瀷鍗曢�夋寜閽�変腑 */
-async function handleTimeRangeTypeChange() {
- // 璁剧疆鏃堕棿鑼冨洿
- let beginTime: Dayjs;
- let endTime: Dayjs;
- switch (timeRangeType.value) {
- case TimeRangeTypeEnum.DAY30: {
- beginTime = dayjs().subtract(30, 'day').startOf('d');
- endTime = dayjs().endOf('d');
- break;
- }
- case TimeRangeTypeEnum.MONTH: {
- beginTime = dayjs().startOf('month');
- endTime = dayjs().endOf('month');
- break;
- }
- case TimeRangeTypeEnum.WEEK: {
- beginTime = dayjs().startOf('week');
- endTime = dayjs().endOf('week');
- break;
- }
- case TimeRangeTypeEnum.YEAR: {
- beginTime = dayjs().startOf('year');
- endTime = dayjs().endOf('year');
- break;
- }
- default: {
- throw new Error(`鏈煡鐨勬椂闂磋寖鍥寸被鍨�: ${timeRangeType.value}`);
- }
- }
- // 鍙戦�佹椂闂磋寖鍥撮�変腑浜嬩欢
- await loadMemberRegisterCountList(beginTime, endTime);
-}
-
-async function loadMemberRegisterCountList(beginTime: Dayjs, endTime: Dayjs) {
- loading.value = true;
- try {
- const list = await getMemberRegisterCountList(
- beginTime.toDate(),
- endTime.toDate(),
- );
- // 鏇存柊 Echarts 鏁版嵁
- await renderEcharts(getMemberStatisticsChartOptions(list));
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- handleTimeRangeTypeChange();
-});
-</script>
-
-<template>
- <Card :bordered="false">
- <template #title>
- <div class="flex items-center justify-between">
- <span>鐢ㄦ埛缁熻</span>
- <RadioGroup
- v-model:value="timeRangeType"
- @change="handleTimeRangeTypeChange"
- >
- <Radio
- v-for="[key, value] in Object.entries(timeRangeConfig)"
- :key="key"
- :value="Number(key)"
- >
- {{ value.name }}
- </Radio>
- </RadioGroup>
- </div>
- </template>
- <Spin :spinning="loading">
- <EchartsUI ref="chartRef" class="h-[300px] w-full" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/home/modules/member-statistics-chart-options.ts b/src/views/mall/home/modules/member-statistics-chart-options.ts
deleted file mode 100644
index bd1e584..0000000
--- a/src/views/mall/home/modules/member-statistics-chart-options.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import dayjs from 'dayjs';
-
-/** 鏃堕棿鑼冨洿绫诲瀷鏋氫妇 */
-export enum TimeRangeTypeEnum {
- DAY30 = 1,
- WEEK = 7,
- MONTH = 30,
- YEAR = 365,
-}
-
-/** 浼氬憳缁熻鍥捐〃閰嶇疆 */
-export function getMemberStatisticsChartOptions(list: any[]): any {
- return {
- dataset: {
- dimensions: ['date', 'count'],
- source: list,
- },
- grid: {
- left: 20,
- right: 20,
- bottom: 20,
- top: 80,
- containLabel: true,
- },
- legend: {
- top: 50,
- },
- series: [{ name: '娉ㄥ唽閲�', type: 'line', smooth: true, areaStyle: {} }],
- toolbox: {
- feature: {
- // 鏁版嵁鍖哄煙缂╂斁
- dataZoom: {
- yAxisIndex: false, // Y杞翠笉缂╂斁
- },
- brush: {
- type: ['lineX', 'clear'], // 鍖哄煙缂╂斁鎸夐挳銆佽繕鍘熸寜閽�
- },
- saveAsImage: { show: true, name: '浼氬憳缁熻' }, // 淇濆瓨涓哄浘鐗�
- },
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- },
- padding: [5, 10],
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- axisTick: {
- show: false,
- },
- axisLabel: {
- formatter: (date: string) => dayjs(date).format('MM-DD'),
- },
- },
- yAxis: {
- axisTick: {
- show: false,
- },
- },
- };
-}
diff --git a/src/views/mall/home/modules/operation-data-card.vue b/src/views/mall/home/modules/operation-data-card.vue
deleted file mode 100644
index 85865d8..0000000
--- a/src/views/mall/home/modules/operation-data-card.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<script lang="ts" setup>
-import { onActivated, onMounted, reactive } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { CountTo } from '../../../../packages/effects/common-ui/src';
-import { fenToYuan } from '../../../../packages/utils/src';
-
-import { Card } from 'ant-design-vue';
-
-import { getTabsCount } from '#/api/mall/product/spu';
-import { getWalletRechargePrice } from '#/api/mall/statistics/pay';
-import { getOrderCount } from '#/api/mall/statistics/trade';
-
-/** 杩愯惀鏁版嵁鍗$墖 */
-defineOptions({ name: 'OperationDataCard' });
-
-const router = useRouter();
-
-/** 鏁版嵁椤规帴鍙� */
-interface DataItem {
- name: string;
- value: number;
- routerName: string;
- prefix?: string;
- decimals?: number;
-}
-
-/** 鏁版嵁 */
-const data = reactive({
- orderUndelivered: { name: '寰呭彂璐ц鍗�', value: 0, routerName: 'TradeOrder' },
- orderAfterSaleApply: {
- name: '閫�娆句腑璁㈠崟',
- value: 0,
- routerName: 'TradeAfterSale',
- },
- orderWaitePickUp: { name: '寰呮牳閿�璁㈠崟', value: 0, routerName: 'TradeOrder' },
- productAlertStock: { name: '搴撳瓨棰勮', value: 0, routerName: 'ProductSpu' },
- productForSale: { name: '涓婃灦鍟嗗搧', value: 0, routerName: 'ProductSpu' },
- productInWarehouse: { name: '浠撳簱鍟嗗搧', value: 0, routerName: 'ProductSpu' },
- withdrawAuditing: {
- name: '鎻愮幇寰呭鏍�',
- value: 0,
- routerName: 'TradeBrokerageWithdraw',
- },
- rechargePrice: {
- name: '璐︽埛鍏呭��',
- value: 0,
- prefix: '锟�',
- decimals: 2,
- routerName: 'PayWalletRecharge',
- },
-});
-
-/** 鏌ヨ璁㈠崟鏁版嵁 */
-async function loadOrderData() {
- const orderCount = await getOrderCount();
- if (orderCount.undelivered) {
- data.orderUndelivered.value = orderCount.undelivered;
- }
- if (orderCount.afterSaleApply) {
- data.orderAfterSaleApply.value = orderCount.afterSaleApply;
- }
- if (orderCount.pickUp) {
- data.orderWaitePickUp.value = orderCount.pickUp;
- }
- if (orderCount.auditingWithdraw) {
- data.withdrawAuditing.value = orderCount.auditingWithdraw;
- }
-}
-
-/** 鏌ヨ鍟嗗搧鏁版嵁 */
-async function loadProductData() {
- const productCount = await getTabsCount();
- data.productForSale.value = productCount['0'] || 0;
- data.productInWarehouse.value = productCount['1'] || 0;
- data.productAlertStock.value = productCount['3'] || 0;
-}
-
-/** 鏌ヨ閽卞寘鍏呭�兼暟鎹� */
-async function loadWalletRechargeData() {
- const paySummary = await getWalletRechargePrice();
- data.rechargePrice.value = Number(fenToYuan(paySummary.rechargePrice || 0));
-}
-
-/** 璺宠浆鍒板搴旈〉闈� */
-function handleClick(routerName: string) {
- router.push({ name: routerName });
-}
-
-/** 婵�娲绘椂 */
-onActivated(() => {
- loadOrderData();
- loadProductData();
- loadWalletRechargeData();
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadOrderData();
- loadProductData();
- loadWalletRechargeData();
-});
-</script>
-
-<template>
- <Card :bordered="false" title="杩愯惀鏁版嵁">
- <div class="flex flex-row flex-wrap items-center gap-8 p-4">
- <div
- v-for="(item, key) in data"
- :key="key"
- class="flex h-20 w-[20%] cursor-pointer flex-col items-center justify-center gap-2"
- @click="handleClick(item.routerName)"
- >
- <CountTo
- :decimals="(item as DataItem).decimals ?? 0"
- :end-val="item.value"
- :prefix="(item as DataItem).prefix ?? ''"
- class="text-3xl"
- />
- <span class="text-center">{{ item.name }}</span>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/mall/home/modules/shortcut-card.vue b/src/views/mall/home/modules/shortcut-card.vue
deleted file mode 100644
index 11574ad..0000000
--- a/src/views/mall/home/modules/shortcut-card.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<script lang="ts" setup>
-import { useRouter } from 'vue-router';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Card } from 'ant-design-vue';
-
-/** 蹇嵎鍏ュ彛鍗$墖 */
-defineOptions({ name: 'ShortcutCard' });
-
-const router = useRouter();
-
-/** 鑿滃崟鍒楄〃 */
-const menuList = [
- {
- name: '鐢ㄦ埛绠$悊',
- icon: 'lucide:user',
- bgColor: 'bg-red-400',
- routerName: 'MemberUser',
- },
- {
- name: '鍟嗗搧绠$悊',
- icon: 'fluent-mdl2:product',
- bgColor: 'bg-orange-400',
- routerName: 'ProductSpu',
- },
- {
- name: '璁㈠崟绠$悊',
- icon: 'lucide:list',
- bgColor: 'bg-yellow-500',
- routerName: 'TradeOrder',
- },
- {
- name: '鍞悗绠$悊',
- icon: 'ri:refund-2-line',
- bgColor: 'bg-green-600',
- routerName: 'TradeAfterSale',
- },
- {
- name: '鍒嗛攢绠$悊',
- icon: 'fa-solid:project-diagram',
- bgColor: 'bg-cyan-500',
- routerName: 'TradeBrokerageUser',
- },
- {
- name: '浼樻儬鍒�',
- icon: 'lucide:ticket',
- bgColor: 'bg-blue-500',
- routerName: 'PromotionCoupon',
- },
- {
- name: '鎷煎洟娲诲姩',
- icon: 'lucide:users',
- bgColor: 'bg-purple-500',
- routerName: 'PromotionBargainActivity',
- },
- {
- name: '浣i噾鎻愮幇',
- icon: 'vaadin:money-withdraw',
- bgColor: 'bg-rose-500',
- routerName: 'TradeBrokerageWithdraw',
- },
-];
-
-/** 璺宠浆鍒拌彍鍗曞搴旈〉闈� */
-function handleMenuClick(routerName: string) {
- router.push({ name: routerName });
-}
-</script>
-
-<template>
- <Card :bordered="false" title="蹇嵎鍏ュ彛">
- <div class="flex flex-row flex-wrap gap-8 p-4">
- <div
- v-for="menu in menuList"
- :key="menu.name"
- class="flex h-20 w-[20%] cursor-pointer flex-col items-center justify-center gap-2"
- @click="handleMenuClick(menu.routerName)"
- >
- <div
- :class="menu.bgColor"
- class="flex h-12 w-12 items-center justify-center rounded text-white"
- >
- <IconifyIcon :icon="menu.icon" class="text-2xl" />
- </div>
- <span>{{ menu.name }}</span>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/mall/home/modules/trade-trend-card.vue b/src/views/mall/home/modules/trade-trend-card.vue
deleted file mode 100644
index ea885f1..0000000
--- a/src/views/mall/home/modules/trade-trend-card.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { EchartsUIType } from '../../../../packages/effects/plugins/src/echarts';
-
-import { onMounted, ref } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../packages/effects/plugins/src/echarts';
-import { fenToYuan } from '../../../../packages/utils/src';
-
-import { Card, Radio, RadioGroup, Spin } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import { getOrderCountTrendComparison } from '#/api/mall/statistics/trade';
-
-import {
- getTradeTrendChartOptions,
- TimeRangeTypeEnum,
-} from './trade-trend-chart-options';
-
-/** 浜ゆ槗閲忚秼鍔� */
-defineOptions({ name: 'TradeTrendCard' });
-
-const loading = ref(false);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-const timeRangeConfig = {
- [TimeRangeTypeEnum.DAY30]: {
- name: '30 澶�',
- seriesCount: 2,
- },
- [TimeRangeTypeEnum.WEEK]: {
- name: '鍛�',
- seriesCount: 4,
- },
- [TimeRangeTypeEnum.MONTH]: {
- name: '鏈�',
- seriesCount: 4,
- },
- [TimeRangeTypeEnum.YEAR]: {
- name: '骞�',
- seriesCount: 4,
- },
-}; // 鏃堕棿鑼冨洿 Map
-const timeRangeType = ref(TimeRangeTypeEnum.DAY30); // 鏃ユ湡蹇嵎閫夋嫨鎸夐挳, 榛樿 30 澶�
-
-/** 鏃堕棿鑼冨洿绫诲瀷鍗曢�夋寜閽�変腑 */
-async function handleTimeRangeTypeChange() {
- // 璁剧疆鏃堕棿鑼冨洿
- let beginTime: Dayjs;
- let endTime: Dayjs;
- switch (timeRangeType.value) {
- case TimeRangeTypeEnum.DAY30: {
- beginTime = dayjs().subtract(30, 'day').startOf('d');
- endTime = dayjs().endOf('d');
- break;
- }
- case TimeRangeTypeEnum.MONTH: {
- beginTime = dayjs().startOf('month');
- endTime = dayjs().endOf('month');
- break;
- }
- case TimeRangeTypeEnum.WEEK: {
- beginTime = dayjs().startOf('week');
- endTime = dayjs().endOf('week');
- break;
- }
- case TimeRangeTypeEnum.YEAR: {
- beginTime = dayjs().startOf('year');
- endTime = dayjs().endOf('year');
- break;
- }
- default: {
- throw new Error(`鏈煡鐨勬椂闂磋寖鍥寸被鍨�: ${timeRangeType.value}`);
- }
- }
- // 鍙戦�佹椂闂磋寖鍥撮�変腑浜嬩欢
- await loadOrderCountTrendComparison(beginTime, endTime);
-}
-
-/** 鏌ヨ璁㈠崟鏁伴噺瓒嬪娍瀵圭収鏁版嵁 */
-async function loadOrderCountTrendComparison(beginTime: Dayjs, endTime: Dayjs) {
- loading.value = true;
- try {
- // 1. 鏌ヨ鏁版嵁
- const list = await getOrderCountTrendComparison(
- timeRangeType.value,
- beginTime.toDate(),
- endTime.toDate(),
- );
- // 2. 澶勭悊鏁版嵁
- const dates: string[] = [];
- const series: any[] = [];
- const config = timeRangeConfig[timeRangeType.value];
- // 鎯呭喌涓�锛歴eriesCount 涓� 2锛堣繎 30 澶╋級
- if (config.seriesCount === 2) {
- const orderPayPriceData: string[] = [];
- const orderPayCountData: number[] = [];
- for (const item of list) {
- dates.push(item.value.date);
- orderPayPriceData.push(fenToYuan(item?.value?.orderPayPrice || 0));
- orderPayCountData.push(item?.value?.orderPayCount || 0);
- }
- series.push(
- {
- name: '璁㈠崟閲戦',
- type: 'bar',
- smooth: true,
- data: orderPayPriceData,
- },
- {
- name: '璁㈠崟鏁伴噺',
- type: 'line',
- smooth: true,
- data: orderPayCountData,
- },
- );
- } else {
- // 鎯呭喌浜岋細seriesCount 涓� 4
- const refPriceData: string[] = [];
- const curPriceData: string[] = [];
- const refCountData: number[] = [];
- const curCountData: number[] = [];
- for (const item of list) {
- dates.push(item.value.date);
- refPriceData.push(fenToYuan(item?.reference?.orderPayPrice || 0));
- curPriceData.push(fenToYuan(item?.value?.orderPayPrice || 0));
- refCountData.push(item?.reference?.orderPayCount || 0);
- curCountData.push(item?.value?.orderPayCount || 0);
- }
- // 鏍规嵁鏃堕棿鑼冨洿绫诲瀷纭畾瀵圭収鏁版嵁鐨勬爣绛炬枃鏈�
- let timeLabel: string[];
- if (timeRangeType.value === TimeRangeTypeEnum.WEEK) {
- timeLabel = ['涓婂懆', '鏈懆'];
- } else if (timeRangeType.value === TimeRangeTypeEnum.MONTH) {
- timeLabel = ['涓婃湀', '鏈湀'];
- } else {
- timeLabel = ['鍘诲勾', '浠婂勾'];
- }
- series.push(
- {
- name: `${timeLabel[0]}閲戦`,
- type: 'bar',
- smooth: true,
- data: refPriceData,
- },
- {
- name: `${timeLabel[1]}閲戦`,
- type: 'bar',
- smooth: true,
- data: curPriceData,
- },
- {
- name: `${timeLabel[0]}鏁伴噺`,
- type: 'line',
- smooth: true,
- data: refCountData,
- },
- {
- name: `${timeLabel[1]}鏁伴噺`,
- type: 'line',
- smooth: true,
- data: curCountData,
- },
- );
- }
-
- // 3. 娓叉煋 Echarts 鐣岄潰
- await renderEcharts(
- getTradeTrendChartOptions(dates, series, timeRangeType.value),
- );
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- handleTimeRangeTypeChange();
-});
-</script>
-
-<template>
- <Card :bordered="false">
- <template #title>
- <div class="flex items-center justify-between">
- <span>浜ゆ槗閲忚秼鍔�</span>
- <RadioGroup
- v-model:value="timeRangeType"
- @change="handleTimeRangeTypeChange"
- >
- <Radio
- v-for="[key, value] in Object.entries(timeRangeConfig)"
- :key="key"
- :value="Number(key)"
- >
- {{ value.name }}
- </Radio>
- </RadioGroup>
- </div>
- </template>
- <Spin :spinning="loading">
- <EchartsUI ref="chartRef" class="w-full" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/home/modules/trade-trend-chart-options.ts b/src/views/mall/home/modules/trade-trend-chart-options.ts
deleted file mode 100644
index 5fd9516..0000000
--- a/src/views/mall/home/modules/trade-trend-chart-options.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import dayjs from 'dayjs';
-
-/** 鏃堕棿鑼冨洿绫诲瀷鏋氫妇 */
-export enum TimeRangeTypeEnum {
- DAY30 = 1,
- WEEK = 7,
- MONTH = 30,
- YEAR = 365,
-}
-
-/** 浜ゆ槗閲忚秼鍔垮浘琛ㄩ厤缃� */
-export function getTradeTrendChartOptions(
- dates: string[],
- series: any[],
- timeRangeType: TimeRangeTypeEnum,
-): any {
- return {
- grid: {
- left: 20,
- right: 20,
- bottom: 20,
- top: 80,
- containLabel: true,
- },
- legend: {
- top: 50,
- data: series.map((item) => item.name),
- },
- series,
- toolbox: {
- feature: {
- // 鏁版嵁鍖哄煙缂╂斁
- dataZoom: {
- yAxisIndex: false, // Y杞翠笉缂╂斁
- },
- brush: {
- type: ['lineX', 'clear'], // 鍖哄煙缂╂斁鎸夐挳銆佽繕鍘熸寜閽�
- },
- saveAsImage: { show: true, name: '璁㈠崟閲忚秼鍔�' }, // 淇濆瓨涓哄浘鐗�
- },
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- },
- padding: [5, 10],
- },
- xAxis: {
- type: 'category',
- inverse: true,
- boundaryGap: false,
- axisTick: {
- show: false,
- },
- data: dates,
- axisLabel: {
- formatter: (date: string) => {
- switch (timeRangeType) {
- case TimeRangeTypeEnum.DAY30: {
- return dayjs(date).format('MM-DD');
- }
- case TimeRangeTypeEnum.MONTH: {
- return dayjs(date).format('D');
- }
- case TimeRangeTypeEnum.WEEK: {
- const weekDay = dayjs(date).day();
- return weekDay === 0 ? '鍛ㄦ棩' : `鍛�${weekDay}`;
- }
- case TimeRangeTypeEnum.YEAR: {
- return `${dayjs(date).format('M')}鏈坄;
- }
- default: {
- return date;
- }
- }
- },
- },
- },
- yAxis: {
- axisTick: {
- show: false,
- },
- },
- };
-}
diff --git a/src/views/mall/product/brand/data.ts b/src/views/mall/product/brand/data.ts
deleted file mode 100644
index b2020af..0000000
--- a/src/views/mall/product/brand/data.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍝佺墝鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ搧鐗屽悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'picUrl',
- label: '鍝佺墝鍥剧墖',
- component: 'ImageUpload',
- componentProps: {
- placeholder: '璇蜂笂浼犲搧鐗屽浘鐗�',
- },
- rules: 'required',
- },
- {
- fieldName: 'sort',
- label: '鍝佺墝鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ュ搧鐗屾帓搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '鍝佺墝鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'description',
- label: '鍝佺墝鎻忚堪',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ搧鐗屾弿杩�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍝佺墝鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ搧鐗屽悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鍝佺墝鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鍝佺墝鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'name',
- title: '鍝佺墝鍚嶇О',
- minWidth: 180,
- },
- {
- field: 'picUrl',
- title: '鍝佺墝鍥剧墖',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'sort',
- title: '鍝佺墝鎺掑簭',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '鍝佺墝鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/product/brand/index.vue b/src/views/mall/product/brand/index.vue
deleted file mode 100644
index 0ea3845..0000000
--- a/src/views/mall/product/brand/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrandApi } from '#/api/mall/product/brand';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteBrand, getBrandPage } from '#/api/mall/product/brand';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍝佺墝 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍝佺墝 */
-function handleEdit(row: MallBrandApi.Brand) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍝佺墝 */
-async function handleDelete(row: MallBrandApi.Brand) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteBrand(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrandPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrandApi.Brand>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍝佺墝鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍝佺墝']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:brand:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:brand:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['product:brand:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/product/brand/modules/form.vue b/src/views/mall/product/brand/modules/form.vue
deleted file mode 100644
index e14abcb..0000000
--- a/src/views/mall/product/brand/modules/form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { MallBrandApi } from '#/api/mall/product/brand';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createBrand, getBrand, updateBrand } from '#/api/mall/product/brand';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallBrandApi.Brand>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍟嗗搧鍝佺墝'])
- : $t('ui.actionTitle.create', ['鍟嗗搧鍝佺墝']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallBrandApi.Brand;
- try {
- await (formData.value?.id ? updateBrand(data) : createBrand(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallBrandApi.Brand>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getBrand(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/4">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/product/category/components/index.ts b/src/views/mall/product/category/components/index.ts
deleted file mode 100644
index c3196cb..0000000
--- a/src/views/mall/product/category/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as ProductCategorySelect } from './select.vue';
diff --git a/src/views/mall/product/category/components/select.vue b/src/views/mall/product/category/components/select.vue
deleted file mode 100644
index b0098fb..0000000
--- a/src/views/mall/product/category/components/select.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<script lang="ts" setup>
-import { computed, onMounted, ref } from 'vue';
-
-import { handleTree } from '../../../../../packages/utils/src';
-
-import { TreeSelect } from 'ant-design-vue';
-
-import { getCategoryList } from '#/api/mall/product/category';
-
-/** 鍟嗗搧鍒嗙被閫夋嫨缁勪欢 */
-defineOptions({ name: 'ProductCategorySelect' });
-
-const props = defineProps({
- modelValue: {
- type: [Number, Array<number>],
- default: undefined,
- }, // 閫変腑鐨� ID
- multiple: {
- type: Boolean,
- default: false,
- }, // 鏄惁澶氶��
- parentId: {
- type: Number,
- default: undefined,
- }, // 涓婄骇鍝佺被鐨勭紪鍙�
-});
-
-/** 鍒嗙被閫夋嫨 */
-const emit = defineEmits(['update:modelValue']);
-
-const categoryList = ref<any[]>([]); // 鍒嗙被鏍�
-
-/** 閫変腑鐨勫垎绫� ID */
-const selectCategoryId = computed({
- get: () => {
- return props.modelValue;
- },
- set: (val: number | number[]) => {
- emit('update:modelValue', val);
- },
-});
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- const data = await getCategoryList({
- parentId: props.parentId,
- });
- categoryList.value = handleTree(data, 'id', 'parentId');
-});
-</script>
-<template>
- <TreeSelect
- v-model:value="selectCategoryId"
- :tree-data="categoryList"
- :field-names="{
- children: 'children',
- label: 'name',
- value: 'id',
- }"
- :multiple="multiple"
- :tree-checkable="multiple"
- class="w-full"
- placeholder="璇烽�夋嫨鍟嗗搧鍒嗙被"
- allow-clear
- tree-default-expand-all
- />
-</template>
diff --git a/src/views/mall/product/category/data.ts b/src/views/mall/product/category/data.ts
deleted file mode 100644
index 8422fee..0000000
--- a/src/views/mall/product/category/data.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { handleTree } from '../../../../packages/utils/src';
-
-import { z } from '#/adapter/form';
-import { getCategoryList } from '#/api/mall/product/category';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'parentId',
- label: '涓婄骇鍒嗙被',
- component: 'ApiTreeSelect',
- componentProps: {
- allowClear: true,
- api: async () => {
- const data = await getCategoryList({ parentId: 0 });
- data.unshift({
- id: 0,
- name: '椤剁骇鍒嗙被',
- } as MallCategoryApi.Category);
- return handleTree(data);
- },
- labelField: 'name',
- valueField: 'id',
- childrenField: 'children',
- placeholder: '璇烽�夋嫨涓婄骇鍒嗙被',
- treeDefaultExpandAll: true,
- },
- rules: 'selectRequired',
- },
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'picUrl',
- label: '绉诲姩绔垎绫诲浘',
- component: 'ImageUpload',
- componentProps: {
- placeholder: '璇蜂笂浼犵Щ鍔ㄧ鍒嗙被鍥�',
- },
- rules: 'required',
- },
- {
- fieldName: 'sort',
- label: '鍒嗙被鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ュ垎绫绘帓搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<MallCategoryApi.Category>['columns'] {
- return [
- {
- field: 'name',
- title: '鍒嗙被鍚嶇О',
- minWidth: 200,
- align: 'left',
- fixed: 'left',
- treeNode: true,
- },
- {
- field: 'picUrl',
- title: '绉诲姩绔垎绫诲浘',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'sort',
- title: '鍒嗙被鎺掑簭',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '鍒嗙被鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 240,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/product/category/index.vue b/src/views/mall/product/category/index.vue
deleted file mode 100644
index feeba6e..0000000
--- a/src/views/mall/product/category/index.vue
+++ /dev/null
@@ -1,181 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-
-import { ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteCategory, getCategoryList } from '#/api/mall/product/category';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍒嗙被 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 娣诲姞涓嬬骇鍒嗙被 */
-function handleAppend(row: MallCategoryApi.Category) {
- formModalApi.setData({ parentId: row.id }).open();
-}
-
-/** 缂栬緫鍒嗙被 */
-function handleEdit(row: MallCategoryApi.Category) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍒嗙被 */
-async function handleDelete(row: MallCategoryApi.Category) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteCategory(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒囨崲鏍戝舰灞曞紑/鏀剁缉鐘舵�� */
-const isExpanded = ref(false);
-function handleExpand() {
- isExpanded.value = !isExpanded.value;
- gridApi.grid.setAllTreeExpand(isExpanded.value);
-}
-
-/** 鏌ョ湅鍟嗗搧鎿嶄綔 */
-const router = useRouter();
-function handleViewSpu(id: number) {
- router.push({
- path: '/mall/product/spu',
- query: { categoryId: id },
- });
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async (_, formValues) => {
- return await getCategoryList(formValues);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- treeConfig: {
- parentField: 'parentId',
- rowField: 'id',
- transform: true,
- reserve: true,
- },
- } as VxeTableGridOptions<MallCategoryApi.Category>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪骇鍝併�戜骇鍝佺鐞嗐�佷骇鍝佸垎绫�"
- url="https://doc.iocoder.cn/crm/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍟嗗搧鍒嗙被鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍒嗙被']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:category:create'],
- onClick: handleCreate,
- },
- {
- label: isExpanded ? '鏀剁缉' : '灞曞紑',
- type: 'primary',
- onClick: handleExpand,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鏂板涓嬬骇',
- type: 'link',
- icon: ACTION_ICON.ADD,
- auth: ['product:category:create'],
- onClick: handleAppend.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:category:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鏌ョ湅鍟嗗搧',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['product:category:update'],
- ifShow: row.parentId !== undefined && row.parentId > 0,
- onClick: handleViewSpu.bind(null, row.id!),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['product:category:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/product/category/modules/form.vue b/src/views/mall/product/category/modules/form.vue
deleted file mode 100644
index 593189a..0000000
--- a/src/views/mall/product/category/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { MallCategoryApi } from '#/api/mall/product/category';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createCategory,
- getCategory,
- updateCategory,
-} from '#/api/mall/product/category';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallCategoryApi.Category>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍟嗗搧鍒嗙被'])
- : $t('ui.actionTitle.create', ['鍟嗗搧鍒嗙被']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallCategoryApi.Category;
- try {
- await (formData.value?.id ? updateCategory(data) : createCategory(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- let data = modalApi.getData<MallCategoryApi.Category>();
- if (!data) {
- return;
- }
- modalApi.lock();
- try {
- if (data.id) {
- data = await getCategory(data.id);
- }
- // 璁剧疆鍒� values
- formData.value = data;
- await formApi.setValues(data);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/4">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/product/comment/data.ts b/src/views/mall/product/comment/data.ts
deleted file mode 100644
index b7e246d..0000000
--- a/src/views/mall/product/comment/data.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCommentApi } from '#/api/mall/product/comment';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'spuId',
- label: '鍟嗗搧',
- component: 'Input',
- componentProps: {
- placeholder: '璇烽�夋嫨鍟嗗搧',
- },
- rules: 'required',
- },
- {
- fieldName: 'skuId',
- label: '鍟嗗搧瑙勬牸',
- component: 'Input',
- componentProps: {
- placeholder: '璇烽�夋嫨鍟嗗搧瑙勬牸',
- },
- dependencies: {
- triggerFields: ['spuId'],
- show: (values) => !!values.spuId,
- },
- rules: 'required',
- },
- {
- fieldName: 'userAvatar',
- label: '鐢ㄦ埛澶村儚',
- component: 'ImageUpload',
- componentProps: {
- placeholder: '璇蜂笂浼犵敤鎴峰ご鍍�',
- },
- rules: 'required',
- },
- {
- fieldName: 'userNickname',
- label: '鐢ㄦ埛鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴峰悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'content',
- label: '璇勮鍐呭',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ瘎璁哄唴瀹�',
- },
- rules: 'required',
- },
- {
- fieldName: 'descriptionScores',
- label: '鎻忚堪鏄熺骇',
- component: 'Rate',
- rules: z.number().min(1).max(5).default(5),
- },
- {
- fieldName: 'benefitScores',
- label: '鏈嶅姟鏄熺骇',
- component: 'Rate',
- rules: z.number().min(1).max(5).default(5),
- },
- {
- fieldName: 'picUrls',
- label: '璇勮鍥剧墖',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 9,
- placeholder: '璇蜂笂浼犺瘎璁哄浘鐗�',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'replyStatus',
- label: '鍥炲鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: [
- { label: '宸插洖澶�', value: true },
- { label: '鏈洖澶�', value: false },
- ],
- placeholder: '璇烽�夋嫨鍥炲鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'spuName',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'userNickname',
- label: '鐢ㄦ埛鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴峰悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'orderId',
- label: '璁㈠崟缂栧彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍗曠紪鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '璇勮鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- onStatusChange?: (
- newStatus: boolean,
- row: MallCommentApi.Comment,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '璇勮缂栧彿',
- fixed: 'left',
- minWidth: 80,
- },
- {
- field: 'skuPicUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鍚嶇О',
- minWidth: 250,
- },
- {
- field: 'skuProperties',
- title: '鍟嗗搧灞炴��',
- minWidth: 200,
- formatter: ({ cellValue }) => {
- return cellValue && cellValue.length > 0
- ? cellValue
- .map((item: any) => `${item.propertyName} : ${item.valueName}`)
- .join('\n')
- : '-';
- },
- },
- {
- field: 'userNickname',
- title: '鐢ㄦ埛鍚嶇О',
- minWidth: 100,
- },
- {
- field: 'descriptionScores',
- title: '鍟嗗搧璇勫垎',
- minWidth: 150,
- slots: {
- default: 'descriptionScores',
- },
- },
- {
- field: 'benefitScores',
- title: '鏈嶅姟璇勫垎',
- minWidth: 150,
- slots: {
- default: 'benefitScores',
- },
- },
- {
- field: 'content',
- title: '璇勮鍐呭',
- minWidth: 210,
- },
- {
- field: 'picUrls',
- title: '璇勮鍥剧墖',
- minWidth: 120,
- cellRender: {
- name: 'CellImages',
- },
- },
- {
- field: 'replyContent',
- title: '鍥炲鍐呭',
- minWidth: 250,
- },
- {
- field: 'createTime',
- title: '璇勮鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'visible',
- title: '鏄惁灞曠ず',
- minWidth: 110,
- align: 'center',
- cellRender: {
- attrs: { beforeChange: onStatusChange },
- name: 'CellSwitch',
- props: {
- checkedValue: true,
- unCheckedValue: false,
- },
- },
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/product/comment/index.vue b/src/views/mall/product/comment/index.vue
deleted file mode 100644
index 0e66456..0000000
--- a/src/views/mall/product/comment/index.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCommentApi } from '#/api/mall/product/comment';
-
-import { h } from 'vue';
-
-import { confirm, DocAlert, Page, prompt, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message, Rate, Textarea } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- getCommentPage,
- replyComment,
- updateCommentVisible,
-} from '#/api/mall/product/comment';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓璇勪环 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 鍥炲璇勪环 */
-function handleReply(row: MallCommentApi.Comment) {
- prompt({
- component: () => {
- return h(Textarea, {
- placeholder: '璇疯緭鍏ュ洖澶嶅唴瀹�',
- });
- },
- content: row.content
- ? `鐢ㄦ埛璇勮锛�${row.content}\n璇疯緭鍏ュ洖澶嶅唴瀹癸細`
- : '璇疯緭鍏ュ洖澶嶅唴瀹癸細',
- title: '鍥炲璇勮',
- modelPropName: 'value',
- }).then(async (val) => {
- if (val) {
- await replyComment({
- id: row.id!,
- replyContent: val,
- });
- handleRefresh();
- }
- });
-}
-
-/** 鏇存柊鐘舵�� */
-async function handleStatusChange(
- newStatus: boolean,
- row: MallCommentApi.Comment,
-): Promise<boolean | undefined> {
- const text = newStatus ? '灞曠ず' : '闅愯棌';
- try {
- await confirm(`纭瑕�${text}璇ヨ瘎璁哄悧锛焋);
- } catch {
- return false;
- }
- // 鏇存柊鐘舵��
- await updateCommentVisible({
- id: row.id!,
- visible: newStatus,
- });
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success(`${text}鎴愬姛`);
- return true;
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleStatusChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCommentPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCommentApi.Comment>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬晢鍝併�戝晢鍝佽瘎浠�"
- url="https://doc.iocoder.cn/mall/product-comment/"
- />
- </template>
- <FormModal @success="handleRefresh" />
- <Grid table-title="璇勮鍒楄〃">
- <template #descriptionScores="{ row }">
- <Rate v-model:value="row.descriptionScores" :disabled="true" />
- </template>
- <template #benefitScores="{ row }">
- <Rate v-model:value="row.benefitScores" :disabled="true" />
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['铏氭嫙璇勮']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:comment:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍥炲',
- type: 'link',
- auth: ['product:comment:update'],
- onClick: handleReply.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/product/comment/modules/form.vue b/src/views/mall/product/comment/modules/form.vue
deleted file mode 100644
index 4ea0f26..0000000
--- a/src/views/mall/product/comment/modules/form.vue
+++ /dev/null
@@ -1,170 +0,0 @@
-<script lang="ts" setup>
-import type { MallCommentApi } from '#/api/mall/product/comment';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { Button, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createComment, getComment } from '#/api/mall/product/comment';
-import { getSpu } from '#/api/mall/product/spu';
-import { $t } from '#/locales';
-import {
- SkuTableSelect,
- SpuShowcase,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<Partial<MallCommentApi.Comment>>({
- descriptionScores: 5,
- benefitScores: 5,
-});
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['铏氭嫙璇勮'])
- : $t('ui.actionTitle.create', ['铏氭嫙璇勮']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const skuTableSelectRef = ref<InstanceType<typeof SkuTableSelect>>();
-const selectedSku = ref<MallSpuApi.Sku>();
-
-/** 澶勭悊鍟嗗搧鐨勯�夋嫨鍙樺寲 */
-async function handleSpuChange(spu?: MallSpuApi.Spu | null) {
- // 澶勭悊鍟嗗搧閫夋嫨锛氬鏋� spu 涓� null 鎴� id 涓� 0锛岃〃绀烘竻绌洪�夋嫨
- const spuId = spu && spu.id ? spu.id : undefined;
- formData.value.spuId = spuId;
- await formApi.setFieldValue('spuId', spuId);
- // 娓呯┖宸查�夎鏍�
- selectedSku.value = undefined;
- formData.value.skuId = undefined;
- await formApi.setFieldValue('skuId', undefined);
-}
-
-/** 鎵撳紑鍟嗗搧瑙勬牸鐨勯�夋嫨寮规 */
-async function openSkuSelect() {
- const currentValues =
- (await formApi.getValues()) as Partial<MallCommentApi.Comment>;
- const currentSpuId = currentValues.spuId ?? formData.value?.spuId;
- if (!currentSpuId) {
- message.warning('璇峰厛閫夋嫨鍟嗗搧');
- return;
- }
- skuTableSelectRef.value?.open({ spuId: currentSpuId });
-}
-
-/** 澶勭悊鍟嗗搧瑙勬牸鐨勯�夋嫨 */
-async function handleSkuSelected(sku: MallSpuApi.Sku) {
- selectedSku.value = sku;
- formData.value.skuId = sku.id;
- await formApi.setFieldValue('skuId', sku.id);
-}
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallCommentApi.Comment;
- try {
- await createComment(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- // 鍏抽棴鏃堕噸缃〃鍗曠姸鎬�
- selectedSku.value = undefined;
- await formApi.setValues({ spuId: undefined, skuId: undefined });
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallCommentApi.Comment>();
- if (!data || !data.id) {
- return;
- }
- // 缂栬緫妯″紡锛氬姞杞芥暟鎹�
- modalApi.lock();
- try {
- formData.value = await getComment(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- // 鍥炴樉宸查�夌殑鍟嗗搧瑙勬牸
- if (formData.value?.spuId && formData.value?.skuId) {
- const spu = await getSpu(formData.value.spuId);
- const sku = spu.skus?.find((item) => item.id === formData.value!.skuId);
- if (sku) {
- selectedSku.value = sku;
- }
- } else {
- selectedSku.value = undefined;
- }
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Form class="mx-4">
- <template #spuId>
- <SpuShowcase
- v-model="(formData as any).spuId"
- :limit="1"
- @change="handleSpuChange"
- />
- </template>
- <template #skuId>
- <div class="flex items-center gap-2">
- <Button
- type="primary"
- :disabled="!formData?.spuId"
- @click="openSkuSelect"
- >
- 閫夋嫨瑙勬牸
- </Button>
- <span
- v-if="
- selectedSku &&
- selectedSku.properties &&
- selectedSku.properties.length > 0
- "
- >
- 宸查�夛細
- {{ selectedSku.properties.map((p: any) => p.valueName).join('/') }}
- </span>
- <span v-else-if="selectedSku">宸查�夛細{{ selectedSku.id }}</span>
- </div>
- </template>
- </Form>
- <SkuTableSelect ref="skuTableSelectRef" @change="handleSkuSelected" />
- </Modal>
-</template>
diff --git a/src/views/mall/product/property/data.ts b/src/views/mall/product/property/data.ts
deleted file mode 100644
index e8c3b20..0000000
--- a/src/views/mall/product/property/data.ts
+++ /dev/null
@@ -1,207 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { getPropertySimpleList } from '#/api/mall/product/property';
-import { getRangePickerDefaultProps } from '#/utils';
-
-// ============================== 灞炴�� ==============================
-
-/** 灞炴�ф柊澧�/淇敼鐨勮〃鍗� */
-export function usePropertyFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '灞炴�у悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ睘鎬у悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 灞炴�у垪琛ㄧ殑鎼滅储琛ㄥ崟 */
-export function usePropertyGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '灞炴�у悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ睘鎬у悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 灞炴�у垪琛ㄧ殑瀛楁 */
-export function usePropertyGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '灞炴�х紪鍙�',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '灞炴�у悕绉�',
- minWidth: 200,
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 180,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- minWidth: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-// ============================== 灞炴�у�� ==============================
-
-/** 灞炴�у�兼柊澧�/淇敼鐨勮〃鍗� */
-export function useValueFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'propertyId',
- label: '灞炴��',
- component: 'ApiSelect',
- componentProps: (values) => {
- return {
- api: getPropertySimpleList,
- placeholder: '璇烽�夋嫨灞炴��',
- labelField: 'name',
- valueField: 'id',
- disabled: !!values.id,
- };
- },
- rules: 'required',
- dependencies: {
- triggerFields: [''],
- },
- },
- {
- fieldName: 'name',
- label: '灞炴�у�煎悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ睘鎬у�煎悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 灞炴�у�煎垪琛ㄦ悳绱㈣〃鍗� */
-export function useValueGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'propertyId',
- label: '灞炴�ч」',
- component: 'ApiSelect',
- componentProps: {
- api: getPropertySimpleList,
- placeholder: '璇烽�夋嫨灞炴�ч」',
- labelField: 'name',
- valueField: 'id',
- disabled: true,
- allowClear: false,
- },
- },
- {
- fieldName: 'name',
- label: '灞炴�у�煎悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ睘鎬у�煎悕绉�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 灞炴�у�艰〃鏍煎垪 */
-export function useValueGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '灞炴�у�肩紪鍙�',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '灞炴�у�煎悕绉�',
- minWidth: 180,
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 180,
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- minWidth: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/product/property/index.vue b/src/views/mall/product/property/index.vue
deleted file mode 100644
index 4b072b1..0000000
--- a/src/views/mall/product/property/index.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-
-import PropertyGrid from './modules/property-grid.vue';
-import ValueGrid from './modules/value-grid.vue';
-
-const searchPropertyId = ref<number>(); // 鎼滅储鐨勫睘鎬� ID
-
-function handlePropertyIdSelect(propertyId: number) {
- searchPropertyId.value = propertyId;
-}
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬晢鍝併�戝晢鍝佸睘鎬�"
- url="https://doc.iocoder.cn/mall/product-property/"
- />
- </template>
- <div class="flex h-full">
- <!-- 宸︿晶灞炴�у垪琛� -->
- <div class="w-1/2 pr-3">
- <PropertyGrid @select="handlePropertyIdSelect" />
- </div>
- <!-- 鍙充晶灞炴�ф暟鎹垪琛� -->
- <div class="w-1/2">
- <ValueGrid :property-id="searchPropertyId" />
- </div>
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/product/property/modules/property-form.vue b/src/views/mall/product/property/modules/property-form.vue
deleted file mode 100644
index 1e27ca0..0000000
--- a/src/views/mall/product/property/modules/property-form.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<script lang="ts" setup>
-import type { MallPropertyApi } from '#/api/mall/product/property';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createProperty,
- getProperty,
- updateProperty,
-} from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-import { usePropertyFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallPropertyApi.Property>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['灞炴��'])
- : $t('ui.actionTitle.create', ['灞炴��']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: usePropertyFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallPropertyApi.Property;
- try {
- await (formData.value?.id ? updateProperty(data) : createProperty(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallPropertyApi.Property>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getProperty(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/product/property/modules/property-grid.vue b/src/views/mall/product/property/modules/property-grid.vue
deleted file mode 100644
index c7466bb..0000000
--- a/src/views/mall/product/property/modules/property-grid.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallPropertyApi } from '#/api/mall/product/property';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteProperty, getPropertyPage } from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-import { usePropertyGridColumns, usePropertyGridFormSchema } from '../data';
-import PropertyForm from './property-form.vue';
-
-const emit = defineEmits(['select']);
-
-const [PropertyFormModal, propertyFormModalApi] = useVbenModal({
- connectedComponent: PropertyForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓灞炴�� */
-function handleCreate() {
- propertyFormModalApi.setData(null).open();
-}
-
-/** 缂栬緫灞炴�� */
-function handleEdit(row: MallPropertyApi.Property) {
- propertyFormModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎灞炴�� */
-async function handleDelete(row: MallPropertyApi.Property) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteProperty(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: usePropertyGridFormSchema(),
- },
- gridOptions: {
- columns: usePropertyGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getPropertyPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isCurrent: true,
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallPropertyApi.Property>,
- gridEvents: {
- cellClick: ({ row }: { row: MallPropertyApi.Property }) => {
- emit('select', row.id);
- },
- },
-});
-</script>
-
-<template>
- <div class="h-full">
- <PropertyFormModal @success="handleRefresh" />
- <Grid table-title="灞炴�у垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['灞炴��']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:property:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:property:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['product:property:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </div>
-</template>
diff --git a/src/views/mall/product/property/modules/value-form.vue b/src/views/mall/product/property/modules/value-form.vue
deleted file mode 100644
index 201edc4..0000000
--- a/src/views/mall/product/property/modules/value-form.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<script lang="ts" setup>
-import type { MallPropertyApi } from '#/api/mall/product/property';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createPropertyValue,
- getPropertyValue,
- updatePropertyValue,
-} from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-import { useValueFormSchema } from '../data';
-
-defineOptions({ name: 'MallPropertyValueForm' });
-
-const emit = defineEmits(['success']);
-const formData = ref<MallPropertyApi.PropertyValue>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['灞炴�у��'])
- : $t('ui.actionTitle.create', ['灞炴�у��']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useValueFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallPropertyApi.PropertyValue;
- try {
- await (formData.value?.id
- ? updatePropertyValue(data)
- : createPropertyValue(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallPropertyApi.PropertyValue>();
- if (!data || !data.id) {
- // 璁剧疆 propertyId
- await formApi.setValues(data);
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getPropertyValue(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/product/property/modules/value-grid.vue b/src/views/mall/product/property/modules/value-grid.vue
deleted file mode 100644
index 19776d5..0000000
--- a/src/views/mall/product/property/modules/value-grid.vue
+++ /dev/null
@@ -1,152 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallPropertyApi } from '#/api/mall/product/property';
-
-import { watch } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deletePropertyValue,
- getPropertyValuePage,
-} from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-import { useValueGridColumns, useValueGridFormSchema } from '../data';
-import ValueForm from './value-form.vue';
-
-const props = defineProps({
- propertyId: {
- type: Number,
- default: undefined,
- },
-});
-
-const [ValueFormModal, valueFormModalApi] = useVbenModal({
- connectedComponent: ValueForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓灞炴�у�� */
-function handleCreate() {
- valueFormModalApi.setData({ propertyId: props.propertyId }).open();
-}
-
-/** 缂栬緫灞炴�у�� */
-function handleEdit(row: MallPropertyApi.PropertyValue) {
- valueFormModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎灞炴�у�� */
-async function handleDelete(row: MallPropertyApi.PropertyValue) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deletePropertyValue(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useValueGridFormSchema(),
- },
- gridOptions: {
- columns: useValueGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getPropertyValuePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- propertyId: props.propertyId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallPropertyApi.PropertyValue>,
-});
-
-/** 鐩戝惉 propertyId 鍙樺寲锛岄噸鏂版煡璇� */
-watch(
- () => props.propertyId,
- (newPropertyId) => {
- if (newPropertyId) {
- // 璁剧疆鎼滅储琛ㄥ崟涓殑 propertyId
- gridApi.formApi.setValues({ propertyId: newPropertyId });
- handleRefresh();
- }
- },
- { immediate: true },
-);
-</script>
-
-<template>
- <div class="flex h-full flex-col">
- <ValueFormModal @success="handleRefresh" />
-
- <Grid table-title="灞炴�у�煎垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['灞炴�у��']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:property:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:property:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['product:property:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </div>
-</template>
diff --git a/src/views/mall/product/spu/components/index.ts b/src/views/mall/product/spu/components/index.ts
deleted file mode 100644
index bedd27a..0000000
--- a/src/views/mall/product/spu/components/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export * from './property-util';
-export { default as SkuList } from './sku-list.vue';
-export { default as SkuTableSelect } from './sku-table-select.vue';
-export { default as SpuAndSkuList } from './spu-and-sku-list.vue';
-export { default as SpuSkuSelect } from './spu-select.vue';
-export { default as SpuShowcase } from './spu-showcase.vue';
-export { default as SpuTableSelect } from './spu-table-select.vue';
-export * from './type';
diff --git a/src/views/mall/product/spu/components/property-util.ts b/src/views/mall/product/spu/components/property-util.ts
deleted file mode 100644
index 7f65249..0000000
--- a/src/views/mall/product/spu/components/property-util.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { PropertyAndValues } from '#/views/mall/product/spu/components/type';
-
-/** 鑾峰緱鍟嗗搧鐨勮鏍煎垪琛� - 鍟嗗搧鐩稿叧鐨勫叕鍏卞嚱鏁帮紙琚叾瀹冩ā鍧楀 promotion 浣跨敤锛� */
-const getPropertyList = (spu: MallSpuApi.Spu): PropertyAndValues[] => {
- // 鐩存帴鎷胯繑鍥炵殑 skus 灞炴�ч�嗗悜鐢熸垚鍑� propertyList
- const properties: PropertyAndValues[] = [];
- // 鍙湁鏄瑙勬牸鎵嶅鐞�
- if (spu.specType) {
- spu.skus?.forEach((sku) => {
- sku.properties?.forEach(
- ({ propertyId, propertyName, valueId, valueName }) => {
- // 娣诲姞灞炴��
- if (!properties?.some((item) => item.id === propertyId)) {
- properties.push({
- id: propertyId!,
- name: propertyName!,
- values: [],
- });
- }
- // 娣诲姞灞炴�у��
- const index = properties?.findIndex((item) => item.id === propertyId);
- if (
- !properties[index]?.values?.some((value) => value.id === valueId)
- ) {
- properties[index]?.values?.push({ id: valueId!, name: valueName! });
- }
- },
- );
- });
- }
- return properties;
-};
-
-export { getPropertyList };
diff --git a/src/views/mall/product/spu/components/sku-list.vue b/src/views/mall/product/spu/components/sku-list.vue
deleted file mode 100644
index 38dbf65..0000000
--- a/src/views/mall/product/spu/components/sku-list.vue
+++ /dev/null
@@ -1,616 +0,0 @@
-<script lang="ts" setup>
-import type { Ref } from 'vue';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type {
- PropertyAndValues,
- RuleConfig,
-} from '#/views/mall/product/spu/components';
-
-import { ref, watch } from 'vue';
-
-import {
- copyValueToTarget,
- formatToFraction,
- getNestedValue,
- isEmpty,
-} from '../../../../../packages/utils/src';
-
-import { Button, Image, Input, InputNumber, message } from 'ant-design-vue';
-
-import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
-import { ImageUpload } from '#/components/upload';
-
-defineOptions({ name: 'SkuList' });
-
-const props = withDefaults(
- defineProps<{
- isActivityComponent?: boolean; // 鏄惁浣滀负 sku 娲诲姩閰嶇疆缁勪欢
- isBatch?: boolean; // 鏄惁浣滀负鎵归噺鎿嶄綔缁勪欢
- isComponent?: boolean; // 鏄惁浣滀负 sku 閫夋嫨缁勪欢
- isDetail?: boolean; // 鏄惁浣滀负 sku 璇︽儏缁勪欢
- propertyList?: PropertyAndValues[];
- propFormData?: MallSpuApi.Spu;
- ruleConfig?: RuleConfig[];
- }>(),
- {
- propFormData: () => ({}) as MallSpuApi.Spu,
- propertyList: () => [],
- ruleConfig: () => [],
- isBatch: false,
- isDetail: false,
- isComponent: false,
- isActivityComponent: false,
- },
-);
-
-const emit = defineEmits<{
- (e: 'selectionChange', value: MallSpuApi.Sku[]): void;
-}>();
-
-const { isBatch, isDetail, isComponent, isActivityComponent } = props;
-
-const formData: Ref<MallSpuApi.Spu | undefined> = ref<MallSpuApi.Spu>();
-const tableHeaders = ref<{ label: string; prop: string }[]>([]);
-
-/** 鍒涘缓绌� SKU 鏁版嵁 */
-function createEmptySku(): MallSpuApi.Sku {
- return {
- name: '', // SKU 鍚嶇О锛屾彁浜ゆ椂浼氳嚜鍔ㄤ娇鐢� SPU 鍚嶇О
- price: 0,
- marketPrice: 0,
- costPrice: 0,
- barCode: '',
- picUrl: '',
- stock: 0,
- weight: 0,
- volume: 0,
- firstBrokeragePrice: 0,
- secondBrokeragePrice: 0,
- };
-}
-
-const skuList = ref<MallSpuApi.Sku[]>([createEmptySku()]);
-
-/** 鎵归噺娣诲姞 */
-function batchAdd() {
- validateProperty();
- formData.value!.skus!.forEach((item: MallSpuApi.Sku) => {
- copyValueToTarget(item, skuList.value[0]);
- });
-}
-
-/** 鏍¢獙鍟嗗搧灞炴�у睘鎬у�� */
-function validateProperty() {
- // 鏍¢獙鍟嗗搧灞炴�у睘鎬у�兼槸鍚︿负绌猴紝鏈変竴涓负绌洪兘涓嶇粰杩�
- const warningInfo = '瀛樺湪灞炴�у睘鎬у�间负绌猴紝璇峰厛妫�鏌ュ畬鍠勫睘鎬у�煎悗閲嶈瘯锛侊紒锛�';
- for (const item of props.propertyList as PropertyAndValues[]) {
- if (!item.values || isEmpty(item.values)) {
- message.warning(warningInfo);
- throw new Error(warningInfo);
- }
- }
-}
-
-/** 鍒犻櫎 SKU */
-function deleteSku(row: MallSpuApi.Sku) {
- const index = formData.value!.skus!.findIndex(
- (sku: MallSpuApi.Sku) =>
- JSON.stringify(sku.properties) === JSON.stringify(row.properties),
- );
- if (index !== -1) {
- formData.value!.skus!.splice(index, 1);
- }
-}
-
-/** 鏍¢獙 SKU 鏁版嵁锛氫繚瀛樻椂锛屾瘡涓晢鍝佽鏍肩殑琛ㄥ崟瑕佹牎楠屻�備緥濡傦細閿�鍞噾棰濇渶浣庢槸 0.01 */
-function validateSku() {
- validateProperty();
- let warningInfo = '璇锋鏌ュ晢鍝佸悇琛岀浉鍏冲睘鎬ч厤缃紝';
- let validate = true;
-
- for (const sku of formData.value!.skus!) {
- for (const rule of props.ruleConfig as RuleConfig[]) {
- const value = getNestedValue(sku, rule.name);
- if (!rule.rule(value)) {
- validate = false;
- warningInfo += rule.message;
- break;
- }
- }
-
- if (!validate) {
- message.warning(warningInfo);
- throw new Error(warningInfo);
- }
- }
-}
-
-/**
- * 閫夋嫨鏃惰Е鍙�
- *
- * @param {object} param0 鍙傛暟瀵硅薄
- * @param {MallSpuApi.Sku[]} param0.records 浼犻�掕繃鏉ョ殑閫変腑鐨� sku 鏄竴涓暟缁�
- */
-function handleSelectionChange({ records }: { records: MallSpuApi.Sku[] }) {
- emit('selectionChange', records);
-}
-
-/** 灏嗕紶杩涙潵鐨勫�艰祴鍊肩粰 skuList */
-watch(
- () => props.propFormData,
- (data) => {
- if (!data) {
- return;
- }
- formData.value = data;
- },
- {
- deep: true,
- immediate: true,
- },
-);
-
-/** 鐢熸垚琛ㄦ暟鎹� */
-function generateTableData(propertyList: PropertyAndValues[]) {
- const propertyValues = propertyList.map((item: PropertyAndValues) =>
- (item.values || []).map((v: { id: number; name: string }) => ({
- propertyId: item.id,
- propertyName: item.name,
- valueId: v.id,
- valueName: v.name,
- })),
- );
-
- const buildSkuList = build(propertyValues);
-
- // 濡傛灉鍥炴樉鐨� sku 灞炴�у拰娣诲姞鐨勫睘鎬т笉涓�鑷村垯閲嶇疆 skus 鍒楄〃
- if (!validateData(propertyList)) {
- formData.value!.skus = [];
- }
-
- for (const item of buildSkuList) {
- const properties = Array.isArray(item) ? item : [item];
- const row = {
- ...createEmptySku(),
- properties,
- };
-
- // 濡傛灉瀛樺湪灞炴�х浉鍚岀殑 sku 鍒欎笉鍋氬鐞�
- const exists = formData.value!.skus!.some(
- (sku: MallSpuApi.Sku) =>
- JSON.stringify(sku.properties) === JSON.stringify(row.properties),
- );
-
- if (!exists) {
- formData.value!.skus!.push(row);
- }
- }
-}
-
-/** 鐢熸垚 skus 鍓嶇疆鏍¢獙 */
-function validateData(propertyList: PropertyAndValues[]): boolean {
- const skuPropertyIds: number[] = [];
- formData.value!.skus!.forEach((sku: MallSpuApi.Sku) =>
- sku.properties
- ?.map((property: MallSpuApi.Property) => property.propertyId)
- ?.forEach((propertyId?: number) => {
- if (!skuPropertyIds.includes(propertyId!)) {
- skuPropertyIds.push(propertyId!);
- }
- }),
- );
- const propertyIds = propertyList.map((item: PropertyAndValues) => item.id);
- return skuPropertyIds.length === propertyIds.length;
-}
-
-/** 鏋勫缓鎵�鏈夋帓鍒楃粍鍚� */
-function build(
- propertyValuesList: MallSpuApi.Property[][],
-): (MallSpuApi.Property | MallSpuApi.Property[])[] {
- if (propertyValuesList.length === 0) {
- return [];
- } else if (propertyValuesList.length === 1) {
- return propertyValuesList[0] || [];
- } else {
- const result: MallSpuApi.Property[][] = [];
- const rest = build(propertyValuesList.slice(1));
- const firstList = propertyValuesList[0];
- if (!firstList) {
- return [];
- }
-
- for (const element of firstList) {
- for (const element_ of rest) {
- // 绗竴娆′笉鏄暟缁勭粨鏋勶紝鍚庨潰鐨勯兘鏄暟缁勭粨鏋�
- if (Array.isArray(element_)) {
- result.push([element!, ...(element_ as MallSpuApi.Property[])]);
- } else {
- result.push([element!, element_ as MallSpuApi.Property]);
- }
- }
- }
- return result;
- }
-}
-
-/** 鐩戝惉灞炴�у垪琛紝鐢熸垚鐩稿叧鍙傛暟鍜岃〃澶� */
-watch(
- () => props.propertyList as PropertyAndValues[],
- (propertyList: PropertyAndValues[]) => {
- // 濡傛灉涓嶆槸澶氳鏍煎垯缁撴潫
- if (!formData.value!.specType) {
- return;
- }
-
- // 濡傛灉褰撳墠缁勪欢浣滀负鎵归噺娣诲姞鏁版嵁浣跨敤锛屽垯閲嶇疆琛ㄦ暟鎹�
- if (props.isBatch) {
- skuList.value = [createEmptySku()];
- }
-
- // 鍒ゆ柇浠g悊瀵硅薄鏄惁涓虹┖
- if (JSON.stringify(propertyList) === '[]') {
- return;
- }
-
- // 閲嶇疆骞剁敓鎴愯〃澶�
- tableHeaders.value = propertyList.map((item, index) => ({
- prop: `name${index}`,
- label: item.name,
- }));
-
- // 濡傛灉鍥炴樉鐨� sku 灞炴�у拰娣诲姞鐨勫睘鎬т竴鑷村垯涓嶅鐞�
- if (validateData(propertyList)) {
- return;
- }
-
- // 娣诲姞鏂板睘鎬ф病鏈夊睘鎬у�间篃涓嶅仛澶勭悊
- if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
- return;
- }
-
- // 鐢熸垚 table 鏁版嵁锛屽嵆 sku 鍒楄〃
- generateTableData(propertyList);
- },
- {
- deep: true,
- immediate: true,
- },
-);
-
-const activitySkuListRef = ref();
-
-/** 鑾峰彇 SKU 琛ㄦ牸寮曠敤 */
-function getSkuTableRef() {
- return activitySkuListRef.value;
-}
-
-defineExpose({
- generateTableData,
- validateSku,
- getSkuTableRef,
-});
-</script>
-
-<template>
- <div class="w-full">
- <!-- 鎯呭喌涓�锛氭坊鍔�/淇敼 -->
- <VxeTable
- v-if="!isDetail && !isActivityComponent"
- :data="isBatch ? skuList : formData?.skus || []"
- border
- max-height="500"
- :column-config="{
- resizable: true,
- }"
- :resizable-config="{
- dragMode: 'fixed',
- }"
- size="small"
- >
- <VxeColumn align="center" title="鍥剧墖" width="120" fixed="left">
- <template #default="{ row }">
- <ImageUpload
- v-model:value="row.picUrl"
- :max-number="1"
- :max-size="2"
- :show-description="false"
- />
- </template>
- </VxeColumn>
- <template v-if="formData?.specType && !isBatch">
- <!-- 鏍规嵁鍟嗗搧灞炴�у姩鎬佹坊鍔� -->
- <VxeColumn
- v-for="(item, index) in tableHeaders"
- :key="index"
- :title="item.label"
- align="center"
- fixed="left"
- min-width="80"
- >
- <template #default="{ row }">
- <span class="font-bold text-[#40aaff]">
- {{ row.properties?.[index]?.valueName }}
- </span>
- </template>
- </VxeColumn>
- </template>
- <VxeColumn align="center" title="鍟嗗搧鏉$爜" width="168">
- <template #default="{ row }">
- <Input v-model:value="row.barCode" class="w-full" />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="閿�鍞环" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.price"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="甯傚満浠�" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.marketPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="鎴愭湰浠�" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.costPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="搴撳瓨" width="168">
- <template #default="{ row }">
- <InputNumber v-model:value="row.stock" :min="0" class="w-full" />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="閲嶉噺(kg)" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.weight"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="浣撶Н(m^3)" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.volume"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <template v-if="formData?.subCommissionType">
- <VxeColumn align="center" title="涓�绾ц繑浣�(鍏�)" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.firstBrokeragePrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="浜岀骇杩斾剑(鍏�)" width="168">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.secondBrokeragePrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- </template>
- <VxeColumn
- v-if="formData?.specType"
- align="center"
- fixed="right"
- title="鎿嶄綔"
- width="100"
- >
- <template #default="{ row }">
- <Button v-if="isBatch" type="link" size="small" @click="batchAdd">
- 鎵归噺娣诲姞
- </Button>
- <Button
- v-else
- type="link"
- size="small"
- danger
- @click="deleteSku(row)"
- >
- 鍒犻櫎
- </Button>
- </template>
- </VxeColumn>
- </VxeTable>
-
- <!-- 鎯呭喌浜岋細璇︽儏 -->
- <VxeTable
- v-if="isDetail"
- ref="activitySkuListRef"
- :data="formData?.skus || []"
- border
- max-height="500"
- size="small"
- :column-config="{
- resizable: true,
- }"
- :resizable-config="{
- dragMode: 'fixed',
- }"
- :checkbox-config="isComponent ? { reserve: true } : undefined"
- @checkbox-change="handleSelectionChange"
- @checkbox-all="handleSelectionChange"
- >
- <VxeColumn v-if="isComponent" type="checkbox" width="45" fixed="left" />
- <VxeColumn align="center" title="鍥剧墖" max-width="140" fixed="left">
- <template #default="{ row }">
- <Image
- v-if="row.picUrl"
- :src="row.picUrl"
- class="h-[50px] w-[50px] cursor-pointer"
- :preview="true"
- />
- </template>
- </VxeColumn>
- <template v-if="formData?.specType && !isBatch">
- <!-- 鏍规嵁鍟嗗搧灞炴�у姩鎬佹坊鍔� -->
- <VxeColumn
- v-for="(item, index) in tableHeaders"
- :key="index"
- :title="item.label"
- align="center"
- max-width="80"
- fixed="left"
- >
- <template #default="{ row }">
- <span class="font-bold text-[#40aaff]">
- {{ row.properties?.[index]?.valueName }}
- </span>
- </template>
- </VxeColumn>
- </template>
- <VxeColumn align="center" title="鍟嗗搧鏉$爜" width="100">
- <template #default="{ row }">
- {{ row.barCode }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="閿�鍞环(鍏�)" width="80">
- <template #default="{ row }">
- {{ row.price }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="甯傚満浠�(鍏�)" width="80">
- <template #default="{ row }">
- {{ row.marketPrice }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="鎴愭湰浠�(鍏�)" width="80">
- <template #default="{ row }">
- {{ row.costPrice }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="搴撳瓨" width="80">
- <template #default="{ row }">
- {{ row.stock }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="閲嶉噺(kg)" width="80">
- <template #default="{ row }">
- {{ row.weight }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="浣撶Н(m^3)" width="80">
- <template #default="{ row }">
- {{ row.volume }}
- </template>
- </VxeColumn>
- <template v-if="formData?.subCommissionType">
- <VxeColumn align="center" title="涓�绾ц繑浣�(鍏�)" width="80">
- <template #default="{ row }">
- {{ row.firstBrokeragePrice }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="浜岀骇杩斾剑(鍏�)" width="80">
- <template #default="{ row }">
- {{ row.secondBrokeragePrice }}
- </template>
- </VxeColumn>
- </template>
- </VxeTable>
-
- <!-- 鎯呭喌涓夛細浣滀负娲诲姩缁勪欢 -->
- <VxeTable
- v-if="isActivityComponent"
- :data="formData?.skus || []"
- border
- max-height="500"
- size="small"
- :column-config="{
- resizable: true,
- }"
- :resizable-config="{
- dragMode: 'fixed',
- }"
- >
- <VxeColumn v-if="isComponent" type="checkbox" width="45" fixed="left" />
- <VxeColumn align="center" title="鍥剧墖" max-width="140" fixed="left">
- <template #default="{ row }">
- <Image
- :src="row.picUrl"
- class="h-[60px] w-[60px] cursor-pointer"
- :preview="true"
- />
- </template>
- </VxeColumn>
- <template v-if="formData?.specType">
- <!-- 鏍规嵁鍟嗗搧灞炴�у姩鎬佹坊鍔� -->
- <VxeColumn
- v-for="(item, index) in tableHeaders"
- :key="index"
- :title="item.label"
- align="center"
- width="80"
- fixed="left"
- >
- <template #default="{ row }">
- <span class="font-bold text-[#40aaff]">
- {{ row.properties?.[index]?.valueName }}
- </span>
- </template>
- </VxeColumn>
- </template>
- <VxeColumn align="center" title="鍟嗗搧鏉$爜" width="100">
- <template #default="{ row }">
- {{ row.barCode }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="閿�鍞环(鍏�)" width="80">
- <template #default="{ row }">
- {{ formatToFraction(row.price) }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="甯傚満浠�(鍏�)" width="80">
- <template #default="{ row }">
- {{ formatToFraction(row.marketPrice) }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="鎴愭湰浠�(鍏�)" width="80">
- <template #default="{ row }">
- {{ formatToFraction(row.costPrice) }}
- </template>
- </VxeColumn>
- <VxeColumn align="center" title="搴撳瓨" width="80">
- <template #default="{ row }">
- {{ row.stock }}
- </template>
- </VxeColumn>
- <!-- 鏂逛究鎵╁睍姣忎釜娲诲姩閰嶇疆鐨勫睘鎬т笉涓�鏍� -->
- <slot name="extension"></slot>
- </VxeTable>
- </div>
-</template>
diff --git a/src/views/mall/product/spu/components/sku-table-select.vue b/src/views/mall/product/spu/components/sku-table-select.vue
deleted file mode 100644
index a93eed7..0000000
--- a/src/views/mall/product/spu/components/sku-table-select.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<!-- SKU 閫夋嫨寮圭獥缁勪欢 -->
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { ref } from 'vue';
-
-import { Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSpu } from '#/api/mall/product/spu';
-
-import { useSkuGridColumns } from './spu-select-data';
-
-interface SpuData {
- spuId: number;
-}
-
-const emit = defineEmits<{
- change: [sku: MallSpuApi.Sku];
-}>();
-
-const visible = ref(false);
-const spuId = ref<number>();
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useSkuGridColumns(),
- height: 400,
- border: true,
- radioConfig: {
- reserve: true,
- highlight: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- pagerConfig: {
- enabled: false,
- },
- },
- gridEvents: {
- radioChange: () => {
- const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku;
- if (selectedRow) {
- emit('change', selectedRow);
- // 鍏抽棴寮圭獥
- visible.value = false;
- gridApi.grid.clearRadioRow();
- spuId.value = undefined;
- }
- },
- },
-});
-
-/** 鍏抽棴寮圭獥 */
-function closeModal() {
- visible.value = false;
- spuId.value = undefined;
-}
-
-/** 鎵撳紑寮圭獥 */
-async function openModal(data?: SpuData) {
- if (!data?.spuId) {
- return;
- }
- spuId.value = data.spuId;
- visible.value = true;
- // 娉ㄦ剰锛歶seVbenVxeGrid 鍏抽棴鍒嗛〉(pagerConfig.enabled=false)鍚庯紝proxyConfig.ajax.query 鐨勭粨鏋滀笉浼氫紶閫掑埌 vxe-table
- // 闇�瑕佹墜鍔ㄨ皟鐢� reloadData 璁剧疆琛ㄦ牸鏁版嵁
- if (!spuId.value) {
- gridApi.grid?.reloadData([]);
- return;
- }
- const spu = await getSpu(spuId.value);
- gridApi.grid?.reloadData(spu.skus || []);
-}
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open: openModal,
-});
-</script>
-
-<template>
- <Modal
- v-model:open="visible"
- title="閫夋嫨瑙勬牸"
- width="700px"
- :destroy-on-close="true"
- :footer="null"
- @cancel="closeModal"
- >
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/product/spu/components/spu-and-sku-list.vue b/src/views/mall/product/spu/components/spu-and-sku-list.vue
deleted file mode 100644
index e9e7614..0000000
--- a/src/views/mall/product/spu/components/spu-and-sku-list.vue
+++ /dev/null
@@ -1,165 +0,0 @@
-<script generic="T extends MallSpuApi.Spu" lang="ts" setup>
-import type { MallSpuApi, RuleConfig, SpuProperty } from './type';
-
-import { ref, watch } from 'vue';
-
-import { confirm } from '../../../../../packages/effects/common-ui/src';
-import { formatToFraction } from '../../../../../packages/utils/src';
-
-import { Button, Image } from 'ant-design-vue';
-
-import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
-
-import SkuList from './sku-list.vue';
-
-defineOptions({ name: 'PromotionSpuAndSkuList' });
-
-const props = withDefaults(
- defineProps<{
- deletable?: boolean; // SPU 鏄惁鍙垹闄�
- ruleConfig: RuleConfig[];
- spuList: T[];
- spuPropertyListP: SpuProperty<T>[];
- }>(),
- {
- deletable: false,
- },
-);
-
-const emit = defineEmits<{
- (e: 'delete', spuId: number): void;
-}>();
-
-const spuData = ref<MallSpuApi.Spu[]>([]); // spu 璇︽儏鏁版嵁鍒楄〃
-const skuListRef = ref<InstanceType<typeof SkuList> | undefined>(); // 鍟嗗搧灞炴�у垪琛≧ef
-const spuPropertyList = ref<SpuProperty<T>[]>([]); // spuId 瀵瑰簲鐨� sku 鐨勫睘鎬у垪琛�
-const expandRowKeys = ref<string[]>([]); // 鎺у埗灞曞紑琛岄渶瑕佽缃� row-key 灞炴�ф墠鑳戒娇鐢紝璇ュ睘鎬т负灞曞紑琛岀殑 keys 鏁扮粍銆�
-
-/**
- * 鑾峰彇鎵�鏈� sku 娲诲姩閰嶇疆
- *
- * @param extendedAttribute 鍦� sku 涓婃墿灞曠殑灞炴�э紝渚嬶細绉掓潃娲诲姩 sku 鎵╁睍灞炴�� productConfig 璇峰弬鑰� seckillActivity.ts
- */
-function getSkuConfigs(extendedAttribute: string) {
- // 楠岃瘉 SKU 鏁版嵁锛堝鏋滄湁 ref 鐨勮瘽锛�
- if (skuListRef.value) {
- skuListRef.value.validateSku();
- }
- const seckillProducts: unknown[] = [];
- spuPropertyList.value.forEach((item) => {
- item.spuDetail.skus?.forEach((sku) => {
- const extendedValue = (sku as Record<string, unknown>)[extendedAttribute];
- if (extendedValue) {
- seckillProducts.push(extendedValue);
- }
- });
- });
- return seckillProducts;
-}
-
-defineExpose({ getSkuConfigs }); // 鏆撮湶鍑虹粰琛ㄥ崟鎻愪氦鏃朵娇鐢�
-
-/** 澶氶�夋椂鍙互鍒犻櫎 SPU */
-async function deleteSpu(spuId: number) {
- await confirm(`鏄惁鍒犻櫎鍟嗗搧缂栧彿涓�${spuId}鐨勬暟鎹紵`);
- const index = spuData.value.findIndex((item) => item.id === spuId);
- if (index !== -1) {
- spuData.value.splice(index, 1);
- emit('delete', spuId);
- }
-}
-
-/** 灏嗕紶杩涙潵鐨勫�艰祴鍊肩粰 spuData */
-watch(
- () => props.spuList,
- (data) => {
- if (!data) return;
- spuData.value = data as MallSpuApi.Spu[];
- },
- {
- deep: true,
- immediate: true,
- },
-);
-
-/** 灏嗕紶杩涙潵鐨勫�艰祴鍊肩粰 spuPropertyList */
-watch(
- () => props.spuPropertyListP,
- (data) => {
- if (!data) return;
- spuPropertyList.value = data as SpuProperty<T>[];
- // 瑙e喅濡傛灉涔嬪墠閫夋嫨鐨勬槸鍗曡鏍� spu 鐨勮瘽鍚庨潰閫夋嫨澶氳鏍� sku 澶氳鏍煎睘鎬т俊鎭笉灞曠ず鐨勯棶棰樸�傝В鍐虫柟娉曪細璁� SkuList 缁勪欢閲嶆柊娓叉煋锛堣鎶樺彔浼氬共鎺夊寘鍚殑缁勪欢灞曞紑鏃朵細閲嶆柊鍔犺浇锛�
- setTimeout(() => {
- expandRowKeys.value = data.map((item) => String(item.spuId));
- }, 200);
- },
- {
- deep: true,
- immediate: true,
- },
-);
-</script>
-
-<template>
- <VxeTable
- :data="spuData"
- :expand-row-keys="expandRowKeys"
- :row-config="{
- keyField: 'id',
- }"
- >
- <VxeColumn type="expand" width="30">
- <template #content="{ row }">
- <SkuList
- ref="skuListRef"
- :is-activity-component="true"
- :prop-form-data="
- spuPropertyList.find((item) => item.spuId === row.id)?.spuDetail
- "
- :property-list="
- spuPropertyList.find((item) => item.spuId === row.id)?.propertyList
- "
- :rule-config="ruleConfig"
- >
- <template #extension>
- <slot></slot>
- </template>
- </SkuList>
- </template>
- </VxeColumn>
- <VxeColumn field="id" align="center" title="鍟嗗搧缂栧彿" min-width="30" />
- <VxeColumn title="鍟嗗搧鍥�" min-width="80">
- <template #default="{ row }">
- <Image
- v-if="row.picUrl"
- :src="row.picUrl"
- class="!h-[30px] !w-[30px] cursor-pointer"
- :preview="true"
- />
- </template>
- </VxeColumn>
- <VxeColumn
- field="name"
- title="鍟嗗搧鍚嶇О"
- min-width="300"
- show-overflow="tooltip"
- />
- <VxeColumn align="center" title="鍟嗗搧鍞环" min-width="90">
- <template #default="{ row }">
- {{ formatToFraction(row.price) }}
- </template>
- </VxeColumn>
- <VxeColumn field="salesCount" align="center" title="閿�閲�" min-width="90" />
- <VxeColumn field="stock" align="center" title="搴撳瓨" min-width="90" />
- <VxeColumn
- v-if="spuData.length > 1 && deletable"
- align="center"
- title="鎿嶄綔"
- min-width="90"
- >
- <template #default="{ row }">
- <Button type="link" danger @click="deleteSpu(row.id)"> 鍒犻櫎</Button>
- </template>
- </VxeColumn>
- </VxeTable>
-</template>
diff --git a/src/views/mall/product/spu/components/spu-select-data.ts b/src/views/mall/product/spu/components/spu-select-data.ts
deleted file mode 100644
index d4cffee..0000000
--- a/src/views/mall/product/spu/components/spu-select-data.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import type { Ref } from 'vue';
-
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps, VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { computed } from 'vue';
-
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(
- categoryTreeList: Ref<MallCategoryApi.Category[] | unknown[]>,
-): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'categoryId',
- label: '鍟嗗搧鍒嗙被',
- component: 'TreeSelect',
- componentProps: {
- treeData: computed(() => categoryTreeList.value),
- fieldNames: {
- label: 'name',
- value: 'id',
- },
- treeCheckStrictly: true,
- placeholder: '璇烽�夋嫨鍟嗗搧鍒嗙被',
- allowClear: true,
- showSearch: true,
- treeNodeFilterProp: 'name',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- isSelectSku: boolean,
-): VxeTableGridOptions['columns'] {
- return [
- {
- type: 'expand',
- width: 30,
- visible: isSelectSku,
- slots: { content: 'expand_content' },
- },
- { type: 'checkbox', width: 55 },
- {
- field: 'id',
- title: '鍟嗗搧缂栧彿',
- minWidth: 100,
- align: 'center',
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥�',
- width: 100,
- align: 'center',
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'name',
- title: '鍟嗗搧鍚嶇О',
- minWidth: 300,
- showOverflow: 'tooltip',
- },
- {
- field: 'price',
- title: '鍟嗗搧鍞环(鍏�)',
- minWidth: 90,
- align: 'center',
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'salesCount',
- title: '閿�閲�',
- minWidth: 90,
- align: 'center',
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- minWidth: 90,
- align: 'center',
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- minWidth: 70,
- align: 'center',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- align: 'center',
- formatter: 'formatDateTime',
- },
- ] as VxeTableGridOptions['columns'];
-}
-
-/** SKU 鍒楄〃鐨勫瓧娈� */
-export function useSkuGridColumns(): VxeGridProps['columns'] {
- return [
- {
- type: 'radio',
- width: 55,
- },
- {
- field: 'id',
- title: '鍟嗗搧缂栧彿',
- minWidth: 100,
- align: 'center',
- },
- {
- field: 'picUrl',
- title: '鍥剧墖',
- width: 100,
- align: 'center',
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'properties',
- title: '瑙勬牸',
- minWidth: 120,
- align: 'center',
- formatter: ({ cellValue }) => {
- return (
- cellValue?.map((p: MallSpuApi.Property) => p.valueName)?.join(' ') ||
- '-'
- );
- },
- },
- {
- field: 'price',
- title: '閿�鍞环(鍏�)',
- width: 120,
- align: 'center',
- formatter: ({ cellValue }) => {
- return fenToYuan(cellValue);
- },
- },
- ];
-}
diff --git a/src/views/mall/product/spu/components/spu-select.vue b/src/views/mall/product/spu/components/spu-select.vue
deleted file mode 100644
index fcdad4a..0000000
--- a/src/views/mall/product/spu/components/spu-select.vue
+++ /dev/null
@@ -1,316 +0,0 @@
-<script lang="ts" setup>
-import type { PropertyAndValues } from './type';
-
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { computed, nextTick, onMounted, ref } from 'vue';
-
-import { handleTree } from '../../../../../packages/utils/src';
-
-import { message, Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCategoryList } from '#/api/mall/product/category';
-import { getSpu, getSpuPage } from '#/api/mall/product/spu';
-
-import { getPropertyList } from './property-util';
-import SkuList from './sku-list.vue';
-import { useGridColumns, useGridFormSchema } from './spu-select-data';
-
-defineOptions({ name: 'SpuSelect' });
-
-const props = withDefaults(defineProps<SpuSelectProps>(), {
- isSelectSku: false,
- radio: false,
-});
-
-const emit = defineEmits<{
- (e: 'select', spuId: number, skuIds?: number[]): void;
-}>();
-
-interface SpuSelectProps {
- // 榛樿涓嶉渶瑕侊紙涓嶉渶瑕佺殑鎯呭喌涓嬪彧杩斿洖 spu锛岄渶瑕佺殑鎯呭喌涓嬭繑鍥� 閫変腑鐨� spu 鍜� sku 鍒楄〃锛�
- // 鍏跺畠娲诲姩闇�瑕侀�夋嫨鍟嗗搧鍜屽晢鍝佸睘鎬у鍏ユ缁勪欢鍗冲彲锛岄渶娣诲姞缁勪欢灞炴�� :isSelectSku='true'
- isSelectSku?: boolean; // 鏄惁闇�瑕侀�夋嫨 sku 灞炴��
- radio?: boolean; // 鏄惁鍗曢�� sku
-}
-
-// ============ 鏁版嵁鐘舵�� ============
-const categoryList = ref<MallCategoryApi.Category[]>([]); // 鍒嗙被鍒楄〃
-const categoryTreeList = ref<MallCategoryApi.Category[]>([]); // 鍒嗙被鏍�
-const propertyList = ref<PropertyAndValues[]>([]); // 鍟嗗搧灞炴�у垪琛�
-const spuData = ref<MallSpuApi.Spu>(); // 褰撳墠灞曞紑鐨勫晢鍝佽鎯�
-const isExpand = ref(false); // 鎺у埗 SKU 鍒楄〃鏄剧ず
-
-// ============ 鍟嗗搧閫夋嫨鐩稿叧 ============
-const selectedSpuId = ref<number>(0); // 閫変腑鐨勫晢鍝� spuId
-const selectedSkuIds = ref<number[]>([]); // 閫変腑鐨勫晢鍝� skuIds
-const skuListRef = ref<InstanceType<typeof SkuList>>(); // 鍟嗗搧灞炴�ч�夋嫨 Ref
-
-/** 澶勭悊 SKU 閫夋嫨鍙樺寲 */
-function selectSku(val: MallSpuApi.Sku[]) {
- const skuTable = skuListRef.value?.getSkuTableRef();
- if (selectedSpuId.value === 0) {
- message.warning('璇峰厛閫夋嫨鍟嗗搧鍐嶉�夋嫨鐩稿簲鐨勮鏍硷紒锛侊紒');
- skuTable?.clearSelection();
- return;
- }
- if (val.length === 0) {
- selectedSkuIds.value = [];
- return;
- }
- if (props.radio) {
- // 鍙�夋嫨涓�涓�
- const firstId = val[0]?.id;
- if (firstId !== undefined) {
- selectedSkuIds.value = [firstId];
- }
- // 濡傛灉澶т簬1涓�
- if (val.length > 1) {
- // 娓呯┖閫夋嫨
- skuTable?.clearSelection();
- // 鍙樻洿涓烘渶鍚庝竴娆¢�夋嫨鐨�
- const lastItem = val.pop();
- if (lastItem) {
- skuTable?.toggleRowSelection(lastItem, true);
- }
- }
- } else {
- selectedSkuIds.value = val
- .map((sku) => sku.id!)
- .filter((id): id is number => id !== undefined);
- }
-}
-
-/** 澶勭悊 SPU 閫夋嫨鍙樺寲 */
-function selectSpu(row: MallSpuApi.Spu) {
- if (!row) {
- selectedSpuId.value = 0;
- return;
- }
- selectedSpuId.value = row.id!;
-
- // 鍒囨崲閫夋嫨 spu 濡傛灉鏈夐�夋嫨鐨� sku 鍒欐竻绌�,纭繚閫夋嫨鐨� sku 鏄搴旂殑 spu 涓嬮潰鐨�
- if (selectedSkuIds.value.length > 0) {
- selectedSkuIds.value = [];
- }
-}
-
-/** 澶勭悊琛屽睍寮�鍙樺寲 */
-async function expandChange(
- row: MallSpuApi.Spu,
- expandedRows?: MallSpuApi.Spu[],
-) {
- // 鍒ゆ柇闇�瑕佸睍寮�鐨� spuId === 閫夋嫨鐨� spuId銆傚鏋滈�夋嫨浜� A 灏卞睍寮� A 鐨� skuList銆傚鏋滈�夋嫨浜� A 鎵嬪姩灞曞紑 B 鍒欓樆鏂�
- // 鐩殑锛氶槻姝㈣閫� sku
- if (selectedSpuId.value !== 0) {
- if (row.id !== selectedSpuId.value) {
- message.warning('浣犲凡閫夋嫨鍟嗗搧璇峰厛鍙栨秷');
- // 闃绘灞曞紑锛岄�氳繃閲嶆柊璁剧疆灞曞紑鐘舵�佹潵淇濇寔褰撳墠閫変腑琛岀殑灞曞紑
- if (row.id !== undefined) {
- const tableData = gridApi.grid.getTableData().fullData;
- const selectedRow = tableData.find(
- (item: MallSpuApi.Spu) => item.id === selectedSpuId.value,
- );
- if (selectedRow) {
- // 鍏抽棴褰撳墠琛岋紝閲嶆柊灞曞紑閫変腑琛�
- gridApi.grid.setRowExpand(selectedRow, true);
- }
- }
- return;
- }
- // 濡傛灉宸插睍寮� skuList 鍒欓�夋嫨姝ゅ搴旂殑 spu 涓嶉渶瑕侀噸鏂拌幏鍙栨覆鏌� skuList
- if (isExpand.value && spuData.value?.id === row.id) {
- return;
- }
- }
- spuData.value = undefined;
- propertyList.value = [];
- isExpand.value = false;
- if (expandedRows?.length === 0) {
- // 濡傛灉灞曞紑涓暟涓� 0锛岀洿鎺ヨ繑鍥�
- return;
- }
- // 鑾峰彇 SPU 璇︽儏
- if (row.id === undefined) {
- return;
- }
- const res = (await getSpu(row.id)) as MallSpuApi.Spu;
- res.skus?.forEach((item) => {
- if (typeof item.price === 'number') {
- item.price = Math.round(item.price * 100);
- }
- if (typeof item.marketPrice === 'number') {
- item.marketPrice = Math.round(item.marketPrice * 100);
- }
- if (typeof item.costPrice === 'number') {
- item.costPrice = Math.round(item.costPrice * 100);
- }
- if (typeof item.firstBrokeragePrice === 'number') {
- item.firstBrokeragePrice = Math.round(item.firstBrokeragePrice * 100);
- }
- if (typeof item.secondBrokeragePrice === 'number') {
- item.secondBrokeragePrice = Math.round(item.secondBrokeragePrice * 100);
- }
- });
- propertyList.value = getPropertyList(res);
- spuData.value = res;
- isExpand.value = true;
-}
-
-const formSchema = computed(() => useGridFormSchema(categoryTreeList)); // 鎼滅储琛ㄥ崟 Schema
-const gridColumns = computed<VxeTableGridOptions['columns']>(() => {
- const columns = useGridColumns(props.isSelectSku);
- // 灏� checkbox 鏇挎崲涓� radio
- return columns?.map((col) => {
- if (col.type === 'checkbox') {
- return { ...col, type: 'radio' };
- }
- return col;
- });
-}); // 琛ㄦ牸鍒楅厤缃�
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema.value,
- layout: 'horizontal',
- collapsed: false,
- },
- gridOptions: {
- columns: gridColumns.value,
- height: 800,
- border: true,
- radioConfig: {
- reserve: true,
- highlight: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- expandConfig: {
- trigger: 'row',
- reserve: true,
- },
- proxyConfig: {
- ajax: {
- async query({ page }: any, formValues: any) {
- return await getSpuPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- tabType: 0,
- ...formValues,
- });
- },
- },
- },
- },
- gridEvents: {
- radioChange: ({ row, $grid }: { $grid: any; row: MallSpuApi.Spu }) => {
- selectSpu(row);
- if (props.isSelectSku) {
- $grid.clearRowExpand();
- $grid.setRowExpand(row, true);
- expandChange(row, [row]);
- }
- },
- toggleRowExpand: ({
- row,
- expanded,
- }: {
- expanded: boolean;
- row: unknown;
- }) => {
- if (expanded) {
- expandChange(row as MallSpuApi.Spu, [row as MallSpuApi.Spu]);
- } else {
- expandChange(row as MallSpuApi.Spu, []);
- }
- },
- },
-});
-
-const visible = ref(false); // 寮圭獥鏄剧ず鐘舵��
-
-/** 鎵撳紑寮圭獥 */
-async function openModal() {
- visible.value = true;
- // 绛夊緟 Grid 缁勪欢瀹屽叏鍒濆鍖栧悗鍐嶆煡璇㈡暟鎹�
- await nextTick();
- if (gridApi.grid) {
- await gridApi.query();
- }
-}
-
-/** 鍏抽棴寮圭獥 */
-function closeModal() {
- visible.value = false;
- selectedSpuId.value = 0;
- selectedSkuIds.value = [];
- spuData.value = undefined;
- propertyList.value = [];
- isExpand.value = false;
-}
-
-/** 纭閫夋嫨 */
-function handleConfirm() {
- if (selectedSpuId.value === 0) {
- message.warning('娌℃湁閫夋嫨浠讳綍鍟嗗搧');
- return;
- }
- if (props.isSelectSku && selectedSkuIds.value.length === 0) {
- message.warning('娌℃湁閫夋嫨浠讳綍鍟嗗搧灞炴��');
- return;
- }
- // 杩斿洖鍚勮嚜 id 鍒楄〃
- props.isSelectSku
- ? emit('select', selectedSpuId.value, selectedSkuIds.value)
- : emit('select', selectedSpuId.value);
-
- // 閲嶇疆閫変腑鐘舵��
- closeModal();
-}
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open: openModal,
-});
-
-/** 鍒濆鍖栧垎绫绘暟鎹� */
-onMounted(async () => {
- categoryList.value = await getCategoryList({});
- categoryTreeList.value = handleTree(
- categoryList.value,
- 'id',
- 'parentId',
- ) as MallCategoryApi.Category[];
-});
-</script>
-
-<template>
- <Modal
- v-model:open="visible"
- title="鍟嗗搧閫夋嫨"
- width="70%"
- :destroy-on-close="true"
- @ok="handleConfirm"
- @cancel="closeModal"
- >
- <Grid>
- <!-- 灞曞紑鍒楀唴瀹癸紙SKU 鍒楄〃锛� -->
- <template v-if="isSelectSku" #expand_content="{ row }">
- <SkuList
- v-if="isExpand && spuData?.id === row.id"
- ref="skuListRef"
- :is-component="true"
- :is-detail="true"
- :prop-form-data="spuData"
- :property-list="propertyList"
- @selection-change="selectSku"
- />
- </template>
- </Grid>
- </Modal>
-</template>
diff --git a/src/views/mall/product/spu/components/spu-showcase.vue b/src/views/mall/product/spu/components/spu-showcase.vue
deleted file mode 100644
index 985de73..0000000
--- a/src/views/mall/product/spu/components/spu-showcase.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-<!-- 鍟嗗搧姗辩獥缁勪欢锛氱敤浜庡睍绀哄拰閫夋嫨鍟嗗搧 SPU -->
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { computed, ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { Image, Tooltip } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-
-import SpuTableSelect from './spu-table-select.vue';
-
-interface SpuShowcaseProps {
- modelValue?: number | number[];
- limit?: number;
- disabled?: boolean;
-}
-
-const props = withDefaults(defineProps<SpuShowcaseProps>(), {
- modelValue: undefined,
- limit: Number.MAX_VALUE,
- disabled: false,
-});
-
-const emit = defineEmits(['update:modelValue', 'change']);
-
-const productSpus = ref<MallSpuApi.Spu[]>([]); // 宸查�夋嫨鐨勫晢鍝佸垪琛�
-const spuTableSelectRef = ref<InstanceType<typeof SpuTableSelect>>(); // 鍟嗗搧閫夋嫨琛ㄦ牸缁勪欢寮曠敤
-const isMultiple = computed(() => props.limit !== 1); // 鏄惁涓哄閫夋ā寮�
-
-/** 璁$畻鏄惁鍙互娣诲姞 */
-const canAdd = computed(() => {
- if (props.disabled) {
- return false;
- }
- if (!props.limit) {
- return true;
- }
- return productSpus.value.length < props.limit;
-});
-
-/** 鐩戝惉 modelValue 鍙樺寲锛屽姞杞藉晢鍝佽鎯� */
-watch(
- () => props.modelValue,
- async (newValue) => {
- // eslint-disable-next-line unicorn/no-nested-ternary
- const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
- if (ids.length === 0) {
- productSpus.value = [];
- return;
- }
- // 鍙湁鍟嗗搧鍙戠敓鍙樺寲鏃舵墠閲嶆柊鏌ヨ
- if (
- productSpus.value.length === 0 ||
- productSpus.value.some((spu) => !ids.includes(spu.id!))
- ) {
- productSpus.value = await getSpuDetailList(ids);
- }
- },
- { immediate: true },
-);
-
-/** 鎵撳紑鍟嗗搧閫夋嫨瀵硅瘽妗� */
-function handleOpenSpuSelect() {
- spuTableSelectRef.value?.open(productSpus.value);
-}
-
-/** 閫夋嫨鍟嗗搧鍚庤Е鍙� */
-function handleSpuSelected(spus: MallSpuApi.Spu | MallSpuApi.Spu[]) {
- productSpus.value = Array.isArray(spus) ? spus : [spus];
- emitSpuChange();
-}
-
-/** 鍒犻櫎鍟嗗搧 */
-function handleRemoveSpu(index: number) {
- productSpus.value.splice(index, 1);
- emitSpuChange();
-}
-
-/** 瑙﹀彂鍙樻洿浜嬩欢 */
-function emitSpuChange() {
- if (props.limit === 1) {
- const spu = productSpus.value.length > 0 ? productSpus.value[0] : null;
- emit('update:modelValue', spu?.id || 0);
- emit('change', spu);
- } else {
- emit(
- 'update:modelValue',
- productSpus.value.map((spu) => spu.id!),
- );
- emit('change', productSpus.value);
- }
-}
-</script>
-
-<template>
- <div class="flex flex-wrap items-center gap-2">
- <!-- 宸查�夊晢鍝佸垪琛� -->
- <div
- v-for="(spu, index) in productSpus"
- :key="spu.id"
- class="group relative h-[60px] w-[60px] overflow-hidden rounded-lg"
- >
- <Tooltip :title="spu.name">
- <div class="relative h-full w-full">
- <Image
- :src="spu.picUrl"
- class="h-full w-full rounded-lg object-cover"
- />
- <!-- 鍒犻櫎鎸夐挳 -->
- <IconifyIcon
- v-if="!disabled"
- icon="lucide:circle-x"
- class="absolute -right-2 -top-2 cursor-pointer text-xl text-red-500 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100"
- @click="handleRemoveSpu(index)"
- />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娣诲姞鍟嗗搧鎸夐挳 -->
- <Tooltip v-if="canAdd" title="閫夋嫨鍟嗗搧">
- <div
- class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors hover:border-primary hover:bg-primary/5"
- @click="handleOpenSpuSelect"
- >
- <IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
- </div>
- </Tooltip>
- </div>
-
- <!-- 鍟嗗搧閫夋嫨瀵硅瘽妗� -->
- <SpuTableSelect
- ref="spuTableSelectRef"
- :multiple="isMultiple"
- @change="handleSpuSelected"
- />
-</template>
diff --git a/src/views/mall/product/spu/components/spu-table-select.vue b/src/views/mall/product/spu/components/spu-table-select.vue
deleted file mode 100644
index f593d36..0000000
--- a/src/views/mall/product/spu/components/spu-table-select.vue
+++ /dev/null
@@ -1,241 +0,0 @@
-<!-- SPU 鍟嗗搧閫夋嫨寮圭獥缁勪欢 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { computed, nextTick, onMounted, ref } from 'vue';
-
-import { handleTree } from '../../../../../packages/utils/src';
-
-import { Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCategoryList } from '#/api/mall/product/category';
-import { getSpuPage } from '#/api/mall/product/spu';
-import { getRangePickerDefaultProps } from '#/utils';
-
-interface SpuTableSelectProps {
- multiple?: boolean; // 鏄惁鍗曢�夛細true - checkbox锛沠alse - radio
-}
-
-const props = withDefaults(defineProps<SpuTableSelectProps>(), {
- multiple: false,
-});
-
-const emit = defineEmits<{
- change: [spu: MallSpuApi.Spu | MallSpuApi.Spu[]];
-}>();
-
-const categoryList = ref<MallCategoryApi.Category[]>([]); // 鍒嗙被鍒楄〃
-const categoryTreeList = ref<any[]>([]); // 鍒嗙被鏍�
-
-/** 寮圭獥鏄剧ず鐘舵�� */
-const visible = ref(false);
-const initData = ref<MallSpuApi.Spu | MallSpuApi.Spu[]>();
-
-/** 鍗曢�夛細澶勭悊閫変腑鍙樺寲 */
-function handleRadioChange() {
- const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Spu;
- if (selectedRow) {
- emit('change', selectedRow);
- closeModal();
- }
-}
-
-/** 鎼滅储琛ㄥ崟 Schema */
-const formSchema = computed<VbenFormSchema[]>(() => [
- {
- fieldName: 'name',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'categoryId',
- label: '鍟嗗搧鍒嗙被',
- component: 'TreeSelect',
- // TODO @鑺嬭壙锛氬彲鑳借娴嬭瘯涓嬶紱
- componentProps: {
- treeData: categoryTreeList,
- fieldNames: {
- label: 'name',
- value: 'id',
- },
- treeCheckStrictly: true,
- placeholder: '璇烽�夋嫨鍟嗗搧鍒嗙被',
- allowClear: true,
- showSearch: true,
- treeNodeFilterProp: 'name',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
-]);
-
-/** 琛ㄦ牸鍒楅厤缃� */
-const gridColumns = computed<VxeGridProps['columns']>(() => {
- const columns: VxeGridProps['columns'] = [];
- if (props.multiple) {
- columns.push({ type: 'checkbox', width: 55 });
- } else {
- columns.push({ type: 'radio', width: 55 });
- }
- columns.push(
- {
- field: 'id',
- title: '鍟嗗搧缂栧彿',
- minWidth: 100,
- align: 'center',
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥�',
- width: 100,
- align: 'center',
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'name',
- title: '鍟嗗搧鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'categoryId',
- title: '鍟嗗搧鍒嗙被',
- minWidth: 120,
- formatter: ({ cellValue }) => {
- const category = categoryList.value?.find((c) => c.id === cellValue);
- return category?.name || '-';
- },
- },
- );
- return columns;
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema.value,
- layout: 'horizontal',
- collapsed: false,
- },
- gridOptions: {
- columns: gridColumns.value,
- height: 500,
- border: true,
- checkboxConfig: {
- reserve: true,
- },
- radioConfig: {
- reserve: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- proxyConfig: {
- ajax: {
- async query({ page }: any, formValues: any) {
- return await getSpuPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- tabType: 0,
- ...formValues,
- });
- },
- },
- },
- },
- gridEvents: {
- radioChange: handleRadioChange,
- },
-});
-
-/** 鎵撳紑寮圭獥 */
-async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) {
- initData.value = data;
- visible.value = true;
- // 绛夊緟 Grid 缁勪欢瀹屽叏鍒濆鍖栧悗鍐嶆煡璇㈡暟鎹�
- await nextTick();
- if (gridApi.grid) {
- // 1. 鍏堟煡璇㈡暟鎹�
- await gridApi.query();
- // 2. 璁剧疆宸查�変腑琛�
- if (props.multiple && Array.isArray(data) && data.length > 0) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData().fullData;
- data.forEach((spu) => {
- const row = tableData.find(
- (item: MallSpuApi.Spu) => item.id === spu.id,
- );
- if (row) {
- gridApi.grid.setCheckboxRow(row, true);
- }
- });
- }, 300);
- } else if (!props.multiple && data && !Array.isArray(data)) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData().fullData;
- const row = tableData.find(
- (item: MallSpuApi.Spu) => item.id === data.id,
- );
- if (row) {
- gridApi.grid.setRadioRow(row);
- }
- }, 300);
- }
- }
-}
-
-/** 鍏抽棴寮圭獥 */
-async function closeModal() {
- visible.value = false;
- await gridApi.grid.clearCheckboxRow();
- await gridApi.grid.clearRadioRow();
- initData.value = undefined;
-}
-
-/** 纭閫夋嫨锛堝閫夋ā寮忥級 */
-function handleConfirm() {
- const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[];
- emit('change', selectedRows);
- closeModal();
-}
-
-defineExpose({
- open: openModal,
-}); // 瀵瑰鏆撮湶鐨勬柟娉�
-
-/** 鍒濆鍖栧垎绫绘暟鎹� */
-onMounted(async () => {
- categoryList.value = await getCategoryList({});
- categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId');
-});
-</script>
-
-<template>
- <Modal
- v-model:open="visible"
- title="閫夋嫨鍟嗗搧"
- width="950px"
- :destroy-on-close="true"
- :footer="props.multiple ? undefined : null"
- @ok="handleConfirm"
- @cancel="closeModal"
- >
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/product/spu/components/type.ts b/src/views/mall/product/spu/components/type.ts
deleted file mode 100644
index 7e1155a..0000000
--- a/src/views/mall/product/spu/components/type.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/** 鍟嗗搧灞炴�у強鍏跺�肩殑鏍戝舰缁撴瀯锛堢敤浜庡墠绔睍绀哄拰鎿嶄綔锛� */
-export interface PropertyAndValues {
- id: number;
- name: string;
- values?: PropertyAndValues[];
-}
-
-export interface RuleConfig {
- // 闇�瑕佹牎楠岀殑瀛楁
- // 渚嬶細name: 'name' 鍒欒〃绀烘牎楠� sku.name 鐨勫��
- // 渚嬶細name: 'productConfig.stock' 鍒欒〃绀烘牎楠� sku.productConfig.name 鐨勫��,姝ゅ productConfig 琛ㄧず鎴戝湪 Sku 涓婃墿灞曠殑灞炴��
- name: string;
- // 鏍¢獙瑙勬牸涓轰竴涓瘉鎺夊嚱鏁帮紝鍏朵腑 arg 涓洪渶瑕佹牎楠岀殑瀛楁鐨勫�笺��
- // 渚嬶細闇�瑕佹牎楠屼环鏍煎繀椤诲ぇ浜�0.01
- // {
- // name:'price',
- // rule:(arg: number) => arg > 0.01
- // }
- rule: (arg: any) => boolean;
- // 鏍¢獙涓嶉�氳繃鏃剁殑娑堟伅鎻愮ず
- message: string;
-}
-
-export interface SpuProperty<T> {
- propertyList: PropertyAndValues[];
- spuDetail: T;
- spuId: number;
-}
-
-// Re-export for use in generic constraint
-
-export { type MallSpuApi } from '#/api/mall/product/spu';
diff --git a/src/views/mall/product/spu/data.ts b/src/views/mall/product/spu/data.ts
deleted file mode 100644
index 1c457b9..0000000
--- a/src/views/mall/product/spu/data.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { handleTree, treeToString } from '../../../../packages/utils/src';
-
-import { getCategoryList } from '#/api/mall/product/category';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-let categoryList: any[] = [];
-getCategoryList({}).then((data) => {
- categoryList = handleTree(data, 'id', 'parentId', 'children');
-});
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'categoryId',
- label: '鍟嗗搧鍒嗙被',
- component: 'ApiTreeSelect',
- componentProps: {
- placeholder: '璇烽�夋嫨鍟嗗搧鍒嗙被',
- allowClear: true,
- options: categoryList,
- fieldNames: { label: 'name', value: 'id', children: 'children' },
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- onStatusChange?: (
- newStatus: number,
- row: MallSpuApi.Spu,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鍟嗗搧缂栧彿',
- fixed: 'left',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鍟嗗搧鍚嶇О',
- fixed: 'left',
- minWidth: 200,
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'categoryId',
- title: '鍟嗗搧鍒嗙被',
- minWidth: 150,
- formatter: ({ row }) => {
- return treeToString(categoryList, row.categoryId);
- },
- },
- {
- field: 'status',
- title: '閿�鍞姸鎬�',
- minWidth: 100,
- cellRender: {
- attrs: { beforeChange: onStatusChange },
- name: 'CellSwitch',
- props: {
- checkedValue: 1,
- checkedChildren: '涓婃灦',
- unCheckedValue: 0,
- unCheckedChildren: '涓嬫灦',
- },
- },
- },
- {
- field: 'price',
- title: '浠锋牸(鍏�)',
- minWidth: 100,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'marketPrice',
- title: '甯傚満浠�(鍏�)',
- minWidth: 100,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'costPrice',
- title: '鎴愭湰浠�(鍏�)',
- minWidth: 100,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'salesCount',
- title: '閿�閲�',
- minWidth: 80,
- },
- {
- field: 'virtualSalesCount',
- title: '铏氭嫙閿�閲�',
- minWidth: 100,
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- minWidth: 80,
- },
- {
- field: 'browseCount',
- title: '娴忚閲�',
- minWidth: 100,
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- minWidth: 80,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 160,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 300,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/product/spu/form/data.ts b/src/views/mall/product/spu/form/data.ts
deleted file mode 100644
index 2c75cf2..0000000
--- a/src/views/mall/product/spu/form/data.ts
+++ /dev/null
@@ -1,310 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { DeliveryTypeEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { handleTree } from '../../../../../packages/utils/src';
-
-import { getSimpleBrandList } from '#/api/mall/product/brand';
-import { getCategoryList } from '#/api/mall/product/category';
-import { getSimpleTemplateList } from '#/api/mall/trade/delivery/expressTemplate';
-
-/** 鍩虹璁剧疆鐨勮〃鍗� */
-export function useInfoFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'categoryId',
- label: '鍒嗙被鍚嶇О',
- component: 'ApiTreeSelect',
- componentProps: {
- api: async () => {
- const data = await getCategoryList({});
- return handleTree(data);
- },
- fieldNames: { label: 'name', value: 'id', children: 'children' },
- placeholder: '璇烽�夋嫨鍟嗗搧鍒嗙被',
- },
- rules: 'required',
- },
- {
- fieldName: 'brandId',
- label: '鍟嗗搧鍝佺墝',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleBrandList,
- labelField: 'name',
- valueField: 'id',
- allowClear: true,
- placeholder: '璇烽�夋嫨鍟嗗搧鍝佺墝',
- },
- rules: 'required',
- },
- {
- fieldName: 'keyword',
- label: '鍟嗗搧鍏抽敭瀛�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸叧閿瓧',
- },
- rules: 'required',
- },
- {
- fieldName: 'introduction',
- label: '鍟嗗搧绠�浠�',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佺畝浠�',
- autoSize: { minRows: 2, maxRows: 2 },
- showCount: true,
- maxlength: 128,
- allowClear: true,
- },
- rules: 'required',
- },
- {
- fieldName: 'picUrl',
- label: '鍟嗗搧灏侀潰鍥�',
- component: 'ImageUpload',
- componentProps: {
- maxSize: 30,
- },
- rules: 'required',
- },
- {
- fieldName: 'sliderPicUrls',
- label: '鍟嗗搧杞挱鍥�',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 10,
- multiple: true,
- maxSize: 30,
- },
- rules: 'required',
- },
- ];
-}
-
-/** 浠锋牸搴撳瓨鐨勮〃鍗� */
-export function useSkuFormSchema(
- propertyList: any[] = [],
- isDetail: boolean = false,
-): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'subCommissionType',
- label: '鍒嗛攢绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- allowClear: true,
- options: [
- {
- label: '榛樿璁剧疆',
- value: false,
- },
- {
- label: '鍗曠嫭璁剧疆',
- value: true,
- },
- ],
- },
- rules: 'required',
- },
- {
- fieldName: 'specType',
- label: '鍟嗗搧瑙勬牸',
- component: 'RadioGroup',
- componentProps: {
- allowClear: true,
- options: [
- {
- label: '鍗曡鏍�',
- value: false,
- },
- {
- label: '澶氳鏍�',
- value: true,
- },
- ],
- },
- rules: 'required',
- },
- // 鍗曡鏍兼椂鏄剧ず鐨� SkuList
- {
- fieldName: 'singleSkuList',
- label: '',
- component: 'Input',
- dependencies: {
- triggerFields: ['specType'],
- // 褰� specType 涓� false锛堝崟瑙勬牸锛夋椂鏄剧ず
- show: (values) => values.specType === false,
- },
- },
- // 澶氳鏍兼椂鏄剧ず鐨勫晢鍝佸睘鎬э紙鍗犱綅锛屽疄闄呴�氳繃鎻掓Ы娓叉煋锛�
- {
- fieldName: 'productAttributes',
- label: '鍟嗗搧灞炴��',
- component: 'Input',
- dependencies: {
- triggerFields: ['specType'],
- // 褰� specType 涓� true锛堝瑙勬牸锛夋椂鏄剧ず
- show: (values) => values.specType === true,
- },
- },
- // 澶氳鏍� - 鎵归噺璁剧疆
- {
- fieldName: 'batchSkuList',
- label: '鎵归噺璁剧疆',
- component: 'Input',
- dependencies: {
- triggerFields: ['specType'],
- // 褰� specType 涓� true锛堝瑙勬牸锛変笖 propertyList 鏈夋暟鎹椂鏄剧ず锛屼笖闈炶鎯呮ā寮�
- show: (values) =>
- values.specType === true && propertyList.length > 0 && !isDetail,
- },
- },
- // 澶氳鏍� - 瑙勬牸鍒楄〃
- {
- fieldName: 'multiSkuList',
- label: '瑙勬牸鍒楄〃',
- component: 'Input',
- dependencies: {
- triggerFields: ['specType'],
- // 褰� specType 涓� true锛堝瑙勬牸锛変笖 propertyList 鏈夋暟鎹椂鏄剧ず
- show: (values) => values.specType === true && propertyList.length > 0,
- },
- },
- ];
-}
-
-/** 鐗╂祦璁剧疆鐨勮〃鍗� */
-export function useDeliveryFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'deliveryTypes',
- label: '閰嶉�佹柟寮�',
- component: 'CheckboxGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_DELIVERY_TYPE, 'number'),
- },
- rules: 'required',
- },
- {
- fieldName: 'deliveryTemplateId',
- label: '杩愯垂妯℃澘',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleTemplateList,
- labelField: 'name',
- valueField: 'id',
- },
- dependencies: {
- triggerFields: ['deliveryTypes'],
- show: (values) =>
- !!values.deliveryTypes &&
- values.deliveryTypes.includes(DeliveryTypeEnum.EXPRESS.type),
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍟嗗搧璇︽儏鐨勮〃鍗� */
-export function useDescriptionFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'description',
- label: '鍟嗗搧璇︽儏',
- component: 'RichTextarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佽鎯�',
- height: 1000,
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍏跺畠璁剧疆鐨勮〃鍗� */
-export function useOtherFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'sort',
- label: '鍟嗗搧鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- },
- rules: 'required',
- },
- {
- fieldName: 'giveIntegral',
- label: '璧犻�佺Н鍒�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- },
- rules: 'required',
- },
- {
- fieldName: 'virtualSalesCount',
- label: '铏氭嫙閿�閲�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- },
- rules: 'required',
- },
- ];
-}
diff --git a/src/views/mall/product/spu/form/index.vue b/src/views/mall/product/spu/form/index.vue
deleted file mode 100644
index d478aea..0000000
--- a/src/views/mall/product/spu/form/index.vue
+++ /dev/null
@@ -1,420 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type {
- PropertyAndValues,
- RuleConfig,
-} from '#/views/mall/product/spu/components';
-
-import { onMounted, ref, watch } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { useTabs } from '../../../../../packages/effects/hooks/src';
-import { convertToInteger, formatToFraction } from '../../../../../packages/utils/src';
-
-import { Button, Card, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createSpu, getSpu, updateSpu } from '#/api/mall/product/spu';
-import { getPropertyList, SkuList } from '#/views/mall/product/spu/components';
-
-import {
- useDeliveryFormSchema,
- useDescriptionFormSchema,
- useInfoFormSchema,
- useOtherFormSchema,
- useSkuFormSchema,
-} from './data';
-import ProductAttributes from './modules/product-attributes.vue';
-import ProductPropertyAddForm from './modules/product-property-add-form.vue';
-
-const spuId = ref<number>();
-const { params, name } = useRoute();
-const { closeCurrentTab } = useTabs();
-const activeTabName = ref('info');
-const formLoading = ref(false); // 琛ㄥ崟鐨勫姞杞戒腑锛�1锛変慨鏀规椂鐨勬暟鎹姞杞斤紱2锛夋彁浜ょ殑鎸夐挳绂佺敤
-const isDetail = ref(name === 'ProductSpuDetail'); // 鏄惁鏌ョ湅璇︽儏
-const skuListRef = ref(); // 鍟嗗搧灞炴�у垪琛� Ref
-
-const formData = ref<MallSpuApi.Spu>({
- name: '',
- categoryId: undefined,
- keyword: '',
- picUrl: '',
- sliderPicUrls: [],
- introduction: '',
- deliveryTypes: [],
- deliveryTemplateId: undefined,
- brandId: undefined,
- specType: false,
- subCommissionType: false,
- skus: [
- {
- name: '', // SKU 鍚嶇О锛屾彁浜ゆ椂浼氳嚜鍔ㄤ娇鐢� SPU 鍚嶇О
- price: 0,
- marketPrice: 0,
- costPrice: 0,
- barCode: '',
- picUrl: '',
- stock: 0,
- weight: 0,
- volume: 0,
- firstBrokeragePrice: 0,
- secondBrokeragePrice: 0,
- },
- ],
- description: '',
- sort: 0,
- giveIntegral: 0,
- virtualSalesCount: 0,
-}); // spu 琛ㄥ崟鏁版嵁
-const propertyList = ref<PropertyAndValues[]>([]); // 鍟嗗搧灞炴�у垪琛�
-const ruleConfig: RuleConfig[] = [
- {
- name: 'stock',
- rule: (arg: number) => arg >= 0,
- message: '鍟嗗搧搴撳瓨蹇呴』澶т簬绛変簬 1 锛侊紒锛�',
- },
- {
- name: 'price',
- rule: (arg: number) => arg >= 0.01,
- message: '鍟嗗搧閿�鍞环鏍煎繀椤诲ぇ浜庣瓑浜� 0.01 鍏冿紒锛侊紒',
- },
- {
- name: 'marketPrice',
- rule: (arg: number) => arg >= 0.01,
- message: '鍟嗗搧甯傚満浠锋牸蹇呴』澶т簬绛変簬 0.01 鍏冿紒锛侊紒',
- },
- {
- name: 'costPrice',
- rule: (arg: number) => arg >= 0.01,
- message: '鍟嗗搧鎴愭湰浠锋牸蹇呴』澶т簬绛変簬 0.00 鍏冿紒锛侊紒',
- },
-]; // sku 鐩稿叧灞炴�ф牎楠岃鍒�
-
-const [InfoForm, infoFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useInfoFormSchema(),
- showDefaultActions: false,
-});
-
-const [SkuForm, skuFormApi] = useVbenForm({
- commonConfig: {
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useSkuFormSchema(propertyList.value, isDetail.value),
- showDefaultActions: false,
- handleValuesChange: (values, fieldsChanged) => {
- if (fieldsChanged.includes('subCommissionType')) {
- formData.value.subCommissionType = values.subCommissionType;
- handleChangeSubCommissionType();
- }
- if (fieldsChanged.includes('specType')) {
- formData.value.specType = values.specType;
- handleChangeSpec();
- }
- },
-});
-
-const [ProductPropertyAddFormModal, productPropertyAddFormApi] = useVbenModal({
- connectedComponent: ProductPropertyAddForm,
- destroyOnClose: true,
-});
-
-const [DeliveryForm, deliveryFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useDeliveryFormSchema(),
- showDefaultActions: false,
-});
-
-const [DescriptionForm, descriptionFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'vertical',
- schema: useDescriptionFormSchema(),
- showDefaultActions: false,
-});
-
-const [OtherForm, otherFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useOtherFormSchema(),
- showDefaultActions: false,
-});
-
-/** tab 鍒囨崲 */
-function handleTabChange(key: string) {
- activeTabName.value = key;
-}
-
-/** 鎻愪氦琛ㄥ崟 */
-async function handleSubmit() {
- const values: MallSpuApi.Spu = await infoFormApi
- .merge(skuFormApi)
- .merge(deliveryFormApi)
- .merge(descriptionFormApi)
- .merge(otherFormApi)
- .submitAllForm(true);
- // 鏍¢獙鍟嗗搧鍚嶇О涓嶈兘涓虹┖锛堢敤浜� SKU name锛�
- if (!values.name || values.name.trim() === '') {
- message.error('鍟嗗搧鍚嶇О涓嶈兘涓虹┖');
- return;
- }
- try {
- // 鏍¢獙 sku
- skuListRef.value.validateSku();
- } catch {
- message.error('銆愬簱瀛樹环鏍笺�戜笉瀹屽杽锛岃濉啓鐩稿叧淇℃伅');
- return;
- }
- // 閲戦杞崲锛氬厓杞垎
- values.skus = formData.value.skus!.map((item) => ({
- ...item,
- name: values.name,
- price: convertToInteger(item.price),
- marketPrice: convertToInteger(item.marketPrice),
- costPrice: convertToInteger(item.costPrice),
- firstBrokeragePrice: convertToInteger(item.firstBrokeragePrice),
- secondBrokeragePrice: convertToInteger(item.secondBrokeragePrice),
- }));
- // 澶勭悊杞挱鍥惧垪琛細涓婁紶缁勪欢鍙兘杩斿洖瀵硅薄鎴栧瓧绗︿覆锛岀粺涓�澶勭悊鎴愬瓧绗︿覆鏁扮粍
- const newSliderPicUrls: any[] = [];
- values.sliderPicUrls!.forEach((item: any) => {
- // 濡傛灉鏄墠绔�夌殑鍥�
- typeof item === 'object'
- ? newSliderPicUrls.push(item.url)
- : newSliderPicUrls.push(item);
- });
- values.sliderPicUrls = newSliderPicUrls;
-
- // 鎻愪氦鏁版嵁
- await (spuId.value ? updateSpu(values) : createSpu(values));
-}
-
-/** 鑾峰緱璇︽儏 */
-async function getDetail() {
- if (isDetail.value) {
- isDetail.value = true;
- infoFormApi.setDisabled(true);
- skuFormApi.setDisabled(true);
- deliveryFormApi.setDisabled(true);
- descriptionFormApi.setDisabled(true);
- otherFormApi.setDisabled(true);
- }
- // 灏� SKU 鐨勫睘鎬э紝鏁寸悊鎴� PropertyAndValues 鏁扮粍
- propertyList.value = getPropertyList(formData.value);
- formLoading.value = true;
- try {
- const res = await getSpu(spuId.value!);
- // 閲戦杞崲锛氬垎杞厓
- res.skus = res.skus?.map((item) => ({
- ...item,
- price: formatToFraction(item.price),
- marketPrice: formatToFraction(item.marketPrice),
- costPrice: formatToFraction(item.costPrice),
- firstBrokeragePrice: formatToFraction(item.firstBrokeragePrice),
- secondBrokeragePrice: formatToFraction(item.secondBrokeragePrice),
- }));
- formData.value = res;
- // 鍒濆鍖栧悇琛ㄥ崟鍊�
- infoFormApi.setValues(res).then();
- skuFormApi.setValues(res).then();
- deliveryFormApi.setValues(res).then();
- descriptionFormApi.setValues(res).then();
- otherFormApi.setValues(res).then();
- // 灏� SKU 鐨勫睘鎬э紝鏁寸悊鎴� PropertyAndValues 鏁扮粍
- propertyList.value = getPropertyList(formData.value);
- } finally {
- formLoading.value = false;
- }
-}
-
-// =========== sku form 閫昏緫 ===========
-
-/** 鎵撳紑灞炴�ф坊鍔犺〃鍗� */
-function openPropertyAddForm() {
- productPropertyAddFormApi.open();
-}
-
-/** 璋冪敤 SkuList generateTableData 鏂规硶*/
-function generateSkus(propertyList: PropertyAndValues[]) {
- skuListRef.value.generateTableData(propertyList);
-}
-
-/** 鍒嗛攢绫诲瀷 */
-function handleChangeSubCommissionType() {
- // 榛樿涓洪浂锛岀被鍨嬪垏鎹㈠悗涔熻閲嶇疆涓洪浂
- for (const item of formData.value.skus!) {
- item.firstBrokeragePrice = 0;
- item.secondBrokeragePrice = 0;
- }
-}
-
-/** 閫夋嫨瑙勬牸 */
-function handleChangeSpec() {
- // 閲嶇疆鍟嗗搧灞炴�у垪琛�
- propertyList.value = [];
- // 閲嶇疆 sku 鍒楄〃
- formData.value.skus = [
- {
- name: '', // SKU 鍚嶇О锛屾彁浜ゆ椂浼氳嚜鍔ㄤ娇鐢� SPU 鍚嶇О
- price: 0,
- marketPrice: 0,
- costPrice: 0,
- barCode: '',
- picUrl: '',
- stock: 0,
- weight: 0,
- volume: 0,
- firstBrokeragePrice: 0,
- secondBrokeragePrice: 0,
- },
- ];
-}
-
-/** 鐩戝惉 sku form schema 鍙樺寲锛屾洿鏂拌〃鍗� */
-watch(
- propertyList,
- () => {
- skuFormApi.updateSchema(
- useSkuFormSchema(propertyList.value, isDetail.value),
- );
- },
- { deep: true },
-);
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- spuId.value = params.id as unknown as number;
- if (!spuId.value) {
- return;
- }
- await getDetail();
-});
-</script>
-
-<template>
- <div>
- <ProductPropertyAddFormModal :property-list="propertyList" />
-
- <Page auto-content-height>
- <Card
- class="h-full w-full"
- :loading="formLoading"
- :tab-list="[
- {
- key: 'info',
- tab: '鍩虹璁剧疆',
- },
- {
- key: 'sku',
- tab: '浠锋牸搴撳瓨',
- },
- {
- key: 'delivery',
- tab: '鐗╂祦璁剧疆',
- },
- {
- key: 'description',
- tab: '鍟嗗搧璇︽儏',
- },
- {
- key: 'other',
- tab: '鍏跺畠璁剧疆',
- },
- ]"
- :active-key="activeTabName"
- @tab-change="handleTabChange"
- >
- <template #tabBarExtraContent>
- <Button type="primary" v-if="!isDetail" @click="handleSubmit">
- 淇濆瓨
- </Button>
- <Button type="default" v-else @click="() => closeCurrentTab()">
- 杩斿洖鍒楄〃
- </Button>
- </template>
-
- <InfoForm class="w-3/5" v-show="activeTabName === 'info'" />
- <SkuForm class="w-full" v-show="activeTabName === 'sku'">
- <template #singleSkuList>
- <SkuList
- ref="skuListRef"
- class="w-full"
- :is-detail="isDetail"
- :prop-form-data="formData"
- :property-list="propertyList"
- :rule-config="ruleConfig"
- />
- </template>
- <template #productAttributes>
- <div>
- <Button class="mb-10px mr-15px" @click="openPropertyAddForm">
- 娣诲姞灞炴��
- </Button>
- <ProductAttributes
- :is-detail="isDetail"
- :property-list="propertyList"
- @success="generateSkus"
- />
- </div>
- </template>
- <template #batchSkuList>
- <SkuList
- :is-batch="true"
- :is-detail="isDetail"
- :prop-form-data="formData"
- :property-list="propertyList"
- />
- </template>
- <template #multiSkuList>
- <SkuList
- ref="skuListRef"
- :is-detail="isDetail"
- :prop-form-data="formData"
- :property-list="propertyList"
- :rule-config="ruleConfig"
- />
- </template>
- </SkuForm>
- <DeliveryForm class="w-3/5" v-show="activeTabName === 'delivery'" />
- <DescriptionForm
- class="w-3/5"
- v-show="activeTabName === 'description'"
- />
- <OtherForm class="w-3/5" v-show="activeTabName === 'other'" />
- </Card>
- </Page>
- </div>
-</template>
-<style lang="scss" scoped>
-:deep(.ant-tabs-tab-btn) {
- font-size: 14px !important;
-}
-</style>
diff --git a/src/views/mall/product/spu/form/modules/product-attributes.vue b/src/views/mall/product/spu/form/modules/product-attributes.vue
deleted file mode 100644
index 78a36da..0000000
--- a/src/views/mall/product/spu/form/modules/product-attributes.vue
+++ /dev/null
@@ -1,228 +0,0 @@
-<!-- 鍟嗗搧鍙戝竷 - 搴撳瓨浠锋牸 - 灞炴�у垪琛� -->
-<script lang="ts" setup>
-import type { MallPropertyApi } from '#/api/mall/product/property';
-import type { PropertyAndValues } from '#/views/mall/product/spu/components';
-
-import { computed, ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { Col, Divider, message, Select, Tag } from 'ant-design-vue';
-
-import {
- createPropertyValue,
- getPropertyValueSimpleList,
-} from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-defineOptions({ name: 'ProductAttributes' });
-
-const props = withDefaults(defineProps<Props>(), {
- propertyList: () => [],
- isDetail: false,
-});
-
-const emit = defineEmits(['success']);
-
-interface Props {
- propertyList?: PropertyAndValues[];
- isDetail?: boolean;
-}
-
-const inputValue = ref<string[]>([]); // 杈撳叆妗嗗�硷紙tags 妯″紡浣跨敤鏁扮粍锛�
-const attributeIndex = ref<null | number>(null); // 鑾峰彇鐒︾偣鏃惰褰曞綋鍓嶅睘鎬ч」鐨� index
-const inputVisible = computed(() => (index: number) => {
- if (attributeIndex.value === null) {
- return false;
- }
- if (attributeIndex.value === index) {
- return true;
- }
-}); // 杈撳叆妗嗘樉闅愭帶鍒�
-
-interface InputRefItem {
- inputRef?: {
- attributes: {
- id: string;
- };
- };
- focus: () => void;
-}
-
-const inputRef = ref<InputRefItem[]>([]); // 鏍囩杈撳叆妗� Ref
-const attributeList = ref<PropertyAndValues[]>([]); // 鍟嗗搧灞炴�у垪琛�
-const attributeOptions = ref<MallPropertyApi.PropertyValue[]>([]); // 鍟嗗搧灞炴�у�间笅鎷夋
-
-/** 瑙e喅 ref 鍦� v-for 涓殑鑾峰彇闂*/
-function setInputRef(el: any) {
- if (el === null || el === undefined) return;
- // 濡傛灉涓嶅瓨鍦� id 鐩稿悓鐨勫厓绱犳墠娣诲姞
- if (
- !inputRef.value.some(
- (item) => item.inputRef?.attributes.id === el.inputRef?.attributes.id,
- )
- ) {
- inputRef.value.push(el);
- }
-}
-
-watch(
- () => props.propertyList,
- (data) => {
- if (!data) {
- return;
- }
- attributeList.value = data;
- },
- {
- deep: true,
- immediate: true,
- },
-);
-
-/** 鍒犻櫎灞炴�у�� */
-function handleCloseValue(index: number, value: PropertyAndValues) {
- if (attributeList.value[index]?.values) {
- attributeList.value[index].values = attributeList.value[
- index
- ].values?.filter((item) => item.id !== value.id);
- }
-}
-
-/** 鍒犻櫎灞炴�� */
-function handleCloseProperty(item: PropertyAndValues) {
- attributeList.value = attributeList.value.filter(
- (attribute) => attribute.id !== item.id,
- );
- emit('success', attributeList.value);
-}
-
-/** 鏄剧ず杈撳叆妗嗗苟鑾峰彇鐒︾偣 */
-async function showInput(index: number) {
- attributeIndex.value = index;
- inputRef.value?.[index]?.focus();
- // 鑾峰彇灞炴�т笅鎷夐�夐」
- // oxlint-disable-next-line typescript/no-non-null-asserted-optional-chain
- await getAttributeOptions(attributeList.value?.[index]?.id!);
-}
-
-/** 瀹氫箟 success 浜嬩欢锛岀敤浜庢搷浣滄垚鍔熷悗鐨勫洖璋� */
-async function handleInputConfirm(index: number, propertyId: number) {
- // 浠庢暟缁勪腑鍙栨渶鍚庝竴涓緭鍏ョ殑鍊硷紙tags 妯″紡涓� inputValue 鏄暟缁勶級
- const currentValue = inputValue.value?.[inputValue.value.length - 1]?.trim();
-
- if (currentValue) {
- // 1. 閲嶅娣诲姞鏍¢獙
- if (
- attributeList.value?.[index]?.values?.find(
- (item) => item.name === currentValue,
- )
- ) {
- message.warning('宸插瓨鍦ㄧ浉鍚屽睘鎬у�硷紝璇烽噸璇�');
- attributeIndex.value = null;
- inputValue.value = [];
- return;
- }
-
- // 2.1 鎯呭喌涓�锛氬睘鎬у�煎凡瀛樺湪锛屽垯鐩存帴浣跨敤骞剁粨鏉�
- const existValue = attributeOptions.value.find(
- (item) => item.name === currentValue,
- );
- if (existValue) {
- attributeIndex.value = null;
- inputValue.value = [];
- attributeList.value?.[index]?.values?.push({
- id: existValue.id!,
- name: existValue.name,
- });
- emit('success', attributeList.value);
- return;
- }
-
- // 2.2 鎯呭喌浜岋細鏂板睘鎬у�硷紝鍒欒繘琛屼繚瀛�
- try {
- const id = await createPropertyValue({
- propertyId,
- name: currentValue,
- });
- attributeList.value?.[index]?.values?.push({
- id,
- name: currentValue,
- });
- message.success($t('ui.actionMessage.operationSuccess'));
- emit('success', attributeList.value);
- } catch {
- message.error($t('ui.actionMessage.operationFailed'));
- }
- }
- attributeIndex.value = null;
- inputValue.value = [];
-}
-
-/** 鑾峰彇鍟嗗搧灞炴�т笅鎷夐�夐」 */
-async function getAttributeOptions(propertyId: number) {
- attributeOptions.value = await getPropertyValueSimpleList(propertyId);
-}
-</script>
-
-<template>
- <Col v-for="(attribute, index) in attributeList" :key="index">
- <Divider class="my-3" />
- <div class="mt-2 flex flex-wrap items-center gap-2">
- <span class="mx-1">灞炴�у悕锛�</span>
- <Tag
- :closable="!isDetail"
- class="mx-1"
- color="success"
- @close="handleCloseProperty(attribute)"
- >
- {{ attribute.name }}
- </Tag>
- </div>
- <div class="mt-2 flex flex-wrap items-center gap-2">
- <span class="mx-1">灞炴�у�硷細</span>
- <Tag
- v-for="(value, valueIndex) in attribute.values"
- :key="valueIndex"
- :closable="!isDetail"
- class="mx-1"
- @close="handleCloseValue(index, value)"
- >
- {{ value?.name }}
- </Tag>
- <Select
- v-show="inputVisible(index)"
- :id="`input${index}`"
- :ref="setInputRef"
- v-model:value="inputValue"
- allow-clear
- mode="tags"
- :max-tag-count="1"
- :filter-option="true"
- size="small"
- style="width: 100px"
- @blur="handleInputConfirm(index, attribute.id)"
- @change="handleInputConfirm(index, attribute.id)"
- @keyup.enter="handleInputConfirm(index, attribute.id)"
- >
- <Select.Option
- v-for="item2 in attributeOptions"
- :key="item2.id"
- :value="item2.name"
- >
- {{ item2.name }}
- </Select.Option>
- </Select>
- <Tag
- v-show="!inputVisible(index)"
- @click="showInput(index)"
- class="mx-1 border-dashed bg-gray-100"
- >
- <div class="flex items-center">
- <IconifyIcon class="mr-2" icon="lucide:plus" />
- 娣诲姞
- </div>
- </Tag>
- </div>
- </Col>
-</template>
diff --git a/src/views/mall/product/spu/form/modules/product-property-add-form.vue b/src/views/mall/product/spu/form/modules/product-property-add-form.vue
deleted file mode 100644
index 8e4076c..0000000
--- a/src/views/mall/product/spu/form/modules/product-property-add-form.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-<!-- 鍟嗗搧鍙戝竷 - 搴撳瓨浠锋牸 - 娣诲姞灞炴�� -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { MallPropertyApi } from '#/api/mall/product/property';
-
-import { ref, watch } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createProperty,
- getPropertySimpleList,
-} from '#/api/mall/product/property';
-import { $t } from '#/locales';
-
-defineOptions({ name: 'ProductPropertyAddForm' });
-
-const props = defineProps({
- propertyList: {
- type: Array,
- default: () => [],
- },
-});
-
-const emit = defineEmits<{
- success: [];
-}>();
-
-const attributeList = ref<any[]>([]); // 鍟嗗搧灞炴�у垪琛�
-const attributeOptions = ref<MallPropertyApi.Property[]>([]); // 鍟嗗搧灞炴�у悕绉颁笅鎷夋
-
-watch(
- () => props.propertyList,
- (data) => {
- if (!data) {
- return;
- }
- attributeList.value = data as any[];
- },
- {
- deep: true,
- immediate: true,
- },
-);
-
-const formSchema: VbenFormSchema[] = [
- {
- fieldName: 'name',
- label: '灞炴�у悕绉�',
- component: 'ApiSelect',
- componentProps: {
- api: async () => {
- const data = await getPropertySimpleList();
- attributeOptions.value = data;
- return data.map((item: MallPropertyApi.Property) => ({
- label: item.name,
- value: item.name,
- }));
- },
- showSearch: true,
- filterOption: true,
- placeholder: '璇烽�夋嫨灞炴�у悕绉般�傚鏋滀笉瀛樺湪锛屽彲鎵嬪姩杈撳叆閫夋嫨',
- mode: 'tags',
- allowClear: true,
- },
- rules: 'required',
- },
-];
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: formSchema,
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- destroyOnClose: true,
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- const values = await formApi.getValues();
- // name 涓烘暟缁勶紝閬嶅巻鏁扮粍锛岃繘琛岄噸澶嶆坊鍔犳牎楠�
- const names = values.name;
- for (const name of names) {
- // 閲嶅娣诲姞鏍¢獙
- for (const attrItem of attributeList.value) {
- if (attrItem.name === name) {
- message.error('璇ュ睘鎬у凡瀛樺湪锛岃鍕块噸澶嶆坊鍔�');
- return;
- }
- }
- }
-
- for (const name of names) {
- const existProperty = attributeOptions.value.find(
- (item: MallPropertyApi.Property) => item.name === name,
- );
- if (existProperty) {
- // 鎯呭喌涓�锛氬鏋滃睘鎬у凡瀛樺湪锛屽垯鐩存帴浣跨敤骞剁粨鏉�
- attributeList.value.push({
- id: existProperty.id,
- name,
- values: [],
- });
- } else {
- // 鎯呭喌浜岋細濡傛灉鏄笉瀛樺湪鐨勫睘鎬э紝鍒欓渶瑕佹墽琛屾柊澧�
- const propertyId = await createProperty({ name });
- attributeList.value.push({
- id: propertyId,
- name,
- values: [],
- });
- }
- }
- message.success($t('ui.actionMessage.operationSuccess'));
- modalApi.unlock();
- await modalApi.close();
- emit('success');
- },
-});
-</script>
-
-<template>
- <Modal title="娣诲姞鍟嗗搧灞炴��">
- <Form />
- </Modal>
-</template>
diff --git a/src/views/mall/product/spu/index.vue b/src/views/mall/product/spu/index.vue
deleted file mode 100644
index 851c6e4..0000000
--- a/src/views/mall/product/spu/index.vue
+++ /dev/null
@@ -1,294 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { confirm, DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-import { ProductSpuStatusEnum } from '../../../../packages/constants/src';
-import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
-
-import { message, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteSpu,
- exportSpu,
- getSpuPage,
- getTabsCount,
- updateStatus,
-} from '#/api/mall/product/spu';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-const { push } = useRouter();
-const route = useRoute();
-const tabType = ref(0);
-const tabsData = ref([
- {
- name: '鍑哄敭涓�',
- type: 0,
- count: 0,
- },
- {
- name: '浠撳簱涓�',
- type: 1,
- count: 0,
- },
- {
- name: '宸插敭缃�',
- type: 2,
- count: 0,
- },
- {
- name: '璀︽垝搴撳瓨',
- type: 3,
- count: 0,
- },
- {
- name: '鍥炴敹绔�',
- type: 4,
- count: 0,
- },
-]);
-
-/** 鍒锋柊琛ㄦ牸 */
-async function handleRefresh() {
- await gridApi.query();
- await getTabCount();
-}
-
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
- const data = await exportSpu(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '鍟嗗搧.xls', source: data });
-}
-
-/** 鑾峰緱姣忎釜 Tab 鐨勬暟閲� */
-async function getTabCount() {
- const res = await getTabsCount();
- for (const objName in res) {
- const index = Number(objName);
- if (tabsData.value[index]) {
- tabsData.value[index].count = res[objName]!;
- }
- }
-}
-
-/** 鍒涘缓鍟嗗搧 */
-function handleCreate() {
- push({ name: 'ProductSpuAdd' });
-}
-
-/** 缂栬緫鍟嗗搧 */
-function handleEdit(row: MallSpuApi.Spu) {
- push({ name: 'ProductSpuEdit', params: { id: row.id } });
-}
-
-/** 鍒犻櫎鍟嗗搧 */
-async function handleDelete(row: MallSpuApi.Spu) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteSpu(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- await handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏇存柊鐘舵�� */
-async function handleStatusChange(
- newStatus: number,
- row: MallSpuApi.Spu,
-): Promise<boolean | undefined> {
- // 浜屾纭
- const text = newStatus ? '涓婃灦' : '涓嬫灦';
- try {
- await confirm(`纭瑕�${text + row.name}鍚�?`);
- } catch {
- return false;
- }
- // 鏇存柊鐘舵��
- await updateStatus({
- id: row.id!,
- status: newStatus,
- });
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success(`${text}鎴愬姛`);
- return true;
-}
-
-/** 娣诲姞鍒颁粨搴� / 鍥炴敹绔欑殑鐘舵�� */
-async function handleStatus02Change(row: MallSpuApi.Spu, newStatus: number) {
- const text =
- newStatus === ProductSpuStatusEnum.RECYCLE.status
- ? '鍔犲叆鍒板洖鏀剁珯'
- : '鎭㈠鍒颁粨搴�';
- await confirm(`纭瑕�"${row.name}"${text}鍚楋紵`);
- const hideLoading = message.loading({
- content: `姝e湪${text}涓�...`,
- duration: 0,
- });
- try {
- await updateStatus({ id: row.id!, status: newStatus });
- message.success(`${text}鎴愬姛`);
- await handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏌ョ湅鍟嗗搧璇︽儏 */
-function handleDetail(row: MallSpuApi.Spu) {
- push({ name: 'ProductSpuDetail', params: { id: row.id } });
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleStatusChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getSpuPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- tabType: tabType.value,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallSpuApi.Spu>,
-});
-
-function onChangeTab(key: any) {
- tabType.value = Number(key);
- gridApi.query();
-}
-
-onMounted(async () => {
- // 瑙f瀽璺敱鐨� categoryId
- if (route.query.categoryId) {
- await gridApi.formApi.setValues({
- categoryId: Number(route.query.categoryId),
- });
- }
- // 鑾峰緱姣忎釜 Tab 鐨勬暟閲�
- await getTabCount();
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬晢鍝併�戝晢鍝� SPU 涓� SKU"
- url="https://doc.iocoder.cn/mall/product-spu-sku/"
- />
- </template>
-
- <Grid>
- <template #toolbar-actions>
- <Tabs @change="onChangeTab" class="w-full">
- <Tabs.TabPane
- v-for="item in tabsData"
- :key="item.type"
- :tab="`${item.name} (${item.count})`"
- />
- </Tabs>
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍟嗗搧']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['product:spu:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.export'),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['product:spu:export'],
- onClick: handleExport,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:spu:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleDetail.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['product:spu:delete'],
- ifShow: () => tabType === 4,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- {
- label: '鎭㈠',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:spu:update'],
- onClick: handleStatus02Change.bind(
- null,
- row,
- ProductSpuStatusEnum.DISABLE.status,
- ),
- },
- {
- label: '鍥炴敹',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['product:spu:update'],
- onClick: handleStatus02Change.bind(
- null,
- row,
- ProductSpuStatusEnum.RECYCLE.status,
- ),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/article/category/data.ts b/src/views/mall/promotion/article/category/data.ts
deleted file mode 100644
index 53fc72a..0000000
--- a/src/views/mall/promotion/article/category/data.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'picUrl',
- label: '鍒嗙被鍥剧墖',
- component: 'ImageUpload',
- componentProps: {
- placeholder: '璇蜂笂浼犲垎绫诲浘鐗�',
- },
- },
- {
- fieldName: 'sort',
- label: '鏄剧ず椤哄簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ユ樉绀洪『搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍒嗙被鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎绫诲悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鍒嗙被鍚嶇О',
- minWidth: 240,
- align: 'left',
- fixed: 'left',
- },
- {
- field: 'picUrl',
- title: '鍒嗙被鍥剧墖',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 150,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'sort',
- title: '鏄剧ず椤哄簭',
- minWidth: 150,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- minWidth: 220,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/article/category/index.vue b/src/views/mall/promotion/article/category/index.vue
deleted file mode 100644
index 97b6510..0000000
--- a/src/views/mall/promotion/article/category/index.vue
+++ /dev/null
@@ -1,135 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
-
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteArticleCategory,
- getArticleCategoryPage,
-} from '#/api/mall/promotion/article/category';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍒嗙被 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍒嗙被 */
-function handleEdit(row: MallArticleCategoryApi.ArticleCategory) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍒嗙被 */
-async function handleDelete(row: MallArticleCategoryApi.ArticleCategory) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteArticleCategory(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getArticleCategoryPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallArticleCategoryApi.ArticleCategory>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戝唴瀹圭鐞�"
- url="https://doc.iocoder.cn/mall/promotion-content/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="鏂囩珷鍒嗙被鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鏂囩珷鍒嗙被']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:article-category:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:article-category:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:article-category:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/article/category/modules/form.vue b/src/views/mall/promotion/article/category/modules/form.vue
deleted file mode 100644
index 464afb2..0000000
--- a/src/views/mall/promotion/article/category/modules/form.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<script lang="ts" setup>
-import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createArticleCategory,
- getArticleCategory,
- updateArticleCategory,
-} from '#/api/mall/promotion/article/category';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallArticleCategoryApi.ArticleCategory>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鏂囩珷鍒嗙被'])
- : $t('ui.actionTitle.create', ['鏂囩珷鍒嗙被']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallArticleCategoryApi.ArticleCategory;
- try {
- await (formData.value?.id
- ? updateArticleCategory(data)
- : createArticleCategory(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallArticleCategoryApi.ArticleCategory>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getArticleCategory(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/article/data.ts b/src/views/mall/promotion/article/data.ts
deleted file mode 100644
index 6d86a80..0000000
--- a/src/views/mall/promotion/article/data.ts
+++ /dev/null
@@ -1,250 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getSimpleArticleCategoryList } from '#/api/mall/promotion/article/category';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-let categoryList: MallArticleCategoryApi.ArticleCategory[] = [];
-getSimpleArticleCategoryList().then((data) => (categoryList = data));
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'title',
- label: '鏂囩珷鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ枃绔犳爣棰�',
- },
- rules: 'required',
- },
- {
- fieldName: 'categoryId',
- label: '鏂囩珷鍒嗙被',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleArticleCategoryList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鏂囩珷鍒嗙被',
- },
- rules: 'required',
- },
- {
- fieldName: 'author',
- label: '鏂囩珷浣滆��',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ枃绔犱綔鑰�',
- },
- },
- {
- fieldName: 'introduction',
- label: '鏂囩珷绠�浠�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ枃绔犵畝浠�',
- },
- },
- {
- fieldName: 'picUrl',
- label: '鏂囩珷灏侀潰',
- component: 'ImageUpload',
- formItemClass: 'col-span-2',
- componentProps: {
- placeholder: '璇蜂笂浼犳枃绔犲皝闈�',
- },
- rules: 'required',
- },
- {
- fieldName: 'recommendHot',
- label: '鏄惁鐑棬',
- component: 'RadioGroup',
- componentProps: {
- buttonStyle: 'solid',
- optionType: 'button',
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- },
- rules: z.boolean().default(false),
- },
- {
- fieldName: 'recommendBanner',
- label: '鏄惁杞挱鍥�',
- component: 'RadioGroup',
- componentProps: {
- buttonStyle: 'solid',
- optionType: 'button',
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- },
- rules: z.boolean().default(false),
- },
- {
- fieldName: 'spuId',
- label: '鍟嗗搧鍏宠仈',
- component: 'Input',
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ユ帓搴�',
- },
- rules: z.number().default(0),
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'content',
- label: '鏂囩珷鍐呭',
- component: 'RichTextarea',
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'categoryId',
- label: '鏂囩珷鍒嗙被',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleArticleCategoryList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鏂囩珷鍒嗙被',
- allowClear: true,
- },
- },
- {
- fieldName: 'title',
- label: '鏂囩珷鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ枃绔犳爣棰�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 200,
- },
- {
- field: 'picUrl',
- title: '灏侀潰',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'categoryId',
- title: '鍒嗙被',
- minWidth: 100,
- formatter: ({ cellValue }) =>
- categoryList.find((item) => item.id === cellValue)?.name || '-',
- },
- {
- field: 'browseCount',
- title: '娴忚閲�',
- minWidth: 100,
- },
- {
- field: 'author',
- title: '浣滆��',
- minWidth: 120,
- },
- {
- field: 'introduction',
- title: '鏂囩珷绠�浠�',
- minWidth: 250,
- },
- {
- field: 'sort',
- title: '鎺掑簭',
- minWidth: 80,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/article/index.vue b/src/views/mall/promotion/article/index.vue
deleted file mode 100644
index 3b6e81a..0000000
--- a/src/views/mall/promotion/article/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallArticleApi } from '#/api/mall/promotion/article';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteArticle, getArticlePage } from '#/api/mall/promotion/article';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鏂囩珷 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鏂囩珷 */
-function handleEdit(row: MallArticleApi.Article) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鏂囩珷 */
-async function handleDelete(row: MallArticleApi.Article) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.title]),
- duration: 0,
- });
- try {
- await deleteArticle(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getArticlePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallArticleApi.Article>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戝唴瀹圭鐞�"
- url="https://doc.iocoder.cn/mall/promotion-content/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="鏂囩珷鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鏂囩珷']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:article:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:article:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:article:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.title]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/article/modules/form.vue b/src/views/mall/promotion/article/modules/form.vue
deleted file mode 100644
index bfaa363..0000000
--- a/src/views/mall/promotion/article/modules/form.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<script lang="ts" setup>
-import type { MallArticleApi } from '#/api/mall/promotion/article';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createArticle,
- getArticle,
- updateArticle,
-} from '#/api/mall/promotion/article';
-import { $t } from '#/locales';
-import { SpuShowcase } from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<Partial<MallArticleApi.Article>>({});
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鏂囩珷'])
- : $t('ui.actionTitle.create', ['鏂囩珷']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 120,
- },
- wrapperClass: 'grid-cols-2',
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- // 鍚屾鍟嗗搧閫夋嫨鍒拌〃鍗曪紝纭繚楠岃瘉鏃惰兘鑾峰彇鍒板��
- if (formData.value.spuId) {
- await formApi.setFieldValue('spuId', formData.value.spuId);
- }
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallArticleApi.Article;
- try {
- await (formData.value?.id ? updateArticle(data) : createArticle(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = {};
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallArticleApi.Article>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getArticle(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5" :close-on-click-modal="false">
- <Form class="mx-4">
- <!-- 鑷畾涔夋彃妲斤細鍟嗗搧閫夋嫨 -->
- <template #spuId>
- <SpuShowcase v-model="formData.spuId" :limit="1" />
- </template>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/banner/data.ts b/src/views/mall/promotion/banner/data.ts
deleted file mode 100644
index d0e7661..0000000
--- a/src/views/mall/promotion/banner/data.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import type { VxeTableGridOptions } from '../../../../packages/effects/plugins/src/vxe-table';
-
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'title',
- label: 'Banner 鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� Banner 鏍囬',
- },
- rules: 'required',
- },
- {
- fieldName: 'picUrl',
- label: '鍥剧墖鍦板潃',
- component: 'ImageUpload',
- componentProps: {
- placeholder: '璇蜂笂浼犲浘鐗�',
- },
- rules: 'required',
- },
- {
- fieldName: 'position',
- label: '瀹氫綅',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_BANNER_POSITION, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: 'required',
- },
- {
- fieldName: 'url',
- label: '璺宠浆鍦板潃',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ烦杞湴鍧�',
- },
- rules: 'required',
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ユ帓搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'memo',
- label: '鎻忚堪',
- component: 'Textarea',
- componentProps: {
- rows: 4,
- placeholder: '璇疯緭鍏ユ弿杩�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'title',
- label: 'Banner 鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏� Banner 鏍囬',
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- title: 'Banner鏍囬',
- field: 'title',
- minWidth: 100,
- },
- {
- title: '鍥剧墖',
- field: 'picUrl',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- title: '鐘舵��',
- field: 'status',
- minWidth: 150,
- cellRender: {
- name: 'CellDict',
- props: {
- type: DICT_TYPE.COMMON_STATUS,
- },
- },
- },
- {
- title: '瀹氫綅',
- field: 'position',
- minWidth: 150,
- cellRender: {
- name: 'CellDict',
- props: {
- type: DICT_TYPE.PROMOTION_BANNER_POSITION,
- },
- },
- },
- {
- title: '璺宠浆鍦板潃',
- field: 'url',
- minWidth: 200,
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎺掑簭',
- field: 'sort',
- minWidth: 100,
- },
- {
- title: '鎻忚堪',
- field: 'memo',
- minWidth: 150,
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/banner/index.vue b/src/views/mall/promotion/banner/index.vue
deleted file mode 100644
index d3e4b6e..0000000
--- a/src/views/mall/promotion/banner/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBannerApi } from '#/api/mall/promotion/banner';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteBanner, getBannerPage } from '#/api/mall/promotion/banner';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓 Banner */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫 Banner */
-function handleEdit(row: MallBannerApi.Banner) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎 Banner */
-async function handleDelete(row: MallBannerApi.Banner) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.title]),
- duration: 0,
- });
- try {
- await deleteBanner(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBannerPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBannerApi.Banner>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戝唴瀹圭鐞�"
- url="https://doc.iocoder.cn/mall/promotion-content/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="Banner鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['Banner']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:banner:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:banner:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:banner:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.title]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/banner/modules/form.vue b/src/views/mall/promotion/banner/modules/form.vue
deleted file mode 100644
index 3b0ba6d..0000000
--- a/src/views/mall/promotion/banner/modules/form.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<script lang="ts" setup>
-import type { MallBannerApi } from '#/api/mall/promotion/banner';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createBanner,
- getBanner,
- updateBanner,
-} from '#/api/mall/promotion/banner';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallBannerApi.Banner>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['Banner'])
- : $t('ui.actionTitle.create', ['Banner']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallBannerApi.Banner;
- try {
- await (formData.value?.id ? updateBanner(data) : createBanner(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallBannerApi.Banner>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getBanner(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/bargain/activity/data.ts b/src/views/mall/promotion/bargain/activity/data.ts
deleted file mode 100644
index 09a5560..0000000
--- a/src/views/mall/promotion/bargain/activity/data.ts
+++ /dev/null
@@ -1,243 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { formatDate } from '../../../../../packages/utils/src';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- },
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'startTime',
- label: '寮�濮嬫椂闂�',
- component: 'DatePicker',
- componentProps: {
- format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'YYYY-MM-DD HH:mm:ss',
- showTime: true,
- placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
- },
- rules: 'required',
- },
- {
- fieldName: 'endTime',
- label: '缁撴潫鏃堕棿',
- component: 'DatePicker',
- componentProps: {
- format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'YYYY-MM-DD HH:mm:ss',
- showTime: true,
- placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
- },
- rules: 'required',
- },
- {
- fieldName: 'helpMaxCount',
- label: '鍔╁姏浜烘暟',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 1,
- placeholder: '杈惧埌璇ヤ汉鏁版墠鑳界爫鍒颁綆浠�',
- },
- rules: 'required',
- },
- {
- fieldName: 'bargainCount',
- label: '鐮嶄环娆℃暟',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 1,
- placeholder: '鏈�澶у府鐮嶆鏁�',
- },
- rules: 'required',
- },
- {
- fieldName: 'totalLimitCount',
- label: '璐拱闄愬埗',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 1,
- placeholder: '鏈�澶ц喘涔版鏁�',
- },
- rules: 'required',
- },
- {
- fieldName: 'randomMinPrice',
- label: '鏈�灏忕爫浠烽噾棰�(鍏�)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- step: 0.01,
- placeholder: '鐢ㄦ埛姣忔鐮嶄环鐨勬渶灏忛噾棰�',
- },
- },
- {
- fieldName: 'randomMaxPrice',
- label: '鏈�澶х爫浠烽噾棰�(鍏�)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- step: 0.01,
- placeholder: '鐢ㄦ埛姣忔鐮嶄环鐨勬渶澶ч噾棰�',
- },
- },
- {
- fieldName: 'spuId',
- label: '鐮嶄环鍟嗗搧',
- component: 'Input',
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'activityTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- formatter: ({ row }) => {
- if (!row.startTime || !row.endTime) return '';
- return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`;
- },
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- },
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'bargainFirstPrice',
- title: '璧峰浠锋牸',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'bargainMinPrice',
- title: '鐮嶄环搴曚环',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'recordUserCount',
- title: '鎬荤爫浠蜂汉鏁�',
- minWidth: 100,
- },
- {
- field: 'recordSuccessUserCount',
- title: '鎴愬姛鐮嶄环浜烘暟',
- minWidth: 110,
- },
- {
- field: 'helpUserCount',
- title: '鍔╁姏浜烘暟',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- minWidth: 80,
- },
- {
- field: 'totalStock',
- title: '鎬诲簱瀛�',
- minWidth: 80,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/bargain/activity/index.vue b/src/views/mall/promotion/bargain/activity/index.vue
deleted file mode 100644
index 73f099e..0000000
--- a/src/views/mall/promotion/bargain/activity/index.vue
+++ /dev/null
@@ -1,165 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBargainActivityApi } from '#/api/mall/promotion/bargain/bargainActivity';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closeBargainActivity,
- deleteBargainActivity,
- getBargainActivityPage,
-} from '#/api/mall/promotion/bargain/bargainActivity';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'PromotionBargainActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鐮嶄环娲诲姩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鐮嶄环娲诲姩 */
-function handleEdit(row: MallBargainActivityApi.BargainActivity) {
- formModalApi.setData(row).open();
-}
-
-/** 鍏抽棴鐮嶄环娲诲姩 */
-async function handleClose(row: MallBargainActivityApi.BargainActivity) {
- await confirm('纭鍏抽棴璇ョ爫浠锋椿鍔ㄥ悧锛�');
- const hideLoading = message.loading({
- content: '鍏抽棴涓�...',
- duration: 0,
- });
- try {
- await closeBargainActivity(row.id!);
- message.success('鍏抽棴鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎鐮嶄环娲诲姩 */
-async function handleDelete(row: MallBargainActivityApi.BargainActivity) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteBargainActivity(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBargainActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBargainActivityApi.BargainActivity>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戠爫浠锋椿鍔�"
- url="https://doc.iocoder.cn/mall/promotion-bargain/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="鐮嶄环娲诲姩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鐮嶄环娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:bargain-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:bargain-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:bargain-activity:close'],
- ifShow: row.status === 0,
- onClick: handleClose.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:bargain-activity:delete'],
- ifShow: row.status !== 0,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/bargain/activity/modules/form.vue b/src/views/mall/promotion/bargain/activity/modules/form.vue
deleted file mode 100644
index a60c5ec..0000000
--- a/src/views/mall/promotion/bargain/activity/modules/form.vue
+++ /dev/null
@@ -1,327 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallBargainActivityApi } from '#/api/mall/promotion/bargain/bargainActivity';
-import type {
- RuleConfig,
- SpuProperty,
-} from '#/views/mall/product/spu/components';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { cloneDeep, convertToInteger, formatToFraction } from '../../../../../../packages/utils/src';
-
-import { Button, InputNumber, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { VxeColumn } from '#/adapter/vxe-table';
-import { getSpu } from '#/api/mall/product/spu';
-import {
- createBargainActivity,
- getBargainActivity,
- updateBargainActivity,
-} from '#/api/mall/promotion/bargain/bargainActivity';
-import { $t } from '#/locales';
-import {
- getPropertyList,
- SpuAndSkuList,
- SpuSkuSelect,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-defineOptions({ name: 'PromotionBargainActivityForm' });
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallBargainActivityApi.BargainActivity>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鐮嶄环娲诲姩'])
- : $t('ui.actionTitle.create', ['鐮嶄环娲诲姩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 130,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-// ================= 鍟嗗搧閫夋嫨鐩稿叧 =================
-
-const spuSkuSelectRef = ref(); // 鍟嗗搧鍜屽睘鎬ч�夋嫨 Ref
-const spuAndSkuListRef = ref(); // SPU 鍜� SKU 鍒楄〃缁勪欢 Ref
-
-const ruleConfig: RuleConfig[] = [
- {
- name: 'productConfig.bargainFirstPrice',
- rule: (arg) => arg > 0,
- message: '鍟嗗搧鐮嶄环璧峰浠锋牸蹇呴』澶т簬 0 锛侊紒锛�',
- },
- {
- name: 'productConfig.bargainMinPrice',
- rule: (arg) => arg >= 0,
- message: '鍟嗗搧鐮嶄环搴曚环涓嶈兘灏忎簬 0 锛侊紒锛�',
- },
- {
- name: 'productConfig.stock',
- rule: (arg) => arg >= 1,
- message: '鍟嗗搧娲诲姩搴撳瓨蹇呴』澶т簬绛変簬 1 锛侊紒锛�',
- },
-];
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 閫夋嫨鐨� SPU 鍒楄〃
-const spuPropertyList = ref<SpuProperty<MallSpuApi.Spu>[]>([]); // SPU 灞炴�у垪琛�
-
-/** 鎵撳紑鍟嗗搧閫夋嫨鍣� */
-function openSpuSelect() {
- spuSkuSelectRef.value?.open();
-}
-
-/** 閫夋嫨鍟嗗搧鍚庣殑鍥炶皟 */
-async function handleSpuSelected(spuId: number, skuIds?: number[]) {
- await formApi.setFieldValue('spuId', spuId);
- await getSpuDetails(spuId, skuIds);
-}
-
-/** 鑾峰彇 SPU 璇︽儏 */
-async function getSpuDetails(
- spuId: number,
- skuIds?: number[],
- products?: MallBargainActivityApi.BargainProduct[],
-) {
- const res = await getSpu(spuId);
- if (!res) {
- return;
- }
-
- spuList.value = [];
-
- // 绛涢�夋寚瀹氱殑 SKU锛堢爫浠锋椿鍔ㄥ彧閫夋嫨涓�涓� SKU锛�
- const selectSkus =
- skuIds === undefined
- ? res.skus
- : res.skus?.filter((sku) => skuIds.includes(sku.id!));
-
- // 涓烘瘡涓� SKU 閰嶇疆鐮嶄环娲诲姩鐩稿叧鐨勯厤缃�
- selectSkus?.forEach((sku) => {
- let config: MallBargainActivityApi.BargainProduct = {
- spuId: res.id!,
- skuId: sku.id!,
- bargainFirstPrice: 1,
- bargainMinPrice: 1,
- stock: 1,
- };
- // 濡傛灉鏄紪杈戞ā寮忥紝鍥炲~宸叉湁閰嶇疆
- if (products !== undefined) {
- const product = products.find((item) => item.skuId === sku.id);
- if (product) {
- // 鍒嗚浆鍏�
- product.bargainFirstPrice = formatToFraction(
- product.bargainFirstPrice,
- ) as unknown as number;
- product.bargainMinPrice = formatToFraction(
- product.bargainMinPrice,
- ) as unknown as number;
- }
- config = product || config;
- }
- // 鍔ㄦ�佹坊鍔� productConfig 灞炴�у埌 SKU
- (
- sku as MallSpuApi.Sku & {
- productConfig: MallBargainActivityApi.BargainProduct;
- }
- ).productConfig = config;
- });
- res.skus = selectSkus;
-
- const spuProperties: SpuProperty<MallSpuApi.Spu>[] = [
- {
- spuId: res.id!,
- spuDetail: res,
- propertyList: getPropertyList(res),
- },
- ];
-
- // 鐩存帴璧嬪�硷紝鍥犱负鐮嶄环娲诲姩鍙�夋嫨涓�涓� SPU
- spuList.value = [res];
- spuPropertyList.value = spuProperties;
-}
-
-// ================= end =================
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- try {
- // 鑾峰彇鐮嶄环鍟嗗搧閰嶇疆锛堟繁鎷疯礉閬垮厤鐩存帴淇敼鍘熷璞★級
- const products: MallBargainActivityApi.BargainProduct[] = cloneDeep(
- spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
- );
- if (products.length === 0) {
- message.error('璇烽�夋嫨鐮嶄环鍟嗗搧');
- return;
- }
- // 浠锋牸闇�瑕佽浆涓哄垎
- products.forEach((item) => {
- item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice);
- item.bargainMinPrice = convertToInteger(item.bargainMinPrice);
- });
-
- // 鎻愪氦琛ㄥ崟
- const values = await formApi.getValues();
- const data = {
- ...values,
- // 鐢ㄦ埛姣忔鐮嶄环閲戦鍏冭浆鍒�
- randomMinPrice: values.randomMinPrice
- ? convertToInteger(values.randomMinPrice)
- : undefined,
- randomMaxPrice: values.randomMaxPrice
- ? convertToInteger(values.randomMaxPrice)
- : undefined,
- // 鍚堝苟鐮嶄环鍟嗗搧閰嶇疆锛堢爫浠锋椿鍔ㄥ彧鏈変竴涓晢鍝侊級
- ...products[0],
- } as MallBargainActivityApi.BargainActivity;
-
- await (formData.value?.id
- ? updateBargainActivity(data)
- : createBargainActivity(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- spuList.value = [];
- spuPropertyList.value = [];
- return;
- }
-
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallBargainActivityApi.BargainActivity>();
- if (!data || !data.id) {
- return;
- }
- // 鍔犺浇鏁版嵁
- modalApi.lock();
- try {
- formData.value = await getBargainActivity(data.id);
- // 瀵归綈娲诲姩鍟嗗搧澶勭悊缁撴瀯
- await getSpuDetails(
- formData.value.spuId,
- [formData.value.skuId],
- [
- {
- spuId: formData.value.spuId,
- skuId: formData.value.skuId,
- bargainFirstPrice: formData.value.bargainFirstPrice, // 鐮嶄环璧峰浠锋牸锛屽崟浣嶅垎
- bargainMinPrice: formData.value.bargainMinPrice, // 鐮嶄环搴曚环
- stock: formData.value.stock, // 娲诲姩搴撳瓨
- },
- ],
- );
- // 璁剧疆琛ㄥ崟鍊兼椂锛屼环鏍煎瓧娈典粠鍒嗚浆鎹负鍏�
- await formApi.setValues({
- ...formData.value,
- randomMinPrice: formData.value.randomMinPrice
- ? formatToFraction(formData.value.randomMinPrice)
- : undefined,
- randomMaxPrice: formData.value.randomMaxPrice
- ? formatToFraction(formData.value.randomMaxPrice)
- : undefined,
- });
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <div>
- <Modal :title="getTitle" class="w-[70%]">
- <Form class="mx-4">
- <!-- 鍟嗗搧閫夋嫨 -->
- <template #spuId>
- <div class="w-full">
- <Button v-if="!formData?.id" type="primary" @click="openSpuSelect">
- 閫夋嫨鍟嗗搧
- </Button>
-
- <!-- SPU 鍜� SKU 鍒楄〃灞曠ず -->
- <SpuAndSkuList
- ref="spuAndSkuListRef"
- :rule-config="ruleConfig"
- :spu-list="spuList"
- :spu-property-list-p="spuPropertyList"
- class="mt-4"
- >
- <!-- 鎵╁睍鍒楋細鐮嶄环娲诲姩鐗规湁閰嶇疆 -->
- <template #default>
- <VxeColumn
- align="center"
- min-width="168"
- title="鐮嶄环璧峰浠锋牸(鍏�)"
- >
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.bargainFirstPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="鐮嶄环搴曚环(鍏�)">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.bargainMinPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="娲诲姩搴撳瓨">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.stock"
- :max="sku.stock"
- :min="0"
- class="w-full"
- />
- </template>
- </VxeColumn>
- </template>
- </SpuAndSkuList>
- </div>
- </template>
- </Form>
- </Modal>
-
- <!-- 鍟嗗搧閫夋嫨鍣ㄥ脊绐楋紙鍗曢�夋ā寮忥級 -->
- <SpuSkuSelect
- ref="spuSkuSelectRef"
- :is-select-sku="true"
- :radio="true"
- @select="handleSpuSelected"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/bargain/record/data.ts b/src/views/mall/promotion/bargain/record/data.ts
deleted file mode 100644
index 1c2e1c6..0000000
--- a/src/views/mall/promotion/bargain/record/data.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'status',
- label: '鐮嶄环鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐮嶄环鐘舵��',
- allowClear: true,
- options: getDictOptions(
- DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS,
- 'number',
- ),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 50,
- },
- {
- field: 'avatar',
- title: '鐢ㄦ埛澶村儚',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鐢ㄦ埛鏄电О',
- minWidth: 100,
- },
- {
- field: 'createTime',
- title: '鍙戣捣鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'activity.name',
- title: '鐮嶄环娲诲姩',
- minWidth: 150,
- },
- {
- field: 'activity.bargainMinPrice',
- title: '鏈�浣庝环',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'bargainPrice',
- title: '褰撳墠浠�',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'activity.helpMaxCount',
- title: '鎬荤爫浠锋鏁�',
- minWidth: 100,
- },
- {
- field: 'helpCount',
- title: '鍓╀綑鐮嶄环娆℃暟',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '鐮嶄环鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS },
- },
- },
- {
- field: 'endTime',
- title: '缁撴潫鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'orderId',
- title: '璁㈠崟缂栧彿',
- minWidth: 100,
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍔╁姏鍒楄〃琛ㄦ牸鍒楅厤缃� */
-export function useHelpGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'userId',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'avatar',
- title: '鐢ㄦ埛澶村儚',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鐢ㄦ埛鏄电О',
- minWidth: 100,
- },
- {
- field: 'reducePrice',
- title: '鐮嶄环閲戦',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'createTime',
- title: '鍔╁姏鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/mall/promotion/bargain/record/index.vue b/src/views/mall/promotion/bargain/record/index.vue
deleted file mode 100644
index bd55600..0000000
--- a/src/views/mall/promotion/bargain/record/index.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBargainRecordApi } from '#/api/mall/promotion/bargain/bargainRecord';
-
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBargainRecordPage } from '#/api/mall/promotion/bargain/bargainRecord';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import HelpListModal from './modules/list.vue';
-
-defineOptions({ name: 'PromotionBargainRecord' });
-
-const [HelpListModalApi, helpListModalApi] = useVbenModal({
- connectedComponent: HelpListModal,
- destroyOnClose: true,
-});
-
-/** 鏌ョ湅鍔╁姏璇︽儏 */
-function handleViewHelp(row: MallBargainRecordApi.BargainRecord) {
- helpListModalApi.setData({ recordId: row.id }).open();
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBargainRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBargainRecordApi.BargainRecord>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戠爫浠锋椿鍔�"
- url="https://doc.iocoder.cn/mall/promotion-bargain/"
- />
- </template>
-
- <HelpListModalApi />
-
- <Grid table-title="鐮嶄环璁板綍鍒楄〃">
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍔╁姏',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['promotion:bargain-help:query'],
- onClick: handleViewHelp.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/bargain/record/modules/list.vue b/src/views/mall/promotion/bargain/record/modules/list.vue
deleted file mode 100644
index 2b1edfc..0000000
--- a/src/views/mall/promotion/bargain/record/modules/list.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBargainHelpApi } from '#/api/mall/promotion/bargain/bargainHelp';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBargainHelpPage } from '#/api/mall/promotion/bargain/bargainHelp';
-
-import { useHelpGridColumns } from '../data';
-
-/** 鍔╁姏鍒楄〃 */
-defineOptions({ name: 'BargainRecordListDialog' });
-
-const recordId = ref<number>();
-const getTitle = computed(() => {
- return `鍔╁姏鍒楄〃 - 璁板綍${recordId.value || ''}`;
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- recordId.value = undefined;
- return;
- }
- // 鑾峰彇浼犲叆鐨勮褰旾D
- const data = modalApi.getData<{ recordId: number }>();
- if (data?.recordId) {
- recordId.value = data.recordId;
- }
- },
-});
-
-const [Grid] = useVbenVxeGrid({
- gridOptions: {
- columns: useHelpGridColumns(),
- height: 600,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- return await getBargainHelpPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- recordId: recordId.value,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- } as VxeTableGridOptions<MallBargainHelpApi.BargainHelp>,
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Grid class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/combination/activity/data.ts b/src/views/mall/promotion/combination/activity/data.ts
deleted file mode 100644
index 053f637..0000000
--- a/src/views/mall/promotion/combination/activity/data.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { formatDate } from '../../../../../packages/utils/src';
-
-/** 琛ㄥ崟閰嶇疆 */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- },
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'startTime',
- label: '寮�濮嬫椂闂�',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
- showTime: true,
- valueFormat: 'x',
- format: 'YYYY-MM-DD HH:mm:ss',
- },
- rules: 'required',
- },
- {
- fieldName: 'endTime',
- label: '缁撴潫鏃堕棿',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
- showTime: true,
- valueFormat: 'x',
- format: 'YYYY-MM-DD HH:mm:ss',
- },
- rules: 'required',
- },
- {
- fieldName: 'userSize',
- label: '鎷煎洟浜烘暟',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '杈惧埌璇ヤ汉鏁板嵆鎴愬洟',
- min: 2,
- },
- rules: 'required',
- },
- {
- fieldName: 'limitDuration',
- label: '闄愬埗鏃堕暱',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '闄愬埗鏃堕暱(灏忔椂)',
- min: 0,
- },
- rules: 'required',
- },
- {
- fieldName: 'totalLimitCount',
- label: '鎬婚檺璐暟閲�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ�婚檺璐暟閲�',
- min: 0,
- },
- },
- {
- fieldName: 'singleLimitCount',
- label: '鍗曟闄愯喘鏁伴噺',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ュ崟娆¢檺璐暟閲�',
- min: 0,
- },
- },
- {
- fieldName: 'virtualGroup',
- label: '铏氭嫙鎴愬洟',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
- },
- },
- {
- fieldName: 'spuId',
- label: '鎷煎洟鍟嗗搧',
- component: 'Input',
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'activityTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- formatter: ({ row }) => {
- if (!row.startTime || !row.endTime) return '';
- return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`;
- },
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- },
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- formatter: ({ cellValue }) => {
- return `楼${(cellValue / 100).toFixed(2)}`;
- },
- },
- {
- field: 'combinationPrice',
- title: '鎷煎洟浠�',
- minWidth: 100,
- formatter: ({ row }) => {
- if (!row.products || row.products.length === 0) return '';
- const combinationPrice = Math.min(
- ...row.products.map((item: any) => item.combinationPrice),
- );
- return `楼${(combinationPrice / 100).toFixed(2)}`;
- },
- },
- {
- field: 'groupCount',
- title: '寮�鍥㈢粍鏁�',
- minWidth: 100,
- },
- {
- field: 'groupSuccessCount',
- title: '鎴愬洟缁勬暟',
- minWidth: 100,
- },
- {
- field: 'recordCount',
- title: '璐拱娆℃暟',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/combination/activity/index.vue b/src/views/mall/promotion/combination/activity/index.vue
deleted file mode 100644
index f12f3fb..0000000
--- a/src/views/mall/promotion/combination/activity/index.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCombinationActivityApi } from '#/api/mall/promotion/combination/combinationActivity';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closeCombinationActivity,
- deleteCombinationActivity,
- getCombinationActivityPage,
-} from '#/api/mall/promotion/combination/combinationActivity';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import CombinationActivityForm from './modules/form.vue';
-
-defineOptions({ name: 'PromotionCombinationActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: CombinationActivityForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鎷煎洟娲诲姩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鎷煎洟娲诲姩 */
-function handleEdit(row: MallCombinationActivityApi.CombinationActivity) {
- formModalApi.setData(row).open();
-}
-
-/** 鍏抽棴鎷煎洟娲诲姩 */
-async function handleClose(
- row: MallCombinationActivityApi.CombinationActivity,
-) {
- await confirm('纭鍏抽棴璇ユ嫾鍥㈡椿鍔ㄥ悧锛�');
- const hideLoading = message.loading({
- content: '鍏抽棴涓�...',
- duration: 0,
- });
- try {
- await closeCombinationActivity(row.id!);
- message.success('鍏抽棴鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎鎷煎洟娲诲姩 */
-async function handleDelete(
- row: MallCombinationActivityApi.CombinationActivity,
-) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteCombinationActivity(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCombinationActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCombinationActivityApi.CombinationActivity>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戞嫾鍥㈡椿鍔�"
- url="https://doc.iocoder.cn/mall/promotion-combination/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="鎷煎洟娲诲姩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鎷煎洟娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:combination-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:combination-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:combination-activity:close'],
- ifShow: row.status === 0,
- onClick: handleClose.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:combination-activity:delete'],
- ifShow: row.status !== 0,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/combination/activity/modules/form.vue b/src/views/mall/promotion/combination/activity/modules/form.vue
deleted file mode 100644
index cf10233..0000000
--- a/src/views/mall/promotion/combination/activity/modules/form.vue
+++ /dev/null
@@ -1,261 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallCombinationActivityApi } from '#/api/mall/promotion/combination/combinationActivity';
-import type {
- RuleConfig,
- SpuProperty,
-} from '#/views/mall/product/spu/components';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { cloneDeep, convertToInteger, formatToFraction } from '../../../../../../packages/utils/src';
-
-import { Button, InputNumber, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { VxeColumn } from '#/adapter/vxe-table';
-import { getSpu } from '#/api/mall/product/spu';
-import {
- createCombinationActivity,
- getCombinationActivity,
- updateCombinationActivity,
-} from '#/api/mall/promotion/combination/combinationActivity';
-import { $t } from '#/locales';
-import {
- getPropertyList,
- SpuAndSkuList,
- SpuSkuSelect,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-defineOptions({ name: 'CombinationActivityForm' });
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallCombinationActivityApi.CombinationActivity>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鎷煎洟娲诲姩'])
- : $t('ui.actionTitle.create', ['鎷煎洟娲诲姩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-// ================= 鍟嗗搧閫夋嫨鐩稿叧 =================
-
-const spuSkuSelectRef = ref(); // 鍟嗗搧鍜屽睘鎬ч�夋嫨 Ref
-const spuAndSkuListRef = ref(); // SPU 鍜� SKU 鍒楄〃缁勪欢 Ref
-
-const ruleConfig: RuleConfig[] = [
- {
- name: 'productConfig.combinationPrice',
- rule: (arg) => arg >= 0.01,
- message: '鍟嗗搧鎷煎洟浠锋牸涓嶈兘灏忎簬 0.01 锛侊紒锛�',
- },
-];
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 閫夋嫨鐨� SPU 鍒楄〃
-const spuPropertyList = ref<SpuProperty<MallSpuApi.Spu>[]>([]); // SPU 灞炴�у垪琛�
-
-/** 鎵撳紑鍟嗗搧閫夋嫨鍣� */
-function openSpuSelect() {
- spuSkuSelectRef.value?.open();
-}
-
-/** 閫夋嫨鍟嗗搧鍚庣殑鍥炶皟 */
-async function handleSpuSelected(spuId: number, skuIds?: number[]) {
- await formApi.setFieldValue('spuId', spuId);
- await getSpuDetails(spuId, skuIds);
-}
-
-/** 鑾峰彇 SPU 璇︽儏 */
-async function getSpuDetails(
- spuId: number,
- skuIds?: number[],
- products?: MallCombinationActivityApi.CombinationProduct[],
-) {
- const res = await getSpu(spuId);
- if (!res) {
- return;
- }
-
- spuList.value = [];
-
- // 绛涢�夋寚瀹氱殑 SKU
- const selectSkus =
- skuIds === undefined
- ? res.skus
- : res.skus?.filter((sku) => skuIds.includes(sku.id!));
-
- // 涓烘瘡涓� SKU 閰嶇疆鎷煎洟娲诲姩鐩稿叧鐨勯厤缃�
- selectSkus?.forEach((sku) => {
- let config: MallCombinationActivityApi.CombinationProduct = {
- spuId: res.id!,
- skuId: sku.id!,
- combinationPrice: 0,
- };
- // 濡傛灉鏄紪杈戞ā寮忥紝鍥炲~宸叉湁閰嶇疆
- if (products !== undefined) {
- const product = products.find((item) => item.skuId === sku.id);
- if (product) {
- // 鍒嗚浆鍏�
- product.combinationPrice = formatToFraction(
- product.combinationPrice,
- ) as unknown as number;
- }
- config = product || config;
- }
- // 鍔ㄦ�佹坊鍔� productConfig 灞炴�у埌 SKU
- (
- sku as MallSpuApi.Sku & {
- productConfig: MallCombinationActivityApi.CombinationProduct;
- }
- ).productConfig = config;
- });
- res.skus = selectSkus;
-
- const spuProperties: SpuProperty<MallSpuApi.Spu>[] = [
- {
- spuId: res.id!,
- spuDetail: res,
- propertyList: getPropertyList(res),
- },
- ];
-
- // 鐩存帴璧嬪�硷紝鍥犱负鎷煎洟娲诲姩鍙�夋嫨涓�涓� SPU
- spuList.value = [res];
- spuPropertyList.value = spuProperties;
-}
-
-// ================= end =================
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- try {
- // 鑾峰彇鎷煎洟鍟嗗搧閰嶇疆锛堟繁鎷疯礉閬垮厤鐩存帴淇敼鍘熷璞★級
- const products: MallCombinationActivityApi.CombinationProduct[] =
- cloneDeep(spuAndSkuListRef.value?.getSkuConfigs('productConfig') || []);
- if (products.length === 0) {
- message.error('璇烽�夋嫨鎷煎洟鍟嗗搧');
- return;
- }
- // 浠锋牸闇�瑕佽浆涓哄垎
- products.forEach((item) => {
- item.combinationPrice = convertToInteger(item.combinationPrice);
- });
-
- // 鎻愪氦琛ㄥ崟
- const values = await formApi.getValues();
- const data = {
- ...values,
- products,
- } as MallCombinationActivityApi.CombinationActivity;
-
- await (formData.value?.id
- ? updateCombinationActivity(data)
- : createCombinationActivity(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- spuList.value = [];
- spuPropertyList.value = [];
- return;
- }
-
- // 鍔犺浇鏁版嵁
- const data =
- modalApi.getData<MallCombinationActivityApi.CombinationActivity>();
- if (!data || !data.id) {
- return;
- }
- // 鍔犺浇鏁版嵁
- modalApi.lock();
- try {
- formData.value = await getCombinationActivity(data.id);
- // 瀵归綈娲诲姩鍟嗗搧澶勭悊缁撴瀯
- await getSpuDetails(
- formData.value.spuId!,
- formData.value.products?.map((sku) => sku.skuId),
- formData.value.products,
- );
- // 璁剧疆琛ㄥ崟鍊�
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <div>
- <Modal :title="getTitle" class="w-[70%]">
- <Form class="mx-4">
- <!-- 鍟嗗搧閫夋嫨 -->
- <template #spuId>
- <div class="w-full">
- <Button v-if="!formData?.id" type="primary" @click="openSpuSelect">
- 閫夋嫨鍟嗗搧
- </Button>
-
- <!-- SPU 鍜� SKU 鍒楄〃灞曠ず -->
- <SpuAndSkuList
- ref="spuAndSkuListRef"
- :rule-config="ruleConfig"
- :spu-list="spuList"
- :spu-property-list-p="spuPropertyList"
- class="mt-4"
- >
- <!-- 鎵╁睍鍒楋細鎷煎洟娲诲姩鐗规湁閰嶇疆 -->
- <template #default>
- <VxeColumn align="center" min-width="168" title="鎷煎洟浠锋牸(鍏�)">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.combinationPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- </template>
- </SpuAndSkuList>
- </div>
- </template>
- </Form>
- </Modal>
-
- <!-- 鍟嗗搧閫夋嫨鍣ㄥ脊绐� -->
- <SpuSkuSelect
- ref="spuSkuSelectRef"
- :is-select-sku="true"
- @select="handleSpuSelected"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/combination/components/index.ts b/src/views/mall/promotion/combination/components/index.ts
deleted file mode 100644
index 36d56af..0000000
--- a/src/views/mall/promotion/combination/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as CombinationShowcase } from './showcase.vue';
diff --git a/src/views/mall/promotion/combination/components/showcase.vue b/src/views/mall/promotion/combination/components/showcase.vue
deleted file mode 100644
index 8188154..0000000
--- a/src/views/mall/promotion/combination/components/showcase.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<!-- 鎷煎洟娲诲姩姗辩獥缁勪欢锛氱敤浜庡睍绀哄拰閫夋嫨鎷煎洟娲诲姩 -->
-<script lang="ts" setup>
-import type { MallCombinationActivityApi } from '#/api/mall/promotion/combination/combinationActivity';
-
-import { computed, ref, watch } from 'vue';
-
-import { CloseCircleFilled, PlusOutlined } from '../../../../../packages/icons/src';
-
-import { Image, Tooltip } from 'ant-design-vue';
-
-import { getCombinationActivityListByIds } from '#/api/mall/promotion/combination/combinationActivity';
-
-import CombinationTableSelect from './table-select.vue';
-
-interface CombinationShowcaseProps {
- modelValue?: number | number[];
- limit?: number;
- disabled?: boolean;
-}
-
-const props = withDefaults(defineProps<CombinationShowcaseProps>(), {
- modelValue: undefined,
- limit: Number.MAX_VALUE,
- disabled: false,
-});
-
-const emit = defineEmits(['update:modelValue', 'change']);
-
-const activityList = ref<MallCombinationActivityApi.CombinationActivity[]>([]); // 宸查�夋嫨鐨勬椿鍔ㄥ垪琛�
-const combinationTableSelectRef =
- ref<InstanceType<typeof CombinationTableSelect>>(); // 娲诲姩閫夋嫨琛ㄦ牸缁勪欢寮曠敤
-const isMultiple = computed(() => props.limit !== 1); // 鏄惁涓哄閫夋ā寮�
-
-/** 璁$畻鏄惁鍙互娣诲姞 */
-const canAdd = computed(() => {
- if (props.disabled) {
- return false;
- }
- if (!props.limit) {
- return true;
- }
- return activityList.value.length < props.limit;
-});
-
-/** 鐩戝惉 modelValue 鍙樺寲锛屽姞杞芥椿鍔ㄨ鎯� */
-watch(
- () => props.modelValue,
- async (newValue) => {
- // eslint-disable-next-line unicorn/no-nested-ternary
- const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
- if (ids.length === 0) {
- activityList.value = [];
- return;
- }
- // 鍙湁娲诲姩鍙戠敓鍙樺寲鏃舵墠閲嶆柊鏌ヨ
- if (
- activityList.value.length === 0 ||
- activityList.value.some((activity) => !ids.includes(activity.id!))
- ) {
- activityList.value = await getCombinationActivityListByIds(ids);
- }
- },
- { immediate: true },
-);
-
-/** 鎵撳紑娲诲姩閫夋嫨瀵硅瘽妗� */
-function handleOpenActivitySelect() {
- combinationTableSelectRef.value?.open(activityList.value);
-}
-
-/** 閫夋嫨娲诲姩鍚庤Е鍙� */
-function handleActivitySelected(
- activities:
- | MallCombinationActivityApi.CombinationActivity
- | MallCombinationActivityApi.CombinationActivity[],
-) {
- activityList.value = Array.isArray(activities) ? activities : [activities];
- emitActivityChange();
-}
-
-/** 鍒犻櫎娲诲姩 */
-function handleRemoveActivity(index: number) {
- activityList.value.splice(index, 1);
- emitActivityChange();
-}
-
-/** 瑙﹀彂鍙樻洿浜嬩欢 */
-function emitActivityChange() {
- if (props.limit === 1) {
- const activity =
- activityList.value.length > 0 ? activityList.value[0] : null;
- emit('update:modelValue', activity?.id || 0);
- emit('change', activity);
- } else {
- emit(
- 'update:modelValue',
- activityList.value.map((activity) => activity.id!),
- );
- emit('change', activityList.value);
- }
-}
-</script>
-
-<template>
- <div class="flex flex-wrap items-center gap-2">
- <!-- 宸查�夋椿鍔ㄥ垪琛� -->
- <div
- v-for="(activity, index) in activityList"
- :key="activity.id"
- class="group relative h-[60px] w-[60px] overflow-hidden rounded-lg"
- >
- <Tooltip :title="activity.name">
- <div class="relative h-full w-full">
- <Image
- :src="activity.picUrl"
- class="h-full w-full rounded-lg object-cover"
- />
- <!-- 鍒犻櫎鎸夐挳 -->
- <CloseCircleFilled
- v-if="!disabled"
- class="absolute -right-2 -top-2 cursor-pointer text-xl text-red-500 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100"
- @click="handleRemoveActivity(index)"
- />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娣诲姞娲诲姩鎸夐挳 -->
- <Tooltip v-if="canAdd" title="閫夋嫨娲诲姩">
- <div
- class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors hover:border-primary hover:bg-primary/5"
- @click="handleOpenActivitySelect"
- >
- <PlusOutlined class="text-xl text-gray-400" />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娲诲姩閫夋嫨瀵硅瘽妗� -->
- <CombinationTableSelect
- ref="combinationTableSelectRef"
- :multiple="isMultiple"
- @change="handleActivitySelected"
- />
-</template>
diff --git a/src/views/mall/promotion/combination/components/table-select.vue b/src/views/mall/promotion/combination/components/table-select.vue
deleted file mode 100644
index d9d2d1c..0000000
--- a/src/views/mall/promotion/combination/components/table-select.vue
+++ /dev/null
@@ -1,281 +0,0 @@
-<!-- 鎷煎洟娲诲姩閫夋嫨寮圭獥缁勪欢 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-import type { MallCombinationActivityApi } from '#/api/mall/promotion/combination/combinationActivity';
-
-import { computed, onMounted, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { fenToYuan, formatDate, handleTree } from '../../../../../packages/utils/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCategoryList } from '#/api/mall/product/category';
-import { getCombinationActivityPage } from '#/api/mall/promotion/combination/combinationActivity';
-
-interface CombinationTableSelectProps {
- multiple?: boolean; // 鏄惁澶氶�夛細true - checkbox锛沠alse - radio
-}
-
-const props = withDefaults(defineProps<CombinationTableSelectProps>(), {
- multiple: false,
-});
-
-const emit = defineEmits<{
- change: [
- activity:
- | MallCombinationActivityApi.CombinationActivity
- | MallCombinationActivityApi.CombinationActivity[],
- ];
-}>();
-
-const categoryList = ref<MallCategoryApi.Category[]>([]); // 鍒嗙被鍒楄〃
-const categoryTreeList = ref<any[]>([]); // 鍒嗙被鏍�
-
-/** 鍗曢�夛細澶勭悊閫変腑鍙樺寲 */
-function handleRadioChange() {
- const selectedRow =
- gridApi.grid.getRadioRecord() as MallCombinationActivityApi.CombinationActivity;
- if (selectedRow) {
- emit('change', selectedRow);
- modalApi.close();
- }
-}
-
-/**
- * 鏍煎紡鍖栨嫾鍥环鏍�
- * @param products
- */
-const formatCombinationPrice = (
- products: MallCombinationActivityApi.CombinationProduct[],
-) => {
- if (!products || products.length === 0) return '-';
- const combinationPrice = Math.min(
- ...products.map((item) => item.combinationPrice || 0),
- );
- return `锟�${fenToYuan(combinationPrice)}`;
-};
-
-/** 鎼滅储琛ㄥ崟 Schema */
-const formSchema = computed<VbenFormSchema[]>(() => [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- clearable: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- clearable: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
-]);
-
-/** 琛ㄦ牸鍒楅厤缃� */
-const gridColumns = computed<VxeGridProps['columns']>(() => {
- const columns: VxeGridProps['columns'] = [];
- if (props.multiple) {
- columns.push({ type: 'checkbox', width: 55 });
- } else {
- columns.push({ type: 'radio', width: 55 });
- }
- columns.push(
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'activityTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- formatter: ({ row }) => {
- return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`;
- },
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- width: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- formatter: ({ cellValue }) => {
- return cellValue ? `锟�${fenToYuan(cellValue)}` : '-';
- },
- },
- {
- field: 'products',
- title: '鎷煎洟浠�',
- minWidth: 100,
- formatter: ({ cellValue }) => {
- return formatCombinationPrice(cellValue);
- },
- },
- {
- field: 'groupCount',
- title: '寮�鍥㈢粍鏁�',
- minWidth: 100,
- },
- {
- field: 'groupSuccessCount',
- title: '鎴愬洟缁勬暟',
- minWidth: 100,
- },
- {
- field: 'recordCount',
- title: '璐拱娆℃暟',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- );
- return columns;
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema.value,
- layout: 'horizontal',
- collapsed: false,
- },
- gridOptions: {
- columns: gridColumns.value,
- height: 500,
- border: true,
- checkboxConfig: {
- reserve: true,
- },
- radioConfig: {
- reserve: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- proxyConfig: {
- ajax: {
- async query({ page }: any, formValues: any) {
- return await getCombinationActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- },
- gridEvents: {
- radioChange: handleRadioChange,
- },
-});
-
-const [Modal, modalApi] = useVbenModal({
- destroyOnClose: true,
- showConfirmButton: props.multiple, // 鐗规畩锛歳adio 鍗曢�夋儏鍐典笅锛岃蛋 handleRadioChange 澶勭悊銆�
- onConfirm: () => {
- const selectedRows =
- gridApi.grid.getCheckboxRecords() as MallCombinationActivityApi.CombinationActivity[];
- emit('change', selectedRows);
- modalApi.close();
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- gridApi.grid.clearCheckboxRow();
- gridApi.grid.clearRadioRow();
- return;
- }
-
- // 1. 鍏堟煡璇㈡暟鎹�
- await gridApi.query();
- // 2. 璁剧疆宸查�変腑琛�
- const data = modalApi.getData<
- | MallCombinationActivityApi.CombinationActivity
- | MallCombinationActivityApi.CombinationActivity[]
- >();
- if (props.multiple && Array.isArray(data) && data.length > 0) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData()
- .fullData as MallCombinationActivityApi.CombinationActivity[];
- data.forEach((activity) => {
- const row = tableData.find((item) => item.id === activity.id);
- if (row) {
- gridApi.grid.setCheckboxRow(row, true);
- }
- });
- }, 300);
- } else if (!props.multiple && data && !Array.isArray(data)) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData()
- .fullData as MallCombinationActivityApi.CombinationActivity[];
- const row = tableData.find((item) => item.id === data.id);
- if (row) {
- gridApi.grid.setRadioRow(row);
- }
- }, 300);
- }
- },
-});
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open: (
- data?:
- | MallCombinationActivityApi.CombinationActivity
- | MallCombinationActivityApi.CombinationActivity[],
- ) => {
- modalApi.setData(data).open();
- },
-});
-
-/** 鍒濆鍖栧垎绫绘暟鎹� */
-onMounted(async () => {
- categoryList.value = await getCategoryList({});
- categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId');
-});
-</script>
-
-<template>
- <Modal title="閫夋嫨娲诲姩" class="w-[950px]">
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/combination/record/data.ts b/src/views/mall/promotion/combination/record/data.ts
deleted file mode 100644
index 8bc09d3..0000000
--- a/src/views/mall/promotion/combination/record/data.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'status',
- label: '鎷煎洟鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鎷煎洟鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- placeholder: ['寮�濮嬫椂闂�', '缁撴潫鏃堕棿'],
- allowClear: true,
- valueFormat: 'YYYY-MM-DD HH:mm:ss',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鎷煎洟缂栧彿',
- minWidth: 80,
- },
- {
- field: 'avatar',
- title: '澶村儚',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鏄电О',
- minWidth: 100,
- },
- {
- field: 'headId',
- title: '寮�鍥㈠洟闀�',
- minWidth: 100,
- },
- {
- field: 'picUrl',
- title: '鎷煎洟鍟嗗搧鍥�',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鎷煎洟鍟嗗搧',
- minWidth: 120,
- },
- {
- field: 'activityName',
- title: '鎷煎洟娲诲姩',
- minWidth: 140,
- },
- {
- field: 'userSize',
- title: '鍑犱汉鍥�',
- minWidth: 80,
- },
- {
- field: 'userCount',
- title: '鍙備笌浜烘暟',
- minWidth: 80,
- },
- {
- field: 'createTime',
- title: '鍙傚洟鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'endTime',
- title: '缁撴潫鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'status',
- title: '鎷煎洟鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鐢ㄦ埛鍒楄〃琛ㄦ牸鍒楅厤缃� */
-export function useUserGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 80,
- },
- {
- field: 'avatar',
- title: '鐢ㄦ埛澶村儚',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鐢ㄦ埛鏄电О',
- minWidth: 100,
- },
- {
- field: 'headId',
- title: '寮�鍥㈠洟闀�',
- minWidth: 100,
- formatter: ({ cellValue }) => {
- return cellValue === 0 ? '鍥㈤暱' : '鍥㈠憳';
- },
- },
- {
- field: 'createTime',
- title: '鍙傚洟鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'endTime',
- title: '缁撴潫鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'status',
- title: '鎷煎洟鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- ];
-}
diff --git a/src/views/mall/promotion/combination/record/index.vue b/src/views/mall/promotion/combination/record/index.vue
deleted file mode 100644
index 15f22f2..0000000
--- a/src/views/mall/promotion/combination/record/index.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCombinationRecordPage } from '#/api/mall/promotion/combination/combinationRecord';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import CombinationUserList from './modules/list.vue';
-
-defineOptions({ name: 'PromotionCombinationRecord' });
-
-const [UserListModal, userListModalApi] = useVbenModal({
- connectedComponent: CombinationUserList,
- destroyOnClose: true,
-});
-
-/** 鏌ョ湅鎷煎洟鐢ㄦ埛 */
-function handleViewUsers(row: any) {
- userListModalApi.setData({ recordId: row.id }).open();
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCombinationRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戞嫾鍥㈡椿鍔�"
- url="https://doc.iocoder.cn/mall/promotion-combination/"
- />
- </template>
-
- <UserListModal />
-
- <Grid table-title="鎷煎洟璁板綍鍒楄〃">
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鏌ョ湅鎴愬憳',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleViewUsers.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/combination/record/modules/list.vue b/src/views/mall/promotion/combination/record/modules/list.vue
deleted file mode 100644
index ce48b14..0000000
--- a/src/views/mall/promotion/combination/record/modules/list.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCombinationRecordPage } from '#/api/mall/promotion/combination/combinationRecord';
-
-import { useUserGridColumns } from '../data';
-
-defineOptions({ name: 'CombinationUserList' });
-
-const headId = ref<number>();
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- headId.value = undefined;
- return;
- }
- const data = modalApi.getData<{ headId: number }>();
- if (data?.headId) {
- headId.value = data.headId;
- }
- },
-});
-
-const [Grid] = useVbenVxeGrid({
- gridOptions: {
- columns: useUserGridColumns(),
- height: 600,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- // 鏆傛椂杩斿洖绌烘暟鎹紝寰匒PI瀹炵幇鍚庢浛鎹�
- return await getCombinationRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- headId: headId.value,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- } as VxeTableGridOptions,
-});
-
-const getTitle = computed(() => {
- return `鎷煎洟鎴愬憳鍒楄〃 (鎷煎洟ID: ${headId.value || ''})`;
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Grid class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/components/app-link-input/data.ts b/src/views/mall/promotion/components/app-link-input/data.ts
deleted file mode 100644
index 3eb3db7..0000000
--- a/src/views/mall/promotion/components/app-link-input/data.ts
+++ /dev/null
@@ -1,220 +0,0 @@
-/** APP 閾炬帴鍒嗙粍 */
-export interface AppLinkGroup {
- name: string; // 鍒嗙粍鍚嶇О
- links: AppLink[]; // 閾炬帴鍒楄〃
-}
-
-/** APP 閾炬帴 */
-export interface AppLink {
- name: string; // 閾炬帴鍚嶇О
- path: string; // 閾炬帴鍦板潃
- type?: APP_LINK_TYPE_ENUM; // 閾炬帴鐨勭被鍨�
-}
-
-/** APP 閾炬帴绫诲瀷锛堥渶瑕佺壒娈婂鐞嗭紝渚嬪鍟嗗搧璇︽儏锛� */
-export enum APP_LINK_TYPE_ENUM {
- ACTIVITY_COMBINATION, // 鎷煎洟娲诲姩
- ACTIVITY_POINT, // 绉垎鍟嗗煄娲诲姩
- ACTIVITY_SECKILL, // 绉掓潃娲诲姩
- ARTICLE_DETAIL, // 鏂囩珷璇︽儏
- COUPON_DETAIL, // 浼樻儬鍒歌鎯�
- DIY_PAGE_DETAIL, // 鑷畾涔夐〉闈㈣鎯�
- PRODUCT_CATEGORY_LIST, // 鍝佺被鍒楄〃
- PRODUCT_DETAIL_COMBINATION, // 鎷煎洟鍟嗗搧璇︽儏
- PRODUCT_DETAIL_NORMAL, // 鍟嗗搧璇︽儏
- PRODUCT_DETAIL_SECKILL, // 绉掓潃鍟嗗搧璇︽儏
- PRODUCT_LIST, // 鍟嗗搧鍒楄〃
-}
-
-/** APP 閾炬帴鍒楄〃锛堝仛涓�涓嬫寔涔呭寲锛燂級 */
-export const APP_LINK_GROUP_LIST = [
- {
- name: '鍟嗗煄',
- links: [
- {
- name: '棣栭〉',
- path: '/pages/index/index',
- },
- {
- name: '鍟嗗搧鍒嗙被',
- path: '/pages/index/category',
- type: APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST,
- },
- {
- name: '璐墿杞�',
- path: '/pages/index/cart',
- },
- {
- name: '涓汉涓績',
- path: '/pages/index/user',
- },
- {
- name: '鍟嗗搧鎼滅储',
- path: '/pages/index/search',
- },
- {
- name: '鑷畾涔夐〉闈�',
- path: '/pages/index/page',
- type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL,
- },
- {
- name: '瀹㈡湇',
- path: '/pages/chat/index',
- },
- {
- name: '绯荤粺璁剧疆',
- path: '/pages/public/setting',
- },
- {
- name: '甯歌闂',
- path: '/pages/public/faq',
- },
- ],
- },
- {
- name: '鍟嗗搧',
- links: [
- {
- name: '鍟嗗搧鍒楄〃',
- path: '/pages/goods/list',
- type: APP_LINK_TYPE_ENUM.PRODUCT_LIST,
- },
- {
- name: '鍟嗗搧璇︽儏',
- path: '/pages/goods/index',
- type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_NORMAL,
- },
- {
- name: '鎷煎洟鍟嗗搧璇︽儏',
- path: '/pages/goods/groupon',
- type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_COMBINATION,
- },
- {
- name: '绉掓潃鍟嗗搧璇︽儏',
- path: '/pages/goods/seckill',
- type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL,
- },
- ],
- },
- {
- name: '钀ラ攢娲诲姩',
- links: [
- {
- name: '鎷煎洟璁㈠崟',
- path: '/pages/activity/groupon/order',
- },
- {
- name: '钀ラ攢鍟嗗搧',
- path: '/pages/activity/index',
- },
- {
- name: '鎷煎洟娲诲姩',
- path: '/pages/activity/groupon/list',
- type: APP_LINK_TYPE_ENUM.ACTIVITY_COMBINATION,
- },
- {
- name: '绉掓潃娲诲姩',
- path: '/pages/activity/seckill/list',
- type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL,
- },
- {
- name: '绉垎鍟嗗煄娲诲姩',
- path: '/pages/activity/point/list',
- type: APP_LINK_TYPE_ENUM.ACTIVITY_POINT,
- },
- {
- name: '绛惧埌涓績',
- path: '/pages/app/sign',
- },
- {
- name: '浼樻儬鍒镐腑蹇�',
- path: '/pages/coupon/list',
- },
- {
- name: '浼樻儬鍒歌鎯�',
- path: '/pages/coupon/detail',
- type: APP_LINK_TYPE_ENUM.COUPON_DETAIL,
- },
- {
- name: '鏂囩珷璇︽儏',
- path: '/pages/public/richtext',
- type: APP_LINK_TYPE_ENUM.ARTICLE_DETAIL,
- },
- ],
- },
- {
- name: '鍒嗛攢鍟嗗煄',
- links: [
- {
- name: '鍒嗛攢涓績',
- path: '/pages/commission/index',
- },
- {
- name: '鎺ㄥ箍鍟嗗搧',
- path: '/pages/commission/goods',
- },
- {
- name: '鍒嗛攢璁㈠崟',
- path: '/pages/commission/order',
- },
- {
- name: '鎴戠殑鍥㈤槦',
- path: '/pages/commission/team',
- },
- ],
- },
- {
- name: '鏀粯',
- links: [
- {
- name: '鍏呭�间綑棰�',
- path: '/pages/pay/recharge',
- },
- {
- name: '鍏呭�艰褰�',
- path: '/pages/pay/recharge-log',
- },
- ],
- },
- {
- name: '鐢ㄦ埛涓績',
- links: [
- {
- name: '鐢ㄦ埛淇℃伅',
- path: '/pages/user/info',
- },
- {
- name: '鐢ㄦ埛璁㈠崟',
- path: '/pages/order/list',
- },
- {
- name: '鍞悗璁㈠崟',
- path: '/pages/order/aftersale/list',
- },
- {
- name: '鍟嗗搧鏀惰棌',
- path: '/pages/user/goods-collect',
- },
- {
- name: '娴忚璁板綍',
- path: '/pages/user/goods-log',
- },
- {
- name: '鍦板潃绠$悊',
- path: '/pages/user/address/list',
- },
- {
- name: '鐢ㄦ埛浣i噾',
- path: '/pages/user/wallet/commission',
- },
- {
- name: '鐢ㄦ埛浣欓',
- path: '/pages/user/wallet/money',
- },
- {
- name: '鐢ㄦ埛绉垎',
- path: '/pages/user/wallet/score',
- },
- ],
- },
-] as AppLinkGroup[];
diff --git a/src/views/mall/promotion/components/app-link-input/index.vue b/src/views/mall/promotion/components/app-link-input/index.vue
deleted file mode 100644
index b412510..0000000
--- a/src/views/mall/promotion/components/app-link-input/index.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script lang="ts" setup>
-import { ref, watch } from 'vue';
-
-import { Button, Input } from 'ant-design-vue';
-
-import AppLinkSelectDialog from './select-dialog.vue';
-
-/** APP 閾炬帴杈撳叆妗� */
-defineOptions({ name: 'AppLinkInput' });
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps({
- modelValue: {
- type: String,
- default: '',
- }, // 褰撳墠閫変腑鐨勯摼鎺�
-});
-
-const emit = defineEmits<{
- 'update:modelValue': [link: string];
-}>();
-
-const dialogRef = ref(); // 閫夋嫨瀵硅瘽妗�
-
-const appLink = ref(''); // 褰撳墠鐨勯摼鎺�
-
-/** 澶勭悊鎵撳紑瀵硅瘽妗� */
-function handleOpenDialog() {
- return dialogRef.value?.open(appLink.value);
-}
-
-/** 澶勭悊 APP 閾炬帴閫変腑 */
-function handleLinkSelected(link: string) {
- appLink.value = link;
-}
-
-watch(
- () => props.modelValue,
- () => (appLink.value = props.modelValue),
- { immediate: true },
-);
-
-watch(
- () => appLink.value,
- () => emit('update:modelValue', appLink.value),
-);
-</script>
-<template>
- <Input v-model:value="appLink" placeholder="杈撳叆鎴栭�夋嫨閾炬帴">
- <template #addonAfter>
- <Button
- @click="handleOpenDialog"
- class="!border-none !bg-transparent !p-0"
- >
- 閫夋嫨
- </Button>
- </template>
- </Input>
-
- <AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
-</template>
diff --git a/src/views/mall/promotion/components/app-link-input/select-dialog.vue b/src/views/mall/promotion/components/app-link-input/select-dialog.vue
deleted file mode 100644
index 2432b38..0000000
--- a/src/views/mall/promotion/components/app-link-input/select-dialog.vue
+++ /dev/null
@@ -1,236 +0,0 @@
-<script lang="ts" setup>
-import type { AppLink } from './data';
-
-import { nextTick, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { getUrlNumberValue } from '../../../../../packages/utils/src';
-
-import { Button, Form, FormItem, Tooltip } from 'ant-design-vue';
-
-import { ProductCategorySelect } from '#/views/mall/product/category/components/';
-
-import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data';
-
-/** APP 閾炬帴閫夋嫨寮规 */
-defineOptions({ name: 'AppLinkSelectDialog' });
-
-const emit = defineEmits<{
- appLinkChange: [appLink: AppLink];
- change: [link: string];
-}>();
-
-const activeGroup = ref(APP_LINK_GROUP_LIST[0]?.name); // 閫変腑鐨勫垎缁勶紝榛樿閫変腑绗竴涓�
-const activeAppLink = ref({} as AppLink); // 閫変腑鐨� APP 閾炬帴
-
-const linkScrollbar = ref<HTMLDivElement>(); // 鍙充晶婊氬姩鏉�
-const groupTitleRefs = ref<HTMLInputElement[]>([]); // 鍒嗙粍鏍囬寮曠敤鍒楄〃
-const groupScrollbar = ref<HTMLDivElement>(); // 鍒嗙粍婊氬姩鏉�
-const groupBtnRefs = ref<HTMLButtonElement[]>([]); // 鍒嗙粍寮曠敤鍒楄〃
-
-const detailSelectDialog = ref<{
- id?: number;
- type?: APP_LINK_TYPE_ENUM;
-}>({
- id: undefined,
- type: undefined,
-}); // 璇︽儏閫夋嫨瀵硅瘽妗�
-
-const [Modal, modalApi] = useVbenModal({
- onConfirm() {
- emit('change', activeAppLink.value.path);
- emit('appLinkChange', activeAppLink.value);
- modalApi.close();
- },
-});
-
-const [DetailSelectModal, detailSelectModalApi] = useVbenModal({
- onConfirm() {
- detailSelectModalApi.close();
- },
-});
-
-defineExpose({ open });
-
-/** 鎵撳紑寮圭獥 */
-async function open(link: string) {
- activeAppLink.value.path = link;
- modalApi.open();
- // 婊氬姩鍒板綋鍓嶇殑閾炬帴
- const group = APP_LINK_GROUP_LIST.find((group) =>
- group.links.some((linkItem) => {
- const sameLink = isSameLink(linkItem.path, link);
- if (sameLink) {
- activeAppLink.value = { ...linkItem, path: link };
- }
- return sameLink;
- }),
- );
- if (group) {
- // 浣跨敤 nextTick 鐨勫師鍥狅細鍙兘 Dom 杩樻病鐢熸垚锛屽鑷存粴鍔ㄥけ璐�
- await nextTick();
- handleGroupSelected(group.name);
- }
-}
-
-/** 澶勭悊 APP 閾炬帴閫変腑 */
-function handleAppLinkSelected(appLink: AppLink) {
- if (!isSameLink(appLink.path, activeAppLink.value.path)) {
- activeAppLink.value = appLink;
- }
- switch (appLink.type) {
- case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: {
- detailSelectDialog.value.type = appLink.type;
- // 杩旀樉
- detailSelectDialog.value.id =
- getUrlNumberValue(
- 'id',
- `http://127.0.0.1${activeAppLink.value.path}`,
- ) || undefined;
- detailSelectModalApi.open();
- break;
- }
- default: {
- break;
- }
- }
-}
-
-/**
- * 澶勭悊鍙充晶閾炬帴鍒楄〃婊氬姩
- *
- * @param {Event} event 婊氬姩浜嬩欢
- * @param {number} event.target.scrollTop 婊氬姩鏉$殑浣嶇疆
- */
-function handleScroll(event: Event) {
- const scrollTop = (event.target as HTMLDivElement).scrollTop;
- const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
- // 鑾峰彇鏍囬鐨勪綅缃俊鎭�
- const { offsetHeight, offsetTop } = titleEl;
- // 鍒ゆ柇鏍囬鏄惁鍦ㄥ彲瑙嗚寖鍥村唴
- return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight;
- });
- // 鍙渶澶勭悊涓�娆�
- if (titleEl && activeGroup.value !== titleEl.textContent) {
- activeGroup.value = titleEl.textContent || '';
- // 鍚屾宸︿晶鐨勬粴鍔ㄦ潯浣嶇疆
- scrollToGroupBtn(activeGroup.value);
- }
-}
-
-/** 澶勭悊鍒嗙粍閫変腑 */
-function handleGroupSelected(group: string) {
- activeGroup.value = group;
- const titleRef = groupTitleRefs.value.find(
- (item: HTMLInputElement) => item.textContent === group,
- );
- if (titleRef && linkScrollbar.value) {
- // 婊氬姩鍒嗙粍鏍囬
- linkScrollbar.value.scrollTop = titleRef.offsetTop;
- }
-}
-
-/** 鑷姩婊氬姩鍒嗙粍鎸夐挳锛岀‘淇濆垎缁勬寜閽繚鎸佸湪鍙鍖哄煙鍐� */
-function scrollToGroupBtn(group: string) {
- const groupBtn = groupBtnRefs.value.find(
- (ref: HTMLButtonElement | undefined) => ref?.textContent === group,
- );
- if (groupBtn && groupScrollbar.value) {
- groupScrollbar.value.scrollTop = groupBtn.offsetTop;
- }
-}
-
-/** 鏄惁涓虹浉鍚岀殑閾炬帴锛堜笉姣旇緝鍙傛暟锛屽彧姣旇緝閾炬帴锛� */
-function isSameLink(link1: string, link2: string) {
- return link2 ? link1?.split('?')[0] === link2.split('?')[0] : false;
-}
-
-/** 澶勭悊璇︽儏閫夋嫨 */
-function handleProductCategorySelected(id: number) {
- // 鐢熸垚 activeAppLink
- const url = new URL(activeAppLink.value.path, 'http://127.0.0.1');
- url.searchParams.set('id', `${id}`);
- activeAppLink.value.path = `${url.pathname}${url.search}`;
-
- // 鍏抽棴瀵硅瘽妗嗭紝骞堕噸缃� id
- detailSelectModalApi.close();
- detailSelectDialog.value.id = undefined;
-}
-</script>
-<template>
- <Modal title="閫夋嫨閾炬帴" class="w-[65%]">
- <div class="flex h-[500px] gap-2">
- <!-- 宸︿晶鍒嗙粍鍒楄〃 -->
- <div
- class="flex h-full flex-col overflow-y-auto border-r border-gray-200 pr-2"
- ref="groupScrollbar"
- >
- <Button
- v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
- :key="groupIndex"
- class="!ml-0 mb-1 mr-4 !justify-start"
- :class="[{ active: activeGroup === group.name }]"
- ref="groupBtnRefs"
- :type="activeGroup === group.name ? 'primary' : 'default'"
- @click="handleGroupSelected(group.name)"
- >
- {{ group.name }}
- </Button>
- </div>
- <!-- 鍙充晶閾炬帴鍒楄〃 -->
- <div
- class="h-full flex-1 overflow-y-auto pl-2"
- @scroll="handleScroll"
- ref="linkScrollbar"
- >
- <div
- v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
- :key="groupIndex"
- class="mb-4 border-b border-gray-100 pb-4 last:mb-0 last:border-b-0"
- >
- <!-- 鍒嗙粍鏍囬 -->
- <div class="mb-2 font-bold" ref="groupTitleRefs">
- {{ group.name }}
- </div>
- <!-- 閾炬帴鍒楄〃 -->
- <Tooltip
- v-for="(appLink, appLinkIndex) in group.links"
- :key="appLinkIndex"
- :title="appLink.path"
- placement="bottom"
- :mouse-enter-delay="0.3"
- >
- <Button
- class="mb-2 ml-0 mr-2"
- :type="
- isSameLink(appLink.path, activeAppLink.path)
- ? 'primary'
- : 'default'
- "
- @click="handleAppLinkSelected(appLink)"
- >
- {{ appLink.name }}
- </Button>
- </Tooltip>
- </div>
- </div>
- </div>
- </Modal>
-
- <DetailSelectModal title="閫夋嫨鍒嗙被" class="w-[65%]">
- <Form class="min-h-[200px]">
- <FormItem
- label="閫夋嫨鍒嗙被"
- v-if="
- detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
- "
- >
- <ProductCategorySelect
- v-model="detailSelectDialog.id"
- :parent-id="0"
- @update:model-value="handleProductCategorySelected"
- />
- </FormItem>
- </Form>
- </DetailSelectModal>
-</template>
diff --git a/src/views/mall/promotion/components/color-input/index.vue b/src/views/mall/promotion/components/color-input/index.vue
deleted file mode 100644
index d4745a0..0000000
--- a/src/views/mall/promotion/components/color-input/index.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { Input } from 'ant-design-vue';
-
-/** 棰滆壊杈撳叆妗� */
-defineOptions({ name: 'ColorInput' });
-
-const props = defineProps({
- modelValue: {
- type: String,
- default: '',
- },
-});
-
-const emit = defineEmits(['update:modelValue']);
-
-const color = computed({
- get: () => {
- return props.modelValue;
- },
- set: (val: string) => {
- emit('update:modelValue', val);
- },
-});
-</script>
-
-<template>
- <div class="flex gap-2">
- <input
- v-model="color"
- type="color"
- class="h-8 w-12 cursor-pointer rounded border border-gray-300"
- />
- <Input v-model:value="color" class="flex-1" placeholder="璇疯緭鍏ラ鑹插��" />
- </div>
-</template>
-
-<style scoped lang="scss">
-input[type='color'] {
- &::-webkit-color-swatch-wrapper {
- padding: 2px;
- }
-
- &::-webkit-color-swatch {
- border: none;
- border-radius: 2px;
- }
-}
-</style>
diff --git a/src/views/mall/promotion/components/diy-editor/components/component-container-property.vue b/src/views/mall/promotion/components/diy-editor/components/component-container-property.vue
deleted file mode 100644
index 373449d..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/component-container-property.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-<script setup lang="ts">
-import type { Ref } from 'vue';
-
-import type { ComponentStyle } from '../util';
-
-import { useVModel } from '@vueuse/core';
-import {
- Col,
- Form,
- FormItem,
- InputNumber,
- Radio,
- RadioGroup,
- Row,
- Slider,
- TabPane,
- Tabs,
- Tree,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-/**
- * 缁勪欢瀹瑰櫒灞炴�э細鐩墠鍙宠竟閮ㄥ垎
- * 鐢ㄤ簬鍖呰9缁勪欢锛屼负缁勪欢鎻愪緵 鑳屾櫙銆佸杈硅窛銆佸唴杈硅窛銆佽竟妗嗙瓑鏍峰紡
- */
-defineOptions({ name: 'ComponentContainer' });
-
-const props = defineProps<{ modelValue: ComponentStyle }>();
-const emit = defineEmits(['update:modelValue']);
-const formData = useVModel(props, 'modelValue', emit);
-const formStyleValues = formData as unknown as Ref<Record<string, number>>;
-
-const treeData: any[] = [
- {
- label: '澶栭儴杈硅窛',
- prop: 'margin',
- children: [
- {
- label: '涓�',
- prop: 'marginTop',
- },
- {
- label: '鍙�',
- prop: 'marginRight',
- },
- {
- label: '涓�',
- prop: 'marginBottom',
- },
- {
- label: '宸�',
- prop: 'marginLeft',
- },
- ],
- },
- {
- label: '鍐呴儴杈硅窛',
- prop: 'padding',
- children: [
- {
- label: '涓�',
- prop: 'paddingTop',
- },
- {
- label: '鍙�',
- prop: 'paddingRight',
- },
- {
- label: '涓�',
- prop: 'paddingBottom',
- },
- {
- label: '宸�',
- prop: 'paddingLeft',
- },
- ],
- },
- {
- label: '杈规鍦嗚',
- prop: 'borderRadius',
- children: [
- {
- label: '涓婂乏',
- prop: 'borderTopLeftRadius',
- },
- {
- label: '涓婂彸',
- prop: 'borderTopRightRadius',
- },
- {
- label: '涓嬪彸',
- prop: 'borderBottomRightRadius',
- },
- {
- label: '涓嬪乏',
- prop: 'borderBottomLeftRadius',
- },
- ],
- },
-];
-
-function handleSliderChange(prop: string) {
- switch (prop) {
- case 'borderRadius': {
- formData.value.borderTopLeftRadius = formData.value.borderRadius;
- formData.value.borderTopRightRadius = formData.value.borderRadius;
- formData.value.borderBottomRightRadius = formData.value.borderRadius;
- formData.value.borderBottomLeftRadius = formData.value.borderRadius;
- break;
- }
- case 'margin': {
- formData.value.marginTop = formData.value.margin;
- formData.value.marginRight = formData.value.margin;
- formData.value.marginBottom = formData.value.margin;
- formData.value.marginLeft = formData.value.margin;
- break;
- }
- case 'padding': {
- formData.value.paddingTop = formData.value.padding;
- formData.value.paddingRight = formData.value.padding;
- formData.value.paddingBottom = formData.value.padding;
- formData.value.paddingLeft = formData.value.padding;
- break;
- }
- }
-}
-</script>
-
-<template>
- <Tabs>
- <!-- 姣忎釜缁勪欢鐨勮嚜瀹氫箟鍐呭 -->
- <TabPane tab="鍐呭" key="content" v-if="$slots.default">
- <slot></slot>
- </TabPane>
-
- <!-- 姣忎釜缁勪欢鐨勯�氱敤鍐呭 -->
- <TabPane tab="鏍峰紡" key="style" force-render>
- <div class="mb-2 bg-gray-100 p-2 text-sm">缁勪欢鏍峰紡锛�</div>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <Form :model="formData">
- <FormItem
- label="缁勪欢鑳屾櫙"
- name="bgType"
- :label-col="{ style: { width: '109px' } }"
- >
- <RadioGroup v-model:value="formData.bgType">
- <Radio value="color">绾壊</Radio>
- <Radio value="img">鍥剧墖</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem
- label="閫夋嫨棰滆壊"
- name="bgColor"
- :label-col="{ style: { width: '109px' } }"
- v-if="formData.bgType === 'color'"
- >
- <ColorInput v-model="formData.bgColor" />
- </FormItem>
- <FormItem
- label="涓婁紶鍥剧墖"
- name="bgImg"
- :label-col="{ style: { width: '109px' } }"
- v-else
- >
- <UploadImg
- v-model="formData.bgImg"
- :limit="1"
- :show-description="false"
- >
- <template #tip>寤鸿瀹藉害 750px</template>
- </UploadImg>
- </FormItem>
- <Tree :tree-data="treeData" default-expand-all :block-node="true">
- <template #title="{ dataRef }">
- <FormItem
- :label="dataRef.label"
- :name="dataRef.prop"
- :label-col="{
- style: { width: dataRef.children ? '80px' : '58px' },
- }"
- class="mb-0 w-full"
- >
- <Row>
- <Col :span="19">
- <Slider
- v-model:value="formStyleValues[dataRef.prop]"
- :max="100"
- :min="0"
- @change="handleSliderChange(dataRef.prop)"
- class="mr-4"
- />
- </Col>
- <Col :span="4">
- <InputNumber
- class="w-[50px]"
- :max="100"
- :min="0"
- v-model:value="formStyleValues[dataRef.prop]"
- />
- </Col>
- </Row>
- </FormItem>
- </template>
- </Tree>
- <slot name="style" :style="formData"></slot>
- </Form>
- </div>
- </TabPane>
- </Tabs>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/component-container.vue b/src/views/mall/promotion/components/diy-editor/components/component-container.vue
deleted file mode 100644
index e7f15ae..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/component-container.vue
+++ /dev/null
@@ -1,273 +0,0 @@
-<script setup lang="ts">
-import type { ComponentStyle, DiyComponent } from '../util';
-
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { Button } from 'ant-design-vue';
-
-import { VerticalButtonGroup } from '#/views/mall/promotion/components';
-
-import { components } from './mobile';
-
-/**
- * 缁勪欢瀹瑰櫒锛氱洰鍓嶅湪涓棿閮ㄥ垎
- * 鐢ㄤ簬鍖呰9缁勪欢锛屼负缁勪欢鎻愪緵 鑳屾櫙銆佸杈硅窛銆佸唴杈硅窛銆佽竟妗嗙瓑鏍峰紡
- */
-defineOptions({ name: 'ComponentContainer', components });
-
-const props = defineProps({
- component: {
- type: Object as () => DiyComponentWithStyle,
- required: true,
- },
- active: {
- type: Boolean,
- default: false,
- },
- canMoveUp: {
- type: Boolean,
- default: false,
- },
- canMoveDown: {
- type: Boolean,
- default: false,
- },
- showToolbar: {
- type: Boolean,
- default: true,
- },
-});
-
-const emits = defineEmits<{
- (e: 'move', direction: number): void;
- (e: 'copy'): void;
- (e: 'delete'): void;
-}>();
-
-type DiyComponentWithStyle = DiyComponent<any> & {
- property: { style?: ComponentStyle };
-};
-
-/** 缁勪欢鏍峰紡 */
-const style = computed(() => {
- const componentStyle = props.component.property.style;
- if (!componentStyle) {
- return {};
- }
- return {
- marginTop: `${componentStyle.marginTop || 0}px`,
- marginBottom: `${componentStyle.marginBottom || 0}px`,
- marginLeft: `${componentStyle.marginLeft || 0}px`,
- marginRight: `${componentStyle.marginRight || 0}px`,
- paddingTop: `${componentStyle.paddingTop || 0}px`,
- paddingRight: `${componentStyle.paddingRight || 0}px`,
- paddingBottom: `${componentStyle.paddingBottom || 0}px`,
- paddingLeft: `${componentStyle.paddingLeft || 0}px`,
- borderTopLeftRadius: `${componentStyle.borderTopLeftRadius || 0}px`,
- borderTopRightRadius: `${componentStyle.borderTopRightRadius || 0}px`,
- borderBottomRightRadius: `${componentStyle.borderBottomRightRadius || 0}px`,
- borderBottomLeftRadius: `${componentStyle.borderBottomLeftRadius || 0}px`,
- overflow: 'hidden',
- background:
- componentStyle.bgType === 'color'
- ? componentStyle.bgColor
- : `url(${componentStyle.bgImg})`,
- };
-});
-
-/** 绉诲姩缁勪欢 */
-const handleMoveComponent = (direction: number) => {
- emits('move', direction);
-};
-
-/** 澶嶅埗缁勪欢 */
-const handleCopyComponent = () => {
- emits('copy');
-};
-
-/** 鍒犻櫎缁勪欢 */
-const handleDeleteComponent = () => {
- emits('delete');
-};
-</script>
-<template>
- <div class="component relative cursor-move" :class="[{ active }]">
- <div :style="style">
- <component :is="component.id" :property="component.property" />
- </div>
- <div
- class="component-wrap absolute -bottom-1 -left-0.5 -right-0.5 block h-full w-full"
- >
- <!-- 宸︿晶锛氱粍浠跺悕锛堟偓娴殑灏忚创鏉★級 -->
- <div class="component-name" v-if="component.name">
- {{ component.name }}
- </div>
- <!-- 鍙充晶锛氱粍浠舵搷浣滃伐鍏锋爮 -->
- <div
- class="component-toolbar"
- v-if="showToolbar && component.name && active"
- >
- <VerticalButtonGroup size="small">
- <Button
- :disabled="!canMoveUp"
- type="primary"
- size="small"
- @click.stop="handleMoveComponent(-1)"
- v-tippy="{
- content: '涓婄Щ',
- delay: 100,
- placement: 'right',
- arrow: true,
- }"
- >
- <IconifyIcon icon="lucide:arrow-up" />
- </Button>
- <Button
- :disabled="!canMoveDown"
- type="primary"
- size="small"
- @click.stop="handleMoveComponent(1)"
- v-tippy="{
- content: '涓嬬Щ',
- delay: 100,
- placement: 'right',
- arrow: true,
- }"
- >
- <IconifyIcon icon="lucide:arrow-down" />
- </Button>
- <Button
- type="primary"
- size="small"
- @click.stop="handleCopyComponent()"
- v-tippy="{
- content: '澶嶅埗',
- delay: 100,
- placement: 'right',
- arrow: true,
- }"
- >
- <IconifyIcon icon="lucide:copy" />
- </Button>
- <Button
- type="primary"
- size="small"
- @click.stop="handleDeleteComponent()"
- v-tippy="{
- content: '鍒犻櫎',
- delay: 100,
- placement: 'right',
- arrow: true,
- }"
- >
- <IconifyIcon icon="lucide:trash-2" />
- </Button>
- </VerticalButtonGroup>
- </div>
- </div>
- </div>
-</template>
-<style scoped lang="scss">
-$active-border-width: 2px;
-$hover-border-width: 1px;
-$name-position: -85px;
-$toolbar-position: -55px;
-
-.component {
- .component-wrap {
- /* 榧犳爣鏀惧埌缁勪欢涓婃椂 */
- &:hover {
- border: $hover-border-width dashed hsl(var(--primary));
- box-shadow: 0 0 5px 0 rgb(24 144 255 / 30%);
-
- .component-name {
- top: $hover-border-width;
-
- /* 闃叉鍔犱簡杈规涔嬪悗锛屼綅缃Щ鍔� */
- left: $name-position - $hover-border-width;
- }
- }
-
- /* 宸︿晶锛氱粍浠跺悕绉� */
- .component-name {
- position: absolute;
- top: $active-border-width;
- left: $name-position;
- display: block;
- width: 80px;
- height: 25px;
- font-size: 12px;
- line-height: 25px;
- color: hsl(var(--text-color));
- text-align: center;
- background: hsl(var(--background));
- box-shadow:
- 0 0 4px #00000014,
- 0 2px 6px #0000000f,
- 0 4px 8px 2px #0000000a;
-
- /* 鍙充晶灏忎笁瑙� */
- &::after {
- position: absolute;
- top: 7.5px;
- right: -10px;
- width: 0;
- height: 0;
- content: ' ';
- border: 5px solid transparent;
- border-left-color: hsl(var(--background));
- }
- }
-
- /* 鍙充晶锛氱粍浠舵搷浣滃伐鍏锋爮 */
- .component-toolbar {
- position: absolute;
- top: 0;
- right: $toolbar-position;
- display: none;
-
- /* 宸︿晶灏忎笁瑙� */
- &::before {
- position: absolute;
- top: 10px;
- left: -10px;
- width: 0;
- height: 0;
- content: ' ';
- border: 5px solid transparent;
- border-right-color: hsl(var(--primary));
- }
- }
- }
-
- /* 閫変腑鐘舵�� */
- &.active {
- margin-bottom: 4px;
-
- .component-wrap {
- margin-bottom: $active-border-width + $active-border-width;
- border: $active-border-width solid hsl(var(--primary)) !important;
- box-shadow: 0 0 10px 0 rgb(24 144 255 / 30%);
-
- .component-name {
- top: 0 !important;
-
- /* 闃叉鍔犱簡杈规涔嬪悗锛屼綅缃Щ鍔� */
- left: $name-position - $active-border-width !important;
- color: #fff;
- background: hsl(var(--primary));
-
- &::after {
- border-left-color: hsl(var(--primary));
- }
- }
-
- .component-toolbar {
- display: block;
- }
- }
- }
-}
-</style>
diff --git a/src/views/mall/promotion/components/diy-editor/components/component-library.vue b/src/views/mall/promotion/components/diy-editor/components/component-library.vue
deleted file mode 100644
index 3ad3750..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/component-library.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<script setup lang="ts">
-import type { DiyComponent, DiyComponentLibrary } from '../util';
-
-import { ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { cloneDeep } from '../../../../../../packages/utils/src';
-
-import { Collapse } from 'ant-design-vue';
-import draggable from 'vuedraggable';
-
-import { componentConfigs } from './mobile/index';
-
-/** 缁勪欢搴擄細鐩墠宸︿晶鐨勩�愬熀纭�缁勪欢銆戙�併�愬浘鏂囩粍浠躲�戦儴鍒� */
-defineOptions({ name: 'ComponentLibrary' });
-
-/** 缁勪欢鍒楄〃 */
-const props = defineProps<{
- list: DiyComponentLibrary[];
-}>();
-
-// TODO @xingyu锛氳涓嶈鎹㈡垚 reactive锛屽拰 ele 涓�鑷�
-const groups = ref<any[]>([]); // 缁勪欢鍒嗙粍
-const extendGroups = ref<string[]>([]); // 灞曞紑鐨勬姌鍙犻潰鏉�
-
-/** 鐩戝惉 list 灞炴�э紝鎸夌収 DiyComponentLibrary 鐨� name 鍒嗙粍 */
-watch(
- () => props.list,
- () => {
- // 娓呴櫎鏃ф暟鎹�
- extendGroups.value = [];
- groups.value = [];
- // 閲嶆柊鐢熸垚鏁版嵁
- props.list.forEach((group) => {
- // 鏄惁灞曞紑鍒嗙粍
- if (group.extended) {
- extendGroups.value.push(group.name);
- }
- // 鏌ユ壘缁勪欢
- const components = group.components
- .map((name) => componentConfigs[name] as DiyComponent<any>)
- .filter(Boolean);
- if (components.length > 0) {
- groups.value.push({
- name: group.name,
- components,
- });
- }
- });
- },
- {
- immediate: true,
- },
-);
-
-/** 鍏嬮殕缁勪欢 */
-function handleCloneComponent(component: DiyComponent<any>) {
- const instance = cloneDeep(component);
- instance.uid = Date.now();
- return instance;
-}
-</script>
-
-<template>
- <div class="z-[1] max-h-[calc(80vh)] shrink-0 select-none overflow-y-auto">
- <Collapse
- v-model:active-key="extendGroups"
- :bordered="false"
- class="bg-card"
- >
- <Collapse.Panel
- v-for="(group, index) in groups"
- :key="group.name"
- :header="group.name"
- :force-render="true"
- >
- <draggable
- class="flex flex-wrap items-center"
- ghost-class="draggable-ghost"
- :item-key="index.toString()"
- :list="group.components"
- :sort="false"
- :group="{ name: 'component', pull: 'clone', put: false }"
- :clone="handleCloneComponent"
- :animation="200"
- :force-fallback="false"
- >
- <template #item="{ element }">
- <div
- class="component flex h-20 w-20 cursor-move flex-col items-center justify-center hover:border-2 hover:border-blue-500"
- >
- <IconifyIcon
- :icon="element.icon"
- class="mb-1 size-8 text-gray-500"
- />
- <span class="mt-1 text-xs">{{ element.name }}</span>
- </div>
- </template>
- </draggable>
- </Collapse.Panel>
- </Collapse>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/config.ts
deleted file mode 100644
index 40e049d..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/config.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 杞挱鍥惧睘鎬� */
-export interface CarouselProperty {
- type: 'card' | 'default'; // 绫诲瀷锛氶粯璁� | 鍗$墖
- indicator: 'dot' | 'number'; // 鎸囩ず鍣ㄦ牱寮忥細鐐� | 鏁板瓧
- autoplay: boolean; // 鏄惁鑷姩鎾斁
- interval: number; // 鎾斁闂撮殧
- height: number; // 杞挱楂樺害
- items: CarouselItemProperty[]; // 杞挱鍐呭
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 杞挱鍐呭灞炴�� */
-export interface CarouselItemProperty {
- type: 'img' | 'video'; // 绫诲瀷锛氬浘鐗� | 瑙嗛
- imgUrl: string; // 鍥剧墖閾炬帴
- videoUrl: string; // 瑙嗛閾炬帴
- url: string; // 璺宠浆閾炬帴
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'Carousel',
- name: '杞挱鍥�',
- icon: 'system-uicons:carousel',
- property: {
- type: 'default',
- indicator: 'dot',
- autoplay: false,
- interval: 3,
- height: 174,
- items: [
- {
- type: 'img',
- imgUrl: 'https://static.iocoder.cn/mall/banner-01.jpg',
- videoUrl: '',
- },
- {
- type: 'img',
- imgUrl: 'https://static.iocoder.cn/mall/banner-02.jpg',
- videoUrl: '',
- },
- ] as CarouselItemProperty[],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<CarouselProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/index.vue
deleted file mode 100644
index af486cc..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/index.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<script setup lang="ts">
-import type { CarouselProperty } from './config';
-
-import { ref } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Carousel, Image } from 'ant-design-vue';
-
-/** 杞挱鍥� */
-defineOptions({ name: 'Carousel' });
-
-defineProps<{ property: CarouselProperty }>();
-
-const currentIndex = ref(0); // 褰撳墠绱㈠紩
-
-/** 澶勭悊绱㈠紩鍙樺寲 */
-const handleIndexChange = (index: number) => {
- currentIndex.value = index + 1;
-};
-</script>
-<template>
- <!-- 鏃犲浘鐗� -->
- <div
- class="flex items-center justify-center bg-gray-300"
- :style="{
- height: property.items.length === 0 ? '250px' : `${property.height}px`,
- }"
- v-if="property.items.length === 0"
- >
- <IconifyIcon icon="tdesign:image" class="text-[120px] text-gray-800" />
- </div>
- <div v-else class="relative">
- <Carousel
- :autoplay="property.autoplay"
- :autoplay-speed="property.interval * 1000"
- :dots="property.indicator !== 'number'"
- @change="handleIndexChange"
- :style="{ height: `${property.height}px` }"
- >
- <div v-for="(item, index) in property.items" :key="index">
- <Image
- class="h-full w-full object-cover"
- :src="item.imgUrl"
- :preview="false"
- />
- </div>
- </Carousel>
- <div
- v-if="property.indicator === 'number'"
- class="absolute bottom-[10px] right-[10px] rounded-xl bg-black px-[8px] py-[2px] text-[10px] text-white opacity-40"
- >
- {{ currentIndex }} / {{ property.items.length }}
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/property.vue
deleted file mode 100644
index f00c5c1..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/carousel/property.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-<script setup lang="ts">
-import type { CarouselProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Form,
- FormItem,
- InputNumber,
- Radio,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadFile from '#/components/upload/file-upload.vue';
-import UploadImg from '#/components/upload/image-upload.vue';
-import { AppLinkInput, Draggable } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 杞挱鍥惧睘鎬ч潰鏉� */
-defineOptions({ name: 'CarouselProperty' });
-
-const props = defineProps<{ modelValue: CarouselProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :model="formData"
- :label-col="{ style: { width: '80px' } }"
- label-align="right"
- >
- <p class="text-base font-bold">鏍峰紡璁剧疆锛�</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <FormItem label="鏍峰紡">
- <RadioGroup v-model:value="formData.type">
- <Tooltip class="item" title="榛樿" placement="bottom">
- <RadioButton value="default">
- <IconifyIcon icon="system-uicons:carousel" class="size-6" />
- </RadioButton>
- </Tooltip>
- <Tooltip class="item" title="鍗$墖" placement="bottom">
- <RadioButton value="card">
- <IconifyIcon icon="ic:round-view-carousel" class="size-6" />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="楂樺害">
- <InputNumber
- v-model:value="formData.height"
- class="mr-[10px] !w-1/2"
- />
- px
- </FormItem>
- <FormItem label="鎸囩ず鍣�">
- <RadioGroup v-model:value="formData.indicator">
- <Radio value="dot">灏忓渾鐐�</Radio>
- <Radio value="number">鏁板瓧</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem label="鏄惁杞挱">
- <Switch v-model:checked="formData.autoplay" />
- </FormItem>
- <FormItem label="鎾斁闂撮殧" v-if="formData.autoplay">
- <Slider
- v-model:value="formData.interval"
- :max="10"
- :min="0.5"
- :step="0.5"
- />
- <p class="text-info">鍗曚綅锛氱</p>
- </FormItem>
- </div>
- <p class="text-base font-bold">鍐呭璁剧疆锛�</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <Draggable v-model="formData.items" :empty-item="{ type: 'img' }">
- <template #default="{ element }">
- <FormItem label="绫诲瀷" name="type">
- <RadioGroup v-model:value="element.type">
- <Radio value="img">鍥剧墖</Radio>
- <Radio value="video">瑙嗛</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍥剧墖" v-if="element.type === 'img'">
- <UploadImg
- v-model="element.imgUrl"
- draggable="false"
- height="80px"
- width="100%"
- class="min-w-20"
- :show-description="false"
- />
- </FormItem>
- <template v-else>
- <FormItem label="灏侀潰">
- <UploadImg
- v-model="element.imgUrl"
- draggable="false"
- :show-description="false"
- height="80px"
- width="100%"
- class="min-w-20"
- />
- </FormItem>
- <FormItem label="瑙嗛">
- <UploadFile
- v-model="element.videoUrl"
- :file-type="['mp4']"
- :limit="1"
- :file-size="100"
- class="min-w-20"
- />
- </FormItem>
- </template>
- <FormItem label="閾炬帴">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- </template>
- </Draggable>
- </div>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/component.tsx b/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/component.tsx
deleted file mode 100644
index 1d785a9..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/component.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-/* eslint-disable vue/one-component-per-file */
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { defineComponent } from 'vue';
-
-import {
- CouponTemplateValidityTypeEnum,
- PromotionDiscountTypeEnum,
-} from '../../../../../../../../packages/constants/src';
-import { floatToFixed2, formatDate } from '../../../../../../../../packages/utils/src';
-
-/** 鏈夋晥鏈� */
-export const CouponValidTerm = defineComponent({
- name: 'CouponValidTerm',
- props: {
- coupon: {
- type: Object as () => MallCouponTemplateApi.CouponTemplate,
- required: true,
- },
- },
- setup(props) {
- const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
- const text =
- coupon.validityType === CouponTemplateValidityTypeEnum.DATE.type
- ? `鏈夋晥鏈燂細${formatDate(coupon.validStartTime, 'YYYY-MM-DD')} 鑷� ${formatDate(
- coupon.validEndTime,
- 'YYYY-MM-DD',
- )}`
- : `棰嗗彇鍚庣 ${coupon.fixedStartTerm} - ${coupon.fixedEndTerm} 澶╁唴鍙敤`;
- return () => <div>{text}</div>;
- },
-});
-
-/** 浼樻儬鍊� */
-export const CouponDiscount = defineComponent({
- name: 'CouponDiscount',
- props: {
- coupon: {
- type: Object as () => MallCouponTemplateApi.CouponTemplate,
- required: true,
- },
- },
- setup(props) {
- const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
- // 鎶樻墸
- let value = `${(coupon.discountPercent ?? 0) / 10}`;
- let suffix = ' 鎶�';
- // 婊″噺
- if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
- value = floatToFixed2(coupon.discountPrice);
- suffix = ' 鍏�';
- }
- return () => (
- <div>
- <span class={'text-20px font-bold'}>{value}</span>
- <span>{suffix}</span>
- </div>
- );
- },
-});
-
-/** 浼樻儬鎻忚堪 */
-export const CouponDiscountDesc = defineComponent({
- name: 'CouponDiscountDesc',
- props: {
- coupon: {
- type: Object as () => MallCouponTemplateApi.CouponTemplate,
- required: true,
- },
- },
- setup(props) {
- const coupon = props.coupon as MallCouponTemplateApi.CouponTemplate;
- // 浣跨敤鏉′欢
- const useCondition =
- coupon.usePrice > 0 ? `婊�${floatToFixed2(coupon.usePrice)}鍏冿紝` : '';
- // 浼樻儬鎻忚堪
- const discountDesc =
- coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
- ? `鍑�${floatToFixed2(coupon.discountPrice)}鍏僠
- : `鎵�${(coupon.discountPercent ?? 0) / 10}鎶榒;
- return () => (
- <div>
- <span>{useCondition}</span>
- <span>{discountDesc}</span>
- </div>
- );
- },
-});
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/config.ts
deleted file mode 100644
index 723461b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/config.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 浼樻儬鍔靛崱鐗囧睘鎬� */
-export interface CouponCardProperty {
- columns: number; // 鍒楁暟
- bgImg: string; // 鑳屾櫙鍥�
- textColor: string; // 鏂囧瓧棰滆壊
- button: {
- bgColor: string; // 鑳屾櫙棰滆壊
- color: string; // 鏂囧瓧棰滆壊
- }; // 鎸夐挳鏍峰紡
- space: number; // 闂磋窛
- couponIds: number[]; // 浼樻儬鍒哥紪鍙峰垪琛�
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'CouponCard',
- name: '浼樻儬鍒�',
- icon: 'ep:ticket',
- property: {
- columns: 1,
- bgImg: '',
- textColor: '#E9B461',
- button: {
- color: '#434343',
- bgColor: '',
- },
- space: 0,
- couponIds: [],
- style: {
- bgType: 'color',
- bgColor: '',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<CouponCardProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/index.vue
deleted file mode 100644
index 2cb2f13..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/index.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<script setup lang="ts">
-import type { CouponCardProperty } from './config';
-
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { onMounted, ref, watch } from 'vue';
-
-import { getCouponTemplateList } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import {
- CouponDiscount,
- CouponDiscountDesc,
- CouponValidTerm,
-} from './component';
-
-/** 浼樻儬鍔靛崱鐗� */
-defineOptions({ name: 'CouponCard' });
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps<{ property: CouponCardProperty }>();
-
-const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 浼樻儬鍔靛垪琛�
-const phoneWidth = ref(375); // 鎵嬫満瀹藉害
-const containerRef = ref(); // 瀹瑰櫒寮曠敤
-const scrollbarWidth = ref('100%'); // 婊氬姩鏉″搴�
-const couponWidth = ref(375); // 浼樻儬鍒稿搴�
-
-/** 鐩戝惉浼樻儬鍒� ID 鍙樺寲锛屽姞杞戒紭鎯犲埜鍒楄〃 */
-watch(
- () => props.property.couponIds,
- async () => {
- if (props.property.couponIds?.length > 0) {
- couponList.value = await getCouponTemplateList(props.property.couponIds);
- }
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-/** 璁$畻甯冨眬鍙傛暟 */
-watch(
- () => [props.property, phoneWidth, couponList.value.length],
- () => {
- // 姣忓垪鐨勫搴︿负锛氾紙鎬诲搴� - 闂磋窛 * (鍒楁暟 - 1)锛�/ 鍒楁暟
- couponWidth.value =
- (phoneWidth.value - props.property.space * (props.property.columns - 1)) /
- props.property.columns;
- // 鏄剧ず婊氬姩鏉�
- scrollbarWidth.value = `${
- couponWidth.value * couponList.value.length +
- props.property.space * (couponList.value.length - 1)
- }px`;
- },
- { immediate: true, deep: true },
-);
-
-/** 鍒濆鍖� */
-onMounted(() => {
- phoneWidth.value = containerRef.value?.offsetWidth || 375;
-});
-</script>
-<template>
- <div class="z-10 min-h-8 overflow-x-auto" ref="containerRef">
- <div
- class="flex flex-row text-xs"
- :style="{
- gap: `${property.space}px`,
- width: scrollbarWidth,
- }"
- >
- <div
- class="box-content"
- :style="{
- background: property.bgImg
- ? `url(${property.bgImg}) 100% center / 100% 100% no-repeat`
- : '#fff',
- width: `${couponWidth}px`,
- color: property.textColor,
- }"
- v-for="(coupon, index) in couponList"
- :key="index"
- >
- <!-- 甯冨眬 1锛�1 鍒�-->
- <div
- v-if="property.columns === 1"
- class="ml-4 flex flex-row justify-between p-2"
- >
- <div class="flex flex-col justify-evenly gap-1">
- <CouponDiscount :coupon="coupon" />
- <CouponDiscountDesc :coupon="coupon" />
- <CouponValidTerm :coupon="coupon" />
- </div>
- <div class="flex flex-col justify-evenly">
- <div
- class="rounded-full px-2 py-0.5"
- :style="{
- color: property.button.color,
- background: property.button.bgColor,
- }"
- >
- 绔嬪嵆棰嗗彇
- </div>
- </div>
- </div>
- <!-- 甯冨眬 2锛�2 鍒�-->
- <div
- v-else-if="property.columns === 2"
- class="ml-4 flex flex-row justify-between p-2"
- >
- <div class="flex flex-col justify-evenly gap-1">
- <CouponDiscount :coupon="coupon" />
- <CouponDiscountDesc :coupon="coupon" />
- <div v-if="coupon.totalCount >= 0">
- 浠呭墿锛歿{ coupon.totalCount - coupon.takeCount }}寮�
- </div>
- <div v-else-if="coupon.totalCount === -1">浠呭墿锛氫笉闄愬埗</div>
- </div>
- <div class="flex flex-col">
- <div
- class="h-full w-5 rounded-full px-0.5 py-2 text-center"
- :style="{
- color: property.button.color,
- background: property.button.bgColor,
- }"
- >
- 绔嬪嵆棰嗗彇
- </div>
- </div>
- </div>
- <!-- 甯冨眬 3锛�3 鍒�-->
- <div v-else class="flex flex-col items-center justify-around gap-1 p-1">
- <CouponDiscount :coupon="coupon" />
- <CouponDiscountDesc :coupon="coupon" />
- <div
- class="rounded-full px-2 py-0.5"
- :style="{
- color: property.button.color,
- background: property.button.bgColor,
- }"
- >
- 绔嬪嵆棰嗗彇
- </div>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/property.vue
deleted file mode 100644
index 37955dd..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/coupon-card/property.vue
+++ /dev/null
@@ -1,180 +0,0 @@
-<script setup lang="ts">
-import type { CouponCardProperty } from './config';
-
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { ref, watch } from 'vue';
-
-import { useVbenModal } from '../../../../../../../../packages/effects/common-ui/src';
-import {
- CouponTemplateTakeTypeEnum,
- PromotionDiscountTypeEnum,
-} from '../../../../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-import { floatToFixed2 } from '../../../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Button,
- Form,
- FormItem,
- RadioButton,
- RadioGroup,
- Slider,
- Tooltip,
- Typography,
-} from 'ant-design-vue';
-
-import { getCouponTemplateList } from '#/api/mall/promotion/coupon/couponTemplate';
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-import CouponSelect from '#/views/mall/promotion/coupon/components/select.vue';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 浼樻儬鍒稿崱鐗囧睘鎬ч潰鏉� */
-defineOptions({ name: 'CouponCardProperty' });
-
-const props = defineProps<{ modelValue: CouponCardProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-const couponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 宸查�夋嫨鐨勪紭鎯犲埜鍒楄〃
-
-const [CouponSelectModal, couponSelectModalApi] = useVbenModal({
- connectedComponent: CouponSelect,
- destroyOnClose: true,
-});
-
-/** 娣诲姞浼樻儬鍔� */
-const handleAddCoupon = () => {
- couponSelectModalApi.open();
-};
-
-/** 澶勭悊浼樻儬鍔甸�夋嫨 */
-const handleCouponSelect = (
- selectedCoupons: MallCouponTemplateApi.CouponTemplate[],
-) => {
- couponList.value = selectedCoupons;
- formData.value.couponIds = selectedCoupons.map((coupon) => coupon.id);
-};
-
-/** 鐩戝惉浼樻儬鍒� ID 鍙樺寲锛屽姞杞戒紭鎯犲埜鍒楄〃 */
-watch(
- () => formData.value.couponIds,
- async () => {
- if (formData.value.couponIds?.length > 0) {
- couponList.value = await getCouponTemplateList(formData.value.couponIds);
- }
- },
- {
- immediate: true,
- deep: true,
- },
-);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData">
- <p class="text-base font-bold">浼樻儬鍒稿垪琛細</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <div
- v-for="(coupon, index) in couponList"
- :key="index"
- class="flex items-center justify-between"
- >
- <Typography>
- <Typography.Title :level="5">
- {{ coupon.name }}
- </Typography.Title>
- <Typography.Text type="secondary">
- <span v-if="coupon.usePrice > 0">
- 婊{ floatToFixed2(coupon.usePrice) }}鍏冿紝
- </span>
- <span
- v-if="
- coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
- "
- >
- 鍑弡{ floatToFixed2(coupon.discountPrice) }}鍏�
- </span>
- <span v-else> 鎵搟{ coupon.discountPercent }}鎶� </span>
- </Typography.Text>
- </Typography>
- </div>
- <FormItem>
- <Button
- @click="handleAddCoupon"
- type="primary"
- ghost
- class="mt-2 w-full"
- >
- <IconifyIcon icon="lucide:plus" />
- 娣诲姞
- </Button>
- </FormItem>
- </div>
- <p class="text-base font-bold">浼樻儬鍒告牱寮忥細</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <FormItem label="鍒楁暟" name="type">
- <RadioGroup v-model:value="formData.columns">
- <Tooltip title="涓�鍒�" placement="bottom">
- <RadioButton :value="1">
- <IconifyIcon
- icon="fluent:text-column-one-24-filled"
- class="inset-0 size-6 items-center"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="浜屽垪" placement="bottom">
- <RadioButton :value="2">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="涓夊垪" placement="bottom">
- <RadioButton :value="3">
- <IconifyIcon
- icon="fluent:text-column-three-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鑳屾櫙鍥剧墖" name="bgImg">
- <UploadImg
- v-model="formData.bgImg"
- height="80px"
- width="100%"
- class="min-w-[160px]"
- :show-description="false"
- />
- </FormItem>
- <FormItem label="鏂囧瓧棰滆壊" name="textColor">
- <ColorInput v-model="formData.textColor" />
- </FormItem>
- <FormItem label="鎸夐挳鑳屾櫙" name="button.bgColor">
- <ColorInput v-model="formData.button.bgColor" />
- </FormItem>
- <FormItem label="鎸夐挳鏂囧瓧" name="button.color">
- <ColorInput v-model="formData.button.color" />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </div>
- </Form>
- </ComponentContainerProperty>
-
- <!-- 浼樻儬鍒搁�夋嫨 -->
- <CouponSelectModal
- :take-type="CouponTemplateTakeTypeEnum.USER.type"
- @success="handleCouponSelect"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/divider/config.ts
deleted file mode 100644
index 3ab6eef..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { DiyComponent } from '../../../util';
-
-/** 鍒嗗壊绾垮睘鎬� */
-export interface DividerProperty {
- height: number; // 楂樺害
- lineWidth: number; // 绾垮
- paddingType: 'horizontal' | 'none'; // 杈硅窛绫诲瀷
- lineColor: string; // 棰滆壊
- borderType: 'dashed' | 'dotted' | 'none' | 'solid'; // 绫诲瀷
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'Divider',
- name: '鍒嗗壊绾�',
- icon: 'tdesign:component-divider-vertical',
- property: {
- height: 30,
- lineWidth: 1,
- paddingType: 'none',
- lineColor: '#dcdfe6',
- borderType: 'solid',
- },
-} as DiyComponent<DividerProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/divider/index.vue
deleted file mode 100644
index e07eda8..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/index.vue
+++ /dev/null
@@ -1,27 +0,0 @@
-<script setup lang="ts">
-import type { DividerProperty } from './config';
-
-/** 椤甸潰椤堕儴瀵艰埅鏍� */
-defineOptions({ name: 'Divider' });
-
-defineProps<{ property: DividerProperty }>();
-</script>
-
-<template>
- <div
- class="flex items-center"
- :style="{
- height: `${property.height}px`,
- }"
- >
- <div
- class="w-full"
- :style="{
- borderTopStyle: property.borderType,
- borderTopColor: property.lineColor,
- borderTopWidth: `${property.lineWidth}px`,
- margin: property.paddingType === 'none' ? '0' : '0px 16px',
- }"
- ></div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/divider/property.vue
deleted file mode 100644
index 59730ad..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/divider/property.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script setup lang="ts">
-import type { DividerProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Form,
- FormItem,
- RadioButton,
- RadioGroup,
- Slider,
- Tooltip,
-} from 'ant-design-vue';
-
-import { ColorInput } from '#/views/mall/promotion/components';
-
-/** 瀵艰埅鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'DividerProperty' });
-
-const props = defineProps<{ modelValue: DividerProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const BORDER_TYPES = [
- {
- icon: 'vaadin:line-h',
- text: '瀹炵嚎',
- type: 'solid',
- },
- {
- icon: 'tabler:line-dashed',
- text: '铏氱嚎',
- type: 'dashed',
- },
- {
- icon: 'tabler:line-dotted',
- text: '鐐圭嚎',
- type: 'dotted',
- },
- {
- icon: 'entypo:progress-empty',
- text: '鏃�',
- type: 'none',
- },
-]; // 绾跨被鍨�
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <Form :model="formData">
- <FormItem label="楂樺害" name="height">
- <Slider v-model:value="formData.height" :min="1" :max="100" />
- </FormItem>
- <FormItem label="閫夋嫨鏍峰紡" name="borderType">
- <RadioGroup v-model:value="formData!.borderType">
- <Tooltip
- placement="top"
- v-for="(item, index) in BORDER_TYPES"
- :key="index"
- :title="item.text"
- >
- <RadioButton :value="item.type">
- <IconifyIcon
- :icon="item.icon"
- class="inset-0 size-6 items-center"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <template v-if="formData.borderType !== 'none'">
- <FormItem label="绾垮" name="lineWidth">
- <Slider v-model:value="formData.lineWidth" :min="1" :max="30" />
- </FormItem>
- <FormItem label="宸﹀彸杈硅窛" name="paddingType">
- <RadioGroup v-model:value="formData!.paddingType">
- <Tooltip title="鏃犺竟璺�" placement="top">
- <RadioButton value="none">
- <IconifyIcon
- icon="tabler:box-padding"
- class="inset-0 size-6 items-center"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="宸﹀彸鐣欒竟" placement="top">
- <RadioButton value="horizontal">
- <IconifyIcon
- icon="vaadin:padding"
- class="inset-0 size-6 items-center"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="棰滆壊">
- <ColorInput v-model="formData.lineColor" />
- </FormItem>
- </template>
- </Form>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/config.ts
deleted file mode 100644
index d93b1f4..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/config.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { DiyComponent } from '../../../util';
-
-/** 鎮诞鎸夐挳灞炴�� */
-export interface FloatingActionButtonProperty {
- direction: 'horizontal' | 'vertical'; // 灞曞紑鏂瑰悜
- showText: boolean; // 鏄惁鏄剧ず鏂囧瓧
- list: FloatingActionButtonItemProperty[]; // 鎸夐挳鍒楄〃
-}
-
-/** 鎮诞鎸夐挳椤瑰睘鎬� */
-export interface FloatingActionButtonItemProperty {
- imgUrl: string; // 鍥剧墖鍦板潃
- url: string; // 璺宠浆杩炴帴
- text: string; // 鏂囧瓧
- textColor: string; // 鏂囧瓧棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'FloatingActionButton',
- name: '鎮诞鎸夐挳',
- icon: 'tabler:float-right',
- position: 'fixed',
- property: {
- direction: 'vertical',
- showText: true,
- list: [{ textColor: '#fff' }],
- },
-} as DiyComponent<FloatingActionButtonProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/index.vue
deleted file mode 100644
index d11fce3..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/index.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<script setup lang="ts">
-import type { FloatingActionButtonProperty } from './config';
-
-import { ref } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Button, Image } from 'ant-design-vue';
-
-/** 鎮诞鎸夐挳 */
-defineOptions({ name: 'FloatingActionButton' });
-
-/** 瀹氫箟灞炴�� */
-defineProps<{ property: FloatingActionButtonProperty }>();
-
-const expanded = ref(false); // 鏄惁灞曞紑
-
-/** 澶勭悊灞曞紑/鎶樺彔 */
-function handleToggleFab() {
- expanded.value = !expanded.value;
-}
-
-function handleActive() {
- expanded.value = false;
-}
-</script>
-<template>
- <div
- class="absolute bottom-8 right-[calc(50%-384px/2+32px)] z-20 flex items-center gap-3"
- :class="[
- {
- 'flex-row': property.direction === 'horizontal',
- 'flex-col': property.direction === 'vertical',
- },
- ]"
- >
- <template v-if="expanded">
- <div
- v-for="(item, index) in property.list"
- :key="index"
- class="flex flex-col items-center"
- @click="handleActive"
- >
- <Image :src="item.imgUrl" :width="28" :height="28" :preview="false">
- <template #error>
- <div class="flex h-full w-full items-center justify-center">
- <IconifyIcon
- icon="lucide:image"
- :color="item.textColor"
- class="inset-0 size-6 items-center"
- />
- </div>
- </template>
- </Image>
- <span
- v-if="property.showText"
- class="mt-1 text-xs"
- :style="{ color: item.textColor }"
- >
- {{ item.text }}
- </span>
- </div>
- </template>
- <!-- todo: @owen 浣跨敤APP涓婚鑹� -->
- <Button type="primary" size="large" shape="circle" @click="handleToggleFab">
- <IconifyIcon
- icon="lucide:plus"
- class="transition-transform duration-300"
- :class="expanded ? 'rotate-[135deg]' : 'rotate-0'"
- />
- </Button>
- </div>
- <!-- 妯℃�佽儗鏅細灞曞紑鏃舵樉绀猴紝鐐瑰嚮鍚庢姌鍙� -->
- <div
- v-if="expanded"
- class="absolute left-[calc(50%-375px/2)] top-0 z-[11] h-full w-[375px] bg-black/40"
- @click="handleToggleFab"
- ></div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/property.vue
deleted file mode 100644
index 1b633ce..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/floating-action-button/property.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-<script setup lang="ts">
-import type { FloatingActionButtonProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Radio, RadioGroup, Switch } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- Draggable,
- InputWithColor,
-} from '#/views/mall/promotion/components';
-
-/** 鎮诞鎸夐挳灞炴�ч潰鏉� */
-defineOptions({ name: 'FloatingActionButtonProperty' });
-
-const props = defineProps<{ modelValue: FloatingActionButtonProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <Form :model="formData">
- <p class="text-base font-bold">鎸夐挳閰嶇疆锛�</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <FormItem label="灞曞紑鏂瑰悜" name="direction">
- <RadioGroup v-model:value="formData.direction">
- <Radio value="vertical">鍨傜洿</Radio>
- <Radio value="horizontal">姘村钩</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem label="鏄剧ず鏂囧瓧" name="showText">
- <Switch v-model:checked="formData.showText" />
- </FormItem>
- </div>
- <p class="text-base font-bold">鎸夐挳鍒楄〃锛�</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
- <template #default="{ element, index }">
- <FormItem label="鍥炬爣" :name="`list[${index}].imgUrl`">
- <UploadImg
- v-model="element.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- />
- </FormItem>
- <FormItem label="鏂囧瓧" :name="`list[${index}].text`">
- <InputWithColor
- v-model="element.text"
- v-model:color="element.textColor"
- />
- </FormItem>
- <FormItem label="璺宠浆閾炬帴" :name="`list[${index}].url`">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- </template>
- </Draggable>
- </div>
- </Form>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/controller.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/controller.ts
deleted file mode 100644
index a688311..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/controller.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import type { CSSProperties } from 'vue';
-
-import type { HotZoneItemProperty } from '../../config';
-
-export const HOT_ZONE_MIN_SIZE = 100; // 鐑尯鐨勬渶灏忓楂�
-
-/** 鎺у埗鐨勭被鍨� */
-export enum CONTROL_TYPE_ENUM {
- LEFT,
- TOP,
- WIDTH,
- HEIGHT,
-}
-
-/** 瀹氫箟鐑尯鐨勬帶鍒剁偣 */
-export interface ControlDot {
- position: string;
- types: CONTROL_TYPE_ENUM[];
- style: CSSProperties;
-}
-
-/** 鐑尯鐨� 8 涓帶鍒剁偣 */
-export const CONTROL_DOT_LIST = [
- {
- position: '宸︿笂瑙�',
- types: [
- CONTROL_TYPE_ENUM.LEFT,
- CONTROL_TYPE_ENUM.TOP,
- CONTROL_TYPE_ENUM.WIDTH,
- CONTROL_TYPE_ENUM.HEIGHT,
- ],
- style: { left: '-5px', top: '-5px', cursor: 'nwse-resize' },
- },
- {
- position: '涓婃柟涓棿',
- types: [CONTROL_TYPE_ENUM.TOP, CONTROL_TYPE_ENUM.HEIGHT],
- style: {
- left: '50%',
- top: '-5px',
- cursor: 'n-resize',
- transform: 'translateX(-50%)',
- },
- },
- {
- position: '鍙充笂瑙�',
- types: [
- CONTROL_TYPE_ENUM.TOP,
- CONTROL_TYPE_ENUM.WIDTH,
- CONTROL_TYPE_ENUM.HEIGHT,
- ],
- style: { right: '-5px', top: '-5px', cursor: 'nesw-resize' },
- },
- {
- position: '鍙充晶涓棿',
- types: [CONTROL_TYPE_ENUM.WIDTH],
- style: {
- right: '-5px',
- top: '50%',
- cursor: 'e-resize',
- transform: 'translateX(-50%)',
- },
- },
- {
- position: '鍙充笅瑙�',
- types: [CONTROL_TYPE_ENUM.WIDTH, CONTROL_TYPE_ENUM.HEIGHT],
- style: { right: '-5px', bottom: '-5px', cursor: 'nwse-resize' },
- },
- {
- position: '涓嬫柟涓棿',
- types: [CONTROL_TYPE_ENUM.HEIGHT],
- style: {
- left: '50%',
- bottom: '-5px',
- cursor: 's-resize',
- transform: 'translateX(-50%)',
- },
- },
- {
- position: '宸︿笅瑙�',
- types: [
- CONTROL_TYPE_ENUM.LEFT,
- CONTROL_TYPE_ENUM.WIDTH,
- CONTROL_TYPE_ENUM.HEIGHT,
- ],
- style: { left: '-5px', bottom: '-5px', cursor: 'nesw-resize' },
- },
- {
- position: '宸︿晶涓棿',
- types: [CONTROL_TYPE_ENUM.LEFT, CONTROL_TYPE_ENUM.WIDTH],
- style: {
- left: '-5px',
- top: '50%',
- cursor: 'w-resize',
- transform: 'translateX(-50%)',
- },
- },
-] as ControlDot[];
-
-// region 鐑尯鐨勭缉鏀�
-export const HOT_ZONE_SCALE_RATE = 2; // 鐑尯鐨勭缉鏀炬瘮渚�
-
-/** 缂╁皬锛氱缉鍥為�傚悎鎵嬫満灞忓箷鐨勫ぇ灏� */
-export function zoomOut(list?: HotZoneItemProperty[]) {
- return (
- list?.map((hotZone) => ({
- ...hotZone,
- left: (hotZone.left /= HOT_ZONE_SCALE_RATE),
- top: (hotZone.top /= HOT_ZONE_SCALE_RATE),
- width: (hotZone.width /= HOT_ZONE_SCALE_RATE),
- height: (hotZone.height /= HOT_ZONE_SCALE_RATE),
- })) || []
- );
-}
-
-/** 鏀惧ぇ锛氫綔鐢ㄦ槸涓轰簡鏂逛究鍦ㄧ數鑴戝睆骞曚笂缂栬緫 */
-export function zoomIn(list?: HotZoneItemProperty[]) {
- return (
- list?.map((hotZone) => ({
- ...hotZone,
- left: (hotZone.left *= HOT_ZONE_SCALE_RATE),
- top: (hotZone.top *= HOT_ZONE_SCALE_RATE),
- width: (hotZone.width *= HOT_ZONE_SCALE_RATE),
- height: (hotZone.height *= HOT_ZONE_SCALE_RATE),
- })) || []
- );
-}
-
-// endregion
-
-/**
- * 灏佽鐑尯鎷栨嫿
- *
- * 娉細涓轰粈涔堜笉浣跨敤vueuse鐨剈seDraggable銆傚湪鏈満鏅笅锛屽叾浣跨敤鏂瑰紡姣旇緝澶嶆潅
- * @param hotZone 鐑尯
- * @param downEvent 榧犳爣鎸変笅浜嬩欢
- * @param callback 鍥炶皟鍑芥暟
- */
-export const useDraggable = (
- hotZone: HotZoneItemProperty,
- downEvent: MouseEvent,
- callback: (
- left: number,
- top: number,
- width: number,
- height: number,
- moveWidth: number,
- moveHeight: number,
- ) => void,
-) => {
- // 闃绘浜嬩欢鍐掓场
- downEvent.stopPropagation();
-
- // 绉诲姩鍓嶇殑榧犳爣鍧愭爣
- const { clientX: startX, clientY: startY } = downEvent;
- // 绉诲姩鍓嶇殑鐑尯鍧愭爣銆佸ぇ灏�
- const { left, top, width, height } = hotZone;
-
- // 鐩戝惉榧犳爣绉诲姩
- const handleMouseMove = (e: MouseEvent) => {
- // 绉诲姩瀹藉害
- const moveWidth = e.clientX - startX;
- // 绉诲姩楂樺害
- const moveHeight = e.clientY - startY;
- // 绉诲姩鍥炶皟
- callback(left, top, width, height, moveWidth, moveHeight);
- };
-
- // 鏉惧紑榧犳爣鍚庯紝缁撴潫鎷栨嫿
- const handleMouseUp = () => {
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
- };
-
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
-};
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/index.vue
deleted file mode 100644
index afb5536..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/components/hot-zone-edit-dialog/index.vue
+++ /dev/null
@@ -1,244 +0,0 @@
-<script setup lang="ts">
-import type { HotZoneItemProperty } from '../../config';
-import type { ControlDot } from './controller';
-
-import type { AppLink } from '#/views/mall/promotion/components/app-link-input/data';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../../../../../../../packages/icons/src';
-
-import { Button, Image } from 'ant-design-vue';
-
-import { AppLinkSelectDialog } from '#/views/mall/promotion/components';
-
-import {
- CONTROL_DOT_LIST,
- CONTROL_TYPE_ENUM,
- HOT_ZONE_MIN_SIZE,
- useDraggable,
- zoomIn,
- zoomOut,
-} from './controller';
-
-/** 鐑尯缂栬緫瀵硅瘽妗� */
-defineOptions({ name: 'HotZoneEditDialog' });
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps({
- modelValue: {
- type: Array<HotZoneItemProperty>,
- default: () => [],
- },
- imgUrl: {
- type: String,
- default: '',
- },
-});
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = ref<HotZoneItemProperty[]>([]);
-
-const [Modal, modalApi] = useVbenModal({
- showCancelButton: false,
- onConfirm() {
- const list = zoomOut(formData.value);
- emit('update:modelValue', list);
- modalApi.close();
- },
-});
-
-/** 鎵撳紑寮圭獥 */
-function open() {
- // 鏀惧ぇ
- formData.value = zoomIn(props.modelValue);
- modalApi.open();
-}
-
-defineExpose({ open }); // 鎻愪緵 open 鏂规硶锛岀敤浜庢墦寮�寮圭獥
-
-const container = ref<HTMLDivElement>(); // 鐑尯瀹瑰櫒
-
-/** 澧炲姞鐑尯 */
-function handleAdd() {
- formData.value.push({
- width: HOT_ZONE_MIN_SIZE,
- height: HOT_ZONE_MIN_SIZE,
- top: 0,
- left: 0,
- } as HotZoneItemProperty);
-}
-
-/** 鍒犻櫎鐑尯 */
-function handleRemove(hotZone: HotZoneItemProperty) {
- formData.value = formData.value.filter((item) => item !== hotZone);
-}
-
-/** 绉诲姩鐑尯 */
-function handleMove(item: HotZoneItemProperty, e: MouseEvent) {
- useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
- setLeft(item, left + moveWidth);
- setTop(item, top + moveHeight);
- });
-}
-
-/** 璋冩暣鐑尯澶у皬銆佷綅缃� */
-function handleResize(
- item: HotZoneItemProperty,
- ctrlDot: ControlDot,
- e: MouseEvent,
-) {
- useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
- ctrlDot.types.forEach((type) => {
- switch (type) {
- case CONTROL_TYPE_ENUM.HEIGHT: {
- {
- // 宸︾Щ鏃讹紝瀹藉害涓哄噺灏�
- const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.TOP)
- ? -1
- : 1;
- setHeight(item, height + moveHeight * direction);
- }
- break;
- }
- case CONTROL_TYPE_ENUM.LEFT: {
- setLeft(item, left + moveWidth);
- break;
- }
- case CONTROL_TYPE_ENUM.TOP: {
- setTop(item, top + moveHeight);
- break;
- }
- case CONTROL_TYPE_ENUM.WIDTH: {
- {
- // 涓婄Щ鏃讹紝楂樺害涓哄噺灏�
- const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.LEFT)
- ? -1
- : 1;
- setWidth(item, width + moveWidth * direction);
- }
- break;
- }
- }
- });
- });
-}
-
-/** 璁剧疆 X 杞村潗鏍� */
-function setLeft(item: HotZoneItemProperty, left: number) {
- // 涓嶈兘瓒呭嚭瀹瑰櫒
- if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
- item.left = left;
- }
-}
-
-/** 璁剧疆Y杞村潗鏍� */
-function setTop(item: HotZoneItemProperty, top: number) {
- // 涓嶈兘瓒呭嚭瀹瑰櫒
- if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
- item.top = top;
- }
-}
-
-/** 璁剧疆瀹藉害 */
-const setWidth = (item: HotZoneItemProperty, width: number) => {
- // 涓嶈兘灏忎簬鏈�灏忓搴� && 涓嶈兘瓒呭嚭瀹瑰櫒鍙宠竟
- if (
- width >= HOT_ZONE_MIN_SIZE &&
- item.left + width <= container.value!.offsetWidth
- ) {
- item.width = width;
- }
-};
-
-/** 璁剧疆楂樺害 */
-const setHeight = (item: HotZoneItemProperty, height: number) => {
- // 涓嶈兘灏忎簬鏈�灏忛珮搴� && 涓嶈兘瓒呭嚭瀹瑰櫒搴曢儴
- if (
- height >= HOT_ZONE_MIN_SIZE &&
- item.top + height <= container.value!.offsetHeight
- ) {
- item.height = height;
- }
-};
-
-const activeHotZone = ref<HotZoneItemProperty>();
-const appLinkDialogRef = ref();
-
-/** 鏄剧ず App 閾炬帴閫夋嫨瀵硅瘽妗� */
-const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
- activeHotZone.value = hotZone;
- appLinkDialogRef.value.open(hotZone.url);
-};
-
-/** 澶勭悊 App 閾炬帴閫夋嫨鍙樻洿 */
-const handleAppLinkChange = (appLink: AppLink) => {
- if (!appLink || !activeHotZone.value) {
- return;
- }
- activeHotZone.value.name = appLink.name;
- activeHotZone.value.url = appLink.path;
-};
-</script>
-
-<template>
- <Modal title="璁剧疆鐑尯" class="w-[780px]">
- <div ref="container" class="w-750px relative h-full">
- <Image
- :src="imgUrl"
- :preview="false"
- class="w-750px pointer-events-none h-full select-none"
- />
- <div
- v-for="(item, hotZoneIndex) in formData"
- :key="hotZoneIndex"
- class="group absolute z-10 flex cursor-move items-center justify-center border text-base opacity-80"
- :style="{
- width: `${item.width}px`,
- height: `${item.height}px`,
- top: `${item.top}px`,
- left: `${item.left}px`,
- color: 'hsl(var(--primary))',
- background:
- 'color-mix(in srgb, hsl(var(--primary)) 30%, transparent)',
- borderColor: 'hsl(var(--primary))',
- }"
- @mousedown="handleMove(item, $event)"
- @dblclick="handleShowAppLinkDialog(item)"
- >
- <span class="pointer-events-none select-none">
- {{ item.name || '鍙屽嚮閫夋嫨閾炬帴' }}
- </span>
- <IconifyIcon
- icon="lucide:x"
- class="absolute right-0 top-0 hidden cursor-pointer rounded-bl-[80%] p-[2px_2px_6px_6px] text-right text-white group-hover:block"
- :style="{ backgroundColor: 'hsl(var(--primary))' }"
- :size="14"
- @click="handleRemove(item)"
- />
-
- <!-- 8 涓帶鍒剁偣 -->
- <span
- class="ctrl-dot absolute z-[11] h-2 w-2 rounded-full bg-white"
- v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
- :key="dotIndex"
- :style="{ ...dot.style, border: 'inherit' }"
- @mousedown="handleResize(item, dot, $event)"
- ></span>
- </div>
- </div>
- <template #prepend-footer>
- <Button @click="handleAdd" type="primary" ghost>
- <IconifyIcon icon="lucide:plus" class="mr-5px" />
- 娣诲姞鐑尯
- </Button>
- </template>
- </Modal>
-
- <AppLinkSelectDialog
- ref="appLinkDialogRef"
- @app-link-change="handleAppLinkChange"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/config.ts
deleted file mode 100644
index 9178ae0..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/config.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鐑尯灞炴�� */
-export interface HotZoneProperty {
- imgUrl: string; // 鍥剧墖鍦板潃
- list: HotZoneItemProperty[]; // 瀵艰埅鑿滃崟鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鐑尯椤圭洰灞炴�� */
-export interface HotZoneItemProperty {
- name: string; // 閾炬帴鐨勫悕绉�
- url: string; // 閾炬帴
- width: number; // 瀹�
- height: number; // 楂�
- top: number; // 涓�
- left: number; // 宸�
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'HotZone',
- name: '鐑尯',
- icon: 'tabler:hand-click',
- property: {
- imgUrl: '',
- list: [] as HotZoneItemProperty[],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<HotZoneProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/index.vue
deleted file mode 100644
index 0d289f1..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/index.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<script setup lang="ts">
-import type { HotZoneProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 鐑尯 */
-defineOptions({ name: 'HotZone' });
-
-const props = defineProps<{ property: HotZoneProperty }>();
-</script>
-
-<template>
- <div class="min-h-30px relative h-full w-full">
- <Image
- :src="props.property.imgUrl"
- class="pointer-events-none h-full w-full select-none"
- :preview="false"
- />
- <div
- v-for="(item, index) in props.property.list"
- :key="index"
- class="absolute z-10 flex cursor-move items-center justify-center border text-sm opacity-80"
- :style="{
- width: `${item.width}px`,
- height: `${item.height}px`,
- top: `${item.top}px`,
- left: `${item.left}px`,
- color: 'hsl(var(--primary))',
- background: 'color-mix(in srgb, hsl(var(--primary)) 30%, transparent)',
- borderColor: 'hsl(var(--primary))',
- }"
- >
- {{ item.name }}
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/property.vue
deleted file mode 100644
index cb669fe..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/hot-zone/property.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script setup lang="ts">
-import type { HotZoneProperty } from './config';
-
-import { ref } from 'vue';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Form, FormItem } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-import HotZoneEditDialog from './components/hot-zone-edit-dialog/index.vue';
-
-/** 鐑尯灞炴�ч潰鏉� */
-defineOptions({ name: 'HotZoneProperty' });
-
-const props = defineProps<{ modelValue: HotZoneProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-const editDialogRef = ref(); // 鐑尯缂栬緫瀵硅瘽妗�
-
-/** 鎵撳紑鐑尯缂栬緫瀵硅瘽妗� */
-function handleOpenEditDialog() {
- editDialogRef.value.open();
-}
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <!-- 琛ㄥ崟 -->
- <Form
- :label-col="{ style: { width: '80px' } }"
- :model="formData"
- class="mt-2"
- >
- <FormItem label="涓婁紶鍥剧墖" name="imgUrl">
- <UploadImg
- v-model="formData.imgUrl"
- height="50px"
- width="auto"
- class="min-w-[80px]"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 鎺ㄨ崘瀹藉害 750 </template>
- </UploadImg>
- </FormItem>
- </Form>
-
- <Button type="primary" ghost class="w-full" @click="handleOpenEditDialog">
- 璁剧疆鐑尯
- </Button>
- </ComponentContainerProperty>
-
- <!-- 鐑尯缂栬緫瀵硅瘽妗� -->
- <HotZoneEditDialog
- ref="editDialogRef"
- v-model="formData.list"
- :img-url="formData.imgUrl"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/config.ts
deleted file mode 100644
index 3b40282..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鍥剧墖灞曠ず灞炴�� */
-export interface ImageBarProperty {
- imgUrl: string; // 鍥剧墖閾炬帴
- url: string; // 璺宠浆閾炬帴
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'ImageBar',
- name: '鍥剧墖灞曠ず',
- icon: 'lucide:image',
- property: {
- imgUrl: '',
- url: '',
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<ImageBarProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/index.vue
deleted file mode 100644
index f52553b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/index.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script setup lang="ts">
-import type { ImageBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 鍥剧墖灞曠ず */
-defineOptions({ name: 'ImageBar' });
-
-defineProps<{ property: ImageBarProperty }>();
-</script>
-<template>
- <div
- class="flex h-12 items-center justify-center bg-card"
- v-if="!property.imgUrl"
- >
- <IconifyIcon icon="lucide:image" class="text-3xl text-gray-600" />
- </div>
- <Image
- class="block h-full min-h-8 w-full"
- v-else
- :src="property.imgUrl"
- :preview="false"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/property.vue
deleted file mode 100644
index 179b013..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/image-bar/property.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<script setup lang="ts">
-import type { ImageBarProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { AppLinkInput } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鍥剧墖灞曠ず灞炴�ч潰鏉� */
-defineOptions({ name: 'ImageBarProperty' });
-
-const props = defineProps<{ modelValue: ImageBarProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- :model="formData"
- >
- <FormItem label="涓婁紶鍥剧墖" name="imgUrl">
- <UploadImg
- v-model="formData.imgUrl"
- draggable="false"
- height="80px"
- width="100%"
- class="min-w-20"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿瀹藉害750 </template>
- </UploadImg>
- </FormItem>
- <FormItem label="閾炬帴" name="url">
- <AppLinkInput v-model="formData.url" />
- </FormItem>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/index.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/index.ts
deleted file mode 100644
index 5b2c4cc..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/index.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 缁勪欢娉ㄥ唽
- *
- * 缁勪欢瑙勮寖锛氭瘡涓瓙鐩綍灏辨槸涓�涓嫭绔嬬殑缁勪欢锛屾瘡涓洰褰曞寘鎷互涓嬩笁涓枃浠讹細
- * 1. config.ts锛氱粍浠堕厤缃紝蹇呴�夛紝鐢ㄤ簬瀹氫箟缁勪欢銆佺粍浠堕粯璁ょ殑灞炴�с�佸畾涔夊睘鎬х殑绫诲瀷
- * 2. index.vue锛氱粍浠跺睍绀猴紝鐢ㄤ簬灞曠ず缁勪欢鐨勬覆鏌撴晥鏋溿�傚彲浠ヤ笉鎻愪緵锛屽 Page锛堥〉闈㈣缃級锛屽彧闇�瑕佸睘鎬ч厤缃〃鍗曞嵆鍙�
- * 3. property.vue锛氱粍浠跺睘鎬ц〃鍗曪紝鐢ㄤ簬閰嶇疆缁勪欢锛屽繀閫夛紝
- *
- * 娉細
- * 缁勪欢 ID 浠� config.ts 涓厤缃殑 id 涓哄噯锛屼笌缁勪欢鐩綍鐨勫悕绉版棤鍏筹紝浣嗚繕鏄缓璁粍浠剁洰褰曠殑鍚嶇О涓庣粍浠� ID 淇濇寔涓�鑷�
- */
-import { defineAsyncComponent } from 'vue';
-
-const viewModules: Record<string, any> = import.meta.glob('./*/*.vue'); // 瀵煎叆缁勪欢鐣岄潰妯″潡
-const configModules: Record<string, any> = import.meta.glob('./*/config.ts', {
- eager: true,
-}); // 瀵煎叆閰嶇疆妯″潡
-
-const components: Record<string, any> = {}; // 鐣岄潰妯″潡
-const componentConfigs: Record<string, any> = {}; // 缁勪欢閰嶇疆妯″潡
-
-type ViewType = 'index' | 'property'; // 缁勪欢鐣岄潰鐨勭被鍨�
-
-/**
- * 娉ㄥ唽缁勪欢鐨勭晫闈㈡ā鍧�
- *
- * @param componentId 缁勪欢ID
- * @param configPath 閰嶇疆妯″潡鐨勬枃浠惰矾寰�
- * @param viewType 缁勪欢鐣岄潰鐨勭被鍨�
- */
-const registerComponentViewModule = (
- componentId: string,
- configPath: string,
- viewType: ViewType,
-) => {
- const viewPath = configPath.replace('config.ts', `${viewType}.vue`);
- const viewModule = viewModules[viewPath];
- if (viewModule) {
- // 瀹氫箟寮傛缁勪欢
- components[componentId] = defineAsyncComponent(viewModule);
- }
-};
-
-// 娉ㄥ唽
-Object.keys(configModules).forEach((modulePath: string) => {
- const component = configModules[modulePath].component;
- const componentId = component?.id;
- if (componentId) {
- // 娉ㄥ唽缁勪欢
- componentConfigs[componentId] = component;
- // 娉ㄥ唽棰勮鐣岄潰
- registerComponentViewModule(componentId, modulePath, 'index');
- // 娉ㄥ唽灞炴�ч厤缃〃鍗�
- registerComponentViewModule(
- `${componentId}Property`,
- modulePath,
- 'property',
- );
- }
-});
-
-export { componentConfigs, components };
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/config.ts
deleted file mode 100644
index c54b2d1..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/config.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { Rect } from '../../../../magic-cube-editor/util';
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 骞垮憡榄旀柟灞炴�� */
-export interface MagicCubeProperty {
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂撮殧
- list: MagicCubeItemProperty[]; // 瀵艰埅鑿滃崟鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 骞垮憡榄旀柟椤圭洰灞炴�� */
-export interface MagicCubeItemProperty extends Rect {
- imgUrl: string; // 鍥炬爣閾炬帴
- url: string; // 閾炬帴
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'MagicCube',
- name: '骞垮憡榄旀柟',
- icon: 'bi:columns',
- property: {
- borderRadiusTop: 0,
- borderRadiusBottom: 0,
- space: 0,
- list: [],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<MagicCubeProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/index.vue
deleted file mode 100644
index a7cf3f1..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/index.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script setup lang="ts">
-import type { MagicCubeProperty } from './config';
-
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 骞垮憡榄旀柟 */
-defineOptions({ name: 'MagicCube' });
-
-const props = defineProps<{ property: MagicCubeProperty }>();
-
-const CUBE_SIZE = 93.75; // 涓�涓柟鍧楃殑澶у皬
-
-/**
- * 璁$畻鏂瑰潡鐨勮鏁�
- * 琛屾暟鐢ㄤ簬璁$畻榄旀柟鐨勬�讳綋楂樺害锛屽瓨鍦ㄤ互涓嬫儏鍐碉細
- * 1. 娌℃湁鏁版嵁鏃讹紝榛樿灏卞彧鏄剧ず涓�琛岀殑楂樺害
- * 2. 搴曢儴鐨勭┖鐧戒笉绠楅珮搴︼紝渚嬪鍙湁绗竴琛屾湁鏁版嵁锛岄偅涔堝氨鍙樉绀轰竴琛岀殑楂樺害
- * 3. 椤堕儴鍙婁腑闂寸殑绌虹櫧绠楅珮搴︼紝渚嬪涓�鍏辨湁鍥涜锛屽彧鏈夋渶鍚庝竴琛屾湁鏁版嵁锛岄偅涔堜篃鏄剧ず鍥涜鐨勯珮搴�
- */
-const rowCount = computed(() => {
- let count = 0;
- if (props.property.list.length > 0) {
- // 鏈�澶ц鍙�
- count = Math.max(
- ...props.property.list.map((item) => item.top + item.height),
- );
- }
- // 淇濊瘉鑷冲皯鏈変竴琛�
- return count === 0 ? 1 : count;
-});
-</script>
-
-<template>
- <div
- class="relative"
- :style="{
- height: `${rowCount * CUBE_SIZE}px`,
- width: `${4 * CUBE_SIZE}px`,
- padding: `${property.space}px`,
- }"
- >
- <div
- v-for="(item, index) in property.list"
- :key="index"
- class="absolute"
- :style="{
- width: `${item.width * CUBE_SIZE - property.space}px`,
- height: `${item.height * CUBE_SIZE - property.space}px`,
- top: `${item.top * CUBE_SIZE}px`,
- left: `${item.left * CUBE_SIZE}px`,
- }"
- >
- <Image
- class="h-full w-full object-cover"
- :src="item.imgUrl"
- :preview="false"
- :style="{
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- >
- <template #error>
- <div class="image-slot">
- <div
- class="flex items-center justify-center"
- :style="{
- width: `${item.width * CUBE_SIZE}px`,
- height: `${item.height * CUBE_SIZE}px`,
- }"
- >
- <IconifyIcon icon="ep-picture" color="gray" :size="CUBE_SIZE" />
- </div>
- </div>
- </template>
- </Image>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/property.vue
deleted file mode 100644
index a6df222..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/magic-cube/property.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script setup lang="ts">
-import type { MagicCubeProperty } from './config';
-
-import { ref } from 'vue';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Slider } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- MagicCubeEditor,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 骞垮憡榄旀柟灞炴�ч潰鏉� */
-defineOptions({ name: 'MagicCubeProperty' });
-
-const props = defineProps<{ modelValue: MagicCubeProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-const selectedHotAreaIndex = ref(-1); // 閫変腑鐨勭儹鍖�
-
-/** 澶勭悊鐑尯琚�変腑浜嬩欢 */
-const handleHotAreaSelected = (_: any, index: number) => {
- selectedHotAreaIndex.value = index;
-};
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :model="formData"
- :label-col="{ style: { width: '80px' } }"
- label-align="right"
- >
- <p class="text-base font-bold">榄旀柟璁剧疆锛�</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <p class="text-xs text-gray-500">姣忔牸灏哄 187 * 187</p>
- <MagicCubeEditor
- v-model="formData.list"
- :rows="4"
- :cols="4"
- @hot-area-selected="handleHotAreaSelected"
- />
- <template v-for="(hotArea, index) in formData.list" :key="index">
- <template v-if="selectedHotAreaIndex === index">
- <FormItem label="涓婁紶鍥剧墖" :name="`list[${index}].imgUrl`">
- <UploadImg
- v-model="hotArea.imgUrl"
- height="80px"
- width="80px"
- :show-description="false"
- />
- </FormItem>
- <FormItem label="閾炬帴" :name="`list[${index}].url`">
- <AppLinkInput v-model="hotArea.url" />
- </FormItem>
- </template>
- </template>
- </div>
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider v-model:value="formData.borderRadiusTop" :max="100" :min="0" />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/config.ts
deleted file mode 100644
index c4dcdc4..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/config.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-import { cloneDeep } from '../../../../../../../../packages/utils/src';
-
-/** 瀹牸瀵艰埅灞炴�� */
-export interface MenuGridProperty {
- column: number; // 鍒楁暟
- list: MenuGridItemProperty[]; // 瀵艰埅鑿滃崟鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹牸瀵艰埅椤圭洰灞炴�� */
-export interface MenuGridItemProperty {
- iconUrl: string; // 鍥炬爣閾炬帴
- title: string; // 鏍囬
- titleColor: string; // 鏍囬棰滆壊
- subtitle: string; // 鍓爣棰�
- subtitleColor: string; // 鍓爣棰橀鑹�
- url: string; // 閾炬帴
- badge: {
- bgColor: string; // 瑙掓爣鑳屾櫙棰滆壊
- show: boolean; // 鏄惁鏄剧ず
- text: string; // 瑙掓爣鏂囧瓧
- textColor: string; // 瑙掓爣鏂囧瓧棰滆壊
- };
-}
-
-/** 瀹牸瀵艰埅椤圭洰榛樿灞炴�� */
-export const EMPTY_MENU_GRID_ITEM_PROPERTY = {
- title: '鏍囬',
- titleColor: '#333',
- subtitle: '鍓爣棰�',
- subtitleColor: '#bbb',
- badge: {
- show: false,
- textColor: '#fff',
- bgColor: '#FF6000',
- },
-} as MenuGridItemProperty;
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'MenuGrid',
- name: '瀹牸瀵艰埅',
- icon: 'bi:grid-3x3-gap',
- property: {
- column: 3,
- list: [cloneDeep(EMPTY_MENU_GRID_ITEM_PROPERTY)],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- marginLeft: 8,
- marginRight: 8,
- padding: 8,
- paddingTop: 8,
- paddingRight: 8,
- paddingBottom: 8,
- paddingLeft: 8,
- borderRadius: 8,
- borderTopLeftRadius: 8,
- borderTopRightRadius: 8,
- borderBottomRightRadius: 8,
- borderBottomLeftRadius: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<MenuGridProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/index.vue
deleted file mode 100644
index 38336f5..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/index.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script setup lang="ts">
-import type { MenuGridProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 瀹牸瀵艰埅 */
-defineOptions({ name: 'MenuGrid' });
-
-defineProps<{ property: MenuGridProperty }>();
-</script>
-
-<template>
- <div class="flex flex-row flex-wrap">
- <div
- v-for="(item, index) in property.list"
- :key="index"
- class="relative flex flex-col items-center pb-4 pt-4"
- :style="{ width: `${100 * (1 / property.column)}%` }"
- >
- <!-- 鍙充笂瑙掕鏍� -->
- <span
- v-if="item.badge?.show"
- class="absolute left-1/2 top-2 z-10 h-4 rounded-full px-2 text-center text-xs leading-5"
- :style="{
- color: item.badge.textColor,
- backgroundColor: item.badge.bgColor,
- }"
- >
- {{ item.badge.text }}
- </span>
- <Image
- v-if="item.iconUrl"
- :width="32"
- :src="item.iconUrl"
- :preview="false"
- />
- <span
- class="mt-2 h-4 text-xs leading-4"
- :style="{ color: item.titleColor }"
- >
- {{ item.title }}
- </span>
- <span
- class="mt-1.5 h-3 text-xs leading-3"
- :style="{ color: item.subtitleColor }"
- >
- {{ item.subtitle }}
- </span>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue
deleted file mode 100644
index eac0f2c..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-grid/property.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-<script setup lang="ts">
-import type { MenuGridProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Radio, RadioGroup, Switch } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- ColorInput,
- Draggable,
- InputWithColor,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-import { EMPTY_MENU_GRID_ITEM_PROPERTY } from './config';
-
-/** 瀹牸瀵艰埅灞炴�ч潰鏉� */
-defineOptions({ name: 'MenuGridProperty' });
-
-const props = defineProps<{ modelValue: MenuGridProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <!-- 琛ㄥ崟 -->
- <Form
- :label-col="{ style: { width: '80px' } }"
- :model="formData"
- class="mt-2"
- >
- <FormItem label="姣忚鏁伴噺" name="column">
- <RadioGroup v-model:value="formData.column">
- <Radio :value="3">3涓�</Radio>
- <Radio :value="4">4涓�</Radio>
- </RadioGroup>
- </FormItem>
- <p class="text-base font-bold">鑿滃崟璁剧疆</p>
- <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
- <Draggable
- v-model="formData.list"
- :empty-item="EMPTY_MENU_GRID_ITEM_PROPERTY"
- >
- <template #default="{ element }">
- <FormItem label="鍥炬爣" name="iconUrl">
- <UploadImg
- v-model="element.iconUrl"
- height="80px"
- width="80px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�44 * 44</template>
- </UploadImg>
- </FormItem>
- <FormItem label="鏍囬" name="title">
- <InputWithColor
- v-model="element.title"
- v-model:color="element.titleColor"
- />
- </FormItem>
- <FormItem label="鍓爣棰�" name="subtitle">
- <InputWithColor
- v-model="element.subtitle"
- v-model:color="element.subtitleColor"
- />
- </FormItem>
- <FormItem label="閾炬帴" name="url">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- <FormItem label="鏄剧ず瑙掓爣" name="badge.show">
- <Switch v-model:checked="element.badge.show" />
- </FormItem>
- <template v-if="element.badge.show">
- <FormItem label="瑙掓爣鍐呭" name="badge.text">
- <InputWithColor
- v-model="element.badge.text"
- v-model:color="element.badge.textColor"
- />
- </FormItem>
- <FormItem label="鑳屾櫙棰滆壊" name="badge.bgColor">
- <ColorInput v-model="element.badge.bgColor" />
- </FormItem>
- </template>
- </template>
- </Draggable>
- </div>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts
deleted file mode 100644
index b5e412c..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/config.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-import { cloneDeep } from '../../../../../../../../packages/utils/src';
-
-/** 鍒楄〃瀵艰埅灞炴�� */
-export interface MenuListProperty {
- list: MenuListItemProperty[]; // 瀵艰埅鑿滃崟鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍒楄〃瀵艰埅椤圭洰灞炴�� */
-export interface MenuListItemProperty {
- iconUrl: string; // 鍥炬爣閾炬帴
- title: string; // 鏍囬
- titleColor: string; // 鏍囬棰滆壊
- subtitle: string; // 鍓爣棰�
- subtitleColor: string; // 鍓爣棰橀鑹�
- url: string; // 閾炬帴
-}
-
-/** 绌虹殑鍒楄〃瀵艰埅椤圭洰灞炴�� */
-export const EMPTY_MENU_LIST_ITEM_PROPERTY = {
- title: '鏍囬',
- titleColor: '#333',
- subtitle: '鍓爣棰�',
- subtitleColor: '#bbb',
-};
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'MenuList',
- name: '鍒楄〃瀵艰埅',
- icon: 'fa-solid:list',
- property: {
- list: [cloneDeep(EMPTY_MENU_LIST_ITEM_PROPERTY)],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<MenuListProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue
deleted file mode 100644
index 7c00688..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/index.vue
+++ /dev/null
@@ -1,40 +0,0 @@
-<script setup lang="ts">
-import type { MenuListProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 鍒楄〃瀵艰埅 */
-defineOptions({ name: 'MenuList' });
-
-defineProps<{ property: MenuListProperty }>();
-</script>
-
-<template>
- <div class="flex min-h-10 flex-col">
- <div
- v-for="(item, index) in property.list"
- :key="index"
- class="flex h-10 flex-row items-center justify-between gap-1 border-t border-gray-200 px-3 first:border-t-0"
- >
- <div class="flex flex-1 flex-row items-center gap-2">
- <Image
- v-if="item.iconUrl"
- class="h-4 w-4"
- :src="item.iconUrl"
- :preview="false"
- />
- <span class="text-base" :style="{ color: item.titleColor }">
- {{ item.title }}
- </span>
- </div>
- <div class="item-center flex flex-row justify-center gap-1">
- <span class="text-xs" :style="{ color: item.subtitleColor }">
- {{ item.subtitle }}
- </span>
- <IconifyIcon icon="ep:arrow-right" color="#000" :size="16" />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue
deleted file mode 100644
index cc771fb..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-list/property.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script setup lang="ts">
-import type { MenuListProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- Draggable,
- InputWithColor,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-import { EMPTY_MENU_LIST_ITEM_PROPERTY } from './config';
-
-/** 鍒楄〃瀵艰埅灞炴�ч潰鏉� */
-defineOptions({ name: 'MenuListProperty' });
-
-const props = defineProps<{ modelValue: MenuListProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <p class="text-base font-bold">鑿滃崟璁剧疆</p>
- <p class="text-xs text-gray-500">鎷栧姩宸︿晶鐨勫皬鍦嗙偣鍙互璋冩暣椤哄簭</p>
- <Form
- :label-col="{ style: { width: '60px' } }"
- :model="formData"
- class="mt-2"
- >
- <Draggable
- v-model="formData.list"
- :empty-item="EMPTY_MENU_LIST_ITEM_PROPERTY"
- >
- <template #default="{ element }">
- <FormItem label="鍥炬爣" name="iconUrl">
- <UploadImg
- v-model="element.iconUrl"
- height="80px"
- width="80px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�44 * 44 </template>
- </UploadImg>
- </FormItem>
- <FormItem label="鏍囬" name="title">
- <InputWithColor
- v-model="element.title"
- v-model:color="element.titleColor"
- />
- </FormItem>
- <FormItem label="鍓爣棰�" name="subtitle">
- <InputWithColor
- v-model="element.subtitle"
- v-model:color="element.subtitleColor"
- />
- </FormItem>
- <FormItem label="閾炬帴" name="url">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- </template>
- </Draggable>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts
deleted file mode 100644
index e39596b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/config.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-import { cloneDeep } from '../../../../../../../../packages/utils/src';
-
-/** 鑿滃崟瀵艰埅灞炴�� */
-export interface MenuSwiperProperty {
- layout: 'icon' | 'iconText'; // 甯冨眬锛氬浘鏍�+鏂囧瓧 | 鍥炬爣
- row: number; // 琛屾暟
- column: number; // 鍒楁暟
- list: MenuSwiperItemProperty[]; // 瀵艰埅鑿滃崟鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鑿滃崟瀵艰埅椤圭洰灞炴�� */
-export interface MenuSwiperItemProperty {
- iconUrl: string; // 鍥炬爣閾炬帴
- title: string; // 鏍囬
- titleColor: string; // 鏍囬棰滆壊
- url: string; // 閾炬帴
- badge: {
- bgColor: string; // 瑙掓爣鑳屾櫙棰滆壊
- show: boolean; // 鏄惁鏄剧ず
- text: string; // 瑙掓爣鏂囧瓧
- textColor: string; // 瑙掓爣鏂囧瓧棰滆壊
- }; // 瑙掓爣
-}
-
-/** 绌鸿彍鍗曞鑸」鐩睘鎬� */
-export const EMPTY_MENU_SWIPER_ITEM_PROPERTY = {
- title: '鏍囬',
- titleColor: '#333',
- badge: {
- show: false,
- textColor: '#fff',
- bgColor: '#FF6000',
- },
-} as MenuSwiperItemProperty;
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'MenuSwiper',
- name: '鑿滃崟瀵艰埅',
- icon: 'bi:grid-3x2-gap',
- property: {
- layout: 'iconText',
- row: 1,
- column: 3,
- list: [cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY)],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<MenuSwiperProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue
deleted file mode 100644
index b18980e..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/index.vue
+++ /dev/null
@@ -1,130 +0,0 @@
-<script setup lang="ts">
-import type { MenuSwiperItemProperty, MenuSwiperProperty } from './config';
-
-import { ref, watch } from 'vue';
-
-import { Carousel, Image } from 'ant-design-vue';
-
-/** 鑿滃崟瀵艰埅 */
-defineOptions({ name: 'MenuSwiper' });
-
-const props = defineProps<{ property: MenuSwiperProperty }>();
-
-const TITLE_HEIGHT = 20; // 鏍囬鐨勯珮搴�
-const ICON_SIZE = 32; // 鍥炬爣鐨勯珮搴�
-const SPACE_Y = 16; // 鍨傜洿闂磋窛锛氫竴琛屼笂涓嬬殑闂磋窛
-
-const pages = ref<MenuSwiperItemProperty[][]>([]); // 鍒嗛〉
-const carouselHeight = ref(0); // 杞挱鍥鹃珮搴�
-
-const rowHeight = ref(0); // 琛岄珮
-const columnWidth = ref(''); // 鍒楀
-
-watch(
- () => props.property,
- () => {
- // 璁$畻鍒楀锛氭瘡涓�鍒楃殑鐧惧垎姣�
- columnWidth.value = `${100 * (1 / props.property.column)}%`;
- // 璁$畻琛岄珮锛氬浘鏍� + 鏂囧瓧锛堜粎鏄剧ず鍥剧墖鏃朵负0锛� + 鍨傜洿闂磋窛 * 2
- rowHeight.value =
- (props.property.layout === 'iconText'
- ? ICON_SIZE + TITLE_HEIGHT
- : ICON_SIZE) +
- SPACE_Y * 2;
- // 璁$畻杞挱鐨勯珮搴︼細琛屾暟 * 琛岄珮
- carouselHeight.value = props.property.row * rowHeight.value;
-
- // 姣忛〉鏁伴噺锛氳鏁� * 鍒楁暟
- const pageSize = props.property.row * props.property.column;
- // 娓呯┖鍒嗛〉
- pages.value = [];
- // 姣忎竴椤电殑鑿滃崟
- let pageItems: MenuSwiperItemProperty[] = [];
- for (const item of props.property.list) {
- // 鏈〉婊″憳锛屾柊寤轰笅涓�椤�
- if (pageItems.length === pageSize) {
- pageItems = [];
- }
- // 澧炲姞涓�椤�
- if (pageItems.length === 0) {
- pages.value.push(pageItems);
- }
- // 鏈〉澧炲姞涓�涓�
- pageItems.push(item);
- }
- },
- { immediate: true, deep: true },
-);
-</script>
-
-<template>
- <Carousel
- :autoplay="false"
- arrows
- dots
- :style="{ height: `${carouselHeight}px` }"
- >
- <div v-for="(page, pageIndex) in pages" :key="pageIndex">
- <div class="flex flex-row flex-wrap">
- <div
- v-for="(item, index) in page"
- :key="index"
- class="relative flex flex-col items-center justify-center"
- :style="{ width: columnWidth, height: `${rowHeight}px` }"
- >
- <div class="relative" :class="`h-${ICON_SIZE}px w-${ICON_SIZE}px`">
- <span
- v-if="item.badge?.show"
- class="absolute -right-2.5 -top-2.5 z-10 h-5 rounded-[10px] px-1.5 text-center text-xs leading-5"
- :style="{
- color: item.badge.textColor,
- backgroundColor: item.badge.bgColor,
- }"
- >
- {{ item.badge.text }}
- </span>
- <Image
- v-if="item.iconUrl"
- :src="item.iconUrl"
- class="h-full w-full"
- :preview="false"
- />
- </div>
- <span
- v-if="property.layout === 'iconText'"
- class="text-xs"
- :style="{
- color: item.titleColor,
- height: `${TITLE_HEIGHT}px`,
- lineHeight: `${TITLE_HEIGHT}px`,
- }"
- >
- {{ item.title }}
- </span>
- </div>
- </div>
- </div>
- </Carousel>
-</template>
-<style lang="scss">
-// Ant Design Vue Carousel 鏍峰紡璋冩暣
-:deep(.ant-carousel .ant-carousel-dots) {
- .ant-carousel-dot {
- width: 6px;
- height: 6px;
- border-radius: 6px;
-
- button {
- width: 6px;
- height: 6px;
- background: hsl(var(--red));
- border-radius: 6px;
- }
- }
-
- .ant-carousel-dot-active button {
- width: 12px;
- background: hsl(var(--red));
- }
-}
-</style>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/property.vue
deleted file mode 100644
index 99ec2ce..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/menu-swiper/property.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<script setup lang="ts">
-import type { MenuSwiperProperty } from './config';
-
-import { cloneDeep } from '../../../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Form,
- FormItem,
- Radio,
- RadioGroup,
- Switch,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- ColorInput,
- Draggable,
- InputWithColor,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-import { EMPTY_MENU_SWIPER_ITEM_PROPERTY } from './config';
-
-/** 鑿滃崟瀵艰埅灞炴�ч潰鏉� */
-defineOptions({ name: 'MenuSwiperProperty' });
-
-const props = defineProps<{ modelValue: MenuSwiperProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData" class="mt-2">
- <FormItem label="甯冨眬" name="layout">
- <RadioGroup v-model:value="formData.layout">
- <Radio value="iconText">鍥炬爣+鏂囧瓧</Radio>
- <Radio value="icon">浠呭浘鏍�</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem label="琛屾暟" name="row">
- <RadioGroup v-model:value="formData.row">
- <Radio :value="1">1琛�</Radio>
- <Radio :value="2">2琛�</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍒楁暟" name="column">
- <RadioGroup v-model:value="formData.column">
- <Radio :value="3">3鍒�</Radio>
- <Radio :value="4">4鍒�</Radio>
- <Radio :value="5">5鍒�</Radio>
- </RadioGroup>
- </FormItem>
- <Card title="鑿滃崟璁剧疆" class="property-group">
- <Draggable
- v-model="formData.list"
- :empty-item="cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY)"
- >
- <template #default="{ element }">
- <FormItem label="鍥炬爣" name="iconUrl">
- <UploadImg
- v-model="element.iconUrl"
- height="80px"
- width="80px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�98 * 98 </template>
- </UploadImg>
- </FormItem>
- <FormItem label="鏍囬" name="title">
- <InputWithColor
- v-model="element.title"
- v-model:color="element.titleColor"
- />
- </FormItem>
- <FormItem label="閾炬帴" name="url">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- <FormItem label="鏄剧ず瑙掓爣" name="badge.show">
- <Switch v-model:checked="element.badge.show" />
- </FormItem>
- <template v-if="element.badge.show">
- <FormItem label="瑙掓爣鍐呭" name="badge.text">
- <InputWithColor
- v-model="element.badge.text"
- v-model:color="element.badge.textColor"
- />
- </FormItem>
- <FormItem label="鑳屾櫙棰滆壊" name="badge.bgColor">
- <ColorInput v-model="element.badge.bgColor" />
- </FormItem>
- </template>
- </template>
- </Draggable>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue
deleted file mode 100644
index ee646d6..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/components/cell-property.vue
+++ /dev/null
@@ -1,166 +0,0 @@
-<script lang="ts" setup>
-import type { NavigationBarCellProperty } from '../config';
-
-import { computed, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- FormItem,
- Input,
- Radio,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import appNavBarMp from '#/assets/imgs/diy/app-nav-bar-mp.png';
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- ColorInput,
- MagicCubeEditor,
-} from '#/views/mall/promotion/components';
-
-/** 瀵艰埅鏍忓崟鍏冩牸灞炴�ч潰鏉� */
-defineOptions({ name: 'NavigationBarCellProperty' });
-
-const props = defineProps({
- isMp: {
- type: Boolean,
- default: true,
- },
- modelValue: {
- type: Array as () => NavigationBarCellProperty[],
- default: () => [],
- },
-});
-
-const emit = defineEmits(['update:modelValue']);
-
-const cellList = useVModel(props, 'modelValue', emit);
-
-/**
- * 璁$畻鍗曞厓鏍兼暟閲�
- * 1. 灏忕▼搴忥細6 涓紙鍥犱负鍙充晶鏈夎兌鍥婃寜閽崰鎹� 2 涓牸瀛愮殑绌洪棿锛�
- * 2. 鍏跺畠骞冲彴锛�8 涓紙鍏ㄩ儴绌洪棿鍙敤锛�
- */
-const cellCount = computed(() => (props.isMp ? 6 : 8));
-
-const selectedHotAreaIndex = ref(0); // 閫変腑鐨勭儹鍖�
-
-/** 澶勭悊鐑尯琚�変腑浜嬩欢 */
-function handleHotAreaSelected(
- cellValue: NavigationBarCellProperty,
- index: number,
-) {
- selectedHotAreaIndex.value = index;
- // 榛樿璁剧疆涓洪�変腑鏂囧瓧锛屽苟璁剧疆灞炴��
- if (!cellValue.type) {
- cellValue.type = 'text';
- cellValue.textColor = '#111111';
- }
- // 濡傛灉鐐瑰嚮鐨勬槸鎼滅储妗嗭紝鍒欏垵濮嬪寲鎼滅储妗嗙殑灞炴��
- if (cellValue.type === 'search') {
- cellValue.placeholderPosition = 'left';
- cellValue.backgroundColor = '#EEEEEE';
- cellValue.textColor = '#969799';
- }
-}
-</script>
-
-<template>
- <div class="h-40px flex items-center justify-center">
- <MagicCubeEditor
- v-model="cellList"
- :cols="cellCount"
- :cube-size="38"
- :rows="1"
- class="m-b-16px"
- @hot-area-selected="handleHotAreaSelected"
- />
- <img
- v-if="isMp"
- alt=""
- style="width: 76px; height: 30px"
- :src="appNavBarMp"
- />
- </div>
- <template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
- <template v-if="selectedHotAreaIndex === Number(cellIndex)">
- <FormItem :name="`cell[${cellIndex}].type`" label="绫诲瀷">
- <RadioGroup
- v-model:value="cell.type"
- @change="handleHotAreaSelected(cell, cellIndex)"
- >
- <Radio value="text">鏂囧瓧</Radio>
- <Radio value="image">鍥剧墖</Radio>
- <Radio value="search">鎼滅储妗�</Radio>
- </RadioGroup>
- </FormItem>
- <!-- 1. 鏂囧瓧 -->
- <template v-if="cell.type === 'text'">
- <FormItem :name="`cell[${cellIndex}].text`" label="鍐呭">
- <Input v-model:value="cell!.text" :maxlength="10" show-count />
- </FormItem>
- <FormItem :name="`cell[${cellIndex}].text`" label="棰滆壊">
- <ColorInput v-model="cell!.textColor" />
- </FormItem>
- <FormItem :name="`cell[${cellIndex}].url`" label="閾炬帴">
- <AppLinkInput v-model="cell.url" />
- </FormItem>
- </template>
- <!-- 2. 鍥剧墖 -->
- <template v-else-if="cell.type === 'image'">
- <FormItem :name="`cell[${cellIndex}].imgUrl`" label="鍥剧墖">
- <UploadImg
- v-model="cell.imgUrl"
- :limit="1"
- height="56px"
- width="56px"
- :show-description="false"
- />
- <span class="text-xs text-gray-500">寤鸿灏哄 56*56</span>
- </FormItem>
- <FormItem :name="`cell[${cellIndex}].url`" label="閾炬帴">
- <AppLinkInput v-model="cell.url" />
- </FormItem>
- </template>
- <!-- 3. 鎼滅储妗� -->
- <template v-else>
- <FormItem label="妗嗕綋棰滆壊" name="backgroundColor">
- <ColorInput v-model="cell.backgroundColor" />
- </FormItem>
- <FormItem class="lef" label="鏂囨湰棰滆壊" name="textColor">
- <ColorInput v-model="cell.textColor" />
- </FormItem>
- <FormItem :name="`cell[${cellIndex}].placeholder`" label="鎻愮ず鏂囧瓧">
- <Input v-model:value="cell.placeholder" :maxlength="10" show-count />
- </FormItem>
- <FormItem label="鏂囨湰浣嶇疆" name="placeholderPosition">
- <RadioGroup v-model:value="cell!.placeholderPosition">
- <Tooltip title="灞呭乏" placement="top">
- <RadioButton value="left">
- <IconifyIcon icon="ant-design:align-left-outlined" />
- </RadioButton>
- </Tooltip>
- <Tooltip title="灞呬腑" placement="top">
- <RadioButton value="center">
- <IconifyIcon icon="ant-design:align-center-outlined" />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鎵竴鎵�" name="showScan">
- <Switch v-model:checked="cell!.showScan" />
- </FormItem>
- <FormItem :name="`cell[${cellIndex}].borderRadius`" label="鍦嗚">
- <Slider v-model:value="cell.borderRadius" :max="100" :min="0" />
- </FormItem>
- </template>
- </template>
- </template>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts
deleted file mode 100644
index b549512..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/config.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { Rect } from '../../../../magic-cube-editor/util';
-import type { DiyComponent } from '../../../util';
-
-/** 椤堕儴瀵艰埅鏍忓睘鎬� */
-export interface NavigationBarProperty {
- bgType: 'color' | 'img'; // 鑳屾櫙绫诲瀷
- bgColor: string; // 鑳屾櫙棰滆壊
- bgImg: string; // 鍥剧墖閾炬帴
- styleType: 'inner' | 'normal'; // 鏍峰紡绫诲瀷锛氶粯璁� | 娌夋蹈寮�
- alwaysShow: boolean; // 甯搁┗鏄剧ず
- mpCells: NavigationBarCellProperty[]; // 灏忕▼搴忓崟鍏冩牸鍒楄〃
- otherCells: NavigationBarCellProperty[]; // 鍏跺畠骞冲彴鍗曞厓鏍煎垪琛�
- _local: {
- previewMp: boolean; // 棰勮椤堕儴瀵艰埅锛堝皬绋嬪簭锛�
- previewOther: boolean; // 棰勮椤堕儴瀵艰埅锛堥潪灏忕▼搴忥級
- }; // 鏈湴鍙橀噺
-}
-
-/** 椤堕儴瀵艰埅鏍� - 鍗曞厓鏍� 灞炴�� */
-export interface NavigationBarCellProperty extends Rect {
- type: 'image' | 'search' | 'text'; // 绫诲瀷锛氭枃瀛� | 鍥剧墖 | 鎼滅储妗�
- text: string; // 鏂囧瓧鍐呭
- textColor: string; // 鏂囧瓧棰滆壊
- imgUrl: string; // 鍥剧墖鍦板潃
- url: string; // 鍥剧墖閾炬帴
- backgroundColor: string; // 鎼滅储妗嗭細妗嗕綋棰滆壊
- placeholder: string; // 鎼滅储妗嗭細鎻愮ず鏂囧瓧
- placeholderPosition: string; // 鎼滅储妗嗭細鎻愮ず鏂囧瓧浣嶇疆
- showScan: boolean; // 鎼滅储妗嗭細鏄惁鏄剧ず鎵竴鎵�
- borderRadius: number; // 鎼滅储妗嗭細杈规鍦嗚鍗婂緞
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'NavigationBar',
- name: '椤堕儴瀵艰埅鏍�',
- icon: 'tabler:layout-navbar',
- property: {
- bgType: 'color',
- bgColor: '#fff',
- bgImg: '',
- styleType: 'normal',
- alwaysShow: true,
- mpCells: [
- {
- type: 'text',
- textColor: '#111111',
- },
- ],
- otherCells: [
- {
- type: 'text',
- textColor: '#111111',
- },
- ],
- _local: {
- previewMp: true,
- previewOther: false,
- },
- },
-} as DiyComponent<NavigationBarProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue
deleted file mode 100644
index 923e65d..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/index.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<script setup lang="ts">
-import type { StyleValue } from 'vue';
-
-import type { SearchProperty } from '../search-bar/config';
-import type {
- NavigationBarCellProperty,
- NavigationBarProperty,
-} from './config';
-
-import { computed } from 'vue';
-
-import appNavbarMp from '#/assets/imgs/diy/app-nav-bar-mp.png';
-
-import SearchBar from '../search-bar/index.vue';
-
-/** 椤甸潰椤堕儴瀵艰埅鏍� */
-defineOptions({ name: 'NavigationBar' });
-
-const props = defineProps<{ property: NavigationBarProperty }>();
-
-/** 璁$畻鑳屾櫙鏍峰紡 */
-const bgStyle = computed(() => {
- const background =
- props.property.bgType === 'img' && props.property.bgImg
- ? `url(${props.property.bgImg}) no-repeat top center / 100% 100%`
- : props.property.bgColor;
- return { background };
-});
-
-/** 鑾峰彇褰撳墠棰勮鐨勫崟鍏冩牸鍒楄〃 */
-const cellList = computed(() =>
- props.property._local?.previewMp
- ? props.property.mpCells
- : props.property.otherCells,
-);
-
-/** 璁$畻鍗曞厓鏍煎搴� */
-const cellWidth = computed(() => {
- return props.property._local?.previewMp
- ? (375 - 80 - 86) / 6
- : (375 - 90) / 8;
-});
-
-/** 鑾峰彇鍗曞厓鏍兼牱寮� */
-function getCellStyle(cell: NavigationBarCellProperty) {
- return {
- width: `${cell.width * cellWidth.value + (cell.width - 1) * 10}px`,
- left: `${cell.left * cellWidth.value + (cell.left + 1) * 10}px`,
- position: 'absolute',
- } as StyleValue;
-}
-
-/** 鑾峰彇鎼滅储妗嗗睘鎬ч厤缃� */
-const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
- return {
- height: 30,
- showScan: false,
- placeholder: cell.placeholder,
- borderRadius: cell.borderRadius,
- } as SearchProperty;
-});
-</script>
-<template>
- <div
- class="flex h-[50px] items-center justify-between bg-white px-[6px]"
- :style="bgStyle"
- >
- <div class="flex h-full w-full items-center">
- <div
- v-for="(cell, cellIndex) in cellList"
- :key="cellIndex"
- :style="getCellStyle(cell)"
- >
- <span v-if="cell.type === 'text'">{{ cell.text }}</span>
- <img
- v-else-if="cell.type === 'image'"
- :src="cell.imgUrl"
- alt=""
- class="h-full w-full"
- />
- <SearchBar v-else :property="getSearchProp(cell)" />
- </div>
- </div>
- <img
- v-if="property._local?.previewMp"
- :src="appNavbarMp"
- alt=""
- style="width: 86px; height: 30px"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue
deleted file mode 100644
index 3603c46..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/navigation-bar/property.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script setup lang="ts">
-import type { NavigationBarProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Radio,
- RadioGroup,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-import NavigationBarCellProperty from './components/cell-property.vue';
-
-/** 瀵艰埅鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'NavigationBarProperty' });
-
-const props = defineProps<{ modelValue: NavigationBarProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const rules: Record<string, any> = {
- name: [{ required: true, message: '璇疯緭鍏ラ〉闈㈠悕绉�', trigger: 'blur' }],
-}; // 琛ㄥ崟鏍¢獙
-
-const formData = useVModel(props, 'modelValue', emit);
-if (!formData.value._local) {
- formData.value._local = { previewMp: true, previewOther: false };
-}
-</script>
-
-<template>
- <Form
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- :model="formData"
- :rules="rules"
- >
- <FormItem label="鏍峰紡" name="styleType">
- <RadioGroup v-model:value="formData!.styleType">
- <Radio value="normal">鏍囧噯</Radio>
- <Tooltip
- title="娌変镜寮忓ご閮ㄤ粎鏀寔寰俊灏忕▼搴忋�丄PP锛屽缓璁〉闈㈢涓�涓粍浠朵负鍥剧墖灞曠ず绫荤粍浠�"
- placement="top"
- >
- <Radio value="inner">娌夋蹈寮�</Radio>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem
- label="甯搁┗鏄剧ず"
- name="alwaysShow"
- v-if="formData.styleType === 'inner'"
- >
- <RadioGroup v-model:value="formData!.alwaysShow">
- <Radio :value="false">鍏抽棴</Radio>
- <Tooltip
- title="甯搁┗鏄剧ず鍏抽棴鍚�,澶撮儴灏忕粍浠跺皢鍦ㄩ〉闈㈡粦鍔ㄦ椂娣″叆"
- placement="top"
- >
- <Radio :value="true">寮�鍚�</Radio>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鑳屾櫙绫诲瀷" name="bgType">
- <RadioGroup v-model:value="formData.bgType">
- <Radio value="color">绾壊</Radio>
- <Radio value="img">鍥剧墖</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem
- label="鑳屾櫙棰滆壊"
- name="bgColor"
- v-if="formData.bgType === 'color'"
- >
- <ColorInput v-model="formData.bgColor" />
- </FormItem>
- <FormItem label="鑳屾櫙鍥剧墖" name="bgImg" v-else>
- <div class="flex items-center">
- <UploadImg
- v-model="formData.bgImg"
- :limit="1"
- width="56px"
- height="56px"
- :show-description="false"
- />
- <span class="mb-2 ml-2 text-xs text-gray-400">寤鸿瀹藉害锛�750</span>
- </div>
- </FormItem>
- <Card class="property-group" :bordered="false">
- <template #title>
- <div class="flex items-center justify-between">
- <span>鍐呭锛堝皬绋嬪簭锛�</span>
- <FormItem name="_local.previewMp" class="mb-0">
- <Checkbox
- v-model:checked="formData._local.previewMp"
- @change="
- formData._local.previewOther = !formData._local.previewMp
- "
- >
- 棰勮
- </Checkbox>
- </FormItem>
- </div>
- </template>
- <NavigationBarCellProperty v-model="formData.mpCells" is-mp />
- </Card>
- <Card class="property-group" :bordered="false">
- <template #title>
- <div class="flex items-center justify-between">
- <span>鍐呭锛堥潪灏忕▼搴忥級</span>
- <FormItem name="_local.previewOther" class="mb-0">
- <Checkbox
- v-model:checked="formData._local.previewOther"
- @change="
- formData._local.previewMp = !formData._local.previewOther
- "
- >
- 棰勮
- </Checkbox>
- </FormItem>
- </div>
- </template>
- <NavigationBarCellProperty v-model="formData.otherCells" :is-mp="false" />
- </Card>
- </Form>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts
deleted file mode 100644
index b8fc2e9..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/config.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鍏憡鏍忓睘鎬� */
-export interface NoticeBarProperty {
- iconUrl: string; // 鍥炬爣鍦板潃
- contents: NoticeContentProperty[]; // 鍏憡鍐呭鍒楄〃
- backgroundColor: string; // 鑳屾櫙棰滆壊
- textColor: string; // 鏂囧瓧棰滆壊
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍐呭灞炴�� */
-export interface NoticeContentProperty {
- text: string; // 鍐呭鏂囧瓧
- url: string; // 閾炬帴鍦板潃
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'NoticeBar',
- name: '鍏憡鏍�',
- icon: 'lucide:bell',
- property: {
- iconUrl: 'http://mall.yudao.iocoder.cn/static/images/xinjian.png',
- contents: [
- {
- text: '',
- url: '',
- },
- ],
- backgroundColor: '#fff',
- textColor: '#333',
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<NoticeBarProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/index.vue
deleted file mode 100644
index 46708fd..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/index.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<script setup lang="ts">
-import type { NoticeBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Carousel, Divider, Image } from 'ant-design-vue';
-
-/** 鍏憡鏍� */
-defineOptions({ name: 'NoticeBar' });
-
-defineProps<{ property: NoticeBarProperty }>();
-</script>
-
-<template>
- <div
- class="flex items-center py-1 text-xs"
- :style="{
- backgroundColor: property.backgroundColor,
- color: property.textColor,
- }"
- >
- <Image :src="property.iconUrl" class="h-[18px]" :preview="false" />
- <Divider type="vertical" />
- <Carousel
- :autoplay="true"
- :dots="false"
- vertical
- class="flex-1 pr-2"
- style="height: 24px"
- >
- <div v-for="(item, index) in property.contents" :key="index">
- <div class="h-6 truncate leading-6">{{ item.text }}</div>
- </div>
- </Carousel>
- <IconifyIcon icon="lucide:arrow-right" />
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue
deleted file mode 100644
index 4f2f032..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/notice-bar/property.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<script setup lang="ts">
-import type { NoticeBarProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Card, Form, FormItem, Input } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- ColorInput,
- Draggable,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鍏憡鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'NoticeBarProperty' });
-
-const props = defineProps<{ modelValue: NoticeBarProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-const rules = {
- content: [
- { required: true, message: '璇疯緭鍏ュ叕鍛�', trigger: 'blur' as const },
- ],
-}; // 琛ㄥ崟鏍¢獙
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData" :rules="rules">
- <FormItem label="鍏憡鍥炬爣" name="iconUrl">
- <UploadImg
- v-model="formData.iconUrl"
- height="48px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip>寤鸿灏哄锛�24 * 24</template>
- </UploadImg>
- </FormItem>
- <FormItem label="鑳屾櫙棰滆壊" name="backgroundColor">
- <ColorInput v-model="formData.backgroundColor" />
- </FormItem>
- <FormItem label="鏂囧瓧棰滆壊" name="textColor">
- <ColorInput v-model="formData.textColor" />
- </FormItem>
- <Card title="鍏憡鍐呭" class="property-group">
- <Draggable v-model="formData.contents">
- <template #default="{ element }">
- <FormItem label="鍏憡" name="text">
- <Input v-model:value="element.text" placeholder="璇疯緭鍏ュ叕鍛�" />
- </FormItem>
- <FormItem label="閾炬帴" name="url">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- </template>
- </Draggable>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts
deleted file mode 100644
index 79661b0..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { DiyComponent } from '../../../util';
-
-/** 椤甸潰璁剧疆灞炴�� */
-export interface PageConfigProperty {
- description: string; // 椤甸潰鎻忚堪
- backgroundColor: string; // 椤甸潰鑳屾櫙棰滆壊
- backgroundImage: string; // 椤甸潰鑳屾櫙鍥剧墖
-}
-
-/** 瀹氫箟椤甸潰缁勪欢 */
-export const component = {
- id: 'PageConfig',
- name: '椤甸潰璁剧疆',
- icon: 'lucide:file-text',
- property: {
- description: '',
- backgroundColor: '#f5f5f5',
- backgroundImage: '',
- },
-} as DiyComponent<PageConfigProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue
deleted file mode 100644
index 5c06402..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/page-config/property.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<script setup lang="ts">
-import type { PageConfigProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Textarea } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-/** 瀵艰埅鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'PageConfigProperty' });
-
-const props = defineProps<{ modelValue: PageConfigProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <Form :model="formData" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
- <FormItem label="椤甸潰鎻忚堪" name="description">
- <Textarea
- v-model:value="formData!.description"
- placeholder="鐢ㄦ埛閫氳繃寰俊鍒嗕韩缁欐湅鍙嬫椂锛屼細鑷姩鏄剧ず椤甸潰鎻忚堪"
- :rows="3"
- />
- </FormItem>
- <FormItem label="鑳屾櫙棰滆壊" name="backgroundColor">
- <ColorInput v-model="formData!.backgroundColor" />
- </FormItem>
- <FormItem label="鑳屾櫙鍥剧墖" name="backgroundImage">
- <UploadImg
- v-model="formData!.backgroundImage"
- :limit="1"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip>寤鸿瀹藉害 750px</template>
- </UploadImg>
- </FormItem>
- </Form>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts
deleted file mode 100644
index 52bbeac..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { DiyComponent } from '../../../util';
-
-/** 寮圭獥骞垮憡灞炴�� */
-export interface PopoverProperty {
- list: PopoverItemProperty[]; // 寮圭獥鍒楄〃
-}
-
-/** 寮圭獥骞垮憡椤圭洰灞炴�� */
-export interface PopoverItemProperty {
- imgUrl: string; // 鍥剧墖鍦板潃
- url: string; // 璺宠浆杩炴帴
- showType: 'always' | 'once'; // 鏄剧ず绫诲瀷锛氫粎鏄剧ず涓�娆°�佹瘡娆″惎鍔ㄩ兘浼氭樉绀�
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'Popover',
- name: '寮圭獥骞垮憡',
- icon: 'carbon:popup',
- position: 'fixed',
- property: {
- list: [{ showType: 'once' }],
- },
-} as DiyComponent<PopoverProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue
deleted file mode 100644
index d512997..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/index.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<script setup lang="ts">
-import type { PopoverProperty } from './config';
-
-import { ref } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 寮圭獥骞垮憡 */
-defineOptions({ name: 'Popover' });
-
-const props = defineProps<{ property: PopoverProperty }>();
-
-const activeIndex = ref(0); // 閫変腑 index
-
-/** 澶勭悊閫変腑 */
-function handleActive(index: number) {
- activeIndex.value = index;
-}
-</script>
-<template>
- <div
- v-for="(item, index) in props.property.list"
- :key="index"
- class="absolute bottom-1/2 right-1/2 h-[454px] w-[292px] rounded border border-gray-300 bg-white p-0.5"
- :style="{
- zIndex: 100 + index + (activeIndex === index ? 100 : 0),
- marginRight: `${-146 - index * 20}px`,
- marginBottom: `${-227 - index * 20}px`,
- }"
- @click="handleActive(index)"
- >
- <Image
- :src="item.imgUrl"
- :preview="false"
- class="h-full w-full object-contain"
- >
- <template #error>
- <div class="flex h-full w-full items-center justify-center">
- <IconifyIcon icon="lucide:image" />
- </div>
- </template>
- </Image>
- <div class="absolute right-1 top-1 text-xs">{{ index + 1 }}</div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue
deleted file mode 100644
index 1f29e08..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/popover/property.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<script setup lang="ts">
-import type { PopoverProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Radio, RadioGroup, Tooltip } from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { AppLinkInput, Draggable } from '#/views/mall/promotion/components';
-
-/** 寮圭獥骞垮憡灞炴�ч潰鏉� */
-defineOptions({ name: 'PopoverProperty' });
-
-const props = defineProps<{ modelValue: PopoverProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <Form :label-col="{ style: { width: '80px' } }" :model="formData">
- <Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
- <template #default="{ element, index }">
- <FormItem label="鍥剧墖" :name="`list[${index}].imgUrl`">
- <UploadImg
- v-model="element.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- />
- </FormItem>
- <FormItem label="璺宠浆閾炬帴" :name="`list[${index}].url`">
- <AppLinkInput v-model="element.url" />
- </FormItem>
- <FormItem label="鏄剧ず娆℃暟" :name="`list[${index}].showType`">
- <RadioGroup v-model:value="element.showType">
- <Tooltip title="鍙樉绀轰竴娆★紝涓嬫鎵撳紑鏃朵笉鏄剧ず" placement="bottom">
- <Radio value="once">涓�娆�</Radio>
- </Tooltip>
- <Tooltip title="姣忔鎵撳紑鏃堕兘浼氭樉绀�" placement="bottom">
- <Radio value="always">涓嶉檺</Radio>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- </template>
- </Draggable>
- </Form>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts
deleted file mode 100644
index 82009b6..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/config.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鍟嗗搧鍗$墖灞炴�� */
-export interface ProductCardProperty {
- layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 甯冨眬绫诲瀷锛氬崟鍒楀ぇ鍥� | 鍗曞垪灏忓浘 | 鍙屽垪
- fields: {
- introduction: ProductCardFieldProperty; // 鍟嗗搧绠�浠�
- marketPrice: ProductCardFieldProperty; // 鍟嗗搧甯傚満浠�
- name: ProductCardFieldProperty; // 鍟嗗搧鍚嶇О
- price: ProductCardFieldProperty; // 鍟嗗搧浠锋牸
- salesCount: ProductCardFieldProperty; // 鍟嗗搧閿�閲�
- stock: ProductCardFieldProperty; // 鍟嗗搧搴撳瓨
- }; // 鍟嗗搧瀛楁
- badge: {
- imgUrl: string; // 瑙掓爣鍥剧墖
- show: boolean; // 鏄惁鏄剧ず
- }; // 瑙掓爣
- btnBuy: {
- bgBeginColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樿捣濮嬮鑹�
- bgEndColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樼粨鏉熼鑹�
- imgUrl: string; // 鍥剧墖鎸夐挳锛氬浘鐗囧湴鍧�
- text: string; // 鏂囧瓧
- type: 'img' | 'text'; // 绫诲瀷锛氭枃瀛� | 鍥剧墖
- }; // 鎸夐挳
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂磋窛
- spuIds: number[]; // 鍟嗗搧缂栧彿鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍟嗗搧瀛楁灞炴�� */
-export interface ProductCardFieldProperty {
- show: boolean; // 鏄惁鏄剧ず
- color: string; // 棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'ProductCard',
- name: '鍟嗗搧鍗$墖',
- icon: 'lucide:grid-3x3',
- property: {
- layoutType: 'oneColBigImg',
- fields: {
- name: { show: true, color: '#000' },
- introduction: { show: true, color: '#999' },
- price: { show: true, color: '#ff3000' },
- marketPrice: { show: true, color: '#c4c4c4' },
- salesCount: { show: true, color: '#c4c4c4' },
- stock: { show: false, color: '#c4c4c4' },
- },
- badge: { show: false, imgUrl: '' },
- btnBuy: {
- type: 'text',
- text: '绔嬪嵆璐拱',
- // todo: @owen 鏍规嵁涓婚鑹查厤缃�
- bgBeginColor: '#FF6000',
- bgEndColor: '#FE832A',
- imgUrl: '',
- },
- borderRadiusTop: 6,
- borderRadiusBottom: 6,
- space: 8,
- spuIds: [],
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<ProductCardProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue
deleted file mode 100644
index bd6890f..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/index.vue
+++ /dev/null
@@ -1,186 +0,0 @@
-<script setup lang="ts">
-import type { ProductCardProperty } from './config';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { ref, watch } from 'vue';
-
-import { fenToYuan } from '../../../../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-
-/** 鍟嗗搧鍗$墖 */
-defineOptions({ name: 'ProductCard' });
-
-const props = defineProps<{ property: ProductCardProperty }>();
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 鍟嗗搧鍒楄〃
-
-watch(
- () => props.property.spuIds,
- async () => {
- spuList.value = await getSpuDetailList(props.property.spuIds);
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-/** 璁$畻鍟嗗搧鐨勯棿璺� */
-function calculateSpace(index: number) {
- const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 鍟嗗搧鐨勫垪鏁�
- const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 绗竴鍒楁病鏈夊乏杈硅窛
- const marginTop = index < columns ? '0' : `${props.property.space}px`; // 绗竴琛屾病鏈変笂杈硅窛
- return { marginLeft, marginTop };
-}
-
-const containerRef = ref(); // 瀹瑰櫒
-
-/** 璁$畻鍟嗗搧鐨勫搴� */
-function calculateWidth() {
- let width = '100%';
- if (props.property.layoutType === 'twoCol') {
- // 鍙屽垪鏃舵瘡鍒楃殑瀹藉害涓猴細锛堟�诲搴� - 闂磋窛锛�/ 2
- width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`;
- }
- return { width };
-}
-</script>
-<template>
- <div
- class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
- ref="containerRef"
- >
- <div
- class="relative box-content flex flex-row flex-wrap overflow-hidden"
- :style="{
- ...calculateSpace(index),
- ...calculateWidth(),
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- v-for="(spu, index) in spuList"
- :key="index"
- >
- <!-- 瑙掓爣 -->
- <div
- v-if="property.badge.show && property.badge.imgUrl"
- class="absolute left-0 top-0 z-[1] items-center justify-center"
- >
- <Image
- :src="property.badge.imgUrl"
- :preview="false"
- class="h-6 w-8 object-cover"
- />
- </div>
- <!-- 鍟嗗搧灏侀潰鍥� -->
- <div
- class="h-36"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-36': property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <Image
- class="h-full w-full object-cover"
- :src="spu.picUrl"
- :preview="false"
- />
- </div>
- <div
- class="box-border flex flex-col gap-2 p-2"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[calc(100vh-140px-16px)]':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <!-- 鍟嗗搧鍚嶇О -->
- <div
- v-if="property.fields.name.show"
- class="text-sm"
- :class="[
- {
- truncate: property.layoutType !== 'oneColSmallImg',
- 'line-clamp-2 overflow-ellipsis':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- :style="{ color: property.fields.name.color }"
- >
- {{ spu.name }}
- </div>
- <!-- 鍟嗗搧绠�浠� -->
- <div
- v-if="property.fields.introduction.show"
- class="truncate text-xs"
- :style="{ color: property.fields.introduction.color }"
- >
- {{ spu.introduction }}
- </div>
- <div>
- <!-- 浠锋牸 -->
- <span
- v-if="property.fields.price.show"
- class="text-base"
- :style="{ color: property.fields.price.color }"
- >
- 锟{ fenToYuan(spu.price as any) }}
- </span>
- <!-- 甯傚満浠� -->
- <span
- v-if="property.fields.marketPrice.show && spu.marketPrice"
- class="ml-1 text-xs line-through"
- :style="{ color: property.fields.marketPrice.color }"
- >锟{ fenToYuan(spu.marketPrice) }}
- </span>
- </div>
- <div class="text-xs">
- <!-- 閿�閲� -->
- <span
- v-if="property.fields.salesCount.show"
- :style="{ color: property.fields.salesCount.color }"
- >
- 宸插敭{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}浠�
- </span>
- <!-- 搴撳瓨 -->
- <span
- v-if="property.fields.stock.show"
- :style="{ color: property.fields.stock.color }"
- >
- 搴撳瓨{{ spu.stock || 0 }}
- </span>
- </div>
- </div>
- <!-- 璐拱鎸夐挳 -->
- <div class="absolute bottom-2 right-2">
- <!-- 鏂囧瓧鎸夐挳 -->
- <span
- v-if="property.btnBuy.type === 'text'"
- class="rounded-full px-3 py-1 text-sm text-white"
- :style="{
- background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
- }"
- >
- {{ property.btnBuy.text }}
- </span>
- <!-- 鍥剧墖鎸夐挳 -->
- <Image
- v-else
- class="size-7 rounded-full object-cover"
- :src="property.btnBuy.imgUrl"
- :preview="false"
- />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue
deleted file mode 100644
index 70450c2..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-card/property.vue
+++ /dev/null
@@ -1,177 +0,0 @@
-<script setup lang="ts">
-import type { ProductCardProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { SpuShowcase } from '#/views/mall/product/spu/components';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鍟嗗搧鍗$墖灞炴�ч潰鏉� */
-defineOptions({ name: 'ProductCardProperty' });
-
-const props = defineProps<{ modelValue: ProductCardProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData">
- <Card title="鍟嗗搧鍒楄〃" class="property-group">
- <SpuShowcase v-model="formData.spuIds" />
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="甯冨眬" name="type">
- <RadioGroup v-model:value="formData.layoutType">
- <Tooltip title="鍗曞垪澶у浘" placement="bottom">
- <RadioButton value="oneColBigImg">
- <IconifyIcon
- icon="fluent:text-column-one-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍗曞垪灏忓浘" placement="bottom">
- <RadioButton value="oneColSmallImg">
- <IconifyIcon
- icon="fluent:text-column-two-left-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍙屽垪" placement="bottom">
- <RadioButton value="twoCol">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍟嗗搧鍚嶇О" name="fields.name.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.name.color" />
- <Checkbox v-model:checked="formData.fields.name.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧绠�浠�" name="fields.introduction.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.introduction.color" />
- <Checkbox v-model:checked="formData.fields.introduction.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧浠锋牸" name="fields.price.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.price.color" />
- <Checkbox v-model:checked="formData.fields.price.show" />
- </div>
- </FormItem>
- <FormItem label="甯傚満浠�" name="fields.marketPrice.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.marketPrice.color" />
- <Checkbox v-model:checked="formData.fields.marketPrice.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧閿�閲�" name="fields.salesCount.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.salesCount.color" />
- <Checkbox v-model:checked="formData.fields.salesCount.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧搴撳瓨" name="fields.stock.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.stock.color" />
- <Checkbox v-model:checked="formData.fields.stock.show" />
- </div>
- </FormItem>
- </Card>
- <Card title="瑙掓爣" class="property-group">
- <FormItem label="瑙掓爣" name="badge.show">
- <Switch v-model:checked="formData.badge.show" />
- </FormItem>
- <FormItem label="瑙掓爣" name="badge.imgUrl" v-if="formData.badge.show">
- <UploadImg
- v-model="formData.badge.imgUrl"
- height="44px"
- width="72px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�36 * 22 </template>
- </UploadImg>
- </FormItem>
- </Card>
- <Card title="鎸夐挳" class="property-group">
- <FormItem label="鎸夐挳绫诲瀷" name="btnBuy.type">
- <RadioGroup v-model:value="formData.btnBuy.type">
- <RadioButton value="text">鏂囧瓧</RadioButton>
- <RadioButton value="img">鍥剧墖</RadioButton>
- </RadioGroup>
- </FormItem>
- <template v-if="formData.btnBuy.type === 'text'">
- <FormItem label="鎸夐挳鏂囧瓧" name="btnBuy.text">
- <Input v-model:value="formData.btnBuy.text" />
- </FormItem>
- <FormItem label="宸︿晶鑳屾櫙" name="btnBuy.bgBeginColor">
- <ColorInput v-model="formData.btnBuy.bgBeginColor" />
- </FormItem>
- <FormItem label="鍙充晶鑳屾櫙" name="btnBuy.bgEndColor">
- <ColorInput v-model="formData.btnBuy.bgEndColor" />
- </FormItem>
- </template>
- <template v-else>
- <FormItem label="鍥剧墖" name="btnBuy.imgUrl">
- <UploadImg
- v-model="formData.btnBuy.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�56 * 56 </template>
- </UploadImg>
- </FormItem>
- </template>
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider
- v-model:value="formData.borderRadiusTop"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts
deleted file mode 100644
index f5d3e4e..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/config.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鍟嗗搧鏍忓睘鎬� */
-export interface ProductListProperty {
- layoutType: 'horizSwiper' | 'threeCol' | 'twoCol'; // 甯冨眬绫诲瀷锛氬弻鍒� | 涓夊垪 | 姘村钩婊戝姩
- fields: {
- name: ProductListFieldProperty; // 鍟嗗搧鍚嶇О
- price: ProductListFieldProperty; // 鍟嗗搧浠锋牸
- }; // 鍟嗗搧瀛楁
- badge: {
- imgUrl: string; // 瑙掓爣鍥剧墖
- show: boolean; // 鏄惁鏄剧ず
- }; // 瑙掓爣
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂磋窛
- spuIds: number[]; // 鍟嗗搧缂栧彿鍒楄〃
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍟嗗搧瀛楁灞炴�� */
-export interface ProductListFieldProperty {
- show: boolean; // 鏄惁鏄剧ず
- color: string; // 棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'ProductList',
- name: '鍟嗗搧鏍�',
- icon: 'fluent:text-column-two-24-filled',
- property: {
- layoutType: 'twoCol',
- fields: {
- name: { show: true, color: '#000' },
- price: { show: true, color: '#ff3000' },
- },
- badge: { show: false, imgUrl: '' },
- borderRadiusTop: 8,
- borderRadiusBottom: 8,
- space: 8,
- spuIds: [],
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<ProductListProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue
deleted file mode 100644
index 38d9d46..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/index.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<script setup lang="ts">
-import type { ProductListProperty } from './config';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-
-import { onMounted, ref, watch } from 'vue';
-
-import { fenToYuan } from '../../../../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-
-/** 鍟嗗搧鏍� */
-defineOptions({ name: 'ProductList' });
-
-const props = defineProps<{ property: ProductListProperty }>();
-
-const spuList = ref<MallSpuApi.Spu[]>([]);
-
-watch(
- () => props.property.spuIds,
- async () => {
- spuList.value = props.property.spuIds.length > 0
- ? await getSpuDetailList(props.property.spuIds)
- : [];
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-const phoneWidth = ref(375); // 鎵嬫満瀹藉害
-const containerRef = ref(); // 瀹瑰櫒
-const columns = ref(2); // 鍟嗗搧鐨勫垪鏁�
-const scrollbarWidth = ref('100%'); // 婊氬姩鏉″搴�
-const imageSize = ref('0'); // 鍟嗗搧鍥惧ぇ灏�
-const gridTemplateColumns = ref(''); // 鍟嗗搧缃戠粶鍒楁暟
-
-/** 璁$畻甯冨眬鍙傛暟 */
-watch(
- () => [props.property, phoneWidth, spuList.value.length],
- () => {
- // 璁$畻鍒楁暟
- columns.value = props.property.layoutType === 'twoCol' ? 2 : 3;
- // 姣忓垪鐨勫搴︿负锛氾紙鎬诲搴� - 闂磋窛 * (鍒楁暟 - 1)锛�/ 鍒楁暟
- const productWidth =
- (phoneWidth.value - props.property.space * (columns.value - 1)) /
- columns.value;
- // 鍟嗗搧鍥惧竷灞�锛�2鍒楁椂锛屽乏鍙冲竷灞� 3鍒楁椂锛屼笂涓嬪竷灞�
- imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`;
- // 鏍规嵁甯冨眬绫诲瀷锛岃绠楄鏁般�佸垪鏁�
- if (props.property.layoutType === 'horizSwiper') {
- // 鍗曡鏄剧ず
- gridTemplateColumns.value = `repeat(auto-fill, ${productWidth}px)`;
- // 鏄剧ず婊氬姩鏉�
- scrollbarWidth.value = `${
- productWidth * spuList.value.length +
- props.property.space * (spuList.value.length - 1)
- }px`;
- } else {
- // 鎸囧畾鍒楁暟
- gridTemplateColumns.value = `repeat(${columns.value}, auto)`;
- // 涓嶆粴鍔�
- scrollbarWidth.value = '100%';
- }
- },
- { immediate: true, deep: true },
-);
-
-/** 鍒濆鍖� */
-onMounted(() => {
- phoneWidth.value = containerRef.value?.offsetWidth || 375;
-});
-</script>
-<template>
- <div class="z-10 min-h-[30px] w-full" ref="containerRef">
- <!-- 鍟嗗搧缃戞牸 -->
- <div
- class="grid overflow-x-auto"
- :style="{
- gridGap: `${property.space}px`,
- gridTemplateColumns,
- width: scrollbarWidth,
- }"
- >
- <!-- 鍟嗗搧 -->
- <div
- class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
- :style="{
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- v-for="(spu, index) in spuList"
- :key="index"
- >
- <!-- 瑙掓爣 -->
- <div
- v-if="property.badge.show"
- class="absolute left-0 top-0 z-10 items-center justify-center"
- >
- <Image
- :src="property.badge.imgUrl"
- :preview="false"
- class="h-[26px] w-[38px] object-cover"
- />
- </div>
- <!-- 鍟嗗搧灏侀潰鍥� -->
- <Image
- :src="spu.picUrl"
- :style="{ width: imageSize, height: imageSize, objectFit: 'cover' }"
- :preview="false"
- />
- <div
- class="box-border flex flex-col gap-2 p-2"
- :class="[
- {
- 'w-[calc(100%-64px)]': columns === 2,
- 'w-full': columns === 3,
- },
- ]"
- >
- <!-- 鍟嗗搧鍚嶇О -->
- <div
- v-if="property.fields.name.show"
- class="truncate text-xs"
- :style="{ color: property.fields.name.color }"
- >
- {{ spu.name }}
- </div>
- <div>
- <!-- 鍟嗗搧浠锋牸 -->
- <span
- v-if="property.fields.price.show"
- class="text-xs"
- :style="{ color: property.fields.price.color }"
- >
- 锟{ fenToYuan(spu.price || 0) }}
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue
deleted file mode 100644
index 8bd6c80..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/product-list/property.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<script setup lang="ts">
-import type { ProductListProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import SpuShowcase from '#/views/mall/product/spu/components/spu-showcase.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鍟嗗搧鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'ProductListProperty' });
-
-const props = defineProps<{ modelValue: ProductListProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- :model="formData"
- >
- <Card title="鍟嗗搧鍒楄〃" class="property-group" :bordered="false">
- <SpuShowcase v-model="formData.spuIds" />
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group" :bordered="false">
- <FormItem label="甯冨眬" name="type">
- <RadioGroup v-model:value="formData.layoutType">
- <Tooltip title="鍙屽垪" placement="bottom">
- <RadioButton value="twoCol">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="涓夊垪" placement="bottom">
- <RadioButton value="threeCol">
- <IconifyIcon
- icon="fluent:text-column-three-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="姘村钩婊戝姩" placement="bottom">
- <RadioButton value="horizSwiper">
- <IconifyIcon icon="system-uicons:carousel" class="size-6" />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍟嗗搧鍚嶇О" name="fields.name.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.name.color" />
- <Checkbox v-model:checked="formData.fields.name.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧浠锋牸" name="fields.price.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.price.color" />
- <Checkbox v-model:checked="formData.fields.price.show" />
- </div>
- </FormItem>
- </Card>
- <Card title="瑙掓爣" class="property-group" :bordered="false">
- <FormItem label="瑙掓爣" name="badge.show">
- <Switch v-model:checked="formData.badge.show" />
- </FormItem>
- <FormItem label="瑙掓爣" name="badge.imgUrl" v-if="formData.badge.show">
- <UploadImg
- v-model="formData.badge.imgUrl"
- height="44px"
- width="72px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�36 * 22 </template>
- </UploadImg>
- </FormItem>
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group" :bordered="false">
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider
- v-model:value="formData.borderRadiusTop"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts
deleted file mode 100644
index ac47bf4..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/config.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 钀ラ攢鏂囩珷灞炴�� */
-export interface PromotionArticleProperty {
- id: number; // 鏂囩珷缂栧彿
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'PromotionArticle',
- name: '钀ラ攢鏂囩珷',
- icon: 'ph:article-medium',
- property: {
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<PromotionArticleProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue
deleted file mode 100644
index cb90e63..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/index.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<script setup lang="ts">
-import type { PromotionArticleProperty } from './config';
-
-import type { MallArticleApi } from '#/api/mall/promotion/article';
-
-import { ref, watch } from 'vue';
-
-import { getArticle } from '#/api/mall/promotion/article';
-
-/** 钀ラ攢鏂囩珷 */
-defineOptions({ name: 'PromotionArticle' });
-
-const props = defineProps<{ property: PromotionArticleProperty }>(); // 瀹氫箟灞炴��
-
-const article = ref<MallArticleApi.Article>(); // 鍟嗗搧鍒楄〃
-
-watch(
- () => props.property.id,
- async () => {
- if (props.property.id) {
- article.value = await getArticle(props.property.id);
- }
- },
- {
- immediate: true,
- },
-);
-</script>
-<template>
- <div class="min-h-[30px]" v-dompurify-html="article?.content"></div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue
deleted file mode 100644
index 2024181..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-article/property.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<script setup lang="ts">
-import type { PromotionArticleProperty } from './config';
-
-import type { MallArticleApi } from '#/api/mall/promotion/article';
-
-import { onMounted, ref } from 'vue';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Select } from 'ant-design-vue';
-
-import { getArticlePage } from '#/api/mall/promotion/article';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 钀ラ攢鏂囩珷灞炴�ч潰鏉� */
-defineOptions({ name: 'PromotionArticleProperty' });
-
-const props = defineProps<{ modelValue: PromotionArticleProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-const formData = useVModel(props, 'modelValue', emit);
-
-const articles = ref<MallArticleApi.Article[]>([]); // 鏂囩珷鍒楄〃
-const loading = ref(false); // 鍔犺浇涓�
-
-/** 鏌ヨ鏂囩珷鍒楄〃 */
-const queryArticleList = async (title?: string) => {
- loading.value = true;
- const { list } = await getArticlePage({
- title,
- pageNo: 1,
- pageSize: 10,
- });
- articles.value = list;
- loading.value = false;
-};
-
-/** 鍒濆鍖� */
-onMounted(() => {
- queryArticleList();
-});
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- :model="formData"
- >
- <FormItem label="鏂囩珷" name="id">
- <Select
- v-model:value="formData.id"
- placeholder="璇烽�夋嫨鏂囩珷"
- class="w-full"
- :show-search="true"
- :loading="loading"
- :options="
- articles.map((item: any) => ({ label: item.title, value: item.id }))
- "
- @search="queryArticleList"
- />
- </FormItem>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts
deleted file mode 100644
index f9db86b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/config.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鎷煎洟灞炴�� */
-export interface PromotionCombinationProperty {
- layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 甯冨眬绫诲瀷锛氬崟鍒� | 涓夊垪
- fields: {
- introduction: PromotionCombinationFieldProperty; // 鍟嗗搧绠�浠�
- marketPrice: PromotionCombinationFieldProperty; // 甯傚満浠�
- name: PromotionCombinationFieldProperty; // 鍟嗗搧鍚嶇О
- price: PromotionCombinationFieldProperty; // 鍟嗗搧浠锋牸
- salesCount: PromotionCombinationFieldProperty; // 鍟嗗搧閿�閲�
- stock: PromotionCombinationFieldProperty; // 鍟嗗搧搴撳瓨
- }; // 鍟嗗搧瀛楁
- badge: {
- imgUrl: string; // 瑙掓爣鍥剧墖
- show: boolean; // 鏄惁鏄剧ず
- }; // 瑙掓爣
- btnBuy: {
- bgBeginColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樿捣濮嬮鑹�
- bgEndColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樼粨鏉熼鑹�
- imgUrl: string; // 鍥剧墖鎸夐挳锛氬浘鐗囧湴鍧�
- text: string; // 鏂囧瓧
- type: 'img' | 'text'; // 绫诲瀷锛氭枃瀛� | 鍥剧墖
- }; // 鎸夐挳
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂磋窛
- activityIds: number[]; // 鎷煎洟娲诲姩缂栧彿
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍟嗗搧瀛楁灞炴�� */
-export interface PromotionCombinationFieldProperty {
- show: boolean; // 鏄惁鏄剧ず
- color: string; // 棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'PromotionCombination',
- name: '鎷煎洟',
- icon: 'mdi:account-group',
- property: {
- layoutType: 'oneColBigImg',
- fields: {
- name: { show: true, color: '#000' },
- introduction: { show: true, color: '#999' },
- price: { show: true, color: '#ff3000' },
- marketPrice: { show: true, color: '#c4c4c4' },
- salesCount: { show: true, color: '#c4c4c4' },
- stock: { show: false, color: '#c4c4c4' },
- },
- badge: { show: false, imgUrl: '' },
- btnBuy: {
- type: 'text',
- text: '鍘绘嫾鍥�',
- bgBeginColor: '#FF6000',
- bgEndColor: '#FE832A',
- imgUrl: '',
- },
- borderRadiusTop: 8,
- borderRadiusBottom: 8,
- space: 8,
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<PromotionCombinationProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue
deleted file mode 100644
index 14de06d..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/index.vue
+++ /dev/null
@@ -1,226 +0,0 @@
-<script setup lang="ts">
-import type { PromotionCombinationProperty } from './config';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallCombinationActivityApi } from '#/api/mall/promotion/combination/combinationActivity';
-
-import { ref, watch } from 'vue';
-
-import { fenToYuan } from '../../../../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-import { getCombinationActivityListByIds } from '#/api/mall/promotion/combination/combinationActivity';
-
-/** 鎷煎洟鍗$墖 */
-defineOptions({ name: 'PromotionCombination' });
-
-const props = defineProps<{ property: PromotionCombinationProperty }>();
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 鍟嗗搧鍒楄〃
-const spuIdList = ref<number[]>([]);
-const combinationActivityList = ref<
- MallCombinationActivityApi.CombinationActivity[]
->([]);
-
-watch(
- () => props.property.activityIds,
- async () => {
- try {
- // 鏂版坊鍔犵殑鎷煎洟缁勪欢锛屾槸娌℃湁娲诲姩ID鐨�
- const activityIds = props.property.activityIds;
- // 妫�鏌ユ椿鍔� ID 鐨勬湁鏁堟��
- if (Array.isArray(activityIds) && activityIds.length > 0) {
- // 鑾峰彇鎷煎洟娲诲姩璇︽儏鍒楄〃
- combinationActivityList.value =
- await getCombinationActivityListByIds(activityIds);
-
- // 鑾峰彇鎷煎洟娲诲姩鐨� SPU 璇︽儏鍒楄〃
- spuList.value = [];
- spuIdList.value = combinationActivityList.value
- .map((activity) => activity.spuId)
- .filter((spuId): spuId is number => typeof spuId === 'number');
- if (spuIdList.value.length > 0) {
- spuList.value = await getSpuDetailList(spuIdList.value);
- }
-
- // 鏇存柊 SPU 鐨勬渶浣庝环鏍�
- combinationActivityList.value.forEach((activity) => {
- // 鍖归厤spuId
- const spu = spuList.value.find((spu) => spu.id === activity.spuId);
- if (spu) {
- // 璧嬪�兼椿鍔ㄤ环鏍硷紝鍝釜鏈�渚垮疁灏辫祴鍊煎摢涓�
- spu.price = Math.min(
- activity.combinationPrice || Infinity,
- spu.price || Infinity,
- );
- }
- });
- }
- } catch (error) {
- console.error('鑾峰彇鎷煎洟娲诲姩缁嗚妭鎴� SPU 缁嗚妭鏃跺嚭閿�:', error);
- }
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-/** 璁$畻鍟嗗搧鐨勯棿璺� */
-function calculateSpace(index: number) {
- const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 鍟嗗搧鐨勫垪鏁�
- const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 绗竴鍒楁病鏈夊乏杈硅窛
- const marginTop = index < columns ? '0' : `${props.property.space}px`; // 绗竴琛屾病鏈変笂杈硅窛
- return { marginLeft, marginTop };
-}
-
-const containerRef = ref(); // 瀹瑰櫒
-
-/** 璁$畻鍟嗗搧鐨勫搴� */
-function calculateWidth() {
- let width = '100%';
- if (props.property.layoutType === 'twoCol') {
- // 鍙屽垪鏃舵瘡鍒楃殑瀹藉害涓猴細锛堟�诲搴� - 闂磋窛锛�/ 2
- width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`;
- }
- return { width };
-}
-</script>
-<template>
- <div
- class="box-content flex min-h-8 w-full flex-row flex-wrap"
- ref="containerRef"
- >
- <div
- class="relative box-content flex flex-row flex-wrap overflow-hidden bg-card"
- :style="{
- ...calculateSpace(index),
- ...calculateWidth(),
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- v-for="(spu, index) in spuList"
- :key="index"
- >
- <!-- 瑙掓爣 -->
- <div
- v-if="property.badge.show"
- class="absolute left-0 top-0 z-[1] items-center justify-center"
- >
- <Image
- :src="property.badge.imgUrl"
- class="h-6 w-8 object-cover"
- :preview="false"
- />
- </div>
- <!-- 鍟嗗搧灏侀潰鍥� -->
- <div
- class="h-[140px]"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[140px]': property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <Image
- class="h-full w-full object-cover"
- :src="spu.picUrl"
- :preview="false"
- />
- </div>
- <div
- class="box-border flex flex-col gap-2 p-2"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[calc(100%-140px-16px)]':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <!-- 鍟嗗搧鍚嶇О -->
- <div
- v-if="property.fields.name.show"
- class="text-sm"
- :class="[
- {
- truncate: property.layoutType !== 'oneColSmallImg',
- 'line-clamp-2 overflow-ellipsis':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- :style="{ color: property.fields.name.color }"
- >
- {{ spu.name }}
- </div>
- <!-- 鍟嗗搧绠�浠� -->
- <div
- v-if="property.fields.introduction.show"
- class="truncate text-xs"
- :style="{ color: property.fields.introduction.color }"
- >
- {{ spu.introduction }}
- </div>
- <div>
- <!-- 浠锋牸 -->
- <span
- v-if="property.fields.price.show"
- class="text-base"
- :style="{ color: property.fields.price.color }"
- >
- 锟{ fenToYuan(spu.price || Infinity) }}
- </span>
- <!-- 甯傚満浠� -->
- <span
- v-if="property.fields.marketPrice.show && spu.marketPrice"
- class="ml-1 text-xs line-through"
- :style="{ color: property.fields.marketPrice.color }"
- >
- 锟{ fenToYuan(spu.marketPrice) }}
- </span>
- </div>
- <div class="text-xs">
- <!-- 閿�閲� -->
- <span
- v-if="property.fields.salesCount.show"
- :style="{ color: property.fields.salesCount.color }"
- >
- 宸插敭{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}浠�
- </span>
- <!-- 搴撳瓨 -->
- <span
- v-if="property.fields.stock.show"
- :style="{ color: property.fields.stock.color }"
- >
- 搴撳瓨{{ spu.stock || 0 }}
- </span>
- </div>
- </div>
- <!-- 璐拱鎸夐挳 -->
- <div class="absolute bottom-2 right-2">
- <!-- 鏂囧瓧鎸夐挳 -->
- <span
- v-if="property.btnBuy.type === 'text'"
- class="rounded-full px-3 py-1 text-sm text-white"
- :style="{
- background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
- }"
- >
- {{ property.btnBuy.text }}
- </span>
- <!-- 鍥剧墖鎸夐挳 -->
- <Image
- v-else
- class="size-7 rounded-full object-cover"
- :src="property.btnBuy.imgUrl"
- :preview="false"
- />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue
deleted file mode 100644
index 8912ca9..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-combination/property.vue
+++ /dev/null
@@ -1,176 +0,0 @@
-<script setup lang="ts">
-import type { PromotionCombinationProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { CombinationShowcase } from '#/views/mall/promotion/combination/components';
-import { ColorInput } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鎷煎洟灞炴�ч潰鏉� */
-defineOptions({ name: 'PromotionCombinationProperty' });
-
-const props = defineProps<{ modelValue: PromotionCombinationProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- :model="formData"
- >
- <Card title="鎷煎洟娲诲姩" class="property-group" :bordered="false">
- <CombinationShowcase v-model="formData.activityIds" />
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group" :bordered="false">
- <FormItem label="甯冨眬" name="type">
- <RadioGroup v-model:value="formData.layoutType">
- <Tooltip title="鍗曞垪澶у浘" placement="bottom">
- <RadioButton value="oneColBigImg">
- <IconifyIcon
- icon="fluent:text-column-one-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍗曞垪灏忓浘" placement="bottom">
- <RadioButton value="oneColSmallImg">
- <IconifyIcon
- icon="fluent:text-column-two-left-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍙屽垪" placement="bottom">
- <RadioButton value="twoCol">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍟嗗搧鍚嶇О" name="fields.name.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.name.color" />
- <Checkbox v-model:checked="formData.fields.name.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧绠�浠�" name="fields.introduction.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.introduction.color" />
- <Checkbox v-model:checked="formData.fields.introduction.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧浠锋牸" name="fields.price.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.price.color" />
- <Checkbox v-model:checked="formData.fields.price.show" />
- </div>
- </FormItem>
- <FormItem label="甯傚満浠�" name="fields.marketPrice.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.marketPrice.color" />
- <Checkbox v-model:checked="formData.fields.marketPrice.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧閿�閲�" name="fields.salesCount.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.salesCount.color" />
- <Checkbox v-model:checked="formData.fields.salesCount.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧搴撳瓨" name="fields.stock.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.stock.color" />
- <Checkbox v-model:checked="formData.fields.stock.show" />
- </div>
- </FormItem>
- </Card>
- <Card title="瑙掓爣" class="property-group" :bordered="false">
- <FormItem label="瑙掓爣" name="badge.show">
- <Switch v-model:checked="formData.badge.show" />
- </FormItem>
- <FormItem label="瑙掓爣" name="badge.imgUrl" v-if="formData.badge.show">
- <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�36 * 22</template>
- </UploadImg>
- </FormItem>
- </Card>
- <Card title="鎸夐挳" class="property-group" :bordered="false">
- <FormItem label="鎸夐挳绫诲瀷" name="btnBuy.type">
- <RadioGroup v-model:value="formData.btnBuy.type">
- <RadioButton value="text">鏂囧瓧</RadioButton>
- <RadioButton value="img">鍥剧墖</RadioButton>
- </RadioGroup>
- </FormItem>
- <template v-if="formData.btnBuy.type === 'text'">
- <FormItem label="鎸夐挳鏂囧瓧" name="btnBuy.text">
- <Input v-model:value="formData.btnBuy.text" />
- </FormItem>
- <FormItem label="宸︿晶鑳屾櫙" name="btnBuy.bgBeginColor">
- <ColorInput v-model="formData.btnBuy.bgBeginColor" />
- </FormItem>
- <FormItem label="鍙充晶鑳屾櫙" name="btnBuy.bgEndColor">
- <ColorInput v-model="formData.btnBuy.bgEndColor" />
- </FormItem>
- </template>
- <template v-else>
- <FormItem label="鍥剧墖" name="btnBuy.imgUrl">
- <UploadImg
- v-model="formData.btnBuy.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�56 * 56</template>
- </UploadImg>
- </FormItem>
- </template>
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group" :bordered="false">
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider
- v-model:value="formData.borderRadiusTop"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/config.ts
deleted file mode 100644
index 8393f8c..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/config.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 绉垎鍟嗗煄灞炴�� */
-export interface PromotionPointProperty {
- layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 甯冨眬绫诲瀷锛氬崟鍒� | 涓夊垪
- fields: {
- introduction: PromotionPointFieldProperty; // 鍟嗗搧绠�浠�
- marketPrice: PromotionPointFieldProperty; // 甯傚満浠�
- name: PromotionPointFieldProperty; // 鍟嗗搧鍚嶇О
- price: PromotionPointFieldProperty; // 鍟嗗搧浠锋牸
- salesCount: PromotionPointFieldProperty; // 鍟嗗搧閿�閲�
- stock: PromotionPointFieldProperty; // 鍟嗗搧搴撳瓨
- }; // 鍟嗗搧瀛楁
- badge: {
- imgUrl: string; // 瑙掓爣鍥剧墖
- show: boolean; // 鏄惁鏄剧ず
- }; // 瑙掓爣
- // 鎸夐挳
- btnBuy: {
- bgBeginColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樿捣濮嬮鑹�
- bgEndColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樼粨鏉熼鑹�
- imgUrl: string; // 鍥剧墖鎸夐挳锛氬浘鐗囧湴鍧�
- text: string; // 鏂囧瓧
- type: 'img' | 'text'; // 绫诲瀷锛氭枃瀛� | 鍥剧墖
- }; // 鎸夐挳
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂磋窛
- activityIds: number[]; // 绉垎娲诲姩缂栧彿
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍟嗗搧瀛楁灞炴�� */
-export interface PromotionPointFieldProperty {
- show: boolean; // 鏄惁鏄剧ず
- color: string; // 棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'PromotionPoint',
- name: '绉垎鍟嗗煄',
- icon: 'ep:present',
- property: {
- layoutType: 'oneColBigImg',
- fields: {
- name: { show: true, color: '#000' },
- introduction: { show: true, color: '#999' },
- price: { show: true, color: '#ff3000' },
- marketPrice: { show: true, color: '#c4c4c4' },
- salesCount: { show: true, color: '#c4c4c4' },
- stock: { show: false, color: '#c4c4c4' },
- },
- badge: { show: false, imgUrl: '' },
- btnBuy: {
- type: 'text',
- text: '绔嬪嵆鍏戞崲',
- bgBeginColor: '#FF6000',
- bgEndColor: '#FE832A',
- imgUrl: '',
- },
- borderRadiusTop: 8,
- borderRadiusBottom: 8,
- space: 8,
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<PromotionPointProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/index.vue
deleted file mode 100644
index 24f514c..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/index.vue
+++ /dev/null
@@ -1,228 +0,0 @@
-<script lang="ts" setup>
-import type { PromotionPointProperty } from './config';
-
-import type { MallPointActivityApi } from '#/api/mall/promotion/point';
-
-import { ref, watch } from 'vue';
-
-import { fenToYuan } from '../../../../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-import { getPointActivityListByIds } from '#/api/mall/promotion/point';
-
-/** 绉垎鍟嗗煄鍗$墖 */
-defineOptions({ name: 'PromotionPoint' });
-
-const props = defineProps<{ property: PromotionPointProperty }>();
-
-const spuList = ref<MallPointActivityApi.SpuExtensionWithPoint[]>([]); // 鍟嗗搧鍒楄〃
-const spuIdList = ref<number[]>([]);
-const pointActivityList = ref<MallPointActivityApi.PointActivity[]>([]);
-
-watch(
- () => props.property.activityIds,
- async () => {
- try {
- // 鏂版坊鍔犵殑绉垎鍟嗗煄缁勪欢锛屾槸娌℃湁娲诲姩ID鐨�
- const activityIds = props.property.activityIds;
- // 妫�鏌ユ椿鍔� ID 鐨勬湁鏁堟��
- if (Array.isArray(activityIds) && activityIds.length > 0) {
- // 鑾峰彇绉垎鍟嗗煄娲诲姩璇︽儏鍒楄〃
- pointActivityList.value = await getPointActivityListByIds(activityIds);
-
- // 鑾峰彇绉垎鍟嗗煄娲诲姩鐨� SPU 璇︽儏鍒楄〃
- spuList.value = [];
- spuIdList.value = pointActivityList.value.map(
- (activity) => activity.spuId,
- );
- if (spuIdList.value.length > 0) {
- spuList.value = (await getSpuDetailList(
- spuIdList.value,
- )) as MallPointActivityApi.SpuExtensionWithPoint[];
- }
-
- // 鏇存柊 SPU 鐨勬渶浣庡厬鎹㈢Н鍒嗗拰鎵�闇�鍏戞崲閲戦
- pointActivityList.value.forEach((activity) => {
- // 鍖归厤spuId
- const spu = spuList.value.find((spu) => spu.id === activity.spuId);
- if (spu) {
- spu.pointStock = activity.stock;
- spu.pointTotalStock = activity.totalStock;
- spu.point = activity.point;
- spu.pointPrice = activity.price;
- }
- });
- }
- } catch (error) {
- console.error('鑾峰彇绉垎鍟嗗煄娲诲姩缁嗚妭鎴� SPU 缁嗚妭鏃跺嚭閿�:', error);
- }
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-/** 璁$畻鍟嗗搧鐨勯棿璺� */
-function calculateSpace(index: number) {
- const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 鍟嗗搧鐨勫垪鏁�
- const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 绗竴鍒楁病鏈夊乏杈硅窛
- const marginTop = index < columns ? '0' : `${props.property.space}px`; // 绗竴琛屾病鏈変笂杈硅窛
- return { marginLeft, marginTop };
-}
-
-const containerRef = ref(); // 瀹瑰櫒
-
-/** 璁$畻鍟嗗搧鐨勫搴� */
-function calculateWidth() {
- let width = '100%';
- if (props.property.layoutType === 'twoCol') {
- // 鍙屽垪鏃舵瘡鍒楃殑瀹藉害涓猴細锛堟�诲搴� - 闂磋窛锛�/ 2
- width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`;
- }
- return { width };
-}
-</script>
-<template>
- <div
- ref="containerRef"
- class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
- >
- <div
- v-for="(spu, index) in spuList"
- :key="index"
- :style="{
- ...calculateSpace(index),
- ...calculateWidth(),
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- class="relative box-content flex flex-row flex-wrap overflow-hidden bg-card"
- >
- <!-- 瑙掓爣 -->
- <div
- v-if="property.badge.show"
- class="absolute left-0 top-0 z-[1] items-center justify-center"
- >
- <Image
- :src="property.badge.imgUrl"
- class="h-6 w-10 object-cover"
- :preview="false"
- />
- </div>
- <!-- 鍟嗗搧灏侀潰鍥� -->
- <div
- class="h-[140px]"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[140px]': property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <Image
- :src="spu.picUrl"
- class="h-full w-full object-cover"
- :preview="false"
- />
- </div>
- <div
- class="box-border flex flex-col gap-2 p-2"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[calc(100%-140px-16px)]':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <!-- 鍟嗗搧鍚嶇О -->
- <div
- v-if="property.fields.name.show"
- class="text-sm"
- :class="[
- {
- truncate: property.layoutType !== 'oneColSmallImg',
- 'line-clamp-2 overflow-ellipsis':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- :style="{ color: property.fields.name.color }"
- >
- {{ spu.name }}
- </div>
- <!-- 鍟嗗搧绠�浠� -->
- <div
- v-if="property.fields.introduction.show"
- :style="{ color: property.fields.introduction.color }"
- class="truncate text-xs"
- >
- {{ spu.introduction }}
- </div>
- <div>
- <!-- 绉垎 -->
- <span
- v-if="property.fields.price.show"
- :style="{ color: property.fields.price.color }"
- class="text-base"
- >
- {{ spu.point }}绉垎
- {{
- !spu.pointPrice || spu.pointPrice === 0
- ? ''
- : `+${fenToYuan(spu.pointPrice)}鍏僠
- }}
- </span>
- <!-- 甯傚満浠� -->
- <span
- v-if="property.fields.marketPrice.show && spu.marketPrice"
- :style="{ color: property.fields.marketPrice.color }"
- class="ml-1 text-xs line-through"
- >
- 锟{ fenToYuan(spu.marketPrice) }}
- </span>
- </div>
- <div class="text-xs">
- <!-- 閿�閲� -->
- <span
- v-if="property.fields.salesCount.show"
- :style="{ color: property.fields.salesCount.color }"
- >
- 宸插厬{{ (spu.pointTotalStock || 0) - (spu.pointStock || 0) }}浠�
- </span>
- <!-- 搴撳瓨 -->
- <span
- v-if="property.fields.stock.show"
- :style="{ color: property.fields.stock.color }"
- >
- 搴撳瓨{{ spu.pointTotalStock || 0 }}
- </span>
- </div>
- </div>
- <!-- 璐拱鎸夐挳 -->
- <div class="absolute bottom-2 right-2">
- <!-- 鏂囧瓧鎸夐挳 -->
- <span
- v-if="property.btnBuy.type === 'text'"
- :style="{
- background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
- }"
- class="rounded-full px-3 py-1 text-sm text-white"
- >
- {{ property.btnBuy.text }}
- </span>
- <!-- 鍥剧墖鎸夐挳 -->
- <Image
- v-else
- :src="property.btnBuy.imgUrl"
- class="size-7 rounded-full object-cover"
- :preview="false"
- />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/property.vue
deleted file mode 100644
index bbddfc4..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-point/property.vue
+++ /dev/null
@@ -1,179 +0,0 @@
-<script lang="ts" setup>
-import type { PromotionPointProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-import { PointShowcase } from '#/views/mall/promotion/point/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 绉垎灞炴�ч潰鏉� */
-defineOptions({ name: 'PromotionPointProperty' });
-
-const props = defineProps<{ modelValue: PromotionPointProperty }>();
-const emit = defineEmits(['update:modelValue']);
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :model="formData"
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- >
- <Card title="绉垎鍟嗗煄娲诲姩" class="property-group">
- <PointShowcase v-model="formData.activityIds" />
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="甯冨眬" name="type">
- <RadioGroup v-model:value="formData.layoutType">
- <Tooltip title="鍗曞垪澶у浘" placement="bottom">
- <RadioButton value="oneColBigImg">
- <IconifyIcon
- icon="fluent:text-column-one-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍗曞垪灏忓浘" placement="bottom">
- <RadioButton value="oneColSmallImg">
- <IconifyIcon
- icon="fluent:text-column-two-left-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍙屽垪" placement="bottom">
- <RadioButton value="twoCol">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍟嗗搧鍚嶇О" name="fields.name.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.name.color" />
- <Checkbox v-model:checked="formData.fields.name.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧绠�浠�" name="fields.introduction.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.introduction.color" />
- <Checkbox v-model:checked="formData.fields.introduction.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧浠锋牸" name="fields.price.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.price.color" />
- <Checkbox v-model:checked="formData.fields.price.show" />
- </div>
- </FormItem>
- <FormItem label="甯傚満浠�" name="fields.marketPrice.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.marketPrice.color" />
- <Checkbox v-model:checked="formData.fields.marketPrice.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧閿�閲�" name="fields.salesCount.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.salesCount.color" />
- <Checkbox v-model:checked="formData.fields.salesCount.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧搴撳瓨" name="fields.stock.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.stock.color" />
- <Checkbox v-model:checked="formData.fields.stock.show" />
- </div>
- </FormItem>
- </Card>
- <Card title="瑙掓爣" class="property-group">
- <FormItem label="瑙掓爣" name="badge.show">
- <Switch v-model:checked="formData.badge.show" />
- </FormItem>
- <FormItem label="瑙掓爣" name="badge.imgUrl" v-if="formData.badge.show">
- <UploadImg
- v-model="formData.badge.imgUrl"
- height="44px"
- width="72px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�36 * 22 </template>
- </UploadImg>
- </FormItem>
- </Card>
- <Card title="鎸夐挳" class="property-group">
- <FormItem label="鎸夐挳绫诲瀷" name="btnBuy.type">
- <RadioGroup v-model:value="formData.btnBuy.type">
- <RadioButton value="text">鏂囧瓧</RadioButton>
- <RadioButton value="img">鍥剧墖</RadioButton>
- </RadioGroup>
- </FormItem>
- <template v-if="formData.btnBuy.type === 'text'">
- <FormItem label="鎸夐挳鏂囧瓧" name="btnBuy.text">
- <Input v-model:value="formData.btnBuy.text" />
- </FormItem>
- <FormItem label="宸︿晶鑳屾櫙" name="btnBuy.bgBeginColor">
- <ColorInput v-model="formData.btnBuy.bgBeginColor" />
- </FormItem>
- <FormItem label="鍙充晶鑳屾櫙" name="btnBuy.bgEndColor">
- <ColorInput v-model="formData.btnBuy.bgEndColor" />
- </FormItem>
- </template>
- <template v-else>
- <FormItem label="鍥剧墖" name="btnBuy.imgUrl">
- <UploadImg
- v-model="formData.btnBuy.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�56 * 56 </template>
- </UploadImg>
- </FormItem>
- </template>
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider
- v-model:value="formData.borderRadiusTop"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/config.ts
deleted file mode 100644
index 87feae6..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/config.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 绉掓潃灞炴�� */
-export interface PromotionSeckillProperty {
- layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'; // 甯冨眬绫诲瀷锛氬崟鍒� | 涓夊垪
- fields: {
- introduction: PromotionSeckillFieldProperty; // 鍟嗗搧绠�浠�
- marketPrice: PromotionSeckillFieldProperty; // 甯傚満浠�
- name: PromotionSeckillFieldProperty; // 鍟嗗搧鍚嶇О
- price: PromotionSeckillFieldProperty; // 鍟嗗搧浠锋牸
- salesCount: PromotionSeckillFieldProperty; // 鍟嗗搧閿�閲�
- stock: PromotionSeckillFieldProperty; // 鍟嗗搧搴撳瓨
- }; // 鍟嗗搧瀛楁
- badge: {
- imgUrl: string; // 瑙掓爣鍥剧墖
- show: boolean; // 鏄惁鏄剧ず
- }; // 瑙掓爣
- btnBuy: {
- bgBeginColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樿捣濮嬮鑹�
- bgEndColor: string; // 鏂囧瓧鎸夐挳锛氳儗鏅笎鍙樼粨鏉熼鑹�
- imgUrl: string; // 鍥剧墖鎸夐挳锛氬浘鐗囧湴鍧�
- text: string; // 鏂囧瓧
- type: 'img' | 'text'; // 绫诲瀷锛氭枃瀛� | 鍥剧墖
- }; // 鎸夐挳
- borderRadiusTop: number; // 涓婂渾瑙�
- borderRadiusBottom: number; // 涓嬪渾瑙�
- space: number; // 闂磋窛
- activityIds: number[]; // 绉掓潃娲诲姩缂栧彿
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 鍟嗗搧瀛楁灞炴�� */
-export interface PromotionSeckillFieldProperty {
- show: boolean; // 鏄惁鏄剧ず
- color: string; // 棰滆壊
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'PromotionSeckill',
- name: '绉掓潃',
- icon: 'mdi:calendar-time',
- property: {
- layoutType: 'oneColBigImg',
- fields: {
- name: { show: true, color: '#000' },
- introduction: { show: true, color: '#999' },
- price: { show: true, color: '#ff3000' },
- marketPrice: { show: true, color: '#c4c4c4' },
- salesCount: { show: true, color: '#c4c4c4' },
- stock: { show: false, color: '#c4c4c4' },
- },
- badge: { show: false, imgUrl: '' },
- btnBuy: {
- type: 'text',
- text: '绔嬪嵆绉掓潃',
- bgBeginColor: '#FF6000',
- bgEndColor: '#FE832A',
- imgUrl: '',
- },
- borderRadiusTop: 8,
- borderRadiusBottom: 8,
- space: 8,
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<PromotionSeckillProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/index.vue
deleted file mode 100644
index 155f680..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/index.vue
+++ /dev/null
@@ -1,223 +0,0 @@
-<script setup lang="ts">
-import type { PromotionSeckillProperty } from './config';
-
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckillActivity';
-
-import { ref, watch } from 'vue';
-
-import { fenToYuan } from '../../../../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { getSpuDetailList } from '#/api/mall/product/spu';
-import { getSeckillActivityListByIds } from '#/api/mall/promotion/seckill/seckillActivity';
-
-/** 绉掓潃鍗$墖 */
-defineOptions({ name: 'PromotionSeckill' });
-const props = defineProps<{ property: PromotionSeckillProperty }>();
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 鍟嗗搧鍒楄〃
-const spuIdList = ref<number[]>([]);
-const seckillActivityList = ref<MallSeckillActivityApi.SeckillActivity[]>([]);
-
-watch(
- () => props.property.activityIds,
- async () => {
- try {
- // 鏂版坊鍔犵殑绉掓潃缁勪欢锛屾槸娌℃湁娲诲姩ID鐨�
- const activityIds = props.property.activityIds;
- // 妫�鏌ユ椿鍔� ID 鐨勬湁鏁堟��
- if (Array.isArray(activityIds) && activityIds.length > 0) {
- // 鑾峰彇绉掓潃娲诲姩璇︽儏鍒楄〃
- seckillActivityList.value =
- await getSeckillActivityListByIds(activityIds);
-
- // 鑾峰彇绉掓潃娲诲姩鐨� SPU 璇︽儏鍒楄〃
- spuList.value = [];
- spuIdList.value = seckillActivityList.value
- .map((activity) => activity.spuId)
- .filter((spuId): spuId is number => typeof spuId === 'number');
- if (spuIdList.value.length > 0) {
- spuList.value = await getSpuDetailList(spuIdList.value);
- }
-
- // 鏇存柊 SPU 鐨勬渶浣庝环鏍�
- seckillActivityList.value.forEach((activity) => {
- // 鍖归厤spuId
- const spu = spuList.value.find((spu) => spu.id === activity.spuId);
- if (spu) {
- // 璧嬪�兼椿鍔ㄤ环鏍硷紝鍝釜鏈�渚垮疁灏辫祴鍊煎摢涓�
- spu.price = Math.min(
- activity.seckillPrice || Infinity,
- spu.price || Infinity,
- );
- }
- });
- }
- } catch (error) {
- console.error('鑾峰彇绉掓潃娲诲姩缁嗚妭鎴� SPU 缁嗚妭鏃跺嚭閿�:', error);
- }
- },
- {
- immediate: true,
- deep: true,
- },
-);
-
-/** 璁$畻鍟嗗搧鐨勯棿璺� */
-function calculateSpace(index: number) {
- const columns = props.property.layoutType === 'twoCol' ? 2 : 1; // 鍟嗗搧鐨勫垪鏁�
- const marginLeft = index % columns === 0 ? '0' : `${props.property.space}px`; // 绗竴鍒楁病鏈夊乏杈硅窛
- const marginTop = index < columns ? '0' : `${props.property.space}px`; // 绗竴琛屾病鏈変笂杈硅窛
- return { marginLeft, marginTop };
-}
-
-const containerRef = ref(); // 瀹瑰櫒
-
-/** 璁$畻鍟嗗搧鐨勫搴� */
-function calculateWidth() {
- let width = '100%';
- if (props.property.layoutType === 'twoCol') {
- // 鍙屽垪鏃舵瘡鍒楃殑瀹藉害涓猴細锛堟�诲搴� - 闂磋窛锛�/ 2
- width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`;
- }
- return { width };
-}
-</script>
-<template>
- <div
- class="box-content flex min-h-[30px] w-full flex-row flex-wrap"
- ref="containerRef"
- >
- <div
- class="relative box-content flex flex-row flex-wrap overflow-hidden bg-card"
- :style="{
- ...calculateSpace(index),
- ...calculateWidth(),
- borderTopLeftRadius: `${property.borderRadiusTop}px`,
- borderTopRightRadius: `${property.borderRadiusTop}px`,
- borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
- borderBottomRightRadius: `${property.borderRadiusBottom}px`,
- }"
- v-for="(spu, index) in spuList"
- :key="index"
- >
- <!-- 瑙掓爣 -->
- <div
- v-if="property.badge.show"
- class="absolute left-0 top-0 z-[1] items-center justify-center"
- >
- <Image
- :src="property.badge.imgUrl"
- class="h-6 w-8 object-cover"
- :preview="false"
- />
- </div>
- <!-- 鍟嗗搧灏侀潰鍥� -->
- <div
- class="h-[140px]"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[140px]': property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <Image
- class="h-full w-full object-cover"
- :src="spu.picUrl"
- :preview="false"
- />
- </div>
- <div
- class="box-border flex flex-col gap-2 p-2"
- :class="[
- {
- 'w-full': property.layoutType !== 'oneColSmallImg',
- 'w-[calc(100%-140px-16px)]':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- >
- <!-- 鍟嗗搧鍚嶇О -->
- <div
- v-if="property.fields.name.show"
- class="text-sm"
- :class="[
- {
- truncate: property.layoutType !== 'oneColSmallImg',
- 'line-clamp-2 overflow-ellipsis':
- property.layoutType === 'oneColSmallImg',
- },
- ]"
- :style="{ color: property.fields.name.color }"
- >
- {{ spu.name }}
- </div>
- <!-- 鍟嗗搧绠�浠� -->
- <div
- v-if="property.fields.introduction.show"
- class="truncate text-xs"
- :style="{ color: property.fields.introduction.color }"
- >
- {{ spu.introduction }}
- </div>
- <div>
- <!-- 浠锋牸 -->
- <span
- v-if="property.fields.price.show"
- class="text-base"
- :style="{ color: property.fields.price.color }"
- >
- 锟{ fenToYuan(spu.price || Infinity) }}
- </span>
- <!-- 甯傚満浠� -->
- <span
- v-if="property.fields.marketPrice.show && spu.marketPrice"
- class="ml-1 text-xs line-through"
- :style="{ color: property.fields.marketPrice.color }"
- >
- 锟{ fenToYuan(spu.marketPrice) }}
- </span>
- </div>
- <div class="text-xs">
- <!-- 閿�閲� -->
- <span
- v-if="property.fields.salesCount.show"
- :style="{ color: property.fields.salesCount.color }"
- >
- 宸插敭{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}浠�
- </span>
- <!-- 搴撳瓨 -->
- <span
- v-if="property.fields.stock.show"
- :style="{ color: property.fields.stock.color }"
- >
- 搴撳瓨{{ spu.stock || 0 }}
- </span>
- </div>
- </div>
- <!-- 璐拱鎸夐挳 -->
- <div class="absolute bottom-2 right-2">
- <!-- 鏂囧瓧鎸夐挳 -->
- <span
- v-if="property.btnBuy.type === 'text'"
- class="rounded-full px-3 py-1 text-xs text-white"
- :style="{
- background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`,
- }"
- >
- {{ property.btnBuy.text }}
- </span>
- <!-- 鍥剧墖鎸夐挳 -->
- <Image
- v-else
- class="size-7 rounded-full object-cover"
- :src="property.btnBuy.imgUrl"
- :preview="false"
- />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/property.vue
deleted file mode 100644
index 52dfeca..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/promotion-seckill/property.vue
+++ /dev/null
@@ -1,181 +0,0 @@
-<script setup lang="ts">
-import type { PromotionSeckillProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import { ColorInput } from '#/views/mall/promotion/components';
-import { SeckillShowcase } from '#/views/mall/promotion/seckill/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 绉掓潃灞炴�ч潰鏉� */
-defineOptions({ name: 'PromotionSeckillProperty' });
-
-const props = defineProps<{ modelValue: PromotionSeckillProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form
- :model="formData"
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- >
- <Card title="绉掓潃娲诲姩" class="property-group">
- <SeckillShowcase v-model="formData.activityIds" />
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="甯冨眬" name="type">
- <RadioGroup v-model:value="formData.layoutType">
- <Tooltip title="鍗曞垪澶у浘" placement="bottom">
- <RadioButton value="oneColBigImg">
- <IconifyIcon
- icon="fluent:text-column-one-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍗曞垪灏忓浘" placement="bottom">
- <RadioButton value="oneColSmallImg">
- <IconifyIcon
- icon="fluent:text-column-two-left-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍙屽垪" placement="bottom">
- <RadioButton value="twoCol">
- <IconifyIcon
- icon="fluent:text-column-two-24-filled"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍟嗗搧鍚嶇О" name="fields.name.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.name.color" />
- <Checkbox v-model:checked="formData.fields.name.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧绠�浠�" name="fields.introduction.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.introduction.color" />
- <Checkbox v-model:checked="formData.fields.introduction.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧浠锋牸" name="fields.price.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.price.color" />
- <Checkbox v-model:checked="formData.fields.price.show" />
- </div>
- </FormItem>
- <FormItem label="甯傚満浠�" name="fields.marketPrice.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.marketPrice.color" />
- <Checkbox v-model:checked="formData.fields.marketPrice.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧閿�閲�" name="fields.salesCount.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.salesCount.color" />
- <Checkbox v-model:checked="formData.fields.salesCount.show" />
- </div>
- </FormItem>
- <FormItem label="鍟嗗搧搴撳瓨" name="fields.stock.show">
- <div class="flex gap-2">
- <ColorInput v-model="formData.fields.stock.color" />
- <Checkbox v-model:checked="formData.fields.stock.show" />
- </div>
- </FormItem>
- </Card>
- <Card title="瑙掓爣" class="property-group">
- <FormItem label="瑙掓爣" name="badge.show">
- <Switch v-model:checked="formData.badge.show" />
- </FormItem>
- <FormItem label="瑙掓爣" name="badge.imgUrl" v-if="formData.badge.show">
- <UploadImg
- v-model="formData.badge.imgUrl"
- height="44px"
- width="72px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�36 * 22 </template>
- </UploadImg>
- </FormItem>
- </Card>
- <Card title="鎸夐挳" class="property-group">
- <FormItem label="鎸夐挳绫诲瀷" name="btnBuy.type">
- <RadioGroup v-model:value="formData.btnBuy.type">
- <RadioButton value="text">鏂囧瓧</RadioButton>
- <RadioButton value="img">鍥剧墖</RadioButton>
- </RadioGroup>
- </FormItem>
- <template v-if="formData.btnBuy.type === 'text'">
- <FormItem label="鎸夐挳鏂囧瓧" name="btnBuy.text">
- <Input v-model:value="formData.btnBuy.text" />
- </FormItem>
- <FormItem label="宸︿晶鑳屾櫙" name="btnBuy.bgBeginColor">
- <ColorInput v-model="formData.btnBuy.bgBeginColor" />
- </FormItem>
- <FormItem label="鍙充晶鑳屾櫙" name="btnBuy.bgEndColor">
- <ColorInput v-model="formData.btnBuy.bgEndColor" />
- </FormItem>
- </template>
- <template v-else>
- <FormItem label="鍥剧墖" name="btnBuy.imgUrl">
- <UploadImg
- v-model="formData.btnBuy.imgUrl"
- height="56px"
- width="56px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄锛�56 * 56</template>
- </UploadImg>
- </FormItem>
- </template>
- </Card>
- <Card title="鍟嗗搧鏍峰紡" class="property-group">
- <FormItem label="涓婂渾瑙�" name="borderRadiusTop">
- <Slider
- v-model:value="formData.borderRadiusTop"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="涓嬪渾瑙�" name="borderRadiusBottom">
- <Slider
- v-model:value="formData.borderRadiusBottom"
- :max="100"
- :min="0"
- />
- </FormItem>
- <FormItem label="闂撮殧" name="space">
- <Slider v-model:value="formData.space" :max="100" :min="0" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts
deleted file mode 100644
index a3c39aa..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/config.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鎼滅储妗嗗睘鎬� */
-export interface SearchProperty {
- height: number; // 鎼滅储鏍忛珮搴�
- showScan: boolean; // 鏄剧ず鎵竴鎵�
- borderRadius: number; // 妗嗕綋鏍峰紡
- placeholder: string; // 鍗犱綅鏂囧瓧
- placeholderPosition: PlaceholderPosition; // 鍗犱綅鏂囧瓧浣嶇疆
- backgroundColor: string; // 妗嗕綋棰滆壊
- textColor: string; // 瀛椾綋棰滆壊
- hotKeywords: string[]; // 鐑瘝
- style: ComponentStyle;
-}
-
-/** 鏂囧瓧浣嶇疆 */
-export type PlaceholderPosition = 'center' | 'left';
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'SearchBar',
- name: '鎼滅储妗�',
- icon: 'lucide:search',
- property: {
- height: 28,
- showScan: false,
- borderRadius: 0,
- placeholder: '鎼滅储鍟嗗搧',
- placeholderPosition: 'left',
- backgroundColor: 'rgb(238, 238, 238)',
- textColor: 'rgb(150, 151, 153)',
- hotKeywords: [],
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- paddingTop: 8,
- paddingRight: 8,
- paddingBottom: 8,
- paddingLeft: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<SearchProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue
deleted file mode 100644
index 765cbe0..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/index.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<script setup lang="ts">
-import type { SearchProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-/** 鎼滅储妗� */
-defineOptions({ name: 'SearchBar' });
-
-defineProps<{ property: SearchProperty }>();
-</script>
-
-<template>
- <div
- :style="{
- color: property.textColor,
- }"
- >
- <!-- 鎼滅储妗� -->
- <div
- class="relative flex min-h-7 items-center text-sm"
- :style="{
- height: `${property.height}px`,
- background: property.backgroundColor,
- borderRadius: `${property.borderRadius}px`,
- }"
- >
- <div
- class="flex w-full items-center gap-0.5 overflow-hidden text-ellipsis whitespace-nowrap break-all px-2"
- :style="{
- justifyContent: property.placeholderPosition,
- }"
- >
- <IconifyIcon icon="lucide:search" />
- <span>{{ property.placeholder || '鎼滅储鍟嗗搧' }}</span>
- </div>
- <div class="absolute right-2 flex items-center justify-center gap-2">
- <!-- 鎼滅储鐑瘝 -->
- <span v-for="(keyword, index) in property.hotKeywords" :key="index">
- {{ keyword }}
- </span>
- <!-- 鎵竴鎵� -->
- <IconifyIcon icon="lucide:scan-barcode" v-show="property.showScan" />
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue
deleted file mode 100644
index f9d97eb..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/search-bar/property.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<script setup lang="ts">
-import type { SearchProperty } from './config';
-
-import { watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-import { isString } from '../../../../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Slider,
- Switch,
- Tooltip,
-} from 'ant-design-vue';
-
-import { ColorInput, Draggable } from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鎼滅储妗嗗睘鎬ч潰鏉� */
-defineOptions({ name: 'SearchProperty' });
-
-const props = defineProps<{ modelValue: SearchProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-/** 鐩戝惉鐑瘝鏁扮粍鍙樺寲 */
-watch(
- () => formData.value.hotKeywords,
- (newVal) => {
- // 鎵惧埌闈炲瓧绗︿覆椤圭殑绱㈠紩
- const nonStringIndex = newVal.findIndex((item) => !isString(item));
- if (nonStringIndex !== -1) {
- formData.value.hotKeywords[nonStringIndex] = '';
- }
- },
- { deep: true, flush: 'post' },
-);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData" :label-col="{ style: { width: '80px' } }">
- <Card title="鎼滅储鐑瘝" class="property-group">
- <Draggable
- v-model="formData.hotKeywords"
- :empty-item="{
- type: 'input',
- placeholder: '璇疯緭鍏ョ儹璇�',
- }"
- >
- <template #default="{ index }">
- <Input
- v-model:value="formData.hotKeywords[index]"
- placeholder="璇疯緭鍏ョ儹璇�"
- />
- </template>
- </Draggable>
- </Card>
- <Card title="鎼滅储鏍峰紡" class="property-group">
- <FormItem label="妗嗕綋鏍峰紡">
- <RadioGroup v-model:value="formData!.borderRadius">
- <Tooltip title="鏂瑰舰" placement="top">
- <RadioButton :value="0">
- <IconifyIcon icon="tabler:input-search" class="size-6" />
- </RadioButton>
- </Tooltip>
- <Tooltip title="鍦嗗舰" placement="top">
- <RadioButton :value="10">
- <IconifyIcon icon="iconoir:input-search" class="size-6" />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鎻愮ず鏂囧瓧" name="placeholder">
- <Input v-model:value="formData.placeholder" />
- </FormItem>
- <FormItem label="鏂囨湰浣嶇疆" name="placeholderPosition">
- <RadioGroup v-model:value="formData!.placeholderPosition">
- <Tooltip title="灞呭乏" placement="top">
- <RadioButton value="left">
- <IconifyIcon
- icon="ant-design:align-left-outlined"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="灞呬腑" placement="top">
- <RadioButton value="center">
- <IconifyIcon
- icon="ant-design:align-center-outlined"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鎵竴鎵�" name="showScan">
- <Switch v-model:checked="formData!.showScan" />
- </FormItem>
- <FormItem label="妗嗕綋楂樺害" name="height">
- <Slider v-model:value="formData!.height" :max="50" :min="28" />
- </FormItem>
- <FormItem label="妗嗕綋棰滆壊" name="backgroundColor">
- <ColorInput v-model="formData.backgroundColor" />
- </FormItem>
- <FormItem label="鏂囨湰棰滆壊" name="textColor">
- <ColorInput v-model="formData.textColor" />
- </FormItem>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/config.ts
deleted file mode 100644
index efdb4da..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/config.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import type { DiyComponent } from '../../../util';
-
-/** 搴曢儴瀵艰埅鑿滃崟灞炴�� */
-export interface TabBarProperty {
- items: TabBarItemProperty[]; // 閫夐」鍒楄〃
- theme: string; // 涓婚
- style: TabBarStyle; // 鏍峰紡
-}
-
-/** 閫夐」灞炴�� */
-export interface TabBarItemProperty {
- text: string; // 鏍囩鏂囧瓧
- url: string; // 閾炬帴
- iconUrl: string; // 榛樿鍥炬爣閾炬帴
- activeIconUrl: string; // 閫変腑鐨勫浘鏍囬摼鎺�
-}
-
-/** 鏍峰紡 */
-export interface TabBarStyle {
- bgType: 'color' | 'img'; // 鑳屾櫙绫诲瀷
- bgColor: string; // 鑳屾櫙棰滆壊
- bgImg: string; // 鍥剧墖閾炬帴
- color: string; // 榛樿棰滆壊
- activeColor: string; // 閫変腑鐨勯鑹�
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'TabBar',
- name: '搴曢儴瀵艰埅',
- icon: 'fluent:table-bottom-row-16-filled',
- property: {
- theme: 'red',
- style: {
- bgType: 'color',
- bgColor: '#fff',
- color: '#282828',
- activeColor: '#fc4141',
- },
- items: [
- {
- text: '棣栭〉',
- url: '/pages/index/index',
- iconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-001.png',
- activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-002.png',
- },
- {
- text: '鍒嗙被',
- url: '/pages/index/category?id=3',
- iconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-001.png',
- activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-002.png',
- },
- {
- text: '璐墿杞�',
- url: '/pages/index/cart',
- iconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-001.png',
- activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-002.png',
- },
- {
- text: '鎴戠殑',
- url: '/pages/index/user',
- iconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-001.png',
- activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-002.png',
- },
- ],
- },
-} as DiyComponent<TabBarProperty>;
-
-export const THEME_LIST = [
- {
- id: 'red',
- name: '涓浗绾�',
- icon: 'icon-park-twotone:theme',
- color: '#d10019',
- },
- {
- id: 'orange',
- name: '妗旀',
- icon: 'icon-park-twotone:theme',
- color: '#f37b1d',
- },
- {
- id: 'gold',
- name: '鏄庨粍',
- icon: 'icon-park-twotone:theme',
- color: '#fbbd08',
- },
- {
- id: 'green',
- name: '姗勬缁�',
- icon: 'icon-park-twotone:theme',
- color: '#8dc63f',
- },
- {
- id: 'cyan',
- name: '澶╅潚',
- icon: 'icon-park-twotone:theme',
- color: '#1cbbb4',
- },
- {
- id: 'blue',
- name: '娴疯摑',
- icon: 'icon-park-twotone:theme',
- color: '#0081ff',
- },
- {
- id: 'purple',
- name: '濮圭传',
- icon: 'icon-park-twotone:theme',
- color: '#6739b6',
- },
- {
- id: 'brightRed',
- name: '瀚g孩',
- icon: 'icon-park-twotone:theme',
- color: '#e54d42',
- },
- {
- id: 'forestGreen',
- name: '妫豢',
- icon: 'icon-park-twotone:theme',
- color: '#39b54a',
- },
- {
- id: 'mauve',
- name: '鏈ㄦЭ',
- icon: 'icon-park-twotone:theme',
- color: '#9c26b0',
- },
- {
- id: 'pink',
- name: '妗冪矇',
- icon: 'icon-park-twotone:theme',
- color: '#e03997',
- },
- {
- id: 'brown',
- name: '妫曡',
- icon: 'icon-park-twotone:theme',
- color: '#a5673f',
- },
- {
- id: 'grey',
- name: '鐜勭伆',
- icon: 'icon-park-twotone:theme',
- color: '#8799a3',
- },
- {
- id: 'gray',
- name: '鑽夌伆',
- icon: 'icon-park-twotone:theme',
- color: '#aaaaaa',
- },
- {
- id: 'black',
- name: '澧ㄩ粦',
- icon: 'icon-park-twotone:theme',
- color: '#333333',
- },
-];
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/index.vue
deleted file mode 100644
index 7326154..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/index.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<script setup lang="ts">
-import type { TabBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 椤甸潰搴曢儴瀵艰埅鏍� */
-defineOptions({ name: 'TabBar' });
-
-defineProps<{ property: TabBarProperty }>();
-</script>
-<template>
- <div class="z-[2] w-full">
- <div
- class="flex flex-row items-center justify-around py-2"
- :style="{
- background:
- property.style.bgType === 'color'
- ? property.style.bgColor
- : `url(${property.style.bgImg})`,
- backgroundSize: '100% 100%',
- backgroundRepeat: 'no-repeat',
- }"
- >
- <div
- v-for="(item, index) in property.items"
- :key="index"
- class="flex w-full flex-col items-center justify-center text-xs"
- >
- <Image
- :src="index === 0 ? item.activeIconUrl : item.iconUrl"
- class="!h-[26px] w-[26px] rounded"
- :preview="false"
- >
- <template #error>
- <div class="flex h-full w-full items-center justify-center">
- <IconifyIcon
- icon="lucide:image"
- class="h-[26px] w-[26px] rounded"
- />
- </div>
- </template>
- </Image>
- <span
- :style="{
- color:
- index === 0 ? property.style.activeColor : property.style.color,
- }"
- >
- {{ item.text }}
- </span>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/property.vue
deleted file mode 100644
index e0ebdce..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/tab-bar/property.vue
+++ /dev/null
@@ -1,146 +0,0 @@
-<script setup lang="ts">
-import type { TabBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Form,
- FormItem,
- Input,
- RadioButton,
- RadioGroup,
- Select,
- SelectOption,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- ColorInput,
- Draggable,
-} from '#/views/mall/promotion/components';
-
-import { component, THEME_LIST } from './config';
-
-/** 搴曢儴瀵艰埅鏍� */
-defineOptions({ name: 'TabBarProperty' });
-
-const props = defineProps<{ modelValue: TabBarProperty }>();
-const emit = defineEmits(['update:modelValue']);
-const formData = useVModel(props, 'modelValue', emit);
-
-// 灏嗘暟鎹簱鐨勫�兼洿鏂板埌鍙充晶灞炴�ф爮
-component.property.items = formData.value.items;
-
-/** 澶勭悊涓婚鍙樻洿 */
-const handleThemeChange = () => {
- const theme = THEME_LIST.find((theme) => theme.id === formData.value.theme);
- if (theme?.color) {
- formData.value.style.activeColor = theme.color;
- }
-};
-</script>
-
-<template>
- <div>
- <!-- 琛ㄥ崟 -->
- <Form
- :model="formData"
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- >
- <FormItem label="涓婚" name="theme">
- <Select v-model:value="formData!.theme" @change="handleThemeChange">
- <SelectOption
- v-for="(theme, index) in THEME_LIST"
- :key="index"
- :label="theme.name"
- :value="theme.id"
- >
- <div class="flex items-center justify-between">
- <IconifyIcon :icon="theme.icon" :color="theme.color" />
- <span>{{ theme.name }}</span>
- </div>
- </SelectOption>
- </Select>
- </FormItem>
- <FormItem label="榛樿棰滆壊">
- <ColorInput v-model="formData!.style.color" />
- </FormItem>
- <FormItem label="閫変腑棰滆壊">
- <ColorInput v-model="formData!.style.activeColor" />
- </FormItem>
- <FormItem label="瀵艰埅鑳屾櫙">
- <RadioGroup v-model:value="formData!.style.bgType">
- <RadioButton value="color">绾壊</RadioButton>
- <RadioButton value="img">鍥剧墖</RadioButton>
- </RadioGroup>
- </FormItem>
- <FormItem label="閫夋嫨棰滆壊" v-if="formData!.style.bgType === 'color'">
- <ColorInput v-model="formData!.style.bgColor" />
- </FormItem>
- <FormItem label="閫夋嫨鍥剧墖" v-if="formData!.style.bgType === 'img'">
- <UploadImg
- v-model="formData!.style.bgImg"
- width="100%"
- height="50px"
- class="min-w-[200px]"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿灏哄 375 * 50 </template>
- </UploadImg>
- </FormItem>
-
- <div class="mb-2 text-base">鍥炬爣璁剧疆</div>
- <div class="mb-2 text-xs text-gray-500">
- 鎷栧姩宸︿笂瑙掔殑灏忓渾鐐瑰彲瀵瑰叾鎺掑簭, 鍥炬爣寤鸿灏哄 44*44
- </div>
- <Draggable v-model="formData.items" :limit="5">
- <template #default="{ element }">
- <div class="mb-2 flex items-center justify-around">
- <div class="flex flex-col items-center justify-between">
- <UploadImg
- v-model="element.iconUrl"
- width="40px"
- height="40px"
- :show-delete="false"
- :show-description="false"
- />
- <div class="text-xs">鏈�変腑</div>
- </div>
- <div>
- <UploadImg
- v-model="element.activeIconUrl"
- width="40px"
- height="40px"
- :show-delete="false"
- :show-description="false"
- />
- <div class="text-xs">宸查�変腑</div>
- </div>
- </div>
- <FormItem
- name="text"
- label="鏂囧瓧"
- :label-col="{ span: 4 }"
- :wrapper-col="{ span: 20 }"
- class="mb-2"
- >
- <Input v-model:value="element.text" placeholder="璇疯緭鍏ユ枃瀛�" />
- </FormItem>
- <FormItem
- name="url"
- label="閾炬帴"
- :label-col="{ span: 4 }"
- :wrapper-col="{ span: 20 }"
- class="mb-0"
- >
- <AppLinkInput v-model="element.url" />
- </FormItem>
- </template>
- </Draggable>
- </Form>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/config.ts
deleted file mode 100644
index 2ef0eb7..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/config.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鏍囬鏍忓睘鎬� */
-export interface TitleBarProperty {
- bgImgUrl: string; // 鑳屾櫙鍥�
- marginLeft: number; // 鍋忕Щ
- textAlign: 'center' | 'left'; // 鏄剧ず浣嶇疆
- title: string; // 涓绘爣棰�
- description: string; // 鍓爣棰�
- titleSize: number; // 鏍囬澶у皬
- descriptionSize: number; // 鎻忚堪澶у皬
- titleWeight: number; // 鏍囬绮楃粏
- descriptionWeight: number; // 鎻忚堪绮楃粏
- titleColor: string; // 鏍囬棰滆壊
- descriptionColor: string; // 鎻忚堪棰滆壊
- height: number; // 楂樺害
- more: {
- show: false; // 鏄惁鏄剧ず鏌ョ湅鏇村
- text: string; // 鑷畾涔夋枃瀛�
- type: 'all' | 'icon' | 'text'; // 鏍峰紡閫夋嫨
- url: string; // 閾炬帴
- }; // 鏌ョ湅鏇村
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'TitleBar',
- name: '鏍囬鏍�',
- icon: 'material-symbols:line-start',
- property: {
- title: '涓绘爣棰�',
- description: '鍓爣棰�',
- titleSize: 16,
- descriptionSize: 12,
- titleWeight: 400,
- textAlign: 'left',
- descriptionWeight: 200,
- titleColor: 'rgba(50, 50, 51, 10)',
- descriptionColor: 'rgba(150, 151, 153, 10)',
- marginLeft: 0,
- height: 40,
- more: {
- // 鏌ョ湅鏇村
- show: false,
- type: 'icon',
- text: '鏌ョ湅鏇村',
- url: '',
- },
- style: {
- bgType: 'color',
- bgColor: '#fff',
- } as ComponentStyle,
- },
-} as DiyComponent<TitleBarProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/index.vue
deleted file mode 100644
index 8c82de0..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/index.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<script setup lang="ts">
-import type { TitleBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Image } from 'ant-design-vue';
-
-/** 鏍囬鏍� */
-defineOptions({ name: 'TitleBar' });
-
-defineProps<{ property: TitleBarProperty }>();
-</script>
-<template>
- <div
- class="relative box-border min-h-[20px] w-full"
- :style="{ height: `${property.height}px` }"
- >
- <Image
- v-if="property.bgImgUrl"
- :src="property.bgImgUrl"
- :preview="false"
- class="w-full object-cover"
- />
- <div
- class="absolute left-0 top-0 flex h-full w-full flex-col justify-center"
- >
- <!-- 鏍囬 -->
- <div
- :style="{
- fontSize: `${property.titleSize}px`,
- fontWeight: property.titleWeight,
- color: property.titleColor,
- textAlign: property.textAlign,
- marginLeft: `${property.marginLeft}px`,
- marginBottom: '4px',
- }"
- v-if="property.title"
- >
- {{ property.title }}
- </div>
- <!-- 鍓爣棰� -->
- <div
- :style="{
- fontSize: `${property.descriptionSize}px`,
- fontWeight: property.descriptionWeight,
- color: property.descriptionColor,
- textAlign: property.textAlign,
- marginLeft: `${property.marginLeft}px`,
- }"
- v-if="property.description"
- >
- {{ property.description }}
- </div>
- </div>
- <!-- 鏇村 -->
- <div
- class="absolute bottom-0 right-2 top-0 m-auto flex items-center justify-center text-[10px] text-[#969799]"
- v-show="property.more.show"
- :style="{
- color: property.descriptionColor,
- }"
- >
- <span v-if="property.more.type !== 'icon'">
- {{ property.more.text }}
- </span>
- <IconifyIcon
- icon="lucide:arrow-right"
- v-if="property.more.type !== 'text'"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/property.vue
deleted file mode 100644
index 15f6e4b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/title-bar/property.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-<script setup lang="ts">
-import type { TitleBarProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Card,
- Checkbox,
- Form,
- FormItem,
- Input,
- Radio,
- RadioButton,
- RadioGroup,
- Slider,
- Tooltip,
-} from 'ant-design-vue';
-
-import UploadImg from '#/components/upload/image-upload.vue';
-import {
- AppLinkInput,
- InputWithColor,
-} from '#/views/mall/promotion/components';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 瀵艰埅鏍忓睘鎬ч潰鏉� */
-defineOptions({ name: 'TitleBarProperty' });
-
-const props = defineProps<{ modelValue: TitleBarProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-const rules = {}; // 琛ㄥ崟鏍¢獙
-</script>
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <Form :model="formData" :rules="rules">
- <Card title="椋庢牸" class="property-group">
- <FormItem label="鑳屾櫙鍥剧墖" name="bgImgUrl">
- <UploadImg
- v-model="formData.bgImgUrl"
- width="100%"
- height="40px"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip>寤鸿灏哄 750*80</template>
- </UploadImg>
- </FormItem>
- <FormItem label="鏍囬浣嶇疆" name="textAlign">
- <RadioGroup v-model:value="formData!.textAlign">
- <Tooltip title="灞呭乏" placement="top">
- <RadioButton value="left">
- <IconifyIcon
- icon="ant-design:align-left-outlined"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- <Tooltip title="灞呬腑" placement="top">
- <RadioButton value="center">
- <IconifyIcon
- icon="ant-design:align-center-outlined"
- class="size-6"
- />
- </RadioButton>
- </Tooltip>
- </RadioGroup>
- </FormItem>
- <FormItem label="鍋忕Щ閲�" name="marginLeft">
- <Slider v-model:value="formData.marginLeft" :max="100" :min="0" />
- </FormItem>
- <FormItem label="楂樺害" name="height">
- <Slider v-model:value="formData.height" :max="200" :min="20" />
- </FormItem>
- </Card>
- <Card title="涓绘爣棰�" class="property-group">
- <FormItem label="鏂囧瓧" name="title">
- <InputWithColor
- v-model="formData.title"
- v-model:color="formData.titleColor"
- show-count
- :maxlength="20"
- />
- </FormItem>
- <FormItem label="澶у皬" name="titleSize">
- <Slider v-model:value="formData.titleSize" :max="60" :min="10" />
- </FormItem>
- <FormItem label="绮楃粏" name="titleWeight">
- <Slider
- v-model:value="formData.titleWeight"
- :min="100"
- :max="900"
- :step="100"
- />
- </FormItem>
- </Card>
- <Card title="鍓爣棰�" class="property-group">
- <FormItem label="鏂囧瓧" name="description">
- <InputWithColor
- v-model="formData.description"
- v-model:color="formData.descriptionColor"
- show-count
- :maxlength="50"
- />
- </FormItem>
- <FormItem label="澶у皬" name="descriptionSize">
- <Slider
- v-model:value="formData.descriptionSize"
- :max="60"
- :min="10"
- />
- </FormItem>
- <FormItem label="绮楃粏" name="descriptionWeight">
- <Slider
- v-model:value="formData.descriptionWeight"
- :min="100"
- :max="900"
- :step="100"
- />
- </FormItem>
- </Card>
- <Card title="鏌ョ湅鏇村" class="property-group">
- <FormItem label="鏄惁鏄剧ず" name="more.show">
- <Checkbox v-model:checked="formData.more.show" />
- </FormItem>
- <!-- 鏇村鎸夐挳鐨� 鏍峰紡閫夋嫨 -->
- <template v-if="formData.more.show">
- <FormItem label="鏍峰紡" name="more.type">
- <RadioGroup v-model:value="formData.more.type">
- <Radio value="text">鏂囧瓧</Radio>
- <Radio value="icon">鍥炬爣</Radio>
- <Radio value="all">鏂囧瓧+鍥炬爣</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem
- label="鏇村鏂囧瓧"
- name="more.text"
- v-show="formData.more.type !== 'icon'"
- >
- <Input v-model:value="formData.more.text" />
- </FormItem>
- <FormItem label="璺宠浆閾炬帴" name="more.url">
- <AppLinkInput v-model="formData.more.url" />
- </FormItem>
- </template>
- </Card>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/config.ts
deleted file mode 100644
index 568cfb9..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鐢ㄦ埛鍗$墖灞炴�� */
-export interface UserCardProperty {
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'UserCard',
- name: '鐢ㄦ埛鍗$墖',
- icon: 'mdi:user-card-details',
- property: {
- style: {
- bgType: 'color',
- bgColor: '',
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<UserCardProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/index.vue
deleted file mode 100644
index b4d04c2..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/index.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<script setup lang="ts">
-import type { UserCardProperty } from './config';
-
-import { IconifyIcon } from '../../../../../../../../packages/icons/src';
-
-import { Avatar } from 'ant-design-vue';
-
-/** 鐢ㄦ埛鍗$墖 */
-defineOptions({ name: 'UserCard' });
-// 瀹氫箟灞炴��
-defineProps<{ property: UserCardProperty }>();
-</script>
-<template>
- <div class="flex flex-col">
- <div class="flex items-center justify-between px-4 py-6">
- <div class="flex flex-1 items-center gap-4">
- <Avatar :size="60" class="flex items-center">
- <IconifyIcon icon="ep:avatar" :size="60" />
- </Avatar>
- <span class="text-[18px] font-bold">鑺嬮亾婧愮爜</span>
- </div>
- <IconifyIcon icon="tdesign:qrcode" :size="20" />
- </div>
- <div class="flex items-center justify-between bg-card px-5 py-2 text-xs">
- <span class="text-orange-500">鐐瑰嚮缁戝畾鎵嬫満鍙�</span>
- <span class="rounded-lg bg-orange-500 px-2 py-1 text-white">
- 鍘荤粦瀹�
- </span>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue
deleted file mode 100644
index 46a0e16..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-card/property.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<script setup lang="ts">
-import type { UserCardProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鐢ㄦ埛鍗$墖灞炴�ч潰鏉� */
-defineOptions({ name: 'UserCardProperty' });
-
-const props = defineProps<{ modelValue: UserCardProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style" />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/config.ts
deleted file mode 100644
index 4666980..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鐢ㄦ埛鍗″埜灞炴�� */
-export interface UserCouponProperty {
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'UserCoupon',
- name: '鐢ㄦ埛鍗″埜',
- icon: 'lucide:ticket',
- property: {
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<UserCouponProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/index.vue
deleted file mode 100644
index a990140..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/index.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-<script setup lang="ts">
-import type { UserCouponProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 鐢ㄦ埛鍗″埜 */
-defineOptions({ name: 'UserCoupon' });
-// 瀹氫箟灞炴��
-defineProps<{ property: UserCouponProperty }>();
-</script>
-<template>
- <Image
- src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/couponCardStyle.png"
- :preview="false"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/property.vue
deleted file mode 100644
index 9a5633b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-coupon/property.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-<script setup lang="ts">
-import type { UserCouponProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-// 鐢ㄦ埛鍗″埜灞炴�ч潰鏉�
-defineOptions({ name: 'UserCouponProperty' });
-
-const props = defineProps<{ modelValue: UserCouponProperty }>();
-const emit = defineEmits(['update:modelValue']);
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style" />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts
deleted file mode 100644
index fcc4a6b..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鐢ㄦ埛璁㈠崟灞炴�� */
-export interface UserOrderProperty {
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'UserOrder',
- name: '鐢ㄦ埛璁㈠崟',
- icon: 'lucide:clipboard-list',
- property: {
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<UserOrderProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue
deleted file mode 100644
index af6f888..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/index.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script setup lang="ts">
-import type { UserOrderProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 鐢ㄦ埛璁㈠崟 */
-defineOptions({ name: 'UserOrder' });
-
-/** 瀹氫箟灞炴�� */
-defineProps<{ property: UserOrderProperty }>();
-</script>
-<template>
- <Image
- src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/orderCardStyle.png"
- :preview="false"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue
deleted file mode 100644
index e670a1e..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-order/property.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<script setup lang="ts">
-import type { UserOrderProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鐢ㄦ埛璁㈠崟灞炴�ч潰鏉� */
-defineOptions({ name: 'UserOrderProperty' });
-
-const props = defineProps<{ modelValue: UserOrderProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style" />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts
deleted file mode 100644
index ab48845..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 鐢ㄦ埛璧勪骇灞炴�� */
-export interface UserWalletProperty {
- style: ComponentStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'UserWallet',
- name: '鐢ㄦ埛璧勪骇',
- icon: 'lucide:wallet',
- property: {
- style: {
- bgType: 'color',
- bgColor: '',
- marginLeft: 8,
- marginRight: 8,
- marginBottom: 8,
- } as ComponentStyle,
- },
-} as DiyComponent<UserWalletProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue
deleted file mode 100644
index 8104d8f..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/index.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script setup lang="ts">
-import type { UserWalletProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 鐢ㄦ埛璧勪骇 */
-defineOptions({ name: 'UserWallet' });
-
-/** 瀹氫箟灞炴�� */
-defineProps<{ property: UserWalletProperty }>();
-</script>
-<template>
- <Image
- src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/walletCardStyle.png"
- :preview="false"
- />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue
deleted file mode 100644
index fc7adf4..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/user-wallet/property.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<script setup lang="ts">
-import type { UserWalletProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 鐢ㄦ埛璧勪骇灞炴�ч潰鏉� */
-defineOptions({ name: 'UserWalletProperty' });
-
-const props = defineProps<{ modelValue: UserWalletProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style" />
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts b/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts
deleted file mode 100644
index db00152..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/config.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { ComponentStyle, DiyComponent } from '../../../util';
-
-/** 瑙嗛鎾斁灞炴�� */
-export interface VideoPlayerProperty {
- videoUrl: string; // 瑙嗛閾炬帴
- posterUrl: string; // 灏侀潰閾炬帴
- autoplay: boolean; // 鏄惁鑷姩鎾斁
- style: VideoPlayerStyle; // 缁勪欢鏍峰紡
-}
-
-/** 瑙嗛鎾斁鏍峰紡 */
-export interface VideoPlayerStyle extends ComponentStyle {
- height: number; // 瑙嗛楂樺害
-}
-
-/** 瀹氫箟缁勪欢 */
-export const component = {
- id: 'VideoPlayer',
- name: '瑙嗛鎾斁',
- icon: 'lucide:video',
- property: {
- videoUrl: '',
- posterUrl: '',
- autoplay: false,
- style: {
- bgType: 'color',
- bgColor: '#fff',
- marginBottom: 8,
- height: 300,
- } as VideoPlayerStyle,
- },
-} as DiyComponent<VideoPlayerProperty>;
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue
deleted file mode 100644
index 08118a3..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/index.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<script setup lang="ts">
-import type { VideoPlayerProperty } from './config';
-
-import { Image } from 'ant-design-vue';
-
-/** 瑙嗛鎾斁 */
-defineOptions({ name: 'VideoPlayer' });
-
-defineProps<{ property: VideoPlayerProperty }>();
-</script>
-<template>
- <div class="w-full" :style="{ height: `${property.style.height}px` }">
- <Image
- class="h-full w-full"
- :src="property.posterUrl"
- v-if="property.posterUrl"
- :preview="false"
- />
- <video
- v-else
- class="h-full w-full"
- :src="property.videoUrl"
- :poster="property.posterUrl"
- :autoplay="property.autoplay"
- controls
- ></video>
- </div>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue b/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue
deleted file mode 100644
index ba48961..0000000
--- a/src/views/mall/promotion/components/diy-editor/components/mobile/video-player/property.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script setup lang="ts">
-import type { VideoPlayerProperty } from './config';
-
-import { useVModel } from '@vueuse/core';
-import { Form, FormItem, Slider, Switch } from 'ant-design-vue';
-
-import UploadFile from '#/components/upload/file-upload.vue';
-import UploadImg from '#/components/upload/image-upload.vue';
-
-import ComponentContainerProperty from '../../component-container-property.vue';
-
-/** 瑙嗛鎾斁灞炴�ч潰鏉� */
-defineOptions({ name: 'VideoPlayerProperty' });
-
-const props = defineProps<{ modelValue: VideoPlayerProperty }>();
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-</script>
-
-<template>
- <ComponentContainerProperty v-model="formData.style">
- <template #style>
- <FormItem label="楂樺害" name="height">
- <Slider v-model:value="formData.style.height" :max="500" :min="100" />
- </FormItem>
- </template>
- <Form
- :model="formData"
- :label-col="{ span: 6 }"
- :wrapper-col="{ span: 18 }"
- >
- <FormItem label="涓婁紶瑙嗛" name="videoUrl">
- <UploadFile
- v-model="formData.videoUrl"
- :file-type="['mp4']"
- :limit="1"
- :file-size="100"
- class="min-w-[80px]"
- />
- </FormItem>
- <FormItem label="涓婁紶灏侀潰" name="posterUrl">
- <UploadImg
- v-model="formData.posterUrl"
- draggable="false"
- height="80px"
- width="100%"
- class="min-w-[80px]"
- :show-description="false"
- >
- <!-- TODO @鑺嬭壙锛氳繖閲屼笉鎻愮ず锛涙槸涓嶆槸缁勪欢寰楀皝瑁呬笅锛�-->
- <template #tip> 寤鸿瀹藉害750 </template>
- </UploadImg>
- </FormItem>
- <FormItem label="鑷姩鎾斁" name="autoplay">
- <Switch v-model:checked="formData.autoplay" />
- </FormItem>
- </Form>
- </ComponentContainerProperty>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/index.vue b/src/views/mall/promotion/components/diy-editor/index.vue
deleted file mode 100644
index df4d874..0000000
--- a/src/views/mall/promotion/components/diy-editor/index.vue
+++ /dev/null
@@ -1,505 +0,0 @@
-<script lang="ts" setup>
-import type { DiyComponent, DiyComponentLibrary, PageConfig } from './util';
-
-import { onMounted, ref, unref, watch } from 'vue';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { cloneDeep, isEmpty, isString } from '../../../../../packages/utils/src';
-
-import { Button, Card, Col, QRCode, Row, Tag, Tooltip } from 'ant-design-vue';
-import draggable from 'vuedraggable';
-
-import statusBarImg from '#/assets/imgs/diy/statusBar.png';
-
-import ComponentContainer from './components/component-container.vue';
-import ComponentLibrary from './components/component-library.vue';
-import { componentConfigs, components } from './components/mobile';
-import { component as NAVIGATION_BAR_COMPONENT } from './components/mobile/navigation-bar/config';
-import { component as PAGE_CONFIG_COMPONENT } from './components/mobile/page-config/config';
-import { component as TAB_BAR_COMPONENT } from './components/mobile/tab-bar/config';
-
-/** 椤甸潰瑁呬慨璇︽儏椤� */
-defineOptions({
- name: 'DiyPageDetail',
- components,
-});
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps({
- modelValue: { type: [String, Object], required: true }, // 椤甸潰閰嶇疆锛屾敮鎸� Json 瀛楃涓�
- title: { type: String, default: '' }, // 鏍囬
- libs: { type: Array<DiyComponentLibrary>, default: () => [] }, // 缁勪欢搴�
- showNavigationBar: { type: Boolean, default: true }, // 鏄惁鏄剧ず椤堕儴瀵艰埅鏍�
- showTabBar: { type: Boolean, default: false }, // 鏄惁鏄剧ず搴曢儴瀵艰埅鑿滃崟
- showPageConfig: { type: Boolean, default: true }, // 鏄惁鏄剧ず椤甸潰閰嶇疆
- previewUrl: { type: String, default: '' }, // 棰勮鍦板潃锛氭彁渚涗簡棰勮鍦板潃锛屾墠浼氭樉绀洪瑙堟寜閽�
-});
-
-const emits = defineEmits(['reset', 'save', 'update:modelValue']); // 宸ュ叿鏍忔搷浣�
-
-// TODO @xingyu锛氳涓嶈鍔犺繖涓紵ele 閲屾槸鏈夎繖涓殑銆�
-// const qrcode = useQRCode(props.previewUrl, {
-// errorCorrectionLevel: 'H',
-// margin: 4,
-// }); // 棰勮浜岀淮鐮�
-
-const pageConfigComponent = ref<DiyComponent<any>>(
- cloneDeep(PAGE_CONFIG_COMPONENT),
-); // 椤甸潰璁剧疆缁勪欢
-const navigationBarComponent = ref<DiyComponent<any>>(
- cloneDeep(NAVIGATION_BAR_COMPONENT),
-); // 椤堕儴瀵艰埅鏍�
-const tabBarComponent = ref<DiyComponent<any>>(cloneDeep(TAB_BAR_COMPONENT)); // 搴曢儴瀵艰埅鑿滃崟
-
-const selectedComponent = ref<DiyComponent<any>>(); // 閫変腑鐨勭粍浠讹紝榛樿閫変腑椤堕儴瀵艰埅鏍�
-const selectedComponentIndex = ref<number>(-1); // 閫変腑鐨勭粍浠剁储寮�
-const pageComponents = ref<DiyComponent<any>[]>([]); // 缁勪欢鍒楄〃
-
-/**
- * 鐩戝惉浼犲叆鐨勯〉闈㈤厤缃�
- * 瑙f瀽鍑� pageConfigComponent 椤甸潰鏁翠綋鐨勯厤缃紝navigationBarComponent銆乸ageComponents銆乼abBarComponent 椤甸潰涓娿�佷腑銆佷笅鐨勯厤缃�
- */
-watch(
- () => props.modelValue,
- () => {
- const modelValue =
- isString(props.modelValue) && !isEmpty(props.modelValue)
- ? (JSON.parse(props.modelValue) as PageConfig)
- : props.modelValue;
- // noinspection SuspiciousTypeOfGuard
- pageConfigComponent.value.property =
- (typeof modelValue !== 'string' && modelValue?.page) ||
- PAGE_CONFIG_COMPONENT.property;
- // noinspection SuspiciousTypeOfGuard
- navigationBarComponent.value.property =
- (typeof modelValue !== 'string' && modelValue?.navigationBar) ||
- NAVIGATION_BAR_COMPONENT.property;
- // noinspection SuspiciousTypeOfGuard
- tabBarComponent.value.property =
- (typeof modelValue !== 'string' && modelValue?.tabBar) ||
- TAB_BAR_COMPONENT.property;
- // 鏌ユ壘瀵瑰簲鐨勯〉闈㈢粍浠�
- // noinspection SuspiciousTypeOfGuard
- pageComponents.value = (
- (typeof modelValue !== 'string' && modelValue?.components) ||
- []
- ).map((item: any) => {
- const component = componentConfigs[item.id];
- return { ...component, property: item.property };
- });
- },
- {
- immediate: true,
- },
-);
-
-/** 閫夋嫨缁勪欢淇敼鍏跺睘鎬у悗鏇存柊瀹冪殑閰嶇疆 */
-watch(
- selectedComponent,
- (val: any) => {
- if (!val || selectedComponentIndex.value === -1) {
- return;
- }
- // 濡傛灉鏄熀纭�璁剧疆椤碉紝榛樿閫変腑鐨勭储寮曟敼鎴� -1锛屼负浜嗛槻姝㈠垹闄ょ粍浠跺悗鍒囨崲鍒版椤靛鑷存姤閿�
- // https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/792
- if (props.showTabBar) {
- selectedComponentIndex.value = -1;
- }
- pageComponents.value[selectedComponentIndex.value] =
- selectedComponent.value!;
- },
- { deep: true },
-);
-
-/** 淇濆瓨 */
-function handleSave() {
- // 鍙戦�佷繚瀛橀�氱煡锛岀敱澶栭儴淇濆瓨
- emits('save');
-}
-
-/** 鐩戝惉閰嶇疆淇敼 */
-function pageConfigChange() {
- const pageConfig = {
- page: pageConfigComponent.value.property,
- navigationBar: navigationBarComponent.value.property,
- tabBar: tabBarComponent.value.property,
- components: pageComponents.value.map((component) => {
- // 鍙繚鐣� APP 鏈夌敤鐨勫瓧娈�
- return { id: component.id, property: component.property };
- }),
- } as PageConfig;
- if (!props.showTabBar) {
- delete pageConfig.tabBar;
- }
- // 鍙戦�佹暟鎹洿鏂伴�氱煡
- const modelValue = isString(props.modelValue)
- ? JSON.stringify(pageConfig)
- : pageConfig;
- emits('update:modelValue', modelValue);
-}
-
-watch(
- () => [
- pageConfigComponent.value.property,
- navigationBarComponent.value.property,
- tabBarComponent.value.property,
- pageComponents.value,
- ],
- () => {
- pageConfigChange();
- },
- { deep: true },
-);
-
-/** 澶勭悊椤甸潰閫変腑锛氭樉绀哄睘鎬ц〃鍗� */
-function handlePageSelected(event: any) {
- if (!props.showPageConfig) {
- return;
- }
- // 閰嶇疆浜嗘牱寮� page-prop-area 鐨勫厓绱狅紝鎵嶆樉绀洪〉闈㈣缃�
- if (event?.target?.classList?.contains('page-prop-area')) {
- handleComponentSelected(unref(pageConfigComponent));
- }
-}
-
-/**
- * 閫変腑缁勪欢
- *
- * @param component 缁勪欢
- * @param index 缁勪欢鐨勭储寮�
- */
-function handleComponentSelected(
- component: DiyComponent<any>,
- index: number = -1,
-) {
- // 浣跨敤娣辨嫹璐濋伩鍏嶅搷搴斿紡杩借釜寰幆璀﹀憡
- selectedComponent.value = component;
- selectedComponentIndex.value = index;
-}
-
-/** 閫変腑椤堕儴瀵艰埅鏍� */
-function handleNavigationBarSelected() {
- handleComponentSelected(unref(navigationBarComponent));
-}
-
-/** 閫変腑搴曢儴瀵艰埅鑿滃崟 */
-function handleTabBarSelected() {
- handleComponentSelected(unref(tabBarComponent));
-}
-
-/** 缁勪欢鍙樺姩锛堟嫋鎷斤級 */
-function handleComponentChange(dragEvent: any) {
- // 鏂板锛屽嵆浠庣粍浠跺簱鎷栨嫿娣诲姞缁勪欢
- if (dragEvent.added) {
- const { element, newIndex } = dragEvent.added;
- handleComponentSelected(element, newIndex);
- } else if (dragEvent.moved) {
- // 鎷栨嫿鎺掑簭
- const { newIndex } = dragEvent.moved;
- // 淇濇寔閫変腑
- selectedComponentIndex.value = newIndex;
- }
-}
-
-/** 浜ゆ崲缁勪欢 */
-function swapComponent(oldIndex: number, newIndex: number) {
- const temp = pageComponents.value[oldIndex]!;
- pageComponents.value[oldIndex] = pageComponents.value[newIndex]!;
- pageComponents.value[newIndex] = temp;
- // 淇濇寔閫変腑
- selectedComponentIndex.value = newIndex;
-}
-
-/** 绉诲姩缁勪欢锛堜笂绉汇�佷笅绉伙級 */
-function handleMoveComponent(index: number, direction: number) {
- const newIndex = index + direction;
- if (newIndex < 0 || newIndex >= pageComponents.value.length) {
- return;
- }
- swapComponent(index, newIndex);
-}
-
-/** 澶嶅埗缁勪欢 */
-function handleCopyComponent(index: number) {
- const component = pageComponents.value[index];
- if (component) {
- const clonedComponent = cloneDeep(component);
- clonedComponent.uid = Date.now();
- pageComponents.value.splice(index + 1, 0, clonedComponent);
- }
-}
-
-/** 鍒犻櫎缁勪欢 */
-function handleDeleteComponent(index: number) {
- pageComponents.value.splice(index, 1);
- if (index < pageComponents.value.length) {
- // 1. 涓嶆槸鏈�鍚庝竴涓粍浠舵椂锛屽垹闄ゅ悗閫変腑涓嬮潰鐨勭粍浠�
- const bottomIndex = index;
- const component = pageComponents.value[bottomIndex];
- if (component) {
- handleComponentSelected(component, bottomIndex);
- }
- } else if (pageComponents.value.length > 0) {
- // 2. 涓嶆槸绗竴涓粍浠舵椂锛屽垹闄ゅ悗閫変腑涓婇潰鐨勭粍浠�
- const topIndex = index - 1;
- const component = pageComponents.value[topIndex];
- if (component) {
- handleComponentSelected(component, topIndex);
- }
- } else {
- // 3. 缁勪欢鍏ㄩ儴鍒犻櫎涔嬪悗锛屾樉绀洪〉闈㈣缃�
- handleComponentSelected(unref(pageConfigComponent));
- }
-}
-
-/** 閲嶇疆 */
-function handleReset() {
- emits('reset');
-}
-
-const [PreviewModal, previewModalApi] = useVbenModal({
- showConfirmButton: false,
- showCancelButton: false,
- onCancel() {
- previewModalApi.close();
- },
-});
-
-/** 棰勮 */
-function handlePreview() {
- previewModalApi.open();
-}
-
-/** 璁剧疆榛樿閫変腑鐨勭粍浠� */
-function setDefaultSelectedComponent() {
- if (props.showPageConfig) {
- selectedComponent.value = unref(pageConfigComponent);
- } else if (props.showNavigationBar) {
- selectedComponent.value = unref(navigationBarComponent);
- } else if (props.showTabBar) {
- selectedComponent.value = unref(tabBarComponent);
- }
-}
-
-watch(
- () => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
- () => setDefaultSelectedComponent(),
-);
-
-/** 鍒濆鍖� */
-onMounted(() => {
- setDefaultSelectedComponent();
-});
-</script>
-<template>
- <Page auto-content-height>
- <!-- 椤堕儴锛氬伐鍏锋爮 -->
- <Row class="flex max-h-12 rounded-lg bg-card">
- <!-- 宸︿晶鎿嶄綔鍖� -->
- <Col :span="8">
- <slot name="toolBarLeft"></slot>
- </Col>
- <!-- 涓績鎿嶄綔鍖� -->
- <Col :span="8">
- <span class="flex h-full items-center justify-center">{{ title }}</span>
- </Col>
- <!-- 鍙充晶鎿嶄綔鍖� -->
- <Col :span="8">
- <Button.Group
- direction="vertical"
- size="large"
- class="flex justify-end"
- >
- <Tooltip title="閲嶇疆">
- <Button @click="handleReset">
- <IconifyIcon class="size-5" icon="lucide:refresh-cw" />
- </Button>
- </Tooltip>
- <Tooltip v-if="previewUrl" title="棰勮">
- <Button @click="handlePreview">
- <IconifyIcon class="size-5" icon="lucide:eye" />
- </Button>
- </Tooltip>
- <Tooltip title="淇濆瓨">
- <Button @click="handleSave">
- <IconifyIcon class="size-5" icon="lucide:check" />
- </Button>
- </Tooltip>
- </Button.Group>
- </Col>
- </Row>
- <!-- 涓績鍖哄煙 -->
- <Row class="mt-4 h-[calc(80vh)]">
- <!-- 宸︿晶锛氱粍浠跺簱锛圕omponentLibrary锛� -->
- <Col :span="6">
- <ComponentLibrary v-if="libs && libs.length > 0" :list="libs" />
- </Col>
- <!-- 涓績锛氳璁″尯鍩燂紙ComponentContainer锛� -->
- <Col :span="12">
- <div
- class="editor-center relative flex max-h-[calc(80vh)] w-full flex-1 flex-col overflow-y-auto"
- @click="handlePageSelected"
- >
- <!-- 鎵嬫満椤堕儴 -->
- <div class="mx-auto flex w-96 flex-col">
- <!-- 鎵嬫満椤堕儴鐘舵�佹爮 -->
- <img alt="" class="h-6 bg-card" :src="statusBarImg" />
- <!-- 鎵嬫満椤堕儴瀵艰埅鏍� -->
- <ComponentContainer
- v-if="showNavigationBar"
- :active="selectedComponent?.id === navigationBarComponent.id"
- :component="navigationBarComponent"
- :show-toolbar="false"
- class="cursor-pointer"
- @click="handleNavigationBarSelected"
- />
- </div>
- <!-- 缁濆瀹氫綅鐨勭粍浠讹細渚嬪 寮圭獥銆佹诞鍔ㄦ寜閽瓑 -->
- <div
- v-for="(component, index) in pageComponents"
- :key="index"
- @click="handleComponentSelected(component, index)"
- >
- <component
- :is="component.id"
- v-if="
- component.position === 'fixed' &&
- selectedComponent?.uid === component.uid
- "
- :property="component.property"
- />
- </div>
- <!-- 鎵嬫満椤甸潰缂栬緫鍖哄煙 -->
- <div
- class="mx-auto min-h-full w-96 bg-no-repeat"
- :style="{
- // backgroundColor: pageConfigComponent.property.backgroundColor,
- backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
- }"
- >
- <div
- class="relative my-0 min-h-full w-full items-center justify-center bg-auto bg-no-repeat"
- >
- <draggable
- v-model="pageComponents"
- :animation="200"
- :force-fallback="false"
- class="min-h-[70vh] w-full"
- filter=".component-toolbar"
- ghost-class="draggable-ghost"
- group="component"
- item-key="index"
- @change="handleComponentChange"
- >
- <template #item="{ element, index }">
- <ComponentContainer
- v-if="!element.position || element.position === 'center'"
- :active="selectedComponentIndex === index"
- :can-move-down="index < pageComponents.length - 1"
- :can-move-up="index > 0"
- :component="element"
- @click="handleComponentSelected(element, index)"
- @copy="handleCopyComponent(index)"
- @delete="handleDeleteComponent(index)"
- @move="
- (direction: number) =>
- handleMoveComponent(index, direction)
- "
- />
- </template>
- </draggable>
- </div>
- </div>
- <!-- 鎵嬫満搴曢儴瀵艰埅 -->
- <div
- v-if="showTabBar"
- class="bottom-2 mx-auto mb-2 w-96 cursor-pointer"
- >
- <ComponentContainer
- :active="selectedComponent?.id === tabBarComponent.id"
- :component="tabBarComponent"
- :show-toolbar="false"
- @click="handleTabBarSelected"
- />
- </div>
- <!-- 鍥哄畾甯冨眬鐨勭粍浠� 鎿嶄綔鎸夐挳鍖� -->
- <div class="absolute right-4 top-0 flex flex-col gap-2">
- <Tag
- v-if="showPageConfig"
- :color="
- selectedComponent?.uid === pageConfigComponent.uid
- ? 'blue'
- : 'default'
- "
- class="cursor-pointer"
- size="large"
- @click="handleComponentSelected(pageConfigComponent)"
- >
- <IconifyIcon
- :icon="pageConfigComponent.icon"
- class="mr-2 size-4"
- />
- <span>{{ pageConfigComponent.name }}</span>
- </Tag>
- <template v-for="(component, index) in pageComponents" :key="index">
- <Tag
- v-if="component.position === 'fixed'"
- :color="
- selectedComponent?.uid === component.uid ? 'blue' : 'default'
- "
- closable
- class="cursor-pointer"
- size="large"
- @click="handleComponentSelected(component)"
- @close="handleDeleteComponent(index)"
- >
- <IconifyIcon :icon="component.icon" class="size-4" />
- <span>{{ component.name }}</span>
- </Tag>
- </template>
- </div>
- </div>
- </Col>
- <!-- 鍙充晶锛氬睘鎬ч潰鏉匡紙ComponentContainerProperty锛� -->
- <Col :span="6" v-if="selectedComponent?.property">
- <Card
- class="h-[calc(80vh)] px-2 py-4"
- :body-style="{ padding: 0 }"
- :head-style="{ padding: 0, minHeight: '40px' }"
- >
- <!-- 缁勪欢鍚嶇О -->
- <template #title>
- <div class="flex h-8 items-center gap-1">
- <IconifyIcon :icon="selectedComponent?.icon" color="gray" />
- <span>{{ selectedComponent?.name }}</span>
- </div>
- </template>
- <div
- class="property mt-0 max-h-[calc(80vh-100px)] overflow-y-auto p-4"
- >
- <component
- :is="`${selectedComponent?.id}Property`"
- :key="selectedComponent?.uid || selectedComponent?.id"
- v-model="selectedComponent.property"
- />
- </div>
- </Card>
- </Col>
- </Row>
-
- <!-- 棰勮寮规 -->
- <PreviewModal title="棰勮" class="w-[700px]">
- <div class="flex justify-around">
- <iframe
- :src="previewUrl"
- class="h-[667px] w-96 rounded-lg border-4 border-solid p-0.5"
- ></iframe>
- <div class="flex flex-col">
- <div class="text-base">鎵嬫満鎵爜棰勮</div>
- <QRCode :value="previewUrl" error-level="H" />
- </div>
- </div>
- </PreviewModal>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/components/diy-editor/util.ts b/src/views/mall/promotion/components/diy-editor/util.ts
deleted file mode 100644
index 8926ca5..0000000
--- a/src/views/mall/promotion/components/diy-editor/util.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import type { NavigationBarProperty } from './components/mobile/navigation-bar/config';
-import type { PageConfigProperty } from './components/mobile/page-config/config';
-import type { TabBarProperty } from './components/mobile/tab-bar/config';
-
-/** 椤甸潰瑁呬慨缁勪欢 */
-export interface DiyComponent<T> {
- uid?: number; // 鐢ㄤ簬鍖哄垎鍚屼竴绉嶇粍浠剁殑涓嶅悓瀹炰緥
- id: string; // 缁勪欢鍞竴鏍囪瘑
- name: string; // 缁勪欢鍚嶇О
- icon: string; // 缁勪欢鍥炬爣
- /*
- 缁勪欢浣嶇疆锛�
- top: 鍥哄畾浜庢墜鏈洪《閮紝渚嬪 椤堕儴鐨勫鑸爮
- bottom: 鍥哄畾浜庢墜鏈哄簳閮紝渚嬪 搴曢儴鐨勮彍鍗曞鑸爮
- center: 浣嶄簬鎵嬫満涓績锛屾瘡涓粍浠跺崰涓�琛岋紝椤哄簭鍚戜笅鎺掑垪
- 绌猴細鍚� center
- fixed: 鐢辩粍浠惰嚜宸卞喅瀹氫綅缃紝濡傚脊绐椾綅浜庢墜鏈轰腑蹇冦�佹诞鍔ㄦ寜閽竴鑸綅浜庢墜鏈哄彸涓嬭
- */
- position?: '' | 'bottom' | 'center' | 'fixed' | 'top';
- property: T; // 缁勪欢灞炴��
-}
-
-/** 椤甸潰瑁呬慨缁勪欢搴� */
-export interface DiyComponentLibrary {
- name: string; // 缁勪欢搴撳悕绉�
- extended: boolean; // 鏄惁灞曞紑
- components: string[]; // 缁勪欢鍒楄〃
-}
-
-/** 缁勪欢鏍峰紡 */
-export interface ComponentStyle {
- bgType: 'color' | 'img'; // 鑳屾櫙绫诲瀷
- bgColor: string; // 鑳屾櫙棰滆壊
- bgImg: string; // 鑳屾櫙鍥剧墖
- // 澶栬竟璺�
- margin: number;
- marginTop: number;
- marginRight: number;
- marginBottom: number;
- marginLeft: number;
- // 鍐呰竟璺�
- padding: number;
- paddingTop: number;
- paddingRight: number;
- paddingBottom: number;
- paddingLeft: number;
- // 杈规鍦嗚
- borderRadius: number;
- borderTopLeftRadius: number;
- borderTopRightRadius: number;
- borderBottomRightRadius: number;
- borderBottomLeftRadius: number;
-}
-
-/** 椤甸潰閰嶇疆 */
-export interface PageConfig {
- page: PageConfigProperty; // 椤甸潰灞炴��
- navigationBar: NavigationBarProperty; // 椤堕儴瀵艰埅鏍忓睘鎬�
- tabBar?: TabBarProperty; // 搴曢儴瀵艰埅鑿滃崟灞炴��
-
- components: PageComponent[]; // 椤甸潰缁勪欢鍒楄〃
-}
-
-export type PageComponent = Pick<DiyComponent<any>, 'id' | 'property'>; // 椤甸潰缁勪欢锛屽彧淇濈暀缁勪欢 ID锛岀粍浠跺睘鎬�
-
-/** 椤甸潰缁勪欢搴� */
-export const PAGE_LIBS = [
- {
- name: '鍩虹缁勪欢',
- extended: true,
- components: [
- 'SearchBar',
- 'NoticeBar',
- 'MenuSwiper',
- 'MenuGrid',
- 'MenuList',
- 'Popover',
- 'FloatingActionButton',
- ],
- },
- {
- name: '鍥炬枃缁勪欢',
- extended: true,
- components: [
- 'ImageBar',
- 'Carousel',
- 'TitleBar',
- 'VideoPlayer',
- 'Divider',
- 'MagicCube',
- 'HotZone',
- ],
- },
- {
- name: '鍟嗗搧缁勪欢',
- extended: true,
- components: ['ProductCard', 'ProductList'],
- },
- {
- name: '鐢ㄦ埛缁勪欢',
- extended: true,
- components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon'],
- },
- {
- name: '钀ラ攢缁勪欢',
- extended: true,
- components: [
- 'PromotionCombination',
- 'PromotionSeckill',
- 'PromotionPoint',
- 'CouponCard',
- 'PromotionArticle',
- ],
- },
-] as DiyComponentLibrary[];
diff --git a/src/views/mall/promotion/components/draggable/index.vue b/src/views/mall/promotion/components/draggable/index.vue
deleted file mode 100644
index 1eb7fe8..0000000
--- a/src/views/mall/promotion/components/draggable/index.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<script setup lang="ts">
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { cloneDeep } from '../../../../../packages/utils/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Tooltip } from 'ant-design-vue';
-import VueDraggable from 'vuedraggable';
-
-/** 鎷栨嫿缁勪欢灏佽 */
-defineOptions({ name: 'Draggable' });
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps({
- modelValue: {
- type: Array,
- default: () => [],
- }, // 缁戝畾鍊�
- emptyItem: {
- type: Object,
- default: () => ({}),
- }, // 绌虹殑鍏冪礌锛氱偣鍑绘坊鍔犳寜閽椂锛屽垱寤哄厓绱犲苟娣诲姞鍒板垪琛紱榛樿涓虹┖瀵硅薄
- limit: {
- type: Number,
- default: Number.MAX_VALUE,
- }, // 鏁伴噺闄愬埗锛氶粯璁や负 0锛岃〃绀轰笉闄愬埗
- min: {
- type: Number,
- default: 1,
- }, // 鏈�灏忔暟閲忥細榛樿涓�1
-});
-
-const emit = defineEmits(['update:modelValue']);
-
-const formData = useVModel(props, 'modelValue', emit);
-
-/** 澶勭悊娣诲姞 */
-function handleAdd() {
- return formData.value.push(cloneDeep(props.emptyItem || {}));
-}
-
-/** 澶勭悊鍒犻櫎 */
-const handleDelete = function (index: number) {
- return formData.value.splice(index, 1);
-};
-</script>
-
-<template>
- <div class="text-sm text-gray-500">鎷栧姩宸︿笂瑙掔殑灏忓渾鐐瑰彲瀵瑰叾鎺掑簭</div>
- <VueDraggable
- :list="formData"
- :force-fallback="true"
- :animation="200"
- handle=".drag-icon"
- class="mt-2"
- item-key="index"
- >
- <template #item="{ element, index }">
- <div class="mb-1 flex flex-col gap-1 rounded border border-gray-200 p-2">
- <!-- 鎿嶄綔鎸夐挳鍖� -->
- <div
- class="-m-2 mb-1 flex flex-row items-center justify-between rounded-t bg-secondary p-2"
- >
- <Tooltip title="鎷栧姩鎺掑簭">
- <IconifyIcon
- icon="lucide:move"
- class="drag-icon cursor-move text-gray-500"
- />
- </Tooltip>
- <Tooltip title="鍒犻櫎">
- <IconifyIcon
- icon="ep:delete"
- class="cursor-pointer text-red-500"
- v-if="formData.length > min"
- @click="handleDelete(index)"
- />
- </Tooltip>
- </div>
- <!-- 鍐呭鍖� -->
- <slot :element="element" :index="index"></slot>
- </div>
- </template>
- </VueDraggable>
- <Tooltip :title="limit < Number.MAX_VALUE ? `鏈�澶氭坊鍔�${limit}涓猔 : undefined">
- <Button
- type="primary"
- ghost
- class="mt-1 w-full"
- :disabled="limit > 0 && formData.length >= limit"
- @click="handleAdd"
- >
- <template #icon>
- <IconifyIcon icon="lucide:plus" />
- </template>
- 娣诲姞
- </Button>
- </Tooltip>
-</template>
diff --git a/src/views/mall/promotion/components/index.ts b/src/views/mall/promotion/components/index.ts
deleted file mode 100644
index 6dbe001..0000000
--- a/src/views/mall/promotion/components/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export { default as AppLinkInput } from './app-link-input/index.vue';
-export { default as AppLinkSelectDialog } from './app-link-input/select-dialog.vue';
-export { default as ColorInput } from './color-input/index.vue';
-export { default as DiyEditor } from './diy-editor/index.vue';
-export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util';
-export { default as Draggable } from './draggable/index.vue';
-export { default as InputWithColor } from './input-with-color/index.vue';
-
-export { default as MagicCubeEditor } from './magic-cube-editor/index.vue';
-export { default as VerticalButtonGroup } from './vertical-button-group/index.vue';
diff --git a/src/views/mall/promotion/components/input-with-color/index.vue b/src/views/mall/promotion/components/input-with-color/index.vue
deleted file mode 100644
index 1671318..0000000
--- a/src/views/mall/promotion/components/input-with-color/index.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<script lang="ts" setup>
-import { useVModels } from '@vueuse/core';
-import { Input } from 'ant-design-vue';
-
-/** 甯﹂鑹查�夋嫨鍣ㄨ緭鍏ユ */
-defineOptions({ name: 'InputWithColor' });
-
-const props = defineProps({
- modelValue: {
- type: String,
- default: '',
- },
- color: {
- type: String,
- default: '',
- },
-});
-
-const emit = defineEmits(['update:modelValue', 'update:color']);
-
-const { modelValue, color } = useVModels(props, emit);
-</script>
-
-<template>
- <div class="flex gap-2">
- <Input v-model:value="modelValue" v-bind="$attrs" class="flex-1" />
- <input
- v-model="color"
- type="color"
- class="h-8 w-10 cursor-pointer rounded border border-gray-300"
- />
- </div>
-</template>
-
-<style scoped lang="scss">
-input[type='color'] {
- &::-webkit-color-swatch-wrapper {
- padding: 2px;
- }
-
- &::-webkit-color-swatch {
- border: none;
- border-radius: 2px;
- }
-}
-</style>
diff --git a/src/views/mall/promotion/components/magic-cube-editor/index.vue b/src/views/mall/promotion/components/magic-cube-editor/index.vue
deleted file mode 100644
index a69656d..0000000
--- a/src/views/mall/promotion/components/magic-cube-editor/index.vue
+++ /dev/null
@@ -1,248 +0,0 @@
-<script lang="ts" setup>
-import type { Point, Rect } from './util';
-
-import { ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { createRect, isContains, isOverlap } from './util';
-
-/**
- * 榄旀柟缂栬緫鍣紝鏈変袱閮ㄥ垎缁勬垚锛�
- * 1. 榄旀柟鐭╅樀锛氫綅浜庡簳灞傦紝鐢辨柟鍧楃粍浠剁殑浜岀淮琛ㄦ牸锛岀敤浜庡垱寤虹儹鍖�
- * 鎿嶄綔鏂规硶锛�
- * 1.1 鐐瑰嚮鍏朵腑涓�涓柟鍧楀氨浼氳繘鍏ョ儹鍖洪�夋嫨妯″紡
- * 1.2 鍐嶆鐐瑰嚮鍙﹀涓�涓柟鍧楁椂锛岀粨鏉熺儹鍖洪�夋嫨妯″紡
- * 1.3 鍦ㄤ袱涓柟鍧椾腑闂寸殑鍖哄煙鍒涘缓鐑尯
- * 濡傛灉涓ゆ鐐瑰嚮鐨勯兘鏄悓涓�鏂瑰潡锛屽氨鍙垱寤轰竴涓牸瀛愮殑鐑尯
- * 2. 鐑尯锛氫綅浜庨《灞傦紝閲囩敤缁濆瀹氫綅锛岃鐩栧湪榄旀柟鐭╅樀涓婇潰銆�
- */
-defineOptions({ name: 'MagicCubeEditor' });
-
-/** 瀹氫箟灞炴�� */
-const props = defineProps({
- modelValue: {
- type: Array as () => Rect[],
- default: () => [],
- }, // 鐑尯鍒楄〃
- rows: {
- type: Number,
- default: 4,
- }, // 琛屾暟锛岄粯璁� 4 琛�
- cols: {
- type: Number,
- default: 4,
- }, // 鍒楁暟锛岄粯璁� 4 鍒�
- cubeSize: {
- type: Number,
- default: 75,
- }, // 鏂瑰潡澶у皬锛屽崟浣峱x锛岄粯璁�75px
-});
-
-const emit = defineEmits(['update:modelValue', 'hotAreaSelected']); // 鍙戦�佹ā鍨嬫洿鏂�
-
-/** 鏂瑰潡 */
-type Cube = {
- active: boolean; // 鏄惁婵�娲�
-} & Point;
-
-const cubes = ref<Cube[][]>([]); // 榄旀柟鐭╅樀锛氭墍鏈夌殑鏂瑰潡
-
-/** 鐩戝惉琛屾暟銆佸垪鏁板彉鍖� */
-watch(
- () => [props.rows, props.cols],
- () => {
- // 娓呯┖榄旀柟
- cubes.value = [];
- if (!props.rows || !props.cols) return;
-
- // 鍒濆鍖栭瓟鏂�
- for (let row = 0; row < props.rows; row++) {
- cubes.value[row] = [];
- for (let col = 0; col < props.cols; col++) {
- cubes.value[row]!.push({ x: col, y: row, active: false });
- }
- }
- },
- { immediate: true },
-);
-
-const hotAreas = ref<Rect[]>([]); // 鐑尯鍒楄〃
-
-/** 鍒濆鍖栫儹鍖� */
-watch(
- () => props.modelValue,
- () => (hotAreas.value = props.modelValue || []),
- { immediate: true },
-);
-
-const hotAreaBeginCube = ref<Cube>(); // 鐑尯璧峰鏂瑰潡
-const isHotAreaSelectMode = () => !!hotAreaBeginCube.value; // 鏄惁寮�鍚簡鐑尯閫夋嫨妯″紡
-
-/**
- * 澶勭悊榧犳爣鐐瑰嚮鏂瑰潡
- *
- * @param currentRow 褰撳墠琛屽彿
- * @param currentCol 褰撳墠鍒楀彿
- */
-function handleCubeClick(currentRow: number, currentCol: number) {
- const currentCube = cubes.value[currentRow]?.[currentCol];
- if (!currentCube) {
- return;
- }
-
- // 鎯呭喌 1锛氳繘鍏ョ儹鍖洪�夋嫨妯″紡
- if (!isHotAreaSelectMode()) {
- hotAreaBeginCube.value = currentCube;
- hotAreaBeginCube.value!.active = true;
- return;
- }
-
- // 鎯呭喌 2锛氱粨鏉熺儹鍖洪�夋嫨妯″紡
- hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube));
- // 缁撴潫鐑尯閫夋嫨妯″紡
- exitHotAreaSelectMode();
- // 鍒涘缓鍚庡氨閫変腑鐑尯
- const hotAreaIndex = hotAreas.value.length - 1;
- const hotArea = hotAreas.value[hotAreaIndex];
- if (hotArea) {
- handleHotAreaSelected(hotArea, hotAreaIndex);
- }
- // 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
- emitUpdateModelValue();
-}
-
-/**
- * 澶勭悊榧犳爣缁忚繃鏂瑰潡
- *
- * @param currentRow 褰撳墠琛屽彿
- * @param currentCol 褰撳墠鍒楀彿
- */
-function handleCellHover(currentRow: number, currentCol: number) {
- // 褰撳墠娌℃湁杩涘叆鐑尯閫夋嫨妯″紡
- if (!isHotAreaSelectMode()) {
- return;
- }
-
- // 褰撳墠宸查�夌殑鍖哄煙
- const currentCube = cubes.value[currentRow]?.[currentCol];
- if (!currentCube) {
- return;
- }
-
- const currentSelectedArea = createRect(hotAreaBeginCube.value!, currentCube);
- // 鐑尯涓嶅厑璁搁噸鍙�
- for (const hotArea of hotAreas.value) {
- // 妫�鏌ユ槸鍚﹂噸鍙�
- if (isOverlap(hotArea, currentSelectedArea)) {
- // 缁撴潫鐑尯閫夋嫨妯″紡
- exitHotAreaSelectMode();
- return;
- }
- }
-
- // 婵�娲婚�変腑鍖哄煙鍐呴儴鐨勬柟鍧�
- eachCube((_, __, cube) => {
- cube.active = isContains(currentSelectedArea, cube);
- });
-}
-
-/** 澶勭悊鐑尯鍒犻櫎 */
-function handleDeleteHotArea(index: number) {
- hotAreas.value.splice(index, 1);
- // 缁撴潫鐑尯閫夋嫨妯″紡
- exitHotAreaSelectMode();
- // 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
- emitUpdateModelValue();
-}
-
-const emitUpdateModelValue = () => emit('update:modelValue', hotAreas.value); // 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
-
-const selectedHotAreaIndex = ref(0); // 鐑尯閫変腑
-
-/** 澶勭悊鐑尯閫変腑 */
-function handleHotAreaSelected(hotArea: Rect, index: number) {
- selectedHotAreaIndex.value = index;
- emit('hotAreaSelected', hotArea, index);
-}
-
-/** 缁撴潫鐑尯閫夋嫨妯″紡 */
-function exitHotAreaSelectMode() {
- // 绉婚櫎鏂瑰潡婵�娲绘爣璁�
- eachCube((_, __, cube) => {
- if (cube.active) {
- cube.active = false;
- }
- });
-
- // 娓呴櫎璧风偣
- hotAreaBeginCube.value = undefined;
-}
-
-/**
- * 杩唬榄旀柟鐭╅樀
- * @param callback 鍥炶皟
- */
-function eachCube(callback: (x: number, y: number, cube: Cube) => void) {
- for (const [x, row] of cubes.value.entries()) {
- if (!row) continue;
- for (const [y, cube] of row.entries()) {
- if (cube) {
- callback(x, y, cube);
- }
- }
- }
-}
-</script>
-<template>
- <table class="relative border-collapse border-spacing-0">
- <!-- 搴曞眰锛氶瓟鏂圭煩闃� -->
- <tbody>
- <tr v-for="(rowCubes, row) in cubes" :key="row">
- <td
- v-for="(cube, col) in rowCubes"
- :key="col"
- class="box-border cursor-pointer border text-center align-middle hover:bg-primary-100 active:bg-primary-200"
- :class="[{ active: cube.active }]"
- :style="{
- width: `${cubeSize}px`,
- height: `${cubeSize}px`,
- }"
- @click="handleCubeClick(row, col)"
- @mouseenter="handleCellHover(row, col)"
- >
- <IconifyIcon icon="lucide:plus" class="inline-block size-6" />
- </td>
- </tr>
- </tbody>
- <!-- 椤跺眰锛氱儹鍖� -->
- <div
- v-for="(hotArea, index) in hotAreas"
- :key="index"
- class="absolute box-border flex items-center justify-center border border-primary bg-primary-200"
- :style="{
- top: `${cubeSize * hotArea.top}px`,
- left: `${cubeSize * hotArea.left}px`,
- height: `${cubeSize * hotArea.height}px`,
- width: `${cubeSize * hotArea.width}px`,
- }"
- @click="handleHotAreaSelected(hotArea, index)"
- @mouseover="exitHotAreaSelectMode"
- >
- <!-- 鍙充笂瑙掔儹鍖哄垹闄ゆ寜閽� -->
- <div
- v-if="selectedHotAreaIndex === index && hotArea.width && hotArea.height"
- class="absolute -right-2 -top-2 z-[1] size-6 h-4 w-4 items-center rounded-lg bg-card"
- @click="handleDeleteHotArea(index)"
- >
- <IconifyIcon
- icon="lucide:x"
- class="inset-0 items-center bg-primary text-white"
- />
- </div>
- <span v-if="hotArea.width">
- {{ `${hotArea.width}脳${hotArea.height}` }}
- </span>
- </div>
- </table>
-</template>
diff --git a/src/views/mall/promotion/components/magic-cube-editor/util.ts b/src/views/mall/promotion/components/magic-cube-editor/util.ts
deleted file mode 100644
index d55cd4b..0000000
--- a/src/views/mall/promotion/components/magic-cube-editor/util.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/** 鍧愭爣鐐� */
-export interface Point {
- x: number;
- y: number;
-}
-
-/** 鐭╁舰 */
-export interface Rect {
- left: number; // 宸︿笂瑙� X 杞村潗鏍�
- top: number; // 宸︿笂瑙� Y 杞村潗鏍�
- right: number; // 鍙充笅瑙� X 杞村潗鏍�
- bottom: number; // 鍙充笅瑙� Y 杞村潗鏍�
- width: number; // 鐭╁舰瀹藉害
- height: number; // 鐭╁舰楂樺害
-}
-
-/**
- * 鍒ゆ柇涓や釜鐭╁舰鏄惁閲嶅彔
- *
- * @param a 鐭╁舰 A
- * @param b 鐭╁舰 B
- */
-export function isOverlap(a: Rect, b: Rect): boolean {
- return (
- a.left < b.left + b.width &&
- a.left + a.width > b.left &&
- a.top < b.top + b.height &&
- a.height + a.top > b.top
- );
-}
-
-/**
- * 妫�鏌ュ潗鏍囩偣鏄惁鍦ㄧ煩褰㈠唴
- * @param hotArea 鐭╁舰
- * @param point 鍧愭爣
- */
-export function isContains(hotArea: Rect, point: Point): boolean {
- return (
- point.x >= hotArea.left &&
- point.x < hotArea.right &&
- point.y >= hotArea.top &&
- point.y < hotArea.bottom
- );
-}
-
-/**
- * 鍦ㄤ袱涓潗鏍囩偣涓棿锛屽垱寤轰竴涓煩褰�
- *
- * 瀛樺湪浠ヤ笅鎯呭喌锛�
- * 1. 涓や釜鍧愭爣鐐规槸鍚屼竴涓綅缃紝鍙崰涓�涓綅缃殑姝f柟褰紝瀹介珮閮戒负 1
- * 2. X 杞村潗鏍囩浉鍚岋紝鍙崰涓�琛岀殑鐭╁舰锛岄珮搴︿负 1
- * 3. Y 杞村潗鏍囩浉鍚岋紝鍙崰涓�鍒楃殑鐭╁舰锛屽搴︿负 1
- * 4. 澶氳澶氬垪鐨勭煩褰�
- *
- * @param a 鍧愭爣鐐逛竴
- * @param b 鍧愭爣鐐逛簩
- */
-export function createRect(a: Point, b: Point): Rect {
- // 璁$畻鐭╁舰鐨勮寖鍥�
- let [left, left2] = [a.x, b.x].toSorted();
- left = left ?? 0;
- left2 = left2 ?? 0;
- let [top, top2] = [a.y, b.y].toSorted();
- top = top ?? 0;
- top2 = top2 ?? 0;
- const right = left2 + 1;
- const bottom = top2 + 1;
- const height = bottom - top;
- const width = right - left;
- return { left, right, top, bottom, height, width };
-}
diff --git a/src/views/mall/promotion/components/vertical-button-group/index.vue b/src/views/mall/promotion/components/vertical-button-group/index.vue
deleted file mode 100644
index 2f865b3..0000000
--- a/src/views/mall/promotion/components/vertical-button-group/index.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script setup lang="ts">
-import { Space } from 'ant-design-vue';
-
-/**
- * 鍨傜洿鎸夐挳缁�
- * Ant Design Vue 鐨勬寜閽粍锛岄�氳繃 Space 瀹炵幇鍨傜洿甯冨眬
- */
-defineOptions({ name: 'VerticalButtonGroup' });
-</script>
-
-<template>
- <Space v-bind="$attrs">
- <Space.Compact direction="vertical">
- <slot></slot>
- </Space.Compact>
- </Space>
-</template>
diff --git a/src/views/mall/promotion/coupon/components/index.ts b/src/views/mall/promotion/coupon/components/index.ts
deleted file mode 100644
index 24cb4e2..0000000
--- a/src/views/mall/promotion/coupon/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as CouponSelect } from './select.vue';
-export { default as CouponSendForm } from './send-form.vue';
diff --git a/src/views/mall/promotion/coupon/components/select-data.ts b/src/views/mall/promotion/coupon/components/select-data.ts
deleted file mode 100644
index 709439f..0000000
--- a/src/views/mall/promotion/coupon/components/select-data.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import {
- discountFormat,
- remainedCountFormat,
- takeLimitCountFormat,
- validityTypeFormat,
-} from '../formatter';
-
-/** 浼樻儬鍒搁�夋嫨鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '浼樻儬鍒稿悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紭鎯犲埜鍚嶇О',
- allowClear: true,
- },
- },
- {
- fieldName: 'discountType',
- label: '浼樻儬绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'),
- placeholder: '璇烽�夋嫨浼樻儬绫诲瀷',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 浼樻儬鍒搁�夋嫨鐨勮〃鏍煎垪 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- { type: 'checkbox', width: 55 },
- {
- field: 'name',
- title: '浼樻儬鍒稿悕绉�',
- minWidth: 140,
- },
- {
- field: 'productScope',
- title: '绫诲瀷',
- minWidth: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE },
- },
- },
- {
- field: 'discountType',
- title: '浼樻儬绫诲瀷',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE },
- },
- },
- {
- field: 'discountPrice',
- title: '浼樻儬鍔涘害',
- minWidth: 100,
- formatter: ({ row }) => discountFormat(row),
- },
- {
- field: 'takeType',
- title: '棰嗗彇鏂瑰紡',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE },
- },
- },
- {
- field: 'validityType',
- title: '浣跨敤鏃堕棿',
- minWidth: 185,
- align: 'center',
- formatter: ({ row }) => validityTypeFormat(row),
- },
- {
- field: 'totalCount',
- title: '鍙戞斁鏁伴噺',
- minWidth: 100,
- align: 'center',
- },
- {
- field: 'remainedCount',
- title: '鍓╀綑鏁伴噺',
- minWidth: 100,
- align: 'center',
- formatter: ({ row }) => remainedCountFormat(row),
- },
- {
- field: 'takeLimitCount',
- title: '棰嗗彇涓婇檺',
- minWidth: 100,
- align: 'center',
- formatter: ({ row }) => takeLimitCountFormat(row),
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 80,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- ];
-}
diff --git a/src/views/mall/promotion/coupon/components/select.vue b/src/views/mall/promotion/coupon/components/select.vue
deleted file mode 100644
index 94d79a0..0000000
--- a/src/views/mall/promotion/coupon/components/select.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { nextTick, ref } from 'vue';
-
-import { Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCouponTemplatePage } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { useGridColumns, useGridFormSchema } from './select-data';
-
-defineOptions({ name: 'CouponSelect' });
-
-const props = defineProps<{
- takeType?: number; // 棰嗗彇鏂瑰紡
-}>();
-
-const emit = defineEmits<{
- (e: 'change', v: MallCouponTemplateApi.CouponTemplate[]): void;
-}>();
-
-const visible = ref(false); // 寮圭獥鏄剧ず鐘舵��
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 500,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCouponTemplatePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- canTakeTypes: props.takeType ? [props.takeType] : [],
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCouponTemplateApi.CouponTemplate>,
-});
-
-/** 鎵撳紑寮圭獥 */
-async function open() {
- visible.value = true;
- // 绛夊緟 Modal 鍜� Grid 缁勪欢鎸傝浇瀹屾垚鍚庡啀鏌ヨ
- await nextTick();
- await gridApi.query();
-}
-
-/** 纭閫夋嫨 */
-function handleConfirm() {
- const selectedRecords = (gridApi.grid?.getCheckboxRecords() ||
- []) as MallCouponTemplateApi.CouponTemplate[];
- emit('change', selectedRecords);
- closeModal();
-}
-
-/** 鍏抽棴寮圭獥 */
-function closeModal() {
- visible.value = false;
-}
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open,
-});
-</script>
-
-<template>
- <Modal
- v-model:open="visible"
- title="閫夋嫨浼樻儬鍒�"
- width="65%"
- :destroy-on-close="true"
- @ok="handleConfirm"
- @cancel="closeModal"
- >
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/coupon/components/send-form-data.ts b/src/views/mall/promotion/coupon/components/send-form-data.ts
deleted file mode 100644
index 35a823b..0000000
--- a/src/views/mall/promotion/coupon/components/send-form-data.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps } from '#/adapter/vxe-table';
-
-import {
- discountFormat,
- remainedCountFormat,
- usePriceFormat,
- validityTypeFormat,
-} from '../formatter';
-
-/** 鎼滅储琛ㄥ崟鐨� schema */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'name',
- label: '浼樻儬鍒稿悕绉�',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紭鎯犲埜鍚嶇О',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridProps['columns'] {
- return [
- {
- title: '浼樻儬鍒稿悕绉�',
- field: 'name',
- minWidth: 120,
- },
- {
- title: '浼樻儬閲戦 / 鎶樻墸',
- field: 'discount',
- minWidth: 120,
- formatter: ({ row }) => discountFormat(row),
- },
- {
- title: '鏈�浣庢秷璐�',
- field: 'usePrice',
- minWidth: 100,
- formatter: ({ row }) => usePriceFormat(row),
- },
- {
- title: '鏈夋晥鏈熼檺',
- field: 'validityType',
- minWidth: 140,
- formatter: ({ row }) => validityTypeFormat(row),
- },
- {
- title: '鍓╀綑鏁伴噺',
- minWidth: 100,
- formatter: ({ row }) => remainedCountFormat(row),
- },
- {
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/coupon/components/send-form.vue b/src/views/mall/promotion/coupon/components/send-form.vue
deleted file mode 100644
index 404fdc9..0000000
--- a/src/views/mall/promotion/coupon/components/send-form.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script lang="ts" setup>
-import type { VxeGridProps } from '#/adapter/vxe-table';
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { CouponTemplateTakeTypeEnum } from '../../../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { sendCoupon } from '#/api/mall/promotion/coupon/coupon';
-import { getCouponTemplatePage } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { useFormSchema, useGridColumns } from './send-form-data';
-
-/** 鍙戦�佷紭鎯犲埜 */
-async function handleSendCoupon(row: MallCouponTemplateApi.CouponTemplate) {
- modalApi.lock();
- try {
- await sendCoupon({
- templateId: row.id,
- userIds: modalApi.getData().userIds,
- });
- message.success('鍙戦�佹垚鍔�');
- await modalApi.close();
- } finally {
- modalApi.unlock();
- }
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 500,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCouponTemplatePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- canTakeTypes: [CouponTemplateTakeTypeEnum.ADMIN.type],
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeGridProps<MallCouponTemplateApi.CouponTemplate>,
-});
-
-const [Modal, modalApi] = useVbenModal({
- showCancelButton: false,
- showConfirmButton: false,
-});
-</script>
-
-<template>
- <Modal title="鍙戦�佷紭鎯犲姷" class="w-1/2">
- <Grid>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍙戦��',
- type: 'link',
- auth: ['promotion:coupon:send'],
- onClick: () => handleSendCoupon(row),
- },
- ]"
- />
- </template>
- </Grid>
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/coupon/data.ts b/src/views/mall/promotion/coupon/data.ts
deleted file mode 100644
index 3e82bd1..0000000
--- a/src/views/mall/promotion/coupon/data.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-import { discountFormat } from './formatter';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'nickname',
- label: '浼氬憳鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ細鍛樻樀绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '棰嗗彇鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'nickname',
- title: '浼氬憳鏄电О',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '浼樻儬鍒稿悕绉�',
- minWidth: 140,
- },
- {
- field: 'productScope',
- title: '绫诲瀷',
- minWidth: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE },
- },
- },
- {
- field: 'discountType',
- title: '浼樻儬',
- minWidth: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE },
- },
- },
- {
- field: 'discountPrice',
- title: '浼樻儬鍔涘害',
- minWidth: 110,
- formatter: ({ row }) => {
- return discountFormat(row);
- },
- },
- {
- field: 'takeType',
- title: '棰嗗彇鏂瑰紡',
- minWidth: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE },
- },
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_COUPON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '棰嗗彇鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'useTime',
- title: '浣跨敤鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- width: 100,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/coupon/formatter.ts b/src/views/mall/promotion/coupon/formatter.ts
deleted file mode 100644
index 5c1cd14..0000000
--- a/src/views/mall/promotion/coupon/formatter.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import {
- CouponTemplateValidityTypeEnum,
- PromotionDiscountTypeEnum,
-} from '../../../../packages/constants/src';
-import { floatToFixed2, formatDate } from '../../../../packages/utils/src';
-
-/** 鏍煎紡鍖栥�愪紭鎯犻噾棰�/鎶樻墸銆� */
-export function discountFormat(row: MallCouponTemplateApi.CouponTemplate) {
- if (row.discountType === PromotionDiscountTypeEnum.PRICE.type) {
- return `楼${floatToFixed2(row.discountPrice)}`;
- }
- if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
- return `${row.discountPercent}%`;
- }
- return `鏈煡銆�${row.discountType}銆慲;
-}
-
-/** 鏍煎紡鍖栥�愰鍙栦笂闄愩�� */
-export function takeLimitCountFormat(
- row: MallCouponTemplateApi.CouponTemplate,
-) {
- if (row.takeLimitCount) {
- if (row.takeLimitCount === -1) {
- return '鏃犻鍙栭檺鍒�';
- }
- return `${row.takeLimitCount} 寮�/浜篳;
- } else {
- return ' ';
- }
-}
-
-/** 鏍煎紡鍖栥�愭湁鏁堟湡闄愩�� */
-export function validityTypeFormat(row: MallCouponTemplateApi.CouponTemplate) {
- if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) {
- return `${formatDate(row.validStartTime)} 鑷� ${formatDate(row.validEndTime)}`;
- }
- if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) {
- return `棰嗗彇鍚庣 ${row.fixedStartTerm} - ${row.fixedEndTerm} 澶╁唴鍙敤`;
- }
- return `鏈煡銆�${row.validityType}銆慲;
-}
-
-/** 鏍煎紡鍖栥�恡otalCount銆� */
-export function totalCountFormat(row: MallCouponTemplateApi.CouponTemplate) {
- if (row.totalCount === -1) {
- return '涓嶉檺鍒�';
- }
- return `${row.totalCount}`;
-}
-
-/** 鏍煎紡鍖栥�愬墿浣欐暟閲忋�� */
-export function remainedCountFormat(row: MallCouponTemplateApi.CouponTemplate) {
- if (row.totalCount === -1) {
- return '涓嶉檺鍒�';
- }
- return `${row.totalCount - row.takeCount}`;
-}
-
-/** 鏍煎紡鍖栥�愭渶浣庢秷璐广�� */
-export function usePriceFormat(row: MallCouponTemplateApi.CouponTemplate) {
- return `楼${floatToFixed2(row.usePrice)}`;
-}
diff --git a/src/views/mall/promotion/coupon/index.vue b/src/views/mall/promotion/coupon/index.vue
deleted file mode 100644
index 3e37af9..0000000
--- a/src/views/mall/promotion/coupon/index.vue
+++ /dev/null
@@ -1,144 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCouponApi } from '#/api/mall/promotion/coupon/coupon';
-
-import { ref } from 'vue';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { message, TabPane, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteCoupon,
- getCouponPage,
-} from '#/api/mall/promotion/coupon/coupon';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-defineOptions({ name: 'PromotionCoupon' });
-
-const activeTab = ref('all');
-const statusTabs = ref(getStatusTabs());
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒犻櫎浼樻儬鍒� */
-async function handleDelete(row: MallCouponApi.Coupon) {
- const hideLoading = message.loading({
- content: '鍥炴敹涓�...',
- duration: 0,
- });
- try {
- await deleteCoupon(row.id!);
- message.success('鍥炴敹鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鑾峰彇鐘舵�侀�夐」鍗¢厤缃� */
-function getStatusTabs() {
- const tabs = [
- {
- label: '鍏ㄩ儴',
- value: 'all',
- },
- ];
- const statusOptions = getDictOptions(DICT_TYPE.PROMOTION_COUPON_STATUS);
- for (const option of statusOptions) {
- tabs.push({
- label: option.label,
- value: String(option.value),
- });
- }
- return tabs;
-}
-
-/** Tab 鍒囨崲 */
-function handleTabChange(tabName: any) {
- activeTab.value = tabName;
- gridApi.query();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const params = {
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- // Tab鐘舵�佽繃婊�
- status:
- activeTab.value === 'all' ? undefined : Number(activeTab.value),
- };
- return await getCouponPage(params);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCouponApi.Coupon>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戜紭鎯犲姷"
- url="https://doc.iocoder.cn/mall/promotion-coupon/"
- />
- </template>
-
- <Grid>
- <template #toolbar-actions>
- <Tabs class="w-full" @change="handleTabChange">
- <TabPane
- v-for="tab in statusTabs"
- :key="tab.value"
- :tab="tab.label"
- />
- </Tabs>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍥炴敹',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:coupon:delete'],
- popConfirm: {
- title:
- '鍥炴敹灏嗕細鏀跺洖浼氬憳棰嗗彇鐨勫緟浣跨敤鐨勪紭鎯犲埜锛屽凡浣跨敤鐨勫皢鏃犳硶鍥炴敹锛岀‘瀹氳鍥炴敹鎵�閫変紭鎯犲埜鍚楋紵',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/coupon/template/data.ts b/src/views/mall/promotion/coupon/template/data.ts
deleted file mode 100644
index 67c3531..0000000
--- a/src/views/mall/promotion/coupon/template/data.ts
+++ /dev/null
@@ -1,471 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import {
- CommonStatusEnum,
- CouponTemplateTakeTypeEnum,
- CouponTemplateValidityTypeEnum,
- DICT_TYPE,
- PromotionDiscountTypeEnum,
- PromotionProductScopeEnum,
-} from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-import {
- discountFormat,
- remainedCountFormat,
- takeLimitCountFormat,
- totalCountFormat,
- validityTypeFormat,
-} from '../formatter';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '浼樻儬鍒稿悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紭鎯犲埜鍚嶇О',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '浼樻儬鍒告弿杩�',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紭鎯犲埜鎻忚堪',
- },
- },
- {
- fieldName: 'productScope',
- label: '浼樻儬鍔电被鍨�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE, 'number'),
- },
- rules: 'required',
- defaultValue: PromotionProductScopeEnum.ALL.scope,
- },
- {
- fieldName: 'productSpuIds',
- label: '鍟嗗搧',
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productScopeValues'],
- show: (model) =>
- model.productScope === PromotionProductScopeEnum.SPU.scope,
- trigger(values, form) {
- // 褰撳姞杞藉凡鏈夋暟鎹椂锛屾牴鎹� productScopeValues 璁剧疆 productSpuIds
- if (
- values.productScope === PromotionProductScopeEnum.SPU.scope &&
- values.productScopeValues
- ) {
- form.setFieldValue('productSpuIds', values.productScopeValues);
- }
- },
- },
- rules: 'required',
- },
- {
- fieldName: 'productCategoryIds',
- label: '鍟嗗搧鍒嗙被',
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productScopeValues'],
- show: (model) =>
- model.productScope === PromotionProductScopeEnum.CATEGORY.scope,
- trigger(values, form) {
- // 褰撳姞杞藉凡鏈夋暟鎹椂锛屾牴鎹� productScopeValues 璁剧疆 productCategoryIds
- if (
- values.productScope === PromotionProductScopeEnum.CATEGORY.scope &&
- values.productScopeValues
- ) {
- const categoryIds = values.productScopeValues;
- // 鍗曢�夋椂浣跨敤鏁扮粍涓嶈兘鍙嶆樉锛屽彇绗竴涓厓绱�
- form.setFieldValue(
- 'productCategoryIds',
- Array.isArray(categoryIds) && categoryIds.length > 0
- ? categoryIds[0]
- : categoryIds,
- );
- }
- },
- },
- rules: 'required',
- },
- {
- fieldName: 'discountType',
- label: '浼樻儬绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'),
- },
- rules: 'required',
- defaultValue: PromotionDiscountTypeEnum.PRICE.type,
- },
- {
- fieldName: 'discountPrice',
- label: '浼樻儬鍒搁潰棰�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '璇疯緭鍏ヤ紭鎯犻噾棰濓紝鍗曚綅锛氬厓',
- addonAfter: '鍏�',
- },
- dependencies: {
- triggerFields: ['discountType'],
- show: (model) =>
- model.discountType === PromotionDiscountTypeEnum.PRICE.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'discountPercent',
- label: '浼樻儬鍒告姌鎵�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 1,
- max: 9.9,
- precision: 1,
- placeholder: '浼樻儬鍒告姌鎵d笉鑳藉皬浜� 1 鎶橈紝涓斾笉鍙ぇ浜� 9.9 鎶�',
- addonAfter: '鎶�',
- },
- dependencies: {
- triggerFields: ['discountType'],
- show: (model) =>
- model.discountType === PromotionDiscountTypeEnum.PERCENT.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'discountLimitPrice',
- label: '鏈�澶氫紭鎯�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '璇疯緭鍏ユ渶澶氫紭鎯�',
- addonAfter: '鍏�',
- },
- dependencies: {
- triggerFields: ['discountType'],
- show: (model) =>
- model.discountType === PromotionDiscountTypeEnum.PERCENT.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'usePrice',
- label: '婊″灏戝厓鍙互浣跨敤',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '鏃犻棬妲涜璁句负 0',
- addonAfter: '鍏�',
- },
- rules: 'required',
- },
- {
- fieldName: 'takeType',
- label: '棰嗗彇鏂瑰紡',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE, 'number'),
- },
- rules: 'required',
- defaultValue: CouponTemplateTakeTypeEnum.USER.type,
- },
- {
- fieldName: 'totalCount',
- label: '鍙戞斁鏁伴噺',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: -1,
- placeholder: '鍙戞斁鏁伴噺锛屾病鏈変箣鍚庝笉鑳介鍙栨垨鍙戞斁锛�-1 涓轰笉闄愬埗',
- addonAfter: '寮�',
- },
- dependencies: {
- triggerFields: ['takeType'],
- show: (model) =>
- model.takeType === CouponTemplateTakeTypeEnum.USER.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'takeLimitCount',
- label: '姣忎汉闄愰涓暟',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: -1,
- placeholder: '璁剧疆涓� -1 鏃讹紝鍙棤闄愰鍙�',
- addonAfter: '寮�',
- },
- dependencies: {
- triggerFields: ['takeType'],
- show: (model) => model.takeType === 1,
- },
- rules: 'required',
- },
- {
- fieldName: 'validityType',
- label: '鏈夋晥鏈熺被鍨�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(
- DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE,
- 'number',
- ),
- },
- defaultValue: CouponTemplateValidityTypeEnum.DATE.type,
- rules: 'required',
- },
- {
- fieldName: 'validTimes',
- label: '鍥哄畾鏃ユ湡',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- valueFormat: 'x',
- },
- dependencies: {
- triggerFields: ['validityType'],
- show: (model) =>
- model.validityType === CouponTemplateValidityTypeEnum.DATE.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'fixedStartTerm',
- label: '棰嗗彇鏃ユ湡',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '绗� 0 涓轰粖澶╃敓鏁�',
- addonBefore: '绗�',
- addonAfter: '澶�',
- },
- dependencies: {
- triggerFields: ['validityType'],
- show: (model) =>
- model.validityType === CouponTemplateValidityTypeEnum.TERM.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'fixedEndTerm',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ョ粨鏉熷ぉ鏁�',
- addonBefore: '鑷�',
- addonAfter: '澶╂湁鏁�',
- },
- dependencies: {
- triggerFields: ['validityType'],
- show: (model) =>
- model.validityType === CouponTemplateValidityTypeEnum.TERM.type,
- },
- rules: 'required',
- },
- {
- fieldName: 'productScopeValues',
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productSpuIds', 'productCategoryIds'],
- show: () => false,
- trigger(values, form) {
- switch (values.productScope) {
- case PromotionProductScopeEnum.CATEGORY.scope: {
- const categoryIds = Array.isArray(values.productCategoryIds)
- ? values.productCategoryIds
- : [values.productCategoryIds];
- form.setFieldValue('productScopeValues', categoryIds);
- break;
- }
- case PromotionProductScopeEnum.SPU.scope: {
- form.setFieldValue('productScopeValues', values.productSpuIds);
- break;
- }
- }
- },
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '浼樻儬鍒稿悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ紭鎯犲姷鍚�',
- allowClear: true,
- },
- },
- {
- fieldName: 'discountType',
- label: '浼樻儬绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨浼樻儬绫诲瀷',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'),
- },
- },
- {
- fieldName: 'status',
- label: '浼樻儬鍒哥姸鎬�',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨浼樻儬鍒哥姸鎬�',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- onStatusChange?: (
- newStatus: number,
- row: MallCouponTemplateApi.CouponTemplate,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'name',
- title: '浼樻儬鍒稿悕绉�',
- minWidth: 140,
- },
- {
- field: 'productScope',
- title: '绫诲瀷',
- minWidth: 130,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE },
- },
- },
- {
- field: 'discountType',
- title: '浼樻儬',
- minWidth: 110,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE },
- },
- },
- {
- field: 'discountPrice',
- title: '浼樻儬鍔涘害',
- minWidth: 110,
- formatter: ({ row }) => {
- return discountFormat(row);
- },
- },
- {
- field: 'takeType',
- title: '棰嗗彇鏂瑰紡',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE },
- },
- },
- {
- field: 'validityType',
- title: '浣跨敤鏃堕棿',
- minWidth: 180,
- formatter: ({ row }) => {
- return validityTypeFormat(row);
- },
- },
- {
- field: 'totalCount',
- title: '鍙戞斁鏁伴噺',
- minWidth: 100,
- formatter: ({ row }) => {
- return totalCountFormat(row);
- },
- },
- {
- field: 'remainedCount',
- title: '鍓╀綑鏁伴噺',
- minWidth: 100,
- formatter: ({ row }) => {
- return remainedCountFormat(row);
- },
- },
- {
- field: 'takeLimitCount',
- title: '棰嗗彇涓婇檺',
- minWidth: 100,
- formatter: ({ row }) => {
- return takeLimitCountFormat(row);
- },
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- align: 'center',
- cellRender: {
- attrs: { beforeChange: onStatusChange },
- name: 'CellSwitch',
- props: {
- checkedValue: CommonStatusEnum.ENABLE,
- unCheckedValue: CommonStatusEnum.DISABLE,
- },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/coupon/template/index.vue b/src/views/mall/promotion/coupon/template/index.vue
deleted file mode 100644
index 5f431af..0000000
--- a/src/views/mall/promotion/coupon/template/index.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum } from '../../../../../packages/constants/src';
-import { $t } from '../../../../../packages/locales/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteCouponTemplate,
- getCouponTemplatePage,
- updateCouponTemplateStatus,
-} from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'PromotionCouponTemplate' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 缂栬緫浼樻儬鍒告ā鏉� */
-function handleEdit(row: MallCouponTemplateApi.CouponTemplate) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒涘缓浼樻儬鍒告ā鏉� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 鍒犻櫎浼樻儬鍒告ā鏉� */
-async function handleDelete(row: MallCouponTemplateApi.CouponTemplate) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteCouponTemplate(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 浼樻儬鍒告ā鏉跨姸鎬佷慨鏀� */
-async function handleStatusChange(
- newStatus: number,
- row: MallCouponTemplateApi.CouponTemplate,
-): Promise<boolean | undefined> {
- try {
- await confirm(
- `浣犺灏�${row.name}鐨勭姸鎬佸垏鎹负銆�${newStatus === CommonStatusEnum.ENABLE ? '鍚敤' : '鍋滅敤'}銆戝悧锛焋,
- );
- } catch {
- return false;
- }
- // 鏇存柊浼樻儬鍒告ā鏉跨姸鎬�
- await updateCouponTemplateStatus(row.id!, newStatus);
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success($t('ui.actionMessage.operationSuccess'));
- return true;
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleStatusChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getCouponTemplatePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCouponTemplateApi.CouponTemplate>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戜紭鎯犲姷"
- url="https://doc.iocoder.cn/mall/promotion-coupon/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="浼樻儬鍒稿垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['浼樻儬鍒告ā鏉�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:coupon-template:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:coupon-template:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:coupon-template:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/coupon/template/modules/form.vue b/src/views/mall/promotion/coupon/template/modules/form.vue
deleted file mode 100644
index bef5fe6..0000000
--- a/src/views/mall/promotion/coupon/template/modules/form.vue
+++ /dev/null
@@ -1,178 +0,0 @@
-<script lang="ts" setup>
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { CouponTemplateTakeTypeEnum } from '../../../../../../packages/constants/src';
-import { convertToInteger, formatToFraction } from '../../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createCouponTemplate,
- getCouponTemplate,
- updateCouponTemplate,
-} from '#/api/mall/promotion/coupon/couponTemplate';
-import { $t } from '#/locales';
-import { ProductCategorySelect } from '#/views/mall/product/category/components';
-import { SpuShowcase } from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<
- Partial<MallCouponTemplateApi.CouponTemplate> & {
- productCategoryIds?: number | number[];
- productSpuIds?: number[];
- }
->({});
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['浼樻儬鍒告ā鏉�'])
- : $t('ui.actionTitle.create', ['浼樻儬鍒告ā鏉�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- // 鍚屾鍟嗗搧/鍒嗙被閫夋嫨鍒拌〃鍗曪紝纭繚楠岃瘉鏃惰兘鑾峰彇鍒板��
- if (formData.value.productSpuIds) {
- await formApi.setFieldValue(
- 'productSpuIds',
- formData.value.productSpuIds,
- );
- }
- if (formData.value.productCategoryIds) {
- await formApi.setFieldValue(
- 'productCategoryIds',
- formData.value.productCategoryIds,
- );
- }
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const formValues = (await formApi.getValues()) as any;
- const data = await processSubmitData(formValues);
- try {
- await (formData.value?.id
- ? updateCouponTemplate(data)
- : createCouponTemplate(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = {};
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallCouponTemplateApi.CouponTemplate>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getCouponTemplate(data.id);
- const processedData = await processLoadData(formData.value as any);
- // 璁剧疆鍒拌〃鍗�
- await formApi.setValues(processedData);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 澶勭悊鎻愪氦鏁版嵁 */
-async function processSubmitData(
- formValues: any,
-): Promise<MallCouponTemplateApi.CouponTemplate> {
- return {
- ...formValues,
- // 閲戦杞崲锛氬厓杞垎
- discountPrice: convertToInteger(formValues.discountPrice),
- discountPercent:
- formValues.discountPercent === undefined
- ? undefined
- : formValues.discountPercent * 10,
- discountLimitPrice: convertToInteger(formValues.discountLimitPrice),
- usePrice: convertToInteger(formValues.usePrice),
- // 澶勭悊鏈夋晥鏈熸椂闂�
- validStartTime:
- formValues.validTimes && formValues.validTimes.length === 2
- ? formValues.validTimes[0]
- : undefined,
- validEndTime:
- formValues.validTimes && formValues.validTimes.length === 2
- ? formValues.validTimes[1]
- : undefined,
- // 澶勭悊鍙戞斁鏁伴噺鍜岄檺棰嗘暟閲�
- totalCount:
- formValues.takeType === CouponTemplateTakeTypeEnum.USER.type
- ? formValues.totalCount
- : -1,
- takeLimitCount:
- formValues.takeType === CouponTemplateTakeTypeEnum.USER.type
- ? formValues.takeLimitCount
- : -1,
- };
-}
-
-/** 澶勭悊鍔犺浇鐨勬暟鎹� */
-async function processLoadData(
- data: MallCouponTemplateApi.CouponTemplate,
-): Promise<any> {
- return {
- ...data,
- // 閲戦杞崲锛氬垎杞厓
- discountPrice: formatToFraction(data.discountPrice),
- discountPercent:
- data.discountPercent === undefined
- ? undefined
- : data.discountPercent / 10,
- discountLimitPrice: formatToFraction(data.discountLimitPrice),
- usePrice: formatToFraction(data.usePrice),
- // 澶勭悊鏈夋晥鏈熸椂闂�
- validTimes:
- data.validStartTime && data.validEndTime
- ? [data.validStartTime, data.validEndTime]
- : [],
- };
-}
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4">
- <!-- 鑷畾涔夋彃妲斤細鍟嗗搧閫夋嫨 -->
- <template #productSpuIds>
- <SpuShowcase v-model="formData.productSpuIds" />
- </template>
- <!-- 鑷畾涔夋彃妲斤細鍒嗙被閫夋嫨 -->
- <template #productCategoryIds>
- <ProductCategorySelect v-model="formData.productCategoryIds" />
- </template>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/discountActivity/data.ts b/src/views/mall/promotion/discountActivity/data.ts
deleted file mode 100644
index 0496b4f..0000000
--- a/src/views/mall/promotion/discountActivity/data.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { formatDate } from '../../../../packages/utils/src';
-
-/** 琛ㄥ崟閰嶇疆 */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- rules: 'required',
- },
- {
- fieldName: 'startTime',
- label: '寮�濮嬫椂闂�',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
- showTime: false,
- valueFormat: 'x',
- format: 'YYYY-MM-DD',
- },
- rules: 'required',
- },
- {
- fieldName: 'endTime',
- label: '缁撴潫鏃堕棿',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
- showTime: false,
- valueFormat: 'x',
- format: 'YYYY-MM-DD',
- },
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- },
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'spuIds',
- label: '娲诲姩鍟嗗搧',
- component: 'Input',
- formItemClass: 'col-span-2',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'activeTime',
- label: '娲诲姩鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- placeholder: ['寮�濮嬫椂闂�', '缁撴潫鏃堕棿'],
- allowClear: true,
- valueFormat: 'YYYY-MM-DD HH:mm:ss',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'activityTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- formatter: ({ row }) => {
- if (!row.startTime || !row.endTime) return '';
- return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`;
- },
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/discountActivity/index.vue b/src/views/mall/promotion/discountActivity/index.vue
deleted file mode 100644
index e9c4515..0000000
--- a/src/views/mall/promotion/discountActivity/index.vue
+++ /dev/null
@@ -1,167 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDiscountActivityApi } from '#/api/mall/promotion/discount/discountActivity';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closeDiscountActivity,
- deleteDiscountActivity,
- getDiscountActivityPage,
-} from '#/api/mall/promotion/discount/discountActivity';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import DiscountActivityForm from './modules/form.vue';
-
-defineOptions({ name: 'PromotionDiscountActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: DiscountActivityForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓婊″噺娲诲姩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫婊″噺娲诲姩 */
-function handleEdit(row: MallDiscountActivityApi.DiscountActivity) {
- formModalApi.setData(row).open();
-}
-
-/** 鍏抽棴婊″噺娲诲姩 */
-async function handleClose(row: MallDiscountActivityApi.DiscountActivity) {
- await confirm('纭鍏抽棴璇ラ檺鏃舵姌鎵f椿鍔ㄥ悧锛�');
- const hideLoading = message.loading({
- content: '姝e湪鍏抽棴涓�...',
- duration: 0,
- });
- try {
- await closeDiscountActivity(row.id!);
- message.success('鍏抽棴鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎婊″噺娲诲姩 */
-async function handleDelete(row: MallDiscountActivityApi.DiscountActivity) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDiscountActivity(row.id!);
- message.success({
- content: $t('ui.actionMessage.deleteSuccess', [row.name]),
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDiscountActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDiscountActivityApi.DiscountActivity>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戦檺鏃舵姌鎵�"
- url="https://doc.iocoder.cn/mall/promotion-discount/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="闄愭椂鎶樻墸娲诲姩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['闄愭椂鎶樻墸娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:discount-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:discount-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:discount-activity:close'],
- ifShow: row.status === 0,
- onClick: handleClose.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:discount-activity:delete'],
- ifShow: row.status !== 0,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/discountActivity/modules/form.vue b/src/views/mall/promotion/discountActivity/modules/form.vue
deleted file mode 100644
index eebbe7b..0000000
--- a/src/views/mall/promotion/discountActivity/modules/form.vue
+++ /dev/null
@@ -1,362 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallDiscountActivityApi } from '#/api/mall/promotion/discount/discountActivity';
-import type {
- PropertyAndValues,
- RuleConfig,
- SpuProperty,
-} from '#/views/mall/product/spu/components';
-
-import { computed, ref } from 'vue';
-
-import { useVbenForm, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { PromotionDiscountTypeEnum } from '../../../../../packages/constants/src';
-import {
- cloneDeep,
- convertToInteger,
- erpCalculatePercentage,
- formatToFraction,
- yuanToFen,
-} from '../../../../../packages/utils/src';
-
-import { Button, InputNumber, message } from 'ant-design-vue';
-
-import { VxeColumn } from '#/adapter/vxe-table';
-import { getSpuDetailList } from '#/api/mall/product/spu';
-import {
- createDiscountActivity,
- getDiscountActivity,
- updateDiscountActivity,
-} from '#/api/mall/promotion/discount/discountActivity';
-import { $t } from '#/locales';
-import {
- getPropertyList,
- SpuAndSkuList,
- SpuSkuSelect,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-defineOptions({ name: 'DiscountActivityForm' });
-
-const emit = defineEmits(['success']);
-
-// ================= 琛ㄥ崟鐩稿叧 =================
-const formData = ref<Partial<MallDiscountActivityApi.DiscountActivity>>({});
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['闄愭椂鎶樻墸娲诲姩'])
- : $t('ui.actionTitle.create', ['闄愭椂鎶樻墸娲诲姩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 100,
- },
- wrapperClass: 'grid-cols-2',
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-// ================= 鍟嗗搧閫夋嫨鐩稿叧 =================
-/** SKU 鎵╁睍绫诲瀷 */
-interface SkuExtension extends MallSpuApi.Sku {
- productConfig: MallDiscountActivityApi.DiscountProduct;
-}
-
-/** SPU 鎵╁睍绫诲瀷 */
-interface SpuExtension extends MallSpuApi.Spu {
- skus?: SkuExtension[];
-}
-
-const spuSelectRef = ref(); // 鍟嗗搧閫夋嫨缁勪欢 Ref
-const spuAndSkuListRef = ref(); // SKU 鍒楄〃缁勪欢 Ref
-const spuList = ref<SpuExtension[]>([]); // 閫夋嫨鐨� SPU 鍒楄〃
-const spuPropertyList = ref<SpuProperty<SpuExtension>[]>([]); // SPU 灞炴�у垪琛�
-const spuIdList = ref<number[]>([]); // 宸查�夋嫨鐨� SPU ID 鍒楄〃
-
-/** SKU 鏍¢獙瑙勫垯閰嶇疆 */
-const ruleConfig: RuleConfig[] = [
- {
- name: 'productConfig.discountPrice',
- rule: (arg) => arg > 0,
- message: '鍟嗗搧浼樻儬閲戦涓嶈兘涓� 0 锛侊紒锛�',
- },
-];
-
-/** 鎵撳紑鍟嗗搧閫夋嫨寮圭獥 */
-function openSpuSelect() {
- spuSelectRef.value?.open();
-}
-
-/** 閫夋嫨鍟嗗搧鍚庣殑鍥炶皟 */
-function handleSpuSelected(spuId: number, skuIds?: number[]) {
- getSpuDetails(spuId, skuIds);
-}
-
-/** 鑾峰彇 SPU 璇︽儏 */
-async function getSpuDetails(
- spuId: number,
- skuIdArr?: number[],
- products?: MallDiscountActivityApi.DiscountProduct[],
- type?: string,
-) {
- // 濡傛灉宸茬粡鍖呭惈璇� SPU 鍒欒烦杩�
- if (spuIdList.value.includes(spuId)) {
- if (type !== 'load') {
- message.error('鏁版嵁閲嶅閫夋嫨锛�');
- }
- return;
- }
- spuIdList.value.push(spuId);
-
- const res = (await getSpuDetailList([spuId])) as SpuExtension[];
- if (res.length === 0) {
- return;
- }
-
- const spu = res[0]!;
- // 绛涢�� SKU
- const selectSkus =
- skuIdArr === undefined
- ? spu.skus
- : spu.skus?.filter((sku) => skuIdArr.includes(sku.id!));
-
- // 涓烘瘡涓� SKU 娣诲姞鎶樻墸閰嶇疆
- selectSkus?.forEach((sku) => {
- let config: MallDiscountActivityApi.DiscountProduct = {
- skuId: sku.id!,
- spuId: spu.id!,
- discountType: 1,
- discountPercent: 0,
- discountPrice: 0,
- };
-
- // 缂栬緫鏃讹紝浣跨敤宸叉湁鐨勯厤缃�
- if (products !== undefined) {
- const product = products.find((item) => item.skuId === sku.id);
- if (product) {
- // 杞崲涓哄厓鏄剧ず
- config = {
- ...product,
- discountPercent: Number(formatToFraction(product.discountPercent)),
- discountPrice: Number(formatToFraction(product.discountPrice)),
- };
- }
- }
- (sku as SkuExtension).productConfig = config;
- });
-
- spu.skus = selectSkus as SkuExtension[];
- spuPropertyList.value.push({
- spuId: spu.id!,
- spuDetail: spu,
- propertyList: getPropertyList(spu) as PropertyAndValues[],
- });
- spuList.value.push(spu);
-}
-
-/** 鍒犻櫎 SPU */
-function handleDeleteSpu(spuId: number) {
- const spuIndex = spuIdList.value.indexOf(spuId);
- if (spuIndex !== -1) {
- spuIdList.value.splice(spuIndex, 1);
- }
- const propertyIndex = spuPropertyList.value.findIndex(
- (item) => item.spuId === spuId,
- );
- if (propertyIndex !== -1) {
- spuPropertyList.value.splice(propertyIndex, 1);
- }
- const listIndex = spuList.value.findIndex((item) => item.id === spuId);
- if (listIndex !== -1) {
- spuList.value.splice(listIndex, 1);
- }
-}
-
-/** 澶勭悊 SKU 浼樻儬閲戦鍙樺姩 */
-function handleSkuDiscountPriceChange(row: SkuExtension) {
- if (row.productConfig.discountPrice <= 0) {
- return;
- }
- // 璁剧疆浼樻儬绫诲瀷锛氭弧鍑�
- row.productConfig.discountType = PromotionDiscountTypeEnum.PRICE.type;
- // 璁$畻鎶樻墸鐧惧垎姣�
- const price = typeof row.price === 'number' ? row.price : Number(row.price);
- const percent = erpCalculatePercentage(
- price - yuanToFen(row.productConfig.discountPrice),
- price,
- );
- row.productConfig.discountPercent =
- typeof percent === 'number' ? percent : Number(percent);
-}
-
-/** 澶勭悊 SKU 鎶樻墸鐧惧垎姣斿彉鍔� */
-function handleSkuDiscountPercentChange(row: SkuExtension) {
- if (
- row.productConfig.discountPercent <= 0 ||
- row.productConfig.discountPercent >= 100
- ) {
- return;
- }
- // 璁剧疆浼樻儬绫诲瀷锛氭姌鎵�
- row.productConfig.discountType = PromotionDiscountTypeEnum.PERCENT.type;
- // 璁$畻浼樻儬閲戦
- const price = typeof row.price === 'number' ? row.price : Number(row.price);
- row.productConfig.discountPrice = Number(
- formatToFraction(price - price * (row.productConfig.discountPercent / 100)),
- );
-}
-
-/** 閲嶇疆琛ㄥ崟 */
-function resetForm() {
- spuList.value = [];
- spuPropertyList.value = [];
- spuIdList.value = [];
- formData.value = {};
-}
-
-// ================= 寮圭獥鐩稿叧 =================
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- // 鏍¢獙鏄惁閫夋嫨浜嗗晢鍝�
- if (spuList.value.length === 0) {
- message.warning('璇烽�夋嫨娲诲姩鍟嗗搧');
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- try {
- // 鑾峰彇鎶樻墸鍟嗗搧閰嶇疆
- const products = cloneDeep(
- spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
- ) as MallDiscountActivityApi.DiscountProduct[];
- // 杞崲閲戦涓哄垎
- products.forEach((item) => {
- item.discountPercent = convertToInteger(item.discountPercent);
- item.discountPrice = convertToInteger(item.discountPrice);
- });
- const data = cloneDeep(
- await formApi.getValues(),
- ) as MallDiscountActivityApi.DiscountActivity;
- data.products = products;
- // 鎻愪氦璇锋眰
- await (formData.value?.id
- ? updateDiscountActivity(data)
- : createDiscountActivity(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- resetForm();
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallDiscountActivityApi.DiscountActivity>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- const activityData = await getDiscountActivity(data.id);
- formData.value = activityData;
- // 鍔犺浇鍟嗗搧璇︽儏
- if (activityData.products && activityData.products.length > 0) {
- // 鎸� spuId 鍒嗙粍
- const spuProductsMap = new Map<
- number,
- MallDiscountActivityApi.DiscountProduct[]
- >();
- for (const product of activityData.products) {
- const spuId = product.spuId;
- if (!spuProductsMap.has(spuId)) {
- spuProductsMap.set(spuId, []);
- }
- spuProductsMap.get(spuId)!.push(product);
- }
- // 鍔犺浇姣忎釜 SPU 鐨勮鎯�
- for (const [spuId, products] of spuProductsMap) {
- const skuIdArr = products.map((p) => p.skuId);
- await getSpuDetails(spuId, skuIdArr, products, 'load');
- }
- }
- // 璁剧疆鍒� values
- await formApi.setValues(activityData);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-[70%]" :title="getTitle">
- <Form>
- <!-- 鑷畾涔夋彃妲斤細鍟嗗搧閫夋嫨 -->
- <template #spuIds>
- <div class="w-full">
- <Button class="mb-4" @click="openSpuSelect">閫夋嫨鍟嗗搧</Button>
- <SpuAndSkuList
- ref="spuAndSkuListRef"
- :deletable="true"
- :rule-config="ruleConfig"
- :spu-list="spuList"
- :spu-property-list-p="spuPropertyList"
- @delete="handleDeleteSpu"
- >
- <!-- 鎵╁睍鍒楋細闄愭椂鎶樻墸娲诲姩鐗规湁閰嶇疆 -->
- <template #default>
- <VxeColumn align="center" min-width="168" title="浼樻儬閲戦(鍏�)">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.productConfig.discountPrice"
- :max="Number(formatToFraction(row.price))"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- @change="handleSkuDiscountPriceChange(row)"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="鎶樻墸鐧惧垎姣�(%)">
- <template #default="{ row }">
- <InputNumber
- v-model:value="row.productConfig.discountPercent"
- :max="100"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- @change="handleSkuDiscountPercentChange(row)"
- />
- </template>
- </VxeColumn>
- </template>
- </SpuAndSkuList>
- </div>
- </template>
- </Form>
- </Modal>
-
- <!-- 鍟嗗搧閫夋嫨寮圭獥 -->
- <SpuSkuSelect
- ref="spuSelectRef"
- :is-select-sku="true"
- @select="handleSpuSelected"
- />
-</template>
diff --git a/src/views/mall/promotion/diy/page/data.ts b/src/views/mall/promotion/diy/page/data.ts
deleted file mode 100644
index cec26ff..0000000
--- a/src/views/mall/promotion/diy/page/data.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 琛ㄥ崟閰嶇疆 */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '椤甸潰鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ〉闈㈠悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- },
- },
- {
- fieldName: 'previewPicUrls',
- component: 'ImageUpload',
- label: '棰勮鍥�',
- componentProps: {
- maxNumber: 10,
- multiple: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '椤甸潰鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ〉闈㈠悕绉�',
- clearable: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 80,
- },
- {
- field: 'previewPicUrls',
- title: '棰勮鍥�',
- minWidth: 120,
- cellRender: {
- name: 'CellImages',
- },
- },
- {
- field: 'name',
- title: '椤甸潰鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/diy/page/decorate/index.vue b/src/views/mall/promotion/diy/page/decorate/index.vue
deleted file mode 100644
index 322ad56..0000000
--- a/src/views/mall/promotion/diy/page/decorate/index.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<script setup lang="ts">
-import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
-
-import { onMounted, ref, unref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { message } from 'ant-design-vue';
-
-import {
- getDiyPageProperty,
- updateDiyPageProperty,
-} from '#/api/mall/promotion/diy/page';
-import { DiyEditor, PAGE_LIBS } from '#/views/mall/promotion/components';
-
-/** 瑁呬慨椤甸潰琛ㄥ崟 */
-defineOptions({ name: 'DiyPageDecorate' });
-
-const route = useRoute();
-
-const formData = ref<MallDiyPageApi.DiyPage>();
-
-/** 鑾峰彇璇︽儏 */
-async function getPageDetail(id: any) {
- const hideLoading = message.loading({
- content: '鍔犺浇涓�...',
- duration: 0,
- });
- try {
- formData.value = await getDiyPageProperty(id);
- } finally {
- hideLoading();
- }
-}
-
-/** 鎻愪氦琛ㄥ崟 */
-async function submitForm() {
- const hideLoading = message.loading({
- content: '淇濆瓨涓�...',
- duration: 0,
- });
- try {
- await updateDiyPageProperty(unref(formData)!);
- message.success('淇濆瓨鎴愬姛');
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- if (!route.params.id) {
- message.warning('鍙傛暟閿欒锛岄〉闈㈢紪鍙蜂笉鑳戒负绌猴紒');
- return;
- }
- formData.value = {} as MallDiyPageApi.DiyPage;
- getPageDetail(route.params.id);
-});
-</script>
-<template>
- <DiyEditor
- v-if="formData?.id"
- v-model="formData!.property"
- :title="formData!.name"
- :libs="PAGE_LIBS"
- @save="submitForm"
- />
-</template>
diff --git a/src/views/mall/promotion/diy/page/index.vue b/src/views/mall/promotion/diy/page/index.vue
deleted file mode 100644
index 392afbd..0000000
--- a/src/views/mall/promotion/diy/page/index.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
-
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteDiyPage, getDiyPagePage } from '#/api/mall/promotion/diy/page';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import DiyPageForm from './modules/form.vue';
-
-defineOptions({ name: 'PromotionDiyPage' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: DiyPageForm,
- destroyOnClose: true,
-});
-
-const { push } = useRouter();
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓 DIY 椤甸潰 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫 DIY 椤甸潰 */
-function handleEdit(row: MallDiyPageApi.DiyPage) {
- formModalApi.setData(row).open();
-}
-
-/** 瑁呬慨椤甸潰 */
-function handleDecorate(row: MallDiyPageApi.DiyPage) {
- push({ name: 'DiyPageDecorate', params: { id: row.id } });
-}
-
-/** 鍒犻櫎 DIY 椤甸潰 */
-async function handleDelete(row: MallDiyPageApi.DiyPage) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDiyPage(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDiyPagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDiyPageApi.DiyPage>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戝晢鍩庤淇�"
- url="https://doc.iocoder.cn/mall/diy/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="瑁呬慨椤甸潰鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['瑁呬慨椤甸潰']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:diy-page:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '瑁呬慨',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:diy-page:update'],
- onClick: handleDecorate.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:diy-page:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:diy-page:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/diy/page/modules/form.vue b/src/views/mall/promotion/diy/page/modules/form.vue
deleted file mode 100644
index 118e51e..0000000
--- a/src/views/mall/promotion/diy/page/modules/form.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script lang="ts" setup>
-import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
-
-import { computed, ref } from 'vue';
-
-import { useVbenForm, useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import {
- createDiyPage,
- getDiyPage,
- updateDiyPage,
-} from '#/api/mall/promotion/diy/page';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDiyPageApi.DiyPage>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['瑁呬慨椤甸潰'])
- : $t('ui.actionTitle.create', ['瑁呬慨椤甸潰']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallDiyPageApi.DiyPage;
- try {
- await (formData.value?.id ? updateDiyPage(data) : createDiyPage(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallDiyPageApi.DiyPage>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDiyPage(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/diy/template/data.ts b/src/views/mall/promotion/diy/template/data.ts
deleted file mode 100644
index 2095a6a..0000000
--- a/src/views/mall/promotion/diy/template/data.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 琛ㄥ崟閰嶇疆 */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '妯℃澘鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユā鏉垮悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- },
- },
- {
- fieldName: 'previewPicUrls',
- component: 'ImageUpload',
- label: '棰勮鍥�',
- componentProps: {
- maxNumber: 10,
- multiple: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '妯℃澘鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユā鏉垮悕绉�',
- clearable: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 80,
- },
- {
- field: 'previewPicUrls',
- title: '棰勮鍥�',
- minWidth: 120,
- cellRender: {
- name: 'CellImages',
- },
- },
- {
- field: 'name',
- title: '妯℃澘鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'used',
- title: '鏄惁浣跨敤',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 250,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/diy/template/decorate/index.vue b/src/views/mall/promotion/diy/template/decorate/index.vue
deleted file mode 100644
index bbf932e..0000000
--- a/src/views/mall/promotion/diy/template/decorate/index.vue
+++ /dev/null
@@ -1,210 +0,0 @@
-<script lang="ts" setup>
-import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
-import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template';
-import type { DiyComponentLibrary } from '#/views/mall/promotion/components'; // 鍟嗗煄鐨� DIY 缁勪欢锛屽湪 DiyEditor 鐩綍涓�
-
-import { onMounted, ref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { useTabs } from '../../../../../../packages/effects/hooks/src';
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-import { useAccessStore } from '../../../../../../packages/stores/src';
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { message, Radio, RadioGroup } from 'ant-design-vue';
-
-import { updateDiyPageProperty } from '#/api/mall/promotion/diy/page';
-import {
- getDiyTemplateProperty,
- updateDiyTemplateProperty,
-} from '#/api/mall/promotion/diy/template';
-import { DiyEditor, PAGE_LIBS } from '#/views/mall/promotion/components';
-
-/** 瑁呬慨妯℃澘琛ㄥ崟 */
-defineOptions({ name: 'DiyTemplateDecorate' });
-
-const route = useRoute();
-const { refreshTab } = useTabs();
-
-const domain = import.meta.env.VITE_MALL_H5_DOMAIN;
-const DIY_PAGE_INDEX_KEY = 'diy_page_index'; // 鐗规畩锛氬瓨鍌� reset 閲嶇疆鏃讹紝褰撳墠 selectedTemplateItem 鍊硷紝浠庤�岃繘琛屾仮澶�
-
-const selectedTemplateItem = ref(0);
-const templateItems = ref([
- { key: 0, name: '鍩虹璁剧疆', icon: 'lucide:settings' },
- { key: 1, name: '棣栭〉', icon: 'lucide:home' },
- { key: 2, name: '鎴戠殑', icon: 'lucide:user' },
-]); // 宸︿笂瑙掑伐鍏锋爮鎿嶄綔鎸夐挳
-
-const formData = ref<MallDiyTemplateApi.DiyTemplateProperty>();
-const currentFormData = ref<
- MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty
->({
- property: '',
-} as MallDiyPageApi.DiyPage); // 褰撳墠缂栬緫鐨勫睘鎬�
-const currentFormDataMap = ref<
- Map<string, MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty>
->(new Map()); // templateItem 瀵瑰簲鐨勭紦瀛�
-
-const previewUrl = ref(''); // 鍟嗗煄 H5 棰勮鍦板潃
-const templateLibs = [] as DiyComponentLibrary[]; // 妯℃澘缁勪欢搴�
-const libs = ref<DiyComponentLibrary[]>(templateLibs); // 褰撳墠缁勪欢搴�
-
-/** 鑾峰彇璇︽儏 */
-async function getPageDetail(id: any) {
- const hideLoading = message.loading({
- content: '鍔犺浇涓�...',
- duration: 0,
- });
- try {
- formData.value = await getDiyTemplateProperty(id);
- // 鎷兼帴鎵嬫満棰勮閾炬帴
- const accessStore = useAccessStore();
- previewUrl.value = `${domain}?templateId=${formData.value.id}&tenantId=${accessStore.tenantId}`;
- } finally {
- hideLoading();
- }
-}
-
-/** 妯℃澘閫夐」鍒囨崲 */
-function handleTemplateItemChange(valObj: any) {
- const val = valObj.target.value;
- // 缂撳瓨妯$増缂栬緫鏁版嵁
- currentFormDataMap.value.set(
- templateItems.value[selectedTemplateItem.value]?.name || '',
- currentFormData.value!,
- );
- // 璇诲彇妯$増缂撳瓨
- const data = currentFormDataMap.value.get(
- templateItems.value[val]?.name || '',
- );
-
- // 鍒囨崲妯$増
- selectedTemplateItem.value = val;
-
- // 鎯呭喌涓�锛氱紪杈戞ā鏉�
- if (val === 0) {
- libs.value = templateLibs;
- currentFormData.value = (isEmpty(data) ? formData.value : data) as
- | MallDiyPageApi.DiyPage
- | MallDiyTemplateApi.DiyTemplateProperty;
- return;
- }
-
- // 鎯呭喌浜岋細缂栬緫椤甸潰
- libs.value = PAGE_LIBS;
- currentFormData.value = (
- isEmpty(data)
- ? formData.value!.pages.find(
- (page: MallDiyPageApi.DiyPage) =>
- page.name === templateItems.value[val]?.name,
- )
- : data
- ) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty;
-}
-
-/** 鎻愪氦琛ㄥ崟 */
-async function submitForm() {
- const hideLoading = message.loading({
- content: '淇濆瓨涓�...',
- duration: 0,
- });
- try {
- // 瀵规墍鏈夌殑 templateItems 閮借繘琛屼繚瀛橈紝鏈夌紦瀛樺垯淇濆瓨缂撳瓨锛岃В鍐抽兘鏈変慨鏀规椂鍙繚瀛樹簡褰撳墠鎵�缂栬緫鐨� templateItem锛屽鑷磋淇晥鏋滃瓨鍦ㄥ樊寮�
- for (const [i, templateItem] of templateItems.value.entries()) {
- const data = currentFormDataMap.value.get(templateItem.name) as any;
- // 鎯呭喌涓�锛氬熀纭�璁剧疆
- if (i === 0) {
- // 鎻愪氦妯℃澘灞炴��
- await updateDiyTemplateProperty(isEmpty(data) ? formData.value! : data);
- continue;
- }
- // 鎻愪氦椤甸潰灞炴��
- // 鎯呭喌浜岋細鎻愪氦褰撳墠姝e湪缂栬緫鐨勯〉闈�
- if (currentFormData.value?.name.includes(templateItem.name)) {
- await updateDiyPageProperty(currentFormData.value!);
- continue;
- }
- // 鎯呭喌涓夛細鎻愪氦椤甸潰缂栬緫缂撳瓨
- if (!isEmpty(data)) {
- await updateDiyPageProperty(data!);
- }
- }
- message.success('淇濆瓨鎴愬姛');
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒锋柊褰撳墠 Tab */
-function handleEditorReset() {
- sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`);
- refreshTab();
-}
-
-/** 鎭㈠褰撳墠 Tab 涔嬪墠鐨� index锛坰electedTemplateItem锛� */
-function recoverPageIndex() {
- // 鎭㈠閲嶇疆鍓嶇殑椤甸潰锛岄粯璁ゆ槸绗竴涓〉闈�
- const pageIndex = Number(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0;
- // 绉婚櫎鏍囪
- sessionStorage.removeItem(DIY_PAGE_INDEX_KEY);
-
- // 閲嶆柊鍒濆鍖栨暟鎹�
- currentFormData.value = formData.value as
- | MallDiyPageApi.DiyPage
- | MallDiyTemplateApi.DiyTemplateProperty;
- currentFormDataMap.value = new Map<
- string,
- MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty
- >();
- // 鍒囨崲椤甸潰
- if (pageIndex !== selectedTemplateItem.value) {
- handleTemplateItemChange(pageIndex);
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- if (!route.params.id) {
- message.warning('鍙傛暟閿欒锛岄〉闈㈢紪鍙蜂笉鑳戒负绌猴紒');
- return;
- }
- // 鏌ヨ璇︽儏
- formData.value = {} as MallDiyTemplateApi.DiyTemplateProperty;
- await getPageDetail(route.params.id);
- // 鎭㈠閲嶇疆鍓嶇殑椤甸潰
- recoverPageIndex();
-});
-</script>
-<template>
- <DiyEditor
- v-if="formData?.id"
- v-model="currentFormData!.property"
- :libs="libs"
- :preview-url="previewUrl"
- :show-navigation-bar="selectedTemplateItem !== 0"
- :show-page-config="selectedTemplateItem !== 0"
- :show-tab-bar="selectedTemplateItem === 0"
- :title="templateItems[selectedTemplateItem]?.name || ''"
- @reset="handleEditorReset"
- @save="submitForm"
- >
- <template #toolBarLeft>
- <RadioGroup
- :value="selectedTemplateItem"
- class="flex items-center"
- size="large"
- @change="handleTemplateItemChange"
- >
- <template v-for="item in templateItems" :key="item.key">
- <Radio.Button :value="item.key">
- <IconifyIcon
- :icon="item.icon"
- class="mt-2 flex size-5 items-center"
- />
- </Radio.Button>
- </template>
- </RadioGroup>
- </template>
- </DiyEditor>
-</template>
diff --git a/src/views/mall/promotion/diy/template/index.vue b/src/views/mall/promotion/diy/template/index.vue
deleted file mode 100644
index 5667aa1..0000000
--- a/src/views/mall/promotion/diy/template/index.vue
+++ /dev/null
@@ -1,179 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template';
-
-import { useRouter } from 'vue-router';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteDiyTemplate,
- getDiyTemplatePage,
- useDiyTemplate,
-} from '#/api/mall/promotion/diy/template';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import DiyTemplateForm from './modules/form.vue';
-
-defineOptions({ name: 'PromotionDiyTemplate' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: DiyTemplateForm,
- destroyOnClose: true,
-});
-
-const router = useRouter();
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓 DIY 妯℃澘 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫 DIY 妯℃澘 */
-function handleEdit(row: MallDiyTemplateApi.DiyTemplate) {
- formModalApi.setData(row).open();
-}
-
-/** 瑁呬慨妯℃澘 */
-function handleDecorate(row: MallDiyTemplateApi.DiyTemplate) {
- router.push({ name: 'DiyTemplateDecorate', params: { id: row.id } });
-}
-
-/** 浣跨敤妯℃澘 */
-async function handleUse(row: MallDiyTemplateApi.DiyTemplate) {
- await confirm(`鏄惁浣跨敤妯℃澘"${row.name}"?`);
- const hideLoading = message.loading({
- content: `姝e湪浣跨敤妯℃澘"${row.name}"...`,
- duration: 0,
- });
- try {
- await useDiyTemplate(row.id as number);
- message.success('浣跨敤鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎 DIY 妯℃澘 */
-async function handleDelete(row: MallDiyTemplateApi.DiyTemplate) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDiyTemplate(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDiyTemplatePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDiyTemplateApi.DiyTemplate>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戝晢鍩庤淇�"
- url="https://doc.iocoder.cn/mall/diy/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="瑁呬慨妯℃澘鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['瑁呬慨妯℃澘']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:diy-template:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '瑁呬慨',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:diy-template:update'],
- onClick: handleDecorate.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:diy-template:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '浣跨敤',
- type: 'link',
- auth: ['promotion:diy-template:use'],
- ifShow: !row.used,
- onClick: handleUse.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:diy-template:delete'],
- ifShow: !row.used,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/diy/template/modules/form.vue b/src/views/mall/promotion/diy/template/modules/form.vue
deleted file mode 100644
index 1d43b73..0000000
--- a/src/views/mall/promotion/diy/template/modules/form.vue
+++ /dev/null
@@ -1,86 +0,0 @@
-<script lang="ts" setup>
-import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template';
-
-import { computed, ref } from 'vue';
-
-import { useVbenForm, useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import {
- createDiyTemplate,
- getDiyTemplate,
- updateDiyTemplate,
-} from '#/api/mall/promotion/diy/template';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDiyTemplateApi.DiyTemplate>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['瑁呬慨妯℃澘'])
- : $t('ui.actionTitle.create', ['瑁呬慨妯℃澘']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallDiyTemplateApi.DiyTemplate;
- try {
- await (formData.value?.id
- ? updateDiyTemplate(data)
- : createDiyTemplate(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallDiyTemplateApi.DiyTemplate>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDiyTemplate(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/kefu/asserts/a.png b/src/views/mall/promotion/kefu/asserts/a.png
deleted file mode 100644
index 3293900..0000000
--- a/src/views/mall/promotion/kefu/asserts/a.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/aini.png b/src/views/mall/promotion/kefu/asserts/aini.png
deleted file mode 100644
index 02cf5c4..0000000
--- a/src/views/mall/promotion/kefu/asserts/aini.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/aixin.png b/src/views/mall/promotion/kefu/asserts/aixin.png
deleted file mode 100644
index 25e6422..0000000
--- a/src/views/mall/promotion/kefu/asserts/aixin.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/baiyan.png b/src/views/mall/promotion/kefu/asserts/baiyan.png
deleted file mode 100644
index d16260a..0000000
--- a/src/views/mall/promotion/kefu/asserts/baiyan.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/bizui.png b/src/views/mall/promotion/kefu/asserts/bizui.png
deleted file mode 100644
index a3b1800..0000000
--- a/src/views/mall/promotion/kefu/asserts/bizui.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/buhaoyisi.png b/src/views/mall/promotion/kefu/asserts/buhaoyisi.png
deleted file mode 100644
index 54c4b3f..0000000
--- a/src/views/mall/promotion/kefu/asserts/buhaoyisi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/bukesiyi.png b/src/views/mall/promotion/kefu/asserts/bukesiyi.png
deleted file mode 100644
index 5f272e3..0000000
--- a/src/views/mall/promotion/kefu/asserts/bukesiyi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/dajing.png b/src/views/mall/promotion/kefu/asserts/dajing.png
deleted file mode 100644
index 8649727..0000000
--- a/src/views/mall/promotion/kefu/asserts/dajing.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/danao.png b/src/views/mall/promotion/kefu/asserts/danao.png
deleted file mode 100644
index aa85a29..0000000
--- a/src/views/mall/promotion/kefu/asserts/danao.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/daxiao.png b/src/views/mall/promotion/kefu/asserts/daxiao.png
deleted file mode 100644
index 26206bc..0000000
--- a/src/views/mall/promotion/kefu/asserts/daxiao.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/dianzan.png b/src/views/mall/promotion/kefu/asserts/dianzan.png
deleted file mode 100644
index 2e7f00e..0000000
--- a/src/views/mall/promotion/kefu/asserts/dianzan.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/emo.png b/src/views/mall/promotion/kefu/asserts/emo.png
deleted file mode 100644
index 9c84551..0000000
--- a/src/views/mall/promotion/kefu/asserts/emo.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/esi.png b/src/views/mall/promotion/kefu/asserts/esi.png
deleted file mode 100644
index 84e9726..0000000
--- a/src/views/mall/promotion/kefu/asserts/esi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/fadai.png b/src/views/mall/promotion/kefu/asserts/fadai.png
deleted file mode 100644
index 0772de2..0000000
--- a/src/views/mall/promotion/kefu/asserts/fadai.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/fankun.png b/src/views/mall/promotion/kefu/asserts/fankun.png
deleted file mode 100644
index 6e18dac..0000000
--- a/src/views/mall/promotion/kefu/asserts/fankun.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/feiwen.png b/src/views/mall/promotion/kefu/asserts/feiwen.png
deleted file mode 100644
index be97616..0000000
--- a/src/views/mall/promotion/kefu/asserts/feiwen.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/fennu.png b/src/views/mall/promotion/kefu/asserts/fennu.png
deleted file mode 100644
index 20c5733..0000000
--- a/src/views/mall/promotion/kefu/asserts/fennu.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/ganga.png b/src/views/mall/promotion/kefu/asserts/ganga.png
deleted file mode 100644
index 30ec329..0000000
--- a/src/views/mall/promotion/kefu/asserts/ganga.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/ganmao.png b/src/views/mall/promotion/kefu/asserts/ganmao.png
deleted file mode 100644
index 35bbb89..0000000
--- a/src/views/mall/promotion/kefu/asserts/ganmao.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/hanyan.png b/src/views/mall/promotion/kefu/asserts/hanyan.png
deleted file mode 100644
index a0bc838..0000000
--- a/src/views/mall/promotion/kefu/asserts/hanyan.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/haochi.png b/src/views/mall/promotion/kefu/asserts/haochi.png
deleted file mode 100644
index 2e52b6b..0000000
--- a/src/views/mall/promotion/kefu/asserts/haochi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/hongxin.png b/src/views/mall/promotion/kefu/asserts/hongxin.png
deleted file mode 100644
index 65b5de8..0000000
--- a/src/views/mall/promotion/kefu/asserts/hongxin.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/huaixiao.png b/src/views/mall/promotion/kefu/asserts/huaixiao.png
deleted file mode 100644
index bc0e76c..0000000
--- a/src/views/mall/promotion/kefu/asserts/huaixiao.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/jingkong.png b/src/views/mall/promotion/kefu/asserts/jingkong.png
deleted file mode 100644
index 7aa6584..0000000
--- a/src/views/mall/promotion/kefu/asserts/jingkong.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/jingshu.png b/src/views/mall/promotion/kefu/asserts/jingshu.png
deleted file mode 100644
index 0e984d6..0000000
--- a/src/views/mall/promotion/kefu/asserts/jingshu.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/jingya.png b/src/views/mall/promotion/kefu/asserts/jingya.png
deleted file mode 100644
index 9ba6bab..0000000
--- a/src/views/mall/promotion/kefu/asserts/jingya.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/kaixin.png b/src/views/mall/promotion/kefu/asserts/kaixin.png
deleted file mode 100644
index 29c9f5d..0000000
--- a/src/views/mall/promotion/kefu/asserts/kaixin.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/keai.png b/src/views/mall/promotion/kefu/asserts/keai.png
deleted file mode 100644
index d3b582c..0000000
--- a/src/views/mall/promotion/kefu/asserts/keai.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/keshui.png b/src/views/mall/promotion/kefu/asserts/keshui.png
deleted file mode 100644
index cef489e..0000000
--- a/src/views/mall/promotion/kefu/asserts/keshui.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/kun.png b/src/views/mall/promotion/kefu/asserts/kun.png
deleted file mode 100644
index 1ddc388..0000000
--- a/src/views/mall/promotion/kefu/asserts/kun.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/lengku.png b/src/views/mall/promotion/kefu/asserts/lengku.png
deleted file mode 100644
index c5c6fee..0000000
--- a/src/views/mall/promotion/kefu/asserts/lengku.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/liuhan.png b/src/views/mall/promotion/kefu/asserts/liuhan.png
deleted file mode 100644
index e6ddc6f..0000000
--- a/src/views/mall/promotion/kefu/asserts/liuhan.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/liukoushui.png b/src/views/mall/promotion/kefu/asserts/liukoushui.png
deleted file mode 100644
index 3e2fba6..0000000
--- a/src/views/mall/promotion/kefu/asserts/liukoushui.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/liulei.png b/src/views/mall/promotion/kefu/asserts/liulei.png
deleted file mode 100644
index dbf8204..0000000
--- a/src/views/mall/promotion/kefu/asserts/liulei.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/mengbi.png b/src/views/mall/promotion/kefu/asserts/mengbi.png
deleted file mode 100644
index a4206ee..0000000
--- a/src/views/mall/promotion/kefu/asserts/mengbi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png b/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png
deleted file mode 100644
index 6f315b9..0000000
--- a/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/nanguo.png b/src/views/mall/promotion/kefu/asserts/nanguo.png
deleted file mode 100644
index 19b9fb9..0000000
--- a/src/views/mall/promotion/kefu/asserts/nanguo.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/outu.png b/src/views/mall/promotion/kefu/asserts/outu.png
deleted file mode 100644
index 2f9a06d..0000000
--- a/src/views/mall/promotion/kefu/asserts/outu.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/picture.svg b/src/views/mall/promotion/kefu/asserts/picture.svg
deleted file mode 100644
index 8811d49..0000000
--- a/src/views/mall/promotion/kefu/asserts/picture.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg t="1720063872285" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6895"
- width="200" height="200">
- <path d="M782.16 880.98c-179.31 23.91-361 23.91-540.32 0C138.89 867.25 62 779.43 62 675.57V348.43c0-103.86 76.89-191.69 179.84-205.41 179.31-23.91 361-23.91 540.31 0C885.11 156.75 962 244.57 962 348.43v327.13c0 103.87-76.89 191.69-179.84 205.42z"
- fill="#FF554D" p-id="6896"></path>
- <path d="M226.11 596.86c-9.74 47.83 17.26 95.6 63.48 111.3C333.49 723.08 394.55 737 469.53 737c59.25 0 105.46-8.69 140.23-19.7 51.59-16.34 79.94-71.16 63.37-122.68-24.47-76.11-65.57-180.7-106.68-180.7-64.62 0-64.62 96.92-64.62 96.92S437.22 317 372.61 317c-82.11 0-117.85 139.12-146.5 279.86z"
- fill="#FFFFFF" p-id="6897"></path>
- <path d="M782 347m-60 0a60 60 0 1 0 120 0 60 60 0 1 0-120 0Z" fill="#FFBC55" p-id="6898"></path>
-</svg>
diff --git a/src/views/mall/promotion/kefu/asserts/shengqi.png b/src/views/mall/promotion/kefu/asserts/shengqi.png
deleted file mode 100644
index 7dce41d..0000000
--- a/src/views/mall/promotion/kefu/asserts/shengqi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/shuizhuo.png b/src/views/mall/promotion/kefu/asserts/shuizhuo.png
deleted file mode 100644
index 97d0f0a..0000000
--- a/src/views/mall/promotion/kefu/asserts/shuizhuo.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/tianshi.png b/src/views/mall/promotion/kefu/asserts/tianshi.png
deleted file mode 100644
index eb922dd..0000000
--- a/src/views/mall/promotion/kefu/asserts/tianshi.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png b/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png
deleted file mode 100644
index 29fbc0e..0000000
--- a/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/xiaoku.png b/src/views/mall/promotion/kefu/asserts/xiaoku.png
deleted file mode 100644
index 88a169d..0000000
--- a/src/views/mall/promotion/kefu/asserts/xiaoku.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/xinsui.png b/src/views/mall/promotion/kefu/asserts/xinsui.png
deleted file mode 100644
index a0f572a..0000000
--- a/src/views/mall/promotion/kefu/asserts/xinsui.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/xiong.png b/src/views/mall/promotion/kefu/asserts/xiong.png
deleted file mode 100644
index 43dfd70..0000000
--- a/src/views/mall/promotion/kefu/asserts/xiong.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/yiwen.png b/src/views/mall/promotion/kefu/asserts/yiwen.png
deleted file mode 100644
index 4c0da70..0000000
--- a/src/views/mall/promotion/kefu/asserts/yiwen.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/yun.png b/src/views/mall/promotion/kefu/asserts/yun.png
deleted file mode 100644
index 56e5d02..0000000
--- a/src/views/mall/promotion/kefu/asserts/yun.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/asserts/ziya.png b/src/views/mall/promotion/kefu/asserts/ziya.png
deleted file mode 100644
index 593ef5e..0000000
--- a/src/views/mall/promotion/kefu/asserts/ziya.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mall/promotion/kefu/index.vue b/src/views/mall/promotion/kefu/index.vue
deleted file mode 100644
index 8e18323..0000000
--- a/src/views/mall/promotion/kefu/index.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-
-import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-import { WebSocketMessageTypeConstants } from '../../../../packages/constants/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import { useWebSocket } from '@vueuse/core';
-import { message } from 'ant-design-vue';
-
-import { useMallKefuStore } from '#/store/mall/kefu';
-
-import ConversationList from './modules/conversation-list.vue';
-import MemberInfo from './modules/member/member-info.vue';
-import MessageList from './modules/message-list.vue';
-
-const accessStore = useAccessStore();
-const kefuStore = useMallKefuStore(); // 瀹㈡湇缂撳瓨
-
-/** 缁勪欢寮曠敤 */
-const messageListRef = ref<InstanceType<typeof MessageList>>();
-const memberInfoRef = ref<InstanceType<typeof MemberInfo>>();
-const conversationListRef = ref<InstanceType<typeof ConversationList>>();
-
-// ======================= WebSocket start =======================
-const url = `${`${import.meta.env.VITE_BASE_URL}/infra/ws`.replace(
- 'http',
- 'ws',
-)}?token=${accessStore.refreshToken}`; // 浣跨敤 refreshToken() :WebSocket 鏃犳硶鏂逛究鐨勫埛鏂拌闂护鐗�
-const server = ref(url); // WebSocket 鏈嶅姟鍦板潃
-
-/** 鍙戣捣 WebSocket 杩炴帴 */
-const { data, close, open } = useWebSocket(server.value, {
- autoReconnect: true,
- heartbeat: true,
-});
-
-/** 鐩戝惉 WebSocket 鏁版嵁 */
-watch(
- () => data.value,
- (newData) => {
- if (!newData) return;
- try {
- // 1. 鏀跺埌蹇冭烦
- if (newData === 'pong') return;
-
- // 2.1 瑙f瀽 type 娑堟伅绫诲瀷
- const jsonMessage = JSON.parse(newData);
- const type = jsonMessage.type;
- if (!type) {
- message.error(`鏈煡鐨勬秷鎭被鍨嬶細${newData}`);
- return;
- }
-
- // 2.2 娑堟伅绫诲瀷锛欿EFU_MESSAGE_TYPE
- if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
- const message = JSON.parse(jsonMessage.content);
- // 鍒锋柊浼氳瘽鍒楄〃
- kefuStore.updateConversation(message.conversationId);
- // 鍒锋柊娑堟伅鍒楄〃
- messageListRef.value?.refreshMessageList(message);
- return;
- }
-
- // 2.3 娑堟伅绫诲瀷锛欿EFU_MESSAGE_ADMIN_READ
- if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
- // 鏇存柊浼氳瘽宸茶
- const message = JSON.parse(jsonMessage.content);
- kefuStore.updateConversationStatus(message.conversationId);
- }
- } catch (error) {
- console.error(error);
- }
- },
- {
- immediate: false, // 涓嶇珛鍗虫墽琛�
- },
-);
-// ======================= WebSocket end =======================
-
-function handleChange(conversation: MallKefuConversationApi.Conversation) {
- messageListRef.value?.getNewMessageList(conversation);
- memberInfoRef.value?.initHistory(conversation);
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- // 鍔犺浇浼氳瘽鍒楄〃
- kefuStore.setConversationList().then(() => {
- conversationListRef.value?.calculationLastMessageTime();
- });
- // 鎵撳紑 websocket 杩炴帴
- open();
-});
-
-/** 閿�姣� */
-onBeforeUnmount(() => {
- // 鍏抽棴 websocket 杩炴帴
- close();
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <div class="flex h-full antialiased">
- <div class="flex h-full w-full flex-row overflow-x-hidden">
- <!-- 浼氳瘽鍒楄〃 -->
- <ConversationList
- class="w-1/6"
- ref="conversationListRef"
- @change="handleChange"
- />
- <!-- 浼氳瘽璇︽儏锛堥�変腑浼氳瘽鐨勬秷鎭垪琛級 -->
- <MessageList class="w-4/6" ref="messageListRef" />
- <!-- 浼氬憳淇℃伅锛堥�変腑浼氳瘽鐨勪細鍛樹俊鎭級 -->
- <MemberInfo class="w-[280px]" ref="memberInfoRef" />
- </div>
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/conversation-list.vue b/src/views/mall/promotion/kefu/modules/conversation-list.vue
deleted file mode 100644
index 12cf439..0000000
--- a/src/views/mall/promotion/kefu/modules/conversation-list.vue
+++ /dev/null
@@ -1,248 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-
-import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
-
-import { confirm } from '../../../../../packages/effects/common-ui/src';
-import { KeFuMessageContentTypeEnum } from '../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { formatPast, jsonParse } from '../../../../../packages/utils/src';
-
-import { Avatar, message } from 'ant-design-vue';
-
-import {
- deleteConversation,
- updateConversationPinned,
-} from '#/api/mall/promotion/kefu/conversation';
-import { useMallKefuStore } from '#/store/mall/kefu';
-
-import { useEmoji } from './tools/emoji';
-
-/** 鎵撳紑鍙充晶鐨勬秷鎭垪琛� */
-const emits = defineEmits<{
- (e: 'change', v: MallKefuConversationApi.Conversation): void;
-}>();
-const kefuStore = useMallKefuStore(); // 瀹㈡湇缂撳瓨
-const { replaceEmoji } = useEmoji();
-const activeConversationId = ref(-1); // 閫変腑鐨勪細璇�
-const collapse = ref(false); // 鎶樺彔鑿滃崟
-
-/** 璁$畻娑堟伅鏈�鍚庡彂閫佹椂闂磋窛绂荤幇鍦ㄨ繃鍘讳簡澶氫箙 */
-const lastMessageTimeMap = ref<Map<number, string>>(new Map<number, string>());
-function calculationLastMessageTime() {
- kefuStore.getConversationList?.forEach((item) => {
- lastMessageTimeMap.value.set(
- item.id,
- formatPast(item.lastMessageTime, 'YYYY-MM-DD'),
- );
- });
-}
-defineExpose({ calculationLastMessageTime });
-
-function openRightMessage(item: MallKefuConversationApi.Conversation) {
- // 鍚屼竴涓細璇濆垯涓嶅鐞�
- if (activeConversationId.value === item.id) {
- return;
- }
- activeConversationId.value = item.id;
- emits('change', item);
-}
-
-/** 鑾峰緱娑堟伅绫诲瀷 */
-const getConversationDisplayText = computed(
- () => (lastMessageContentType: number, lastMessageContent: string) => {
- switch (lastMessageContentType) {
- case KeFuMessageContentTypeEnum.IMAGE: {
- return '[鍥剧墖娑堟伅]';
- }
- case KeFuMessageContentTypeEnum.ORDER: {
- return '[璁㈠崟娑堟伅]';
- }
- case KeFuMessageContentTypeEnum.PRODUCT: {
- return '[鍟嗗搧娑堟伅]';
- }
- case KeFuMessageContentTypeEnum.SYSTEM: {
- return '[绯荤粺娑堟伅]';
- }
- case KeFuMessageContentTypeEnum.TEXT: {
- return replaceEmoji(
- jsonParse(lastMessageContent).text || lastMessageContent,
- );
- }
- case KeFuMessageContentTypeEnum.VIDEO: {
- return '[瑙嗛娑堟伅]';
- }
- case KeFuMessageContentTypeEnum.VOICE: {
- return '[璇煶娑堟伅]';
- }
- default: {
- return '';
- }
- }
- },
-);
-
-// ======================= 鍙抽敭鑿滃崟 =======================
-const showRightMenu = ref(false); // 鏄剧ず鍙抽敭鑿滃崟
-const rightMenuStyle = ref<any>({}); // 鍙抽敭鑿滃崟 Style
-const rightClickConversation = ref<MallKefuConversationApi.Conversation>(
- {} as MallKefuConversationApi.Conversation,
-); // 鍙抽敭閫変腑鐨勪細璇濆璞�
-
-/** 鎵撳紑鍙抽敭鑿滃崟 */
-function rightClick(
- mouseEvent: PointerEvent,
- item: MallKefuConversationApi.Conversation,
-) {
- rightClickConversation.value = item;
- // 鏄剧ず鍙抽敭鑿滃崟
- showRightMenu.value = true;
- rightMenuStyle.value = {
- top: `${mouseEvent.clientY - 110}px`,
- left: collapse.value
- ? `${mouseEvent.clientX - 80}px`
- : `${mouseEvent.clientX - 210}px`,
- };
-}
-/** 鍏抽棴鍙抽敭鑿滃崟 */
-function closeRightMenu() {
- showRightMenu.value = false;
-}
-
-/** 缃《浼氳瘽 */
-async function updateConversationPinnedFn(pinned: boolean) {
- // 1. 浼氳瘽缃《/鍙栨秷缃《
- await updateConversationPinned({
- id: rightClickConversation.value.id,
- pinned,
- });
- message.success(pinned ? '缃《鎴愬姛' : '鍙栨秷缃《鎴愬姛');
- // 2. 鍏抽棴鍙抽敭鑿滃崟锛屾洿鏂颁細璇濆垪琛�
- closeRightMenu();
- await kefuStore.updateConversation(rightClickConversation.value.id);
-}
-
-/** 鍒犻櫎浼氳瘽 */
-async function deleteConversationFn() {
- // 1. 鍒犻櫎浼氳瘽
- confirm('鎮ㄧ‘瀹氳鍒犻櫎璇ヤ細璇濆悧锛�').then(async () => {
- await deleteConversation(rightClickConversation.value.id);
- // 2. 鍏抽棴鍙抽敭鑿滃崟锛屾洿鏂颁細璇濆垪琛�
- closeRightMenu();
- kefuStore.deleteConversation(rightClickConversation.value.id);
- });
-}
-
-/** 鐩戝惉鍙抽敭鑿滃崟鐨勬樉绀虹姸鎬侊紝娣诲姞鐐瑰嚮浜嬩欢鐩戝惉鍣� */
-watch(showRightMenu, (val) => {
- if (val) {
- document.body.addEventListener('click', closeRightMenu);
- } else {
- document.body.removeEventListener('click', closeRightMenu);
- }
-});
-
-const timer = ref<any>();
-
-/** 鍒濆鍖� */
-onMounted(() => {
- timer.value = setInterval(calculationLastMessageTime, 1000 * 10); // 鍗佺璁$畻涓�娆�
-});
-
-/** 缁勪欢鍗歌浇鍓� */
-onBeforeUnmount(() => {
- clearInterval(timer.value);
-});
-</script>
-
-<template>
- <div
- class="flex flex-shrink-0 flex-col border-r border-gray-200 bg-background p-4"
- >
- <div class="flex h-12 w-full flex-row items-center justify-between">
- <span class="text-lg font-bold">浼氳瘽璁板綍</span>
- <span
- class="flex h-4 w-4 items-center justify-center rounded-full bg-indigo-100 text-indigo-600"
- >
- {{ kefuStore.getConversationList.length }}
- </span>
- </div>
- <div class="mt-2 flex flex-col">
- <div class="-mx-2 mt-4 flex h-48 flex-col space-y-1 overflow-y-auto">
- <button
- v-for="(item, index) in kefuStore.getConversationList"
- :key="index"
- class="flex flex-row items-center rounded-xl p-2 hover:bg-gray-100"
- :class="{
- 'bg-gray-500/50': item.id === activeConversationId,
- }"
- @click="openRightMessage(item)"
- @contextmenu.prevent="rightClick($event as PointerEvent, item)"
- >
- <div
- class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200"
- >
- <Avatar :src="item.userAvatar" alt="avatar" />
- </div>
- <div class="ml-2 text-sm font-semibold">
- {{ item.userNickname || 'null' }}
- </div>
- <!-- 鏈�鍚庤亰澶╁唴瀹� -->
- <div
- v-dompurify-html="
- getConversationDisplayText(
- item.lastMessageContentType,
- item.lastMessageContent,
- )
- "
- class="line-clamp-1 flex items-center text-sm text-gray-500"
- ></div>
- <div
- v-if="item.adminUnreadMessageCount > 0"
- class="ml-auto flex h-4 w-4 items-center justify-center rounded bg-red-500 text-xs leading-none text-white"
- >
- {{ item.adminUnreadMessageCount }}
- </div>
- </button>
- </div>
- </div>
-
- <!-- 鍙抽敭锛岃繘琛屾搷浣滐紙绫讳技寰俊锛� -->
- <ul
- v-show="showRightMenu"
- :style="rightMenuStyle"
- class="absolute z-[9999] m-0 w-32 list-none rounded-xl bg-background p-1 shadow-md"
- >
- <li
- v-show="!rightClickConversation.adminPinned"
- class="flex cursor-pointer items-center rounded-xl px-4 py-2 transition-colors hover:bg-gray-500/50"
- @click.stop="updateConversationPinnedFn(true)"
- >
- <IconifyIcon class="mr-1" icon="lucide:arrow-up-to-line" />
- 缃《浼氳瘽
- </li>
- <li
- v-show="rightClickConversation.adminPinned"
- class="flex cursor-pointer items-center rounded-xl px-4 py-2 transition-colors hover:bg-gray-500/50"
- @click.stop="updateConversationPinnedFn(false)"
- >
- <IconifyIcon class="mr-1" icon="lucide:arrow-down-from-line" />
- 鍙栨秷缃《
- </li>
- <li
- class="flex cursor-pointer items-center rounded-xl px-4 py-2 transition-colors hover:bg-gray-500/50"
- @click.stop="deleteConversationFn"
- >
- <IconifyIcon class="mr-1" color="red" icon="lucide:trash-2" />
- 鍒犻櫎浼氳瘽
- </li>
- <li
- class="flex cursor-pointer items-center rounded-xl px-4 py-2 transition-colors hover:bg-gray-500/50"
- @click.stop="closeRightMenu"
- >
- <IconifyIcon class="mr-1" color="red" icon="lucide:x" />
- 鍙栨秷
- </li>
- </ul>
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/member/member-info.vue b/src/views/mall/promotion/kefu/modules/member/member-info.vue
deleted file mode 100644
index 3474520..0000000
--- a/src/views/mall/promotion/kefu/modules/member/member-info.vue
+++ /dev/null
@@ -1,220 +0,0 @@
-<!-- 鍙充晶淇℃伅锛氫細鍛樹俊鎭� + 鏈�杩戞祻瑙� + 浜ゆ槗璁㈠崟 -->
-<script lang="ts" setup>
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-import type { MemberUserApi } from '#/api/member/user';
-import type { PayWalletApi } from '#/api/pay/wallet/balance';
-
-import { computed, nextTick, ref, toRefs, watch } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-import { useScroll } from '@vueuse/core';
-import { Empty, message } from 'ant-design-vue';
-
-import { getUser } from '#/api/member/user';
-import { getWallet } from '#/api/pay/wallet/balance';
-import AccountInfo from '#/views/member/user/detail/modules/account-info.vue';
-import BasicInfo from '#/views/member/user/detail/modules/basic-info.vue';
-
-import OrderBrowsingHistory from './order-browsing-history.vue';
-import ProductBrowsingHistory from './product-browsing-history.vue';
-
-const activeTab = ref<string>('浼氬憳淇℃伅');
-
-const tabActivation = computed(() => (tab: string) => activeTab.value === tab);
-
-/** tab 鍒囨崲 */
-const productBrowsingHistoryRef =
- ref<InstanceType<typeof ProductBrowsingHistory>>();
-const orderBrowsingHistoryRef =
- ref<InstanceType<typeof OrderBrowsingHistory>>();
-async function handleClick(tab: string) {
- activeTab.value = tab;
- await nextTick();
- await getHistoryList();
-}
-
-/** 鑾峰緱鍘嗗彶鏁版嵁 */
-async function getHistoryList() {
- switch (activeTab.value) {
- case '浜ゆ槗璁㈠崟': {
- await orderBrowsingHistoryRef.value?.getHistoryList(conversation.value);
- break;
- }
- case '浼氬憳淇℃伅': {
- await getUserData();
- await getUserWallet();
- break;
- }
- case '鏈�杩戞祻瑙�': {
- await productBrowsingHistoryRef.value?.getHistoryList(conversation.value);
- break;
- }
- default: {
- break;
- }
- }
-}
-
-/** 鍔犺浇涓嬩竴椤垫暟鎹� */
-async function loadMore() {
- switch (activeTab.value) {
- case '浜ゆ槗璁㈠崟': {
- await orderBrowsingHistoryRef.value?.loadMore();
- break;
- }
- case '浼氬憳淇℃伅': {
- break;
- }
- case '鏈�杩戞祻瑙�': {
- await productBrowsingHistoryRef.value?.loadMore();
- break;
- }
- default: {
- break;
- }
- }
-}
-
-/** 娴忚鍘嗗彶鍒濆鍖� */
-const conversation = ref<MallKefuConversationApi.Conversation>(
- {} as MallKefuConversationApi.Conversation,
-); // 鐢ㄦ埛浼氳瘽
-async function initHistory(val: MallKefuConversationApi.Conversation) {
- activeTab.value = '浼氬憳淇℃伅';
- conversation.value = val;
- await nextTick();
- await getHistoryList();
-}
-defineExpose({ initHistory });
-
-/** 澶勭悊娑堟伅鍒楄〃婊氬姩浜嬩欢(debounce 闄愭祦) */
-const scrollbarRef = ref<InstanceType<any>>();
-const { arrivedState } = useScroll(scrollbarRef);
-const { bottom } = toRefs(arrivedState);
-watch(
- () => bottom.value,
- async (newVal) => {
- if (newVal) {
- await loadMore();
- }
- },
-);
-
-/** 鏌ヨ鐢ㄦ埛閽卞寘淇℃伅 */
-const WALLET_INIT_DATA = {
- balance: 0,
- totalExpense: 0,
- totalRecharge: 0,
-} as PayWalletApi.Wallet; // 閽卞寘鍒濆鍖栨暟鎹�
-const wallet = ref<PayWalletApi.Wallet>(WALLET_INIT_DATA); // 閽卞寘淇℃伅
-
-async function getUserWallet() {
- if (!conversation.value.userId) {
- wallet.value = WALLET_INIT_DATA;
- return;
- }
- wallet.value =
- (await getWallet({ userId: conversation.value.userId })) ||
- WALLET_INIT_DATA;
-}
-
-/** 鑾峰緱鐢ㄦ埛 */
-const loading = ref(true); // 鍔犺浇涓�
-const user = ref<MemberUserApi.User>({} as MemberUserApi.User);
-async function getUserData() {
- loading.value = true;
- try {
- const res = await getUser(conversation.value.userId);
- if (res) {
- user.value = res;
- } else {
- user.value = {} as MemberUserApi.User;
- message.error('浼氬憳涓嶅瓨鍦紒');
- }
- } finally {
- loading.value = false;
- }
-}
-</script>
-
-<template>
- <div class="flex h-full flex-auto flex-col bg-background">
- <div
- class="mt-4 flex h-12 items-center justify-around before:absolute before:bottom-0 before:left-0 before:h-1 before:w-full before:scale-y-[0.3] before:bg-gray-200 before:content-['']"
- >
- <div
- :class="{
- 'before:border-b-2 before:border-primary': tabActivation('浼氬憳淇℃伅'),
- }"
- class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
- @click="handleClick('浼氬憳淇℃伅')"
- >
- 浼氬憳淇℃伅
- </div>
- <div
- :class="{
- 'before:border-b-2 before:border-primary': tabActivation('鏈�杩戞祻瑙�'),
- }"
- class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
- @click="handleClick('鏈�杩戞祻瑙�')"
- >
- 鏈�杩戞祻瑙�
- </div>
- <div
- :class="{
- 'before:border-b-2 before:border-primary': tabActivation('浜ゆ槗璁㈠崟'),
- }"
- class="relative flex w-full cursor-pointer items-center justify-center before:pointer-events-none before:absolute before:inset-0 before:content-[''] hover:before:border-b-2 hover:before:border-gray-500/50"
- @click="handleClick('浜ゆ槗璁㈠崟')"
- >
- 浜ゆ槗璁㈠崟
- </div>
- </div>
- <div class="relative m-0 h-full w-full overflow-x-auto p-2">
- <template v-if="!isEmpty(conversation)">
- <div
- v-loading="loading"
- v-if="activeTab === '浼氬憳淇℃伅'"
- class="relative overflow-y-auto overflow-x-hidden"
- >
- <!-- 鍩烘湰淇℃伅 -->
- <BasicInfo :user="user" mode="kefu">
- <template #title>
- <span class="text-sm font-bold">鍩烘湰淇℃伅</span>
- </template>
- </BasicInfo>
- <!-- 璐︽埛淇℃伅 -->
- <AccountInfo
- :column="1"
- :user="user"
- :wallet="wallet"
- mode="kefu"
- class="mt-2"
- >
- <template #title>
- <span class="text-sm font-bold">璐︽埛淇℃伅</span>
- </template>
- </AccountInfo>
- </div>
- <div
- v-show="activeTab !== '浼氬憳淇℃伅'"
- ref="scrollbarRef"
- class="relative h-full overflow-y-auto overflow-x-hidden"
- >
- <!-- 鏈�杩戞祻瑙� -->
- <ProductBrowsingHistory
- v-if="activeTab === '鏈�杩戞祻瑙�'"
- ref="productBrowsingHistoryRef"
- />
- <!-- 浜ゆ槗璁㈠崟 -->
- <OrderBrowsingHistory
- v-if="activeTab === '浜ゆ槗璁㈠崟'"
- ref="orderBrowsingHistoryRef"
- />
- </div>
- </template>
- <Empty v-else description="璇烽�夋嫨宸︿晶鐨勪竴涓細璇濆悗寮�濮�" class="mt-[20%]" />
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue b/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue
deleted file mode 100644
index a7a5fa6..0000000
--- a/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-
-import { computed, reactive, ref } from 'vue';
-
-import { getOrderPage } from '#/api/mall/trade/order';
-import OrderItem from '#/views/mall/promotion/kefu/modules/message/order-item.vue';
-
-const list = ref<any>([]); // 鍒楄〃
-const total = ref(0); // 鎬绘暟
-const queryParams = reactive({
- pageNo: 1,
- pageSize: 10,
- userId: 0,
-});
-const skipGetMessageList = computed(() => {
- // 宸插姞杞藉埌鏈�鍚庝竴椤电殑璇濆垯涓嶈Е鍙戞柊鐨勬秷鎭幏鍙�
- return (
- total.value > 0 &&
- Math.ceil(total.value / queryParams.pageSize) === queryParams.pageNo
- );
-}); // 璺宠繃娑堟伅鑾峰彇
-
-/** 鑾峰緱娴忚璁板綍 */
-async function getHistoryList(val: MallKefuConversationApi.Conversation) {
- queryParams.userId = val.userId;
- const res = await getOrderPage(queryParams);
- total.value = res.total;
- list.value = res.list;
-}
-
-/** 鍔犺浇涓嬩竴椤垫暟鎹� */
-async function loadMore() {
- if (skipGetMessageList.value) {
- return;
- }
- queryParams.pageNo += 1;
- const res = await getOrderPage(queryParams);
- total.value = res.total;
- list.value = [...list.value, ...res.list];
-}
-defineExpose({ getHistoryList, loadMore });
-</script>
-
-<template>
- <OrderItem v-for="item in list" :key="item.id" :order="item" />
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue b/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue
deleted file mode 100644
index c35d87b..0000000
--- a/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-
-import { computed, reactive, ref } from 'vue';
-
-import { getBrowseHistoryPage } from '#/api/mall/product/history';
-import ProductItem from '#/views/mall/promotion/kefu/modules/message/product-item.vue';
-
-const list = ref<any>([]); // 鍒楄〃
-const total = ref(0); // 鎬绘暟
-const queryParams = reactive({
- pageNo: 1,
- pageSize: 10,
- userId: 0,
- userDeleted: false,
-});
-const skipGetMessageList = computed(() => {
- // 宸插姞杞藉埌鏈�鍚庝竴椤电殑璇濆垯涓嶈Е鍙戞柊鐨勬秷鎭幏鍙�
- return (
- total.value > 0 &&
- Math.ceil(total.value / queryParams.pageSize) === queryParams.pageNo
- );
-}); // 璺宠繃娑堟伅鑾峰彇
-
-/** 鑾峰緱娴忚璁板綍 */
-async function getHistoryList(val: MallKefuConversationApi.Conversation) {
- queryParams.userId = val.userId;
- const res = await getBrowseHistoryPage(queryParams);
- total.value = res.total;
- list.value = res.list;
-}
-
-/** 鍔犺浇涓嬩竴椤垫暟鎹� */
-async function loadMore() {
- if (skipGetMessageList.value) {
- return;
- }
- queryParams.pageNo += 1;
- const res = await getBrowseHistoryPage(queryParams);
- total.value = res.total;
- list.value = [...list.value, ...res.list];
-}
-defineExpose({ getHistoryList, loadMore });
-</script>
-
-<template>
- <ProductItem
- v-for="item in list"
- :key="item.id"
- :pic-url="item.picUrl"
- :price="item.price"
- :sales-count="item.salesCount"
- :spu-id="item.spuId"
- :stock="item.stock"
- :title="item.spuName"
- class="mb-2.5"
- />
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/message-list.vue b/src/views/mall/promotion/kefu/modules/message-list.vue
deleted file mode 100644
index 344d437..0000000
--- a/src/views/mall/promotion/kefu/modules/message-list.vue
+++ /dev/null
@@ -1,420 +0,0 @@
-<script lang="ts" setup>
-import type { Emoji } from './tools/emoji';
-
-import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
-import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
-
-import { computed, reactive, ref, toRefs, unref, watch } from 'vue';
-
-import { KeFuMessageContentTypeEnum, UserTypeEnum } from '../../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { formatDate, isEmpty, jsonParse } from '../../../../../packages/utils/src';
-
-import { useScroll } from '@vueuse/core';
-import { Avatar, Empty, Image, notification, Textarea } from 'ant-design-vue';
-import dayjs from 'dayjs';
-import relativeTime from 'dayjs/plugin/relativeTime';
-
-import {
- getKeFuMessageList,
- sendKeFuMessage,
- updateKeFuMessageReadStatus,
-} from '#/api/mall/promotion/kefu/message';
-import { useMallKefuStore } from '#/store/mall/kefu';
-
-import MessageItem from './message/message-item.vue';
-import OrderItem from './message/order-item.vue';
-import ProductItem from './message/product-item.vue';
-import { useEmoji } from './tools/emoji';
-import EmojiSelectPopover from './tools/emoji-select-popover.vue';
-import PictureSelectUpload from './tools/picture-select-upload.vue';
-
-dayjs.extend(relativeTime);
-
-const message = ref(''); // 娑堟伅寮圭獥
-const { replaceEmoji } = useEmoji();
-const messageList = ref<MallKefuMessageApi.Message[]>([]); // 娑堟伅鍒楄〃
-const conversation = ref<MallKefuConversationApi.Conversation>(
- {} as MallKefuConversationApi.Conversation,
-); // 鐢ㄦ埛浼氳瘽
-const showNewMessageTip = ref(false); // 鏄剧ず鏈夋柊娑堟伅鎻愮ず
-const queryParams = reactive({
- conversationId: 0,
- createTime: undefined,
-});
-const total = ref(0); // 娑堟伅鎬绘潯鏁�
-const refreshContent = ref(false); // 鍐呭鍒锋柊,涓昏瑙e喅浼氳瘽娑堟伅椤甸潰楂樺害涓嶄竴鑷村鑷寸殑婊氬姩鍔熻兘绮惧害澶辨晥
-const kefuStore = useMallKefuStore(); // 瀹㈡湇缂撳瓨
-const skipGetMessageList = ref(false); // 璺宠繃娑堟伅鑾峰彇
-const loadHistory = ref(false); // 鍔犺浇鍘嗗彶娑堟伅
-
-/** 鑾锋倝娑堟伅鍐呭 */
-const getMessageContent = computed(
- () => (item: any) => jsonParse(item.content),
-);
-
-/** 鑾峰緱娑堟伅鍒楄〃 */
-async function getMessageList() {
- const res: any = await getKeFuMessageList(queryParams as any);
- if (isEmpty(res)) {
- // 褰撹繑鍥炵殑鏄┖鍒楄〃璇存槑娌℃湁娑堟伅鎴栬�呭凡缁忔煡璇㈠畬浜嗗巻鍙叉秷鎭�
- skipGetMessageList.value = true;
- return;
- }
- queryParams.createTime = formatDate(
- (res as any).at(-1).createTime,
- 'YYYY-MM-DD HH:mm:ss',
- ) as any;
-
- // 濡傛灉 createTime 瀛樺湪锛岃鏄庢槸鍔犺浇鍘嗗彶娑堟伅锛岄渶瑕佽拷鍔犲埌鍒楄〃
- if (queryParams.createTime) {
- // 鎯呭喌涓�锛氬姞杞藉巻鍙叉秷鎭紝杩藉姞鍒板垪琛�
- for (const item of res as any) {
- pushMessage(item);
- }
- } else {
- // 鎯呭喌浜岋細鍔犺浇鏈�鏂版秷鎭紝鐩存帴鏇挎崲鍒楄〃
- messageList.value = res;
- }
- refreshContent.value = true;
-}
-
-/** 娣诲姞娑堟伅 */
-function pushMessage(message: any) {
- if (messageList.value.some((val) => val.id === message.id)) {
- return;
- }
- messageList.value.push(message);
-}
-
-/** 鎸夌収鏃堕棿鍊掑簭锛岃幏鍙栨秷鎭垪琛� */
-const getMessageList0 = computed(() => {
- // 浣跨敤灞曞紑杩愮畻绗﹀垱寤烘柊鏁扮粍锛岄伩鍏嶇洿鎺ヤ慨鏀瑰師鏁扮粍
- return [...messageList.value].toSorted(
- (a: any, b: any) => a.createTime - b.createTime,
- );
-});
-
-/** 鍒锋柊娑堟伅鍒楄〃 */
-async function refreshMessageList(message?: any) {
- if (!conversation.value) {
- return;
- }
-
- if (message === undefined) {
- queryParams.createTime = undefined;
- await getMessageList();
- } else {
- // 褰撳墠鏌ヨ浼氳瘽涓庢秷鎭墍灞炰細璇濅笉涓�鑷村垯涓嶅仛澶勭悊
- if (message.conversationId !== conversation.value.id) {
- return;
- }
- pushMessage(message);
- }
-
- if (loadHistory.value) {
- // 鍙充笅瑙掓樉绀烘湁鏂版秷鎭彁绀�
- showNewMessageTip.value = true;
- } else {
- // 婊氬姩鍒版渶鏂版秷鎭
- await handleToNewMessage();
- }
-}
-
-/** 鑾峰緱鏂颁細璇濈殑娑堟伅鍒楄〃, 鐐瑰嚮鍒囨崲鏃讹紝璇诲彇缂撳瓨锛涚劧鍚庡紓姝ヨ幏鍙栨柊娑堟伅锛宮erge 涓嬶紱 */
-async function getNewMessageList(val: MallKefuConversationApi.Conversation) {
- // 1. 缂撳瓨褰撳墠浼氳瘽娑堟伅鍒楄〃
- kefuStore.saveMessageList(conversation.value.id, messageList.value);
- // 2.1 浼氳瘽鍒囨崲,閲嶇疆鐩稿叧鍙傛暟
- messageList.value = kefuStore.getConversationMessageList(val.id) || [];
- total.value = messageList.value.length || 0;
- loadHistory.value = false;
- refreshContent.value = false;
- skipGetMessageList.value = false;
- // 2.2 璁剧疆浼氳瘽鐩稿叧灞炴��
- conversation.value = val;
- queryParams.conversationId = val.id;
- queryParams.createTime = undefined;
- // 3. 鑾峰彇娑堟伅
- await refreshMessageList();
-}
-defineExpose({ getNewMessageList, refreshMessageList });
-
-/** 鏄惁鏄剧ず鑱婂ぉ鍖哄煙 */
-function showMessageList() {
- return !isEmpty(conversation.value);
-}
-
-/** 澶勭悊琛ㄦ儏閫夋嫨 */
-function handleEmojiSelect(item: Emoji) {
- message.value += item.name;
-}
-
-/** 澶勭悊鍥剧墖鍙戦�� */
-async function handleSendPicture(picUrl: string) {
- // 缁勭粐鍙戦�佹秷鎭�
- const msg = {
- conversationId: conversation.value.id,
- contentType: KeFuMessageContentTypeEnum.IMAGE,
- content: JSON.stringify({ picUrl }),
- };
- await sendMessage(msg);
-}
-
-/** 鍙戦�佹枃鏈秷鎭� */
-async function handleSendMessage(event: any) {
- // shift 涓嶅彂閫�
- if (event.shiftKey) {
- return;
- }
- // 1. 鏍¢獙娑堟伅鏄惁涓虹┖
- if (isEmpty(unref(message.value)?.trim())) {
- notification.warning({ message: '璇疯緭鍏ユ秷鎭悗鍐嶅彂閫佸摝锛�' });
- message.value = '';
- return;
- }
- // 2. 缁勭粐鍙戦�佹秷鎭�
- const msg = {
- conversationId: conversation.value.id,
- contentType: KeFuMessageContentTypeEnum.TEXT,
- content: JSON.stringify({ text: message.value }),
- };
- await sendMessage(msg);
-}
-
-/** 鐪熸鍙戦�佹秷鎭� 銆愬叡鐢ㄣ��*/
-async function sendMessage(msg: MallKefuMessageApi.MessageSend) {
- // 鍙戦�佹秷鎭�
- await sendKeFuMessage(msg);
- message.value = '';
- // 鍔犺浇娑堟伅鍒楄〃
- await refreshMessageList();
- // 鏇存柊浼氳瘽缂撳瓨
- await kefuStore.updateConversation(conversation.value.id);
-}
-
-/** 婊氬姩鍒板簳閮� */
-const scrollbarRef = ref<HTMLElement | null>(null);
-const { y, arrivedState } = useScroll(scrollbarRef);
-
-const { bottom } = toRefs(arrivedState);
-
-async function scrollToBottom() {
- if (!scrollbarRef.value) return;
- // 1. 棣栨鍔犺浇鏃舵粴鍔ㄥ埌鏈�鏂版秷鎭紝濡傛灉鍔犺浇鐨勬槸鍘嗗彶娑堟伅鍒欎笉婊氬姩
- if (loadHistory.value) {
- return;
- }
- // 2.1 婊氬姩鍒版渶鏂版秷鎭紝鍏抽棴鏂版秷鎭彁绀�
- // 浣跨敤 useScroll 鐩戝惉婊氬姩瀹瑰櫒
- y.value = scrollbarRef.value.scrollHeight - scrollbarRef.value!.clientHeight;
- showNewMessageTip.value = false;
- // 2.2 娑堟伅宸茶
- await updateKeFuMessageReadStatus(conversation.value.id);
-}
-
-/** 鏌ョ湅鏂版秷鎭� */
-async function handleToNewMessage() {
- loadHistory.value = false;
- await scrollToBottom();
-}
-
-watch(
- () => bottom.value,
- (newVal) => {
- if (newVal) {
- if (skipGetMessageList.value) {
- return;
- }
- // 婊氬姩鍒板簳閮ㄤ簡
- if (newVal) {
- loadHistory.value = false;
- refreshMessageList();
- }
-
- // 瑙﹂《鑷姩鍔犺浇涓嬩竴椤垫暟鎹�
- if (arrivedState.top) {
- handleOldMessage();
- }
- }
- },
-);
-/** 鍔犺浇鍘嗗彶娑堟伅 */
-async function handleOldMessage() {
- loadHistory.value = true;
- await getMessageList();
-}
-
-/** 鏄惁鏄剧ず鏃堕棿 */
-function showTime(item: MallKefuMessageApi.Message, index: number) {
- if (unref(messageList.value)[index + 1]) {
- const dateString = dayjs(
- unref(messageList.value)[index + 1]!.createTime,
- ).fromNow();
- return dateString !== dayjs(unref(item).createTime).fromNow();
- }
- return false;
-}
-</script>
-
-<template>
- <div
- v-if="showMessageList()"
- class="flex h-full flex-auto flex-col bg-background p-4"
- >
- <div class="flex h-full flex-auto flex-shrink-0 flex-col">
- <div class="flex h-12 w-full flex-row items-center justify-between">
- <span class="text-lg font-bold">{{ conversation.userNickname }}</span>
- </div>
- <div
- ref="scrollbarRef"
- class="mb-4 flex h-full flex-col overflow-x-auto rounded-lg bg-gray-100 p-2"
- >
- <div class="flex flex-col">
- <!-- 娑堟伅鍒楄〃 -->
- <div
- v-for="(item, index) in getMessageList0"
- :key="item.id"
- class="w-full"
- >
- <div class="mb-5 flex items-center justify-center">
- <!-- 鏃ユ湡 -->
- <div
- v-if="
- item.contentType !== KeFuMessageContentTypeEnum.SYSTEM &&
- showTime(item, index)
- "
- class="w-fit rounded-lg bg-black/10 px-2 text-xs"
- >
- {{ formatDate(item.createTime) }}
- </div>
- <!-- 绯荤粺娑堟伅 -->
- <div
- v-if="item.contentType === KeFuMessageContentTypeEnum.SYSTEM"
- class="w-fit rounded-lg bg-black/10 px-2 text-xs"
- >
- {{ item.content }}
- </div>
- </div>
- <div
- :class="[
- item.senderType === UserTypeEnum.MEMBER
- ? 'justify-start'
- : item.senderType === UserTypeEnum.ADMIN
- ? 'justify-end'
- : '',
- ]"
- class="mb-5 flex w-full"
- >
- <Avatar
- v-if="item.senderType === UserTypeEnum.MEMBER"
- :src="conversation.userAvatar"
- alt="avatar"
- class="size-8"
- />
- <div
- :class="{
- 'w-auto max-w-[50%] p-1 font-medium text-gray-500 transition-all duration-200 hover:scale-105':
- KeFuMessageContentTypeEnum.TEXT === item.contentType,
- 'm-1 break-words rounded-lg bg-gray-100':
- KeFuMessageContentTypeEnum.TEXT === item.contentType &&
- item.senderType === UserTypeEnum.MEMBER,
- 'm-1 break-words rounded-lg bg-blue-50':
- KeFuMessageContentTypeEnum.TEXT === item.contentType &&
- item.senderType === UserTypeEnum.ADMIN,
- }"
- >
- <!-- 鏂囨湰娑堟伅 -->
- <MessageItem :message="item">
- <template
- v-if="KeFuMessageContentTypeEnum.TEXT === item.contentType"
- >
- <div
- v-dompurify-html="
- replaceEmoji(
- getMessageContent(item).text || item.content,
- )
- "
- class="h-full w-full text-justify"
- ></div>
- </template>
- </MessageItem>
- <!-- 鍥剧墖娑堟伅 -->
- <MessageItem :message="item">
- <Image
- v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
- :initial-index="0"
- :preview-src-list="[
- getMessageContent(item).picUrl || item.content,
- ]"
- :src="getMessageContent(item).picUrl || item.content"
- class="mx-2 !w-52"
- fit="contain"
- preview-teleported
- />
- </MessageItem>
- <!-- 鍟嗗搧娑堟伅 -->
- <MessageItem :message="item">
- <ProductItem
- v-if="
- KeFuMessageContentTypeEnum.PRODUCT === item.contentType
- "
- :pic-url="getMessageContent(item).picUrl"
- :price="getMessageContent(item).price"
- :sales-count="getMessageContent(item).salesCount"
- :spu-id="getMessageContent(item).spuId"
- :stock="getMessageContent(item).stock"
- :title="getMessageContent(item).spuName"
- class="mx-2 max-w-80"
- />
- </MessageItem>
- <!-- 璁㈠崟娑堟伅 -->
- <MessageItem :message="item">
- <OrderItem
- v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
- :message="item"
- class="mx-2 max-w-full"
- />
- </MessageItem>
- </div>
- <Avatar
- v-if="item.senderType === UserTypeEnum.ADMIN"
- :src="item.senderAvatar"
- alt="avatar"
- />
- </div>
- </div>
- </div>
- </div>
- <div
- v-show="showNewMessageTip"
- class="absolute bottom-9 right-9 z-10 flex cursor-pointer items-center rounded-lg p-2 text-xs shadow-md"
- @click="handleToNewMessage"
- >
- <span>鏈夋柊娑堟伅</span>
- <IconifyIcon class="ml-1" icon="lucide:arrow-down-from-line" />
- </div>
- <div class="flex flex-col">
- <div class="-mt-2 flex flex-col rounded-xl border">
- <div class="flex h-10 w-full items-center">
- <EmojiSelectPopover @select-emoji="handleEmojiSelect" />
- <PictureSelectUpload
- class="ml-4 mt-1 cursor-pointer"
- @send-picture="handleSendPicture"
- />
- </div>
- <Textarea
- v-model:value="message"
- :rows="4"
- class="border-none p-2"
- placeholder="杈撳叆娑堟伅锛孍nter鍙戦�侊紝Shift+Enter鎹㈣"
- @press-enter="handleSendMessage"
- />
- </div>
- </div>
- </div>
- </div>
- <div v-else class="relative bg-background">
- <Empty description="璇烽�夋嫨宸︿晶鐨勪竴涓細璇濆悗寮�濮�" class="mt-[20%]" />
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/message/message-item.vue b/src/views/mall/promotion/kefu/modules/message/message-item.vue
deleted file mode 100644
index ed5b4de..0000000
--- a/src/views/mall/promotion/kefu/modules/message/message-item.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
-
-import { UserTypeEnum } from '../../../../../../packages/constants/src';
-
-/** 娑堟伅缁勪欢 */
-
-defineProps<{
- message: MallKefuMessageApi.Message;
-}>();
-</script>
-
-<template>
- <div
- :class="[
- message.senderType === UserTypeEnum.MEMBER
- ? `ml-2`
- : message.senderType === UserTypeEnum.ADMIN
- ? `mr-2`
- : '',
- ]"
- >
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/message/order-item.vue b/src/views/mall/promotion/kefu/modules/message/order-item.vue
deleted file mode 100644
index 8497cb4..0000000
--- a/src/views/mall/promotion/kefu/modules/message/order-item.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<script lang="ts" setup>
-import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
-
-import { computed } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { fenToYuan, isObject, jsonParse } from '../../../../../../packages/utils/src';
-
-import ProductItem from './product-item.vue';
-
-const props = defineProps<{
- message?: MallKefuMessageApi.Message;
- order?: any;
-}>();
-
-const { push } = useRouter();
-
-const getMessageContent = computed(() =>
- props.message === undefined
- ? props.order
- : jsonParse(props!.message!.content),
-);
-
-/** 鏌ョ湅璁㈠崟璇︽儏 */
-function openDetail(id: number) {
- push({ name: 'TradeOrderDetail', params: { id } });
-}
-
-/** 鏍煎紡鍖栬鍗曠姸鎬佺殑棰滆壊 */
-function formatOrderColor(order: any) {
- if (order.status === 0) {
- return 'text-gray-500';
- }
- if (
- order.status === 10 ||
- order.status === 20 ||
- (order.status === 30 && !order.commentStatus)
- ) {
- return 'text-orange-500';
- }
- if (order.status === 30 && order.commentStatus) {
- return 'text-green-500';
- }
- return 'text-red-500';
-}
-
-/** 鏍煎紡鍖栬鍗曠姸鎬� */
-function formatOrderStatus(order: any) {
- if (order.status === 0) {
- return '寰呬粯娆�';
- }
- if (order.status === 10 && order.deliveryType === 1) {
- return '寰呭彂璐�';
- }
- if (order.status === 10 && order.deliveryType === 2) {
- return '寰呮牳閿�';
- }
- if (order.status === 20) {
- return '寰呮敹璐�';
- }
- if (order.status === 30 && !order.commentStatus) {
- return '寰呰瘎浠�';
- }
- if (order.status === 30 && order.commentStatus) {
- return '宸插畬鎴�';
- }
- return '宸插叧闂�';
-}
-</script>
-
-<template>
- <div
- v-if="isObject(getMessageContent)"
- :key="getMessageContent.id"
- class="mb-2 rounded-md bg-gray-500/30 p-2"
- >
- <div class="flex h-6 items-center justify-between px-1 font-bold">
- <div class="flex flex-row text-sm">
- <div>璁㈠崟鍙凤細</div>
- <span
- class="cursor-pointer text-primary hover:underline"
- @click="openDetail(getMessageContent.id)"
- >
- {{ getMessageContent.no }}
- </span>
- </div>
- <div
- :class="formatOrderColor(getMessageContent)"
- class="text-sm font-bold"
- >
- {{ formatOrderStatus(getMessageContent) }}
- </div>
- </div>
- <div
- v-for="item in getMessageContent.items"
- :key="item.id"
- class="border-b border-gray-200"
- >
- <ProductItem
- :num="item.count"
- :pic-url="item.picUrl"
- :price="item.price"
- :sku-text="
- item.properties.map((property: any) => property.valueName).join(' ')
- "
- :spu-id="item.spuId"
- :title="item.spuName"
- />
- </div>
- <div class="flex justify-end pr-1.5 pt-2.5 font-bold">
- <div class="flex items-center">
- <div class="text-sm leading-normal">
- 鍏� {{ getMessageContent?.productCount }} 浠跺晢鍝�,鎬婚噾棰�:
- </div>
- <div class="text-sm font-medium leading-normal">
- 锟{ fenToYuan(getMessageContent?.payPrice) }}
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/message/product-item.vue b/src/views/mall/promotion/kefu/modules/message/product-item.vue
deleted file mode 100644
index 4344044..0000000
--- a/src/views/mall/promotion/kefu/modules/message/product-item.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<script lang="ts" setup>
-import { useRouter } from 'vue-router';
-
-import { fenToYuan } from '../../../../../../packages/utils/src';
-
-import { Button, Image } from 'ant-design-vue';
-
-defineProps({
- spuId: {
- type: Number,
- default: 0,
- },
- picUrl: {
- type: String,
- default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto',
- },
- title: {
- type: String,
- default: '',
- },
- price: {
- type: [String, Number],
- default: '',
- },
- salesCount: {
- type: [String, Number],
- default: '',
- },
- stock: {
- type: [String, Number],
- default: '',
- },
-});
-
-const { push } = useRouter();
-
-/** 鏌ョ湅鍟嗗搧璇︽儏 */
-function openDetail(spuId: number) {
- push({ name: 'ProductSpuDetail', params: { id: spuId } });
-}
-</script>
-
-<template>
- <div
- class="mb-2.5 flex w-full cursor-pointer items-center rounded-lg bg-gray-500/30 p-2.5"
- @click.stop="openDetail(spuId)"
- >
- <!-- 宸︿晶鍟嗗搧鍥剧墖-->
- <div class="mr-2 w-16">
- <Image
- :initial-index="0"
- :preview-src-list="[picUrl]"
- :src="picUrl"
- class="h-full w-full rounded-lg"
- fit="contain"
- preview-teleported
- @click.stop
- />
- </div>
- <!-- 鍙充晶鍟嗗搧淇℃伅 -->
- <div class="flex-1">
- <div class="line-clamp-1 w-full text-base font-bold">{{ title }}</div>
- <div class="my-1">
- <span class="mr-5">搴撳瓨: {{ stock || 0 }}</span>
- <span>閿�閲�: {{ salesCount || 0 }}</span>
- </div>
- <div class="flex items-center justify-between">
- <span class="text-red-500">锟{ fenToYuan(price) }}</span>
- <Button size="small" text type="primary">璇︽儏</Button>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue b/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue
deleted file mode 100644
index f50e043..0000000
--- a/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<!-- emoji 琛ㄦ儏閫夋嫨缁勪欢 -->
-<script lang="ts" setup>
-import type { Emoji } from './emoji';
-
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../../../packages/icons/src';
-
-import { List, Popover } from 'ant-design-vue';
-
-import { useEmoji } from './emoji';
-
-/** 閫夋嫨 emoji 琛ㄦ儏 */
-const emits = defineEmits<{
- (e: 'selectEmoji', v: Emoji): void;
-}>();
-const { getEmojiList } = useEmoji();
-const emojiList = computed(() => getEmojiList());
-
-function handleSelect(item: Emoji) {
- // 鏁翠釜 emoji 鏁版嵁浼犻�掑嚭鍘伙紝鏂逛究浠ュ悗杈撳叆妗嗙洿鎺ユ樉绀鸿〃鎯�
- emits('selectEmoji', item);
-}
-</script>
-
-<template>
- <Popover placement="top" trigger="click">
- <template #content>
- <List height="300px" class="w-[500px]">
- <ul class="ml-2 flex flex-wrap px-2">
- <li
- v-for="(item, index) in emojiList"
- :key="index"
- :title="item.name"
- class="w-1/10 m-2 flex cursor-pointer items-center justify-center border border-solid border-primary p-2"
- @click="handleSelect(item)"
- >
- <img :src="item.url" class="size-4" />
- </li>
- </ul>
- </List>
- </template>
- <IconifyIcon
- class="ml-2.5 size-6 cursor-pointer"
- icon="twemoji:grinning-face"
- />
- </Popover>
-</template>
diff --git a/src/views/mall/promotion/kefu/modules/tools/emoji.ts b/src/views/mall/promotion/kefu/modules/tools/emoji.ts
deleted file mode 100644
index 8391f6b..0000000
--- a/src/views/mall/promotion/kefu/modules/tools/emoji.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { onMounted, ref } from 'vue';
-
-import { isEmpty } from '../../../../../../packages/utils/src';
-
-const emojiList = [
- { name: '[绗戞帀鐗橾', file: 'xiaodiaoya.png' },
- { name: '[鍙埍]', file: 'keai.png' },
- { name: '[鍐烽叿]', file: 'lengku.png' },
- { name: '[闂槾]', file: 'bizui.png' },
- { name: '[鐢熸皵]', file: 'shengqi.png' },
- { name: '[鎯婃亹]', file: 'jingkong.png' },
- { name: '[鐬岀潯]', file: 'keshui.png' },
- { name: '[澶х瑧]', file: 'daxiao.png' },
- { name: '[鐖卞績]', file: 'aixin.png' },
- { name: '[鍧忕瑧]', file: 'huaixiao.png' },
- { name: '[椋炲惢]', file: 'feiwen.png' },
- { name: '[鐤戦棶]', file: 'yiwen.png' },
- { name: '[寮�蹇僝', file: 'kaixin.png' },
- { name: '[鍙戝憜]', file: 'fadai.png' },
- { name: '[娴佹唱]', file: 'liulei.png' },
- { name: '[姹楅]', file: 'hanyan.png' },
- { name: '[鎯婃倸]', file: 'jingshu.png' },
- { name: '[鍥皛]', file: 'kun.png' },
- { name: '[蹇冪]', file: 'xinsui.png' },
- { name: '[澶╀娇]', file: 'tianshi.png' },
- { name: '[鏅昡', file: 'yun.png' },
- { name: '[鍟奭', file: 'a.png' },
- { name: '[鎰ゆ�抅', file: 'fennu.png' },
- { name: '[鐫$潃]', file: 'shuizhuo.png' },
- { name: '[闈㈡棤琛ㄦ儏]', file: 'mianwubiaoqing.png' },
- { name: '[闅捐繃]', file: 'nanguo.png' },
- { name: '[鐘洶]', file: 'fankun.png' },
- { name: '[濂藉悆]', file: 'haochi.png' },
- { name: '[鍛曞悙]', file: 'outu.png' },
- { name: '[榫囩墮]', file: 'ziya.png' },
- { name: '[鎳垫瘮]', file: 'mengbi.png' },
- { name: '[鐧界溂]', file: 'baiyan.png' },
- { name: '[楗挎]', file: 'esi.png' },
- { name: '[鍑禲', file: 'xiong.png' },
- { name: '[鎰熷啋]', file: 'ganmao.png' },
- { name: '[娴佹睏]', file: 'liuhan.png' },
- { name: '[绗戝摥]', file: 'xiaoku.png' },
- { name: '[娴佸彛姘碷', file: 'liukoushui.png' },
- { name: '[灏村艾]', file: 'ganga.png' },
- { name: '[鎯婅]', file: 'jingya.png' },
- { name: '[澶ф儕]', file: 'dajing.png' },
- { name: '[涓嶅ソ鎰忔�漖', file: 'buhaoyisi.png' },
- { name: '[澶ч椆]', file: 'danao.png' },
- { name: '[涓嶅彲鎬濊]', file: 'bukesiyi.png' },
- { name: '[鐖变綘]', file: 'aini.png' },
- { name: '[绾㈠績]', file: 'hongxin.png' },
- { name: '[鐐硅禐]', file: 'dianzan.png' },
- { name: '[鎭堕瓟]', file: 'emo.png' },
-];
-
-export interface Emoji {
- name: string;
- url: string;
-}
-
-export function useEmoji() {
- const emojiPathList = ref<any[]>([]);
-
- /** 鍔犺浇鏈湴鍥剧墖 */
- async function initStaticEmoji() {
- const pathList = import.meta.glob('../../asserts/*.{png,jpg,jpeg,svg}');
- for (const path in pathList) {
- const imageModule: any = await pathList[path]?.();
- emojiPathList.value.push({ path, src: imageModule.default });
- }
- }
-
- /** 鍒濆鍖� */
- onMounted(async () => {
- if (isEmpty(emojiPathList.value)) {
- await initStaticEmoji();
- }
- });
-
- /**
- * 灏嗘枃鏈腑鐨勮〃鎯呮浛鎹㈡垚鍥剧墖
- *
- * @return 鏇挎崲鍚庣殑鏂囨湰
- * @param content 娑堟伅鍐呭
- */
- function replaceEmoji(content: string) {
- let newData = content;
- if (typeof newData !== 'object') {
- const reg = /\[(.+?)\]/g; // [] 涓嫭鍙�
- const zhEmojiName = newData.match(reg);
- if (zhEmojiName) {
- zhEmojiName.forEach((item) => {
- const emojiFile = getEmojiFileByName(item);
- newData = newData.replace(
- item,
- `<img style="width: 20px;height: 20px;margin:0 1px 3px 1px;vertical-align: middle;" src="${emojiFile}" alt=""/>`,
- );
- });
- }
- }
- return newData;
- }
-
- /** 鑾峰緱鎵�鏈夎〃鎯� */
- function getEmojiList(): Emoji[] {
- return emojiList.map((item) => ({
- url: getEmojiFileByName(item.name),
- name: item.name,
- })) as Emoji[];
- }
-
- function getEmojiFileByName(name: string) {
- for (const emoji of emojiList) {
- if (emoji.name === name) {
- const emojiPath = emojiPathList.value.find(
- (item: { path: string; src: string }) =>
- item.path.includes(emoji.file),
- );
- return emojiPath ? emojiPath.src : undefined;
- }
- }
- return false;
- }
-
- return { replaceEmoji, getEmojiList };
-}
diff --git a/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue b/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue
deleted file mode 100644
index 65f5a75..0000000
--- a/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<!-- 鍥剧墖閫夋嫨 -->
-<script lang="ts" setup>
-import { Image, message } from 'ant-design-vue';
-
-import { useUpload } from '#/components/upload/use-upload';
-import { $t } from '#/locales';
-import Picture from '#/views/mall/promotion/kefu/asserts/picture.svg';
-
-/** 閫夋嫨骞朵笂浼犳枃浠� */
-const emits = defineEmits<{
- (e: 'sendPicture', v: string): void;
-}>();
-
-async function selectAndUpload() {
- const files: any = await getFiles();
- message.success($t('ui.upload.imgUploading'));
- const res = await useUpload().httpRequest(files[0].file);
- message.success($t('ui.upload.uploadSuccess'));
- emits('sendPicture', res);
-}
-
-/** 鍞よ捣鏂囦欢閫夋嫨绐楀彛锛屽苟鑾峰彇閫夋嫨鐨勬枃浠� */
-async function getFiles(options = {}) {
- const { multiple, accept, limit, fileSize } = {
- multiple: true,
- accept: 'image/jpeg, image/png, image/gif', // 榛樿閫夋嫨鍥剧墖
- limit: 1,
- fileSize: 500,
- ...options,
- };
-
- // 鍒涘缓鏂囦欢閫夋嫨鍏冪礌
- const input = document.createElement('input');
- input.type = 'file';
- input.style.display = 'none';
- if (multiple) input.multiple = true;
- if (accept) input.accept = accept;
-
- // 灏嗘枃浠堕�夋嫨鍏冪礌娣诲姞鍒版枃妗d腑
- document.body.append(input);
-
- // 瑙﹀彂鏂囦欢閫夋嫨鍏冪礌鐨勭偣鍑讳簨浠�
- input.click();
-
- // 绛夊緟鏂囦欢閫夋嫨鍏冪礌鐨� change 浜嬩欢
- // 绉婚櫎涓嶅繀瑕佺殑 try/catch 鍖呰锛岀洿鎺ヨ繑鍥� Promise
- return await new Promise((resolve, reject) => {
- input.addEventListener('change', (event: any) => {
- const filesArray = [...(event?.target?.files || [])];
-
- // 浠庢枃妗d腑绉婚櫎鏂囦欢閫夋嫨鍏冪礌
- input.remove();
-
- // 鍒ゆ柇鏄惁瓒呭嚭涓婁紶鏁伴噺闄愬埗
- if (filesArray.length > limit) {
- // 浣跨敤 Error 瀵硅薄浣滀负 reject 鐨勫師鍥�
- reject(new Error(`瓒呭嚭涓婁紶鏁伴噺闄愬埗锛屾渶澶氬厑璁� ${limit} 涓枃浠禶));
- return;
- }
- // 鍒ゆ柇鏄惁瓒呭嚭涓婁紶鏂囦欢澶у皬闄愬埗
- const overSizedFiles = filesArray.filter(
- (file: File) => file.size / 1024 ** 2 > fileSize,
- );
- if (overSizedFiles.length > 0) {
- // 浣跨敤 Error 瀵硅薄浣滀负 reject 鐨勫師鍥�
- reject(new Error(`鏂囦欢澶у皬瓒呭嚭闄愬埗锛屽崟涓枃浠舵渶澶у厑璁� ${fileSize}MB`));
- return;
- }
-
- // 鐢熸垚鏂囦欢鍒楄〃锛屽苟娣诲姞 uid
- const fileList = filesArray.map((file, index) => ({
- file,
- uid: Date.now() + index,
- }));
- resolve(fileList);
- });
- });
-}
-</script>
-
-<template>
- <div>
- <Image
- :preview="false"
- :src="Picture"
- width="35px"
- height="35px"
- @click="selectAndUpload"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/point/activity/data.ts b/src/views/mall/promotion/point/activity/data.ts
deleted file mode 100644
index 4290751..0000000
--- a/src/views/mall/promotion/point/activity/data.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勮〃鏍煎垪 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- },
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- formatter: ({ row }) => `锟�${fenToYuan(row.marketPrice)}`,
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- minWidth: 80,
- },
- {
- field: 'totalStock',
- title: '鎬诲簱瀛�',
- minWidth: 80,
- },
- {
- field: 'redeemedQuantity',
- title: '宸插厬鎹㈡暟閲�',
- minWidth: 100,
- formatter: ({ row }) => {
- return (row.totalStock || 0) - (row.stock || 0);
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ユ帓搴�',
- },
- defaultValue: 0,
- rules: 'required',
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- },
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'spuId',
- label: '娲诲姩鍟嗗搧',
- component: 'Input',
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- ];
-}
diff --git a/src/views/mall/promotion/point/activity/index.vue b/src/views/mall/promotion/point/activity/index.vue
deleted file mode 100644
index 39dbb2e..0000000
--- a/src/views/mall/promotion/point/activity/index.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closePointActivity,
- deletePointActivity,
- getPointActivityPage,
-} from '#/api/mall/promotion/point';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'PromotionPointActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓绉垎鍟嗗煄娲诲姩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫绉垎鍟嗗煄娲诲姩 */
-function handleEdit(row: any) {
- formModalApi.setData(row).open();
-}
-
-/** 鍏抽棴绉垎鍟嗗煄娲诲姩 */
-async function handleClose(row: any) {
- const hideLoading = message.loading({
- content: '姝e湪鍏抽棴涓�...',
- duration: 0,
- });
- try {
- await closePointActivity(row.id);
- message.success('鍏抽棴鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎绉垎鍟嗗煄娲诲姩 */
-async function handleDelete(row: any) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.spuName]),
- duration: 0,
- });
- try {
- await deletePointActivity(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.spuName]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getPointActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="绉垎鍟嗗煄娲诲姩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绉垎娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:point-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:point-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.CLOSE,
- ifShow: row.status === 0,
- auth: ['promotion:point-activity:close'],
- popConfirm: {
- title: '纭鍏抽棴璇ョН鍒嗗晢鍩庢椿鍔ㄥ悧锛�',
- confirm: handleClose.bind(null, row),
- },
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- ifShow: row.status !== 0,
- auth: ['promotion:point-activity:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.spuName]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/point/activity/modules/form.vue b/src/views/mall/promotion/point/activity/modules/form.vue
deleted file mode 100644
index 74d6e82..0000000
--- a/src/views/mall/promotion/point/activity/modules/form.vue
+++ /dev/null
@@ -1,286 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallPointActivityApi } from '#/api/mall/promotion/point';
-import type {
- RuleConfig,
- SpuProperty,
-} from '#/views/mall/product/spu/components';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { cloneDeep, convertToInteger, formatToFraction } from '../../../../../../packages/utils/src';
-
-import { Button, InputNumber, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { VxeColumn } from '#/adapter/vxe-table';
-import { getSpu } from '#/api/mall/product/spu';
-import {
- createPointActivity,
- getPointActivity,
- updatePointActivity,
-} from '#/api/mall/promotion/point';
-import { $t } from '#/locales';
-import {
- getPropertyList,
- SpuAndSkuList,
- SpuSkuSelect,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallPointActivityApi.PointActivity>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绉垎娲诲姩'])
- : $t('ui.actionTitle.create', ['绉垎娲诲姩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-// ================= 鍟嗗搧閫夋嫨鐩稿叧 =================
-
-const spuSkuSelectRef = ref(); // 鍟嗗搧鍜屽睘鎬ч�夋嫨 Ref
-const spuAndSkuListRef = ref(); // SPU 鍜� SKU 鍒楄〃缁勪欢 Ref
-
-const ruleConfig: RuleConfig[] = [
- {
- name: 'productConfig.stock',
- rule: (arg) => arg >= 1,
- message: '鍟嗗搧鍙厬鎹㈠簱瀛樺繀椤诲ぇ浜庣瓑浜� 1 锛侊紒锛�',
- },
- {
- name: 'productConfig.point',
- rule: (arg) => arg >= 1,
- message: '鍟嗗搧鎵�闇�鍏戞崲绉垎蹇呴』澶т簬绛変簬 1 锛侊紒锛�',
- },
- {
- name: 'productConfig.count',
- rule: (arg) => arg >= 1,
- message: '鍟嗗搧鍙厬鎹㈡鏁板繀椤诲ぇ浜庣瓑浜� 1 锛侊紒锛�',
- },
-]; // SKU 瑙勫垯閰嶇疆
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 閫夋嫨鐨� SPU 鍒楄〃
-const spuPropertyList = ref<SpuProperty<MallSpuApi.Spu>[]>([]); // SPU 灞炴�у垪琛�
-
-/** 鎵撳紑鍟嗗搧閫夋嫨鍣� */
-function openSpuSelect() {
- spuSkuSelectRef.value?.open();
-}
-
-/** 閫夋嫨鍟嗗搧鍚庣殑鍥炶皟 */
-async function handleSpuSelected(spuId: number, skuIds?: number[]) {
- await formApi.setFieldValue('spuId', spuId);
- await getSpuDetails(spuId, skuIds);
-}
-
-/** 鑾峰彇 SPU 璇︽儏 */
-async function getSpuDetails(
- spuId: number,
- skuIds?: number[],
- products?: MallPointActivityApi.PointProduct[],
-) {
- const res = await getSpu(spuId);
- if (!res) {
- return;
- }
-
- spuList.value = [];
-
- // 绛涢�夋寚瀹氱殑 SKU
- const selectSkus =
- skuIds === undefined
- ? res.skus
- : res.skus?.filter((sku) => skuIds.includes(sku.id!));
- // 涓烘瘡涓� SKU 閰嶇疆绉垎鍟嗗煄鐩稿叧鐨勯厤缃�
- selectSkus?.forEach((sku) => {
- let config: MallPointActivityApi.PointProduct = {
- skuId: sku.id!,
- stock: 0,
- price: 0,
- point: 0,
- count: 0,
- };
- // 濡傛灉鏄紪杈戞ā寮忥紝鍥炲~宸叉湁閰嶇疆
- if (products !== undefined) {
- const product = products.find((item) => item.skuId === sku.id);
- if (product) {
- product.price = formatToFraction(product.price) as unknown as number;
- }
- config = product || config;
- }
- // 鍔ㄦ�佹坊鍔� productConfig 灞炴�у埌 SKU
- (
- sku as MallSpuApi.Sku & {
- productConfig: MallPointActivityApi.PointProduct;
- }
- ).productConfig = config;
- });
- res.skus = selectSkus;
-
- const spuProperties: SpuProperty<MallSpuApi.Spu>[] = [
- {
- spuId: res.id!,
- spuDetail: res,
- propertyList: getPropertyList(res),
- },
- ]; // 鏋勫缓 SPU 灞炴�у垪琛�
-
- // 鐩存帴璧嬪�硷紝鍥犱负姣忔鍙�夋嫨涓�涓� SPU
- spuList.value = [res];
- spuPropertyList.value = spuProperties;
-}
-
-// ================= end =================
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- try {
- // 鑾峰彇绉垎鍟嗗煄鍟嗗搧閰嶇疆锛堟繁鎷疯礉閬垮厤鐩存帴淇敼鍘熷璞★級
- const products: MallPointActivityApi.PointProduct[] = cloneDeep(
- spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
- );
- // 浠锋牸闇�瑕佽浆涓哄垎
- products.forEach((item) => {
- item.price = convertToInteger(item.price);
- });
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallPointActivityApi.PointActivity;
- data.products = products;
- await (formData.value?.id
- ? updatePointActivity(data)
- : createPointActivity(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- spuList.value = [];
- spuPropertyList.value = [];
- return;
- }
-
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallPointActivityApi.PointActivity>();
- if (!data || !data.id) {
- return;
- }
- // 鍔犺浇鏁版嵁
- modalApi.lock();
- try {
- formData.value = await getPointActivity(data.id);
- await getSpuDetails(
- formData.value.spuId,
- formData.value.products?.map((sku) => sku.skuId),
- formData.value.products,
- );
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <div>
- <Modal :title="getTitle" class="w-[70%]">
- <Form class="mx-4">
- <!-- 鍟嗗搧閫夋嫨 -->
- <template #spuId>
- <div class="w-full">
- <Button v-if="!formData?.id" type="primary" @click="openSpuSelect">
- 閫夋嫨鍟嗗搧
- </Button>
-
- <!-- SPU 鍜� SKU 鍒楄〃灞曠ず -->
- <SpuAndSkuList
- ref="spuAndSkuListRef"
- :rule-config="ruleConfig"
- :spu-list="spuList"
- :spu-property-list-p="spuPropertyList"
- class="mt-4"
- >
- <!-- 鎵╁睍鍒楋細绉垎鍟嗗煄鐗规湁閰嶇疆 -->
- <template #default>
- <VxeColumn align="center" min-width="168" title="鍙厬鎹㈠簱瀛�">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.stock"
- :max="sku.stock"
- :min="0"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="鍙厬鎹㈡鏁�">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.count"
- :min="0"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="鎵�闇�绉垎">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.point"
- :min="0"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="鎵�闇�閲戦(鍏�)">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.price"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- </template>
- </SpuAndSkuList>
- </div>
- </template>
- </Form>
- </Modal>
-
- <!-- 鍟嗗搧閫夋嫨鍣ㄥ脊绐� -->
- <SpuSkuSelect
- ref="spuSkuSelectRef"
- :is-select-sku="true"
- @select="handleSpuSelected"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/point/components/index.ts b/src/views/mall/promotion/point/components/index.ts
deleted file mode 100644
index 4271f4e..0000000
--- a/src/views/mall/promotion/point/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as PointShowcase } from './showcase.vue';
-export { default as PointTableSelect } from './table-select.vue';
diff --git a/src/views/mall/promotion/point/components/showcase.vue b/src/views/mall/promotion/point/components/showcase.vue
deleted file mode 100644
index 6ad6b8d..0000000
--- a/src/views/mall/promotion/point/components/showcase.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<!-- 绉垎鍟嗗煄娲诲姩姗辩獥缁勪欢锛氱敤浜庡睍绀哄拰閫夋嫨绉垎鍟嗗煄娲诲姩 -->
-<script lang="ts" setup>
-import type { MallPointActivityApi } from '#/api/mall/promotion/point';
-
-import { computed, ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { Image, Tooltip } from 'ant-design-vue';
-
-import { getPointActivityListByIds } from '#/api/mall/promotion/point';
-
-import TableSelect from './table-select.vue';
-
-interface PointShowcaseProps {
- modelValue?: number | number[];
- limit?: number;
- disabled?: boolean;
-}
-
-const props = withDefaults(defineProps<PointShowcaseProps>(), {
- modelValue: undefined,
- limit: Number.MAX_VALUE,
- disabled: false,
-});
-
-const emit = defineEmits(['update:modelValue', 'change']);
-
-const pointActivityList = ref<MallPointActivityApi.PointActivity[]>([]); // 宸查�夋嫨鐨勬椿鍔ㄥ垪琛�
-const pointTableSelectRef = ref<InstanceType<typeof TableSelect>>(); // 娲诲姩閫夋嫨琛ㄦ牸缁勪欢寮曠敤
-const isMultiple = computed(() => props.limit !== 1); // 鏄惁涓哄閫夋ā寮�
-
-/** 璁$畻鏄惁鍙互娣诲姞 */
-const canAdd = computed(() => {
- if (props.disabled) {
- return false;
- }
- if (!props.limit) {
- return true;
- }
- return pointActivityList.value.length < props.limit;
-});
-
-/** 鐩戝惉 modelValue 鍙樺寲锛屽姞杞芥椿鍔ㄨ鎯� */
-watch(
- () => props.modelValue,
- async (newValue) => {
- // eslint-disable-next-line unicorn/no-nested-ternary
- const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
- if (ids.length === 0) {
- pointActivityList.value = [];
- return;
- }
- // 鍙湁娲诲姩鍙戠敓鍙樺寲鏃舵墠閲嶆柊鏌ヨ
- if (
- pointActivityList.value.length === 0 ||
- pointActivityList.value.some((activity) => !ids.includes(activity.id!))
- ) {
- pointActivityList.value = await getPointActivityListByIds(ids);
- }
- },
- { immediate: true },
-);
-
-/** 鎵撳紑娲诲姩閫夋嫨瀵硅瘽妗� */
-function handleOpenActivitySelect() {
- pointTableSelectRef.value?.open(pointActivityList.value);
-}
-
-/** 閫夋嫨娲诲姩鍚庤Е鍙� */
-function handleActivitySelected(
- activities:
- | MallPointActivityApi.PointActivity
- | MallPointActivityApi.PointActivity[],
-) {
- pointActivityList.value = Array.isArray(activities)
- ? activities
- : [activities];
- emitActivityChange();
-}
-
-/** 鍒犻櫎娲诲姩 */
-function handleRemoveActivity(index: number) {
- pointActivityList.value.splice(index, 1);
- emitActivityChange();
-}
-
-/** 瑙﹀彂鍙樻洿浜嬩欢 */
-function emitActivityChange() {
- if (props.limit === 1) {
- const activity =
- pointActivityList.value.length > 0 ? pointActivityList.value[0] : null;
- emit('update:modelValue', activity?.id || 0);
- emit('change', activity);
- } else {
- emit(
- 'update:modelValue',
- pointActivityList.value.map((activity) => activity.id!),
- );
- emit('change', pointActivityList.value);
- }
-}
-</script>
-
-<template>
- <div class="flex flex-wrap items-center gap-2">
- <!-- 宸查�夋椿鍔ㄥ垪琛� -->
- <div
- v-for="(activity, index) in pointActivityList"
- :key="activity.id"
- class="relative h-[60px] w-[60px] overflow-hidden rounded-lg border border-dashed border-gray-300"
- >
- <Tooltip :title="activity.spuName">
- <div class="relative h-full w-full">
- <Image
- :preview="true"
- :src="activity.picUrl"
- class="h-full w-full rounded-lg object-cover"
- />
- <!-- 鍒犻櫎鎸夐挳 -->
- <IconifyIcon
- v-if="!disabled"
- icon="lucide:x"
- class="absolute -right-2 -top-2 z-10 h-5 w-5 cursor-pointer text-red-500 hover:text-red-600"
- @click="handleRemoveActivity(index)"
- />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娣诲姞娲诲姩鎸夐挳 -->
- <Tooltip v-if="canAdd" title="閫夋嫨娲诲姩">
- <div
- class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border border-dashed border-gray-300 hover:border-blue-400"
- @click="handleOpenActivitySelect"
- >
- <IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娲诲姩閫夋嫨瀵硅瘽妗� -->
- <TableSelect
- ref="pointTableSelectRef"
- :multiple="isMultiple"
- @change="handleActivitySelected"
- />
-</template>
diff --git a/src/views/mall/promotion/point/components/table-select.vue b/src/views/mall/promotion/point/components/table-select.vue
deleted file mode 100644
index ae8c4c6..0000000
--- a/src/views/mall/promotion/point/components/table-select.vue
+++ /dev/null
@@ -1,237 +0,0 @@
-<!-- 绉垎鍟嗗煄娲诲姩閫夋嫨寮圭獥缁勪欢 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps } from '#/adapter/vxe-table';
-import type { MallPointActivityApi } from '#/api/mall/promotion/point';
-
-import { computed } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getPointActivityPage } from '#/api/mall/promotion/point';
-
-interface PointTableSelectProps {
- multiple?: boolean; // 鏄惁鍗曢�夛細true - checkbox锛沠alse - radio
-}
-
-const props = withDefaults(defineProps<PointTableSelectProps>(), {
- multiple: false,
-});
-
-const emit = defineEmits<{
- change: [
- activity:
- | MallPointActivityApi.PointActivity
- | MallPointActivityApi.PointActivity[],
- ];
-}>();
-
-/** 鍗曢�夛細澶勭悊閫変腑鍙樺寲 */
-function handleRadioChange() {
- const selectedRow =
- gridApi.grid.getRadioRecord() as MallPointActivityApi.PointActivity;
- if (selectedRow) {
- emit('change', selectedRow);
- modalApi.close();
- }
-}
-
-/** 璁$畻宸插厬鎹㈡暟閲� */
-const getRedeemedQuantity = (row: MallPointActivityApi.PointActivity) =>
- (row.totalStock || 0) - (row.stock || 0);
-
-/** 鎼滅储琛ㄥ崟 Schema */
-const formSchema = computed<VbenFormSchema[]>(() => [
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- clearable: true,
- },
- },
-]);
-
-/** 琛ㄦ牸鍒楅厤缃� */
-const gridColumns = computed<VxeGridProps['columns']>(() => {
- const columns: VxeGridProps['columns'] = [];
- if (props.multiple) {
- columns.push({ type: 'checkbox', width: 55 });
- } else {
- columns.push({ type: 'radio', width: 55 });
- }
- columns.push(
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 100,
- align: 'center',
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- width: 100,
- align: 'center',
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 200,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- align: 'center',
- formatter: ({ cellValue }) => {
- return `楼${(cellValue / 100).toFixed(2)}`;
- },
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- minWidth: 80,
- align: 'center',
- },
- {
- field: 'totalStock',
- title: '鎬诲簱瀛�',
- minWidth: 80,
- align: 'center',
- },
- {
- field: 'redeemedQuantity',
- title: '宸插厬鎹㈡暟閲�',
- minWidth: 100,
- align: 'center',
- formatter: ({ row }) => getRedeemedQuantity(row),
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- align: 'center',
- formatter: 'formatDateTime',
- },
- );
- return columns;
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema.value,
- layout: 'horizontal',
- collapsed: false,
- },
- gridOptions: {
- columns: gridColumns.value,
- height: 500,
- border: true,
- checkboxConfig: {
- reserve: true,
- },
- radioConfig: {
- reserve: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- proxyConfig: {
- ajax: {
- async query({ page }: any, formValues: any) {
- return await getPointActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- },
- gridEvents: {
- radioChange: handleRadioChange,
- },
-});
-
-const [Modal, modalApi] = useVbenModal({
- destroyOnClose: true,
- showConfirmButton: props.multiple, // 鐗规畩锛歳adio 鍗曢�夋儏鍐典笅锛岃蛋 handleRadioChange 澶勭悊銆�
- onConfirm: () => {
- const selectedRows =
- gridApi.grid.getCheckboxRecords() as MallPointActivityApi.PointActivity[];
- emit('change', selectedRows);
- modalApi.close();
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- await gridApi.grid.clearCheckboxRow();
- await gridApi.grid.clearRadioRow();
- return;
- }
-
- // 1. 鍏堟煡璇㈡暟鎹�
- await gridApi.query();
- // 2. 璁剧疆宸查�変腑琛�
- const data = modalApi.getData<
- MallPointActivityApi.PointActivity | MallPointActivityApi.PointActivity[]
- >();
- if (props.multiple && Array.isArray(data) && data.length > 0) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData()
- .fullData as MallPointActivityApi.PointActivity[];
- data.forEach((activity) => {
- const row = tableData.find((item) => item.id === activity.id);
- if (row) {
- gridApi.grid.setCheckboxRow(row, true);
- }
- });
- }, 300);
- } else if (!props.multiple && data && !Array.isArray(data)) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData()
- .fullData as MallPointActivityApi.PointActivity[];
- const row = tableData.find((item) => item.id === data.id);
- if (row) {
- gridApi.grid.setRadioRow(row);
- }
- }, 300);
- }
- },
-});
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open: (
- data?:
- | MallPointActivityApi.PointActivity
- | MallPointActivityApi.PointActivity[],
- ) => {
- modalApi.setData(data).open();
- },
-});
-</script>
-
-<template>
- <Modal title="閫夋嫨娲诲姩" class="w-[950px]">
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/rewardActivity/data.ts b/src/views/mall/promotion/rewardActivity/data.ts
deleted file mode 100644
index 789f15e..0000000
--- a/src/views/mall/promotion/rewardActivity/data.ts
+++ /dev/null
@@ -1,251 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import {
- DICT_TYPE,
- PromotionConditionTypeEnum,
- PromotionProductScopeEnum,
-} from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { $t } from '../../../../packages/locales/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '娲诲姩鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勮〃鏍煎垪 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'productScope',
- title: '娲诲姩鑼冨洿',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE },
- },
- },
- {
- field: 'startTime',
- title: '娲诲姩寮�濮嬫椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'endTime',
- title: '娲诲姩缁撴潫鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- allowClear: true,
- },
- },
- {
- fieldName: 'startAndEndTime',
- label: '娲诲姩鏃堕棿',
- component: 'RangePicker',
- rules: 'required',
- componentProps: {
- showTime: true,
- format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
- placeholder: [
- $t('utils.rangePicker.beginTime'),
- $t('utils.rangePicker.endTime'),
- ],
- },
- },
- {
- fieldName: 'conditionType',
- label: '鏉′欢绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(PromotionConditionTypeEnum.PRICE.type),
- },
- {
- fieldName: 'productScope',
- label: '娲诲姩鑼冨洿',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(PromotionProductScopeEnum.ALL.scope),
- },
- {
- fieldName: 'productSpuIds',
- label: '閫夋嫨鍟嗗搧',
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productScopeValues'],
- show: (values) => {
- return values.productScope === PromotionProductScopeEnum.SPU.scope;
- },
- trigger(values, form) {
- // 褰撳姞杞藉凡鏈夋暟鎹椂锛屾牴鎹� productScopeValues 璁剧疆 productSpuIds
- if (
- values.productScope === PromotionProductScopeEnum.SPU.scope &&
- values.productScopeValues
- ) {
- form.setFieldValue('productSpuIds', values.productScopeValues);
- }
- },
- },
- rules: 'required',
- },
- {
- fieldName: 'productCategoryIds',
- label: '閫夋嫨鍒嗙被',
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productScopeValues'],
- show: (values) => {
- return (
- values.productScope === PromotionProductScopeEnum.CATEGORY.scope
- );
- },
- trigger(values, form) {
- // 褰撳姞杞藉凡鏈夋暟鎹椂锛屾牴鎹� productScopeValues 璁剧疆 productCategoryIds
- if (
- values.productScope === PromotionProductScopeEnum.CATEGORY.scope &&
- values.productScopeValues
- ) {
- form.setFieldValue('productCategoryIds', values.productScopeValues);
- }
- },
- },
- rules: 'required',
- },
- {
- fieldName: 'rules',
- label: '浼樻儬璁剧疆',
- component: 'Input',
- formItemClass: 'items-start',
- rules: z
- .array(z.any())
- .min(1, { message: '璇锋坊鍔犺嚦灏戜竴鏉′紭鎯犺鍒�' })
- .default([]),
- },
- {
- fieldName: 'productScopeValues', // 闅愯棌瀛楁锛氱敤浜庤嚜鍔ㄥ悓姝� productScopeValues
- component: 'Input',
- dependencies: {
- triggerFields: ['productScope', 'productSpuIds', 'productCategoryIds'],
- show: () => false,
- trigger(values, form) {
- switch (values.productScope) {
- case PromotionProductScopeEnum.CATEGORY.scope: {
- let categoryIds: number[] = [];
- if (Array.isArray(values.productCategoryIds)) {
- categoryIds = values.productCategoryIds;
- } else if (values.productCategoryIds) {
- categoryIds = [values.productCategoryIds];
- }
- form.setFieldValue('productScopeValues', categoryIds);
- break;
- }
- case PromotionProductScopeEnum.SPU.scope: {
- form.setFieldValue('productScopeValues', values.productSpuIds);
- break;
- }
- }
- },
- },
- },
- ];
-}
diff --git a/src/views/mall/promotion/rewardActivity/index.vue b/src/views/mall/promotion/rewardActivity/index.vue
deleted file mode 100644
index bcb5b87..0000000
--- a/src/views/mall/promotion/rewardActivity/index.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallRewardActivityApi } from '#/api/mall/promotion/reward/rewardActivity';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum } from '../../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closeRewardActivity,
- deleteRewardActivity,
- getRewardActivityPage,
-} from '#/api/mall/promotion/reward/rewardActivity';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'PromotionRewardActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓婊″噺閫佹椿鍔� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫婊″噺閫佹椿鍔� */
-function handleEdit(row: MallRewardActivityApi.RewardActivity) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍏抽棴婊″噺閫佹椿鍔� */
-async function handleClose(row: MallRewardActivityApi.RewardActivity) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.closing', [row.name]),
- duration: 0,
- });
- try {
- await closeRewardActivity(row.id!);
- message.success($t('ui.actionMessage.closeSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎婊″噺閫佹椿鍔� */
-async function handleDelete(row: MallRewardActivityApi.RewardActivity) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteRewardActivity(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getRewardActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallRewardActivityApi.RewardActivity>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="婊″噺閫佹椿鍔�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:reward-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:reward-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.CLOSE,
- auth: ['promotion:reward-activity:close'],
- ifShow: row.status === CommonStatusEnum.ENABLE,
- popConfirm: {
- title: '纭鍏抽棴璇ユ弧鍑忛�佹椿鍔ㄥ悧锛�',
- confirm: handleClose.bind(null, row),
- },
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:reward-activity:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/rewardActivity/modules/form.vue b/src/views/mall/promotion/rewardActivity/modules/form.vue
deleted file mode 100644
index 822aa48..0000000
--- a/src/views/mall/promotion/rewardActivity/modules/form.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-<script lang="ts" setup>
-import type { MallRewardActivityApi } from '#/api/mall/promotion/reward/rewardActivity';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import {
- PromotionConditionTypeEnum,
- PromotionProductScopeEnum,
-} from '../../../../../packages/constants/src';
-import { cloneDeep, convertToInteger, formatToFraction } from '../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createRewardActivity,
- getReward,
- updateRewardActivity,
-} from '#/api/mall/promotion/reward/rewardActivity';
-import { $t } from '#/locales';
-import { ProductCategorySelect } from '#/views/mall/product/category/components';
-import { SpuShowcase } from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-import RewardRule from './reward-rule.vue';
-
-const emit = defineEmits(['success']);
-
-const createDefaultFormData = (): Partial<MallRewardActivityApi.RewardActivity> => ({
- conditionType: PromotionConditionTypeEnum.PRICE.type,
- productScope: PromotionProductScopeEnum.ALL.scope,
- productScopeValues: [],
- productCategoryIds: [],
- productSpuIds: [],
- rules: [],
-});
-
-const formData = ref<Partial<MallRewardActivityApi.RewardActivity>>(
- createDefaultFormData(),
-);
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['婊″噺閫�'])
- : $t('ui.actionTitle.create', ['婊″噺閫�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- // 鍦ㄩ獙璇佸墠鍚屾 formData 涓殑鍊煎埌琛ㄥ崟涓�
- await formApi.setFieldValue('rules', formData.value.rules || []);
- // 鍚屾鍟嗗搧/鍒嗙被閫夋嫨鍒拌〃鍗曪紝纭繚楠岃瘉鏃惰兘鑾峰彇鍒板��
- if (formData.value.productSpuIds) {
- await formApi.setFieldValue(
- 'productSpuIds',
- formData.value.productSpuIds,
- );
- }
- if (formData.value.productCategoryIds) {
- await formApi.setFieldValue(
- 'productCategoryIds',
- formData.value.productCategoryIds,
- );
- }
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- try {
- const values = await formApi.getValues();
- // 浣跨敤 formData.value 浣滀负鍩虹锛岀‘淇� rules 鏉ヨ嚜 formData
- const data = { ...values, ...formData.value };
- if (data.startAndEndTime && Array.isArray(data.startAndEndTime)) {
- data.startTime = data.startAndEndTime[0];
- data.endTime = data.startAndEndTime[1];
- delete data.startAndEndTime;
- }
- // 娣辨嫹璐� rules 閬垮厤淇敼鍘熷鏁版嵁
- const rules = cloneDeep(
- data.rules,
- ) as unknown as MallRewardActivityApi.RewardRule[];
- rules.forEach((item: any) => {
- item.discountPrice = convertToInteger(item.discountPrice || 0);
- if (data.conditionType === PromotionConditionTypeEnum.PRICE.type) {
- item.limit = convertToInteger(item.limit || 0);
- }
- });
- data.rules = rules;
- await (data.id
- ? updateRewardActivity(data as MallRewardActivityApi.RewardActivity)
- : createRewardActivity(data as MallRewardActivityApi.RewardActivity));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = createDefaultFormData();
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallRewardActivityApi.RewardActivity>();
- if (!data || !data.id) {
- formData.value = createDefaultFormData();
- await formApi.setValues(formData.value);
- return;
- }
- modalApi.lock();
- try {
- const result = {
- ...createDefaultFormData(),
- ...(await getReward(data.id)),
- };
- result.startAndEndTime = [
- result.startTime ? String(result.startTime) : undefined,
- result.endTime ? String(result.endTime) : undefined,
- ] as any[];
- result.rules?.forEach((item: any) => {
- item.discountPrice = formatToFraction(item.discountPrice || 0);
- if (result.conditionType === PromotionConditionTypeEnum.PRICE.type) {
- item.limit = formatToFraction(item.limit || 0);
- }
- });
- formData.value = result;
- // 璁剧疆鍒� values
- await formApi.setValues(result);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/3">
- <Form class="mx-6">
- <!-- 鑷畾涔夋彃妲斤細浼樻儬瑙勫垯 -->
- <template #rules>
- <RewardRule v-model="formData" />
- </template>
- <!-- 鑷畾涔夋彃妲斤細鍟嗗搧閫夋嫨 -->
- <template #productSpuIds>
- <SpuShowcase v-model="formData.productSpuIds" />
- </template>
- <!-- 鑷畾涔夋彃妲斤細鍒嗙被閫夋嫨 -->
- <template #productCategoryIds>
- <ProductCategorySelect v-model="formData.productCategoryIds" multiple />
- </template>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue b/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue
deleted file mode 100644
index be829c0..0000000
--- a/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-<script lang="ts" setup>
-import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
-import type { MallRewardActivityApi } from '#/api/mall/promotion/reward/rewardActivity';
-
-import { nextTick, onMounted, ref, watch } from 'vue';
-
-import { CouponTemplateTakeTypeEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import { Button, Input } from 'ant-design-vue';
-
-import { getCouponTemplateList } from '#/api/mall/promotion/coupon/couponTemplate';
-import { DictTag } from '#/components/dict-tag';
-import { CouponSelect } from '#/views/mall/promotion/coupon/components';
-import { discountFormat } from '#/views/mall/promotion/coupon/formatter';
-
-defineOptions({ name: 'RewardRuleCouponSelect' });
-
-const props = defineProps<{
- modelValue: MallRewardActivityApi.RewardRule;
-}>();
-
-const emits = defineEmits<{
- (e: 'update:modelValue', v: any): void;
-}>();
-
-/** 閫夋嫨璧犻�佺殑浼樻儬绫诲瀷鎷撳睍 */
-interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate {
- giveCount?: number;
-}
-
-const rewardRule = useVModel(props, 'modelValue', emits);
-const list = ref<GiveCoupon[]>([]); // 閫夋嫨鐨勪紭鎯犲姷鍒楄〃
-
-/** 閫夋嫨浼樻儬鍒� */
-const selectRef = ref<InstanceType<typeof CouponSelect>>();
-function handleSelect() {
- selectRef.value?.open();
-}
-
-/** 閫夋嫨浼樻儬鍒稿悗鐨勫洖璋� */
-function handleChange(val: any[]) {
- for (const item of val) {
- if (list.value.some((v) => v.id === item.id)) {
- continue;
- }
- list.value.push(item);
- }
-}
-
-/** 鍒犻櫎浼樻儬鍒� */
-function handleDelete(index: number) {
- list.value.splice(index, 1);
-}
-
-/** 鍒濆鍖栬禒閫佺殑浼樻儬鍒稿垪琛� */
-async function initGiveCouponList() {
- // 鏍¢獙浼樻儬鍒稿瓨鍦�
- if (
- !rewardRule.value ||
- !rewardRule.value.giveCouponTemplateCounts ||
- Object.keys(rewardRule.value.giveCouponTemplateCounts).length === 0
- ) {
- return;
- }
- const tempLateIds = Object.keys(
- rewardRule.value.giveCouponTemplateCounts,
- ) as unknown as number[];
- const data = await getCouponTemplateList(tempLateIds);
- if (!data) {
- return;
- }
- data.forEach((coupon) => {
- list.value.push({
- ...coupon,
- giveCount: rewardRule.value.giveCouponTemplateCounts![coupon.id],
- });
- });
-}
-
-/** 鐩戝惉 list 鍙樺寲锛岃嚜鍔ㄥ悓姝ュ埌 rewardRule */
-watch(
- list,
- (val) => {
- if (!rewardRule.value) {
- return;
- }
- // 鏍稿績锛氭竻绌� giveCouponTemplateCounts锛岃В鍐冲垹闄や笉鐢熸晥鐨勯棶棰�
- rewardRule.value.giveCouponTemplateCounts = {};
- // 璁剧疆浼樻儬鍒稿拰鍏舵暟閲忕殑瀵瑰簲
- val.forEach((item) => {
- rewardRule.value.giveCouponTemplateCounts![item.id] = item.giveCount!;
- });
- },
- { deep: true },
-);
-
-onMounted(async () => {
- await nextTick();
- await initGiveCouponList();
-});
-</script>
-
-<template>
- <div>
- <!-- 宸查�変紭鎯犲埜鍒楄〃 -->
- <div v-if="list.length > 0" class="mb-2 flex flex-col gap-2">
- <div
- v-for="(item, index) in list"
- :key="item.id"
- class="flex items-center justify-between rounded-md border border-gray-200 bg-white px-3 py-2 transition-all hover:border-blue-400 hover:shadow-sm"
- >
- <div class="flex flex-wrap items-center gap-3">
- <span class="font-medium text-gray-800">{{ item.name }}</span>
- <span class="flex items-center gap-1 text-sm text-gray-500">
- <DictTag
- :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE"
- :value="item.productScope"
- />
- </span>
- <span class="flex items-center gap-1 text-sm text-gray-500">
- <DictTag
- :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE"
- :value="item.discountType"
- />
- {{ discountFormat(item) }}
- </span>
- </div>
- <div class="flex shrink-0 items-center gap-2">
- <span class="text-gray-500">閫�</span>
- <Input
- v-model:value="item.giveCount"
- class="!w-20"
- placeholder=""
- type="number"
- size="small"
- />
- <span class="text-gray-500">寮�</span>
- <Button type="link" danger size="small" @click="handleDelete(index)">
- 鍒犻櫎
- </Button>
- </div>
- </div>
- </div>
-
- <!-- 娣诲姞鎸夐挳 -->
- <Button type="link" class="!pl-0" @click="handleSelect">
- + 娣诲姞浼樻儬鍒�
- </Button>
-
- <!-- 浼樻儬鍒搁�夋嫨寮圭獥 -->
- <CouponSelect
- ref="selectRef"
- :take-type="CouponTemplateTakeTypeEnum.ADMIN.type"
- @change="handleChange"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/rewardActivity/modules/reward-rule.vue b/src/views/mall/promotion/rewardActivity/modules/reward-rule.vue
deleted file mode 100644
index c52e6b9..0000000
--- a/src/views/mall/promotion/rewardActivity/modules/reward-rule.vue
+++ /dev/null
@@ -1,193 +0,0 @@
-<script lang="ts" setup>
-import type { MallRewardActivityApi } from '#/api/mall/promotion/reward/rewardActivity';
-
-import { computed } from 'vue';
-
-import { PromotionConditionTypeEnum } from '../../../../../packages/constants/src';
-
-import { useVModel } from '@vueuse/core';
-import {
- Button,
- Card,
- Col,
- Form,
- FormItem,
- Input,
- InputNumber,
- Row,
- Switch,
- Tag,
-} from 'ant-design-vue';
-
-import RewardRuleCouponSelect from './reward-rule-coupon-select.vue';
-
-defineOptions({ name: 'RewardRule' });
-
-const props = defineProps<{
- modelValue: Partial<MallRewardActivityApi.RewardActivity>;
-}>();
-
-const emits = defineEmits<{
- (e: 'update:modelValue', v: any): void;
-}>();
-
-const formData = useVModel(props, 'modelValue', emits);
-
-const isPriceCondition = computed(() => {
- return (
- formData.value?.conditionType === PromotionConditionTypeEnum.PRICE.type
- );
-});
-
-/** 澶勭悊鏂板 */
-function handleAdd() {
- if (!formData.value.rules) {
- formData.value.rules = [];
- }
- formData.value.rules.push({
- limit: 0,
- discountPrice: 0,
- freeDelivery: false,
- point: 0,
- });
-}
-
-/** 澶勭悊鍒犻櫎 */
-function handleDelete(ruleIndex: number) {
- formData.value.rules?.splice(ruleIndex, 1);
-}
-</script>
-
-<template>
- <Row :gutter="[16, 16]">
- <template v-if="formData.rules">
- <Col v-for="(rule, index) in formData.rules" :key="index" :span="24">
- <Card size="small" class="rounded-lg">
- <!-- 瑙勫垯鏍囬 -->
- <template #title>
- <div class="flex items-center">
- <span class="text-base font-medium">
- 娲诲姩灞傜骇 {{ index + 1 }}
- </span>
- </div>
- </template>
- <template v-if="index !== 0" #extra>
- <Button
- type="link"
- danger
- size="small"
- @click="handleDelete(index)"
- >
- 鍒犻櫎
- </Button>
- </template>
-
- <Form :model="rule" layout="horizontal">
- <!-- 浼樻儬闂ㄦ -->
- <FormItem label="浼樻儬闂ㄦ:" :colon="false" class="mb-3">
- <div
- class="flex items-center gap-2 rounded-md bg-gray-50 px-3 py-2"
- >
- <span>婊�</span>
- <InputNumber
- v-if="isPriceCondition"
- v-model:value="rule.limit"
- :min="0"
- :precision="2"
- :step="0.1"
- class="!w-40"
- placeholder="璇疯緭鍏ラ噾棰�"
- />
- <Input
- v-else
- v-model:value="rule.limit"
- :min="0"
- class="!w-40"
- placeholder="璇疯緭鍏ユ暟閲�"
- type="number"
- />
- <span>{{ isPriceCondition ? '鍏�' : '浠�' }}</span>
- </div>
- </FormItem>
- <!-- 浼樻儬鍐呭 -->
- <FormItem label="浼樻儬鍐呭:" :colon="false" class="!mb-0">
- <div class="flex flex-col gap-3">
- <!-- 璁㈠崟閲戦浼樻儬 -->
- <div
- class="flex items-center gap-2 rounded-md bg-gray-50 px-3 py-2"
- >
- <span class="!w-21 shrink-0 text-sm text-gray-500">
- 璁㈠崟閲戦浼樻儬
- </span>
- <span>鍑�</span>
- <InputNumber
- v-model:value="rule.discountPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="!w-32"
- placeholder="璇疯緭鍏ラ噾棰�"
- />
- <span>鍏�</span>
- </div>
- <!-- 鍖呴偖 -->
- <div
- class="flex items-center gap-2 rounded-md bg-gray-50 px-3 py-2"
- >
- <span class="w-20 shrink-0 text-sm text-gray-500">鍖呴偖</span>
- <Switch
- v-model:checked="rule.freeDelivery"
- checked-children="鏄�"
- un-checked-children="鍚�"
- />
- </div>
- <!-- 閫佺Н鍒� -->
- <div
- class="flex items-center gap-2 rounded-md bg-gray-50 px-3 py-2"
- >
- <span class="w-20 shrink-0 text-sm text-gray-500">
- 閫佺Н鍒�
- </span>
- <span>閫�</span>
- <InputNumber
- v-model:value="rule.point"
- :min="0"
- class="!w-32"
- placeholder="璇疯緭鍏ョН鍒�"
- />
- <span>绉垎</span>
- </div>
- <!-- 閫佷紭鎯犲埜 -->
- <div
- class="flex flex-col items-start gap-2 rounded-md bg-gray-50 px-3 py-2"
- >
- <span class="w-20 shrink-0 text-sm text-gray-500">
- 閫佷紭鎯犲埜
- </span>
- <RewardRuleCouponSelect
- :model-value="rule"
- @update:model-value="
- (val) => (formData.rules![index] = val)
- "
- />
- </div>
- </div>
- </FormItem>
- </Form>
- </Card>
- </Col>
- </template>
-
- <!-- 娣诲姞瑙勫垯鎸夐挳 -->
- <Col :span="24" class="mt-2">
- <Button type="primary" @click="handleAdd">+ 娣诲姞浼樻儬瑙勫垯</Button>
- </Col>
-
- <!-- 鎻愮ず淇℃伅 -->
- <Col :span="24" class="mt-2">
- <Tag color="warning">
- 鎻愮ず锛氳禒閫佺Н鍒嗕负 0 鏃朵笉璧犻�侊紱鏈�夋嫨浼樻儬鍒告椂涓嶈禒閫併��
- </Tag>
- </Col>
- </Row>
-</template>
diff --git a/src/views/mall/promotion/seckill/activity/data.ts b/src/views/mall/promotion/seckill/activity/data.ts
deleted file mode 100644
index c61b5e8..0000000
--- a/src/views/mall/promotion/seckill/activity/data.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getSimpleSeckillConfigList } from '#/api/mall/promotion/seckill/seckillConfig';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- ];
-}
-
-/** 鏂板/缂栬緫鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '绉掓潃娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- },
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'startTime',
- label: '娲诲姩寮�濮嬫椂闂�',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩寮�濮嬫椂闂�',
- showTime: true,
- format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
- class: 'w-full',
- },
- rules: 'required',
- },
- {
- fieldName: 'endTime',
- label: '娲诲姩缁撴潫鏃堕棿',
- component: 'DatePicker',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩缁撴潫鏃堕棿',
- showTime: true,
- format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
- class: 'w-full',
- },
- rules: 'required',
- },
- {
- fieldName: 'configIds',
- label: '绉掓潃鏃舵',
- component: 'ApiSelect',
- componentProps: {
- placeholder: '璇烽�夋嫨绉掓潃鏃舵',
- mode: 'multiple',
- api: getSimpleSeckillConfigList,
- labelField: 'name',
- valueField: 'id',
- class: 'w-full',
- },
- rules: 'required',
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'totalLimitCount',
- label: '鎬婚檺璐暟閲�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ�婚檺璐暟閲�',
- min: 0,
- },
- rules: z.number().min(0).default(0),
- },
- {
- fieldName: 'singleLimitCount',
- label: '鍗曟闄愯喘鏁伴噺',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ュ崟娆¢檺璐暟閲�',
- min: 0,
- },
- rules: z.number().min(0).default(0),
- },
- {
- fieldName: 'sort',
- label: '鎺掑簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ帓搴�',
- min: 0,
- },
- rules: z.number().min(0).default(0),
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- rows: 4,
- },
- formItemClass: 'col-span-2',
- },
- {
- fieldName: 'spuId',
- label: '绉掓潃鍟嗗搧',
- component: 'Input',
- formItemClass: 'col-span-2',
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'configIds',
- title: '绉掓潃鏃舵',
- minWidth: 220,
- slots: { default: 'configIds' },
- },
- {
- field: 'startTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- slots: { default: 'timeRange' },
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- formatter: ({ row }) => `锟�${(row.marketPrice / 100).toFixed(2)}`,
- },
- {
- field: 'seckillPrice',
- title: '绉掓潃浠�',
- minWidth: 100,
- formatter: ({ row }) => {
- if (!(row.products || row.products.length === 0)) {
- return '锟�0.00';
- }
- const seckillPrice = Math.min(
- ...row.products.map((item: any) => item.seckillPrice),
- );
- return `锟�${(seckillPrice / 100).toFixed(2)}`;
- },
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- align: 'center',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'stock',
- title: '搴撳瓨',
- align: 'center',
- minWidth: 80,
- },
- {
- field: 'totalStock',
- title: '鎬诲簱瀛�',
- align: 'center',
- minWidth: 80,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- align: 'center',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- align: 'center',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/seckill/activity/formatter.ts b/src/views/mall/promotion/seckill/activity/formatter.ts
deleted file mode 100644
index 38647c8..0000000
--- a/src/views/mall/promotion/seckill/activity/formatter.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { formatDate } from '../../../../../packages/utils/src';
-
-// 鍏ㄥ眬鍙橀噺锛岀敤浜庡瓨鍌ㄩ厤缃垪琛�
-let configList: any[] = [];
-
-/** 璁剧疆閰嶇疆鍒楄〃 */
-export function setConfigList(list: any[]) {
- configList = list;
-}
-
-/** 鏍煎紡鍖栭厤缃悕绉� */
-export function formatConfigNames(configId: number | string): string {
- const config = configList.find((item) => item.id === configId);
- return config === null || config === undefined
- ? ''
- : `${config.name}[${config.startTime} ~ ${config.endTime}]`;
-}
-
-/** 鏍煎紡鍖栫鏉�浠锋牸 */
-export function formatSeckillPrice(products: any[]): string {
- if (!products || products.length === 0) {
- return '锟�0.00';
- }
- const seckillPrice = Math.min(...products.map((item) => item.seckillPrice));
- return `锟�${(seckillPrice / 100).toFixed(2)}`;
-}
-
-/** 鏍煎紡鍖栨椿鍔ㄦ椂闂磋寖鍥� */
-export function formatTimeRange(
- startTime: Date | string | undefined,
- endTime: Date | string | undefined,
-): string {
- if (startTime && endTime) {
- return `${formatDate(startTime, 'YYYY-MM-DD')} ~ ${formatDate(endTime, 'YYYY-MM-DD')}`;
- }
- return '';
-}
diff --git a/src/views/mall/promotion/seckill/activity/index.vue b/src/views/mall/promotion/seckill/activity/index.vue
deleted file mode 100644
index f13e9e1..0000000
--- a/src/views/mall/promotion/seckill/activity/index.vue
+++ /dev/null
@@ -1,196 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckillActivity';
-
-import { onMounted } from 'vue';
-
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- closeSeckillActivity,
- deleteSeckillActivity,
- getSeckillActivityPage,
-} from '#/api/mall/promotion/seckill/seckillActivity';
-import { getSimpleSeckillConfigList } from '#/api/mall/promotion/seckill/seckillConfig';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import { formatConfigNames, formatTimeRange, setConfigList } from './formatter';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'SeckillActivity' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 缂栬緫娲诲姩 */
-function handleEdit(row: MallSeckillActivityApi.SeckillActivity) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒涘缓娲诲姩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 鍏抽棴娲诲姩 */
-async function handleClose(row: MallSeckillActivityApi.SeckillActivity) {
- const hideLoading = message.loading({
- content: '娲诲姩鍏抽棴涓�...',
- duration: 0,
- });
- try {
- await closeSeckillActivity(row.id as number);
- message.success({
- content: '鍏抽棴鎴愬姛',
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍒犻櫎娲诲姩 */
-async function handleDelete(row: MallSeckillActivityApi.SeckillActivity) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteSeckillActivity(row.id as number);
- message.success({
- content: $t('ui.actionMessage.deleteSuccess', [row.name]),
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getSeckillActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallSeckillActivityApi.SeckillActivity>,
-});
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- // 鑾峰緱绉掓潃鏃堕棿娈甸厤缃�
- const configList = await getSimpleSeckillConfigList();
- setConfigList(configList);
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯惀閿�銆戠鏉�娲诲姩"
- url="https://doc.iocoder.cn/mall/promotion-seckill/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="绉掓潃娲诲姩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绉掓潃娲诲姩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:seckill-activity:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
-
- <template #configIds="{ row }">
- <div class="flex flex-wrap gap-1">
- <Tag
- v-for="(configId, index) in row.configIds"
- :key="index"
- class="mr-1"
- >
- {{ formatConfigNames(configId) }}
- </Tag>
- </div>
- </template>
-
- <template #timeRange="{ row }">
- {{ formatTimeRange(row.startTime, row.endTime) }}
- </template>
-
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:seckill-activity:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍏抽棴',
- type: 'link',
- danger: true,
- auth: ['promotion:seckill-activity:close'],
- ifShow: row.status === 0,
- popConfirm: {
- title: '纭鍏抽棴璇ョ鏉�娲诲姩鍚楋紵',
- confirm: handleClose.bind(null, row),
- },
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- auth: ['promotion:seckill-activity:delete'],
- ifShow: row.status !== 0,
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/seckill/activity/modules/form.vue b/src/views/mall/promotion/seckill/activity/modules/form.vue
deleted file mode 100644
index 96e4679..0000000
--- a/src/views/mall/promotion/seckill/activity/modules/form.vue
+++ /dev/null
@@ -1,277 +0,0 @@
-<script lang="ts" setup>
-import type { MallSpuApi } from '#/api/mall/product/spu';
-import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckillActivity';
-import type {
- RuleConfig,
- SpuProperty,
-} from '#/views/mall/product/spu/components';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { cloneDeep, convertToInteger, formatToFraction } from '../../../../../../packages/utils/src';
-
-import { Button, InputNumber, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { VxeColumn } from '#/adapter/vxe-table';
-import { getSpu } from '#/api/mall/product/spu';
-import {
- createSeckillActivity,
- getSeckillActivity,
- updateSeckillActivity,
-} from '#/api/mall/promotion/seckill/seckillActivity';
-import { $t } from '#/locales';
-import {
- getPropertyList,
- SpuAndSkuList,
- SpuSkuSelect,
-} from '#/views/mall/product/spu/components';
-
-import { useFormSchema } from '../data';
-
-defineOptions({ name: 'PromotionSeckillActivityForm' });
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallSeckillActivityApi.SeckillActivity>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绉掓潃娲诲姩'])
- : $t('ui.actionTitle.create', ['绉掓潃娲诲姩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-// ================= 鍟嗗搧閫夋嫨鐩稿叧 =================
-
-const spuSkuSelectRef = ref(); // 鍟嗗搧鍜屽睘鎬ч�夋嫨 Ref
-const spuAndSkuListRef = ref(); // SPU 鍜� SKU 鍒楄〃缁勪欢 Ref
-
-const ruleConfig: RuleConfig[] = [
- {
- name: 'productConfig.stock',
- rule: (arg) => arg >= 1,
- message: '鍟嗗搧绉掓潃搴撳瓨蹇呴』澶т簬绛変簬 1 锛侊紒锛�',
- },
- {
- name: 'productConfig.seckillPrice',
- rule: (arg) => arg >= 0.01,
- message: '鍟嗗搧绉掓潃浠锋牸蹇呴』澶т簬绛変簬 0.01 锛侊紒锛�',
- },
-];
-
-const spuList = ref<MallSpuApi.Spu[]>([]); // 閫夋嫨鐨� SPU 鍒楄〃
-const spuPropertyList = ref<SpuProperty<MallSpuApi.Spu>[]>([]); // SPU 灞炴�у垪琛�
-
-/** 鎵撳紑鍟嗗搧閫夋嫨鍣� */
-function openSpuSelect() {
- spuSkuSelectRef.value?.open();
-}
-
-/** 閫夋嫨鍟嗗搧鍚庣殑鍥炶皟 */
-async function handleSpuSelected(spuId: number, skuIds?: number[]) {
- await formApi.setFieldValue('spuId', spuId);
- await getSpuDetails(spuId, skuIds);
-}
-
-/** 鑾峰彇 SPU 璇︽儏 */
-async function getSpuDetails(
- spuId: number,
- skuIds?: number[],
- products?: MallSeckillActivityApi.SeckillProduct[],
-) {
- const res = await getSpu(spuId);
- if (!res) {
- return;
- }
-
- spuList.value = [];
-
- // 绛涢�夋寚瀹氱殑 SKU
- const selectSkus =
- skuIds === undefined
- ? res.skus
- : res.skus?.filter((sku) => skuIds.includes(sku.id!));
-
- // 涓烘瘡涓� SKU 閰嶇疆绉掓潃娲诲姩鐩稿叧鐨勯厤缃�
- selectSkus?.forEach((sku) => {
- let config: MallSeckillActivityApi.SeckillProduct = {
- spuId: res.id!,
- skuId: sku.id!,
- stock: 0,
- seckillPrice: 0,
- };
- // 濡傛灉鏄紪杈戞ā寮忥紝鍥炲~宸叉湁閰嶇疆
- if (products !== undefined) {
- const product = products.find((item) => item.skuId === sku.id);
- if (product) {
- // 鍒嗚浆鍏�
- product.seckillPrice = formatToFraction(
- product.seckillPrice,
- ) as unknown as number;
- }
- config = product || config;
- }
- // 鍔ㄦ�佹坊鍔� productConfig 灞炴�у埌 SKU
- (
- sku as MallSpuApi.Sku & {
- productConfig: MallSeckillActivityApi.SeckillProduct;
- }
- ).productConfig = config;
- });
- res.skus = selectSkus;
-
- const spuProperties: SpuProperty<MallSpuApi.Spu>[] = [
- {
- spuId: res.id!,
- spuDetail: res,
- propertyList: getPropertyList(res),
- },
- ];
-
- // 鐩存帴璧嬪�硷紝鍥犱负绉掓潃娲诲姩鍙�夋嫨涓�涓� SPU
- spuList.value = [res];
- spuPropertyList.value = spuProperties;
-}
-
-// ================= end =================
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- try {
- // 鑾峰彇绉掓潃鍟嗗搧閰嶇疆锛堟繁鎷疯礉閬垮厤鐩存帴淇敼鍘熷璞★級
- const products: MallSeckillActivityApi.SeckillProduct[] = cloneDeep(
- spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
- );
- if (products.length === 0) {
- message.error('璇烽�夋嫨绉掓潃鍟嗗搧');
- return;
- }
- // 浠锋牸闇�瑕佽浆涓哄垎
- products.forEach((item) => {
- item.seckillPrice = convertToInteger(item.seckillPrice);
- });
-
- // 鎻愪氦琛ㄥ崟
- const values = await formApi.getValues();
- const data = {
- ...values,
- products,
- } as MallSeckillActivityApi.SeckillActivity;
-
- await (formData.value?.id
- ? updateSeckillActivity(data)
- : createSeckillActivity(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- spuList.value = [];
- spuPropertyList.value = [];
- return;
- }
-
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallSeckillActivityApi.SeckillActivity>();
- if (!data || !data.id) {
- return;
- }
- // 鍔犺浇鏁版嵁
- modalApi.lock();
- try {
- formData.value = await getSeckillActivity(data.id);
- // 瀵归綈娲诲姩鍟嗗搧澶勭悊缁撴瀯
- await getSpuDetails(
- formData.value.spuId!,
- formData.value.products?.map((sku) => sku.skuId),
- formData.value.products,
- );
- // 璁剧疆琛ㄥ崟鍊�
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <div>
- <Modal :title="getTitle" class="w-[70%]">
- <Form class="mx-4">
- <!-- 鍟嗗搧閫夋嫨 -->
- <template #spuId>
- <div class="w-full">
- <Button v-if="!formData?.id" type="primary" @click="openSpuSelect">
- 閫夋嫨鍟嗗搧
- </Button>
-
- <!-- SPU 鍜� SKU 鍒楄〃灞曠ず -->
- <SpuAndSkuList
- ref="spuAndSkuListRef"
- :rule-config="ruleConfig"
- :spu-list="spuList"
- :spu-property-list-p="spuPropertyList"
- class="mt-4"
- >
- <!-- 鎵╁睍鍒楋細绉掓潃娲诲姩鐗规湁閰嶇疆 -->
- <template #default>
- <VxeColumn align="center" min-width="168" title="绉掓潃搴撳瓨">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.stock"
- :max="sku.stock"
- :min="0"
- class="w-full"
- />
- </template>
- </VxeColumn>
- <VxeColumn align="center" min-width="168" title="绉掓潃浠锋牸(鍏�)">
- <template #default="{ row: sku }">
- <InputNumber
- v-model:value="sku.productConfig.seckillPrice"
- :min="0"
- :precision="2"
- :step="0.1"
- class="w-full"
- />
- </template>
- </VxeColumn>
- </template>
- </SpuAndSkuList>
- </div>
- </template>
- </Form>
- </Modal>
-
- <!-- 鍟嗗搧閫夋嫨鍣ㄥ脊绐� -->
- <SpuSkuSelect
- ref="spuSkuSelectRef"
- :is-select-sku="true"
- @select="handleSpuSelected"
- />
- </div>
-</template>
diff --git a/src/views/mall/promotion/seckill/components/index.ts b/src/views/mall/promotion/seckill/components/index.ts
deleted file mode 100644
index dd45216..0000000
--- a/src/views/mall/promotion/seckill/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as SeckillShowcase } from './showcase.vue';
diff --git a/src/views/mall/promotion/seckill/components/showcase.vue b/src/views/mall/promotion/seckill/components/showcase.vue
deleted file mode 100644
index 6f17736..0000000
--- a/src/views/mall/promotion/seckill/components/showcase.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<!-- 绉掓潃娲诲姩姗辩獥缁勪欢锛氱敤浜庡睍绀哄拰閫夋嫨绉掓潃娲诲姩 -->
-<script lang="ts" setup>
-import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckillActivity';
-
-import { computed, ref, watch } from 'vue';
-
-import { IconifyIcon } from '../../../../../packages/icons/src';
-
-import { Image, Tooltip } from 'ant-design-vue';
-
-import { getSeckillActivityListByIds } from '#/api/mall/promotion/seckill/seckillActivity';
-
-import SeckillTableSelect from './table-select.vue';
-
-interface SeckillShowcaseProps {
- modelValue?: number | number[];
- limit?: number;
- disabled?: boolean;
-}
-
-const props = withDefaults(defineProps<SeckillShowcaseProps>(), {
- modelValue: undefined,
- limit: Number.MAX_VALUE,
- disabled: false,
-});
-
-const emit = defineEmits(['update:modelValue', 'change']);
-
-const activityList = ref<MallSeckillActivityApi.SeckillActivity[]>([]); // 宸查�夋嫨鐨勬椿鍔ㄥ垪琛�
-const seckillTableSelectRef = ref<InstanceType<typeof SeckillTableSelect>>(); // 娲诲姩閫夋嫨琛ㄦ牸缁勪欢寮曠敤
-const isMultiple = computed(() => props.limit !== 1); // 鏄惁涓哄閫夋ā寮�
-
-/** 璁$畻鏄惁鍙互娣诲姞 */
-const canAdd = computed(() => {
- if (props.disabled) {
- return false;
- }
- if (!props.limit) {
- return true;
- }
- return activityList.value.length < props.limit;
-});
-
-/** 鐩戝惉 modelValue 鍙樺寲锛屽姞杞芥椿鍔ㄨ鎯� */
-watch(
- () => props.modelValue,
- async (newValue) => {
- // eslint-disable-next-line unicorn/no-nested-ternary
- const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
- if (ids.length === 0) {
- activityList.value = [];
- return;
- }
- // 鍙湁娲诲姩鍙戠敓鍙樺寲鏃舵墠閲嶆柊鏌ヨ
- if (
- activityList.value.length === 0 ||
- activityList.value.some((activity) => !ids.includes(activity.id!))
- ) {
- activityList.value = await getSeckillActivityListByIds(ids as number[]);
- }
- },
- { immediate: true },
-);
-
-/** 鎵撳紑娲诲姩閫夋嫨瀵硅瘽妗� */
-function handleOpenActivitySelect() {
- seckillTableSelectRef.value?.open(activityList.value);
-}
-
-/** 閫夋嫨娲诲姩鍚庤Е鍙� */
-function handleActivitySelected(
- activities:
- | MallSeckillActivityApi.SeckillActivity
- | MallSeckillActivityApi.SeckillActivity[],
-) {
- activityList.value = Array.isArray(activities) ? activities : [activities];
- emitActivityChange();
-}
-
-/** 鍒犻櫎娲诲姩 */
-function handleRemoveActivity(index: number) {
- activityList.value.splice(index, 1);
- emitActivityChange();
-}
-
-/** 瑙﹀彂鍙樻洿浜嬩欢 */
-function emitActivityChange() {
- if (props.limit === 1) {
- const activity =
- activityList.value.length > 0 ? activityList.value[0] : null;
- emit('update:modelValue', activity?.id || 0);
- emit('change', activity);
- } else {
- emit(
- 'update:modelValue',
- activityList.value.map((activity) => activity.id!),
- );
- emit('change', activityList.value);
- }
-}
-</script>
-
-<template>
- <div class="flex flex-wrap items-center gap-2">
- <!-- 宸查�夋椿鍔ㄥ垪琛� -->
- <div
- v-for="(activity, index) in activityList"
- :key="activity.id"
- class="relative h-[60px] w-[60px] overflow-hidden rounded-lg border border-dashed border-gray-300"
- >
- <Tooltip :title="activity.name">
- <div class="relative h-full w-full">
- <Image
- :preview="true"
- :src="activity.picUrl"
- class="h-full w-full rounded-lg object-cover"
- />
- <!-- 鍒犻櫎鎸夐挳 -->
- <!-- TODO @鑺嬭壙锛氱瓑寰呭拰 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue 杩涗竴姝ョ粺涓� -->
- <IconifyIcon
- v-if="!disabled"
- icon="lucide:x"
- class="absolute -right-2 -top-2 z-10 h-5 w-5 cursor-pointer text-red-500 hover:text-red-600"
- @click="handleRemoveActivity(index)"
- />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娣诲姞娲诲姩鎸夐挳 -->
- <Tooltip v-if="canAdd" title="閫夋嫨娲诲姩">
- <div
- class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border border-dashed border-gray-300 hover:border-blue-400"
- @click="handleOpenActivitySelect"
- >
- <IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
- </div>
- </Tooltip>
- </div>
-
- <!-- 娲诲姩閫夋嫨瀵硅瘽妗� -->
- <SeckillTableSelect
- ref="seckillTableSelectRef"
- :multiple="isMultiple"
- @change="handleActivitySelected"
- />
-</template>
diff --git a/src/views/mall/promotion/seckill/components/table-select.vue b/src/views/mall/promotion/seckill/components/table-select.vue
deleted file mode 100644
index 5c953b5..0000000
--- a/src/views/mall/promotion/seckill/components/table-select.vue
+++ /dev/null
@@ -1,277 +0,0 @@
-<!-- 绉掓潃娲诲姩閫夋嫨寮圭獥缁勪欢 -->
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps } from '#/adapter/vxe-table';
-import type { MallCategoryApi } from '#/api/mall/product/category';
-import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckillActivity';
-
-import { computed, onMounted, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { fenToYuan, formatDate, handleTree } from '../../../../../packages/utils/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getCategoryList } from '#/api/mall/product/category';
-import { getSeckillActivityPage } from '#/api/mall/promotion/seckill/seckillActivity';
-
-interface SeckillTableSelectProps {
- multiple?: boolean; // 鏄惁澶氶�夛細true - checkbox锛沠alse - radio
-}
-
-const props = withDefaults(defineProps<SeckillTableSelectProps>(), {
- multiple: false,
-});
-
-const emit = defineEmits<{
- change: [
- activity:
- | MallSeckillActivityApi.SeckillActivity
- | MallSeckillActivityApi.SeckillActivity[],
- ];
-}>();
-
-const categoryList = ref<MallCategoryApi.Category[]>([]); // 鍒嗙被鍒楄〃
-const categoryTreeList = ref<any[]>([]); // 鍒嗙被鏍�
-
-/** 鍗曢�夛細澶勭悊閫変腑鍙樺寲 */
-function handleRadioChange() {
- const selectedRow =
- gridApi.grid.getRadioRecord() as MallSeckillActivityApi.SeckillActivity;
- if (selectedRow) {
- emit('change', selectedRow);
- modalApi.close();
- }
-}
-
-/**
- * 鏍煎紡鍖栫鏉�浠锋牸
- * @param products
- */
-const formatSeckillPrice = (
- products: MallSeckillActivityApi.SeckillProduct[],
-) => {
- if (!products || products.length === 0) return '-';
- const seckillPrice = Math.min(
- ...products.map((item) => item.seckillPrice || 0),
- );
- return `锟�${fenToYuan(seckillPrice)}`;
-};
-
-/** 鎼滅储琛ㄥ崟 Schema */
-const formSchema = computed<VbenFormSchema[]>(() => [
- {
- fieldName: 'name',
- label: '娲诲姩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ椿鍔ㄥ悕绉�',
- clearable: true,
- },
- },
- {
- fieldName: 'status',
- label: '娲诲姩鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娲诲姩鐘舵��',
- clearable: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
-]);
-
-/** 琛ㄦ牸鍒楅厤缃� */
-const gridColumns = computed<VxeGridProps['columns']>(() => {
- const columns: VxeGridProps['columns'] = [];
- if (props.multiple) {
- columns.push({ type: 'checkbox', width: 55 });
- } else {
- columns.push({ type: 'radio', width: 55 });
- }
- columns.push(
- {
- field: 'id',
- title: '娲诲姩缂栧彿',
- minWidth: 80,
- align: 'center',
- },
- {
- field: 'name',
- title: '娲诲姩鍚嶇О',
- minWidth: 140,
- },
- {
- field: 'activityTime',
- title: '娲诲姩鏃堕棿',
- minWidth: 210,
- formatter: ({ row }) => {
- return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`;
- },
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- width: 100,
- align: 'center',
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鏍囬',
- minWidth: 300,
- },
- {
- field: 'marketPrice',
- title: '鍘熶环',
- minWidth: 100,
- align: 'center',
- formatter: ({ cellValue }) => {
- return cellValue ? `锟�${fenToYuan(cellValue)}` : '-';
- },
- },
- {
- field: 'products',
- title: '绉掓潃浠�',
- minWidth: 100,
- align: 'center',
- formatter: ({ cellValue }) => {
- return formatSeckillPrice(cellValue);
- },
- },
- {
- field: 'status',
- title: '娲诲姩鐘舵��',
- minWidth: 100,
- align: 'center',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- align: 'center',
- cellRender: {
- name: 'CellDatetime',
- },
- },
- );
- return columns;
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema.value,
- layout: 'horizontal',
- collapsed: false,
- },
- gridOptions: {
- columns: gridColumns.value,
- height: 500,
- border: true,
- checkboxConfig: {
- reserve: true,
- },
- radioConfig: {
- reserve: true,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- proxyConfig: {
- ajax: {
- async query({ page }: any, formValues: any) {
- return await getSeckillActivityPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- },
- gridEvents: {
- radioChange: handleRadioChange,
- },
-});
-
-const [Modal, modalApi] = useVbenModal({
- destroyOnClose: true,
- showConfirmButton: props.multiple, // 鐗规畩锛歳adio 鍗曢�夋儏鍐典笅锛岃蛋 handleRadioChange 澶勭悊銆�
- onConfirm: () => {
- const selectedRows =
- gridApi.grid.getCheckboxRecords() as MallSeckillActivityApi.SeckillActivity[];
- emit('change', selectedRows);
- modalApi.close();
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- await gridApi.grid.clearCheckboxRow();
- await gridApi.grid.clearRadioRow();
- return;
- }
-
- // 1. 鍏堟煡璇㈡暟鎹�
- await gridApi.query();
- // 2. 璁剧疆宸查�変腑琛�
- const data = modalApi.getData<
- | MallSeckillActivityApi.SeckillActivity
- | MallSeckillActivityApi.SeckillActivity[]
- >();
- if (props.multiple && Array.isArray(data) && data.length > 0) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData().fullData;
- data.forEach((activity) => {
- const row = tableData.find(
- (item: MallSeckillActivityApi.SeckillActivity) =>
- item.id === activity.id,
- );
- if (row) {
- gridApi.grid.setCheckboxRow(row, true);
- }
- });
- }, 300);
- } else if (!props.multiple && data && !Array.isArray(data)) {
- setTimeout(() => {
- const tableData = gridApi.grid.getTableData().fullData;
- const row = tableData.find(
- (item: MallSeckillActivityApi.SeckillActivity) => item.id === data.id,
- );
- if (row) {
- gridApi.grid.setRadioRow(row);
- }
- }, 300);
- }
- },
-});
-
-/** 瀵瑰鏆撮湶鐨勬柟娉� */
-defineExpose({
- open: (
- data?:
- | MallSeckillActivityApi.SeckillActivity
- | MallSeckillActivityApi.SeckillActivity[],
- ) => {
- modalApi.setData(data).open();
- },
-});
-
-/** 鍒濆鍖栧垎绫绘暟鎹� */
-onMounted(async () => {
- categoryList.value = await getCategoryList({});
- categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId');
-});
-</script>
-
-<template>
- <Modal title="閫夋嫨娲诲姩" class="w-[950px]">
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/promotion/seckill/config/data.ts b/src/views/mall/promotion/seckill/config/data.ts
deleted file mode 100644
index 48110f3..0000000
--- a/src/views/mall/promotion/seckill/config/data.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallSeckillConfigApi } from '#/api/mall/promotion/seckill/seckillConfig';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '绉掓潃鏃舵鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ鏉�鏃舵鍚嶇О',
- },
- rules: 'required',
- },
- {
- fieldName: 'startTime',
- label: '寮�濮嬫椂闂寸偣',
- component: 'TimePicker',
- componentProps: {
- format: 'HH:mm',
- valueFormat: 'HH:mm',
- placeholder: '璇烽�夋嫨寮�濮嬫椂闂寸偣',
- },
- rules: 'required',
- },
- {
- fieldName: 'endTime',
- label: '缁撴潫鏃堕棿鐐�',
- component: 'TimePicker',
- componentProps: {
- format: 'HH:mm',
- valueFormat: 'HH:mm',
- placeholder: '璇烽�夋嫨缁撴潫鏃堕棿鐐�',
- },
- rules: 'required',
- },
- {
- fieldName: 'sliderPicUrls',
- label: '绉掓潃杞挱鍥�',
- component: 'ImageUpload',
- componentProps: {
- multiple: true,
- maxNumber: 5,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '绉掓潃鏃舵鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ鏉�鏃舵鍚嶇О',
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(
- onStatusChange?: (
- newStatus: number,
- row: MallSeckillConfigApi.SeckillConfig,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- title: '绉掓潃鏃舵鍚嶇О',
- field: 'name',
- minWidth: 200,
- },
- {
- title: '寮�濮嬫椂闂寸偣',
- field: 'startTime',
- minWidth: 120,
- },
- {
- title: '缁撴潫鏃堕棿鐐�',
- field: 'endTime',
- minWidth: 120,
- },
- {
- title: '绉掓潃杞挱鍥�',
- field: 'sliderPicUrls',
- minWidth: 100,
- cellRender: {
- name: 'CellImages',
- },
- },
- {
- title: '娲诲姩鐘舵��',
- field: 'status',
- minWidth: 100,
- cellRender: {
- attrs: { beforeChange: onStatusChange },
- name: 'CellSwitch',
- props: {
- checkedValue: 1,
- checkedChildren: '鍚敤',
- unCheckedValue: 0,
- unCheckedChildren: '绂佺敤',
- },
- },
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/promotion/seckill/config/index.vue b/src/views/mall/promotion/seckill/config/index.vue
deleted file mode 100644
index 1a14dfc..0000000
--- a/src/views/mall/promotion/seckill/config/index.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallSeckillConfigApi } from '#/api/mall/promotion/seckill/seckillConfig';
-
-import { confirm, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteSeckillConfig,
- getSeckillConfigPage,
- updateSeckillConfigStatus,
-} from '#/api/mall/promotion/seckill/seckillConfig';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓绉掓潃鏃舵 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫绉掓潃鏃舵 */
-function handleEdit(row: MallSeckillConfigApi.SeckillConfig) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎绉掓潃鏃舵 */
-async function handleDelete(row: MallSeckillConfigApi.SeckillConfig) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteSeckillConfig(row.id as number);
- message.success({
- content: $t('ui.actionMessage.deleteSuccess', [row.name]),
- });
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 淇敼鐘舵�� */
-async function handleStatusChange(
- newStatus: number,
- row: MallSeckillConfigApi.SeckillConfig,
-): Promise<boolean | undefined> {
- // 浜屾纭
- const text = row.status === 0 ? '鍚敤' : '鍋滅敤';
- try {
- await confirm(`纭瑕�${text + row.name}鍚�?`);
- } catch {
- return false;
- }
- // 鏇存柊鐘舵��
- await updateSeckillConfigStatus(row.id, newStatus);
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success(`${text}鎴愬姛`);
- return true;
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleStatusChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getSeckillConfigPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallSeckillConfigApi.SeckillConfig>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="绉掓潃鏃舵鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绉掓潃鏃舵']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['promotion:seckill-config:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['promotion:seckill-config:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:seckill-config:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/promotion/seckill/config/modules/form.vue b/src/views/mall/promotion/seckill/config/modules/form.vue
deleted file mode 100644
index 208a320..0000000
--- a/src/views/mall/promotion/seckill/config/modules/form.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<script lang="ts" setup>
-import type { MallSeckillConfigApi } from '#/api/mall/promotion/seckill/seckillConfig';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createSeckillConfig,
- getSeckillConfig,
- updateSeckillConfig,
-} from '#/api/mall/promotion/seckill/seckillConfig';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallSeckillConfigApi.SeckillConfig>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绉掓潃鏃舵'])
- : $t('ui.actionTitle.create', ['绉掓潃鏃舵']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallSeckillConfigApi.SeckillConfig;
- try {
- await (formData.value?.id
- ? updateSeckillConfig(data)
- : createSeckillConfig(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallSeckillConfigApi.SeckillConfig>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getSeckillConfig(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/statistics/member/index.vue b/src/views/mall/statistics/member/index.vue
deleted file mode 100644
index 4a91f61..0000000
--- a/src/views/mall/statistics/member/index.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<script lang="ts" setup>
-import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
-
-import { onMounted, ref } from 'vue';
-
-import { DocAlert, Page, SummaryCard } from '../../../../packages/effects/common-ui/src';
-import { fenToYuan } from '../../../../packages/utils/src';
-
-import { Col, Row } from 'ant-design-vue';
-
-import { getMemberSummary } from '#/api/mall/statistics/member';
-
-import MemberAreaCard from './modules/area-card.vue';
-import MemberFunnelCard from './modules/funnel-card.vue';
-import MemberSexCard from './modules/sex-card.vue';
-import MemberTerminalCard from './modules/terminal-card.vue';
-
-/** 浼氬憳缁熻 */
-defineOptions({ name: 'MemberStatistics' });
-
-const loading = ref(true); // 鍔犺浇涓�
-const summary = ref<MallMemberStatisticsApi.SummaryRespVO>(); // 浼氬憳缁熻鏁版嵁
-
-/** 鏌ヨ浼氬憳缁熻 */
-async function loadMemberSummary() {
- summary.value = await getMemberSummary();
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- loading.value = true;
- try {
- await loadMemberSummary();
- } finally {
- loading.value = false;
- }
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮粺璁°�戜細鍛樸�佸晢鍝併�佷氦鏄撶粺璁�"
- url="https://doc.iocoder.cn/mall/statistics/"
- />
- </template>
-
- <div class="flex flex-col gap-4">
- <!-- 缁熻鍗$墖 -->
- <Row :gutter="16">
- <Col :md="6" :sm="12" :xs="24">
- <SummaryCard
- title="绱浼氬憳鏁�"
- :value="summary?.userCount || 0"
- icon="fa-solid:users"
- icon-color="text-blue-500"
- icon-bg-color="bg-blue-100"
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <SummaryCard
- title="绱鍏呭�间汉鏁�"
- :value="summary?.rechargeUserCount || 0"
- icon="fa-solid:user"
- icon-color="text-purple-500"
- icon-bg-color="bg-purple-100"
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <SummaryCard
- title="绱鍏呭�奸噾棰�"
- :value="Number(fenToYuan(summary?.rechargePrice || 0))"
- :decimals="2"
- prefix="锟�"
- icon="fa-solid:money-check-alt"
- icon-color="text-yellow-500"
- icon-bg-color="bg-yellow-100"
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24">
- <SummaryCard
- title="绱娑堣垂閲戦"
- :value="Number(fenToYuan(summary?.expensePrice || 0))"
- :decimals="2"
- prefix="锟�"
- icon="fa-solid:yen-sign"
- icon-color="text-green-500"
- icon-bg-color="bg-green-100"
- />
- </Col>
- </Row>
- <!-- 浼氬憳姒傝鍜屼細鍛樼粓绔� -->
- <Row :gutter="16">
- <Col :md="18" :sm="24" :xs="24">
- <MemberFunnelCard />
- </Col>
- <Col :md="6" :sm="24" :xs="24">
- <MemberTerminalCard />
- </Col>
- </Row>
- <!-- 浼氬憳鍦板煙鍒嗗竷鍜屾�у埆姣斾緥 -->
- <Row :gutter="16">
- <Col :md="18" :sm="24" :xs="24">
- <MemberAreaCard />
- </Col>
- <Col :md="6" :sm="24" :xs="24">
- <MemberSexCard />
- </Col>
- </Row>
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/statistics/member/modules/area-card.vue b/src/views/mall/statistics/member/modules/area-card.vue
deleted file mode 100644
index fe42d01..0000000
--- a/src/views/mall/statistics/member/modules/area-card.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
-
-import { onMounted, ref, shallowRef } from 'vue';
-
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Spin } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getMemberAreaStatisticsList } from '#/api/mall/statistics/member';
-
-import { getAreaChartOptions, getAreaTableColumns } from './area-chart-options';
-
-/** 浼氬憳鍦板煙鍒嗗竷鍗$墖 */
-defineOptions({ name: 'MemberAreaCard' });
-
-const loading = ref(true);
-const areaStatisticsList = shallowRef<
- MallMemberStatisticsApi.AreaStatisticsRespVO[]
->([]);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: getAreaTableColumns(),
- height: 300,
- border: true,
- showOverflow: true,
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- enabled: false,
- },
- },
-});
-
-/** 鎸夌収鐪佷唤锛屾煡璇細鍛樼粺璁″垪琛� */
-async function loadMemberAreaStatisticsList() {
- loading.value = true;
- try {
- const list = await getMemberAreaStatisticsList();
- areaStatisticsList.value = list.map(
- (item: MallMemberStatisticsApi.AreaStatisticsRespVO) => ({
- ...item,
- areaName: areaReplace(item.areaName),
- }),
- );
- // 娓叉煋鍥捐〃
- const chartOptions = getAreaChartOptions(areaStatisticsList.value);
- await renderEcharts(chartOptions);
-
- // 鍔犺浇琛ㄦ牸鏁版嵁
- await gridApi.grid.loadData(areaStatisticsList.value);
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍩庡競鍚嶅吋瀹癸細ECharts 鍦板悕瀛樺湪宸紓 */
-function areaReplace(areaName: string): string {
- if (!areaName) {
- return areaName;
- }
- return areaName
- .replace('缁村惥灏旇嚜娌诲尯', '')
- .replace('澹棌鑷不鍖�', '')
- .replace('鍥炴棌鑷不鍖�', '')
- .replace('鑷不鍖�', '')
- .replace('鐪�', '');
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadMemberAreaStatisticsList();
-});
-</script>
-
-<template>
- <Card :bordered="false" title="浼氬憳鍦板煙鍒嗗竷" class="h-full">
- <Spin :spinning="loading">
- <div class="flex gap-4">
- <div class="w-2/5">
- <EchartsUI ref="chartRef" />
- </div>
- <div class="w-3/5">
- <Grid />
- </div>
- </div>
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/member/modules/area-chart-options.ts b/src/views/mall/statistics/member/modules/area-chart-options.ts
deleted file mode 100644
index 0dd0714..0000000
--- a/src/views/mall/statistics/member/modules/area-chart-options.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
-
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-/** 浼氬憳鍦板煙鍒嗗竷鍥捐〃閰嶇疆 */
-export function getAreaChartOptions(
- data: MallMemberStatisticsApi.AreaStatisticsRespVO[],
-): any {
- if (!data || data.length === 0) {
- return {
- title: {
- text: '鏆傛棤鏁版嵁',
- left: 'center',
- top: 'center',
- textStyle: {
- color: '#999',
- fontSize: 14,
- },
- },
- };
- }
-
- // 璁$畻 min 鍜� max 鍊�
- let min = Number.POSITIVE_INFINITY;
- let max = Number.NEGATIVE_INFINITY;
- const mapData = data.map((item) => {
- const payUserCount = item.orderPayUserCount || 0;
- min = Math.min(min, payUserCount);
- max = Math.max(max, payUserCount);
- return {
- ...item,
- name: item.areaName,
- value: payUserCount,
- };
- });
- // 濡傛灉鎵�鏈夊�奸兘涓� 0锛岃缃悎鐞嗙殑 min 鍜� max 鍊�
- if (min === max && min === 0) {
- min = 0;
- max = 10;
- }
-
- // 杩斿洖鍥捐〃閰嶇疆
- return {
- tooltip: {
- trigger: 'item',
- formatter: (params: any) => {
- const itemData = params?.data;
- if (!itemData) {
- return `${params?.name || ''}<br/>鏆傛棤鏁版嵁`;
- }
- return `${itemData.areaName || params.name}<br/>
-浼氬憳鏁伴噺锛�${itemData.userCount || 0}<br/>
-璁㈠崟鍒涘缓鏁伴噺锛�${itemData.orderCreateUserCount || 0}<br/>
-璁㈠崟鏀粯鏁伴噺锛�${itemData.orderPayUserCount || 0}<br/>
-璁㈠崟鏀粯閲戦锛氾骏${Number(fenToYuan(itemData.orderPayPrice || 0)).toFixed(2)}`;
- },
- },
- visualMap: {
- text: ['楂�', '浣�'],
- realtime: false,
- calculable: true,
- top: 'middle',
- left: 10,
- min,
- max,
- inRange: {
- color: ['#e6f3ff', '#1890ff', '#0050b3'],
- },
- },
- series: [
- {
- name: '浼氬憳鍦板煙鍒嗗竷',
- type: 'map',
- map: 'china',
- roam: false,
- selectedMode: false,
- itemStyle: {
- borderColor: '#389e0d',
- borderWidth: 0.5,
- },
- emphasis: {
- itemStyle: {
- areaColor: '#ffec3d',
- borderWidth: 1,
- },
- },
- data: mapData,
- },
- ],
- };
-}
-
-/** VXE Grid 琛ㄦ牸鍒楅厤缃� */
-export function getAreaTableColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'areaName',
- title: '鐪佷唤',
- minWidth: 80,
- sortable: true,
- showOverflow: 'tooltip',
- },
- {
- field: 'userCount',
- title: '浼氬憳鏁伴噺',
- minWidth: 100,
- sortable: true,
- },
- {
- field: 'orderCreateUserCount',
- title: '璁㈠崟鍒涘缓鏁伴噺',
- minWidth: 120,
- sortable: true,
- },
- {
- field: 'orderPayUserCount',
- title: '璁㈠崟鏀粯鏁伴噺',
- minWidth: 120,
- sortable: true,
- },
- {
- field: 'orderPayPrice',
- title: '璁㈠崟鏀粯閲戦',
- minWidth: 120,
- sortable: true,
- formatter: 'formatFenToYuanAmount',
- },
- ];
-}
diff --git a/src/views/mall/statistics/member/modules/funnel-card.vue b/src/views/mall/statistics/member/modules/funnel-card.vue
deleted file mode 100644
index 89345e7..0000000
--- a/src/views/mall/statistics/member/modules/funnel-card.vue
+++ /dev/null
@@ -1,147 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import { ref } from 'vue';
-
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { Card } from 'ant-design-vue';
-
-import { getMemberAnalyse } from '#/api/mall/statistics/member';
-import { ShortcutDateRangePicker } from '#/components/shortcut-date-range-picker';
-
-/** 浼氬憳姒傝鍗$墖 */
-defineOptions({ name: 'MemberFunnelCard' });
-
-const loading = ref(false);
-const analyseData = ref<any>();
-
-/** 鏌ヨ浼氬憳姒傝鏁版嵁鍒楄〃 */
-async function loadData(times: [Dayjs, Dayjs]) {
- if (!times || times.length !== 2) {
- return;
- }
- loading.value = true;
- try {
- analyseData.value = await getMemberAnalyse({
- times,
- });
- } finally {
- loading.value = false;
- }
-}
-
-/** 鏃堕棿鑼冨洿鏀瑰彉 */
-const handleTimeRangeChange = (times: [Dayjs, Dayjs]) => {
- loadData(times);
-};
-
-/** 璁$畻鐜瘮澧為暱鐜� */
-const calculateRelativeRate = (value?: number, reference?: number) => {
- if (!reference || reference === 0) {
- return 0;
- }
- return (((value || 0) - reference) / reference) * 100;
-};
-</script>
-
-<template>
- <Card :bordered="false" :loading="loading">
- <template #title>
- <div class="flex items-center justify-between">
- <span>浼氬憳姒傝</span>
- <ShortcutDateRangePicker @change="handleTimeRangeChange" />
- </div>
- </template>
- <div class="min-w-[900px] py-4">
- <div class="flex h-24">
- <div class="flex w-[75%] bg-blue-50">
- <div class="ml-[50px] flex flex-col justify-center">
- <div class="font-bold">
- 娉ㄥ唽鐢ㄦ埛鏁伴噺锛�
- {{ analyseData?.comparison?.value?.registerUserCount || 0 }}
- </div>
- <div class="mt-2 text-sm">
- 鐜瘮澧為暱鐜囷細
- {{
- calculateRelativeRate(
- analyseData?.comparison?.value?.registerUserCount,
- analyseData?.comparison?.reference?.registerUserCount,
- ).toFixed(2)
- }}%
- </div>
- </div>
- </div>
- <div
- class="-ml-[154px] mt-1.5 flex w-[308px] flex-col items-center justify-center bg-blue-500 text-sm text-white [transform:perspective(5em)_rotateX(-11deg)]"
- >
- <span class="text-2xl font-bold">
- {{ analyseData?.visitUserCount || 0 }}
- </span>
- <span>璁垮</span>
- </div>
- </div>
- <div class="flex h-24">
- <div class="flex w-[75%] bg-cyan-50">
- <div class="ml-[50px] flex flex-col justify-center">
- <div class="font-bold">
- 娲昏穬鐢ㄦ埛鏁伴噺锛�
- {{ analyseData?.comparison?.value?.visitUserCount || 0 }}
- </div>
- <div class="mt-2 text-sm">
- 鐜瘮澧為暱鐜囷細
- {{
- calculateRelativeRate(
- analyseData?.comparison?.value?.visitUserCount,
- analyseData?.comparison?.reference?.visitUserCount,
- ).toFixed(2)
- }}%
- </div>
- </div>
- </div>
- <div
- class="-ml-[112px] mt-[6.8px] flex h-[100px] w-[224px] flex-col items-center justify-center bg-cyan-500 text-sm text-white [transform:perspective(7em)_rotateX(-20deg)]"
- >
- <span class="text-2xl font-bold">
- {{ analyseData?.orderUserCount || 0 }}
- </span>
- <span>涓嬪崟</span>
- </div>
- </div>
- <div class="flex h-24">
- <div class="flex w-[75%] bg-slate-50">
- <div class="ml-[50px] flex flex-row gap-x-16">
- <div class="flex flex-col justify-center">
- <div class="font-bold">
- 鍏呭�肩敤鎴锋暟閲忥細
- {{ analyseData?.comparison?.value?.rechargeUserCount || 0 }}
- </div>
- <div class="mt-2 text-sm">
- 鐜瘮澧為暱鐜囷細
- {{
- calculateRelativeRate(
- analyseData?.comparison?.value?.rechargeUserCount,
- analyseData?.comparison?.reference?.rechargeUserCount,
- ).toFixed(2)
- }}%
- </div>
- </div>
- <div class="flex flex-col justify-center">
- <div class="font-bold">
- 瀹㈠崟浠凤細{{ fenToYuan(analyseData?.atv || 0) }}
- </div>
- </div>
- </div>
- </div>
- <div
- class="-ml-[72px] mt-[13px] flex h-[92px] w-[144px] flex-col items-center justify-center bg-slate-500 text-sm text-white [transform:perspective(3em)_rotateX(-13deg)]"
- >
- <span class="text-2xl font-bold">
- {{ analyseData?.payUserCount || 0 }}
- </span>
- <span>鎴愪氦鐢ㄦ埛</span>
- </div>
- </div>
- </div>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/member/modules/sex-card.vue b/src/views/mall/statistics/member/modules/sex-card.vue
deleted file mode 100644
index b07d4bf..0000000
--- a/src/views/mall/statistics/member/modules/sex-card.vue
+++ /dev/null
@@ -1,62 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member';
-
-import { onMounted, ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Spin } from 'ant-design-vue';
-
-import { getMemberSexStatisticsList } from '#/api/mall/statistics/member';
-
-import { getSexChartOptions } from './sex-chart-options';
-
-/** 浼氬憳鎬у埆姣斾緥鍗$墖 */
-defineOptions({ name: 'MemberSexCard' });
-
-const loading = ref(true);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 鎸夌収鎬у埆锛屾煡璇細鍛樼粺璁″垪琛� */
-async function loadMemberSexStatisticsList() {
- loading.value = true;
- try {
- const list = await getMemberSexStatisticsList();
- const dictDataList = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number');
- dictDataList.push({ label: '鏈煡', value: null } as any);
- const chartData = dictDataList.map((dictData: any) => {
- const userCount = list.find(
- (item: MallMemberStatisticsApi.SexStatisticsRespVO) =>
- item.sex === dictData.value,
- )?.userCount;
- return {
- name: dictData.label,
- value: userCount || 0,
- };
- });
-
- // 娓叉煋鍥捐〃
- await renderEcharts(getSexChartOptions(chartData));
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadMemberSexStatisticsList();
-});
-</script>
-
-<template>
- <Card :bordered="false" title="浼氬憳鎬у埆姣斾緥" class="h-full">
- <Spin :spinning="loading">
- <EchartsUI ref="chartRef" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/member/modules/sex-chart-options.ts b/src/views/mall/statistics/member/modules/sex-chart-options.ts
deleted file mode 100644
index 81a7faf..0000000
--- a/src/views/mall/statistics/member/modules/sex-chart-options.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/** 浼氬憳鎬у埆姣斾緥鍥捐〃閰嶇疆 */
-export function getSexChartOptions(data: any[]): any {
- return {
- tooltip: {
- trigger: 'item',
- confine: true,
- formatter: '{a} <br/>{b} : {c} ({d}%)',
- },
- legend: {
- orient: 'vertical',
- left: 'right',
- },
- series: [
- {
- name: '浼氬憳鎬у埆',
- type: 'pie',
- roseType: 'area',
- label: {
- show: false,
- },
- labelLine: {
- show: false,
- },
- data,
- },
- ],
- };
-}
diff --git a/src/views/mall/statistics/member/modules/terminal-card.vue b/src/views/mall/statistics/member/modules/terminal-card.vue
deleted file mode 100644
index 9a43889..0000000
--- a/src/views/mall/statistics/member/modules/terminal-card.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { onMounted, ref } from 'vue';
-
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-
-import { Card, Spin } from 'ant-design-vue';
-
-import { getMemberTerminalStatisticsList } from '#/api/mall/statistics/member';
-
-import { getTerminalChartOptions } from './terminal-chart-options';
-
-/** 浼氬憳缁堢鍗$墖 */
-defineOptions({ name: 'MemberTerminalCard' });
-
-const loading = ref(true);
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 鎸夌収缁堢锛屾煡璇細鍛樼粺璁″垪琛� */
-const loadMemberTerminalStatisticsList = async () => {
- loading.value = true;
- try {
- const list = await getMemberTerminalStatisticsList();
- const dictDataList = getDictOptions('terminal', 'number');
- const chartData = dictDataList.map((dictData: any) => {
- const userCount = list.find(
- (item: any) => item.terminal === dictData.value,
- )?.userCount;
- return {
- name: dictData.label,
- value: userCount || 0,
- };
- });
- // 鏇存柊 Echarts 鏁版嵁
- await renderEcharts(getTerminalChartOptions(chartData));
- } finally {
- loading.value = false;
- }
-};
-
-/** 鍒濆鍖� */
-onMounted(() => {
- loadMemberTerminalStatisticsList();
-});
-</script>
-
-<template>
- <Card :bordered="false" title="浼氬憳缁堢" class="h-full">
- <Spin :spinning="loading">
- <EchartsUI ref="chartRef" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/member/modules/terminal-chart-options.ts b/src/views/mall/statistics/member/modules/terminal-chart-options.ts
deleted file mode 100644
index c3233bd..0000000
--- a/src/views/mall/statistics/member/modules/terminal-chart-options.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/** 浼氬憳缁堢缁熻鍥鹃厤缃� */
-export function getTerminalChartOptions(data: any[]): any {
- return {
- tooltip: {
- trigger: 'item',
- confine: true,
- formatter: '{a} <br/>{b} : {c} ({d}%)',
- },
- legend: {
- orient: 'vertical',
- left: 'right',
- },
- series: [
- {
- name: '浼氬憳缁堢',
- type: 'pie',
- label: {
- show: false,
- },
- labelLine: {
- show: false,
- },
- data,
- },
- ],
- };
-}
diff --git a/src/views/mall/statistics/product/index.vue b/src/views/mall/statistics/product/index.vue
deleted file mode 100644
index 52fbf72..0000000
--- a/src/views/mall/statistics/product/index.vue
+++ /dev/null
@@ -1,27 +0,0 @@
-<script lang="ts" setup>
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-
-import ProductRankCard from './modules/rank-card.vue';
-import ProductSummaryCard from './modules/summary-card.vue';
-
-/** 鍟嗗搧缁熻 */
-defineOptions({ name: 'ProductStatistics' });
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮粺璁°�戜細鍛樸�佸晢鍝併�佷氦鏄撶粺璁�"
- url="https://doc.iocoder.cn/mall/statistics/"
- />
- </template>
-
- <div class="flex flex-col gap-4">
- <!-- 鍟嗗搧姒傝 -->
- <ProductSummaryCard />
- <!-- 鍟嗗搧鎺掕 -->
- <ProductRankCard />
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/statistics/product/modules/rank-card.vue b/src/views/mall/statistics/product/modules/rank-card.vue
deleted file mode 100644
index 57a96e8..0000000
--- a/src/views/mall/statistics/product/modules/rank-card.vue
+++ /dev/null
@@ -1,135 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { ref } from 'vue';
-
-import { buildSortingField } from '../../../../../packages/effects/request/src';
-import { formatDateTime } from '../../../../../packages/utils/src';
-
-import { Card } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getProductStatisticsRankPage } from '#/api/mall/statistics/product';
-import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
-
-/** 鍟嗗搧鎺掕 */
-defineOptions({ name: 'ProductRankCard' });
-
-const searchTimes = ref<string[]>([]);
-
-/** 澶勭悊鏃ユ湡鑼冨洿鍙樺寲 */
-const handleDateRangeChange = (times?: [Dayjs, Dayjs]) => {
- if (times?.length !== 2) {
- return;
- }
- searchTimes.value = [
- formatDateTime(times[0]) as string,
- formatDateTime(times[1]) as string,
- ];
- gridApi.query();
-};
-
-const columns: VxeTableGridOptions['columns'] = [
- { field: 'spuId', title: '鍟嗗搧 ID', minWidth: 100 },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥剧墖',
- minWidth: 100,
- cellRender: { name: 'CellImage' },
- },
- {
- field: 'name',
- title: '鍟嗗搧鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'browseCount',
- title: '娴忚閲�',
- minWidth: 100,
- sortable: true,
- },
- {
- field: 'browseUserCount',
- title: '璁垮鏁�',
- minWidth: 100,
- sortable: true,
- },
- {
- field: 'cartCount',
- title: '鍔犺喘浠舵暟',
- minWidth: 110,
- sortable: true,
- },
- {
- field: 'orderCount',
- title: '涓嬪崟浠舵暟',
- minWidth: 110,
- sortable: true,
- },
- {
- field: 'orderPayCount',
- title: '鏀粯浠舵暟',
- minWidth: 110,
- sortable: true,
- },
- {
- field: 'orderPayPrice',
- title: '鏀粯閲戦锛堝厓锛�',
- minWidth: 120,
- formatter: 'formatFenToYuanAmount',
- sortable: true,
- },
- {
- field: 'favoriteCount',
- title: '鏀惰棌鏁�',
- minWidth: 100,
- sortable: true,
- },
- {
- field: 'browseConvertPercent',
- title: '璁垮-鏀粯杞寲鐜�(%)',
- minWidth: 160,
- sortable: true,
- formatter: ({ cellValue }) => `${cellValue || 0}%`,
- },
-];
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns,
- height: 400,
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page, sorts }) => {
- return await getProductStatisticsRankPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- ...buildSortingField(sorts),
- });
- },
- },
- sort: true,
- },
- sortConfig: {
- remote: true,
- multiple: false,
- },
- toolbarConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions,
-});
-</script>
-
-<template>
- <Card :bordered="false" title="鍟嗗搧鎺掕">
- <template #extra>
- <ShortcutDateRangePicker @change="handleDateRangeChange" />
- </template>
- <Grid />
- </Card>
-</template>
diff --git a/src/views/mall/statistics/product/modules/summary-card.vue b/src/views/mall/statistics/product/modules/summary-card.vue
deleted file mode 100644
index f4142d1..0000000
--- a/src/views/mall/statistics/product/modules/summary-card.vue
+++ /dev/null
@@ -1,252 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
-import type { MallProductStatisticsApi } from '#/api/mall/statistics/product';
-
-import { ref } from 'vue';
-
-import { confirm, SummaryCard } from '../../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-import {
- downloadFileFromBlobPart,
- fenToYuan,
- formatDateTime,
- isSameDay,
-} from '../../../../../packages/utils/src';
-
-import { Button, Card, Col, Row, Spin } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import {
- exportProductStatisticsExcel,
- getProductStatisticsAnalyse,
- getProductStatisticsList,
-} from '#/api/mall/statistics/product';
-import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
-import { $t } from '#/locales';
-
-import { getProductSummaryChartOptions } from './summary-chart-options';
-
-/** 鍟嗗搧姒傚喌 */
-defineOptions({ name: 'ProductSummaryCard' });
-
-const trendLoading = ref(true); // 鍟嗗搧鐘舵�佸姞杞戒腑
-const exportLoading = ref(false); // 瀵煎嚭鐨勫姞杞戒腑
-const trendSummary =
- ref<DataComparisonRespVO<MallProductStatisticsApi.ProductStatisticsRespVO>>(); // 鍟嗗搧鐘跺喌缁熻鏁版嵁
-const searchTimes = ref<string[]>([]);
-
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 璁$畻鐜瘮鐧惧垎姣� */
-const calculateRelativeRate = (value?: number, reference?: number): string => {
- const refValue = Number(reference || 0);
- const curValue = Number(value || 0);
- if (!refValue || refValue === 0) {
- return '0.00';
- }
- return (((curValue - refValue) / refValue) * 100).toFixed(2);
-};
-
-/** 澶勭悊鏃ユ湡鑼冨洿鍙樺寲 */
-const handleDateRangeChange = (times?: [Dayjs, Dayjs]) => {
- if (times?.length !== 2) {
- loadProductTrendData();
- return;
- }
- // 澶勭悊鏃堕棿: 寮�濮嬩笌鎴鍦ㄥ悓涓�澶╃殑, 鎶樼嚎鍥惧嚭涓嶆潵, 闇�瑕佸欢闀夸竴澶�
- let adjustedTimes = times;
- if (isSameDay(times[0], times[1])) {
- adjustedTimes = [dayjs(times[0]).subtract(1, 'd'), times[1]];
- }
- searchTimes.value = [
- formatDateTime(adjustedTimes[0]) as string,
- formatDateTime(adjustedTimes[1]) as string,
- ];
-
- // 鏌ヨ鏁版嵁
- loadProductTrendData();
-};
-
-/** 澶勭悊鍟嗗搧鐘跺喌鏌ヨ */
-const loadProductTrendData = async () => {
- trendLoading.value = true;
- try {
- await Promise.all([loadProductTrendSummary(), loadProductStatisticsList()]);
- } finally {
- trendLoading.value = false;
- }
-};
-
-/** 鏌ヨ鍟嗗搧鐘跺喌鏁版嵁缁熻 */
-async function loadProductTrendSummary() {
- trendSummary.value = await getProductStatisticsAnalyse({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
-}
-
-/** 鏌ヨ鍟嗗搧鐘跺喌鏁版嵁鍒楄〃 */
-async function loadProductStatisticsList() {
- const list = await getProductStatisticsList({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
-
- // 娓叉煋鍥捐〃
- await renderEcharts(getProductSummaryChartOptions(list));
-}
-
-/** 瀵煎嚭鎸夐挳鎿嶄綔 */
-async function handleExport() {
- try {
- // 瀵煎嚭鐨勪簩娆$‘璁�
- await confirm('纭瀵煎嚭鍟嗗搧鐘跺喌鏁版嵁鍚楋紵');
- // 鍙戣捣瀵煎嚭
- exportLoading.value = true;
- const data = await exportProductStatisticsExcel({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
- // 澶勭悊涓嬭浇
- downloadFileFromBlobPart({ fileName: '鍟嗗搧鐘跺喌.xlsx', source: data });
- } finally {
- exportLoading.value = false;
- }
-}
-</script>
-
-<template>
- <Card :bordered="false" title="鍟嗗搧姒傚喌" class="h-full">
- <template #extra>
- <!-- 鏌ヨ鏉′欢 -->
- <div class="flex items-center gap-2">
- <ShortcutDateRangePicker @change="handleDateRangeChange">
- <Button class="ml-4" @click="handleExport" :loading="exportLoading">
- <template #icon>
- <IconifyIcon icon="lucide:download" />
- </template>
- {{ $t('page.action.export') }}
- </Button>
- </ShortcutDateRangePicker>
- </div>
- </template>
-
- <!-- 缁熻鍊� -->
- <Row :gutter="16" class="mb-4">
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="鍟嗗搧娴忚閲�"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝鎵�鏈夊晢鍝佽鎯呴〉琚闂殑娆℃暟锛屼竴涓汉鍦ㄧ粺璁℃椂闂村唴璁块棶澶氭璁颁负澶氭"
- icon="lucide:eye"
- icon-color="text-blue-500"
- icon-bg-color="bg-blue-100"
- :decimals="0"
- :value="trendSummary?.value?.browseCount || 0"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.browseCount,
- trendSummary?.reference?.browseCount,
- )
- "
- />
- </Col>
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="鍟嗗搧璁垮鏁�"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝璁块棶浠讳綍鍟嗗搧璇︽儏椤电殑浜烘暟锛屼竴涓汉鍦ㄧ粺璁℃椂闂磋寖鍥村唴璁块棶澶氭鍙涓轰竴涓�"
- icon="lucide:users"
- icon-color="text-purple-500"
- icon-bg-color="bg-purple-100"
- :decimals="0"
- :value="trendSummary?.value?.browseUserCount || 0"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.browseUserCount,
- trendSummary?.reference?.browseUserCount,
- )
- "
- />
- </Col>
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="鏀粯浠舵暟"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝鎴愬姛浠樻璁㈠崟鐨勫晢鍝佷欢鏁颁箣鍜�"
- icon="lucide:credit-card"
- icon-color="text-yellow-500"
- icon-bg-color="bg-yellow-100"
- :decimals="0"
- :value="trendSummary?.value?.orderPayCount || 0"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.orderPayCount,
- trendSummary?.reference?.orderPayCount,
- )
- "
- />
- </Col>
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="鏀粯閲戦"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝鎴愬姛浠樻璁㈠崟鐨勫晢鍝侀噾棰濅箣鍜�"
- icon="lucide:banknote"
- icon-color="text-green-500"
- icon-bg-color="bg-green-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.orderPayPrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.orderPayPrice,
- trendSummary?.reference?.orderPayPrice,
- )
- "
- />
- </Col>
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="閫�娆句欢鏁�"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝鎴愬姛閫�娆剧殑鍟嗗搧浠舵暟涔嬪拰"
- icon="lucide:wallet"
- icon-color="text-cyan-500"
- icon-bg-color="bg-cyan-100"
- :decimals="0"
- :value="trendSummary?.value?.afterSaleCount || 0"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.afterSaleCount,
- trendSummary?.reference?.afterSaleCount,
- )
- "
- />
- </Col>
- <Col :xl="8" :md="8" :sm="24" class="mb-4">
- <SummaryCard
- title="閫�娆鹃噾棰�"
- tooltip="鍦ㄩ�夊畾鏉′欢涓嬶紝鎴愬姛閫�娆剧殑鍟嗗搧閲戦涔嬪拰"
- icon="lucide:receipt"
- icon-color="text-orange-500"
- icon-bg-color="bg-orange-100"
- prefix="锟�"
- :decimals="2"
- :value="
- Number(fenToYuan(trendSummary?.value?.afterSaleRefundPrice || 0))
- "
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.afterSaleRefundPrice,
- trendSummary?.reference?.afterSaleRefundPrice,
- )
- "
- />
- </Col>
- </Row>
-
- <!-- 鎶樼嚎鍥� -->
- <Spin :spinning="trendLoading">
- <EchartsUI ref="chartRef" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/product/modules/summary-chart-options.ts b/src/views/mall/statistics/product/modules/summary-chart-options.ts
deleted file mode 100644
index ec6e474..0000000
--- a/src/views/mall/statistics/product/modules/summary-chart-options.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-/** 鍟嗗搧缁熻鎶樼嚎鍥鹃厤缃� */
-export function getProductSummaryChartOptions(data: any[]): any {
- // 澶勭悊鏁版嵁锛氬皢閲戦浠庡垎杞崲涓哄厓
- const processedData = data.map((item) => ({
- ...item,
- orderPayPrice: Number((item.orderPayPrice / 100).toFixed(2)),
- afterSaleRefundPrice: Number((item.afterSaleRefundPrice / 100).toFixed(2)),
- }));
-
- return {
- dataset: {
- dimensions: [
- 'time',
- 'browseCount',
- 'browseUserCount',
- 'orderPayPrice',
- 'afterSaleRefundPrice',
- ],
- source: processedData,
- },
- grid: {
- left: 20,
- right: 20,
- bottom: 20,
- top: 80,
- containLabel: true,
- },
- legend: {
- top: 50,
- },
- series: [
- {
- name: '鍟嗗搧娴忚閲�',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#B37FEB' },
- },
- {
- name: '鍟嗗搧璁垮鏁�',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#FFAB2B' },
- },
- {
- name: '鏀粯閲戦',
- type: 'bar',
- smooth: true,
- yAxisIndex: 1,
- itemStyle: { color: '#1890FF' },
- },
- {
- name: '閫�娆鹃噾棰�',
- type: 'bar',
- smooth: true,
- yAxisIndex: 1,
- itemStyle: { color: '#00C050' },
- },
- ],
- toolbox: {
- feature: {
- // 鏁版嵁鍖哄煙缂╂斁
- dataZoom: {
- yAxisIndex: false, // Y杞翠笉缂╂斁
- },
- brush: {
- type: ['lineX', 'clear'], // 鍖哄煙缂╂斁鎸夐挳銆佽繕鍘熸寜閽�
- },
- saveAsImage: {
- show: true,
- name: '鍟嗗搧鐘跺喌',
- }, // 淇濆瓨涓哄浘鐗�
- },
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- },
- padding: [5, 10],
- },
- xAxis: {
- type: 'category',
- boundaryGap: true,
- axisTick: {
- show: false,
- },
- },
- yAxis: [
- {
- type: 'value',
- name: '閲戦',
- axisLine: {
- show: false,
- },
- axisTick: {
- show: false,
- },
- axisLabel: {
- color: '#7F8B9C',
- },
- splitLine: {
- show: true,
- lineStyle: {
- color: '#F5F7F9',
- },
- },
- },
- {
- type: 'value',
- name: '鏁伴噺',
- axisLine: {
- show: false,
- },
- axisTick: {
- show: false,
- },
- axisLabel: {
- color: '#7F8B9C',
- },
- splitLine: {
- show: true,
- lineStyle: {
- color: '#F5F7F9',
- },
- },
- },
- ],
- };
-}
diff --git a/src/views/mall/statistics/trade/index.vue b/src/views/mall/statistics/trade/index.vue
deleted file mode 100644
index 146c21a..0000000
--- a/src/views/mall/statistics/trade/index.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<script lang="ts" setup>
-import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
-import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
-
-import { onMounted, ref } from 'vue';
-
-import { DocAlert, Page, StatisticCard } from '../../../../packages/effects/common-ui/src';
-import { fenToYuan } from '../../../../packages/utils/src';
-
-import { Col, Row } from 'ant-design-vue';
-
-import { getTradeStatisticsSummary } from '#/api/mall/statistics/trade';
-
-import TradeTrendCard from './modules/trend-card.vue';
-
-/** 浜ゆ槗缁熻 */
-defineOptions({ name: 'TradeStatistics' });
-
-const loading = ref(true); // 鍔犺浇涓�
-const summary =
- ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeSummaryRespVO>>(); // 浜ゆ槗缁熻鏁版嵁
-
-/** 璁$畻鐜瘮鐧惧垎姣� */
-function calculateRelativeRate(value?: number, reference?: number): string {
- const refValue = Number(reference || 0);
- const curValue = Number(value || 0);
- if (!refValue || refValue === 0) {
- return '0.00';
- }
- return (((curValue - refValue) / refValue) * 100).toFixed(2);
-}
-
-/** 鏌ヨ浜ゆ槗缁熻 */
-async function loadTradeStatisticsSummary() {
- summary.value = await getTradeStatisticsSummary();
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- loading.value = true;
- await loadTradeStatisticsSummary();
- loading.value = false;
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮粺璁°�戜細鍛樸�佸晢鍝併�佷氦鏄撶粺璁�"
- url="https://doc.iocoder.cn/mall/statistics/"
- />
- </template>
-
- <!-- 浜ゆ槗姒傝鍗$墖 -->
- <div class="flex flex-col gap-4">
- <Row :gutter="16">
- <Col :sm="6" :xs="12">
- <StatisticCard
- tooltip="鏄ㄦ棩璁㈠崟鏁伴噺"
- title="鏄ㄦ棩璁㈠崟鏁伴噺"
- :value="summary?.value?.yesterdayOrderCount || 0"
- :percent="
- calculateRelativeRate(
- summary?.value?.yesterdayOrderCount,
- summary?.reference?.yesterdayOrderCount,
- )
- "
- />
- </Col>
- <Col :sm="6" :xs="12">
- <StatisticCard
- tooltip="鏈湀璁㈠崟鏁伴噺"
- title="鏈湀璁㈠崟鏁伴噺"
- :value="summary?.value?.monthOrderCount || 0"
- :percent="
- calculateRelativeRate(
- summary?.value?.monthOrderCount,
- summary?.reference?.monthOrderCount,
- )
- "
- />
- </Col>
- <Col :sm="6" :xs="12">
- <StatisticCard
- tooltip="鏄ㄦ棩鏀粯閲戦"
- title="鏄ㄦ棩鏀粯閲戦"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(summary?.value?.yesterdayPayPrice || 0))"
- :percent="
- calculateRelativeRate(
- summary?.value?.yesterdayPayPrice,
- summary?.reference?.yesterdayPayPrice,
- )
- "
- />
- </Col>
- <Col :sm="6" :xs="12">
- <StatisticCard
- tooltip="鏈湀鏀粯閲戦"
- title="鏈湀鏀粯閲戦"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(summary?.value?.monthPayPrice || 0))"
- :percent="
- calculateRelativeRate(
- summary?.value?.monthPayPrice,
- summary?.reference?.monthPayPrice,
- )
- "
- />
- </Col>
- </Row>
-
- <!-- 浜ゆ槗瓒嬪娍 -->
- <TradeTrendCard />
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/statistics/trade/modules/trend-card.vue b/src/views/mall/statistics/trade/modules/trend-card.vue
deleted file mode 100644
index 2b7b8dc..0000000
--- a/src/views/mall/statistics/trade/modules/trend-card.vue
+++ /dev/null
@@ -1,284 +0,0 @@
-<script lang="ts" setup>
-import type { Dayjs } from 'dayjs';
-
-import type { EchartsUIType } from '../../../../../packages/effects/plugins/src/echarts';
-
-import type { DataComparisonRespVO } from '#/api/mall/statistics/common';
-import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
-
-import { ref } from 'vue';
-
-import { confirm, SummaryCard } from '../../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../../packages/icons/src';
-import { EchartsUI, useEcharts } from '../../../../../packages/effects/plugins/src/echarts';
-import {
- downloadFileFromBlobPart,
- fenToYuan,
- formatDateTime,
- isSameDay,
-} from '../../../../../packages/utils/src';
-
-import { Button, Card, Col, Row, Spin } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import {
- exportTradeStatisticsExcel,
- getTradeStatisticsAnalyse,
- getTradeStatisticsList,
-} from '#/api/mall/statistics/trade';
-import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
-import { $t } from '#/locales';
-
-import { getTradeTrendChartOptions } from './trend-chart-options';
-
-/** 浜ゆ槗瓒嬪娍 */
-defineOptions({ name: 'TradeTrendCard' });
-
-const trendLoading = ref(true); // 浜ゆ槗鐘舵�佸姞杞戒腑
-const exportLoading = ref(false); // 瀵煎嚭鐨勫姞杞戒腑
-const trendSummary =
- ref<DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummaryRespVO>>(); // 浜ゆ槗鐘跺喌缁熻鏁版嵁
-const searchTimes = ref<string[]>([]);
-
-const chartRef = ref<EchartsUIType>();
-const { renderEcharts } = useEcharts(chartRef);
-
-/** 璁$畻鐜瘮鐧惧垎姣� */
-const calculateRelativeRate = (value?: number, reference?: number): string => {
- const refValue = Number(reference || 0);
- const curValue = Number(value || 0);
- if (!refValue || refValue === 0) {
- return '0.00';
- }
- return (((curValue - refValue) / refValue) * 100).toFixed(2);
-};
-
-/** 澶勭悊鏃ユ湡鑼冨洿鍙樺寲 */
-const handleDateRangeChange = (times?: [Dayjs, Dayjs]) => {
- if (times?.length !== 2) {
- loadTradeTrendData();
- return;
- }
- // 澶勭悊鏃堕棿: 寮�濮嬩笌鎴鍦ㄥ悓涓�澶╃殑, 鎶樼嚎鍥惧嚭涓嶆潵, 闇�瑕佸欢闀夸竴澶�
- let adjustedTimes = times;
- if (isSameDay(times[0], times[1])) {
- adjustedTimes = [dayjs(times[0]).subtract(1, 'd'), times[1]];
- }
- searchTimes.value = [
- formatDateTime(adjustedTimes[0]) as string,
- formatDateTime(adjustedTimes[1]) as string,
- ];
-
- // 鏌ヨ鏁版嵁
- loadTradeTrendData();
-};
-
-/** 澶勭悊浜ゆ槗鐘跺喌鏌ヨ */
-async function loadTradeTrendData() {
- trendLoading.value = true;
- try {
- await Promise.all([
- loadTradeStatisticsAnalyse(),
- loadTradeStatisticsList(),
- ]);
- } finally {
- trendLoading.value = false;
- }
-}
-
-/** 鏌ヨ浜ゆ槗鐘跺喌鏁版嵁缁熻 */
-async function loadTradeStatisticsAnalyse() {
- trendSummary.value = await getTradeStatisticsAnalyse({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
-}
-
-/** 鏌ヨ浜ゆ槗鐘跺喌鏁版嵁鍒楄〃 */
-async function loadTradeStatisticsList() {
- const list = await getTradeStatisticsList({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
-
- // 娓叉煋鍥捐〃
- await renderEcharts(getTradeTrendChartOptions(list));
-}
-
-/** 瀵煎嚭鎸夐挳鎿嶄綔 */
-async function handleExport() {
- try {
- // 瀵煎嚭鐨勪簩娆$‘璁�
- await confirm('纭瀵煎嚭浜ゆ槗鐘跺喌鏁版嵁鍚楋紵');
- // 鍙戣捣瀵煎嚭
- exportLoading.value = true;
- const data = await exportTradeStatisticsExcel({
- times: searchTimes.value.length > 0 ? searchTimes.value : undefined,
- });
- // 澶勭悊涓嬭浇
- downloadFileFromBlobPart({ fileName: '浜ゆ槗鐘跺喌.xlsx', source: data });
- } finally {
- exportLoading.value = false;
- }
-}
-</script>
-
-<template>
- <Card :bordered="false" title="浜ゆ槗鐘跺喌" class="h-full">
- <template #extra>
- <!-- 鏌ヨ鏉′欢 -->
- <div class="flex items-center gap-2">
- <ShortcutDateRangePicker @change="handleDateRangeChange">
- <Button class="ml-4" @click="handleExport" :loading="exportLoading">
- <template #icon>
- <IconifyIcon icon="lucide:download" />
- </template>
- {{ $t('page.action.export') }}
- </Button>
- </ShortcutDateRangePicker>
- </div>
- </template>
-
- <!-- 缁熻鍊� -->
- <Row :gutter="16" class="mb-4">
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="钀ヤ笟棰�"
- tooltip="鍟嗗搧鏀粯閲戦銆佸厖鍊奸噾棰�"
- icon="lucide:banknote"
- icon-color="text-blue-500"
- icon-bg-color="bg-blue-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.turnoverPrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.turnoverPrice,
- trendSummary?.reference?.turnoverPrice,
- )
- "
- />
- </Col>
-
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="鍟嗗搧鏀粯閲戦"
- tooltip="鐢ㄦ埛璐拱鍟嗗搧鐨勫疄闄呮敮浠橀噾棰濓紝鍖呮嫭寰俊鏀粯銆佷綑棰濇敮浠樸�佹敮浠樺疂鏀粯銆佺嚎涓嬫敮浠橀噾棰濓紙鎷煎洟鍟嗗搧鍦ㄦ垚鍥箣鍚庤鍏ワ紝绾夸笅鏀粯璁㈠崟鍦ㄥ悗鍙扮‘璁ゆ敮浠樺悗璁″叆锛�"
- icon="lucide:shopping-cart"
- icon-color="text-purple-500"
- icon-bg-color="bg-purple-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.orderPayPrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.orderPayPrice,
- trendSummary?.reference?.orderPayPrice,
- )
- "
- />
- </Col>
-
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="鍏呭�奸噾棰�"
- tooltip="鐢ㄦ埛鎴愬姛鍏呭�肩殑閲戦"
- icon="lucide:credit-card"
- icon-color="text-yellow-500"
- icon-bg-color="bg-yellow-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.rechargePrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.rechargePrice,
- trendSummary?.reference?.rechargePrice,
- )
- "
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="鏀嚭閲戦"
- tooltip="浣欓鏀粯閲戦銆佹敮浠樹剑閲戦噾棰濄�佸晢鍝侀��娆鹃噾棰�"
- icon="lucide:trending-down"
- icon-color="text-green-500"
- icon-bg-color="bg-green-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.expensePrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.expensePrice,
- trendSummary?.reference?.expensePrice,
- )
- "
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="浣欓鏀粯閲戦"
- tooltip="鐢ㄦ埛涓嬪崟鏃朵娇鐢ㄤ綑棰濆疄闄呮敮浠樼殑閲戦"
- icon="lucide:wallet"
- icon-color="text-cyan-500"
- icon-bg-color="bg-cyan-100"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(trendSummary?.value?.walletPayPrice || 0))"
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.walletPayPrice,
- trendSummary?.reference?.walletPayPrice,
- )
- "
- />
- </Col>
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="鏀粯浣i噾閲戦"
- tooltip="鍚庡彴缁欐帹骞垮憳鏀粯鐨勬帹骞夸剑閲戯紝浠ュ疄闄呮敮浠樹负鍑�"
- icon="lucide:gift"
- icon-color="text-orange-500"
- icon-bg-color="bg-orange-100"
- prefix="锟�"
- :decimals="2"
- :value="
- Number(
- fenToYuan(trendSummary?.value?.brokerageSettlementPrice || 0),
- )
- "
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.brokerageSettlementPrice,
- trendSummary?.reference?.brokerageSettlementPrice,
- )
- "
- />
- </Col>
-
- <Col :md="6" :sm="12" :xs="24" class="mb-4">
- <SummaryCard
- title="鍟嗗搧閫�娆鹃噾棰�"
- tooltip="鐢ㄦ埛鎴愬姛閫�娆剧殑鍟嗗搧閲戦"
- icon="lucide:undo-2"
- icon-color="text-red-500"
- icon-bg-color="bg-red-100"
- prefix="锟�"
- :decimals="2"
- :value="
- Number(fenToYuan(trendSummary?.value?.afterSaleRefundPrice || 0))
- "
- :percent="
- calculateRelativeRate(
- trendSummary?.value?.afterSaleRefundPrice,
- trendSummary?.reference?.afterSaleRefundPrice,
- )
- "
- />
- </Col>
- </Row>
-
- <!-- 鎶樼嚎鍥� -->
- <Spin :spinning="trendLoading">
- <EchartsUI ref="chartRef" />
- </Spin>
- </Card>
-</template>
diff --git a/src/views/mall/statistics/trade/modules/trend-chart-options.ts b/src/views/mall/statistics/trade/modules/trend-chart-options.ts
deleted file mode 100644
index a0eb6de..0000000
--- a/src/views/mall/statistics/trade/modules/trend-chart-options.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade';
-
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-/** 浜ゆ槗瓒嬪娍鎶樼嚎鍥鹃厤缃� */
-export function getTradeTrendChartOptions(
- data: MallTradeStatisticsApi.TradeTrendSummaryRespVO[],
-): any {
- // 澶勭悊鏁版嵁锛氬皢鍒嗚浆鎹负鍏�
- const processedData = data.map((item) => ({
- ...item,
- turnoverPrice: Number(fenToYuan(item.turnoverPrice)),
- orderPayPrice: Number(fenToYuan(item.orderPayPrice)),
- rechargePrice: Number(fenToYuan(item.rechargePrice)),
- expensePrice: Number(fenToYuan(item.expensePrice)),
- }));
-
- return {
- dataset: {
- dimensions: [
- 'date',
- 'turnoverPrice',
- 'orderPayPrice',
- 'rechargePrice',
- 'expensePrice',
- ],
- source: processedData,
- },
- grid: {
- left: 20,
- right: 20,
- bottom: 20,
- top: 80,
- containLabel: true,
- },
- legend: {
- top: 50,
- },
- series: [
- {
- name: '钀ヤ笟棰�',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#1890FF' },
- },
- {
- name: '鍟嗗搧鏀粯閲戦',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#722ED1' },
- },
- {
- name: '鍏呭�奸噾棰�',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#FAAD14' },
- },
- {
- name: '鏀嚭閲戦',
- type: 'line',
- smooth: true,
- itemStyle: { color: '#52C41A' },
- },
- ],
- toolbox: {
- feature: {
- // 鏁版嵁鍖哄煙缂╂斁
- dataZoom: {
- yAxisIndex: false, // Y杞翠笉缂╂斁
- },
- brush: {
- type: ['lineX', 'clear'], // 鍖哄煙缂╂斁鎸夐挳銆佽繕鍘熸寜閽�
- },
- saveAsImage: {
- show: true,
- name: '浜ゆ槗鐘跺喌',
- }, // 淇濆瓨涓哄浘鐗�
- },
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- },
- padding: [5, 10],
- formatter(params: any) {
- let result = `<div><strong>${params[0].data.time}</strong></div>`;
- params.forEach((item: any) => {
- result += `<div style="margin: 4px 0;">
- <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${item.color};"></span>
- ${item.seriesName}: 锟�${item.data[item.dimensionNames[item.encode.y[0]]]}
- </div>`;
- });
- return result;
- },
- },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- axisTick: {
- show: false,
- },
- },
- yAxis: {
- type: 'value',
- axisLine: {
- show: false,
- },
- axisTick: {
- show: false,
- },
- axisLabel: {
- formatter: '锟value}',
- color: '#7F8B9C',
- },
- splitLine: {
- show: true,
- lineStyle: {
- color: '#F5F7F9',
- },
- },
- },
- };
-}
diff --git a/src/views/mall/trade/afterSale/data.ts b/src/views/mall/trade/afterSale/data.ts
deleted file mode 100644
index 33520f8..0000000
--- a/src/views/mall/trade/afterSale/data.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鎷掔粷鍞悗琛ㄥ崟鐨� schema 閰嶇疆 */
-export function useDisagreeFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Textarea',
- fieldName: 'reason',
- label: '鎷掔粷鍘熷洜',
- componentProps: {
- placeholder: '璇疯緭鍏ユ嫆缁濆師鍥�',
- rows: 4,
- },
- rules: z.string().min(2, { message: '鎷掔粷鍘熷洜涓嶈兘灏戜簬 2 涓瓧绗�' }),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'spuName',
- label: '鍟嗗搧鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鍝佸悕绉�',
- },
- },
- {
- fieldName: 'no',
- label: '閫�娆剧紪鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ��娆剧紪鍙�',
- },
- },
- {
- fieldName: 'orderNo',
- label: '璁㈠崟缂栧彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍗曠紪鍙�',
- },
- },
- {
- fieldName: 'status',
- label: '鍞悗鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鍞悗鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'way',
- label: '鍞悗鏂瑰紡',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_WAY, 'number'),
- placeholder: '璇烽�夋嫨鍞悗鏂瑰紡',
- allowClear: true,
- },
- },
- {
- fieldName: 'type',
- label: '鍞悗绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鍞悗绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- field: 'no',
- title: '閫�娆剧紪鍙�',
- fixed: 'left',
- minWidth: 200,
- },
- {
- field: 'orderNo',
- title: '璁㈠崟缂栧彿',
- fixed: 'left',
- minWidth: 200,
- slots: { default: 'orderNo' },
- },
- {
- field: 'productInfo',
- title: '鍟嗗搧淇℃伅',
- minWidth: 600,
- slots: { default: 'productInfo' },
- },
- {
- field: 'refundPrice',
- title: '璁㈠崟閲戦',
- width: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'user.nickname',
- title: '涔板',
- minWidth: 120,
- },
- {
- field: 'createTime',
- title: '鐢宠鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'status',
- title: '鍞悗鐘舵��',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: {
- type: DICT_TYPE.TRADE_AFTER_SALE_STATUS,
- },
- },
- },
- {
- field: 'way',
- title: '鍞悗鏂瑰紡',
- width: 100,
- cellRender: {
- name: 'CellDict',
- props: {
- type: DICT_TYPE.TRADE_AFTER_SALE_WAY,
- },
- },
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- align: 'center',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/trade/afterSale/detail/data.ts b/src/views/mall/trade/afterSale/detail/data.ts
deleted file mode 100644
index 8b589dc..0000000
--- a/src/views/mall/trade/afterSale/detail/data.ts
+++ /dev/null
@@ -1,221 +0,0 @@
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { fenToYuan, formatDate } from '../../../../../packages/utils/src';
-
-import { Image } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-
-/** 璁㈠崟淇℃伅 schema */
-export function useOrderInfoSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'orderNo',
- label: '璁㈠崟鍙�',
- },
- {
- field: 'order.deliveryType',
- label: '閰嶉�佹柟寮�',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_DELIVERY_TYPE,
- value: val,
- }),
- },
- {
- field: 'order.type',
- label: '璁㈠崟绫诲瀷',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_ORDER_TYPE,
- value: val,
- }),
- },
- {
- field: 'order.receiverName',
- label: '鏀惰揣浜�',
- },
- {
- field: 'order.userRemark',
- label: '涔板鐣欒█',
- },
- {
- field: 'order.terminal',
- label: '璁㈠崟鏉ユ簮',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TERMINAL,
- value: val,
- }),
- },
- {
- field: 'order.receiverMobile',
- label: '鑱旂郴鐢佃瘽',
- },
- {
- field: 'order.remark',
- label: '鍟嗗澶囨敞',
- },
- {
- field: 'order.payOrderId',
- label: '鏀粯鍗曞彿',
- },
- {
- field: 'order.payChannelCode',
- label: '浠樻鏂瑰紡',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_CHANNEL_CODE,
- value: val,
- }),
- },
- {
- field: 'user.nickname',
- label: '涔板',
- },
- ];
-}
-
-/** 鍞悗淇℃伅 schema */
-export function useAfterSaleInfoSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'no',
- label: '閫�娆剧紪鍙�',
- },
- {
- field: 'auditTime',
- label: '鐢宠鏃堕棿',
- render: (val) => formatDate(val) as string,
- },
- {
- field: 'type',
- label: '鍞悗绫诲瀷',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_AFTER_SALE_TYPE,
- value: val,
- }),
- },
- {
- field: 'way',
- label: '鍞悗鏂瑰紡',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_AFTER_SALE_WAY,
- value: val,
- }),
- },
- {
- field: 'refundPrice',
- label: '閫�娆鹃噾棰�',
- render: (val) => fenToYuan(val ?? 0),
- },
- {
- field: 'applyReason',
- label: '閫�娆惧師鍥�',
- },
- {
- field: 'applyDescription',
- label: '琛ュ厖鎻忚堪',
- },
- {
- field: 'applyPicUrls',
- label: '鍑瘉鍥剧墖',
- render: (val) => {
- const images = val || [];
- return h(
- 'div',
- { class: 'flex gap-10px' },
- images.map((url: string, index: number) =>
- h(Image, {
- key: index,
- src: url,
- width: 60,
- height: 60,
- }),
- ),
- );
- },
- },
- ];
-}
-
-/** 閫�娆剧姸鎬� schema */
-export function useRefundStatusSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'status',
- label: '閫�娆剧姸鎬�',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_AFTER_SALE_STATUS,
- value: val,
- }),
- },
- {
- field: 'reminder',
- label: '鎻愰啋',
- render: () =>
- h('div', { class: 'text-red-500 mb-10px' }, [
- h('div', '濡傛灉鏈彂璐э紝璇风偣鍑诲悓鎰忛��娆剧粰涔板銆�'),
- h('div', '濡傛灉瀹為檯宸插彂璐э紝璇蜂富鍔ㄤ笌涔板鑱旂郴銆�'),
- h('div', '濡傛灉璁㈠崟鏁翠綋閫�娆惧悗锛屼紭鎯犲埜鍜屼綑棰濅細閫�杩樼粰涔板.'),
- ]),
- },
- ];
-}
-
-/** 鍟嗗搧淇℃伅 columns */
-export function useProductColumns() {
- return [
- {
- field: 'spuName',
- title: '鍟嗗搧淇℃伅',
- minWidth: 300,
- slots: { default: 'spuName' },
- },
- {
- field: 'price',
- title: '鍟嗗搧鍘熶环',
- minWidth: 150,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'count',
- title: '鏁伴噺',
- minWidth: 100,
- },
- {
- field: 'payPrice',
- title: '鍚堣',
- minWidth: 150,
- formatter: 'formatFenToYuanAmount',
- },
- ];
-}
-
-/** 鎿嶄綔鏃ュ織 columns */
-export function useOperateLogSchema() {
- return [
- {
- field: 'createTime',
- title: '鎿嶄綔鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'userType',
- title: '鎿嶄綔浜�',
- width: 100,
- slots: { default: 'userType' },
- },
- {
- field: 'content',
- title: '鎿嶄綔鍐呭',
- },
- ];
-}
diff --git a/src/views/mall/trade/afterSale/detail/index.vue b/src/views/mall/trade/afterSale/detail/index.vue
deleted file mode 100644
index c3a5c8f..0000000
--- a/src/views/mall/trade/afterSale/detail/index.vue
+++ /dev/null
@@ -1,307 +0,0 @@
-<script setup lang="ts">
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallAfterSaleApi } from '#/api/mall/trade/afterSale';
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { confirm, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { useTabs } from '../../../../../packages/effects/hooks/src';
-import { $t } from '../../../../../packages/locales/src';
-
-import { message, Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- agreeAfterSale,
- getAfterSale,
- receiveAfterSale,
- refundAfterSale,
- refuseAfterSale,
-} from '#/api/mall/trade/afterSale';
-import { useDescription } from '#/components/description';
-import { DictTag } from '#/components/dict-tag';
-import { TableAction } from '#/components/table-action';
-
-import DisagreeForm from '../modules/disagree-form.vue';
-import {
- useAfterSaleInfoSchema,
- useOperateLogSchema,
- useOrderInfoSchema,
- useProductColumns,
- useRefundStatusSchema,
-} from './data';
-
-defineOptions({ name: 'TradeAfterSaleDetail' });
-
-const route = useRoute();
-const router = useRouter();
-const tabs = useTabs();
-
-const loading = ref(false);
-const afterSaleId = ref(0);
-const afterSale = ref<MallAfterSaleApi.AfterSale>({
- order: {},
- orderItem: {},
- logs: [],
-});
-
-const [OrderDescriptions] = useDescription({
- title: '璁㈠崟淇℃伅',
- bordered: false,
- column: 3,
- class: 'mx-4',
- schema: useOrderInfoSchema(),
-});
-
-const [AfterSaleDescriptions] = useDescription({
- title: '鍞悗淇℃伅',
- bordered: false,
- column: 3,
- class: 'mx-4',
- schema: useAfterSaleInfoSchema(),
-});
-
-const [RefundStatusDescriptions] = useDescription({
- title: '閫�娆剧姸鎬�',
- bordered: false,
- column: 1,
- class: 'mx-4',
- schema: useRefundStatusSchema(),
-});
-
-const [ProductGrid, productGridApi] = useVbenVxeGrid({
- gridOptions: {
- cellConfig: {
- height: 60,
- },
- columns: useProductColumns(),
- data: [],
- height: 'auto',
- border: true,
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallOrderApi.OrderItem>,
-});
-
-const [OperateLogGrid, operateLogGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useOperateLogSchema(),
- data: [],
- border: true,
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions,
-});
-
-const [DisagreeModal, disagreeModalApi] = useVbenModal({
- connectedComponent: DisagreeForm,
- destroyOnClose: true,
-});
-
-/** 鑾峰緱璇︽儏 */
-async function getDetail() {
- loading.value = true;
- try {
- const res = await getAfterSale(afterSaleId.value);
- if (res === null) {
- message.error('鍞悗璁㈠崟涓嶅瓨鍦�');
- handleBack();
- return;
- }
- afterSale.value = res;
- productGridApi.setGridOptions({ data: [afterSale.value.orderItem] });
- operateLogGridApi.setGridOptions({
- data: afterSale.value.logs || [],
- });
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍚屾剰鍞悗 */
-async function handleAgree() {
- await confirm('鏄惁鍚屾剰鍞悗锛�');
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊涓�...',
- duration: 0,
- });
- try {
- await agreeAfterSale(afterSale.value.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- await getDetail();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎷掔粷鍞悗 */
-function handleDisagree() {
- disagreeModalApi.setData({ afterSale: afterSale.value }).open();
-}
-
-/** 纭鏀惰揣 */
-async function handleReceive() {
- await confirm('鏄惁纭鏀惰揣锛�');
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊涓�...',
- duration: 0,
- });
- try {
- await receiveAfterSale(afterSale.value.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- await getDetail();
- } finally {
- hideLoading();
- }
-}
-
-/** 鎷掔粷鏀惰揣 */
-async function handleRefuse() {
- await confirm('鏄惁鎷掔粷鏀惰揣锛�');
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊涓�...',
- duration: 0,
- });
- try {
- await refuseAfterSale(afterSale.value.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- await getDetail();
- } finally {
- hideLoading();
- }
-}
-
-/** 纭閫�娆� */
-async function handleRefund() {
- await confirm('鏄惁纭閫�娆撅紵');
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊涓�...',
- duration: 0,
- });
- try {
- await refundAfterSale(afterSale.value.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- await getDetail();
- } finally {
- hideLoading();
- }
-}
-
-/** 杩斿洖鍒楄〃椤� */
-function handleBack() {
- tabs.closeCurrentTab();
- router.push({ name: 'TradeAfterSale' });
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- afterSaleId.value = Number(route.params.id);
- getDetail();
-});
-</script>
-
-<template>
- <Page auto-content-height :title="afterSale.no" :loading="loading">
- <template #extra>
- <TableAction
- :actions="[
- {
- label: '杩斿洖',
- type: 'default',
- icon: 'lucide:arrow-left',
- onClick: handleBack,
- },
- {
- label: '鍚屾剰鍞悗',
- type: 'primary',
- onClick: handleAgree,
- ifShow: afterSale.status === 10,
- },
- {
- label: '鎷掔粷鍞悗',
- type: 'primary',
- danger: true,
- onClick: handleDisagree,
- ifShow: afterSale.status === 10,
- },
- {
- label: '纭鏀惰揣',
- type: 'primary',
- onClick: handleReceive,
- ifShow: afterSale.status === 30,
- },
- {
- label: '鎷掔粷鏀惰揣',
- type: 'primary',
- danger: true,
- onClick: handleRefuse,
- ifShow: afterSale.status === 30,
- },
- {
- label: '纭閫�娆�',
- type: 'primary',
- onClick: handleRefund,
- ifShow: afterSale.status === 40,
- },
- ]"
- />
- </template>
-
- <!-- 鎷掔粷鍞悗寮圭獥 -->
- <DisagreeModal @success="getDetail" />
-
- <!-- 璁㈠崟淇℃伅 -->
- <div class="mb-4">
- <OrderDescriptions :data="afterSale" />
- </div>
- <!-- 鍞悗淇℃伅 -->
- <div class="mb-4">
- <AfterSaleDescriptions :data="afterSale" />
- </div>
- <!-- 閫�娆剧姸鎬� -->
- <div class="mb-4">
- <RefundStatusDescriptions :data="afterSale" />
- </div>
- <!-- 鍟嗗搧淇℃伅 -->
- <div class="mb-4">
- <ProductGrid table-title="鍟嗗搧淇℃伅">
- <template #spuName="{ row }">
- <div class="flex flex-1 flex-col items-start gap-1 text-left">
- <span class="text-sm">{{ row.spuName }}</span>
- <div class="flex flex-wrap gap-1">
- <Tag
- v-for="property in row.properties"
- :key="property.propertyId!"
- size="small"
- >
- {{ property.propertyName }}: {{ property.valueName }}
- </Tag>
- </div>
- </div>
- </template>
- </ProductGrid>
- </div>
- <!-- 鎿嶄綔鏃ュ織 -->
- <div>
- <OperateLogGrid table-title="鍞悗鏃ュ織">
- <template #userType="{ row }">
- <Tag v-if="row.userId === 0" color="default">绯荤粺</Tag>
- <DictTag v-else :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
- </template>
- </OperateLogGrid>
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/trade/afterSale/index.vue b/src/views/mall/trade/afterSale/index.vue
deleted file mode 100644
index 9f6416d..0000000
--- a/src/views/mall/trade/afterSale/index.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallAfterSaleApi } from '#/api/mall/trade/afterSale';
-
-import { onMounted, ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { Button, Image, Tabs, Tag } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getAfterSalePage } from '#/api/mall/trade/afterSale';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-const { push } = useRouter();
-
-const statusTabs = ref([
- {
- label: '鍏ㄩ儴',
- value: '0',
- },
-]);
-const statusTab = ref(statusTabs.value[0]!.value);
-
-/** 澶勭悊閫�娆� */
-function handleOpenAfterSaleDetail(row: MallAfterSaleApi.AfterSale) {
- push({ name: 'TradeAfterSaleDetail', params: { id: row.id } });
-}
-
-/** 鏌ョ湅璁㈠崟璇︽儏 */
-function handleOpenOrderDetail(row: MallAfterSaleApi.AfterSale) {
- push({ name: 'TradeOrderDetail', params: { id: row.orderId } });
-}
-
-/** 鍒囨崲鍞悗鐘舵�� */
-function handleChangeStatus(key: number | string) {
- statusTab.value = key.toString();
- gridApi.query();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 60,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAfterSalePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- status:
- statusTab.value === '0' ? undefined : Number(statusTab.value),
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallAfterSaleApi.AfterSale>,
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- for (const dict of getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS)) {
- statusTabs.value.push({
- label: dict.label,
- value: dict.value.toString(),
- });
- }
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪氦鏄撱�戝敭鍚庨��娆�"
- url="https://doc.iocoder.cn/mall/trade-aftersale/"
- />
- </template>
-
- <Grid>
- <template #toolbar-actions>
- <Tabs
- v-model:active-key="statusTab"
- class="w-full"
- @change="handleChangeStatus"
- >
- <Tabs.TabPane
- v-for="tab in statusTabs"
- :key="tab.value"
- :tab="tab.label"
- />
- </Tabs>
- </template>
- <template #orderNo="{ row }">
- <Button type="link" @click="handleOpenOrderDetail(row)">
- {{ row.orderNo }}
- </Button>
- </template>
- <template #productInfo="{ row }">
- <div class="flex items-start gap-2 text-left">
- <Image
- v-if="row.picUrl"
- :src="row.picUrl"
- :width="40"
- :height="40"
- :preview="{ src: row.picUrl }"
- />
- <div class="flex flex-1 flex-col gap-1">
- <span class="text-sm">{{ row.spuName }}</span>
- <div class="mt-1 flex flex-wrap gap-1">
- <Tag
- v-for="property in row.properties"
- :key="property.propertyId!"
- size="small"
- color="blue"
- >
- {{ property.propertyName }}: {{ property.valueName }}
- </Tag>
- </div>
- </div>
- </div>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '澶勭悊閫�娆�',
- type: 'link',
- onClick: handleOpenAfterSaleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/afterSale/modules/disagree-form.vue b/src/views/mall/trade/afterSale/modules/disagree-form.vue
deleted file mode 100644
index 2230e03..0000000
--- a/src/views/mall/trade/afterSale/modules/disagree-form.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<script setup lang="ts">
-import type { MallAfterSaleApi } from '#/api/mall/trade/afterSale';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { $t } from '../../../../../packages/locales/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { disagreeAfterSale } from '#/api/mall/trade/afterSale';
-
-import { useDisagreeFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallAfterSaleApi.AfterSale>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useDisagreeFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- try {
- const data =
- (await formApi.getValues()) as MallAfterSaleApi.AfterSaleDisagreeReqVO;
- await disagreeAfterSale(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{ afterSale: MallAfterSaleApi.AfterSale }>();
- if (!data?.afterSale) {
- return;
- }
- formData.value = data.afterSale;
- modalApi.lock();
- try {
- // 璁剧疆琛ㄥ崟鏁版嵁
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal title="鎷掔粷鍞悗" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/brokerage/record/data.ts b/src/views/mall/trade/brokerage/record/data.ts
deleted file mode 100644
index c48b0f3..0000000
--- a/src/views/mall/trade/brokerage/record/data.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴风紪鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'bizType',
- label: '涓氬姟绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_BIZ_TYPE, 'number'),
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 60,
- },
- {
- field: 'userId',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'userAvatar',
- title: '澶村儚',
- minWidth: 70,
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'userNickname',
- title: '鏄电О',
- minWidth: 80,
- },
- {
- field: 'bizType',
- title: '涓氬姟绫诲瀷',
- minWidth: 85,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.BROKERAGE_RECORD_BIZ_TYPE },
- },
- },
- {
- field: 'bizId',
- title: '涓氬姟缂栧彿',
- minWidth: 80,
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 110,
- },
- {
- field: 'price',
- title: '閲戦',
- minWidth: 60,
- formatter: ({ row }) => `锟�${fenToYuan(row.price)}`,
- },
- {
- field: 'description',
- title: '璇存槑',
- minWidth: 120,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 85,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.BROKERAGE_RECORD_STATUS },
- },
- },
- {
- field: 'unfreezeTime',
- title: '瑙e喕鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/mall/trade/brokerage/record/index.vue b/src/views/mall/trade/brokerage/record/index.vue
deleted file mode 100644
index cf9332d..0000000
--- a/src/views/mall/trade/brokerage/record/index.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageRecordApi } from '#/api/mall/trade/brokerage/record';
-
-import { DocAlert, Page } from '../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBrokerageRecordPage } from '#/api/mall/trade/brokerage/record';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-defineOptions({ name: 'TradeBrokerageRecord' });
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrokerageRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageRecordApi.BrokerageRecord>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪氦鏄撱�戝垎閿�杩斾剑"
- url="https://doc.iocoder.cn/mall/trade-brokerage/"
- />
- </template>
-
- <Grid table-title="鍒嗛攢杩斾剑璁板綍" />
- </Page>
-</template>
diff --git a/src/views/mall/trade/brokerage/user/data.ts b/src/views/mall/trade/brokerage/user/data.ts
deleted file mode 100644
index 4b69921..0000000
--- a/src/views/mall/trade/brokerage/user/data.ts
+++ /dev/null
@@ -1,367 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'bindUserId',
- label: '鎺ㄥ箍鍛樼紪鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ帹骞垮憳缂栧彿',
- allowClear: true,
- },
- },
- {
- fieldName: 'brokerageEnabled',
- label: '鎺ㄥ箍璧勬牸',
- component: 'RadioGroup',
- componentProps: {
- placeholder: '璇烽�夋嫨鎺ㄥ箍璧勬牸',
- allowClear: true,
- options: [
- { label: '鏈�', value: true },
- { label: '鏃�', value: false },
- ],
- },
- defaultValue: true,
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- onBrokerageEnabledChange?: (
- newEnabled: boolean,
- row: MallBrokerageUserApi.BrokerageUser,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'avatar',
- title: '澶村儚',
- minWidth: 70,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'nickname',
- title: '鏄电О',
- minWidth: 80,
- },
- {
- field: 'brokerageUserCount',
- title: '鎺ㄥ箍浜烘暟',
- minWidth: 80,
- },
- {
- field: 'brokerageOrderCount',
- title: '鎺ㄥ箍璁㈠崟鏁伴噺',
- minWidth: 110,
- },
- {
- field: 'brokerageOrderPrice',
- title: '鎺ㄥ箍璁㈠崟閲戦',
- minWidth: 110,
- formatter: ({ row }) => `锟�${fenToYuan(row.brokerageOrderPrice)}`,
- },
- {
- field: 'withdrawPrice',
- title: '宸叉彁鐜伴噾棰�',
- minWidth: 100,
- formatter: ({ row }) => `锟�${fenToYuan(row.withdrawPrice)}`,
- },
- {
- field: 'withdrawCount',
- title: '宸叉彁鐜版鏁�',
- minWidth: 100,
- },
- {
- field: 'price',
- title: '鏈彁鐜伴噾棰�',
- minWidth: 100,
- formatter: ({ row }) => `锟�${fenToYuan(row.price)}`,
- },
- {
- field: 'frozenPrice',
- title: '鍐荤粨涓剑閲�',
- minWidth: 100,
- formatter: ({ row }) => `锟�${fenToYuan(row.frozenPrice)}`,
- },
- {
- field: 'brokerageEnabled',
- title: '鎺ㄥ箍璧勬牸',
- minWidth: 80,
- align: 'center',
- cellRender: {
- attrs: { beforeChange: onBrokerageEnabledChange },
- name: 'CellSwitch',
- props: {
- checkedValue: true,
- uncheckedValue: false,
- checkedChildren: '鏈�',
- unCheckedChildren: '鏃�',
- },
- },
- },
- {
- field: 'brokerageTime',
- title: '鎴愪负鎺ㄥ箍鍛樻椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'bindUserId',
- title: '涓婄骇鎺ㄥ箍鍛樼紪鍙�',
- minWidth: 150,
- },
- {
- field: 'bindUserTime',
- title: '鎺ㄥ箍鍛樼粦瀹氭椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍒涘缓鍒嗛攢鍛樿〃鍗曢厤缃� */
-export function useCreateFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鍒嗛攢鍛樼紪鍙�',
- component: 'InputSearch',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎閿�鍛樼紪鍙�',
- },
- rules: 'required',
- },
- {
- fieldName: 'bindUserId',
- label: '涓婄骇鎺ㄥ箍鍛樼紪鍙�',
- component: 'InputSearch',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ笂绾ф帹骞垮憳缂栧彿',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 淇敼鍒嗛攢鐢ㄦ埛琛ㄥ崟閰嶇疆 */
-export function useUpdateFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'bindUserId',
- label: '涓婄骇鎺ㄥ箍鍛樼紪鍙�',
- component: 'InputSearch',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ笂绾ф帹骞垮憳缂栧彿',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鐢ㄦ埛鍒楄〃寮圭獥鎼滅储琛ㄥ崟閰嶇疆 */
-export function useUserListFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'level',
- label: '鐢ㄦ埛绫诲瀷',
- component: 'Select',
- componentProps: {
- options: [
- { label: '鍏ㄩ儴', value: undefined },
- { label: '涓�绾ф帹骞夸汉', value: '1' },
- { label: '浜岀骇鎺ㄥ箍浜�', value: '2' },
- ],
- allowClear: true,
- },
- },
- {
- fieldName: 'bindUserTime',
- label: '缁戝畾鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鐢ㄦ埛鍒楄〃寮圭獥琛ㄦ牸鍒楅厤缃� */
-export function useUserListColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'avatar',
- title: '澶村儚',
- minWidth: 70,
- cellRender: {
- name: 'CellImage',
- props: {
- width: 24,
- height: 24,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鏄电О',
- minWidth: 80,
- },
- {
- field: 'brokerageUserCount',
- title: '鎺ㄥ箍浜烘暟',
- minWidth: 80,
- },
- {
- field: 'brokerageOrderCount',
- title: '鎺ㄥ箍璁㈠崟鏁伴噺',
- minWidth: 110,
- },
- {
- field: 'brokerageEnabled',
- title: '鎺ㄥ箍璧勬牸',
- minWidth: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'bindUserTime',
- title: '缁戝畾鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
-
-/** 鎺ㄥ箍璁㈠崟鍒楄〃寮圭獥鎼滅储琛ㄥ崟閰嶇疆 */
-export function useOrderListFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'sourceUserLevel',
- label: '鐢ㄦ埛绫诲瀷',
- component: 'Select',
- componentProps: {
- options: [
- { label: '鍏ㄩ儴', value: 0 },
- { label: '涓�绾ф帹骞夸汉', value: 1 },
- { label: '浜岀骇鎺ㄥ箍浜�', value: 2 },
- ],
- },
- defaultValue: 0,
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鎺ㄥ箍璁㈠崟鍒楄〃寮圭獥琛ㄦ牸鍒楅厤缃� */
-export function useOrderListColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'bizId',
- title: '璁㈠崟缂栧彿',
- minWidth: 80,
- },
- {
- field: 'sourceUserId',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'sourceUserAvatar',
- title: '澶村儚',
- minWidth: 70,
- cellRender: {
- name: 'CellImage',
- props: {
- width: 24,
- height: 24,
- },
- },
- },
- {
- field: 'sourceUserNickname',
- title: '鏄电О',
- minWidth: 80,
- },
- {
- field: 'price',
- title: '浣i噾',
- minWidth: 100,
- formatter: ({ row }) => `锟�${fenToYuan(row.price)}`,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 85,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.BROKERAGE_RECORD_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/mall/trade/brokerage/user/index.vue b/src/views/mall/trade/brokerage/user/index.vue
deleted file mode 100644
index f3ba504..0000000
--- a/src/views/mall/trade/brokerage/user/index.vue
+++ /dev/null
@@ -1,202 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { $t } from '../../../../../packages/locales/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- clearBindUser,
- getBrokerageUserPage,
- updateBrokerageEnabled,
-} from '#/api/mall/trade/brokerage/user';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import CreateForm from './modules/create-form.vue';
-import BrokerageOrderListModal from './modules/order-list-modal.vue';
-import UpdateForm from './modules/update-form.vue';
-import BrokerageUserListModal from './modules/user-list-modal.vue';
-
-defineOptions({ name: 'TradeBrokerageUser' });
-
-const [CreateFormModal, createFormModalApi] = useVbenModal({
- connectedComponent: CreateForm,
- destroyOnClose: true,
-});
-
-const [UpdateFormModal, updateModalApi] = useVbenModal({
- connectedComponent: UpdateForm,
- destroyOnClose: true,
-});
-
-const [OrderListModal, orderListModalApi] = useVbenModal({
- connectedComponent: BrokerageOrderListModal,
- destroyOnClose: true,
-});
-
-const [UserListModal, userListModalApi] = useVbenModal({
- connectedComponent: BrokerageUserListModal,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍒嗛攢鍛� */
-function handleCreate() {
- createFormModalApi.open();
-}
-
-/** 淇敼鍒嗛攢鍛� */
-function handleUpdateForm(row: MallBrokerageUserApi.BrokerageUser) {
- updateModalApi.setData(row).open();
-}
-
-/** 鎵撳紑鎺ㄥ箍浜哄垪琛� */
-function handleOpenUserList(row: MallBrokerageUserApi.BrokerageUser) {
- userListModalApi.setData(row).open();
-}
-
-/** 鎵撳紑鎺ㄥ箍璁㈠崟鍒楄〃 */
-function handleOpenOrderList(row: MallBrokerageUserApi.BrokerageUser) {
- orderListModalApi.setData(row).open();
-}
-
-/** 娓呴櫎涓婄骇鎺ㄥ箍浜� */
-async function handleClearBindUser(row: MallBrokerageUserApi.BrokerageUser) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.nickname]),
- duration: 0,
- });
- try {
- await clearBindUser({ id: row.id! });
- message.success($t('ui.actionMessage.deleteSuccess', [row.nickname]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏇存柊鎺ㄥ箍璧勬牸 */
-async function handleBrokerageEnabledChange(
- newEnabled: boolean,
- row: MallBrokerageUserApi.BrokerageUser,
-): Promise<boolean | undefined> {
- const text = newEnabled ? '寮�閫�' : '鍏抽棴';
- try {
- await confirm(`浣犺灏�${row.nickname}鐨勬帹骞胯祫鏍煎垏鎹负銆�${text}銆戝悧锛焋);
- } catch {
- return false;
- }
- // 鏇存柊鎺ㄥ箍璧勬牸
- await updateBrokerageEnabled({
- id: row.id!,
- enabled: newEnabled,
- });
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success($t('ui.actionMessage.operationSuccess'));
- handleRefresh();
- return true;
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleBrokerageEnabledChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrokerageUserPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageUserApi.BrokerageUser>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪氦鏄撱�戝垎閿�杩斾剑"
- url="https://doc.iocoder.cn/mall/trade-brokerage/"
- />
- </template>
-
- <!-- 鍒涘缓鍒嗛攢鍛� -->
- <CreateFormModal @success="handleRefresh" />
- <!-- 淇敼鍒嗛攢鍛� -->
- <UpdateFormModal @success="handleRefresh" />
- <!-- 鎺ㄥ箍浜哄垪琛� -->
- <UserListModal />
- <!-- 鎺ㄥ箍璁㈠崟鍒楄〃 -->
- <OrderListModal />
- <Grid table-title="鍒嗛攢鐢ㄦ埛鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍒嗛攢鍛�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['trade:brokerage-user:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :drop-down-actions="[
- {
- label: '鎺ㄥ箍浜�',
- type: 'link',
- auth: ['trade:brokerage-user:user-query'],
- onClick: handleOpenUserList.bind(null, row),
- },
- {
- label: '鎺ㄥ箍璁㈠崟',
- type: 'link',
- auth: ['trade:brokerage-user:order-query'],
- onClick: handleOpenOrderList.bind(null, row),
- },
- {
- label: '淇敼涓婄骇鎺ㄥ箍浜�',
- type: 'link',
- auth: ['trade:brokerage-user:update-bind-user'],
- onClick: handleUpdateForm.bind(null, row),
- },
- {
- label: '娓呴櫎涓婄骇鎺ㄥ箍浜�',
- type: 'link',
- auth: ['trade:brokerage-user:clear-bind-user'],
- ifShow: !!row.bindUserId,
- onClick: handleClearBindUser.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/brokerage/user/modules/create-form.vue b/src/views/mall/trade/brokerage/user/modules/create-form.vue
deleted file mode 100644
index 94c532e..0000000
--- a/src/views/mall/trade/brokerage/user/modules/create-form.vue
+++ /dev/null
@@ -1,168 +0,0 @@
-<script lang="ts" setup>
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-import { $t } from '../../../../../../packages/locales/src';
-import { formatDate, isEmpty } from '../../../../../../packages/utils/src';
-
-import {
- Avatar,
- Descriptions,
- DescriptionsItem,
- Divider,
- InputSearch,
- message,
-} from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createBrokerageUser,
- getBrokerageUser,
-} from '#/api/mall/trade/brokerage/user';
-import { getUser } from '#/api/member/user';
-import { DictTag } from '#/components/dict-tag';
-
-import { useCreateFormSchema } from '../data';
-
-defineOptions({ name: 'BrokerageUserCreateForm' });
-
-const emit = defineEmits(['success']);
-
-const formData = ref<any>({
- userId: undefined,
- bindUserId: undefined,
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useCreateFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- await formApi.setValues(formData.value);
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- try {
- await createBrokerageUser(formData.value);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- onOpenChange: async (isOpen: boolean) => {
- if (!isOpen) {
- return;
- }
- // 閲嶇疆琛ㄥ崟
- formData.value = {
- userId: undefined,
- bindUserId: undefined,
- };
- await formApi.setValues(formData.value);
- // 閲嶇疆鐢ㄦ埛淇℃伅
- user.value = undefined;
- bindUser.value = undefined;
- },
-});
-
-/** 鐢ㄦ埛淇℃伅 */
-const user = ref<MallBrokerageUserApi.BrokerageUser | undefined>();
-const bindUser = ref<MallBrokerageUserApi.BrokerageUser | undefined>();
-
-/** 鏌ヨ鍒嗛攢鍛樺拰鍒嗛攢鍛� */
-async function handleSearchUser(id: number, userType: string) {
- if (isEmpty(id)) {
- message.warning(`璇峰厛杈撳叆${userType}缂栧彿鍚庨噸璇曪紒锛侊紒`);
- return;
- }
- if (
- userType === '鍒嗛攢鍛�' &&
- formData.value?.bindUserId === formData.value?.userId
- ) {
- message.error('涓嶈兘缁戝畾鑷繁涓烘帹骞垮憳');
- return;
- }
-
- const userData =
- userType === '鍒嗛攢鍛�' ? await getUser(id) : await getBrokerageUser(id);
- if (!userData) {
- message.warning(`${userType}涓嶅瓨鍦╜);
- return;
- }
- if (userType === '鍒嗛攢鍛�') {
- user.value = userData as MallBrokerageUserApi.BrokerageUser;
- } else {
- bindUser.value = userData as MallBrokerageUserApi.BrokerageUser;
- }
-}
-</script>
-
-<template>
- <Modal title="鍒涘缓鍒嗛攢鍛�" class="w-1/3">
- <Form>
- <template #userId>
- <InputSearch
- v-model:value="formData.userId"
- placeholder="璇疯緭鍏ュ垎閿�鍛樼紪鍙�"
- @search="handleSearchUser(formData.userId, '鍒嗛攢鍛�')"
- />
- </template>
- <template #bindUserId>
- <InputSearch
- v-model:value="formData.bindUserId"
- placeholder="璇疯緭鍏ヤ笂绾у垎閿�鍛樼紪鍙�"
- @search="handleSearchUser(formData.bindUserId, '涓婄骇鍒嗛攢鍛�')"
- />
- </template>
- </Form>
-
- <!-- 鍒嗛攢鍛樹俊鎭睍绀� -->
- <Divider v-if="user" />
- <Descriptions v-if="user" title="鍒嗛攢鍛樹俊鎭�" :column="1" bordered>
- <DescriptionsItem label="澶村儚">
- <Avatar :src="user?.avatar" />
- </DescriptionsItem>
- <DescriptionsItem label="鏄电О">
- {{ user?.nickname }}
- </DescriptionsItem>
- </Descriptions>
- <!-- 涓婄骇鎺ㄥ箍鍛樹俊鎭睍绀� -->
- <Divider v-if="bindUser" />
- <Descriptions v-if="bindUser" title="涓婄骇鎺ㄥ箍鍛樹俊鎭�" :column="1" bordered>
- <DescriptionsItem label="澶村儚">
- <Avatar :src="bindUser?.avatar" />
- </DescriptionsItem>
- <DescriptionsItem label="鏄电О">
- {{ bindUser?.nickname }}
- </DescriptionsItem>
- <DescriptionsItem label="鍒嗛攢璧勬牸">
- <DictTag
- :type="DICT_TYPE.INFRA_BOOLEAN_STRING"
- :value="bindUser?.brokerageEnabled"
- />
- </DescriptionsItem>
- <DescriptionsItem label="鎴愪负鍒嗛攢鍛樼殑鏃堕棿">
- {{ formatDate(bindUser?.brokerageTime) }}
- </DescriptionsItem>
- </Descriptions>
- </Modal>
-</template>
diff --git a/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue b/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue
deleted file mode 100644
index c86f31e..0000000
--- a/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageRecordApi } from '#/api/mall/trade/brokerage/record';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { BrokerageRecordBizTypeEnum } from '../../../../../../packages/constants/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBrokerageRecordPage } from '#/api/mall/trade/brokerage/record';
-
-import { useOrderListColumns, useOrderListFormSchema } from '../data';
-
-/** 鎺ㄥ箍璁㈠崟鍒楄〃 */
-defineOptions({ name: 'BrokerageOrderListModal' });
-
-const [Modal, modalApi] = useVbenModal({});
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useOrderListFormSchema(),
- },
- gridOptions: {
- columns: useOrderListColumns(),
- height: '600',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const params = {
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: modalApi.getData()?.id,
- bizType: BrokerageRecordBizTypeEnum.ORDER.type,
- sourceUserLevel:
- formValues.sourceUserLevel === 0
- ? undefined
- : formValues.sourceUserLevel,
- status: formValues.status,
- createTime: formValues.createTime,
- };
- return await getBrokerageRecordPage(params);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageRecordApi.BrokerageRecord>,
-});
-</script>
-
-<template>
- <Modal title="鎺ㄥ箍璁㈠崟鍒楄〃" class="w-3/5">
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/brokerage/user/modules/update-form.vue b/src/views/mall/trade/brokerage/user/modules/update-form.vue
deleted file mode 100644
index c9bae94..0000000
--- a/src/views/mall/trade/brokerage/user/modules/update-form.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<script lang="ts" setup>
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { DICT_TYPE } from '../../../../../../packages/constants/src';
-import { $t } from '../../../../../../packages/locales/src';
-import { formatDate, isEmpty } from '../../../../../../packages/utils/src';
-
-import {
- Avatar,
- Descriptions,
- DescriptionsItem,
- Divider,
- InputSearch,
- message,
-} from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- getBrokerageUser,
- updateBindUser,
-} from '#/api/mall/trade/brokerage/user';
-import { DictTag } from '#/components/dict-tag';
-
-import { useUpdateFormSchema } from '../data';
-
-defineOptions({ name: 'BrokerageUserUpdateForm' });
-
-const emit = defineEmits(['success']);
-
-const formData = ref<any>({
- id: undefined,
- bindUserId: undefined,
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useUpdateFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- await formApi.setValues(formData.value);
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- try {
- await updateBindUser(formData.value);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- onOpenChange: async (isOpen: boolean) => {
- if (!isOpen) {
- return;
- }
- const data = modalApi.getData<MallBrokerageUserApi.BrokerageUser>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = {
- id: data.id,
- bindUserId: data.bindUserId,
- };
- await formApi.setValues(formData.value);
- if (data.bindUserId) {
- await handleSearchUser(data.bindUserId, '涓婄骇鍒嗛攢鍛�');
- }
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 缁戝畾鐢ㄦ埛淇℃伅 */
-const bindUser = ref<MallBrokerageUserApi.BrokerageUser | undefined>();
-
-/** 鏌ヨ鍒嗛攢鍛� */
-async function handleSearchUser(id: number, userType: string) {
- if (isEmpty(id)) {
- message.warning(`璇峰厛杈撳叆${userType}缂栧彿鍚庨噸璇曪紒锛侊紒`);
- return;
- }
- if (formData.value?.bindUserId === formData.value?.id) {
- message.error('涓嶈兘缁戝畾鑷繁涓烘帹骞夸汉');
- return;
- }
-
- const userData = await getBrokerageUser(id);
- if (!userData) {
- message.warning(`${userType}涓嶅瓨鍦╜);
- return;
- }
- bindUser.value = userData as MallBrokerageUserApi.BrokerageUser;
-}
-</script>
-
-<template>
- <Modal title="淇敼涓婄骇鎺ㄥ箍浜�" class="w-1/3">
- <Form>
- <template #bindUserId>
- <InputSearch
- v-model:value="formData.bindUserId"
- placeholder="璇疯緭鍏ヤ笂绾у垎閿�鍛樼紪鍙�"
- @search="handleSearchUser(formData.bindUserId, '涓婄骇鍒嗛攢鍛�')"
- />
- </template>
- </Form>
-
- <!-- 涓婄骇鎺ㄥ箍鍛樹俊鎭睍绀� -->
- <Divider v-if="bindUser" />
- <Descriptions v-if="bindUser" title="涓婄骇鎺ㄥ箍鍛樹俊鎭�" :column="1" bordered>
- <DescriptionsItem label="澶村儚">
- <Avatar :src="bindUser?.avatar" />
- </DescriptionsItem>
- <DescriptionsItem label="鏄电О">
- {{ bindUser?.nickname }}
- </DescriptionsItem>
- <DescriptionsItem label="鍒嗛攢璧勬牸">
- <DictTag
- :type="DICT_TYPE.INFRA_BOOLEAN_STRING"
- :value="bindUser?.brokerageEnabled"
- />
- </DescriptionsItem>
- <DescriptionsItem label="鎴愪负鍒嗛攢鍛樼殑鏃堕棿">
- {{ formatDate(bindUser?.brokerageTime) }}
- </DescriptionsItem>
- </Descriptions>
- </Modal>
-</template>
diff --git a/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue b/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue
deleted file mode 100644
index 045e926..0000000
--- a/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBrokerageUserPage } from '#/api/mall/trade/brokerage/user';
-
-import { useUserListColumns, useUserListFormSchema } from '../data';
-
-defineOptions({ name: 'BrokerageUserListModal' });
-
-const [Modal, modalApi] = useVbenModal({});
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useUserListFormSchema(),
- },
- gridOptions: {
- columns: useUserListColumns(),
- height: '600',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrokerageUserPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- bindUserId: modalApi.getData().id,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageUserApi.BrokerageUser>,
-});
-</script>
-
-<template>
- <Modal title="鎺ㄥ箍浜哄垪琛�" class="w-3/5">
- <Grid />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/brokerage/withdraw/data.ts b/src/views/mall/trade/brokerage/withdraw/data.ts
deleted file mode 100644
index 017cdea..0000000
--- a/src/views/mall/trade/brokerage/withdraw/data.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴风紪鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'type',
- label: '鎻愮幇绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鎻愮幇绫诲瀷',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, 'number'),
- },
- },
- {
- fieldName: 'userAccount',
- label: '璐﹀彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ处鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'userName',
- label: '鐪熷疄濮撳悕',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ湡瀹炲鍚�',
- allowClear: true,
- },
- },
- {
- fieldName: 'bankName',
- label: '鎻愮幇閾惰',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鎻愮幇閾惰',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_BANK_NAME, 'string'),
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鐢宠鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 80,
- },
- {
- field: 'userId',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 80,
- },
- {
- field: 'userNickname',
- title: '鐢ㄦ埛鏄电О',
- minWidth: 80,
- },
- {
- field: 'price',
- title: '鎻愮幇閲戦',
- minWidth: 80,
- formatter: 'formatAmount2',
- },
- {
- field: 'feePrice',
- title: '鎻愮幇鎵嬬画璐�',
- minWidth: 80,
- formatter: 'formatAmount2',
- },
- {
- field: 'type',
- title: '鎻愮幇鏂瑰紡',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.BROKERAGE_WITHDRAW_TYPE },
- },
- },
- {
- title: '鎻愮幇淇℃伅',
- minWidth: 200,
- slots: { default: 'withdraw-info' },
- },
- {
- field: 'createTime',
- title: '鐢宠鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 120,
- },
- {
- title: '鐘舵��',
- minWidth: 200,
- slots: { default: 'status-info' },
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/trade/brokerage/withdraw/index.vue b/src/views/mall/trade/brokerage/withdraw/index.vue
deleted file mode 100644
index 6047911..0000000
--- a/src/views/mall/trade/brokerage/withdraw/index.vue
+++ /dev/null
@@ -1,225 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallBrokerageWithdrawApi } from '#/api/mall/trade/brokerage/withdraw';
-
-import { h } from 'vue';
-
-import { confirm, Page, prompt } from '../../../../../packages/effects/common-ui/src';
-import {
- BrokerageWithdrawStatusEnum,
- BrokerageWithdrawTypeEnum,
- DICT_TYPE,
-} from '../../../../../packages/constants/src';
-import { formatDateTime } from '../../../../../packages/utils/src';
-
-import { Input, message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- approveBrokerageWithdraw,
- getBrokerageWithdrawPage,
- rejectBrokerageWithdraw,
-} from '#/api/mall/trade/brokerage/withdraw';
-import { DictTag } from '#/components/dict-tag';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-/** 鍒嗛攢浣i噾鎻愮幇 */
-defineOptions({ name: 'BrokerageWithdraw' });
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瀹℃牳閫氳繃 */
-async function handleApprove(row: MallBrokerageWithdrawApi.BrokerageWithdraw) {
- await confirm('纭畾瑕佸鏍搁�氳繃鍚楋紵');
- const hideLoading = message.loading({
- content: '瀹℃牳閫氳繃涓� ...',
- duration: 0,
- });
- try {
- await approveBrokerageWithdraw(row.id);
- message.success($t('ui.actionMessage.operationSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 瀹℃牳椹冲洖 */
-function handleReject(row: MallBrokerageWithdrawApi.BrokerageWithdraw) {
- prompt({
- component: () => {
- return h(Input, {
- placeholder: '璇疯緭鍏ラ┏鍥炲師鍥�',
- allowClear: true,
- rules: [{ required: true, message: '璇疯緭鍏ラ┏鍥炲師鍥�' }],
- });
- },
- content: '璇疯緭鍏ラ┏鍥炲師鍥�',
- title: '椹冲洖',
- modelPropName: 'value',
- }).then(async (val) => {
- if (val) {
- await rejectBrokerageWithdraw({
- id: row.id!,
- auditReason: val,
- });
- handleRefresh();
- }
- });
-}
-
-/** 閲嶆柊杞处 */
-async function handleRetryTransfer(
- row: MallBrokerageWithdrawApi.BrokerageWithdraw,
-) {
- await confirm('纭畾瑕侀噸鏂拌浆璐﹀悧锛�');
- const hideLoading = message.loading({
- content: '瀹℃牳閫氳繃涓� ...',
- duration: 0,
- });
- try {
- await approveBrokerageWithdraw(row.id);
- message.success($t('ui.actionMessage.operationSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- cellConfig: {
- height: 90,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrokerageWithdrawPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageWithdrawApi.BrokerageWithdraw>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid table-title="浣i噾鎻愮幇鍒楄〃">
- <template #withdraw-info="{ row }">
- <div
- v-if="row.type === BrokerageWithdrawTypeEnum.WALLET.type"
- class="text-left"
- >
- -
- </div>
- <div v-else class="text-left">
- <div v-if="row.userAccount" class="text-left">
- 璐﹀彿锛歿{ row.userAccount }}
- </div>
- <div v-if="row.userName" class="text-left">
- 鐪熷疄濮撳悕锛歿{ row.userName }}
- </div>
- <template v-if="row.type === BrokerageWithdrawTypeEnum.BANK.type">
- <div v-if="row.bankName" class="text-left">
- 閾惰鍚嶇О锛歿{ row.bankName }}
- </div>
- <div v-if="row.bankAddress" class="text-left">
- 寮�鎴峰湴鍧�锛歿{ row.bankAddress }}
- </div>
- </template>
- <div v-if="row.qrCodeUrl" class="mt-2 text-left">
- <div class="flex items-start gap-2">
- <span class="flex-shrink-0">鏀舵鐮侊細</span>
- <img :src="row.qrCodeUrl" class="h-10 w-10 flex-shrink-0" />
- </div>
- </div>
- </div>
- </template>
- <template #status-info="{ row }">
- <div class="text-left">
- <DictTag
- :value="row.status"
- :type="DICT_TYPE.BROKERAGE_WITHDRAW_STATUS"
- />
- <div
- v-if="row.auditTime"
- class="mt-1 text-left text-xs text-gray-500"
- >
- 鏃堕棿锛歿{ formatDateTime(row.auditTime) }}
- </div>
- <div
- v-if="row.auditReason"
- class="mt-1 text-left text-xs text-gray-500"
- >
- 瀹℃牳鍘熷洜锛歿{ row.auditReason }}
- </div>
- <div
- v-if="row.transferErrorMsg"
- class="mt-1 text-left text-xs text-red-500"
- >
- 杞处澶辫触鍘熷洜锛歿{ row.transferErrorMsg }}
- </div>
- </div>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '閫氳繃',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['trade:brokerage-withdraw:audit'],
- ifShow:
- row.status === BrokerageWithdrawStatusEnum.AUDITING.status &&
- !row.payTransferId,
- onClick: () => handleApprove(row),
- },
- {
- label: '椹冲洖',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['trade:brokerage-withdraw:audit'],
- ifShow:
- row.status === BrokerageWithdrawStatusEnum.AUDITING.status &&
- !row.payTransferId,
- onClick: () => handleReject(row),
- },
- {
- label: '閲嶆柊杞处',
- type: 'link',
- icon: ACTION_ICON.REFRESH,
- auth: ['trade:brokerage-withdraw:audit'],
- ifShow:
- row.status === BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.status,
- onClick: () => handleRetryTransfer(row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/config/data.ts b/src/views/mall/trade/config/data.ts
deleted file mode 100644
index 4bcb123..0000000
--- a/src/views/mall/trade/config/data.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-export const schema: VbenFormSchema[] = [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'type',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'afterSaleRefundReasons',
- label: '閫�娆剧悊鐢�',
- component: 'Select',
- componentProps: {
- mode: 'tags',
- placeholder: '璇风洿鎺ヨ緭鍏ラ��娆剧悊鐢�',
- class: 'w-full',
- },
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'afterSale',
- },
- },
- {
- fieldName: 'afterSaleReturnReasons',
- label: '閫�璐х悊鐢�',
- component: 'Select',
- componentProps: {
- mode: 'tags',
- placeholder: '璇风洿鎺ヨ緭鍏ラ��璐х悊鐢�',
- },
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'afterSale',
- },
- },
- {
- fieldName: 'deliveryExpressFreeEnabled',
- label: '鍚敤鍖呴偖',
- component: 'Switch',
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'delivery',
- },
- help: '鍟嗗煄鏄惁鍚敤鍏ㄥ満鍖呴偖',
- },
- {
- fieldName: 'deliveryExpressFreePrice',
- label: '婊¢鍖呴偖',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '璇疯緭鍏ユ弧棰濆寘閭噾棰�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'delivery',
- },
- help: '鍟嗗煄鍟嗗搧婊″灏戦噾棰濆嵆鍙寘閭紝鍗曚綅锛氬厓',
- },
- {
- fieldName: 'deliveryPickUpEnabled',
- label: '鍚敤闂ㄥ簵鑷彁',
- component: 'Switch',
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'delivery',
- },
- },
- {
- fieldName: 'brokerageEnabled',
- label: '鍚敤鍒嗕剑',
- component: 'Switch',
- help: '鍟嗗煄鏄惁寮�鍚垎閿�妯″紡',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- },
- {
- fieldName: 'brokerageEnabledCondition',
- label: '鍒嗕剑妯″紡',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.BROKERAGE_ENABLED_CONDITION, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '浜轰汉鍒嗛攢锛氭瘡涓敤鎴烽兘鍙互鎴愪负鎺ㄥ箍鍛� \n 鎸囧畾鍒嗛攢锛氫粎鍙湪鍚庡彴鎵嬪姩璁剧疆鎺ㄥ箍鍛�',
- },
- {
- fieldName: 'brokerageBindMode',
- label: '鍒嗛攢鍏崇郴缁戝畾',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.BROKERAGE_BIND_MODE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '棣栨缁戝畾锛氬彧瑕佺敤鎴锋病鏈夋帹骞夸汉锛岄殢鏃堕兘鍙互缁戝畾鎺ㄥ箍鍏崇郴 \n 娉ㄥ唽缁戝畾锛氬彧鏈夋柊鐢ㄦ埛娉ㄥ唽鏃舵垨棣栨杩涘叆绯荤粺鏃舵墠鍙互缁戝畾鎺ㄥ箍鍏崇郴',
- },
- {
- fieldName: 'brokeragePosterUrls',
- label: '鍒嗛攢娴锋姤鍥�',
- component: 'ImageUpload',
- componentProps: {
- maxNumber: 9,
- },
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '涓汉涓績鍒嗛攢娴锋姤鍥剧墖锛屽缓璁昂瀵� 600x1000',
- },
- {
- fieldName: 'brokerageFirstPercent',
- label: '涓�绾ц繑浣f瘮渚嬶紙%锛�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- max: 100,
- placeholder: '璇疯緭鍏ヤ竴绾ц繑浣f瘮渚�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '璁㈠崟浜ゆ槗鎴愬姛鍚庣粰鎺ㄥ箍浜鸿繑浣g殑鐧惧垎姣�',
- },
- {
- fieldName: 'brokerageSecondPercent',
- label: '浜岀骇杩斾剑姣斾緥锛�%锛�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- max: 100,
- placeholder: '璇疯緭鍏ヤ簩绾ц繑浣f瘮渚�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '璁㈠崟浜ゆ槗鎴愬姛鍚庣粰鎺ㄥ箍浜虹殑鎺ㄨ崘浜鸿繑浣g殑鐧惧垎姣�',
- },
- {
- fieldName: 'brokerageFrozenDays',
- label: '浣i噾鍐荤粨澶╂暟',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ヤ剑閲戝喕缁撳ぉ鏁�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '闃叉鐢ㄦ埛閫�娆撅紝浣i噾琚彁鐜颁簡锛屾墍浠ラ渶瑕佽缃剑閲戝喕缁撴椂闂达紝鍗曚綅锛氬ぉ',
- },
- {
- fieldName: 'brokerageWithdrawMinPrice',
- label: '鎻愮幇鏈�浣庨噾棰濓紙鍏冿級',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '璇疯緭鍏ユ彁鐜版渶浣庨噾棰�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '鐢ㄦ埛鎻愮幇鏈�浣庨噾棰濋檺鍒讹紝鍗曚綅锛氬厓',
- },
- {
- fieldName: 'brokerageWithdrawFeePercent',
- label: '鎻愮幇鎵嬬画璐癸紙%锛�',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- max: 100,
- precision: 2,
- placeholder: '璇疯緭鍏ユ彁鐜版墜缁垂鐧惧垎姣�',
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '鎻愮幇鎵嬬画璐圭櫨鍒嗘瘮锛岃寖鍥� 0-100锛�0 涓烘棤鎻愮幇鎵嬬画璐广�備緥锛氳缃� 10锛屽嵆鏀跺彇 10% 鎵嬬画璐癸紝鎻愮幇10 鍏冿紝鍒拌处 9 鍏冿紝1 鍏冩墜缁垂',
- },
- {
- fieldName: 'brokerageWithdrawTypes',
- label: '鎻愮幇鏂瑰紡',
- component: 'CheckboxGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, 'number'),
- },
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- show: (values) => values.type === 'brokerage',
- },
- help: '鍟嗗煄寮�閫氭彁鐜扮殑浠樻鏂瑰紡',
- },
-];
diff --git a/src/views/mall/trade/config/index.vue b/src/views/mall/trade/config/index.vue
deleted file mode 100644
index 4c33918..0000000
--- a/src/views/mall/trade/config/index.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<script lang="ts" setup>
-import type { MallTradeConfigApi } from '#/api/mall/trade/config';
-
-import { onMounted, ref } from 'vue';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-import { fenToYuan, yuanToFen } from '../../../../packages/utils/src';
-
-import { Card, message, Tabs } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getTradeConfig, saveTradeConfig } from '#/api/mall/trade/config';
-import { $t } from '#/locales';
-
-import { schema } from './data';
-
-const activeKey = ref('afterSale');
-const formData = ref<MallTradeConfigApi.Config & { type?: string }>();
-
-/** 鑾峰彇閰嶇疆 */
-async function getConfigInfo() {
- const res = await getTradeConfig();
- if (!res) {
- return;
- }
- formData.value = res;
- // 杞崲閲戦鍗曚綅
- formData.value.deliveryExpressFreePrice = Number.parseFloat(
- fenToYuan(formData.value.deliveryExpressFreePrice!),
- );
- formData.value.brokerageWithdrawMinPrice = Number.parseFloat(
- fenToYuan(formData.value.brokerageWithdrawMinPrice!),
- );
- formData.value!.type = activeKey.value;
- formApi.updateSchema(schema);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
-}
-
-/** 鍒囨崲 Tab */
-function handleTabChange(key: any) {
- activeKey.value = key;
- formData.value!.type = activeKey.value;
- formApi.setValues(formData.value!);
- formApi.updateSchema(schema);
-}
-
-/** 鎻愪氦琛ㄥ崟 */
-async function handleSubmit() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MallTradeConfigApi.Config;
- // 杞崲閲戦鍗曚綅
- data.deliveryExpressFreePrice = yuanToFen(data.deliveryExpressFreePrice!);
- data.brokerageWithdrawMinPrice = yuanToFen(data.brokerageWithdrawMinPrice!);
- await saveTradeConfig(data);
- message.success($t('ui.actionMessage.operationSuccess'));
-}
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- labelWidth: 150,
- },
- layout: 'horizontal',
- handleSubmit,
- schema,
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- getConfigInfo();
-});
-</script>
-
-<template>
- <Page>
- <template #doc>
- <DocAlert
- title="銆愪氦鏄撱�戜氦鏄撹鍗�"
- url="https://doc.iocoder.cn/mall/trade-order/"
- />
- <DocAlert
- title="銆愪氦鏄撱�戣喘鐗╄溅"
- url="https://doc.iocoder.cn/mall/trade-cart/"
- />
- </template>
- <Card>
- <Tabs :active-key="activeKey" @change="handleTabChange">
- <Tabs.TabPane tab="鍞悗" key="afterSale" :force-render="true" />
- <Tabs.TabPane tab="閰嶉��" key="delivery" :force-render="true" />
- <Tabs.TabPane tab="鍒嗛攢" key="brokerage" :force-render="true" />
- </Tabs>
- <Form class="w-2/5" />
- </Card>
- </Page>
-</template>
diff --git a/src/views/mall/trade/delivery/express/data.ts b/src/views/mall/trade/delivery/express/data.ts
deleted file mode 100644
index 4fbc621..0000000
--- a/src/views/mall/trade/delivery/express/data.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'code',
- label: '鍏徃缂栫爜',
- componentProps: {
- placeholder: '璇疯緭鍏ュ揩閫掔紪鐮�',
- },
- rules: 'required',
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: '鍏徃鍚嶇О',
- componentProps: {
- placeholder: '璇疯緭鍏ュ揩閫掑悕绉�',
- },
- rules: 'required',
- },
- {
- component: 'ImageUpload',
- fieldName: 'logo',
- label: '鍏徃 logo',
- rules: 'required',
- help: '鎺ㄨ崘 180x180 鍥剧墖鍒嗚鲸鐜�',
- },
- {
- fieldName: 'sort',
- label: '鏄剧ず椤哄簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ユ樉绀洪『搴�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '蹇�掑叕鍙稿悕绉�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ揩閫掑叕鍙稿悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'code',
- label: '蹇�掑叕鍙哥紪鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ揩閫掑叕鍙哥紪鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'code',
- title: '鍏徃缂栫爜',
- minWidth: 120,
- },
- {
- field: 'name',
- title: '鍏徃鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'logo',
- title: '鍏徃 logo',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'sort',
- title: '鏄剧ず椤哄簭',
- minWidth: 100,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 130,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/trade/delivery/express/index.vue b/src/views/mall/trade/delivery/express/index.vue
deleted file mode 100644
index 4694803..0000000
--- a/src/views/mall/trade/delivery/express/index.vue
+++ /dev/null
@@ -1,143 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDeliveryExpressApi } from '#/api/mall/trade/delivery/express';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteDeliveryExpress,
- exportDeliveryExpress,
- getDeliveryExpressPage,
-} from '#/api/mall/trade/delivery/express';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
- const data = await exportDeliveryExpress(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '蹇�掑叕鍙�.xls', source: data });
-}
-
-/** 鍒涘缓蹇�掑叕鍙� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫蹇�掑叕鍙� */
-function handleEdit(row: MallDeliveryExpressApi.DeliveryExpress) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎蹇�掑叕鍙� */
-async function handleDelete(row: MallDeliveryExpressApi.DeliveryExpress) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDeliveryExpress(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDeliveryExpressPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDeliveryExpressApi.DeliveryExpress>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="蹇�掑叕鍙稿垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['蹇�掑叕鍙�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['trade:delivery:express:create'],
- onClick: handleCreate,
- },
- {
- label: $t('ui.actionTitle.export'),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['trade:delivery:express:export'],
- onClick: handleExport,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['trade:delivery:express:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['trade:delivery:express:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/delivery/express/modules/form.vue b/src/views/mall/trade/delivery/express/modules/form.vue
deleted file mode 100644
index f58b004..0000000
--- a/src/views/mall/trade/delivery/express/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryExpressApi } from '#/api/mall/trade/delivery/express';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createDeliveryExpress,
- getDeliveryExpress,
- updateDeliveryExpress,
-} from '#/api/mall/trade/delivery/express';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDeliveryExpressApi.DeliveryExpress>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['蹇�掑叕鍙�'])
- : $t('ui.actionTitle.create', ['蹇�掑叕鍙�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallDeliveryExpressApi.DeliveryExpress;
- try {
- await (formData.value?.id
- ? updateDeliveryExpress(data)
- : createDeliveryExpress(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallDeliveryExpressApi.DeliveryExpress>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDeliveryExpress(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/delivery/expressTemplate/data.ts b/src/views/mall/trade/delivery/expressTemplate/data.ts
deleted file mode 100644
index c9307c6..0000000
--- a/src/views/mall/trade/delivery/expressTemplate/data.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-
-/** 璁¤垂鏂瑰紡鍒楁爣棰樻槧灏� */
-export const CHARGE_MODE_TITLE_MAP: Record<
- number,
- {
- extraCountTitle: string;
- startCountTitle: string;
- }
-> = {
- 1: { startCountTitle: '棣栦欢', extraCountTitle: '缁欢' },
- 2: { startCountTitle: '棣栦欢閲嶉噺(kg)', extraCountTitle: '缁欢閲嶉噺(kg)' },
- 3: { startCountTitle: '棣栦欢浣撶Н(m鲁)', extraCountTitle: '缁欢浣撶Н(m鲁)' },
-};
-
-/** 鍖呴偖鏂瑰紡鍒楁爣棰樻槧灏� */
-export const FREE_MODE_TITLE_MAP: Record<number, { freeCountTitle: string }> = {
- 1: { freeCountTitle: '鍖呴偖浠舵暟' },
- 2: { freeCountTitle: '鍖呴偖閲嶉噺(kg)' },
- 3: { freeCountTitle: '鍖呴偖浣撶Н(m鲁)' },
-};
-
-/** 杩愯垂璁剧疆琛ㄦ牸鍒� */
-export function useChargesColumns(
- chargeMode = 1,
-): VxeTableGridOptions['columns'] {
- const chargeTitleMap = CHARGE_MODE_TITLE_MAP[chargeMode];
- return [
- {
- field: 'areaIds',
- title: '鍖哄煙',
- minWidth: 300,
- slots: { default: 'areaIds' },
- },
- {
- field: 'startCount',
- title: chargeTitleMap?.startCountTitle,
- width: 120,
- slots: { default: 'startCount' },
- },
- {
- field: 'startPrice',
- title: '杩愯垂(鍏�)',
- width: 120,
- slots: { default: 'startPrice' },
- },
- {
- field: 'extraCount',
- title: chargeTitleMap?.extraCountTitle,
- width: 120,
- slots: { default: 'extraCount' },
- },
- {
- field: 'extraPrice',
- title: '缁垂(鍏�)',
- width: 120,
- slots: { default: 'extraPrice' },
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍖呴偖璁剧疆琛ㄦ牸鍒� */
-export function useFreesColumns(
- chargeMode = 1,
-): VxeTableGridOptions['columns'] {
- const freeTitleMap = FREE_MODE_TITLE_MAP[chargeMode];
- return [
- {
- field: 'areaIds',
- title: '鍖哄煙',
- minWidth: 300,
- slots: { default: 'areaIds' },
- },
- {
- field: 'freeCount',
- title: freeTitleMap?.freeCountTitle,
- width: 120,
- slots: { default: 'freeCount' },
- },
- {
- field: 'freePrice',
- title: '鍖呴偖閲戦(鍏�)',
- width: 120,
- slots: { default: 'freePrice' },
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: '妯℃澘鍚嶇О',
- componentProps: {
- placeholder: '璇疯緭鍏ユā鏉垮悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'chargeMode',
- label: '璁¤垂鏂瑰紡',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(1),
- },
- {
- fieldName: 'sort',
- label: '鏄剧ず椤哄簭',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ樉绀洪『搴�',
- min: 0,
- },
- rules: 'required',
- },
- {
- fieldName: 'charges',
- label: '杩愯垂璁剧疆',
- component: 'Input',
- formItemClass: 'col-span-3',
- },
- {
- fieldName: 'frees',
- label: '鍖呴偖璁剧疆',
- component: 'Input',
- formItemClass: 'col-span-3',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '妯℃澘鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユā鏉垮悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'chargeMode',
- label: '璁¤垂鏂瑰紡',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨璁¤垂鏂瑰紡',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE, 'number'),
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '妯℃澘鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'chargeMode',
- title: '璁¤垂鏂瑰紡',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.EXPRESS_CHARGE_MODE },
- },
- },
- {
- field: 'sort',
- title: '鏄剧ず椤哄簭',
- minWidth: 100,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 130,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/trade/delivery/expressTemplate/index.vue b/src/views/mall/trade/delivery/expressTemplate/index.vue
deleted file mode 100644
index 33a051d..0000000
--- a/src/views/mall/trade/delivery/expressTemplate/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDeliveryExpressTemplateApi } from '#/api/mall/trade/delivery/expressTemplate';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteDeliveryExpressTemplate,
- getDeliveryExpressTemplatePage,
-} from '#/api/mall/trade/delivery/expressTemplate';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓蹇�掓ā鏉� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫蹇�掓ā鏉� */
-function handleEdit(
- row: MallDeliveryExpressTemplateApi.DeliveryExpressTemplate,
-) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎蹇�掓ā鏉� */
-async function handleDelete(
- row: MallDeliveryExpressTemplateApi.DeliveryExpressTemplate,
-) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDeliveryExpressTemplate(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDeliveryExpressTemplatePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="蹇�掓ā鏉垮垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['蹇�掓ā鏉�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['trade:delivery:express-template:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['trade:delivery:express-template:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['trade:delivery:express-template:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue b/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue
deleted file mode 100644
index 77427ca..0000000
--- a/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue
+++ /dev/null
@@ -1,220 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryExpressTemplateApi } from '#/api/mall/trade/delivery/expressTemplate';
-import type { SystemAreaApi } from '#/api/system/area';
-
-import { computed, nextTick, ref, watch } from 'vue';
-
-import { InputNumber, TreeSelect } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-
-import { CHARGE_MODE_TITLE_MAP, useChargesColumns } from '../data';
-
-interface Props {
- items?: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateCharge[];
- chargeMode?: number;
- areaTree?: SystemAreaApi.Area[];
-}
-
-const props = withDefaults(defineProps<Props>(), {
- items: () => [],
- chargeMode: 1,
- areaTree: () => [],
-});
-
-const emit = defineEmits(['update:items']);
-
-const tableData = ref<any[]>([]);
-const columnTitle = computed(() => CHARGE_MODE_TITLE_MAP[props.chargeMode]);
-
-/** 琛ㄦ牸閰嶇疆 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useChargesColumns(props.chargeMode),
- data: tableData.value,
- minHeight: 200,
- autoResize: true,
- border: true,
- rowConfig: {
- keyField: 'seq',
- isHover: true,
- },
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- enabled: false,
- },
- },
-});
-
-/** 鐩戝惉澶栭儴浼犲叆鐨勬暟鎹� */
-watch(
- () => props.items,
- async (items) => {
- if (!items) {
- return;
- }
- tableData.value = [...items];
- await nextTick();
- await gridApi.grid.reloadData(tableData.value);
- },
- {
- immediate: true,
- },
-);
-
-/** 鐩戝惉璁¤垂鏂瑰紡鍙樺寲 */
-watch(
- () => props.chargeMode,
- () => {
- const columns = useChargesColumns(props.chargeMode);
- if (gridApi.grid && columns) {
- gridApi.grid.reloadColumn(columns);
- }
- },
-);
-
-/** 澶勭悊鏂板 */
-function handleAdd() {
- const newRow = {
- areaIds: [],
- startCount: undefined,
- startPrice: undefined,
- extraCount: undefined,
- extraPrice: undefined,
- };
- tableData.value.push(newRow);
- emit('update:items', [...tableData.value]);
-}
-
-/** 澶勭悊鍒犻櫎 */
-function handleDelete(row: any) {
- const index = tableData.value.findIndex((item) => item.seq === row.seq);
- if (index !== -1) {
- tableData.value.splice(index, 1);
- }
- emit('update:items', [...tableData.value]);
-}
-
-/** 澶勭悊琛屾暟鎹彉鏇� */
-function handleRowChange(row: any) {
- const index = tableData.value.findIndex((item) => item.seq === row.seq);
- if (index === -1) {
- tableData.value.push(row);
- } else {
- tableData.value[index] = row;
- }
- emit('update:items', [...tableData.value]);
-}
-
-/** 琛ㄥ崟鏍¢獙 */
-function validate() {
- for (let i = 0; i < tableData.value.length; i++) {
- const item = tableData.value[i];
- if (!item.areaIds || item.areaIds.length === 0) {
- throw new Error(`杩愯垂璁剧疆绗� ${i + 1} 琛岋細鍖哄煙涓嶈兘涓虹┖`);
- }
- if (!item.startCount || item.startCount <= 0) {
- throw new Error(
- `杩愯垂璁剧疆绗� ${i + 1} 琛岋細${columnTitle.value?.startCountTitle}蹇呴』澶т簬 0`,
- );
- }
- if (!item.startPrice || item.startPrice <= 0) {
- throw new Error(`杩愯垂璁剧疆绗� ${i + 1} 琛岋細杩愯垂蹇呴』澶т簬0`);
- }
- if (!item.extraCount || item.extraCount <= 0) {
- throw new Error(
- `杩愯垂璁剧疆绗� ${i + 1} 琛岋細${columnTitle.value?.extraCountTitle}蹇呴』澶т簬 0`,
- );
- }
- if (!item.extraPrice || item.extraPrice <= 0) {
- throw new Error(`杩愯垂璁剧疆绗� ${i + 1} 琛岋細缁垂蹇呴』澶т簬 0`);
- }
- }
-}
-
-defineExpose({
- validate,
-});
-</script>
-
-<template>
- <Grid class="w-full">
- <template #areaIds="{ row }">
- <!-- TODO 鑺嬭壙锛氬彲浼樺寲锛屼娇鐢� Cascade銆備笉杩囪矊浼� antd 鍦� multiple 璨屼技鏈� bug锛� -->
- <TreeSelect
- v-model:value="row.areaIds"
- :tree-data="areaTree"
- :field-names="{
- label: 'name',
- value: 'id',
- children: 'children',
- }"
- placeholder="璇烽�夋嫨鍦板尯"
- class="w-full"
- multiple
- tree-checkable
- :max-tag-count="1"
- @change="handleRowChange(row)"
- />
- </template>
- <template #startCount="{ row }">
- <InputNumber
- v-model:value="row.startCount"
- :min="1"
- @change="handleRowChange(row)"
- />
- </template>
- <template #startPrice="{ row }">
- <InputNumber
- v-model:value="row.startPrice"
- :min="0"
- :precision="2"
- @change="handleRowChange(row)"
- />
- </template>
- <template #extraCount="{ row }">
- <InputNumber
- v-model:value="row.extraCount"
- :min="1"
- @change="handleRowChange(row)"
- />
- </template>
- <template #extraPrice="{ row }">
- <InputNumber
- v-model:value="row.extraPrice"
- :min="0"
- :precision="2"
- @change="handleRowChange(row)"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- popConfirm: {
- title: '纭鍒犻櫎璇ュ尯鍩熷悧锛�',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- <template #bottom>
- <TableAction
- class="mt-2 flex justify-center"
- :actions="[
- {
- label: '娣诲姞璁¤垂鍖哄煙',
- type: 'default',
- onClick: handleAdd,
- },
- ]"
- />
- </template>
- </Grid>
-</template>
diff --git a/src/views/mall/trade/delivery/expressTemplate/modules/form.vue b/src/views/mall/trade/delivery/expressTemplate/modules/form.vue
deleted file mode 100644
index 69760b4..0000000
--- a/src/views/mall/trade/delivery/expressTemplate/modules/form.vue
+++ /dev/null
@@ -1,197 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryExpressTemplateApi } from '#/api/mall/trade/delivery/expressTemplate';
-import type { SystemAreaApi } from '#/api/system/area';
-
-import { computed, onMounted, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-import { cloneDeep, fenToYuan, yuanToFen } from '../../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createDeliveryExpressTemplate,
- getDeliveryExpressTemplate,
- updateDeliveryExpressTemplate,
-} from '#/api/mall/trade/delivery/expressTemplate';
-import { getAreaTree } from '#/api/system/area';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-import ChargeItemForm from './charge-item-form.vue';
-import FreeItemForm from './free-item-form.vue';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>();
-const chargeItemFormRef = ref<InstanceType<typeof ChargeItemForm>>();
-const freeItemFormRef = ref<InstanceType<typeof FreeItemForm>>();
-const areaTree = ref<SystemAreaApi.Area[]>([]);
-
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['蹇�掓ā鏉�'])
- : $t('ui.actionTitle.create', ['蹇�掓ā鏉�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 120,
- },
- wrapperClass: 'grid-cols-3',
- layout: 'vertical',
- schema: useFormSchema(),
- showDefaultActions: false,
- handleValuesChange: (values, changedFields) => {
- // 鐩殑锛氳Е鍙戝瓙琛ㄥ崟鐨� columns 鍙樺寲
- if (changedFields.includes('chargeMode')) {
- formData.value!.chargeMode = values.chargeMode;
- }
- },
-});
-
-/** 鏇存柊杩愯垂璁剧疆 */
-const handleUpdateCharges = async (
- charges: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateCharge[],
-) => {
- formData.value =
- await formApi.getValues<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>();
- formData.value.charges = charges;
- await formApi.setValues({
- charges,
- });
-};
-
-/** 鏇存柊鍖呴偖璁剧疆 */
-const handleUpdateFrees = async (
- frees: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateFree[],
-) => {
- formData.value =
- await formApi.getValues<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>();
- formData.value.frees = frees;
- await formApi.setValues({
- frees,
- });
-};
-
-/** 鍒涘缓鎴栨洿鏂板揩閫掓ā鏉� */
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- const chargeFormInstance = Array.isArray(chargeItemFormRef.value)
- ? chargeItemFormRef.value[0]
- : chargeItemFormRef.value;
- const freeFormInstance = Array.isArray(freeItemFormRef.value)
- ? freeItemFormRef.value[0]
- : freeItemFormRef.value;
- try {
- chargeFormInstance.validate();
- freeFormInstance.validate();
- } catch (error: any) {
- message.error(error.message || '瀛愯〃鍗曢獙璇佸け璐�');
- return;
- }
-
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = cloneDeep(
- await formApi.getValues(),
- ) as MallDeliveryExpressTemplateApi.DeliveryExpressTemplate;
- try {
- // 杞崲閲戦鍗曚綅
- data.charges?.forEach(
- (
- item: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateCharge,
- ) => {
- item.startPrice = yuanToFen(item.startPrice);
- item.extraPrice = yuanToFen(item.extraPrice);
- },
- );
- data.frees?.forEach(
- (item: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateFree) => {
- item.freePrice = yuanToFen(item.freePrice);
- },
- );
- await (formData.value?.id
- ? updateDeliveryExpressTemplate(data)
- : createDeliveryExpressTemplate(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data =
- modalApi.getData<MallDeliveryExpressTemplateApi.DeliveryExpressTemplate>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDeliveryExpressTemplate(data.id);
- // 杞崲閲戦鍗曚綅
- formData.value.charges?.forEach(
- (
- item: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateCharge,
- ) => {
- item.startPrice = Number.parseFloat(fenToYuan(item.startPrice));
- item.extraPrice = Number.parseFloat(fenToYuan(item.extraPrice));
- },
- );
- formData.value.frees?.forEach(
- (item: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateFree) => {
- item.freePrice = Number.parseFloat(fenToYuan(item.freePrice));
- },
- );
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- areaTree.value = await getAreaTree();
-});
-</script>
-
-<template>
- <Modal class="w-1/2" :title="getTitle">
- <Form class="mx-3">
- <template #charges>
- <ChargeItemForm
- ref="chargeItemFormRef"
- :items="formData?.charges"
- :charge-mode="formData?.chargeMode"
- :area-tree="areaTree"
- @update:items="handleUpdateCharges"
- />
- </template>
- <template #frees>
- <FreeItemForm
- ref="freeItemFormRef"
- :items="formData?.frees"
- :charge-mode="formData?.chargeMode"
- :area-tree="areaTree"
- @update:items="handleUpdateFrees"
- />
- </template>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue b/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue
deleted file mode 100644
index 1c8531a..0000000
--- a/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue
+++ /dev/null
@@ -1,195 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryExpressTemplateApi } from '#/api/mall/trade/delivery/expressTemplate';
-import type { SystemAreaApi } from '#/api/system/area';
-
-import { computed, nextTick, ref, watch } from 'vue';
-
-import { InputNumber, TreeSelect } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-
-import { FREE_MODE_TITLE_MAP, useFreesColumns } from '../data';
-
-interface Props {
- items?: MallDeliveryExpressTemplateApi.DeliveryExpressTemplateFree[];
- chargeMode?: number;
- areaTree?: SystemAreaApi.Area[];
-}
-
-const props = withDefaults(defineProps<Props>(), {
- items: () => [],
- chargeMode: 1,
- areaTree: () => [],
-});
-
-const emit = defineEmits(['update:items']);
-
-const tableData = ref<any[]>([]);
-const columnTitle = computed(() => FREE_MODE_TITLE_MAP[props.chargeMode]);
-
-/** 琛ㄦ牸閰嶇疆 */
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useFreesColumns(props.chargeMode),
- data: tableData.value,
- minHeight: 200,
- autoResize: true,
- border: true,
- rowConfig: {
- keyField: 'seq',
- isHover: true,
- },
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- enabled: false,
- },
- },
-});
-
-/** 鐩戝惉澶栭儴浼犲叆鐨勬暟鎹� */
-watch(
- () => props.items,
- async (items) => {
- if (!items) {
- return;
- }
- tableData.value = [...items];
- await nextTick();
- await gridApi.grid.reloadData(tableData.value);
- },
- {
- immediate: true,
- },
-);
-
-/** 鐩戝惉璁¤垂鏂瑰紡鍙樺寲 */
-watch(
- () => props.chargeMode,
- () => {
- const columns = useFreesColumns(props.chargeMode);
- if (gridApi.grid && columns) {
- gridApi.grid.reloadColumn(columns);
- }
- },
-);
-
-/** 澶勭悊鏂板 */
-function handleAdd() {
- const newRow = {
- areaIds: [],
- freeCount: undefined,
- freePrice: undefined,
- };
- tableData.value.push(newRow);
- emit('update:items', [...tableData.value]);
-}
-
-/** 澶勭悊鍒犻櫎 */
-function handleDelete(row: any) {
- const index = tableData.value.findIndex((item) => item.seq === row.seq);
- if (index !== -1) {
- tableData.value.splice(index, 1);
- }
- emit('update:items', [...tableData.value]);
-}
-
-/** 澶勭悊琛屾暟鎹彉鏇� */
-function handleRowChange(row: any) {
- const index = tableData.value.findIndex((item) => item.seq === row.seq);
- if (index === -1) {
- tableData.value.push(row);
- } else {
- tableData.value[index] = row;
- }
- emit('update:items', [...tableData.value]);
-}
-
-/** 琛ㄥ崟鏍¢獙 */
-function validate() {
- for (let i = 0; i < tableData.value.length; i++) {
- const item = tableData.value[i];
- if (!item.areaIds || item.areaIds.length === 0) {
- throw new Error(`鍖呴偖璁剧疆绗� ${i + 1} 琛岋細鍖哄煙涓嶈兘涓虹┖`);
- }
- if (!item.freeCount || item.freeCount <= 0) {
- throw new Error(
- `鍖呴偖璁剧疆绗� ${i + 1} 琛岋細${columnTitle.value?.freeCountTitle}蹇呴』澶т簬 0`,
- );
- }
- if (!item.freePrice || item.freePrice <= 0) {
- throw new Error(`鍖呴偖璁剧疆绗� ${i + 1} 琛岋細鍖呴偖閲戦蹇呴』澶т簬 0`);
- }
- }
-}
-
-defineExpose({
- validate,
-});
-</script>
-
-<template>
- <Grid class="w-full">
- <template #areaIds="{ row }">
- <!-- TODO 鑺嬭壙锛氬彲浼樺寲锛屼娇鐢� Cascade銆備笉杩囪矊浼� antd 鍦� multiple 璨屼技鏈� bug锛� -->
- <TreeSelect
- v-model:value="row.areaIds"
- :tree-data="areaTree"
- :field-names="{
- label: 'name',
- value: 'id',
- children: 'children',
- }"
- placeholder="璇烽�夋嫨鍦板尯"
- class="w-full"
- multiple
- tree-checkable
- :max-tag-count="1"
- @change="handleRowChange(row)"
- />
- </template>
- <template #freeCount="{ row }">
- <InputNumber
- v-model:value="row.freeCount"
- :min="1"
- @change="handleRowChange(row)"
- />
- </template>
- <template #freePrice="{ row }">
- <InputNumber
- v-model:value="row.freePrice"
- :min="0"
- :precision="2"
- @change="handleRowChange(row)"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- popConfirm: {
- title: '纭鍒犻櫎璇ュ尯鍩熷悧锛�',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- <template #bottom>
- <TableAction
- class="mt-2 flex justify-center"
- :actions="[
- {
- label: '娣诲姞鍖呴偖鍖哄煙',
- type: 'default',
- onClick: handleAdd,
- },
- ]"
- />
- </template>
- </Grid>
-</template>
diff --git a/src/views/mall/trade/delivery/pickUpOrder/data.ts b/src/views/mall/trade/delivery/pickUpOrder/data.ts
deleted file mode 100644
index e6dc0bb..0000000
--- a/src/views/mall/trade/delivery/pickUpOrder/data.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-
-import { ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { useUserStore } from '../../../../../packages/stores/src';
-
-import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-const userStore = useUserStore();
-const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.DeliveryPickUpStore[]>(
- [],
-);
-getSimpleDeliveryPickUpStoreList().then((res) => {
- pickUpStoreList.value = res;
- // 绉婚櫎鑷繁鏃犳硶鏍搁攢鐨勯棬搴�
- const userId = userStore?.userInfo?.id;
- pickUpStoreList.value = pickUpStoreList.value.filter((item) =>
- item.verifyUserIds?.includes(userId),
- );
-});
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- {
- fieldName: 'pickUpStoreIds',
- label: '鑷彁闂ㄥ簵',
- component: 'Select',
- componentProps: {
- options: pickUpStoreList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鑷彁闂ㄥ簵',
- },
- defaultValue: pickUpStoreList.value[0]?.id,
- },
- {
- fieldName: 'no',
- label: '璁㈠崟鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍗曞彿',
- allowClear: true,
- },
- },
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛 UID',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴� UID',
- allowClear: true,
- },
- },
- {
- fieldName: 'userNickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋樀绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'userMobile',
- label: '鐢ㄦ埛鐢佃瘽',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴风數璇�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- field: 'no',
- title: '璁㈠崟鍙�',
- fixed: 'left',
- minWidth: 180,
- },
- {
- field: 'user.nickname',
- title: '鐢ㄦ埛淇℃伅',
- minWidth: 100,
- },
- {
- field: 'brokerageUser.nickname',
- title: '鎺ㄨ崘浜轰俊鎭�',
- minWidth: 100,
- },
- {
- field: 'spuName',
- title: '鍟嗗搧淇℃伅',
- minWidth: 300,
- slots: { default: 'spuName' },
- },
- {
- field: 'payPrice',
- title: '瀹炰粯閲戦(鍏�)',
- formatter: 'formatAmount2',
- minWidth: 180,
- },
- {
- field: 'storeStaffName',
- title: '鏍搁攢鍛�',
- minWidth: 160,
- },
- {
- field: 'pickUpStoreId',
- title: '鏍搁攢闂ㄥ簵',
- minWidth: 160,
- formatter: ({ row }) => {
- return (
- pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId)
- ?.name || ''
- );
- },
- },
- {
- field: 'payStatus',
- title: '鏀粯鐘舵��',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- minWidth: 80,
- },
- {
- field: 'status',
- title: '璁㈠崟鐘舵��',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TRADE_ORDER_STATUS },
- },
- minWidth: 80,
- },
- {
- field: 'createTime',
- title: '涓嬪崟鏃堕棿',
- formatter: 'formatDateTime',
- minWidth: 160,
- },
- ];
-}
diff --git a/src/views/mall/trade/delivery/pickUpOrder/index.vue b/src/views/mall/trade/delivery/pickUpOrder/index.vue
deleted file mode 100644
index e06873c..0000000
--- a/src/views/mall/trade/delivery/pickUpOrder/index.vue
+++ /dev/null
@@ -1,289 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { h, ref } from 'vue';
-
-import { Page, prompt, SummaryCard } from '../../../../../packages/effects/common-ui/src';
-import { DeliveryTypeEnum } from '../../../../../packages/constants/src';
-import { $t } from '../../../../../packages/locales/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { Card, Image, Input, message, Tag } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- getOrderPage,
- getOrderSummary,
- pickUpOrderByVerifyCode,
-} from '#/api/mall/trade/order';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-const summary = ref<MallOrderApi.OrderSummaryRespVO>();
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鑾峰彇璁㈠崟缁熻鏁版嵁 */
-async function getOrderSum() {
- const query = await gridApi.formApi.getValues();
- query.deliveryType = DeliveryTypeEnum.PICK_UP.type;
- summary.value = await getOrderSummary(query);
-}
-
-/** 鏍搁攢璁㈠崟 */
-async function handlePickup(pickUpVerifyCode?: string) {
- // 濡傛灉娌℃湁浼犳牳閿�鐮侊紝鍒欏脊绐楄緭鍏�
- if (!pickUpVerifyCode) {
- await prompt({
- component: () => {
- return h(Input, {});
- },
- content: '璇疯緭鍏ユ牳閿�鐮�',
- title: '鏍搁攢璁㈠崟',
- modelPropName: 'value',
- }).then(async (val) => {
- if (val) {
- pickUpVerifyCode = val;
- }
- });
- }
- if (!pickUpVerifyCode) {
- return;
- }
-
- // 鎵ц鏍搁攢
- const hideLoading = message.loading({
- content: '璁㈠崟鏍搁攢涓� ...',
- duration: 0,
- });
- try {
- await pickUpOrderByVerifyCode(pickUpVerifyCode);
- message.success($t('ui.actionMessage.operationSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const port = ref('');
-const ports = ref([]);
-const reader = ref('');
-const serialPort = ref(false); // 鏄惁杩炴帴鎵爜鏋�
-
-/** 杩炴帴鎵爜鏋� */
-async function connectToSerialPort() {
- try {
- // 鍒ゆ柇娴忚鍣ㄦ敮鎸佷覆鍙i�氫俊
- if (
- 'serial' in navigator &&
- navigator.serial !== null &&
- typeof navigator.serial === 'object' &&
- 'requestPort' in navigator.serial
- ) {
- // 鎻愮ず鐢ㄦ埛閫夋嫨涓�涓覆鍙�
- port.value = await (navigator.serial as any).requestPort();
- } else {
- message.error('娴忚鍣ㄤ笉鏀寔鎵爜鏋繛鎺ワ紝璇锋洿鎹㈡祻瑙堝櫒閲嶈瘯');
- return;
- }
-
- // 鑾峰彇鐢ㄦ埛涔嬪墠鎺堜簣璇ョ綉绔欒闂潈闄愮殑鎵�鏈変覆鍙�
- ports.value = await (navigator.serial as any).getPorts();
-
- // 绛夊緟涓插彛鎵撳紑
- await (port.value as any).open({
- baudRate: 9600,
- dataBits: 8,
- stopBits: 2,
- });
-
- message.success('鎴愬姛杩炴帴鎵爜鏋�');
- serialPort.value = true;
- await readData();
- } catch (error) {
- // 澶勭悊杩炴帴涓插彛鍑洪敊鐨勬儏鍐�
- console.error('Error connecting to serial port:', error);
- }
-}
-
-/** 鐩戝惉鎵爜鏋緭鍏� */
-async function readData() {
- reader.value = (port.value as any).readable.getReader();
- let data = ''; // 鎵爜鏁版嵁
- // 鐩戝惉鏉ヨ嚜涓插彛鐨勬暟鎹�
- while (true) {
- const { value, done } = await (reader.value as any).read();
- if (done) {
- // 鍏佽绋嶅悗鍏抽棴涓插彛
- (reader.value as any).releaseLock();
- break;
- }
- // 鑾峰彇鍙戦�佺殑鏁版嵁
- const serialData = new TextDecoder().decode(value);
- data = `${data}${serialData}`;
- if (serialData.includes('\r')) {
- // 璇诲彇缁撴潫
- const codeData = data.replace('\r', '');
- data = ''; // 娓呯┖涓嬫璇诲彇涓嶄細鍙犲姞
- console.warn(`浜岀淮鐮佹暟鎹�:${codeData}`);
- // 澶勭悊鎷垮埌鏁版嵁閫昏緫
- await handlePickup(codeData);
- }
- }
-}
-
-/** 鏂紑鎵爜鏋繛鎺� */
-async function cutPort() {
- if (port.value === '') {
- message.warning('璇峰厛杩炴帴鎴栨墦寮�鎵爜鏋�');
- } else {
- await (reader.value as any).cancel();
- await (port.value as any).close();
- port.value = '';
- console.warn('鏂紑鎵爜鏋繛鎺�');
- message.success('宸叉垚鍔熸柇寮�鎵爜鏋繛鎺�');
- serialPort.value = false;
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 80,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- await getOrderSum();
- return await getOrderPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- deliveryType: DeliveryTypeEnum.PICK_UP.type,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallOrderApi.Order>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Card class="mb-2">
- <div class="flex flex-row gap-4">
- <SummaryCard
- class="flex flex-1"
- title="璁㈠崟鏁伴噺"
- icon="icon-park-outline:transaction-order"
- icon-color="bg-blue-100"
- icon-bg-color="text-blue-500"
- :value="summary?.orderCount || 0"
- />
- <SummaryCard
- class="flex flex-1"
- title="璁㈠崟閲戦"
- icon="streamline:money-cash-file-dollar-common-money-currency-cash-file"
- icon-color="bg-purple-100"
- icon-bg-color="text-purple-500"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(summary?.orderPayPrice || 0))"
- />
- <SummaryCard
- class="flex flex-1"
- title="閫�娆惧崟鏁�"
- icon="heroicons:receipt-refundAfterSale"
- icon-color="bg-yellow-100"
- icon-bg-color="text-yellow-500"
- :value="summary?.afterSaleCount || 0"
- />
- <SummaryCard
- class="flex flex-1"
- title="閫�娆鹃噾棰�"
- icon="ri:refundAfterSale-2-line"
- icon-color="bg-green-100"
- icon-bg-color="text-green-500"
- prefix="锟�"
- :decimals="2"
- :value="Number(fenToYuan(summary?.afterSalePrice || 0))"
- />
- </div>
- </Card>
-
- <Grid class="h-4/5" table-title="鏍搁攢璁㈠崟">
- <template #spuName="{ row }">
- <div class="flex flex-col gap-2">
- <div
- v-for="item in row.items"
- :key="item.id!"
- class="flex items-start gap-2 text-left"
- >
- <Image
- :src="item.picUrl"
- :alt="item.spuName"
- :width="60"
- :height="60"
- :preview="{
- src: item.picUrl,
- }"
- />
- <div class="flex flex-1 flex-col gap-1">
- <span class="text-sm">{{ item.spuName }}</span>
- <div class="flex flex-wrap gap-1">
- <Tag
- v-for="property in item.properties"
- :key="property.propertyId!"
- size="small"
- >
- {{ property.propertyName }}: {{ property.valueName }}
- </Tag>
- </div>
- <span class="text-xs text-gray-500">
- {{ fenToYuan(item.price!) }} 鍏� x {{ item.count }}
- </span>
- </div>
- </div>
- </div>
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鏍搁攢',
- type: 'primary',
- icon: 'lucide:circle-check-big',
- auth: ['trade:order:pick-up'],
- onClick: handlePickup.bind(null, undefined),
- },
- {
- label: serialPort ? '鏂紑鎵弿鏋�' : '杩炴帴鎵弿鏋�',
- type: 'primary',
- icon: serialPort ? 'lucide:circle-x' : 'lucide:circle-play',
- danger: serialPort,
- onClick: serialPort ? cutPort : connectToSerialPort,
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/delivery/pickUpStore/data.ts b/src/views/mall/trade/delivery/pickUpStore/data.ts
deleted file mode 100644
index 4ff7d36..0000000
--- a/src/views/mall/trade/delivery/pickUpStore/data.ts
+++ /dev/null
@@ -1,267 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getSimpleUserList } from '#/api/system/user';
-import { AreaCascader } from '#/components/area';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: '闂ㄥ簵鍚嶇О',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楀悕绉�',
- },
- },
- {
- component: 'Input',
- fieldName: 'phone',
- label: '闂ㄥ簵鎵嬫満',
- rules: 'mobileRequired',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楁墜鏈�',
- },
- },
- {
- component: 'ImageUpload',
- fieldName: 'logo',
- label: '闂ㄥ簵 logo',
- rules: 'required',
- formItemClass: 'col-span-2',
- componentProps: {
- placeholder: '璇蜂笂浼犻棬搴� logo',
- },
- help: '鎺ㄨ崘 180x180 鍥剧墖鍒嗚鲸鐜�',
- },
- {
- component: 'Textarea',
- fieldName: 'introduction',
- label: '闂ㄥ簵绠�浠�',
- formItemClass: 'col-span-2',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楃畝浠�',
- rows: 4,
- },
- },
- {
- fieldName: 'areaId',
- label: '闂ㄥ簵鎵�鍦ㄥ湴鍖�',
- component: markRaw(AreaCascader),
- rules: 'required',
- componentProps: {
- allowClear: true,
- class: '!w-full',
- placeholder: '璇烽�夋嫨鐪佸競鍖�',
- showSearch: true,
- },
- },
- {
- component: 'Input',
- fieldName: 'detailAddress',
- label: '闂ㄥ簵璇︾粏鍦板潃',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楄缁嗗湴鍧�',
- },
- },
- {
- component: 'TimeRangePicker',
- fieldName: 'rangeTime',
- label: '钀ヤ笟鏃堕棿',
- rules: 'required',
- componentProps: {
- format: 'HH:mm',
- },
- },
- {
- fieldName: 'status',
- label: '闂ㄥ簵鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- component: 'Input',
- fieldName: 'longitude',
- label: '缁忓害',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楃粡搴�',
- },
- },
- {
- component: 'Input',
- fieldName: 'latitude',
- label: '绾害',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楃含搴�',
- },
- },
- ];
-}
-
-/** 缁戝畾搴楀憳鐨勮〃鍗� */
-export function useBindFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: '闂ㄥ簵鍚嶇О',
- dependencies: {
- triggerFields: ['id'],
- disabled: true,
- },
- },
- {
- component: 'ApiSelect',
- fieldName: 'verifyUserIds',
- label: '闂ㄥ簵搴楀憳',
- rules: 'required',
- componentProps: {
- api: getSimpleUserList,
- labelField: 'nickname',
- valueField: 'id',
- mode: 'tags',
- allowClear: true,
- placeholder: '璇烽�夋嫨闂ㄥ簵搴楀憳',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'phone',
- label: '闂ㄥ簵鎵嬫満',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楁墜鏈�',
- allowClear: true,
- },
- },
- {
- fieldName: 'name',
- label: '闂ㄥ簵鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ棬搴楀悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '闂ㄥ簵鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨闂ㄥ簵鐘舵��',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 80,
- },
- {
- field: 'logo',
- title: '闂ㄥ簵 logo',
- minWidth: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'name',
- title: '闂ㄥ簵鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'phone',
- title: '闂ㄥ簵鎵嬫満',
- minWidth: 120,
- },
- {
- field: 'detailAddress',
- title: '鍦板潃',
- minWidth: 200,
- },
- {
- field: 'openingTime',
- title: '钀ヤ笟鏃堕棿',
- minWidth: 160,
- formatter: ({ row }) => {
- return `${row.openingTime} ~ ${row.closingTime}`;
- },
- },
- {
- field: 'status',
- title: '寮�鍚姸鎬�',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 160,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 220,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mall/trade/delivery/pickUpStore/index.vue b/src/views/mall/trade/delivery/pickUpStore/index.vue
deleted file mode 100644
index b031aec..0000000
--- a/src/views/mall/trade/delivery/pickUpStore/index.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteDeliveryPickUpStore,
- getDeliveryPickUpStorePage,
-} from '#/api/mall/trade/delivery/pickUpStore';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import BindForm from './modules/bind-form.vue';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [BindFormModal, bindFormModalApi] = useVbenModal({
- connectedComponent: BindForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓闂ㄥ簵 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫闂ㄥ簵 */
-function handleEdit(row: MallDeliveryPickUpStoreApi.DeliveryPickUpStore) {
- formModalApi.setData(row).open();
-}
-
-/** 缁戝畾搴楀憳 */
-function handleBind(row: MallDeliveryPickUpStoreApi.DeliveryPickUpStore) {
- bindFormModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎闂ㄥ簵 */
-async function handleDelete(
- row: MallDeliveryPickUpStoreApi.DeliveryPickUpStore,
-) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteDeliveryPickUpStore(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDeliveryPickUpStorePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <BindFormModal />
- <Grid table-title="闂ㄥ簵鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['闂ㄥ簵']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['trade:delivery:pick-up-store:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['trade:delivery:pick-up-store:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '缁戝畾搴楀憳',
- type: 'link',
- icon: ACTION_ICON.ADD,
- auth: ['trade:delivery:pick-up-store:update'],
- onClick: handleBind.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['trade:delivery:pick-up-store:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue b/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue
deleted file mode 100644
index 8a78e7e..0000000
--- a/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- bindDeliveryPickUpStore,
- getDeliveryPickUpStore,
-} from '#/api/mall/trade/delivery/pickUpStore';
-import { $t } from '#/locales';
-
-import { useBindFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useBindFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallDeliveryPickUpStoreApi.DeliveryPickUpBindReqVO;
- try {
- await bindDeliveryPickUpStore(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data =
- modalApi.getData<MallDeliveryPickUpStoreApi.DeliveryPickUpBindReqVO>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDeliveryPickUpStore(data.id);
- // 璁剧疆鍒� values
- formData.value.verifyUserIds = formData.value.verifyUsers?.map(
- (item: any) => item.id,
- );
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal title="缁戝畾搴楀憳" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/delivery/pickUpStore/modules/form.vue b/src/views/mall/trade/delivery/pickUpStore/modules/form.vue
deleted file mode 100644
index 7eab462..0000000
--- a/src/views/mall/trade/delivery/pickUpStore/modules/form.vue
+++ /dev/null
@@ -1,162 +0,0 @@
-<script lang="ts" setup>
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../../packages/effects/common-ui/src';
-
-import { Button, message } from 'ant-design-vue';
-import dayjs from 'dayjs';
-
-import { useVbenForm } from '#/adapter/form';
-import { getTradeConfig } from '#/api/mall/trade/config';
-import {
- createDeliveryPickUpStore,
- getDeliveryPickUpStore,
- updateDeliveryPickUpStore,
-} from '#/api/mall/trade/delivery/pickUpStore';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['闂ㄥ簵'])
- : $t('ui.actionTitle.create', ['闂ㄥ簵']);
-});
-
-const mapVisible = ref(false); // 鏄惁鏄剧ず鍦板浘寮圭獥
-const tencentLbsUrl = ref(''); // 鑵捐浣嶇疆鏈嶅姟 url
-
-/** 鑾峰彇缁忕含搴︾浉鍏虫柟娉� */
-function selectAddress(loc: any) {
- if (loc.latlng?.lat) {
- formApi.setFieldValue('latitude', loc.latlng.lat);
- }
- if (loc.latlng?.lng) {
- formApi.setFieldValue('longitude', loc.latlng.lng);
- }
- mapVisible.value = false;
-}
-
-/** 鍒濆鍖栬吘璁湴鍥� */
-async function initTencentLbsMap() {
- // 浠庨厤缃腑鑾峰彇鑵捐鍦板浘鐨� key
- const data = await getTradeConfig();
- const key = data.tencentLbsKey;
- if (!key) {
- message.warning('璇峰厛閰嶇疆鑵捐浣嶇疆鏈嶅姟鐨� key');
- return;
- }
-
- // 璁剧疆 url
- if (!window) {
- return;
- }
- (window as any).selectAddress = selectAddress;
- window.addEventListener(
- 'message',
- (event) => {
- // 鎺ユ敹浣嶇疆淇℃伅锛岀敤鎴烽�夋嫨纭浣嶇疆鐐瑰悗閫夌偣缁勪欢浼氳Е鍙戣浜嬩欢锛屽洖浼犵敤鎴风殑浣嶇疆淇℃伅
- const loc = event.data;
- if (loc && loc.module === 'locationPicker') {
- // 闃叉鍏朵粬搴旂敤涔熶細鍚戣椤甸潰 post 淇℃伅锛岄渶鍒ゆ柇 module 鏄惁涓� 'locationPicker'
- (window.parent as any).selectAddress(loc);
- }
- },
- false,
- );
- tencentLbsUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp`;
-}
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- labelWidth: 120,
- },
- fieldMappingTime: [['rangeTime', ['openingTime', 'closingTime'], 'HH:mm']],
- wrapperClass: 'grid-cols-2',
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallDeliveryPickUpStoreApi.DeliveryPickUpStore;
- try {
- await (formData.value?.id
- ? updateDeliveryPickUpStore(data)
- : createDeliveryPickUpStore(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data =
- modalApi.getData<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>();
- if (!data || !data.id) {
- // 鍒濆鍖栧湴鍥�
- await initTencentLbsMap();
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getDeliveryPickUpStore(data.id);
- formData.value.rangeTime = [
- dayjs(formData.value.openingTime, 'HH:mm'),
- dayjs(formData.value.closingTime, 'HH:mm'),
- ];
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- // 鍒濆鍖栧湴鍥�
- await initTencentLbsMap();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- <template #prepend-footer>
- <Button @click="mapVisible = true"> 鑾峰彇缁忕含搴� </Button>
- </template>
- </Modal>
-
- <!-- 鍦板浘寮圭獥 -->
- <a-modal
- v-model:open="mapVisible"
- title="鑾峰彇缁忕含搴�"
- :width="800"
- :footer="null"
- >
- <iframe
- v-if="mapVisible && tencentLbsUrl"
- :src="tencentLbsUrl"
- class="h-[600px] w-full border-0"
- ></iframe>
- </a-modal>
-</template>
diff --git a/src/views/mall/trade/order/data.ts b/src/views/mall/trade/order/data.ts
deleted file mode 100644
index 351f1a9..0000000
--- a/src/views/mall/trade/order/data.ts
+++ /dev/null
@@ -1,452 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-
-import { markRaw } from 'vue';
-
-import { DeliveryTypeEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-import { convertToInteger, formatToFraction } from '../../../../packages/utils/src';
-
-import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express';
-import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
-import { AreaCascader } from '#/components/area';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍏宠仈鏁版嵁 */
-let pickUpStoreList: MallDeliveryPickUpStoreApi.DeliveryPickUpStore[] = [];
-getSimpleDeliveryPickUpStoreList().then((data) => {
- pickUpStoreList = data;
-});
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'status',
- label: '璁㈠崟鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_ORDER_STATUS, 'number'),
- placeholder: '璇烽�夋嫨璁㈠崟鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'payChannelCode',
- label: '鏀粯鏂瑰紡',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'number'),
- placeholder: '璇烽�夋嫨鏀粯鏂瑰紡',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- {
- fieldName: 'terminal',
- label: '璁㈠崟鏉ユ簮',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TERMINAL, 'number'),
- placeholder: '璇烽�夋嫨璁㈠崟鏉ユ簮',
- allowClear: true,
- },
- },
- {
- fieldName: 'deliveryType',
- label: '閰嶉�佹柟寮�',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.TRADE_DELIVERY_TYPE, 'number'),
- placeholder: '璇烽�夋嫨閰嶉�佹柟寮�',
- allowClear: true,
- },
- },
- {
- fieldName: 'logisticsId',
- label: '蹇�掑叕鍙�',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeliveryExpressList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨蹇�掑叕鍙�',
- allowClear: true,
- },
- dependencies: {
- triggerFields: ['deliveryType'],
- show: (values) => values.deliveryType === DeliveryTypeEnum.EXPRESS.type,
- },
- },
- {
- fieldName: 'pickUpStoreId',
- label: '鑷彁闂ㄥ簵',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeliveryPickUpStoreList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鑷彁闂ㄥ簵',
- allowClear: true,
- },
- dependencies: {
- triggerFields: ['deliveryType'],
- show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type,
- },
- },
- {
- fieldName: 'pickUpVerifyCode',
- label: '鏍搁攢鐮�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ牳閿�鐮�',
- allowClear: true,
- },
- dependencies: {
- triggerFields: ['deliveryType'],
- show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type,
- },
- },
- {
- fieldName: 'no',
- label: '璁㈠崟鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ鍗曞彿',
- allowClear: true,
- },
- },
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛 UID',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴� UID',
- allowClear: true,
- },
- },
- {
- fieldName: 'userNickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋樀绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'userMobile',
- label: '鐢ㄦ埛鐢佃瘽',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴风數璇�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- type: 'expand',
- width: 80,
- slots: { content: 'expand_content' },
- fixed: 'left',
- },
- {
- field: 'no',
- title: '璁㈠崟鍙�',
- fixed: 'left',
- minWidth: 180,
- },
- {
- field: 'createTime',
- title: '涓嬪崟鏃堕棿',
- formatter: 'formatDateTime',
- minWidth: 160,
- },
- {
- field: 'terminal',
- title: '璁㈠崟鏉ユ簮',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TERMINAL },
- },
- minWidth: 120,
- },
- {
- field: 'payChannelCode',
- title: '鏀粯鏂瑰紡',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
- },
- minWidth: 120,
- },
- {
- field: 'payTime',
- title: '鏀粯鏃堕棿',
- formatter: 'formatDateTime',
- minWidth: 160,
- },
- {
- field: 'type',
- title: '璁㈠崟绫诲瀷',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TRADE_ORDER_TYPE },
- },
- minWidth: 80,
- },
- {
- field: 'payPrice',
- title: '瀹為檯鏀粯',
- formatter: 'formatAmount2',
- minWidth: 180,
- },
- {
- field: 'user',
- title: '涔板/鏀惰揣浜�',
- formatter: ({ row }) => {
- if (row.deliveryType === DeliveryTypeEnum.EXPRESS.type) {
- return `涔板锛�${row.user?.nickname} / 鏀惰揣浜猴細 ${row.receiverName} ${row.receiverMobile}${row.receiverAreaName}${row.receiverDetailAddress}`;
- }
- if (row.deliveryType === DeliveryTypeEnum.PICK_UP.type) {
- return `闂ㄥ簵鍚嶇О锛�${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.name} /
- 闂ㄥ簵鎵嬫満锛�${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.phone} /
- 鑷彁闂ㄥ簵锛�${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.detailAddress}
- `;
- }
- return '';
- },
- minWidth: 180,
- },
- {
- field: 'deliveryType',
- title: '閰嶉�佹柟寮�',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TRADE_DELIVERY_TYPE },
- },
- minWidth: 80,
- },
- {
- field: 'status',
- title: '璁㈠崟鐘舵��',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TRADE_ORDER_STATUS },
- },
- minWidth: 80,
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璁㈠崟澶囨敞琛ㄥ崟閰嶇疆 */
-export function useRemarkFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Input',
- componentProps: {
- type: 'textarea',
- rows: 3,
- },
- },
- ];
-}
-
-/** 璁㈠崟璋冧环琛ㄥ崟閰嶇疆 */
-export function usePriceFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'payPrice',
- label: '搴斾粯閲戦(鎬�)',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ簲浠橀噾棰�(鎬�)',
- disabled: true,
- formatter: (value: string) => `${value}鍏僠,
- },
- },
- {
- fieldName: 'adjustPrice',
- label: '璁㈠崟璋冧环',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ヨ鍗曡皟浠�',
- step: 0.1,
- precision: 2,
- },
- help: '璁㈠崟璋冧环銆� 姝f暟锛屽姞浠凤紱璐熸暟锛屽噺浠�',
- rules: 'required',
- },
- {
- fieldName: 'newPayPrice',
- label: '璋冧环鍚�',
- component: 'Input',
- componentProps: {
- placeholder: '',
- formatter: (value: string) => `${value}鍏僠,
- },
- dependencies: {
- triggerFields: ['payPrice', 'adjustPrice'],
- disabled: true,
- trigger(values, form) {
- const originalPrice = convertToInteger(values.payPrice);
- const adjustPrice = convertToInteger(values.adjustPrice);
- const newPrice = originalPrice + adjustPrice;
- form.setFieldValue('newPayPrice', formatToFraction(newPrice));
- },
- },
- },
- ];
-}
-
-/** 璁㈠崟淇敼鍦板潃琛ㄥ崟閰嶇疆 */
-export function useAddressFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'receiverName',
- label: '鏀朵欢浜�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敹浠朵汉鍚嶇О',
- },
- rules: 'required',
- },
- {
- fieldName: 'receiverMobile',
- label: '鎵嬫満鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敹浠朵汉鎵嬫満鍙�',
- },
- rules: 'required',
- },
- {
- fieldName: 'receiverAreaId',
- label: '鎵�鍦ㄥ湴',
- component: markRaw(AreaCascader),
- componentProps: {
- allowClear: true,
- changeOnSelect: true,
- class: '!w-full',
- placeholder: '璇烽�夋嫨鏀朵欢浜烘墍鍦ㄥ湴',
- showSearch: true,
- },
- rules: 'required',
- },
- {
- fieldName: 'receiverDetailAddress',
- label: '璇︾粏鍦板潃',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敹浠朵汉璇︾粏鍦板潃',
- type: 'textarea',
- rows: 3,
- },
- rules: 'required',
- },
- ];
-}
-
-/** 璁㈠崟鍙戣揣琛ㄥ崟閰嶇疆 */
-export function useDeliveryFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'expressType',
- label: '鍙戣揣鏂瑰紡',
- component: 'RadioGroup',
- componentProps: {
- options: [
- { label: '蹇��', value: 'express' },
- { label: '鏃犻渶鍙戣揣', value: 'none' },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 'express',
- },
- {
- fieldName: 'logisticsId',
- label: '鐗╂祦鍏徃',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleDeliveryExpressList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鐗╂祦鍏徃',
- },
- dependencies: {
- triggerFields: ['expressType'],
- show: (values) => values.expressType === 'express',
- },
- rules: 'required',
- },
- {
- fieldName: 'logisticsNo',
- label: '鐗╂祦鍗曞彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ墿娴佸崟鍙�',
- },
- dependencies: {
- triggerFields: ['expressType'],
- show: (values) => values.expressType === 'express',
- },
- rules: 'required',
- },
- ];
-}
diff --git a/src/views/mall/trade/order/detail/data.ts b/src/views/mall/trade/order/detail/data.ts
deleted file mode 100644
index 62a8b90..0000000
--- a/src/views/mall/trade/order/detail/data.ts
+++ /dev/null
@@ -1,255 +0,0 @@
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { fenToYuan, formatDateTime } from '../../../../../packages/utils/src';
-
-import { DictTag } from '#/components/dict-tag';
-
-/** 璁㈠崟鍩虹淇℃伅 schema */
-export function useOrderInfoSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'no',
- label: '璁㈠崟鍙�',
- },
- {
- field: 'user.nickname',
- label: '涔板',
- },
- {
- field: 'type',
- label: '璁㈠崟绫诲瀷',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_ORDER_TYPE,
- value: val,
- }),
- },
- {
- field: 'terminal',
- label: '璁㈠崟鏉ユ簮',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TERMINAL,
- value: val,
- }),
- },
- {
- field: 'userRemark',
- label: '涔板鐣欒█',
- },
- {
- field: 'remark',
- label: '鍟嗗澶囨敞',
- },
- {
- field: 'payOrderId',
- label: '鏀粯鍗曞彿',
- },
- {
- field: 'payChannelCode',
- label: '浠樻鏂瑰紡',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_CHANNEL_CODE,
- value: val,
- }),
- },
- {
- field: 'brokerageUser.nickname',
- label: '鎺ㄥ箍鐢ㄦ埛',
- },
- ];
-}
-
-/** 璁㈠崟鐘舵�佷俊鎭� schema */
-export function useOrderStatusSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'status',
- label: '璁㈠崟鐘舵��',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_ORDER_STATUS,
- value: val,
- }),
- },
- {
- field: 'reminder',
- label: '鎻愰啋',
- render: () =>
- h('div', { class: 'space-y-1' }, [
- h('div', '涔板浠樻鎴愬姛鍚庯紝璐ф灏嗙洿鎺ヨ繘鍏ユ偍鐨勫晢鎴峰彿锛堝井淇°�佹敮浠樺疂锛�'),
- h('div', '璇峰強鏃跺叧娉ㄤ綘鍙戝嚭鐨勫寘瑁圭姸鎬侊紝纭繚鍙互閰嶉�佽嚦涔板鎵嬩腑'),
- h(
- 'div',
- '濡傛灉涔板琛ㄧず娌℃敹鍒拌揣鎴栬揣鐗╂湁闂锛岃鍙婃椂鑱旂郴涔板澶勭悊锛屽弸濂藉崗鍟�',
- ),
- ]),
- },
- ];
-}
-
-/** 璁㈠崟閲戦淇℃伅 schema */
-export function useOrderPriceSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'totalPrice',
- label: '鍟嗗搧鎬婚',
- render: (val) => `${fenToYuan(val ?? 0)} 鍏僠,
- },
- {
- field: 'deliveryPrice',
- label: '杩愯垂閲戦',
- render: (val) => `${fenToYuan(val ?? 0)} 鍏僠,
- },
- {
- field: 'adjustPrice',
- label: '璁㈠崟璋冧环',
- render: (val) => `${fenToYuan(val ?? 0)} 鍏僠,
- },
- {
- field: 'couponPrice',
- label: '浼樻儬鍔典紭鎯�',
- render: (val) =>
- h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 鍏僠),
- },
- {
- field: 'vipPrice',
- label: 'VIP 浼樻儬',
- render: (val) =>
- h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 鍏僠),
- },
- {
- field: 'discountPrice',
- label: '娲诲姩浼樻儬',
- render: (val) =>
- h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 鍏僠),
- },
- {
- field: 'pointPrice',
- label: '绉垎鎶垫墸',
- render: (val) =>
- h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 鍏僠),
- },
- {
- field: 'payPrice',
- label: '搴斾粯閲戦',
- render: (val) => `${fenToYuan(val ?? 0)} 鍏僠,
- },
- ];
-}
-
-/** 鏀惰揣淇℃伅 schema */
-export function useDeliveryInfoSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'deliveryType',
- label: '閰嶉�佹柟寮�',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.TRADE_DELIVERY_TYPE,
- value: val,
- }),
- },
- {
- field: 'receiverName',
- label: '鏀惰揣浜�',
- },
- {
- field: 'receiverMobile',
- label: '鑱旂郴鐢佃瘽',
- },
- {
- field: 'receiverAddress',
- label: '鏀惰揣鍦板潃',
- render: (val, data) => `${data?.receiverAreaName} ${val}`.trim(),
- },
- {
- field: 'deliveryTime',
- label: '鍙戣揣鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- ];
-}
-
-/** 鍟嗗搧淇℃伅 columns */
-export function useProductColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'spuName',
- title: '鍟嗗搧',
- minWidth: 300,
- slots: { default: 'spuName' },
- },
- {
- field: 'price',
- title: '鍟嗗搧鍘熶环',
- width: 150,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'count',
- title: '鏁伴噺',
- width: 100,
- },
- {
- field: 'payPrice',
- title: '鍚堣',
- width: 150,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'afterSaleStatus',
- title: '鍞悗鐘舵��',
- width: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS },
- },
- },
- ];
-}
-
-/** 鐗╂祦璇︽儏 columns */
-export function useExpressTrackColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'time',
- title: '鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'content',
- title: '鐗╂祦鐘舵��',
- minWidth: 300,
- },
- ];
-}
-
-/** 鎿嶄綔鏃ュ織 columns */
-export function useOperateLogColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'createTime',
- title: '鎿嶄綔鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'userType',
- title: '鎿嶄綔浜�',
- width: 100,
- slots: { default: 'userType' },
- },
- {
- field: 'content',
- title: '鎿嶄綔鍐呭',
- minWidth: 200,
- },
- ];
-}
diff --git a/src/views/mall/trade/order/detail/index.vue b/src/views/mall/trade/order/detail/index.vue
deleted file mode 100644
index 81ba6b3..0000000
--- a/src/views/mall/trade/order/detail/index.vue
+++ /dev/null
@@ -1,349 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallDeliveryExpressApi } from '#/api/mall/trade/delivery/express';
-import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { confirm, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import {
- DeliveryTypeEnum,
- DICT_TYPE,
- TradeOrderStatusEnum,
-} from '../../../../../packages/constants/src';
-import { useTabs } from '../../../../../packages/effects/hooks/src';
-
-import { message, Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express';
-import { getDeliveryPickUpStore } from '#/api/mall/trade/delivery/pickUpStore';
-import {
- getExpressTrackList,
- getOrder,
- pickUpOrder,
-} from '#/api/mall/trade/order';
-import { useDescription } from '#/components/description';
-import { DictTag } from '#/components/dict-tag';
-import { TableAction } from '#/components/table-action';
-
-import AddressForm from '../modules/address-form.vue';
-import DeliveryForm from '../modules/delivery-form.vue';
-import PriceForm from '../modules/price-form.vue';
-import RemarkForm from '../modules/remark-form.vue';
-import {
- useDeliveryInfoSchema,
- useExpressTrackColumns,
- useOperateLogColumns,
- useOrderInfoSchema,
- useOrderPriceSchema,
- useOrderStatusSchema,
- useProductColumns,
-} from './data';
-
-defineOptions({ name: 'TradeOrderDetail' });
-
-const route = useRoute();
-const router = useRouter();
-const tabs = useTabs();
-
-const loading = ref(false);
-const orderId = ref(0);
-const order = ref<MallOrderApi.Order>({
- logs: [],
-});
-const deliveryExpressList = ref<MallDeliveryExpressApi.DeliveryExpress[]>([]);
-const expressTrackList = ref<any[]>([]);
-const pickUpStore = ref<
- MallDeliveryPickUpStoreApi.DeliveryPickUpStore | undefined
->();
-
-const [OrderInfoDescriptions] = useDescription({
- title: '璁㈠崟淇℃伅',
- bordered: false,
- column: 3,
- class: 'mx-4',
- schema: useOrderInfoSchema(),
-});
-
-const [OrderStatusDescriptions] = useDescription({
- title: '璁㈠崟鐘舵��',
- bordered: false,
- column: 1,
- class: 'mx-4',
- schema: useOrderStatusSchema(),
-});
-
-const [OrderPriceDescriptions] = useDescription({
- title: '璐圭敤淇℃伅',
- bordered: false,
- column: 4,
- class: 'mx-4',
- schema: useOrderPriceSchema(),
-});
-
-const [DeliveryInfoDescriptions] = useDescription({
- title: '鏀惰揣淇℃伅',
- bordered: false,
- column: 3,
- class: 'mx-4',
- schema: useDeliveryInfoSchema(),
-});
-
-const [ProductGrid, productGridApi] = useVbenVxeGrid({
- gridOptions: {
- cellConfig: {
- height: 60,
- },
- columns: useProductColumns(),
- data: [],
- height: 'auto',
- border: true,
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallOrderApi.OrderItem>,
-});
-
-const [ExpressTrackGrid, expressTrackGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useExpressTrackColumns(),
- data: [],
- border: true,
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions,
-});
-
-const [OperateLogGrid, operateLogGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useOperateLogColumns(),
- data: [],
- border: true,
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions,
-});
-
-const [DeliveryFormModal, deliveryFormModalApi] = useVbenModal({
- connectedComponent: DeliveryForm,
- destroyOnClose: true,
-});
-
-const [RemarkFormModal, remarkFormModalApi] = useVbenModal({
- connectedComponent: RemarkForm,
- destroyOnClose: true,
-});
-
-const [AddressFormModal, addressFormModalApi] = useVbenModal({
- connectedComponent: AddressForm,
- destroyOnClose: true,
-});
-
-const [PriceFormModal, priceFormModalApi] = useVbenModal({
- connectedComponent: PriceForm,
- destroyOnClose: true,
-});
-
-/** 鑾峰緱璇︽儏 */
-async function getDetail() {
- loading.value = true;
- try {
- const res = await getOrder(orderId.value);
- if (res === null) {
- message.error('浜ゆ槗璁㈠崟涓嶅瓨鍦�');
- handleBack();
- return;
- }
- order.value = res;
- productGridApi.setGridOptions({ data: res.items || [] });
- operateLogGridApi.setGridOptions({ data: res.logs || [] });
-
- // 濡傛灉閰嶉�佹柟寮忎负蹇�掞紝鍒欐煡璇㈢墿娴佸叕鍙�
- if (res.deliveryType === DeliveryTypeEnum.EXPRESS.type) {
- deliveryExpressList.value = await getSimpleDeliveryExpressList();
- if (res.logisticsId) {
- expressTrackList.value = await getExpressTrackList(res.id!);
- expressTrackGridApi.setGridOptions({
- data: expressTrackList.value || [],
- });
- }
- } else if (
- res.deliveryType === DeliveryTypeEnum.PICK_UP.type &&
- res.pickUpStoreId
- ) {
- pickUpStore.value = await getDeliveryPickUpStore(res.pickUpStoreId);
- }
- } finally {
- loading.value = false;
- }
-}
-
-/** 鍚勭鎿嶄綔 */
-const handleRemark = () => {
- remarkFormModalApi.setData(order.value).open();
-};
-
-const handleDelivery = () => {
- deliveryFormModalApi.setData(order.value).open();
-};
-
-const handleUpdateAddress = () => {
- addressFormModalApi.setData(order.value).open();
-};
-
-const handleUpdatePrice = () => {
- priceFormModalApi.setData(order.value).open();
-};
-
-/** 鏍搁攢 */
-const handlePickUp = async () => {
- await confirm('纭鏍搁攢璁㈠崟鍚楋紵');
- const hideLoading = message.loading({
- content: '姝e湪澶勭悊涓�...',
- duration: 0,
- });
- try {
- await pickUpOrder(order.value.id!);
- message.success('鏍搁攢鎴愬姛');
- await getDetail();
- } finally {
- hideLoading();
- }
-};
-
-/** 杩斿洖鍒楄〃椤� */
-function handleBack() {
- tabs.closeCurrentTab();
- router.push({ name: 'TradeOrder' });
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- orderId.value = Number(route.params.id);
- await getDetail();
-});
-</script>
-
-<template>
- <Page auto-content-height :title="order.no" :loading="loading">
- <template #extra>
- <TableAction
- :actions="[
- {
- label: '杩斿洖',
- type: 'default',
- icon: 'lucide:arrow-left',
- onClick: handleBack,
- },
- {
- label: '璋冩暣浠锋牸',
- type: 'primary',
- onClick: handleUpdatePrice,
- ifShow: order.status === TradeOrderStatusEnum.UNPAID.status,
- },
- {
- label: '澶囨敞',
- type: 'primary',
- onClick: handleRemark,
- },
- {
- label: '鍙戣揣',
- type: 'primary',
- onClick: handleDelivery,
- ifShow:
- order.status === TradeOrderStatusEnum.UNDELIVERED.status &&
- order.deliveryType === DeliveryTypeEnum.EXPRESS.type,
- },
- {
- label: '淇敼鍦板潃',
- type: 'primary',
- onClick: handleUpdateAddress,
- ifShow:
- order.status === TradeOrderStatusEnum.UNDELIVERED.status &&
- order.deliveryType === DeliveryTypeEnum.EXPRESS.type,
- },
- {
- label: '鏍搁攢',
- type: 'primary',
- onClick: handlePickUp,
- ifShow:
- order.status === TradeOrderStatusEnum.UNDELIVERED.status &&
- order.deliveryType === DeliveryTypeEnum.PICK_UP.type,
- },
- ]"
- />
- </template>
-
- <!-- 鍚勭鎿嶄綔鐨勫脊绐� -->
- <DeliveryFormModal @success="getDetail" />
- <RemarkFormModal @success="getDetail" />
- <AddressFormModal @success="getDetail" />
- <PriceFormModal @success="getDetail" />
-
- <!-- 璁㈠崟淇℃伅 -->
- <div class="mb-4">
- <OrderInfoDescriptions :data="order" />
- </div>
- <!-- 璁㈠崟鐘舵�� -->
- <div class="mb-4">
- <OrderStatusDescriptions :data="order" />
- </div>
- <!-- 鍟嗗搧淇℃伅 -->
- <div class="mb-4">
- <ProductGrid table-title="鍟嗗搧淇℃伅">
- <template #spuName="{ row }">
- <div class="flex flex-1 flex-col items-start gap-1 text-left">
- <span class="text-sm">{{ row.spuName }}</span>
- <div class="flex flex-wrap gap-1">
- <Tag
- v-for="property in row.properties"
- :key="property.propertyId!"
- size="small"
- >
- {{ property.propertyName }}: {{ property.valueName }}
- </Tag>
- </div>
- </div>
- </template>
- </ProductGrid>
- </div>
- <!-- 璐圭敤淇℃伅 -->
- <div class="mb-4">
- <OrderPriceDescriptions :data="order" />
- </div>
- <!-- 鏀惰揣淇℃伅 -->
- <div class="mb-4">
- <DeliveryInfoDescriptions :data="order" />
- </div>
- <!-- 鐗╂祦璇︽儏 -->
- <div v-if="expressTrackList.length > 0" class="mb-4">
- <ExpressTrackGrid table-title="鐗╂祦璇︽儏" />
- </div>
- <!-- 鎿嶄綔鏃ュ織 -->
- <div>
- <OperateLogGrid table-title="鎿嶄綔鏃ュ織">
- <template #userType="{ row }">
- <Tag v-if="row.userType === 0" color="default"> 绯荤粺 </Tag>
- <DictTag v-else :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
- </template>
- </OperateLogGrid>
- </div>
- </Page>
-</template>
diff --git a/src/views/mall/trade/order/index.vue b/src/views/mall/trade/order/index.vue
deleted file mode 100644
index d3785fb..0000000
--- a/src/views/mall/trade/order/index.vue
+++ /dev/null
@@ -1,174 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import {
- DeliveryTypeEnum,
- DICT_TYPE,
- TradeOrderStatusEnum,
-} from '../../../../packages/constants/src';
-import { fenToYuan } from '../../../../packages/utils/src';
-
-import { Image, List, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getOrderPage } from '#/api/mall/trade/order';
-import { DictTag } from '#/components/dict-tag';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import DeliveryForm from './modules/delivery-form.vue';
-import RemarkForm from './modules/remark-form.vue';
-
-const { push } = useRouter();
-
-const [DeliveryFormModal, deliveryFormModalApi] = useVbenModal({
- connectedComponent: DeliveryForm,
- destroyOnClose: true,
-});
-
-const [RemarkFormModal, remarkFormModalApi] = useVbenModal({
- connectedComponent: RemarkForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 璇︽儏 */
-function handleDetail(row: MallOrderApi.Order) {
- push({ name: 'TradeOrderDetail', params: { id: row.id } });
-}
-
-/** 鍙戣揣 */
-function handleDelivery(row: MallOrderApi.Order) {
- deliveryFormModalApi.setData(row).open();
-}
-
-/** 澶囨敞 */
-function handleRemark(row: MallOrderApi.Order) {
- remarkFormModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- expandConfig: {
- trigger: 'row',
- expandAll: true,
- padding: true,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getOrderPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallOrderApi.Order>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪氦鏄撱�戜氦鏄撹鍗�"
- url="https://doc.iocoder.cn/mall/trade-order/"
- />
- <DocAlert
- title="銆愪氦鏄撱�戣喘鐗╄溅"
- url="https://doc.iocoder.cn/mall/trade-cart/"
- />
- </template>
-
- <DeliveryFormModal @success="handleRefresh" />
- <RemarkFormModal @success="handleRefresh" />
- <Grid table-title="璁㈠崟鍒楄〃">
- <template #expand_content="{ row }">
- <List item-layout="vertical" :data-source="row.items">
- <template #renderItem="{ item }">
- <List.Item>
- <List.Item.Meta>
- <template #title>
- {{ item.spuName }}
- <Tag
- color="blue"
- v-for="property in item.properties"
- :key="property.propertyId"
- >
- {{ property.propertyName }} : {{ property.valueName }}
- </Tag>
- </template>
- <template #avatar>
- <Image :src="item.picUrl" :width="40" :height="40" />
- </template>
- <template #description>
- {{
- `鍘熶环锛�${fenToYuan(item.price)} 鍏� / 鏁伴噺锛�${item.count} 涓猔
- }}
- |
- <DictTag
- :type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
- :value="item.afterSaleStatus"
- />
- </template>
- </List.Item.Meta>
- </List.Item>
- </template>
- </List>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['trade:order:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- :drop-down-actions="[
- {
- label: '鍙戣揣',
- type: 'link',
- ifShow: () =>
- row.deliveryType === DeliveryTypeEnum.EXPRESS.type &&
- (row.status === TradeOrderStatusEnum.UNDELIVERED.status ||
- row.status === TradeOrderStatusEnum.DELIVERED.status),
- onClick: handleDelivery.bind(null, row),
- },
- {
- label: '澶囨敞',
- type: 'link',
- onClick: handleRemark.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mall/trade/order/modules/address-form.vue b/src/views/mall/trade/order/modules/address-form.vue
deleted file mode 100644
index c91f71b..0000000
--- a/src/views/mall/trade/order/modules/address-form.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<script lang="ts" setup>
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { updateOrderAddress } from '#/api/mall/trade/order';
-import { $t } from '#/locales';
-
-import { useAddressFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallOrderApi.Order>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useAddressFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- await updateOrderAddress(data as MallOrderApi.OrderUpdateAddressReqVO);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallOrderApi.Order>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = data;
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="$t('ui.actionTitle.edit', ['鏀惰揣鍦板潃'])" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/order/modules/delivery-form.vue b/src/views/mall/trade/order/modules/delivery-form.vue
deleted file mode 100644
index e2eae83..0000000
--- a/src/views/mall/trade/order/modules/delivery-form.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script lang="ts" setup>
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { deliveryOrder } from '#/api/mall/trade/order';
-import { $t } from '#/locales';
-
-import { useDeliveryFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref({
- id: 0,
- expressType: 'express',
- logisticsId: undefined,
- logisticsNo: '',
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useDeliveryFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- if (data.expressType === 'none') {
- // 鏃犻渶鍙戣揣鐨勬儏鍐�
- data.logisticsId = 0;
- data.logisticsNo = '';
- }
- try {
- await deliveryOrder(data as MallOrderApi.OrderUpdateDeliveryReqVO);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallOrderApi.Order>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = data as any;
- formData.value.expressType = data.logisticsId === 0 ? 'none' : 'express';
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal title="鍙戣揣" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/order/modules/price-form.vue b/src/views/mall/trade/order/modules/price-form.vue
deleted file mode 100644
index 0c6c10d..0000000
--- a/src/views/mall/trade/order/modules/price-form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { updateOrderPrice } from '#/api/mall/trade/order';
-import { $t } from '#/locales';
-
-import { usePriceFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref({
- id: 0,
- payPrice: '0',
- adjustPrice: '0',
- newPayPrice: '0',
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: usePriceFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- await updateOrderPrice({
- id: data.id,
- adjustPrice: data.adjustPrice * 100, // 杞崲涓哄垎
- });
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallOrderApi.Order>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value.id = data.id;
- formData.value.payPrice = fenToYuan(data.payPrice || 0);
- formData.value.adjustPrice = fenToYuan(data.adjustPrice || 0);
- formData.value.newPayPrice = formData.value.payPrice;
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-1/3" :title="$t('ui.actionTitle.edit', ['璁㈠崟浠锋牸'])">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mall/trade/order/modules/remark-form.vue b/src/views/mall/trade/order/modules/remark-form.vue
deleted file mode 100644
index f3b6798..0000000
--- a/src/views/mall/trade/order/modules/remark-form.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script lang="ts" setup>
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { updateOrderRemark } from '#/api/mall/trade/order';
-import { $t } from '#/locales';
-
-import { useRemarkFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MallOrderApi.OrderUpdateDeliveryReqVO>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useRemarkFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MallOrderApi.OrderUpdateRemarkReqVO;
- try {
- await updateOrderRemark(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MallOrderApi.Order>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- // 璁剧疆鍒� values
- await formApi.setValues({
- id: data.id,
- remark: data.remark || '',
- });
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal title="璁㈠崟澶囨敞" class="w-1/3">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/config/data.ts b/src/views/member/config/data.ts
deleted file mode 100644
index 179467b..0000000
--- a/src/views/member/config/data.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-
-export const schema: VbenFormSchema[] = [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Switch',
- fieldName: 'pointTradeDeductEnable',
- label: '绉垎鎶垫墸',
- help: '涓嬪崟绉垎鏄惁鎶电敤璁㈠崟閲戦',
- },
- {
- component: 'InputNumber',
- fieldName: 'pointTradeDeductUnitPrice',
- label: '绉垎鎶垫墸',
- help: '绉垎鎶电敤姣斾緥(1 绉垎鎶靛灏戦噾棰�)锛屽崟浣嶏細鍏�',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- placeholder: '璇疯緭鍏ョН鍒嗘姷鎵e崟浠�',
- },
- },
- {
- component: 'InputNumber',
- fieldName: 'pointTradeDeductMaxPrice',
- label: '绉垎鎶垫墸鏈�澶у��',
- help: '鍗曟涓嬪崟绉垎浣跨敤涓婇檺锛�0 涓嶉檺鍒�',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ョН鍒嗘姷鎵f渶澶у��',
- },
- },
- {
- component: 'InputNumber',
- fieldName: 'pointTradeGivePoint',
- label: '1 鍏冭禒閫佸灏戝垎',
- help: '涓嬪崟鏀粯閲戦鎸夋瘮渚嬭禒閫佺Н鍒嗭紙瀹為檯鏀粯 1 鍏冭禒閫佸灏戠Н鍒嗭級',
- componentProps: {
- class: '!w-full',
- min: 0,
- placeholder: '璇疯緭鍏ヨ禒閫佺Н鍒嗘瘮渚�',
- },
- },
-];
diff --git a/src/views/member/config/index.vue b/src/views/member/config/index.vue
deleted file mode 100644
index 8a32922..0000000
--- a/src/views/member/config/index.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<script lang="ts" setup>
-import type { MemberConfigApi } from '#/api/member/config';
-
-import { onMounted, ref } from 'vue';
-
-import { Page } from '../../../packages/effects/common-ui/src';
-import { fenToYuan, yuanToFen } from '../../../packages/utils/src';
-
-import { Card, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getConfig, saveConfig } from '#/api/member/config';
-import { $t } from '#/locales';
-
-import { schema } from './data';
-
-const formData = ref<MemberConfigApi.Config>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema,
- handleSubmit,
-});
-
-/** 鎻愪氦琛ㄥ崟 */
-async function handleSubmit() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MemberConfigApi.Config;
- // 杞崲閲戦鍗曚綅
- data.pointTradeDeductUnitPrice = yuanToFen(data.pointTradeDeductUnitPrice);
- await saveConfig(data);
- // 鍏抽棴骞舵彁绀�
- message.success($t('ui.actionMessage.operationSuccess'));
-}
-
-/** 鑾峰彇閰嶇疆 */
-async function getConfigInfo() {
- const res = await getConfig();
- formData.value = res;
- // 杞崲閲戦鍗曚綅
- res.pointTradeDeductUnitPrice = Number.parseFloat(
- fenToYuan(res.pointTradeDeductUnitPrice),
- );
- // 璁剧疆鍒� values
- await formApi.setValues(res);
-}
-
-/** 鍒濆鍖� */
-onMounted(() => {
- getConfigInfo();
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Card title="绉垎璁剧疆">
- <Form class="w-1/4" />
- </Card>
- </Page>
-</template>
diff --git a/src/views/member/group/data.ts b/src/views/member/group/data.ts
deleted file mode 100644
index c9cb52d..0000000
--- a/src/views/member/group/data.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍒嗙粍鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎缁勫悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍒嗙粍鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ垎缁勫悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- placeholder: ['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡'],
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鍒嗙粍鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 200,
- showOverflow: 'tooltip',
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 130,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/member/group/index.vue b/src/views/member/group/index.vue
deleted file mode 100644
index 06b1457..0000000
--- a/src/views/member/group/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberGroupApi } from '#/api/member/group';
-
-import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteGroup, getGroupPage } from '#/api/member/group';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鍒嗙粍 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫鍒嗙粍 */
-function handleEdit(row: MemberGroupApi.Group) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎鍒嗙粍 */
-async function handleDelete(row: MemberGroupApi.Group) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteGroup(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getGroupPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberGroupApi.Group>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍒嗙粍鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍒嗙粍']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['member:group:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['member:group:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['member:group:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/member/group/modules/form.vue b/src/views/member/group/modules/form.vue
deleted file mode 100644
index 7f0abf7..0000000
--- a/src/views/member/group/modules/form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { MemberGroupApi } from '#/api/member/group';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createGroup, getGroup, updateGroup } from '#/api/member/group';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberGroupApi.Group>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍒嗙粍'])
- : $t('ui.actionTitle.create', ['鍒嗙粍']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MemberGroupApi.Group;
- try {
- await (formData.value?.id ? updateGroup(data) : createGroup(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberGroupApi.Group>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getGroup(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/level/data.ts b/src/views/member/level/data.ts
deleted file mode 100644
index 7619506..0000000
--- a/src/views/member/level/data.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '绛夌骇鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ瓑绾у悕绉�',
- },
- rules: 'required',
- },
- {
- fieldName: 'level',
- label: '绛夌骇',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 0,
- placeholder: '璇疯緭鍏ョ瓑绾�',
- },
- rules: 'required',
- },
- {
- fieldName: 'experience',
- label: '鍗囩骇缁忛獙',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 0,
- placeholder: '璇疯緭鍏ュ崌绾х粡楠�',
- },
- rules: 'required',
- },
- {
- fieldName: 'discountPercent',
- label: '浜彈鎶樻墸(%)',
- component: 'InputNumber',
- componentProps: {
- class: '!w-full',
- min: 0,
- max: 100,
- precision: 0,
- placeholder: '璇疯緭鍏ヤ韩鍙楁姌鎵�',
- },
- rules: 'required',
- },
- {
- fieldName: 'icon',
- label: '绛夌骇鍥炬爣',
- component: 'ImageUpload',
- },
- {
- fieldName: 'backgroundUrl',
- label: '绛夌骇鑳屾櫙鍥�',
- component: 'ImageUpload',
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '绛夌骇鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ瓑绾у悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '绛夌骇缂栧彿',
- minWidth: 80,
- },
- {
- field: 'icon',
- title: '绛夌骇鍥炬爣',
- minWidth: 100,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'backgroundUrl',
- title: '绛夌骇鑳屾櫙鍥�',
- minWidth: 120,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'name',
- title: '绛夌骇鍚嶇О',
- minWidth: 120,
- },
- {
- field: 'level',
- title: '绛夌骇',
- minWidth: 80,
- },
- {
- field: 'experience',
- title: '鍗囩骇缁忛獙',
- minWidth: 100,
- },
- {
- field: 'discountPercent',
- title: '浜彈鎶樻墸(%)',
- minWidth: 120,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 180,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/member/level/index.vue b/src/views/member/level/index.vue
deleted file mode 100644
index eabf887..0000000
--- a/src/views/member/level/index.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberLevelApi } from '#/api/member/level';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteLevel, getLevelList } from '#/api/member/level';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓绛夌骇 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫绛夌骇 */
-function handleEdit(row: MemberLevelApi.Level) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎绛夌骇 */
-async function handleDelete(row: MemberLevelApi.Level) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteLevel(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async (_, formValues) => {
- return await getLevelList({
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberLevelApi.Level>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳绛夌骇銆佺Н鍒嗐�佺鍒�"
- url="https://doc.iocoder.cn/member/level/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="绛夌骇鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绛夌骇']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['member:level:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['member:level:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['member:level:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/member/level/modules/form.vue b/src/views/member/level/modules/form.vue
deleted file mode 100644
index 9142b58..0000000
--- a/src/views/member/level/modules/form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { MemberLevelApi } from '#/api/member/level';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createLevel, getLevel, updateLevel } from '#/api/member/level';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberLevelApi.Level>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绛夌骇'])
- : $t('ui.actionTitle.create', ['绛夌骇']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 110,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MemberLevelApi.Level;
- try {
- await (formData.value?.id ? updateLevel(data) : createLevel(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberLevelApi.Level>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getLevel(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/point/record/data.ts b/src/views/member/point/record/data.ts
deleted file mode 100644
index dc08f7e..0000000
--- a/src/views/member/point/record/data.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋樀绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'bizType',
- label: '涓氬姟绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
- allowClear: true,
- options: getDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE, 'number'),
- },
- },
- {
- fieldName: 'title',
- label: '绉垎鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョН鍒嗘爣棰�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createDate',
- label: '鑾峰緱鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'createTime',
- title: '鑾峰緱鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'nickname',
- title: '鐢ㄦ埛',
- minWidth: 150,
- },
- {
- field: 'point',
- title: '鑾峰緱绉垎',
- minWidth: 120,
- slots: {
- default: ({ row }) => {
- return h(
- Tag,
- {
- color: row.point > 0 ? 'blue' : 'red',
- },
- () => (row.point > 0 ? `+${row.point}` : row.point),
- );
- },
- },
- },
- {
- field: 'totalPoint',
- title: '鎬荤Н鍒�',
- minWidth: 100,
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 200,
- },
- {
- field: 'description',
- title: '鎻忚堪',
- minWidth: 200,
- },
- {
- field: 'bizId',
- title: '涓氬姟缂栫爜',
- minWidth: 120,
- },
- {
- field: 'bizType',
- title: '涓氬姟绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.MEMBER_POINT_BIZ_TYPE },
- },
- },
- ];
-}
diff --git a/src/views/member/point/record/index.vue b/src/views/member/point/record/index.vue
deleted file mode 100644
index 6acfb7a..0000000
--- a/src/views/member/point/record/index.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberPointRecordApi } from '#/api/member/point/record';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getRecordPage } from '#/api/member/point/record';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberPointRecordApi.Record>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳绛夌骇銆佺Н鍒嗐�佺鍒�"
- url="https://doc.iocoder.cn/member/level/"
- />
- </template>
-
- <Grid table-title="绉垎璁板綍鍒楄〃" />
- </Page>
-</template>
diff --git a/src/views/member/signin/config/data.ts b/src/views/member/signin/config/data.ts
deleted file mode 100644
index 34e28eb..0000000
--- a/src/views/member/signin/config/data.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'InputNumber',
- fieldName: 'day',
- label: '绛惧埌澶╂暟',
- help: '鍙厑璁歌缃� 1-7锛岄粯璁ょ鍒� 7 澶╀负涓�涓懆鏈�',
- componentProps: {
- class: '!w-full',
- min: 1,
- max: 7,
- precision: 0,
- placeholder: '璇疯緭鍏ョ鍒板ぉ鏁�',
- },
- rules: z.number().min(1).max(7, '绛惧埌澶╂暟蹇呴』鍦� 1-7 涔嬮棿'),
- },
- {
- component: 'InputNumber',
- fieldName: 'point',
- label: '鑾峰緱绉垎',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 0,
- placeholder: '璇疯緭鍏ヨ幏寰楃Н鍒�',
- },
- rules: z.number().min(0, '鑾峰緱绉垎涓嶈兘灏忎簬 0'),
- },
- {
- component: 'InputNumber',
- fieldName: 'experience',
- label: '濂栧姳缁忛獙',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 0,
- placeholder: '璇疯緭鍏ュ鍔辩粡楠�',
- },
- rules: z.number().min(0, '濂栧姳缁忛獙涓嶈兘灏忎簬 0'),
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'day',
- title: '绛惧埌澶╂暟',
- minWidth: 120,
- formatter: ({ cellValue }) => ['绗�', cellValue, '澶�'].join(' '),
- },
- {
- field: 'point',
- title: '鑾峰緱绉垎',
- minWidth: 120,
- },
- {
- field: 'experience',
- title: '濂栧姳缁忛獙',
- minWidth: 120,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/member/signin/config/index.vue b/src/views/member/signin/config/index.vue
deleted file mode 100644
index e1e4eea..0000000
--- a/src/views/member/signin/config/index.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberSignInConfigApi } from '#/api/member/signin/config';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteSignInConfig,
- getSignInConfigList,
-} from '#/api/member/signin/config';
-import { $t } from '#/locales';
-
-import { useGridColumns } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓绛惧埌閰嶇疆 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫绛惧埌閰嶇疆 */
-function handleEdit(row: MemberSignInConfigApi.SignInConfig) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎绛惧埌閰嶇疆 */
-async function handleDelete(row: MemberSignInConfigApi.SignInConfig) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting'),
- duration: 0,
- });
- try {
- await deleteSignInConfig(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async () => {
- return await getSignInConfigList();
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberSignInConfigApi.SignInConfig>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳绛夌骇銆佺Н鍒嗐�佺鍒�"
- url="https://doc.iocoder.cn/member/level/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="绛惧埌閰嶇疆鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绛惧埌閰嶇疆']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['point:sign-in-config:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['point:sign-in-config:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['point:sign-in-config:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.day]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/member/signin/config/modules/form.vue b/src/views/member/signin/config/modules/form.vue
deleted file mode 100644
index 614e1d1..0000000
--- a/src/views/member/signin/config/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { MemberSignInConfigApi } from '#/api/member/signin/config';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createSignInConfig,
- getSignInConfig,
- updateSignInConfig,
-} from '#/api/member/signin/config';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberSignInConfigApi.SignInConfig>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['绛惧埌閰嶇疆'])
- : $t('ui.actionTitle.create', ['绛惧埌閰嶇疆']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 90,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MemberSignInConfigApi.SignInConfig;
- try {
- await (formData.value?.id
- ? updateSignInConfig(data)
- : createSignInConfig(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberSignInConfigApi.SignInConfig>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getSignInConfig(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/signin/record/data.ts b/src/views/member/signin/record/data.ts
deleted file mode 100644
index 5449b88..0000000
--- a/src/views/member/signin/record/data.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { h } from 'vue';
-
-import { Tag } from 'ant-design-vue';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'nickname',
- label: '绛惧埌鐢ㄦ埛',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ鍒扮敤鎴�',
- allowClear: true,
- },
- },
- {
- fieldName: 'day',
- label: '绛惧埌澶╂暟',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ鍒板ぉ鏁�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '绛惧埌鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'nickname',
- title: '绛惧埌鐢ㄦ埛',
- minWidth: 150,
- },
- {
- field: 'day',
- title: '绛惧埌澶╂暟',
- minWidth: 120,
- formatter: ({ cellValue }) => ['绗�', cellValue, '澶�'].join(' '),
- },
- {
- field: 'point',
- title: '鑾峰緱绉垎',
- minWidth: 120,
- slots: {
- default: ({ row }) => {
- return h(
- Tag,
- {
- class: 'mr-5px',
- color: row.point > 0 ? 'blue' : 'red',
- },
- () => (row.point > 0 ? `+${row.point}` : row.point),
- );
- },
- },
- },
- {
- field: 'createTime',
- title: '绛惧埌鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/member/signin/record/index.vue b/src/views/member/signin/record/index.vue
deleted file mode 100644
index 6c82425..0000000
--- a/src/views/member/signin/record/index.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberSignInRecordApi } from '#/api/member/signin/record';
-
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSignInRecordPage } from '#/api/member/signin/record';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getSignInRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberSignInRecordApi.SignInRecord>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳绛夌骇銆佺Н鍒嗐�佺鍒�"
- url="https://doc.iocoder.cn/member/level/"
- />
- </template>
- <Grid table-title="绛惧埌璁板綍鍒楄〃" />
- </Page>
-</template>
diff --git a/src/views/member/tag/data.ts b/src/views/member/tag/data.ts
deleted file mode 100644
index 36f3c20..0000000
--- a/src/views/member/tag/data.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: '鏍囩鍚嶇О',
- componentProps: {
- placeholder: '璇疯緭鍏ユ爣绛惧悕绉�',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鏍囩鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ爣绛惧悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- placeholder: ['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡'],
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鏍囩鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 150,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/member/tag/index.vue b/src/views/member/tag/index.vue
deleted file mode 100644
index 554a926..0000000
--- a/src/views/member/tag/index.vue
+++ /dev/null
@@ -1,131 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberTagApi } from '#/api/member/tag';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteMemberTag, getMemberTagPage } from '#/api/member/tag';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓浼氬憳鏍囩 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫浼氬憳鏍囩 */
-function handleEdit(row: MemberTagApi.Tag) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎浼氬憳鏍囩 */
-async function handleDelete(row: MemberTagApi.Tag) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteMemberTag(row.id as number);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getMemberTagPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberTagApi.Tag>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳鐢ㄦ埛銆佹爣绛俱�佸垎缁�"
- url="https://doc.iocoder.cn/member/user/"
- />
- </template>
- <FormModal @success="handleRefresh" />
- <Grid table-title="浼氬憳鏍囩鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['浼氬憳鏍囩']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['member:tag:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['member:tag:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['member:tag:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/member/tag/modules/form.vue b/src/views/member/tag/modules/form.vue
deleted file mode 100644
index 16e089c..0000000
--- a/src/views/member/tag/modules/form.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script lang="ts" setup>
-import type { MemberTagApi } from '#/api/member/tag';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createMemberTag,
- getMemberTag,
- updateMemberTag,
-} from '#/api/member/tag';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberTagApi.Tag>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['浼氬憳鏍囩'])
- : $t('ui.actionTitle.create', ['浼氬憳鏍囩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MemberTagApi.Tag;
- try {
- await (formData.value?.id
- ? updateMemberTag(data)
- : createMemberTag(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberTagApi.Tag>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getMemberTag(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-2/5" :title="getTitle">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/user/data.ts b/src/views/member/user/data.ts
deleted file mode 100644
index 7b4f2e3..0000000
--- a/src/views/member/user/data.ts
+++ /dev/null
@@ -1,529 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { h, markRaw } from 'vue';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-import { convertToInteger, formatToFraction } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { z } from '#/adapter/form';
-import { getSimpleGroupList } from '#/api/member/group';
-import { getSimpleLevelList } from '#/api/member/level';
-import { getSimpleTagList } from '#/api/member/tag';
-import { AreaCascader } from '#/components/area';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'mobile',
- label: '鎵嬫満鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ墜鏈哄彿',
- },
- rules: 'required',
- },
- {
- fieldName: 'email',
- label: '閭',
- component: 'Input',
- componentProps: {
- allowClear: true,
- maxlength: 50,
- placeholder: '璇疯緭鍏ラ偖绠�',
- },
- rules: z.string().email('閭鏍煎紡涓嶆纭�').or(z.literal('')).optional(),
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE).optional(),
- },
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋樀绉�',
- },
- },
- {
- fieldName: 'avatar',
- label: '澶村儚',
- component: 'ImageUpload',
- },
- {
- fieldName: 'name',
- label: '鐪熷疄鍚嶅瓧',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ湡瀹炲悕瀛�',
- },
- },
- {
- fieldName: 'sex',
- label: '鐢ㄦ埛鎬у埆',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- {
- fieldName: 'birthday',
- label: '鍑虹敓鏃ユ湡',
- component: 'DatePicker',
- componentProps: {
- format: 'YYYY-MM-DD',
- valueFormat: 'x',
- placeholder: '璇烽�夋嫨鍑虹敓鏃ユ湡',
- },
- },
- {
- fieldName: 'areaId',
- label: '鎵�鍦ㄥ湴',
- component: markRaw(AreaCascader),
- componentProps: {
- allowClear: true,
- changeOnSelect: true,
- class: '!w-full',
- placeholder: '璇烽�夋嫨鎵�鍦ㄥ湴',
- showSearch: true,
- },
- },
- {
- fieldName: 'tagIds',
- label: '鐢ㄦ埛鏍囩',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleTagList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨鐢ㄦ埛鏍囩',
- },
- },
- {
- fieldName: 'groupId',
- label: '鐢ㄦ埛鍒嗙粍',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleGroupList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鐢ㄦ埛鍒嗙粍',
- },
- },
- {
- fieldName: 'mark',
- label: '浼氬憳澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ細鍛樺娉�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋樀绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'mobile',
- label: '鎵嬫満鍙�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ墜鏈哄彿',
- allowClear: true,
- },
- },
- {
- fieldName: 'email',
- label: '閭',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ラ偖绠�',
- allowClear: true,
- },
- },
- {
- fieldName: 'loginDate',
- label: '鐧诲綍鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '娉ㄥ唽鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- {
- fieldName: 'tagIds',
- label: '鐢ㄦ埛鏍囩',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleTagList,
- labelField: 'name',
- valueField: 'id',
- mode: 'multiple',
- placeholder: '璇烽�夋嫨鐢ㄦ埛鏍囩',
- allowClear: true,
- },
- },
- {
- fieldName: 'levelId',
- label: '鐢ㄦ埛绛夌骇',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleLevelList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鐢ㄦ埛绛夌骇',
- allowClear: true,
- },
- },
- {
- fieldName: 'groupId',
- label: '鐢ㄦ埛鍒嗙粍',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleGroupList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鐢ㄦ埛鍒嗙粍',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- type: 'checkbox',
- width: 50,
- },
- {
- field: 'id',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 100,
- },
- {
- field: 'avatar',
- title: '澶村儚',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'mobile',
- title: '鎵嬫満鍙�',
- minWidth: 120,
- },
- {
- field: 'email',
- title: '閭',
- minWidth: 180,
- },
- {
- field: 'nickname',
- title: '鏄电О',
- minWidth: 120,
- },
- {
- field: 'levelName',
- title: '绛夌骇',
- minWidth: 100,
- },
- {
- field: 'groupName',
- title: '鍒嗙粍',
- minWidth: 100,
- },
- {
- field: 'tagNames',
- title: '鐢ㄦ埛鏍囩',
- minWidth: 150,
- slots: {
- default: ({ row }) => {
- return row.tagNames?.map((tagName: string, index: number) => {
- return h(
- Tag,
- {
- key: index,
- class: 'mr-1',
- color: 'blue',
- },
- () => tagName,
- );
- });
- },
- },
- },
- {
- field: 'point',
- title: '绉垎',
- minWidth: 80,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'loginDate',
- title: '鐧诲綍鏃堕棿',
- minWidth: 160,
- formatter: 'formatDateTime',
- },
- {
- field: 'createTime',
- title: '娉ㄥ唽鏃堕棿',
- minWidth: 160,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 淇敼鐢ㄦ埛绛夌骇 */
-export function useLevelFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'levelId',
- label: '鐢ㄦ埛绛夌骇',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleLevelList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '璇烽�夋嫨鐢ㄦ埛绛夌骇',
- allowClear: true,
- },
- },
- {
- fieldName: 'reason',
- label: '淇敼鍘熷洜',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ヤ慨鏀瑰師鍥�',
- },
- rules: 'required',
- },
- ];
-}
-
-/** 淇敼鐢ㄦ埛浣欓 */
-export function useBalanceFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'balance',
- label: '鍙樺姩鍓嶄綑棰�(鍏�)',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'changeType',
- label: '鍙樺姩绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: [
- { label: '澧炲姞', value: 1 },
- { label: '鍑忓皯', value: -1 },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 1,
- },
- {
- fieldName: 'changeBalance',
- label: '鍙樺姩浣欓(鍏�)',
- component: 'InputNumber',
- rules: 'required',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- step: 0.1,
- placeholder: '璇疯緭鍏ュ彉鍔ㄤ綑棰�',
- },
- defaultValue: 0,
- },
- {
- fieldName: 'balanceResult',
- label: '鍙樺姩鍚庝綑棰�(鍏�)',
- component: 'Input',
- dependencies: {
- triggerFields: ['balance', 'changeBalance', 'changeType'],
- disabled: true,
- trigger(values, form) {
- form.setFieldValue(
- 'balanceResult',
- formatToFraction(
- convertToInteger(values.balance) +
- convertToInteger(values.changeBalance) * values.changeType,
- ),
- );
- },
- },
- },
- ];
-}
-
-/** 淇敼鐢ㄦ埛绉垎 */
-export function usePointFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'nickname',
- label: '鐢ㄦ埛鏄电О',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'point',
- label: '鍙樺姩鍓嶇Н鍒�',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'changeType',
- label: '鍙樺姩绫诲瀷',
- component: 'RadioGroup',
- componentProps: {
- options: [
- { label: '澧炲姞', value: 1 },
- { label: '鍑忓皯', value: -1 },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 1,
- },
- {
- fieldName: 'changePoint',
- label: '鍙樺姩绉垎',
- component: 'InputNumber',
- rules: 'required',
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 0,
- placeholder: '璇疯緭鍏ュ彉鍔ㄧН鍒�',
- },
- },
- {
- fieldName: 'pointResult',
- label: '鍙樺姩鍚庣Н鍒�',
- component: 'Input',
- componentProps: {
- placeholder: '',
- },
- dependencies: {
- triggerFields: ['point', 'changePoint', 'changeType'],
- disabled: true,
- trigger(values, form) {
- form.setFieldValue(
- 'pointResult',
- values.point + values.changePoint * values.changeType ||
- values.point,
- );
- },
- },
- rules: z.number().min(0),
- },
- ];
-}
diff --git a/src/views/member/user/detail/index.vue b/src/views/member/user/detail/index.vue
deleted file mode 100644
index 901b71f..0000000
--- a/src/views/member/user/detail/index.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<script setup lang="ts">
-import type { MemberUserApi } from '#/api/member/user';
-import type { PayWalletApi } from '#/api/pay/wallet/balance';
-
-import { onMounted, ref } from 'vue';
-import { useRoute } from 'vue-router';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { useTabs } from '../../../../packages/effects/hooks/src';
-
-import { Button, Card, message, TabPane, Tabs } from 'ant-design-vue';
-
-import { getUser } from '#/api/member/user';
-import { getWallet } from '#/api/pay/wallet/balance';
-import { $t } from '#/locales';
-
-import Form from '../modules/form.vue';
-import AccountInfo from './modules/account-info.vue';
-import AddressList from './modules/address-list.vue';
-import AfterSaleList from './modules/after-sale-list.vue';
-import BalanceList from './modules/balance-list.vue';
-import BasicInfo from './modules/basic-info.vue';
-import BrokerageList from './modules/brokerage-list.vue';
-import CouponList from './modules/coupon-list.vue';
-import ExperienceRecordList from './modules/experience-record-list.vue';
-import FavoriteList from './modules/favorite-list.vue';
-import OrderList from './modules/order-list.vue';
-import PointList from './modules/point-list.vue';
-import SignList from './modules/sign-list.vue';
-
-const route = useRoute();
-const { closeCurrentTab, refreshTab } = useTabs();
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const userId = Number(route.query.id);
-const user = ref<MemberUserApi.User>();
-const wallet = ref<PayWalletApi.Wallet>();
-
-/** 鑾峰彇浼氬憳璇︽儏 */
-async function getUserDetail() {
- if (!userId) {
- message.error('鍙傛暟閿欒锛屼細鍛樼紪鍙蜂笉鑳戒负绌猴紒');
- await closeCurrentTab();
- return;
- }
- user.value = await getUser(userId);
- wallet.value = (await getWallet({ userId })) || {
- balance: 0,
- totalExpense: 0,
- totalRecharge: 0,
- };
-}
-
-/** 缂栬緫浼氬憳 */
-function handleEdit() {
- formModalApi.setData(user.value).open();
-}
-
-/** 鍒濆鍖� */
-onMounted(async () => {
- await getUserDetail();
-});
-</script>
-<template>
- <Page auto-content-height>
- <FormModal @success="refreshTab" />
- <div class="flex">
- <BasicInfo v-if="user" class="w-3/5" :user="user" mode="member">
- <template #title> 鍩烘湰淇℃伅 </template>
- <template #extra>
- <Button type="primary" @click="handleEdit">
- {{ $t('common.edit') }}
- </Button>
- </template>
- </BasicInfo>
- <AccountInfo
- v-if="user && wallet"
- class="ml-4 w-2/5"
- :user="user"
- :wallet="wallet"
- >
- <template #title> 璐︽埛淇℃伅 </template>
- </AccountInfo>
- </div>
- <div class="mt-4">
- <Card title="璐︽埛鏄庣粏">
- <Tabs>
- <TabPane tab="绉垎" key="PointList">
- <PointList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="绛惧埌" key="SignList">
- <SignList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="鎴愰暱鍊�" key="ExperienceRecordList">
- <ExperienceRecordList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="浣欓" key="BalanceList">
- <BalanceList class="h-full" :wallet-id="wallet?.id" />
- </TabPane>
- <TabPane tab="鏀惰揣鍦板潃" key="AddressList">
- <AddressList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="璁㈠崟绠$悊" key="OrderList">
- <OrderList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="鍞悗绠$悊" key="AfterSaleList">
- <AfterSaleList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="鏀惰棌璁板綍" key="FavoriteList">
- <FavoriteList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="浼樻儬鍔�" key="CouponList">
- <CouponList class="h-full" :user-id="userId" />
- </TabPane>
- <TabPane tab="鎺ㄥ箍鐢ㄦ埛" key="BrokerageList">
- <BrokerageList class="h-full" :user-id="userId" />
- </TabPane>
- </Tabs>
- </Card>
- </div>
- </Page>
-</template>
diff --git a/src/views/member/user/detail/modules/account-info.vue b/src/views/member/user/detail/modules/account-info.vue
deleted file mode 100644
index 9c3920e..0000000
--- a/src/views/member/user/detail/modules/account-info.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script setup lang="ts">
-import type { MemberUserApi } from '#/api/member/user';
-import type { PayWalletApi } from '#/api/pay/wallet/balance';
-
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { Card } from 'ant-design-vue';
-
-import { useDescription } from '#/components/description';
-
-const props = withDefaults(
- defineProps<{
- mode?: 'kefu' | 'member';
- user: MemberUserApi.User;
- wallet: PayWalletApi.Wallet;
- }>(),
- {
- mode: 'member',
- },
-);
-
-const [Descriptions] = useDescription({
- bordered: false,
- column: props.mode === 'member' ? 2 : 1,
- schema: [
- {
- field: 'levelName',
- label: '绛夌骇',
- render: (val) => val || '-',
- },
- {
- field: 'experience',
- label: '鎴愰暱鍊�',
- render: (val) => val || 0,
- },
- {
- field: 'point',
- label: '褰撳墠绉垎',
- render: (val) => val || 0,
- },
- {
- field: 'totalPoint',
- label: '鎬荤Н鍒�',
- render: (val) => val || 0,
- },
- {
- field: 'balance',
- label: '褰撳墠浣欓',
- render: (val) => fenToYuan(val || 0),
- },
- {
- field: 'totalExpense',
- label: '鏀嚭閲戦',
- render: (val) => fenToYuan(val || 0),
- },
- {
- field: 'totalRecharge',
- label: '鍏呭�奸噾棰�',
- render: (val) => fenToYuan(val || 0),
- },
- ],
-});
-</script>
-
-<template>
- <Card>
- <template #title>
- <slot name="title"></slot>
- </template>
- <template #extra>
- <slot name="extra"></slot>
- </template>
- <Descriptions
- :column="mode === 'member' ? 2 : 1"
- :data="{
- ...user,
- ...wallet,
- }"
- />
- </Card>
-</template>
diff --git a/src/views/member/user/detail/modules/address-list.vue b/src/views/member/user/detail/modules/address-list.vue
deleted file mode 100644
index f697b38..0000000
--- a/src/views/member/user/detail/modules/address-list.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberAddressApi } from '#/api/member/address';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getAddressList } from '#/api/member/address';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const columns = [
- {
- field: 'id',
- title: '鍦板潃缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '鏀朵欢浜哄悕绉�',
- minWidth: 120,
- },
- {
- field: 'mobile',
- title: '鎵嬫満鍙�',
- minWidth: 130,
- },
- {
- field: 'areaName',
- title: '鎵�鍦ㄥ湴鍖�',
- minWidth: 120,
- },
- {
- field: 'detailAddress',
- title: '鏀朵欢璇︾粏鍦板潃',
- minWidth: 200,
- },
- {
- field: 'defaultStatus',
- title: '鏄惁榛樿',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- formatter: 'formatDateTime',
- minWidth: 160,
- },
-];
-
-const [Grid] = useVbenVxeGrid({
- gridOptions: {
- columns,
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async () => {
- return await getAddressList({
- userId: props.userId,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberAddressApi.Address>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/after-sale-list.vue b/src/views/member/user/detail/modules/after-sale-list.vue
deleted file mode 100644
index 78ac7b1..0000000
--- a/src/views/member/user/detail/modules/after-sale-list.vue
+++ /dev/null
@@ -1,155 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallAfterSaleApi } from '#/api/mall/trade/afterSale';
-
-import { onMounted, ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { Button, Image, Tabs, Tag } from 'ant-design-vue';
-
-import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getAfterSalePage } from '#/api/mall/trade/afterSale';
-import {
- useGridColumns,
- useGridFormSchema,
-} from '#/views/mall/trade/afterSale/data';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const { push } = useRouter();
-
-const statusTabs = ref([
- {
- label: '鍏ㄩ儴',
- value: '0',
- },
-]);
-const statusTab = ref(statusTabs.value[0]!.value);
-
-/** 澶勭悊閫�娆� */
-function handleOpenAfterSaleDetail(row: MallAfterSaleApi.AfterSale) {
- push({ name: 'TradeAfterSaleDetail', params: { id: row.id } });
-}
-
-/** 鏌ョ湅璁㈠崟璇︽儏 */
-function handleOpenOrderDetail(row: MallAfterSaleApi.AfterSale) {
- push({ name: 'TradeOrderDetail', params: { id: row.orderId } });
-}
-
-/** 鍒囨崲鍞悗鐘舵�� */
-function handleChangeStatus(key: number | string) {
- statusTab.value = key.toString();
- gridApi.query();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 60,
- },
- columns: useGridColumns(),
- keepSource: true,
- pagerConfig: {
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAfterSalePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- status:
- statusTab.value === '0' ? undefined : Number(statusTab.value),
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallAfterSaleApi.AfterSale>,
-});
-
-/** 鍒濆鍖� */
-onMounted(() => {
- for (const dict of getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS)) {
- statusTabs.value.push({
- label: dict.label,
- value: dict.value.toString(),
- });
- }
-});
-</script>
-
-<template>
- <Grid>
- <template #toolbar-actions>
- <Tabs
- v-model:active-key="statusTab"
- class="w-full"
- @change="handleChangeStatus"
- >
- <Tabs.TabPane
- v-for="tab in statusTabs"
- :key="tab.value"
- :tab="tab.label"
- />
- </Tabs>
- </template>
- <template #orderNo="{ row }">
- <Button type="link" @click="handleOpenOrderDetail(row)">
- {{ row.orderNo }}
- </Button>
- </template>
- <template #productInfo="{ row }">
- <div class="flex items-start gap-2 text-left">
- <Image
- v-if="row.picUrl"
- :src="row.picUrl"
- :width="40"
- :height="40"
- :preview="{ src: row.picUrl }"
- />
- <div class="flex flex-1 flex-col gap-1">
- <span class="text-sm">{{ row.spuName }}</span>
- <div class="mt-1 flex flex-wrap gap-1">
- <Tag
- v-for="property in row.properties"
- :key="property.propertyId!"
- size="small"
- color="blue"
- >
- {{ property.propertyName }}: {{ property.valueName }}
- </Tag>
- </div>
- </div>
- </div>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '澶勭悊閫�娆�',
- type: 'link',
- onClick: handleOpenAfterSaleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
-</template>
diff --git a/src/views/member/user/detail/modules/balance-list.vue b/src/views/member/user/detail/modules/balance-list.vue
deleted file mode 100644
index 86b148c..0000000
--- a/src/views/member/user/detail/modules/balance-list.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { WalletTransactionApi } from '#/api/pay/wallet/transaction';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getTransactionPage } from '#/api/pay/wallet/transaction';
-import { useTransactionGridColumns } from '#/views/pay/wallet/balance/data';
-
-const props = defineProps<{
- walletId: number | undefined;
-}>();
-
-const [Grid] = useVbenVxeGrid({
- gridOptions: {
- columns: useTransactionGridColumns(),
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getTransactionPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- walletId: props.walletId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<WalletTransactionApi.Transaction>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/basic-info.vue b/src/views/member/user/detail/modules/basic-info.vue
deleted file mode 100644
index 8512118..0000000
--- a/src/views/member/user/detail/modules/basic-info.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<script setup lang="ts">
-import type { MemberUserApi } from '#/api/member/user';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { formatDate } from '../../../../../packages/utils/src';
-
-import { Avatar, Card, Col, Row } from 'ant-design-vue';
-
-import { useDescription } from '#/components/description';
-import { DictTag } from '#/components/dict-tag';
-
-const props = withDefaults(
- defineProps<{ mode?: 'kefu' | 'member'; user: MemberUserApi.User }>(),
- {
- mode: 'member',
- },
-);
-
-const [Descriptions] = useDescription({
- bordered: false,
- column: props.mode === 'member' ? 2 : 1,
- schema: [
- {
- field: 'name',
- label: '鐢ㄦ埛鍚�',
- },
- {
- field: 'nickname',
- label: '鏄电О',
- },
- {
- field: 'mobile',
- label: '鎵嬫満鍙�',
- },
- {
- field: 'email',
- label: '閭',
- },
- {
- field: 'sex',
- label: '鎬у埆',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.SYSTEM_USER_SEX,
- value: val,
- }),
- },
- {
- field: 'areaName',
- label: '鎵�鍦ㄥ湴',
- },
- {
- field: 'registerIp',
- label: '娉ㄥ唽 IP',
- },
- {
- field: 'birthday',
- label: '鐢熸棩',
- render: (val) => formatDate(val)?.toString() || '-',
- },
- {
- field: 'createTime',
- label: '娉ㄥ唽鏃堕棿',
- render: (val) => formatDate(val)?.toString() || '-',
- },
- {
- field: 'loginDate',
- label: '鏈�鍚庣櫥褰曟椂闂�',
- render: (val) => formatDate(val)?.toString() || '-',
- },
- ],
-});
-</script>
-
-<template>
- <Card>
- <template #title>
- <slot name="title"></slot>
- </template>
- <template #extra>
- <slot name="extra"></slot>
- </template>
- <Row v-if="mode === 'member'" :gutter="24">
- <Col :span="6">
- <Avatar :size="180" shape="square" :src="user.avatar" />
- </Col>
- <Col :span="18">
- <Descriptions :column="2" :data="user" />
- </Col>
- </Row>
- <template v-else-if="mode === 'kefu'">
- <Avatar :size="140" shape="square" :src="user.avatar" />
- <Descriptions :column="1" :data="user" />
- </template>
- </Card>
-</template>
diff --git a/src/views/member/user/detail/modules/brokerage-list.vue b/src/views/member/user/detail/modules/brokerage-list.vue
deleted file mode 100644
index 5adb98f..0000000
--- a/src/views/member/user/detail/modules/brokerage-list.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<script setup lang="ts">
-import type {
- VxeGridPropTypes,
- VxeTableGridOptions,
-} from '#/adapter/vxe-table';
-import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getBrokerageUserPage } from '#/api/mall/trade/brokerage/user';
-import { getRangePickerDefaultProps } from '#/utils';
-
-defineOptions({ name: 'BrokerageList' });
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const formSchema = (): any[] => {
- return [
- {
- fieldName: 'level',
- label: '鐢ㄦ埛绫诲瀷',
- component: 'Select',
- componentProps: {
- options: [
- {
- label: '鍏ㄩ儴',
- value: 0,
- },
- {
- label: '涓�绾�',
- value: 1,
- },
- {
- label: '浜岀骇',
- value: 2,
- },
- ],
- placeholder: '璇烽�夋嫨鐢ㄦ埛绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'bindUserTime',
- label: '缁戝畾鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- clearable: true,
- },
- },
- ];
-};
-
-const columns = (): VxeGridPropTypes.Columns => {
- return [
- {
- field: 'id',
- title: '鐢ㄦ埛缂栧彿',
- },
- {
- field: 'avatar',
- title: '澶村儚',
- cellRender: {
- name: 'CellImage',
- props: {
- height: 40,
- width: 40,
- shape: 'circle',
- },
- },
- },
- {
- field: 'nickname',
- title: '鏄电О',
- },
- {
- field: 'level',
- title: '绛夌骇',
- formatter: (row: any) => {
- return row.level === 1 ? '涓�绾�' : '浜岀骇';
- },
- },
- {
- field: 'bindUserTime',
- title: '缁戝畾鏃堕棿',
- formatter: 'formatDateTime',
- },
- ];
-};
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: formSchema(),
- },
- gridOptions: {
- columns: columns(),
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getBrokerageUserPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- bindUserId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallBrokerageUserApi.BrokerageUser>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/coupon-list.vue b/src/views/member/user/detail/modules/coupon-list.vue
deleted file mode 100644
index 7428a86..0000000
--- a/src/views/member/user/detail/modules/coupon-list.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallCouponApi } from '#/api/mall/promotion/coupon/coupon';
-
-import { ref } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { message, TabPane, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteCoupon,
- getCouponPage,
-} from '#/api/mall/promotion/coupon/coupon';
-import {
- useGridColumns as useCouponGridColumns,
- useGridFormSchema as useCouponGridFormSchema,
-} from '#/views/mall/promotion/coupon/data';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const activeTab = ref('all');
-const statusTabs = ref(getStatusTabs());
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗曪紙杩囨护鎺変細鍛樼浉鍏冲瓧娈碉級 */
-function useGridFormSchema() {
- const excludeFields = new Set(['nickname']);
- return useCouponGridFormSchema().filter(
- (item) => !excludeFields.has(item.fieldName),
- );
-}
-
-/** 鍒楄〃鐨勫瓧娈碉紙杩囨护鎺変細鍛樼浉鍏冲瓧娈碉級 */
-function useGridColumns() {
- const excludeFields = new Set(['nickname']);
- return useCouponGridColumns()?.filter(
- (item) => item.field && !excludeFields.has(item.field),
- );
-}
-
-/** 鑾峰彇鐘舵�侀�夐」鍗¢厤缃� */
-function getStatusTabs() {
- const tabs = [
- {
- label: '鍏ㄩ儴',
- value: 'all',
- },
- ];
- const statusOptions = getDictOptions(DICT_TYPE.PROMOTION_COUPON_STATUS);
- for (const option of statusOptions) {
- tabs.push({
- label: option.label,
- value: String(option.value),
- });
- }
- return tabs;
-}
-
-/** Tab 鍒囨崲 */
-function handleTabChange(tabName: any) {
- activeTab.value = tabName;
- gridApi.query();
-}
-
-/** 鍒犻櫎浼樻儬鍒� */
-async function handleDelete(row: MallCouponApi.Coupon) {
- const hideLoading = message.loading({
- content: '鍥炴敹涓�...',
- duration: 0,
- });
- try {
- await deleteCoupon(row.id!);
- message.success('鍥炴敹鎴愬姛');
- await gridApi.query();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- keepSource: true,
- pagerConfig: {
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const params = {
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- // Tab鐘舵�佽繃婊�
- status:
- activeTab.value === 'all' ? undefined : Number(activeTab.value),
- };
- return await getCouponPage(params);
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallCouponApi.Coupon>,
-});
-</script>
-
-<template>
- <Grid>
- <template #toolbar-actions>
- <Tabs class="w-full" @change="handleTabChange">
- <TabPane v-for="tab in statusTabs" :key="tab.value" :tab="tab.label" />
- </Tabs>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍥炴敹',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['promotion:coupon:delete'],
- popConfirm: {
- title:
- '鍥炴敹灏嗕細鏀跺洖浼氬憳棰嗗彇鐨勫緟浣跨敤鐨勪紭鎯犲埜锛屽凡浣跨敤鐨勫皢鏃犳硶鍥炴敹锛岀‘瀹氳鍥炴敹鎵�閫変紭鎯犲埜鍚楋紵',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
-</template>
diff --git a/src/views/member/user/detail/modules/experience-record-list.vue b/src/views/member/user/detail/modules/experience-record-list.vue
deleted file mode 100644
index a264c13..0000000
--- a/src/views/member/user/detail/modules/experience-record-list.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<script lang="ts" setup>
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridProps, VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberExperienceRecordApi } from '#/api/member/experience-record';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { getDictOptions } from '../../../../../packages/effects/hooks/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getExperienceRecordPage } from '#/api/member/experience-record';
-import { getRangePickerDefaultProps } from '#/utils';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-/** 琛ㄥ崟鎼滅储 schema */
-function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'bizType',
- label: '涓氬姟绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.MEMBER_EXPERIENCE_BIZ_TYPE, 'number'),
- placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'title',
- label: '鏍囬',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ爣棰�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createDate',
- label: '鑾峰緱鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-function useGridColumns(): VxeGridProps['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'createTime',
- title: '鑾峰緱鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'experience',
- title: '缁忛獙',
- minWidth: 100,
- slots: {
- default: ({ row }) => {
- return h(
- Tag,
- {
- class: 'mr-1',
- color: row.experience > 0 ? 'blue' : 'red',
- },
- () => (row.experience > 0 ? `+${row.experience}` : row.experience),
- );
- },
- },
- },
- {
- field: 'totalExperience',
- title: '鎬荤粡楠�',
- minWidth: 100,
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 200,
- },
- {
- field: 'description',
- title: '鎻忚堪',
- minWidth: 250,
- },
- {
- field: 'bizId',
- title: '涓氬姟缂栧彿',
- minWidth: 120,
- },
- {
- field: 'bizType',
- title: '涓氬姟绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.MEMBER_EXPERIENCE_BIZ_TYPE },
- },
- },
- ];
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getExperienceRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberExperienceRecordApi.ExperienceRecord>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/favorite-list.vue b/src/views/member/user/detail/modules/favorite-list.vue
deleted file mode 100644
index 7de6485..0000000
--- a/src/views/member/user/detail/modules/favorite-list.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<script setup lang="ts">
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallFavoriteApi } from '#/api/mall/product/favorite';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getFavoritePage } from '#/api/mall/product/favorite';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const columns = [
- {
- field: 'id',
- title: '鍟嗗搧缂栧彿',
- minWidth: 100,
- },
- {
- field: 'picUrl',
- title: '鍟嗗搧鍥�',
- minWidth: 100,
- cellRender: {
- name: 'CellImage',
- props: {
- width: 24,
- height: 24,
- },
- },
- },
- {
- field: 'name',
- title: '鍟嗗搧鍚嶇О',
- minWidth: 200,
- },
- {
- field: 'price',
- title: '鍟嗗搧鍞环',
- formatter: 'formatAmount2',
- minWidth: 120,
- },
- {
- field: 'salesCount',
- title: '閿�閲�',
- minWidth: 100,
- },
- {
- field: 'createTime',
- title: '鏀惰棌鏃堕棿',
- formatter: 'formatDateTime',
- minWidth: 160,
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PRODUCT_SPU_STATUS },
- },
- },
-];
-
-const [Grid] = useVbenVxeGrid({
- gridOptions: {
- columns,
- keepSource: true,
- pagerConfig: {
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getFavoritePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallFavoriteApi.Favorite>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/order-list.vue b/src/views/member/user/detail/modules/order-list.vue
deleted file mode 100644
index a789bbb..0000000
--- a/src/views/member/user/detail/modules/order-list.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MallOrderApi } from '#/api/mall/trade/order';
-
-import { useRouter } from 'vue-router';
-
-import { DICT_TYPE } from '../../../../../packages/constants/src';
-import { fenToYuan } from '../../../../../packages/utils/src';
-
-import { Image, List, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getOrderPage } from '#/api/mall/trade/order';
-import { DictTag } from '#/components/dict-tag';
-import { $t } from '#/locales';
-import {
- useGridColumns,
- useGridFormSchema as useOrderGridFormSchema,
-} from '#/views/mall/trade/order/data';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const { push } = useRouter();
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗曪紙杩囨护鎺夌敤鎴风浉鍏冲瓧娈碉級 */
-function useGridFormSchema() {
- const excludeFields = new Set(['userId', 'userNickname']);
- return useOrderGridFormSchema().filter(
- (item) => !excludeFields.has(item.fieldName),
- );
-}
-
-/** 璇︽儏 */
-function handleDetail(row: MallOrderApi.Order) {
- push({ name: 'TradeOrderDetail', params: { id: row.id } });
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- expandConfig: {
- trigger: 'row',
- expandAll: true,
- padding: true,
- },
- columns: useGridColumns(),
- keepSource: true,
- pagerConfig: {
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getOrderPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MallOrderApi.Order>,
-});
-</script>
-
-<template>
- <Grid table-title="璁㈠崟鍒楄〃">
- <template #expand_content="{ row }">
- <List item-layout="vertical" :data-source="row.items">
- <template #renderItem="{ item }">
- <List.Item>
- <List.Item.Meta>
- <template #title>
- {{ item.spuName }}
- <Tag
- color="blue"
- v-for="property in item.properties"
- :key="property.propertyId"
- >
- {{ property.propertyName }} : {{ property.valueName }}
- </Tag>
- </template>
- <template #avatar>
- <Image :src="item.picUrl" :width="40" :height="40" />
- </template>
- <template #description>
- {{
- `鍘熶环锛�${fenToYuan(item.price)} 鍏� / 鏁伴噺锛�${item.count} 涓猔
- }}
- |
- <DictTag
- :type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
- :value="item.afterSaleStatus"
- />
- </template>
- </List.Item.Meta>
- </List.Item>
- </template>
- </List>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['trade:order:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
-</template>
diff --git a/src/views/member/user/detail/modules/point-list.vue b/src/views/member/user/detail/modules/point-list.vue
deleted file mode 100644
index 40ef482..0000000
--- a/src/views/member/user/detail/modules/point-list.vue
+++ /dev/null
@@ -1,68 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberPointRecordApi } from '#/api/member/point/record';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getRecordPage } from '#/api/member/point/record';
-import {
- useGridColumns as usePointGridColumns,
- useGridFormSchema as usePointGridFormSchema,
-} from '#/views/member/point/record/data';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗曪紙杩囨护鎺夌敤鎴风浉鍏冲瓧娈碉級 */
-function useGridFormSchema() {
- const excludeFields = new Set(['nickname']);
- return usePointGridFormSchema().filter(
- (item) => !excludeFields.has(item.fieldName),
- );
-}
-
-/** 鍒楄〃鐨勫瓧娈碉紙杩囨护鎺夌敤鎴风浉鍏冲瓧娈碉級 */
-function useGridColumns() {
- const excludeFields = new Set(['nickname']);
- return usePointGridColumns()?.filter(
- (item) => item.field && !excludeFields.has(item.field),
- );
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- keepSource: true,
- pagerConfig: {
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberPointRecordApi.Record>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/detail/modules/sign-list.vue b/src/views/member/user/detail/modules/sign-list.vue
deleted file mode 100644
index afede65..0000000
--- a/src/views/member/user/detail/modules/sign-list.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberSignInRecordApi } from '#/api/member/signin/record';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getSignInRecordPage } from '#/api/member/signin/record';
-import {
- useGridColumns as useSignInGridColumns,
- useGridFormSchema as useSignInGridFormSchema,
-} from '#/views/member/signin/record/data';
-
-const props = defineProps<{
- userId: number;
-}>();
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗曪紙杩囨护鎺夌敤鎴风浉鍏冲瓧娈碉級 */
-function useGridFormSchema() {
- const excludeFields = new Set(['nickname']);
- return useSignInGridFormSchema().filter(
- (item) => !excludeFields.has(item.fieldName),
- );
-}
-
-/** 鍒楄〃鐨勫瓧娈碉紙杩囨护鎺夌敤鎴风浉鍏冲瓧娈碉級 */
-function useGridColumns() {
- const excludeFields = new Set(['nickname']);
- return useSignInGridColumns()?.filter(
- (item) => item.field && !excludeFields.has(item.field),
- );
-}
-
-const [Grid] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getSignInRecordPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- userId: props.userId,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberSignInRecordApi.SignInRecord>,
-});
-</script>
-
-<template>
- <Grid />
-</template>
diff --git a/src/views/member/user/index.vue b/src/views/member/user/index.vue
deleted file mode 100644
index a7c5eaa..0000000
--- a/src/views/member/user/index.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MemberUserApi } from '#/api/member/user';
-
-import { ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { isEmpty } from '../../../packages/utils/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getUserPage } from '#/api/member/user';
-import { $t } from '#/locales';
-
-import { CouponSendForm } from '../../mall/promotion/coupon/components';
-import { useGridColumns, useGridFormSchema } from './data';
-import BalanceForm from './modules/balance-form.vue';
-import Form from './modules/form.vue';
-import LevelForm from './modules/level-form.vue';
-import PointForm from './modules/point-form.vue';
-
-const router = useRouter();
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [PointFormModal, pointFormModalApi] = useVbenModal({
- connectedComponent: PointForm,
- destroyOnClose: true,
-});
-
-const [BalanceFormModal, balanceFormModalApi] = useVbenModal({
- connectedComponent: BalanceForm,
- destroyOnClose: true,
-});
-
-const [LevelFormModal, levelFormModalApi] = useVbenModal({
- connectedComponent: LevelForm,
- destroyOnClose: true,
-});
-
-const [CouponSendFormModal, couponSendFormModalApi] = useVbenModal({
- connectedComponent: CouponSendForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 缂栬緫浼氬憳 */
-function handleEdit(row: MemberUserApi.User) {
- formModalApi.setData(row).open();
-}
-
-/** 淇敼浼氬憳绛夌骇 */
-function handleUpdateLevel(row: MemberUserApi.User) {
- levelFormModalApi.setData(row).open();
-}
-
-/** 淇敼浼氬憳绉垎 */
-function handleUpdatePoint(row: MemberUserApi.User) {
- pointFormModalApi.setData(row).open();
-}
-
-/** 淇敼浼氬憳浣欓 */
-function handleUpdateBalance(row: MemberUserApi.User) {
- balanceFormModalApi.setData(row).open();
-}
-
-/** 鍙戦�佷紭鎯犲埜 */
-async function handleSendCoupon() {
- couponSendFormModalApi
- .setData({
- userIds: checkedIds.value,
- })
- .open();
-}
-
-const checkedIds = ref<number[]>([]);
-function handleRowCheckboxChange({
- records,
-}: {
- records: MemberUserApi.User[];
-}) {
- checkedIds.value = records.map((item) => item.id!);
-}
-
-/** 鏌ョ湅浼氬憳璇︽儏 */
-function handleViewDetail(row: MemberUserApi.User) {
- router.push({
- name: 'MemberUserDetail',
- query: {
- id: row.id,
- },
- });
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getUserPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MemberUserApi.User>,
- gridEvents: {
- checkboxAll: handleRowCheckboxChange,
- checkboxChange: handleRowCheckboxChange,
- },
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="浼氬憳鐢ㄦ埛銆佹爣绛俱�佸垎缁�"
- url="https://doc.iocoder.cn/member/user/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <PointFormModal @success="handleRefresh" />
- <BalanceFormModal @success="handleRefresh" />
- <LevelFormModal @success="handleRefresh" />
- <CouponSendFormModal />
- <Grid table-title="浼氬憳鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鍙戦�佷紭鎯犲埜',
- type: 'primary',
- icon: 'lucide:mouse-pointer-2',
- disabled: isEmpty(checkedIds),
- auth: ['promotion:coupon:send'],
- onClick: handleSendCoupon,
- },
- ]"
- />
- </template>
-
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleViewDetail.bind(null, row),
- },
- ]"
- :drop-down-actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- auth: ['member:user:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '淇敼绛夌骇',
- type: 'link',
- auth: ['member:user:update-level'],
- onClick: handleUpdateLevel.bind(null, row),
- },
- {
- label: '淇敼绉垎',
- type: 'link',
- auth: ['member:user:update-point'],
- onClick: handleUpdatePoint.bind(null, row),
- },
- {
- label: '淇敼浣欓',
- type: 'link',
- auth: ['pay:wallet:update-balance'],
- onClick: handleUpdateBalance.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/member/user/modules/balance-form.vue b/src/views/member/user/modules/balance-form.vue
deleted file mode 100644
index d24840b..0000000
--- a/src/views/member/user/modules/balance-form.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<script lang="ts" setup>
-import type { MemberUserApi } from '#/api/member/user';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { fenToYuan, yuanToFen } from '../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getUser } from '#/api/member/user';
-import { getWallet, updateWalletBalance } from '#/api/pay/wallet/balance';
-import { $t } from '#/locales';
-
-import { useBalanceFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref({
- id: undefined as number | undefined,
- nickname: '',
- balance: '0',
- changeBalance: 0,
- changeType: 1,
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useBalanceFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- await updateWalletBalance({
- userId: data.id!,
- balance: yuanToFen(data.changeBalance) * data.changeType,
- });
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberUserApi.User>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- const user = await getUser(data.id);
- if (!user || !user.id) {
- return;
- }
- const wallet = await getWallet({ userId: user.id });
- formData.value.id = user.id;
- formData.value.nickname = user.nickname || '';
- formData.value.balance = fenToYuan(wallet.balance);
- formData.value.changeType = 1; // 榛樿澧炲姞浣欓
- formData.value.changeBalance = 0; // 鍙樺姩浣欓榛樿 0
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-1/3" :title="$t('ui.actionTitle.edit', ['鐢ㄦ埛浣欓'])">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/user/modules/form.vue b/src/views/member/user/modules/form.vue
deleted file mode 100644
index 4c6c333..0000000
--- a/src/views/member/user/modules/form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { MemberUserApi } from '#/api/member/user';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getUser, updateUser } from '#/api/member/user';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberUserApi.User>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['浼氬憳'])
- : $t('ui.actionTitle.create', ['浼氬憳']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MemberUserApi.User;
- try {
- await updateUser(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberUserApi.User>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getUser(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/user/modules/level-form.vue b/src/views/member/user/modules/level-form.vue
deleted file mode 100644
index edef003..0000000
--- a/src/views/member/user/modules/level-form.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<script lang="ts" setup>
-import type { MemberUserApi } from '#/api/member/user';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getUser, updateUserLevel } from '#/api/member/user';
-import { $t } from '#/locales';
-
-import { useLevelFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberUserApi.User>();
-const getTitle = computed(() => {
- return $t('ui.actionTitle.edit', ['鐢ㄦ埛绛夌骇']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useLevelFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as MemberUserApi.UserUpdateLevelReqVO;
- try {
- await updateUserLevel(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberUserApi.User>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getUser(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/member/user/modules/point-form.vue b/src/views/member/user/modules/point-form.vue
deleted file mode 100644
index 635747e..0000000
--- a/src/views/member/user/modules/point-form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { MemberUserApi } from '#/api/member/user';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getUser, updateUserPoint } from '#/api/member/user';
-import { $t } from '#/locales';
-
-import { usePointFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MemberUserApi.User>();
-const getTitle = computed(() => {
- return $t('ui.actionTitle.edit', ['鐢ㄦ埛绉垎']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: usePointFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = await formApi.getValues();
- try {
- await updateUserPoint({
- id: data.id,
- point: data.changePoint * data.changeType,
- });
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MemberUserApi.User>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getUser(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/2">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mes/cal/calendar/index.vue b/src/views/mes/cal/calendar/index.vue
index aeb8388..332d5d5 100644
--- a/src/views/mes/cal/calendar/index.vue
+++ b/src/views/mes/cal/calendar/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { Tabs } from 'ant-design-vue';
import dayjs from 'dayjs';
@@ -18,14 +18,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愭帓鐝�戞帓鐝鍒掋�佹帓鐝棩鍘�"
- url="https://doc.iocoder.cn/mes/cal/calendar/"
- />
- </template>
- <div class="bg-card rounded-md p-3">
+ <Page auto-content-height><div class="bg-card rounded-md p-3">
<Tabs v-model:active-key="activeTab" type="card">
<Tabs.TabPane key="type" tab="鎸夊垎绫�" force-render>
<TypeView />
diff --git a/src/views/mes/cal/holiday/index.vue b/src/views/mes/cal/holiday/index.vue
index 86fb0b3..789bfaa 100644
--- a/src/views/mes/cal/holiday/index.vue
+++ b/src/views/mes/cal/holiday/index.vue
@@ -4,7 +4,7 @@
import { onMounted, ref, watch } from 'vue';
import { useAccess } from '../../../../packages/effects/access/src';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { HolidayType } from '../../../../packages/constants/src';
import { Button, Calendar, message, Tag } from 'ant-design-vue';
@@ -139,14 +139,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愭帓鐝�戠彮缁勮缃�佽妭鍋囨棩璁剧疆"
- url="https://doc.iocoder.cn/mes/cal/team/"
- />
- </template>
- <HolidayFormModal @success="getList" />
+ <Page auto-content-height><HolidayFormModal @success="getList" />
<div class="bg-card overflow-hidden rounded-md">
<Calendar v-model:value="currentDate" class="mes-holiday-calendar">
<template #headerRender>
diff --git a/src/views/mes/cal/plan/index.vue b/src/views/mes/cal/plan/index.vue
index 65e1f3f..974104c 100644
--- a/src/views/mes/cal/plan/index.vue
+++ b/src/views/mes/cal/plan/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalPlanApi } from '#/api/mes/cal/plan';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesCalPlanStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -92,14 +92,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愭帓鐝�戞帓鐝鍒掋�佹帓鐝棩鍘�"
- url="https://doc.iocoder.cn/mes/cal/calendar/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鎺掔彮璁″垝鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/cal/team/index.vue b/src/views/mes/cal/team/index.vue
index 16e0e16..d64f96e 100644
--- a/src/views/mes/cal/team/index.vue
+++ b/src/views/mes/cal/team/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesCalTeamApi } from '#/api/mes/cal/team';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -92,14 +92,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愭帓鐝�戠彮缁勮缃�佽妭鍋囨棩璁剧疆"
- url="https://doc.iocoder.cn/mes/cal/team/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐝粍鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/checkplan/index.vue b/src/views/mes/dv/checkplan/index.vue
index 5a53e04..ee1b496 100644
--- a/src/views/mes/dv/checkplan/index.vue
+++ b/src/views/mes/dv/checkplan/index.vue
@@ -3,7 +3,7 @@
import type { MesDvCheckPlanApi } from '#/api/mes/dv/checkplan';
import type { ActionItem } from '#/components/table-action/typing';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesDvCheckPlanStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -161,14 +161,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戠偣妫�淇濆吇椤圭洰銆佺偣妫�淇濆吇鏂规"
- url="https://doc.iocoder.cn/mes/dv/check-plan/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐐规淇濆吇鏂规鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/checkrecord/index.vue b/src/views/mes/dv/checkrecord/index.vue
index ad7a34a..d540a87 100644
--- a/src/views/mes/dv/checkrecord/index.vue
+++ b/src/views/mes/dv/checkrecord/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesDvCheckRecordApi } from '#/api/mes/dv/checkrecord';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesDvCheckRecordStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -95,14 +95,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戠偣妫�璁板綍銆佷繚鍏昏褰曘�佺淮淇崟"
- url="https://doc.iocoder.cn/mes/dv/check-record/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐐规璁板綍鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/machinery/index.vue b/src/views/mes/dv/machinery/index.vue
index 05a1550..a048b48 100644
--- a/src/views/mes/dv/machinery/index.vue
+++ b/src/views/mes/dv/machinery/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -120,14 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戣澶囩被鍨嬨�佽澶囧彴璐�"
- url="https://doc.iocoder.cn/mes/dv/device/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<div class="flex h-full w-full">
<div class="mr-4 h-full w-1/6">
diff --git a/src/views/mes/dv/machinery/type/index.vue b/src/views/mes/dv/machinery/type/index.vue
index 8cf1b65..7ee2da4 100644
--- a/src/views/mes/dv/machinery/type/index.vue
+++ b/src/views/mes/dv/machinery/type/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { handleTree } from '../../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -104,14 +104,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戣澶囩被鍨嬨�佽澶囧彴璐�"
- url="https://doc.iocoder.cn/mes/dv/device/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="璁惧绫诲瀷鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/maintenrecord/index.vue b/src/views/mes/dv/maintenrecord/index.vue
index 6b20650..77f6fbd 100644
--- a/src/views/mes/dv/maintenrecord/index.vue
+++ b/src/views/mes/dv/maintenrecord/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesDvMaintenRecordApi } from '#/api/mes/dv/maintenrecord';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesDvMaintenRecordStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -95,14 +95,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戠偣妫�璁板綍銆佷繚鍏昏褰曘�佺淮淇崟"
- url="https://doc.iocoder.cn/mes/dv/check-record/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="淇濆吇璁板綍鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/repair/index.vue b/src/views/mes/dv/repair/index.vue
index f98e068..c1b2bd7 100644
--- a/src/views/mes/dv/repair/index.vue
+++ b/src/views/mes/dv/repair/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesDvRepairApi } from '#/api/mes/dv/repair';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesDvRepairStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -101,14 +101,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戠偣妫�璁板綍銆佷繚鍏昏褰曘�佺淮淇崟"
- url="https://doc.iocoder.cn/mes/dv/check-record/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="缁翠慨宸ュ崟鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/dv/subject/index.vue b/src/views/mes/dv/subject/index.vue
index 004bd41..c468cef 100644
--- a/src/views/mes/dv/subject/index.vue
+++ b/src/views/mes/dv/subject/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesDvSubjectApi } from '#/api/mes/dv/subject';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -95,14 +95,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯澶囥�戠偣妫�淇濆吇椤圭洰銆佺偣妫�淇濆吇鏂规"
- url="https://doc.iocoder.cn/mes/dv/check-plan/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐐规淇濆吇椤圭洰鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/home/index.vue b/src/views/mes/home/index.vue
index a929184..a9a3ac2 100644
--- a/src/views/mes/home/index.vue
+++ b/src/views/mes/home/index.vue
@@ -4,7 +4,7 @@
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import { Col, Row } from 'ant-design-vue';
@@ -36,15 +36,7 @@
</script>
<template>
- <Page>
- <template #doc>
- <DocAlert
- title="MES 鎵嬪唽锛堝姛鑳藉紑鍚級"
- url="https://doc.iocoder.cn/mes/build/"
- />
- </template>
-
- <!-- 绗竴琛岋細鏍稿績 KPI 姹囨�诲崱鐗� -->
+ <Page><!-- 绗竴琛岋細鏍稿績 KPI 姹囨�诲崱鐗� -->
<KpiCards :summary="summary" class="mb-4" @navigate="handleNavigate" />
<!-- 绗簩琛岋細鐢熶骇瓒嬪娍 + 寰呭姙寮傚父 -->
diff --git a/src/views/mes/md/autocode/index.vue b/src/views/mes/md/autocode/index.vue
index 55f8b3a..6fdec96 100644
--- a/src/views/mes/md/autocode/index.vue
+++ b/src/views/mes/md/autocode/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdAutoCodeRuleApi } from '#/api/mes/md/autocode/rule';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,15 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戠紪鐮佽鍒�"
- url="https://doc.iocoder.cn/mes/md/autocode/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="缂栫爜瑙勫垯鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/md/client/index.vue b/src/views/mes/md/client/index.vue
index 42dae9e..084f73f 100644
--- a/src/views/mes/md/client/index.vue
+++ b/src/views/mes/md/client/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdClientApi } from '#/api/mes/md/client';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -103,15 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戝鎴风鐞嗐�佷緵搴斿晢绠$悊"
- url="https://doc.iocoder.cn/mes/md/client-vendor/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<Grid table-title="瀹㈡埛鍒楄〃">
diff --git a/src/views/mes/md/item/index.vue b/src/views/mes/md/item/index.vue
index 24fcc6c..5a9253c 100644
--- a/src/views/mes/md/item/index.vue
+++ b/src/views/mes/md/item/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { DICT_TYPE } from '../../../../packages/constants/src';
import { getDictLabel } from '../../../../packages/effects/hooks/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -141,15 +141,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戠墿鏂欎骇鍝併�佸垎绫汇�佽閲忓崟浣�"
- url="https://doc.iocoder.cn/mes/md/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<div class="flex h-full w-full">
diff --git a/src/views/mes/md/item/type/index.vue b/src/views/mes/md/item/type/index.vue
index 4e1860f..0634e13 100644
--- a/src/views/mes/md/item/type/index.vue
+++ b/src/views/mes/md/item/type/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -101,14 +101,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戠墿鏂欎骇鍝併�佸垎绫汇�佽閲忓崟浣�"
- url="https://doc.iocoder.cn/mes/md/product/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐗╂枡鍒嗙被鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/md/unitmeasure/index.vue b/src/views/mes/md/unitmeasure/index.vue
index 6b737fe..f9f49d4 100644
--- a/src/views/mes/md/unitmeasure/index.vue
+++ b/src/views/mes/md/unitmeasure/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdUnitMeasureApi } from '#/api/mes/md/unitmeasure';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,15 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戠墿鏂欎骇鍝併�佸垎绫汇�佽閲忓崟浣�"
- url="https://doc.iocoder.cn/mes/md/product/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="璁¢噺鍗曚綅鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/md/vendor/index.vue b/src/views/mes/md/vendor/index.vue
index f4829d6..3f68372 100644
--- a/src/views/mes/md/vendor/index.vue
+++ b/src/views/mes/md/vendor/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdVendorApi } from '#/api/mes/md/vendor';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -103,15 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戝鎴风鐞嗐�佷緵搴斿晢绠$悊"
- url="https://doc.iocoder.cn/mes/md/client-vendor/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<Grid table-title="渚涘簲鍟嗗垪琛�">
diff --git a/src/views/mes/md/workstation/index.vue b/src/views/mes/md/workstation/index.vue
index 67a281e..17021d7 100644
--- a/src/views/mes/md/workstation/index.vue
+++ b/src/views/mes/md/workstation/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戣溅闂磋缃�佸伐浣滅珯璁剧疆"
- url="https://doc.iocoder.cn/mes/md/workshop/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Grid table-title="宸ヤ綔绔欏垪琛�">
diff --git a/src/views/mes/md/workstation/workshop/index.vue b/src/views/mes/md/workstation/workshop/index.vue
index 8bdc10e..dd49a24 100644
--- a/src/views/mes/md/workstation/workshop/index.vue
+++ b/src/views/mes/md/workstation/workshop/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesMdWorkshopApi } from '#/api/mes/md/workstation/workshop';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { Button, message } from 'ant-design-vue';
@@ -88,15 +88,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戣溅闂磋缃�佸伐浣滅珯璁剧疆"
- url="https://doc.iocoder.cn/mes/md/workshop/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="杞﹂棿鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/pro/andon/record/index.vue b/src/views/mes/pro/andon/record/index.vue
index 10963d0..92f51b9 100644
--- a/src/views/mes/pro/andon/record/index.vue
+++ b/src/views/mes/pro/andon/record/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { MesProAndonStatusEnum } from '../../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
@@ -101,14 +101,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戝畨鐏厤缃�佸畨鐏懠鍙�"
- url="https://doc.iocoder.cn/mes/pro/andon/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ConfigList ref="configModalRef" />
<Grid table-title="瀹夌伅鍛煎彨璁板綍">
<template #toolbar-tools>
diff --git a/src/views/mes/pro/card/index.vue b/src/views/mes/pro/card/index.vue
index 3fd7c86..292a6ac 100644
--- a/src/views/mes/pro/card/index.vue
+++ b/src/views/mes/pro/card/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesProCardStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -111,15 +111,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戠敓浜ф帓浜с�佸伐搴忔祦杞崱"
- url="https://doc.iocoder.cn/mes/pro/schedule-card/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢熶骇娴佽浆鍗″垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/pro/feedback/index.vue b/src/views/mes/pro/feedback/index.vue
index bdfec1e..1a376a3 100644
--- a/src/views/mes/pro/feedback/index.vue
+++ b/src/views/mes/pro/feedback/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProFeedbackApi } from '#/api/mes/pro/feedback';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesProFeedbackStatusEnum } from '../../../../packages/constants/src';
import { useUserStore } from '../../../../packages/stores/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -103,14 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戠敓浜ф姤宸�"
- url="https://doc.iocoder.cn/mes/pro/feedback/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢熶骇鎶ュ伐鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/pro/process/index.vue b/src/views/mes/pro/process/index.vue
index 2a03c35..8662934 100644
--- a/src/views/mes/pro/process/index.vue
+++ b/src/views/mes/pro/process/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProProcessApi } from '#/api/mes/pro/process';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -96,15 +96,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戝伐搴忚缃�佸伐鑹烘祦绋�"
- url="https://doc.iocoder.cn/mes/pro/process-route/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢熶骇宸ュ簭鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/pro/route/index.vue b/src/views/mes/pro/route/index.vue
index e7ab244..2cb7372 100644
--- a/src/views/mes/pro/route/index.vue
+++ b/src/views/mes/pro/route/index.vue
@@ -3,7 +3,7 @@
import type { MesProRouteApi } from '#/api/mes/pro/route';
import { useAccess } from '../../../../packages/effects/access/src';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
import { getDictLabel } from '../../../../packages/effects/hooks/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -117,15 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戝伐搴忚缃�佸伐鑹烘祦绋�"
- url="https://doc.iocoder.cn/mes/pro/process-route/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="宸ヨ壓璺嚎鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/pro/task/edit/index.vue b/src/views/mes/pro/task/edit/index.vue
index ebda36b..646ddce 100644
--- a/src/views/mes/pro/task/edit/index.vue
+++ b/src/views/mes/pro/task/edit/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
-import { DocAlert, Page } from '../../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../../packages/effects/common-ui/src';
import { Badge, Button, message } from 'ant-design-vue';
@@ -62,15 +62,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戠敓浜ф帓浜с�佸伐搴忔祦杞崱"
- url="https://doc.iocoder.cn/mes/pro/schedule-card/"
- />
- </template>
-
- <div class="mb-3 flex items-center justify-between">
+ <Page auto-content-height><div class="mb-3 flex items-center justify-between">
<span class="text-sm text-gray-500">
鍙洿鎺ユ嫋鎷�/鎷変几浠诲姟鏉★紝鎴栧弻鍑荤紪杈戝紑濮嬫椂闂村拰鏃堕暱锛屼慨鏀瑰悗鐐瑰嚮銆屾壒閲忎繚瀛樸��
</span>
diff --git a/src/views/mes/pro/task/index.vue b/src/views/mes/pro/task/index.vue
index 2605318..a97bb1c 100644
--- a/src/views/mes/pro/task/index.vue
+++ b/src/views/mes/pro/task/index.vue
@@ -5,7 +5,7 @@
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
MesProWorkOrderStatusEnum,
MesProWorkOrderTypeEnum,
@@ -100,15 +100,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戠敓浜ф帓浜с�佸伐搴忔祦杞崱"
- url="https://doc.iocoder.cn/mes/pro/schedule-card/"
- />
- </template>
-
- <ScheduleModal @success="handleRefresh" />
+ <Page auto-content-height><ScheduleModal @success="handleRefresh" />
<!-- 鎺掍骇鐢樼壒鍥鹃瑙� -->
<Card title="鎺掍骇鐢樼壒鍥�" class="mb-4">
diff --git a/src/views/mes/pro/workorder/index.vue b/src/views/mes/pro/workorder/index.vue
index 56f0103..3bc1a53 100644
--- a/src/views/mes/pro/workorder/index.vue
+++ b/src/views/mes/pro/workorder/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
BarcodeBizTypeEnum,
MesProWorkOrderStatusEnum,
@@ -141,15 +141,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戠敓浜у伐鍗�"
- url="https://doc.iocoder.cn/mes/pro/work-order/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢熶骇宸ュ崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/pro/workrecord/index.vue b/src/views/mes/pro/workrecord/index.vue
index dd507d0..cc741d1 100644
--- a/src/views/mes/pro/workrecord/index.vue
+++ b/src/views/mes/pro/workrecord/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProWorkRecordApi } from '#/api/mes/pro/workrecord';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -58,15 +58,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愮敓浜с�戝伐浣滆褰�"
- url="https://doc.iocoder.cn/mes/pro/work-record/"
- />
- </template>
-
- <WorkRecordStatusBar @change="handleRefresh" />
+ <Page auto-content-height><WorkRecordStatusBar @change="handleRefresh" />
<Grid table-title="宸ヤ綔璁板綍鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/batchtrace/index.vue b/src/views/mes/qc/batchtrace/index.vue
index 85b78d6..729d9e3 100644
--- a/src/views/mes/qc/batchtrace/index.vue
+++ b/src/views/mes/qc/batchtrace/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getBatchPage } from '#/api/mes/wm/batch';
@@ -52,15 +52,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戞壒娆$鐞嗐�佸簱瀛樼幇鏈夐噺銆佸簱瀛樹簨鍔�"
- url="https://doc.iocoder.cn/mes/wm/stock/"
- />
- </template>
-
- <DetailModal />
+ <Page auto-content-height><DetailModal />
<Grid table-title="鎵规杩芥函鍒楄〃">
<template #actions="{ row }">
diff --git a/src/views/mes/qc/defect/index.vue b/src/views/mes/qc/defect/index.vue
index 4f5bad4..776d17d 100644
--- a/src/views/mes/qc/defect/index.vue
+++ b/src/views/mes/qc/defect/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcDefectApi } from '#/api/mes/qc/defect';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -87,15 +87,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戞娴嬮」璁剧疆銆佸父瑙佺己闄�"
- url="https://doc.iocoder.cn/mes/qc/base/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="缂洪櫡绫诲瀷鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/indicator/index.vue b/src/views/mes/qc/indicator/index.vue
index 323b82d..cfca5ec 100644
--- a/src/views/mes/qc/indicator/index.vue
+++ b/src/views/mes/qc/indicator/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIndicatorApi } from '#/api/mes/qc/indicator';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -91,15 +91,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戞娴嬮」璁剧疆銆佸父瑙佺己闄�"
- url="https://doc.iocoder.cn/mes/qc/base/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="璐ㄦ鎸囨爣鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/ipqc/index.vue b/src/views/mes/qc/ipqc/index.vue
index ec2ae15..022beb4 100644
--- a/src/views/mes/qc/ipqc/index.vue
+++ b/src/views/mes/qc/ipqc/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIpqcApi } from '#/api/mes/qc/ipqc';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesQcStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戣繃绋嬫楠岋紙IPQC锛�"
- url="https://doc.iocoder.cn/mes/qc/ipqc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="杩囩▼妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/iqc/index.vue b/src/views/mes/qc/iqc/index.vue
index 393d207..8181b05 100644
--- a/src/views/mes/qc/iqc/index.vue
+++ b/src/views/mes/qc/iqc/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcIqcApi } from '#/api/mes/qc/iqc';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesQcStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戞潵鏂欐楠岋紙IQC锛�"
- url="https://doc.iocoder.cn/mes/qc/iqc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鏉ユ枡妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/oqc/index.vue b/src/views/mes/qc/oqc/index.vue
index c34507c..9bd6bfa 100644
--- a/src/views/mes/qc/oqc/index.vue
+++ b/src/views/mes/qc/oqc/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcOqcApi } from '#/api/mes/qc/oqc';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesQcStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戝嚭璐ф楠岋紙OQC锛�"
- url="https://doc.iocoder.cn/mes/qc/oqc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍑鸿揣妫�楠屽崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/pendinginspect/index.vue b/src/views/mes/qc/pendinginspect/index.vue
index adb0576..75785b0 100644
--- a/src/views/mes/qc/pendinginspect/index.vue
+++ b/src/views/mes/qc/pendinginspect/index.vue
@@ -6,7 +6,7 @@
import type { MesQcPendingInspectApi } from '#/api/mes/qc/pendinginspect';
import type { MesQcRqcApi } from '#/api/mes/qc/rqc';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesQcTypeEnum } from '../../../../packages/constants/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -136,15 +136,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戝緟妫�浠诲姟銆佹楠岀粨鏋溿�佺己闄疯褰�"
- url="https://doc.iocoder.cn/mes/qc/pending-inspect/"
- />
- </template>
-
- <IqcFormModal @success="handleRefresh" />
+ <Page auto-content-height><IqcFormModal @success="handleRefresh" />
<IpqcFormModal @success="handleRefresh" />
<OqcFormModal @success="handleRefresh" />
<RqcFormModal @success="handleRefresh" />
diff --git a/src/views/mes/qc/rqc/index.vue b/src/views/mes/qc/rqc/index.vue
index 49154c1..dd81561 100644
--- a/src/views/mes/qc/rqc/index.vue
+++ b/src/views/mes/qc/rqc/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcRqcApi } from '#/api/mes/qc/rqc';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesQcStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戦��璐ф楠岋紙RQC锛�"
- url="https://doc.iocoder.cn/mes/qc/rqc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閫�璐ф楠屽崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/qc/template/index.vue b/src/views/mes/qc/template/index.vue
index 02179a2..3a80835 100644
--- a/src/views/mes/qc/template/index.vue
+++ b/src/views/mes/qc/template/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesQcTemplateApi } from '#/api/mes/qc/template';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { DICT_TYPE } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -98,15 +98,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愯川閲忋�戣川妫�鏂规"
- url="https://doc.iocoder.cn/mes/qc/template/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="璐ㄦ鏂规鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/tm/tool/index.vue b/src/views/mes/tm/tool/index.vue
index 5b29221..53f5e5c 100644
--- a/src/views/mes/tm/tool/index.vue
+++ b/src/views/mes/tm/tool/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, Card, message } from 'ant-design-vue';
@@ -105,15 +105,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬伐鍏枫�戝伐鍏风被鍨嬨�佸伐瑁呭す鍏峰彴璐�"
- url="https://doc.iocoder.cn/mes/tm/tool/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<div class="flex h-full w-full">
<Card class="mr-4 h-full w-1/6">
<TmToolTypeList @select="handleToolTypeSelect" />
diff --git a/src/views/mes/tm/tool/type/index.vue b/src/views/mes/tm/tool/type/index.vue
index 269b44a..e5efb8c 100644
--- a/src/views/mes/tm/tool/type/index.vue
+++ b/src/views/mes/tm/tool/type/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesTmToolTypeApi } from '#/api/mes/tm/tool/type';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
import { Button, message } from 'ant-design-vue';
@@ -95,15 +95,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬伐鍏枫�戝伐鍏风被鍨嬨�佸伐瑁呭す鍏峰彴璐�"
- url="https://doc.iocoder.cn/mes/tm/tool/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="宸ュ叿绫诲瀷鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/mes/wm/arrivalnotice/index.vue b/src/views/mes/wm/arrivalnotice/index.vue
index 6664648..bd075ac 100644
--- a/src/views/mes/wm/arrivalnotice/index.vue
+++ b/src/views/mes/wm/arrivalnotice/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmArrivalNoticeStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -107,15 +107,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝埌璐ч�氱煡銆侀噰璐叆搴撱�侀噰璐��璐�"
- url="https://doc.iocoder.cn/mes/wm/purchase-in/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍒拌揣閫氱煡鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/barcode/config/index.vue b/src/views/mes/wm/barcode/config/index.vue
index c2a5c24..bf04d7a 100644
--- a/src/views/mes/wm/barcode/config/index.vue
+++ b/src/views/mes/wm/barcode/config/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
@@ -103,15 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鏉$爜閰嶇疆鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/barcode/index.vue b/src/views/mes/wm/barcode/index.vue
index 870fd76..f4ab5aa 100644
--- a/src/views/mes/wm/barcode/index.vue
+++ b/src/views/mes/wm/barcode/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -143,15 +143,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Grid table-title="鏉$爜鍒楄〃">
diff --git a/src/views/mes/wm/itemreceipt/index.vue b/src/views/mes/wm/itemreceipt/index.vue
index 5ee04e1..8f225bd 100644
--- a/src/views/mes/wm/itemreceipt/index.vue
+++ b/src/views/mes/wm/itemreceipt/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmItemReceiptStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝埌璐ч�氱煡銆侀噰璐叆搴撱�侀噰璐��璐�"
- url="https://doc.iocoder.cn/mes/wm/purchase-in/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閲囪喘鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/materialstock/index.vue b/src/views/mes/wm/materialstock/index.vue
index d836070..321e65b 100644
--- a/src/views/mes/wm/materialstock/index.vue
+++ b/src/views/mes/wm/materialstock/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Button, Card, message } from 'ant-design-vue';
@@ -120,15 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戞壒娆$鐞嗐�佸簱瀛樼幇鏈夐噺銆佸簱瀛樹簨鍔�"
- url="https://doc.iocoder.cn/mes/wm/stock/"
- />
- </template>
-
- <AreaModal />
+ <Page auto-content-height><AreaModal />
<WmBatchDetail ref="batchDetailRef" />
<div class="flex h-full w-full">
diff --git a/src/views/mes/wm/miscissue/index.vue b/src/views/mes/wm/miscissue/index.vue
index d8338dc..2c252e2 100644
--- a/src/views/mes/wm/miscissue/index.vue
+++ b/src/views/mes/wm/miscissue/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmMiscIssueStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -110,15 +110,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝叾浠栧叆搴撱�佸叾浠栧嚭搴�"
- url="https://doc.iocoder.cn/mes/wm/misc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鏉傞」鍑哄簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/miscreceipt/index.vue b/src/views/mes/wm/miscreceipt/index.vue
index 9c86e90..b5ff34c 100644
--- a/src/views/mes/wm/miscreceipt/index.vue
+++ b/src/views/mes/wm/miscreceipt/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmMiscReceiptStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -110,15 +110,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝叾浠栧叆搴撱�佸叾浠栧嚭搴�"
- url="https://doc.iocoder.cn/mes/wm/misc/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鏉傞」鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/outsourceissue/index.vue b/src/views/mes/wm/outsourceissue/index.vue
index c5f6691..da33276 100644
--- a/src/views/mes/wm/outsourceissue/index.vue
+++ b/src/views/mes/wm/outsourceissue/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmOutsourceIssueStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝鍗忓彂鏂欍�佸鍗忓叆搴�"
- url="https://doc.iocoder.cn/mes/wm/outsource/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="澶栧崗鍙戞枡鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/outsourcereceipt/index.vue b/src/views/mes/wm/outsourcereceipt/index.vue
index b19144b..c362079 100644
--- a/src/views/mes/wm/outsourcereceipt/index.vue
+++ b/src/views/mes/wm/outsourcereceipt/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmOutsourceReceiptStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝鍗忓彂鏂欍�佸鍗忓叆搴�"
- url="https://doc.iocoder.cn/mes/wm/outsource/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="澶栧崗鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/packages/index.vue b/src/views/mes/wm/packages/index.vue
index c4a227f..39a6e17 100644
--- a/src/views/mes/wm/packages/index.vue
+++ b/src/views/mes/wm/packages/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmPackageStatusEnum } from '../../../../packages/constants/src';
import { Button, message } from 'ant-design-vue';
@@ -103,15 +103,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戣皟鎷ㄥ崟銆佽绠辩鐞�"
- url="https://doc.iocoder.cn/mes/wm/transfer/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="瑁呯鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/productissue/index.vue b/src/views/mes/wm/productissue/index.vue
index 39ca3cf..b4eb53d 100644
--- a/src/views/mes/wm/productissue/index.vue
+++ b/src/views/mes/wm/productissue/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmProductIssueStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -123,15 +123,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戠敓浜ч鏂欍�佺敓浜ч��鏂欍�佺墿鏂欐秷鑰�"
- url="https://doc.iocoder.cn/mes/wm/issue-return/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="棰嗘枡鍑哄簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/productreceipt/index.vue b/src/views/mes/wm/productreceipt/index.vue
index f26146e..e25201a 100644
--- a/src/views/mes/wm/productreceipt/index.vue
+++ b/src/views/mes/wm/productreceipt/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmProductReceiptStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜骇鍝佷骇鍑恒�佷骇鍝佸叆搴�"
- url="https://doc.iocoder.cn/mes/wm/product-in/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浜у搧鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/productsales/index.vue b/src/views/mes/wm/productsales/index.vue
index 7bc694d..4d865a6 100644
--- a/src/views/mes/wm/productsales/index.vue
+++ b/src/views/mes/wm/productsales/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmProductSalesStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -120,15 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝彂璐ч�氱煡銆侀攢鍞嚭搴撱�侀攢鍞��璐�"
- url="https://doc.iocoder.cn/mes/wm/sales-out/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閿�鍞嚭搴撳崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/returnissue/index.vue b/src/views/mes/wm/returnissue/index.vue
index d2840c9..c17d74c 100644
--- a/src/views/mes/wm/returnissue/index.vue
+++ b/src/views/mes/wm/returnissue/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmReturnIssueStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -120,15 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戠敓浜ч鏂欍�佺敓浜ч��鏂欍�佺墿鏂欐秷鑰�"
- url="https://doc.iocoder.cn/mes/wm/issue-return/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐢熶骇閫�鏂欏崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/returnsales/index.vue b/src/views/mes/wm/returnsales/index.vue
index 66769dc..2b4485c 100644
--- a/src/views/mes/wm/returnsales/index.vue
+++ b/src/views/mes/wm/returnsales/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmReturnSalesStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -120,15 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝彂璐ч�氱煡銆侀攢鍞嚭搴撱�侀攢鍞��璐�"
- url="https://doc.iocoder.cn/mes/wm/sales-out/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閿�鍞��璐у崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/returnvendor/index.vue b/src/views/mes/wm/returnvendor/index.vue
index a41427e..98c5e59 100644
--- a/src/views/mes/wm/returnvendor/index.vue
+++ b/src/views/mes/wm/returnvendor/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmReturnVendorStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -115,15 +115,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝埌璐ч�氱煡銆侀噰璐叆搴撱�侀噰璐��璐�"
- url="https://doc.iocoder.cn/mes/wm/purchase-in/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="渚涘簲鍟嗛��璐у崟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/salesnotice/index.vue b/src/views/mes/wm/salesnotice/index.vue
index a8d74c5..4ec69bd 100644
--- a/src/views/mes/wm/salesnotice/index.vue
+++ b/src/views/mes/wm/salesnotice/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmSalesNoticeStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -102,15 +102,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝彂璐ч�氱煡銆侀攢鍞嚭搴撱�侀攢鍞��璐�"
- url="https://doc.iocoder.cn/mes/wm/sales-out/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍙戣揣閫氱煡鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/sn/index.vue b/src/views/mes/wm/sn/index.vue
index 51986fa..53103d1 100644
--- a/src/views/mes/wm/sn/index.vue
+++ b/src/views/mes/wm/sn/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmSnApi } from '#/api/mes/wm/sn';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -106,15 +106,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<DetailModal />
<Grid table-title="SN 鐮佸垪琛�">
diff --git a/src/views/mes/wm/stocktaking/plan/index.vue b/src/views/mes/wm/stocktaking/plan/index.vue
index 41b64a3..468efb7 100644
--- a/src/views/mes/wm/stocktaking/plan/index.vue
+++ b/src/views/mes/wm/stocktaking/plan/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { CommonStatusEnum } from '../../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
@@ -117,15 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝簱瀛樼洏鐐�"
- url="https://doc.iocoder.cn/mes/wm/stocktaking/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐩樼偣鏂规鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/stocktaking/task/index.vue b/src/views/mes/wm/stocktaking/task/index.vue
index d27d58b..2d17095 100644
--- a/src/views/mes/wm/stocktaking/task/index.vue
+++ b/src/views/mes/wm/stocktaking/task/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { MesWmStockTakingTaskStatusEnum } from '../../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../../packages/utils/src';
@@ -117,15 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戝簱瀛樼洏鐐�"
- url="https://doc.iocoder.cn/mes/wm/stocktaking/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐩樼偣浠诲姟鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/transfer/index.vue b/src/views/mes/wm/transfer/index.vue
index e2c3d60..b9f9cdb 100644
--- a/src/views/mes/wm/transfer/index.vue
+++ b/src/views/mes/wm/transfer/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { MesWmTransferStatusEnum } from '../../../../packages/constants/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
@@ -120,15 +120,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戣皟鎷ㄥ崟銆佽绠辩鐞�"
- url="https://doc.iocoder.cn/mes/wm/transfer/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="杞Щ鍗曞垪琛�">
<template #toolbar-tools>
diff --git a/src/views/mes/wm/warehouse/area/index.vue b/src/views/mes/wm/warehouse/area/index.vue
index c8d680b..7d7485a 100644
--- a/src/views/mes/wm/warehouse/area/index.vue
+++ b/src/views/mes/wm/warehouse/area/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRoute } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
import { Alert, Button, message } from 'ant-design-vue';
@@ -146,15 +146,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Alert
diff --git a/src/views/mes/wm/warehouse/index.vue b/src/views/mes/wm/warehouse/index.vue
index f5040bf..69eb3c9 100644
--- a/src/views/mes/wm/warehouse/index.vue
+++ b/src/views/mes/wm/warehouse/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
import { Button, message } from 'ant-design-vue';
@@ -112,15 +112,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Grid table-title="浠撳簱鍒楄〃">
diff --git a/src/views/mes/wm/warehouse/location/index.vue b/src/views/mes/wm/warehouse/location/index.vue
index 972478c..57cbbf7 100644
--- a/src/views/mes/wm/warehouse/location/index.vue
+++ b/src/views/mes/wm/warehouse/location/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
-import { DocAlert, Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
import { Alert, Button, message } from 'ant-design-vue';
@@ -147,15 +147,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愪粨搴撱�戜粨搴撲笌搴撳尯搴撲綅銆佹潯鐮佽祴鐮併�丼N鐮�"
- url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Alert
diff --git a/src/views/mp/account/data.ts b/src/views/mp/account/data.ts
deleted file mode 100644
index eb57a79..0000000
--- a/src/views/mp/account/data.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鍚嶇О',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ悕绉�',
- },
- },
- {
- fieldName: 'account',
- label: '寰俊鍙�',
- component: 'Input',
- help: '鍦ㄥ井淇″叕浼楀钩鍙帮紙mp.weixin.qq.com锛夌殑鑿滃崟 [璁剧疆涓庡紑鍙� - 鍏紬鍙疯缃� - 璐﹀彿璇︽儏] 涓兘鎵惧埌銆屽井淇″彿銆�',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ井淇″彿',
- },
- },
- {
- fieldName: 'appId',
- label: 'appId',
- component: 'Input',
- help: '鍦ㄥ井淇″叕浼楀钩鍙帮紙mp.weixin.qq.com锛夌殑鑿滃崟 [璁剧疆涓庡紑鍙� - 鍏紬鍙疯缃� - 鍩烘湰璁剧疆] 涓兘鎵惧埌銆屽紑鍙戣�匢D(AppID)銆�',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ叕浼楀彿 appId',
- },
- },
- {
- fieldName: 'appSecret',
- label: 'appSecret',
- component: 'Input',
- help: '鍦ㄥ井淇″叕浼楀钩鍙帮紙mp.weixin.qq.com锛夌殑鑿滃崟 [璁剧疆涓庡紑鍙� - 鍏紬鍙疯缃� - 鍩烘湰璁剧疆] 涓兘鎵惧埌銆屽紑鍙戣�呭瘑鐮�(AppSecret)銆�',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ叕浼楀彿 appSecret',
- },
- },
- {
- fieldName: 'token',
- label: 'token',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ叕浼楀彿 token',
- },
- },
- {
- fieldName: 'aesKey',
- label: '娑堟伅鍔犺В瀵嗗瘑閽�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ秷鎭姞瑙e瘑瀵嗛挜',
- },
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 鎼滅储琛ㄥ崟閰嶇疆 */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ悕绉�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- title: '鍚嶇О',
- field: 'name',
- minWidth: 150,
- },
- {
- title: '寰俊鍙�',
- field: 'account',
- minWidth: 180,
- },
- {
- title: 'appId',
- field: 'appId',
- minWidth: 180,
- },
- {
- title: '鏈嶅姟鍣ㄥ湴鍧�(URL)',
- field: 'utl',
- minWidth: 360,
- slots: {
- default: ({ row }) => {
- return `http://鏈嶅姟绔湴鍧�/admin-api/mp/open/${row.appId}`;
- },
- },
- },
- {
- title: '浜岀淮鐮�',
- field: 'qrCodeUrl',
- minWidth: 120,
- cellRender: { name: 'CellImage' },
- },
- {
- title: '澶囨敞',
- field: 'remark',
- minWidth: 150,
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/account/index.vue b/src/views/mp/account/index.vue
deleted file mode 100644
index 511990b..0000000
--- a/src/views/mp/account/index.vue
+++ /dev/null
@@ -1,179 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpAccountApi } from '#/api/mp/account';
-
-import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- clearAccountQuota,
- deleteAccount,
- generateAccountQrCode,
- getAccountPage,
-} from '#/api/mp/account';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓璐﹀彿 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫璐﹀彿 */
-function handleEdit(row: MpAccountApi.Account) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎璐﹀彿 */
-async function handleDelete(row: MpAccountApi.Account) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteAccount(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鐢熸垚浜岀淮鐮� */
-async function handleGenerateQrCode(row: MpAccountApi.Account) {
- const hideLoading = message.loading({
- content: '姝e湪鐢熸垚浜岀淮鐮佷腑...',
- duration: 0,
- });
- try {
- await generateAccountQrCode(row.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 娓呯┖ API 閰嶉 */
-async function handleCleanQuota(row: MpAccountApi.Account) {
- const hideLoading = message.loading({
- content: '姝e湪娓呯┖ API 閰嶉',
- duration: 0,
- });
- try {
- await clearAccountQuota(row.id!);
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAccountPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpAccountApi.Account>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍏紬鍙疯处鍙峰垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍏紬鍙疯处鍙�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mp:account:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['mp:account:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:account:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- :drop-down-actions="[
- {
- label: '鐢熸垚浜岀淮鐮�',
- type: 'link',
- icon: 'qrcode',
- auth: ['mp:account:qr-code'],
- onClick: handleGenerateQrCode.bind(null, row),
- },
- {
- label: '娓呯┖ API 閰嶉',
- type: 'link',
- danger: true,
- icon: 'clear',
- auth: ['mp:account:clear-quota'],
- popConfirm: {
- title: '浣犵‘璁よ娓呯┖ API 閰嶉锛�',
- confirm: handleCleanQuota.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mp/account/modules/form.vue b/src/views/mp/account/modules/form.vue
deleted file mode 100644
index b31a4e9..0000000
--- a/src/views/mp/account/modules/form.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { MpAccountApi } from '#/api/mp/account';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createAccount, getAccount, updateAccount } from '#/api/mp/account';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MpAccountApi.Account>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍏紬鍙疯处鍙�'])
- : $t('ui.actionTitle.create', ['鍏紬鍙疯处鍙�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MpAccountApi.Account;
- try {
- await (formData.value?.id ? updateAccount(data) : createAccount(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<MpAccountApi.Account>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getAccount(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mp/autoReply/data.ts b/src/views/mp/autoReply/data.ts
deleted file mode 100644
index e859b2e..0000000
--- a/src/views/mp/autoReply/data.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-
-import { markRaw } from 'vue';
-
-import {
- AutoReplyMsgType,
- DICT_TYPE,
- RequestMessageTypes,
-} from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-import { WxReply } from '#/views/mp/components';
-
-/** 鑾峰彇琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(
- msgType: AutoReplyMsgType,
-): VxeGridPropTypes.Columns {
- const columns: VxeGridPropTypes.Columns = [];
- // 璇锋眰娑堟伅绫诲瀷鍒楋紙浠呮秷鎭洖澶嶆樉绀猴級
- if (msgType === AutoReplyMsgType.Message) {
- columns.push({
- field: 'requestMessageType',
- title: '璇锋眰娑堟伅绫诲瀷',
- minWidth: 120,
- });
- }
- // 鍏抽敭璇嶅垪锛堜粎鍏抽敭璇嶅洖澶嶆樉绀猴級
- if (msgType === AutoReplyMsgType.Keyword) {
- columns.push({
- field: 'requestKeyword',
- title: '鍏抽敭璇�',
- minWidth: 150,
- });
- }
- // 鍖归厤绫诲瀷鍒楋紙浠呭叧閿瘝鍥炲鏄剧ず锛�
- if (msgType === AutoReplyMsgType.Keyword) {
- columns.push({
- field: 'requestMatch',
- title: '鍖归厤绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH },
- },
- });
- }
- // 鍥炲娑堟伅绫诲瀷鍒�
- columns.push(
- {
- field: 'responseMessageType',
- title: '鍥炲娑堟伅绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.MP_MESSAGE_TYPE },
- },
- },
- {
- field: 'responseContent',
- title: '鍥炲鍐呭',
- minWidth: 200,
- slots: { default: 'replyContent' },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- );
- return columns;
-}
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(msgType: AutoReplyMsgType): VbenFormSchema[] {
- const schema: VbenFormSchema[] = [];
- // 娑堟伅绫诲瀷锛堜粎娑堟伅鍥炲鏄剧ず锛�
- if (msgType === AutoReplyMsgType.Message) {
- schema.push({
- fieldName: 'requestMessageType',
- label: '娑堟伅绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨',
- options: getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE).filter((d) =>
- RequestMessageTypes.has(d.value as string),
- ),
- },
- });
- }
- // 鍖归厤绫诲瀷锛堜粎鍏抽敭璇嶅洖澶嶆樉绀猴級
- if (msgType === AutoReplyMsgType.Keyword) {
- schema.push({
- fieldName: 'requestMatch',
- label: '鍖归厤绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨鍖归厤绫诲瀷',
- allowClear: true,
- options: getDictOptions(
- DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH,
- 'number',
- ),
- },
- rules: 'required',
- });
- }
- // 鍏抽敭璇嶏紙浠呭叧閿瘝鍥炲鏄剧ず锛�
- if (msgType === AutoReplyMsgType.Keyword) {
- schema.push({
- fieldName: 'requestKeyword',
- label: '鍏抽敭璇�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ唴瀹�',
- allowClear: true,
- },
- rules: 'required',
- });
- }
- // 鍥炲娑堟伅
- schema.push({
- fieldName: 'reply',
- label: '鍥炲娑堟伅',
- component: markRaw(WxReply),
- modelPropName: 'modelValue',
- });
- return schema;
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- ];
-}
diff --git a/src/views/mp/autoReply/index.vue b/src/views/mp/autoReply/index.vue
deleted file mode 100644
index b272590..0000000
--- a/src/views/mp/autoReply/index.vue
+++ /dev/null
@@ -1,230 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpAutoReplyApi } from '#/api/mp/autoReply';
-
-import { computed, nextTick, ref } from 'vue';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { AutoReplyMsgType } from '../../../packages/constants/src';
-import { IconifyIcon } from '../../../packages/icons/src';
-
-import { message, Row, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteAutoReply,
- getAutoReply,
- getAutoReplyPage,
-} from '#/api/mp/autoReply';
-import { $t } from '#/locales';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import ReplyContent from './modules/content.vue';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'MpAutoReply' });
-
-const msgType = ref<string>(String(AutoReplyMsgType.Keyword)); // 娑堟伅绫诲瀷
-
-const showCreateButton = computed(() => {
- if (Number(msgType.value) !== AutoReplyMsgType.Follow) {
- return true;
- }
- try {
- const tableData = gridApi.grid?.getTableData();
- return (tableData?.tableData?.length || 0) <= 0;
- } catch {
- return true;
- }
-}); // 璁$畻鏄惁鏄剧ず鏂板鎸夐挳锛氬叧娉ㄦ椂鍥炲绫诲瀷鍙湁鍦ㄦ病鏈夋暟鎹椂鎵嶆樉绀�
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鍒囨崲鍥炲绫诲瀷 */
-async function onTabChange(tabName: string) {
- msgType.value = tabName;
- await nextTick();
- // 鏇存柊 columns
- const columns = useGridColumns(Number(msgType.value) as AutoReplyMsgType);
- if (columns) {
- // 浣跨敤 setGridOptions 鏇存柊鍒楅厤缃�
- gridApi.setGridOptions({ columns });
- // 绛夊緟鍒楅厤缃洿鏂板畬鎴�
- await nextTick();
- }
- // 鏌ヨ鏁版嵁
- await gridApi.query();
-}
-
-/** 鏂板鑷姩鍥炲 */
-async function handleCreate() {
- const formValues = await gridApi.formApi.getValues();
- formModalApi
- .setData({
- msgType: Number(msgType.value) as AutoReplyMsgType,
- accountId: formValues.accountId,
- })
- .open();
-}
-
-/** 淇敼鑷姩鍥炲 */
-async function handleEdit(row: MpAutoReplyApi.AutoReply) {
- const data = await getAutoReply(row.id!);
- formModalApi
- .setData({
- msgType: Number(msgType.value) as AutoReplyMsgType,
- accountId: row.accountId,
- row: data,
- })
- .open();
-}
-
-/** 鍒犻櫎鑷姩鍥炲 */
-async function handleDelete(row: MpAutoReplyApi.AutoReply) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', ['鑷姩鍥炲']),
- duration: 0,
- });
- try {
- await deleteAutoReply(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(Number(msgType.value) as AutoReplyMsgType),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAutoReplyPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- type: Number(msgType.value) as AutoReplyMsgType,
- ...formValues,
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpAutoReplyApi.AutoReply>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鑷姩鍥炲" url="https://doc.iocoder.cn/mp/auto-reply/" />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid>
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-actions>
- <Tabs
- v-model:active-key="msgType"
- class="w-full"
- @change="(activeKey) => onTabChange(activeKey as string)"
- >
- <Tabs.TabPane :key="String(AutoReplyMsgType.Follow)">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:star" class="mr-2px" /> 鍏虫敞鏃跺洖澶�
- </Row>
- </template>
- </Tabs.TabPane>
- <Tabs.TabPane :key="String(AutoReplyMsgType.Message)">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:message-circle-more" class="mr-2px" />
- 娑堟伅鍥炲
- </Row>
- </template>
- </Tabs.TabPane>
- <Tabs.TabPane :key="String(AutoReplyMsgType.Keyword)">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:newspaper" class="mr-2px" />
- 鍏抽敭璇嶅洖澶�
- </Row>
- </template>
- </Tabs.TabPane>
- </Tabs>
- </template>
- <template #toolbar-tools>
- <TableAction
- v-if="showCreateButton"
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鑷姩鍥炲']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mp:auto-reply:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #replyContent="{ row }">
- <ReplyContent :row="row" />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['mp:auto-reply:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:auto-reply:delete'],
- popConfirm: {
- title: '鏄惁纭鍒犻櫎姝ゆ暟鎹�?',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mp/autoReply/modules/content.vue b/src/views/mp/autoReply/modules/content.vue
deleted file mode 100644
index caa022c..0000000
--- a/src/views/mp/autoReply/modules/content.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script lang="ts" setup>
-import {
- WxMusic,
- WxNews,
- WxVideoPlayer,
- WxVoicePlayer,
-} from '#/views/mp/components';
-
-defineOptions({ name: 'ReplyContentCell' });
-
-const props = defineProps<{
- row: any;
-}>();
-</script>
-
-<template>
- <div>
- <div v-if="props.row.responseMessageType === 'text'">
- {{ props.row.responseContent }}
- </div>
- <div v-else-if="props.row.responseMessageType === 'voice'">
- <WxVoicePlayer
- v-if="props.row.responseMediaUrl"
- :url="props.row.responseMediaUrl"
- />
- </div>
- <div v-else-if="props.row.responseMessageType === 'image'">
- <a target="_blank" :href="props.row.responseMediaUrl">
- <img :src="props.row.responseMediaUrl" class="w-[100px]" />
- </a>
- </div>
- <div
- v-else-if="
- props.row.responseMessageType === 'video' ||
- props.row.responseMessageType === 'shortvideo'
- "
- >
- <WxVideoPlayer
- v-if="props.row.responseMediaUrl"
- :url="props.row.responseMediaUrl"
- class="mt-[10px]"
- />
- </div>
- <div v-else-if="props.row.responseMessageType === 'news'">
- <WxNews :articles="props.row.responseArticles" />
- </div>
- <div v-else-if="props.row.responseMessageType === 'music'">
- <WxMusic
- :title="props.row.responseTitle"
- :description="props.row.responseDescription"
- :thumb-media-url="props.row.responseThumbMediaUrl"
- :music-url="props.row.responseMusicUrl"
- :hq-music-url="props.row.responseHqMusicUrl"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/mp/autoReply/modules/form.vue b/src/views/mp/autoReply/modules/form.vue
deleted file mode 100644
index 4cc5d45..0000000
--- a/src/views/mp/autoReply/modules/form.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from '#/views/mp/components/wx-reply/types';
-
-import { computed, nextTick, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { AutoReplyMsgType, ReplyType } from '../../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createAutoReply, updateAutoReply } from '#/api/mp/autoReply';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<{
- accountId?: number;
- msgType: AutoReplyMsgType;
- row?: any;
-}>();
-const getTitle = computed(() => {
- return formData.value?.row?.id
- ? $t('ui.actionTitle.edit', ['鑷姩鍥炲'])
- : $t('ui.actionTitle.create', ['鑷姩鍥炲']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(AutoReplyMsgType.Keyword),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
-
- // 澶勭悊鍥炲娑堟伅
- const submitForm: any = await formApi.getValues();
- // 纭繚 type 瀛楁浣跨敤褰撳墠閫変腑鐨� tab 鍊�
- submitForm.type = formData.value?.msgType;
- // 纭繚 accountId 瀛楁瀛樺湪
- submitForm.accountId = formData.value?.accountId;
- // 缂栬緫妯″紡涓嬶紝纭繚 id 瀛楁瀛樺湪锛堜粠 row 涓幏鍙栵紝鍥犱负琛ㄥ崟 schema 涓病鏈� id 瀛楁锛�
- if (formData.value?.row?.id && !submitForm.id) {
- submitForm.id = formData.value.row.id;
- }
- const reply = submitForm.reply as Reply;
- if (reply) {
- submitForm.responseMessageType = reply.type;
- submitForm.responseContent = reply.content;
- submitForm.responseMediaId = reply.mediaId;
- submitForm.responseMediaUrl = reply.url;
- submitForm.responseTitle = reply.title;
- submitForm.responseDescription = reply.description;
- submitForm.responseThumbMediaId = reply.thumbMediaId;
- submitForm.responseThumbMediaUrl = reply.thumbMediaUrl;
- submitForm.responseArticles = reply.articles;
- submitForm.responseMusicUrl = reply.musicUrl;
- submitForm.responseHqMusicUrl = reply.hqMusicUrl;
- }
- delete submitForm.reply;
-
- modalApi.lock();
- try {
- if (submitForm.id === undefined) {
- await createAutoReply(submitForm);
- message.success('鏂板鎴愬姛');
- } else {
- await updateAutoReply(submitForm);
- message.success('淇敼鎴愬姛');
- }
- await modalApi.close();
- emit('success');
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{
- accountId?: number;
- msgType: AutoReplyMsgType;
- row?: any;
- }>();
- if (!data) {
- return;
- }
- // 鍏堟洿鏂� schema锛岀‘淇濊〃鍗曞瓧娈垫纭�
- formApi.setState({ schema: useFormSchema(data.msgType) });
- // 绛夊緟 schema 鏇存柊瀹屾垚
- await nextTick();
-
- formData.value = data;
- if (data.row?.id) {
- // 缂栬緫锛氬姞杞芥暟鎹�
- const rowData = data.row;
- const formValues: any = {
- ...rowData,
- reply: {
- type: rowData.responseMessageType,
- accountId: data.accountId || -1,
- content: rowData.responseContent,
- mediaId: rowData.responseMediaId,
- url: rowData.responseMediaUrl,
- title: rowData.responseTitle,
- description: rowData.responseDescription,
- thumbMediaId: rowData.responseThumbMediaId,
- thumbMediaUrl: rowData.responseThumbMediaUrl,
- articles: rowData.responseArticles,
- musicUrl: rowData.responseMusicUrl,
- hqMusicUrl: rowData.responseHqMusicUrl,
- },
- };
- await formApi.setValues(formValues);
- } else {
- // 鏂板缓锛氬垵濮嬪寲琛ㄥ崟
- const initialValues: any = {
- id: undefined,
- accountId: data.accountId || -1,
- type: data.msgType,
- requestKeyword: undefined,
- requestMatch: data.msgType === AutoReplyMsgType.Keyword ? 1 : undefined,
- requestMessageType: undefined,
- reply: {
- type: ReplyType.Text,
- accountId: data.accountId || -1,
- },
- };
- await formApi.setValues(initialValues);
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-4/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mp/components/index.ts b/src/views/mp/components/index.ts
deleted file mode 100644
index abab7ff..0000000
--- a/src/views/mp/components/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
-export { default as WxLocation } from './wx-location/wx-location.vue';
-export { default as WxMaterialSelect } from './wx-material-select/wx-material-select.vue';
-export { default as WxMsg } from './wx-msg/wx-msg.vue';
-export { default as WxMusic } from './wx-music/wx-music.vue';
-export { default as WxNews } from './wx-news/wx-news.vue';
-export { default as WxReply } from './wx-reply/wx-reply.vue';
-export { default as WxVideoPlayer } from './wx-video-play/wx-video-play.vue';
-export { default as WxVoicePlayer } from './wx-voice-play/wx-voice-play.vue';
diff --git a/src/views/mp/components/wx-account-select/wx-account-select.vue b/src/views/mp/components/wx-account-select/wx-account-select.vue
deleted file mode 100644
index 3d2b48f..0000000
--- a/src/views/mp/components/wx-account-select/wx-account-select.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-<script lang="ts" setup>
-import type { SelectValue } from 'ant-design-vue/es/select';
-
-import type { MpAccountApi } from '#/api/mp/account';
-
-import { onMounted, ref } from 'vue';
-import { useRouter } from 'vue-router';
-
-import { message, Select } from 'ant-design-vue';
-
-import { getSimpleAccountList } from '#/api/mp/account';
-
-defineOptions({ name: 'WxAccountSelect' });
-
-const emit = defineEmits<{
- (e: 'change', id: number, name: string): void;
-}>();
-
-const { push } = useRouter();
-
-const account = ref<MpAccountApi.Account>({
- id: -1,
- name: '',
-}); // 褰撳墠閫変腑鐨勫叕浼楀彿
-const accountList = ref<MpAccountApi.Account[]>([]); // 鍏紬鍙峰垪琛�
-
-/** 鏌ヨ鍏紬鍙峰垪琛� */
-async function handleQuery() {
- accountList.value = await getSimpleAccountList();
- if (accountList.value.length === 0) {
- message.error('鏈厤缃叕浼楀彿锛岃鍦ㄣ�愬叕浼楀彿绠$悊 -> 璐﹀彿绠$悊銆戣彍鍗曪紝杩涜閰嶇疆');
- await push({ name: 'MpAccount' });
- return;
- }
-
- // 榛樿閫変腑绗竴涓紝濡傛棤鏁版嵁鍒欎笉鎵ц
- const first = accountList.value[0];
- if (first) {
- account.value.id = first.id;
- account.value.name = first.name;
- emit('change', account.value.id, account.value.name);
- }
-}
-
-/** 鍒囨崲閫変腑鍏紬鍙� */
-function onChanged(id: SelectValue) {
- const found = accountList.value.find((v) => v.id === id);
- if (found) {
- account.value.name = found.name;
- emit('change', account.value.id, account.value.name);
- }
-}
-
-/** 鍒濆鍖� */
-onMounted(handleQuery);
-</script>
-
-<template>
- <Select
- v-model:value="account.id"
- placeholder="璇烽�夋嫨鍏紬鍙�"
- class="!w-full"
- @change="onChanged"
- >
- <Select.Option v-for="item in accountList" :key="item.id" :value="item.id">
- {{ item.name }}
- </Select.Option>
- </Select>
-</template>
diff --git a/src/views/mp/components/wx-location/types.ts b/src/views/mp/components/wx-location/types.ts
deleted file mode 100644
index 9566c2a..0000000
--- a/src/views/mp/components/wx-location/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export interface WxLocationProps {
- label: string;
- locationX: number;
- locationY: number;
- qqMapKey?: string;
-}
diff --git a/src/views/mp/components/wx-location/wx-location.vue b/src/views/mp/components/wx-location/wx-location.vue
deleted file mode 100644
index 83704c9..0000000
--- a/src/views/mp/components/wx-location/wx-location.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<script lang="ts" setup>
-import type { WxLocationProps } from './types';
-
-import { computed, onMounted, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Col, message, Row } from 'ant-design-vue';
-
-import { getTradeConfig } from '#/api/mall/trade/config';
-
-/** 寰俊娑堟伅 - 瀹氫綅 */
-defineOptions({ name: 'WxLocation' });
-
-const props = withDefaults(defineProps<WxLocationProps>(), {
- qqMapKey: '', // QQ 鍦板浘鐨勫瘑閽� https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
-});
-
-const fetchedQqMapKey = ref('');
-const resolvedQqMapKey = computed(
- () => props.qqMapKey || fetchedQqMapKey.value || '',
-);
-
-const mapUrl = computed(() => {
- return `https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=${props.locationY}&pointy=${props.locationX}&name=${props.label}&ref=yudao`;
-});
-
-const mapImageUrl = computed(() => {
- return `https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|${props.locationX},${props.locationY}&key=${resolvedQqMapKey.value}&size=250*180`;
-});
-
-async function fetchQqMapKey() {
- try {
- const data = await getTradeConfig();
- fetchedQqMapKey.value = data.tencentLbsKey ?? '';
- if (!fetchedQqMapKey.value) {
- message.warning('璇峰厛閰嶇疆鑵捐浣嶇疆鏈嶅姟瀵嗛挜');
- }
- } catch {
- message.error('鑾峰彇鑵捐浣嶇疆鏈嶅姟瀵嗛挜澶辫触');
- }
-}
-
-onMounted(async () => {
- if (!props.qqMapKey) {
- await fetchQqMapKey();
- }
-});
-
-defineExpose({
- locationX: props.locationX,
- locationY: props.locationY,
- label: props.label,
- qqMapKey: resolvedQqMapKey,
-});
-</script>
-
-<template>
- <div>
- <a :href="mapUrl" target="_blank" class="text-primary">
- <Col>
- <Row>
- <img :src="mapImageUrl" alt="鍦板浘浣嶇疆" />
- </Row>
- <Row class="mt-2">
- <IconifyIcon icon="lucide:map-pin" class="mr-1" />
- {{ label }}
- </Row>
- </Col>
- </a>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-material-select/wx-material-select.vue b/src/views/mp/components/wx-material-select/wx-material-select.vue
deleted file mode 100644
index 83b4122..0000000
--- a/src/views/mp/components/wx-material-select/wx-material-select.vue
+++ /dev/null
@@ -1,575 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMaterialApi } from '#/api/mp/material';
-
-import { computed, watch } from 'vue';
-
-import { Page } from '../../../../packages/effects/common-ui/src';
-import { NewsType } from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Button, Pagination, Row, Spin } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getDraftPage } from '#/api/mp/draft';
-import { getFreePublishPage } from '#/api/mp/freePublish';
-import { getMaterialPage } from '#/api/mp/material';
-import { WxNews, WxVideoPlayer, WxVoicePlayer } from '#/views/mp/components';
-
-/** 寰俊绱犳潗閫夋嫨 */
-defineOptions({ name: 'WxMaterialSelect' });
-
-const props = withDefaults(
- defineProps<{
- accountId: number;
- newsType?: NewsType;
- type: string;
- }>(),
- {
- newsType: NewsType.Published,
- },
-);
-
-const emit = defineEmits<{
- (e: 'selectMaterial', item: any): void;
-}>();
-
-const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
- [
- {
- field: 'mediaId',
- title: '缂栧彿',
- align: 'center',
- minWidth: 160,
- },
- {
- field: 'name',
- title: '鏂囦欢鍚�',
- minWidth: 200,
- },
- {
- field: 'voice',
- title: '璇煶',
- minWidth: 200,
- align: 'center',
- slots: { default: 'voice' },
- },
- {
- field: 'createTime',
- title: '涓婁紶鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- align: 'center',
- slots: { default: 'actions' },
- },
- ];
-
-const videoGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
- [
- {
- field: 'mediaId',
- title: '缂栧彿',
- minWidth: 160,
- },
- {
- field: 'name',
- title: '鏂囦欢鍚�',
- minWidth: 200,
- },
- {
- field: 'title',
- title: '鏍囬',
- minWidth: 200,
- },
- {
- field: 'introduction',
- title: '浠嬬粛',
- minWidth: 220,
- },
- {
- field: 'video',
- title: '瑙嗛',
- minWidth: 220,
- align: 'center',
- slots: { default: 'video' },
- },
- {
- field: 'createTime',
- title: '涓婁紶鏃堕棿',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- align: 'center',
- slots: { default: 'actions' },
- },
- ];
-
-// Image Grid
-const [ImageGrid, imageGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: [],
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, { accountId }) => {
- const finalAccountId = accountId ?? props.accountId;
- if (!finalAccountId) {
- return { list: [], total: 0 };
- }
- return await getMaterialPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- accountId: finalAccountId,
- type: 'image',
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'mediaId',
- isHover: true,
- },
- toolbarConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<MpMaterialApi.Material>,
-});
-
-// News Grid
-const [NewsGrid, newsGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: [],
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: false,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, { accountId }) => {
- const finalAccountId = accountId ?? props.accountId;
- if (!finalAccountId) {
- return { list: [], total: 0 };
- }
- if (props.newsType === NewsType.Published) {
- const data = await getFreePublishPage({
- accountId: finalAccountId,
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- });
- data.list.forEach((item: any) => {
- const articles = item.content.newsItem;
- articles.forEach((article: any) => {
- article.picUrl = article.thumbUrl;
- });
- });
- return data;
- } else {
- const data = await getDraftPage({
- accountId: finalAccountId,
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- });
- data.list.forEach((draft: any) => {
- const articles = draft.content.newsItem;
- articles.forEach((article: any) => {
- article.picUrl = article.thumbUrl;
- });
- });
- return data;
- }
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'mediaId',
- isHover: true,
- },
- toolbarConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<any>,
-});
-
-const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
- gridOptions: {
- border: true,
- columns: voiceGridColumns,
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: true,
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, { accountId }) => {
- const finalAccountId = accountId ?? props.accountId;
- if (!finalAccountId) {
- return { list: [], total: 0 };
- }
- return await getMaterialPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- accountId: finalAccountId,
- type: 'voice',
- });
- },
- },
- },
- rowConfig: {
- keyField: 'mediaId',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- } as VxeTableGridOptions<MpMaterialApi.Material>,
-});
-
-const [VideoGrid, videoGridApi] = useVbenVxeGrid({
- gridOptions: {
- border: true,
- columns: videoGridColumns,
- height: 'auto',
- keepSource: true,
- pagerConfig: {
- enabled: true,
- pageSize: 10,
- },
- proxyConfig: {
- ajax: {
- query: async ({ page }, { accountId }) => {
- const finalAccountId = accountId ?? props.accountId;
- if (finalAccountId === undefined || finalAccountId === null) {
- return { list: [], total: 0 };
- }
- return await getMaterialPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- accountId: finalAccountId,
- type: 'video',
- });
- },
- },
- },
- rowConfig: {
- keyField: 'mediaId',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- } as VxeTableGridOptions<MpMaterialApi.Material>,
-});
-
-// 浠� Grid 鑾峰彇鏁版嵁
-const imageList = computed(() => {
- try {
- const tableData = imageGridApi.grid?.getTableData();
- return (tableData?.tableData as MpMaterialApi.Material[]) || [];
- } catch {
- return [];
- }
-});
-
-const imageLoading = computed(() => {
- return imageGridApi.grid?.loading || false;
-});
-
-const imageTotal = computed(() => {
- try {
- const proxyInfo = imageGridApi.grid?.getProxyInfo();
- return proxyInfo?.pager?.total || 0;
- } catch {
- return 0;
- }
-});
-
-const imageCurrentPage = computed({
- get: () => {
- try {
- return imageGridApi.grid?.pagerConfig?.currentPage || 1;
- } catch {
- return 1;
- }
- },
- set: (value: number) => {
- imageGridApi.grid?.commitProxy('page', { currentPage: value });
- },
-});
-
-const imagePageSize = computed({
- get: () => {
- try {
- return imageGridApi.grid?.pagerConfig?.pageSize || 10;
- } catch {
- return 10;
- }
- },
- set: (value: number) => {
- imageGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
- },
-});
-
-const newsList = computed(() => {
- try {
- const tableData = newsGridApi.grid?.getTableData();
- return (tableData?.tableData as any[]) || [];
- } catch {
- return [];
- }
-});
-
-const newsLoading = computed(() => {
- return newsGridApi.grid?.loading || false;
-});
-
-const newsTotal = computed(() => {
- try {
- const proxyInfo = newsGridApi.grid?.getProxyInfo();
- return proxyInfo?.pager?.total || 0;
- } catch {
- return 0;
- }
-});
-
-const newsCurrentPage = computed({
- get: () => {
- try {
- return newsGridApi.grid?.pagerConfig?.currentPage || 1;
- } catch {
- return 1;
- }
- },
- set: (value: number) => {
- newsGridApi.grid?.commitProxy('page', { currentPage: value });
- },
-});
-
-const newsPageSize = computed({
- get: () => {
- try {
- return newsGridApi.grid?.pagerConfig?.pageSize || 10;
- } catch {
- return 10;
- }
- },
- set: (value: number) => {
- newsGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
- },
-});
-
-function selectMaterialFun(item: any) {
- emit('selectMaterial', item);
-}
-
-// 鐩戝惉 accountId 鍙樺寲
-watch(
- () => props.accountId,
- (accountId) => {
- switch (props.type) {
- case 'image': {
- imageGridApi.reload({ accountId });
- break;
- }
- case 'news': {
- newsGridApi.reload({ accountId });
- break;
- }
- case 'video': {
- videoGridApi.reload({ accountId });
- break;
- }
- case 'voice': {
- voiceGridApi.reload({ accountId });
- break;
- }
- }
- },
- { immediate: true },
-);
-
-// 鐩戝惉 type 鍙樺寲
-watch(
- () => props.type,
- () => {
- switch (props.type) {
- case 'image': {
- imageGridApi.reload({ accountId: props.accountId });
- break;
- }
- case 'news': {
- newsGridApi.reload({ accountId: props.accountId });
- break;
- }
- case 'video': {
- videoGridApi.reload({ accountId: props.accountId });
- break;
- }
- case 'voice': {
- voiceGridApi.reload({ accountId: props.accountId });
- break;
- }
- }
- },
-);
-
-// 鐩戝惉 newsType 鍙樺寲
-watch(
- () => props.newsType,
- () => {
- if (props.type === 'news') {
- newsGridApi.reload({ accountId: props.accountId });
- }
- },
-);
-</script>
-
-<template>
- <Page :bordered="false" class="pb-8">
- <!-- 绫诲瀷锛歩mage -->
- <template v-if="props.type === 'image'">
- <div class="image-grid-wrapper">
- <ImageGrid>
- <template #default>
- <Spin :spinning="imageLoading">
- <div
- class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
- >
- <div
- v-for="item in imageList"
- :key="item.mediaId"
- class="mb-2.5 h-72 break-inside-avoid border border-[#eaeaea] p-2.5"
- >
- <img
- class="h-48 w-full object-contain"
- :src="item.url"
- alt="绱犳潗鍥剧墖"
- />
- <p class="truncate text-center text-xs leading-[30px]">
- {{ item.name }}
- </p>
- <Row class="flex justify-center pt-2.5">
- <Button type="primary" @click="selectMaterialFun(item)">
- 閫夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- </Row>
- </div>
- </div>
- </Spin>
- <Pagination
- v-model:current="imageCurrentPage"
- v-model:page-size="imagePageSize"
- :total="imageTotal"
- class="mt-4"
- show-size-changer
- />
- </template>
- </ImageGrid>
- </div>
- </template>
-
- <!-- 绫诲瀷锛歷oice -->
- <template v-else-if="props.type === 'voice'">
- <VoiceGrid>
- <template #voice="{ row }">
- <WxVoicePlayer :url="row.url" />
- </template>
- <template #actions="{ row }">
- <Button type="link" @click="selectMaterialFun(row)">
- 閫夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:plus" />
- </template>
- </Button>
- </template>
- </VoiceGrid>
- </template>
-
- <!-- 绫诲瀷锛歷ideo -->
- <template v-else-if="props.type === 'video'">
- <VideoGrid>
- <template #video="{ row }">
- <WxVideoPlayer :url="row.url" />
- </template>
- <template #actions="{ row }">
- <Button type="link" @click="selectMaterialFun(row)">
- 閫夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-plus" />
- </template>
- </Button>
- </template>
- </VideoGrid>
- </template>
-
- <!-- 绫诲瀷锛歯ews -->
- <template v-else-if="props.type === 'news'">
- <div class="news-grid-wrapper">
- <NewsGrid>
- <template #default>
- <Spin :spinning="newsLoading">
- <div
- class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
- >
- <div
- v-for="item in newsList"
- :key="item.mediaId"
- class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
- >
- <div v-if="item.content && item.content.newsItem">
- <WxNews :articles="item.content.newsItem" />
- <Row class="flex justify-center pt-2.5">
- <Button type="primary" @click="selectMaterialFun(item)">
- 閫夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- </Row>
- </div>
- </div>
- </div>
- </Spin>
- <Pagination
- v-model:current="newsCurrentPage"
- v-model:page-size="newsPageSize"
- :total="newsTotal"
- class="mt-4"
- show-size-changer
- />
- </template>
- </NewsGrid>
- </div>
- </template>
- </Page>
-</template>
-
-<style scoped>
-.image-grid-wrapper :deep(.vxe-grid--body-wrapper) {
- display: none;
-}
-
-.news-grid-wrapper :deep(.vxe-grid--body-wrapper) {
- display: none;
-}
-</style>
diff --git a/src/views/mp/components/wx-msg/msg-event.vue b/src/views/mp/components/wx-msg/msg-event.vue
deleted file mode 100644
index 24b039e..0000000
--- a/src/views/mp/components/wx-msg/msg-event.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script lang="ts" setup>
-import { Tag } from 'ant-design-vue';
-
-defineOptions({ name: 'MsgEvent' });
-
-defineProps<{
- item: any;
-}>();
-</script>
-
-<template>
- <div>
- <div v-if="item.event === 'subscribe'">
- <Tag color="success">鍏虫敞</Tag>
- </div>
- <div v-else-if="item.event === 'unsubscribe'">
- <Tag color="error">鍙栨秷鍏虫敞</Tag>
- </div>
- <div v-else-if="item.event === 'CLICK'">
- <Tag>鐐瑰嚮鑿滃崟</Tag>
- 銆恵{ item.eventKey }}銆�
- </div>
- <div v-else-if="item.event === 'VIEW'">
- <Tag>鐐瑰嚮鑿滃崟閾炬帴</Tag>
- 銆恵{ item.eventKey }}銆�
- </div>
- <div v-else-if="item.event === 'scancode_waitmsg'">
- <Tag>鎵爜缁撴灉</Tag>
- 銆恵{ item.eventKey }}銆�
- </div>
- <div v-else-if="item.event === 'scancode_push'">
- <Tag>鎵爜缁撴灉</Tag>
- 銆恵{ item.eventKey }}銆�
- </div>
- <div v-else-if="item.event === 'pic_sysphoto'">
- <Tag>绯荤粺鎷嶇収鍙戝浘</Tag>
- </div>
- <div v-else-if="item.event === 'pic_photo_or_album'">
- <Tag>鎷嶇収鎴栬�呯浉鍐�</Tag>
- </div>
- <div v-else-if="item.event === 'pic_weixin'">
- <Tag>寰俊鐩稿唽</Tag>
- </div>
- <div v-else-if="item.event === 'location_select'">
- <Tag>閫夋嫨鍦扮悊浣嶇疆</Tag>
- </div>
- <div v-else>
- <Tag color="error">鏈煡浜嬩欢绫诲瀷</Tag>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-msg/msg-list.vue b/src/views/mp/components/wx-msg/msg-list.vue
deleted file mode 100644
index 99f6b4a..0000000
--- a/src/views/mp/components/wx-msg/msg-list.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { MpUserApi } from '#/api/mp/user/index';
-
-import { preferences } from '../../../../packages/preferences/src';
-import { formatDateTime } from '../../../../packages/utils/src';
-
-import Msg from './msg.vue';
-
-defineOptions({ name: 'MsgList' });
-
-const props = defineProps<{
- accountId: number;
- list: any[];
- user: Partial<MpUserApi.User>;
-}>();
-
-const SendFrom = {
- MpBot: 2,
- User: 1,
-} as const; // 鍙戦�佹潵婧�
-
-function getAvatar(sendFrom: number) {
- return sendFrom === SendFrom.User
- ? props.user.avatar
- : preferences.app.defaultAvatar;
-}
-
-function getNickname(sendFrom: number) {
- return sendFrom === SendFrom.User ? props.user.nickname : '鍏紬鍙�';
-}
-</script>
-<template>
- <div class="execution" v-for="item in props.list" :key="item.id">
- <div
- class="mb-[30px] flex items-start"
- :class="{ 'flex-row-reverse': item.sendFrom === SendFrom.MpBot }"
- >
- <div class="flex w-20 flex-col items-center text-center">
- <img
- :src="getAvatar(item.sendFrom)"
- class="mb-2 h-12 w-12 rounded-full border border-transparent object-cover"
- />
- <div class="text-sm font-semibold text-[#999]">
- {{ getNickname(item.sendFrom) }}
- </div>
- </div>
- <div class="relative mx-2 flex-1 rounded-[5px] border border-[#dedede]">
- <span
- v-if="item.sendFrom === SendFrom.MpBot"
- class="pointer-events-none absolute -left-2 top-[10px] h-0 w-0 border-y-[8px] border-r-[8px] border-y-transparent border-r-[transparent]"
- :class="{
- '-right-4 left-auto border-l-[8px] border-r-0 border-l-[#f8f8f8]':
- item.sendFrom === SendFrom.MpBot,
- }"
- ></span>
- <span
- v-if="item.sendFrom === SendFrom.User"
- class="pointer-events-none absolute -left-[7px] top-[10px] h-0 w-0 border-y-[8px] border-r-[8px] border-y-transparent border-r-[#f8f8f8]"
- :class="{
- '-right-[7px] left-auto border-l-[8px] border-r-0 border-l-[#f8f8f8]':
- item.sendFrom === SendFrom.MpBot,
- }"
- ></span>
- <div
- class="flex items-center justify-between rounded-t-[5px] border-b border-[#eee] bg-[#f8f8f8] px-[15px] py-[5px]"
- >
- <div class="text-xs text-gray-500">
- {{ formatDateTime(item.createTime) }}
- </div>
- </div>
- <div
- class="overflow-hidden rounded-b-[5px] p-[15px] text-sm text-[#333]"
- :class="
- item.sendFrom === SendFrom.MpBot ? 'bg-[#6BED72]' : 'bg-white'
- "
- >
- <Msg :item="item" />
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-msg/msg.vue b/src/views/mp/components/wx-msg/msg.vue
deleted file mode 100644
index eb22aa6..0000000
--- a/src/views/mp/components/wx-msg/msg.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import { MpMsgType } from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import {
- WxLocation,
- WxMusic,
- WxNews,
- WxVideoPlayer,
- WxVoicePlayer,
-} from '#/views/mp/components';
-
-import MsgEvent from './msg-event.vue';
-
-defineOptions({ name: 'WxMsg' });
-
-withDefaults(
- defineProps<{
- item?: any;
- }>(),
- {
- item: {},
- },
-);
-</script>
-
-<template>
- <div>
- <MsgEvent v-if="item.type === MpMsgType.Event" :item="item" />
-
- <div v-else-if="item.type === MpMsgType.Text">{{ item.content }}</div>
-
- <div v-else-if="item.type === MpMsgType.Voice">
- <WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" />
- </div>
-
- <div v-else-if="item.type === MpMsgType.Image">
- <a :href="item.mediaUrl" target="_blank">
- <img :src="item.mediaUrl" class="w-[100px]" alt="鍥剧墖娑堟伅" />
- </a>
- </div>
-
- <div
- v-else-if="item.type === MpMsgType.Video || item.type === 'shortvideo'"
- class="text-center"
- >
- <WxVideoPlayer :url="item.mediaUrl" />
- </div>
-
- <div v-else-if="item.type === MpMsgType.Link" class="flex flex-col gap-2">
- <a :href="item.url" target="_blank" class="text-success no-underline">
- <div class="flex items-center text-sm font-medium text-[#52c41a]">
- <IconifyIcon icon="lucide:link" class="mr-1" />
- {{ item.title }}
- </div>
- </a>
- <div class="text-xs text-[#666]">{{ item.description }}</div>
- </div>
-
- <div v-else-if="item.type === MpMsgType.Location">
- <WxLocation
- :label="item.label"
- :location-y="item.locationY"
- :location-x="item.locationX"
- />
- </div>
-
- <div v-else-if="item.type === MpMsgType.News" class="w-[300px]">
- <WxNews :articles="item.articles" />
- </div>
-
- <div v-else-if="item.type === MpMsgType.Music">
- <WxMusic
- :title="item.title"
- :description="item.description"
- :thumb-media-url="item.thumbMediaUrl"
- :music-url="item.musicUrl"
- :hq-music-url="item.hqMusicUrl"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-msg/wx-msg.vue b/src/views/mp/components/wx-msg/wx-msg.vue
deleted file mode 100644
index d1347f9..0000000
--- a/src/views/mp/components/wx-msg/wx-msg.vue
+++ /dev/null
@@ -1,178 +0,0 @@
-<script lang="ts" setup>
-import type { MpUserApi } from '#/api/mp/user/index';
-
-import { nextTick, onMounted, reactive, ref, unref } from 'vue';
-
-import { preferences } from '../../../../packages/preferences/src';
-
-import { Button, message, Spin } from 'ant-design-vue';
-
-import { getMessagePage, sendMessage } from '#/api/mp/message';
-import { getUser } from '#/api/mp/user';
-import { WxReply } from '#/views/mp/components';
-
-import MsgList from './msg-list.vue';
-
-defineOptions({ name: 'WxMsg' });
-
-const props = defineProps<{
- userId: number;
-}>();
-
-const accountId = ref(-1); // 鍏紬鍙稩D锛岄渶瑕侀�氳繃userId鍒濆鍖�
-const loading = ref(false); // 娑堟伅鍒楄〃鏄惁姝e湪鍔犺浇涓�
-const hasMore = ref(true); // 鏄惁鍙互鍔犺浇鏇村
-const list = ref<any[]>([]); // 娑堟伅鍒楄〃
-const queryParams = reactive({
- accountId,
- pageNo: 1, // 褰撳墠椤垫暟
- pageSize: 14, // 姣忛〉鏄剧ず澶氬皯鏉�
-});
-
-const user: Partial<MpUserApi.User> = reactive({
- accountId, // 鍏紬鍙疯处鍙风紪鍙�
- avatar: preferences.app.defaultAvatar,
- nickname: '鐢ㄦ埛', // 鐢变簬寰俊涓嶅啀鎻愪緵鏄电О锛岀洿鎺ヤ娇鐢�"鐢ㄦ埛"灞曠ず
-});
-
-// ========= 娑堟伅鍙戦�� =========
-const sendLoading = ref(false); // 鍙戦�佹秷鎭槸鍚﹀姞杞戒腑
-const reply = ref<any>({
- accountId: -1,
- articles: [],
- type: 'text',
-}); // 寰俊鍙戦�佹秷鎭�
-
-const replySelectRef = ref<InstanceType<typeof WxReply> | null>(null); // WxReply缁勪欢ref锛岀敤浜庢秷鎭彂閫佹垚鍔熷悗娓呴櫎鍐呭
-const msgDivRef = ref<HTMLDivElement | null>(null); // 娑堟伅鏄剧ず绐楀彛ref锛岀敤浜庢粴鍔ㄥ埌搴曢儴
-
-/** 瀹屾垚鍔犺浇 */
-onMounted(async () => {
- const data = await getUser(props.userId);
- user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname;
- user.avatar = data.avatar?.length > 0 ? data.avatar : user.avatar;
- accountId.value = data.accountId;
- reply.value.accountId = data.accountId;
-
- refreshChange();
-});
-
-/** 鎵ц鍙戦�� */
-async function sendMsg() {
- if (!unref(reply)) {
- return;
- }
- // 鍏紬鍙烽檺鍒讹細瀹㈡湇娑堟伅锛屽叕浼楀彿鍙厑璁稿彂閫佷竴鏉�
- if (
- reply.value.type === 'news' &&
- reply.value.articles &&
- reply.value.articles.length > 1
- ) {
- reply.value.articles = [reply.value.articles[0]];
- message.success('鍥炬枃娑堟伅鏉℃暟闄愬埗鍦� 1 鏉′互鍐咃紝宸查粯璁ゅ彂閫佺涓�鏉�');
- }
-
- const data = await sendMessage({
- ...reply.value,
- userId: props.userId,
- } as any);
- sendLoading.value = false;
-
- list.value = [...list.value, data];
- await scrollToBottom();
-
- // 鍙戦�佸悗娓呯┖鏁版嵁
- replySelectRef.value?.clear();
-}
-
-function loadMore() {
- queryParams.pageNo++;
- getPage(queryParams, null);
-}
-
-async function getPage(page: any, params: any = null) {
- loading.value = true;
- const dataTemp = await getMessagePage(
- Object.assign(
- {
- accountId: page.accountId,
- pageNo: page.pageNo,
- pageSize: page.pageSize,
- userId: props.userId,
- },
- params,
- ),
- );
-
- const scrollHeight = msgDivRef.value?.scrollHeight ?? 0;
- // 澶勭悊鏁版嵁
- const data = dataTemp.list.toReversed();
- list.value = [...data, ...list.value];
- loading.value = false;
- if (data.length < queryParams.pageSize || data.length === 0) {
- hasMore.value = false;
- }
- queryParams.pageNo = page.pageNo;
- queryParams.pageSize = page.pageSize;
- // 婊氬姩鍒板師鏉ョ殑浣嶇疆
- if (queryParams.pageNo === 1) {
- // 瀹氫綅鍒版秷鎭簳閮�
- await scrollToBottom();
- } else if (data.length > 0) {
- // 瀹氫綅婊氬姩鏉�
- await nextTick();
- if (scrollHeight !== 0 && msgDivRef.value) {
- msgDivRef.value.scrollTop =
- msgDivRef.value.scrollHeight - scrollHeight - 100;
- }
- }
-}
-
-function refreshChange() {
- getPage(queryParams);
-}
-
-/** 瀹氫綅鍒版秷鎭簳閮� */
-async function scrollToBottom() {
- await nextTick();
- if (msgDivRef.value) {
- msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight;
- }
-}
-</script>
-
-<template>
- <div class="flex h-full flex-col">
- <div ref="msgDivRef" class="mx-2.5 flex-1 overflow-auto bg-[#eaeaea]">
- <!-- 鍔犺浇鏇村 -->
- <Spin :spinning="loading" />
- <div v-if="!loading">
- <div
- v-if="hasMore"
- class="cursor-pointer rounded p-3 text-center text-sm text-[#409eff] transition-colors duration-300 hover:bg-[#f5f7fa]"
- @click="loadMore"
- >
- <span>鐐瑰嚮鍔犺浇鏇村</span>
- </div>
- <div
- v-else
- class="cursor-not-allowed rounded p-3 text-center text-sm text-[#909399] hover:bg-transparent"
- >
- <span>娌℃湁鏇村浜�</span>
- </div>
- </div>
-
- <!-- 娑堟伅鍒楄〃 -->
- <MsgList :list="list" :account-id="accountId" :user="user" />
- </div>
-
- <div class="p-2.5">
- <Spin :spinning="sendLoading">
- <WxReply ref="replySelectRef" v-model="reply" />
- <Button type="primary" class="float-right mb-2 mt-2" @click="sendMsg">
- 鍙戦��(S)
- </Button>
- </Spin>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-music/types.ts b/src/views/mp/components/wx-music/types.ts
deleted file mode 100644
index 5164f53..0000000
--- a/src/views/mp/components/wx-music/types.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface WxMusicProps {
- title?: string;
- description?: string;
- musicUrl?: string;
- hqMusicUrl?: string;
- thumbMediaUrl: string;
-}
diff --git a/src/views/mp/components/wx-music/wx-music.vue b/src/views/mp/components/wx-music/wx-music.vue
deleted file mode 100644
index 8bd2a50..0000000
--- a/src/views/mp/components/wx-music/wx-music.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<script lang="ts" setup>
-import type { WxMusicProps } from './types';
-
-import { computed } from 'vue';
-
-import { Typography } from 'ant-design-vue';
-
-/** 寰俊娑堟伅 - 闊充箰 */
-defineOptions({ name: 'WxMusic' });
-
-const props = withDefaults(defineProps<WxMusicProps>(), {
- title: '',
- description: '',
- musicUrl: '',
- hqMusicUrl: '',
- thumbMediaUrl: '',
-});
-
-const href = computed(() => props.hqMusicUrl || props.musicUrl);
-
-defineExpose({
- musicUrl: props.musicUrl,
-});
-</script>
-
-<template>
- <div>
- <Typography.Link
- :href="href"
- target="_blank"
- class="text-success no-underline"
- >
- <div
- class="flex items-center rounded-[5px] border border-[#e8e8e8] bg-white p-2.5 transition hover:border-black/10 hover:shadow-sm"
- >
- <div
- class="mr-3 h-[60px] w-[60px] overflow-hidden rounded-[4px] border border-transparent"
- >
- <img
- :src="thumbMediaUrl"
- alt="闊充箰灏侀潰"
- class="h-full w-full object-cover"
- />
- </div>
- <div class="min-w-0 flex-1">
- <div class="mb-2 truncate text-sm font-medium text-[#333]">
- {{ title }}
- </div>
- <div class="truncate text-xs text-[#666]">
- {{ description }}
- </div>
- </div>
- </div>
- </Typography.Link>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-news/wx-news.vue b/src/views/mp/components/wx-news/wx-news.vue
deleted file mode 100644
index 6e52761..0000000
--- a/src/views/mp/components/wx-news/wx-news.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script lang="ts" setup>
-/** 寰俊娑堟伅 - 鍥炬枃 */
-defineOptions({ name: 'WxNews' });
-
-const props = withDefaults(
- defineProps<{
- articles?: any[] | null;
- }>(),
- {
- articles: null,
- },
-);
-
-defineExpose({
- articles: props.articles,
-});
-</script>
-
-<template>
- <div class="mx-auto flex w-full flex-col gap-[10px] bg-white">
- <div v-for="(article, index) in articles" :key="index">
- <!-- 澶存潯 -->
- <a v-if="index === 0" :href="article.url" target="_blank">
- <div class="mx-auto w-full">
- <div class="relative w-full bg-[#acadae]">
- <img
- :src="article.picUrl"
- :preview="false"
- class="w-[100px] object-cover"
- />
- <div
- class="absolute bottom-0 left-0 ml-[10px] inline-block w-[98%] whitespace-normal p-[1%] text-base text-white"
- >
- <span>{{ article.title }}</span>
- </div>
- </div>
- </div>
- </a>
- <!-- 浜屾潯/涓夋潯绛夌瓑 -->
- <a v-else :href="article.url" target="_blank">
- <div class="bg-white">
- <div class="relative box-border p-[10px]">
- <div class="flex items-center">
- <div class="flex-1 text-sm">{{ article.title }}</div>
-
- <img
- :src="article.picUrl"
- class="h-[70px] w-[70px] object-cover"
- alt="鏂囩珷鍥剧墖"
- />
- </div>
- </div>
- </div>
- </a>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-image.vue b/src/views/mp/components/wx-reply/tab-image.vue
deleted file mode 100644
index 213b173..0000000
--- a/src/views/mp/components/wx-reply/tab-image.vue
+++ /dev/null
@@ -1,181 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import type { UploadRawFile } from '#/views/mp/hooks/useUpload';
-
-import { computed, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue';
-
-import { WxMaterialSelect } from '#/views/mp/components';
-import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
-
-defineOptions({ name: 'TabImage' });
-
-const props = defineProps<{
- modelValue: Reply;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-const accessStore = useAccessStore();
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
-const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
-const reply = computed<Reply>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const showDialog = ref(false);
-const fileList = ref([]);
-const uploadData = reactive({
- accountId: reply.value.accountId,
- introduction: '',
- title: '',
- type: 'image',
-});
-
-/** 鍥剧墖涓婁紶鍓嶆牎楠� */
-function beforeImageUpload(rawFile: UploadRawFile) {
- return useBeforeUpload(UploadType.Image, 2)(rawFile);
-}
-
-/** 鑷畾涔変笂浼犺姹� */
-async function customRequest(options: any) {
- const { file, onSuccess, onError } = options;
-
- const formData = new FormData();
- formData.append('file', file);
- formData.append('accountId', String(uploadData.accountId));
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
-
- try {
- const response = await fetch(UPLOAD_URL, {
- method: 'POST',
- headers: HEADERS,
- body: formData,
- });
-
- const result = await response.json();
-
- if (result.code !== 0) {
- message.error(result.msg || '涓婁紶鍑洪敊');
- onError(new Error(result.msg || '涓婁紶澶辫触'));
- return;
- }
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- // 涓婁紶濂界殑鏂囦欢锛屾湰璐ㄦ槸涓礌鏉愶紝鎵�浠ュ彲浠ヨ繘琛岄�変腑
- selectMaterial(result.data);
- message.success('涓婁紶鎴愬姛');
- onSuccess(result, file);
- } catch (error) {
- message.error('涓婁紶澶辫触锛岃閲嶈瘯');
- onError(error);
- }
-}
-
-/** 鍒犻櫎鍥剧墖 */
-function onDelete() {
- reply.value.mediaId = null;
- reply.value.url = null;
- reply.value.name = null;
-}
-
-/** 閫夋嫨绱犳潗 */
-function selectMaterial(item: any) {
- showDialog.value = false;
- reply.value.mediaId = item.mediaId;
- reply.value.url = item.url;
- reply.value.name = item.name;
-}
-</script>
-
-<template>
- <div>
- <!-- 鎯呭喌涓�锛氬凡缁忛�夋嫨濂界礌鏉愩�佹垨鑰呬笂浼犲ソ鍥剧墖 -->
- <div
- v-if="reply.url"
- class="mx-auto mb-[10px] border border-[#eaeaea] p-[10px]"
- >
- <img class="w-full" :src="reply.url" alt="鍥剧墖绱犳潗" />
- <p
- v-if="reply.name"
- class="overflow-hidden text-ellipsis whitespace-nowrap text-center text-xs"
- >
- {{ reply.name }}
- </p>
- <Row class="pt-[10px] text-center" justify="center">
- <Button danger shape="circle" @click="onDelete">
- <template #icon>
- <IconifyIcon icon="lucide:trash-2" />
- </template>
- </Button>
- </Row>
- </div>
-
- <!-- 鎯呭喌浜岋細鏈仛瀹屼笂杩版搷浣� -->
- <Row v-else class="text-center" align="middle">
- <!-- 閫夋嫨绱犳潗 -->
- <Col
- :span="12"
- class="flex h-[160px] w-[49.5%] flex-col items-center justify-center border border-[#eaeaea] py-[50px]"
- >
- <Button type="primary" @click="showDialog = true">
- 绱犳潗搴撻�夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- <Modal
- v-model:open="showDialog"
- title="閫夋嫨鍥剧墖"
- :width="1200"
- :footer="null"
- destroy-on-close
- >
- <WxMaterialSelect
- type="image"
- :account-id="reply.accountId"
- @select-material="selectMaterial"
- />
- </Modal>
- </Col>
-
- <!-- 鏂囦欢涓婁紶 -->
- <Col
- :span="12"
- class="float-right flex h-[160px] w-[49.5%] flex-col items-center justify-center border border-[#eaeaea] py-[50px]"
- >
- <Upload
- :custom-request="customRequest"
- :multiple="true"
- :max-count="1"
- :file-list="fileList"
- :before-upload="beforeImageUpload"
- :show-upload-list="false"
- >
- <Button type="primary">
- 涓婁紶鍥剧墖
- <template #icon>
- <IconifyIcon icon="lucide:upload" />
- </template>
- </Button>
- </Upload>
- <div class="mt-2 text-center text-xs leading-[18px] text-[#666]">
- 鏀寔 bmp/png/jpeg/jpg/gif 鏍煎紡锛屽ぇ灏忎笉瓒呰繃 2M
- </div>
- </Col>
- </Row>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-music.vue b/src/views/mp/components/wx-reply/tab-music.vue
deleted file mode 100644
index caca687..0000000
--- a/src/views/mp/components/wx-reply/tab-music.vue
+++ /dev/null
@@ -1,184 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import type { UploadRawFile } from '#/views/mp/hooks/useUpload';
-
-import { computed, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import {
- Button,
- Col,
- Input,
- message,
- Modal,
- Row,
- Upload,
-} from 'ant-design-vue';
-
-import { WxMaterialSelect } from '#/views/mp/components';
-import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
-
-defineOptions({ name: 'TabMusic' });
-
-const props = defineProps<{
- modelValue: Reply;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
-const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
-const reply = computed<Reply>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const showDialog = ref(false);
-const fileList = ref([]);
-const uploadData = reactive({
- accountId: reply.value.accountId,
- introduction: '',
- title: '',
- type: 'thumb', // 闊充箰绫诲瀷涓� thumb
-});
-
-/** 鍥剧墖涓婁紶鍓嶆牎楠� */
-function beforeImageUpload(rawFile: UploadRawFile) {
- return useBeforeUpload(UploadType.Image, 2)(rawFile);
-}
-
-/** 鑷畾涔変笂浼犺姹� */
-async function customRequest(options: any) {
- const { file, onSuccess, onError } = options;
-
- const formData = new FormData();
- formData.append('file', file);
- formData.append('accountId', String(uploadData.accountId));
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
-
- try {
- const response = await fetch(UPLOAD_URL, {
- method: 'POST',
- headers: HEADERS,
- body: formData,
- });
-
- const result = await response.json();
-
- if (result.code !== 0) {
- message.error(result.msg || '涓婁紶鍑洪敊');
- onError(new Error(result.msg || '涓婁紶澶辫触'));
- return;
- }
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- // 涓婁紶濂界殑鏂囦欢锛屾湰璐ㄦ槸涓礌鏉愶紝鎵�浠ュ彲浠ヨ繘琛岄�変腑
- selectMaterial(result.data);
- message.success('涓婁紶鎴愬姛');
- onSuccess(result, file);
- } catch (error) {
- message.error('涓婁紶澶辫触锛岃閲嶈瘯');
- onError(error);
- }
-}
-
-/** 閫夋嫨绱犳潗 */
-function selectMaterial(item: any) {
- showDialog.value = false;
- reply.value.thumbMediaId = item.mediaId;
- reply.value.thumbMediaUrl = item.url;
-}
-</script>
-
-<template>
- <div>
- <Row align="middle" justify="center">
- <Col :span="6">
- <div class="flex flex-col items-center gap-2">
- <div
- class="flex h-[100px] w-[100px] items-center justify-center rounded border border-[#d9d9d9]"
- >
- <img
- v-if="reply.thumbMediaUrl"
- :src="reply.thumbMediaUrl"
- alt="闊充箰灏侀潰"
- class="h-full w-full object-cover"
- />
- <IconifyIcon
- v-else
- icon="lucide:plus"
- :size="40"
- class="text-gray-400"
- />
- </div>
- <div class="flex items-center justify-center">
- <Upload
- :custom-request="customRequest"
- :multiple="true"
- :max-count="1"
- :file-list="fileList"
- :before-upload="beforeImageUpload"
- :show-upload-list="false"
- >
- <Button type="link">鏈湴涓婁紶</Button>
- </Upload>
- <Button type="link" class="ml-2" @click="showDialog = true">
- 绱犳潗搴撻�夋嫨
- </Button>
- </div>
- </div>
- <Modal
- v-model:open="showDialog"
- title="閫夋嫨灏侀潰鍥�"
- :width="1200"
- :footer="null"
- destroy-on-close
- >
- <WxMaterialSelect
- type="image"
- :account-id="reply.accountId"
- @select-material="selectMaterial"
- />
- </Modal>
- </Col>
- <Col :span="18">
- <div class="flex flex-col gap-4">
- <Input
- :value="reply.title || undefined"
- placeholder="璇疯緭鍏ユ爣棰�"
- class="mb-5"
- @update:value="(val) => (reply.title = val || null)"
- />
- <Input
- :value="reply.description || undefined"
- placeholder="璇疯緭鍏ユ弿杩�"
- @update:value="(val) => (reply.description = val || null)"
- />
- </div>
- </Col>
- </Row>
- <div class="mt-5">
- <Input
- :value="reply.musicUrl || undefined"
- placeholder="璇疯緭鍏ラ煶涔愰摼鎺�"
- class="mb-5"
- @update:value="(val) => (reply.musicUrl = val || null)"
- />
- <Input
- :value="reply.hqMusicUrl || undefined"
- placeholder="璇疯緭鍏ラ珮璐ㄩ噺闊充箰閾炬帴"
- @update:value="(val) => (reply.hqMusicUrl = val || null)"
- />
- </div>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-news.vue b/src/views/mp/components/wx-reply/tab-news.vue
deleted file mode 100644
index 6fa366d..0000000
--- a/src/views/mp/components/wx-reply/tab-news.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import { computed, ref } from 'vue';
-
-import { NewsType } from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Button, Col, Modal, Row } from 'ant-design-vue';
-
-import { WxMaterialSelect, WxNews } from '#/views/mp/components';
-
-defineOptions({ name: 'TabNews' });
-
-const props = defineProps<{
- modelValue: Reply;
- newsType?: NewsType;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-const reply = computed<Reply>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const showDialog = ref(false);
-
-/** 閫夋嫨绱犳潗 */
-function selectMaterial(item: any) {
- showDialog.value = false;
- reply.value.articles = item.content.newsItem;
-}
-
-/** 鍒犻櫎鍥炬枃 */
-function onDelete() {
- reply.value.articles = [];
-}
-</script>
-
-<template>
- <div>
- <Row>
- <div
- v-if="reply.articles && reply.articles.length > 0"
- class="mx-auto mb-[10px] w-[280px] border border-[#eaeaea] p-[10px]"
- >
- <WxNews :articles="reply.articles" />
- <Col class="pt-[10px] text-center">
- <Button danger shape="circle" @click="onDelete">
- <template #icon>
- <IconifyIcon icon="lucide:trash-2" />
- </template>
- </Button>
- </Col>
- </div>
-
- <!-- 閫夋嫨绱犳潗 -->
- <Col v-if="!reply.content" :span="24">
- <Row class="text-center" align="middle">
- <Col :span="24">
- <Button type="primary" @click="showDialog = true">
- {{
- newsType === NewsType.Published
- ? '閫夋嫨宸插彂甯冨浘鏂�'
- : '閫夋嫨鑽夌绠卞浘鏂�'
- }}
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- </Col>
- </Row>
- </Col>
- <Modal
- v-model:open="showDialog"
- title="閫夋嫨鍥炬枃"
- :width="1200"
- :footer="null"
- destroy-on-close
- >
- <WxMaterialSelect
- type="news"
- :account-id="reply.accountId"
- :news-type="newsType"
- @select-material="selectMaterial"
- />
- </Modal>
- </Row>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-text.vue b/src/views/mp/components/wx-reply/tab-text.vue
deleted file mode 100644
index 914d943..0000000
--- a/src/views/mp/components/wx-reply/tab-text.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script lang="ts" setup>
-import { computed } from 'vue';
-
-import { Textarea } from 'ant-design-vue';
-
-const props = defineProps<{
- modelValue?: null | string;
-}>();
-
-const emit = defineEmits<{
- (e: 'input', v: null | string): void;
- (e: 'update:modelValue', v: null | string): void;
-}>();
-
-const content = computed({
- get: () => props.modelValue ?? '',
- set: (val: string) => {
- emit('update:modelValue', val || null);
- emit('input', val || null);
- },
-});
-</script>
-
-<template>
- <Textarea v-model:value="content" :rows="5" placeholder="璇疯緭鍏ュ唴瀹�" />
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-video.vue b/src/views/mp/components/wx-reply/tab-video.vue
deleted file mode 100644
index d25dd5a..0000000
--- a/src/views/mp/components/wx-reply/tab-video.vue
+++ /dev/null
@@ -1,184 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import type { UploadRawFile } from '#/views/mp/hooks/useUpload';
-
-import { computed, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import {
- Button,
- Col,
- Input,
- message,
- Modal,
- Row,
- Upload,
-} from 'ant-design-vue';
-
-import { WxMaterialSelect, WxVideoPlayer } from '#/views/mp/components';
-import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
-
-defineOptions({ name: 'TabVideo' });
-
-const props = defineProps<{
- modelValue: Reply;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-const accessStore = useAccessStore();
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
-const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
-
-const reply = computed<Reply>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const showDialog = ref(false);
-const fileList = ref([]);
-const uploadData = reactive({
- accountId: reply.value.accountId,
- introduction: '',
- title: '',
- type: 'video',
-});
-
-/** 瑙嗛涓婁紶鍓嶆牎楠� */
-function beforeVideoUpload(rawFile: UploadRawFile) {
- return useBeforeUpload(UploadType.Video, 10)(rawFile);
-}
-
-/** 鑷畾涔変笂浼犺姹� */
-async function customRequest(options: any) {
- const { file, onSuccess, onError } = options;
-
- const formData = new FormData();
- formData.append('file', file);
- formData.append('accountId', String(uploadData.accountId));
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
-
- try {
- const response = await fetch(UPLOAD_URL, {
- method: 'POST',
- headers: HEADERS,
- body: formData,
- });
-
- const result = await response.json();
-
- if (result.code !== 0) {
- message.error(result.msg || '涓婁紶鍑洪敊');
- onError(new Error(result.msg || '涓婁紶澶辫触'));
- return;
- }
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- // 閫夋嫨绱犳潗
- selectMaterial(result.data);
- message.success('涓婁紶鎴愬姛');
- onSuccess(result, file);
- } catch (error) {
- message.error('涓婁紶澶辫触锛岃閲嶈瘯');
- onError(error);
- }
-}
-
-/** 閫夋嫨绱犳潗鍚庤缃� */
-function selectMaterial(item: any) {
- showDialog.value = false;
-
- reply.value.mediaId = item.mediaId;
- reply.value.url = item.url;
- reply.value.name = item.name;
-
- // title銆乮ntroduction锛氫粠 item 鍒� tempObjItem锛屽洜涓虹礌鏉愰噷鏈� title銆乮ntroduction
- if (item.title) {
- reply.value.title = item.title || '';
- }
- if (item.introduction) {
- reply.value.description = item.introduction || '';
- }
-}
-</script>
-
-<template>
- <div>
- <Row :gutter="[0, 16]">
- <Col :span="24">
- <Input
- :value="reply.title || undefined"
- placeholder="璇疯緭鍏ユ爣棰�"
- @update:value="(val) => (reply.title = val || null)"
- />
- </Col>
- <Col :span="24">
- <Input
- :value="reply.description || undefined"
- placeholder="璇疯緭鍏ユ弿杩�"
- @update:value="(val) => (reply.description = val || null)"
- />
- </Col>
- <Col :span="24">
- <Row class="w-full pt-[10px] text-center" justify="center">
- <WxVideoPlayer v-if="reply.url" :url="reply.url" />
- </Row>
- </Col>
- <Col :span="24">
- <Row class="text-center" align="middle">
- <!-- 閫夋嫨绱犳潗 -->
- <Col :span="12">
- <Button type="primary" @click="showDialog = true">
- 绱犳潗搴撻�夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- <Modal
- v-model:open="showDialog"
- title="閫夋嫨瑙嗛"
- :width="1200"
- :footer="null"
- destroy-on-close
- >
- <WxMaterialSelect
- type="video"
- :account-id="reply.accountId"
- @select-material="selectMaterial"
- />
- </Modal>
- </Col>
-
- <!-- 鏂囦欢涓婁紶 -->
- <Col :span="12">
- <Upload
- :custom-request="customRequest"
- :multiple="true"
- :max-count="1"
- :file-list="fileList"
- :before-upload="beforeVideoUpload"
- :show-upload-list="false"
- >
- <Button type="primary">
- 鏂板缓瑙嗛
- <template #icon>
- <IconifyIcon icon="lucide:upload" />
- </template>
- </Button>
- </Upload>
- </Col>
- </Row>
- </Col>
- </Row>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/tab-voice.vue b/src/views/mp/components/wx-reply/tab-voice.vue
deleted file mode 100644
index eab3fcc..0000000
--- a/src/views/mp/components/wx-reply/tab-voice.vue
+++ /dev/null
@@ -1,182 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import type { UploadRawFile } from '#/views/mp/hooks/useUpload';
-
-import { computed, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue';
-
-import { WxMaterialSelect, WxVoicePlayer } from '#/views/mp/components';
-import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
-
-defineOptions({ name: 'TabVoice' });
-
-const props = defineProps<{
- modelValue: Reply;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-const accessStore = useAccessStore();
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
-const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
-
-const reply = computed<Reply>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-const showDialog = ref(false);
-const fileList = ref([]);
-const uploadData = reactive({
- accountId: reply.value.accountId,
- introduction: '',
- title: '',
- type: 'voice',
-});
-
-/** 璇煶涓婁紶鍓嶆牎楠� */
-function beforeVoiceUpload(rawFile: UploadRawFile) {
- return useBeforeUpload(UploadType.Voice, 10)(rawFile);
-}
-
-/** 鑷畾涔変笂浼犺姹� */
-async function customRequest(options: any) {
- const { file, onSuccess, onError } = options;
-
- const formData = new FormData();
- formData.append('file', file);
- formData.append('accountId', String(uploadData.accountId));
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
-
- try {
- const response = await fetch(UPLOAD_URL, {
- method: 'POST',
- headers: HEADERS,
- body: formData,
- });
-
- const result = await response.json();
-
- if (result.code !== 0) {
- message.error(result.msg || '涓婁紶鍑洪敊');
- onError(new Error(result.msg || '涓婁紶澶辫触'));
- return;
- }
-
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- // 涓婁紶濂界殑鏂囦欢锛屾湰璐ㄦ槸涓礌鏉愶紝鎵�浠ュ彲浠ヨ繘琛岄�変腑
- selectMaterial(result.data);
- message.success('涓婁紶鎴愬姛');
- onSuccess(result, file);
- } catch (error) {
- message.error('涓婁紶澶辫触锛岃閲嶈瘯');
- onError(error);
- }
-}
-
-/** 鍒犻櫎璇煶 */
-function onDelete() {
- reply.value.mediaId = null;
- reply.value.url = null;
- reply.value.name = null;
-}
-
-/** 閫夋嫨绱犳潗 */
-function selectMaterial(item: Reply) {
- showDialog.value = false;
- reply.value.mediaId = item.mediaId;
- reply.value.url = item.url;
- reply.value.name = item.name;
-}
-</script>
-
-<template>
- <div>
- <div
- v-if="reply.url"
- class="mx-auto mb-[10px] border border-[#eaeaea] p-[10px]"
- >
- <p
- class="overflow-hidden text-ellipsis whitespace-nowrap text-center text-xs"
- >
- {{ reply.name }}
- </p>
- <Row class="w-full pt-[10px] text-center" justify="center">
- <WxVoicePlayer :url="reply.url" />
- </Row>
- <Row class="w-full pt-[10px] text-center" justify="center">
- <Button danger shape="circle" @click="onDelete">
- <template #icon>
- <IconifyIcon icon="lucide:trash-2" />
- </template>
- </Button>
- </Row>
- </div>
-
- <Row v-else class="text-center">
- <!-- 閫夋嫨绱犳潗 -->
- <Col
- :span="12"
- class="flex h-[160px] w-[49.5%] flex-col items-center justify-center border border-[#eaeaea] py-[50px]"
- >
- <Button type="primary" @click="showDialog = true">
- 绱犳潗搴撻�夋嫨
- <template #icon>
- <IconifyIcon icon="lucide:circle-check" />
- </template>
- </Button>
- <Modal
- v-model:open="showDialog"
- title="閫夋嫨璇煶"
- :width="1200"
- :footer="null"
- destroy-on-close
- >
- <WxMaterialSelect
- type="voice"
- :account-id="reply.accountId"
- @select-material="selectMaterial"
- />
- </Modal>
- </Col>
-
- <!-- 鏂囦欢涓婁紶 -->
- <Col
- :span="12"
- class="float-right flex h-[160px] w-[49.5%] flex-col items-center justify-center border border-[#eaeaea] py-[50px]"
- >
- <Upload
- :custom-request="customRequest"
- :multiple="true"
- :max-count="1"
- :file-list="fileList"
- :before-upload="beforeVoiceUpload"
- :show-upload-list="false"
- >
- <Button type="primary">
- 鐐瑰嚮涓婁紶
- <template #icon>
- <IconifyIcon icon="lucide:upload" />
- </template>
- </Button>
- </Upload>
- <div class="mt-2 text-center text-xs leading-[18px] text-[#666]">
- 鏍煎紡鏀寔 mp3/wma/wav/amr锛屾枃浠跺ぇ灏忎笉瓒呰繃 2M锛屾挱鏀鹃暱搴︿笉瓒呰繃 60s
- </div>
- </Col>
- </Row>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-reply/types.ts b/src/views/mp/components/wx-reply/types.ts
deleted file mode 100644
index cfd7bd2..0000000
--- a/src/views/mp/components/wx-reply/types.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { Ref } from 'vue';
-
-import type { ReplyType } from '../../../../packages/constants/src';
-
-import { unref } from 'vue';
-
-export interface Reply {
- accountId: number;
- articles?: any[];
- content?: null | string;
- description?: null | string;
- hqMusicUrl?: null | string;
- introduction?: null | string;
- mediaId?: null | string;
- musicUrl?: null | string;
- name?: null | string;
- thumbMediaId?: null | string;
- thumbMediaUrl?: null | string;
- title?: null | string;
- type: ReplyType;
- url?: null | string;
-}
-
-/** 鍒╃敤鏃х殑 reply[accountId, type] 鍒濆鍖栨柊鐨� Reply */
-export function createEmptyReply(old: Ref<Reply> | Reply): Reply {
- return {
- accountId: unref(old).accountId,
- articles: [],
- content: null,
- description: null,
- hqMusicUrl: null,
- introduction: null,
- mediaId: null,
- musicUrl: null,
- name: null,
- thumbMediaId: null,
- thumbMediaUrl: null,
- title: null,
- type: unref(old).type,
- url: null,
- };
-}
diff --git a/src/views/mp/components/wx-reply/wx-reply.vue b/src/views/mp/components/wx-reply/wx-reply.vue
deleted file mode 100644
index 9157a9d..0000000
--- a/src/views/mp/components/wx-reply/wx-reply.vue
+++ /dev/null
@@ -1,183 +0,0 @@
-<script lang="ts" setup>
-import type { Reply } from './types';
-
-import { computed, ref, unref, watch } from 'vue';
-
-import { NewsType, ReplyType } from '../../../../packages/constants/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Row, Tabs } from 'ant-design-vue';
-
-import TabImage from './tab-image.vue';
-import TabMusic from './tab-music.vue';
-import TabNews from './tab-news.vue';
-import TabText from './tab-text.vue';
-import TabVideo from './tab-video.vue';
-import TabVoice from './tab-voice.vue';
-import { createEmptyReply } from './types';
-
-/** 娑堟伅鍥炲閫夋嫨 */
-defineOptions({ name: 'WxReplySelect' });
-
-const props = withDefaults(defineProps<Props>(), {
- newsType: undefined,
-});
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Reply): void;
-}>();
-
-interface Props {
- modelValue: Reply | undefined;
- newsType?: NewsType;
-}
-
-const defaultReply: Reply = {
- accountId: -1,
- type: ReplyType.Text,
-}; // 鎻愪緵涓�涓粯璁ょ殑 Reply 瀵硅薄锛岄伩鍏� undefined 瀵艰嚧鐨勯敊璇�
-
-const reply = computed<Reply>({
- get: () => props.modelValue || defaultReply,
- set: (val) => emit('update:modelValue', val),
-});
-
-const tabCache = new Map<ReplyType, Reply>(); // 浣滀负澶氫釜鏍囩淇濆瓨鍚勮嚜 Reply 鐨勭紦瀛�
-const currentTab = ref<ReplyType>(props.modelValue?.type || ReplyType.Text); // 閲囩敤鐙珛鐨� ref 鏉ヤ繚瀛樺綋鍓� tab锛岄伩鍏嶅湪 watch 鏍囩鍙樺寲锛屽 reply 杩涜璧嬪�间細浜х敓浜嗗惊鐜皟鐢�
-
-// 鐩戝惉 modelValue 鍙樺寲锛屽悓姝ユ洿鏂� currentTab 鍜岀紦瀛�
-watch(
- () => props.modelValue,
- (newValue) => {
- if (newValue?.type) {
- // 濡傛灉绫诲瀷鍙樺寲锛屾洿鏂� currentTab
- if (newValue.type !== currentTab.value) {
- currentTab.value = newValue.type;
- }
- // 濡傛灉 modelValue 鏈夋暟鎹紝鏇存柊瀵瑰簲 tab 鐨勭紦瀛�
- if (newValue.type) {
- tabCache.set(newValue.type, { ...newValue });
- }
- }
- },
- { immediate: true, deep: true },
-);
-
-watch(
- currentTab,
- (newTab, oldTab) => {
- // 绗竴娆¤繘鍏ワ細oldTab 涓� undefined
- // 鍒ゆ柇 newTab 鏄洜涓� Reply 涓� Partial
- if (oldTab === undefined || newTab === undefined) {
- return;
- }
-
- // 淇濆瓨鏃ab鐨勬暟鎹埌缂撳瓨
- const oldReply = unref(reply);
- // 鍙湁褰撴棫tab鐨剅eply鏈夊疄闄呮暟鎹椂鎵嶇紦瀛橈紙閬垮厤缂撳瓨绌烘暟鎹級
- if (oldReply && oldTab === oldReply.type) {
- tabCache.set(oldTab, oldReply);
- }
-
- // 浠庣紦瀛橀噷闈㈠彇鍑烘柊tab鍐呭锛屾湁鍒欒鐩朢eply锛屾病鏈夊垯鍒涘缓绌篟eply
- const temp = tabCache.get(newTab);
- if (temp) {
- reply.value = temp;
- } else {
- // 濡傛灉褰撳墠reply鐨勭被鍨嬪氨鏄柊tab鐨勭被鍨嬶紝璇存槑杩欐槸浠庡閮ㄤ紶鍏ョ殑鏁版嵁锛屽簲璇ヤ繚鐣�
- const currentReply = unref(reply);
- if (currentReply && currentReply.type === newTab) {
- // 杩欐槸浠庡閮ㄤ紶鍏ョ殑鏁版嵁锛岀洿鎺ョ紦瀛樺苟浣跨敤
- tabCache.set(newTab, currentReply);
- // 涓嶉渶瑕佷慨鏀箁eply.value锛屽洜涓哄畠宸茬粡鏄纭殑浜�
- } else {
- // 鍒涘缓鏂扮殑绌簉eply
- const newData = createEmptyReply(reply);
- newData.type = newTab;
- reply.value = newData;
- }
- }
- },
- {
- immediate: true,
- },
-);
-
-/** 娓呴櫎闄や簡`type`, `accountId`鐨勫瓧娈� */
-function clear() {
- reply.value = createEmptyReply(reply);
-}
-
-defineExpose({
- clear,
-});
-</script>
-
-<template>
- <Tabs v-model:active-key="currentTab" type="card">
- <!-- 绫诲瀷 1锛氭枃鏈� -->
- <Tabs.TabPane :key="ReplyType.Text">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:file-text" class="mr-1" />
- 鏂囨湰
- </Row>
- </template>
- <TabText v-model="reply.content" />
- </Tabs.TabPane>
-
- <!-- 绫诲瀷 2锛氬浘鐗� -->
- <Tabs.TabPane :key="ReplyType.Image">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:image" class="mr-1" />
- 鍥剧墖
- </Row>
- </template>
- <TabImage v-model="reply" />
- </Tabs.TabPane>
-
- <!-- 绫诲瀷 3锛氳闊� -->
- <Tabs.TabPane :key="ReplyType.Voice">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:mic" class="mr-1" />
- 璇煶
- </Row>
- </template>
- <TabVoice v-model="reply" />
- </Tabs.TabPane>
-
- <!-- 绫诲瀷 4锛氳棰� -->
- <Tabs.TabPane :key="ReplyType.Video">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:video" class="mr-1" />
- 瑙嗛
- </Row>
- </template>
- <TabVideo v-model="reply" />
- </Tabs.TabPane>
-
- <!-- 绫诲瀷 5锛氬浘鏂� -->
- <Tabs.TabPane :key="ReplyType.News">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:newspaper" class="mr-1" />
- 鍥炬枃
- </Row>
- </template>
- <TabNews v-model="reply" :news-type="newsType" />
- </Tabs.TabPane>
-
- <!-- 绫诲瀷 6锛氶煶涔� -->
- <Tabs.TabPane :key="ReplyType.Music">
- <template #tab>
- <Row align="middle">
- <IconifyIcon icon="lucide:music" class="mr-1" />
- 闊充箰
- </Row>
- </template>
- <TabMusic v-model="reply" />
- </Tabs.TabPane>
- </Tabs>
-</template>
diff --git a/src/views/mp/components/wx-video-play/wx-video-play.vue b/src/views/mp/components/wx-video-play/wx-video-play.vue
deleted file mode 100644
index da307ca..0000000
--- a/src/views/mp/components/wx-video-play/wx-video-play.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { VideoPlayer } from '@videojs-player/vue';
-import { Modal } from 'ant-design-vue';
-
-import 'video.js/dist/video-js.css';
-
-/** 寰俊娑堟伅 - 瑙嗛 */
-defineOptions({ name: 'WxVideoPlayer' });
-
-const props = defineProps<{
- url: string;
-}>();
-
-const dialogVideo = ref(false);
-
-function playVideo() {
- dialogVideo.value = true;
-}
-</script>
-
-<template>
- <div class="cursor-pointer" @click="playVideo()">
- <!-- 鎻愮ず -->
- <div class="flex items-center">
- <IconifyIcon icon="lucide:circle-play" :size="32" class="mr-2" />
- <p class="text-sm">鐐瑰嚮鎾斁瑙嗛</p>
- </div>
-
- <!-- 寮圭獥鎾斁 -->
- <Modal
- v-model:open="dialogVideo"
- title="瑙嗛鎾斁"
- :footer="null"
- :width="850"
- destroy-on-close
- >
- <VideoPlayer
- v-if="dialogVideo"
- class="video-player vjs-big-play-centered"
- :src="props.url"
- poster=""
- crossorigin="anonymous"
- controls
- playsinline
- :volume="0.6"
- :width="800"
- :playback-rates="[0.7, 1.0, 1.5, 2.0]"
- />
- </Modal>
- </div>
-</template>
diff --git a/src/views/mp/components/wx-voice-play/wx-voice-play.vue b/src/views/mp/components/wx-voice-play/wx-voice-play.vue
deleted file mode 100644
index 0d991ba..0000000
--- a/src/views/mp/components/wx-voice-play/wx-voice-play.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Tag } from 'ant-design-vue';
-import BenzAMRRecorder from 'benz-amr-recorder'; // 鍥犱负寰俊璇煶鏄� amr 鏍煎紡锛屾墍浠ラ渶瑕佺敤鍒� amr 瑙g爜鍣細https://www.npmjs.com/package/benz-amr-recorder
-
-/** 寰俊娑堟伅 - 璇煶 */
-defineOptions({ name: 'WxVoicePlayer' });
-
-const props = withDefaults(
- defineProps<{
- content?: string; // 璇煶鏂囨湰
- url: string; // 璇煶鍦板潃锛屼緥濡傝锛歨ttps://www.iocoder.cn/xxx.amr
- }>(),
- {
- content: '',
- },
-);
-
-const amr = ref<any>();
-const playing = ref(false);
-const duration = ref<number>();
-
-/** 澶勭悊鐐瑰嚮锛屾挱鏀炬垨鏆傚仠 */
-function playVoice() {
- // 鎯呭喌涓�锛氭湭鍒濆鍖栵紝鍒欏垱寤� BenzAMRRecorder
- if (amr.value === undefined) {
- amrInit();
- return;
- }
- // 鎯呭喌浜岋細宸茬粡鍒濆鍖栵紝鍒欐牴鎹儏鍐垫挱鏀炬垨鏆傛椂
- if (amr.value.isPlaying()) {
- amrStop();
- } else {
- amrPlay();
- }
-}
-
-/** 闊抽鍒濆鍖� */
-function amrInit() {
- amr.value = new BenzAMRRecorder();
- // 璁剧疆鎾斁
- amr.value.initWithUrl(props.url).then(() => {
- amrPlay();
- duration.value = amr.value.getDuration();
- });
- // 鐩戝惉鏆傚仠
- amr.value.onEnded(() => {
- playing.value = false;
- });
-}
-
-/** 闊抽鎾斁 */
-function amrPlay() {
- playing.value = true;
- amr.value.play();
-}
-
-/** 闊抽鏆傚仠 */
-function amrStop() {
- playing.value = false;
- amr.value.stop();
-}
-</script>
-
-<template>
- <div
- class="flex min-h-[50px] min-w-[120px] cursor-pointer flex-col items-center justify-center rounded-[10px] bg-[#eaeaea] p-[8px_12px]"
- @click="playVoice"
- >
- <div class="flex items-center">
- <IconifyIcon
- v-if="playing !== true"
- icon="lucide:circle-play"
- :size="32"
- />
- <IconifyIcon v-else icon="lucide:circle-pause" :size="32" />
- <span v-if="duration" class="ml-2 text-xs">{{ duration }} 绉�</span>
- </div>
- <div v-if="content" class="mt-2">
- <Tag color="success">璇煶璇嗗埆</Tag>
- {{ content }}
- </div>
- </div>
-</template>
diff --git a/src/views/mp/draft/data.ts b/src/views/mp/draft/data.ts
deleted file mode 100644
index cb2a540..0000000
--- a/src/views/mp/draft/data.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { formatDateTime } from '../../../packages/utils/src';
-
-/** 鑾峰彇琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'cover',
- title: '鍥剧墖',
- width: 360,
- slots: { default: 'cover' },
- },
- {
- field: 'title',
- title: '鏍囬',
- slots: { default: 'title' },
- },
- {
- field: 'updateTime',
- title: '淇敼鏃堕棿',
- formatter: ({ row }) => {
- return formatDateTime(row.updateTime * 1000);
- },
- },
- {
- title: '鎿嶄綔',
- width: 200,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- ];
-}
diff --git a/src/views/mp/draft/index.vue b/src/views/mp/draft/index.vue
deleted file mode 100644
index e0fd562..0000000
--- a/src/views/mp/draft/index.vue
+++ /dev/null
@@ -1,266 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpDraftApi } from '#/api/mp/draft';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { $t } from '../../../packages/locales/src';
-
-import { Image, message, Typography } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { createEmptyNewsItem, deleteDraft, getDraftPage } from '#/api/mp/draft';
-import { submitFreePublish } from '#/api/mp/freePublish';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'MpDraft' });
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鏂板鑽夌 */
-async function handleCreate() {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- formModalApi
- .setData({
- isCreating: true,
- accountId,
- newsList: [createEmptyNewsItem()],
- })
- .open();
-}
-
-/** 淇敼鑽夌 */
-async function handleEdit(row: MpDraftApi.DraftArticle) {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- formModalApi
- .setData({
- isCreating: false,
- accountId,
- mediaId: row.mediaId,
- newsList: row.content.newsItem,
- })
- .open();
-}
-
-/** 鍒犻櫎鑽夌 */
-async function handleDelete(row: MpDraftApi.DraftArticle) {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting'),
- duration: 0,
- });
- try {
- await deleteDraft(accountId, row.mediaId);
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍙戝竷鑽夌 */
-async function handlePublish(row: MpDraftApi.DraftArticle) {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- await confirm(
- '浣犳鍦ㄩ�氳繃鍙戝竷鐨勬柟寮忓彂琛ㄥ唴瀹广�� 鍙戝竷涓嶅崰鐢ㄧ兢鍙戞鏁帮紝涓�澶╁彲澶氭鍙戝竷銆�' +
- '宸插彂甯冨唴瀹逛笉浼氭帹閫佺粰鐢ㄦ埛锛屼篃涓嶄細灞曠ず鍦ㄥ叕浼楀彿涓婚〉涓�� ' +
- '鍙戝竷鍚庯紝浣犲彲浠ュ墠寰�鍙戣〃璁板綍鑾峰彇閾炬帴锛屼篃鍙互灏嗗彂甯冨唴瀹规坊鍔犲埌鑷畾涔夎彍鍗曘�佽嚜鍔ㄥ洖澶嶃�佽瘽棰樺拰椤甸潰妯℃澘涓��',
- );
- const hideLoading = message.loading({
- content: '鍙戝竷涓�...',
- duration: 0,
- });
- try {
- await submitFreePublish(accountId, row.mediaId);
- message.success('鍙戝竷鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const drafts = await getDraftPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- // 灏� thumbUrl 杞垚 picUrl锛屼繚璇� wx-news 缁勪欢鍙互棰勮灏侀潰
- drafts.list.forEach((draft: any) => {
- const newsList = draft.content?.newsItem;
- if (newsList) {
- newsList.forEach((item: any) => {
- item.picUrl = item.thumbUrl || item.picUrl;
- });
- }
- });
- return {
- list: drafts.list,
- total: drafts.total,
- };
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'mediaId',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpDraftApi.DraftArticle>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍏紬鍙峰浘鏂�" url="https://doc.iocoder.cn/mp/article/" />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="鑽夌鍒楄〃">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍥炬枃鑽夌']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mp:draft:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #cover="{ row }">
- <div
- v-if="row.content?.newsItem && row.content.newsItem.length > 0"
- class="flex flex-col items-center justify-center gap-1"
- >
- <Image
- v-for="(item, index) in row.content.newsItem"
- :key="index"
- :src="item.picUrl || item.thumbUrl"
- class="h-36 !w-[300px] rounded object-cover"
- :alt="`鏂囩珷 ${index + 1} 灏侀潰鍥綻"
- />
- </div>
- <span v-else class="text-gray-400">-</span>
- </template>
- <template #title="{ row }">
- <div
- v-if="row.content?.newsItem && row.content.newsItem.length > 0"
- class="space-y-1"
- >
- <div
- v-for="(item, index) in row.content.newsItem"
- :key="index"
- class="flex h-36 items-center justify-center"
- >
- <Typography.Link :href="(item as any).url" target="_blank">
- {{ item.title }}
- </Typography.Link>
- </div>
- </div>
- <span v-else class="text-gray-400">-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍙戝竷',
- type: 'link',
- icon: ACTION_ICON.UPLOAD,
- auth: ['mp:free-publish:submit'],
- onClick: handlePublish.bind(null, row),
- },
- {
- label: '缂栬緫',
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['mp:draft:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:draft:delete'],
- popConfirm: {
- title: '鏄惁纭鍒犻櫎姝ゆ暟鎹�?',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
-
-<style lang="scss" scoped>
-:deep(.vxe-table--body-wrapper) {
- .vxe-table--body {
- .vxe-body--column {
- .vxe-cell {
- height: auto !important;
- padding: 0;
- }
- }
- }
-}
-</style>
diff --git a/src/views/mp/draft/modules/cover-select.vue b/src/views/mp/draft/modules/cover-select.vue
deleted file mode 100644
index 9a54929..0000000
--- a/src/views/mp/draft/modules/cover-select.vue
+++ /dev/null
@@ -1,150 +0,0 @@
-<script lang="ts" setup>
-import type { UploadFile } from 'ant-design-vue';
-
-import type { MpDraftApi } from '#/api/mp/draft';
-
-import { computed, inject, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import { Button, Image, message, Modal, Upload } from 'ant-design-vue';
-
-import { UploadType, useBeforeUpload } from '#/utils/useUpload';
-import { WxMaterialSelect } from '#/views/mp/components/';
-
-const props = defineProps<{
- isFirst: boolean;
- modelValue: MpDraftApi.NewsItem;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: MpDraftApi.NewsItem): void;
-}>();
-
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-permanent`; // 涓婁紶姘镐箙绱犳潗鐨勫湴鍧�
-const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
-const newsItem = computed<MpDraftApi.NewsItem>({
- get() {
- return props.modelValue;
- },
- set(val) {
- emit('update:modelValue', val);
- },
-});
-
-const accountId = inject<number>('accountId');
-const dialogVisible = ref(false);
-
-const fileList = ref<UploadFile[]>([]);
-interface UploadData {
- type: UploadType;
- accountId: number;
-}
-const uploadData: UploadData = reactive({
- type: UploadType.Image,
- accountId: accountId!,
-});
-
-function handleOpenDialog() {
- dialogVisible.value = true;
-}
-
-/** 绱犳潗閫夋嫨瀹屾垚浜嬩欢 */
-function onMaterialSelected(item: any) {
- dialogVisible.value = false;
- newsItem.value.thumbMediaId = item.mediaId;
- newsItem.value.thumbUrl = item.url;
-}
-
-/** 涓婁紶鍓嶆牎楠� */
-const onBeforeUpload = (file: UploadFile) =>
- useBeforeUpload(UploadType.Image, 2)(file as any);
-
-/** 涓婁紶閿欒澶勭悊 */
-function onUploadChange(info: any) {
- if (info.file.status === 'error') {
- onUploadError(info.file.error || new Error('涓婁紶澶辫触'));
- }
-}
-
-/** 涓婁紶鎴愬姛澶勭悊 */
-function onUploadSuccess(res: any) {
- if (res.code !== 0) {
- message.error(`涓婁紶鍑洪敊锛�${res.msg}`);
- return false;
- }
-
- // 閲嶇疆涓婁紶鏂囦欢鐨勮〃鍗�
- fileList.value = [];
- // 璁剧疆鑽夌鐨勫皝闈㈠瓧娈�
- newsItem.value.thumbMediaId = res.data.mediaId;
- newsItem.value.thumbUrl = res.data.url;
-}
-
-/** 涓婁紶澶辫触澶勭悊 */
-function onUploadError(err: Error) {
- message.error(`涓婁紶澶辫触: ${err.message}`);
-}
-</script>
-
-<template>
- <div>
- <p>灏侀潰:</p>
- <div class="flex w-full flex-col items-center justify-center text-center">
- <Image
- v-if="newsItem.thumbUrl"
- class="max-h-[300px] w-[300px]"
- :src="newsItem.thumbUrl"
- :preview="false"
- />
- <IconifyIcon
- v-else
- icon="lucide:plus"
- class="border border-[#d9d9d9] text-center text-[28px] leading-[120px] text-[#8c939d]"
- :class="isFirst ? 'h-[120px] w-[230px]' : 'h-[120px] w-[120px]'"
- />
- <div class="m-[5px]">
- <div class="flex items-center justify-center">
- <Upload
- :action="UPLOAD_URL"
- :headers="HEADERS"
- :file-list="fileList"
- :data="{ ...uploadData }"
- :before-upload="onBeforeUpload"
- @success="onUploadSuccess"
- @change="onUploadChange"
- >
- <template #default>
- <Button size="small" type="primary">鏈湴涓婁紶</Button>
- </template>
- </Upload>
- <Button
- size="small"
- type="primary"
- class="ml-[5px]"
- @click="handleOpenDialog"
- >
- 绱犳潗搴撻�夋嫨
- </Button>
- </div>
-
- <div class="ml-[5px] mt-[5px] text-xs text-[#999]">
- 鏀寔 bmp/png/jpeg/jpg/gif 鏍煎紡锛屽ぇ灏忎笉瓒呰繃 2M
- </div>
- </div>
- <Modal
- v-model:open="dialogVisible"
- title="鍥剧墖閫夋嫨"
- width="65%"
- :footer="null"
- >
- <WxMaterialSelect
- type="image"
- :account-id="accountId!"
- @select-material="onMaterialSelected"
- />
- </Modal>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/draft/modules/form.vue b/src/views/mp/draft/modules/form.vue
deleted file mode 100644
index e9eaafb..0000000
--- a/src/views/mp/draft/modules/form.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<script lang="ts" setup>
-import type { MpDraftApi } from '#/api/mp/draft';
-
-import { computed, provide, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { createDraft, updateDraft } from '#/api/mp/draft';
-
-import NewsForm from './news-form.vue';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<{
- accountId: number;
- mediaId?: string;
- newsList?: MpDraftApi.NewsItem[];
-}>();
-const newsList = ref<MpDraftApi.NewsItem[]>([]);
-
-const getTitle = computed(() => {
- return formData.value?.mediaId ? '淇敼鍥炬枃' : '鏂板缓鍥炬枃';
-});
-
-provide(
- 'accountId',
- computed(() => formData.value?.accountId),
-); // 鎻愪緵 accountId 缁欏瓙缁勪欢
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- if (!formData.value) {
- return;
- }
- modalApi.lock();
- try {
- if (formData.value.mediaId) {
- await updateDraft(
- formData.value.accountId,
- formData.value.mediaId,
- newsList.value,
- );
- message.success('鏇存柊鎴愬姛');
- } else {
- await createDraft(formData.value.accountId, newsList.value);
- message.success('鏂板鎴愬姛');
- }
- await modalApi.close();
- emit('success');
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- newsList.value = [];
- return;
- }
- const data = modalApi.getData<{
- accountId: number;
- isCreating: boolean;
- mediaId?: string;
- newsList?: MpDraftApi.NewsItem[];
- }>();
- if (!data) {
- return;
- }
- formData.value = {
- accountId: data.accountId,
- mediaId: data.mediaId,
- newsList: data.newsList,
- };
- newsList.value = data.newsList || [];
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-4/5" destroy-on-close>
- <NewsForm
- v-if="formData"
- v-model="newsList"
- :is-creating="!formData.mediaId"
- />
- </Modal>
-</template>
diff --git a/src/views/mp/draft/modules/news-form.vue b/src/views/mp/draft/modules/news-form.vue
deleted file mode 100644
index 5c31715..0000000
--- a/src/views/mp/draft/modules/news-form.vue
+++ /dev/null
@@ -1,267 +0,0 @@
-<script lang="ts" setup>
-import type { MpDraftApi } from '#/api/mp/draft';
-
-import { computed, ref } from 'vue';
-
-import { confirm } from '../../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Button, Col, Input, Layout, Row, Textarea } from 'ant-design-vue';
-
-import { createEmptyNewsItem } from '#/api/mp/draft';
-import { Tinymce as RichTextarea } from '#/components/tinymce';
-
-import CoverSelect from './cover-select.vue';
-
-defineOptions({ name: 'NewsForm' });
-
-const props = defineProps<{
- isCreating: boolean;
- modelValue: MpDraftApi.NewsItem[] | null;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: MpDraftApi.NewsItem[]): void;
-}>();
-
-const newsList = computed<MpDraftApi.NewsItem[]>({
- get() {
- return props.modelValue === null
- ? [createEmptyNewsItem()]
- : props.modelValue;
- },
- set(val) {
- emit('update:modelValue', val);
- },
-});
-
-const activeNewsIndex = ref(0);
-const activeNewsItem = computed(() => {
- const item = newsList.value[activeNewsIndex.value];
- if (!item) {
- return createEmptyNewsItem();
- }
- return item;
-});
-
-/** 灏嗗浘鏂囧悜涓嬬Щ鍔� */
-function moveDownNews(index: number) {
- const current = newsList.value[index];
- const next = newsList.value[index + 1];
- if (current && next) {
- newsList.value[index] = next;
- newsList.value[index + 1] = current;
- activeNewsIndex.value = index + 1;
- }
-}
-
-/** 灏嗗浘鏂囧悜涓婄Щ鍔� */
-function moveUpNews(index: number) {
- const current = newsList.value[index];
- const prev = newsList.value[index - 1];
- if (current && prev) {
- newsList.value[index] = prev;
- newsList.value[index - 1] = current;
- activeNewsIndex.value = index - 1;
- }
-}
-
-/** 鍒犻櫎鎸囧畾 index 鐨勫浘鏂� */
-async function removeNews(index: number) {
- await confirm('纭畾鍒犻櫎璇ュ浘鏂囧悧?');
- newsList.value.splice(index, 1);
- if (activeNewsIndex.value === index) {
- activeNewsIndex.value = 0;
- }
-}
-
-/** 娣诲姞涓�涓浘鏂� */
-function plusNews() {
- newsList.value.push(createEmptyNewsItem());
- activeNewsIndex.value = newsList.value.length - 1;
-}
-</script>
-
-<template>
- <Layout>
- <Layout.Sider width="40%" theme="light">
- <div class="mx-auto mb-[10px] w-[60%] border border-[#eaeaea] p-[10px]">
- <div v-for="(news, index) in newsList" :key="index">
- <div
- class="group relative mx-auto mb-[10px] w-full cursor-pointer border-[2px] bg-white"
- v-if="index === 0"
- :class="
- activeNewsIndex === index
- ? 'border-green-500'
- : 'border-transparent'
- "
- @click="activeNewsIndex = index"
- >
- <div class="relative w-full bg-[#acadae]">
- <img
- class="max-h-[200px] min-h-[100px] w-full object-cover"
- :src="news.thumbUrl"
- />
- <div
- class="absolute bottom-0 left-0 mb-[5px] ml-[5px] inline-block h-[25px] w-[100%] overflow-hidden text-ellipsis whitespace-nowrap p-[1%] text-[18px] text-white"
- >
- {{ news.title }}
- </div>
- </div>
- <div
- class="absolute bottom-0 right-[-45px] top-0 flex flex-col justify-center gap-[10px] py-[5px] text-center"
- v-if="newsList.length > 1"
- >
- <Button
- type="default"
- shape="circle"
- size="small"
- @click="() => moveDownNews(index)"
- >
- <IconifyIcon icon="lucide:arrow-down" />
- </Button>
- <Button
- v-if="isCreating"
- type="primary"
- danger
- shape="circle"
- size="small"
- @click="() => removeNews(index)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- </Button>
- </div>
- </div>
- <div
- class="group relative mx-auto mb-[10px] cursor-pointer border-[2px] bg-white"
- v-if="index > 0"
- :class="
- activeNewsIndex === index
- ? 'border-green-500'
- : 'border-transparent'
- "
- @click="activeNewsIndex = index"
- >
- <div class="relative flex items-center justify-between">
- <div
- class="mb-[5px] ml-[5px] h-[25px] flex-1 overflow-hidden text-ellipsis whitespace-nowrap p-[1%] text-[16px]"
- >
- {{ news.title }}
- </div>
- <img
- class="block h-[90px] w-[90px] object-cover"
- :src="news.thumbUrl"
- />
- </div>
- <div
- class="absolute bottom-0 right-[-45px] top-0 flex flex-col justify-center gap-[10px] py-[5px] text-center"
- >
- <Button
- v-if="newsList.length > index + 1"
- shape="circle"
- type="default"
- size="small"
- @click="() => moveDownNews(index)"
- >
- <IconifyIcon icon="lucide:arrow-down" />
- </Button>
- <Button
- v-if="index > 0"
- type="default"
- shape="circle"
- size="small"
- @click="() => moveUpNews(index)"
- >
- <IconifyIcon icon="lucide:arrow-up" />
- </Button>
- <Button
- v-if="isCreating"
- type="primary"
- danger
- size="small"
- shape="circle"
- @click="() => removeNews(index)"
- >
- <IconifyIcon icon="lucide:trash-2" />
- </Button>
- </div>
- </div>
- </div>
- <Row
- justify="center"
- class="mt-[5px] border-t border-[#eaeaea] pt-[5px] text-center"
- >
- <Button
- type="primary"
- shape="circle"
- @click="plusNews"
- v-if="newsList.length < 8 && isCreating"
- >
- <IconifyIcon icon="lucide:plus" />
- </Button>
- </Row>
- </div>
- </Layout.Sider>
- <Layout.Content class="bg-white">
- <div v-if="newsList.length > 0 && activeNewsItem">
- <!-- 鏍囬銆佷綔鑰呫�佸師鏂囧湴鍧� -->
- <Row :gutter="20">
- <Col :span="24">
- <Input
- v-model:value="activeNewsItem.title"
- placeholder="璇疯緭鍏ユ爣棰橈紙蹇呭~锛�"
- />
- </Col>
- <Col :span="24" class="mt-[5px]">
- <Input
- v-model:value="activeNewsItem.author"
- placeholder="璇疯緭鍏ヤ綔鑰�"
- />
- </Col>
- <Col :span="24" class="mt-[5px]">
- <Input
- v-model:value="activeNewsItem.contentSourceUrl"
- placeholder="璇疯緭鍏ュ師鏂囧湴鍧�"
- />
- </Col>
- </Row>
- <!-- 灏侀潰鍜屾憳瑕� -->
- <Row :gutter="20">
- <Col :span="12">
- <CoverSelect
- v-model="activeNewsItem"
- :is-first="activeNewsIndex === 0"
- />
- </Col>
- <Col :span="12">
- <p>鎽樿:</p>
- <Textarea
- :rows="8"
- v-model:value="activeNewsItem.digest"
- placeholder="璇疯緭鍏ユ憳瑕�"
- class="inline-block w-full align-top"
- :maxlength="120"
- :show-count="true"
- />
- </Col>
- </Row>
- <!--瀵屾枃鏈紪杈戝櫒缁勪欢-->
- <Row>
- <Col :span="24">
- <RichTextarea v-model="activeNewsItem.content" />
- </Col>
- </Row>
- </div>
- </Layout.Content>
- </Layout>
-</template>
-
-<style lang="scss" scoped>
-:deep(.ant-row) {
- margin-bottom: 20px;
-}
-
-:deep(.ant-row:last-child) {
- margin-bottom: 0;
-}
-</style>
diff --git a/src/views/mp/freePublish/data.ts b/src/views/mp/freePublish/data.ts
deleted file mode 100644
index 1c7ce61..0000000
--- a/src/views/mp/freePublish/data.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-import type { MpAccountApi } from '#/api/mp/account';
-
-import { formatDateTime } from '../../../packages/utils/src';
-
-import { getSimpleAccountList } from '#/api/mp/account';
-
-let accountList: MpAccountApi.Account[] = [];
-getSimpleAccountList().then((data) => (accountList = data));
-
-/** 鎼滅储琛ㄥ崟閰嶇疆 */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Select',
- componentProps: {
- options: accountList.map((item) => ({
- label: item.name,
- value: item.id,
- })),
- placeholder: '璇烽�夋嫨鍏紬鍙�',
- clearable: true,
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- field: 'cover',
- title: '鍥剧墖',
- width: 360,
- slots: { default: 'cover' },
- },
- {
- field: 'title',
- title: '鏍囬',
- slots: { default: 'title' },
- },
- {
- field: 'updateTime',
- title: '淇敼鏃堕棿',
- formatter: ({ row }) => {
- return formatDateTime(row.updateTime * 1000);
- },
- },
- {
- title: '鎿嶄綔',
- width: 120,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue
deleted file mode 100644
index bacd78f..0000000
--- a/src/views/mp/freePublish/index.vue
+++ /dev/null
@@ -1,172 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpFreePublishApi } from '#/api/mp/freePublish';
-
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
-
-import { Image, message, Typography } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteFreePublish, getFreePublishPage } from '#/api/mp/freePublish';
-import { $t } from '#/locales';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鍒犻櫎鏂囩珷 */
-async function handleDelete(row: MpFreePublishApi.FreePublish) {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting'),
- duration: 0,
- });
- try {
- await deleteFreePublish(accountId, row.articleId!);
- message.success($t('ui.actionMessage.deleteSuccess'));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- submitOnChange: true,
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- if (!formValues.accountId) {
- return {
- list: [],
- total: 0,
- };
- }
- const res = await getFreePublishPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- // 灏� thumbUrl 杞垚 picUrl锛屼繚璇� wx-news 缁勪欢鍙互棰勮灏侀潰
- res.list.forEach((record: any) => {
- const newsList = record.content?.newsItem;
- if (newsList) {
- newsList.forEach((item: any) => {
- item.picUrl = item.thumbUrl || item.picUrl;
- });
- }
- });
- return {
- list: res.list as unknown as MpFreePublishApi.FreePublish[],
- total: res.total,
- };
- },
- },
- },
- rowConfig: {
- keyField: 'articleId',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpFreePublishApi.FreePublish>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍏紬鍙峰浘鏂�" url="https://doc.iocoder.cn/mp/article/" />
- </template>
- <Grid table-title="鍙戣〃璁板綍">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #cover="{ row }">
- <div
- v-if="row.content?.newsItem && row.content.newsItem.length > 0"
- class="flex flex-col items-center justify-center gap-1"
- >
- <Image
- v-for="(item, index) in row.content.newsItem"
- :key="index"
- :src="item.picUrl || item.thumbUrl"
- class="h-36 !w-[300px] rounded object-cover"
- :alt="`鏂囩珷 ${index + 1} 灏侀潰鍥綻"
- />
- </div>
- <span v-else class="text-gray-400">-</span>
- </template>
- <template #title="{ row }">
- <div
- v-if="row.content?.newsItem && row.content.newsItem.length > 0"
- class="space-y-1"
- >
- <div
- v-for="(item, index) in row.content.newsItem"
- :key="index"
- class="flex h-36 items-center justify-center"
- >
- <Typography.Link :href="(item as any).url" target="_blank">
- {{ item.title }}
- </Typography.Link>
- </div>
- </div>
- <span v-else class="text-gray-400">-</span>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍒犻櫎',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:free-publish:delete'],
- popConfirm: {
- title: '鏄惁纭鍒犻櫎姝ゆ暟鎹�?',
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
-
-<style lang="scss" scoped>
-:deep(.vxe-table--body-wrapper) {
- .vxe-table--body {
- .vxe-body--column {
- .vxe-cell {
- height: auto !important;
- padding: 0;
- }
- }
- }
-}
-</style>
diff --git a/src/views/mp/hooks/useUpload.ts b/src/views/mp/hooks/useUpload.ts
deleted file mode 100644
index 14e2a4f..0000000
--- a/src/views/mp/hooks/useUpload.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { message } from 'ant-design-vue';
-// TODO @xingyu锛氳繖绉嶏紝瑕佹兂鍔炴硶鍏ㄥ眬鍏变韩璧锋潵涔堬紵
-
-import { $t } from '#/locales';
-
-export enum UploadType {
- Image = 'image',
- Video = 'video',
- Voice = 'voice',
-}
-
-interface UploadTypeConfig {
- allowTypes: string[];
- maxSizeMB: number;
- i18nKey: string;
-}
-
-export interface UploadRawFile {
- name: string;
- size: number;
- type: string;
-}
-
-const UPLOAD_CONFIGS: Record<UploadType, UploadTypeConfig> = {
- [UploadType.Image]: {
- allowTypes: [
- 'image/jpeg',
- 'image/png',
- 'image/gif',
- 'image/bmp',
- 'image/jpg',
- ],
- maxSizeMB: 2,
- i18nKey: 'mp.upload.image',
- },
- [UploadType.Video]: {
- allowTypes: ['video/mp4'],
- maxSizeMB: 10,
- i18nKey: 'mp.upload.video',
- },
- [UploadType.Voice]: {
- allowTypes: [
- 'audio/mp3',
- 'audio/mpeg',
- 'audio/wma',
- 'audio/wav',
- 'audio/amr',
- ],
- maxSizeMB: 2,
- i18nKey: 'mp.upload.voice',
- },
-};
-
-export const useBeforeUpload = (type: UploadType, maxSizeMB?: number) => {
- const fn = (rawFile: UploadRawFile): boolean => {
- const config = UPLOAD_CONFIGS[type];
- const finalMaxSize = maxSizeMB ?? config.maxSizeMB;
-
- // 鏍煎紡涓嶆纭�
- if (!config.allowTypes.includes(rawFile.type)) {
- const typeName = $t(config.i18nKey);
- message.error($t('mp.upload.invalidFormat', [typeName]));
- return false;
- }
-
- // 澶у皬涓嶆纭�
- if (rawFile.size / 1024 / 1024 > finalMaxSize) {
- const typeName = $t(config.i18nKey);
- message.error($t('mp.upload.maxSize', [typeName, finalMaxSize]));
- return false;
- }
-
- return true;
- };
-
- return fn;
-};
diff --git a/src/views/mp/material/index.vue b/src/views/mp/material/index.vue
deleted file mode 100644
index d1820bc..0000000
--- a/src/views/mp/material/index.vue
+++ /dev/null
@@ -1,258 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMaterialApi } from '#/api/mp/material';
-
-import { provide, ref } from 'vue';
-
-import { useAccess } from '../../../packages/effects/access/src';
-import { confirm, DocAlert, Page } from '../../../packages/effects/common-ui/src';
-import { IconifyIcon } from '../../../packages/icons/src';
-import { $t } from '../../../packages/locales/src';
-
-import { Button, message, Tabs } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import {
- useGridFormSchema,
- useImageGridColumns,
- useVideoGridColumns,
- useVoiceGridColumns,
-} from './modules/data';
-import { UploadType } from './modules/upload';
-import UploadFile from './modules/UploadFile.vue';
-import UploadVideo from './modules/UploadVideo.vue';
-
-defineOptions({ name: 'MpMaterial' });
-
-const { hasAccessByCodes } = useAccess();
-
-const type = ref<UploadType>(UploadType.Image); // 绱犳潗绫诲瀷
-const showCreateVideo = ref(false); // 鏄惁鏂板缓瑙嗛鐨勫脊绐�
-
-const accountId = ref(-1);
-provide('accountId', accountId);
-
-// 鏍规嵁绫诲瀷鑾峰彇瀵瑰簲鐨勫垪閰嶇疆
-const getColumnsByType = () => {
- switch (type.value) {
- case UploadType.Image: {
- return useImageGridColumns();
- }
- case UploadType.Video: {
- return useVideoGridColumns();
- }
- case UploadType.Voice: {
- return useVoiceGridColumns();
- }
- default: {
- return [];
- }
- }
-};
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: getColumnsByType(),
- height: 'auto',
- keepSource: true,
- pagerConfig: {},
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- const finalAccountId = formValues?.accountId ?? accountId.value;
- if (!finalAccountId || finalAccountId === -1) {
- return { list: [], total: 0 };
- }
- return await getMaterialPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- type: type.value,
- permanent: true,
- accountId: finalAccountId,
- ...formValues,
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- cellConfig: {
- height: type.value === UploadType.Image ? 220 : undefined,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpMaterialApi.Material>,
-});
-
-// 褰� tab 鍒囨崲鏃讹紝鏇存柊 Grid 鐨� columns 鍜� rowConfig
-async function onTabChange() {
- const columns = getColumnsByType();
- gridApi.setGridOptions({
- columns,
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- cellConfig: {
- height: type.value === UploadType.Image ? 220 : undefined,
- },
- });
- await gridApi.reload();
-}
-
-async function handleAccountChange(id: number) {
- accountId.value = id;
- // 鍚屾璁剧疆琛ㄥ崟鍊�
- await gridApi.formApi.setValues({ accountId: id });
- await gridApi.formApi.submitForm();
-}
-
-async function handleRefresh() {
- await gridApi.query();
-}
-
-/** 澶勭悊鍒犻櫎鎿嶄綔 */
-async function handleDelete(id: number) {
- await confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ枃浠�, 鏄惁缁х画?');
- const hideLoading = message.loading({
- content: '姝e湪鍒犻櫎...',
- duration: 0,
- });
- try {
- await deletePermanentMaterial(id);
- message.success('鍒犻櫎鎴愬姛');
- await handleRefresh();
- } finally {
- hideLoading();
- }
-}
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍏紬鍙风礌鏉�" url="https://doc.iocoder.cn/mp/material/" />
- </template>
- <Grid class="material-grid">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-actions>
- <Tabs v-model:active-key="type" class="w-full" @change="onTabChange">
- <!-- tab 1锛氬浘鐗� -->
- <Tabs.TabPane :key="UploadType.Image">
- <template #tab>
- <span class="flex items-center">
- <IconifyIcon icon="lucide:image" class="mr-1" />
- 鍥剧墖
- </span>
- </template>
- </Tabs.TabPane>
-
- <!-- tab 2锛氳闊� -->
- <Tabs.TabPane :key="UploadType.Voice">
- <template #tab>
- <span class="flex items-center">
- <IconifyIcon icon="lucide:mic" class="mr-1" />
- 璇煶
- </span>
- </template>
- </Tabs.TabPane>
-
- <!-- tab 3锛氳棰� -->
- <Tabs.TabPane :key="UploadType.Video">
- <template #tab>
- <span class="flex items-center">
- <IconifyIcon icon="lucide:video" class="mr-1" />
- 瑙嗛
- </span>
- </template>
- </Tabs.TabPane>
- </Tabs>
- </template>
- <template #toolbar-tools>
- <UploadFile
- v-if="
- hasAccessByCodes(['mp:material:upload-permanent']) &&
- type === UploadType.Image
- "
- :type="UploadType.Image"
- @uploaded="handleRefresh"
- />
- <UploadFile
- v-if="
- hasAccessByCodes(['mp:material:upload-permanent']) &&
- type === UploadType.Voice
- "
- :type="UploadType.Voice"
- @uploaded="handleRefresh"
- />
- <Button
- v-if="
- hasAccessByCodes(['mp:material:upload-permanent']) &&
- type === UploadType.Video
- "
- type="primary"
- @click="showCreateVideo = true"
- >
- 鏂板缓瑙嗛
- </Button>
- </template>
-
- <!-- 鍥剧墖鍒楃殑 slot -->
- <template #image="{ row }">
- <div class="flex items-center justify-center" style="height: 192px">
- <img
- :src="row.url"
- class="object-contain"
- style="display: block; max-width: 100%; max-height: 192px"
- />
- </div>
- </template>
-
- <!-- 璇煶鍒楃殑 slot -->
- <template #voice="{ row }">
- <audio :src="row.url" controls style="width: 160px"></audio>
- </template>
-
- <!-- 瑙嗛鍒楃殑 slot -->
- <template #video="{ row }">
- <video
- :src="row.url"
- controls
- style="width: 200px; height: 150px"
- ></video>
- </template>
-
- <!-- 鎿嶄綔鍒楃殑 slot -->
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:material:delete'],
- onClick: () => handleDelete(row.id!),
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 鏂板缓瑙嗛鐨勫脊绐� -->
- <UploadVideo v-model:open="showCreateVideo" @uploaded="handleRefresh" />
- </Page>
-</template>
diff --git a/src/views/mp/material/modules/UploadFile.vue b/src/views/mp/material/modules/UploadFile.vue
deleted file mode 100644
index 4dbe6fa..0000000
--- a/src/views/mp/material/modules/UploadFile.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<script lang="ts" setup>
-import type { UploadProps } from 'ant-design-vue';
-
-import type { UploadData } from './upload';
-
-import { inject, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import { Button, message, Upload } from 'ant-design-vue';
-
-import {
- beforeImageUpload,
- beforeVoiceUpload,
- HEADERS,
- UPLOAD_URL,
- UploadType,
-} from './upload';
-
-const props = defineProps<{ type: UploadType }>();
-
-const emit = defineEmits<{
- uploaded: [v: void];
-}>();
-
-const accountId = inject<number>('accountId');
-
-const fileList = ref<any[]>([]);
-const uploadData: UploadData = reactive({
- accountId: accountId!,
- introduction: '',
- title: '',
- type: props.type,
-});
-
-/** 涓婁紶鍓嶆鏌� */
-const onBeforeUpload =
- props.type === UploadType.Image ? beforeImageUpload : beforeVoiceUpload;
-
-/** 鑷畾涔変笂浼� */
-const customRequest: UploadProps['customRequest'] = async function (options) {
- const { file, onError, onSuccess } = options;
-
- const formData = new FormData();
- formData.append('file', file as File);
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
- formData.append('accountId', String(uploadData.accountId));
-
- try {
- const response = await fetch(UPLOAD_URL, {
- body: formData,
- headers: HEADERS,
- method: 'POST',
- });
-
- const res = await response.json();
-
- if (res.code !== 0) {
- message.error(`涓婁紶鍑洪敊锛�${res.msg}`);
- onError?.(new Error(res.msg));
- return;
- }
-
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- message.success('涓婁紶鎴愬姛');
- onSuccess?.(res);
- emit('uploaded');
- } catch (error: any) {
- message.error(`涓婁紶澶辫触: ${error.message}`);
- onError?.(error);
- }
-};
-</script>
-
-<template>
- <Upload
- :action="UPLOAD_URL"
- :before-upload="onBeforeUpload"
- :custom-request="customRequest"
- :file-list="fileList"
- :headers="HEADERS"
- :multiple="true"
- >
- <Button type="primary">
- <IconifyIcon icon="lucide:upload" class="mr-1" />
- 鐐瑰嚮涓婁紶
- </Button>
- <template #itemRender="{ file, actions }">
- <div class="flex items-center">
- <span>{{ file.name }}</span>
- <Button type="link" size="small" @click="actions.remove"> 鍒犻櫎 </Button>
- </div>
- </template>
- </Upload>
- <div v-if="$slots.default" class="ml-1 text-sm text-gray-500">
- <slot></slot>
- </div>
-</template>
diff --git a/src/views/mp/material/modules/UploadVideo.vue b/src/views/mp/material/modules/UploadVideo.vue
deleted file mode 100644
index fe78dc2..0000000
--- a/src/views/mp/material/modules/UploadVideo.vue
+++ /dev/null
@@ -1,160 +0,0 @@
-<script lang="ts" setup>
-import type { FormInstance, UploadProps } from 'ant-design-vue';
-
-import type { UploadData } from './upload';
-
-import { inject, reactive, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import {
- Button,
- Divider,
- Form,
- Input,
- message,
- Modal,
- Upload,
-} from 'ant-design-vue';
-
-import { beforeVideoUpload, HEADERS, UPLOAD_URL, UploadType } from './upload';
-
-withDefaults(
- defineProps<{
- open?: boolean;
- }>(),
- {
- open: false,
- },
-);
-
-const emit = defineEmits<{
- 'update:open': [v: boolean];
- uploaded: [v: void];
-}>();
-
-const accountId = inject<number>('accountId');
-
-const uploadRules = {
- introduction: [
- { message: '璇疯緭鍏ユ弿杩�', required: true, trigger: 'blur' } as const,
- ],
- title: [{ message: '璇疯緭鍏ユ爣棰�', required: true, trigger: 'blur' } as const],
-};
-
-function handleCancel() {
- emit('update:open', false);
-}
-
-const fileList = ref<any[]>([]);
-
-const uploadData: UploadData = reactive({
- accountId: accountId!,
- introduction: '',
- title: '',
- type: UploadType.Video,
-});
-
-const uploadFormRef = ref<FormInstance | null>(null);
-const uploadVideoRef = ref<any>(null);
-
-async function submitVideo() {
- await uploadFormRef.value?.validate();
- uploadVideoRef.value?.submit();
-}
-
-/** 鑷畾涔変笂浼� */
-const customRequest: UploadProps['customRequest'] = async function (options) {
- const { file, onError, onSuccess } = options;
-
- const formData = new FormData();
- formData.append('file', file as File);
- formData.append('type', uploadData.type);
- formData.append('title', uploadData.title);
- formData.append('introduction', uploadData.introduction);
- formData.append('accountId', String(uploadData.accountId));
-
- try {
- const response = await fetch(UPLOAD_URL, {
- body: formData,
- headers: HEADERS,
- method: 'POST',
- });
-
- const res = await response.json();
-
- if (res.code !== 0) {
- message.error(`涓婁紶鍑洪敊锛�${res.msg}`);
- onError?.(new Error(res.msg));
- return;
- }
-
- // 娓呯┖涓婁紶鏃剁殑鍚勭鏁版嵁
- fileList.value = [];
- uploadData.title = '';
- uploadData.introduction = '';
-
- emit('update:open', false);
- message.success('涓婁紶鎴愬姛');
- onSuccess?.(res);
- emit('uploaded');
- } catch (error: any) {
- message.error(`涓婁紶澶辫触: ${error.message}`);
- onError?.(error);
- }
-};
-</script>
-
-<template>
- <Modal
- :open="open"
- title="鏂板缓瑙嗛"
- width="600px"
- @cancel="handleCancel"
- @ok="submitVideo"
- >
- <Upload
- ref="uploadVideoRef"
- :action="UPLOAD_URL"
- :auto-upload="false"
- :before-upload="beforeVideoUpload"
- :custom-request="customRequest"
- :file-list="fileList"
- :headers="HEADERS"
- :limit="1"
- :multiple="true"
- class="mb-4"
- >
- <Button type="primary">
- <IconifyIcon icon="lucide:video" class="mr-1" />
- 閫夋嫨瑙嗛
- </Button>
- </Upload>
- <div class="mb-4 ml-1 text-sm text-gray-500">
- 鏍煎紡鏀寔 MP4锛屾枃浠跺ぇ灏忎笉瓒呰繃 10MB
- </div>
-
- <Divider />
-
- <Form
- ref="uploadFormRef"
- :model="uploadData"
- :rules="uploadRules"
- layout="vertical"
- >
- <Form.Item label="鏍囬" name="title">
- <Input
- v-model:value="uploadData.title"
- placeholder="鏍囬灏嗗睍绀哄湪鐩稿叧鎾斁椤甸潰锛屽缓璁~鍐欐竻鏅般�佸噯纭�佺敓鍔ㄧ殑鏍囬"
- />
- </Form.Item>
- <Form.Item label="鎻忚堪" name="introduction">
- <Input.TextArea
- v-model:value="uploadData.introduction"
- :rows="3"
- placeholder="浠嬬粛璇皢灞曠ず鍦ㄧ浉鍏虫挱鏀鹃〉闈紝寤鸿濉啓绠�娲佹槑纭�佹湁淇℃伅閲忕殑鍐呭"
- />
- </Form.Item>
- </Form>
- </Modal>
-</template>
diff --git a/src/views/mp/material/modules/data.ts b/src/views/mp/material/modules/data.ts
deleted file mode 100644
index cd3e07a..0000000
--- a/src/views/mp/material/modules/data.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMaterialApi } from '#/api/mp/material';
-
-/** 瑙嗛琛ㄦ牸鍒楅厤缃� */
-export function useVideoGridColumns(): VxeTableGridOptions<MpMaterialApi.Material>['columns'] {
- return [
- {
- field: 'mediaId',
- title: '缂栧彿',
- align: 'center',
- width: 160,
- },
- {
- field: 'name',
- title: '鏂囦欢鍚�',
- align: 'center',
- minWidth: 100,
- },
- {
- field: 'title',
- title: '鏍囬',
- align: 'center',
- minWidth: 200,
- },
- {
- field: 'introduction',
- title: '浠嬬粛',
- align: 'center',
- minWidth: 220,
- },
- {
- field: 'video',
- title: '瑙嗛',
- align: 'center',
- width: 220,
- slots: { default: 'video' },
- },
- {
- field: 'createTime',
- title: '涓婁紶鏃堕棿',
- align: 'center',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- align: 'center',
- fixed: 'right',
- width: 180,
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璇煶琛ㄦ牸鍒楅厤缃� */
-export function useVoiceGridColumns(): VxeTableGridOptions<MpMaterialApi.Material>['columns'] {
- return [
- {
- field: 'mediaId',
- title: '缂栧彿',
- align: 'center',
- width: 160,
- },
- {
- field: 'name',
- title: '鏂囦欢鍚�',
- align: 'center',
- minWidth: 100,
- },
- {
- field: 'voice',
- title: '璇煶',
- align: 'center',
- width: 220,
- slots: { default: 'voice' },
- },
- {
- field: 'createTime',
- title: '涓婁紶鏃堕棿',
- align: 'center',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- align: 'center',
- fixed: 'right',
- width: 160,
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍥剧墖琛ㄦ牸鍒楅厤缃� */
-export function useImageGridColumns(): VxeTableGridOptions<MpMaterialApi.Material>['columns'] {
- return [
- {
- field: 'mediaId',
- title: '缂栧彿',
- align: 'center',
- width: 400,
- },
- {
- field: 'name',
- title: '鏂囦欢鍚�',
- align: 'center',
- width: 200,
- },
- {
- field: 'url',
- title: '鍥剧墖',
- align: 'center',
- width: 200,
- slots: { default: 'image' },
- },
- {
- field: 'createTime',
- title: '涓婁紶鏃堕棿',
- align: 'center',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- align: 'center',
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- ];
-}
diff --git a/src/views/mp/material/modules/upload.ts b/src/views/mp/material/modules/upload.ts
deleted file mode 100644
index 668feed..0000000
--- a/src/views/mp/material/modules/upload.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import type { UploadProps } from 'ant-design-vue';
-
-import type { UploadRawFile } from '#/views/mp/hooks/useUpload';
-
-import { useAccessStore } from '../../../../packages/stores/src';
-
-import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
-
-const accessStore = useAccessStore();
-const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` }; // 璇锋眰澶�
-const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-permanent`; // 涓婁紶鍦板潃
-
-interface UploadData {
- accountId: number;
- introduction: string;
- title: string;
- type: UploadType;
-}
-
-const beforeImageUpload: UploadProps['beforeUpload'] = function (
- rawFile: UploadRawFile,
-) {
- return useBeforeUpload(UploadType.Image, 2)(rawFile);
-};
-
-const beforeVoiceUpload: UploadProps['beforeUpload'] = function (
- rawFile: UploadRawFile,
-) {
- return useBeforeUpload(UploadType.Voice, 2)(rawFile);
-};
-
-const beforeVideoUpload: UploadProps['beforeUpload'] = function (
- rawFile: UploadRawFile,
-) {
- return useBeforeUpload(UploadType.Video, 10)(rawFile);
-};
-
-export {
- beforeImageUpload,
- beforeVideoUpload,
- beforeVoiceUpload,
- HEADERS,
- UPLOAD_URL,
- type UploadData,
- UploadType,
-};
diff --git a/src/views/mp/menu/assets/iphone_backImg.png b/src/views/mp/menu/assets/iphone_backImg.png
deleted file mode 100644
index bb09591..0000000
--- a/src/views/mp/menu/assets/iphone_backImg.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mp/menu/assets/menu_foot.png b/src/views/mp/menu/assets/menu_foot.png
deleted file mode 100644
index 4a89d4b..0000000
--- a/src/views/mp/menu/assets/menu_foot.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mp/menu/assets/menu_head.png b/src/views/mp/menu/assets/menu_head.png
deleted file mode 100644
index 248cfb7..0000000
--- a/src/views/mp/menu/assets/menu_head.png
+++ /dev/null
Binary files differ
diff --git a/src/views/mp/menu/data.ts b/src/views/mp/menu/data.ts
deleted file mode 100644
index ddb2bea..0000000
--- a/src/views/mp/menu/data.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { getSimpleAccountList } from '#/api/mp/account';
-
-/** 鑿滃崟鏈�変腑鏍囪瘑 */
-export const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__';
-
-/** 鑿滃崟绾у埆鏋氫妇 */
-export enum Level {
- Child = '2',
- Parent = '1',
- Undefined = '0',
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleAccountList,
- labelField: 'name',
- valueField: 'id',
- autoSelect: 'first',
- placeholder: '璇烽�夋嫨鍏紬鍙�',
- },
- },
- ];
-}
diff --git a/src/views/mp/menu/index.vue b/src/views/mp/menu/index.vue
deleted file mode 100644
index 63214a9..0000000
--- a/src/views/mp/menu/index.vue
+++ /dev/null
@@ -1,371 +0,0 @@
-<script lang="ts" setup>
-import type { Menu, RawMenu } from './modules/types';
-
-import { nextTick, onMounted, ref } from 'vue';
-
-import { confirm, ContentWrap, DocAlert, Page } from '../../../packages/effects/common-ui/src';
-import { handleTree } from '../../../packages/utils/src';
-
-import { Button, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getSimpleAccountList } from '#/api/mp/account';
-import { deleteMenu, getMenuList, saveMenu } from '#/api/mp/menu';
-import {
- Level,
- MENU_NOT_SELECTED,
- useGridFormSchema,
-} from '#/views/mp/menu/data';
-import Editor from '#/views/mp/menu/modules/editor.vue';
-import Previewer from '#/views/mp/menu/modules/previewer.vue';
-
-import iphoneBackImg from './assets/iphone_backImg.png';
-import menuFootImg from './assets/menu_foot.png';
-import menuHeadImg from './assets/menu_head.png';
-
-defineOptions({ name: 'MpMenu' });
-
-// ======================== 鍒楄〃鏌ヨ ========================
-
-const loading = ref(false); // 閬僵灞�
-const accountId = ref(-1);
-const accountName = ref<string>('');
-const menuList = ref<Menu[]>([]);
-
-const [AccountForm, accountFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-[240px]',
- },
- },
- layout: 'horizontal',
- schema: useGridFormSchema(),
- wrapperClass: 'grid-cols-1',
- showDefaultActions: false,
- handleValuesChange: async (values, changedFields) => {
- // 褰� accountId 瀛楁鍙樺寲鏃讹紙鍖呮嫭 autoSelect 鑷姩閫夋嫨锛夛紝鍚屾鏇存柊 accountId
- if (changedFields.includes('accountId') && values.accountId) {
- await onAccountChanged(values);
- }
- },
-});
-
-// ======================== 鑿滃崟鎿嶄綔 ========================
-
-// 褰撳墠閫変腑鑿滃崟缂栫爜锛�
-// * 涓�绾э紙'x'锛�
-// * 浜岀骇锛�'x-y'锛�
-// * 鏈�変腑锛圡ENU_NOT_SELECTED锛�
-const activeIndex = ref<string>(MENU_NOT_SELECTED);
-// 浜岀骇鑿滃崟鏄剧ず鏍囧織: 褰掑睘鐨勪竴绾ц彍鍗昳ndex
-// * 鏈垵濮嬪寲锛�-1
-// * 鍒濆鍖栵細x
-const parentIndex = ref(-1);
-
-// ======================== 鑿滃崟缂栬緫 ========================
-
-const showRightPanel = ref(false); // 鍙宠竟閰嶇疆鏄剧ず榛樿璇︽儏杩樻槸閰嶇疆璇︽儏
-const isParent = ref<boolean>(true); // 鏄惁涓�绾ц彍鍗曪紝鎺у埗Editor涓璶ame瀛楁闀垮害
-const activeMenu = ref<Menu>({}); // 閫変腑鑿滃崟锛孍ditor鐨刴odelValue
-
-// 涓�浜涗复鏃跺�兼斁鍦ㄨ繖閲岃繘琛屽垽鏂紝濡傛灉鏀惧湪 activeMenu锛岀敱浜庡紩鐢ㄥ叧绯伙紝menu 涔熶細澶氫簡澶氫綑鐨勫弬鏁�
-const tempSelfObj = ref<{
- grand: Level;
- x: number;
- y: number;
-}>({
- grand: Level.Undefined,
- x: 0,
- y: 0,
-});
-const dialogNewsVisible = ref(false); // 璺宠浆鍥炬枃鏃剁殑绱犳潗閫夋嫨寮圭獥
-
-/** 渚﹀惉鍏紬鍙峰彉鍖� */
-async function onAccountChanged(values: Record<string, any>) {
- accountId.value = values.accountId;
- // 浠� API 鑾峰彇鍏紬鍙峰垪琛ㄥ苟鏌ユ壘瀵瑰簲鐨勫叕浼楀彿鍚嶇О
- const accountList = await getSimpleAccountList();
- const account = accountList.find((item) => item.id === values.accountId);
- accountName.value = account?.name || '';
- await getList();
-}
-
-/** 鍒濆鍖栬处鍙稩D - 浣滀负澶囩敤鏂规锛岄槻姝� handleValuesChange 鏈Е鍙� */
-async function initAccountId() {
- await nextTick(); // 绛夊緟琛ㄥ崟鍒濆鍖栧畬鎴�
- const values = await accountFormApi.getValues();
- if (values?.accountId && accountId.value === -1) {
- // 濡傛灉琛ㄥ崟鏈夊�间絾 accountId 杩樻槸鍒濆鍊硷紝鍒欐墜鍔ㄨЕ鍙戜竴娆�
- await onAccountChanged(values);
- }
-}
-
-/** 缁勪欢鎸傝浇鏃跺垵濮嬪寲璐﹀彿 ID */
-onMounted(async () => {
- await nextTick();
- await initAccountId();
-});
-
-/** 鏌ヨ骞惰浆鎹㈣彍鍗� */
-async function getList() {
- loading.value = true;
- try {
- const data = await getMenuList(accountId.value);
- const menuData = menuListToFrontend(data);
- menuList.value = handleTree(menuData, 'id') as Menu[];
- } finally {
- loading.value = false;
- }
-}
-
-/** 鎼滅储鎸夐挳鎿嶄綔 */
-function handleQuery() {
- resetForm();
- getList();
-}
-
-/** 灏嗗悗绔繑鍥炵殑 menuList锛岃浆鎹㈡垚鍓嶇鐨� menuList */
-function menuListToFrontend(list: any[]) {
- if (!list) {
- return [];
- }
-
- const result: RawMenu[] = [];
- list.forEach((item: RawMenu) => {
- const menu: any = {
- ...item,
- reply: {
- type: item.replyMessageType,
- accountId: item.accountId,
- content: item.replyContent,
- mediaId: item.replyMediaId,
- url: item.replyMediaUrl,
- title: item.replyTitle,
- description: item.replyDescription,
- thumbMediaId: item.replyThumbMediaId,
- thumbMediaUrl: item.replyThumbMediaUrl,
- articles: item.replyArticles,
- musicUrl: item.replyMusicUrl,
- hqMusicUrl: item.replyHqMusicUrl,
- },
- };
- result.push(menu as RawMenu);
- });
- return result;
-}
-
-/** 閲嶇疆琛ㄥ崟锛屾竻绌鸿〃鍗曟暟鎹� */
-function resetForm() {
- // 鑿滃崟鎿嶄綔
- activeIndex.value = MENU_NOT_SELECTED;
- parentIndex.value = -1;
-
- // 鑿滃崟缂栬緫
- showRightPanel.value = false;
- activeMenu.value = {};
- tempSelfObj.value = { grand: Level.Undefined, x: 0, y: 0 };
- dialogNewsVisible.value = false;
-}
-
-// ======================== 鑿滃崟鎿嶄綔 ========================
-
-/** 涓�绾ц彍鍗曠偣鍑讳簨浠� */
-function menuClicked(parent: Menu, x: number) {
- // 鍙充晶鐨勮〃鍗曠浉鍏�
- showRightPanel.value = true; // 鍙宠竟鑿滃崟
- activeMenu.value = parent; // 杩欎釜濡傛灉鏀惧湪椤堕儴锛宖lag 浼氭病鏈夈�傚洜涓洪噸鏂拌祴鍊间簡銆�
- tempSelfObj.value.grand = Level.Parent; // 琛ㄧず涓�绾ц彍鍗�
- tempSelfObj.value.x = x; // 琛ㄧず涓�绾ц彍鍗曠储寮�
- isParent.value = true;
-
- // 宸︿晶鐨勯�変腑
- activeIndex.value = `${x}`; // 鑿滃崟閫変腑鏍峰紡
- parentIndex.value = x; // 浜岀骇鑿滃崟鏄剧ず鏍囧織
-}
-
-/** 浜岀骇鑿滃崟鐐瑰嚮浜嬩欢 */
-function subMenuClicked(child: Menu, x: number, y: number) {
- // 鍙充晶鐨勮〃鍗曠浉鍏�
- showRightPanel.value = true; // 鍙宠竟鑿滃崟
- activeMenu.value = child; // 灏嗙偣鍑荤殑鏁版嵁鏀惧埌涓存椂鍙橀噺锛屽璞℃湁寮曠敤浣滅敤
- tempSelfObj.value.grand = Level.Child; // 琛ㄧず浜岀骇鑿滃崟
- tempSelfObj.value.x = x; // 琛ㄧず涓�绾ц彍鍗曠储寮�
- tempSelfObj.value.y = y; // 琛ㄧず浜岀骇鑿滃崟绱㈠紩
- isParent.value = false;
-
- // 宸︿晶鐨勯�変腑
- activeIndex.value = `${x}-${y}`;
-}
-
-/** 鍒犻櫎褰撳墠鑿滃崟 */
-async function onDeleteMenu() {
- await confirm('纭畾瑕佸垹闄ゅ悧?');
- if (tempSelfObj.value.grand === Level.Parent) {
- // 涓�绾ц彍鍗曠殑鍒犻櫎鏂规硶
- menuList.value.splice(tempSelfObj.value.x, 1);
- } else if (tempSelfObj.value.grand === Level.Child) {
- // 浜岀骇鑿滃崟鐨勫垹闄ゆ柟娉�
- menuList.value[tempSelfObj.value.x]?.children?.splice(
- tempSelfObj.value.y,
- 1,
- );
- }
- // 鎻愮ず
- message.success('鍒犻櫎鎴愬姛');
-
- // 澶勭悊鑿滃崟鐨勯�変腑
- activeMenu.value = {};
- showRightPanel.value = false;
- activeIndex.value = MENU_NOT_SELECTED;
-}
-
-// ======================== 鑿滃崟缂栬緫 ========================
-
-/** 淇濆瓨鑿滃崟 */
-async function onSave() {
- await confirm('纭畾瑕佷繚瀛樺悧?');
- const hideLoading = message.loading({
- content: '淇濆瓨涓�...',
- duration: 0,
- });
- try {
- await saveMenu(accountId.value, menuListToBackend());
- await getList();
- message.success('鍙戝竷鎴愬姛');
- } finally {
- hideLoading();
- }
-}
-
-/** 娓呯┖鑿滃崟 */
-async function onClear() {
- await confirm('纭畾瑕佸垹闄ゅ悧?');
- const hideLoading = message.loading({
- content: '鍒犻櫎涓�...',
- duration: 0,
- });
- try {
- await deleteMenu(accountId.value);
- handleQuery();
- message.success('娓呯┖鎴愬姛');
- } finally {
- hideLoading();
- }
-}
-
-/** 灏嗗墠绔殑 menuList锛岃浆鎹㈡垚鍚庣鎺ユ敹鐨� menuList */
-function menuListToBackend() {
- const result: any[] = [];
- menuList.value.forEach((item) => {
- const menu = menuToBackend(item);
- result.push(menu);
-
- // 澶勭悊瀛愯彍鍗�
- if (!item.children || item.children.length <= 0) {
- return;
- }
- menu.children = [];
- item.children.forEach((subItem) => {
- menu.children.push(menuToBackend(subItem));
- });
- });
- return result;
-}
-
-/** 灏嗗墠绔殑 menu锛岃浆鎹㈡垚鍚庣鎺ユ敹鐨� menu */
-// TODO: @鑺嬭壙锛岄渶瑕佹牴鎹悗鍙� API 鍒犻櫎涓嶉渶瑕佺殑瀛楁
-function menuToBackend(menu: any) {
- return {
- ...menu,
- children: undefined, // 涓嶅鐞嗗瓙鑺傜偣
- reply: undefined, // 绋嶅悗澶嶅埗
- replyMessageType: menu.reply.type,
- replyContent: menu.reply.content,
- replyMediaId: menu.reply.mediaId,
- replyMediaUrl: menu.reply.url,
- replyTitle: menu.reply.title,
- replyDescription: menu.reply.description,
- replyThumbMediaId: menu.reply.thumbMediaId,
- replyThumbMediaUrl: menu.reply.thumbMediaUrl,
- replyArticles: menu.reply.articles,
- replyMusicUrl: menu.reply.musicUrl,
- replyHqMusicUrl: menu.reply.hqMusicUrl,
- };
-}
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍏紬鍙疯彍鍗�" url="https://doc.iocoder.cn/mp/menu/" />
- </template>
-
- <!-- 鎼滅储宸ヤ綔鏍� -->
- <AccountForm class="-mb-15px w-240px" @values-change="onAccountChanged" />
-
- <ContentWrap>
- <div
- class="mx-auto w-[1200px] after:clear-both after:table after:content-['']"
- v-loading="loading"
- >
- <!--宸﹁竟閰嶇疆鑿滃崟-->
- <div
- class="relative float-left box-border block h-[715px] w-[350px] bg-[length:100%_auto] bg-no-repeat p-[518px_25px_88px]"
- :style="{ backgroundImage: `url(${iphoneBackImg})` }"
- >
- <div
- class="relative bottom-[426px] left-0 h-[64px] w-[300px] bg-[length:100%] bg-[position:0_0] bg-no-repeat text-center text-white"
- :style="{ backgroundImage: `url(${menuHeadImg})` }"
- >
- <div
- class="absolute left-0 top-[33px] w-full text-center text-sm text-white"
- >
- {{ accountName }}
- </div>
- </div>
- <div
- class="bg-[position:0_0] bg-no-repeat pl-[43px] text-xs after:clear-both after:table after:content-['']"
- :style="{ backgroundImage: `url(${menuFootImg})` }"
- >
- <Previewer
- v-model="menuList"
- :account-id="accountId"
- :active-index="activeIndex"
- :parent-index="parentIndex"
- @menu-clicked="(parent, x) => menuClicked(parent, x)"
- @submenu-clicked="(child, x, y) => subMenuClicked(child, x, y)"
- />
- </div>
- <div class="mt-[15px] flex items-center justify-center gap-[10px]">
- <Button
- type="primary"
- @click="onSave"
- v-access:code="['mp:menu:save']"
- >
- 淇濆瓨骞跺彂甯冭彍鍗�
- </Button>
- <Button danger @click="onClear" v-access:code="['mp:menu:delete']">
- 娓呯┖鑿滃崟
- </Button>
- </div>
- </div>
- <!--鍙宠竟閰嶇疆-->
- <div
- class="float-left ml-5 box-border w-[63%] bg-[#e8e7e7] p-5"
- v-if="showRightPanel"
- >
- <Editor
- :account-id="accountId"
- :is-parent="isParent"
- v-model="activeMenu"
- @delete="onDeleteMenu"
- />
- </div>
- <!-- 涓�杩涢〉闈㈠氨鏄剧ず鐨勯粯璁ら〉闈紝褰撶偣鍑诲乏杈规寜閽殑鏃跺�欙紝灏变笉鏄剧ず浜�-->
- <div v-else class="float-left ml-5 box-border w-[63%] bg-[#e8e7e7] p-5">
- <p>璇烽�夋嫨鑿滃崟閰嶇疆</p>
- </div>
- </div>
- </ContentWrap>
- </Page>
-</template>
diff --git a/src/views/mp/menu/modules/editor.vue b/src/views/mp/menu/modules/editor.vue
deleted file mode 100644
index 9ae4f2b..0000000
--- a/src/views/mp/menu/modules/editor.vue
+++ /dev/null
@@ -1,226 +0,0 @@
-<script lang="ts" setup>
-import { computed, ref } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import {
- Button,
- Col,
- Input,
- message,
- Modal,
- Row,
- Select,
-} from 'ant-design-vue';
-
-import { WxMaterialSelect, WxNews, WxReply } from '#/views/mp/components';
-
-import { menuOptions } from './types';
-
-const props = defineProps<{
- accountId: number;
- isParent: boolean;
- modelValue: any;
-}>();
-
-const emit = defineEmits<{
- (e: 'delete', v: void): void;
- (e: 'update:modelValue', v: any): void;
-}>();
-
-const menu = computed({
- get() {
- return props.modelValue;
- },
- set(val) {
- emit('update:modelValue', val);
- },
-});
-const showNewsDialog = ref(false);
-const isLeave = computed<boolean>(() => !(menu.value.children?.length > 0));
-
-// ======================== 鑿滃崟缂栬緫锛堢礌鏉愰�夋嫨锛� ========================
-
-/** 閫夋嫨绱犳潗 */
-function selectMaterial(item: any) {
- const articleId = item.articleId;
- const articles = item.content.newsItem;
- // 鎻愮ず锛岄拡瀵瑰鍥炬枃
- if (articles.length > 1) {
- message.warning('鎮ㄩ�夋嫨鐨勬槸澶氬浘鏂囷紝灏嗛粯璁よ烦杞涓�绡�');
- }
- showNewsDialog.value = false;
-
- // 璁剧疆鑿滃崟鐨勫洖澶�
- menu.value.articleId = articleId;
- menu.value.replyArticles = [];
- articles.forEach((article: any) => {
- menu.value.replyArticles.push({
- title: article.title,
- description: article.digest,
- picUrl: article.picUrl,
- url: article.url,
- });
- });
-}
-
-/** 鍒犻櫎绱犳潗 */
-function deleteMaterial() {
- delete menu.value.articleId;
- delete menu.value.replyArticles;
-}
-</script>
-
-<template>
- <div>
- <div>
- <div class="mb-[15px] text-right">
- <Button type="primary" danger @click="emit('delete')">
- <IconifyIcon icon="lucide:trash-2" />
- 鍒犻櫎褰撳墠鑿滃崟
- </Button>
- </div>
- <div>
- <span>鑿滃崟鍚嶇О锛�</span>
- <Input
- class="mr-[2%] w-[240px]"
- v-model:value="menu.name"
- placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�"
- :maxlength="isParent ? 4 : 7"
- allow-clear
- />
- </div>
- <div v-if="isLeave">
- <div class="mt-5">
- <span>鑿滃崟鏍囪瘑锛�</span>
- <Input
- class="mr-[2%] w-[240px]"
- v-model:value="menu.menuKey"
- placeholder="璇疯緭鍏ヨ彍鍗� KEY"
- allow-clear
- />
- </div>
- <div class="mt-5">
- <span>鑿滃崟鍐呭锛�</span>
- <Select
- v-model:value="menu.type"
- placeholder="璇烽�夋嫨"
- class="mr-[2%] w-[240px]"
- allow-clear
- >
- <Select.Option
- v-for="item in menuOptions"
- :label="item.label"
- :value="item.value"
- :key="item.value"
- >
- {{ item.label }}
- </Select.Option>
- </Select>
- </div>
- <div
- class="mt-5 rounded-[5px] bg-white p-[20px_10px]"
- v-if="menu.type === 'view'"
- >
- <span>璺宠浆閾炬帴锛�</span>
- <Input
- class="mr-[2%] w-[240px]"
- v-model:value="menu.url"
- placeholder="璇疯緭鍏ラ摼鎺�"
- allow-clear
- />
- </div>
- <div
- class="mt-5 rounded-[5px] bg-white p-[20px_10px]"
- v-if="menu.type === 'miniprogram'"
- >
- <div class="mb-5 flex items-center">
- <div class="w-[20%]">灏忕▼搴忕殑 appid 锛�</div>
- <Input
- class="mr-[2%] flex-1"
- v-model:value="menu.miniProgramAppId"
- placeholder="璇疯緭鍏ュ皬绋嬪簭鐨刟ppid"
- allow-clear
- />
- </div>
- <div class="mb-5 flex items-center">
- <div class="w-[20%]">灏忕▼搴忕殑椤甸潰璺緞锛�</div>
- <Input
- class="mr-[2%] flex-1"
- v-model:value="menu.miniProgramPagePath"
- placeholder="璇疯緭鍏ュ皬绋嬪簭鐨勯〉闈㈣矾寰勶紝濡傦細pages/index"
- allow-clear
- />
- </div>
- <div class="mb-5 flex items-center">
- <div class="w-[20%]">灏忕▼搴忕殑澶囩敤缃戦〉锛�</div>
- <Input
- class="mr-[2%] flex-1"
- v-model:value="menu.url"
- placeholder="涓嶆敮鎸佸皬绋嬪簭鐨勮�佺増鏈鎴风灏嗘墦寮�鏈綉椤�"
- allow-clear
- />
- </div>
- <p class="mt-[10px] text-[#29b6f6]">
- tips:闇�瑕佸拰鍏紬鍙疯繘琛屽叧鑱旀墠鍙互鎶婂皬绋嬪簭缁戝畾甯﹀井淇¤彍鍗曚笂鍝燂紒
- </p>
- </div>
- <div
- class="mt-5 rounded-[5px] bg-white p-[20px_10px]"
- v-if="menu.type === 'article_view_limited'"
- >
- <Row>
- <div
- class="mx-auto mb-[10px] w-[280px] border border-[#eaeaea] p-[10px]"
- v-if="menu && menu.replyArticles"
- >
- <WxNews :articles="menu.replyArticles" />
- <Row
- class="pt-[10px] text-center"
- justify="center"
- align="middle"
- >
- <Button
- type="primary"
- danger
- shape="circle"
- @click="deleteMaterial"
- >
- <IconifyIcon icon="lucide:trash-2" />
- </Button>
- </Row>
- </div>
- <div v-else>
- <Row justify="center">
- <Col :span="24" class="text-center">
- <Button type="primary" @click="showNewsDialog = true">
- 绱犳潗搴撻�夋嫨
- <IconifyIcon icon="lucide:circle-check" />
- </Button>
- </Col>
- </Row>
- </div>
- <Modal
- title="閫夋嫨鍥炬枃"
- v-model:open="showNewsDialog"
- width="80%"
- destroy-on-close
- >
- <WxMaterialSelect
- type="news"
- :account-id="props.accountId"
- @select-material="selectMaterial"
- />
- </Modal>
- </Row>
- </div>
- <div
- class="configur-content mt-5"
- v-if="menu.type === 'click' || menu.type === 'scancode_waitmsg'"
- >
- <WxReply v-model="menu.reply" />
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/src/views/mp/menu/modules/previewer.vue b/src/views/mp/menu/modules/previewer.vue
deleted file mode 100644
index 528fd79..0000000
--- a/src/views/mp/menu/modules/previewer.vue
+++ /dev/null
@@ -1,202 +0,0 @@
-<script lang="ts" setup>
-import type { Menu } from './types';
-
-import { computed } from 'vue';
-
-import { IconifyIcon } from '../../../../packages/icons/src';
-
-import draggable from 'vuedraggable';
-
-const props = defineProps<{
- accountId: number;
- activeIndex: string;
- modelValue: Menu[];
- parentIndex: number;
-}>();
-
-const emit = defineEmits<{
- (e: 'update:modelValue', v: Menu[]): void;
- (e: 'menuClicked', parent: Menu, x: number): void;
- (e: 'submenuClicked', child: Menu, x: number, y: number): void;
-}>();
-
-const menuList = computed<Menu[]>({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
-});
-
-/** 娣诲姞妯悜涓�绾ц彍鍗� */
-function addMenu() {
- const index = menuList.value.length;
- const menu = {
- name: '鑿滃崟鍚嶇О',
- children: [],
- reply: {
- // 鐢ㄤ簬瀛樺偍鍥炲鍐呭
- type: 'text',
- accountId: props.accountId, // 淇濊瘉缁勪欢閲岋紝鍙互浣跨敤鍒板搴旂殑鍏紬鍙�
- },
- };
- menuList.value[index] = menu;
- menuClicked(menu, index - 1);
-}
-
-/** 娣诲姞妯悜浜岀骇鑿滃崟锛沺arent 琛ㄧず瑕佹搷浣滅殑鐖惰彍鍗� */
-function addSubMenu(i: number, parent: any) {
- parent.children[parent.children.length] = {
- name: '瀛愯彍鍗曞悕绉�',
- reply: {
- // 鐢ㄤ簬瀛樺偍鍥炲鍐呭
- type: 'text',
- accountId: props.accountId, // 淇濊瘉缁勪欢閲岋紝鍙互浣跨敤鍒板搴旂殑鍏紬鍙�
- },
- };
- subMenuClicked(
- parent.children[parent.children.length - 1],
- i,
- parent.children.length - 1,
- );
-}
-
-/** 涓�绾ц彍鍗曠偣鍑� */
-function menuClicked(parent: Menu, x: number) {
- emit('menuClicked', parent, x);
-}
-
-/** 浜岀骇鑿滃崟鐐瑰嚮 */
-function subMenuClicked(child: Menu, x: number, y: number) {
- emit('submenuClicked', child, x, y);
-}
-
-/**
- * 澶勭悊涓�绾ц彍鍗曞睍寮�鍚庤鎷栧姩锛屾縺娲�(灞曞紑)鍘熸潵娲诲姩鐨勪竴绾ц彍鍗�
- *
- * @param options - 鎷栧姩鍙傛暟瀵硅薄
- * @param options.oldIndex - 涓�绾ц彍鍗曟嫋鍔ㄥ墠鐨勪綅缃�
- * @param options.newIndex - 涓�绾ц彍鍗曟嫋鍔ㄥ悗鐨勪綅缃�
- */
-function onParentDragEnd({
- oldIndex,
- newIndex,
-}: {
- newIndex: number;
- oldIndex: number;
-}) {
- // 浜岀骇鑿滃崟娌℃湁灞曞紑锛岀洿鎺ヨ繑鍥�
- if (props.activeIndex === '__MENU_NOT_SELECTED__') {
- return;
- }
-
- // 浣跨敤涓�涓緟鍔╂暟缁勬潵妯℃嫙鑿滃崟绉诲姩锛岀劧鍚庢壘鍒板睍寮�鐨勪簩绾ц彍鍗曠殑鏂颁笅鏍嘸newParent`
- const positions = Array.from({ length: menuList.value.length }).fill(false);
- positions[props.parentIndex] = true;
- const [out] = positions.splice(oldIndex, 1); // 绉诲嚭鑿滃崟锛屼繚瀛樺埌鍙橀噺out
- positions.splice(newIndex, 0, out ?? false); // 鎶妎ut鍙橀噺鎻掑叆琚Щ鍑虹殑鑿滃崟
- const newParentIndex = positions.indexOf(true);
-
- // 鎵惧埌鑿滃崟鍏冪礌锛岃Е鍙戜竴绾ц彍鍗曠偣鍑�
- const parent = menuList.value[newParentIndex];
- if (parent && newParentIndex !== -1) {
- emit('menuClicked', parent, newParentIndex);
- }
-}
-
-/**
- * 澶勭悊浜岀骇鑿滃崟灞曞紑鍚庤鎷栧姩锛屾縺娲昏鎷栧姩鐨勮彍鍗�
- *
- * @param options - 鎷栧姩鍙傛暟瀵硅薄
- * @param options.newIndex - 浜岀骇鑿滃崟鎷栧姩鍚庣殑浣嶇疆
- */
-function onChildDragEnd({ newIndex }: { newIndex: number }) {
- const x = props.parentIndex;
- const y = newIndex;
- const children = menuList.value[x]?.children;
- if (children && children?.length > 0) {
- const child = children[y];
- if (child) {
- emit('submenuClicked', child, x, y);
- }
- }
-}
-</script>
-
-<template>
- <draggable
- v-model="menuList"
- item-key="id"
- ghost-class="draggable-ghost"
- :animation="400"
- @end="onParentDragEnd"
- >
- <template #item="{ element: parent, index: x }">
- <div
- class="relative float-left box-border block w-[85.5px] cursor-pointer border border-[#ebedee] bg-white text-center"
- >
- <!-- 涓�绾ц彍鍗� -->
- <div
- @click="menuClicked(parent, x)"
- class="box-border flex h-[44px] w-full items-center justify-center leading-[44px]"
- :class="{ 'border border-[#2bb673]': props.activeIndex === `${x}` }"
- >
- <IconifyIcon icon="lucide:panel-right-open" color="black" />
- {{ parent.name }}
- </div>
- <!-- 浠ヤ笅涓轰簩绾ц彍鍗�-->
- <div
- class="absolute bottom-[45px] left-0 w-[85.5px]"
- v-if="props.parentIndex === x && parent.children"
- >
- <draggable
- v-model="parent.children"
- item-key="id"
- ghost-class="draggable-ghost"
- :animation="400"
- @end="onChildDragEnd"
- >
- <template #item="{ element: child, index: y }">
- <div
- class="relative float-left box-border block w-[85.5px] cursor-pointer border border-[#ebedee] bg-white text-center"
- >
- <div
- class="box-border h-[44px] text-center leading-[44px]"
- :class="{
- 'border border-[#2bb673]':
- props.activeIndex === `${x}-${y}`,
- }"
- @click="subMenuClicked(child, x, y)"
- >
- {{ child.name }}
- </div>
- </div>
- </template>
- </draggable>
- <!-- 浜岀骇鑿滃崟鍔犲彿锛� 褰撻暱搴� 灏忎簬 5 鎵嶆樉绀轰簩绾ц彍鍗曠殑鍔犲彿 -->
- <div
- class="relative float-left box-border block flex h-[46px] w-[85.5px] cursor-pointer items-center justify-center border border-[#ebedee] bg-white text-center leading-[46px]"
- v-if="!parent.children || parent.children.length < 5"
- @click="addSubMenu(x, parent)"
- >
- <IconifyIcon icon="lucide:plus" class="text-[#2bb673]" />
- </div>
- </div>
- </div>
- </template>
- </draggable>
-
- <!-- 涓�绾ц彍鍗曞姞鍙� -->
- <div
- class="relative float-left box-border block flex h-[46px] w-[85.5px] cursor-pointer items-center justify-center border border-[#ebedee] bg-white text-center leading-[46px]"
- v-if="menuList.length < 3"
- @click="addMenu"
- >
- <IconifyIcon icon="lucide:plus" class="text-[#2bb673]" />
- </div>
-</template>
-
-<style lang="scss" scoped>
-.draggable-ghost {
- background: #f7fafc;
- border: 1px solid #4299e1;
- opacity: 0.5;
-}
-</style>
diff --git a/src/views/mp/menu/modules/types.ts b/src/views/mp/menu/modules/types.ts
deleted file mode 100644
index ae0edbd..0000000
--- a/src/views/mp/menu/modules/types.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-export interface Replay {
- title: string;
- description: string;
- picUrl: string;
- url: string;
-}
-
-export type MenuType =
- | ''
- | 'article_view_limited'
- | 'click'
- | 'location_select'
- | 'pic_photo_or_album'
- | 'pic_sysphoto'
- | 'pic_weixin'
- | 'scancode_push'
- | 'scancode_waitmsg'
- | 'view';
-
-interface _RawMenu {
- // db
- id: number;
- parentId: number;
- accountId: number;
- appId: string;
- createTime: number;
-
- // mp-native
- name: string;
- menuKey: string;
- type: MenuType;
- url: string;
- miniProgramAppId: string;
- miniProgramPagePath: string;
- articleId: string;
- replyMessageType: string;
- replyContent: string;
- replyMediaId: string;
- replyMediaUrl: string;
- replyThumbMediaId: string;
- replyThumbMediaUrl: string;
- replyTitle: string;
- replyDescription: string;
- replyArticles: Replay;
- replyMusicUrl: string;
- replyHqMusicUrl: string;
-}
-
-export type RawMenu = Partial<_RawMenu>;
-
-interface _Reply {
- type: string;
- accountId: number;
- content: string;
- mediaId: string;
- url: string;
- thumbMediaId: string;
- thumbMediaUrl: string;
- title: string;
- description: string;
- articles: null | Replay[];
- musicUrl: string;
- hqMusicUrl: string;
-}
-
-export type Reply = Partial<_Reply>;
-
-interface _Menu extends RawMenu {
- children: _Menu[];
- reply: Reply;
-}
-
-export type Menu = Partial<_Menu>;
-
-export const menuOptions = [
- {
- value: 'view',
- label: '璺宠浆缃戦〉',
- },
- {
- value: 'miniprogram',
- label: '璺宠浆灏忕▼搴�',
- },
- {
- value: 'click',
- label: '鐐瑰嚮鍥炲',
- },
- {
- value: 'article_view_limited',
- label: '璺宠浆鍥炬枃娑堟伅',
- },
- {
- value: 'scancode_push',
- label: '鎵爜鐩存帴杩斿洖缁撴灉',
- },
- {
- value: 'scancode_waitmsg',
- label: '鎵爜鍥炲',
- },
- {
- value: 'pic_sysphoto',
- label: '绯荤粺鎷嶇収鍙戝浘',
- },
- {
- value: 'pic_photo_or_album',
- label: '鎷嶇収鎴栬�呯浉鍐�',
- },
- {
- value: 'pic_weixin',
- label: '寰俊鐩稿唽',
- },
- {
- value: 'location_select',
- label: '閫夋嫨鍦扮悊浣嶇疆',
- },
-];
diff --git a/src/views/mp/message/data.ts b/src/views/mp/message/data.ts
deleted file mode 100644
index c9f0608..0000000
--- a/src/views/mp/message/data.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMessageApi } from '#/api/mp/message';
-
-import { DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- {
- fieldName: 'type',
- label: '娑堟伅绫诲瀷',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨娑堟伅绫诲瀷',
- options: getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE),
- allowClear: true,
- },
- },
- {
- fieldName: 'openid',
- label: '鐢ㄦ埛鏍囪瘑',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋爣璇�',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions<MpMessageApi.Message>['columns'] {
- return [
- {
- field: 'createTime',
- title: '鍙戦�佹椂闂�',
- width: 180,
- align: 'center',
- slots: { default: 'createTime' },
- },
- {
- field: 'type',
- title: '娑堟伅绫诲瀷',
- width: 80,
- align: 'center',
- },
- {
- field: 'sendFrom',
- title: '鍙戦�佹柟',
- width: 80,
- align: 'center',
- slots: { default: 'sendFrom' },
- },
- {
- field: 'openid',
- title: '鐢ㄦ埛鏍囪瘑',
- width: 300,
- align: 'center',
- },
- {
- field: 'content',
- title: '鍐呭',
- align: 'left',
- minWidth: 320,
- slots: { default: 'content' },
- },
- {
- field: 'actions',
- title: '鎿嶄綔',
- width: 120,
- align: 'center',
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue
deleted file mode 100644
index 91bb630..0000000
--- a/src/views/mp/message/index.vue
+++ /dev/null
@@ -1,238 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMessageApi } from '#/api/mp/message';
-
-import { ref } from 'vue';
-
-import { Page } from '../../../packages/effects/common-ui/src';
-import { MpMsgType as MsgType } from '../../../packages/constants/src';
-import { formatDate2 } from '../../../packages/utils/src';
-
-import { Image, Modal, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getMessagePage } from '#/api/mp/message';
-import {
- WxAccountSelect,
- WxLocation,
- WxMsg,
- WxMusic,
- WxNews,
- WxVideoPlayer,
- WxVoicePlayer,
-} from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-
-defineOptions({ name: 'MpMessage' });
-
-// 娑堟伅瀵硅瘽妗�
-const messageBoxVisible = ref(false);
-const messageBoxUserId = ref(0);
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鎵撳紑娑堟伅鍙戦�佺獥鍙� */
-function handleSend(userId: number) {
- messageBoxUserId.value = userId;
- messageBoxVisible.value = true;
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getMessagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- showOverflow: 'tooltip',
- } as VxeTableGridOptions<MpMessageApi.Message>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <Grid>
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #createTime="{ row }">
- {{ row.createTime ? formatDate2(row.createTime) : '' }}
- </template>
-
- <template #sendFrom="{ row }">
- <Tag v-if="row.sendFrom === 1" color="success">绮変笣</Tag>
- <Tag v-else>鍏紬鍙�</Tag>
- </template>
-
- <template #content="{ row }">
- <div
- v-if="
- (row.type as string) === (MsgType.Event as string) &&
- (row.event as string) === 'subscribe'
- "
- >
- <Tag color="success">鍏虫敞</Tag>
- </div>
- <div
- v-else-if="
- (row.type as string) === (MsgType.Event as string) &&
- (row.event as string) === 'unsubscribe'
- "
- >
- <Tag color="error">鍙栨秷鍏虫敞</Tag>
- </div>
- <div
- v-else-if="
- (row.type as string) === (MsgType.Event as string) &&
- (row.event as string) === 'CLICK'
- "
- >
- <Tag>鐐瑰嚮鑿滃崟</Tag>
- 銆恵{ row.eventKey }}銆�
- </div>
- <div v-else-if="row.type === MsgType.Event && row.event === 'VIEW'">
- <Tag>鐐瑰嚮鑿滃崟閾炬帴</Tag>
- 銆恵{ row.eventKey }}銆�
- </div>
- <div
- v-else-if="
- row.type === MsgType.Event && row.event === 'scancode_waitmsg'
- "
- >
- <Tag>鎵爜缁撴灉</Tag>
- 銆恵{ row.eventKey }}銆�
- </div>
- <div
- v-else-if="
- row.type === MsgType.Event && row.event === 'scancode_push'
- "
- >
- <Tag>鎵爜缁撴灉</Tag>
- 銆恵{ row.eventKey }}銆�
- </div>
- <div
- v-else-if="row.type === MsgType.Event && row.event === 'pic_sysphoto'"
- >
- <Tag>绯荤粺鎷嶇収鍙戝浘</Tag>
- </div>
- <div
- v-else-if="
- row.type === MsgType.Event && row.event === 'pic_photo_or_album'
- "
- >
- <Tag>鎷嶇収鎴栬�呯浉鍐�</Tag>
- </div>
- <div
- v-else-if="row.type === MsgType.Event && row.event === 'pic_weixin'"
- >
- <Tag>寰俊鐩稿唽</Tag>
- </div>
- <div
- v-else-if="
- row.type === MsgType.Event && row.event === 'location_select'
- "
- >
- <Tag>閫夋嫨鍦扮悊浣嶇疆</Tag>
- </div>
- <div v-else-if="row.type === MsgType.Event">
- <Tag color="error">鏈煡浜嬩欢绫诲瀷</Tag>
- </div>
-
- <div v-else-if="row.type === MsgType.Text">{{ row.content }}</div>
- <div v-else-if="row.type === MsgType.Voice">
- <WxVoicePlayer
- :url="row.mediaUrl || ''"
- :content="row.recognition || ''"
- />
- </div>
- <div v-else-if="row.type === MsgType.Image">
- <a :href="row.mediaUrl" target="_blank">
- <Image :src="row.mediaUrl" :width="100" :preview="false" />
- </a>
- </div>
- <div
- v-else-if="row.type === MsgType.Video || row.type === 'shortvideo'"
- >
- <WxVideoPlayer :url="row.mediaUrl || ''" class="mt-2" />
- </div>
- <div v-else-if="row.type === MsgType.Link">
- <Tag>閾炬帴</Tag>
- 锛�
- <a :href="row.url" target="_blank">{{ row.title }}</a>
- </div>
- <div v-else-if="row.type === MsgType.Location">
- <WxLocation
- :label="row.label || ''"
- :location-y="row.locationY || 0"
- :location-x="row.locationX || 0"
- />
- </div>
- <div v-else-if="row.type === MsgType.Music">
- <WxMusic
- :title="row.title"
- :description="row.description"
- :thumb-media-url="row.thumbMediaUrl || ''"
- :music-url="row.musicUrl"
- :hq-music-url="row.hqMusicUrl"
- />
- </div>
- <div v-else-if="row.type === MsgType.News">
- <WxNews :articles="row.articles" />
- </div>
- <div v-else>
- <Tag color="error">鏈煡娑堟伅绫诲瀷</Tag>
- </div>
- </template>
-
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '娑堟伅',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: () => handleSend(row.userId || 0),
- },
- ]"
- />
- </template>
- </Grid>
-
- <!-- 鍙戦�佹秷鎭殑寮圭獥 -->
- <Modal
- v-model:open="messageBoxVisible"
- title="绮変笣娑堟伅鍒楄〃"
- :width="800"
- :footer="null"
- destroy-on-close
- >
- <WxMsg :user-id="messageBoxUserId" />
- </Modal>
- </Page>
-</template>
diff --git a/src/views/mp/messageTemplate/data.ts b/src/views/mp/messageTemplate/data.ts
deleted file mode 100644
index aa0d59b..0000000
--- a/src/views/mp/messageTemplate/data.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-
-import { getUserPage } from '#/api/mp/user';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- ];
-}
-
-/** 鍙戦�佹秷鎭ā鏉胯〃鍗� */
-export function useSendFormSchema(accountId?: number): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- label: '妯℃澘缂栧彿',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'title',
- label: '妯℃澘鏍囬',
- component: 'Input',
- componentProps: {
- disabled: true,
- },
- },
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛',
- component: 'ApiSelect',
- componentProps: {
- api: async () => {
- if (!accountId) {
- return [];
- }
- const data = await getUserPage({
- pageNo: 1,
- pageSize: 100,
- accountId,
- });
- return (data.list || []).map((user) => ({
- label: user.nickname || user.openid,
- value: user.id,
- }));
- },
- showSearch: true,
- placeholder: '璇烽�夋嫨鐢ㄦ埛',
- },
- rules: 'required',
- },
- {
- fieldName: 'data',
- label: '妯℃澘鏁版嵁',
- component: 'Textarea',
- componentProps: {
- rows: 4,
- placeholder:
- '璇疯緭鍏ユā鏉挎暟鎹紙JSON 鏍煎紡锛夛紝渚嬪锛歿"keyword1": {"value": "娴嬭瘯鍐呭"}}',
- },
- },
- {
- fieldName: 'url',
- label: '璺宠浆閾炬帴',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ烦杞摼鎺�',
- },
- },
- {
- fieldName: 'miniProgramAppId',
- label: '灏忕▼搴� appId',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ皬绋嬪簭 appId',
- },
- },
- {
- fieldName: 'miniProgramPagePath',
- label: '灏忕▼搴忛〉闈㈣矾寰�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ皬绋嬪簭椤甸潰璺緞',
- },
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- title: '鍏紬鍙锋ā鏉� ID',
- field: 'templateId',
- minWidth: 400,
- },
- {
- title: '鏍囬',
- field: 'title',
- minWidth: 150,
- },
- {
- title: '妯℃澘鍐呭',
- field: 'content',
- minWidth: 400,
- },
- {
- title: '妯℃澘绀轰緥',
- field: 'example',
- minWidth: 200,
- },
- {
- title: '涓�绾ц涓�',
- field: 'primaryIndustry',
- minWidth: 120,
- },
- {
- title: '浜岀骇琛屼笟',
- field: 'deputyIndustry',
- minWidth: 120,
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- formatter: 'formatDateTime',
- minWidth: 180,
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/messageTemplate/index.vue b/src/views/mp/messageTemplate/index.vue
deleted file mode 100644
index d31c356..0000000
--- a/src/views/mp/messageTemplate/index.vue
+++ /dev/null
@@ -1,166 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpMessageTemplateApi } from '#/api/mp/messageTemplate';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteMessageTemplate,
- getMessageTemplateList,
- syncMessageTemplate,
-} from '#/api/mp/messageTemplate';
-import { $t } from '#/locales';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import SendForm from './modules/send-form.vue';
-
-const [SendFormModal, sendFormModalApi] = useVbenModal({
- connectedComponent: SendForm,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鍚屾妯℃澘 */
-async function handleSync() {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
-
- await confirm('鏄惁纭鍚屾娑堟伅妯℃澘锛�');
- const hideLoading = message.loading({
- content: '姝e湪鍚屾娑堟伅妯℃澘...',
- duration: 0,
- });
- try {
- await syncMessageTemplate(accountId);
- message.success('鍚屾娑堟伅妯℃澘鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍙戦�佹秷鎭� */
-function handleSend(row: MpMessageTemplateApi.MessageTemplate) {
- sendFormModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎妯℃澘 */
-async function handleDelete(row: MpMessageTemplateApi.MessageTemplate) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.title]),
- duration: 0,
- });
- try {
- await deleteMessageTemplate(row.id);
- message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async (_params, formValues) => {
- return await getMessageTemplateList({
- accountId: formValues.accountId,
- });
- },
- },
- autoLoad: false,
- },
- pagerConfig: {
- enabled: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpMessageTemplateApi.MessageTemplate>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="妯$増娑堟伅"
- url="https://doc.iocoder.cn/mp/message-template/"
- />
- </template>
-
- <SendFormModal @success="handleRefresh" />
- <Grid table-title="鍏紬鍙锋秷鎭ā鏉垮垪琛�">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鍚屾',
- type: 'primary',
- icon: 'lucide:refresh-ccw',
- auth: ['mp:message-template:sync'],
- onClick: handleSync,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍙戦��',
- type: 'link',
- icon: 'lucide:send',
- auth: ['mp:message-template:send'],
- onClick: handleSend.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:message-template:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.title]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mp/messageTemplate/modules/send-form.vue b/src/views/mp/messageTemplate/modules/send-form.vue
deleted file mode 100644
index 54b25cd..0000000
--- a/src/views/mp/messageTemplate/modules/send-form.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<script lang="ts" setup>
-import type { MpMessageTemplateApi } from '#/api/mp/messageTemplate';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { sendMessageTemplate } from '#/api/mp/messageTemplate';
-
-import { useSendFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MpMessageTemplateApi.MessageTemplate>();
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鏋勫缓鍙戦�佽姹�
- const values = await formApi.getValues();
- const sendData: MpMessageTemplateApi.MessageTemplateSendVO = {
- id: formData.value?.id || 0,
- userId: values.userId,
- data: values.data || undefined,
- url: values.url || undefined,
- miniProgramAppId: values.miniProgramAppId || undefined,
- miniProgramPagePath: values.miniProgramPagePath || undefined,
- };
- // 濡傛灉濉啓浜嗗皬绋嬪簭淇℃伅锛岄渶瑕佹嫾鎺ユ垚 miniprogram 瀛楁
- if (sendData.miniProgramAppId && sendData.miniProgramPagePath) {
- sendData.miniprogram = JSON.stringify({
- appid: sendData.miniProgramAppId,
- pagepath: sendData.miniProgramPagePath,
- });
- }
- // 濡傛灉濉啓浜� data 瀛楁
- if (sendData.data && typeof sendData.data === 'string') {
- try {
- sendData.data = JSON.parse(sendData.data);
- } catch {
- message.error('妯℃澘鏁版嵁鏍煎紡涓嶆纭紝璇疯緭鍏ユ湁鏁堢殑 JSON 鏍煎紡');
- modalApi.unlock();
- return;
- }
- }
-
- // 鎻愪氦琛ㄥ崟
- try {
- await sendMessageTemplate(sendData);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success('鍙戦�佹垚鍔�');
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鑾峰彇鏁版嵁
- const data = modalApi.getData<MpMessageTemplateApi.MessageTemplate>();
- if (!data) {
- return;
- }
- formData.value = data;
- // 鏇存柊 form schema
- const schema = useSendFormSchema(data.accountId);
- formApi.setState({ schema });
- // 璁剧疆鍒� values
- await formApi.setValues({
- id: data.id,
- title: data.title,
- });
- },
-});
-</script>
-
-<template>
- <Modal class="w-[600px]" title="鍙戦�佹秷鎭ā鏉�">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mp/statistics/chart-options.ts b/src/views/mp/statistics/chart-options.ts
deleted file mode 100644
index 700cb34..0000000
--- a/src/views/mp/statistics/chart-options.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import type { MpStatisticsApi } from '#/api/mp/statistics';
-
-/** 鐢ㄦ埛澧炲噺鏁版嵁鍥捐〃閰嶇疆椤� */
-export function userSummaryOption(
- data: MpStatisticsApi.StatisticsUserSummaryRespVO[],
- dates: string[],
-): any {
- return {
- color: ['#67C23A', '#E5323E'],
- legend: {
- data: ['鏂板鐢ㄦ埛', '鍙栨秷鍏虫敞鐨勭敤鎴�'],
- },
- tooltip: {},
- xAxis: {
- data: dates,
- },
- yAxis: {
- minInterval: 1,
- },
- series: [
- {
- name: '鏂板鐢ㄦ埛',
- type: 'bar',
- label: {
- show: true,
- },
- barGap: 0,
- data: data.map((item) => item.newUser), // 鏂板鐢ㄦ埛鐨勬暟鎹�
- },
- {
- name: '鍙栨秷鍏虫敞鐨勭敤鎴�',
- type: 'bar',
- label: {
- show: true,
- },
- data: data.map((item) => item.cancelUser), // 鍙栨秷鍏虫敞鐨勭敤鎴风殑鏁版嵁
- },
- ],
- };
-}
-
-/** 绱鐢ㄦ埛鏁版嵁鍥捐〃閰嶇疆椤� */
-export function userCumulateOption(
- data: MpStatisticsApi.StatisticsUserCumulateRespVO[],
- dates: string[],
-): any {
- return {
- legend: {
- data: ['绱鐢ㄦ埛閲�'],
- },
- xAxis: {
- type: 'category',
- data: dates,
- },
- yAxis: {
- minInterval: 1,
- },
- series: [
- {
- name: '绱鐢ㄦ埛閲�',
- data: data.map((item) => item.cumulateUser), // 绱鐢ㄦ埛閲忕殑鏁版嵁
- type: 'line',
- smooth: true,
- label: {
- show: true,
- },
- },
- ],
- };
-}
-
-/** 娑堟伅鍙戦�佹鍐垫暟鎹浘琛ㄩ厤缃」 */
-export function upstreamMessageOption(
- data: MpStatisticsApi.StatisticsUpstreamMessageRespVO[],
- dates: string[],
-): any {
- return {
- color: ['#67C23A', '#E5323E'],
- legend: {
- data: ['鐢ㄦ埛鍙戦�佷汉鏁�', '鐢ㄦ埛鍙戦�佹潯鏁�'],
- },
- tooltip: {},
- xAxis: {
- data: dates, // X 杞寸殑鏃ユ湡鑼冨洿
- },
- yAxis: {
- minInterval: 1,
- },
- series: [
- {
- name: '鐢ㄦ埛鍙戦�佷汉鏁�',
- type: 'line',
- smooth: true,
- label: {
- show: true,
- },
- data: data.map((item) => item.msgUser), // 鐢ㄦ埛鍙戦�佷汉鏁扮殑鏁版嵁
- },
- {
- name: '鐢ㄦ埛鍙戦�佹潯鏁�',
- type: 'line',
- smooth: true,
- label: {
- show: true,
- },
- data: data.map((item) => item.msgCount), // 鐢ㄦ埛鍙戦�佹潯鏁扮殑鏁版嵁
- },
- ],
- };
-}
-
-/** 鎺ュ彛鍒嗘瀽鍐垫暟鎹浘琛ㄩ厤缃」 */
-export function interfaceSummaryOption(
- data: MpStatisticsApi.StatisticsInterfaceSummaryRespVO[],
- dates: string[],
-): any {
- return {
- color: ['#67C23A', '#E5323E', '#E6A23C', '#409EFF'],
- legend: {
- data: ['琚姩鍥炲鐢ㄦ埛娑堟伅鐨勬鏁�', '澶辫触娆℃暟', '鏈�澶ц�楁椂', '鎬昏�楁椂'],
- },
- tooltip: {},
- xAxis: {
- data: dates, // X 杞寸殑鏃ユ湡鑼冨洿
- },
- yAxis: {},
- series: [
- {
- name: '琚姩鍥炲鐢ㄦ埛娑堟伅鐨勬鏁�',
- type: 'bar',
- label: {
- show: true,
- },
- barGap: 0,
- data: data.map((item) => item.callbackCount), // 琚姩鍥炲鐢ㄦ埛娑堟伅鐨勬鏁扮殑鏁版嵁
- },
- {
- name: '澶辫触娆℃暟',
- type: 'bar',
- label: {
- show: true,
- },
- data: data.map((item) => item.failCount), // 澶辫触娆℃暟鐨勬暟鎹�
- },
- {
- name: '鏈�澶ц�楁椂',
- type: 'bar',
- label: {
- show: true,
- },
- data: data.map((item) => item.maxTimeCost), // 鏈�澶ц�楁椂鐨勬暟鎹�
- },
- {
- name: '鎬昏�楁椂',
- type: 'bar',
- label: {
- show: true,
- },
- data: data.map((item) => item.totalTimeCost), // 鎬昏�楁椂鐨勬暟鎹�
- },
- ],
- };
-}
diff --git a/src/views/mp/statistics/data.ts b/src/views/mp/statistics/data.ts
deleted file mode 100644
index f697a88..0000000
--- a/src/views/mp/statistics/data.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { beginOfDay, endOfDay, formatDateTime } from '../../../packages/utils/src';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- {
- fieldName: 'dateRange',
- label: '鏃堕棿鑼冨洿',
- component: 'RangePicker',
- componentProps: {
- format: 'YYYY-MM-DD',
- valueFormat: 'YYYY-MM-DD HH:mm:ss',
- },
- defaultValue: [
- formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
- formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))),
- ],
- },
- ];
-}
diff --git a/src/views/mp/statistics/index.vue b/src/views/mp/statistics/index.vue
deleted file mode 100644
index ec8df08..0000000
--- a/src/views/mp/statistics/index.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<script lang="ts" setup>
-import type { EchartsUIType } from '../../../packages/effects/plugins/src/echarts';
-
-import { ref } from 'vue';
-
-import { ContentWrap, Page } from '../../../packages/effects/common-ui/src';
-import { EchartsUI, useEcharts } from '../../../packages/effects/plugins/src/echarts';
-import { addTime, betweenDay, formatDate } from '../../../packages/utils/src';
-
-import { Card, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- getInterfaceSummary,
- getUpstreamMessage,
- getUserCumulate,
- getUserSummary,
-} from '#/api/mp/statistics';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import {
- interfaceSummaryOption,
- upstreamMessageOption,
- userCumulateOption,
- userSummaryOption,
-} from './chart-options';
-import { useGridFormSchema } from './data';
-
-const userSummaryRef = ref<EchartsUIType>();
-const { renderEcharts: renderUserSummaryEcharts } = useEcharts(userSummaryRef);
-
-const userCumulateRef = ref<EchartsUIType>();
-const { renderEcharts: renderUserCumulateEcharts } =
- useEcharts(userCumulateRef);
-
-const upstreamMessageRef = ref<EchartsUIType>();
-const { renderEcharts: renderUpstreamMessageEcharts } =
- useEcharts(upstreamMessageRef);
-
-const interfaceSummaryRef = ref<EchartsUIType>();
-const { renderEcharts: renderInterfaceSummaryEcharts } =
- useEcharts(interfaceSummaryRef);
-
-/** 鍔犺浇鏁版嵁 */
-async function getSummary(values: Record<string, any>) {
- const accountId = values.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- const dateRange = values.dateRange;
- if (!dateRange) {
- message.warning('璇峰厛閫夋嫨鏃堕棿鑼冨洿');
- return;
- }
- // 蹇呴』閫夋嫨 7 澶╁唴锛屽洜涓哄叕浼楀彿鏈夋椂闂磋法搴﹂檺鍒朵负 7
- if (betweenDay(dateRange[0], dateRange[1]) >= 7) {
- message.error('鏃堕棿闂撮殧 7 澶╀互鍐咃紝璇烽噸鏂伴�夋嫨');
- return;
- }
- // 澶勭悊鏃ユ湡
- const days = betweenDay(dateRange[0], dateRange[1]);
- const dates = Array.from(
- { length: days },
- (_, index) =>
- formatDate(addTime(dateRange[0], index), 'YYYY-MM-DD') as string,
- );
-
- // 鐢ㄦ埛澧炲噺鏁版嵁
- const userSummaryData = await getUserSummary({
- accountId,
- date: dateRange,
- });
- await renderUserSummaryEcharts(userSummaryOption(userSummaryData, dates));
- // 绱鐢ㄦ埛鏁版嵁
- const userCumulateData = await getUserCumulate({
- accountId,
- date: dateRange,
- });
- await renderUserCumulateEcharts(userCumulateOption(userCumulateData, dates));
- // 娑堟伅鍙戦�佹鍐垫暟鎹�
- const upstreamMessageData = await getUpstreamMessage({
- accountId,
- date: dateRange,
- });
- await renderUpstreamMessageEcharts(
- upstreamMessageOption(upstreamMessageData, dates),
- );
- // 鎺ュ彛鍒嗘瀽鏁版嵁
- const interfaceSummaryData = await getInterfaceSummary({
- accountId,
- date: dateRange,
- });
- await renderInterfaceSummaryEcharts(
- interfaceSummaryOption(interfaceSummaryData, dates),
- );
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- queryFormApi.setValues({ accountId });
- queryFormApi.submitForm();
-}
-
-const [QueryForm, queryFormApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- },
- layout: 'horizontal',
- schema: useGridFormSchema(),
- wrapperClass: 'grid-cols-1 md:grid-cols-2',
- handleSubmit: getSummary,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <ContentWrap class="h-full w-full">
- <QueryForm>
- <template #accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- </QueryForm>
-
- <div class="flex h-1/3 w-full gap-4">
- <Card class="h-full w-1/2" title="鐢ㄦ埛澧炲噺鏁版嵁">
- <EchartsUI ref="userSummaryRef" />
- </Card>
- <Card class="h-full w-1/2" title="绱鐢ㄦ埛鏁版嵁">
- <EchartsUI ref="userCumulateRef" />
- </Card>
- </div>
- <div class="mt-4 flex h-1/3 w-full gap-4">
- <Card class="h-full w-1/2" title="娑堟伅鍙戦�佹鍐垫暟鎹�">
- <EchartsUI ref="upstreamMessageRef" />
- </Card>
- <Card class="h-full w-1/2" title="鎺ュ彛鍒嗘瀽鏁版嵁">
- <EchartsUI ref="interfaceSummaryRef" />
- </Card>
- </div>
- </ContentWrap>
- </Page>
-</template>
diff --git a/src/views/mp/tag/data.ts b/src/views/mp/tag/data.ts
deleted file mode 100644
index ffce47f..0000000
--- a/src/views/mp/tag/data.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeGridPropTypes } from '#/adapter/vxe-table';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '鏍囩鍚嶇О',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ悕绉�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- ];
-}
-
-/** 琛ㄦ牸鍒楅厤缃� */
-export function useGridColumns(): VxeGridPropTypes.Columns {
- return [
- {
- title: '缂栧彿',
- field: 'id',
- minWidth: 80,
- },
- {
- title: '鏍囩鍚嶇О',
- field: 'name',
- minWidth: 150,
- },
- {
- title: '绮変笣鏁�',
- field: 'count',
- minWidth: 100,
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- formatter: 'formatDateTime',
- minWidth: 180,
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue
deleted file mode 100644
index 5979665..0000000
--- a/src/views/mp/tag/index.vue
+++ /dev/null
@@ -1,178 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpTagApi } from '#/api/mp/tag';
-
-import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteTag, getTagPage, syncTag } from '#/api/mp/tag';
-import { $t } from '#/locales';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 鍒涘缓鏍囩 */
-async function handleCreate() {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- formModalApi.setData({ accountId }).open();
-}
-
-/** 缂栬緫鏍囩 */
-async function handleEdit(row: MpTagApi.Tag) {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
- formModalApi.setData({ row, accountId }).open();
-}
-
-/** 鍒犻櫎鏍囩 */
-async function handleDelete(row: MpTagApi.Tag) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteTag(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鍚屾鏍囩 */
-async function handleSync() {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
-
- await confirm('鏄惁纭鍚屾鏍囩锛�');
- const hideLoading = message.loading({
- content: '姝e湪鍚屾鏍囩...',
- duration: 0,
- });
- try {
- await syncTag(accountId);
- message.success('鍚屾鏍囩鎴愬姛');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getTagPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpTagApi.Tag>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍏紬鍙锋爣绛惧垪琛�">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍏紬鍙锋爣绛�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mp:tag:create'],
- onClick: handleCreate,
- },
- {
- label: '鍚屾',
- type: 'primary',
- icon: 'lucide:refresh-ccw',
- auth: ['mp:tag:sync'],
- onClick: handleSync,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['mp:tag:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['mp:tag:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mp/tag/modules/form.vue b/src/views/mp/tag/modules/form.vue
deleted file mode 100644
index 26b4b99..0000000
--- a/src/views/mp/tag/modules/form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { MpTagApi } from '#/api/mp/tag';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createTag, getTag, updateTag } from '#/api/mp/tag';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const formData = ref<MpTagApi.Tag>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鏍囩'])
- : $t('ui.actionTitle.create', ['鏍囩']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 120,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as MpTagApi.Tag;
- try {
- await (formData.value?.id ? updateTag(data) : createTag(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{ accountId: number; row: MpTagApi.Tag }>();
- if (!data || !data.row || !data.accountId) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getTag(data.row.id!);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-2/5">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/mp/user/data.ts b/src/views/mp/user/data.ts
deleted file mode 100644
index fc18882..0000000
--- a/src/views/mp/user/data.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-/** 淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'nickname',
- label: '鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ樀绉�',
- },
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'accountId',
- label: '鍏紬鍙�',
- component: 'Input',
- },
- {
- fieldName: 'openid',
- label: '鐢ㄦ埛鏍囪瘑',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴锋爣璇�',
- allowClear: true,
- },
- },
- {
- fieldName: 'nickname',
- label: '鏄电О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ樀绉�',
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'openid',
- title: '鐢ㄦ埛鏍囪瘑',
- minWidth: 260,
- },
- {
- field: 'headImageUrl',
- title: '鐢ㄦ埛澶村儚',
- minWidth: 80,
- cellRender: {
- name: 'CellImage',
- },
- },
- {
- field: 'nickname',
- title: '鏄电О',
- minWidth: 120,
- },
- {
- field: 'remark',
- title: '澶囨敞',
- minWidth: 120,
- },
- {
- field: 'tagIds',
- title: '鏍囩',
- minWidth: 200,
- cellRender: {
- name: 'CellTags',
- },
- },
- {
- field: 'subscribeStatus',
- title: '璁㈤槄鐘舵��',
- minWidth: 100,
- align: 'center',
- formatter: ({ cellValue }) => {
- return cellValue === 0 ? '宸茶闃�' : '鏈闃�';
- },
- },
- {
- field: 'subscribeTime',
- title: '璁㈤槄鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/mp/user/index.vue b/src/views/mp/user/index.vue
deleted file mode 100644
index c9ffa5c..0000000
--- a/src/views/mp/user/index.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { MpUserApi } from '#/api/mp/user';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getUserPage, syncUser } from '#/api/mp/user';
-import { $t } from '#/locales';
-import { WxAccountSelect } from '#/views/mp/components';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-defineOptions({ name: 'MpUser' });
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍏紬鍙峰彉鍖栨椂鏌ヨ鏁版嵁 */
-function handleAccountChange(accountId: number) {
- gridApi.formApi.setValues({ accountId });
- gridApi.formApi.submitForm();
-}
-
-/** 缂栬緫鐢ㄦ埛 */
-function handleEdit(row: MpUserApi.User) {
- formModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍚屾绮変笣 */
-async function handleSync() {
- const formValues = await gridApi.formApi.getValues();
- const accountId = formValues.accountId;
- if (!accountId) {
- message.warning('璇峰厛閫夋嫨鍏紬鍙�');
- return;
- }
-
- await confirm('鏄惁纭鍚屾绮変笣锛�');
- const hideLoading = message.loading({
- content: '姝e湪鍚屾绮変笣...',
- duration: 0,
- });
- try {
- await syncUser(accountId);
- message.success(
- '寮�濮嬩粠寰俊鍏紬鍙峰悓姝ョ矇涓濅俊鎭紝鍚屾闇�瑕佷竴娈垫椂闂达紝寤鸿绋嶅悗鍐嶆煡璇�',
- );
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getUserPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- autoLoad: false,
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<MpUserApi.User>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍏紬鍙风矇涓�" url="https://doc.iocoder.cn/mp/user/" />
- </template>
-
- <FormModal @success="handleRefresh" />
-
- <Grid table-title="绮変笣鍒楄〃">
- <template #form-accountId>
- <WxAccountSelect @change="handleAccountChange" />
- </template>
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鍚屾',
- type: 'primary',
- icon: ACTION_ICON.REFRESH,
- auth: ['mp:user:sync'],
- onClick: handleSync,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['mp:user:update'],
- onClick: handleEdit.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/mp/user/modules/form.vue b/src/views/mp/user/modules/form.vue
deleted file mode 100644
index 0bc88ab..0000000
--- a/src/views/mp/user/modules/form.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-<script lang="ts" setup>
-import type { MpUserApi } from '#/api/mp/user';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getUser, updateUser } from '#/api/mp/user';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<MpUserApi.User>();
-const getTitle = computed(() => {
- return $t('ui.actionTitle.edit', ['绮変笣']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const values = (await formApi.getValues()) as MpUserApi.User;
- try {
- await updateUser({ ...formData.value, ...values });
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<{ id: number }>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getUser(data.id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-[600px]">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/pay/app/data.ts b/src/views/pay/app/data.ts
deleted file mode 100644
index 31e02c0..0000000
--- a/src/views/pay/app/data.ts
+++ /dev/null
@@ -1,674 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayAppApi } from '#/api/pay/app';
-
-import { h } from 'vue';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { InputUpload } from '#/components/upload';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '搴旂敤鍚�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ簲鐢ㄥ悕',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'Select',
- componentProps: {
- placeholder: '璇烽�夋嫨寮�鍚姸鎬�',
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- clearable: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(
- onStatusChange?: (
- newStatus: number,
- row: PayAppApi.App,
- ) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'appKey',
- title: '搴旂敤鏍囪瘑',
- minWidth: 40,
- },
- {
- field: 'name',
- title: '搴旂敤鍚�',
- minWidth: 40,
- },
- {
- field: 'status',
- title: '鐘舵��',
- align: 'center',
- minWidth: 40,
- cellRender: {
- attrs: { beforeChange: onStatusChange },
- name: 'CellSwitch',
- props: {
- checkedValue: CommonStatusEnum.ENABLE,
- unCheckedValue: CommonStatusEnum.DISABLE,
- },
- },
- },
- {
- title: '鏀粯瀹濋厤缃�',
- children: [
- {
- title: 'APP',
- slots: {
- default: 'alipayAppConfig',
- },
- },
- {
- title: 'PC 缃戠珯',
- slots: {
- default: 'alipayPCConfig',
- },
- },
- {
- title: 'WAP 缃戠珯',
- slots: {
- default: 'alipayWAPConfig',
- },
- minWidth: 10,
- },
- {
- title: '鎵爜',
- slots: {
- default: 'alipayQrConfig',
- },
- },
- {
- title: '鏉$爜',
- slots: {
- default: 'alipayBarConfig',
- },
- },
- ],
- },
- {
- title: '寰俊閰嶇疆',
- children: [
- {
- title: '灏忕▼搴�',
- slots: {
- default: 'wxLiteConfig',
- },
- },
- {
- title: 'JSAPI',
- slots: {
- default: 'wxPubConfig',
- },
- },
- {
- title: 'APP',
- slots: {
- default: 'wxAppConfig',
- },
- },
- {
- title: 'Native',
- slots: {
- default: 'wxNativeConfig',
- },
- },
- {
- title: 'WAP 缃戠珯',
- slots: {
- default: 'wxWapConfig',
- },
- minWidth: 10,
- },
- {
- title: '鏉$爜',
- slots: {
- default: 'wxBarConfig',
- },
- },
- ],
- },
- {
- title: '閽卞寘鏀粯閰嶇疆',
- field: 'walletConfig',
- slots: {
- default: 'walletConfig',
- },
- },
- {
- title: '妯℃嫙鏀粯閰嶇疆',
- field: 'mockConfig',
- slots: {
- default: 'mockConfig',
- },
- },
- {
- title: '鎿嶄綔',
- width: 140,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 搴旂敤鏂板/淇敼鐨勮〃鍗� */
-export function useAppFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '搴旂敤鍚�',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ簲鐢ㄥ悕',
- },
- },
- {
- fieldName: 'appKey',
- label: '搴旂敤鏍囪瘑',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ簲鐢ㄦ爣璇�',
- },
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'RadioGroup',
- rules: z.number().default(CommonStatusEnum.ENABLE),
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- {
- fieldName: 'orderNotifyUrl',
- label: '鏀粯缁撴灉鐨勫洖璋冨湴鍧�',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敮浠樼粨鏋滅殑鍥炶皟鍦板潃',
- },
- },
- {
- fieldName: 'refundNotifyUrl',
- label: '閫�娆剧粨鏋滅殑鍥炶皟鍦板潃',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ラ��娆剧粨鏋滅殑鍥炶皟鍦板潃',
- },
- },
- {
- fieldName: 'transferNotifyUrl',
- label: '杞处缁撴灉鐨勫洖璋冨湴鍧�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ浆璐︾粨鏋滅殑鍥炶皟鍦板潃',
- },
- },
- {
- fieldName: 'remark',
- label: '澶囨敞',
- component: 'Textarea',
- componentProps: {
- rows: 3,
- placeholder: '璇疯緭鍏ュ娉�',
- },
- },
- ];
-}
-
-/** 娓犻亾鏂板/淇敼鐨勮〃鍗� */
-export function useChannelFormSchema(formType: string = ''): VbenFormSchema[] {
- const schema: VbenFormSchema[] = [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- label: '搴旂敤缂栧彿',
- fieldName: 'appId',
- component: 'Input',
- dependencies: {
- show: () => false,
- triggerFields: [''],
- },
- },
- {
- label: '娓犻亾缂栫爜',
- fieldName: 'code',
- component: 'Input',
- dependencies: {
- show: () => false,
- triggerFields: [''],
- },
- },
- {
- label: '娓犻亾璐圭巼',
- fieldName: 'feeRate',
- component: 'InputNumber',
- rules: 'required',
- componentProps: {
- class: '!w-full',
- placeholder: '璇疯緭鍏ユ笭閬撹垂鐜�',
- addonAfter: '%',
- },
- defaultValue: 0,
- },
- {
- label: '娓犻亾鐘舵��',
- fieldName: 'status',
- component: 'RadioGroup',
- rules: z.number().default(CommonStatusEnum.ENABLE),
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- ];
- // 娣诲姞閫氱敤瀛楁
- // 鏍规嵁绫诲瀷娣诲姞鐗瑰畾瀛楁
- if (formType.includes('alipay_')) {
- schema.push(
- {
- label: '寮�鏀惧钩鍙� APPID',
- fieldName: 'config.appId',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ紑鏀惧钩鍙� APPID',
- },
- },
- {
- label: '缃戝叧鍦板潃',
- fieldName: 'config.serverUrl',
- component: 'RadioGroup',
- rules: 'required',
- componentProps: {
- options: [
- {
- value: 'https://openapi.alipay.com/gateway.do',
- label: '绾夸笂鐜',
- },
- {
- value: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do',
- label: '娌欑鐜',
- },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- {
- label: '绠楁硶绫诲瀷',
- fieldName: 'config.signType',
- component: 'RadioGroup',
- rules: 'required',
- componentProps: {
- options: [
- {
- value: 'RSA2',
- label: 'RSA2',
- },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 'RSA2',
- },
- {
- label: '鍏挜绫诲瀷',
- fieldName: 'config.mode',
- component: 'RadioGroup',
- rules: 'required',
- componentProps: {
- options: [
- {
- value: 1,
- label: '鍏挜妯″紡',
- },
- {
- value: 2,
- label: '璇佷功妯″紡',
- },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- {
- label: '搴旂敤绉侀挜',
- fieldName: 'config.privateKey',
- component: 'Textarea',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ簲鐢ㄧ閽�',
- rows: 3,
- },
- },
- {
- label: '鏀粯瀹濆叕閽�',
- fieldName: 'config.alipayPublicKey',
- component: 'Textarea',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敮浠樺疂鍏挜',
- rows: 3,
- },
- dependencies: {
- show(values: any) {
- return values?.config?.mode === 1;
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '鍟嗘埛鍏挜搴旂敤璇佷功',
- fieldName: 'config.appCertContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: { rows: 3, placeholder: '璇蜂笂浼犲晢鎴峰叕閽ュ簲鐢ㄨ瘉涔�' },
- fileUploadProps: {
- accept: ['crt'],
- },
- }),
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.mode === 2;
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '鏀粯瀹濆叕閽ヨ瘉涔�',
- fieldName: 'config.alipayPublicCertContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: { rows: 3, placeholder: '璇蜂笂浼犳敮浠樺疂鍏挜璇佷功' },
- fileUploadProps: {
- accept: ['crt'],
- },
- }),
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.mode === 2;
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '鏍硅瘉涔�',
- fieldName: 'config.rootCertContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: { rows: 3, placeholder: '璇蜂笂浼犳牴璇佷功' },
- fileUploadProps: {
- accept: ['crt'],
- },
- }),
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.mode === 2;
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '鎺ュ彛鍐呭鍔犲瘑鏂瑰紡',
- fieldName: 'config.encryptType',
- component: 'RadioGroup',
- rules: 'required',
- componentProps: {
- options: [
- {
- value: 'NONE',
- label: '鏃犲姞瀵�',
- },
- {
- value: 'AES',
- label: 'AES',
- },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 'NONE',
- },
- {
- label: '鎺ュ彛鍐呭鍔犲瘑瀵嗛挜',
- fieldName: 'config.encryptKey',
- component: 'Input',
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.encryptType === 'AES';
- },
- triggerFields: ['config.encryptType', 'encryptType', 'config'],
- },
- },
- );
- } else if (formType.includes('wx_')) {
- schema.push(
- {
- label: '寰俊 APPID',
- fieldName: 'config.appId',
- help: '鍓嶅線寰俊鍟嗘埛骞冲彴[https://pay.weixin.qq.com/index.php/extend/merchant_appid/mapay_platform/account_manage]鏌ョ湅 APPID',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ井淇� APPID',
- },
- },
- {
- label: '鍟嗘埛鍙�',
- fieldName: 'config.mchId',
- help: '鍓嶅線寰俊鍟嗘埛骞冲彴[https://pay.weixin.qq.com/index.php/extend/pay_setting]鏌ョ湅鍟嗘埛鍙�',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鎴峰彿',
- },
- },
- {
- label: 'API 鐗堟湰',
- fieldName: 'config.apiVersion',
- component: 'RadioGroup',
- rules: 'required',
- componentProps: {
- options: [
- {
- label: 'v2',
- value: 'v2',
- },
- {
- label: 'v3',
- value: 'v3',
- },
- ],
- buttonStyle: 'solid',
- optionType: 'button',
- },
- },
- {
- label: '鍟嗘埛瀵嗛挜',
- fieldName: 'config.mchKey',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ晢鎴峰瘑閽�',
- },
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v2';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: 'apiclient_cert.p12 璇佷功',
- fieldName: 'config.keyContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: {
- rows: 3,
- placeholder: '璇蜂笂浼� apiclient_cert.p12 璇佷功',
- },
- fileUploadProps: {
- accept: ['p12'],
- },
- }),
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v2';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: 'API V3 瀵嗛挜',
- fieldName: 'config.apiV3Key',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏� API V3 瀵嗛挜',
- },
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v3';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: 'apiclient_key.pem 璇佷功',
- fieldName: 'config.privateKeyContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: {
- rows: 3,
- placeholder: '璇蜂笂浼� apiclient_key.pem 璇佷功',
- },
- fileUploadProps: {
- accept: ['pem'],
- },
- }),
- rules: 'required',
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v3';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '璇佷功搴忓垪鍙�',
- fieldName: 'config.certSerialNo',
- component: 'Input',
- help: '鍓嶅線寰俊鍟嗘埛骞冲彴[https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage]鏌ョ湅璇佷功搴忓垪鍙�',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ヨ瘉涔﹀簭鍒楀彿',
- },
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v3';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: 'public_key.pem 璇佷功',
- fieldName: 'config.publicKeyContent',
- component: h(InputUpload, {
- inputType: 'textarea',
- textareaProps: {
- rows: 3,
- placeholder: '璇蜂笂浼� public_key.pem 璇佷功',
- },
- fileUploadProps: {
- accept: ['pem'],
- },
- }),
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v3';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- {
- label: '鍏挜 ID',
- fieldName: 'config.publicKeyId',
- component: 'Input',
- help: '寰俊鏀粯鍏挜浜у搧绠�浠嬪強浣跨敤璇存槑[https://pay.weixin.qq.com/doc/v3/merchant/4012153196]',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ叕閽� ID',
- },
- dependencies: {
- show(values: any) {
- return values?.config?.apiVersion === 'v3';
- },
- triggerFields: ['config.mode', 'mode', 'config'],
- },
- },
- );
- }
- // 娣诲姞澶囨敞瀛楁锛堟墍鏈夌被鍨嬮兘鏈夛級
- schema.push({
- label: '澶囨敞',
- fieldName: 'remark',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ娉�',
- },
- });
- return schema;
-}
diff --git a/src/views/pay/app/index.vue b/src/views/pay/app/index.vue
deleted file mode 100644
index 6799a3f..0000000
--- a/src/views/pay/app/index.vue
+++ /dev/null
@@ -1,259 +0,0 @@
-<script lang="ts" setup>
-import type { ActionItem, VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayAppApi } from '#/api/pay/app';
-
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { CommonStatusEnum, PayChannelEnum } from '../../../packages/constants/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteApp, getAppPage, updateAppStatus } from '#/api/pay/app';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import AppForm from './modules/app-form.vue';
-import ChannelForm from './modules/channel-form.vue';
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-const [AppFormModal, appFormModalApi] = useVbenModal({
- connectedComponent: AppForm,
- destroyOnClose: true,
-});
-
-const [ChannelFormModal, channelFormModalApi] = useVbenModal({
- connectedComponent: ChannelForm,
- destroyOnClose: true,
-});
-
-/** 鍒涘缓搴旂敤 */
-function handleCreate() {
- appFormModalApi.setData(null).open();
-}
-
-/** 缂栬緫搴旂敤 */
-function handleEdit(row: PayAppApi.App) {
- appFormModalApi.setData({ id: row.id }).open();
-}
-
-/** 鍒涘缓/缂栬緫娓犻亾 */
-async function handleChannelForm(row: PayAppApi.App, payCode: string) {
- channelFormModalApi.setData({ appId: row.id, code: payCode }).open();
-}
-
-/** 鍒犻櫎搴旂敤 */
-async function handleDelete(row: PayAppApi.App) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteApp(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏇存柊搴旂敤鐘舵�� */
-async function handleStatusChange(
- newStatus: number,
- row: PayAppApi.App,
-): Promise<boolean | undefined> {
- const text = newStatus === CommonStatusEnum.ENABLE ? '鍚敤' : '鍋滅敤';
- try {
- await confirm(`纭瑕�${text + row.name}搴旂敤鍚�?`);
- } catch {
- return false;
- }
- // 鏇存柊鐘舵��
- await updateAppStatus({
- id: row.id!,
- status: newStatus,
- });
- // 鎻愮ず骞惰繑鍥炴垚鍔�
- message.success(`${text}鎴愬姛`);
- return true;
-}
-
-/** 鐢熸垚娓犻亾閰嶇疆鎸夐挳 */
-function createChannelConfigAction(
- row: PayAppApi.App,
- channelCode: string,
-): ActionItem[] {
- const exists = row.channelCodes?.includes(channelCode);
- return [
- {
- type: 'primary',
- size: 'small',
- icon: exists ? 'lucide:check' : 'lucide:x',
- danger: !exists,
- shape: 'circle',
- onClick: handleChannelForm.bind(null, row, channelCode),
- },
- ];
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(handleStatusChange),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getAppPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayAppApi.App>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鏀粯鍔熻兘寮�鍚�" url="https://doc.iocoder.cn/pay/build/" />
- </template>
-
- <AppFormModal @success="handleRefresh" />
- <ChannelFormModal @success="handleRefresh" />
-
- <Grid table-title="搴旂敤鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['搴旂敤']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['pay:app:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['pay:app:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['pay:app:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- <template #alipayAppConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.ALIPAY_APP.code)
- "
- />
- </template>
- <template #alipayPCConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.ALIPAY_PC.code)
- "
- />
- </template>
- <template #alipayWAPConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.ALIPAY_WAP.code)
- "
- />
- </template>
- <template #alipayQrConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.ALIPAY_QR.code)
- "
- />
- </template>
- <template #alipayBarConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.ALIPAY_BAR.code)
- "
- />
- </template>
- <template #wxLiteConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WX_LITE.code)"
- />
- </template>
- <template #wxPubConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WX_PUB.code)"
- />
- </template>
- <template #wxAppConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WX_APP.code)"
- />
- </template>
- <template #wxNativeConfig="{ row }">
- <TableAction
- :actions="
- createChannelConfigAction(row, PayChannelEnum.WX_NATIVE.code)
- "
- />
- </template>
- <template #wxWapConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WX_WAP.code)"
- />
- </template>
- <template #wxBarConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WX_BAR.code)"
- />
- </template>
- <template #walletConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.WALLET.code)"
- />
- </template>
- <template #mockConfig="{ row }">
- <TableAction
- :actions="createChannelConfigAction(row, PayChannelEnum.MOCK.code)"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/app/modules/app-form.vue b/src/views/pay/app/modules/app-form.vue
deleted file mode 100644
index bbed703..0000000
--- a/src/views/pay/app/modules/app-form.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<script lang="ts" setup>
-import type { PayAppApi } from '#/api/pay/app';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { $t } from '../../../../packages/locales/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createApp, getApp, updateApp } from '#/api/pay/app';
-
-import { useAppFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<PayAppApi.App>();
-const title = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['搴旂敤'])
- : $t('ui.actionTitle.create', ['搴旂敤']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 160,
- },
- layout: 'horizontal',
- schema: useAppFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as PayAppApi.App;
- try {
- await (formData.value?.id ? updateApp(data) : createApp(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const { id } = modalApi.getData() as {
- id?: number;
- };
- if (!id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getApp(id);
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-<template>
- <Modal :title="title" class="w-2/5">
- <Form />
- </Modal>
-</template>
diff --git a/src/views/pay/app/modules/channel-form.vue b/src/views/pay/app/modules/channel-form.vue
deleted file mode 100644
index 533a815..0000000
--- a/src/views/pay/app/modules/channel-form.vue
+++ /dev/null
@@ -1,139 +0,0 @@
-<script lang="ts" setup>
-import type { PayChannelApi } from '#/api/pay/channel';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { CommonStatusEnum } from '../../../../packages/constants/src';
-import { $t } from '../../../../packages/locales/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createChannel, getChannel, updateChannel } from '#/api/pay/channel';
-
-import { useChannelFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<any>();
-const title = computed(() => {
- return formData.value?.id === 0
- ? $t('ui.actionTitle.create', ['娓犻亾'])
- : $t('ui.actionTitle.edit', ['娓犻亾']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 160,
- },
- layout: 'horizontal',
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as PayChannelApi.Channel;
- data.config = JSON.stringify(data.config || {});
- try {
- await (data.id ? updateChannel(data) : createChannel(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const { appId, code } = modalApi.getData() as {
- appId?: number;
- code?: string;
- };
- if (!appId || !code) {
- return;
- }
- modalApi.lock();
- formData.value = {
- appId,
- code,
- status: CommonStatusEnum.ENABLE,
- remark: '',
- feeRate: 0,
- config: {},
- };
- if (code.includes('alipay_')) {
- formData.value.config = {
- appId: undefined,
- serverUrl: undefined,
- signType: 'RSA2',
- mode: undefined,
- privateKey: undefined,
- alipayPublicKey: undefined,
- appCertContent: undefined,
- alipayPublicCertContent: undefined,
- rootCertContent: undefined,
- encryptType: undefined,
- encryptKey: undefined,
- };
- } else if (code.includes('mock')) {
- formData.value.config = {
- name: 'mock-conf',
- };
- } else if (code.includes('wallet')) {
- formData.value.config = {
- config: {
- name: 'wallet-conf',
- },
- };
- } else if (code.includes('wx')) {
- formData.value.config = {
- appId: undefined,
- mchId: undefined,
- apiVersion: undefined,
- mchKey: undefined,
- keyContent: undefined,
- privateKeyContent: undefined,
- certSerialNo: undefined,
- apiV3Key: undefined,
- publicKeyContent: undefined,
- publicKeyId: undefined,
- };
- }
-
- try {
- const res = await getChannel(appId, code);
- if (res) {
- formData.value = {
- ...res,
- config: JSON.parse(res.config),
- };
- }
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="title" class="w-2/5">
- <Form :schema="useChannelFormSchema(formData?.code)" />
- </Modal>
-</template>
diff --git a/src/views/pay/cashier/data.ts b/src/views/pay/cashier/data.ts
deleted file mode 100644
index d9c8a3c..0000000
--- a/src/views/pay/cashier/data.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import {
- SvgAlipayAppIcon,
- SvgAlipayBarIcon,
- SvgAlipayPcIcon,
- SvgAlipayQrIcon,
- SvgAlipayWapIcon,
- SvgMockIcon,
- SvgWalletIcon,
- SvgWxAppIcon,
- SvgWxBarIcon,
- SvgWxLiteIcon,
- SvgWxNativeIcon,
- SvgWxPubIcon,
-} from '../../../packages/icons/src';
-
-export const channelsAlipay = [
- {
- name: '鏀粯瀹� PC 缃戠珯鏀粯',
- icon: SvgAlipayPcIcon,
- code: 'alipay_pc',
- },
- {
- name: '鏀粯瀹� Wap 缃戠珯鏀粯',
- icon: SvgAlipayWapIcon,
- code: 'alipay_wap',
- },
- {
- name: '鏀粯瀹� App 缃戠珯鏀粯',
- icon: SvgAlipayAppIcon,
- code: 'alipay_app',
- },
- {
- name: '鏀粯瀹濇壂鐮佹敮浠�',
- icon: SvgAlipayQrIcon,
- code: 'alipay_qr',
- },
- {
- name: '鏀粯瀹濇潯鐮佹敮浠�',
- icon: SvgAlipayBarIcon,
- code: 'alipay_bar',
- },
-];
-
-export const channelsWechat = [
- {
- name: '寰俊鍏紬鍙锋敮浠�',
- icon: SvgWxPubIcon,
- code: 'wx_pub',
- },
- {
- name: '寰俊灏忕▼搴忔敮浠�',
- icon: SvgWxLiteIcon,
- code: 'wx_lite',
- },
- {
- name: '寰俊 App 鏀粯',
- icon: SvgWxAppIcon,
- code: 'wx_app',
- },
- {
- name: '寰俊鎵爜鏀粯',
- icon: SvgWxNativeIcon,
- code: 'wx_native',
- },
- {
- name: '寰俊鏉$爜鏀粯',
- icon: SvgWxBarIcon,
- code: 'wx_bar',
- },
-];
-
-export const channelsMock = [
- {
- name: '閽卞寘鏀粯',
- icon: SvgWalletIcon,
- code: 'wallet',
- },
- {
- name: '妯℃嫙鏀粯',
- icon: SvgMockIcon,
- code: 'mock',
- },
-];
diff --git a/src/views/pay/cashier/index.vue b/src/views/pay/cashier/index.vue
deleted file mode 100644
index 3d3af8a..0000000
--- a/src/views/pay/cashier/index.vue
+++ /dev/null
@@ -1,397 +0,0 @@
-<script setup lang="ts">
-import type { PayOrderApi } from '#/api/pay/order';
-
-import { onBeforeUnmount, onMounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-
-import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import {
- PayChannelEnum,
- PayDisplayModeEnum,
- PayOrderStatusEnum,
-} from '../../../packages/constants/src';
-import { useTabs } from '../../../packages/effects/hooks/src';
-import { fenToYuan, formatDate } from '../../../packages/utils/src';
-
-import {
- Button,
- Card,
- Descriptions,
- Input,
- message,
- QRCode,
-} from 'ant-design-vue';
-
-import { getOrder, submitOrder } from '#/api/pay/order';
-
-import { channelsAlipay, channelsMock, channelsWechat } from './data';
-
-defineOptions({ name: 'PayCashier' });
-
-const route = useRoute();
-const { push } = useRouter();
-const { closeCurrentTab } = useTabs();
-
-const id = ref(); // 鏀粯鍗曞彿
-const title = ref('鏀粯璁㈠崟');
-const returnUrl = ref<string>(); // 鏀粯瀹岀殑鍥炶皟鍦板潃
-const payOrder = ref<PayOrderApi.Order>();
-const interval = ref<any>(undefined); // 瀹氭椂浠诲姟锛岃疆璇㈡槸鍚﹀畬鎴愭敮浠�
-
-const [Modal, modalApi] = useVbenModal({
- showConfirmButton: false,
- destroyOnClose: true,
-});
-
-/** 灞曠ず褰㈠紡锛氫簩缁寸爜 */
-const qrCode = ref({
- url: '',
- visible: false,
-});
-
-/** 灞曠ず褰㈠紡锛氭潯褰㈢爜 */
-const barCode = ref({
- channelCode: '',
- value: '',
- visible: false,
-});
-
-/** 鑾峰緱鏀粯淇℃伅 */
-async function getDetail() {
- // 1. 鑾峰彇璺敱鍙傛暟
- id.value = route.query.id;
- if (route.query.returnUrl) {
- returnUrl.value = decodeURIComponent(route.query.returnUrl as string);
- }
- // 1.1 鏈紶閫掕鍗曠紪鍙�
- if (!id.value) {
- message.error('鏈紶閫掓敮浠樺崟鍙凤紝鏃犳硶鏌ョ湅瀵瑰簲鐨勬敮浠樹俊鎭�');
- goReturnUrl('cancel');
- return;
- }
- const res = await getOrder(id.value);
- // 1.2 鏃犳硶鏌ヨ鍒版敮浠樹俊鎭�
- if (!res) {
- message.error('鏀粯璁㈠崟涓嶅瓨鍦紝璇锋鏌ワ紒');
- goReturnUrl('cancel');
- return;
- }
- // 1.3 濡傛灉宸叉敮浠樸�佹垨鑰呭凡鍏抽棴锛屽垯鐩存帴璺宠浆
- if (res.status === PayOrderStatusEnum.SUCCESS.status) {
- message.success('鏀粯鎴愬姛');
- goReturnUrl('success');
- return;
- } else if (res.status === PayOrderStatusEnum.CLOSED.status) {
- message.error('鏃犳硶鏀粯锛屽師鍥狅細璁㈠崟宸插叧闂�');
- goReturnUrl('close');
- return;
- }
- // 2. 姝e父灞曠ず鏀粯淇℃伅
- payOrder.value = res;
-}
-
-/** 澶勭悊鏀粯 */
-function handlePay(channelCode: string) {
- switch (channelCode) {
- // 鏉″舰鐮佹敮浠橈紝闇�瑕佺壒娈婂鐞�
- case PayChannelEnum.ALIPAY_BAR.code: {
- title.value = '鈥滄敮浠樺疂鈥濇潯鐮佹敮浠�';
- barCode.value = {
- channelCode,
- value: '',
- visible: true,
- };
- modalApi.open();
- break;
- }
- case PayChannelEnum.WX_BAR.code: {
- title.value = '鈥滃井淇♀�濇潯鐮佹敮浠�';
- barCode.value = {
- channelCode,
- value: '',
- visible: true,
- };
- modalApi.open();
- break;
- }
- // 寰俊鍏紬鍙枫�佸皬绋嬪簭鏀粯锛屾棤娉曞湪 PC 缃戦〉涓繘琛�
- case PayChannelEnum.WX_LITE.code: {
- message.error('寰俊灏忕▼搴忥細涓嶆敮鎸� PC 缃戠珯');
- break;
- }
- case PayChannelEnum.WX_PUB.code: {
- message.error('寰俊鍏紬鍙锋敮浠橈細涓嶆敮鎸� PC 缃戠珯');
- break;
- }
- default: {
- submit(channelCode);
- break;
- }
- }
-}
-
-/** 鎻愪氦鏀粯 */
-async function submit(channelCode: string) {
- try {
- const submitParam = {
- id: id.value,
- channelCode,
- returnUrl: location.href, // 鏀粯鎴愬姛鍚庯紝鏀粯娓犻亾璺宠浆鍥炲綋鍓嶉〉锛涘啀鐢卞綋鍓嶉〉锛岃烦杞洖 {@link returnUrl} 瀵瑰簲鐨勫湴鍧�
- ...buildSubmitParam(channelCode),
- };
- const data = await submitOrder(submitParam);
- // 鐩存帴杩斿洖宸叉敮浠樼殑鎯呭喌锛屼緥濡傝鎵爜鏀粯
- if (data.status === PayOrderStatusEnum.SUCCESS.status) {
- clearQueryInterval();
- message.success('鏀粯鎴愬姛锛�');
- goReturnUrl('success');
- return;
- }
-
- // 灞曠ず瀵瑰簲鐨勭晫闈�
- switch (data.displayMode) {
- case PayDisplayModeEnum.APP.mode: {
- displayApp(channelCode);
- break;
- }
- case PayDisplayModeEnum.QR_CODE.mode: {
- displayQrCode(channelCode, data);
- break;
- }
- case PayDisplayModeEnum.URL.mode: {
- displayUrl(data);
- break;
- }
- // No default
- }
-
- // 鎵撳紑杞浠诲姟
- createQueryInterval();
- } finally {
- //
- }
-}
-
-/** 鏋勫缓鎻愪氦鏀粯鐨勯澶栧弬鏁� */
-function buildSubmitParam(channelCode: string) {
- // 鈶� 鏀粯瀹� BarCode 鏀粯鏃讹紝闇�瑕佷紶閫� authCode 鏉″舰鐮�
- if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
- return {
- channelExtras: {
- auth_code: barCode.value.value,
- },
- };
- }
- // 鈶� 寰俊 BarCode 鏀粯鏃讹紝闇�瑕佷紶閫� authCode 鏉″舰鐮�
- if (channelCode === PayChannelEnum.WX_BAR.code) {
- return {
- channelExtras: {
- authCode: barCode.value.value,
- },
- };
- }
- return {};
-}
-
-/** 鎻愪氦鏀粯鍚庯紝URL 鐨勫睍绀哄舰寮� */
-function displayUrl(data: any) {
- location.href = data.displayContent;
-}
-
-/** 鎻愪氦鏀粯鍚庯紙鎵爜鏀粯锛� */
-function displayQrCode(channelCode: string, data: any) {
- title.value = '璇蜂娇鐢ㄦ墜鏈烘祻瑙堝櫒鈥滄壂涓�鎵��';
- if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
- // 鑰冭檻鍒� WAP 娴嬭瘯锛屾墍浠ュ紩瀵兼墜鏈烘祻瑙堝櫒鎼�
- } else if (channelCode.indexOf('alipay_') === 0) {
- title.value = '璇蜂娇鐢ㄦ敮浠樺疂鈥滄壂涓�鎵�濇壂鐮佹敮浠�';
- } else if (channelCode.indexOf('wx_') === 0) {
- title.value = '璇蜂娇鐢ㄥ井淇♀�滄壂涓�鎵�濇壂鐮佹敮浠�';
- }
- qrCode.value = {
- url: data.displayContent,
- visible: true,
- };
-}
-
-/** 鎻愪氦鏀粯鍚庯紙App锛� */
-function displayApp(channelCode: string) {
- if (channelCode === PayChannelEnum.ALIPAY_APP.code) {
- message.error('鏀粯瀹� App 鏀粯锛氭棤娉曞湪缃戦〉鏀粯锛�');
- }
- if (channelCode === PayChannelEnum.WX_APP.code) {
- message.error('寰俊 App 鏀粯锛氭棤娉曞湪缃戦〉鏀粯锛�');
- }
-}
-
-/** 杞鏌ヨ浠诲姟 */
-function createQueryInterval() {
- if (interval.value) {
- return;
- }
- interval.value = setInterval(async () => {
- const data = await getOrder(id.value);
- // 宸叉敮浠�
- if (data.status === PayOrderStatusEnum.SUCCESS.status) {
- clearQueryInterval();
- message.success('鏀粯鎴愬姛锛�');
- goReturnUrl('success');
- }
- // 宸插彇娑�
- if (data.status === PayOrderStatusEnum.CLOSED.status) {
- clearQueryInterval();
- message.error('鏀粯宸插叧闂紒');
- goReturnUrl('close');
- }
- }, 1000 * 2);
-}
-
-/** 娓呯┖鏌ヨ浠诲姟 */
-function clearQueryInterval() {
- // 娓呯┖鏁版嵁
- qrCode.value = {
- url: '',
- visible: false,
- };
- barCode.value = {
- channelCode: '',
- value: '',
- visible: false,
- };
- // 娓呯┖浠诲姟
- clearInterval(interval.value);
- interval.value = undefined;
-}
-
-/**
- * 鍥炲埌涓氬姟鐨� URL
- *
- * @param payResult 鏀粯缁撴灉
- * 鈶� success锛氭敮浠樻垚鍔�
- * 鈶� cancel锛氬彇娑堟敮浠�
- * 鈶� close锛氭敮浠樺凡鍏抽棴
- */
-function goReturnUrl(payResult: string) {
- // 娓呯悊浠诲姟
- clearQueryInterval();
-
- // 鏈厤缃殑鎯呭喌涓嬶紝鍙兘鍏抽棴
- if (!returnUrl.value) {
- closeCurrentTab();
- return;
- }
-
- const url = returnUrl.value.includes('?')
- ? `${returnUrl.value}&payResult=${payResult}`
- : `${returnUrl.value}?payResult=${payResult}`;
- // 濡傛灉鏈夐厤缃紝涓旀槸 http 寮�澶达紝鍒欐祻瑙堝櫒璺宠浆
- if (returnUrl.value.indexOf('http') === 0) {
- location.href = url;
- } else {
- closeCurrentTab();
- push({ path: url });
- }
-}
-
-/** 椤甸潰鍔犺浇鏃讹紝鑾峰彇鏀粯淇℃伅 */
-onMounted(async () => {
- await getDetail();
-});
-
-/** 椤甸潰鍗歌浇鏃讹紝娓呯悊瀹氭椂浠诲姟 */
-onBeforeUnmount(() => {
- clearQueryInterval();
-});
-</script>
-<template>
- <Page auto-content-height>
- <Card class="mt-4">
- <Descriptions :column="3" :title="payOrder?.subject ?? '鍟嗗搧璇︽儏'">
- <Descriptions.Item label="鏀粯鍗曞彿">
- {{ payOrder?.id }}
- </Descriptions.Item>
- <Descriptions.Item label="鍟嗗搧鏍囬">
- {{ payOrder?.subject }}
- </Descriptions.Item>
- <Descriptions.Item label="鍟嗗搧鍐呭">
- {{ payOrder?.body }}
- </Descriptions.Item>
- <Descriptions.Item label="鏀粯閲戦">
- {{ `锟�${fenToYuan(payOrder?.price || 0)}` }}
- </Descriptions.Item>
- <Descriptions.Item label="鍒涘缓鏃堕棿">
- {{ formatDate(payOrder?.createTime) }}
- </Descriptions.Item>
- <Descriptions.Item label="杩囨湡鏃堕棿">
- {{ formatDate(payOrder?.expireTime) }}
- </Descriptions.Item>
- </Descriptions>
- </Card>
- <Card title="閫夋嫨鏀粯瀹濇敮浠�" class="mt-4">
- <div class="flex">
- <div
- class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500"
- v-for="channel in channelsAlipay"
- :key="channel.code"
- @click="handlePay(channel.code)"
- >
- <div class="flex items-center justify-center">
- <component :is="channel.icon" class="h-10 w-10" />
- </div>
- <div class="mt-2 pt-1 text-center">{{ channel.name }}</div>
- </div>
- </div>
- </Card>
- <Card title="閫夋嫨寰俊鏀粯" class="mt-4">
- <div class="flex">
- <div
- class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500"
- v-for="channel in channelsWechat"
- :key="channel.code"
- @click="handlePay(channel.code)"
- >
- <div class="flex items-center justify-center">
- <component :is="channel.icon" class="h-10 w-10" />
- </div>
- <div class="mt-2 pt-1 text-center">{{ channel.name }}</div>
- </div>
- </div>
- </Card>
- <Card title="閫夋嫨鍏跺畠鏀粯" class="mt-4">
- <div class="flex">
- <div
- class="mr-4 w-40 cursor-pointer items-center border-2 border-gray-200 pb-1 pt-4 text-center hover:border-blue-500"
- v-for="channel in channelsMock"
- :key="channel.code"
- @click="handlePay(channel.code)"
- >
- <div class="flex items-center justify-center">
- <component :is="channel.icon" class="h-10 w-10" />
- </div>
- <div class="mt-2 pt-1 text-center">{{ channel.name }}</div>
- </div>
- </div>
- </Card>
- <Modal class="w-2/5" :title="title">
- <QRCode v-if="qrCode.visible" :value="qrCode.url" />
- <Input
- v-if="barCode.visible"
- v-model:value="barCode.value"
- placeholder="璇疯緭鍏ユ潯褰㈢爜"
- required
- />
- <div class="text-right" v-if="barCode.visible">
- 鎴栦娇鐢�
- <Button
- type="link"
- danger
- target="_blank"
- href="https://baike.baidu.com/item/鏉$爜鏀粯/10711903"
- >
- (鎵爜鏋�/鎵爜鐩�)
- </Button>
- 鎵爜
- </div>
- </Modal>
- </Page>
-</template>
diff --git a/src/views/pay/demo/order/data.ts b/src/views/pay/demo/order/data.ts
deleted file mode 100644
index 84759dd..0000000
--- a/src/views/pay/demo/order/data.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { formatDateTime } from '../../../../packages/utils/src';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'spuId',
- label: '鍟嗗搧',
- component: 'Select',
- componentProps: {
- options: [
- { label: '鍗庝负鎵嬫満 --- 1.00鍏�', value: 1 },
- { label: '灏忕背鐢佃 --- 10.00鍏�', value: 2 },
- { label: '鑻规灉鎵嬭〃 --- 100.00鍏�', value: 3 },
- { label: '鍗庣绗旇鏈� --- 1000.00鍏�', value: 4 },
- { label: '钄氭潵姹借溅 --- 200000.00鍏�', value: 5 },
- ],
- placeholder: '璇烽�夋嫨涓嬪崟鍟嗗搧',
- allowClear: true,
- },
- rules: 'required',
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '璁㈠崟缂栧彿',
- minWidth: 100,
- },
- {
- field: 'userId',
- title: '鐢ㄦ埛缂栧彿',
- minWidth: 100,
- },
- {
- field: 'spuName',
- title: '鍟嗗搧鍚嶅瓧',
- minWidth: 150,
- },
- {
- field: 'price',
- title: '鏀粯浠锋牸',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'refundPrice',
- title: '閫�娆鹃噾棰�',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'payOrderId',
- title: '鏀粯鍗曞彿',
- minWidth: 120,
- },
- {
- field: 'payStatus',
- title: '鏄惁鏀粯',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
- },
- },
- {
- field: 'payTime',
- title: '鏀粯鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'refundTime',
- title: '閫�娆炬椂闂�',
- minWidth: 180,
- formatter: ({ cellValue, row }) => {
- if (cellValue) {
- return formatDateTime(cellValue) as string;
- }
- if (row.payRefundId) {
- return '閫�娆句腑锛岀瓑寰呴��娆剧粨鏋�';
- }
- return '';
- },
- },
- {
- title: '鎿嶄綔',
- width: 220,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/pay/demo/order/index.vue b/src/views/pay/demo/order/index.vue
deleted file mode 100644
index 96d41c9..0000000
--- a/src/views/pay/demo/order/index.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DemoOrderApi } from '#/api/pay/demo/order';
-
-import { useRouter } from 'vue-router';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getDemoOrderPage, refundDemoOrder } from '#/api/pay/demo/order';
-
-import { useGridColumns } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const router = useRouter();
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓璁㈠崟 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 鏀粯鎸夐挳鎿嶄綔 */
-function handlePay(row: DemoOrderApi.Order) {
- router.push({
- name: 'PayCashier',
- query: {
- id: row.payOrderId,
- returnUrl: encodeURIComponent(`/pay/demo/order?id=${row.id}`),
- },
- });
-}
-
-/** 閫�娆炬寜閽搷浣� */
-async function handleRefund(row: DemoOrderApi.Order) {
- const hideLoading = message.loading({
- content: '閫�娆句腑锛岃绋嶅悗...',
- duration: 0,
- });
- try {
- await refundDemoOrder(row.id as number);
- message.success('鍙戣捣閫�娆炬垚鍔燂紒');
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDemoOrderPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- },
- } as VxeTableGridOptions<DemoOrderApi.Order>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鏀粯瀹濇敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/alipay-pay-demo/"
- />
- <DocAlert
- title="鏀粯瀹濄�佸井淇¢��娆炬帴鍏�"
- url="https://doc.iocoder.cn/pay/refund-demo/"
- />
- <DocAlert
- title="寰俊鍏紬鍙锋敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/wx-pub-pay-demo/"
- />
- <DocAlert
- title="寰俊灏忕▼搴忔敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/wx-lite-pay-demo/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="绀轰緥璁㈠崟鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: '鍙戣捣璁㈠崟',
- type: 'primary',
- icon: ACTION_ICON.ADD,
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍓嶅線鏀粯',
- type: 'link',
- icon: ACTION_ICON.ADD,
- ifShow: !row.payStatus,
- onClick: handlePay.bind(null, row),
- },
- {
- label: '鍙戣捣閫�娆�',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.EDIT,
- ifShow: row.payStatus && !row.payRefundId,
- popConfirm: {
- title: '纭畾鍙戣捣閫�娆惧悧锛�',
- confirm: handleRefund.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/demo/order/modules/form.vue b/src/views/pay/demo/order/modules/form.vue
deleted file mode 100644
index fc2b71d..0000000
--- a/src/views/pay/demo/order/modules/form.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<script lang="ts" setup>
-import type { DemoOrderApi } from '#/api/pay/demo/order';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createDemoOrder } from '#/api/pay/demo/order';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as DemoOrderApi.Order;
- try {
- await createDemoOrder(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-1/4" title="鍙戣捣璁㈠崟">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/pay/demo/withdraw/data.ts b/src/views/pay/demo/withdraw/data.ts
deleted file mode 100644
index e971d49..0000000
--- a/src/views/pay/demo/withdraw/data.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'subject',
- label: '鎻愮幇鏍囬',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ユ彁鐜版爣棰�',
- },
- },
- {
- fieldName: 'price',
- label: '鎻愮幇閲戦',
- component: 'InputNumber',
- rules: 'required',
- componentProps: {
- class: '!w-full',
- min: 1,
- precision: 2,
- step: 0.01,
- placeholder: '璇疯緭鍏ユ彁鐜伴噾棰�',
- },
- },
- {
- fieldName: 'type',
- label: '鎻愮幇绫诲瀷',
- component: 'Select',
- rules: 'required',
- componentProps: {
- options: [
- { label: '鏀粯瀹�', value: 1 },
- { label: '寰俊浣欓', value: 2 },
- { label: '閽卞寘浣欓', value: 3 },
- ],
- placeholder: '璇烽�夋嫨鎻愮幇绫诲瀷',
- },
- },
- {
- fieldName: 'userAccount',
- label: '鏀舵浜鸿处鍙�',
- component: 'Input',
- rules: 'required',
- dependencies: {
- triggerFields: ['type'],
- componentProps: (values) => {
- const type = values.type;
- let placeholder = '璇疯緭鍏ユ敹娆句汉璐﹀彿';
- switch (type) {
- case 1: {
- placeholder = '璇疯緭鍏ユ敮浠樺疂璐﹀彿';
- break;
- }
- case 2: {
- placeholder = '璇疯緭鍏ュ井淇� openid';
- break;
- }
- case 3: {
- placeholder = '璇疯緭鍏ラ挶鍖呯紪鍙�';
- break;
- }
- }
- return {
- placeholder,
- };
- },
- },
- },
- {
- fieldName: 'userName',
- label: '鏀舵浜哄鍚�',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ユ敹娆句汉濮撳悕',
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鎻愮幇鍗曠紪鍙�',
- minWidth: 100,
- },
- {
- field: 'subject',
- title: '鎻愮幇鏍囬',
- minWidth: 150,
- },
- {
- field: 'type',
- title: '鎻愮幇绫诲瀷',
- minWidth: 100,
- slots: { default: 'type' },
- },
- {
- field: 'price',
- title: '鎻愮幇閲戦',
- minWidth: 100,
- formatter: 'formatAmount2',
- },
- {
- field: 'userName',
- title: '鏀舵浜哄鍚�',
- minWidth: 120,
- },
- {
- field: 'userAccount',
- title: '鏀舵浜鸿处鍙�',
- minWidth: 150,
- },
- {
- field: 'status',
- title: '鎻愮幇鐘舵��',
- minWidth: 100,
- slots: { default: 'status' },
- },
- {
- field: 'payTransferId',
- title: '杞处鍗曞彿',
- minWidth: 120,
- },
- {
- field: 'transferChannelCode',
- title: '杞处娓犻亾',
- minWidth: 130,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
- },
- },
- {
- field: 'transferTime',
- title: '杞处鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'transferErrorMsg',
- title: '杞处澶辫触鍘熷洜',
- minWidth: 150,
- },
- {
- title: '鎿嶄綔',
- width: 220,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/pay/demo/withdraw/index.vue b/src/views/pay/demo/withdraw/index.vue
deleted file mode 100644
index 9613634..0000000
--- a/src/views/pay/demo/withdraw/index.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DemoWithdrawApi } from '#/api/pay/demo/withdraw';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-import { erpPriceInputFormatter } from '../../../../packages/utils/src';
-
-import { message, Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- getDemoWithdrawPage,
- transferDemoWithdraw,
-} from '#/api/pay/demo/withdraw';
-
-import { useGridColumns } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓鎻愮幇鍗� */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 澶勭悊杞处鎿嶄綔 */
-async function handleTransfer(row: DemoWithdrawApi.Withdraw) {
- const hideLoading = message.loading({
- content: '杞处涓紝璇风◢鍚�...',
- duration: 0,
- });
- try {
- const payTransferId = await transferDemoWithdraw(row.id as number);
- message.success(`杞处鎻愪氦鎴愬姛锛岃浆璐﹀崟鍙凤細${payTransferId}`);
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getDemoWithdrawPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<DemoWithdrawApi.Withdraw>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鏀粯瀹濊浆璐︽帴鍏�"
- url="https://doc.iocoder.cn/pay/alipay-transfer-demo/"
- />
- <DocAlert
- title="寰俊杞处鎺ュ叆"
- url="https://doc.iocoder.cn/pay/wx-transfer-demo/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
- <Grid table-title="绀轰緥鎻愮幇鍗曞垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['绀轰緥鎻愮幇鍗�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #type="{ row }">
- <Tag v-if="row.type === 1">鏀粯瀹�</Tag>
- <Tag v-else-if="row.type === 2">寰俊浣欓</Tag>
- <Tag v-else-if="row.type === 3">閽卞寘浣欓</Tag>
- </template>
- <template #price="{ row }">
- <span>锟{ erpPriceInputFormatter(row.price) }}</span>
- </template>
- <template #status="{ row }">
- <Tag v-if="row.status === 0 && !row.payTransferId" type="warning">
- 绛夊緟杞处
- </Tag>
- <Tag v-else-if="row.status === 0 && row.payTransferId" type="info">
- 杞处涓�
- </Tag>
- <Tag v-else-if="row.status === 10" type="success"> 杞处鎴愬姛 </Tag>
- <Tag v-else-if="row.status === 20" type="danger"> 杞处澶辫触 </Tag>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '鍙戣捣杞处',
- type: 'link',
- icon: ACTION_ICON.ADD,
- ifShow: row.status === 0 && !row.payTransferId,
- onClick: handleTransfer.bind(null, row),
- },
- {
- label: '閲嶆柊杞处',
- type: 'link',
- danger: true,
- icon: ACTION_ICON.EDIT,
- ifShow: row.status === 20,
- onClick: handleTransfer.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/demo/withdraw/modules/form.vue b/src/views/pay/demo/withdraw/modules/form.vue
deleted file mode 100644
index e20bce4..0000000
--- a/src/views/pay/demo/withdraw/modules/form.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<script lang="ts" setup>
-import type { DemoWithdrawApi } from '#/api/pay/demo/withdraw';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createDemoWithdraw } from '#/api/pay/demo/withdraw';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 80,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data = (await formApi.getValues()) as DemoWithdrawApi.Withdraw;
- try {
- await createDemoWithdraw(data);
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal class="w-1/4" :title="$t('ui.actionTitle.create', ['绀轰緥鎻愮幇鍗�'])">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/pay/notify/data.ts b/src/views/pay/notify/data.ts
deleted file mode 100644
index a53b70a..0000000
--- a/src/views/pay/notify/data.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-import { formatDateTime } from '../../../packages/utils/src';
-
-import { getAppList } from '#/api/pay/app';
-import { DictTag } from '#/components/dict-tag';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'appId',
- label: '搴旂敤缂栧彿',
- component: 'ApiSelect',
- componentProps: {
- api: getAppList,
- labelField: 'name',
- valueField: 'id',
- autoSelect: 'first',
- placeholder: '璇烽�夋嫨搴旂敤缂栧彿',
- },
- },
- {
- fieldName: 'type',
- label: '閫氱煡绫诲瀷',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.PAY_NOTIFY_TYPE, 'number'),
- placeholder: '璇烽�夋嫨閫氱煡绫诲瀷',
- },
- },
- {
- fieldName: 'dataId',
- label: '鍏宠仈缂栧彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ叧鑱旂紪鍙�',
- },
- },
- {
- fieldName: 'status',
- label: '閫氱煡鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- options: getDictOptions(DICT_TYPE.PAY_NOTIFY_STATUS, 'number'),
- placeholder: '璇烽�夋嫨閫氱煡鐘舵��',
- },
- },
- {
- fieldName: 'merchantOrderId',
- label: '鍟嗘埛璁㈠崟缂栧彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴疯鍗曠紪鍙�',
- },
- },
- {
- fieldName: 'merchantRefundId',
- label: '鍟嗘埛閫�娆剧紪鍙�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴烽��娆剧紪鍙�',
- },
- },
- {
- fieldName: 'merchantTransferId',
- label: '鍟嗘埛杞处缂栧彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴疯浆璐︾紪鍙�',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '浠诲姟缂栧彿',
- minWidth: 100,
- },
- {
- field: 'appName',
- title: '搴旂敤鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'merchantInfo',
- title: '鍟嗘埛鍗曚俊鎭�',
- minWidth: 240,
- slots: {
- default: 'merchantInfo',
- },
- },
- {
- field: 'type',
- title: '閫氱煡绫诲瀷',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_NOTIFY_TYPE },
- },
- },
- {
- field: 'dataId',
- title: '鍏宠仈缂栧彿',
- minWidth: 120,
- },
- {
- field: 'status',
- title: '閫氱煡鐘舵��',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_NOTIFY_STATUS },
- },
- },
- {
- field: 'lastExecuteTime',
- title: '鏈�鍚庨�氱煡鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'nextNotifyTime',
- title: '涓嬫閫氱煡鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'notifyTimes',
- title: '閫氱煡娆℃暟',
- minWidth: 120,
- formatter: ({ row }) => `${row.notifyTimes} / ${row.maxNotifyTimes}`,
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璇︽儏鐨勫瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'appId',
- label: '搴旂敤缂栧彿',
- },
- {
- field: 'appName',
- label: '搴旂敤鍚嶇О',
- },
- {
- field: 'type',
- label: '閫氱煡绫诲瀷',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_NOTIFY_TYPE,
- value: val,
- }),
- },
- {
- field: 'dataId',
- label: '鍏宠仈缂栧彿',
- },
- {
- field: 'status',
- label: '閫氱煡鐘舵��',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_NOTIFY_STATUS,
- value: val,
- }),
- },
- {
- field: 'merchantOrderId',
- label: '鍟嗘埛璁㈠崟缂栧彿',
- },
- {
- field: 'lastExecuteTime',
- label: '鏈�鍚庨�氱煡鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'nextNotifyTime',
- label: '涓嬫閫氱煡鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'notifyTimes',
- label: '閫氱煡娆℃暟',
- },
- {
- field: 'maxNotifyTimes',
- label: '鏈�澶ч�氱煡娆℃暟',
- },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'updateTime',
- label: '鏇存柊鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- ];
-}
-
-/** 璇︽儏鐨勬棩蹇楀瓧娈� */
-export function useDetailLogColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '鏃ュ織缂栧彿',
- minWidth: 120,
- },
- {
- field: 'status',
- title: '閫氱煡鐘舵��',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_NOTIFY_STATUS },
- },
- },
- {
- field: 'notifyTimes',
- title: '閫氱煡娆℃暟',
- minWidth: 120,
- },
- {
- field: 'createTime',
- title: '閫氱煡鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'response',
- title: '鍝嶅簲缁撴灉',
- minWidth: 200,
- },
- ];
-}
diff --git a/src/views/pay/notify/index.vue b/src/views/pay/notify/index.vue
deleted file mode 100644
index ce60334..0000000
--- a/src/views/pay/notify/index.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayNotifyApi } from '#/api/pay/notify';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { $t } from '../../../packages/locales/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getNotifyTaskPage } from '#/api/pay/notify';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: Detail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鏌ョ湅璇︽儏 */
-function handleDetail(row: PayNotifyApi.NotifyTask) {
- detailModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 80,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getNotifyTaskPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayNotifyApi.NotifyTask>,
-});
-</script>
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鏀粯鍔熻兘寮�鍚�" url="https://doc.iocoder.cn/pay/build/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
- <Grid table-title="閫氱煡鍒楄〃">
- <template #merchantInfo="{ row }">
- <div class="flex flex-col gap-1 text-left">
- <p class="text-sm" v-if="row.merchantOrderId">
- <Tag size="small" color="blue">鍟嗘埛璁㈠崟缂栧彿</Tag>
- {{ row.merchantOrderId }}
- </p>
- <p class="text-sm" v-if="row.merchantRefundId">
- <Tag size="small" color="orange">鍟嗘埛閫�娆剧紪鍙�</Tag>
- {{ row.merchantRefundId }}
- </p>
- <p class="text-sm" v-if="row.merchantTransferId">
- <Tag size="small" color="green">鍟嗘埛杞处缂栧彿</Tag>
- {{ row.merchantTransferId }}
- </p>
- </div>
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['pay:notify:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/notify/modules/detail.vue b/src/views/pay/notify/modules/detail.vue
deleted file mode 100644
index c237d94..0000000
--- a/src/views/pay/notify/modules/detail.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayNotifyApi } from '#/api/pay/notify';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { Divider } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getNotifyTaskDetail } from '#/api/pay/notify';
-import { useDescription } from '#/components/description';
-
-import { useDetailLogColumns, useDetailSchema } from '../data';
-
-const formData = ref<PayNotifyApi.NotifyTask>();
-
-const [Description] = useDescription({
- bordered: true,
- column: 2,
- schema: useDetailSchema(),
-});
-
-const [LogGrid, logGridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useDetailLogColumns(),
- height: 'auto',
- keepSource: true,
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- pagerConfig: {
- enabled: false,
- },
- toolbarConfig: {
- enabled: true,
- refresh: true,
- },
- } as VxeTableGridOptions,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<PayNotifyApi.NotifyTask>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getNotifyTaskDetail(data.id);
- logGridApi.grid?.reloadData(formData.value?.logs || []);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal
- title="閫氱煡璇︽儏"
- class="w-1/2"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <Description :data="formData" />
-
- <Divider />
-
- <LogGrid table-title="鏀粯閫氱煡鍒楄〃" />
- </Modal>
-</template>
diff --git a/src/views/pay/order/data.ts b/src/views/pay/order/data.ts
deleted file mode 100644
index b1037a0..0000000
--- a/src/views/pay/order/data.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-import { erpPriceInputFormatter, formatDateTime } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'appId',
- label: '搴旂敤缂栧彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ簲鐢ㄧ紪鍙�',
- },
- },
- {
- fieldName: 'channelCode',
- label: '鏀粯娓犻亾',
- component: 'Select',
- componentProps: {
- allowClear: true,
- placeholder: '璇烽�夋嫨鏀粯娓犻亾',
- options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'),
- },
- },
- {
- fieldName: 'merchantOrderId',
- label: '鍟嗘埛鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴峰崟鍙�',
- },
- },
- {
- fieldName: 'no',
- label: '鏀粯鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ敮浠樺崟鍙�',
- },
- },
- {
- fieldName: 'channelOrderNo',
- label: '娓犻亾鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ笭閬撳崟鍙�',
- },
- },
- {
- fieldName: 'status',
- label: '鏀粯鐘舵��',
- component: 'Select',
- componentProps: {
- allowClear: true,
- placeholder: '璇烽�夋嫨鏀粯鐘舵��',
- options: getDictOptions(DICT_TYPE.PAY_ORDER_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'price',
- title: '鏀粯閲戦',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'refundPrice',
- title: '閫�娆鹃噾棰�',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'channelFeePrice',
- title: '鎵嬬画閲戦',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'no',
- title: '璁㈠崟鍙�',
- minWidth: 240,
- slots: {
- default: 'no',
- },
- },
- {
- field: 'status',
- title: '鏀粯鐘舵��',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_ORDER_STATUS },
- },
- },
- {
- field: 'channelCode',
- title: '鏀粯娓犻亾',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
- },
- },
- {
- field: 'successTime',
- title: '鏀粯鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'appName',
- title: '鏀粯搴旂敤',
- minWidth: 150,
- },
- {
- field: 'subject',
- title: '鍟嗗搧鏍囬',
- minWidth: 200,
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璇︽儏鐨勫瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'merchantOrderId',
- label: '鍟嗘埛鍗曞彿',
- },
- {
- field: 'no',
- label: '鏀粯鍗曞彿',
- },
- {
- field: 'appId',
- label: '搴旂敤缂栧彿',
- },
- {
- field: 'appName',
- label: '搴旂敤鍚嶇О',
- },
- {
- field: 'status',
- label: '鏀粯鐘舵��',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_ORDER_STATUS,
- value: val,
- }),
- },
- {
- field: 'price',
- label: '鏀粯閲戦',
- render: (val) => `锟�${erpPriceInputFormatter(val)}`,
- },
- {
- field: 'channelFeePrice',
- label: '鎵嬬画璐�',
- render: (val) => `锟�${erpPriceInputFormatter(val)}`,
- },
- {
- field: 'channelFeeRate',
- label: '鎵嬬画璐规瘮渚�',
- render: (val) => `${erpPriceInputFormatter(val)}%`,
- },
- {
- field: 'successTime',
- label: '鏀粯鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'expireTime',
- label: '澶辨晥鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'updateTime',
- label: '鏇存柊鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'subject',
- label: '鍟嗗搧鏍囬',
- },
- {
- field: 'body',
- label: '鍟嗗搧鎻忚堪',
- },
- {
- field: 'channelCode',
- label: '鏀粯娓犻亾',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_CHANNEL_CODE,
- value: val,
- }),
- },
- {
- field: 'userIp',
- label: '鏀粯 IP',
- },
- {
- field: 'channelOrderNo',
- label: '娓犻亾鍗曞彿',
- render: (val) => (val ? h(Tag, { color: 'green' }, () => val) : ''),
- },
- {
- field: 'channelUserId',
- label: '娓犻亾鐢ㄦ埛',
- },
- {
- field: 'refundPrice',
- label: '閫�娆鹃噾棰�',
- render: (val) => `锟�${erpPriceInputFormatter(val)}`,
- },
- {
- field: 'notifyUrl',
- label: '閫氱煡 URL',
- },
- {
- field: 'channelNotifyData',
- label: '鏀粯閫氶亾寮傛鍥炶皟鍐呭',
- },
- ];
-}
diff --git a/src/views/pay/order/index.vue b/src/views/pay/order/index.vue
deleted file mode 100644
index 17154ff..0000000
--- a/src/views/pay/order/index.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayOrderApi } from '#/api/pay/order';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { downloadFileFromBlobPart } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { exportOrder, getOrderPage } from '#/api/pay/order';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: Detail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瀵煎嚭鏀粯璁㈠崟 */
-async function handleExport() {
- const data = await exportOrder(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '鏀粯璁㈠崟.xls', source: data });
-}
-
-/** 鏌ョ湅璇︽儏 */
-function handleDetail(row: PayOrderApi.Order) {
- detailModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 80,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getOrderPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayOrderApi.Order>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鏀粯瀹濇敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/alipay-pay-demo/"
- />
- <DocAlert
- title="寰俊鍏紬鍙锋敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/wx-pub-pay-demo/"
- />
- <DocAlert
- title="寰俊灏忕▼搴忔敮浠樻帴鍏�"
- url="https://doc.iocoder.cn/pay/wx-lite-pay-demo/"
- />
- </template>
- <DetailModal @success="handleRefresh" />
- <Grid table-title="鏀粯璁㈠崟鍒楄〃">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.export', ['鏀粯璁㈠崟']),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['pay:order:export'],
- onClick: handleExport,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['pay:order:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- <template #no="{ row }">
- <div class="flex flex-col gap-1 text-left">
- <p class="text-sm">
- <Tag size="small" color="blue"> 鍟嗘埛</Tag> {{ row.merchantOrderId }}
- </p>
- <p class="text-sm" v-if="row.no">
- <Tag size="small" color="orange">鏀粯</Tag> {{ row.no }}
- </p>
- <p class="text-sm" v-if="row.channelOrderNo">
- <Tag size="small" color="green">娓犻亾</Tag>
- {{ row.channelOrderNo }}
- </p>
- </div>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/order/modules/detail.vue b/src/views/pay/order/modules/detail.vue
deleted file mode 100644
index a85b07a..0000000
--- a/src/views/pay/order/modules/detail.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<script lang="ts" setup>
-import type { PayOrderApi } from '#/api/pay/order';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { getOrderDetail } from '#/api/pay/order';
-import { useDescription } from '#/components/description';
-
-import { useDetailSchema } from '../data';
-
-const formData = ref<PayOrderApi.Order>();
-
-const [Descriptions] = useDescription({
- bordered: true,
- column: 2,
- schema: useDetailSchema(),
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<PayOrderApi.Order>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getOrderDetail(data.id);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-<template>
- <Modal
- title="璁㈠崟璇︽儏"
- class="w-1/2"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <Descriptions :data="formData" />
- </Modal>
-</template>
diff --git a/src/views/pay/refund/data.ts b/src/views/pay/refund/data.ts
deleted file mode 100644
index 1eeb0ec..0000000
--- a/src/views/pay/refund/data.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-import { erpPriceInputFormatter, formatDateTime } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'appId',
- label: '搴旂敤缂栧彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ簲鐢ㄧ紪鍙�',
- },
- },
- {
- fieldName: 'channelCode',
- label: '閫�娆炬笭閬�',
- component: 'Select',
- componentProps: {
- allowClear: true,
- placeholder: '璇烽�夋嫨閫�娆炬笭閬�',
- options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'),
- },
- },
- {
- fieldName: 'merchantOrderId',
- label: '鍟嗘埛鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴峰崟鍙�',
- },
- },
- {
- fieldName: 'merchantRefundId',
- label: '閫�娆惧崟鍙�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ラ��娆惧崟鍙�',
- },
- },
- {
- fieldName: 'channelOrderNo',
- label: '娓犻亾鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ笭閬撳崟鍙�',
- },
- },
- {
- fieldName: 'channelRefundNo',
- label: '娓犻亾閫�娆惧崟鍙�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ笭閬撻��娆惧崟鍙�',
- },
- },
- {
- fieldName: 'status',
- label: '閫�娆剧姸鎬�',
- component: 'Select',
- componentProps: {
- allowClear: true,
- placeholder: '璇烽�夋嫨閫�娆剧姸鎬�',
- options: getDictOptions(DICT_TYPE.PAY_REFUND_STATUS, 'number'),
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'payPrice',
- title: '鏀粯閲戦',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'refundPrice',
- title: '閫�娆鹃噾棰�',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'merchantRefundId',
- title: '閫�娆惧崟鍙�',
- minWidth: 240,
- slots: {
- default: 'no',
- },
- },
- {
- field: 'status',
- title: '閫�娆剧姸鎬�',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_REFUND_STATUS },
- },
- },
- {
- field: 'channelCode',
- title: '閫�娆炬笭閬�',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
- },
- },
- {
- field: 'successTime',
- title: '閫�娆炬椂闂�',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'reason',
- title: '閫�娆惧師鍥�',
- minWidth: 200,
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璇︽儏鐨勫瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- // 鍩烘湰淇℃伅閮ㄥ垎
- {
- field: 'merchantRefundId',
- label: '鍟嗘埛閫�娆惧崟鍙�',
- render: (val) => h(Tag, {}, () => val || '-'),
- },
- {
- field: 'channelRefundNo',
- label: '娓犻亾閫�娆惧崟鍙�',
- render: (val) => h(Tag, { color: 'success' }, () => val || '-'),
- },
- {
- field: 'merchantOrderId',
- label: '鍟嗘埛鏀粯鍗曞彿',
- render: (val) => h(Tag, {}, () => val || '-'),
- },
- {
- field: 'channelOrderNo',
- label: '娓犻亾鏀粯鍗曞彿',
- render: (val) => h(Tag, { color: 'success' }, () => val || '-'),
- },
- {
- field: 'appId',
- label: '搴旂敤缂栧彿',
- },
- {
- field: 'appName',
- label: '搴旂敤鍚嶇О',
- },
- {
- field: 'payPrice',
- label: '鏀粯閲戦',
- render: (val) =>
- h(
- Tag,
- { color: 'success' },
- () => `锟�${erpPriceInputFormatter(val || 0)}`,
- ),
- },
- {
- field: 'refundPrice',
- label: '閫�娆鹃噾棰�',
- render: (val) =>
- h(
- Tag,
- { color: 'danger' },
- () => `锟�${erpPriceInputFormatter(val || 0)}`,
- ),
- },
- {
- field: 'status',
- label: '閫�娆剧姸鎬�',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_REFUND_STATUS,
- value: val,
- }),
- },
- {
- field: 'successTime',
- label: '閫�娆炬椂闂�',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'updateTime',
- label: '鏇存柊鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- // 娓犻亾淇℃伅閮ㄥ垎
- {
- field: 'channelCode',
- label: '閫�娆炬笭閬�',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_CHANNEL_CODE,
- value: val,
- }),
- },
- {
- field: 'reason',
- label: '閫�娆惧師鍥�',
- },
- {
- field: 'userIp',
- label: '閫�娆� IP',
- },
- {
- field: 'notifyUrl',
- label: '閫氱煡 URL',
- },
- // 閿欒淇℃伅閮ㄥ垎
- {
- field: 'channelErrorCode',
- label: '娓犻亾閿欒鐮�',
- },
- {
- field: 'channelErrorMsg',
- label: '娓犻亾閿欒鐮佹弿杩�',
- },
- {
- field: 'channelNotifyData',
- label: '鏀粯閫氶亾寮傛鍥炶皟鍐呭',
- },
- ];
-}
diff --git a/src/views/pay/refund/index.vue b/src/views/pay/refund/index.vue
deleted file mode 100644
index b9b9f10..0000000
--- a/src/views/pay/refund/index.vue
+++ /dev/null
@@ -1,124 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayRefundApi } from '#/api/pay/refund';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { downloadFileFromBlobPart } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { exportRefund, getRefundPage } from '#/api/pay/refund';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: Detail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瀵煎嚭閫�娆捐鍗� */
-async function handleExport() {
- const data = await exportRefund(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '閫�娆捐鍗�.xls', source: data });
-}
-
-/** 鏌ョ湅璇︽儏 */
-function handleDetail(row: PayRefundApi.Refund) {
- detailModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 80,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getRefundPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayRefundApi.Refund>,
-});
-</script>
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鏀粯瀹濄�佸井淇¢��娆炬帴鍏�"
- url="https://doc.iocoder.cn/pay/refund-demo/"
- />
- </template>
- <DetailModal @success="handleRefresh" />
- <Grid table-title="鏀粯閫�娆惧垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.export', ['閫�娆捐鍗�']),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['pay:refund:export'],
- onClick: handleExport,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['pay:refund:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- <template #no="{ row }">
- <div class="flex flex-col gap-1 text-left">
- <p class="text-sm">
- <Tag size="small" color="blue"> 鍟嗘埛</Tag> {{ row.merchantOrderId }}
- </p>
- <p class="text-sm" v-if="row.merchantRefundId">
- <Tag size="small" color="orange">閫�娆�</Tag>
- {{ row.merchantRefundId }}
- </p>
- <p class="text-sm" v-if="row.channelRefundNo">
- <Tag size="small" color="green">娓犻亾</Tag>
- {{ row.channelRefundNo }}
- </p>
- </div>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/refund/modules/detail.vue b/src/views/pay/refund/modules/detail.vue
deleted file mode 100644
index 659b083..0000000
--- a/src/views/pay/refund/modules/detail.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script lang="ts" setup>
-import type { PayRefundApi } from '#/api/pay/refund';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { getRefund } from '#/api/pay/refund';
-import { useDescription } from '#/components/description';
-
-import { useDetailSchema } from '../data';
-
-const formData = ref<PayRefundApi.Refund>();
-
-const [Descriptions] = useDescription({
- bordered: true,
- column: 2,
- schema: useDetailSchema(),
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<PayRefundApi.Refund>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getRefund(data.id);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal
- title="閫�娆捐鎯�"
- class="w-1/2"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <Descriptions :data="formData" />
- </Modal>
-</template>
diff --git a/src/views/pay/transfer/data.ts b/src/views/pay/transfer/data.ts
deleted file mode 100644
index 24d14ba..0000000
--- a/src/views/pay/transfer/data.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { DescriptionItemSchema } from '#/components/description';
-
-import { h } from 'vue';
-
-import { DICT_TYPE } from '../../../packages/constants/src';
-import { getDictOptions } from '../../../packages/effects/hooks/src';
-import { erpPriceInputFormatter, formatDateTime } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { DictTag } from '#/components/dict-tag';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'no',
- label: '杞处鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ヨ浆璐﹀崟鍙�',
- },
- },
- {
- fieldName: 'channelCode',
- label: '杞处娓犻亾',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE),
- allowClear: true,
- placeholder: '璇烽�夋嫨鏀粯娓犻亾',
- },
- },
- {
- fieldName: 'merchantTransferId',
- label: '鍟嗘埛鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ュ晢鎴峰崟鍙�',
- },
- },
- {
- fieldName: 'type',
- label: '绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PAY_TRANSFER_TYPE),
- allowClear: true,
- placeholder: '璇烽�夋嫨绫诲瀷',
- },
- },
- {
- fieldName: 'status',
- label: '杞处鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.PAY_TRANSFER_STATUS),
- allowClear: true,
- placeholder: '璇烽�夋嫨杞处鐘舵��',
- },
- },
- {
- fieldName: 'userName',
- label: '鏀舵浜哄鍚�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ敹娆句汉濮撳悕',
- },
- },
- {
- fieldName: 'userAccount',
- label: '鏀舵浜鸿处鍙�',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ敹娆句汉璐﹀彿',
- },
- },
- {
- fieldName: 'channelTransferNo',
- label: '娓犻亾鍗曞彿',
- component: 'Input',
- componentProps: {
- allowClear: true,
- placeholder: '璇疯緭鍏ユ笭閬撳崟鍙�',
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'price',
- title: '杞处閲戦',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'merchantTransferId',
- title: '杞处鍗曞彿',
- minWidth: 350,
- slots: {
- default: 'no',
- },
- },
- {
- field: 'status',
- title: '杞处鐘舵��',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_TRANSFER_STATUS },
- },
- },
- {
- field: 'channelCode',
- title: '杞处娓犻亾',
- minWidth: 140,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.PAY_CHANNEL_CODE },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'successTime',
- title: '杞处鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- field: 'subject',
- title: '杞处鏍囬',
- minWidth: 150,
- },
- {
- field: 'appName',
- title: '鏀粯搴旂敤',
- minWidth: 150,
- },
- {
- field: 'userName',
- title: '鏀舵浜哄鍚�',
- minWidth: 150,
- },
- {
- field: 'userAccount',
- title: '鏀舵璐﹀彿',
- minWidth: 200,
- },
- {
- title: '鎿嶄綔',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 璇︽儏鐨勫瓧娈� */
-export function useDetailSchema(): DescriptionItemSchema[] {
- return [
- {
- field: 'merchantTransferId',
- label: '鍟嗘埛鍗曞彿',
- render: (val) => h(Tag, {}, () => val),
- },
- {
- field: 'no',
- label: '杞处鍗曞彿',
- render: (val) => h(Tag, { color: 'orange' }, () => val),
- },
- {
- field: 'appId',
- label: '搴旂敤缂栧彿',
- },
- {
- field: 'status',
- label: '杞处鐘舵��',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_TRANSFER_STATUS,
- value: val,
- }),
- },
- {
- field: 'price',
- label: '杞处閲戦',
- render: (val) =>
- h(
- Tag,
- { color: 'success' },
- () => `锟�${erpPriceInputFormatter(val || 0)}`,
- ),
- },
- {
- field: 'successTime',
- label: '杞处鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'createTime',
- label: '鍒涘缓鏃堕棿',
- render: (val) => formatDateTime(val) as string,
- },
- {
- field: 'userName',
- label: '鏀舵浜哄鍚�',
- },
- {
- field: 'userAccount',
- label: '鏀舵浜鸿处鍙�',
- },
- {
- field: 'channelCode',
- label: '鏀粯娓犻亾',
- render: (val) =>
- h(DictTag, {
- type: DICT_TYPE.PAY_CHANNEL_CODE,
- value: val,
- }),
- },
- {
- field: 'userIp',
- label: '鏀粯 IP',
- },
- {
- field: 'channelTransferNo',
- label: '娓犻亾鍗曞彿',
- render: (val) => (val ? h(Tag, { color: 'success' }, () => val) : ''),
- },
- {
- field: 'notifyUrl',
- label: '閫氱煡 URL',
- },
- {
- field: 'channelNotifyData',
- label: '杞处娓犻亾閫氱煡鍐呭',
- },
- ];
-}
diff --git a/src/views/pay/transfer/index.vue b/src/views/pay/transfer/index.vue
deleted file mode 100644
index 5a16660..0000000
--- a/src/views/pay/transfer/index.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayTransferApi } from '#/api/pay/transfer';
-
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
-import { downloadFileFromBlobPart } from '../../../packages/utils/src';
-
-import { Tag } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { exportTransfer, getTransferPage } from '#/api/pay/transfer';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: Detail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 瀵煎嚭杞处鍗� */
-async function handleExport() {
- const data = await exportTransfer(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '杞处鍗�.xls', source: data });
-}
-
-/** 鏌ョ湅杞处璇︽儏 */
-function handleDetail(row: PayTransferApi.Transfer) {
- detailModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- cellConfig: {
- height: 80,
- },
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getTransferPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayTransferApi.Transfer>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="杞处绠$悊" url="https://doc.iocoder.cn/pay/transfer/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
- <Grid table-title="杞处鍗曞垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.export', ['杞处鍗�']),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['pay:transfer:export'],
- onClick: handleExport,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['pay:transfer:query'],
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- <template #no="{ row }">
- <div class="flex flex-col gap-1 text-left">
- <p class="text-sm">
- <Tag size="small" color="blue"> 鍟嗘埛</Tag>
- {{ row.merchantTransferId }}
- </p>
- <p class="text-sm" v-if="row.no">
- <Tag size="small" color="orange">杞处</Tag> {{ row.no }}
- </p>
- <p class="text-sm" v-if="row.channelTransferNo">
- <Tag size="small" color="green">娓犻亾</Tag>
- {{ row.channelTransferNo }}
- </p>
- </div>
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/transfer/modules/detail.vue b/src/views/pay/transfer/modules/detail.vue
deleted file mode 100644
index 8345e92..0000000
--- a/src/views/pay/transfer/modules/detail.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<script lang="ts" setup>
-import type { PayTransferApi } from '#/api/pay/transfer';
-
-import { ref } from 'vue';
-
-import { useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { getTransfer } from '#/api/pay/transfer';
-import { useDescription } from '#/components/description';
-
-import { useDetailSchema } from '../data';
-
-const formData = ref<PayTransferApi.Transfer>();
-
-const [Descriptions] = useDescription({
- bordered: true,
- column: 2,
- schema: useDetailSchema(),
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data = modalApi.getData<PayTransferApi.Transfer>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getTransfer(data.id);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal
- title="杞处鍗曡鎯�"
- class="w-1/2"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <Descriptions :data="formData" />
- </Modal>
-</template>
diff --git a/src/views/pay/wallet/balance/data.ts b/src/views/pay/wallet/balance/data.ts
deleted file mode 100644
index 52b9080..0000000
--- a/src/views/pay/wallet/balance/data.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'userId',
- label: '鐢ㄦ埛缂栧彿',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ョ敤鎴风紪鍙�',
- allowClear: true,
- },
- },
- {
- fieldName: 'userType',
- label: '鐢ㄦ埛绫诲瀷',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'),
- placeholder: '璇烽�夋嫨鐢ㄦ埛绫诲瀷',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- title: '缂栧彿',
- field: 'id',
- minWidth: 100,
- },
- {
- title: '鐢ㄦ埛缂栧彿',
- field: 'userId',
- minWidth: 120,
- },
- {
- title: '鐢ㄦ埛绫诲瀷',
- field: 'userType',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.USER_TYPE },
- },
- },
- {
- title: '浣欓',
- field: 'balance',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- title: '绱鏀嚭',
- field: 'totalExpense',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- title: '绱鍏呭��',
- field: 'totalRecharge',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- title: '鍐荤粨閲戦',
- field: 'freezePrice',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- title: '鍒涘缓鏃堕棿',
- field: 'createTime',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- field: 'actions',
- width: 80,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
-
-/** 閽卞寘浜ゆ槗璁板綍鍒楄〃瀛楁 */
-export function useTransactionGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '缂栧彿',
- minWidth: 100,
- },
- {
- field: 'title',
- title: '鍏宠仈涓氬姟鏍囬',
- minWidth: 200,
- },
- {
- field: 'price',
- title: '浜ゆ槗閲戦',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'balance',
- title: '閽卞寘浣欓',
- minWidth: 120,
- formatter: 'formatAmount2',
- },
- {
- field: 'createTime',
- title: '浜ゆ槗鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- ];
-}
diff --git a/src/views/pay/wallet/balance/index.vue b/src/views/pay/wallet/balance/index.vue
deleted file mode 100644
index 0f8910b..0000000
--- a/src/views/pay/wallet/balance/index.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayWalletApi } from '#/api/pay/wallet/balance';
-
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getWalletPage } from '#/api/pay/wallet/balance';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Detail from './modules/detail.vue';
-
-const [DetailModal, detailModalApi] = useVbenModal({
- connectedComponent: Detail,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鏌ョ湅閽卞寘 */
-function handleDetail(row: Required<PayWalletApi.Wallet>) {
- detailModalApi.setData(row).open();
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getWalletPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<PayWalletApi.Wallet>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="閽卞寘浣欓" url="https://doc.iocoder.cn/pay/build/" />
- </template>
-
- <DetailModal @reload="handleRefresh" />
- <Grid>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.detail'),
- type: 'link',
- icon: ACTION_ICON.VIEW,
- onClick: handleDetail.bind(null, row),
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/wallet/balance/modules/detail.vue b/src/views/pay/wallet/balance/modules/detail.vue
deleted file mode 100644
index b167aa7..0000000
--- a/src/views/pay/wallet/balance/modules/detail.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script setup lang="ts">
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { PayWalletApi } from '#/api/pay/wallet/balance';
-import type { WalletTransactionApi } from '#/api/pay/wallet/transaction';
-
-import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getTransactionPage } from '#/api/pay/wallet/transaction';
-
-import { useTransactionGridColumns } from '../data';
-
-const [Grid, gridApi] = useVbenVxeGrid({
- gridOptions: {
- columns: useTransactionGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }) => {
- return await getTransactionPage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- walletId: modalApi.getData<PayWalletApi.Wallet>().id,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- enabled: false,
- },
- } as VxeTableGridOptions<WalletTransactionApi.Transaction>,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- return;
- }
- // 鍔犺浇鏁版嵁
- modalApi.lock();
- try {
- await gridApi.query();
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-<template>
- <Modal
- title="閽卞寘浜ゆ槗璁板綍"
- class="w-1/2"
- :show-cancel-button="false"
- :show-confirm-button="false"
- >
- <Page auto-content-height>
- <Grid />
- </Page>
- </Modal>
-</template>
diff --git a/src/views/pay/wallet/rechargePackage/data.ts b/src/views/pay/wallet/rechargePackage/data.ts
deleted file mode 100644
index b46d67e..0000000
--- a/src/views/pay/wallet/rechargePackage/data.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
-
-import { z } from '#/adapter/form';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 鏂板/淇敼鐨勮〃鍗� */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'id',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '濂楅鍚嶇О',
- component: 'Input',
- rules: 'required',
- componentProps: {
- placeholder: '璇疯緭鍏ュ椁愬悕绉�',
- },
- },
- {
- fieldName: 'payPrice',
- label: '鏀粯閲戦(鍏�)',
- component: 'InputNumber',
- rules: z.number().min(0, '鏀粯閲戦涓嶈兘灏忎簬0'),
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- step: 0.01,
- placeholder: '璇疯緭鍏ユ敮浠橀噾棰�',
- },
- },
- {
- fieldName: 'bonusPrice',
- label: '璧犻�侀噾棰�(鍏�)',
- component: 'InputNumber',
- rules: z.number().min(0, '璧犻�侀噾棰濅笉鑳藉皬浜�0'),
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 2,
- step: 0.01,
- placeholder: '璇疯緭鍏ヨ禒閫侀噾棰�',
- },
- },
- {
- fieldName: 'status',
- label: '寮�鍚姸鎬�',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- rules: z.number().default(CommonStatusEnum.ENABLE),
- },
- ];
-}
-
-/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '濂楅鍚嶇О',
- component: 'Input',
- componentProps: {
- placeholder: '璇疯緭鍏ュ椁愬悕绉�',
- allowClear: true,
- },
- },
- {
- fieldName: 'status',
- label: '鐘舵��',
- component: 'Select',
- componentProps: {
- options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
- placeholder: '璇烽�夋嫨鐘舵��',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '鍒涘缓鏃堕棿',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 鍒楄〃鐨勫瓧娈� */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- field: 'id',
- title: '濂楅缂栧彿',
- minWidth: 100,
- },
- {
- field: 'name',
- title: '濂楅鍚嶇О',
- minWidth: 150,
- },
- {
- field: 'payPrice',
- title: '鏀粯閲戦',
- minWidth: 120,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'bonusPrice',
- title: '璧犻�侀噾棰�',
- minWidth: 120,
- formatter: 'formatFenToYuanAmount',
- },
- {
- field: 'status',
- title: '鐘舵��',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.COMMON_STATUS },
- },
- },
- {
- field: 'createTime',
- title: '鍒涘缓鏃堕棿',
- minWidth: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '鎿嶄綔',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/src/views/pay/wallet/rechargePackage/index.vue b/src/views/pay/wallet/rechargePackage/index.vue
deleted file mode 100644
index 4e92772..0000000
--- a/src/views/pay/wallet/rechargePackage/index.vue
+++ /dev/null
@@ -1,130 +0,0 @@
-<script lang="ts" setup>
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { WalletRechargePackageApi } from '#/api/pay/wallet/rechargePackage';
-
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
-
-import { message } from 'ant-design-vue';
-
-import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import {
- deleteWalletRechargePackage,
- getWalletRechargePackagePage,
-} from '#/api/pay/wallet/rechargePackage';
-import { $t } from '#/locales';
-
-import { useGridColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
- connectedComponent: Form,
- destroyOnClose: true,
-});
-
-/** 鍒锋柊琛ㄦ牸 */
-function handleRefresh() {
- gridApi.query();
-}
-
-/** 鍒涘缓濂楅 */
-function handleCreate() {
- formModalApi.setData(null).open();
-}
-
-/** 缂栬緫濂楅 */
-function handleEdit(row: WalletRechargePackageApi.WalletRechargePackage) {
- formModalApi.setData(row).open();
-}
-
-/** 鍒犻櫎濂楅 */
-async function handleDelete(
- row: WalletRechargePackageApi.WalletRechargePackage,
-) {
- const hideLoading = message.loading({
- content: $t('ui.actionMessage.deleting', [row.name]),
- duration: 0,
- });
- try {
- await deleteWalletRechargePackage(row.id!);
- message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
- formOptions: {
- schema: useGridFormSchema(),
- },
- gridOptions: {
- columns: useGridColumns(),
- height: 'auto',
- keepSource: true,
- proxyConfig: {
- ajax: {
- query: async ({ page }, formValues) => {
- return await getWalletRechargePackagePage({
- pageNo: page.currentPage,
- pageSize: page.pageSize,
- ...formValues,
- });
- },
- },
- },
- rowConfig: {
- keyField: 'id',
- isHover: true,
- },
- toolbarConfig: {
- refresh: true,
- search: true,
- },
- } as VxeTableGridOptions<WalletRechargePackageApi.WalletRechargePackage>,
-});
-</script>
-
-<template>
- <Page auto-content-height>
- <FormModal @success="handleRefresh" />
- <Grid table-title="鍏呭�煎椁愬垪琛�">
- <template #toolbar-tools>
- <TableAction
- :actions="[
- {
- label: $t('ui.actionTitle.create', ['鍏呭�煎椁�']),
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['pay:wallet-recharge-package:create'],
- onClick: handleCreate,
- },
- ]"
- />
- </template>
- <template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['pay:wallet-recharge-package:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['pay:wallet-recharge-package:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
- </template>
- </Grid>
- </Page>
-</template>
diff --git a/src/views/pay/wallet/rechargePackage/modules/form.vue b/src/views/pay/wallet/rechargePackage/modules/form.vue
deleted file mode 100644
index b9b646b..0000000
--- a/src/views/pay/wallet/rechargePackage/modules/form.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script lang="ts" setup>
-import type { WalletRechargePackageApi } from '#/api/pay/wallet/rechargePackage';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { fenToYuan, yuanToFen } from '../../../../../packages/utils/src';
-
-import { message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import {
- createWalletRechargePackage,
- getWalletRechargePackage,
- updateWalletRechargePackage,
-} from '#/api/pay/wallet/rechargePackage';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<WalletRechargePackageApi.WalletRechargePackage>();
-const getTitle = computed(() => {
- return formData.value?.id
- ? $t('ui.actionTitle.edit', ['鍏呭�煎椁�'])
- : $t('ui.actionTitle.create', ['鍏呭�煎椁�']);
-});
-
-const [Form, formApi] = useVbenForm({
- commonConfig: {
- componentProps: {
- class: 'w-full',
- },
- formItemClass: 'col-span-2',
- labelWidth: 100,
- },
- layout: 'horizontal',
- schema: useFormSchema(),
- showDefaultActions: false,
-});
-
-const [Modal, modalApi] = useVbenModal({
- async onConfirm() {
- const { valid } = await formApi.validate();
- if (!valid) {
- return;
- }
- modalApi.lock();
- // 鎻愪氦琛ㄥ崟
- const data =
- (await formApi.getValues()) as WalletRechargePackageApi.WalletRechargePackage;
- try {
- // 杞崲閲戦鍗曚綅
- data.payPrice = yuanToFen(data.payPrice);
- data.bonusPrice = yuanToFen(data.bonusPrice);
- await (formData.value?.id
- ? updateWalletRechargePackage(data)
- : createWalletRechargePackage(data));
- // 鍏抽棴骞舵彁绀�
- await modalApi.close();
- emit('success');
- message.success($t('ui.actionMessage.operationSuccess'));
- } finally {
- modalApi.unlock();
- }
- },
- async onOpenChange(isOpen: boolean) {
- if (!isOpen) {
- formData.value = undefined;
- return;
- }
- // 鍔犺浇鏁版嵁
- const data =
- modalApi.getData<WalletRechargePackageApi.WalletRechargePackage>();
- if (!data || !data.id) {
- return;
- }
- modalApi.lock();
- try {
- formData.value = await getWalletRechargePackage(data.id);
- // 杞崲閲戦鍗曚綅
- formData.value.payPrice = Number.parseFloat(
- fenToYuan(formData.value.payPrice),
- );
- formData.value.bonusPrice = Number.parseFloat(
- fenToYuan(formData.value.bonusPrice),
- );
- // 璁剧疆鍒� values
- await formApi.setValues(formData.value);
- } finally {
- modalApi.unlock();
- }
- },
-});
-</script>
-
-<template>
- <Modal :title="getTitle" class="w-1/4">
- <Form class="mx-4" />
- </Modal>
-</template>
diff --git a/src/views/report/goview/index.vue b/src/views/report/goview/index.vue
index 8b14558..c0e3759 100644
--- a/src/views/report/goview/index.vue
+++ b/src/views/report/goview/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { useAccessStore } from '../../../packages/stores/src';
defineOptions({ name: 'GoView' });
@@ -14,14 +14,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="澶у睆璁捐鍣�"
- url="https://doc.iocoder.cn/report/screen/"
- />
- </template>
-
- <IFrame :src="src" />
+ <Page auto-content-height><IFrame :src="src" />
</Page>
</template>
diff --git a/src/views/report/jmreport/bi.vue b/src/views/report/jmreport/bi.vue
index 1d8da6e..a50d77c 100644
--- a/src/views/report/jmreport/bi.vue
+++ b/src/views/report/jmreport/bi.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { useAccessStore } from '../../../packages/stores/src';
defineOptions({ name: 'JimuBI' });
@@ -15,11 +15,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="澶у睆璁捐鍣�" url="https://doc.iocoder.cn/screen/" />
- </template>
-
- <IFrame :src="src" />
+ <Page auto-content-height><IFrame :src="src" />
</Page>
</template>
diff --git a/src/views/report/jmreport/index.vue b/src/views/report/jmreport/index.vue
index 8a0801d..73f1923 100644
--- a/src/views/report/jmreport/index.vue
+++ b/src/views/report/jmreport/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, IFrame, Page } from '../../../packages/effects/common-ui/src';
+import { IFrame, Page } from '../../../packages/effects/common-ui/src';
import { useAccessStore } from '../../../packages/stores/src';
defineOptions({ name: 'JimuReport' });
@@ -15,11 +15,6 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鎶ヨ〃璁捐鍣�" url="https://doc.iocoder.cn/report/" />
- </template>
-
- <IFrame :src="src" />
+ <Page auto-content-height><IFrame :src="src" />
</Page>
</template>
diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue
index 764f7c3..da4c766 100644
--- a/src/views/system/area/index.vue
+++ b/src/views/system/area/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAreaTree } from '#/api/system/area';
@@ -55,12 +55,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鍦板尯 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鍦板尯鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue
index 50338c4..5caa27d 100644
--- a/src/views/system/dict/index.vue
+++ b/src/views/system/dict/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import DataGrid from './modules/data-grid.vue';
import TypeGrid from './modules/type-grid.vue';
@@ -14,12 +14,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="瀛楀吀绠$悊" url="https://doc.iocoder.cn/system-dict/" />
- </template>
-
- <div class="flex h-full">
+ <Page auto-content-height><div class="flex h-full">
<!-- 宸︿晶瀛楀吀绫诲瀷鍒楄〃 -->
<div class="w-1/2 pr-3">
<TypeGrid @select="handleDictTypeSelect" />
diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue
index d22de75..1272808 100644
--- a/src/views/system/loginlog/index.vue
+++ b/src/views/system/loginlog/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemLoginLogApi } from '#/api/system/login-log';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -65,12 +65,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绯荤粺鏃ュ織" url="https://doc.iocoder.cn/system-log/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="鐧诲綍鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue
index 25250d1..d0b7eff 100644
--- a/src/views/system/mail/account/index.vue
+++ b/src/views/system/mail/account/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -116,12 +116,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="閭欢閰嶇疆" url="https://doc.iocoder.cn/mail" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="閭璐﹀彿鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue
index 12f9e97..650441a 100644
--- a/src/views/system/mail/log/index.vue
+++ b/src/views/system/mail/log/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemMailLogApi } from '#/api/system/mail/log';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { DICT_TYPE } from '../../../../packages/constants/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -59,12 +59,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="閭欢閰嶇疆" url="https://doc.iocoder.cn/mail" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="閭欢鏃ュ織鍒楄〃">
<template #userInfo="{ row }">
<div v-if="row.userType && row.userId" class="flex items-center gap-1">
diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue
index e22e185..88873fc 100644
--- a/src/views/system/mail/template/index.vue
+++ b/src/views/system/mail/template/index.vue
@@ -5,7 +5,7 @@
import { onMounted, ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -140,12 +140,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="閭欢閰嶇疆" url="https://doc.iocoder.cn/mail" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<SendModal />
<Grid table-title="閭欢妯℃澘鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
index 3f43ceb..376ecc4 100644
--- a/src/views/system/menu/index.vue
+++ b/src/views/system/menu/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { SystemMenuTypeEnum } from '../../../packages/constants/src';
import { IconifyIcon } from '../../../packages/icons/src';
@@ -97,16 +97,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鍔熻兘鏉冮檺"
- url="https://doc.iocoder.cn/resource-permission"
- />
- <DocAlert title="鑿滃崟璺敱" url="https://doc.iocoder.cn/vue3/route/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鑿滃崟鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/notify/message/index.vue b/src/views/system/notify/message/index.vue
index aef59a0..3f89ad0 100644
--- a/src/views/system/notify/message/index.vue
+++ b/src/views/system/notify/message/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getNotifyMessagePage } from '#/api/system/notify/message';
@@ -58,12 +58,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绔欏唴淇�" url="https://doc.iocoder.cn/notify/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="绔欏唴淇″垪琛�">
<template #actions="{ row }">
<TableAction
diff --git a/src/views/system/notify/my/index.vue b/src/views/system/notify/my/index.vue
index 6ec98b2..db8f77a 100644
--- a/src/views/system/notify/my/index.vue
+++ b/src/views/system/notify/my/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -141,12 +141,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绔欏唴淇¢厤缃�" url="https://doc.iocoder.cn/notify/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="鎴戠殑绔欏唴淇�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/notify/template/index.vue b/src/views/system/notify/template/index.vue
index e21f37c..0864b76 100644
--- a/src/views/system/notify/template/index.vue
+++ b/src/views/system/notify/template/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -135,12 +135,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绔欏唴淇�" url="https://doc.iocoder.cn/notify/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<SendModal />
<Grid table-title="绔欏唴淇℃ā鏉垮垪琛�">
<template #toolbar-tools>
diff --git a/src/views/system/oauth2/client/index.vue b/src/views/system/oauth2/client/index.vue
index 4603c37..4d30e66 100644
--- a/src/views/system/oauth2/client/index.vue
+++ b/src/views/system/oauth2/client/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -117,15 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="OAuth 2.0锛圫SO 鍗曠偣鐧诲綍锛�"
- url="https://doc.iocoder.cn/oauth2/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="OAuth2 瀹㈡埛绔垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/oauth2/token/index.vue b/src/views/system/oauth2/token/index.vue
index 98ee406..0f3eaa7 100644
--- a/src/views/system/oauth2/token/index.vue
+++ b/src/views/system/oauth2/token/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -100,15 +100,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="OAuth 2.0锛圫SO 鍗曠偣鐧诲綍锛�"
- url="https://doc.iocoder.cn/oauth2/"
- />
- </template>
-
- <Grid table-title="浠ょ墝鍒楄〃">
+ <Page auto-content-height><Grid table-title="浠ょ墝鍒楄〃">
<template #toolbar-tools>
<TableAction
:actions="[
diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue
index 9d58f68..3d72152 100644
--- a/src/views/system/operatelog/index.vue
+++ b/src/views/system/operatelog/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemOperateLogApi } from '#/api/system/operate-log';
-import { DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -65,12 +65,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="绯荤粺鏃ュ織" url="https://doc.iocoder.cn/system-log/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="鎿嶄綔鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
index cdc6373..d6c3b82 100644
--- a/src/views/system/role/index.vue
+++ b/src/views/system/role/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -147,16 +147,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="鍔熻兘鏉冮檺"
- url="https://doc.iocoder.cn/resource-permission"
- />
- <DocAlert title="鏁版嵁鏉冮檺" url="https://doc.iocoder.cn/data-permission" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<AssignDataPermissionFormModel @success="handleRefresh" />
<AssignMenuFormModel @success="handleRefresh" />
<Grid table-title="瑙掕壊鍒楄〃">
diff --git a/src/views/system/sms/channel/index.vue b/src/views/system/sms/channel/index.vue
index 8357e0b..343f5ca 100644
--- a/src/views/system/sms/channel/index.vue
+++ b/src/views/system/sms/channel/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -117,12 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鐭俊閰嶇疆" url="https://doc.iocoder.cn/sms/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="鐭俊娓犻亾鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/sms/log/index.vue b/src/views/system/sms/log/index.vue
index c58f4fa..cc31b8a 100644
--- a/src/views/system/sms/log/index.vue
+++ b/src/views/system/sms/log/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemSmsLogApi } from '#/api/system/sms/log';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
@@ -65,12 +65,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鐭俊閰嶇疆" url="https://doc.iocoder.cn/sms/" />
- </template>
-
- <DetailModal @success="handleRefresh" />
+ <Page auto-content-height><DetailModal @success="handleRefresh" />
<Grid table-title="鐭俊鏃ュ織鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/sms/template/index.vue b/src/views/system/sms/template/index.vue
index 530c5e8..f1998d5 100644
--- a/src/views/system/sms/template/index.vue
+++ b/src/views/system/sms/template/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -135,12 +135,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鐭俊閰嶇疆" url="https://doc.iocoder.cn/sms/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<SendModal />
<Grid table-title="鐭俊妯℃澘鍒楄〃">
<template #toolbar-tools>
diff --git a/src/views/system/social/client/index.vue b/src/views/system/social/client/index.vue
index 24835f5..8ddac45 100644
--- a/src/views/system/social/client/index.vue
+++ b/src/views/system/social/client/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -117,12 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="涓夋柟鐧诲綍" url="https://doc.iocoder.cn/social-user/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="绀句氦瀹㈡埛绔垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/social/user/index.vue b/src/views/system/social/user/index.vue
index 76a3c41..4fbd276 100644
--- a/src/views/system/social/user/index.vue
+++ b/src/views/system/social/user/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemSocialUserApi } from '#/api/system/social/user';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSocialUserPage } from '#/api/system/social/user';
@@ -53,12 +53,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="涓夋柟鐧诲綍" url="https://doc.iocoder.cn/social-user/" />
- </template>
-
- <DetailModal />
+ <Page auto-content-height><DetailModal />
<Grid table-title="绀句氦鐢ㄦ埛鍒楄〃">
<template #actions="{ row }">
<TableAction
diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue
index fd163b5..29fb88d 100644
--- a/src/views/system/tenant/index.vue
+++ b/src/views/system/tenant/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -123,12 +123,7 @@
});
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="SaaS 澶氱鎴�" url="https://doc.iocoder.cn/saas-tenant/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="绉熸埛鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue
index e94975a..dbaa7c3 100644
--- a/src/views/system/tenantPackage/index.vue
+++ b/src/views/system/tenantPackage/index.vue
@@ -4,7 +4,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { isEmpty } from '../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -117,12 +117,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="SaaS 澶氱鎴�" url="https://doc.iocoder.cn/saas-tenant/" />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="绉熸埛濂楅鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 056fa9b..83e43ff 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
-import { confirm, DocAlert, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { DICT_TYPE } from '../../../packages/constants/src';
import { getDictLabel } from '../../../packages/effects/hooks/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../packages/utils/src';
@@ -189,17 +189,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert title="鐢ㄦ埛浣撶郴" url="https://doc.iocoder.cn/user-center/" />
- <DocAlert title="涓夋柟鐧婚檰" url="https://doc.iocoder.cn/social-user/" />
- <DocAlert
- title="Excel 瀵煎叆瀵煎嚭"
- url="https://doc.iocoder.cn/excel-import-and-export/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<ResetPasswordModal @success="handleRefresh" />
<AssignRoleModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
diff --git a/src/views/wms/home/index.vue b/src/views/wms/home/index.vue
index 7e1deee..a7e6e76 100644
--- a/src/views/wms/home/index.vue
+++ b/src/views/wms/home/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import { DocAlert, Page } from '../../../packages/effects/common-ui/src';
+import { Page } from '../../../packages/effects/common-ui/src';
import { formatDateTime } from '../../../packages/utils/src';
import { Button, Card } from 'ant-design-vue';
@@ -42,14 +42,7 @@
</script>
<template>
- <Page>
- <template #doc>
- <DocAlert
- title="WMS 鎵嬪唽锛堝姛鑳藉紑鍚級"
- url="https://doc.iocoder.cn/wms/build/"
- />
- </template>
- <div class="flex flex-col gap-2">
+ <Page><div class="flex flex-col gap-2">
<Card :body-style="{ padding: '16px' }">
<div class="flex flex-wrap items-center justify-between gap-4">
<div>
diff --git a/src/views/wms/inventory/history/index.vue b/src/views/wms/inventory/history/index.vue
index b2598cc..eee2e67 100644
--- a/src/views/wms/inventory/history/index.vue
+++ b/src/views/wms/inventory/history/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsInventoryHistoryApi } from '#/api/wms/inventory/history';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getInventoryHistoryPage } from '#/api/wms/inventory/history';
@@ -49,15 +49,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝簱瀛樿褰曘�佹祦姘淬�佺粺璁�"
- url="https://doc.iocoder.cn/wms/inventory/"
- />
- </template>
-
- <Grid table-title="搴撳瓨娴佹按">
+ <Page auto-content-height><Grid table-title="搴撳瓨娴佹按">
<template #itemInfo="{ row }">
<div class="flex flex-col gap-1 py-1 leading-5">
<div class="text-sm">{{ row.itemName || '-' }}</div>
diff --git a/src/views/wms/inventory/index/index.vue b/src/views/wms/inventory/index/index.vue
index b578321..400bfe3 100644
--- a/src/views/wms/inventory/index/index.vue
+++ b/src/views/wms/inventory/index/index.vue
@@ -6,7 +6,7 @@
import { ref } from 'vue';
-import { DocAlert, Page } from '../../../../packages/effects/common-ui/src';
+import { Page } from '../../../../packages/effects/common-ui/src';
import { Checkbox } from 'ant-design-vue';
@@ -153,15 +153,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬簱瀛樸�戝簱瀛樿褰曘�佹祦姘淬�佺粺璁�"
- url="https://doc.iocoder.cn/wms/inventory/"
- />
- </template>
-
- <Grid table-title="搴撳瓨缁熻">
+ <Page auto-content-height><Grid table-title="搴撳瓨缁熻">
<template #toolbar-tools>
<Checkbox v-model:checked="filterZero" @change="handleFilterZeroChange">
杩囨护鎺夊簱瀛樹负 0 鐨勫晢鍝�
diff --git a/src/views/wms/md/item/index.vue b/src/views/wms/md/item/index.vue
index 5ba09a2..c6e7219 100644
--- a/src/views/wms/md/item/index.vue
+++ b/src/views/wms/md/item/index.vue
@@ -5,7 +5,7 @@
import { ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { Card, message } from 'ant-design-vue';
@@ -165,15 +165,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戝晢鍝併�丼KU銆佸垎绫汇�佸搧鐗�"
- url="https://doc.iocoder.cn/wms/md/item/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<div class="flex h-full w-full">
<Card class="mr-4 h-full w-1/6">
diff --git a/src/views/wms/md/merchant/index.vue b/src/views/wms/md/merchant/index.vue
index 1480f13..db9bdfd 100644
--- a/src/views/wms/md/merchant/index.vue
+++ b/src/views/wms/md/merchant/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsMerchantApi } from '#/api/wms/md/merchant';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戝線鏉ヤ紒涓氾紙渚涘簲鍟嗐�佸鎴凤級"
- url="https://doc.iocoder.cn/wms/md/merchant/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="寰�鏉ヤ紒涓氬垪琛�">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/wms/md/warehouse/index.vue b/src/views/wms/md/warehouse/index.vue
index 1f46da7..d8191b7 100644
--- a/src/views/wms/md/warehouse/index.vue
+++ b/src/views/wms/md/warehouse/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsWarehouseApi } from '#/api/wms/md/warehouse';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
import { message } from 'ant-design-vue';
@@ -93,15 +93,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬熀纭�銆戜粨搴�"
- url="https://doc.iocoder.cn/wms/md/warehouse/"
- />
- </template>
-
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<Grid table-title="浠撳簱鍒楄〃">
<template #toolbar-tools>
<TableAction
diff --git a/src/views/wms/order/check/index.vue b/src/views/wms/order/check/index.vue
index 827462a..f2de5da 100644
--- a/src/views/wms/order/check/index.vue
+++ b/src/views/wms/order/check/index.vue
@@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
OrderDeleteStatusList,
OrderStatusEnum,
@@ -212,14 +212,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬崟鎹�戠洏搴�"
- url="https://doc.iocoder.cn/wms/order/check/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<DetailModal />
<CheckOrderPrint ref="printRef" />
diff --git a/src/views/wms/order/movement/index.vue b/src/views/wms/order/movement/index.vue
index f7ab92e..831c75d 100644
--- a/src/views/wms/order/movement/index.vue
+++ b/src/views/wms/order/movement/index.vue
@@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
OrderDeleteStatusList,
OrderStatusEnum,
@@ -212,14 +212,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬崟鎹�戠Щ搴�"
- url="https://doc.iocoder.cn/wms/order/movement/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<DetailModal />
<MovementOrderPrint ref="printRef" />
diff --git a/src/views/wms/order/receipt/index.vue b/src/views/wms/order/receipt/index.vue
index 16a26e4..301911d 100644
--- a/src/views/wms/order/receipt/index.vue
+++ b/src/views/wms/order/receipt/index.vue
@@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
OrderDeleteStatusList,
OrderStatusEnum,
@@ -213,12 +213,7 @@
<template>
<Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬崟鎹�戝叆搴�"
- url="https://doc.iocoder.cn/wms/order/receipt/"
- />
- </template>
+<!---->
<FormModal @success="handleRefresh" />
<DetailModal />
<ReceiptOrderPrint ref="printRef" />
diff --git a/src/views/wms/order/shipment/index.vue b/src/views/wms/order/shipment/index.vue
index 869398b..0c32974 100644
--- a/src/views/wms/order/shipment/index.vue
+++ b/src/views/wms/order/shipment/index.vue
@@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
-import { DocAlert, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import {
OrderDeleteStatusList,
OrderStatusEnum,
@@ -212,14 +212,7 @@
</script>
<template>
- <Page auto-content-height>
- <template #doc>
- <DocAlert
- title="銆愬崟鎹�戝嚭搴�"
- url="https://doc.iocoder.cn/wms/order/shipment/"
- />
- </template>
- <FormModal @success="handleRefresh" />
+ <Page auto-content-height><FormModal @success="handleRefresh" />
<DetailModal />
<ShipmentOrderPrint ref="printRef" />
--
Gitblit v1.9.3