瀏覽代碼

服务单元接口调试

zhangjie 1 年之前
父節點
當前提交
d7d018ed29

+ 2 - 0
components.d.ts

@@ -14,6 +14,7 @@ declare module 'vue' {
     SButtons: typeof import('./src/components/global/s-buttons/index.vue')['default']
     SearchForm: typeof import('./src/components/global/search-form/index.vue')['default']
     SearchFormItem: typeof import('./src/components/global/search-form/components/search-form-item.vue')['default']
+    SelectArea: typeof import('./src/components/common/select-area/index.vue')['default']
     SelectRole: typeof import('./src/components/common/select-role/index.vue')['default']
     SelectServiceLevel: typeof import('./src/components/common/select-service-level/index.vue')['default']
     SelectServiceUnit: typeof import('./src/components/common/select-service-unit/index.vue')['default']
@@ -22,6 +23,7 @@ declare module 'vue' {
     ServiceLevel: typeof import('./src/components/common/service-level/index.vue')['default']
     TAside: typeof import('tdesign-vue-next')['Aside']
     TButton: typeof import('tdesign-vue-next')['Button']
+    TCascader: typeof import('tdesign-vue-next')['Cascader']
     TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
     TCheckboxGroup: typeof import('tdesign-vue-next')['CheckboxGroup']
     TCol: typeof import('tdesign-vue-next')['Col']

+ 1 - 0
package.json

@@ -22,6 +22,7 @@
     "@vueuse/core": "^9.13.0",
     "autoprefixer": "^10.4.14",
     "axios": "^1.2.1",
+    "china-division": "^2.6.1",
     "crypto-js": "^4.1.1",
     "dayjs": "^1.11.7",
     "echarts": "^5.4.2",

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

@@ -31,3 +31,46 @@ export const serviceUnitCancelApi = (id) =>
     url: '/api/service/service/unit/cancel',
     params: { id },
   });
+
+// range-manage
+export const serviceScopeQueryApi = (data) =>
+  request({
+    url: '/api/service/service/scope/page',
+    params: data,
+  });
+export const serviceScopeUnbindCrmQueryApi = (data) =>
+  request({
+    url: '/api/service/service/scope/unbind/page',
+    params: data,
+  });
+export const serviceScopeSubTotalApi = () =>
+  request({
+    url: '/api/service/service/scope/subTotal',
+  });
+export const serviceScopeBindBatchApi = (data) =>
+  request({
+    url: '/api/service/service/scope/bind_batch',
+    data,
+  });
+export const serviceScopeUnbindApi = (crmId) =>
+  request({
+    url: '/api/service/service/scope/unbind',
+    params: { crmId },
+  });
+
+// regional-planning
+export const serviceRegionQueryApi = (data) =>
+  request({
+    url: '/api/service/service/region/page',
+    params: data,
+  });
+export const serviceRegionEditApi = (data) =>
+  request({
+    url: '/api/service/service/region/edit',
+    data,
+  });
+export const serviceRegionRemoveApi = (serviceRegionId) =>
+  request({
+    url: '/api/service/service/region/remove',
+    params: { serviceRegionId },
+  });

+ 49 - 0
src/components/common/select-area/area.js

@@ -0,0 +1,49 @@
+import pcaData from 'china-division/dist/pca-code.json';
+
+export const buildData = (limitLevel) => {
+  let areaCodeMap = {};
+
+  const transferData = (list, parent, parentLevel) => {
+    let curLevel = parentLevel + 1;
+    // if (list.length === 1 && list[0].name === '市辖区' && limitLevel === 2) {
+    //   return transferData(list[0].children, parent, parentLevel);
+    // }
+    return list.map((area) => {
+      let narea = { ...area };
+      if (narea.name === '市辖区' && parent) {
+        narea.name = parent.name;
+      }
+      let pathInfo = { pathName: '', pathCode: '' };
+      if (parent) {
+        pathInfo.pathName = `${parent.pathName}_${narea.name}`;
+        pathInfo.pathCode = `${parent.pathCode}_${narea.code}`;
+      } else {
+        pathInfo.pathName = `${narea.name}`;
+        pathInfo.pathCode = `${narea.code}`;
+      }
+      areaCodeMap[narea.code] = narea.name;
+
+      if (area.children && area.children.length && curLevel < limitLevel) {
+        return {
+          label: narea.name,
+          value: narea.code,
+          children: transferData(
+            area.children,
+            { ...area, ...pathInfo },
+            curLevel
+          ),
+        };
+      } else {
+        areaCodeMap[pathInfo.pathName] = pathInfo.pathCode;
+
+        return {
+          label: narea.name,
+          value: narea.code,
+        };
+      }
+    });
+  };
+  const areaData = transferData(pcaData, null, 0);
+
+  return { areaData, areaCodeMap };
+};

+ 84 - 0
src/components/common/select-area/index.vue

