Commit b4ce186289fdfa0dc32c7fce57e82669cf0ddda9

Authored by 曾国涛
2 parents 63da3108 1a3bab69

Merge remote-tracking branch 'origin/master' into zgt

# Conflicts:
#	.umirc.ts
#	src/pages/Client/index.tsx
.umirc.ts
@@ -86,13 +86,13 @@ export default defineConfig({ @@ -86,13 +86,13 @@ export default defineConfig({
86 icon: 'BookOutlined', 86 icon: 'BookOutlined',
87 access: 'canReadAdmin', 87 access: 'canReadAdmin',
88 }, 88 },
89 - /*{ 89 + {
90 name: '客户管理', 90 name: '客户管理',
91 path: '/client', 91 path: '/client',
92 component: './Client', 92 component: './Client',
93 icon: 'BookOutlined', 93 icon: 'BookOutlined',
94 access: 'canReadAdmin', 94 access: 'canReadAdmin',
95 - },*/ 95 + },
96 { 96 {
97 name: '打印', 97 name: '打印',
98 path: '/print', 98 path: '/print',
src/pages/Client/Components/ClientDrawer.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import {
  3 + postAdminClientAddAdminClient,
  4 + postAdminClientModifyClientInfo,
  5 + postDistrictSelectByLevel,
  6 + postDistrictSelectByNameAndLevel,
  7 + postDistrictSelOrderProvince,
  8 + postServiceConstClientLevels,
  9 + postServiceConstTradeStatus,
  10 +} from '@/services';
  11 +import { enumToSelect } from '@/utils';
  12 +import {
  13 + DrawerForm,
  14 + ProFormDateTimePicker,
  15 + ProFormSelect,
  16 + ProFormText,
  17 +} from '@ant-design/pro-components';
  18 +import { Button, Form, message } from 'antd';
  19 +import { useState } from 'react';
  20 +
  21 +export default ({ optType, record, onFinish }) => {
  22 + const [form] = Form.useForm();
  23 + //省市区
  24 + const [province, setProvince] = useState('');
  25 + const [city, setCity] = useState('');
  26 + const optTypeEnum = {
  27 + add: {
  28 + text: '新增',
  29 + button: <Button type="primary">新增</Button>,
  30 + readonly: false,
  31 + onFinish: async (values) => {
  32 + const res = await postAdminClientAddAdminClient({
  33 + data: values,
  34 + });
  35 + if (res.result === RESPONSE_CODE.SUCCESS) {
  36 + message.success('新增成功');
  37 + // 不返回不会关闭弹框
  38 + onFinish();
  39 + return true;
  40 + }
  41 + },
  42 + },
  43 + edit: {
  44 + text: '编辑',
  45 + button: <a type="primary">编辑</a>,
  46 + readonly: false,
  47 + onFinish: async (values) => {
  48 + const res = await postAdminClientModifyClientInfo({
  49 + data: values,
  50 + });
  51 + if (res.result === RESPONSE_CODE.SUCCESS) {
  52 + message.success('编辑成功');
  53 + // 不返回不会关闭弹框
  54 + onFinish();
  55 + return true;
  56 + }
  57 + },
  58 + },
  59 + detail: {
  60 + text: '详情',
  61 + button: <a type="primary">详情</a>,
  62 + readonly: true,
  63 + },
  64 + };
  65 +
  66 + return (
  67 + <DrawerForm
  68 + title={optTypeEnum[optType].text}
  69 + resize={{
  70 + onResize() {
  71 + console.log('resize!');
  72 + },
  73 + maxWidth: window.innerWidth * 0.8,
  74 + minWidth: 300,
  75 + }}
  76 + initialValues={record}
  77 + form={form}
  78 + trigger={optTypeEnum[optType].button}
  79 + autoFocusFirstInput
  80 + drawerProps={{
  81 + destroyOnClose: true,
  82 + }}
  83 + submitTimeout={2000}
  84 + width={500}
  85 + readonly={optTypeEnum[optType].readonly}
  86 + onFinish={optTypeEnum[optType].onFinish}
  87 + >
  88 + <ProFormText name="id" label="id" hidden={true} />
  89 +
  90 + <ProFormText
  91 + name="name"
  92 + label="客户名称"
  93 + placeholder="请输入名称"
  94 + rules={[
  95 + {
  96 + required: true,
  97 + message: '请输入客户名称',
  98 + },
  99 + ]}
  100 + />
  101 + <ProFormText
  102 + name="companyName"
  103 + label="单位名称"
  104 + placeholder="请输入单位名称"
  105 + rules={[
  106 + {
  107 + required: true,
  108 + message: '请输入单位名称',
  109 + },
  110 + ]}
  111 + />
  112 + <div
  113 + style={{
  114 + display: 'flex',
  115 + justifyContent: 'space-between',
  116 + width: 340,
  117 + }}
  118 + >
  119 + <ProFormSelect
  120 + name="province"
  121 + key="province"
  122 + width={100}
  123 + label="省"
  124 + allowClear={false}
  125 + onChange={(value) => {
  126 + console.log(value);
  127 +
  128 + if (value !== undefined || value !== null) {
  129 + console.log('setProvince');
  130 +
  131 + setProvince(value);
  132 + }
  133 + }}
  134 + placeholder="请选择"
  135 + rules={[
  136 + {
  137 + required: true,
  138 + message: '请选择!',
  139 + },
  140 + ]}
  141 + request={async () => {
  142 + let province = [];
  143 + let res = await postDistrictSelectByLevel({ data: 1 });
  144 + if (res) {
  145 + res.data.forEach((item) => {
  146 + province.push({ value: item.district, label: item.district });
  147 + });
  148 + }
  149 + return province;
  150 + }}
  151 + />
  152 + <ProFormSelect
  153 + key={province}
  154 + name="city"
  155 + width={100}
  156 + label="市"
  157 + allowClear={false}
  158 + disabled={province === ''}
  159 + placeholder="请选择"
  160 + onChange={(value) => {
  161 + if (value !== undefined || value !== null) {
  162 + setCity(value);
  163 + }
  164 + }}
  165 + rules={[
  166 + {
  167 + required: true,
  168 + message: '请选择!',
  169 + },
  170 + ]}
  171 + request={async () => {
  172 + let cityOptions = [];
  173 + console.log(form.getFieldValue('id'));
  174 + if (form.getFieldValue('id')) {
  175 + const resp = await postDistrictSelOrderProvince({
  176 + data: form.getFieldValue('id'),
  177 + });
  178 + if (
  179 + resp.data.province !== null &&
  180 + resp.data.province !== undefined
  181 + ) {
  182 + console.log('province is ok');
  183 + let res = await postDistrictSelectByNameAndLevel({
  184 + data: { district: resp.data.province, level: 1 },
  185 + });
  186 + if (res && res.data) {
  187 + cityOptions = res.data.map((item) => ({
  188 + value: item.district,
  189 + label: item.district,
  190 + }));
  191 + }
  192 + }
  193 + }
  194 + if (province !== '') {
  195 + console.log(province);
  196 + console.log('province is okk');
  197 + let res = await postDistrictSelectByNameAndLevel({
  198 + data: { district: province, level: 1 },
  199 + });
  200 + if (res && res.data) {
  201 + cityOptions = res.data.map((item) => ({
  202 + value: item.district,
  203 + label: item.district,
  204 + }));
  205 + }
  206 + }
  207 + return cityOptions;
  208 + }}
  209 + />
  210 + <ProFormSelect
  211 + key={city ? city.toString() : 'district'}
  212 + name="district"
  213 + width={100}
  214 + label="区"
  215 + allowClear={false}
  216 + onChange={(value) => {
  217 + if (value !== undefined || value !== null) {
  218 + /*setDistrict(value);*/
  219 + }
  220 + }}
  221 + disabled={city === ''}
  222 + placeholder="请选择"
  223 + rules={[
  224 + {
  225 + required: true,
  226 + message: '请选择!',
  227 + },
  228 + ]}
  229 + request={async () => {
  230 + let districtOptions = [];
  231 + if (form.getFieldValue('id')) {
  232 + const resp = await postDistrictSelOrderProvince({
  233 + data: form.getFieldValue('id'),
  234 + });
  235 + if (resp.data.city !== null && resp.data.city !== undefined) {
  236 + let res = await postDistrictSelectByNameAndLevel({
  237 + data: { district: resp.data.city, level: 2 },
  238 + });
  239 + if (res && res.data) {
  240 + districtOptions = res.data.map((item) => ({
  241 + value: item.district,
  242 + label: item.district,
  243 + }));
  244 + }
  245 + }
  246 + }
  247 + if (city !== '') {
  248 + let res = await postDistrictSelectByNameAndLevel({
  249 + data: { district: city, level: 2 },
  250 + });
  251 + if (res && res.data) {
  252 + districtOptions = res.data.map((item) => ({
  253 + value: item.district,
  254 + label: item.district,
  255 + }));
  256 + }
  257 + }
  258 + return districtOptions;
  259 + }}
  260 + />
  261 + </div>
  262 + <ProFormText
  263 + name="companyAddress"
  264 + label="详细地址"
  265 + placeholder="请输入单位地址"
  266 + rules={[
  267 + {
  268 + required: true,
  269 + message: '请输入单位地址',
  270 + },
  271 + ]}
  272 + />
  273 + <ProFormText
  274 + name="phoneNumber"
  275 + label="联系电话"
  276 + placeholder="请输入联系电话"
  277 + rules={[
  278 + {
  279 + required: true,
  280 + message: '请输入联系电话',
  281 + },
  282 + ]}
  283 + />
  284 + <ProFormText
  285 + name="source"
  286 + label="客户来源"
  287 + placeholder="请输入客户来源"
  288 + />
  289 + <ProFormText
  290 + name="requirements"
  291 + label="客户需求"
  292 + placeholder="请输入客户需求"
  293 + rules={[
  294 + {
  295 + required: true,
  296 + message: '请输入客户需求',
  297 + },
  298 + ]}
  299 + />
  300 + <ProFormText name="referrers" label="推荐人" placeholder="请输入推荐人" />
  301 + <ProFormSelect
  302 + name="hasScheme"
  303 + label="是否已报方案"
  304 + placeholder="请选择是否已报方案"
  305 + options={[
  306 + {
  307 + label: '是',
  308 + value: true,
  309 + },
  310 + {
  311 + label: '否',
  312 + value: false,
  313 + },
  314 + ]}
  315 + rules={[
  316 + {
  317 + required: true,
  318 + message: '请选择是否已报方案',
  319 + },
  320 + ]}
  321 + />
  322 + <ProFormDateTimePicker
  323 + name="quoteDatetime"
  324 + label="报价时间"
  325 + placeholder="请输入报价时间"
  326 + />
  327 + <ProFormSelect
  328 + name="level"
  329 + label="客户等级"
  330 + placeholder="请输入客户等级"
  331 + rules={[
  332 + {
  333 + required: true,
  334 + message: '请输入客户等级',
  335 + },
  336 + ]}
  337 + request={async () => {
  338 + const res = await postServiceConstClientLevels();
  339 + return enumToSelect(res.data);
  340 + }}
  341 + />
  342 + <ProFormSelect
  343 + name="tradeStatus"
  344 + label="跟进状态"
  345 + placeholder="请输入跟进状态"
  346 + rules={[
  347 + {
  348 + required: true,
  349 + message: '请输入跟进状态',
  350 + },
  351 + ]}
  352 + request={async () => {
  353 + const res = await postServiceConstTradeStatus();
  354 + return enumToSelect(res.data);
  355 + }}
  356 + />
  357 + <ProFormText name="notes" label="备注" placeholder="请输入备注" />
  358 + </DrawerForm>
  359 + );
  360 +};
src/pages/Client/Components/ClientImportModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { orderExport } from '@/services/order';
  3 +import { blobToJson } from '@/utils';
  4 +import { ModalForm, ProFormUploadDragger } from '@ant-design/pro-components';
  5 +import { Button, Form, message } from 'antd';
  6 +import { RcFile } from 'antd/es/upload';
  7 +import axios from 'axios';
  8 +
  9 +export default () => {
  10 + const [form] = Form.useForm();
  11 + const [messageApi, contextHolder] = message.useMessage();
  12 + const downloadImportTemplate = () => {
  13 + messageApi.open({
  14 + type: 'loading',
  15 + content: '正在导入...',
  16 + duration: 0,
  17 + });
  18 + orderExport(
  19 + '/api/admin/client/downloadImportTemplate',
  20 + '客户导入模板.xlsx',
  21 + 'post',
  22 + {},
  23 + () => {
  24 + messageApi.destroy();
  25 + },
  26 + );
  27 + };
  28 +
  29 + return (
  30 + <ModalForm
  31 + title="导入客户信息"
  32 + trigger={<Button type="primary">导入客户信息</Button>}
  33 + form={form}
  34 + autoFocusFirstInput
  35 + modalProps={{
  36 + destroyOnClose: true,
  37 + onCancel: () => console.log('run'),
  38 + }}
  39 + submitTimeout={2000}
  40 + onFinish={async (values) => {
  41 + const formData = new FormData();
  42 + values.file.forEach((file) => {
  43 + formData.append('file', file.originFileObj as RcFile);
  44 + });
  45 + axios({
  46 + url: '/api/admin/client/importClient',
  47 + method: 'post',
  48 + responseType: 'blob',
  49 + headers: {
  50 + Authorization: localStorage.getItem('token'),
  51 + 'Content-Type':
  52 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  53 + },
  54 + data: formData,
  55 + })
  56 + .then((response) => {
  57 + let data = response.data;
  58 + if (data.type === 'application/json') {
  59 + blobToJson(data).then((dataJson) => {
  60 + if (dataJson?.result === RESPONSE_CODE.SUCCESS) {
  61 + message.success(dataJson?.message);
  62 + } else {
  63 + message.error(dataJson?.message);
  64 + }
  65 + });
  66 + } else {
  67 + message.error('上传失败,已下载错误信息表格');
  68 + // 创建一个新的 Blob 对象,它包含了服务器响应的数据(即你的 Excel 文件)
  69 + const blob = new Blob([response.data]); // Excel 的 MIME 类型
  70 + const downloadUrl = window.URL.createObjectURL(blob);
  71 + const a = document.createElement('a');
  72 + a.href = downloadUrl;
  73 + a.download = '银行流水导入模板.xlsx'; // 你可以为文件命名
  74 + document.body.appendChild(a);
  75 + a.click(); // 模拟点击操作来下载文件
  76 + URL.revokeObjectURL(downloadUrl); // 释放掉 blob 对象所占用的内存
  77 + document.body.removeChild(a);
  78 + }
  79 + })
  80 + .catch((error) => {
  81 + // 处理错误
  82 + message.error('系统出现异常了,请联系管理员', error);
  83 + })
  84 + .finally(() => {});
  85 + return true;
  86 + }}
  87 + >
  88 + <ProFormUploadDragger max={1} label="上传" name="file" />
  89 + <Button type="link" onClick={downloadImportTemplate}>
  90 + 下载导入模板
  91 + </Button>
  92 + {contextHolder}
  93 + </ModalForm>
  94 + );
  95 +};
src/pages/Client/Components/CommunicationHistoryModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import {
  3 + postAdminClientAddOrModifyClientComunicationInfo,
  4 + postAdminClientQueryClientComunicationInfo,
  5 + postAdminClientRemoveClientComunicationInfo,
  6 +} from '@/services';
  7 +import {
  8 + ActionType,
  9 + EditableProTable,
  10 + ModalForm,
  11 + ProFormInstance,
  12 +} from '@ant-design/pro-components';
  13 +import { Popconfirm, message } from 'antd';
  14 +import { useEffect, useRef, useState } from 'react';
  15 +export default ({ clientId }) => {
  16 + const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
  17 + const [dataSource, setDataSource] = useState();
  18 + const ref = useRef<ProFormInstance>();
  19 + const actionRef = useRef<ActionType>();
  20 + const columns = [
  21 + {
  22 + title: '跟进时间',
  23 + dataIndex: 'datetime',
  24 + width: 50,
  25 + valueType: 'dateTime',
  26 + rules: [{ required: true, message: '请选择时间' }],
  27 + },
  28 + {
  29 + title: '方式',
  30 + width: 50,
  31 + dataIndex: 'way',
  32 + rules: [{ required: true, message: '请选择方式' }],
  33 + },
  34 + {
  35 + title: '内容',
  36 + width: 100,
  37 + valueType: 'textarea',
  38 + rules: [{ required: true, message: '请输入内容' }],
  39 + dataIndex: 'content',
  40 + },
  41 + {
  42 + title: '操作',
  43 + valueType: 'option',
  44 + width: 50,
  45 + render: (text, record, _, action) => [
  46 + <a
  47 + key="editable"
  48 + onClick={() => {
  49 + action?.startEditable?.(record.tid);
  50 + }}
  51 + >
  52 + 编辑
  53 + </a>,
  54 + <Popconfirm
  55 + key={'delete'}
  56 + title="删除记录"
  57 + description="确认删除记录?"
  58 + onConfirm={async () => {
  59 + setDataSource(dataSource.filter((item) => item.tid !== record.tid));
  60 + const res = await postAdminClientRemoveClientComunicationInfo({
  61 + query: {
  62 + id: record.id,
  63 + },
  64 + });
  65 + if (res.result === RESPONSE_CODE.SUCCESS) {
  66 + message.success(res.message);
  67 + action?.reload();
  68 + } else {
  69 + message.error('删除失败');
  70 + }
  71 + }}
  72 + okText="是"
  73 + cancelText="否"
  74 + >
  75 + <a type={'danger'}>删除</a>
  76 + </Popconfirm>,
  77 + ],
  78 + },
  79 + ];
  80 +
  81 + useEffect(() => {
  82 + console.log('clientId', clientId);
  83 + }, []);
  84 + return (
  85 + <ModalForm
  86 + title="跟进记录"
  87 + trigger={<a type="primary">跟进记录</a>}
  88 + modalProps={{
  89 + destroyOnClose: true,
  90 + }}
  91 + >
  92 + <EditableProTable
  93 + rowKey="tid"
  94 + formRef={ref}
  95 + actionRef={actionRef}
  96 + recordCreatorProps={{
  97 + record: () => ({ tid: (Math.random() * 1000000).toFixed(0) }),
  98 + }}
  99 + loading={false}
  100 + columns={columns}
  101 + request={async () => {
  102 + const res = await postAdminClientQueryClientComunicationInfo({
  103 + data: {
  104 + clientId: clientId,
  105 + },
  106 + });
  107 + if (res.result === RESPONSE_CODE.SUCCESS) {
  108 + console.log(JSON.stringify(res.data));
  109 + return {
  110 + ...res.data,
  111 + data: res.data.data.map((item) => {
  112 + return {
  113 + ...item,
  114 + tid: (Math.random() * 1000000).toFixed(0),
  115 + };
  116 + }),
  117 + };
  118 + } else {
  119 + message.error('获取失败');
  120 + }
  121 + }}
  122 + value={dataSource}
  123 + onChange={setDataSource}
  124 + editable={{
  125 + type: 'multiple',
  126 + editableKeys,
  127 + onSave: async (rowKey, data, row) => {
  128 + console.log(rowKey, data, row);
  129 + const res = await postAdminClientAddOrModifyClientComunicationInfo({
  130 + data: {
  131 + ...data,
  132 + clientId: clientId,
  133 + },
  134 + });
  135 + if (res.result === RESPONSE_CODE.SUCCESS) {
  136 + message.success(res.message);
  137 + } else {
  138 + message.error('修改失败');
  139 + }
  140 + actionRef.current?.reload();
  141 + },
  142 + onChange: setEditableRowKeys,
  143 + }}
  144 + />
  145 + {/*<ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
  146 + <ProFormField
  147 + ignoreFormItem
  148 + fieldProps={{
  149 + style: {
  150 + width: '100%',
  151 + },
  152 + }}
  153 + mode="read"
  154 + valueType="jsonCode"
  155 + text={JSON.stringify(dataSource)}
  156 + />
  157 + </ProCard>*/}
  158 + </ModalForm>
  159 + );
  160 +};
src/pages/Client/index.tsx
1 -import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';  
2 -import type { ActionType, ProColumns } from '@ant-design/pro-components';  
3 -import { ProTable, TableDropdown } from '@ant-design/pro-components';  
4 -import { request } from '@umijs/max';  
5 -import { Button, Dropdown, Space, Tag } from 'antd'; 1 +import ClientDrawer from '@/pages/Client/Components/ClientDrawer';
  2 +import ClientImportModal from '@/pages/Client/Components/ClientImportModal';
  3 +import CommunicationHistoryModal from '@/pages/Client/Components/CommunicationHistoryModal';
  4 +import {
  5 + postAdminClientQueryClientPage,
  6 + postServiceConstClientLevels,
  7 + postServiceConstTradeStatus,
  8 +} from '@/services';
  9 +import { orderExport } from '@/services/order';
  10 +import { enumToSelect } from '@/utils';
  11 +import type { ActionType } from '@ant-design/pro-components';
  12 +import { ProTable } from '@ant-design/pro-components';
  13 +import { Button, message } from 'antd';
6 import { useRef } from 'react'; 14 import { useRef } from 'react';
7 15
8 -export const waitTimePromise = async (time: number = 100) => {  
9 - return new Promise((resolve) => {  
10 - setTimeout(() => {  
11 - resolve(true);  
12 - }, time);  
13 - });  
14 -};  
15 -  
16 -export const waitTime = async (time: number = 100) => {  
17 - await waitTimePromise(time);  
18 -};  
19 -  
20 -const columns: ProColumns<GithubIssueItem>[] = [ 16 +const columns = [
21 { 17 {
22 dataIndex: 'index', 18 dataIndex: 'index',
23 valueType: 'indexBorder', 19 valueType: 'indexBorder',
24 width: 48, 20 width: 48,
25 }, 21 },
26 { 22 {
27 - title: '标题',  
28 - dataIndex: 'title',  
29 - copyable: true,  
30 - ellipsis: true,  
31 - tooltip: '标题过长会自动收缩',  
32 - formItemProps: {  
33 - rules: [  
34 - {  
35 - required: true,  
36 - message: '此项为必填项',  
37 - },  
38 - ],  
39 - }, 23 + title: '客户名称',
  24 + dataIndex: 'name',
  25 + width: 100,
  26 + hideInSearch: true,
  27 + },
  28 + {
  29 + title: '单位名称',
  30 + width: 150,
  31 + dataIndex: 'companyName',
  32 + hideInSearch: true,
  33 + },
  34 + {
  35 + title: '单位地址',
  36 + width: 250,
  37 + dataIndex: 'companyAddressText',
  38 + hideInSearch: true,
  39 + },
  40 + {
  41 + title: '联系电话',
  42 + width: 150,
  43 + dataIndex: 'phoneNumber',
  44 + hideInSearch: true,
40 }, 45 },
41 { 46 {
42 - disable: true,  
43 - title: '状态',  
44 - dataIndex: 'state',  
45 - filters: true,  
46 - onFilter: true,  
47 - ellipsis: true, 47 + title: '客户来源',
  48 + width: 150,
  49 + dataIndex: 'source',
  50 + hideInSearch: true,
  51 + },
  52 + {
  53 + title: '推荐人',
  54 + dataIndex: 'referrers',
  55 + width: 150,
  56 + hideInSearch: true,
  57 + },
  58 + {
  59 + title: '客户需求',
  60 + dataIndex: 'requirements',
  61 + width: 150,
  62 + hideInSearch: true,
  63 + },
  64 + {
  65 + title: '是否已报方案',
  66 + width: 150,
  67 + dataIndex: 'hasSchemeText',
  68 + hideInSearch: true,
  69 + },
  70 + {
  71 + title: '报价时间',
  72 + key: 'since',
  73 + width: 150,
  74 + dataIndex: 'quoteDatetime',
  75 + valueType: 'dateTime',
  76 + hideInSearch: true,
  77 + },
  78 + {
  79 + title: '跟进状态',
  80 + width: 150,
  81 + dataIndex: 'tradeStatusText',
  82 + hideInSearch: true,
  83 + },
  84 + {
  85 + title: '客户等级',
  86 + width: 150,
  87 + dataIndex: 'levelText',
  88 + hideInSearch: true,
  89 + },
  90 + {
  91 + title: '创建时间',
  92 + key: 'since',
  93 + width: 150,
  94 + dataIndex: 'createTime',
  95 + valueType: 'dateTime',
  96 + hideInSearch: true,
  97 + },
  98 + {
  99 + title: '最新跟进时间',
  100 + key: 'since',
  101 + width: 150,
  102 + dataIndex: 'latestCommunicationTime',
  103 + valueType: 'dateTime',
  104 + hideInSearch: true,
  105 + },
  106 + {
  107 + title: '客户名称',
  108 + dataIndex: 'nameLike',
  109 + valueType: 'Text',
  110 + hideInTable: true,
  111 + },
  112 + {
  113 + title: '单位名称',
  114 + dataIndex: 'companyNameLike',
  115 + valueType: 'Text',
  116 + hideInTable: true,
  117 + },
  118 + {
  119 + title: '联系电话',
  120 + dataIndex: 'phoneNumberLike',
  121 + valueType: 'Text',
  122 + hideInTable: true,
  123 + },
  124 + {
  125 + title: '是否已报方案',
  126 + dataIndex: 'hasScheme',
48 valueType: 'select', 127 valueType: 'select',
49 valueEnum: { 128 valueEnum: {
50 - all: { text: '超长'.repeat(50) },  
51 - open: {  
52 - text: '未解决',  
53 - status: 'Error', 129 + true: {
  130 + text: '是',
  131 + value: true,
54 }, 132 },
55 - closed: {  
56 - text: '已解决',  
57 - status: 'Success',  
58 - disabled: true,  
59 - },  
60 - processing: {  
61 - text: '解决中',  
62 - status: 'Processing', 133 + false: {
  134 + text: '否',
  135 + value: false,
63 }, 136 },
64 }, 137 },
  138 + hideInTable: true,
65 }, 139 },
66 { 140 {
67 - disable: true,  
68 - title: '标签',  
69 - dataIndex: 'labels',  
70 - search: false,  
71 - renderFormItem: (_, { defaultRender }) => {  
72 - return defaultRender(_); 141 + title: '客户等级',
  142 + dataIndex: 'level',
  143 + valueType: 'select',
  144 + hideInTable: true,
  145 + request: async () => {
  146 + const res = await postServiceConstClientLevels();
  147 + return enumToSelect(res.data);
73 }, 148 },
74 - render: (_, record) => (  
75 - <Space>  
76 - {record.labels.map(({ name, color }) => (  
77 - <Tag color={color} key={name}>  
78 - {name}  
79 - </Tag>  
80 - ))}  
81 - </Space>  
82 - ),  
83 }, 149 },
84 { 150 {
85 - title: '创建时间',  
86 - key: 'showTime',  
87 - dataIndex: 'created_at',  
88 - valueType: 'date',  
89 - sorter: true,  
90 - hideInSearch: true, 151 + title: '跟进状态',
  152 + dataIndex: 'tradeStatus',
  153 + valueType: 'select',
  154 + hideInTable: true,
  155 + request: async () => {
  156 + const res = await postServiceConstTradeStatus();
  157 + return enumToSelect(res.data);
  158 + },
91 }, 159 },
92 { 160 {
93 title: '创建时间', 161 title: '创建时间',
94 - dataIndex: 'created_at',  
95 valueType: 'dateRange', 162 valueType: 'dateRange',
96 hideInTable: true, 163 hideInTable: true,
97 search: { 164 search: {
98 transform: (value) => { 165 transform: (value) => {
99 - return {  
100 - startTime: value[0],  
101 - endTime: value[1],  
102 - }; 166 + if (value) {
  167 + return {
  168 + createTimeGe: value[0],
  169 + createTimeLe: value[1],
  170 + };
  171 + }
103 }, 172 },
104 }, 173 },
105 }, 174 },
@@ -107,119 +176,133 @@ const columns: ProColumns&lt;GithubIssueItem&gt;[] = [ @@ -107,119 +176,133 @@ const columns: ProColumns&lt;GithubIssueItem&gt;[] = [
107 title: '操作', 176 title: '操作',
108 valueType: 'option', 177 valueType: 'option',
109 key: 'option', 178 key: 'option',
110 - render: (text, record, _, action) => [  
111 - <a  
112 - key="editable"  
113 - onClick={() => {  
114 - action?.startEditable?.(record.id);  
115 - }}  
116 - >  
117 - 编辑  
118 - </a>,  
119 - <a href={record.url} target="_blank" rel="noopener noreferrer" key="view">  
120 - 查看  
121 - </a>,  
122 - <TableDropdown  
123 - key="actionGroup"  
124 - onSelect={() => action?.reload()}  
125 - menus={[  
126 - { key: 'copy', name: '复制' },  
127 - { key: 'delete', name: '删除' },  
128 - ]}  
129 - />,  
130 - ], 179 + width: 150,
  180 + render: (text, record, index, action) => {
  181 + console.log(JSON.stringify(record));
  182 + return [
  183 + <CommunicationHistoryModal
  184 + key={'communicationHistory'}
  185 + clientId={record.id}
  186 + />,
  187 + <ClientDrawer
  188 + key={'detail'}
  189 + record={record}
  190 + optType={'detail'}
  191 + onFinish={() => {
  192 + action.reload();
  193 + }}
  194 + ></ClientDrawer>,
  195 + <ClientDrawer
  196 + key={'edit'}
  197 + record={record}
  198 + optType={'edit'}
  199 + onFinish={() => {
  200 + action.reload();
  201 + }}
  202 + ></ClientDrawer>,
  203 + ];
  204 + },
131 }, 205 },
132 ]; 206 ];
133 207
134 export default () => { 208 export default () => {
  209 + const [messageApi, contextHolder] = message.useMessage();
135 const actionRef = useRef<ActionType>(); 210 const actionRef = useRef<ActionType>();
136 return ( 211 return (
137 - <ProTable  
138 - columns={columns}  
139 - actionRef={actionRef}  
140 - cardBordered  
141 - request={async (params, sort, filter) => {  
142 - console.log(sort, filter);  
143 - await waitTime(2000);  
144 - return request('https://proapi.azurewebsites.net/github/issues', {  
145 - params,  
146 - });  
147 - }}  
148 - editable={{  
149 - type: 'multiple',  
150 - }}  
151 - columnsState={{  
152 - persistenceKey: 'pro-table-singe-demos',  
153 - persistenceType: 'localStorage',  
154 - defaultValue: {  
155 - option: { fixed: 'right', disable: true },  
156 - },  
157 - onChange(value) {  
158 - console.log('value: ', value);  
159 - },  
160 - }}  
161 - rowKey="id"  
162 - search={{  
163 - labelWidth: 'auto',  
164 - }}  
165 - options={{  
166 - setting: {  
167 - listsHeight: 400,  
168 - },  
169 - }}  
170 - form={{  
171 - // 由于配置了 transform,提交的参与与定义的不同这里需要转化一下  
172 - syncToUrl: (values, type) => {  
173 - if (type === 'get') {  
174 - return {  
175 - ...values,  
176 - created_at: [values.startTime, values.endTime],  
177 - };  
178 - }  
179 - return values;  
180 - },  
181 - }}  
182 - pagination={{  
183 - pageSize: 5,  
184 - onChange: (page) => console.log(page),  
185 - }}  
186 - dateFormatter="string"  
187 - headerTitle="高级表格"  
188 - toolBarRender={() => [  
189 - <Button  
190 - key="button"  
191 - icon={<PlusOutlined />}  
192 - onClick={() => {  
193 - actionRef.current?.reload();  
194 - }}  
195 - type="primary"  
196 - >  
197 - 新建  
198 - </Button>,  
199 - <Dropdown  
200 - key="menu"  
201 - menu={{  
202 - items: [  
203 - {  
204 - label: '1st item',  
205 - key: '1',  
206 - },  
207 - {  
208 - label: '2nd item',  
209 - key: '1',  
210 - },  
211 - {  
212 - label: '3rd item',  
213 - key: '1',  
214 - },  
215 - ],  
216 - }}  
217 - >  
218 - <Button>  
219 - <EllipsisOutlined />  
220 - </Button>  
221 - </Dropdown>,  
222 - ]}  
223 - /> 212 + <>
  213 + <ProTable
  214 + columns={columns}
  215 + actionRef={actionRef}
  216 + cardBordered
  217 + request={async (params) => {
  218 + const res = await postAdminClientQueryClientPage({
  219 + data: {
  220 + ...params,
  221 + },
  222 + });
  223 + const data = res.data;
  224 + return data;
  225 + }}
  226 + search={{
  227 + defaultCollapsed: false,
  228 + optionRender: (searchConfig, formProps, dom) => [
  229 + ...dom.reverse(),
  230 + <Button
  231 + key="out"
  232 + onClick={() => {
  233 + const values = searchConfig?.form?.getFieldsValue();
  234 + messageApi.open({
  235 + type: 'loading',
  236 + content: '导出中...',
  237 + duration: 0,
  238 + });
  239 + orderExport(
  240 + '/api/admin/client/exportClients',
  241 + '客户信息.xlsx',
  242 + 'POST',
  243 + values,
  244 + () => {
  245 + messageApi.destroy();
  246 + },
  247 + );
  248 + }}
  249 + >
  250 + 导出
  251 + </Button>,
  252 + ],
  253 + }}
  254 + scroll={{
  255 + x: 1400,
  256 + }}
  257 + editable={{
  258 + type: 'multiple',
  259 + }}
  260 + columnsState={{
  261 + persistenceKey: 'pro-table-singe-demos',
  262 + persistenceType: 'localStorage',
  263 + defaultValue: {
  264 + option: { fixed: 'right', disable: true },
  265 + },
  266 + onChange(value) {
  267 + console.log('value: ', value);
  268 + },
  269 + }}
  270 + rowKey="id"
  271 + options={{
  272 + setting: {
  273 + listsHeight: 400,
  274 + },
  275 + }}
  276 + form={{
  277 + // 由于配置了 transform,提交的参与与定义的不同这里需要转化一下
  278 + syncToUrl: (values, type) => {
  279 + if (type === 'get') {
  280 + return {
  281 + ...values,
  282 + created_at: [values.startTime, values.endTime],
  283 + };
  284 + }
  285 + return values;
  286 + },
  287 + }}
  288 + pagination={{
  289 + pageSize: 5,
  290 + onChange: (page) => console.log(page),
  291 + }}
  292 + dateFormatter="string"
  293 + headerTitle="高级表格"
  294 + toolBarRender={() => [
  295 + <ClientDrawer
  296 + optType={'add'}
  297 + key="button"
  298 + onFinish={() => {
  299 + actionRef.current.reload();
  300 + }}
  301 + ></ClientDrawer>,
  302 + <ClientImportModal key="import" />,
  303 + ]}
  304 + />
  305 + {contextHolder}
  306 + </>
224 ); 307 );
225 }; 308 };
src/pages/Order/components/OrderDrawer.tsx
@@ -3,7 +3,6 @@ import { @@ -3,7 +3,6 @@ import {
3 postCanrdApiUserAddressList, 3 postCanrdApiUserAddressList,
4 postCanrdApiUserDetail, 4 postCanrdApiUserDetail,
5 postCanrdApiUserNowMoneyCheck, 5 postCanrdApiUserNowMoneyCheck,
6 - postDistrictAddOrderAndProvince,  
7 postDistrictSelectByLevel, 6 postDistrictSelectByLevel,
8 postDistrictSelectByNameAndLevel, 7 postDistrictSelectByNameAndLevel,
9 postDistrictSelOrderProvince, 8 postDistrictSelOrderProvince,
@@ -877,21 +876,12 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -877,21 +876,12 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
877 if (typeof values.erpCustomerId !== 'string') { 876 if (typeof values.erpCustomerId !== 'string') {
878 values.erpCustomerId = values.erpCustomerId?.id; 877 values.erpCustomerId = values.erpCustomerId?.id;
879 } 878 }
880 - 879 + values.province = province;
  880 + values.city = city;
  881 + values.district = district;
881 //新增 882 //新增
882 if (optType('add') || optType('copy')) { 883 if (optType('add') || optType('copy')) {
883 res = await postServiceOrderAddOrder({ data: values }); 884 res = await postServiceOrderAddOrder({ data: values });
884 - if (res && res.data.length === 2) {  
885 - const orderMainProDo = {  
886 - oId: res.data[0],  
887 - province: province,  
888 - city: city,  
889 - district: district,  
890 - };  
891 - await postDistrictAddOrderAndProvince({  
892 - data: orderMainProDo,  
893 - });  
894 - }  
895 } 885 }
896 //修改或者申请售后或者申请修改 886 //修改或者申请售后或者申请修改
897 if ( 887 if (
@@ -915,6 +905,9 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -915,6 +905,9 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
915 values.deleteSubOrderLists = diff; 905 values.deleteSubOrderLists = diff;
916 906
917 if (optType('edit')) { 907 if (optType('edit')) {
  908 + values.province = province;
  909 + values.city = city;
  910 + values.district = district;
918 res = await postServiceOrderUpdateOrder({ data: values }); 911 res = await postServiceOrderUpdateOrder({ data: values });
919 } 912 }
920 913
@@ -1134,14 +1127,20 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1134,14 +1127,20 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1134 const resp = await postDistrictSelOrderProvince({ 1127 const resp = await postDistrictSelOrderProvince({
1135 data: form.getFieldValue('id'), 1128 data: form.getFieldValue('id'),
1136 }); 1129 });
1137 - console.log();  
1138 -  
1139 - setProvince(resp.data.province);  
1140 - form.setFieldValue('province', resp.data.province);  
1141 - setCity(resp.data.city);  
1142 - form.setFieldValue('city', resp.data.city);  
1143 - setDistrict(resp.data.district);  
1144 - form.setFieldValue('district', resp.data.district); 1130 + if (resp && resp.data) {
  1131 + if (resp.data.province) {
  1132 + setProvince(resp.data.province);
  1133 + form.setFieldValue('province', resp.data.province);
  1134 + }
  1135 + if (resp.data.city) {
  1136 + setCity(resp.data.city);
  1137 + form.setFieldValue('city', resp.data.city);
  1138 + }
  1139 + if (resp.data.district) {
  1140 + setDistrict(resp.data.district);
  1141 + form.setFieldValue('district', resp.data.district);
  1142 + }
  1143 + }
1145 console.log(form.getFieldsValue()); 1144 console.log(form.getFieldsValue());
1146 } 1145 }
1147 //判断如果是在修改或者复制,那么第一次请求的时候,默认生成当前收货人信息的option 1146 //判断如果是在修改或者复制,那么第一次请求的时候,默认生成当前收货人信息的option
@@ -1228,7 +1227,11 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1228,7 +1227,11 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1228 labelInValue: true, 1227 labelInValue: true,
1229 }} 1228 }}
1230 onChange={(value) => { 1229 onChange={(value) => {
  1230 + console.log(value);
  1231 +
1231 if (value !== undefined || value !== null) { 1232 if (value !== undefined || value !== null) {
  1233 + console.log('setProvince');
  1234 +
1232 setProvince(value?.value); 1235 setProvince(value?.value);
1233 } 1236 }
1234 }} 1237 }}
@@ -1273,8 +1276,31 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1273,8 +1276,31 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1273 }, 1276 },
1274 ]} 1277 ]}
1275 request={async () => { 1278 request={async () => {
  1279 + let cityOptions = [];
  1280 + console.log(form.getFieldValue('id'));
  1281 + if (form.getFieldValue('id')) {
  1282 + const resp = await postDistrictSelOrderProvince({
  1283 + data: form.getFieldValue('id'),
  1284 + });
  1285 + if (
  1286 + resp.data.province !== null &&
  1287 + resp.data.province !== undefined
  1288 + ) {
  1289 + console.log('province is ok');
  1290 + let res = await postDistrictSelectByNameAndLevel({
  1291 + data: { district: resp.data.province, level: 1 },
  1292 + });
  1293 + if (res && res.data) {
  1294 + cityOptions = res.data.map((item) => ({
  1295 + value: item.district,
  1296 + label: item.district,
  1297 + }));
  1298 + }
  1299 + }
  1300 + }
1276 if (province !== '') { 1301 if (province !== '') {
1277 - let cityOptions = []; 1302 + console.log(province);
  1303 + console.log('province is okk');
1278 let res = await postDistrictSelectByNameAndLevel({ 1304 let res = await postDistrictSelectByNameAndLevel({
1279 data: { district: province, level: 1 }, 1305 data: { district: province, level: 1 },
1280 }); 1306 });
@@ -1284,9 +1310,8 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1284,9 +1310,8 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1284 label: item.district, 1310 label: item.district,
1285 })); 1311 }));
1286 } 1312 }
1287 - return cityOptions;  
1288 } 1313 }
1289 - return []; 1314 + return cityOptions;
1290 }} 1315 }}
1291 /> 1316 />
1292 <ProFormSelect 1317 <ProFormSelect
@@ -1312,10 +1337,24 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1312,10 +1337,24 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1312 }, 1337 },
1313 ]} 1338 ]}
1314 request={async () => { 1339 request={async () => {
1315 - console.log(form.getFieldsValue());  
1316 - 1340 + let districtOptions = [];
  1341 + if (form.getFieldValue('id')) {
  1342 + const resp = await postDistrictSelOrderProvince({
  1343 + data: form.getFieldValue('id'),
  1344 + });
  1345 + if (resp.data.city !== null && resp.data.city !== undefined) {
  1346 + let res = await postDistrictSelectByNameAndLevel({
  1347 + data: { district: resp.data.city, level: 2 },
  1348 + });
  1349 + if (res && res.data) {
  1350 + districtOptions = res.data.map((item) => ({
  1351 + value: item.district,
  1352 + label: item.district,
  1353 + }));
  1354 + }
  1355 + }
  1356 + }
1317 if (city !== '') { 1357 if (city !== '') {
1318 - let districtOptions = [];  
1319 let res = await postDistrictSelectByNameAndLevel({ 1358 let res = await postDistrictSelectByNameAndLevel({
1320 data: { district: city, level: 2 }, 1359 data: { district: city, level: 2 },
1321 }); 1360 });
@@ -1325,9 +1364,8 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; { @@ -1325,9 +1364,8 @@ export default ({ onClose, data, subOrders, orderOptType }) =&gt; {
1325 label: item.district, 1364 label: item.district,
1326 })); 1365 }));
1327 } 1366 }
1328 - return districtOptions;  
1329 } 1367 }
1330 - return []; 1368 + return districtOptions;
1331 }} 1369 }}
1332 /> 1370 />
1333 </div> 1371 </div>
src/pages/Order/constant.ts
1 -import { postServiceOrderQueryCustomerInformation } from '@/services'; 1 +import {
  2 + postOrderErpOrderZoNingSelectSaleUserHasZoning,
  3 + postServiceOrderQueryCustomerInformation,
  4 +} from '@/services';
