Sfoglia il codice sorgente

Merge branch 'master' into dev

luzhenxing 2 mesi fa
parent
commit
03a72f23aa

+ 0 - 1
.gitignore

@@ -41,7 +41,6 @@ pnpm-lock.yaml
 .vscode/launch.json
 
 *.log
-
 dist
 
 .env.development

+ 0 - 1
dist/assets/index-UreuKZMA.css

@@ -1 +0,0 @@
-.custom-modal[data-v-2467eef5]{display:flex;justify-content:center;align-items:center}

+ 7 - 0
src/api/model/baseModel.ts

@@ -62,6 +62,13 @@ export interface BaseIDsReq {
   updateType?: number;//1 批量追加标签 -1 批量移除标签
 }
 
+export interface ChangeBlockListReq {
+  ownerWxid: string;
+  wxid: string;
+  type: number;
+  ai: boolean;
+}
+
 export interface BaseUUIDReq {
   id: string;
 }

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

@@ -1,6 +1,13 @@
 import { defHttp } from '@/utils/http/axios';
 import { ErrorMessageMode } from '/#/axios';
-import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
+import {
+  BaseDataResp,
+  BaseListReq,
+  BaseResp,
+  BaseIDsReq,
+  BaseIDReq,
+  ChangeBlockListReq
+} from '@/api/model/baseModel';
 import { ContactInfo, ContactListResp, AddNewFriendInfo } from './model/contactModel';
 
 enum Api {
@@ -8,6 +15,7 @@ enum Api {
   UpdateContact = '/wechat-api/contact/update',
   GetContactList = '/wechat-api/contact/list',
   DeleteContact = '/wechat-api/contact/delete',
+  ChangeBlockList = '/wechat-api/contact/changeBlockList',
   GetContactById = '/wechat-api/contact',
   AddNewFriend = '/wechat-api/contact/addNewFriend',
   UpdateContactLabels = '/wechat-api/label_relationship/batch_update_contact_labels'
@@ -63,6 +71,16 @@ export const deleteContact = (params: BaseIDsReq, mode: ErrorMessageMode = 'noti
   );
 };
 
+export const changeBlockList = (params: ChangeBlockListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.ChangeBlockList, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
 /**
  *  @description: Get contact By ID
  */

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

@@ -0,0 +1,74 @@
+import { defHttp } from '@/utils/http/axios';
+import { ErrorMessageMode } from '/#/axios';
+import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
+import { CreditBalanceInfo, CreditBalanceListResp } from './model/creditBalanceModel';
+
+enum Api {
+  CreateCreditBalance = '/wechat-api/credit_balance/create',
+  UpdateCreditBalance = '/wechat-api/credit_balance/update',
+  GetCreditBalanceList = '/wechat-api/credit_balance/list',
+  DeleteCreditBalance = '/wechat-api/credit_balance/delete',
+  GetCreditBalanceById = '/wechat-api/credit_balance',
+}
+
+/**
+ * @description: Get credit balance list
+ */
+
+export const getCreditBalanceList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<CreditBalanceListResp>>(
+    { url: Api.GetCreditBalanceList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new credit balance
+ */
+export const createCreditBalance = (params: CreditBalanceInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateCreditBalance, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the credit balance
+ */
+export const updateCreditBalance = (params: CreditBalanceInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateCreditBalance, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete credit balances
+ */
+export const deleteCreditBalance = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteCreditBalance, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get credit balance By ID
+ */
+export const getCreditBalanceById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<CreditBalanceInfo>>(
+    { url: Api.GetCreditBalanceById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 21 - 0
src/api/wechat/model/creditBalanceModel.ts

@@ -0,0 +1,21 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: CreditBalance info response
+ */
+export interface CreditBalanceInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  userId?: string;
+  balance?: number;
+  status?: number;
+  organizationId?: number;
+  user?: BUserInfo;
+}
+
+/**
+ *  @description: CreditBalance list response
+ */
+
+export type CreditBalanceListResp = BaseListResp<CreditBalanceInfo>;

+ 26 - 0
src/api/wechat/model/whatsappModel.ts

@@ -0,0 +1,26 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: Whatsapp info response
+ */
+export interface WhatsappInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  status?: number;
+  ak?: string;
+  sk?: string;
+  callback?: string;
+  account?: string;
+  nickname?: string;
+  phone?: string;
+  organizationId?: number;
+  agentId?: number;
+  apiBase?: string;
+  apiKey?: string;
+}
+
+/**
+ *  @description: Whatsapp list response
+ */
+export type WhatsappListResp = BaseListResp<WhatsappInfo>;

+ 74 - 0
src/api/wechat/whatsapp.ts

@@ -0,0 +1,74 @@
+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';
+
+enum Api {
+  CreateWhatsapp = '/wechat-api/whatsapp/create',
+  UpdateWhatsapp = '/wechat-api/whatsapp/update',
+  GetWhatsappList = '/wechat-api/whatsapp/list',
+  DeleteWhatsapp = '/wechat-api/whatsapp/delete',
+  GetWhatsappById = '/wechat-api/whatsapp',
+}
+
+/**
+ * @description: Get whatsapp list
+ */
+
+export const getWhatsappList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappListResp>>(
+    { url: Api.GetWhatsappList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new whatsapp
+ */
+export const createWhatsapp = (params: WhatsappInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateWhatsapp, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the whatsapp
+ */
+export const updateWhatsapp = (params: WhatsappInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateWhatsapp, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete whatsapps
+ */
+export const deleteWhatsapp = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteWhatsapp, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get whatsapp By ID
+ */
+export const getWhatsappById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappInfo>>(
+    { url: Api.GetWhatsappById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 31 - 0
src/locales/lang/en/wechat.ts

@@ -252,4 +252,35 @@ export default {
     editLabelTagging: 'Edit LabelTagging',
     labelTaggingList: 'LabelTagging List',
   },
+  whatsapp: {
+    status: 'Status',
+    ak: 'Ak',
+    sk: 'Sk',
+    callback: 'Callback',
+    wxid: 'Wxid',
+    account: 'Account',
+    nickname: 'Nickname',
+    phone: 'Phone',
+    organizationId: 'OrganizationId',
+    agentId: 'AgentId',
+    apiBase: 'ApiBase',
+    apiKey: 'ApiKey',
+    allowList: 'AllowList',
+    groupAllowList: 'GroupAllowList',
+    blockList: 'BlockList',
+    groupBlockList: 'GroupBlockList',
+    addWhatsapp: 'Add Whatsapp',
+    editWhatsapp: 'Edit Whatsapp',
+    whatsappList: 'Whatsapp List',
+  },
+  creditBalance: {
+    userId: 'UserId',
+    balance: 'Balance',
+    status: 'Status',
+    organizationId: 'OrganizationId',
+    user: 'User',
+    addCreditBalance: 'Add CreditBalance',
+    editCreditBalance: 'Edit CreditBalance',
+    creditBalanceList: 'CreditBalance List',
+  },
 };

+ 33 - 1
src/locales/lang/zh-CN/wechat.ts

@@ -31,7 +31,7 @@ export default {
     type: '类型',
     wxid: '微信ID',
     account: '微信账号',
-    nickname: '称',
+    nickname: '称',
     markname: '备注',
     headimg: '头像',
     sex: '性别',
@@ -46,6 +46,7 @@ export default {
     addContact: '添加 Contact',
     editContact: '编辑 Contact',
     contactList: 'Contact 列表',
+    isInBlockList: '模式',
   },
   label: {
     status: 'Status',
@@ -264,4 +265,35 @@ export default {
     editLabelTagging: '编辑 标签',
     labelTaggingList: '标签 列表',
   },
+  whatsapp: {
+    status: '状态',
+    ak: '访问密钥',
+    sk: '密钥',
+    callback: '回调地址',
+    wxid: '微信ID',
+    account: '账号',
+    nickname: '昵称',
+    phone: '电话',
+    organizationId: '组织ID',
+    agentId: '代理ID',
+    apiBase: 'API地址',
+    apiKey: 'API密钥',
+    allowList: '白名单',
+    groupAllowList: '群组白名单',
+    blockList: '黑名单',
+    groupBlockList: '群组黑名单',
+    addWhatsapp: '添加 Whatsapp',
+    editWhatsapp: '编辑 Whatsapp',
+    whatsappList: 'Whatsapp 列表',
+  },
+  creditBalance: {
+    userId: '用户ID',
+    balance: '余额',
+    status: '状态',
+    organizationId: '组织ID',
+    user: '用户',
+    addCreditBalance: '添加余额',
+    editCreditBalance: '编辑余额',
+    creditBalanceList: '余额列表',
+  },
 };

+ 24 - 16
src/views/dashboard/workbench/components/ChartsAreaLine.vue

@@ -1,19 +1,21 @@
 <template>
-  <Flex justify="space-between">
-    <span class="label">
-      {{ label }}
-      <Tooltip v-if="tip" placement="right">
-        <template #title>{{ tip }}</template>
-        <ExclamationCircleOutlined class="icon-exclamation" />
-      </Tooltip>
-    </span>
-    <span v-if="rate && rate !== 0" :style="{ color: rate >= 0 ? '#10B981' : '#df3c2f' }">
-      <ArrowUpOutlined v-if="rate > 0" class="icon-arrow" />
-      <ArrowDownOutlined v-else-if="rate < 0" class="icon-arrow" />
-      {{ Math.abs(rate) }}%
-    </span>
-  </Flex>
-  <div ref="echartsLineRef" class="echarts-box"></div>
+  <section class="section">
+    <Flex justify="space-between">
+      <span class="label">
+        {{ label }}
+        <Tooltip v-if="tip" placement="right">
+          <template #title>{{ tip }}</template>
+          <ExclamationCircleOutlined class="icon-exclamation" />
+        </Tooltip>
+      </span>
+      <span v-if="rate && rate !== 0" :style="{ color: rate >= 0 ? '#10B981' : '#df3c2f' }">
+        <ArrowUpOutlined v-if="rate > 0" class="icon-arrow" />
+        <ArrowDownOutlined v-else-if="rate < 0" class="icon-arrow" />
+        {{ Math.abs(rate) }}%
+      </span>
+    </Flex>
+    <div ref="echartsLineRef" class="echarts-box"></div>
+  </section>
 </template>
 
 <script lang="ts" setup>
@@ -45,7 +47,7 @@
         left: 0,
         right: 0,
         bottom: 0,
-        top: 0,
+        top: 30,
       },
       xAxis: {
         show: false,
@@ -75,6 +77,11 @@
 </script>
 
 <style lang="scss" scoped>
+  .section {
+    padding: 16px;
+    background-color: #fff;
+  }
+
   .label {
     opacity: 0.6;
   }
@@ -92,5 +99,6 @@
     width: 100%;
     height: 140px;
     margin-top: 10px;
+    background-color: #fff;
   }
 </style>

+ 25 - 2
src/views/dashboard/workbench/components/ChartsLine.vue

@@ -1,5 +1,7 @@
 <template>
-  <div ref="echartsLineRef" style="height: 400px"></div>
+  <section class="section">
+    <div ref="echartsLineRef" style="height: 400px;"></div>
+  </section>
 </template>
 
 <script lang="ts" setup>
@@ -23,14 +25,28 @@
         trigger: 'axis',
       },
       grid: {
-        right: 0,
+        bottom: 40,
+        right: 30,
         containLabel: false,
       },
       xAxis: {
+        boundaryGap: false,
+        splitLine: {
+          show: true,
+          lineStyle: {
+            type: 'dashed',
+          },
+        },
         type: 'category',
         data: labels,
       },
       yAxis: {
+        splitLine: {
+          show: true,
+          lineStyle: {
+            type: 'dashed',
+          },
+        },
         type: 'value',
       },
       series: [
@@ -43,3 +59,10 @@
     });
   });
 </script>
+
+<style lang="scss" scoped>
+  .section {
+    padding: 16px;
+    background-color: #fff;
+  }
+</style>

+ 10 - 1
src/views/dashboard/workbench/components/ChartsPie.vue

@@ -1,5 +1,7 @@
 <template>
-  <div ref="echartsLineRef" style="height: 400px"></div>
+  <section class="section">
+    <div ref="echartsLineRef" style="height: 400px"></div>
+  </section>
 </template>
 <script lang="ts" setup>
   import { Ref, ref, watchEffect } from 'vue';
@@ -46,3 +48,10 @@
     });
   });
 </script>
+
+<style lang="scss" scoped>
+  .section {
+    padding: 16px;
+    background-color: #fff;
+  }
+</style>

+ 6 - 0
src/views/dashboard/workbench/components/TableList.vue

@@ -37,6 +37,12 @@
 </script>
 <style lang="scss" scoped>
   .section {
+    min-height: 432px;
+    padding: 18px;
+    padding-bottom: 14px;
+    background-color: #fff;
+
+
     .title {
       font-size: 18px;
       font-weight: bold;

+ 5 - 8
src/views/dashboard/workbench/index.vue

@@ -62,9 +62,9 @@
 
     <!-- 数据展示区域 -->
     <Spin :spinning="loading" size="large">
-      <Space v-if="charts" direction="vertical" :size="32">
+      <Space v-if="charts" direction="vertical" :size="16">
         <!-- 数据概览卡片 -->
-        <Row :gutter="[32, 32]">
+        <Row :gutter="[16, 16]">
           <Col v-for="item in CHART_CONFIG.AREA_LINE_MAP" :key="item.key" :span="6">
             <ChartsAreaLine
               :label="item.label"
@@ -78,7 +78,7 @@
         </Row>
 
         <!-- 趋势图表 -->
-        <Row :gutter="32">
+        <Row :gutter="16">
           <Col :span="12">
             <ChartsLine
               title="Token 消耗趋势"
@@ -96,7 +96,7 @@
         </Row>
 
         <!-- 标签分布和账号分析 -->
-        <Row :gutter="32">
+        <Row :gutter="16">
           <Col :span="12">
             <ChartsPie title="标签分布" :data="charts.label_dist" />
           </Col>
@@ -287,7 +287,7 @@
   onMounted(async () => {
     // 初始化数据
     fetchSelectList();
-    handleDateShortcut(0); // 默认显示今天的数据
+    handleDateShortcut(7); // 默认显示7天的数据
 
     // 如果是超级管理员,加载部门列表
     if (isSuper.value) {
@@ -321,8 +321,5 @@
       top: 0;
     }
 
-    :deep(.ant-spin-container) {
-      padding: 24px;
-    }
   }
 </style>

+ 2 - 2
src/views/sys/dictionary/index.vue

@@ -26,11 +26,11 @@
                 onClick: handleAddDetail.bind(null, record),
               },
               {
-                label: '编辑',
+                label: t('common.edit'),
                 onClick: handleEdit.bind(null, record),
               },
               {
-                label: '删除',
+                label: t('common.delete'),
                 color: 'error',
                 popConfirm: {
                   title: t('common.deleteConfirm'),

+ 2 - 2
src/views/wechat/category/index.vue

@@ -22,11 +22,11 @@
           <TableAction
             :actions="[
               {
-                icon: 'clarity:note-edit-line',
+                label: t('common.edit'),
                 onClick: handleEdit.bind(null, record),
               },
               {
-                icon: 'ant-design:delete-outlined',
+                label: t('common.delete'),
                 color: 'error',
                 popConfirm: {
                   title: t('common.deleteConfirm'),

+ 27 - 4
src/views/wechat/contact/contact.data.ts

@@ -4,15 +4,23 @@ import {getLabelSelectList} from "@/api/wechat/label";
 const { t } = useI18n();
 
 export const columns: BasicColumn[] = [
+  // {
+  //   title: t('wechat.contact.wxWxid'),
+  //   dataIndex: 'wxWxid',
+  //   width: 100,
+  // },
   {
     title: t('wechat.contact.wxWxid'),
-    dataIndex: 'wxWxid',
+    dataIndex: 'wxWxidNickname',
     width: 100,
   },
   {
     title: t('wechat.contact.type'),
     dataIndex: 'type',
     width: 50,
+    customRender: ({ record }) => {
+      return record.type == 1 ? '联系人' : '群组'
+    },
   },
   {
     title: t('wechat.contact.wxid'),
@@ -27,9 +35,24 @@ export const columns: BasicColumn[] = [
     align: 'left',
   },
   {
+    title: t('wechat.contact.markname'),
+    dataIndex: 'markname',
+    width: 100,
+    align: 'left',
+  },
+  {
+    title: t('wechat.contact.isInBlockList'),
+    dataIndex: 'isInBlockList',
+    width: 100,
+    align: 'left',
+    customRender: ({ record }) => {
+      return record.isInBlockList == true ? '人工' : 'AI'
+    },
+  },
+  {
     title: t('wechat.contact.lag'),
     dataIndex: 'labelRelationships',
-    width: 200,
+    width: 100,
     customRender: ({ text }) => {
       return text.map(label => label.label).join(', ');
     },
@@ -103,7 +126,7 @@ export const formSchema: FormSchema[] = [
           return { label: label.label, value: label.value };
         });
       }),
-      params: { page: 1, pageSize: 100, type: 1 },
+      params: { page: 1, pageSize: 1000, type: 1 },
       mode: 'tags',
       tokenSeparators: [','],
       change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数
@@ -232,7 +255,7 @@ export const groupLabelFormSchema: FormSchema[] = [
           return { label: label.label, value: label.value };
         });
       }),
-      params: { page: 1, pageSize: 100, type: 2 },
+      params: { page: 1, pageSize: 1000, type: 2 },
       mode: 'tags',
       tokenSeparators: [','],
       change: 'handleChange', // 这里你需要在你的组件中定义 handleChange 函数

+ 19 - 2
src/views/wechat/contact/index.vue

@@ -37,6 +37,10 @@
           <TableAction
             :actions="[
               {
+                label: record.isInBlockList == true ? '转 AI' : '转人工',
+                onClick: handleIsInBlockList.bind(null, record),
+              },
+              {
                 label: '发信息',
                 onClick: handleMsg.bind(null, record),
               },
@@ -101,7 +105,12 @@
   import { useI18n } from 'vue-i18n';
 
   import { columns, searchFormSchema } from './contact.data';
-  import { getContactList, deleteContact, updateContactLabels } from '@/api/wechat/contact';
+  import {
+    getContactList,
+    deleteContact,
+    updateContactLabels,
+    changeBlockList
+  } from '@/api/wechat/contact';
 
   export default defineComponent({
     name: 'ContactManagement',
@@ -204,6 +213,13 @@
         });
       }
 
+      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] });
         if (result.code === 0) {
@@ -287,6 +303,7 @@
         registerDrawerMsg,
         handleCreate,
         handleMsg,
+        handleIsInBlockList,
         handleEdit,
         handleDelete,
         handleSuccess,
@@ -314,4 +331,4 @@
     justify-content: center;
     align-items: center;
   }
-</style>
+</style>

+ 75 - 0
src/views/wechat/credit_balance/CreditBalanceDrawer.vue

@@ -0,0 +1,75 @@
+<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 './creditBalance.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { useI18n } from 'vue-i18n';
+
+  import { createCreditBalance, updateCreditBalance } from '@/api/wechat/creditBalance';
+
+  export default defineComponent({
+    name: 'CreditBalanceDrawer',
+    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.creditBalance.addCreditBalance') : t('wechat.creditBalance.editCreditBalance'),
+      );
+
+      async function handleSubmit() {
+        const values = await validate();
+        setDrawerProps({ confirmLoading: true });
+        values['id'] = unref(isUpdate) ? Number(values['id']) : 0;
+        let result = unref(isUpdate) ? await updateCreditBalance(values) : await createCreditBalance(values);
+        if (result.code === 0) {
+          closeDrawer();
+          emit('success');
+        }
+        setDrawerProps({ confirmLoading: false });
+      }
+
+      return {
+        registerDrawer,
+        registerForm,
+        getTitle,
+        handleSubmit,
+      };
+    },
+  });
+</script>