@@ -0,0 +1,84 @@
+<template>
+  <t-cascader
+    v-model="selected"
+    :options="options"
+    :clearable="clearable"
+    :disabled="disabled"
+    :valueType="valueType"
+    @change="onChange"
+  >
+  </t-cascader>
+</template>
+
+<script setup name="SelectArea">
+import { ref, watch } from 'vue';
+import { buildData } from './area';
+
+let selected = ref();
+
+const emit = defineEmits(['update:modelValue', 'change']);
+const props = defineProps({
+  modelValue: { type: [Object, Array, String] },
+  level: { type: Number, default: 3 },
+  clearable: { type: Boolean, default: true },
+  disabled: { type: Boolean, default: false },
+  valueType: { type: String, default: 'single' },
+});
+
+let options = [];
+let areaCodeMap = {};
+
+const initData = () => {
+  const data = buildData(props.level);
+  options = data.areaData;
+  areaCodeMap = data.areaCodeMap;
+};
+initData();
+
+const getVal = (list) => {
+  if (!list.length) return;
+  return list.map((item) => areaCodeMap[item]);
+};
+
+const onChange = (val) => {
+  let value = null;
+  if (props.valueType === 'single') {
+    value = areaCodeMap[val];
+  } else {
+    value = getVal(val);
+  }
+  emit('update:modelValue', value);
+  emit('change', value);
+};
+
+// tips: valueType等于single时,不保证复现结果,因为存在不同城市区域名称相同情况
+const parseCode = (val) => {
+  let value = val;
+  if (props.valueType !== 'single') {
+    value = val.join('_');
+  }
+
+  const validName = Object.keys(areaCodeMap).find((item) =>
+    item.includes(value)
+  );
+  if (!validName) return;
+
+  const validCode = areaCodeMap[validName].split('_');
+  return props.valueType === 'single'
+    ? validCode[props.level - 1]
+    : validCode.slice(0, props.level);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+
+    const code = parseCode(val);
+    selected.value = code;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 4 - 4
src/components/common/select-role/index.vue

@@ -18,9 +18,9 @@ let selected = ref('');
 
 const attrs = useAttrs();
 
-const emit = defineEmits(['update:value', 'change']);
+const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
-  value: { type: [Number, String], default: '' },
+  modelValue: { type: [Number, String], default: '' },
 });
 
 const search = async () => {
@@ -32,7 +32,7 @@ const search = async () => {
 };
 
 const onChange = () => {
-  emit('update:value', selected.value);
+  emit('update:modelValue', selected.value);
   emit('change', selected.value);
 };
 
@@ -41,7 +41,7 @@ onMounted(() => {
 });
 
 watch(
-  () => props.value,
+  () => props.modelValue,
   (val) => {
     selected.value = val;
   }

+ 4 - 4
src/components/common/select-service-level/index.vue

@@ -18,9 +18,9 @@ let selected = ref('');
 
 const attrs = useAttrs();
 
-const emit = defineEmits(['update:value', 'change']);
+const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
-  value: { type: [Number, String], default: '' },
+  modelValue: { type: [Number, String], default: '' },
 });
 
 const search = async () => {
@@ -32,7 +32,7 @@ const search = async () => {
 };
 
 const onChange = () => {
-  emit('update:value', selected.value);
+  emit('update:modelValue', selected.value);
   emit('change', selected.value);
 };
 
@@ -41,7 +41,7 @@ onMounted(() => {
 });
 
 watch(
-  () => props.value,
+  () => props.modelValue,
   (val) => {
     selected.value = val;
   }

+ 10 - 7
src/components/common/select-service-unit/index.vue

@@ -18,23 +18,26 @@ let selected = ref('');
 
 const attrs = useAttrs();
 
-const emit = defineEmits(['update:value', 'change']);
+const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
-  value: { type: [Number, String], default: '' },
+  modelValue: { type: [Number, String], default: '' },
+  status: { type: String },
 });
 
 const search = async () => {
   optionList.value = [];
-  const res = await serviceUnitQueryApi({ pageNumber: 1, pageSize: 100 }).catch(
-    () => {}
-  );
+
+  let data = { pageNumber: 1, pageSize: 100 };
+  if (props.status) data.status = props.status;
+
+  const res = await serviceUnitQueryApi(data).catch(() => {});
   if (!res) return;
 
   optionList.value = res.records || [];
 };
 
 const onChange = () => {
-  emit('update:value', selected.value);
+  emit('update:modelValue', selected.value);
   emit('change', selected.value);
 };
 
@@ -43,7 +46,7 @@ onMounted(() => {
 });
 
 watch(
-  () => props.value,
+  () => props.modelValue,
   (val) => {
     selected.value = val;
   }

+ 4 - 4
src/components/common/select-supplier/index.vue

@@ -18,9 +18,9 @@ let selected = ref('');
 
 const attrs = useAttrs();
 
-const emit = defineEmits(['update:value', 'change']);
+const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
-  value: { type: [Number, String], default: '' },
+  modelValue: { type: [Number, String], default: '' },
   type: { type: String, default: '' },
 });
 
