Browse Source

Merge branch 'master' of http://git.ascrm.cn:3000/scrm/wechat-ui

kyoyue 5 months ago
parent
commit
64330d37f8

+ 4 - 0
.gitignore

@@ -3,6 +3,8 @@ node-jiti
 .DS_Store
 .DS_Store
 .cache
 .cache
 .turbo
 .turbo
+.pnpm-store
+v8-compile-cache-0
 
 
 tests/server/static
 tests/server/static
 tests/server/static/upload
 tests/server/static/upload
@@ -36,3 +38,5 @@ pnpm-lock.yaml
 .env.development
 .env.development
 .env.production
 .env.production
 .vscode/launch.json
 .vscode/launch.json
+
+*.log

+ 74 - 0
src/api/wechat/agentBase.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 { AgentBaseInfo, AgentBaseListResp } from './model/agentBaseModel';
+
+enum Api {
+  CreateAgentBase = '/wechat-api/agent_base/create',
+  UpdateAgentBase = '/wechat-api/agent_base/update',
+  GetAgentBaseList = '/wechat-api/agent_base/list',
+  DeleteAgentBase = '/wechat-api/agent_base/delete',
+  GetAgentBaseById = '/wechat-api/agent_base',
+}
+
+/**
+ * @description: Get agent base list
+ */
+
+export const getAgentBaseList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<AgentBaseListResp>>(
+    { url: Api.GetAgentBaseList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new agent base
+ */
+export const createAgentBase = (params: AgentBaseInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateAgentBase, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the agent base
+ */
+export const updateAgentBase = (params: AgentBaseInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateAgentBase, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete agent bases
+ */
+export const deleteAgentBase = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteAgentBase, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get agent base By ID
+ */
+export const getAgentBaseById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<AgentBaseInfo>>(
+    { url: Api.GetAgentBaseById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 17 - 0
src/api/wechat/model/agentBaseModel.ts

@@ -0,0 +1,17 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: AgentBase info response
+ */
+export interface AgentBaseInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  organizationId?: number;
+}
+
+/**
+ *  @description: AgentBase list response
+ */
+
+export type AgentBaseListResp = BaseListResp<AgentBaseInfo>;

+ 20 - 0
src/api/wechat/model/tokenModel.ts

@@ -0,0 +1,20 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: Token info response
+ */
+export interface TokenInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  expireAt?: number;
+  expireAtStr?: string;
+  token?: string;
+  mac?: string;
+}
+
+/**
+ *  @description: Token list response
+ */
+
+export type TokenListResp = BaseListResp<TokenInfo>;

+ 82 - 0
src/api/wechat/token.ts

@@ -0,0 +1,82 @@
+/*
+ * @Author: kaka
+ * @Date: 2024-09-19 12:55:00
+ * @Description: 
+ * @LastEditTime: 2024-09-19 12:58:46
+ * @LastEditors: kaka
+ * @FilePath: /wechat-ui/src/api/wechat/token.ts
+ */
+import { defHttp } from '@/utils/http/axios';
+import { ErrorMessageMode } from '/#/axios';
+import { BaseDataResp, BaseListReq, BaseResp, BaseIDsReq, BaseIDReq } from '@/api/model/baseModel';
+import { TokenInfo, TokenListResp } from './model/tokenModel';
+
+enum Api {
+  CreateToken = '/wechat-api/token/third/create',
+  UpdateToken = '/wechat-api/token/third/update',
+  GetTokenList = '/wechat-api/token/third/list',
+  DeleteToken = '/wechat-api/token/third/delete',
+  GetTokenById = '/wechat-api/token/third/detail',
+}
+
+/**
+ * @description: Get token list
+ */
+
+export const getTokenList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<TokenListResp>>(
+    { url: Api.GetTokenList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new token
+ */
+export const createToken = (params: TokenInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateToken, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the token
+ */
+export const updateToken = (params: TokenInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateToken, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete tokens
+ */
+export const deleteToken = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteToken, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get token By ID
+ */
+export const getTokenById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<TokenInfo>>(
+    { url: Api.GetTokenById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 2 - 0
src/enums/pageEnum.ts

@@ -23,6 +23,8 @@ export enum PageEnum {
   SEND_RECORD = '/wechat/batch_msg/sendRecord',
   SEND_RECORD = '/wechat/batch_msg/sendRecord',
   //聚合聊天
   //聚合聊天
   WECHAT_MAIN = '/wechat/wechatMain',
   WECHAT_MAIN = '/wechat/wechatMain',
+  //角色知识库
+  AGENT_BASE = '/wechat/agent/agentBase',
 }
 }
 
 
 export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';
 export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';

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

@@ -123,4 +123,19 @@ export default {
     editEmployee: 'Edit Employee',
     editEmployee: 'Edit Employee',
     employeeList: 'Employee List',
     employeeList: 'Employee List',
   },
   },
+  token: {
+    expireAt: 'ExpireAt',
+    expireAtStr: 'ExpireAtStr',
+    token: 'Token',
+    mac: 'Mac',
+    addToken: 'Add Token',
+    editToken: 'Edit Token',
+    tokenList: 'Token List',
+  },
+  agentBase: {
+    id: 'Id',
+    addAgentBase: 'Add AgentBase',
+    editAgentBase: 'Edit AgentBase',
+    agentBaseList: 'AgentBase List',
+  },
 };
 };

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

@@ -127,4 +127,19 @@ export default {
     editEmployee: '编辑数字员工',
     editEmployee: '编辑数字员工',
     employeeList: '数字员工列表',
     employeeList: '数字员工列表',
   },
   },
+  token: {
+    expireAt: 'ExpireAt',
+    expireAtStr: 'ExpireAtStr',
+    token: 'Token',
+    mac: 'Mac',
+    addToken: '添加 Token',
+    editToken: '编辑 Token',
+    tokenList: 'Token 列表',
+  },
+  agentBase: {
+    id: 'Id',
+    addAgentBase: '添加 AgentBase',
+    editAgentBase: '编辑 AgentBase',
+    agentBaseList: 'AgentBase 列表',
+  },
 };
 };

+ 67 - 0
src/views/wechat/agent/agent_base/AgentBaseDrawer.vue

@@ -0,0 +1,67 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    showFooter
+    :title="getTitle"
+    width="35%"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicForm, useForm } from '@/components/Form';
+  import { formSchema } from './agentBase.data';
+  import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
+  import { useI18n } from 'vue-i18n';
+
+  import { createAgentBase, updateAgentBase } from '@/api/wechat/agentBase';
+  import {useRoute} from "vue-router";
+
+  const emit = defineEmits(['success', 'register']);
+  const isUpdate = ref(true);
+  const { t } = useI18n();
+
+  const route = useRoute();
+  const collectionId = route.query.collectionId;
+
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    labelWidth: 160,
+    baseColProps: { span: 24 },
+    layout: 'vertical',
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+    await resetFields();
+    setDrawerProps({ confirmLoading: false });
+
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      await setFieldsValue({
+        ...data.record,
+      });
+    }
+  });
+
+  const getTitle = computed(() =>
+    !unref(isUpdate) ? t('wechat.agentBase.addAgentBase') : t('wechat.agentBase.editAgentBase'),
+  );
+
+  async function handleSubmit() {
+    const values = await validate();
+    setDrawerProps({ confirmLoading: true });
+    values['id'] = unref(isUpdate) ? values['id'] : "0";
+    values['collectionId'] = collectionId; // Add collectionId to values
+    let result = unref(isUpdate) ? await updateAgentBase(values) : await createAgentBase(values);
+    if (result.code === 0) {
+      closeDrawer();
+      emit('success');
+    }
+    setDrawerProps({ confirmLoading: false });
+  }
+</script>

