Răsfoiți Sursa

fix:增加whatsap联系人和群发页面

jimmyyem 2 săptămâni în urmă
părinte
comite
263b65d4f3

+ 30 - 0
src/api/whatsapp_batch/model/whatsappBatchModel.ts

@@ -0,0 +1,30 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: WhatsappBatch info response
+ */
+export interface WhatsappBatchInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  status?: number;
+  batchNo?: string;
+  taskName?: string;
+  fromPhone?: string;
+  msg?: string;
+  tag?: string;
+  tagids?: string;
+  total?: number;
+  success?: number;
+  fail?: number;
+  startTime?: number;
+  stopTime?: number;
+  sendTime?: number;
+  organizationId?: number;
+}
+
+/**
+ *  @description: WhatsappBatch list response
+ */
+
+export type WhatsappBatchListResp = BaseListResp<WhatsappBatchInfo>;

+ 74 - 0
src/api/whatsapp_batch/whatsappBatch.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 { 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',
+}
+
+/**
+ * @description: Get whatsapp batch list
+ */
+
+export const getWhatsappBatchList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappBatchListResp>>(
+    { url: Api.GetWhatsappBatchList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new whatsapp batch
+ */
+export const createWhatsappBatch = (params: WhatsappBatchInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateWhatsappBatch, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the whatsapp batch
+ */
+export const updateWhatsappBatch = (params: WhatsappBatchInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateWhatsappBatch, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete whatsapp batchs
+ */
+export const deleteWhatsappBatch = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteWhatsappBatch, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get whatsapp batch By ID
+ */
+export const getWhatsappBatchById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappBatchInfo>>(
+    { url: Api.GetWhatsappBatchById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 30 - 0
src/api/whatsapp_contact/model/whatsappContactModel.ts

@@ -0,0 +1,30 @@
+import { BaseListResp } from '@/api/model/baseModel';
+
+/**
+ *  @description: WhatsappContact info response
+ */
+export interface WhatsappContactInfo {
+  id?: number;
+  createdAt?: number;
+  updatedAt?: number;
+  status?: number;
+  cc?: string;
+  phone?: string;
+  name?: string;
+  markname?: string;
+  organizationId?: number;
+  cname?: string;
+  csex?: number;
+  cage?: number;
+  carea?: string;
+  cmobile?: string;
+  cbirtharea?: string;
+  cidcardNo?: string;
+  ctitle?: string;
+}
+
+/**
+ *  @description: WhatsappContact list response
+ */
+
+export type WhatsappContactListResp = BaseListResp<WhatsappContactInfo>;

+ 74 - 0
src/api/whatsapp_contact/whatsappContact.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 { WhatsappContactInfo, WhatsappContactListResp } from './model/whatsappContactModel';
+
+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',
+}
+
+/**
+ * @description: Get whatsapp contact list
+ */
+
+export const getWhatsappContactList = (params: BaseListReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappContactListResp>>(
+    { url: Api.GetWhatsappContactList, params },
+    { errorMessageMode: mode },
+  );
+};
+
+/**
+ *  @description: Create a new whatsapp contact
+ */
+export const createWhatsappContact = (params: WhatsappContactInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.CreateWhatsappContact, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Update the whatsapp contact
+ */
+export const updateWhatsappContact = (params: WhatsappContactInfo, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.UpdateWhatsappContact, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Delete whatsapp contacts
+ */
+export const deleteWhatsappContact = (params: BaseIDsReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseResp>(
+    { url: Api.DeleteWhatsappContact, params: params },
+    {
+      errorMessageMode: mode,
+      successMessageMode: mode,
+    },
+  );
+};
+
+/**
+ *  @description: Get whatsapp contact By ID
+ */
+export const getWhatsappContactById = (params: BaseIDReq, mode: ErrorMessageMode = 'notice') => {
+  return defHttp.post<BaseDataResp<WhatsappContactInfo>>(
+    { url: Api.GetWhatsappContactById, params: params },
+    {
+      errorMessageMode: mode,
+    },
+  );
+};

+ 21 - 0
src/locales/lang/en/whatsapp_batch.ts

@@ -0,0 +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: 'Add WhatsappBatch',
+    editWhatsappBatch: 'Edit WhatsappBatch',
+    whatsappBatchList: 'WhatsappBatch List',
+  },
+};

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

@@ -0,0 +1,21 @@
+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: 'Add WhatsappContact',
+    editWhatsappContact: 'Edit WhatsappContact',
+    whatsappContactList: 'WhatsappContact List',
+  },
+};

+ 21 - 0
src/locales/lang/zh-CN/whatsapp_batch.ts

@@ -0,0 +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 列表',
+  },
+};

+ 21 - 0
src/locales/lang/zh-CN/whatsapp_contact.ts

@@ -0,0 +1,21 @@
+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 列表',
+  },
+};

