61 次代碼提交 263b65d4f3 ... dc71e24b6d

作者 SHA1 備註 提交日期
  luzhenxing dc71e24b6d commit 6 天之前
  luzhenxing b81931a077 commit 6 天之前
  luzhenxing 3834d4df82 commit 6 天之前
  luzhenxing 80abcd0d7a commit 6 天之前
  luzhenxing 05c2fdf4a7 commit 6 天之前
  luzhenxing 077909988f commit 6 天之前
  luzhenxing 42382013ad commit 1 周之前
  luzhenxing fa6e365122 commit 1 周之前
  luzhenxing 37eeef6817 commit 1 周之前
  luzhenxing a09aac2776 commit 1 周之前
  luzhenxing b439463847 commit 1 周之前
  luzhenxing 02f6367e81 commit 1 周之前
  luzhenxing 2c85e052c3 commit 1 周之前
  luzhenxing 3bad306852 commit 1 周之前
  luzhenxing c9e8315702 commit 1 周之前
  luzhenxing 661666dabc commit 1 周之前
  luzhenxing d5ffb212ff commit 1 周之前
  luzhenxing 60975d2b5d commit 1 周之前
  luzhenxing 1c64d139ec commit 1 周之前
  luzhenxing 028f29a5b2 commit 1 周之前
  luzhenxing e0d84760a4 commit 1 周之前
  luzhenxing 5ededda34b commit 1 周之前
  luzhenxing 8870383972 commit 1 周之前
  luzhenxing edd24c1938 commit 1 周之前
  luzhenxing 3877db64a0 commit 1 周之前
  luzhenxing ed000cdbec commit 2 周之前
  luzhenxing 6195abee45 commit 2 周之前
  luzhenxing 876edd96ad Merge branch 'master' into dev 2 周之前
  luzhenxing fdeecbad28 commit 2 周之前
  luzhenxing 7269d9f201 commit 2 周之前
  luzhenxing 434c0e5e8f commit 2 周之前
  luzhenxing 5d4a388579 commit 2 周之前
  luzhenxing 96adaa78fc commit 2 周之前
  luzhenxing eeebd7c619 commit 2 周之前
  boweniac 7afb068eee Merge branch 'dev' of http://git.ascrm.cn:3000/scrm/wechat-ui into dev 2 周之前
  boweniac 71bc7fbbd1 修改whatsapp黑白名单设置接口地址 2 周之前
  luzhenxing 89e286efba commit 2 周之前
  boweniac 0bc655a9a6 增加更改Agent的接口 3 周之前
  luzhenxing 62a9b31475 commit 1 月之前
  luzhenxing 17e86c0d85 commit 1 月之前
  luzhenxing 0b71e2ce46 commit 1 月之前
  luzhenxing 046b1772ff commit 1 月之前
  luzhenxing 12220f5146 commit 1 月之前
  luzhenxing 9f96b6306f commit 1 月之前
  luzhenxing d618d05d8b commit 1 月之前
  luzhenxing f30649b8ae commit 1 月之前
  luzhenxing 094a245676 commit 1 月之前
  luzhenxing 6a56262e90 commit 1 月之前
  luzhenxing b846f40363 commit 1 月之前
  luzhenxing 998d9059b6 commit 1 月之前
  luzhenxing c42df9327d Merge branch 'master' into dev 1 月之前
  luzhenxing ff70eb95f8 Merge branch 'master' into dev 1 月之前
  luzhenxing d2b6b0bbba commit 1 月之前
  luzhenxing a3e4bebda5 commit 1 月之前
  luzhenxing 6648ab1979 commit 1 月之前
  luzhenxing bc2d75f196 commit 1 月之前
  luzhenxing b3b545c4a6 commit 1 月之前
  luzhenxing 03a72f23aa Merge branch 'master' into dev 1 月之前
  boweniac 7e5bfc78d5 清除dist文件夹内容 1 月之前
  boweniac c6b2cb7b68 清除dist内容 1 月之前
  boweniac aac74b94a6 更新忽略文件 1 月之前
共有 82 個文件被更改,包括 4317 次插入1885 次删除
  1. 2 0
      .gitignore
  2. 1 1
      dist/_app.config.js
  3. 0 1
      dist/assets/FrameBlank-Ch0vF7cw.js
  4. 0 1
      dist/assets/index-BesJFFku.js
  5. 0 1
      dist/assets/index-DcZ6pATw.js
  6. 0 0
      dist/assets/leftStage-BFbvbneL.css
  7. 0 1
      dist/assets/msgContant-CyavEXZ6.css
  8. 0 1
      dist/assets/onMountedOrActivated-C3HH8-CK.js
  9. 0 0
      dist/assets/userPhasesDrawer-BBz1irUJ.css
  10. 0 4
      dist/assets/vue-B5pbXMv7.js
  11. 0 0
      dist/index.html
  12. 4 4
      src/api/model/baseModel.ts
  13. 15 1
      src/api/wechat/contact.ts
  14. 25 0
      src/api/wechat/creditBalance.ts
  15. 1 0
      src/api/wechat/label.ts
  16. 4 1
      src/api/wechat/model/usageDetailModel.ts
  17. 71 5
      src/api/wechat/model/whatsappModel.ts
  18. 3 3
      src/api/wechat/usageDetail.ts
  19. 169 1
      src/api/wechat/whatsapp.ts
  20. 16 5
      src/api/whatsapp_batch/whatsappBatch.ts
  21. 1 1
      src/api/whatsapp_channel/model/whatsappChannelModel.ts
  22. 4 1
      src/api/whatsapp_channel/whatsappChannel.ts
  23. 62 7
      src/api/whatsapp_contact/whatsappContact.ts
  24. 24 0
      src/api/whatsapp_template/model/whatsappTemplateModel.ts
  25. 20 0
      src/api/whatsapp_template/whatsappTemplate.ts
  26. 27 17
      src/locales/lang/en/wechat.ts
  27. 2 2
      src/locales/lang/en/whatsapp_channel.ts
  28. 1 0
      src/locales/lang/en/whatsapp_contact.ts
  29. 15 0
      src/locales/lang/en/whatsapp_template.ts
  30. 13 2
      src/locales/lang/zh-CN/wechat.ts
  31. 17 17
      src/locales/lang/zh-CN/whatsapp_batch.ts
  32. 13 12
      src/locales/lang/zh-CN/whatsapp_channel.ts
  33. 18 17
      src/locales/lang/zh-CN/whatsapp_contact.ts
  34. 15 0
      src/locales/lang/zh-CN/whatsapp_template.ts
  35. 165 0
      src/utils/countryCode.ts
  36. 1 0
      src/utils/http/axios/index.ts
  37. 76 0
      src/utils/languageCode.ts
  38. 0 0
      src/views/components/SelectAIAgentModal.vue
  39. 0 1
      src/views/sys/user/user.data.ts
  40. 50 59
      src/views/wechat/contact/ContactDrawer.vue
  41. 48 63
      src/views/wechat/contact/GroupLabelDrawer.vue
  42. 116 130
      src/views/wechat/contact/contact.data.ts
  43. 160 206
      src/views/wechat/contact/index.vue
  44. 15 75
      src/views/wechat/credit_balance/creditBalance.data.ts
  45. 139 98
      src/views/wechat/credit_balance/index.vue
  46. 29 0
      src/views/wechat/credit_balance/usage/PayRechargeModal.vue
  47. 32 0
      src/views/wechat/credit_balance/usage/UsageDetailModal.vue
  48. 97 0
      src/views/wechat/credit_balance/usage/index.vue
  49. 1 5
      src/views/wechat/token/index.vue
  50. 0 3
      src/views/wechat/token/token.data.ts
  51. 1 1
      src/views/wechat/wx/index.vue
  52. 133 0
      src/views/whatsapp/whatsapp/AutomationModal.vue
  53. 210 0
      src/views/whatsapp/whatsapp/BlackWhiteModal.vue
  54. 56 0
      src/views/whatsapp/whatsapp/CreateWhatsappDrawer.vue
  55. 64 0
      src/views/whatsapp/whatsapp/EditBusinessDrawer.vue
  56. 87 0
      src/views/whatsapp/whatsapp/TokenDetailModal.vue
  57. 206 0
      src/views/whatsapp/whatsapp/VerifyCodeModal.vue
  58. 0 75
      src/views/whatsapp/whatsapp/WhatsappDrawer.vue
  59. 277 95
      src/views/whatsapp/whatsapp/index.vue
  60. 94 0
      src/views/whatsapp/whatsapp/qrcode/QrcodeDrawer.vue
  61. 131 0
      src/views/whatsapp/whatsapp/qrcode/index.vue
  62. 235 122
      src/views/whatsapp/whatsapp/whatsapp.data.ts
  63. 194 0
      src/views/whatsapp/whatsapp_batch/WhatsappBatchDrawer.vue
  64. 113 0
      src/views/whatsapp/whatsapp_batch/index.vue
  65. 93 0
      src/views/whatsapp/whatsapp_batch/send_record/index.vue
  66. 27 50
      src/views/whatsapp/whatsapp_batch/whatsappBatch.data.ts
  67. 2 0
      src/views/whatsapp/whatsapp_channel/WhatsappChannelDrawer.vue
  68. 130 0
      src/views/whatsapp/whatsapp_channel/index.vue
  69. 79 82
      src/views/whatsapp/whatsapp_channel/whatsappChannel.data.ts
  70. 0 152
      src/views/whatsapp/whatsapp_channel/whatsapp_channel/index.vue
  71. 67 0
      src/views/whatsapp/whatsapp_contact/GroupLabelDrawer.vue
  72. 66 0
      src/views/whatsapp/whatsapp_contact/ImportTempleModal.vue
  73. 101 0
      src/views/whatsapp/whatsapp_contact/SendMsgDrawer.vue
  74. 70 0
      src/views/whatsapp/whatsapp_contact/WhatsappContactDrawer.vue
  75. 231 0
      src/views/whatsapp/whatsapp_contact/index.vue
  76. 86 108
      src/views/whatsapp/whatsapp_contact/whatsappContact.data.ts
  77. 35 0
      src/views/whatsapp/whatsapp_template/index.vue
  78. 57 0
      src/views/whatsapp/whatsapp_template/whatsappTemplate.data.ts
  79. 0 75
      src/views/whatsapp_batch/whatsapp_batch/WhatsappBatchDrawer.vue
  80. 0 152
      src/views/whatsapp_batch/whatsapp_batch/index.vue
  81. 0 75
      src/views/whatsapp_contact/whatsapp_contact/WhatsappContactDrawer.vue
  82. 0 152
      src/views/whatsapp_contact/whatsapp_contact/index.vue

+ 2 - 0
.gitignore

@@ -42,3 +42,5 @@ pnpm-lock.yaml
 
 *.log
 dist
+
+.env.development

+ 1 - 1
dist/_app.config.js

@@ -1 +1 @@
-window.__PRODUCTION____APP__CONF__={"VITE_GLOB_APP_TITLE":"","VITE_GLOB_API_URL":"https://wxadminapi-debug.gkscrm.com","VITE_GLOB_UPLOAD_URL":"https://wxadminapi-debug.gkscrm.com","VITE_GLOB_API_URL_PREFIX":""};Object.freeze(window.__PRODUCTION____APP__CONF__);Object.defineProperty(window,"__PRODUCTION____APP__CONF__",{configurable:false,writable:false,});
+window.__PRODUCTION____APP__CONF__={"VITE_GLOB_APP_TITLE":"","VITE_GLOB_API_URL":"https://wxadminapi.gkscrm.com","VITE_GLOB_UPLOAD_URL":"https://wxadminapi.gkscrm.com","VITE_GLOB_API_URL_PREFIX":""};Object.freeze(window.__PRODUCTION____APP__CONF__);Object.defineProperty(window,"__PRODUCTION____APP__CONF__",{configurable:false,writable:false,});

+ 0 - 1
dist/assets/FrameBlank-Ch0vF7cw.js

@@ -1 +0,0 @@
-import{d as e,_ as a,$ as n}from"./vue-B5pbXMv7.js";const _=e({name:"FrameBlank",__name:"FrameBlank",setup(r){return(t,o)=>(a(),n("div"))}});export{_ as default};

+ 0 - 1
dist/assets/index-BesJFFku.js

@@ -1 +0,0 @@
-import{d as c,S as p,u as _,_ as i,$ as u}from"./vue-B5pbXMv7.js";const y=c({__name:"index",setup(l){const{currentRoute:s,replace:n}=p(),{params:r,query:e}=_(s),{path:a}=r,{_redirect_type:o}=e;Reflect.deleteProperty(e,"_redirect_type"),Reflect.deleteProperty(r,"path");const t=Array.isArray(a)?a.join("/"):a;return n(o==="name"?{name:t,query:e,params:r}:{path:t.startsWith("/")?t:"/"+t,query:e}),(d,m)=>(i(),u("div"))}});export{y as default};

+ 0 - 1
dist/assets/index-DcZ6pATw.js

@@ -1 +0,0 @@
-import{W as s}from"./vue-B5pbXMv7.js";const o=s("store",{state:()=>({sopNameAndTimeVilidate:!1,sopTaskDtat:{},sopLabelList:[],sopTaskId:void 0,sopStageId:void 0,sop_stage_list:[],stageDrawer:!1,stageNodeId:void 0,isAddNode:!1,tableId:"",isTried:!1,promoterDrawer:!1,flowPermission1:{},approverDrawer:!1,approverConfig1:{},copyerDrawer:!1,copyerConfig1:{},conditionDrawer:!1,conditionsConfig1:{conditionNodes:[]}}),actions:{setSopNameAndTime(e){this.sopNameAndTimeVilidate=e},setSopStageList(e){this.sop_stage_list=e},setStageDrawer(e){this.stageDrawer=e},setStageNodeId(e){this.stageNodeId=e},setIsAddNode(e){this.isAddNode=e},setSopTaskDtat(e){this.sopTaskDtat=e},setSopTaskId(e){this.sopTaskId=e},setSopStageId(e){this.sopStageId=e},setSopLabelList(e){this.sopLabelList=e},setTableId(e){this.tableId=e},setIsTried(e){this.isTried=e},setPromoter(e){this.promoterDrawer=e},setFlowPermission(e){this.flowPermission1=e},setApprover(e){this.approverDrawer=e},setApproverConfig(e){this.approverConfig1=e},setCopyer(e){this.copyerDrawer=e},setCopyerConfig(e){this.copyerConfig1=e},setCondition(e){this.conditionDrawer=e},setConditionsConfig(e){this.conditionsConfig1=e}}});export{o as u};

文件差異過大導致無法顯示
+ 0 - 0
dist/assets/leftStage-BFbvbneL.css


+ 0 - 1
dist/assets/msgContant-CyavEXZ6.css