+ 126 - 0
src/views/wechat/credit_balance/creditBalance.data.ts

@@ -0,0 +1,126 @@
+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';
+
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+  {
+    title: t('wechat.creditBalance.userId'),
+    dataIndex: 'userId',
+    width: 100,
+  },
+  {
+    title: t('wechat.creditBalance.balance'),
+    dataIndex: 'balance',
+    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,
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'userId',
+    label: t('wechat.creditBalance.userId'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'organizationId',
+    label: t('wechat.creditBalance.organizationId'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'userId',
+    label: t('wechat.creditBalance.userId'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'balance',
+    label: t('wechat.creditBalance.balance'),
+    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 },
+      ],
+    },
+  },
+];

+ 152 - 0
src/views/wechat/credit_balance/index.vue

@@ -0,0 +1,152 @@
+<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('wechat.creditBalance.addCreditBalance') }}
+        </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),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <CreditBalanceDrawer @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 CreditBalanceDrawer from './CreditBalanceDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './creditBalance.data';
+  import { getCreditBalanceList, deleteCreditBalance } from '@/api/wechat/creditBalance';
+
+  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 [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;
+          },
+        },
+      });
+
+      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] });
+        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 handleSuccess() {
+        await reload();
+      }
+
+      return {
+        t,
+        registerTable,
+        registerDrawer,
+        handleCreate,
+        handleEdit,
+        handleDelete,
+        handleSuccess,
+        handleBatchDelete,
+        showDeleteButton,
+      };
+    },
+  });
+</script>

