刘洋 9 달 전
부모
커밋
ccab2dc626

+ 8 - 0
src/api/service-unit.js

@@ -200,3 +200,11 @@ export const saveAllocationInfoApi = (data) =>
     data,
     loading: true,
   });
+
+export const exportPipeiApi = (data) =>
+  request({
+    url: '/api/admin/tb/crm/receiver/export',
+    params: data,
+    loading: true,
+    download: true,
+  });

+ 14 - 0
src/api/sop.js

@@ -292,3 +292,17 @@ export const saveAllocationApi = (data) =>
     data,
     loading: true,
   });
+
+export const projectMonitorList = (params) =>
+  request({
+    url: '/api/admin/project/monitor/page',
+    params,
+    loading: true,
+  });
+
+export const getWorkCardInfo = (params) =>
+  request({
+    url: '/api/admin/project/monitor/user/detail',
+    params,
+    loading: true,
+  });

+ 17 - 0
src/api/system.js

@@ -237,3 +237,20 @@ export const taskQueryApi = (data) =>
     url: '/api/admin/task/query',
     params: data,
   });
+
+export const getReceiveInfoApi = (data) =>
+  request({
+    url: '/api/sys/custom/receiver/page',
+    params: data,
+  });
+
+export const editReceiveInfoApi = (data) =>
+  request({
+    url: '/api/sys/custom/receiver/save',
+    data,
+  });
+export const deleteReceiveInfoApi = (data) =>
+  request({
+    url: '/api/sys/custom/receiver/delete',
+    params: data,
+  });

BIN
src/assets/imgs/home_bg.png


BIN
src/assets/imgs/home_head.png


BIN
src/assets/imgs/home_logo.png


+ 10 - 5
src/components/common/select-url-user/index.vue

@@ -8,9 +8,9 @@
   >
     <t-option
       v-for="item in optionList"
-      :key="item.id"
-      :value="item.id"
-      :label="item.realName"
+      :key="item[props.valueKey]"
+      :value="item[props.valueKey]"
+      :label="item[props.labelKey]"
     />
   </t-select>
 </template>