2 import { enumToProTableEnumValue } from '@/utils'; 5 import { enumToProTableEnumValue } from '@/utils';
3 import { getReceivingCompanyOptions, isSupplier } from '@/utils/order'; 6 import { getReceivingCompanyOptions, isSupplier } from '@/utils/order';
4 export const COMFIR_RECEIPT_IMAGES_NUMBER = 3; 7 export const COMFIR_RECEIPT_IMAGES_NUMBER = 3;
@@ -601,6 +604,25 @@ export const MAIN_ORDER_COLUMNS = [ @@ -601,6 +604,25 @@ export const MAIN_ORDER_COLUMNS = [
601 valueEnum: enumToProTableEnumValue(PRODUCT_BELONG_DEPARTMENT_OPTIONS), 604 valueEnum: enumToProTableEnumValue(PRODUCT_BELONG_DEPARTMENT_OPTIONS),
602 }, 605 },
603 { 606 {
  607 + title: '所属大区',
  608 + dataIndex: 'orderZoning',
  609 + valueType: 'select',
  610 + hideInTable: true,
  611 + request: async () => {
  612 + const res = await postOrderErpOrderZoNingSelectSaleUserHasZoning({
  613 + data: JSON.parse(localStorage.getItem('userInfo')).id,
  614 + });
  615 + let sel = [];
  616 + res.data.forEach((item) => {
  617 + sel.push({
  618 + label: item.zoning,
  619 + value: item.id,
  620 + });
  621 + });
  622 + return sel;
  623 + },
  624 + },
  625 + {
604 title: '创建日期', 626 title: '创建日期',
605 dataIndex: 'createTime', 627 dataIndex: 'createTime',
606 valueType: 'dateTimeRange', 628 valueType: 'dateTimeRange',
src/pages/OrderReport/index.tsx
1 import { 1 import {
2 getOrderErpOrderZoNingSelectAll, 2 getOrderErpOrderZoNingSelectAll,
3 - getOrderErpOrderZoNingSelectUserAll,  
4 postOrderErpOrderZoNingSelectSaleUserByProvince, 3 postOrderErpOrderZoNingSelectSaleUserByProvince,
5 postServiceOrderQueryReportFormsInformation, 4 postServiceOrderQueryReportFormsInformation,
6 postServiceOrderQuerySalesCode, 5 postServiceOrderQuerySalesCode,
@@ -66,7 +65,7 @@ const OrderReportPage = () =&gt; { @@ -66,7 +65,7 @@ const OrderReportPage = () =&gt; {
66 setLoading(true); 65 setLoading(true);
67 let body = { 66 let body = {
68 statisticsMethod: '', 67 statisticsMethod: '',
69 - salesCode: [null], 68 + // salesCode: [null],
70 productBelongBusiness: form.getFieldValue('productBelongBusiness'), 69 productBelongBusiness: form.getFieldValue('productBelongBusiness'),
71 maxAccount: form.getFieldValue('maxAccount'), 70 maxAccount: form.getFieldValue('maxAccount'),
72 includeExperimentalEquipment: form.getFieldValue( 71 includeExperimentalEquipment: form.getFieldValue(
@@ -77,14 +76,14 @@ const OrderReportPage = () =&gt; { @@ -77,14 +76,14 @@ const OrderReportPage = () =&gt; {
77 }; 76 };
78 // console.log(form.getFieldValue('zoning').value); 77 // console.log(form.getFieldValue('zoning').value);
79 78
80 - let res = await getOrderErpOrderZoNingSelectUserAll();  
81 - if (res && res.data) {  
82 - let safeUserList = [];  
83 - res.data.forEach((element) => {  
84 - safeUserList.push(element.userName);  
85 - });  
86 - body = { ...body, salesCode: safeUserList };  
87 - } 79 + // let res = await getOrderErpOrderZoNingSelectUserAll();
  80 + // if (res && res.data) {
  81 + // let safeUserList = [];
  82 + // res.data.forEach((element) => {
  83 + // safeUserList.push(element.userName);
  84 + // });
  85 + // body = { ...body, salesCode: safeUserList };
  86 + // }
88 if (form.getFieldValue('salesCode')) { 87 if (form.getFieldValue('salesCode')) {
89 body = { ...body, salesCode: [form.getFieldValue('salesCode')] }; 88 body = { ...body, salesCode: [form.getFieldValue('salesCode')] };
90 } 89 }
src/pages/ZoNing/components/table.tsx
@@ -110,6 +110,7 @@ export default () =&gt; { @@ -110,6 +110,7 @@ export default () =&gt; {
110 key: 'state', 110 key: 'state',
111 dataIndex: 'orderUserShowList', 111 dataIndex: 'orderUserShowList',
112 valueType: 'select', 112 valueType: 'select',
  113 + ellipsis: true,
113 width: 200, 114 width: 200,
114 }, 115 },
115 { 116 {