+ 50 - 0
src/views/wechat/agent/agent_base/agentBase.data.ts

@@ -0,0 +1,50 @@
+import { BasicColumn, FormSchema } from '@/components/Table';
+import { useI18n } from '@/hooks/web/useI18n';
+import { formatToDateTime } from '@/utils/dateUtil';
+
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+  {
+    title: '问题',
+    dataIndex: 'q',
+    width: 100,
+  },
+  {
+    title: '答案',
+    dataIndex: 'a',
+    width: 100,
+  },
+  {
+    title: t('common.createTime'),
+    dataIndex: 'createdAt',
+    width: 70,
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'q',
+    label: '问题',
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'a',
+    label: '答案',
+    component: 'Input',
+    required: true,
+  },
+];

+ 141 - 0
src/views/wechat/agent/agent_base/index.vue

@@ -0,0 +1,141 @@
+<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.agentBase.addAgentBase') }}
+        </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>
+    <AgentBaseDrawer @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 { useDrawer } from '@/components/Drawer';
+  import AgentBaseDrawer from './AgentBaseDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './agentBase.data';
+  import { getAgentBaseList, deleteAgentBase } from '@/api/wechat/agentBase';
+
+  import { useRoute } from 'vue-router';
+
+  defineOptions({ name: 'AgentBaseManagement' });
+
+  const { t } = useI18n();
+  const selectedIds = ref<number[] | string[]>();
+  const showDeleteButton = ref<boolean>(false);
+
+  const route = useRoute();
+  const collectionId = route.query.collectionId;
+
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const [registerTable, { reload }] = useTable({
+    title: t('wechat.agentBase.agentBaseList'),
+    api: (params) => getAgentBaseList({ ...params, collectionId }),
+    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 deleteAgentBase({ id: 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 deleteAgentBase({ ids: selectedIds.value as number[] });
+        if (result.code === 0) {
+          showDeleteButton.value = false;
+          await reload();
+        }
+      },
+      onCancel() {
+        console.log('Cancel');
+      },
+    });
+  }
+
+  async function handleSuccess() {
+    await reload();
+  }
+</script>