@@ -30,6 +30,8 @@ const props = defineProps({
   modelValue: { type: [Number, String, Array], default: '' },
   url: { type: String, default: '' },
   params: { type: Object, default: {} },
+  valueKey: { type: String, default: 'id' },
+  labelKey: { type: String, default: 'realName' },
 });
 const isMultiple = computed(() => {
   const multiple = attrs.multiple;
@@ -52,9 +54,12 @@ const search = async () => {
 const onChange = () => {
   const selectedData = isMultiple.value
     ? optionList.value.filter(
-        (item) => selected.value && selected.value.includes(item.id)
+        (item) =>
+          selected.value && selected.value.includes(item[props.valueKey])
       )
-    : optionList.value.filter((item) => selected.value === item.id);
+    : optionList.value.filter(
+        (item) => selected.value === item[props.valueKey]
+      );
   emit('update:modelValue', selected.value);
   emit('change', isMultiple.value ? selectedData : selectedData[0]);
 };

+ 2 - 0
src/components/global/index.js

@@ -26,6 +26,7 @@ import SButton from './s-buttons/index.vue';
 import ImageView from './image-viewer/index.vue';
 import Robot from './robot/index.vue';
 import MyTable from './my-table/index.vue';
+import WorkCard from './work-card/index.vue';
 
 use([
   CanvasRenderer,
@@ -54,5 +55,6 @@ export default {
     Vue.component('ImageView', ImageView);
     Vue.component('Robot', Robot);
     Vue.component('MyTable', MyTable);
+    Vue.component('WorkCard', WorkCard);
   },
 };

+ 196 - 0
src/components/global/work-card/index.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="work-card">
+    <div class="home">
+      <img src="../../../assets/imgs/home_bg.png" class="home-bg" />
+      <div class="logo-info">
+        <img
+          src="../../../assets/imgs/home_logo.png"
+          style="width: 80px; margin-bottom: 16px"
+        />
+        <div class="txt">实施工作证</div>
+        <img
+          :src="data.photoPath"
+          style="width: 220px; margin-top: 16px; margin-bottom: 16px"
+        />
+        <div class="u-name">{{ data.name }}</div>
+        <div class="u-info"> 认证编号:{{ data.code }} </div>
+        <div class="u-info"> 手机号码:{{ data.mobileNumber }} </div>
+      </div>
+      <div class="experience">
+        <div>项目经验:</div>
+        <div v-for="item in projectExperience">{{ item }}</div>
+      </div>
+      <div class="background-box">
+        <div
+          class="background"
+          v-for="item in domList"
+          :style="[item]"
+          :key="item"
+          >{{ content }}
+        </div>
+      </div>
+    </div>
+    <div class="close" @click="emit('close')">&times;</div>
+  </div>
+</template>
+<script name="WorkCard" setup>
+import { ref, onMounted, computed } from 'vue';
+import { getWorkCardInfo } from '@/api/sop';
+const projectExperience = computed(() => {
+  return data.value.projectExperience || [];
+});
+const emit = defineEmits(['close']);
+const props = defineProps(['userId']);
+const data = ref({
+  name: '',
+  code: '',
+  mobileNumber: '',
+  photoPath: '',
+  photoPath: '',
+  projectExperience: [],
+});
+const domList = ref([]);
+const h = ref(window.innerHeight * 0.9);
+const w = ref(window.innerHeight * 0.44);
+const initWarter = () => {
+  let screenWidth = w.value;
+  let screenHeight = h.value;
+  let gapX = 0,
+    gapY = 0,
+    width = 0,
+    height = 40,
+    color = '#fff',
+    alpha = 0.4,
+    fontSize = 16,
+    angle = 20,
+    zIndex = 999;
+
+  let heightNum = Math.ceil(screenHeight / (gapY * 2 + height));
+  let widthNum = Math.ceil(
+    screenWidth / (gapX * 2 + (width || screenWidth / 2))
+  );
+
+  let num = heightNum * widthNum;
+
+  for (let i = 0; i < num; i++) {
+    let mask_div = {};
+    mask_div.transform = 'rotate(-' + angle + 'deg)';
+    mask_div.visibility = '';
+    mask_div.overflow = 'hidden';
+    mask_div.margin = `${gapY}px ${gapX}px`;
+    mask_div.zIndex = zIndex;
+    mask_div.pointerEvents = 'none';
+    mask_div.opacity = alpha;
+    mask_div.fontSize = `${fontSize}px`;
+    mask_div.fontFamily = '微软雅黑';
+    mask_div.color = color;
+    mask_div.width = (width || screenWidth / 3) + 'px';
+    mask_div.height = height + 'px';
+
+    domList.value.push(mask_div);
+  }
+};
+const getData = () => {
+  getWorkCardInfo({ userId: props.userId }).then((res) => {
+    data.value = res || {};
+  });
+};
+onMounted(() => {
+  initWarter();
+  getData();
+});
+</script>
+<style lang="less" scoped>
+.work-card {
+  position: fixed;
+  z-index: 3333;
+  left: 0;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.3);
+  .experience {
+    font-size: 12px;
+    padding: 8px 12px 0 12px;
+    line-height: 18px;
+    color: #262626;
+  }
+  .close {
+    width: 32px;
+    height: 32px;
+    border-radius: 16px;
+    background: rgba(0, 0, 0, 0.7);
+    text-align: center;
+    line-height: 30px;
+    cursor: pointer;
+    font-size: 20px;
+    color: #fff;
+    position: absolute;
+    right: 30px;
+    top: 20px;
+    transition: all 0.3s;
+    &:hover {
+      background: rgba(0, 0, 0, 0.9);
+    }
+  }
+  .home {
+    height: 90vh;
+    width: 44vh;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translateX(-50%) translateY(-50%);
+    .background-box {
+      position: fixed;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      pointer-events: none;
+      display: flex;
+      flex-wrap: wrap;
+      z-index: -1;
+      .background {
+        box-sizing: border-box;
+        text-align: center;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        pointer-events: none;
+        background-attachment: fixed;
+        background-repeat: repeat;
+        background-origin: content-box;
+        background-position: center;
+      }
+    }
+    .home-bg {
+      position: absolute;
+      top: 0;
+      left: 0;
+      height: 100%;
+      width: 100%;
+      z-index: -10;
+    }
+    .logo-info {
+      text-align: center;
+      padding-top: 30px;
+      .u-name {
+        font-size: 32px;
+        font-weight: bold;
+        letter-spacing: 3px;
+        margin-bottom: 15px;
+      }
+      .u-info {
+        font-size: 16px;
+        color: #8c8c8c;
+        line-height: 30px;
+      }
+      .txt {
+        font-size: 20px;
+        font-weight: bold;
+        color: #262626;
+      }
+    }
+  }
+}
+</style>

+ 1 - 1
src/config/constants.js

@@ -276,7 +276,7 @@ export const DEVICE_TRANSPORT_METHOD = {
   OTHER: '其它',
 };
 
-const PROJECT_PROCESS = {
+export const PROJECT_PROCESS = {
   PREPARE: '准备',
   SCAN: '扫描',
   MARK: '评卷',

+ 14 - 6
src/router/modules/sop.js

@@ -78,7 +78,7 @@ export default {
             import('@/views/sop/sop-manage/device-out-in/index.vue'),
           meta: {
             title: '设备出入库登记',
-            sort: 3,
+            sort: 4,
             alias: 'deviceInOutSop',
             icon: 'device-outin',
           },
@@ -90,15 +90,23 @@ export default {
             import('@/views/sop/sop-manage/project-change-report/index.vue'),
           meta: {
             title: '项目计划变更报备',
-            sort: 4,
+            sort: 5,
             alias: 'projectExchange',
             icon: 'project-change',
           },
         },
-        // {
-        //   name: 'ProjectMonitor',
-        //   path: '/sop/sop-manage/project-monitor',
-        // },
+        {
+          name: 'ProjectMonitor',
+          path: '/sop/sop-manage/project-monitor',
+          component: () =>
+            import('@/views/sop/sop-manage/project-monitor/index.vue'),
+          meta: {
+            title: '项目监控',
+            sort: 3,
+            alias: 'ProjectMonitor',
+            icon: 'project-change',
+          },
+        },
       ],
     },
     {

+ 12 - 0
src/router/modules/system.js

@@ -80,6 +80,18 @@ export default {
             icon: 'ding-configure',
           },
         },
+        {
+          name: 'ReceiveInfo',
+          path: '/system/config-manage/receive-info',
+          component: () =>
+            import('@/views/system/config-manage/receive-info/index.vue'),
+          meta: {
+            title: '收件信息',
+            sort: 1,
+            alias: 'receiver',
+            icon: 'ding-configure',
+          },
+        },
       ],
     },
     {

+ 22 - 7
src/views/resource-guard/person-guard/person-files/add-person-file-dialog.vue

@@ -168,11 +168,14 @@
         </t-col>
         <t-col :span="12">
           <t-form-item label="项目经验">
-            <t-textarea
-              class="project-experience"
-              v-model="formData.projectExperience"
-              :maxlength="200"
-            ></t-textarea>
+            <t-row :gutter="[10, 10]">
+              <t-col :span="4" v-for="(input, i) in formData.projectExperience">
+                <t-input
+                  v-model="formData.projectExperience[i]"
+                  :maxlength="20"
+                ></t-input>
+              </t-col>
+            </t-row>
           </t-form-item>
         </t-col>
       </t-row>
@@ -230,13 +233,25 @@ const { formData, isEdit } = useClearDialog(
     authenticationScore: '',
     authenticationValidTime: '',
     remark: '',
-    projectExperience: '',
+    projectExperience: ['', '', '', '', '', ''],
   },
   props,
   formRef,
   () => {
     for (let key in formData) {
-      formData[key] = props.curRow[key];
+      if (
+        key === 'projectExperience' &&
+        typeof props.curRow[key] === 'string'
+      ) {
+        let originArr = ['', '', '', '', '', ''];
+        let arr = JSON.parse(props.curRow[key]);
+        for (let i = 0; i < arr.length; i++) {
+          originArr[i] = arr[i];
+        }
+        formData['projectExperience'] = originArr;
+      } else {
+        formData[key] = props.curRow[key];
+      }
     }
     formData.id = props.curRow.userArchivesId;
     formData.roleIds = props.curRow.roleInfoList.map((item) => item.roleId);

+ 36 - 16
src/views/service-unit/dispatch/dispatch-manage/index.vue

@@ -44,6 +44,15 @@
           type="ACCOUNT_MANAGER"
         ></select-type-user>
       </template>
+      <template #export="{ item, params }">
+        <t-button
+          theme="success"
+          v-if="perm.BUTTON_Export"
+          style="margin-left: 10px"
+          @click="exportHandle"
+          >匹配导出</t-button
+        >
+      </template>
     </SearchForm>
     <div class="flex-1 page-wrap">
       <p class="page-wrap-tips">
@@ -206,6 +215,7 @@ import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
 import { ErrorCircleFilledIcon, FactCheckIcon } from 'tdesign-icons-vue-next';
 import CreateSop from './create-sop.vue';
 import usePermission from '@/hooks/usePermission';
+import { exportPipeiApi } from '@/api/service-unit';
 const { perm } = usePermission();
 
 import {
@@ -236,6 +246,8 @@ const selectChange = (value, { selectedRowData }) => {
   selectedRows.value = selectedRowData;
 };
 
+const exportFile = () => {};
+
 const fields = ref([
   {
     prop: 'serviceId',
@@ -272,22 +284,7 @@ const fields = ref([
       clearable: true,
     },
   },
-  {
-    type: 'buttons',
-    colSpan: 3,
-    children: [
-      {
-        type: 'button',
-        text: '搜索',
-        attrs: {
-          style: { marginLeft: '0 !important' },
-        },
-        onClick: () => {
-          refresh();
-        },
-      },
-    ],
-  },
+
   {
     prop: 'custom',
     label: '客户名称',
@@ -317,6 +314,19 @@ const fields = ref([
       valueType: 'time-stamp',
     },
   },
+  {
+    type: 'button',
+    text: '搜索',
+    attrs: {
+      style: { marginLeft: '0 !important' },
+    },
+    onClick: () => {
+      refresh();
+    },
+  },
+  {
+    cell: 'export',
+  },
 ]);
 const params = reactive({
   serviceId: '',
@@ -489,6 +499,16 @@ const handleBatchDisable = () => {
 onMounted(() => {
   getWaitCount();
 });
+
+const exportHandle = () => {
+  if (!params.serviceId) {
+    MessagePlugin.error('请选择服务单元');
+    return;
+  }
+  exportPipeiApi({ serviceUnitId: params.serviceId }).then(() => {
+    MessagePlugin.success('导出成功');
+  });
+};
 </script>
 
 <style lang="less" scoped>

+ 142 - 2
src/views/sop/sop-manage/project-monitor/index.vue

@@ -1,5 +1,145 @@
 <template>
-  <div class="project-monitor flex flex-col h-full">项目监控 </div>
+  <div class="project-monitor flex flex-col h-full">
+    <SearchForm :fields="fields" :params="params" :search="search">
+      <template #service="{ item, params }">
+        <select-service-unit v-model="params[item.prop]"></select-service-unit>
+      </template>
+      <template #customer="{ item, params }">
+        <select-customer v-model="params[item.prop]"></select-customer>
+      </template>
+      <template #buttons>
+        <t-button theme="primary" @click="search">搜索</t-button>
+      </template>
+    </SearchForm>
+
+    <div class="flex-1 page-wrap">
+      <t-table
+        size="small"
+        row-key="id"
+        :columns="columns"
+        :data="tableData"
+        bordered
+        :pagination="{
+          defaultCurrent: 1,
+          defaultPageSize: 10,
+          onChange,
+          showJumper: true,
+          showPageSize: false,
+          total: pagination.total,
+        }"
+        v-loading="tableLoading"
+      >
+        <template #regionCoordinator="{ col, row }">
+          <t-link
+            theme="primary"
+            hover="color"
+            @click="showCard(row.regionCoordinator?.userId)"
+          >
+            {{ row.regionCoordinator?.userName }}
+          </t-link>
+        </template>
+        <template #projectManager="{ col, row }">
+          <t-link
+            theme="primary"
+            hover="color"
+            @click="showCard(row.projectManager?.userId)"
+          >
+            {{ row.projectManager?.userName }}
+          </t-link>
+        </template>
+        <template #engineerList="{ col, row }">
+          <t-link theme="primary" hover="color" @click="showCard(row)">
+            {{
+              (row.engineerList || []).map((item) => item.userName).join('、')
+            }}
+          </t-link>
+        </template>
+      </t-table>
+    </div>
+    <WorkCard
+      v-if="showCardFlag"
+      @close="showCardFlag = false"
+      :userId="userId"
+    ></WorkCard>
+  </div>
 </template>
-<script name="ProjectMonitor" setup></script>
+<script name="ProjectMonitor" setup>
+import { reactive, ref } from 'vue';
+import { PROJECT_PROCESS } from '@/config/constants';
+import { dictToOptionList } from '@/utils/tool';
+import { projectMonitorList } from '@/api/sop';
+import useFetchTable from '@/hooks/useFetchTable';
+
+const showCardFlag = ref(false);
+const userId = ref();
+const showCard = (id) => {
+  if (id) {
+    userId.value = id;
+    showCardFlag.value = true;
+  }
+};
+
+const fields = ref([
+  {
+    prop: 'serviceUnitId',
+    label: '服务单元',
+    cell: 'service',
+    labelWidth: 70,
+    attrs: {
+      clearable: true,
+    },
+  },
+  {
+    prop: 'customerId',
+    label: '客户',
+    cell: 'customer',
+    labelWidth: 70,
+    attrs: {
+      clearable: true,
+    },
+  },
+  {
+    prop: 'process',
+    label: '进度',
+    labelWidth: 70,
+    type: 'select',
+    options: [...dictToOptionList(PROJECT_PROCESS)],
+    attrs: {
+      clearable: true,
+    },
+  },
+  {
+    prop: 'buttons',
+    colSpan: 4,
+    labelWidth: 16,
+  },
+]);
+const params = reactive({
+  serviceUnitId: '',
+  customerId: '',
+  process: '',
+});
+
+const columns = [
+  { colKey: 'customName', title: '客户名称', width: 200 },
+  { colKey: 'crmName', title: '项目名称', width: 140 },
+  { colKey: 'courseName', title: '科目名称', width: 120 },
+  { colKey: 'process', title: '当前进度', width: 140 },
+  { colKey: 'leadName', title: '大区经理', width: 200 },
+  { colKey: 'regionCoordinator', title: '区域协调人', width: 200 },
+  { colKey: 'projectManager', title: '项目经理', width: 200 },
+  { colKey: 'engineerList', title: '工程师', width: 200 },
+];
+
+const {
+  loading: tableLoading,
+  pagination,
+  tableData,
+  search,
+  fetchData,
+  onChange,
+} = useFetchTable(projectMonitorList, {
+  params: params,
+});
+</script>
 <style lang="less" scoped></style>

+ 173 - 0
src/views/system/config-manage/receive-info/AddDialog.vue

@@ -0,0 +1,173 @@
+<template>
+  <my-dialog
+    :visible="visible"
+    :header="title"
+    :width="1000"
+    :closeOnOverlayClick="false"
+    attach="body"
+    @close="emit('update:visible', false)"
+  >
+    <t-form ref="formRef" :data="formData" :rules="rules" labelWidth="120px">
+      <t-row :gutter="[0, 20]">
+        <t-col :span="6">
+          <t-form-item label="业务类型" name="type">
+            <t-select v-model="formData.type">
+              <t-option
+                v-for="(val, key) in CUSTOMER_TYPE"
+                :key="key"
+                :label="val"
+                :value="key"
+              ></t-option>
+            </t-select>
+          </t-form-item>
+        </t-col>
+        <t-col :span="6"></t-col>
+        <t-col :span="6">
+          <t-form-item label="客户名称" name="name">
+            <t-input v-model="formData.name"></t-input>
+          </t-form-item>
+        </t-col>
+        <t-col :span="6">
+          <t-form-item label="具体单位" name="unit">
+            <t-input v-model="formData.unit"></t-input>
+          </t-form-item>
+        </t-col>
+        <t-col :span="6">
+          <t-form-item label="收件人姓名" name="consignee">
+            <t-input v-model="formData.consignee"></t-input>
+          </t-form-item>
+        </t-col>
+        <t-col :span="6">
+          <t-form-item label="收件人电话" name="consigneePhone">
+            <t-input v-model="formData.consigneePhone"></t-input>
+          </t-form-item>
+        </t-col>
+        <t-col :span="12">
+          <t-form-item label="收件人地址" name="consigneeAddress">
+            <t-textarea v-model="formData.consigneeAddress"></t-textarea>
+          </t-form-item>
+        </t-col>
+      </t-row>
+    </t-form>
+    <template #foot>
+      <t-button theme="default" @click="emit('update:visible', false)"
+        >取消</t-button
+      >
+      <t-button theme="primary" @click="save">保存</t-button>
+    </template>
+  </my-dialog>
+</template>
+<script setup name="AddReceiveInfoDialog">
+import { ref, computed } from 'vue';
+import { MessagePlugin } from 'tdesign-vue-next';
+import useClearDialog from '@/hooks/useClearDialog';
+import { editReceiveInfoApi } from '@/api/system';
+import { CUSTOMER_TYPE } from '@/config/constants';
+
+const emit = defineEmits(['update:visible']);
+const props = defineProps({
+  visible: Boolean,
+  curRow: Object,
+});
+
+const formRef = ref(null);
+const title = computed(() => {
+  return (isEdit.value ? '编辑' : '新增') + '客户收件地址';
+});
+
+const { formData, isEdit } = useClearDialog(
+  {
+    type: '',
+    name: '',
+    unit: '',
+    consignee: '',
+    consigneePhone: '',
+    consigneeAddress: '',
+    id: '',
+  },
+  props,
+  formRef,
+  () => {
+    for (let key in formData) {
+      formData[key] = props.curRow[key];
+    }
+    formData.id = props.curRow?.id || undefined;
+  }
+);
+
+const rules = {
+  type: [
+    {
+      required: true,
+      message: '业务类型必选',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  name: [
+    {
+      required: true,
+      message: '客户名称必填',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  unit: [
+    {
+      required: true,
+      message: '具体单位必填写',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  consignee: [
+    {
+      required: true,
+      message: '收件人姓名必填',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  consigneePhone: [
+    {
+      required: true,
+      message: '收件人电话必填',
+      type: 'error',
+      trigger: 'change',
+    },
+    {
+      telnumber: true,
+      message: '请输入合适的手机号码',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  consigneeAddress: [
+    {
+      required: true,
+      message: '收件人地址必填',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+};
+
+const save = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  const res = await editReceiveInfoApi(formData).catch(() => {});
+  if (!res) return;
+
+  MessagePlugin.success('保存成功');
+  emit('update:visible', false);
+  emit('success');
+};
+</script>
+<style lang="less">
+.project-experience {
+  & > .t-textarea__inner {
+    height: 130px !important;
+  }
+}
+</style>

+ 176 - 0
src/views/system/config-manage/receive-info/index.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="receive-info">
+    <div class="page-action">
+      <t-space size="small">
+        <t-button
+          v-if="perm.BUTTON_Add"
+          theme="primary"
+          @click="handleEdit(null)"
+        >
+          <template #icon><svg-icon name="add-circle" color="#fff" /></template>
+          新增客户收件地址
+        </t-button>
+      </t-space>
+    </div>
+    <SearchForm :fields="fields" :params="params" :search="mixinSearch">
+      <template #name>
+        <select-url-user
+          v-model="params.name"
+          valueKey="name"
+          labelKey="name"
+          url="/api/sys/custom/receiver/list"
+          :key="customSelectKey"
+        ></select-url-user>
+      </template>
+    </SearchForm>
+    <div class="flex-1 page-wrap">
+      <t-table
+        size="small"
+        row-key="id"
+        :columns="columns"
+        :data="tableData"
+        bordered
+        v-loading="tableLoading"
+        :pagination="{
+          defaultCurrent: 1,
+          defaultPageSize: 10,
+          onChange,
+          showJumper: true,
+          showPageSize: false,
+          total: pagination.total,
+          current: pagination.pageNumber,
+        }"
+      >
+        <template #type="{ col, row }">
+          {{ CUSTOMER_TYPE[row[col.colKey]] }}
+        </template>
+        <template #operate="{ row }">
+          <div class="table-operations" @click.stop>
+            <t-link
+              v-if="perm.LINK_Update"
+              theme="primary"
+              hover="color"
+              @click="handleEdit(row)"
+            >
+              修改
+            </t-link>
+
+            <t-link
+              v-if="perm.LINK_Update"
+              theme="primary"
+              hover="color"
+              @click="handleDelete(row)"
+            >
+              删除
+            </t-link>
+          </div>
+        </template>
+      </t-table>
+    </div>
+
+    <AddDialog
+      v-model:visible="showAddDialog"
+      :curRow="curRow"
+      @success="addSuccessHandle"
+    ></AddDialog>
+  </div>
+</template>
+<script name="ReceiveInfo" setup>
+import { ref, reactive } from 'vue';
+import { DialogPlugin } from 'tdesign-vue-next';
+import usePermission from '@/hooks/usePermission';
+import useFetchTable from '@/hooks/useFetchTable';
+import { dictToOptionList } from '@/utils/tool';
+import { CUSTOMER_TYPE } from '@/config/constants';
+import { getReceiveInfoApi, deleteReceiveInfoApi } from '@/api/system';
+import AddDialog from './AddDialog.vue';
+
+const customSelectKey = ref(Date.now() + '');
+const curRow = ref();
+const showAddDialog = ref(false);
+const handleEdit = (row) => {
+  curRow.value = row;
+  showAddDialog.value = true;
+};
+const handleDelete = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '系统通知',
+    body: `确定删除该条数据吗?`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    theme: 'warning',
+    onConfirm: async () => {
+      confirmDia.hide();
+      const res = await deleteReceiveInfoApi({ id: row.id }).catch(() => {});
+      if (!res) return;
+      MessagePlugin.success('删除成功');
+      fetchData();
+    },
+  });
+};
+const addSuccessHandle = () => {
+  fetchData();
+  customSelectKey.value = Date.now() + '';
+};
+
+const { perm } = usePermission();
+const params = reactive({
+  type: '',
+  name: '',
+});
+const fields = ref([
+  {
+    prop: 'type',
+    label: '业务类型',
+    labelWidth: 70,
+    type: 'select',
+    options: dictToOptionList(CUSTOMER_TYPE),
+  },
+  {
+    prop: 'name',
+    label: '客户',
+    labelWidth: 70,
+  },
+  {
+    type: 'buttons',
+    colSpan: 2,
+    labelWidth: 0,
+    children: [
+      {
+        type: 'button',
+        text: '搜索',
+        onClick: () => {
+          search();
+        },
+      },
+    ],
+  },
+]);
+
+const columns = [
+  { colKey: 'type', title: '用户类型', width: 130 },
+  { colKey: 'name', title: '客户名称' },
+  { colKey: 'unit', title: '具体单位' },
+  { colKey: 'consignee', title: '收件人姓名', width: 130 },
+  { colKey: 'consigneePhone', title: '收件人电话', width: 130 },
+  { colKey: 'consigneeAddress', title: '收件地址' },
+  {
+    title: '管理',
+    colKey: 'operate',
+    fixed: 'right',
+    width: 160,
+  },
+];
+
+const {
+  pagination,
+  tableData,
+  fetchData,
+  search,
+  onChange,
+  loading: tableLoading,
+} = useFetchTable(getReceiveInfoApi, {
+  params: params,
+});
+</script>
+<style lang="less" scoped></style>