@@ -1 +0,0 @@
-.disabled-upload[data-v-21a9aa7f]{cursor:not-allowed;pointer-events:none;opacity:.5}.ant-form-item[data-v-21a9aa7f]{margin-bottom:0}.message-content-btn[data-v-21a9aa7f]{width:800px;background-color:#f9f9f9;padding:10px 30px 10px 40px;display:flex;justify-content:left}.message-content-btn1[data-v-21a9aa7f]{width:800px;background-color:#f9f9f9;padding:10px 130px 10px 50px;display:flex;justify-content:left}.create-btn[data-v-21a9aa7f]{width:120px;border-radius:3px}.message-content[data-v-21a9aa7f]{width:800px;background-color:#f9f9f9;padding:10px 30px;display:flex;justify-content:center}.custom-textarea[data-v-21a9aa7f]{width:100%;border-radius:4px 4px 0 0;background-color:#f9f8f8}.warning-style[data-v-21a9aa7f]{color:#ff4949;font-size:12px;margin-left:2px;position:absolute;bottom:-13px}.custom-placeholder-wrapper[data-v-21a9aa7f]{position:relative;display:flex;flex-direction:column;width:720px}.custom-placeholder[data-v-21a9aa7f]{position:absolute;top:10px;left:10px;color:#a9a9a9;pointer-events:none;user-select:none;font-size:14px}.custom-placeholder .highlight[data-v-21a9aa7f]{color:#1c53d9;background-color:#e8edfb;padding:3px 5px;margin:0 5px;font-size:12px}.textarea-bottom[data-v-21a9aa7f]{display:flex;background-color:#f5f8fd;justify-content:space-between;padding:3px 16px;border:1px solid #c0ccda;border-top:0 none;border-radius:0 0 4px 4px}.ant-dropdown-link[data-v-21a9aa7f]{color:#778ca2}.character-count[data-v-21a9aa7f]{text-align:right;color:#a9a9a9;font-size:12px;margin-top:5px}.upload-contant[data-v-21a9aa7f]{width:750px;display:flex}::deep .ant-upload-wrapper .ant-upload-drag[data-v-21a9aa7f]{background-color:#fff;padding:20px 0}.upload[data-v-21a9aa7f]{width:600px;display:flex}.upload[data-v-21a9aa7f]:hover{border-color:#1890ff}.upload-file-style[data-v-21a9aa7f]{min-width:400px;padding:5px;display:inline-block;background:#f6f6fa;cursor:pointer}

+ 0 - 1
dist/assets/onMountedOrActivated-C3HH8-CK.js

@@ -1 +0,0 @@
-import{o as e,y as n,P as a}from"./vue-B5pbXMv7.js";function d(t){let o;e(()=>{t(),n(()=>{o=!0})}),a(()=>{o&&t()})}export{d as o};

文件差異過大導致無法顯示
+ 0 - 0
dist/assets/userPhasesDrawer-BBz1irUJ.css


文件差異過大導致無法顯示
+ 0 - 4
dist/assets/vue-B5pbXMv7.js


文件差異過大導致無法顯示
+ 0 - 0
dist/index.html


+ 4 - 4
src/api/model/baseModel.ts

@@ -22,7 +22,7 @@ export interface BaseResp {
 
 export interface BaseIDReq {
   id?: number;
-  type: number;
+  type?: number;
 }
 
 export interface BaseNodeReq{
@@ -54,9 +54,9 @@ export interface BaseNodeReq{
 
 export interface BaseIDsReq {
   ids?: any[];
-  departmentName: string;
-  avatar: string;
-  departmentRemark: string;
+  departmentName?: string;
+  avatar?: string;
+  departmentRemark?: string;
   contactIds?: any[];
   labelIds?: any[];
   updateType?: number;//1 批量追加标签 -1 批量移除标签

+ 15 - 1
src/api/wechat/contact.ts

@@ -18,7 +18,8 @@ enum Api {
   ChangeBlockList = '/wechat-api/contact/changeBlockList',
   GetContactById = '/wechat-api/contact',
   AddNewFriend = '/wechat-api/contact/addNewFriend',
-  UpdateContactLabels = '/wechat-api/label_relationship/batch_update_contact_labels'
+  UpdateBatchContactLabels = '/wechat-api/label_relationship/batch_update_contact_labels',
+  UpdateContactLabels = '/wechat-api/label_relationship/update_contact_labels',
 }
 
 /**
@@ -118,3 +119,16 @@ export const updateContactLabels = (params: BaseIDsReq, mode: ErrorMessageMode =
     },
   );
 };
+
+/**
+ *  @description: 批量更新联系人标签
+ */
+export const updateBatchContactLabels = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateBatchContactLabels, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};

+ 25 - 0
src/api/wechat/creditBalance.ts

@@ -9,6 +9,9 @@ enum Api {
   GetCreditBalanceList = '/wechat-api/credit_balance/list',
   DeleteCreditBalance = '/wechat-api/credit_balance/delete',
   GetCreditBalanceById = '/wechat-api/credit_balance',
+  GetCreditUsageList= '/wechat-api/credit_usage/list',
+  GetUsageDetail= '/wechat-api/usage_detail',
+  GetPayRecharge= '/wechat-api/pay_recharge',
 }
 
 /**
@@ -21,6 +24,12 @@ export const getCreditBalanceList = (params: BaseListReq, mode: ErrorMessageMode
     { errorMessageMode: mode },
   );
 };
+export const getCreditUsageList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<CreditBalanceListResp>>(
+    { url: Api.GetCreditUsageList, params },
+    { errorMessageMode: mode },
+  );
+};
 
 /**
  *  @description: Create a new credit balance
@@ -72,3 +81,19 @@ export const getCreditBalanceById = (params: BaseIDReq, mode: ErrorMessageMode =
     },
   );
 };
+
+export const getUsageDetail = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<CreditBalanceInfo>>(
+    { url: Api.GetUsageDetail, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};
+
+export const getPayRecharge = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.GetPayRecharge, params: params },
+    { errorMessageMode: mode },
+  );
+};

+ 1 - 0
src/api/wechat/label.ts

@@ -20,6 +20,7 @@ export interface LabelSelectListReq {
   page: number;
   pageSize: number;
   type?: number;
+  ctype?: number;
 }
 
 export const getLabelSelectList = (params: LabelSelectListReq, mode: ErrorMessageMode = 'notice') => {

+ 4 - 1
src/api/wechat/model/usageDetailModel.ts

@@ -1,5 +1,8 @@
-import { BaseListResp } from '@/api/model/baseModel';
+import { BaseListResp, BaseListReq } from '@/api/model/baseModel';
 
+export interface UsageDetailListReq extends BaseListReq {
+  botId: string
+}
 /**
  *  @description: UsageDetail info response
  */

+ 71 - 5
src/api/wechat/model/whatsappModel.ts

@@ -3,24 +3,90 @@ import { BaseListResp } from '@/api/model/baseModel';
 /**
  *  @description: Whatsapp info response
  */
+interface AgentInfo {
+  id: number | null;
+  createdAt: number | null;
+  updatedAt: number | null;
+  name: string;
+  role: string;
+  status: number;
+  background: string;
+  examples: string;
+  dataset_id: number | null;
+  collection_id: number | null;
+}
+
 export interface WhatsappInfo {
   id?: number;
   createdAt?: number;
   updatedAt?: number;
-  status?: number;
-  ak?: string;
-  sk?: string;
+  status?: number | null;
+  waId?: string;
   callback?: string;
+  agentId?: number;
+  agentInfo?: AgentInfo;
   account?: string;
-  nickname?: string;
   phone?: string;
+  cc?: string;
+  phoneName?: string;
+  phoneStatus?: number;
+  tokens?: string | null;
+  frequency?: number | null;
   organizationId?: number;
-  agentId?: number;
+  organizationName?: string;
   apiBase?: string;
   apiKey?: string;
+  allowList?: string | null;
+  groupAllowList?: string | null;
+  blockList?: string | null;
+  groupBlockList?: string | null;
 }
 
 /**
  *  @description: Whatsapp list response
  */
 export type WhatsappListResp = BaseListResp<WhatsappInfo>;
+
+export interface AutomationResp {
+  prompts?: string[];
+}
+
+export interface AutomationReq {
+  cc?: string;
+  phone?: string;
+  waId?: string;
+  prompts?: string[];
+  method?: string;
+  locale?: string;
+  code?: string;
+}
+
+export interface BusinessInfo {
+  phone?: string;
+  waId?: string;
+  vertical?: string;
+  description?: string;
+  email?: string;
+  address?: string;
+  profilePictureUrl?: string;
+  websites?: string[] | string | null;
+  about?: string;
+}
+
+export interface AllowBlockList {
+  id?: number;
+  allowList?: string;
+  groupAllowList?: string;
+  blockList?: string;
+  groupBlockList?: string;
+}
+
+export interface Qrcode {
+  waId?: string;
+  phone?: string;
+  deepLinkUrl?: string;
+  generateQrImage?: string;
+  prefilledMessage?: string;
+  qrImageUrl?: string;
+  qrdlCode?: string;
+}

+ 3 - 3
src/api/wechat/usageDetail.ts

@@ -1,7 +1,7 @@
 import { defHttp } from '@/utils/http/axios';
 import { ErrorMessageMode } from '/#/axios';
-import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
-import { UsageDetailInfo, UsageDetailListResp } from './model/usageDetailModel';
+import { BaseDataResp, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
+import { UsageDetailInfo, UsageDetailListReq, UsageDetailListResp } from './model/usageDetailModel';
 
 enum Api {
   CreateUsageDetail = '/wechat-api/usage_detail/create',
@@ -15,7 +15,7 @@ enum Api {
  * @description: Get usage detail list
  */
 
-export const getUsageDetailList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+export const getUsageDetailList = (params: UsageDetailListReq, mode: ErrorMessageMode = 'notice') => {
   return defHttp.post<BaseDataResp<UsageDetailListResp>>(
     { url: Api.GetUsageDetailList, params },
     { errorMessageMode: mode },

+ 169 - 1
src/api/wechat/whatsapp.ts

@@ -1,14 +1,29 @@
 import { defHttp } from '@/utils/http/axios';
 import { ErrorMessageMode } from '/#/axios';
 import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
-import { WhatsappInfo, WhatsappListResp } from './model/whatsappModel';
+import { AutomationReq, AutomationResp, BusinessInfo, WhatsappInfo, WhatsappListResp, AllowBlockList, Qrcode } from './model/whatsappModel';
 
 enum Api {
   CreateWhatsapp = '/wechat-api/whatsapp/create',
   UpdateWhatsapp = '/wechat-api/whatsapp/update',
+  UpdateWhatsappAgent = '/wechat-api/whatsapp/updateAgent',
   GetWhatsappList = '/wechat-api/whatsapp/list',
   DeleteWhatsapp = '/wechat-api/whatsapp/delete',
   GetWhatsappById = '/wechat-api/whatsapp',
+  GetAutomation = '/wechat-api/whatsapp/getAutomation',
+  SetAutomation = '/wechat-api/whatsapp/setAutomation',
+  GetBusinessInfo = '/wechat-api/whatsapp/getBusinessInfo',
+  SetBusinessInfo = '/wechat-api/whatsapp/setBusinessInfo',
+  SendCode = '/wechat-api/whatsapp/sendCode',
+  SubmitCode = '/wechat-api/whatsapp/submitCode',
+  GetAllowBlockList = '/wechat-api/whatsapp/getAllowBlockList',
+  SetAllowBlockList = '/wechat-api/whatsapp/updateAllowAndBlockList',
+  RegisterPhoneNumber = '/wechat-api/whatsapp/registerPhoneNumber',
+  DeregisterPhoneNumber = '/wechat-api/whatsapp/deregisterPhoneNumber',
+  GetQrcode = '/wechat-api/whatsapp/getQrcode',
+  CreateQrcode = '/wechat-api/whatsapp/createQrcode',
+  UpdateQrcode = '/wechat-api/whatsapp/updateQrcode',
+  RemoveQrcode = '/wechat-api/whatsapp/removeQrcode',
 }
 
 /**
@@ -49,6 +64,19 @@ export const updateWhatsapp = (params: WhatsappInfo, mode: ErrorMessageMode = 'n
 };
 
 /**
+ *  @description: Update the whatsapp agnet
+ */
+export const updateWhatsappAgent = (params: WhatsappInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateWhatsappAgent, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
  *  @description: Delete whatsapps
  */
 export const deleteWhatsapp = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
@@ -72,3 +100,143 @@ export const getWhatsappById = (params: BaseIDReq, mode: ErrorMessageMode = 'not
     },
   );
 };
+
+/**
+ *  @description: Get automation
+ */
+export const getAutomation = (params: AutomationReq, mode: ErrorMessageMode = 'notice',) => {
+  return defHttp.post<BaseDataResp<AutomationResp>>(
+    { url: Api.GetAutomation, params: params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Set automation
+ */
+export const setAutomation = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappInfo>>(
+    { url: Api.SetAutomation, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Get business info
+ */
+export const getBusinessInfo = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<BusinessInfo>>(
+    { url: Api.GetBusinessInfo, params: params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Set business info
+ */
+export const setBusinessInfo = (params: BusinessInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.SetBusinessInfo, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Send code
+ */
+export const sendCode = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.SendCode, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Submit code
+ */
+export const submitCode = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.SubmitCode, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Get allow block list
+ */
+export const getAllowBlockList = (params: { id: number }, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<AllowBlockList>>(
+    { url: Api.GetAllowBlockList, params: params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Set allow block list
+ */
+export const setAllowBlockList = (params: AllowBlockList, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.SetAllowBlockList, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Register phone number
+ */
+export const registerPhoneNumber = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.RegisterPhoneNumber, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Deregister phone number
+ */
+export const deregisterPhoneNumber = (params: AutomationReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeregisterPhoneNumber, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Get qrcode
+ */
+export const getQrcode = (params: WhatsappInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<Qrcode>>(
+    { url: Api.GetQrcode, params: params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create qrcode
+ */
+export const createQrcode = (params: Qrcode, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateQrcode, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Update qrcode
+ */
+export const updateQrcode = (params: Qrcode, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateQrcode, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Remove qrcode
+ */
+export const removeQrcode = (params: Qrcode, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.RemoveQrcode, params: params },
+    { errorMessageMode: mode, successMessageMode: mode },
+  );
+};

+ 16 - 5
src/api/whatsapp_batch/whatsappBatch.ts

@@ -4,11 +4,12 @@ import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/ap
 import { WhatsappBatchInfo, WhatsappBatchListResp } from './model/whatsappBatchModel';
 
 enum Api {
-  CreateWhatsappBatch = '/wechat-api/whatsapp_batch/create',
-  UpdateWhatsappBatch = '/wechat-api/whatsapp_batch/update',
-  GetWhatsappBatchList = '/wechat-api/whatsapp_batch/list',
-  DeleteWhatsappBatch = '/wechat-api/whatsapp_batch/delete',
-  GetWhatsappBatchById = '/wechat-api/whatsapp_batch',
+  CreateWhatsappBatch = '/wechat-api/batch_msg/createWhatcappBatchMsg',
+  UpdateWhatsappBatch = '/wechat-api/batch_msg/updateWhatcappBatchMsg',
+  GetWhatsappBatchList = '/wechat-api/batch_msg/getWhatcappBatchMsgList',
+  DeleteWhatsappBatch = '/wechat-api/batch_msg/removeWhatcappBatchMsg',
+  GetWhatsappBatchById = '/wechat-api/batch_msg/getWhatcappBatchMsg',
+  GetWhatsappBatchHistory = '/wechat-api/batch_msg/getWhatcappBatchMsgHistory',
 }
 
 /**
@@ -72,3 +73,13 @@ export const getWhatsappBatchById = (params: BaseIDReq, mode: ErrorMessageMode =
     },
   );
 };
+
+/**
+ *  @description: Get whatsapp batch history
+ */
+export const getWhatsappBatchHistory = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappBatchListResp>>(
+    { url: Api.GetWhatsappBatchHistory, params },
+    { errorMessageMode: mode },
+  );
+};

+ 1 - 1
src/api/whatsapp_channel/model/whatsappChannelModel.ts

@@ -10,7 +10,7 @@ export interface WhatsappChannelInfo {
   status?: number;
   ak?: string;
   sk?: string;
-  waId?: number;
+  waId?: string;
   waName?: string;
   wabaId?: number;
   businessId?: number;

+ 4 - 1
src/api/whatsapp_channel/whatsappChannel.ts

@@ -11,11 +11,14 @@ enum Api {
   GetWhatsappChannelById = '/wechat-api/whatsapp_channel',
 }
 
+interface GetWhatsappChannelListParams extends BaseListReq {
+  organizationId?: number
+}
 /**
  * @description: Get whatsapp channel list
  */
 
-export const getWhatsappChannelList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+export const getWhatsappChannelList = (params: GetWhatsappChannelListParams, mode: ErrorMessageMode = 'notice') => {
   return defHttp.post<BaseDataResp<WhatsappChannelListResp>>(
     { url: Api.GetWhatsappChannelList, params },
     { errorMessageMode: mode },

+ 62 - 7
src/api/whatsapp_contact/whatsappContact.ts

@@ -1,16 +1,20 @@
 import { defHttp } from '@/utils/http/axios';
-import { ErrorMessageMode } from '/#/axios';
+import { ErrorMessageMode, UploadFileParams } from '/#/axios';
 import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
 import { WhatsappContactInfo, WhatsappContactListResp } from './model/whatsappContactModel';
+import { ContentTypeEnum } from '/@/enums/httpEnum';
 
 enum Api {
-  CreateWhatsappContact = '/wechat-api/whatsapp_contact/create',
-  UpdateWhatsappContact = '/wechat-api/whatsapp_contact/update',
-  GetWhatsappContactList = '/wechat-api/whatsapp_contact/list',
-  DeleteWhatsappContact = '/wechat-api/whatsapp_contact/delete',
-  GetWhatsappContactById = '/wechat-api/whatsapp_contact',
+  CreateWhatsappContact = '/wechat-api/contact/createWhatsappContact',
+  UpdateWhatsappContact = '/wechat-api/contact/updateWhatsappContact',
+  GetWhatsappContactList = '/wechat-api/contact/getWhatsappContactList',
+  DeleteWhatsappContact = '/wechat-api/contact/deleteWhatsappContact',
+  GetWhatsappContactById = '/wechat-api/contact/getWhatsappContact',
+  UpdateContactLabels = '/wechat-api/label_relationship/setWhatsappContactBatchLabel',
+  UpdateContactLabel = '/wechat-api/label_relationship/setWhatsappContactLabel',
+  SendBatchMsgText = '/wechat-api/batch_msg/sendBatchMsgText',
+  ImportWhatsappContact = '/wechat-api/contact/importWhatsappContact',
 }
-
 /**
  * @description: Get whatsapp contact list
  */
@@ -72,3 +76,54 @@ export const getWhatsappContactById = (params: BaseIDReq, mode: ErrorMessageMode
     },
   );
 };
+
+/**
+ *  @description: Update contact labels
+ */
+export const updateContactLabels = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateContactLabels, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update contact label
+ */
+export const updateContactLabel = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateContactLabel, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Send batch msg text
+ */
+export const sendBatchMsgText = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.SendBatchMsgText, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+export const importWhatsappContact = (params, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post({
+    url: Api.ImportWhatsappContact,
+    params,
+    headers: {
+      'Content-type': ContentTypeEnum.FORM_DATA
+    }
+  }, {
+    errorMessageMode: mode
+  })
+};

+ 24 - 0
src/api/whatsapp_template/model/whatsappTemplateModel.ts

@@ -0,0 +1,24 @@
+export interface WhatsappTemplateInfo {
+  id: number;
+  name: string;
+  category: string;
+  language: string;
+  status: string;
+  components: string;
+}
+
+export interface WhatsappTemplateListReq {
+  page: number;
+  pageSize: number;
+  name?: string;
+  category?: string;
+  language?: string;
+}
+
+export interface WhatsappTemplateReq {
+  id?: number;
+  name: string;
+  category: string;
+  language: string;
+  components: string;
+} 

+ 20 - 0
src/api/whatsapp_template/whatsappTemplate.ts

@@ -0,0 +1,20 @@
+import { defHttp } from '@/utils/http/axios';
+import { BaseListResp, BaseResp } from '@/api/model/baseModel';
+import { WhatsappTemplateInfo, WhatsappTemplateListReq } from './model/whatsappTemplateModel';
+
+enum Api {
+  WhatsappTemplate = '/wechat-api/whatsapp/listTemplate',
+  RemoveTemplate = '/wechat-api/whatsapp/removeTemplate',
+}
+
+export const getWhatsappTemplateList = (params: WhatsappTemplateListReq) =>
+  defHttp.post<BaseListResp<WhatsappTemplateInfo>>({
+    url: Api.WhatsappTemplate,
+    params,
+  });
+
+export const deleteWhatsappTemplate = (params: { ids: number[] }) =>
+  defHttp.delete<BaseResp>({
+    url: Api.RemoveTemplate,
+    params,
+  }); 

+ 27 - 17
src/locales/lang/en/wechat.ts

@@ -254,33 +254,43 @@ export default {
   },
   whatsapp: {
     status: 'Status',
-    ak: 'Ak',
-    sk: 'Sk',
-    callback: 'Callback',
-    wxid: 'Wxid',
+    ak: 'Access Key',
+    sk: 'Secret Key',
+    callback: 'Callback URL',
+    wxid: 'WxID',
     account: 'Account',
     nickname: 'Nickname',
     phone: 'Phone',
-    organizationId: 'OrganizationId',
-    agentId: 'AgentId',
-    apiBase: 'ApiBase',
-    apiKey: 'ApiKey',
-    allowList: 'AllowList',
-    groupAllowList: 'GroupAllowList',
-    blockList: 'BlockList',
-    groupBlockList: 'GroupBlockList',
+    phoneName: 'Phone Name',
+    phoneStatus: 'Phone Status',
+    tokens: 'Tokens',
+    frequency: 'Frequency',
+    organizationId: 'Organization ID',
+    organizationName: 'Tenant',
+    agentId: 'Agent ID',
+    agentInfo: {
+      name: 'AI Role'
+    },
+    apiBase: 'API Base URL',
+    apiKey: 'API Key',
+    allowList: 'Allow List',
+    groupAllowList: 'Group Allow List',
+    blockList: 'Block List',
+    groupBlockList: 'Group Block List',
     addWhatsapp: 'Add Whatsapp',
     editWhatsapp: 'Edit Whatsapp',
     whatsappList: 'Whatsapp List',
   },
   creditBalance: {
-    userId: 'UserId',
+    userId: 'User ID',
     balance: 'Balance',
     status: 'Status',
-    organizationId: 'OrganizationId',
+    organizationId: 'Organization ID',
+    organizationName: 'Tenant',
     user: 'User',
-    addCreditBalance: 'Add CreditBalance',
-    editCreditBalance: 'Edit CreditBalance',
-    creditBalanceList: 'CreditBalance List',
+    userName: 'User Name',
+    addCreditBalance: 'Add Balance',
+    editCreditBalance: 'Edit Balance',
+    creditBalanceList: 'Balance List',
   },
 };

+ 2 - 2
src/locales/lang/en/whatsapp_channel.ts

@@ -1,8 +1,8 @@
 export default {
   whatsappChannel: {
     status: 'Status',
-    ak: 'Ak',
-    sk: 'Sk',
+    ak: 'access_key_id',
+    sk: 'access_key_secret',
     waId: 'WaId',
     waName: 'WaName',
     wabaId: 'WabaId',

+ 1 - 0
src/locales/lang/en/whatsapp_contact.ts

@@ -13,6 +13,7 @@ export default {
     cmobile: 'Cmobile',
     cbirtharea: 'Cbirtharea',
     cidcardNo: 'CidcardNo',
+    cbirthday: 'Cbirthday',
     ctitle: 'Ctitle',
     addWhatsappContact: 'Add WhatsappContact',
     editWhatsappContact: 'Edit WhatsappContact',

+ 15 - 0
src/locales/lang/en/whatsapp_template.ts

@@ -0,0 +1,15 @@
+export default {
+  whatsappTemplate: {
+    templateCode: 'template code',
+    name: 'name',
+    category: 'category',
+    language: 'language',
+    status: 'status',
+    auditStatus: 'audit status',
+    templateType: 'template type',
+    components: 'components',
+    addWhatsappTemplate: 'Add Template',
+    editWhatsappTemplate: 'Edit Template',
+    whatsappTemplateList: 'Template List',
+  },
+}; 

+ 13 - 2
src/locales/lang/zh-CN/wechat.ts

@@ -274,10 +274,18 @@ export default {
     account: '账号',
     nickname: '昵称',
     phone: '电话',
+    phoneName: '电话名称',
+    phoneStatus: '电话状态',
+    tokens: '令牌',
+    frequency: '频率',
     organizationId: '组织ID',
+    organizationName: '租户',
     agentId: '代理ID',
-    apiBase: 'API地址',
-    apiKey: 'API密钥',
+    agentInfo: {
+      name: 'AI角色'
+    },
+    apiBase: '模型地址',
+    apiKey: '模型密钥',
     allowList: '白名单',
     groupAllowList: '群组白名单',
     blockList: '黑名单',
@@ -291,7 +299,10 @@ export default {
     balance: '余额',
     status: '状态',
     organizationId: '组织ID',
+    organizationName: '租户',
     user: '用户',
+    userName: '用户名称',
+    nickName: '昵称',
     addCreditBalance: '添加余额',
     editCreditBalance: '编辑余额',
     creditBalanceList: '余额列表',

+ 17 - 17
src/locales/lang/zh-CN/whatsapp_batch.ts

@@ -1,21 +1,21 @@
 export default {
   whatsappBatch: {
-    status: 'Status',
-    batchNo: 'BatchNo',
-    taskName: 'TaskName',
-    fromPhone: 'FromPhone',
-    msg: 'Msg',
-    tag: 'Tag',
-    tagids: 'Tagids',
-    total: 'Total',
-    success: 'Success',
-    fail: 'Fail',
-    startTime: 'StartTime',
-    stopTime: 'StopTime',
-    sendTime: 'SendTime',
-    organizationId: 'OrganizationId',
-    addWhatsappBatch: '添加 WhatsappBatch',
-    editWhatsappBatch: '编辑 WhatsappBatch',
-    whatsappBatchList: 'WhatsappBatch 列表',
+    status: '状态',
+    batchNo: '批次号',
+    taskName: '任务名称',
+    fromPhone: '号码',
+    msg: '消息内容',
+    tag: '标签',
+    tagids: '标签ID',
+    total: '总数',
+    success: '成功数',
+    fail: '失败数',
+    startTime: '计划开始时间',
+    stopTime: '结束时间',
+    sendTime: '发送时间',
+    organizationId: '组织ID',
+    addWhatsappBatch: '添加',
+    editWhatsappBatch: '编辑',
+    whatsappBatchList: '列表',
   },
 };

+ 13 - 12
src/locales/lang/zh-CN/whatsapp_channel.ts

@@ -1,16 +1,17 @@
 export default {
   whatsappChannel: {
-    status: 'Status',
-    ak: 'Ak',
-    sk: 'Sk',
-    waId: 'WaId',
-    waName: 'WaName',
-    wabaId: 'WabaId',
-    businessId: 'BusinessId',
-    organizationId: 'OrganizationId',
-    verifyAccount: 'VerifyAccount',
-    addWhatsappChannel: '添加 WhatsappChannel',
-    editWhatsappChannel: '编辑 WhatsappChannel',
-    whatsappChannelList: 'WhatsappChannel 列表',
+    status: '状态',
+    ak: 'access_key_id',
+    sk: 'access_key_secret',
+    waId: '通道ID',
+    waName: '通道名称',
+    wabaId: 'WABA ID',
+    businessId: '商业平台ID',
+    organizationId: '租户ID',
+    organizationName: '租户',
+    verifyAccount: '认证主体',
+    addWhatsappChannel: '添加通道',
+    editWhatsappChannel: '编辑通道',
+    whatsappChannelList: '通道列表',
   },
 };

+ 18 - 17
src/locales/lang/zh-CN/whatsapp_contact.ts

@@ -1,21 +1,22 @@
 export default {
   whatsappContact: {
-    status: 'Status',
-    cc: 'Cc',
-    phone: 'Phone',
-    name: 'Name',
-    markname: 'Markname',
-    organizationId: 'OrganizationId',
-    cname: 'Cname',
-    csex: 'Csex',
-    cage: 'Cage',
-    carea: 'Carea',
-    cmobile: 'Cmobile',
-    cbirtharea: 'Cbirtharea',
-    cidcardNo: 'CidcardNo',
-    ctitle: 'Ctitle',
-    addWhatsappContact: '添加 WhatsappContact',
-    editWhatsappContact: '编辑 WhatsappContact',
-    whatsappContactList: 'WhatsappContact 列表',
+    status: '状态',
+    cc: '国家代码',
+    phone: '手机号',
+    name: '名称',
+    markname: '备注',
+    organizationId: '组织',
+    cname: '姓名',
+    csex: '性别',
+    cage: '年龄',
+    carea: '地区',
+    cmobile: '手机',
+    cbirtharea: '籍贯',
+    cidcardNo: '身份证号',
+    cbirthday: '出生日期',
+    ctitle: '称呼',
+    addWhatsappContact: '添加',
+    editWhatsappContact: '编辑',
+    whatsappContactList: '联系人列表',
   },
 };

+ 15 - 0
src/locales/lang/zh-CN/whatsapp_template.ts

@@ -0,0 +1,15 @@
+export default {
+  whatsappTemplate: {
+    templateCode: '模板编码',
+    name: '模板名称',
+    category: '类别',
+    language: '语言',
+    status: '状态',
+    auditStatus: '审核状态',
+    templateType: '模板类型',
+    components: '组件',
+    addWhatsappTemplate: '添加模板',
+    editWhatsappTemplate: '编辑模板',
+    whatsappTemplateList: '模板列表',
+  },
+}; 

+ 165 - 0
src/utils/countryCode.ts

@@ -0,0 +1,165 @@
+// 国家/地区区号代码
+export const countryCode = {
+  '86': '中国',
+  '54': '阿根廷',
+  '55': '巴西', 
+  '56': '智利',
+  '57': '哥伦比亚',
+  '20': '埃及',
+  '33': '法国',
+  '49': '德国',
+  '91': '印度',
+  '62': '印度尼西亚',
+  '972': '以色列',
+  '39': '意大利',
+  '60': '马来西亚',
+  '52': '墨西哥',
+  '31': '荷兰',
+  '234': '尼日利亚',
+  '92': '巴基斯坦',
+  '51': '秘鲁',
+  '7': '俄罗斯',
+  '966': '沙特阿拉伯',
+  '27': '南非',
+  '34': '西班牙',
+  '90': '土耳其',
+  '971': '阿拉伯联合酋长国',
+  '44': '英国',
+  // 北美地区
+  '1': '美国/加拿大',
+  // 非洲其他国家/地区
+  '213': '阿尔及利亚',
+  '244': '安哥拉',
+  '229': '贝宁',
+  '267': '博茨瓦纳',
+  '226': '布基纳法索',
+  '257': '布隆迪',
+  '237': '喀麦隆',
+  '235': '乍得',
+  '242': '刚果共和国',
+  '291': '厄立特里亚',
+  '251': '埃塞俄比亚',
+  '241': '加蓬',
+  '220': '冈比亚',
+  '233': '加纳',
+  '245': '几内亚比绍',
+  '225': '科特迪瓦',
+  '254': '肯尼亚',
+  '266': '莱索托',
+  '231': '利比里亚',
+  '218': '利比亚',
+  '261': '马达加斯加',
+  '265': '马拉维',
+  '223': '马里',
+  '222': '毛里塔尼亚',
+  '212': '摩洛哥',
+  '258': '莫桑比克',
+  '264': '纳米比亚',
+  '227': '尼日尔',
+  '250': '卢旺达',
+  '221': '塞内加尔',
+  '232': '塞拉利昂',
+  '252': '索马里',
+  '211': '南苏丹',
+  '249': '苏丹',
+  '268': '斯威士兰',
+  '255': '坦桑尼亚',
+  '228': '多哥',
+  '216': '突尼斯',
+  '256': '乌干达',
+  '260': '赞比亚',
+  // 亚太其他国家/地区
+  '93': '阿富汗',
+  '61': '澳大利亚',
+  '880': '孟加拉国',
+  '855': '柬埔寨',
+  '852': '中国香港',
+  '81': '日本',
+  '856': '老挝',
+  '976': '蒙古',
+  '977': '尼泊尔',
+  '64': '新西兰',
+  '675': '巴布亚新几内亚',
+  '63': '菲律宾',
+  '65': '新加坡',
+  '94': '斯里兰卡',
+  '886': '中国台湾',
+  '66': '泰国',
+  '84': '越南',
+  '992': '塔吉克斯坦',
+  '993': '土库曼斯坦',
+  '998': '乌兹别克斯坦',
+  // 中东欧其他国家/地区
+  '355': '阿尔巴尼亚',
+  '374': '亚美尼亚',
+  '994': '阿塞拜疆',
+  '375': '白俄罗斯',
+  '359': '保加利亚',
+  '385': '克罗地亚',
+  '420': '捷克共和国',
+  '995': '格鲁吉亚',
+  '30': '希腊',
+  '36': '匈牙利',
+  '371': '拉脱维亚',
+  '370': '立陶宛',
+  '373': '摩尔多瓦',
+  '389': '北马其顿',
+  '48': '波兰',
+  '40': '罗马尼亚',
+  '381': '塞尔维亚',
+  '421': '斯洛伐克',
+  '386': '斯洛文尼亚',
+  '380': '乌克兰',
+  // 西欧其他国家/地区
+  '43': '奥地利',
+  '32': '比利时',
+  '45': '丹麦',
+  '358': '芬兰',
+  '353': '爱尔兰',
+  '47': '挪威',
+  '351': '葡萄牙',
+  '46': '瑞典',
+  '41': '瑞士',
+  // 拉丁美洲其他国家/地区
+  '591': '玻利维亚',
+  '506': '哥斯达黎加',
+  '1-809': '多米尼加共和国',
+  '1-829': '多米尼加共和国',
+  '1-849': '多米尼加共和国',
+  '593': '厄瓜多尔',
+  '503': '萨尔瓦多',
+  '502': '危地马拉',
+  '509': '海地',
+  '504': '洪都拉斯',
+  '1-876': '牙买加',
+  '1-658': '牙买加',
+  '505': '尼加拉瓜',
+  '507': '巴拿马',
+  '595': '巴拉圭',
+  '1-787': '波多黎各',
+  '1-939': '波多黎各',
+  '598': '乌拉圭',
+  '58': '委内瑞拉',
+  // 中东其他国家/地区
+  '973': '巴林',
+  '964': '伊拉克',
+  '962': '约旦',
+  '965': '科威特',
+  '961': '黎巴嫩',
+  '968': '阿曼',
+  '974': '卡塔尔',
+  '967': '也门',
+};
+
+// 转换为列表格式供选择器使用
+export const countryCodeList = Object.entries(countryCode)
+  .sort((a, b) => {
+    // 将中国(区号86)排在第一位
+    if (a[0] === '86') return -1;
+    if (b[0] === '86') return 1;
+    return 0;
+  })
+  .map(([code, name]) => ({
+    value: code,
+    label: `${name} (+${code})`,
+  }));

+ 1 - 0
src/utils/http/axios/index.ts

@@ -209,6 +209,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
     // 深度合并
     deepMerge(
       {
+        baseURL: import.meta.env.VITE_GLOB_API_URL,
         // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
         // authentication schemes,e.g: Bearer
         // authenticationScheme: 'Bearer',

+ 76 - 0
src/utils/languageCode.ts

@@ -0,0 +1,76 @@
+// 语言代码选项
+export const languageCode = {
+  'zh_CN': '简体中文',
+  'en': '英语',
+  'en_GB': '英语(英国)',
+  'en_US': '英语(美国)',
+  'af': '南非荷兰语',
+  'sq': '阿尔巴尼亚语',
+  'ar': '阿拉伯语',
+  'az': '阿塞拜疆语',
+  'bn': '孟加拉语',
+  'bg': '保加利亚语',
+  'ca': '加泰罗尼亚语',
+  'zh_HK': '繁体中文(香港)',
+  'zh_TW': '繁体中文(台湾)',
+  'hr': '克罗地亚语',
+  'cs': '捷克语',
+  'da': '丹麦语',
+  'nl': '荷兰语',
+  'et': '爱沙尼亚语',
+  'fil': '菲律宾语',
+  'fi': '芬兰语',
+  'fr': '法语',
+  'de': '德语',
+  'el': '希腊语',
+  'gu': '古吉拉特语',
+  'ha': '豪萨语',
+  'he': '希伯来语',
+  'hi': '印地语',
+  'hu': '匈牙利语',
+  'id': '印尼语',
+  'ga': '爱尔兰语',
+  'it': '意大利语',
+  'ja': '日语',
+  'kn': '卡纳达语',
+  'kk': '哈萨克语',
+  'ko': '韩语',
+  'lo': '老挝语',
+  'lv': '拉脱维亚语',
+  'lt': '立陶宛语',
+  'mk': '马其顿语',
+  'ms': '马来语',
+  'ml': '马拉雅拉姆语',
+  'mr': '马拉地语',
+  'nb': '挪威语',
+  'fa': '波斯语',
+  'pl': '波兰语',
+  'pt_BR': '葡萄牙语(巴西)',
+  'pt_PT': '葡萄牙语(葡萄牙)',
+  'pa': '旁遮普语',
+  'ro': '罗马尼亚语',
+  'ru': '俄语',
+  'sr': '塞尔维亚语',
+  'sk': '斯洛伐克语',
+  'sl': '斯洛文尼亚语',
+  'es': '西班牙语',
+  'es_AR': '西班牙语(阿根廷)',
+  'es_ES': '西班牙语(西班牙)',
+  'es_MX': '西班牙语(墨西哥)',
+  'sw': '斯瓦希里语',
+  'sv': '瑞典语',
+  'ta': '泰米尔语',
+  'te': '泰卢固语',
+  'th': '泰语',
+  'tr': '土耳其语',
+  'uk': '乌克兰语',
+  'ur': '乌尔都语',
+  'uz': '乌兹别克语',
+  'vi': '越南语',
+  'zu': '祖鲁语'
+};
+
+export const languageCodeList = Object.keys(languageCode).map(key => ({
+  value: key,
+  label: `${languageCode[key]} (${key})`,
+}))

+ 0 - 0
src/views/components/ SelectAIAgentModal.vue → src/views/components/SelectAIAgentModal.vue


+ 0 - 1
src/views/sys/user/user.data.ts

@@ -9,7 +9,6 @@ import { getDepartmentList } from '@/api/sys/department';
 import { getPositionList } from '@/api/sys/position';
 import { updateUser } from '@/api/sys/user';
 import { uploadApi } from '@/api/fms/cloudFile';
-
 const { t } = useI18n();
 
 export const columns: BasicColumn[] = [

+ 50 - 59
src/views/wechat/contact/ContactDrawer.vue

@@ -7,78 +7,69 @@
     width="500px"
     @ok="handleSubmit"
   >
-    <BasicForm @register="registerForm" />
+    <BasicForm @register="registerForm" class="form-container" />
   </BasicDrawer>
 </template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
   import { BasicForm, useForm } from '@/components/Form/index';
   import { formSchema } from './contact.data';
   import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
   import { useI18n } from 'vue-i18n';
 
-  import { createContact } from '@/api/wechat/contact';
-  import {updateLabelRelationships} from "@/api/wechat/labelRelationship";
+  import { updateContact } from '@/api/wechat/contact';
+  import { formatToDateTime } from '@/utils/dateUtil';
 
-  export default defineComponent({
-    name: 'ContactDrawer',
-    components: { BasicDrawer, BasicForm },
-    emits: ['success', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const { t } = useI18n();
+  const emit = defineEmits(['success', 'register']);
+  const isUpdate = ref(true);
+  const { t } = useI18n();
 
-      const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
-        labelWidth: 160,
-        baseColProps: { span: 24 },
-        layout: 'vertical',
-        schemas: formSchema,
-        showActionButtonGroup: false,
-      });
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
 
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    setDrawerProps({ confirmLoading: false });
 
-        isUpdate.value = !!data?.isUpdate;
+    isUpdate.value = !!data?.isUpdate;
 
-        if (unref(isUpdate)) {
-          setFieldsValue({
-            ...data.record,
-          });
-        }
+    if (unref(isUpdate)) {
+      setFieldsValue({
+        ...data.record,
       });
+    }
+  });
 
-      const getTitle = computed(() =>
-        !unref(isUpdate) ? t('wechat.contact.addContact') : t('wechat.contact.editContact'),
-      );
-
-      async function handleSubmit() {
-        const values = await validate();
-        console.log(values,'values')
-        setDrawerProps({ confirmLoading: true });
-        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
-
-        const labelRelationshipsInfo = {
-          status: 1,
-          labelIds: values.labelRelationships,
-          contactId: values.id,
-        };
-
-        let result = unref(isUpdate) ? await updateLabelRelationships(labelRelationshipsInfo) : await createContact(values);
-        if (result.code === 0) {
-          closeDrawer();
-          emit('success');
-        }
-        setDrawerProps({ confirmLoading: false });
-      }
+  const getTitle = computed(() =>
+    !unref(isUpdate) ? '添加联系人' : '编辑联系人',
+  );
 
-      return {
-        registerDrawer,
-        registerForm,
-        getTitle,
-        handleSubmit,
-      };
-    },
-  });
+  async function handleSubmit() {
+    const values = await validate();
+    if (!values.labelIds) {
+      values.labelIds = []
+    }
+    console.log(values,'values')
+    setDrawerProps({ confirmLoading: true });
+    values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
+    values['cbirthday'] = formatToDateTime(values['cbirthday'], 'YYYY-MM-DD');
+    let result = await updateContact(values)
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
 </script>
+<style lang="scss" scoped>
+  .form-container {
+    ::v-deep(.ant-input-number) {
+      width: 100%;
+    }
+  }
+</style>

+ 48 - 63
src/views/wechat/contact/GroupLabelDrawer.vue

@@ -3,82 +3,67 @@
     v-bind="$attrs"
     @register="registerDrawer"
     showFooter
-    :title="getTitle"
-    width="500px"
+    title="编辑标签"
+    width="35%"
     @ok="handleSubmit"
   >
-    <BasicForm @register="registerForm" />
+    <BasicForm @register="registerForm" class="form-container" />
   </BasicDrawer>
 </template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
+<script lang="ts" setup>
+  import { ref, unref } from 'vue';
   import { BasicForm, useForm } from '@/components/Form/index';
   import { groupLabelFormSchema } from './contact.data';
   import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
   import { useI18n } from 'vue-i18n';
+  import { formatToDateTime } from '@/utils/dateUtil';
+  import { updateContactLabels } from '@/api/wechat/contact';
 
-  import { createContact } from '@/api/wechat/contact';
-  import {updateLabelRelationships} from "@/api/wechat/labelRelationship";
+  const emit = defineEmits(['success', 'register']);
+  const isUpdate = ref(true);
 
-  export default defineComponent({
-    name: 'GroupLabelDrawer',
-    components: { BasicDrawer, BasicForm },
-    emits: ['success', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const { t } = useI18n();
-
-      const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
-        labelWidth: 160,
-        baseColProps: { span: 24 },
-        layout: 'vertical',
-        schemas: groupLabelFormSchema,
-        showActionButtonGroup: false,
-      });
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: groupLabelFormSchema,
+    showActionButtonGroup: false,
+  });
 
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    setDrawerProps({ confirmLoading: false });
 
-        isUpdate.value = !!data?.isUpdate;
+    isUpdate.value = !!data?.isUpdate;
 
-        if (unref(isUpdate)) {
-          setFieldsValue({
-            ...data.record,
-          });
-        }
+    if (unref(isUpdate)) {
+      setFieldsValue({
+        labelIds: data.record.labelRelationships.map(item => item.value),
+        contactId: data.record.id
       });
-
-      const getTitle = computed(() =>
-        !unref(isUpdate) ? t('wechat.contact.addContact') : t('wechat.contact.editContact'),
-      );
-
-      async function handleSubmit() {
-        const values = await validate();
-        console.log(values,'values')
-        setDrawerProps({ confirmLoading: true });
-        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
-
-        const labelRelationshipsInfo = {
-          status: 1,
-          labelIds: values.labelRelationships,
-          contactId: values.id,
-        };
-
-        let result = unref(isUpdate) ? await updateLabelRelationships(labelRelationshipsInfo) : await createContact(values);
-        if (result.code === 0) {
-          closeDrawer();
-          emit('success');
-        }
-        setDrawerProps({ confirmLoading: false });
-      }
-
-      return {
-        registerDrawer,
-        registerForm,
-        getTitle,
-        handleSubmit,
-      };
-    },
+    }
   });
+
+  async function handleSubmit() {
+    const values = await validate();
+    console.log(values,'values')
+    values.contactId = Number(values.contactId)
+    if (!values.labelIds) {
+      values.labelIds = []
+    }
+    setDrawerProps({ confirmLoading: true });
+    let result = await updateContactLabels(values);
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
 </script>
+<style lang="scss" scoped>
+  .form-container {
+    ::v-deep(.ant-input-number) {
+      width: 100%;
+    }
+  }
+</style>

+ 116 - 130
src/views/wechat/contact/contact.data.ts

@@ -17,7 +17,7 @@ export const columns: BasicColumn[] = [
   {
     title: t('wechat.contact.type'),
     dataIndex: 'type',
-    width: 50,
+    width: 80,
     customRender: ({ record }) => {
       return record.type == 1 ? '联系人' : '群组'
     },
@@ -43,7 +43,7 @@ export const columns: BasicColumn[] = [
   {
     title: t('wechat.contact.isInBlockList'),
     dataIndex: 'isInBlockList',
-    width: 100,
+    width: 50,
     align: 'left',
     customRender: ({ record }) => {
       return record.isInBlockList == true ? '人工' : 'AI'
@@ -57,6 +57,62 @@ export const columns: BasicColumn[] = [
       return text.map(label => label.label).join(', ');
     },
   },
+  {
+    title: '姓名',
+    dataIndex: 'cname',
+    width: 100,
+    align: 'left',
+  },
+  {
+    title: '称呼',
+    dataIndex: 'ctitle',
+    width: 100,
+    align: 'left',
+  },
+  {
+    title: '身份证号',
+    dataIndex: 'cidcardNo',
+    width: 150,
+    align: 'left',
+  },
+  {
+    title: '地区',
+    dataIndex: 'carea',
+    width: 120,
+    align: 'left',
+  },
+  {
+    title: '年龄',
+    dataIndex: 'cage',
+    width: 80,
+    align: 'left',
+  },
+  {
+    title: '籍贯',
+    dataIndex: 'cbirtharea',
+    width: 120,
+    align: 'left',
+  },
+  {
+    title: '出生日期',
+    dataIndex: 'cbirthday',
+    width: 120,
+    align: 'left',
+  },
+  {
+    title: '性别',
+    dataIndex: 'sex',
+    width: 80,
+    align: 'left',
+    customRender: ({ text }) => {
+      const sexMap = {
+        0: '未知',
+        1: '男',
+        2: '女',
+      };
+      return sexMap[text] || '未知';
+    },
+  },
 ];
 
 export const searchFormSchema: FormSchema[] = [
@@ -110,152 +166,82 @@ export const formSchema: FormSchema[] = [
     show: false,
   },
   {
-    field: 'type',
-    label: t('wechat.contact.type'),
+    field: 'cname',
+    label: t('whatsapp_contact.whatsappContact.cname'),
+    component: 'Input',
+  },
+  {
+    field: 'sex',
+    label: t('whatsapp_contact.whatsappContact.csex'),
+    component: 'RadioButtonGroup',
+    componentProps: {
+      options: [
+        { label: '未知', value: 0 },
+        { label: '男', value: 1 },
+        { label: '女', value: 2 },
+      ],
+    },
+  },
+  {
+    field: 'ctitle',
+    label: t('whatsapp_contact.whatsappContact.ctitle'),
+    component: 'Input',
+  },
+  {
+    field: 'cage',
+    label: t('whatsapp_contact.whatsappContact.cage'),
     component: 'InputNumber',
-    show: false,
-    required: false,
   },
   {
-    field: 'labelRelationships',
-    label: t('wechat.contact.labels'),
-    component: 'ApiSelect',
-    componentProps: (renderCallbackParams) => ({
-      api: (params) => getLabelSelectList(params).then(response => {
-        return response.data.map((label) => {
-          return { label: label.label, value: label.value };
-        });
-      }),
-      params: { page: 1, pageSize: 1000, type: 1 },
-      mode: 'tags',
-      tokenSeparators: [','],
-      change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数
-    }),
-    required: false,
+    field: 'cbirthday',
+    label: t('whatsapp_contact.whatsappContact.cbirthday'),
+    component: 'DatePicker',
+  },
+  {
+    field: 'carea',
+    label: t('whatsapp_contact.whatsappContact.carea'),
+    component: 'Input',
+  },
+  {
+    field: 'cbirtharea',
+    label: t('whatsapp_contact.whatsappContact.cbirtharea'),
+    component: 'Input',
+  },
+  {
+    field: 'cidcardNo',
+    label: t('whatsapp_contact.whatsappContact.cidcardNo'),
+    component: 'Input',
+  },
+  {
+    field: 'markname',
+    label: t('whatsapp_contact.whatsappContact.markname'),
+    component: 'InputTextArea',
+    componentProps: {
+      showCount: false,
+      autoSize: {
+        minRows: 2,
+        maxRows: 5
+      }
+    },
   },
-  // {
-  //   field: 'wxWxid',
-  //   label: t('wechat.contact.wxWxid'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'wxid',
-  //   label: t('wechat.contact.wxid'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'account',
-  //   label: t('wechat.contact.account'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'nickname',
-  //   label: t('wechat.contact.nickname'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'markname',
-  //   label: t('wechat.contact.markname'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'headimg',
-  //   label: t('wechat.contact.headimg'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'sex',
-  //   label: t('wechat.contact.sex'),
-  //   component: 'InputNumber',
-  //   required: true,
-  // },
-  // {
-  //   field: 'starrole',
-  //   label: t('wechat.contact.starrole'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'dontseeit',
-  //   label: t('wechat.contact.dontseeit'),
-  //   component: 'InputNumber',
-  //   required: true,
-  // },
-  // {
-  //   field: 'dontseeme',
-  //   label: t('wechat.contact.dontseeme'),
-  //   component: 'InputNumber',
-  //   required: true,
-  // },
-  // {
-  //   field: 'lag',
-  //   label: t('wechat.contact.lag'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'gid',
-  //   label: t('wechat.contact.gid'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'gname',
-  //   label: t('wechat.contact.gname'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'v3',
-  //   label: t('wechat.contact.v3'),
-  //   component: 'Input',
-  //   required: true,
-  // },
-  // {
-  //   field: 'status',
-  //   label: t('wechat.contact.status'),
-  //   component: 'RadioButtonGroup',
-  //   defaultValue: 1,
-  //   componentProps: {
-  //     options: [
-  //       { label: t('common.on'), value: 1 },
-  //       { label: t('common.off'), value: 2 },
-  //     ],
-  //   },
-  // },
 ];
 
 export const groupLabelFormSchema: FormSchema[] = [
   {
-    field: 'id',
+    field: 'contactId',
     label: 'ID',
     component: 'Input',
     show: false,
   },
   {
-    field: 'type',
-    label: t('wechat.contact.type'),
-    component: 'InputNumber',
-    show: false,
-    required: false,
-  },
-  {
-    field: 'labelRelationships',
+    field: 'labelIds',
     label: t('wechat.contact.labels'),
     component: 'ApiSelect',
     componentProps: (renderCallbackParams) => ({
       api: (params) => getLabelSelectList(params).then(response => {
-        return response.data.map((label) => {
-          return { label: label.label, value: label.value };
-        });
+        return response.data
       }),
-      params: { page: 1, pageSize: 1000, type: 2 },
+      params: { page: 1, pageSize: 1000, type: 1 },
       mode: 'tags',
       tokenSeparators: [','],
       change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数

+ 160 - 206
src/views/wechat/contact/index.vue

@@ -14,7 +14,7 @@
         <!-- <Tooltip v-show="selectedIds && !selectedIds.length" title="请先勾选"> -->
           <a-button
             @click="handleAddLabel"
-            style="position: absolute; left: 0px"
+            style="position: absolute; left: 0"
             :disabled="selectedIds && !selectedIds.length"
           >
             + 增加标签
@@ -37,6 +37,10 @@
           <TableAction
             :actions="[
               {
+                label: t('common.edit'),
+                onClick: handleEdit.bind(null, record),
+              },
+              {
                 label: record.isInBlockList == true ? '转 AI' : '转人工',
                 onClick: handleIsInBlockList.bind(null, record),
               },
@@ -45,8 +49,8 @@
                 onClick: handleMsg.bind(null, record),
               },
               {
-                label: '编辑',
-                onClick: handleEdit.bind(null, record),
+                label: '编辑标签',
+                onClick: handleEditLabel.bind(null, record),
               },
               // {
               //   icon: 'ant-design:delete-outlined',
@@ -87,15 +91,14 @@
     </Modal>
     <GroupLabelDrawer @register="registerGroupLabelDrawer" @success="handleSuccess" />
     <ContactDrawer @register="registerDrawer" @success="handleSuccess" />
-    <SendMsgDrawer @register="registerDrawerMsg" @success="handleSuccess1" />
+    <SendMsgDrawer @register="registerDrawerMsg" @success="handleSuccess" />
   </div>
 </template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref, onMounted, reactive } from 'vue';
-  import { Modal, Form, FormItem, Select, message ,Tooltip} from 'ant-design-vue';
+<script lang="ts" setup>
+  import { createVNode, ref, onMounted, reactive } from 'vue';
+  import { Modal, Form, FormItem, Select, message } from 'ant-design-vue';
   import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
   import { BasicTable, useTable, TableAction } from '@/components/Table';
-  import { Button } from '@/components/Button';
 
   import { getLabelSelectList } from '@/api/wechat/label';
   import { useDrawer } from '@/components/Drawer';
@@ -108,227 +111,178 @@
   import {
     getContactList,
     deleteContact,
-    updateContactLabels,
+    updateBatchContactLabels,
     changeBlockList
   } from '@/api/wechat/contact';
 
-  export default defineComponent({
-    name: 'ContactManagement',
-    components: {
-      BasicTable,
-      SendMsgDrawer,
-      ContactDrawer,
-      GroupLabelDrawer,
-      TableAction,
-      Button,
-      Modal,
-      Select,
-      Form,
-      FormItem,
-      Tooltip,
+  const { t } = useI18n();
+  // const selectedIds = ref<number[] | string[]>();
+  const selectedIds = ref([]);
+  const showDeleteButton = ref<boolean>(false);
+  const modalVisible = ref(false);
+  const modal_title = ref('增加标签');
+  const form = reactive({
+    lableName: [],
+  });
+  const initialForm = reactive({
+    lableName: [],
+  });
+  const actionLabel = ref([]);
+
+  const [registerGroupLabelDrawer, { openDrawer: openGroupLabelDrawer,  }] = useDrawer();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerDrawerMsg, { openDrawer: openDrawerMsg }] = useDrawer();
+  const [registerTable, { reload ,clearSelectedRowKeys}] = useTable({
+    title: t('wechat.contact.contactList'),
+    api: getContactList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: false,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 280,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    },
+    rowKey: 'id',
+    rowSelection: {
+      type: 'checkbox',
+      onChange: (selectedRowKeys, _selectedRows) => {
+        selectedIds.value = selectedRowKeys as number[];
+        showDeleteButton.value = selectedRowKeys.length > 0;
+      },
     },
-    setup() {
-      const { t } = useI18n();
-      // const selectedIds = ref<number[] | string[]>();
-      const selectedIds = ref([]);
-      const showDeleteButton = ref<boolean>(false);
-      const modalVisible = ref(false);
-      const modal_title = ref('增加标签');
-      const form = reactive({
-        lableName: [],
-      });
-      const initialForm = reactive({
-        lableName: [],
-      });
-      const actionLabel = ref([]);
+  });
 
-      const [registerGroupLabelDrawer, { openGroupLabelDrawer }] = useDrawer();
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerDrawerMsg, { openDrawerMsg }] = useDrawer();
-      const [registerTable, { reload ,clearSelectedRowKeys}] = useTable({
-        title: t('wechat.contact.contactList'),
-        api: getContactList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: false,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 80,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: undefined,
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
+  onMounted(async () => {
+    let res = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
+    actionLabel.value = res.data;
+  });
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+  async function handleAddLabel() {
+    modalVisible.value = true;
+    modal_title.value = '增加标签';
+  }
 
-      onMounted(async () => {
-        let res = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
-        actionLabel.value = res.data;
-      });
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
-      async function handleAddLabel() {
-        modalVisible.value = true;
-        modal_title.value = '增加标签';
-      }
+  function handleDeleteLabel() {
 
-      function handleDeleteLabel() {
+    modalVisible.value = true;
+    modal_title.value = '移除标签';
+  }
 
-        modalVisible.value = true;
-        modal_title.value = '移除标签';
-      }
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
 
-      function handleEdit(record: Recordable) {
-        if (record.type == 1) {
-          openDrawer(true, {
-            record,
-            isUpdate: true,
-          });
-        } else {
-          openGroupLabelDrawer(true, {
-            record,
-            isUpdate: true,
-          });
-        }
-      }
-      function handleMsg(record: Recordable) {
-        openDrawerMsg(true, {
-          record,
-        });
-      }
+  function handleEditLabel(record: Recordable) {
+    openGroupLabelDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+  function handleMsg(record: Recordable) {
+    openDrawerMsg(true, {
+      record,
+    });
+  }
 
-      async function handleIsInBlockList(record: Recordable) {
-        const result = await changeBlockList({ ownerWxid: record.wxWxid, wxid: record.wxid, type: record.type, ai: record.isInBlockList });
-        if (result.code === 0) {
-          await reload();
-        }
-      }
+  async function handleIsInBlockList(record: Recordable) {
+    const result = await changeBlockList({ ownerWxid: record.wxWxid, wxid: record.wxid, type: record.type, ai: record.isInBlockList });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
 
-      async function handleDelete(record: Recordable) {
-        const result = await deleteContact({ ids: [record.id] });
+  async function handleDelete(record: Recordable) {
+    const result = await deleteContact({ ids: [record.id] });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  async function handleBatchDelete() {
+    Modal.confirm({
+      title: t('common.deleteConfirm'),
+      icon: createVNode(ExclamationCircleOutlined),
+      async onOk() {
+        const result = await deleteContact({ ids: selectedIds.value as number[] });
         if (result.code === 0) {
+          showDeleteButton.value = false;
           await reload();
         }
-      }
-
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteContact({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
 
-      async function handleSuccess() {
+  async function handleSuccess() {
+    await reload();
+  }
+  async function handleOk() {
+    let contactIds = selectedIds.value as number[];
+    let labelIds = form.lableName;
+    if (modal_title.value === '增加标签') {
+      let res = await updateBatchContactLabels({ updateType: 1, contactIds, labelIds });
+      if (res.code === 0) {
+        message.success('标签添加成功');
+        modalVisible.value = false;
         await reload();
+        selectedIds.value = [];
+        form.lableName = [];
+        clearSelectedRowKeys();
       }
-      async function handleSuccess1() {
+    } else {
+      let res = await updateBatchContactLabels({ updateType: -1, contactIds, labelIds });
+      if (res.code === 0) {
+        message.success('标签移除成功');
+        modalVisible.value = false;
         await reload();
+        selectedIds.value = [];
+        form.lableName = [];
+        clearSelectedRowKeys();
       }
-      async function handleOk() {
-        let contactIds = selectedIds.value as number[];
-        let labelIds = form.lableName;
-        if (modal_title.value === '增加标签') {
-          let res = await updateContactLabels({ updateType: 1, contactIds, labelIds });
-          if (res.code === 0) {
-            message.success('标签添加成功');
-            modalVisible.value = false;
-            await reload();
-            selectedIds.value = [];
-            form.lableName = [];
-            clearSelectedRowKeys();
-          }
-        } else {
-          let res = await updateContactLabels({ updateType: -1, contactIds, labelIds });
-          if (res.code === 0) {
-            message.success('标签移除成功');
-            modalVisible.value = false;
-            await reload();
-            selectedIds.value = [];
-            form.lableName = [];
-            clearSelectedRowKeys();
-          }
-        }
-      }
-      async function handleCancel() {
-        Object.assign(form, initialForm);
-      }
-      // const labelOptions = ref([]);
-      //
-      // async function fetchLabelList() {
-      //   const response = await getLabelList({ page: 1, pageSize: 100 });
-      //   const labels = response.data;
-      //
-      //   labelOptions.value = labels.map((label) => {
-      //     return { label: label.name, value: label.id };
-      //   });
-      // }
-      //
-      // onMounted(fetchLabelList);
-      //
-      // function getLabelOptions() {
-      //     return labelOptions.value;
-      // }
-
-      return {
-        t,
-        registerTable,
-        registerGroupLabelDrawer,
-        registerDrawer,
-        registerDrawerMsg,
-        handleCreate,
-        handleMsg,
-        handleIsInBlockList,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleSuccess1,
-        handleBatchDelete,
-        showDeleteButton,
-        handleDeleteLabel,
-        handleAddLabel,
-        handleOk,
-        handleCancel,
-        form,
-        modalVisible,
-        modal_title,
-        actionLabel,
-        selectedIds,
-        // labelOptions,
-        // getLabelOptions,
-      };
-    },
-  });
+    }
+  }
+  async function handleCancel() {
+    Object.assign(form, initialForm);
+  }
+  // const labelOptions = ref([]);
+  //
+  // async function fetchLabelList() {
+  //   const response = await getLabelList({ page: 1, pageSize: 100 });
+  //   const labels = response.data;
+  //
+  //   labelOptions.value = labels.map((label) => {
+  //     return { label: label.name, value: label.id };
+  //   });
+  // }
+  //
+  // onMounted(fetchLabelList);
+  //
+  // function getLabelOptions() {
+  //     return labelOptions.value;
+  // }
 </script>
 <style lang="less" scoped>
   .custom-modal {
     display: flex;
-    justify-content: center;
     align-items: center;
+    justify-content: center;
   }
 </style>

+ 15 - 75
src/views/wechat/credit_balance/creditBalance.data.ts

@@ -1,16 +1,14 @@
 import { BasicColumn, FormSchema } from '@/components/Table';
 import { useI18n } from '@/hooks/web/useI18n';
 import { formatToDateTime } from '@/utils/dateUtil';
-import { updateCreditBalance } from '@/api/wechat/creditBalance';
-import { Switch } from 'ant-design-vue';
-import { h } from 'vue';
+import {getDepartmentList} from "@/api/sys/department";
 
 const { t } = useI18n();
 
 export const columns: BasicColumn[] = [
   {
-    title: t('wechat.creditBalance.userId'),
-    dataIndex: 'userId',
+    title: t('wechat.creditBalance.organizationName'),
+    dataIndex: 'organizationName',
     width: 100,
   },
   {
@@ -19,43 +17,6 @@ export const columns: BasicColumn[] = [
     width: 100,
   },
   {
-    title: t('wechat.creditBalance.organizationId'),
-    dataIndex: 'organizationId',
-    width: 100,
-  },
-  {
-    title: t('wechat.creditBalance.user'),
-    dataIndex: 'user',
-    width: 100,
-  },
-  {
-    title: t('common.status'),
-    dataIndex: 'status',
-    width: 50,
-    customRender: ({ record }) => {
-      if (!Reflect.has(record, 'pendingStatus')) {
-        record.pendingStatus = false;
-      }
-      return h(Switch, {
-        checked: record.status === 1,
-        checkedChildren: t('common.on'),
-        unCheckedChildren: t('common.off'),
-        loading: record.pendingStatus,
-        onChange(checked, _) {
-          record.pendingStatus = true;
-          const newStatus = checked ? 1 : 2;
-          updateCreditBalance({ id: record.id, status: newStatus })
-            .then(() => {
-              record.status = newStatus;
-            })
-            .finally(() => {
-              record.pendingStatus = false;
-            });
-        },
-      });
-    },
-  },
-  {
     title: t('common.createTime'),
     dataIndex: 'createdAt',
     width: 70,
@@ -82,15 +43,18 @@ export const searchFormSchema: FormSchema[] = [
 
 export const formSchema: FormSchema[] = [
   {
-    field: 'id',
-    label: 'ID',
-    component: 'Input',
-    show: false,
-  },
-  {
-    field: 'userId',
-    label: t('wechat.creditBalance.userId'),
-    component: 'Input',
+    field: 'organizationId',
+    label: t('wechat.whatsapp.organizationName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getDepartmentList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.name,
+          value: label.id
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
     required: true,
   },
   {
@@ -99,28 +63,4 @@ export const formSchema: FormSchema[] = [
     component: 'InputNumber',
     required: true,
   },
-  {
-    field: 'organizationId',
-    label: t('wechat.creditBalance.organizationId'),
-    component: 'InputNumber',
-    required: true,
-  },
-  {
-    field: 'user',
-    label: t('wechat.creditBalance.user'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'status',
-    label: t('wechat.creditBalance.status'),
-    component: 'RadioButtonGroup',
-    defaultValue: 1,
-    componentProps: {
-      options: [
-        { label: t('common.on'), value: 1 },
-        { label: t('common.off'), value: 2 },
-      ],
-    },
-  },
 ];

+ 139 - 98
src/views/wechat/credit_balance/index.vue

@@ -22,131 +22,172 @@
           <TableAction
             :actions="[
               {
-                label: t('common.edit'),
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                label: t('common.delete'),
-                color: 'error',
-                popConfirm: {
-                  title: t('common.deleteConfirm'),
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
+                label: '查看明细',
+                onClick: handleCreditUsageList.bind(null, record),
               },
             ]"
           />
         </template>
       </template>
     </BasicTable>
+    <Modal
+      width="70vw"
+      v-model:open="creditUsageVisible"
+      title="积分明细"
+      @ok="creditUsageVisible = false"
+    >
+      <Table
+        :loading="creditUsageTableLoading"
+        :dataSource="creditUsageDataSource"
+        :columns="creditUsageColumns"
+        :pagination="creditUsageTablePagination"
+        :scroll="{ y: '50vh' }"
+      ></Table>
+    </Modal>
     <CreditBalanceDrawer @register="registerDrawer" @success="handleSuccess" />
   </div>
 </template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
-  import { Modal } from 'ant-design-vue';
+<script lang="ts" setup>
+  import { createVNode, ref, reactive } from 'vue';
+  import { Modal, Table } from 'ant-design-vue';
   import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
   import { BasicTable, useTable, TableAction } from '@/components/Table';
   import { Button } from '@/components/Button';
-
+  import { useRouter } from 'vue-router';
   import { useDrawer } from '@/components/Drawer';
   import CreditBalanceDrawer from './CreditBalanceDrawer.vue';
   import { useI18n } from 'vue-i18n';
 
   import { columns, searchFormSchema } from './creditBalance.data';
-  import { getCreditBalanceList, deleteCreditBalance } from '@/api/wechat/creditBalance';
+  import { getCreditBalanceList, deleteCreditBalance, getCreditUsageList } from '@/api/wechat/creditBalance';
+  import { formatToDateTime } from '/@/utils/dateUtil';
 
-  export default defineComponent({
-    name: 'CreditBalanceManagement',
-    components: { BasicTable, CreditBalanceDrawer, TableAction, Button },
-    setup() {
-      const { t } = useI18n();
-      const selectedIds = ref<number[] | string[]>();
-      const showDeleteButton = ref<boolean>(false);
+  const router = useRouter();
 
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerTable, { reload }] = useTable({
-        title: t('wechat.creditBalance.creditBalanceList'),
-        api: getCreditBalanceList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: true,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 120,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: 'right',
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          columnWidth: 40,
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>();
+  const showDeleteButton = ref<boolean>(false);
 
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerTable, { reload }] = useTable({
+    title: t('wechat.creditBalance.creditBalanceList'),
+    api: getCreditBalanceList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 120,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    }
+  });
 
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
+  
+  const creditUsageVisible = ref(false);const creditUsageTableLoading = ref(false)
+  const creditUsageDataSource = ref([])
+  const creditUsageTablePagination = reactive({
+    pageSize: 20,
+    total: 0,
+    hideOnSinglePage: true
+  })
+  const creditUsageColumns = [{
+    title: '类型',
+    dataIndex: 'ntype',
+    customRender: ({ record }) => {
+      const map = {
+        1: '消耗',
+        2: '充值'
       }
+      return map[record.ntype]
+    }
+  }, {
+    title: '用户',
+    dataIndex: 'userId',
+    key: 'userId'
+  }, {
+    title: '用量',
+    dataIndex: 'number',
+    key: 'number'
+  }, {
+    title: '原因',
+    dataIndex: 'reason',
+    key: 'reason'
+  }, {
+    title: t('wechat.usageDetail.createTime'),
+    dataIndex: 'createdAt',
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    }
+  },]
+
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
 
-      async function handleDelete(record: Recordable) {
-        const result = await deleteCreditBalance({ ids: [record.id] });
+  async function handleDelete(record: Recordable) {
+    const result = await deleteCreditBalance({ ids: [record.id] });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  async function handleBatchDelete() {
+    Modal.confirm({
+      title: t('common.deleteConfirm'),
+      icon: createVNode(ExclamationCircleOutlined),
+      async onOk() {
+        const result = await deleteCreditBalance({ ids: selectedIds.value as number[] });
         if (result.code === 0) {
+          showDeleteButton.value = false;
           await reload();
         }
-      }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
 
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteCreditBalance({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
+  async function handleSuccess() {
+    await reload();
+  }
 
-      async function handleSuccess() {
-        await reload();
-      }
+  async function handleCreditUsageList(record: Recordable) {
+    // creditUsageVisible.value = true
+    // creditUsageTableLoading.value = true
+    // try {
+    //   const res = await getCreditUsageList({ organizationId: record.organizationId, page: 1, pageSize: 1000 })
+    //   console.log(res.data)
+    //   creditUsageDataSource.value = res.data.data
+    //   creditUsageTablePagination.total = res.data.total
+    // } catch(e) {
 
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleBatchDelete,
-        showDeleteButton,
-      };
-    },
-  });
+    // } finally {
+    //   creditUsageTableLoading.value = false
+    // }
+    router.push({
+      path: `/wechat/credit_balance/usage`,
+      query: {
+        id: record.organizationId
+      }
+    });
+  }
 </script>
+

+ 29 - 0
src/views/wechat/credit_balance/usage/PayRechargeModal.vue

@@ -0,0 +1,29 @@
+<template>
+  <BasicModal title="充值详情" :canFullscreen="false" width="600px" @register="registerModal" @ok="handleOk">
+    <Form :model="info" :labelCol="{span: 4}">
+      <FormItem label="订单号" prop="info.outTradeNo">{{ info.outTradeNo }}</FormItem>
+      <FormItem label="用户" prop="info.userInfo.usernmae">{{ info.userInfo.usernmae }}</FormItem>
+      <FormItem label="用户" prop="info.organizationName">{{ info.organizationName }}</FormItem>
+      <FormItem label="用量" prop="info.number">{{ info.number }}</FormItem>
+      <FormItem label="金额" prop="info.money">{{ info.money }}</FormItem>
+      <FormItem label="时间" prop="info.updatedAt">{{ formatToDateTime(info.updatedAt) }}</FormItem>
+    </Form>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { Form, FormItem } from 'ant-design-vue';
+import { getPayRecharge } from '/@/api/wechat/creditBalance';
+import { BasicModal, useModalInner } from '/@/components/Modal'
+import { formatToDateTime } from '@/utils/dateUtil';
+
+const info = ref({})
+const [registerModal, { closeModal }] = useModalInner(async (params) => {
+  const res = await getPayRecharge(params);
+  info.value = res.data;
+});
+
+const handleOk = () => {
+  closeModal();
+}
+</script>

+ 32 - 0
src/views/wechat/credit_balance/usage/UsageDetailModal.vue

@@ -0,0 +1,32 @@
+<template>
+  <BasicModal title="积分使用详情" width="800px" @register="registerModal" @ok="handleOk">
+    <Form :model="info" :labelCol="{span: 4}">
+      <FormItem label="发送方wxid" prop="info.receiverId">{{ info.receiverId }}</FormItem>
+      <FormItem label="接收方wxid" prop="info.botId">{{ info.botId }}</FormItem>
+      <FormItem label="App" prop="info.App">{{ info.App }}</FormItem>
+      <FormItem label="发送内容" prop="info.request">{{ info.request }}</FormItem>
+      <FormItem label="回复内容" prop="info.response">{{ info.response }}</FormItem>
+      <FormItem label="令牌总量" prop="info.completionTokens">{{ info.completionTokens }}</FormItem>
+      <FormItem label="发送令牌量" prop="info.promptTokens">{{ info.promptTokens }}</FormItem>
+      <FormItem label="回复令牌量" prop="info.totalTokens">{{ info.totalTokens }}</FormItem>
+      <FormItem label="时间" prop="info.updatedAt">{{ formatToDateTime(info.updatedAt) }}</FormItem>
+    </Form>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { Form, FormItem } from 'ant-design-vue';
+import { getUsageDetail } from '/@/api/wechat/creditBalance';
+import { BasicModal, useModalInner } from '/@/components/Modal'
+import { formatToDateTime } from '@/utils/dateUtil';
+
+const info = ref({})
+const [registerModal, { closeModal }] = useModalInner(async (params) => {
+  const res = await getUsageDetail(params);
+  info.value = res.data;
+});
+
+const handleOk = () => {
+  closeModal();
+}
+</script>

+ 97 - 0
src/views/wechat/credit_balance/usage/index.vue

@@ -0,0 +1,97 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            v-if="record.nid"
+            :actions="[
+              {
+                label: '查看详情',
+                onClick: handleDetail.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <UsageDetailModal @register="registerUsageDetailModal" />
+    <PayRechargeModal @register="registerPayRechargeModal" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { getCreditUsageList } from '@/api/wechat/creditBalance';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+  import { useModal } from '@/components/Modal';
+  import UsageDetailModal from './UsageDetailModal.vue';
+  import PayRechargeModal from './PayRechargeModal.vue';
+  
+  import { useRoute } from 'vue-router';
+  import { useI18n } from 'vue-i18n';
+  import { formatToDateTime } from '/@/utils/dateUtil';
+  const { t } = useI18n();
+  
+  const [registerUsageDetailModal, { openModal: openUsageDetailModal }] = useModal();
+  const [registerPayRechargeModal, { openModal: openPayRechargeModal }] = useModal();
+  const route = useRoute();
+  const organizationId = parseInt(route.query.id as string);
+  const columns = [
+    {
+      title: '类型',
+      dataIndex: 'ntype',
+      customRender: ({ record }) => {
+        const map = {
+          1: '消耗',
+          2: '充值'
+        }
+        return map[record.ntype]
+      }
+    }, {
+      title: '用户',
+      dataIndex: 'userInfo.username',
+      customRender: ({ record }) => record.userInfo.username,
+    }, {
+      title: '用量',
+      dataIndex: 'number',
+      key: 'number'
+    }, {
+      title: '原因',
+      dataIndex: 'reason',
+      key: 'reason'
+    }, {
+      title: t('wechat.usageDetail.createTime'),
+      dataIndex: 'createdAt',
+      customRender: ({ record }) => {
+        return formatToDateTime(record.createdAt);
+      }
+    },
+  ]
+  const [registerTable] = useTable({
+    api: (params) => {
+      return getCreditUsageList({
+        ...params,
+        organizationId,
+      });
+    },
+    columns,
+    useSearchForm: false,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 120,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    }
+  });
+  const handleDetail = (record) => {
+    if (record.ntype === 1) {
+      openUsageDetailModal(true, {id: record.nid});
+    } else if (record.ntype === 2) {
+      openPayRechargeModal(true, {id: record.nid});
+    }
+  }
+</script>

+ 1 - 5
src/views/wechat/token/index.vue

@@ -22,10 +22,6 @@
           <TableAction
             :actions="[
               {
-                label: '选择 AI 角色',
-                onClick: handleEditMode.bind(null, record),
-              },
-              {
                 label: '编辑',
                 onClick: handleEdit.bind(null, record),
                 ifShow: permCode === '001',
@@ -63,7 +59,7 @@ import { useI18n } from 'vue-i18n';
 import { columns, searchFormSchema } from './token.data';
 import { getTokenList, deleteToken, updateToken } from '@/api/wechat/token';
 import { getPermCode } from '@/api/sys/user';
-import SelectAIAgentModal from '@/views/components/ SelectAIAgentModal.vue';
+import SelectAIAgentModal from '@/views/components/SelectAIAgentModal.vue';
 import { useModal } from '/@/components/Modal';
 let permCode = ref('');
 const { t } = useI18n();

+ 0 - 3
src/views/wechat/token/token.data.ts

@@ -102,9 +102,6 @@ export const formSchema: FormSchema[] = [
         }));
       }),
       params: { page: 1, pageSize: 100, type: 1 },
-      mode: 'tag',
-      tokenSeparators: [','],
-      change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数
     }),
     required: true,
   },

+ 1 - 1
src/views/wechat/wx/index.vue

@@ -235,7 +235,7 @@
   import { getUsageDetailList } from '@/api/wechat/usageDetail'
   import type { TabsProps } from 'ant-design-vue';
   import {formatToDateTime} from "@/utils/dateUtil";
-  import SelectAIAgentModal from '@/views/components/ SelectAIAgentModal.vue';
+  import SelectAIAgentModal from '@/views/components/SelectAIAgentModal.vue';
 
   const blockType = ref<TabsProps['all']>('part');
   const blockGroupType = ref<TabsProps['all']>('part');

+ 133 - 0
src/views/whatsapp/whatsapp/AutomationModal.vue

@@ -0,0 +1,133 @@
+<template>
+  <BasicModal
+    v-bind="$attrs" title="开场白设置"
+    okText="保存"
+    @register="register" @ok="handleSubmit"
+    width="50vw"
+    :minHeight="300" :height="500">
+    <div class="automation-container">
+      <Space direction="vertical" size="middle" style="width: 100%;">
+        <div v-for="(item, index) in prompts" :key="index" class="welcome-item">
+          <Input v-model:value="prompts[index]" placeholder="请输入开场白" />
+          <MinusCircleOutlined class="delete-icon" @click="removeWelcome(index)" />
+        </div>
+        <div class="add-button" @click="addWelcome">
+          <PlusOutlined />
+          <span>添加</span>
+        </div>
+      </Space>
+    </div>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { BasicModal, useModalInner } from '@/components/Modal';
+import { Input, Space } from 'ant-design-vue';
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
+import { message } from 'ant-design-vue';
+import { getAutomation, setAutomation } from '@/api/wechat/whatsapp';
+
+// 开场白列表
+const prompts = ref<string[]>([]);
+const data = ref<any>({});
+
+// 注册模态框,接收外部传入的数据
+const [register, { closeModal }] = useModalInner(async (record) => {
+  data.value = record;
+  // TODO: 获取已有的开场白列表
+  const res = await getAutomation({ phone: `${data.value.cc}${data.value.phone}`, waId: data.value.waId });
+  prompts.value = res.data.prompts || [];
+});
+
+/**
+ * 验证开场白列表是否包含空值
+ * @returns {boolean} - 如果包含空值返回true,否则返回false
+ */
+function hasEmptyWelcome(): boolean {
+  return prompts.value.some(item => !item.trim());
+}
+
+/**
+ * 添加新的开场白
+ */
+function addWelcome() {
+  if (hasEmptyWelcome()) {
+    message.error('开场白内容不能为空');
+    return false;
+  }
+  prompts.value.push('');
+}
+
+/**
+ * 删除指定索引的开场白
+ * @param index - 要删除的开场白索引
+ */
+function removeWelcome(index: number) {
+  prompts.value.splice(index, 1);
+}
+
+/**
+ * 提交开场白列表
+ * @returns {Promise<void>}
+ */
+async function handleSubmit() {
+  if (hasEmptyWelcome()) {
+    message.error('开场白内容不能为空');
+    return false;
+  }
+  try {
+    // TODO: 保存开场白列表
+    await setAutomation({
+      phone: `${data.value.cc}${data.value.phone}`,
+      waId: data.value.waId,
+      prompts: prompts.value,
+    });
+    closeModal();
+  } catch (error) {
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.automation-container {
+  padding: 0 16px;
+
+  .welcome-item {
+    display: flex;
+    align-items: center;
+
+    .delete-icon {
+      margin-left: 8px;
+      color: #999;
+      font-size: 16px;
+      cursor: pointer;
+
+      &:hover {
+        color: #ff4d4f;
+      }
+    }
+  }
+}
+
+.add-button {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 8px;
+  border: 1px dashed #d9d9d9;
+  border-radius: 2px;
+  color: #666;
+  cursor: pointer;
+
+  &:hover {
+    border-color: #1890ff;
+    color: #1890ff;
+  }
+
+  span {
+    margin-left: 4px;
+  }
+}
+
+</style>

+ 210 - 0
src/views/whatsapp/whatsapp/BlackWhiteModal.vue

@@ -0,0 +1,210 @@
+<template>
+  <BasicModal
+    v-bind="$attrs" title="黑白名单"
+    okText="保存"
+    @register="register" @ok="handleSubmit"
+    :can-fullscreen="false"
+    width="700px"
+  >
+  <Form :model="blackWhiteForm">
+    <FormItem name="allowList" label="客户白名单" style="margin-left: 30px">
+      <Switch
+        v-model:checked="flagAllowList"
+        @onChange="flagAllowList = !flagAllowList"
+      />
+      <div
+        v-if="flagAllowList"
+        style="margin-top: 10px; color: #9a9a9a; font-size: 12px;"
+      >
+        开启白名单表示仅向白名单内的客户发送消息
+      </div>
+      <div v-else style="margin-top: 10px; color: #9a9a9a; font-size: 12px;">
+        关闭白名单表示允许向所有客户发送消息
+      </div>
+      <FormItem v-if="flagAllowList" name="nickname" label="">
+        <Textarea v-model:value="blackWhiteForm.allowList" placeholder="每行一个号码" />
+      </FormItem>
+    </FormItem>
+    <FormItem name="groupAllowList" label="客群白名单" style="margin-left: 30px">
+      <Switch v-model:checked="flagGroupAllowList" />
+      <div
+        v-if="flagGroupAllowList"
+        style="margin-top: 10px; color: #9a9a9a; font-size: 12px;"
+      >
+        开启白名单表示仅向白名单内的客群发送消息
+      </div>
+      <div v-else style="margin-top: 10px; color: #9a9a9a; font-size: 12px;">
+        关闭白名单表示允许向所有客群发送消息
+      </div>
+      <FormItem v-if="flagGroupAllowList" name="nickname" label="">
+        <Textarea v-model:value="blackWhiteForm.groupAllowList" placeholder="每行一个号码" />
+      </FormItem>
+    </FormItem>
+
+    <FormItem name="blockList" label="客户黑名单" style="margin-left: 30px">
+      <Switch v-model:checked="flagBlockList" />
+      <div
+        v-if="flagBlockList"
+        style="margin-top: 10px; color: #9a9a9a; font-size: 12px;"
+      >
+        黑名单优先级高于白名单
+      </div>
+      <div v-else style="margin-top: 10px; color: #9a9a9a; font-size: 12px;">
+        关闭黑名单表示允许向所有客户发送消息
+      </div>
+      <FormItem v-if="flagBlockList" name="groupBlockList" label="">
+        <RadioGroup v-model:value="blockType" style="margin-bottom: 16px">
+          <RadioButton value="ALL">全部客户</RadioButton>
+          <RadioButton value="PART">部分客户</RadioButton>
+        </RadioGroup>
+      </FormItem>
+      <FormItem v-if="flagBlockList && blockType == 'PART'" name="nickname" label="">
+        <Textarea v-model:value="blackWhiteForm.blockList" placeholder="每行一个号码" />
+      </FormItem>
+    </FormItem>
+
+    <FormItem name="groupBlockList" label="客群黑名单" style="margin-left: 30px">
+      <Switch v-model:checked="flagGroupBlockList" />
+      <div
+        v-if="flagGroupBlockList"
+        style="margin-top: 10px; color: #9a9a9a; font-size: 12px;"
+      >
+        黑名单优先级高于白名单
+      </div>
+      <div v-else style="margin-top: 10px; color: #9a9a9a; font-size: 12px;">
+        关闭黑名单表示允许向所有客群发送消息
+      </div>
+      <FormItem v-if="flagGroupBlockList" name="groupBlockList" label="">
+        <RadioGroup v-model:value="blockGroupType" style="margin-bottom: 16px">
+          <RadioButton value="ALL">全部客群</RadioButton>
+          <RadioButton value="PART">部分客群</RadioButton>
+        </RadioGroup>
+      </FormItem>
+      <FormItem v-if="flagGroupBlockList && blockGroupType == 'PART'" name="groupBlockList" label="">
+        <Textarea v-model:value="blackWhiteForm.groupBlockList" placeholder="每行一个号码" />
+      </FormItem>
+    </FormItem>
+  </Form>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+import { ref, reactive } from 'vue';
+import { BasicModal, useModalInner } from '@/components/Modal';
+import { Form, FormItem, Textarea, Switch, RadioGroup, RadioButton } from 'ant-design-vue';
+import { getAllowBlockList, setAllowBlockList } from '@/api/wechat/whatsapp';
+import { AllowBlockList } from '/@/api/wechat/model/whatsappModel';
+
+const id = ref(0);
+const flagAllowList = ref(false);
+const flagGroupAllowList = ref(false);
+const flagBlockList = ref(false);
+const flagGroupBlockList = ref(false);
+const blockType = ref('ALL');
+const blockGroupType = ref('ALL');
+const blackWhiteForm = reactive<AllowBlockList>({
+  allowList: '',
+  groupAllowList: '',
+  blockList: '',
+  groupBlockList: '',
+});
+
+/**
+ * 重置表单所有字段到初始状态
+ */
+const resetForm = () => {
+  flagAllowList.value = false;
+  flagGroupAllowList.value = false;
+  flagBlockList.value = false;
+  flagGroupBlockList.value = false;
+  blockType.value = 'ALL';
+  blockGroupType.value = 'ALL';
+  blackWhiteForm.allowList = '';
+  blackWhiteForm.groupAllowList = '';
+  blackWhiteForm.blockList = '';
+  blackWhiteForm.groupBlockList = '';
+}
+
+/**
+ * 模态框内部注册方法,用于初始化数据
+ * @param record - 包含id的记录对象
+ */
+const [register, { closeModal }] = useModalInner(async (record) => {
+  resetForm();
+  id.value = record.id;
+  const { data: { allowList, groupAllowList, blockList, groupBlockList } } = await getAllowBlockList({ id: record.id });
+  
+  // 处理客户白名单
+  if (allowList !== '' && allowList?.toLocaleLowerCase() !== 'all') {
+    flagAllowList.value = true;
+    blackWhiteForm.allowList = allowList;
+  }
+  
+  // 处理客群白名单
+  if (groupAllowList !== '' && groupAllowList?.toLocaleLowerCase() !== 'all') {
+    flagGroupAllowList.value = true;
+    blackWhiteForm.groupAllowList = groupAllowList;
+  }
+  
+  // 处理客户黑名单
+  if (blockList !== '') {
+    flagBlockList.value = true
+    if (blockList?.toLocaleLowerCase() == 'all') {
+      blockType.value = 'ALL';
+    } else {
+      blockType.value = 'PART';
+      blackWhiteForm.blockList = blockList;
+    }
+  }
+  
+  // 处理客群黑名单
+  if (groupBlockList !== '') {
+    flagGroupBlockList.value = true
+    if (groupBlockList?.toLocaleLowerCase() == 'all') {
+      blockGroupType.value = 'ALL';
+    } else {
+      blockGroupType.value = 'PART';
+      blackWhiteForm.groupBlockList = groupBlockList;
+    }
+  }
+});
+
+const emit = defineEmits(['success'])
+
+/**
+ * 提交表单处理方法
+ * 根据各个开关状态构建并提交黑白名单数据
+ */
+const handleSubmit = async () => {
+  const params: AllowBlockList = {
+    id: id.value,
+    allowList: 'ALL',          // 默认允许所有客户
+    groupAllowList: 'ALL',     // 默认允许所有客群
+    blockList: '',             // 默认无黑名单
+    groupBlockList: '',        // 默认无客群黑名单
+  }
+  
+  // 如果启用了客户白名单,使用表单中的白名单值
+  if (flagAllowList.value) {
+    params.allowList = blackWhiteForm.allowList;
+  }
+  
+  // 如果启用了客群白名单,使用表单中的客群白名单值
+  if (flagGroupAllowList.value) {
+    params.groupAllowList = blackWhiteForm.groupAllowList;
+  }
+  
+  // 如果启用了客户黑名单,根据类型设置值
+  if (flagBlockList.value) {
+    params.blockList = blockType.value == 'ALL' ? 'ALL' : blackWhiteForm.blockList;
+  }
+  
+  // 如果启用了客群黑名单,根据类型设置值
+  if (flagGroupBlockList.value) {
+    params.groupBlockList = blockType.value == 'ALL' ? 'ALL' : blackWhiteForm.groupBlockList;
+  }
+  
+  await setAllowBlockList(params);
+  emit('success')
+  closeModal()
+};
+</script>

+ 56 - 0
src/views/whatsapp/whatsapp/CreateWhatsappDrawer.vue

@@ -0,0 +1,56 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    title="添加号码"
+    width="35%"
+    @ok="handleSubmit"
+    @open-change="handleVisibleChange"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { createFormSchema } from './whatsapp.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+
+  import { createWhatsapp } from '@/api/wechat/whatsapp';
+
+  const emit = defineEmits(['success']);
+
+  const [registerForm, { resetFields, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: createFormSchema,
+    showActionButtonGroup: false,
+  });
+  const handleVisibleChange = (visible) => {
+    if (visible) {
+      setDrawerProps({ confirmLoading: false });
+      resetFields();
+    }
+  }
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner();
+
+  async function handleSubmit() {
+    const values = await validate();
+    // values.phone = Number(values.phone)
+    setDrawerProps({ confirmLoading: true });
+    try {
+      const res = await createWhatsapp(values) 
+      if (res.code === 0) {
+        closeDrawer();
+        emit('success');
+      }
+    } catch (error) {
+      console.log(error);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+
+</script>

+ 64 - 0
src/views/whatsapp/whatsapp/EditBusinessDrawer.vue

@@ -0,0 +1,64 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    title="编辑商业信息"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { formSchema } from './whatsapp.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { useI18n } from 'vue-i18n';
+
+  import { getBusinessInfo, setBusinessInfo } from '@/api/wechat/whatsapp';
+
+  const emit = defineEmits(['success']);
+
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (record) => {
+    setDrawerProps({ confirmLoading: false });
+    const businessInfo = await getBusinessInfo({
+      phone: `${record.cc}${record.phone}`,
+      waId: record.waId,
+    });
+    businessInfo.data.websites = businessInfo.data.websites?.[0] || '';
+    // resetFields();
+    setFieldsValue({
+      phone: `${record.cc}${record.phone}`,
+      waId: record.waId,
+      ...businessInfo.data,
+    });
+  });
+
+  async function handleSubmit() {
+    const values = await validate();
+    values['websites'] = [values['websites']]
+    setDrawerProps({ confirmLoading: true });
+    try {
+      const res = await setBusinessInfo(values) 
+      if (res.code === 0) {
+        closeDrawer();
+        emit('success');
+      }
+    } catch (error) {
+      console.log(error);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+
+</script>

+ 87 - 0
src/views/whatsapp/whatsapp/TokenDetailModal.vue

@@ -0,0 +1,87 @@
+<template>
+  <BasicModal
+    v-bind="$attrs" title="令牌详情"
+    @register="register"
+    @ok="closeModal"
+    width="70vw"
+  >
+    <Table
+      :loading="tokenTableLoading"
+      :dataSource="tokenDataSource"
+      :columns="tokenColumns"
+      :pagination="tokenTablePagination"
+      :scroll="{ y: '50vh' }"
+    ></Table>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, reactive } from 'vue';
+  import { Table } from 'ant-design-vue';
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { useI18n } from 'vue-i18n';
+  import {formatToDateTime} from "@/utils/dateUtil";
+  import { getUsageDetailList } from '@/api/wechat/usageDetail'
+  import { UsageDetailInfo } from '/@/api/wechat/model/usageDetailModel';
+
+  const { t } = useI18n();
+  const [register, { closeModal }] = useModalInner(async (record) => {
+    tokenTableLoading.value = true
+    try {
+      const res = await getUsageDetailList({ botId: `${record.cc}${record.phone}`, page: 1, pageSize: 1000 })
+      tokenDataSource.value = res.data.data
+      tokenTablePagination.total = res.data.total
+    } catch(e) {
+
+    } finally {
+      tokenTableLoading.value = false
+    }
+  });
+  const tokenTableLoading = ref(true)
+  const tokenDataSource = ref<UsageDetailInfo[]>([])
+  const tokenTablePagination = reactive({
+    pageSize: 20,
+    total: 0,
+    hideOnSinglePage: true
+  })
+  const tokenColumns = [{
+    title: t('wechat.usageDetail.botId'),
+    dataIndex: 'botId',
+    key: 'botId'
+  }, {
+    title: t('wechat.usageDetail.receiverId'),
+    dataIndex: 'receiverId',
+    key: 'receiverId'
+  }, {
+    title: t('wechat.usageDetail.app'),
+    dataIndex: 'App',
+    key: 'App'
+  }, {
+    title: t('wechat.usageDetail.request'),
+    dataIndex: 'request',
+    key: 'request',
+    ellipsis: true,
+  }, {
+    title: t('wechat.usageDetail.response'),
+    dataIndex: 'response',
+    key: 'response',
+    ellipsis: true,
+  }, {
+    title: t('wechat.usageDetail.totalTokens'),
+    dataIndex: 'totalTokens',
+    key: 'totalTokens',
+  }, {
+    title: t('wechat.usageDetail.promptTokens'),
+    dataIndex: 'promptTokens',
+    key: 'promptTokens',
+  }, {
+    title: t('wechat.usageDetail.completionTokens'),
+    dataIndex: 'completionTokens',
+    key: 'completionTokens',
+  },{
+    title: t('wechat.usageDetail.createTime'),
+    dataIndex: 'createdAt',
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    }
+  }]
+</script>

+ 206 - 0
src/views/whatsapp/whatsapp/VerifyCodeModal.vue

@@ -0,0 +1,206 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    title="获取验证码"
+    ok-text="验证"
+    @register="register"
+    @ok="handleSubmit"
+    :can-fullscreen="false"
+    width="500px"
+  >
+    <Form
+      :model="formData"
+      ref="formRef"
+      :label-col="{ span: 5 }"
+    >
+      <FormItem
+        label="验证号码"
+        name="phone"
+        :rules="[{ required: true, message: '请输入验证号码' }]"
+      >
+        <Input :addon-before="`+${formData.cc}`" v-model:value="formData.phone" disabled />
+      </FormItem>
+      
+      <FormItem
+        label="语言"
+        name="locale"
+        :rules="[{ required: true, message: '请选择语言' }]"
+      >
+        <Select
+          v-model:value="formData.locale"
+          :options="languageCodeList"
+        />
+      </FormItem>
+
+      <FormItem
+        label="验证码"
+        name="code"
+        :rules="[{ 
+          required: true, 
+          message: '请输入完整的验证码',
+          validator: validateCode
+        }]"
+      >
+        <FormItemRest>
+          <div class="flex verification-code-group">
+            <Input
+              v-for="(_, index) in 6"
+              :key="index"
+              v-model:value="code[index]"
+              :class="[
+                '!w-12 text-center',
+                index === 0 ? 'first-input' : '',
+                index === 5 ? 'last-input' : '',
+                'code-input'
+              ]"
+              :maxlength="1"
+              @input="handleCodeInput($event, index)"
+              @keydown.delete="handleBackspace($event, index)"
+            />
+          </div>
+        </FormItemRest>
+      </FormItem>
+
+      <FormItem :wrapper-col="{ span: 24 }">
+        <div class="flex justify-center gap-4">
+          <Button type="primary" @click="getCode('sms')">获取短信验证码</Button>
+          <Button type="primary" @click="getCode('voice')">获取语音验证码</Button>
+        </div>
+      </FormItem>
+    </Form>
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue';
+import { BasicModal, useModalInner } from '@/components/Modal';
+import { Input, Select, Button, Form, FormItem, FormItemRest } from 'ant-design-vue';
+import type { FormInstance, Rule } from 'ant-design-vue';
+import { languageCodeList } from '@/utils/languageCode';
+import { sendCode, submitCode } from '@/api/wechat/whatsapp';
+
+const [register, { closeModal }] = useModalInner((record: Recordable) => {
+  formData.waId = record.waId;
+  formData.phone = record.phone;
+  formData.cc = record.cc;
+  formData.locale = languageCodeList[0].value;
+  formData.code = '';
+  code.value = ['', '', '', '', '', ''];
+});
+
+const formRef = ref<FormInstance>();
+
+const formData = reactive({
+  waId: '',
+  cc: '',
+  phone: '',
+  locale: '',
+  code: '',
+});
+
+const emit = defineEmits(['success'])
+const code = ref(['', '', '', '', '', '']);
+
+// 验证码校验函数
+const validateCode = async (_rule: Rule, value: string) => {
+  const codeStr = code.value.join('');
+  if (codeStr.length !== 6) {
+    return Promise.reject('请输入完整的验证码');
+  }
+  if (!/^\d{6}$/.test(codeStr)) {
+    return Promise.reject('验证码必须是6位数字');
+  }
+  return Promise.resolve();
+};
+
+const handleCodeInput = (e: Event, index: number) => {
+  const input = e.target as HTMLInputElement;
+  const value = input.value;
+  
+  // 只允许输入数字
+  if (!/^\d*$/.test(value)) {
+    code.value[index] = '';
+    return;
+  }
+
+  // 自动跳转到下一个输入框
+  if (value && index < 5) {
+    const nextInput = document.querySelector(`input[maxlength="1"]:nth-child(${index + 2})`);
+    (nextInput as HTMLElement)?.focus();
+  }
+
+  // 更新 formData 中的验证码
+  formData.code = code.value.join('');
+};
+
+const handleBackspace = (e: KeyboardEvent, index: number) => {
+  if (index > 0 && !code.value[index]) {
+    code.value[index - 1] = '';
+    const prevInput = document.querySelector(`input[maxlength="1"]:nth-child(${index})`);
+    (prevInput as HTMLElement)?.focus();
+    formData.code = code.value.join('');
+  }
+};
+
+const getCode = async (method: string) => {
+  await sendCode({
+    waId: formData.waId,
+    phone: formData.phone,
+    cc: formData.cc,
+    locale: formData.locale,
+    method,
+  });
+};
+
+const handleSubmit = async () => {
+  try {
+    await formRef.value?.validate();
+    const res = await submitCode({
+      waId: formData.waId,
+      phone: formData.phone,
+      cc: formData.cc,
+      locale: formData.locale,
+      code: formData.code,
+    });
+    if (res.code === 0) {
+      setTimeout(() => {
+        closeModal();
+        emit('success');
+      }, 1000);
+    }
+  } catch (error) {
+    console.error('表单验证失败:', error);
+  }
+};
+</script>
+
+<style scoped>
+.verification-code-group {
+  gap: 1px;
+}
+
+.code-input {
+  border-right: none;
+  border-radius: 0;
+}
+
+.code-input:focus {
+  z-index: 1;
+  border-right: 1px solid #4096ff;
+}
+
+.first-input {
+  border-top-left-radius: 6px !important;
+  border-bottom-left-radius: 6px !important;
+}
+
+.last-input {
+  border-right: 1px solid #d9d9d9;
+  border-top-right-radius: 6px !important;
+  border-bottom-right-radius: 6px !important;
+}
+
+.last-input:focus {
+  border-right: 1px solid #4096ff;
+}
+</style>

+ 0 - 75
src/views/whatsapp/whatsapp/WhatsappDrawer.vue

@@ -1,75 +0,0 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    @register="registerDrawer"
-    showFooter
-    :title="getTitle"
-    width="35%"
-    @ok="handleSubmit"
-  >
-    <BasicForm @register="registerForm" />
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
-  import { BasicForm, useForm } from '@/components/Form/index';
-  import { formSchema } from './whatsapp.data';
-  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
-  import { useI18n } from 'vue-i18n';
-
-  import { createWhatsapp, updateWhatsapp } from '@/api/wechat/whatsapp';
-
-  export default defineComponent({
-    name: 'WhatsappDrawer',
-    components: { BasicDrawer, BasicForm },
-    emits: ['success', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const { t } = useI18n();
-
-      const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
-        labelWidth: 160,
-        baseColProps: { span: 24 },
-        layout: 'vertical',
-        schemas: formSchema,
-        showActionButtonGroup: false,
-      });
-
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-
-        isUpdate.value = !!data?.isUpdate;
-
-        if (unref(isUpdate)) {
-          setFieldsValue({
-            ...data.record,
-          });
-        }
-      });
-
-      const getTitle = computed(() =>
-        !unref(isUpdate) ? t('wechat.whatsapp.addWhatsapp') : t('wechat.whatsapp.editWhatsapp'),
-      );
-
-      async function handleSubmit() {
-        const values = await validate();
-        setDrawerProps({ confirmLoading: true });
-        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
-        let result = unref(isUpdate) ? await updateWhatsapp(values) : await createWhatsapp(values);
-        if (result.code === 0) {
-          closeDrawer();
-          emit('success');
-        }
-        setDrawerProps({ confirmLoading: false });
-      }
-
-      return {
-        registerDrawer,
-        registerForm,
-        getTitle,
-        handleSubmit,
-      };
-    },
-  });
-</script>

+ 277 - 95
src/views/whatsapp/whatsapp/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <BasicTable @register="registerTable">
+    <BasicTable @register="registerTable" :loading="tableLoading">
       <template #tableTitle>
         <Button
           type="primary"
@@ -13,9 +13,7 @@
         </Button>
       </template>
       <template #toolbar>
-        <a-button type="primary" @click="handleCreate">
-          {{ t('wechat.whatsapp.addWhatsapp') }}
-        </a-button>
+        <a-button type="primary" @click="handleCreate">添加号码</a-button>
       </template>
       <template #bodyCell="{ column, record }">
         <template v-if="column.key === 'action'">
@@ -26,6 +24,47 @@
                 onClick: handleEdit.bind(null, record),
               },
               {
+                label: '选择 AI 角色',
+                onClick: handleEditMode.bind(null, record),
+              },
+              {
+                label: '黑白名单',
+                onClick: handleBlackAndWhiteList.bind(null, record),
+              },
+              {
+                label: '令牌详情',
+                onClick: handleTokenDetail.bind(null, record),
+              },
+              {
+                label: '开场白',
+                onClick: handleAutomation.bind(null, record),
+              },
+              {
+                ifShow: record.phoneStatus !== 'CONNECTED',
+                label: '验证',
+                onClick: handleVerify.bind(null, record),
+              },
+              {
+                ifShow: record.phoneStatus == 'DISCONNECTED',
+                label: '登录',
+                onClick: handleLogin.bind(null, record),
+              },
+              {
+                ifShow: record.phoneStatus == 'CONNECTED',
+                label: '退出',
+                color: 'error',
+                popConfirm: {
+                  title: '是否确认退出账号?',
+                  placement: 'left',
+                  confirm: handleLogout.bind(null, record),
+                },
+              },
+              {
+                ifShow: record.phoneStatus == 'CONNECTED',
+                label: '二维码',
+                onClick: handleQrCode.bind(null, record),
+              },
+              {
                 label: t('common.delete'),
                 color: 'error',
                 popConfirm: {
@@ -39,114 +78,257 @@
         </template>
       </template>
     </BasicTable>
-    <WhatsappDrawer @register="registerDrawer" @success="handleSuccess" />
+    <CreateWhatsappDrawer @register="registerCreateWhatsappDrawer" @success="handleSuccess" />
+    <EditBusinessDrawer @register="registerEditBusinessDrawer" @success="handleSuccess" />
+    <SelectAIAgentModal @register="registerAIAgentModal" @ok="handleUpdateAgent" />
+    <AutomationModal @register="registerAutomationModal" />
+    <BlackWhiteModal @register="registerBlackWhiteModal" @success="reload" />
+    <VerifyCodeModal @register="registerVerifyCodeModal" />
+    <TokenDetailModal @register="registerTokenDetailModal" />
   </div>
 </template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+  import { createVNode, ref } from 'vue';
+  import { useRouter } from 'vue-router';
   import { Modal } from 'ant-design-vue';
   import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
   import { BasicTable, useTable, TableAction } from '@/components/Table';
   import { Button } from '@/components/Button';
-
   import { useDrawer } from '@/components/Drawer';
-  import WhatsappDrawer from './WhatsappDrawer.vue';
+  import CreateWhatsappDrawer from './CreateWhatsappDrawer.vue';
+  import EditBusinessDrawer from './EditBusinessDrawer.vue';
   import { useI18n } from 'vue-i18n';
-
   import { columns, searchFormSchema } from './whatsapp.data';
-  import { getWhatsappList, deleteWhatsapp } from '@/api/wechat/whatsapp';
-
-  export default defineComponent({
-    name: 'WhatsappManagement',
-    components: { BasicTable, WhatsappDrawer, TableAction, Button },
-    setup() {
-      const { t } = useI18n();
-      const selectedIds = ref<number[] | string[]>();
-      const showDeleteButton = ref<boolean>(false);
-
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerTable, { reload }] = useTable({
-        title: t('wechat.whatsapp.whatsappList'),
-        api: getWhatsappList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: true,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 120,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: 'right',
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          columnWidth: 40,
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
+  import {
+    getWhatsappList,
+    deleteWhatsapp,
+    updateWhatsappAgent,
+    registerPhoneNumber,
+    deregisterPhoneNumber,
+  } from '@/api/wechat/whatsapp';
+  import SelectAIAgentModal from '@/views/components/SelectAIAgentModal.vue';
+  import AutomationModal from './AutomationModal.vue';
+  import BlackWhiteModal from './BlackWhiteModal.vue';
+  import VerifyCodeModal from './VerifyCodeModal.vue';
+  import TokenDetailModal from './TokenDetailModal.vue';
+  import { useModal } from '/@/components/Modal';
+  import { BaseIDsReq } from '@/api/model/baseModel';
+  import { WhatsappInfo } from '@/api/wechat/model/whatsappModel';
 
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
+  const router = useRouter();
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>();
+  const showDeleteButton = ref<boolean>(false);
+  const tableLoading = ref<boolean>(false);
 
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
-      }
+  const [registerCreateWhatsappDrawer, { openDrawer: openCreateDrawer }] = useDrawer();
+  const [registerEditBusinessDrawer, { openDrawer: openEditDrawer }] = useDrawer();
+  const [registerAIAgentModal, { openModal: openAIAgentModal, closeModal: closeAIAgentModal }] = useModal();
+  const [registerBlackWhiteModal, { openModal: openBlackWhiteModal }] = useModal();
+  const [registerVerifyCodeModal, { openModal: openVerifyCodeModal }] = useModal();
+  const [registerTokenDetailModal, { openModal: openTokenDetailModal }] = useModal();
+  const [registerAutomationModal, { openModal: openAutomationModal }] = useModal();
+  const [registerTable, { reload }] = useTable({
+    title: t('wechat.whatsapp.whatsappList'),
+    api: getWhatsappList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 540,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    },
+    rowKey: 'id',
+    rowSelection: {
+      type: 'checkbox',
+      columnWidth: 40,
+      onChange: (selectedRowKeys, _selectedRows) => {
+        selectedIds.value = selectedRowKeys as number[];
+        showDeleteButton.value = selectedRowKeys.length > 0;
+      },
+    },
+  });
 
-      async function handleDelete(record: Recordable) {
-        const result = await deleteWhatsapp({ ids: [record.id] });
+  /**
+   * 处理创建新WhatsApp账号操作
+   */
+  function handleCreate() {
+    openCreateDrawer(true);
+  }
+
+  /**
+   * 处理编辑WhatsApp账号信息
+   * @param record 当前行记录数据
+   */
+  function handleEdit(record: Recordable) {
+    openEditDrawer(true, record);
+  }
+
+  /**
+   * 处理删除单个WhatsApp账号
+   * @param record 要删除的账号记录
+   */
+  async function handleDelete(record: Recordable) {
+    const deleteParams: BaseIDsReq = {
+      ids: record.id,
+    };
+    const result = await deleteWhatsapp(deleteParams);
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  /**
+   * 处理批量删除WhatsApp账号
+   */
+  async function handleBatchDelete() {
+    Modal.confirm({
+      title: t('common.deleteConfirm'),
+      icon: createVNode(ExclamationCircleOutlined),
+      async onOk() {
+        const deleteParams: BaseIDsReq = {
+          ids: selectedIds.value as number[],
+          departmentName: '',
+          avatar: '',
+          departmentRemark: '',
+        };
+        const result = await deleteWhatsapp(deleteParams);
         if (result.code === 0) {
+          showDeleteButton.value = false;
           await reload();
         }
-      }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
 
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteWhatsapp({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
+  /**
+   * 操作成功后的回调处理
+   */
+  async function handleSuccess() {
+    await reload();
+  }
 
-      async function handleSuccess() {
-        await reload();
+  /**
+   * 处理AI角色选择
+   * @param record 当前账号记录
+   */
+  function handleEditMode(record: Recordable) {
+    const name = record.agentInfo?.name ?? '定制 AI 角色';
+    const id = record.agentInfo?.id ?? 0;
+    openAIAgentModal(true, {
+      accountId: record.id,
+      name,
+      id,
+      organizationId: record.organizationId,
+    });
+  }
+
+  /**
+   * 更新AI代理信息
+   * @param param0 包含账号ID和代理ID的对象
+   */
+  async function handleUpdateAgent({ id, agentId }) {
+    const updateParams: Partial<WhatsappInfo> = {
+      id,
+      agentId,
+    };
+    await updateWhatsappAgent(updateParams);
+    closeAIAgentModal();
+    reload();
+  }
+
+  /**
+   * 打开黑白名单设置界面
+   * @param record 当前账号记录
+   */
+  function handleBlackAndWhiteList(record: Recordable) {
+    openBlackWhiteModal(true, record);
+  }
+
+  /**
+   * 查看账号令牌详情
+   * @param record 当前账号记录
+   */
+  function handleTokenDetail(record: Recordable) {
+    openTokenDetailModal(true, record);
+  }
+
+  /**
+   * 设置自动回复开场白
+   * @param record 当前账号记录
+   */
+  function handleAutomation(record: Recordable) {
+    openAutomationModal(true, record);
+  }
+
+  /**
+   * 处理账号验证流程
+   * @param record 当前账号记录
+   */
+  function handleVerify(record: Recordable) {
+    openVerifyCodeModal(true, record);
+  }
+
+  /**
+   * 处理WhatsApp账号登录
+   * @param record 当前账号记录
+   */
+  async function handleLogin(record: Recordable) {
+    tableLoading.value = true;
+    try {
+      const result = await registerPhoneNumber({
+        waId: record.waId,
+        phone: `${record.cc}${record.phone}`,
+      });
+      if (result.code === 0) {
+        reload();
       }
+    } catch (e) {
+      console.error(e);
+    } finally {
+      tableLoading.value = false;
+    }
+  }
 
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleBatchDelete,
-        showDeleteButton,
-      };
-    },
-  });
+  /**
+   * 处理WhatsApp账号退出
+   * @param record 当前账号记录
+   */
+  async function handleLogout(record: Recordable) {
+    const result = await deregisterPhoneNumber({
+      waId: record.waId,
+      phone: `${record.cc}${record.phone}`,
+    });
+    if (result.code === 0) {
+      reload();
+    }
+  }
+
+  /**
+   * 显示WhatsApp登录二维码
+   * @param record 当前账号记录
+   */
+  function handleQrCode(record: Recordable) {
+    router.push({
+      path: '/whatsapp/qrcode',
+      query: {
+        waId: record.waId,
+        cc: record.cc,
+        phone: record.phone,
+      },
+    });
+  }
 </script>
+

+ 94 - 0
src/views/whatsapp/whatsapp/qrcode/QrcodeDrawer.vue

@@ -0,0 +1,94 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    :title="getTitle"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script setup lang="ts">
+  import { computed, unref, ref } from 'vue';
+  import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { createQrcode, updateQrcode } from '/@/api/wechat/whatsapp';
+
+  const emit = defineEmits(['success']);
+  const isUpdate = ref(true);
+  const getTitle = computed(() =>
+    !unref(isUpdate) ? '添加二维码' : '编辑二维码',
+  );
+  
+  const waId = ref('')
+  const phone = ref('')
+  const formSchema: FormSchema[] = [
+    {
+      field: 'qrdlCode',
+      label: 'qrdlCode',
+      component: 'Input',
+      show: false,
+      defaultValue: '',
+    },
+    {
+      field: 'prefilledMessage',
+      label: '预填信息',
+      component: 'InputTextArea',
+      required: true,
+      componentProps: {
+        autosize: {
+          minRows: 4,
+          maxRows: 10
+        }
+      },
+    },
+    {
+      field: 'generateQrImage',
+      label: '二维码格式',
+      component: 'RadioButtonGroup',
+      required: true,
+      componentProps: {
+        options: [
+          { label: 'PNG', value: 'PNG' },
+          { label: 'SVG', value: 'SVG' },
+        ],
+      },
+    },
+  ];
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    waId.value = data.waId
+    phone.value = data.phone
+    setDrawerProps({ confirmLoading: false });
+
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      setFieldsValue({
+        ...data.record,
+      });
+    }
+  });
+
+  async function handleSubmit() {
+    const values = await validate();
+    setDrawerProps({ confirmLoading: true });
+    values['waId'] = waId.value
+    values['phone'] = phone.value
+    let result = unref(isUpdate) ? await updateQrcode(values) : await createQrcode(values);
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
+</script>

+ 131 - 0
src/views/whatsapp/whatsapp/qrcode/index.vue

@@ -0,0 +1,131 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate">添加</a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '删除',
+                color: 'error',
+                popConfirm: {
+                  title: t('common.deleteConfirm'),
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                label: '查看',
+                onClick: handleView.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <QrcodeDrawer @register="registerDrawer" @success="reload" />
+    <Modal v-model:visible="visible" title="查看二维码" @ok="handleOk">
+      <div class="flex justify-center items-center p-10">
+        <img :src="qrcodeUrl" alt="二维码" width="320px" height="320px" />
+      </div>
+    </Modal>
+  </div>
+</template>
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+  import { useRoute } from 'vue-router';
+  import { useI18n } from 'vue-i18n';
+  import { getQrcode } from '/@/api/wechat/whatsapp';
+  import QrcodeDrawer from './QrcodeDrawer.vue';
+  import { useDrawer } from '@/components/Drawer';
+  import { removeQrcode } from '/@/api/wechat/whatsapp';
+  import { Modal } from 'ant-design-vue';
+  const { t } = useI18n();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  
+  const visible = ref(false)
+  const qrcodeUrl = ref('')
+  const route = useRoute();
+  const waId = route.query.waId as string;
+  const phone = route.query.cc as string + route.query.phone as string;
+  const columns = [
+    {
+      title: '预填消息',
+      dataIndex: 'prefilledMessage',
+      key: 'prefilledMessage'
+    }, {
+      title: '二维码格式',
+      dataIndex: 'generateQrImage',
+      key: 'generateQrImage'
+    }, {
+      title: '短链接',
+      dataIndex: 'deepLinkUrl',
+      key: 'deepLinkUrl'
+    }
+  ]
+  const [registerTable, { reload }] = useTable({
+    api: (params) => {
+      return getQrcode({
+        ...params,
+        waId,
+        phone,
+      });
+    },
+    columns,
+    useSearchForm: false,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 200,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    }
+  });
+
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+      waId,
+      phone,
+    });
+  }
+
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record,
+      waId,
+      phone,
+      isUpdate: true,
+    });
+  }
+
+  async function handleDelete(record: Recordable) {
+    const result = await removeQrcode({ 
+      waId,
+      phone,
+      qrdlCode: record.qrdlCode,
+     });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  function handleView(record: Recordable) {
+    qrcodeUrl.value = record.qrImageUrl;
+    visible.value = true;
+  }
+  function handleOk() {
+    visible.value = false;
+  }
+</script>

+ 235 - 122
src/views/whatsapp/whatsapp/whatsapp.data.ts

@@ -1,116 +1,128 @@
 import { BasicColumn, FormSchema } from '@/components/Table';
 import { useI18n } from '@/hooks/web/useI18n';
-import { formatToDateTime } from '@/utils/dateUtil';
-import { updateWhatsapp } from '@/api/wechat/whatsapp';
-import { Switch } from 'ant-design-vue';
-import { h } from 'vue';
 
 const { t } = useI18n();
+import { useUserStore } from '@/store/modules/user';
+const userStore = useUserStore();
+const userInfo = userStore.getUserInfo
+const isSuper = userInfo.roleName.some(str => str == '超级管理员')
+import { uploadApi } from '@/api/fms/file';
+import { getDepartmentList } from '/@/api/sys/department';
+import { getWhatsappChannelList } from '/@/api/whatsapp_channel/whatsappChannel';
+import { countryCodeList } from '@/utils/countryCode';
+import { ref } from 'vue';
+
+const businessVerify = [
+  { label: '零售', value: 'RETAIL' },
+  { label: '旅游', value: 'TRAVEL' },
+  { label: '专业服务', value: 'PROF_SERVICES' },
+  { label: '服装', value: 'APPAREL' },
+  { label: '政府', value: 'GOVT' },
+  { label: '娱乐', value: 'ENTERTAIN' },
+  { label: '杂货', value: 'GROCERY' },
+  { label: '酒店', value: 'HOTEL' },
+  { label: '其他', value: 'OTHER' },
+  { label: '汽车', value: 'AUTO' },
+  { label: '金融', value: 'FINANCE' },
+  { label: '美容', value: 'BEAUTY' },
+  { label: '教育', value: 'EDU' },
+  { label: '活动策划', value: 'EVENT_PLAN' },
+  { label: '非营利组织', value: 'NONPROFIT' },
+  { label: '餐厅', value: 'RESTAURANT' },
+  { label: '健康', value: 'HEALTH' }
+];
 
 export const columns: BasicColumn[] = [
   {
-    title: t('wechat.whatsapp.ak'),
-    dataIndex: 'ak',
-    width: 100,
-  },
-  {
-    title: t('wechat.whatsapp.sk'),
-    dataIndex: 'sk',
-    width: 100,
-  },
-  {
-    title: t('wechat.whatsapp.callback'),
-    dataIndex: 'callback',
-    width: 100,
-  },
-  {
-    title: t('wechat.whatsapp.account'),
-    dataIndex: 'account',
-    width: 100,
-  },
-  {
-    title: t('wechat.whatsapp.nickname'),
-    dataIndex: 'nickname',
-    width: 100,
-  },
-  {
     title: t('wechat.whatsapp.phone'),
     dataIndex: 'phone',
-    width: 100,
   },
   {
-    title: t('wechat.whatsapp.organizationId'),
-    dataIndex: 'organizationId',
-    width: 100,
+    title: t('wechat.whatsapp.phoneName'),
+    dataIndex: 'phoneName',
   },
   {
-    title: t('wechat.whatsapp.agentId'),
-    dataIndex: 'agentId',
-    width: 100,
+    title: t('wechat.whatsapp.phoneStatus'),
+    dataIndex: 'phoneStatus',
+    customRender: ({ record }) => {
+      return statusCode[record.phoneStatus]
+    },
   },
   {
-    title: t('wechat.whatsapp.apiBase'),
-    dataIndex: 'apiBase',
-    width: 100,
+    title: '评分质量',
+    dataIndex: 'quality',
+    customRender: ({ record }) => {
+      return qualityCode[record.quality]
+    },
   },
   {
-    title: t('wechat.whatsapp.apiKey'),
-    dataIndex: 'apiKey',
-    width: 100,
+    title: '发送限制',
+    dataIndex: 'frequency',
+    customRender: ({ record }) => {
+      return frequencyCode[record.frequency]
+    },
   },
   {
-    title: t('common.status'),
-    dataIndex: 'status',
-    width: 80,
+    title: '验证状态',
+    dataIndex: 'verifyStatus',
     customRender: ({ record }) => {
-      if (!Reflect.has(record, 'pendingStatus')) {
-        record.pendingStatus = false;
-      }
-      return h(Switch, {
-        checked: record.status === 1,
-        checkedChildren: t('common.on'),
-        unCheckedChildren: t('common.off'),
-        loading: record.pendingStatus,
-        onChange(checked, _) {
-          record.pendingStatus = true;
-          const newStatus = checked ? 1 : 2;
-          updateWhatsapp({ id: record.id, status: newStatus })
-            .then(() => {
-              record.status = newStatus;
-            })
-            .finally(() => {
-              record.pendingStatus = false;
-            });
-        },
-      });
+      return verifyStatusCode[record.verifyStatus]
     },
   },
   {
-    title: t('common.createTime'),
-    dataIndex: 'createdAt',
-    width: 170,
+    title: t('wechat.whatsapp.organizationName'),
+    dataIndex: 'organizationName',
+  },
+  {
+    title: t('wechat.whatsapp.agentInfo.name'),
+    dataIndex: 'agentInfo.name',
     customRender: ({ record }) => {
-      return formatToDateTime(record.createdAt);
+      return record.agentInfo.name == null ? '定制 AI 角色' : record.agentInfo.name
     },
   },
 ];
 
 export const searchFormSchema: FormSchema[] = [
   {
-    field: 'ak',
-    label: t('wechat.whatsapp.ak'),
-    component: 'Input',
+    ifShow: isSuper,
+    field: 'organizationId',
+    label: t('whatsapp_channel.whatsappChannel.organizationName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getDepartmentList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.name,
+          value: label.id
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
     colProps: { span: 8 },
   },
   {
-    field: 'sk',
-    label: t('wechat.whatsapp.sk'),
+    field: 'waName',
+    label: t('whatsapp_channel.whatsappChannel.waName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getWhatsappChannelList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.waName,
+          value: label.waName
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
+    colProps: { span: 8 },
+  },
+  {
+    field: 'phone',
+    label: t('wechat.whatsapp.phone'),
     component: 'Input',
     colProps: { span: 8 },
   },
   {
-    field: 'callback',
-    label: t('wechat.whatsapp.callback'),
+    field: 'phoneName',
+    label: t('wechat.whatsapp.phoneName'),
     component: 'Input',
     colProps: { span: 8 },
   },
@@ -118,81 +130,182 @@ export const searchFormSchema: FormSchema[] = [
 
 export const formSchema: FormSchema[] = [
   {
-    field: 'id',
-    label: 'ID',
-    component: 'Input',
-    show: false,
+    field: 'profilePictureUrl',
+    label: t('sys.user.avatar'),
+    required: false,
+    component: 'ImageUpload',
+    rules: [{ required: false, message: '请上传头像' }],
+    componentProps: {
+      api: uploadApi,
+      onChange: (value) => {
+        console.log(value);
+      }
+    },
+    show: true,
   },
   {
-    field: 'ak',
-    label: t('wechat.whatsapp.ak'),
+    show: false,
+    field: 'phone',
+    label: t('wechat.whatsapp.phone'),
     component: 'Input',
-    required: true,
   },
   {
-    field: 'sk',
-    label: t('wechat.whatsapp.sk'),
+    show: false,
+    field: 'waId',
+    label: t('whatsapp_channel.whatsappChannel.waId'),
     component: 'Input',
-    required: true,
   },
   {
-    field: 'callback',
-    label: t('wechat.whatsapp.callback'),
-    component: 'Input',
-    required: true,
+    field: 'vertical',
+    label: '行业',
+    component: 'Select',
+    componentProps: () => ({
+      options: businessVerify,
+    }),
   },
   {
-    field: 'account',
-    label: t('wechat.whatsapp.account'),
-    component: 'Input',
-    required: true,
+    field: 'description',
+    label: '描述',
+    component: 'InputTextArea',
+    componentProps: {
+      autosize: {
+        minRows: 3,
+        maxRows: 10
+      }
+    },
   },
   {
-    field: 'nickname',
-    label: t('wechat.whatsapp.nickname'),
-    component: 'Input',
-    required: true,
+    field: 'address',
+    label: '地址',
+    component: 'Input'
   },
   {
-    field: 'phone',
-    label: t('wechat.whatsapp.phone'),
+    field: 'email',
+    label: '邮箱',
+    component: 'Input'
+  },
+  {
+    field: 'websites',
+    label: '网站',
     component: 'Input',
-    required: true,
+    required: false,
   },
+];
+
+const waOptions = ref<{ label: string, value: string }[]>([])
+const getWaOptions = async (organizationId: number | undefined) => {
+  const res = await getWhatsappChannelList({ page: 1, pageSize: 1000, organizationId })
+  waOptions.value = res.data.data.map((item) => ({
+    label: item.waName || '',
+    value: item.waId || ''
+  }))
+}
+
+getWaOptions()
+
+export const createFormSchema: FormSchema[] = [
   {
-    field: 'organizationId',
-    label: t('wechat.whatsapp.organizationId'),
-    component: 'InputNumber',
-    required: true,
+    ifShow: false,
+    field: 'waName',
+    label: t('whatsapp_channel.whatsappChannel.waName'),
+    component: 'Input',
+    dynamicDisabled: true,
   },
   {
-    field: 'agentId',
-    label: t('wechat.whatsapp.agentId'),
-    component: 'InputNumber',
+    ifShow: isSuper,
+    field: 'organizationId',
+    label: t('whatsapp_channel.whatsappChannel.organizationName'),
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => ({
+      api: (params) => getDepartmentList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.name,
+          value: label.id
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+      onChange: (value, _) => {
+        if (_) {
+          getWaOptions(value)
+          formModel.waId = ''
+          formModel.waName = ''
+        }
+      }
+    }),
     required: true,
   },
   {
-    field: 'apiBase',
-    label: t('wechat.whatsapp.apiBase'),
-    component: 'Input',
+    field: 'waId',
+    label: '通道',
+    component: 'Select',
+    componentProps: ({ formModel }) => ({
+      options: waOptions.value,
+      onChange: (_, option) => {
+        if (option) {
+          formModel.waName = option.label
+        }
+      }
+    }),
     required: true,
   },
   {
-    field: 'apiKey',
-    label: t('wechat.whatsapp.apiKey'),
+    field: 'phoneName',
+    label: '名称',
     component: 'Input',
     required: true,
   },
   {
-    field: 'status',
-    label: t('wechat.whatsapp.status'),
-    component: 'RadioButtonGroup',
-    defaultValue: 1,
+    field: 'cc',
+    label: '国家代码',
+    component: 'Select',
     componentProps: {
-      options: [
-        { label: t('common.on'), value: 1 },
-        { label: t('common.off'), value: 2 },
-      ],
+      options: countryCodeList,
     },
+    required: true,
   },
-];
+  {
+    field: 'phone',
+    label: '号码',
+    component: 'Input',
+    required: true,
+  }
+]
+
+export const statusCode = {
+  MIGRATED: '已迁移',
+  FLAGGED: '被标记',
+  DISCONNECTED: '已断开',
+  UNVERIFIED: '未验证',
+  BANNED: '已封禁',
+  RATE_LIMITED: '限制发送',
+  PENDING: '待定',
+  CONNECTED: '已连接',
+  UNKNOWN: '未知',
+  DELETED: '已删除',
+  RESTRICTED: '已受限',
+}
+
+export const verifyStatusCode = {
+  REVOKED: '已撤销',
+  MORE_INFORMATION_REQUESTED: '需要提供更多信息',
+  VERIFIED: '校验通过',
+  REJECTED: '校验被拒绝',
+  NOT_VERIFIED: '未校验',
+}
+
+export const qualityCode = {
+  UNKNOWN: '未知',
+  RED: '低质量',
+  YELLOW: '中质量',
+  GREEN: '高质量',
+}
+
+export const frequencyCode = {
+  UNKNOWN: '未知',
+  TIER_100K: '100000',
+  TIER_UNLIMITED: 'unlimited',
+  TIER_250: '250',
+  TIER_1K: '1000',
+  TIER_50: '50',
+  TIER_10K: '10000',
+}

+ 194 - 0
src/views/whatsapp/whatsapp_batch/WhatsappBatchDrawer.vue

@@ -0,0 +1,194 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    title="添加群发消息"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <Form
+      :model="form"
+      :label-col="{ span: 4 }"
+      :wrapper-col="{ span: 20 }"
+      ref="formRef"
+      :rules="rules">
+      <FormItem label="任务名称" name="taskName">
+        <Input v-model:value="form.taskName" placeholder="请输入" />
+      </FormItem>
+      <FormItem label="发送账号" name="phone">
+        <Select v-model:value="form.phone" placeholder="请选择" style="width: 100%" :options="phoneOptions" @change="handlePhoneChange" />
+      </FormItem>
+      <FormItem label="客户标签" name="labels">
+        <FormItem>
+          <Checkbox
+            :disabled="btnDisabled"
+            v-model:checked="checked"
+            @change="handleSelectAll"
+          >
+            全选
+          </Checkbox>
+          <Tooltip>
+            <template #title>全选勾选后,包含所有联系人</template>
+            <QuestionCircleOutlined />
+          </Tooltip>
+        </FormItem>
+        <Select
+          :disabled="btnDisabled || checked"
+          v-model:value="form.labels"
+          :options="actionLabel"
+          allowClear
+          mode="multiple"
+          size="middle"
+          placeholder="请选择"
+          :max-tag-count="2"
+          @change="handleSelectChange"
+        ></Select>
+      </FormItem>
+      <FormItem label="模板" name="templateCode">
+        <Select v-model:value="templateName" placeholder="请选择" style="width: 100%" :options="templateOptions" @change="handleTemplateChange" />
+      </FormItem>
+    </Form>
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, reactive, onMounted } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { Form, FormItem, Input, Select, Checkbox, Tooltip } from 'ant-design-vue';
+  import { QuestionCircleOutlined } from '@ant-design/icons-vue';
+  import { getWhatsappList } from '/@/api/wechat/whatsapp';
+  import { getLabelSelectList } from '/@/api/wechat/label';
+  import { getWhatsappTemplateList } from '/@/api/whatsapp_template/whatsappTemplate';
+  import { createWhatsappBatch } from '/@/api/whatsapp_batch/whatsappBatch';
+  import type { Rule } from 'ant-design-vue/es/form';
+  
+  // 定义表单接口
+  interface FormState {
+    taskName: string;
+    phone: string;
+    cc: string;
+    tags: string;
+    labels: string[];
+    templateCode: string;
+    templateName: string;
+    lang: string;
+    msg?: string;
+  }
+
+  // 定义选项接口
+  interface SelectOption {
+    label: string;
+    value: string;
+  }
+
+  const emit = defineEmits(['success']);
+  const formRef = ref();
+  const checked = ref(false);
+  const phoneOptions = ref<SelectOption[]>([]);
+  const btnDisabled = ref(false);
+  const actionLabel = ref<any[]>([]);
+  const templateName = ref('');
+  const templateList = ref<any[]>([]);
+  const templateOptions = ref<SelectOption[]>([]);
+
+
+  // 初始化表单数据
+  const form = reactive<FormState>({
+    taskName: '',
+    phone: '',
+    cc: '',
+    tags: '',
+    labels: [],
+    templateCode: '',
+    templateName: '',
+    lang: '',
+  });
+
+  // 表单验证规则
+  const rules: Record<string, Rule[]> = {
+    taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
+    phone: [{ required: true, message: '请选择发送账号', trigger: 'blur' }],
+    labels: [{ required: true, message: '请选择客户标签', trigger: 'blur' }],
+    templateCode: [{ required: true, message: '请选择模板', trigger: 'blur' }],
+  };
+
+  // 初始化数据
+  onMounted(async () => {
+    // 获取WhatsApp账号列表
+    const res = await getWhatsappList({page: 1, pageSize: 1000});
+    phoneOptions.value = res.data.data
+      .filter(item => item.phoneStatus === 'CONNECTED')
+      .map(item => ({
+        label: `+${item.cc} ${item.phone}`,
+        value: `${item.cc}-${item.phone}`
+      }));
+
+    // 获取标签列表
+    const labelRes = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
+    actionLabel.value = labelRes.data;
+
+    // 获取模板列表
+    const templateRes = await getWhatsappTemplateList({page: 1, pageSize: 1000});
+    templateList.value = templateRes.data.data;
+    templateOptions.value = templateList.value.map(item => ({
+      label: `${item.name} (${item.language})`,
+      value: `${item.name}-${item.templateCode}-${item.language}`
+    }));
+  });
+
+  // 抽屉控制
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async () => {
+    templateName.value = '';
+    formRef.value?.resetFields();
+  });
+
+  // 处理手机号选择变更
+  const handlePhoneChange = (value: string) => {
+    const [cc, phone] = value.split('-');
+    form.cc = cc;
+    form.phone = phone;
+  };
+
+  // 处理全选操作
+  function handleSelectAll() {
+    if (checked.value) {
+      form.labels = actionLabel.value.map(item => item.value);
+      form.tags = actionLabel.value.map(item => item.label).join(',');
+    } else {
+      form.labels = [];
+      form.tags = ''; 
+    }
+  }
+
+  // 处理标签选择变更
+  function handleSelectChange(value: string[], option: any[]) {
+    const allOptions = actionLabel.value;
+    checked.value = form.labels.length === allOptions.length;
+    form.tags = option.map(item => item.label).join(',');
+  }
+
+  // 处理模板选择变更
+  function handleTemplateChange(value: string) {
+    const [templateName, templateCode, lang] = value.split('-');
+    form.templateName = templateName;
+    form.templateCode = templateCode;
+    form.lang = lang;
+  }
+
+  // 提交表单
+  async function handleSubmit() {
+    try {
+      await formRef.value?.validate();
+      setDrawerProps({ confirmLoading: true });
+      const res = await createWhatsappBatch(form);
+      if (res.code === 0) {
+        closeDrawer();
+        emit('success');
+      }
+    } catch (error) {
+      console.error('表单验证失败:', error);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 113 - 0
src/views/whatsapp/whatsapp_batch/index.vue

@@ -0,0 +1,113 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #tableTitle>
+        <Button
+          type="primary"
+          danger
+          preIcon="ant-design:delete-outlined"
+          v-if="showDeleteButton"
+          @click="handleBatchDelete"
+        >
+          {{ t('common.delete') }}
+        </Button>
+      </template>
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate">
+          {{ t('whatsapp_batch.whatsappBatch.addWhatsappBatch') }}
+        </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: '发送记录',
+                onClick: handleSendRecord.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <WhatsappBatchDrawer @register="registerDrawer" @success="handleSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { createVNode, ref } from 'vue';
+  import { Modal } from 'ant-design-vue';
+  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+  import { Button } from '@/components/Button';
+  import { useRouter } from 'vue-router';
+
+  import { useDrawer } from '@/components/Drawer';
+  import WhatsappBatchDrawer from './WhatsappBatchDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './whatsappBatch.data';
+  import { getWhatsappBatchList, deleteWhatsappBatch } from '@/api/whatsapp_batch/whatsappBatch';
+
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>();
+  const showDeleteButton = ref<boolean>(false);
+  const router = useRouter();
+
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerTable, { reload }] = useTable({
+    title: t('whatsapp_batch.whatsappBatch.whatsappBatchList'),
+    api: getWhatsappBatchList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 100,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    },
+  });
+
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+
+  //发送记录
+  function handleSendRecord(record: Recordable) {
+    console.log('发送记录');
+    router.push({
+      path: '/whatsapp/whatsapp_batch/send_record',
+      query: { id: record.id },
+    });
+  }
+
+  async function handleBatchDelete() {
+    Modal.confirm({
+      title: t('common.deleteConfirm'),
+      icon: createVNode(ExclamationCircleOutlined),
+      async onOk() {
+        const result = await deleteWhatsappBatch({ ids: selectedIds.value as number[] });
+        if (result.code === 0) {
+          showDeleteButton.value = false;
+          await reload();
+        }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
+
+  async function handleSuccess() {
+    await reload();
+  }
+</script>

+ 93 - 0
src/views/whatsapp/whatsapp_batch/send_record/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useRoute } from 'vue-router';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getWhatsappBatchHistory } from '/@/api/whatsapp_batch/whatsappBatch';
+  import { formatToDateTime } from '@/utils/dateUtil';
+  const route = useRoute();
+  const sourceId = route.query.id;
+
+  // 定义表格列
+  const columns = [
+    { title: '序号', dataIndex: 'index', width: 80 },
+    { title: '收件人', dataIndex: 'to', width: 200 },
+    { title: '发件人', width: 200, customRender: ({ record }) => `${record.cc}${record.phone}` },
+    {
+      title: '执行状态',
+      dataIndex: 'status',
+      width: 120,
+      customRender: ({ record }) => {
+        const statusMap = {
+          0: '未发送',
+          1: '发送成功',
+          2: '发送失败',
+        };
+        return statusMap[record.status] || '未知状态';
+      },
+    },
+    {
+      title: '执行时间',
+      dataIndex: 'sendTime',
+      width: 180,
+      customRender: ({ record, text }) => {
+        return record.status === 0 ? '' : text ? formatToDateTime(text) : '';
+      },
+    },
+  ];
+
+  // 定义搜索表单
+  const searchFormSchema = [
+    {
+      field: 'status',
+      label: '执行状态',
+      component: 'Select',
+      componentProps: {
+        options: [
+          { label: '未发送', value: 0 },
+          { label: '成功', value: 1 },
+          { label: '失败', value: 2 },
+        ],
+      },
+      colProps: { span: 8 },
+    },
+    {
+      field: 'phone',
+      label: '发送手机',
+      component: 'Input',
+      colProps: { span: 8 },
+    },
+  ];
+
+  // 注册表格
+  const [registerTable] = useTable({
+    api: (params) => getWhatsappBatchHistory({ id: Number(sourceId), ...params }),
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+  });
+
+  // 查看记录
+  function viewRecord(record: Recordable) {
+    console.log('查看发送记录', record);
+  }
+</script>
+
+<style scoped>
+  .container {
+    margin: 20px;
+    border-radius: 4px;
+    background-color: #fff;
+  }
+</style>

+ 27 - 50
src/views/whatsapp_batch/whatsapp_batch/whatsappBatch.data.ts → src/views/whatsapp/whatsapp_batch/whatsappBatch.data.ts

@@ -1,9 +1,6 @@
 import { BasicColumn, FormSchema } from '@/components/Table';
 import { useI18n } from '@/hooks/web/useI18n';
 import { formatToDateTime } from '@/utils/dateUtil';
-import { updateWhatsappBatch } from '@/api/whatsapp_batch/whatsappBatch';
-import { Switch } from 'ant-design-vue';
-import { h } from 'vue';
 
 const { t } = useI18n();
 
@@ -11,21 +8,16 @@ export const columns: BasicColumn[] = [
   {
     title: t('whatsapp_batch.whatsappBatch.batchNo'),
     dataIndex: 'batchNo',
-    width: 100,
+    width: 200,
   },
   {
     title: t('whatsapp_batch.whatsappBatch.taskName'),
     dataIndex: 'taskName',
-    width: 100,
+    width: 150,
   },
   {
     title: t('whatsapp_batch.whatsappBatch.fromPhone'),
-    dataIndex: 'fromPhone',
-    width: 100,
-  },
-  {
-    title: t('whatsapp_batch.whatsappBatch.msg'),
-    dataIndex: 'msg',
+    dataIndex: 'phone',
     width: 100,
   },
   {
@@ -34,9 +26,8 @@ export const columns: BasicColumn[] = [
     width: 100,
   },
   {
-    title: t('whatsapp_batch.whatsappBatch.tagids'),
-    dataIndex: 'tagids',
-    width: 100,
+    title: t('whatsapp_batch.whatsappBatch.startTime'),
+    dataIndex: 'startTimeStr',
   },
   {
     title: t('whatsapp_batch.whatsappBatch.total'),
@@ -54,56 +45,42 @@ export const columns: BasicColumn[] = [
     width: 100,
   },
   {
-    title: t('whatsapp_batch.whatsappBatch.startTime'),
-    dataIndex: 'startTime',
+    title: t('common.status'),
+    dataIndex: 'status',
+    customRender: ({ record }) => {
+      switch (record.status) {
+        case 0:
+          return '未开始';   // 启用状态
+        case 1:
+          return '发送中';  // 禁用状态
+        case 2:
+          return '发送完成';   // 开始状态
+        case 3:
+          return '发送中止';    // 暂停状态
+        default:
+          return '未知';   // 未知状态
+      }
+    },
     width: 100,
   },
   {
-    title: t('whatsapp_batch.whatsappBatch.stopTime'),
-    dataIndex: 'stopTime',
+    title: '模板编码',
+    dataIndex: 'templateCode',
     width: 100,
   },
   {
-    title: t('whatsapp_batch.whatsappBatch.sendTime'),
-    dataIndex: 'sendTime',
+    title: '模板名称',
+    dataIndex: 'templateName',
     width: 100,
   },
   {
-    title: t('whatsapp_batch.whatsappBatch.organizationId'),
-    dataIndex: 'organizationId',
+    title: '语言',
+    dataIndex: 'lang',
     width: 100,
   },
   {
-    title: t('common.status'),
-    dataIndex: 'status',
-    width: 50,
-    customRender: ({ record }) => {
-      if (!Reflect.has(record, 'pendingStatus')) {
-        record.pendingStatus = false;
-      }
-      return h(Switch, {
-        checked: record.status === 1,
-        checkedChildren: t('common.on'),
-        unCheckedChildren: t('common.off'),
-        loading: record.pendingStatus,
-        onChange(checked, _) {
-          record.pendingStatus = true;
-          const newStatus = checked ? 1 : 2;
-          updateWhatsappBatch({ id: record.id, status: newStatus })
-            .then(() => {
-              record.status = newStatus;
-            })
-            .finally(() => {
-              record.pendingStatus = false;
-            });
-        },
-      });
-    },
-  },
-  {
     title: t('common.createTime'),
     dataIndex: 'createdAt',
-    width: 70,
     customRender: ({ record }) => {
       return formatToDateTime(record.createdAt);
     },

+ 2 - 0
src/views/whatsapp/whatsapp_channel/whatsapp_channel/WhatsappChannelDrawer.vue → src/views/whatsapp/whatsapp_channel/WhatsappChannelDrawer.vue

@@ -56,6 +56,8 @@
         const values = await validate();
         setDrawerProps({ confirmLoading: true });
         values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
+        values['wabaId'] = Number(values['wabaId'])
+        values['businessId'] = Number(values['businessId'])
         let result = unref(isUpdate) ? await updateWhatsappChannel(values) : await createWhatsappChannel(values);
         if (result.code === 0) {
           closeDrawer();

+ 130 - 0
src/views/whatsapp/whatsapp_channel/index.vue

@@ -0,0 +1,130 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #tableTitle>
+        <Button
+          type="primary"
+          danger
+          preIcon="ant-design:delete-outlined"
+          v-if="showDeleteButton"
+          @click="handleBatchDelete"
+        >
+          {{ t('common.delete') }}
+        </Button>
+      </template>
+      <template #toolbar>
+        <a-button v-if="isSuper" type="primary" @click="handleCreate">
+          {{ t('whatsapp_channel.whatsappChannel.addWhatsappChannel') }}
+        </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '删除',
+                color: 'error',
+                popConfirm: {
+                  title: t('common.deleteConfirm'),
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <WhatsappChannelDrawer @register="registerDrawer" @success="handleSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { createVNode, ref } from 'vue';
+  import { Modal } from 'ant-design-vue';
+  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+  import { Button } from '@/components/Button';
+
+  import { useUserStore } from '@/store/modules/user';
+  import { useDrawer } from '@/components/Drawer';
+  import WhatsappChannelDrawer from './WhatsappChannelDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './whatsappChannel.data';
+  import { getWhatsappChannelList, deleteWhatsappChannel } from '@/api/whatsapp_channel/whatsappChannel';
+
+  const userStore = useUserStore();
+  const userInfo = userStore.getUserInfo
+  const isSuper = userInfo.roleName.some(str => str == '超级管理员')
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>();
+  const showDeleteButton = ref<boolean>(false);
+
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerTable, { reload }] = useTable({
+    title: t('whatsapp_channel.whatsappChannel.whatsappChannelList'),
+    api: getWhatsappChannelList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: true,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      ifShow: isSuper,
+      width: 150,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    }
+  });
+
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+
+  async function handleDelete(record: Recordable) {
+    const result = await deleteWhatsappChannel({ ids: [record.id] });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  async function handleBatchDelete() {
+    Modal.confirm({
+      title: t('common.deleteConfirm'),
+      icon: createVNode(ExclamationCircleOutlined),
+      async onOk() {
+        const result = await deleteWhatsappChannel({ ids: selectedIds.value as number[] });
+        if (result.code === 0) {
+          showDeleteButton.value = false;
+          await reload();
+        }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
+
+  async function handleSuccess() {
+    await reload();
+  }
+</script>

+ 79 - 82
src/views/whatsapp/whatsapp_channel/whatsapp_channel/whatsappChannel.data.ts → src/views/whatsapp/whatsapp_channel/whatsappChannel.data.ts

@@ -4,81 +4,44 @@ import { formatToDateTime } from '@/utils/dateUtil';
 import { updateWhatsappChannel } from '@/api/whatsapp_channel/whatsappChannel';
 import { Switch } from 'ant-design-vue';
 import { h } from 'vue';
+import { getDepartmentList } from '/@/api/sys/department';
+import { getWhatsappChannelList } from '@/api/whatsapp_channel/whatsappChannel';
 
+import { useUserStore } from '@/store/modules/user';
 const { t } = useI18n();
+const userStore = useUserStore();
+const userInfo = userStore.getUserInfo
+const isSuper = userInfo.roleName.some(str => str == '超级管理员')
 
 export const columns: BasicColumn[] = [
   {
-    title: t('whatsapp_channel.whatsappChannel.ak'),
-    dataIndex: 'ak',
-    width: 100,
-  },
-  {
-    title: t('whatsapp_channel.whatsappChannel.sk'),
-    dataIndex: 'sk',
-    width: 100,
-  },
-  {
     title: t('whatsapp_channel.whatsappChannel.waId'),
     dataIndex: 'waId',
-    width: 100,
   },
   {
     title: t('whatsapp_channel.whatsappChannel.waName'),
     dataIndex: 'waName',
-    width: 100,
+  },
+  {
+    title: t('whatsapp_channel.whatsappChannel.organizationName'),
+    dataIndex: 'organizationName',
   },
   {
     title: t('whatsapp_channel.whatsappChannel.wabaId'),
     dataIndex: 'wabaId',
-    width: 100,
   },
   {
     title: t('whatsapp_channel.whatsappChannel.businessId'),
     dataIndex: 'businessId',
-    width: 100,
-  },
-  {
-    title: t('whatsapp_channel.whatsappChannel.organizationId'),
-    dataIndex: 'organizationId',
-    width: 100,
   },
   {
     title: t('whatsapp_channel.whatsappChannel.verifyAccount'),
     dataIndex: 'verifyAccount',
-    width: 100,
-  },
-  {
-    title: t('common.status'),
-    dataIndex: 'status',
-    width: 50,
-    customRender: ({ record }) => {
-      if (!Reflect.has(record, 'pendingStatus')) {
-        record.pendingStatus = false;
-      }
-      return h(Switch, {
-        checked: record.status === 1,
-        checkedChildren: t('common.on'),
-        unCheckedChildren: t('common.off'),
-        loading: record.pendingStatus,
-        onChange(checked, _) {
-          record.pendingStatus = true;
-          const newStatus = checked ? 1 : 2;
-          updateWhatsappChannel({ id: record.id, status: newStatus })
-            .then(() => {
-              record.status = newStatus;
-            })
-            .finally(() => {
-              record.pendingStatus = false;
-            });
-        },
-      });
-    },
   },
   {
     title: t('common.createTime'),
     dataIndex: 'createdAt',
-    width: 70,
+    ifShow: false,
     customRender: ({ record }) => {
       return formatToDateTime(record.createdAt);
     },
@@ -87,20 +50,57 @@ export const columns: BasicColumn[] = [
 
 export const searchFormSchema: FormSchema[] = [
   {
-    field: 'ak',
-    label: t('whatsapp_channel.whatsappChannel.ak'),
-    component: 'Input',
+    ifShow: isSuper,
+    field: 'organizationId',
+    label: t('whatsapp_channel.whatsappChannel.organizationName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getDepartmentList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.name,
+          value: label.id
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
     colProps: { span: 8 },
   },
   {
-    field: 'sk',
-    label: t('whatsapp_channel.whatsappChannel.sk'),
+    field: 'waName',
+    label: t('whatsapp_channel.whatsappChannel.waName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getWhatsappChannelList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.waName,
+          value: label.waName
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
+    colProps: { span: 8 },
+  },
+  {
+    field: 'waId',
+    label: t('whatsapp_channel.whatsappChannel.waId'),
     component: 'Input',
     colProps: { span: 8 },
   },
   {
-    field: 'waName',
-    label: t('whatsapp_channel.whatsappChannel.waName'),
+    field: 'wabaId',
+    label: t('whatsapp_channel.whatsappChannel.wabaId'),
+    component: 'InputNumber',
+    componentProps: {
+      style: {
+        width: '100%'
+      },
+      controls: false,
+    },
+    colProps: { span: 8 },
+  },
+  {
+    field: 'verifyAccount',
+    label: t('whatsapp_channel.whatsappChannel.verifyAccount'),
     component: 'Input',
     colProps: { span: 8 },
   },
@@ -114,21 +114,24 @@ export const formSchema: FormSchema[] = [
     show: false,
   },
   {
-    field: 'ak',
-    label: t('whatsapp_channel.whatsappChannel.ak'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'sk',
-    label: t('whatsapp_channel.whatsappChannel.sk'),
-    component: 'Input',
+    field: 'organizationId',
+    label: t('whatsapp_channel.whatsappChannel.organizationName'),
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getDepartmentList(params).then(response => {
+        return response.data.data.map((label) => ({
+          label: label.name,
+          value: label.id
+        }));
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+    }),
     required: true,
   },
   {
     field: 'waId',
     label: t('whatsapp_channel.whatsappChannel.waId'),
-    component: 'InputNumber',
+    component: 'Input',
     required: true,
   },
   {
@@ -140,37 +143,31 @@ export const formSchema: FormSchema[] = [
   {
     field: 'wabaId',
     label: t('whatsapp_channel.whatsappChannel.wabaId'),
-    component: 'InputNumber',
+    component: 'Input',
     required: true,
   },
   {
     field: 'businessId',
     label: t('whatsapp_channel.whatsappChannel.businessId'),
-    component: 'InputNumber',
+    component: 'Input',
     required: true,
   },
   {
-    field: 'organizationId',
-    label: t('whatsapp_channel.whatsappChannel.organizationId'),
-    component: 'InputNumber',
+    field: 'ak',
+    label: t('whatsapp_channel.whatsappChannel.ak'),
+    component: 'Input',
     required: true,
   },
   {
-    field: 'verifyAccount',
-    label: t('whatsapp_channel.whatsappChannel.verifyAccount'),
+    field: 'sk',
+    label: t('whatsapp_channel.whatsappChannel.sk'),
     component: 'Input',
     required: true,
   },
   {
-    field: 'status',
-    label: t('whatsapp_channel.whatsappChannel.status'),
-    component: 'RadioButtonGroup',
-    defaultValue: 1,
-    componentProps: {
-      options: [
-        { label: t('common.on'), value: 1 },
-        { label: t('common.off'), value: 2 },
-      ],
-    },
+    field: 'verifyAccount',
+    label: t('whatsapp_channel.whatsappChannel.verifyAccount'),
+    component: 'Input',
+    required: true,
   },
 ];

+ 0 - 152
src/views/whatsapp/whatsapp_channel/whatsapp_channel/index.vue

@@ -1,152 +0,0 @@
-<template>
-  <div>
-    <BasicTable @register="registerTable">
-      <template #tableTitle>
-        <Button
-          type="primary"
-          danger
-          preIcon="ant-design:delete-outlined"
-          v-if="showDeleteButton"
-          @click="handleBatchDelete"
-        >
-          {{ t('common.delete') }}
-        </Button>
-      </template>
-      <template #toolbar>
-        <a-button type="primary" @click="handleCreate">
-          {{ t('whatsapp_channel.whatsappChannel.addWhatsappChannel') }}
-        </a-button>
-      </template>
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'action'">
-          <TableAction
-            :actions="[
-              {
-                icon: 'clarity:note-edit-line',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                icon: 'ant-design:delete-outlined',
-                color: 'error',
-                popConfirm: {
-                  title: t('common.deleteConfirm'),
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
-          />
-        </template>
-      </template>
-    </BasicTable>
-    <WhatsappChannelDrawer @register="registerDrawer" @success="handleSuccess" />
-  </div>
-</template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
-  import { Modal } from 'ant-design-vue';
-  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
-  import { BasicTable, useTable, TableAction } from '@/components/Table';
-  import { Button } from '@/components/Button';
-
-  import { useDrawer } from '@/components/Drawer';
-  import WhatsappChannelDrawer from './WhatsappChannelDrawer.vue';
-  import { useI18n } from 'vue-i18n';
-
-  import { columns, searchFormSchema } from './whatsappChannel.data';
-  import { getWhatsappChannelList, deleteWhatsappChannel } from '@/api/whatsapp_channel/whatsappChannel';
-
-  export default defineComponent({
-    name: 'WhatsappChannelManagement',
-    components: { BasicTable, WhatsappChannelDrawer, TableAction, Button },
-    setup() {
-      const { t } = useI18n();
-      const selectedIds = ref<number[] | string[]>();
-      const showDeleteButton = ref<boolean>(false);
-
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerTable, { reload }] = useTable({
-        title: t('whatsapp_channel.whatsappChannel.whatsappChannelList'),
-        api: getWhatsappChannelList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: true,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 30,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: undefined,
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          columnWidth: 20,
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
-
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
-
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
-      }
-
-      async function handleDelete(record: Recordable) {
-        const result = await deleteWhatsappChannel({ ids: [record.id] });
-        if (result.code === 0) {
-          await reload();
-        }
-      }
-
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteWhatsappChannel({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
-
-      async function handleSuccess() {
-        await reload();
-      }
-
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleBatchDelete,
-        showDeleteButton,
-      };
-    },
-  });
-</script>

+ 67 - 0
src/views/whatsapp/whatsapp_contact/GroupLabelDrawer.vue

@@ -0,0 +1,67 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    :title="getTitle"
+    width="500px"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, unref } from 'vue';
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { groupLabelFormSchema } from './whatsappContact.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { useI18n } from 'vue-i18n';
+
+  import {updateContactLabel} from "@/api/whatsapp_contact/whatsappContact";
+
+  const emit = defineEmits(['success', 'register']);
+  
+  const isUpdate = ref(true);
+  const { t } = useI18n();
+
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: groupLabelFormSchema,
+    showActionButtonGroup: false,
+  });
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    setDrawerProps({ confirmLoading: false });
+
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      setFieldsValue({
+        labelIds: data.record.labelRelationships.map(item => item.value),
+        contactId: data.record.id
+      });
+    }
+  });
+
+  const getTitle = t('wechat.contact.editContact')
+
+  async function handleSubmit() {
+    const values = await validate();
+    console.log(values,'values')
+    values.contactId = Number(values.contactId)
+    if (!values.labelIds) {
+      values.labelIds = []
+    }
+    setDrawerProps({ confirmLoading: true });
+
+    let result = await updateContactLabel(values)
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
+</script>

+ 66 - 0
src/views/whatsapp/whatsapp_contact/ImportTempleModal.vue

@@ -0,0 +1,66 @@
+<template>
+  <BasicModal
+  @register="register"
+  title="导入"
+  :can-fullscreen="false"
+  okText="上传"
+  :ok-button-props="{ disabled: !fileList.length, loading: uploading }"
+  @ok="handleOk"
+  @cancel="fileList.length = 0"
+  >
+    <Upload 
+      v-model:file-list="fileList"
+      :beforeUpload="handleBeforeUpload"
+      name="file"
+      accept=".csv,.xlsx,.xls"
+    >
+      <a-button type="primary">
+        <UploadOutlined />
+        上传文件
+      </a-button>
+    </Upload>
+    <div class="mt-2 c-gray">支持扩展名:.xlsx .csv</div>
+    <div class="mt-2">
+      <a href="https://aifile.ascrm.cn/system/whatsapp_contact_template.csv" download>下载模板</a>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { Upload } from 'ant-design-vue';
+  import { UploadOutlined } from '@ant-design/icons-vue';
+  import { importWhatsappContact } from '@/api/whatsapp_contact/whatsappContact';
+  import { ref } from 'vue';
+  import { UploadProps } from 'ant-design-vue/es/vc-upload/interface';
+ 
+  const fileList = ref<UploadProps['fileList']>([]);
+  const uploading = ref<boolean>(false);
+
+  const handleBeforeUpload = (file: File) => {
+    fileList.value = [file];
+    return false;
+  };
+
+  const emit = defineEmits(['success']);
+  const [register, { closeModal }] = useModalInner();
+
+  const handleOk = async () => {
+    if (!fileList.value.length) {
+      return;
+    }
+    
+    uploading.value = true;
+    try {
+      const formData = new FormData();
+      formData.append('file', fileList.value[0].originFileObj);
+      const res = await importWhatsappContact(formData);
+      if (res.code === 0) {
+        fileList.value = [];
+        closeModal();
+        emit('success');
+      }
+    } finally {
+      uploading.value = false;
+    }
+  };
+</script>

+ 101 - 0
src/views/whatsapp/whatsapp_contact/SendMsgDrawer.vue

@@ -0,0 +1,101 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    title="发送消息"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" class="form-container" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, unref, onMounted } from 'vue';
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { FormSchema } from '@/components/Table';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  
+  import { sendBatchMsgText } from '@/api/whatsapp_contact/whatsappContact';
+  import { getWhatsappList } from '@/api/wechat/whatsapp';
+  import { getWhatsappTemplateList } from '@/api/whatsapp_template/whatsappTemplate';
+
+  const emit = defineEmits(['success', 'register']);
+  const phoneOptions = ref([]);
+  const templateOptions = ref([]);
+  const templateList = ref([]);
+  const phone = ref('');
+  const schemas: FormSchema[] = [
+    {
+      field: 'phone',
+      label: '发消息人',
+      component: 'Select',
+      componentProps: {
+        options: phoneOptions,
+      },
+      required: true,
+    },
+    {
+      field: 'to',
+      label: '收消息人',
+      component: 'Input',
+      dynamicDisabled: true,
+      required: true,
+    },
+    {
+      field: 'templateIndex',
+      label: '模板',
+      component: 'Select',
+      componentProps: {
+        options: templateOptions,
+      },
+      required: true,
+    },
+  ]
+
+  onMounted(async () => {
+    const res = await getWhatsappList({page: 1, pageSize: 1000});
+    phoneOptions.value = res.data.data.filter(item => item.phoneStatus === 'CONNECTED').map(item => ({
+      label: `+${item.cc} ${item.phone}`,
+      value: `${item.cc}${item.phone}`
+    }));
+    const templateRes = await getWhatsappTemplateList({page: 1, pageSize: 1000});
+    templateList.value = templateRes.data.data
+    templateOptions.value = templateList.value.map((item, i) => ({
+      label: `${item.name} (${item.language})`,
+      value: i
+    }));
+  })
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas,
+    showActionButtonGroup: false,
+  });
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    phone.value = '';
+    setDrawerProps({ confirmLoading: false });
+
+    setFieldsValue({
+      to: `${data.record['cc']}${data.record['phone']}`,
+    });
+  });
+
+  async function handleSubmit() {
+    const values = await validate();
+    values.templateCode = templateList.value[values.templateIndex].templateCode;
+    values.lang = templateList.value[values.templateIndex].language;
+    delete values.templateIndex;
+    setDrawerProps({ confirmLoading: true });
+
+    let result = await sendBatchMsgText(values)
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
+</script>

+ 70 - 0
src/views/whatsapp/whatsapp_contact/WhatsappContactDrawer.vue

@@ -0,0 +1,70 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    :title="getTitle"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" class="form-container" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { formSchema } from './whatsappContact.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { useI18n } from 'vue-i18n';
+  import { formatToDateTime } from '@/utils/dateUtil';
+  import { createWhatsappContact, updateWhatsappContact } from '@/api/whatsapp_contact/whatsappContact';
+
+  const emit = defineEmits(['success', 'register']);
+  const isUpdate = ref(true);
+  const { t } = useI18n();
+
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    resetFields();
+    setDrawerProps({ confirmLoading: false });
+
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      setFieldsValue({
+        ...data.record,
+      });
+    }
+  });
+
+  const getTitle = computed(() =>
+    !unref(isUpdate) ? t('whatsapp_contact.whatsappContact.addWhatsappContact') : t('whatsapp_contact.whatsappContact.editWhatsappContact'),
+  );
+
+  async function handleSubmit() {
+    const values = await validate();
+    setDrawerProps({ confirmLoading: true });
+    values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
+    values['cbirthday'] = formatToDateTime(values['cbirthday'], 'YYYY-MM-DD');
+    let result = unref(isUpdate) ? await updateWhatsappContact(values) : await createWhatsappContact(values);
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
+</script>
+<style lang="scss" scoped>
+  .form-container {
+    ::v-deep(.ant-input-number) {
+      width: 100%;
+    }
+  }
+</style>

+ 231 - 0
src/views/whatsapp/whatsapp_contact/index.vue

@@ -0,0 +1,231 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #tableTitle>
+        <a-button
+          @click="handleAddLabel"
+          style="position: absolute; left: 0;"
+          :disabled="selectedIds && !selectedIds.length"
+        >
+          + 增加标签
+        </a-button>
+        <a-button
+          @click="handleDeleteLabel"
+          style="position: absolute; left: 110px;"
+          :disabled="selectedIds && !selectedIds.length"
+        >
+          - 移除标签
+        </a-button>
+      </template>
+      <template #toolbar>
+        <a-button @click="openImportTempleModal">导入</a-button>
+        <a-button type="primary" @click="handleCreate">
+          {{ t('whatsapp_contact.whatsappContact.addWhatsappContact') }}
+        </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: t('common.edit'),
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: t('common.delete'),
+                color: 'error',
+                popConfirm: {
+                  title: t('common.deleteConfirm'),
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                label: '发送消息',
+                onClick: handleSendMsg.bind(null, record),
+              },
+              {
+                label: '编辑标签',
+                onClick: handleEditLabel.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <Modal
+      width="700px"
+      height="500px"
+      v-model:open="modalVisible"
+      :title="modal_title"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      class="custom-modal"
+    >
+      <Form :model="form" layout="inline" style="height: 300px">
+        <FormItem name="lableName" label="选择标签" style="margin-left: 30px">
+          <Select
+            v-model:value="form.lableName"
+            :options="actionLabel"
+            allowClear
+            mode="multiple"
+            size="middle"
+            placeholder="请选择"
+            :style="{ width: '560px', margin: '0 5px' }"
+          ></Select>
+        </FormItem>
+      </Form>
+    </Modal>
+    <ImportTempleModal @register="registerImportTempleModal" @success="handleSuccess" />
+    <WhatsappContactDrawer @register="registerDrawer" @success="handleSuccess" />
+    <GroupLabelDrawer @register="registerGroupLabelDrawer" @success="handleSuccess" />
+    <SendMsgDrawer @register="registerSendMsgDrawer" @success="handleSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref, reactive, onMounted } from 'vue';
+  import { Modal, Form, FormItem, Select, message } from 'ant-design-vue';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+
+  import { useModal } from '@/components/Modal';
+  import { useDrawer } from '@/components/Drawer';
+  import ImportTempleModal from './ImportTempleModal.vue';
+  import WhatsappContactDrawer from './WhatsappContactDrawer.vue';
+  import GroupLabelDrawer from './GroupLabelDrawer.vue';
+  import SendMsgDrawer from './SendMsgDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './whatsappContact.data';
+  import { getWhatsappContactList, deleteWhatsappContact, updateContactLabels } from '@/api/whatsapp_contact/whatsappContact';
+  import { getLabelSelectList } from '@/api/wechat/label';
+  import { LabelSelectListInfo } from '@/api/wechat/model/labelModel';
+
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>([]);
+  
+  const modalVisible = ref(false);
+  const modal_title = ref('增加标签');
+  const form = reactive({
+    lableName: [],
+  });
+  const initialForm = reactive({
+    lableName: [],
+  });
+  const actionLabel = ref<LabelSelectListInfo[]>([]);
+
+  const [registerImportTempleModal, { openModal: openImportTempleModal }] = useModal();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerGroupLabelDrawer, { openDrawer: openGroupLabelDrawer }] = useDrawer();
+  const [registerSendMsgDrawer, { openDrawer: openSendMsgDrawer }] = useDrawer();
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    title: t('whatsapp_contact.whatsappContact.whatsappContactList'),
+    api: getWhatsappContactList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: false,
+    showIndexColumn: false,
+    clickToRowSelect: false,
+    actionColumn: {
+      width: 280,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right',
+    },
+    rowKey: 'id',
+    rowSelection: {
+      type: 'checkbox',
+      columnWidth: 40,
+      onChange: (selectedRowKeys, _selectedRows) => {
+        selectedIds.value = selectedRowKeys as number[];
+      },
+    },
+  });
+  onMounted(async () => {
+    let res = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
+    actionLabel.value = res.data;
+  });
+
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+
+  async function handleDelete(record: Recordable) {
+    const result = await deleteWhatsappContact({ ids: [record.id] });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+
+  async function handleSuccess() {
+    await reload();
+  }
+
+  
+  async function handleAddLabel() {
+    modalVisible.value = true;
+    modal_title.value = '增加标签';
+  }
+
+  function handleDeleteLabel() {
+
+    modalVisible.value = true;
+    modal_title.value = '移除标签';
+  }
+
+  function handleEditLabel(record: Recordable) {
+    openGroupLabelDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+  
+  async function handleOk() {
+    let contactIds = selectedIds.value as number[];
+    let labelIds = form.lableName;
+    if (modal_title.value === '增加标签') {
+      let res = await updateContactLabels({ updateType: 1, contactIds, labelIds });
+      if (res.code === 0) {
+        message.success('标签添加成功');
+        modalVisible.value = false;
+        await reload();
+        selectedIds.value = [];
+        form.lableName = [];
+        clearSelectedRowKeys();
+      }
+    } else {
+      let res = await updateContactLabels({ updateType: -1, contactIds, labelIds });
+      if (res.code === 0) {
+        message.success('标签移除成功');
+        modalVisible.value = false;
+        await reload();
+        selectedIds.value = [];
+        form.lableName = [];
+        clearSelectedRowKeys();
+      }
+    }
+  }
+  async function handleCancel() {
+    Object.assign(form, initialForm);
+  }
+
+  function handleSendMsg(record: Recordable) {
+    openSendMsgDrawer(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+</script>

+ 86 - 108
src/views/whatsapp_contact/whatsapp_contact/whatsappContact.data.ts → src/views/whatsapp/whatsapp_contact/whatsappContact.data.ts

@@ -1,9 +1,7 @@
 import { BasicColumn, FormSchema } from '@/components/Table';
 import { useI18n } from '@/hooks/web/useI18n';
-import { formatToDateTime } from '@/utils/dateUtil';
-import { updateWhatsappContact } from '@/api/whatsapp_contact/whatsappContact';
-import { Switch } from 'ant-design-vue';
-import { h } from 'vue';
+import { countryCodeList } from '@/utils/countryCode';
+import { getLabelSelectList } from '/@/api/wechat/label';
 
 const { t } = useI18n();
 
@@ -20,7 +18,7 @@ export const columns: BasicColumn[] = [
   },
   {
     title: t('whatsapp_contact.whatsappContact.name'),
-    dataIndex: 'name',
+    dataIndex: 'cname',
     width: 100,
   },
   {
@@ -29,18 +27,21 @@ export const columns: BasicColumn[] = [
     width: 100,
   },
   {
-    title: t('whatsapp_contact.whatsappContact.organizationId'),
-    dataIndex: 'organizationId',
-    width: 100,
+    title: '标签',
+    dataIndex: 'labelRelationships',
+    customRender: ({ text }) => {
+      return text.map(label => label.label).join(', ');
+    },
+    width: 200,
   },
   {
-    title: t('whatsapp_contact.whatsappContact.cname'),
-    dataIndex: 'cname',
+    title: t('whatsapp_contact.whatsappContact.csex'),
+    dataIndex: 'sex',
     width: 100,
   },
   {
-    title: t('whatsapp_contact.whatsappContact.csex'),
-    dataIndex: 'csex',
+    title: t('whatsapp_contact.whatsappContact.ctitle'),
+    dataIndex: 'ctitle',
     width: 100,
   },
   {
@@ -49,13 +50,13 @@ export const columns: BasicColumn[] = [
     width: 100,
   },
   {
-    title: t('whatsapp_contact.whatsappContact.carea'),
-    dataIndex: 'carea',
+    title: t('whatsapp_contact.whatsappContact.cbirthday'),
+    dataIndex: 'cbirthday',
     width: 100,
   },
   {
-    title: t('whatsapp_contact.whatsappContact.cmobile'),
-    dataIndex: 'cmobile',
+    title: t('whatsapp_contact.whatsappContact.carea'),
+    dataIndex: 'carea',
     width: 100,
   },
   {
@@ -68,53 +69,24 @@ export const columns: BasicColumn[] = [
     dataIndex: 'cidcardNo',
     width: 100,
   },
-  {
-    title: t('whatsapp_contact.whatsappContact.ctitle'),
-    dataIndex: 'ctitle',
-    width: 100,
-  },
-  {
-    title: t('common.status'),
-    dataIndex: 'status',
-    width: 50,
-    customRender: ({ record }) => {
-      if (!Reflect.has(record, 'pendingStatus')) {
-        record.pendingStatus = false;
-      }
-      return h(Switch, {
-        checked: record.status === 1,
-        checkedChildren: t('common.on'),
-        unCheckedChildren: t('common.off'),
-        loading: record.pendingStatus,
-        onChange(checked, _) {
-          record.pendingStatus = true;
-          const newStatus = checked ? 1 : 2;
-          updateWhatsappContact({ id: record.id, status: newStatus })
-            .then(() => {
-              record.status = newStatus;
-            })
-            .finally(() => {
-              record.pendingStatus = false;
-            });
-        },
-      });
-    },
-  },
-  {
-    title: t('common.createTime'),
-    dataIndex: 'createdAt',
-    width: 70,
-    customRender: ({ record }) => {
-      return formatToDateTime(record.createdAt);
-    },
-  },
 ];
 
 export const searchFormSchema: FormSchema[] = [
   {
-    field: 'cc',
-    label: t('whatsapp_contact.whatsappContact.cc'),
-    component: 'Input',
+    field: 'labelIDs',
+    label: '标签',
+    component: 'ApiSelect',
+    componentProps: () => ({
+      api: (params) => getLabelSelectList(params).then(response => {
+        return response.data.map((label) => {
+          return { label: label.label, value: label.value };
+        });
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+      mode: 'tags',
+      tokenSeparators: [','],
+      change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数
+    }),
     colProps: { span: 8 },
   },
   {
@@ -141,91 +113,97 @@ export const formSchema: FormSchema[] = [
   {
     field: 'cc',
     label: t('whatsapp_contact.whatsappContact.cc'),
-    component: 'Input',
-    required: true,
+    component: 'Select',
+    componentProps: {
+      options: countryCodeList,
+    },
   },
   {
     field: 'phone',
     label: t('whatsapp_contact.whatsappContact.phone'),
     component: 'Input',
-    required: true,
-  },
-  {
-    field: 'name',
-    label: t('whatsapp_contact.whatsappContact.name'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'markname',
-    label: t('whatsapp_contact.whatsappContact.markname'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'organizationId',
-    label: t('whatsapp_contact.whatsappContact.organizationId'),
-    component: 'InputNumber',
-    required: true,
   },
   {
     field: 'cname',
     label: t('whatsapp_contact.whatsappContact.cname'),
     component: 'Input',
-    required: true,
   },
   {
-    field: 'csex',
+    field: 'sex',
     label: t('whatsapp_contact.whatsappContact.csex'),
-    component: 'InputNumber',
-    required: true,
+    component: 'RadioButtonGroup',
+    componentProps: {
+      options: [
+        { label: '未知', value: 0 },
+        { label: '男', value: 1 },
+        { label: '女', value: 2 },
+      ],
+    },
+  },
+  {
+    field: 'ctitle',
+    label: t('whatsapp_contact.whatsappContact.ctitle'),
+    component: 'Input',
   },
   {
     field: 'cage',
     label: t('whatsapp_contact.whatsappContact.cage'),
     component: 'InputNumber',
-    required: true,
   },
   {
-    field: 'carea',
-    label: t('whatsapp_contact.whatsappContact.carea'),
-    component: 'Input',
-    required: true,
+    field: 'cbirthday',
+    label: t('whatsapp_contact.whatsappContact.cbirthday'),
+    component: 'DatePicker',
   },
   {
-    field: 'cmobile',
-    label: t('whatsapp_contact.whatsappContact.cmobile'),
+    field: 'carea',
+    label: t('whatsapp_contact.whatsappContact.carea'),
     component: 'Input',
-    required: true,
   },
   {
     field: 'cbirtharea',
     label: t('whatsapp_contact.whatsappContact.cbirtharea'),
     component: 'Input',
-    required: true,
   },
   {
     field: 'cidcardNo',
     label: t('whatsapp_contact.whatsappContact.cidcardNo'),
     component: 'Input',
-    required: true,
-  },
-  {
-    field: 'ctitle',
-    label: t('whatsapp_contact.whatsappContact.ctitle'),
-    component: 'Input',
-    required: true,
   },
   {
-    field: 'status',
-    label: t('whatsapp_contact.whatsappContact.status'),
-    component: 'RadioButtonGroup',
-    defaultValue: 1,
+    field: 'markname',
+    label: t('whatsapp_contact.whatsappContact.markname'),
+    component: 'InputTextArea',
     componentProps: {
-      options: [
-        { label: t('common.on'), value: 1 },
-        { label: t('common.off'), value: 2 },
-      ],
+      showCount: false,
+      autoSize: {
+        minRows: 2,
+        maxRows: 5
+      }
     },
   },
 ];
+
+export const groupLabelFormSchema: FormSchema[] = [
+  {
+    field: 'contactId',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'labelIds',
+    label: t('wechat.contact.labels'),
+    component: 'ApiSelect',
+    componentProps: (renderCallbackParams) => ({
+      api: (params) => getLabelSelectList(params).then(response => {
+        return response.data
+      }),
+      params: { page: 1, pageSize: 1000, type: 1 },
+      mode: 'tags',
+      tokenSeparators: [','],
+      change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数
+    }),
+    required: false,
+  }
+];

+ 35 - 0
src/views/whatsapp/whatsapp_template/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #tableTitle></template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { BasicTable, useTable } from '@/components/Table';
+  import { useI18n } from 'vue-i18n';
+  import { columns, searchFormSchema } from './whatsappTemplate.data';
+  import { getWhatsappTemplateList, deleteWhatsappTemplate } from '@/api/whatsapp_template/whatsappTemplate';
+
+  const { t } = useI18n();
+  const [registerTable, { reload }] = useTable({
+    title: t('whatsapp_template.whatsappTemplate.whatsappTemplateList'),
+    api: getWhatsappTemplateList,
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+    },
+    useSearchForm: true,
+    showTableSetting: true,
+    bordered: false,
+    showIndexColumn: false,
+  });
+
+  async function handleDelete(record: Recordable) {
+    const result = await deleteWhatsappTemplate({ ids: [record.id] });
+    if (result.code === 0) {
+      await reload();
+    }
+  }
+</script> 

+ 57 - 0
src/views/whatsapp/whatsapp_template/whatsappTemplate.data.ts

@@ -0,0 +1,57 @@
+import { BasicColumn, FormSchema } from '@/components/Table';
+import { useI18n } from '@/hooks/web/useI18n';
+import { getWhatsappChannelList } from '/@/api/whatsapp_channel/whatsappChannel';
+
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+  {
+    title: t('whatsapp_template.whatsappTemplate.templateCode'),
+    dataIndex: 'templateCode',
+    width: 120,
+  },
+  {
+    title: t('whatsapp_template.whatsappTemplate.name'),
+    dataIndex: 'name',
+    width: 120,
+  },
+  {
+    title: t('whatsapp_template.whatsappTemplate.language'),
+    dataIndex: 'language',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_template.whatsappTemplate.category'),
+    dataIndex: 'category',
+    width: 120,
+  },
+  {
+    title: t('whatsapp_template.whatsappTemplate.auditStatus'),
+    dataIndex: 'auditStatus',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_template.whatsappTemplate.templateType'),
+    dataIndex: 'templateType',
+    width: 100,
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'waId',
+    label: '通道',
+    component: 'ApiSelect',
+    componentProps: {
+      api: getWhatsappChannelList,
+      params: {
+        page: 1,
+        pageSize: 1000,
+      },
+      resultField: 'data.data',
+      labelField: 'waName',
+      valueField: 'waId',
+    },
+    colProps: { span: 8 },
+  },
+];

+ 0 - 75
src/views/whatsapp_batch/whatsapp_batch/WhatsappBatchDrawer.vue

@@ -1,75 +0,0 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    @register="registerDrawer"
-    showFooter
-    :title="getTitle"
-    width="35%"
-    @ok="handleSubmit"
-  >
-    <BasicForm @register="registerForm" />
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
-  import { BasicForm, useForm } from '@/components/Form/index';
-  import { formSchema } from './whatsappBatch.data';
-  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
-  import { useI18n } from 'vue-i18n';
-
-  import { createWhatsappBatch, updateWhatsappBatch } from '@/api/whatsapp_batch/whatsappBatch';
-
-  export default defineComponent({
-    name: 'WhatsappBatchDrawer',
-    components: { BasicDrawer, BasicForm },
-    emits: ['success', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const { t } = useI18n();
-
-      const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
-        labelWidth: 160,
-        baseColProps: { span: 24 },
-        layout: 'vertical',
-        schemas: formSchema,
-        showActionButtonGroup: false,
-      });
-
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-
-        isUpdate.value = !!data?.isUpdate;
-
-        if (unref(isUpdate)) {
-          setFieldsValue({
-            ...data.record,
-          });
-        }
-      });
-
-      const getTitle = computed(() =>
-        !unref(isUpdate) ? t('whatsapp_batch.whatsappBatch.addWhatsappBatch') : t('whatsapp_batch.whatsappBatch.editWhatsappBatch'),
-      );
-
-      async function handleSubmit() {
-        const values = await validate();
-        setDrawerProps({ confirmLoading: true });
-        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
-        let result = unref(isUpdate) ? await updateWhatsappBatch(values) : await createWhatsappBatch(values);
-        if (result.code === 0) {
-          closeDrawer();
-          emit('success');
-        }
-        setDrawerProps({ confirmLoading: false });
-      }
-
-      return {
-        registerDrawer,
-        registerForm,
-        getTitle,
-        handleSubmit,
-      };
-    },
-  });
-</script>

+ 0 - 152
src/views/whatsapp_batch/whatsapp_batch/index.vue

@@ -1,152 +0,0 @@
-<template>
-  <div>
-    <BasicTable @register="registerTable">
-      <template #tableTitle>
-        <Button
-          type="primary"
-          danger
-          preIcon="ant-design:delete-outlined"
-          v-if="showDeleteButton"
-          @click="handleBatchDelete"
-        >
-          {{ t('common.delete') }}
-        </Button>
-      </template>
-      <template #toolbar>
-        <a-button type="primary" @click="handleCreate">
-          {{ t('whatsapp_batch.whatsappBatch.addWhatsappBatch') }}
-        </a-button>
-      </template>
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'action'">
-          <TableAction
-            :actions="[
-              {
-                icon: 'clarity:note-edit-line',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                icon: 'ant-design:delete-outlined',
-                color: 'error',
-                popConfirm: {
-                  title: t('common.deleteConfirm'),
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
-          />
-        </template>
-      </template>
-    </BasicTable>
-    <WhatsappBatchDrawer @register="registerDrawer" @success="handleSuccess" />
-  </div>
-</template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
-  import { Modal } from 'ant-design-vue';
-  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
-  import { BasicTable, useTable, TableAction } from '@/components/Table';
-  import { Button } from '@/components/Button';
-
-  import { useDrawer } from '@/components/Drawer';
-  import WhatsappBatchDrawer from './WhatsappBatchDrawer.vue';
-  import { useI18n } from 'vue-i18n';
-
-  import { columns, searchFormSchema } from './whatsappBatch.data';
-  import { getWhatsappBatchList, deleteWhatsappBatch } from '@/api/whatsapp_batch/whatsappBatch';
-
-  export default defineComponent({
-    name: 'WhatsappBatchManagement',
-    components: { BasicTable, WhatsappBatchDrawer, TableAction, Button },
-    setup() {
-      const { t } = useI18n();
-      const selectedIds = ref<number[] | string[]>();
-      const showDeleteButton = ref<boolean>(false);
-
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerTable, { reload }] = useTable({
-        title: t('whatsapp_batch.whatsappBatch.whatsappBatchList'),
-        api: getWhatsappBatchList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: true,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 30,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: undefined,
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          columnWidth: 20,
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
-
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
-
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
-      }
-
-      async function handleDelete(record: Recordable) {
-        const result = await deleteWhatsappBatch({ ids: [record.id] });
-        if (result.code === 0) {
-          await reload();
-        }
-      }
-
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteWhatsappBatch({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
-
-      async function handleSuccess() {
-        await reload();
-      }
-
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleBatchDelete,
-        showDeleteButton,
-      };
-    },
-  });
-</script>

+ 0 - 75
src/views/whatsapp_contact/whatsapp_contact/WhatsappContactDrawer.vue

@@ -1,75 +0,0 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    @register="registerDrawer"
-    showFooter
-    :title="getTitle"
-    width="35%"
-    @ok="handleSubmit"
-  >
-    <BasicForm @register="registerForm" />
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
-  import { BasicForm, useForm } from '@/components/Form/index';
-  import { formSchema } from './whatsappContact.data';
-  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
-  import { useI18n } from 'vue-i18n';
-
-  import { createWhatsappContact, updateWhatsappContact } from '@/api/whatsapp_contact/whatsappContact';
-
-  export default defineComponent({
-    name: 'WhatsappContactDrawer',
-    components: { BasicDrawer, BasicForm },
-    emits: ['success', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const { t } = useI18n();
-
-      const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
-        labelWidth: 160,
-        baseColProps: { span: 24 },
-        layout: 'vertical',
-        schemas: formSchema,
-        showActionButtonGroup: false,
-      });
-
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-
-        isUpdate.value = !!data?.isUpdate;
-
-        if (unref(isUpdate)) {
-          setFieldsValue({
-            ...data.record,
-          });
-        }
-      });
-
-      const getTitle = computed(() =>
-        !unref(isUpdate) ? t('whatsapp_contact.whatsappContact.addWhatsappContact') : t('whatsapp_contact.whatsappContact.editWhatsappContact'),
-      );
-
-      async function handleSubmit() {
-        const values = await validate();
-        setDrawerProps({ confirmLoading: true });
-        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
-        let result = unref(isUpdate) ? await updateWhatsappContact(values) : await createWhatsappContact(values);
-        if (result.code === 0) {
-          closeDrawer();
-          emit('success');
-        }
-        setDrawerProps({ confirmLoading: false });
-      }
-
-      return {
-        registerDrawer,
-        registerForm,
-        getTitle,
-        handleSubmit,
-      };
-    },
-  });
-</script>

+ 0 - 152
src/views/whatsapp_contact/whatsapp_contact/index.vue

@@ -1,152 +0,0 @@
-<template>
-  <div>
-    <BasicTable @register="registerTable">
-      <template #tableTitle>
-        <Button
-          type="primary"
-          danger
-          preIcon="ant-design:delete-outlined"
-          v-if="showDeleteButton"
-          @click="handleBatchDelete"
-        >
-          {{ t('common.delete') }}
-        </Button>
-      </template>
-      <template #toolbar>
-        <a-button type="primary" @click="handleCreate">
-          {{ t('whatsapp_contact.whatsappContact.addWhatsappContact') }}
-        </a-button>
-      </template>
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'action'">
-          <TableAction
-            :actions="[
-              {
-                icon: 'clarity:note-edit-line',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                icon: 'ant-design:delete-outlined',
-                color: 'error',
-                popConfirm: {
-                  title: t('common.deleteConfirm'),
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
-          />
-        </template>
-      </template>
-    </BasicTable>
-    <WhatsappContactDrawer @register="registerDrawer" @success="handleSuccess" />
-  </div>
-</template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
-  import { Modal } from 'ant-design-vue';
-  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
-  import { BasicTable, useTable, TableAction } from '@/components/Table';
-  import { Button } from '@/components/Button';
-
-  import { useDrawer } from '@/components/Drawer';
-  import WhatsappContactDrawer from './WhatsappContactDrawer.vue';
-  import { useI18n } from 'vue-i18n';
-
-  import { columns, searchFormSchema } from './whatsappContact.data';
-  import { getWhatsappContactList, deleteWhatsappContact } from '@/api/whatsapp_contact/whatsappContact';
-
-  export default defineComponent({
-    name: 'WhatsappContactManagement',
-    components: { BasicTable, WhatsappContactDrawer, TableAction, Button },
-    setup() {
-      const { t } = useI18n();
-      const selectedIds = ref<number[] | string[]>();
-      const showDeleteButton = ref<boolean>(false);
-
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerTable, { reload }] = useTable({
-        title: t('whatsapp_contact.whatsappContact.whatsappContactList'),
-        api: getWhatsappContactList,
-        columns,
-        formConfig: {
-          labelWidth: 120,
-          schemas: searchFormSchema,
-        },
-        useSearchForm: true,
-        showTableSetting: true,
-        bordered: true,
-        showIndexColumn: false,
-        clickToRowSelect: false,
-        actionColumn: {
-          width: 30,
-          title: t('common.action'),
-          dataIndex: 'action',
-          fixed: undefined,
-        },
-        rowKey: 'id',
-        rowSelection: {
-          type: 'checkbox',
-          columnWidth: 20,
-          onChange: (selectedRowKeys, _selectedRows) => {
-            selectedIds.value = selectedRowKeys as number[];
-            showDeleteButton.value = selectedRowKeys.length > 0;
-          },
-        },
-      });
-
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
-
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
-      }
-
-      async function handleDelete(record: Recordable) {
-        const result = await deleteWhatsappContact({ ids: [record.id] });
-        if (result.code === 0) {
-          await reload();
-        }
-      }
-
-      async function handleBatchDelete() {
-        Modal.confirm({
-          title: t('common.deleteConfirm'),
-          icon: createVNode(ExclamationCircleOutlined),
-          async onOk() {
-            const result = await deleteWhatsappContact({ ids: selectedIds.value as number[] });
-            if (result.code === 0) {
-              showDeleteButton.value = false;
-              await reload();
-            }
-          },
-          onCancel() {
-            console.log('Cancel');
-          },
-        });
-      }
-
-      async function handleSuccess() {
-        await reload();
-      }
-
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        handleSuccess,
-        handleBatchDelete,
-        showDeleteButton,
-      };
-    },
-  });
-</script>

部分文件因文件數量過多而無法顯示