@@ -35,7 +35,7 @@ const search = async () => {
 };
 
 const onChange = () => {
-  emit('update:value', selected.value);
+  emit('update:modelValue', selected.value);
   emit('change', selected.value);
 };
 
@@ -44,7 +44,7 @@ onMounted(() => {
 });
 
 watch(
-  () => props.value,
+  () => props.modelValue,
   (val) => {
     selected.value = val;
   }

+ 4 - 4
src/components/common/select-user/index.vue

@@ -18,9 +18,9 @@ let selected = ref('');
 
 const attrs = useAttrs();
 
-const emit = defineEmits(['update:value', 'change']);
+const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
-  value: { type: [Number, String], default: '' },
+  modelValue: { type: [Number, String], default: '' },
   roleType: { type: String, default: '' },
 });
 
@@ -38,7 +38,7 @@ const search = async () => {
 };
 
 const onChange = () => {
-  emit('update:value', selected.value);
+  emit('update:modelValue', selected.value);
   emit('change', selected.value);
 };
 
@@ -47,7 +47,7 @@ onMounted(() => {
 });
 
 watch(
-  () => props.value,
+  () => props.modelValue,
   (val) => {
     selected.value = val;
   }

+ 126 - 67
src/views/service-unit/service-unit-manage/add-range/index.vue

@@ -7,7 +7,7 @@
         <t-button
           theme="success"
           :disabled="!selectedRowKeys.length"
-          @click="regionalHandle"
+          @click="handlerBatchBind"
           >批量划定</t-button
         >
       </div>
@@ -21,82 +21,91 @@
           defaultCurrent: 1,
           defaultPageSize: 10,
           onChange,
-          total: pagination.total,
+          current: pagination.page,
         }"
+        v-loading="tableLoading"
         :selected-row-keys="selectedRowKeys"
         select-on-row-click
         @select-change="selectChange"
       >
+        <template #type="{ col, row }">
+          {{ customerTypeFilter(row[col.colKey]) }}
+        </template>
+        <template #begin-time="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #exam-start-time="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #exam-end-time="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
+        <template #create-time="{ col, row }">
+          {{ timestampFilter(row[col.colKey]) }}
+        </template>
       </t-table>
     </div>
+
+    <!-- SelectServiceUnitDialog -->
+    <select-service-unit-dialog
+      v-model:visible="showSelectServiceUnitDialog"
+      :crm-ids="selectedRowKeys"
+      @success="search"
+    ></select-service-unit-dialog>
   </div>
 </template>
 
 <script setup name="AddRange">
 import { reactive, ref } from 'vue';
-import { getTableData } from '@/api/test';
+import { omit } from 'lodash';
+import { MessagePlugin } from 'tdesign-vue-next';
+import { serviceScopeUnbindCrmQueryApi } from '@/api/service-unit';
 import useFetchTable from '@/hooks/useFetchTable';
+import { CUSTOMER_TYPE } from '@/config/constants';
+import { customerTypeFilter, timestampFilter } from '@/utils/filter';
+import SelectServiceUnitDialog from './select-service-unit-dialog.vue';
+
+let showSelectServiceUnitDialog = ref(false);
 