+ 24 - 25
src/views/wechat/token/token.data.ts

@@ -1,7 +1,6 @@
 import { BasicColumn, FormSchema } from '@/components/Table';
 import { useI18n } from '@/hooks/web/useI18n';
 import { formatToDateTime } from '@/utils/dateUtil';
-import {getLabelSelectList} from "@/api/wechat/label";
 import {getAgentList} from "@/api/wechat/agent";
 import {getDepartmentList} from "@/api/sys/department";
 
@@ -133,30 +132,30 @@ export const formSchema: FormSchema[] = [
   //   component: 'Input',
   //   required: true,
   // },
-  {
-    field: 'custom_agent_base',
-    label: t('wechat.token.customAgentBase'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'custom_agent_key',
-    label: t('wechat.token.customAgentKey'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'openai_base',
-    label: t('wechat.token.openaiBase'),
-    component: 'Input',
-    required: true,
-  },
-  {
-    field: 'openai_key',
-    label: t('wechat.token.openaiKey'),
-    component: 'Input',
-    required: true,
-  },
+  // {
+  //   field: 'custom_agent_base',
+  //   label: t('wechat.token.customAgentBase'),
+  //   component: 'Input',
+  //   required: true,
+  // },
+  // {
+  //   field: 'custom_agent_key',
+  //   label: t('wechat.token.customAgentKey'),
+  //   component: 'Input',
+  //   required: true,
+  // },
+  // {
+  //   field: 'openai_base',
+  //   label: t('wechat.token.openaiBase'),
+  //   component: 'Input',
+  //   required: true,
+  // },
+  // {
+  //   field: 'openai_key',
+  //   label: t('wechat.token.openaiKey'),
+  //   component: 'Input',
+  //   required: true,
+  // },
   // {
   //   field: 'mac',
   //   label: t('wechat.token.mac'),

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

@@ -0,0 +1,75 @@
+<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>

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

@@ -0,0 +1,152 @@
+<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('wechat.whatsapp.addWhatsapp') }}
+        </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),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <WhatsappDrawer @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 WhatsappDrawer from './WhatsappDrawer.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;
+          },
+        },
+      });
+
+      function handleCreate() {
+        openDrawer(true, {
+          isUpdate: false,
+        });
+      }
+
+      function handleEdit(record: Recordable) {
+        openDrawer(true, {
+          record,
+          isUpdate: true,
+        });
+      }
+
+      async function handleDelete(record: Recordable) {
+        const result = await deleteWhatsapp({ 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 deleteWhatsapp({ 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>

+ 198 - 0
src/views/wechat/whatsapp/whatsapp.data.ts

@@ -0,0 +1,198 @@
+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();
+
+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.agentId'),
+    dataIndex: 'agentId',
+    width: 100,
+  },
+  {
+    title: t('wechat.whatsapp.apiBase'),
+    dataIndex: 'apiBase',
+    width: 100,
+  },
+  {
+    title: t('wechat.whatsapp.apiKey'),
+    dataIndex: 'apiKey',
+    width: 100,
+  },
+  {
+    title: t('common.status'),
+    dataIndex: 'status',
+    width: 80,
+    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;
+            });
+        },
+      });
+    },
+  },
+  {
+    title: t('common.createTime'),
+    dataIndex: 'createdAt',
+    width: 170,
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'ak',
+    label: t('wechat.whatsapp.ak'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'sk',
+    label: t('wechat.whatsapp.sk'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'callback',
+    label: t('wechat.whatsapp.callback'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'ak',
+    label: t('wechat.whatsapp.ak'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'sk',
+    label: t('wechat.whatsapp.sk'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'callback',
+    label: t('wechat.whatsapp.callback'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'account',
+    label: t('wechat.whatsapp.account'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'nickname',
+    label: t('wechat.whatsapp.nickname'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'phone',
+    label: t('wechat.whatsapp.phone'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'organizationId',
+    label: t('wechat.whatsapp.organizationId'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'agentId',
+    label: t('wechat.whatsapp.agentId'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'apiBase',
+    label: t('wechat.whatsapp.apiBase'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'apiKey',
+    label: t('wechat.whatsapp.apiKey'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'status',
+    label: t('wechat.whatsapp.status'),
+    component: 'RadioButtonGroup',
+    defaultValue: 1,
+    componentProps: {
+      options: [
+        { label: t('common.on'), value: 1 },
+        { label: t('common.off'), value: 2 },
+      ],
+    },
+  },
+];