+ 75 - 0
src/views/whatsapp_batch/whatsapp_batch/WhatsappBatchDrawer.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 './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>

+ 152 - 0
src/views/whatsapp_batch/whatsapp_batch/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('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>

+ 231 - 0
src/views/whatsapp_batch/whatsapp_batch/whatsappBatch.data.ts

@@ -0,0 +1,231 @@
+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();
+
+export const columns: BasicColumn[] = [
+  {
+    title: t('whatsapp_batch.whatsappBatch.batchNo'),
+    dataIndex: 'batchNo',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.taskName'),
+    dataIndex: 'taskName',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.fromPhone'),
+    dataIndex: 'fromPhone',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.msg'),
+    dataIndex: 'msg',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.tag'),
+    dataIndex: 'tag',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.tagids'),
+    dataIndex: 'tagids',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.total'),
+    dataIndex: 'total',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.success'),
+    dataIndex: 'success',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.fail'),
+    dataIndex: 'fail',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.startTime'),
+    dataIndex: 'startTime',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.stopTime'),
+    dataIndex: 'stopTime',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.sendTime'),
+    dataIndex: 'sendTime',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_batch.whatsappBatch.organizationId'),
+    dataIndex: 'organizationId',
+    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);
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'batchNo',
+    label: t('whatsapp_batch.whatsappBatch.batchNo'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'taskName',
+    label: t('whatsapp_batch.whatsappBatch.taskName'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'fromPhone',
+    label: t('whatsapp_batch.whatsappBatch.fromPhone'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'batchNo',
+    label: t('whatsapp_batch.whatsappBatch.batchNo'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'taskName',
+    label: t('whatsapp_batch.whatsappBatch.taskName'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'fromPhone',
+    label: t('whatsapp_batch.whatsappBatch.fromPhone'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'msg',
+    label: t('whatsapp_batch.whatsappBatch.msg'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'tag',
+    label: t('whatsapp_batch.whatsappBatch.tag'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'tagids',
+    label: t('whatsapp_batch.whatsappBatch.tagids'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    field: 'total',
+    label: t('whatsapp_batch.whatsappBatch.total'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'success',
+    label: t('whatsapp_batch.whatsappBatch.success'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'fail',
+    label: t('whatsapp_batch.whatsappBatch.fail'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'startTime',
+    label: t('whatsapp_batch.whatsappBatch.startTime'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'stopTime',
+    label: t('whatsapp_batch.whatsappBatch.stopTime'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'sendTime',
+    label: t('whatsapp_batch.whatsappBatch.sendTime'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'organizationId',
+    label: t('whatsapp_batch.whatsappBatch.organizationId'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    field: 'status',
+    label: t('whatsapp_batch.whatsappBatch.status'),
+    component: 'RadioButtonGroup',
+    defaultValue: 1,
+    componentProps: {
+      options: [
+        { label: t('common.on'), value: 1 },
+        { label: t('common.off'), value: 2 },
+      ],
+    },
+  },
+];

+ 75 - 0
src/views/whatsapp_contact/whatsapp_contact/WhatsappContactDrawer.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 './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>

+ 152 - 0
src/views/whatsapp_contact/whatsapp_contact/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('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>

+ 231 - 0
src/views/whatsapp_contact/whatsapp_contact/whatsappContact.data.ts

@@ -0,0 +1,231 @@
+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';
+
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+  {
+    title: t('whatsapp_contact.whatsappContact.cc'),
+    dataIndex: 'cc',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.phone'),
+    dataIndex: 'phone',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.name'),
+    dataIndex: 'name',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.markname'),
+    dataIndex: 'markname',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.organizationId'),
+    dataIndex: 'organizationId',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.cname'),
+    dataIndex: 'cname',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.csex'),
+    dataIndex: 'csex',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.cage'),
+    dataIndex: 'cage',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.carea'),
+    dataIndex: 'carea',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.cmobile'),
+    dataIndex: 'cmobile',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.cbirtharea'),
+    dataIndex: 'cbirtharea',
+    width: 100,
+  },
+  {
+    title: t('whatsapp_contact.whatsappContact.cidcardNo'),
+    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',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'phone',
+    label: t('whatsapp_contact.whatsappContact.phone'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+  {
+    field: 'name',
+    label: t('whatsapp_contact.whatsappContact.name'),
+    component: 'Input',
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    field: 'id',
+    label: 'ID',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'cc',
+    label: t('whatsapp_contact.whatsappContact.cc'),
+    component: 'Input',
+    required: true,
+  },
+  {
+    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',
+    label: t('whatsapp_contact.whatsappContact.csex'),
+    component: 'InputNumber',
+    required: true,
+  },
+  {
+    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: 'cmobile',
+    label: t('whatsapp_contact.whatsappContact.cmobile'),
+    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,
+    componentProps: {
+      options: [
+        { label: t('common.on'), value: 1 },
+        { label: t('common.off'), value: 2 },
+      ],
+    },
+  },
+];