-const selectedRowKeys = ref([]);
-const selectChange = (value, { selectedRowData }) => {
-  selectedRowKeys.value = value;
-};
-const regionalHandle = () => {};
-const columns = [
-  {
-    colKey: 'row-select',
-    type: 'multiple',
-    width: 50,
-    fixed: 'left',
-  },
-  { colKey: 'a', title: '项目单号', minWidth: 80 },
-  { colKey: 'b', title: '派单时间', width: 140 },
-  { colKey: 'c', title: '派单人', minWidth: 80 },
-  { colKey: 'd', title: '客户类型', minWidth: 80 },
-  { colKey: 'e', title: '客户名称', minWidth: 80 },
-  { colKey: 'f', title: '项目名称', minWidth: 80 },
-  { colKey: 'g', title: '实施产品', minWidth: 80 },
-  { colKey: 'h', title: '考试开始时间', width: 140 },
-  { colKey: 'i', title: '考试结束时间', width: 140 },
-  { colKey: 'j', title: '大区经理', minWidth: 80 },
-  { colKey: 'k', title: '提交人', minWidth: 80 },
-  { colKey: 'l', title: '提交时间', width: 140 },
-];
-const {
-  loading: tableLoading,
-  pagination,
-  tableData,
-  fetchData,
-  onChange,
-} = useFetchTable(getTableData);
 const fields = ref([
   {
-    prop: 'a',
-    label: '服务单元',
+    prop: 'crmUserId',
+    label: '派单人',
     type: 'select',
     labelWidth: 80,
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'b',
-    label: '大区经理',
+    prop: 'productType',
+    label: '客户类型',
     type: 'select',
     labelWidth: 80,
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'c',
-    label: '派单人',
-    type: 'select',
+    prop: 'customName',
+    label: '客户名称',
     labelWidth: 80,
     colSpan: 5,
+    options: dictToOptionList(CUSTOMER_TYPE),
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'd',
-    label: '客户类型',
-    type: 'select',
+    prop: 'crmNo',
+    label: '项目单号',
     labelWidth: 80,
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
     type: 'buttons',
@@ -105,38 +114,88 @@ const fields = ref([
       {
         type: 'button',
         text: '查询',
+        onClick: () => {
+          search();
+        },
       },
     ],
   },
   {
-    prop: 'e',
-    label: '客户名称',
-    labelWidth: 80,
-    colSpan: 5,
-  },
-  {
-    prop: 'f',
-    label: '项目单号',
-    labelWidth: 80,
-    colSpan: 5,
-  },
-  {
-    prop: 'g',
+    prop: 'crmTime',
     label: '派单时间',
     type: 'daterange',
     labelWidth: 80,
     colSpan: 10,
+    attrs: {
+      clearable: true,
+    },
   },
 ]);
 const params = reactive({
-  a: '',
-  b: '',
-  c: '',
-  d: '',
-  e: '',
-  f: '',
-  g: [],
+  crmUserId: '',
+  productType: '',
+  customName: '',
+  crmNo: '',
+  crmTime: [],
+});
+const computedParams = computed(() => {
+  let data = omit(params, ['crmTime']);
+  data.startTime = params.crmTime[0];
+  data.endTime = params.crmTime[1];
+  return data;
 });
-</script>
 
-<style></style>
+const selectedRowKeys = ref([]);
+const selectChange = (value) => {
+  selectedRowKeys.value = value;
+};
+const columns = [
+  {
+    colKey: 'row-select',
+    type: 'multiple',
+    width: 50,
+    fixed: 'left',
+  },
+  { colKey: 'crmNo', title: '项目单号', minWidth: 80 },
+  { colKey: 'beginTime', title: '派单时间', width: 170, cell: 'begin-time' },
+  { colKey: 'crmUserName', title: '派单人', minWidth: 80 },
+  { colKey: 'productType', title: '客户类型', width: 90, cell: 'type' },
+  { colKey: 'customName', title: '客户名称', minWidth: 100 },
+  { colKey: 'productName', title: '项目名称', minWidth: 80 },
+  { colKey: 'productName', title: '实施产品', minWidth: 80 },
+  {
+    colKey: 'examStartTime',
+    title: '考试开始时间',
+    width: 170,
+    cell: 'exam-start-time',
+  },
+  {
+    colKey: 'examEndTime',
+    title: '考试结束时间',
+    width: 170,
+    cell: 'exam-end-time',
+  },
+  { colKey: 'creatorName', title: '提交人', minWidth: 80 },
+  { colKey: 'createTime', title: '提交时间', width: 170, cell: 'create-time' },
+];
+const {
+  loading: tableLoading,
+  pagination,
+  tableData,
+  search,
+  onChange,
+} = useFetchTable(serviceScopeUnbindCrmQueryApi, {
+  fetchDataHandle: () => {
+    selectedRowKeys.value = [];
+  },
+  params: computedParams,
+});
+
+const handlerBatchBind = () => {
+  if (!selectedRowKeys.value.length) {
+    MessagePlugin.error('请选择要划定的记录');
+    return;
+  }
+  showSelectServiceUnitDialog.value = true;
+};
+</script>

+ 71 - 0
src/views/service-unit/service-unit-manage/add-range/select-service-unit-dialog.vue

@@ -0,0 +1,71 @@
+<template>
+  <my-dialog
+    :visible="visible"
+    header="确认提示"
+    :width="600"
+    attach="body"
+    :closeOnOverlayClick="false"
+    @close="emit('update:visible', false)"
+    @opened="onOpened"
+  >
+    <p>请选择您要需要将这些派单划定的服务单元!</p>
+    <t-form ref="formRef" :data="formData" :rules="rules" :labelWidth="140">
+      <t-row :gutter="[0, 20]">
+        <t-col :span="12">
+          <t-form-item label="服务单元名称" name="serviceUnitId">
+            <select-service-unit v-model="formData.serviceUnitId">
+            </select-service-unit>
+          </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="SelectServiceUnitDialog">
+import { ref, reactive } from 'vue';
+import { MessagePlugin } from 'tdesign-vue-next';
+import { serviceScopeBindBatchApi } from '@/api/service-unit';
+
+const emit = defineEmits(['update:visible', 'success']);
+const formRef = ref(null);
+
+const props = defineProps({
+  visible: Boolean,
+  crmIds: Array,
+});
+
+const formData = reactive({ serviceUnitId: null, crmIdList: [] });
+const rules = {
+  serviceUnitId: [
+    {
+      required: true,
+      message: '服务单元必选',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+};
+
+const onOpened = () => {
+  formData.serviceUnitId = null;
+  formData.crmIdList = props.crmIds;
+  formRef.value.clearValidate();
+};
+
+const save = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  const res = await serviceScopeBindBatchApi(formData).catch(() => {});
+  if (!res) return;
+  MessagePlugin.success('操作成功');
+  emit('update:visible', false);
+  emit('success');
+};
+</script>

+ 128 - 66
src/views/service-unit/service-unit-manage/range-manage/index.vue

@@ -1,12 +1,27 @@
 <template>
   <div class="unit-manage flex flex-col h-full">
-    <SearchForm :fields="fields" :params="params"></SearchForm>
+    <SearchForm :fields="fields" :params="params">
+      <template #service-unit="{ item, params }">
+        <select-service-unit
+          v-model="params[item.prop]"
+          clearable
+        ></select-service-unit>
+      </template>
+    </SearchForm>
     <div class="flex-1 page-wrap">
-      <div class="btn-group">
-        <t-button theme="success" @click="addServiceRange"
-          >新增服务范围</t-button
-        >
+      <div class="flex justify-between items-center">
+        <t-space>
+          <span>共计服务派单:{{ totalData.totalCrmCount }}个</span>
+          <span>已划定派单:{{ totalData.bindCrmCount }}个</span>
+          <span>待划定派单:{{ totalData.unbindCrmCount }}个</span>
+        </t-space>
+        <div class="btn-group">
+          <t-button theme="success" @click="handleAddRange"
+            >新增服务范围</t-button
+          >
+        </div>
       </div>
+
       <t-table
         size="small"
         row-key="id"
@@ -18,95 +33,79 @@
           defaultPageSize: 10,
           onChange,
           total: pagination.total,
+          current: pagination.page,
         }"
+        v-loading="tableLoading"
       >
+        <template #type="{ col, row }">
+          {{ customerTypeFilter(row[col.colKey]) }}
+        </template>
+        <template #operate="{ row }">
+          <div class="table-operations">
+            <t-link theme="danger" hover="color" @click="handleDelete(row)">
+              移除
+            </t-link>
+          </div>
+        </template>
       </t-table>
     </div>
   </div>
 </template>
 
 <script setup lang="jsx" name="RangeManage">
-import { ref, reactive } from 'vue';
-import { getTableData } from '@/api/test';
-import useFetchTable from '@/hooks/useFetchTable';
+import { ref, reactive, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
-const router = useRouter();
-const addServiceRange = () => {
-  router.push({ name: 'AddRange' });
-};
-const columns = [
-  { colKey: 'a', title: '项目单号', width: 120 },
-  { colKey: 'b', title: '客户名称', minWidth: 100 },
-  { colKey: 'c', title: '客户类型', width: 90 },
-  { colKey: 'd', title: '业务类型', minWidth: 100 },
-  { colKey: 'e', title: '省份', minWidth: 60 },
-  { colKey: 'f', title: '城市', minWidth: 60 },
-  { colKey: 'g', title: '区县', minWidth: 60 },
-  { colKey: 'h', title: '服务档位', width: 90 },
-  {
-    title: '操作',
-    colKey: 'operate',
-    fixed: 'right',
-    width: 120,
-    cell: (h, { row }) => {
-      return (
-        <div class="table-operations">
-          <t-popconfirm
-            onConfirm={() => {
-              deleteRow(row);
-            }}
-            theme="warning"
-            content="您确定要将当前派单从派单服务单元中移除吗?"
-          >
-            <t-link theme="primary" hover="color">
-              移除
-            </t-link>
-          </t-popconfirm>
-        </div>
-      );
-    },
-  },
-];
-const {
-  loading: tableLoading,
-  pagination,
-  tableData,
-  fetchData,
-  onChange,
-} = useFetchTable(getTableData);
+import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
+import {
+  serviceScopeQueryApi,
+  serviceScopeUnbindApi,
+  serviceScopeSubTotalApi,
+} from '@/api/service-unit';
+import useFetchTable from '@/hooks/useFetchTable';
+import { CUSTOMER_TYPE } from '@/config/constants';
+import { dictToOptionList } from '@/utils/tool';
+import { customerTypeFilter } from '@/utils/filter';
 
-const refresh = async () => {};
-const deleteRow = (row) => {
-  console.log('deleteRow');
-};
+const router = useRouter();
 
 const fields = ref([
   {
-    prop: 'a',
+    prop: 'serviceUnitId',
     label: '服务单元名称',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    cell: 'service-unit',
   },
   {
-    prop: 'b',
+    prop: 'city',
     label: '区域',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'c',
+    prop: 'productType',
     label: '客户类型',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    options: dictToOptionList(CUSTOMER_TYPE),
+    attrs: {
+      clearable: true,
+    },
   },
   {
-    prop: 'd',
+    prop: 'customName',
     label: '客户名称',
     labelWidth: 100,
     colSpan: 5,
+    attrs: {
+      clearable: true,
+    },
   },
   {
     type: 'buttons',
@@ -115,16 +114,79 @@ const fields = ref([
       {
         type: 'button',
         text: '查询',
+        onClick: () => {
+          search();
+          getTotalData();
+        },
       },
     ],
   },
 ]);
 const params = reactive({
-  a: '',
-  b: '',
-  c: '',
-  d: '',
+  serviceUnitId: '',
+  city: '',
+  productType: '',
+  customName: '',
 });
-</script>
 
-<style></style>
+const columns = [
+  { colKey: 'serviceUnitName', title: '服务单元', minWidth: 100 },
+  { colKey: 'crmNo', title: '项目单号', width: 120 },
+  { colKey: 'customName', title: '客户名称', minWidth: 100 },
+  { colKey: 'productType', title: '客户类型', width: 90, cell: 'type' },
+  { colKey: 'province', title: '省份', minWidth: 60 },
+  { colKey: 'city', title: '城市', minWidth: 60 },
+  { colKey: 'area', title: '区县', minWidth: 60 },
+  { colKey: 'level', title: '服务档位', width: 90 },
+  {
+    title: '操作',
+    colKey: 'operate',
+    fixed: 'right',
+    width: 120,
+    cell: 'operate',
+  },
+];
+const {
+  loading: tableLoading,
+  pagination,
+  tableData,
+  search,
+  fetchData,
+  onChange,
+} = useFetchTable(serviceScopeQueryApi, { params }, false);
+
+let totalData = ref({
+  totalCrmCount: 0,
+  bindCrmCount: 0,
+  unbindCrmCount: 0,
+});
+
+const getTotalData = async () => {
+  const res = await serviceScopeSubTotalApi();
+  totalData.value = res;
+};
+
+const handleAddRange = () => {
+  router.push({ name: 'AddRange' });
+};
+const handleDelete = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '移除提示',
+    body: `您确定要将当前派单从派单服务单元中移除吗?`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    onConfirm: async () => {
+      confirmDia.hide();
+      const res = await serviceScopeUnbindApi(row.crmId).catch(() => {});
+      if (!res) return;
+      MessagePlugin.success('删除成功');
+      fetchData();
+    },
+  });
+};
+
+onMounted(() => {
+  search();
+  getTotalData();
+});
+</script>