+ 38 - 15
src/views/wechat/agent/index.vue

@@ -20,21 +20,7 @@
       <template #bodyCell="{ column, record }">
       <template #bodyCell="{ column, record }">
         <template v-if="column.key === 'action'">
         <template v-if="column.key === 'action'">
           <TableAction
           <TableAction
-            :actions="[
-              {
-                label: '编辑',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                label: '删除',
-                color: 'error',
-                popConfirm: {
-                  title: t('common.deleteConfirm'),
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
+            :actions="getActions(record)"
           />
           />
         </template>
         </template>
       </template>
       </template>
@@ -55,11 +41,14 @@
 
 
   import { columns, searchFormSchema } from './agent.data';
   import { columns, searchFormSchema } from './agent.data';
   import { getAgentList, deleteAgent } from '@/api/wechat/agent';
   import { getAgentList, deleteAgent } from '@/api/wechat/agent';
+  import {PageEnum} from "@/enums/pageEnum";
+  import {useRouter} from "vue-router";
 
 
   export default defineComponent({
   export default defineComponent({
     name: 'AgentManagement',
     name: 'AgentManagement',
     components: { BasicTable, AgentDrawer, TableAction, Button },
     components: { BasicTable, AgentDrawer, TableAction, Button },
     setup() {
     setup() {
+      const router = useRouter();
       const { t } = useI18n();
       const { t } = useI18n();
       const selectedIds = ref<number[] | string[]>();
       const selectedIds = ref<number[] | string[]>();
       const showDeleteButton = ref<boolean>(false);
       const showDeleteButton = ref<boolean>(false);
@@ -136,6 +125,38 @@
         await reload();
         await reload();
       }
       }
 
 
+      //打开知识库
+      function handleDataBase(record: Recordable) {
+        console.log('打开知识库')
+        router.push({
+          path: PageEnum.AGENT_BASE,
+          query: { collectionId: record.collection_id }
+        });
+      }
+
+      function getActions(record: Recordable) {
+        return [
+          {
+            label: '编辑',
+            onClick: handleEdit.bind(null, record),
+          },
+          {
+            label: '删除',
+            color: 'error',
+            popConfirm: {
+              title: t('common.deleteConfirm'),
+              placement: 'left',
+              confirm: handleDelete.bind(null, record),
+            },
+          },
+          {
+            onClick: handleDataBase.bind(null, record),
+            label: '知识库',
+            ifShow: true,
+          }
+        ];
+      }
+
       return {
       return {
         t,
         t,
         registerTable,
         registerTable,
@@ -146,6 +167,8 @@
         handleSuccess,
         handleSuccess,
         handleBatchDelete,
         handleBatchDelete,
         showDeleteButton,
         showDeleteButton,
+        handleDataBase,
+        getActions,
       };
       };
     },
     },
   });
   });

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

+ 152 - 0
src/views/wechat/token/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.token.addToken') }}
+        </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>
+    <TokenDrawer @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 TokenDrawer from './TokenDrawer.vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { columns, searchFormSchema } from './token.data';
+  import { getTokenList, deleteToken } from '@/api/wechat/token';
+
+  export default defineComponent({
+    name: 'TokenManagement',
+    components: { BasicTable, TokenDrawer, 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.token.tokenList'),
+        api: getTokenList,
+        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 deleteToken({ 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 deleteToken({ 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>

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

@@ -0,0 +1,84 @@
+import { BasicColumn, FormSchema } from '@/components/Table';
+import { useI18n } from '@/hooks/web/useI18n';
+import { formatToDateTime } from '@/utils/dateUtil';
+
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+  {
+    title: t('wechat.token.expireAt'),
+    dataIndex: 'expireAt',
+    width: 100,
+  },
+  {
+    title: t('wechat.token.expireAtStr'),
+    dataIndex: 'expireAtStr',
+    width: 100,
+  },
+  {
+    title: t('wechat.token.token'),
+    dataIndex: 'token',
+    width: 100,
+  },
+  {
+    title: t('wechat.token.mac'),
+    dataIndex: 'mac',
+    width: 100,
+  },
+  {
+    title: t('common.createTime'),
+    dataIndex: 'createdAt',
+    width: 70,
+    customRender: ({ record }) => {
+      return formatToDateTime(record.createdAt);
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'token',
+    label: t('wechat.token.token'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'mac',
+    label: t('wechat.token.mac'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'expireAt',
+    label: t('wechat.token.expireAt'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'expireAtStr',
+    label: t('wechat.token.expireAtStr'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'token',
+    label: t('wechat.token.token'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'mac',
+    label: t('wechat.token.mac'),
+    component: 'Input',
+    required: true,
+  },
+];