+ 114 - 59
src/views/service-unit/service-unit-manage/regional-planning/add-region-dialog.vue

@@ -1,30 +1,36 @@
 <template>
   <my-dialog
     :visible="visible"
-    @close="emit('update:visible', false)"
-    :header="`${isEdit ? '修改' : '新增'}大区`"
-    :width="750"
+    :header="title"
+    :width="600"
+    attach="body"
     :closeOnOverlayClick="false"
+    top="10vh"
+    @close="emit('update:visible', false)"
   >
-    <t-form ref="formRef" :model="formData" layout="inline" labelWidth="120px">
-      <t-form-item label="服务单元名称:">
-        <t-select v-model="formData.a"> </t-select>
+    <t-form ref="formRef" :data="formData" :rules="rules" label-width="140px">
+      <t-form-item label="服务单元名称:" name="serviceUnitId">
+        <select-service-unit v-model="formData.serviceUnitId">
+        </select-service-unit>
       </t-form-item>
-      <t-form-item label="大区经理:">
-        <t-select v-model="formData.b"> </t-select>
+      <t-form-item label="大区经理:" name="leadId">
+        <select-user v-model="formData.leadId"> </select-user>
+      </t-form-item>
+      <t-form-item label-width="0px" name="targetValue">
+        <div style="height: 400px; width: 100%">
+          <t-transfer
+            v-model="targetValue"
+            v-model:checked="checkedRef"
+            :data="areaData"
+            style="justify-content: center"
+            @checked-change="handleCheckedChange"
+          >
+            <template #tree="slotProps">
+              <t-tree v-bind="slotProps" checkable hover transition />
+            </template>
+          </t-transfer>
+        </div>
       </t-form-item>
-      <t-transfer
-        v-model="formData.c"
-        v-model:checked="checkedRef"
-        :data="leftData"
-        @change="onChange"
-        @checked-change="handleCheckedChange"
-        style="margin: 15px auto 0"
-      >
-        <template #tree="slotProps">
-          <t-tree v-bind="slotProps" checkable hover expand-all transition />
-        </template>
-      </t-transfer>
     </t-form>
     <template #foot>
       <t-button theme="default" @click="emit('update:visible', false)"
@@ -34,60 +40,109 @@
     </template>
   </my-dialog>
 </template>
+
 <script setup name="AddRegionDialog">
+import { ref, computed } from 'vue';
+import { MessagePlugin } from 'tdesign-vue-next';
 import useClearDialog from '@/hooks/useClearDialog';
-import { ref } from 'vue';
+import { serviceRegionEditApi } from '@/api/service-unit';
+import { buildData } from '@/components/common/select-area/area';
+
 const emit = defineEmits(['update:visible']);
 const formRef = ref(null);
+
+const { areaData, areaCodeMap } = buildData(2);
+const checkedRef = ref([]);
+const targetValue = ref([]);
+
 const props = defineProps({
   visible: Boolean,
   curRow: Object,
 });
-const getDetail = async () => {
-  //编辑状态下获取回显数据的接口请求业务,如果curRow里的字段够用,就直接把curRow里的字段赋值给formData
-  alert('获取详情中...');
+
+const title = computed(() => {
+  return (isEdit.value ? '编辑' : '新增') + '大区';
+});
+
+const initFormData = {
+  serviceRegionId: null,
+  serviceUnitId: '',
+  leadId: '',
 };
+
 const { formData, isEdit } = useClearDialog(
-  {
-    a: '',
-    b: '',
-    c: [],
-  },
+  initFormData,
   props,
-  getDetail
+  formRef,
+  () => {
+    for (let key in initFormData) {
+      formData[key] = props.curRow[key];
+    }
+    targetValue.value = props.curRow.areaDtoList.map((item) => {
+      const codes = areaCodeMap[`${item.province}_${item.city}`].split('_');
+      return codes[1];
+    });
+  }
 );
-const checkedRef = ref([]);
-const leftData = [
-  { value: 1, label: '1' },
-  {
-    value: 2,
-    label: '2',
-    children: [
-      {
-        value: 2.1,
-        label: '21',
+
+const rules = {
+  serviceUnitId: [
+    {
+      required: true,
+      message: '服务单元必选',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  leadId: [
+    {
+      required: true,
+      message: '大区经理必选',
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
+  targetValue: [
+    {
+      validator: () => {
+        if (!targetValue.value || !targetValue.value.length)
+          return { result: false, message: '请完成区域设置' };
+
+        return { result: true, type: 'success' };
       },
-    ],
-  },
-];
-const handleCheckedChange = ({
-  checked,
-  sourceChecked,
-  targetChecked,
-  type,
-}) => {
-  checkedRef.value = checked;
-  console.log('handleCheckedChange', {
-    checked,
-    sourceChecked,
-    targetChecked,
-    type,
-  });
+      type: 'error',
+      trigger: 'change',
+    },
+  ],
 };
 
-const onChange = (newTargetValue) => {
-  console.log('onChange', newTargetValue);
+const handleCheckedChange = ({ checked }) => {
+  checkedRef.value = checked;
 };
 
-const save = () => {};
+const save = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  let data = { ...formData };
+  data.areaDtoList = targetValue.value.map((item) => {
+    return {
+      province: areaCodeMap[item.substring(0, 2)],
+      address: '',
+      area: '',
+      country: '中国',
+      city: areaCodeMap[item],
+    };
+  });
+
+  let result = true;
+  await serviceRegionEditApi(data).catch(() => {
+    result = false;
+  });
+  if (!result) return;
+
+  MessagePlugin.success('保存成功');
+  emit('update:visible', false);
+  emit('success');
+};
 </script>

+ 101 - 62
src/views/service-unit/service-unit-manage/regional-planning/index.vue

@@ -1,16 +1,26 @@
 <template>
   <div class="unit-manage flex flex-col h-full">
-    <SearchForm :fields="fields" :params="params"></SearchForm>
+    <SearchForm :fields="fields" :params="params">
+      <template #service="{ item, params }">
+        <select-service-unit
+          v-model="params[item.prop]"
+          clearable
+        ></select-service-unit>
+      </template>
+      <template #lead="{ item, params }">
+        <select-user v-model="params[item.prop]" clearable></select-user>
+      </template>
+      <template #city="{ item, params }">
+        <select-area
+          v-model="params[item.prop]"
+          clearable
+          :level="2"
+        ></select-area>
+      </template>
+    </SearchForm>
     <div class="flex-1 page-wrap">
       <div class="btn-group">
-        <t-button
-          theme="success"
-          @click="
-            curRow = null;
-            showAddRegionDialog = true;
-          "
-          >新增</t-button
-        >
+        <t-button theme="success" @click="handleAdd">新增</t-button>
       </div>
       <t-table
         size="small"
@@ -23,113 +33,142 @@
           defaultPageSize: 10,
           onChange,
           total: pagination.total,
+          current: pagination.page,
         }"
+        v-loading="tableLoading"
         :selected-row-keys="selectedRowKeys"
         select-on-row-click
         @select-change="selectChange"
       >
+        <template #operate="{ row }">
+          <div class="table-operations">
+            <t-link theme="primary" hover="color" @click="handleEdit(row)">
+              修改大区
+            </t-link>
+            <t-link theme="danger" hover="color" @click="handleDelete(row)">
+              删除
+            </t-link>
+          </div>
+        </template>
       </t-table>
     </div>
 
     <AddRegionDialog
       v-model:visible="showAddRegionDialog"
       :curRow="curRow"
+      @success="fetchData"
     ></AddRegionDialog>
   </div>
 </template>
 
 <script setup lang="jsx" name="RegionalPlanning">
 import { ref, reactive } from 'vue';
-import { getTableData } from '@/api/test';
+import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
+import {
+  serviceRegionQueryApi,
+  serviceRegionRemoveApi,
+} from '@/api/service-unit';
 import useFetchTable from '@/hooks/useFetchTable';
 import AddRegionDialog from './add-region-dialog.vue';
+
 const curRow = ref(null);
 const showAddRegionDialog = ref(false);
+
 const selectedRowKeys = ref([]);
-const selectChange = (value, { selectedRowData }) => {
+const selectChange = (value) => {
   selectedRowKeys.value = value;
 };
 
-const columns = [
-  { colKey: 'a', title: '服务单元', minWidth: 150 },
-  { colKey: 'b', title: '大区经理', minWidth: 80 },
-  { colKey: 'c', title: '区域划分', minWidth: 150 },
-  { colKey: 'd', title: '派单共计', minWidth: 80 },
-  {
-    title: '操作',
-    colKey: 'operate',
-    fixed: 'right',
-    width: 150,
-    cell: (h, { row }) => {
-      return (
-        <div class="table-operations">
-          <t-link
-            theme="primary"
-            hover="color"
-            onClick={(e) => {
-              e.stopPropagation();
-              curRow.value = row;
-              showAddRegionDialog.value = true;
-            }}
-          >
-            修改大区
-          </t-link>
-          <t-link theme="primary" hover="color">
-            删除
-          </t-link>
-        </div>
-      );
-    },
-  },
-];
-const {
-  loading: tableLoading,
-  pagination,
-  tableData,
-  fetchData,
-  onChange,
-} = useFetchTable(getTableData);
-
-const refresh = async () => {};
-
 const fields = ref([
   {
-    prop: 'a',
+    prop: 'serviceUnitId',
     label: '服务单元名称',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    cell: 'service',
   },
   {
-    prop: 'b',
+    prop: 'leadId',
     label: '大区经理',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    cell: 'lead',
   },
   {
-    prop: 'c',
+    prop: 'city',
     label: '区域',
     type: 'select',
     labelWidth: 100,
     colSpan: 5,
+    cell: 'city',
   },
   {
     type: 'buttons',
-    colSpan: 3,
+    colSpan: 2,
     children: [
       {
         type: 'button',
         text: '查询',
+        onClick: () => {
+          search();
+        },
       },
     ],
   },
 ]);
 const params = reactive({
-  a: '',
-  b: '',
-  c: '',
+  serviceUnitId: '',
+  leadId: '',
+  city: '',
 });
-</script>
 
-<style></style>
+const columns = [
+  { colKey: 'serviceUnitName', title: '服务单元', minWidth: 150 },
+  { colKey: 'leadName', title: '大区经理', minWidth: 80 },
+  { colKey: 'regionInfo', title: '区域划分', minWidth: 150 },
+  { colKey: 'orderCount', title: '派单共计', minWidth: 80 },
+  {
+    title: '操作',
+    colKey: 'operate',
+    fixed: 'right',
+    width: 150,
+    cell: 'operate',
+  },
+];
+const {
+  loading: tableLoading,
+  pagination,
+  tableData,
+  search,
+  fetchData,
+  onChange,
+} = useFetchTable(serviceRegionQueryApi, { params });
+
+const handleAdd = () => {
+  curRow.value = null;
+  showAddRegionDialog.value = true;
+};
+const handleEdit = (row) => {
+  curRow.value = row;
+  showAddRegionDialog.value = true;
+};
+const handleDelete = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '删除提示',
+    body: `您确定要删除当前规划的大区吗?`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    onConfirm: async () => {
+      confirmDia.hide();
+      const res = await serviceRegionRemoveApi(row.serviceRegionId).catch(
+        () => {}
+      );
+      if (!res) return;
+      MessagePlugin.success('删除成功');
+      fetchData();
+    },
+  });
+};
+</script>

+ 2 - 2
src/views/work-hours/work-hours-manage/work-attendance/index.vue

@@ -146,10 +146,10 @@ const { pagination, tableData, fetchData, search, onChange } = useFetchTable(
     },
   }
 );
-let statisticsInfo = reactive({ a: 1, b: 2, c: 3, d: 4, e: 5 });
+let statisticsInfo = ref({ a: 1, b: 2, c: 3, d: 4, e: 5 });
 const getStatisticsInfo = async () => {
   const res = await workAttendanceInfoApi(params);
-  statisticsInfo = res.data || {};
+  statisticsInfo.value = res || {};
 };
 
 const fields = ref([