Commit 0afddbf9116c1f9a70d29071736a8ed103b9b431

Authored by 曾国涛
2 parents 7be35598 f8f768b0

Merge remote-tracking branch 'origin/afterFeed' into master2

# Conflicts:
#	src/services/definition.ts
#	src/services/request.ts

Too many changes to show.

To preserve performance only 27 of 67 files are displayed.

.umirc.ts
... ... @@ -14,9 +14,9 @@ export default defineConfig({
14 14 },
15 15 proxy: {
16 16 '/api/': {
17   - target: 'http://localhost:8085/',
  17 + // target: 'http://localhost:8085/',
18 18 // target: 'http://192.168.1.6:8085/',
19   - // target: 'http://39.108.227.113:8085/',
  19 + target: 'http://39.108.227.113:8085/',
20 20 changeOrigin: true,
21 21 pathRewrite: { '^/api': '' },
22 22 },
... ... @@ -51,7 +51,6 @@ export default defineConfig({
51 51 {
52 52 name: '订单预警',
53 53 path: 'OrderWarning',
54   - access: 'canReadAdminAndSales',
55 54 component: './Order/OrderWarning',
56 55 },
57 56 {
... ... @@ -60,6 +59,11 @@ export default defineConfig({
60 59 access: 'canReadAdminAndSalesManager',
61 60 component: './Order/WarningWhitelist',
62 61 },
  62 + {
  63 + name: '订单售后',
  64 + path: 'FeedBack',
  65 + component: './Order/FeedBack',
  66 + },
63 67 ],
64 68 },
65 69 {
... ... @@ -150,11 +154,11 @@ export default defineConfig({
150 154 name: '客户管理',
151 155 path: '/Client',
152 156 icon: 'BookOutlined',
153   - access: 'canReadAdminAndSalesAndWarehouseKeeper',
  157 + access: 'canReadAdminAndSales',
154 158 routes: [
155 159 {
156 160 name: '客户列表',
157   - path: 'clint',
  161 + path: 'client',
158 162 component: './Client/Client',
159 163 },
160 164 {
... ...
src/pages/Client/Client/Components/ClientDrawer.tsx
... ... @@ -8,6 +8,7 @@ import {
8 8 postResearchGroupsNameSet,
9 9 postServiceConstClientLevels,
10 10 postServiceConstClientSource,
  11 + postServiceOrderQuerySalesCode,
11 12 } from '@/services';
12 13  
13 14 import { enumToSelect } from '@/utils';
... ... @@ -18,7 +19,7 @@ import {
18 19 } from '@ant-design/pro-components';
19 20 import { Button, Form, message } from 'antd';
20 21 // import { options } from 'node_modules/axios/index.cjs';
21   -import { useState } from 'react';
  22 +import { useEffect, useState } from 'react';
22 23  
23 24 export default ({ optType, record, onFinish }) => {
24 25 const [form] = Form.useForm();
... ... @@ -26,6 +27,24 @@ export default ({ optType, record, onFinish }) => {
26 27 const [province, setProvince] = useState('');
27 28 const [city, setCity] = useState('');
28 29 const [showReferrers, setShowReferrers] = useState(false); // 控制显示的状态,初始为false
  30 + const [affiliateSalesOptions, setAffiliateSalesOptions] = useState([]);
  31 +
  32 + useEffect(() => {
  33 + const fetchAffiliateSalesOptions = async () => {
  34 + try {
  35 + const res = await postServiceOrderQuerySalesCode();
  36 + const options = res.data?.map((item) => ({
  37 + label: item.userName,
  38 + value: item.userName,
  39 + }));
  40 + setAffiliateSalesOptions(options || []);
  41 + } catch (error) {
  42 + console.error('Failed to fetch assign people options:', error);
  43 + }
  44 + };
  45 + fetchAffiliateSalesOptions();
  46 + }, []);
  47 +
29 48 const optTypeEnum = {
30 49 add: {
31 50 text: '新增',
... ... @@ -152,8 +171,6 @@ export default ({ optType, record, onFinish }) => {
152 171 console.log(value);
153 172  
154 173 if (value !== undefined || value !== null) {
155   - console.log('setProvince');
156   -
157 174 setProvince(value);
158 175 }
159 176 }}
... ... @@ -350,6 +367,15 @@ export default ({ optType, record, onFinish }) => {
350 367 label="课题组名称"
351 368 placeholder="请输入名称"
352 369 />
  370 + <Form.Item label="关联销售" name="affiliateSales">
  371 + <ProFormSelect
  372 + name="affiliateSales"
  373 + options={affiliateSalesOptions}
  374 + placeholder="请选择关联销售"
  375 + rules={[{ required: true, message: '请选择关联销售' }]}
  376 + mode="multiple"
  377 + />
  378 + </Form.Item>
353 379 <ProFormText
354 380 name="department"
355 381 label="部门"
... ...
src/pages/Client/Client/Components/ClientInformationModal.tsx
1 1 import { RESPONSE_CODE } from '@/constants/enum';
2   -import UploadC from '@/pages/Invoice/waitProcessRecord/components/UploadSingleImg';
3 2 import {
4 3 postAdminClientAddOrModifyClientComunicationInfo,
5 4 postAdminClientQueryClientPage,
... ... @@ -10,19 +9,43 @@ import {
10 9 } from '@/services';
11 10 import { enumToSelect } from '@/utils';
12 11 import {
  12 + FormListActionType,
13 13 ModalForm,
  14 + ProCard,
14 15 ProFormDateTimePicker,
  16 + ProFormList,
15 17 ProFormSelect,
16 18 ProFormText,
17 19 ProFormTextArea,
18 20 ProFormUploadDragger,
19 21 } from '@ant-design/pro-components';
20   -import { Button, Col, Form, Row, message } from 'antd';
  22 +import { Button, Form, message } from 'antd';
21 23 import { RcFile } from 'antd/es/upload';
  24 +import { useRef } from 'react';
22 25 import './style.css';
23 26 export default ({ data, type, reloadTable }) => {
24 27 const [form] = Form.useForm();
  28 + const actionRef = useRef<
  29 + FormListActionType<{
  30 + name: string;
  31 + }>
  32 + >();
  33 + const fileList: any = [];
25 34 const onfinish = async (values) => {
  35 + let list = values.ticketsList;
  36 + list = list.map((item) => {
  37 + let itemAnnexUrl = null;
  38 + // 尝试从 item.filePaths 中获取 annexUrl
  39 + if (item?.filePaths) {
  40 + itemAnnexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null;
  41 + } else {
  42 + itemAnnexUrl = null;
  43 + }
  44 + item.annexUrl = itemAnnexUrl;
  45 + return item;
  46 + });
  47 +
  48 + values.list = list;
26 49 const resSearchId = await postAdminClientQueryClientPage({
27 50 data: {
28 51 groupFilter: 'all',
... ... @@ -54,25 +77,32 @@ export default ({ data, type, reloadTable }) =&gt; {
54 77 const editOnfinish = async (values) => {
55 78 // setEditClientId(data.clientId);
56 79 values.clientId = data.clientId;
57   - console.log(values, '5656editClientId');
58   -
59   - // const resSearchId = await postAdminClientQueryClientPage({
60   - // data: {
61   - // groupFilter: 'all',
62   - // },
63   - // });
64   - // const matchingItem = resSearchId.data.data.find(
65   - // (item) => item.id === values.name,
66   - // );
  80 + let list = values.ticketsList;
  81 + list = list.map((item, index) => {
  82 + // 尝试从 item.filePaths 中获取 annexUrl
  83 + const itemAnnexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null;
67 84  
  85 + // 如果 itemAnnexUrl 为 null,则使用 data.ticketsList 对应的 annexUrl
  86 + if (
  87 + itemAnnexUrl === null &&
  88 + data.ticketsList[index]?.annexUrl &&
  89 + index < data.ticketsList.length
  90 + ) {
  91 + item.annexUrl = data.ticketsList[index].annexUrl; // 确保对比中的 annesUrl 也是 null
  92 + } else {
  93 + item.annexUrl = itemAnnexUrl; // 否则,使用从 item.filePaths 中获取的值
  94 + }
  95 + return item;
  96 + });
  97 + values.list = list;
68 98 const res = await postAdminClientAddOrModifyClientComunicationInfo({
69 99 data: {
70 100 ...values,
71 101 // ticketsAttachments: form.getFieldValue("ticketsAttachments"),
72   - ticketsAttachments:
73   - form.getFieldValue('ticketsAttachments') !== undefined
74   - ? form.getFieldValue('ticketsAttachments')
75   - : data?.ticketsAttachments,
  102 + // ticketsAttachments:
  103 + // form.getFieldValue('ticketsAttachments') !== undefined
  104 + // ? form.getFieldValue('ticketsAttachments')
  105 + // : data?.ticketsAttachments,
76 106 // ticketsAttachments: data?.annexUrl,
77 107 },
78 108 });
... ... @@ -150,18 +180,6 @@ export default ({ data, type, reloadTable }) =&gt; {
150 180 groupFilter: 'all',
151 181 },
152 182 });
153   - console.log(data, '5656data?.nameedit');
154   - // const namesArray = res.data.data.map((item) => item.name);
155   - // const formattedObject = res.data.data.reduce((acc, name) => {
156   - // acc[name] = name; // 将名称作为键和值
157   - // return acc;
158   - // }, {});
159   - // console.log(namesArray, '5656namesArray');
160   - // const formattedObject = res.data.data.reduce((acc, item) => {
161   - // acc[item.name] = item.name; // 使用 name 作为键,id 作为值
162   - // return acc;
163   - // }, {});
164   - // return enumToSelect(formattedObject);
165 183 const options = res.data.data.reduce((acc, item) => {
166 184 acc.push({ label: item.name, value: item.id }); // 使用 name 作为 label,id 作为 value
167 185 return acc;
... ... @@ -180,7 +198,7 @@ export default ({ data, type, reloadTable }) =&gt; {
180 198 label="联系人"
181 199 width="sm"
182 200 placeholder="请输入联系人"
183   - initialValue={data?.content}
  201 + initialValue={data?.contact}
184 202 readonly={optType[type].readOnly}
185 203 rules={[
186 204 {
... ... @@ -194,7 +212,7 @@ export default ({ data, type, reloadTable }) =&gt; {
194 212 label="联系电话"
195 213 width="sm"
196 214 placeholder="请输入联系电话"
197   - initialValue={data?.content}
  215 + initialValue={data?.contactPhone}
198 216 readonly={optType[type].readOnly}
199 217 rules={[
200 218 {
... ... @@ -314,7 +332,168 @@ export default ({ data, type, reloadTable }) =&gt; {
314 332 hidden
315 333 ></ProFormText>
316 334 <ProFormText initialValue={data?.id} name="id" hidden></ProFormText>
317   - <div className="styled-text">
  335 + <h2>工单指派</h2>
  336 + <ProFormList
  337 + // creatorButtonProps={{ disabled: optType('after-sales-check') }}
  338 + // deleteIconProps={!optType('after-sales-check')}
  339 + deleteIconProps={true}
  340 + name="ticketsList"
  341 + label=""
  342 + copyIconProps={false} //复制按钮不显示
  343 + // initialValue={[
  344 + // {
  345 + // ticketsType: '',
  346 + // ticketsDetail: '',
  347 + // assignPeople: '',
  348 + // ticketsAttachment: '',
  349 + // ticketsAttachments: '',
  350 + // },
  351 + // ]}
  352 + initialValue={
  353 + optType[type].title === '修改跟进记录'
  354 + ? data.ticketsList || []
  355 + : [
  356 + {
  357 + type: '',
  358 + detailText: '',
  359 + assignPeople: '',
  360 + annexUrl: '',
  361 + },
  362 + ]
  363 + }
  364 + actionGuard={{
  365 + beforeRemoveRow: async () => {
  366 + return new Promise((resolve) => {
  367 + let list = form.getFieldValue('ticketsList');
  368 + if (list && list.length === 1) {
  369 + message.error('至少需要保留一个工单');
  370 + resolve(false);
  371 + return;
  372 + }
  373 + resolve(true);
  374 + });
  375 + },
  376 + }}
  377 + itemRender={(doms, listMeta) => {
  378 + if (optType[type].title === '修改跟进记录') {
  379 + // let i = 0;
  380 + // // let defaultFileList = data.ticketsList?.annexUrl?.map((annex) => {
  381 + // // return {
  382 + // // uid: i++,
  383 + // // name: annex,
  384 + // // status: 'uploaded',
  385 + // // url: annex,
  386 + // // response: { data: [annex] },
  387 + // // };
  388 + // // });
  389 + let defaultFileList = [
  390 + {
  391 + uid: listMeta.index,
  392 + name: data.ticketsList[listMeta.index]?.annexUrl,
  393 + status: 'uploaded',
  394 + url: data.ticketsList[listMeta.index]?.annexUrl,
  395 + response: {
  396 + data: [data.ticketsList[listMeta.index]?.annexUrl],
  397 + },
  398 + },
  399 + ];
  400 + fileList[listMeta.index] = defaultFileList;
  401 + }
  402 + let itemFileList = fileList[listMeta.index];
  403 + return (
  404 + <ProCard
  405 + bordered
  406 + extra={doms.action}
  407 + title={'工单指派' + (listMeta.index + 1)}
  408 + style={{
  409 + marginBlockEnd: 8,
  410 + }}
  411 + >
  412 + {[
  413 + <ProFormSelect
  414 + key={'type' + listMeta.index}
  415 + name="type"
  416 + label="工单类型"
  417 + width="sm"
  418 + placeholder="请输入工单类型"
  419 + readonly={optType[type].readOnly}
  420 + fieldProps={{
  421 + labelInValue: false,
  422 + }}
  423 + // initialValue={data?.ticketsList?.type ? data?.ticketsList?.type + '' : null}
  424 + initialValue={
  425 + data?.ticketsList ? data?.ticketsList?.type + '' : null
  426 + }
  427 + request={async () => {
  428 + return [
  429 + { label: '问题', value: 'QUESTION' },
  430 + { label: '需求', value: 'DEMAND' },
  431 + { label: '建议', value: 'ADVICE' },
  432 + ];
  433 + }}
  434 + />,
  435 + <ProFormTextArea
  436 + key={'detailText' + listMeta.index}
  437 + name="detailText"
  438 + label="工单详情"
  439 + placeholder="请输入工单详情"
  440 + initialValue={data?.detailText ? data?.detailText + '' : null}
  441 + readonly={optType[type].readOnly}
  442 + ></ProFormTextArea>,
  443 + <ProFormSelect
  444 + key={'assignPeople' + listMeta.index}
  445 + name="assignPeople"
  446 + width="sm"
  447 + readonly={optType[type].readOnly}
  448 + fieldProps={{
  449 + labelInValue: false,
  450 + }}
  451 + initialValue={
  452 + data?.assignPeople ? data?.assignPeople + '' : null
  453 + }
  454 + label="指派人员"
  455 + request={async () => {
  456 + const res = await postOrderErpUsersListByPage({
  457 + data: {
  458 + pageSize: 10000,
  459 + },
  460 + });
  461 +
  462 + const userOptions = res.data.records
  463 + ? res.data.records.map((user) => ({
  464 + label: user.userName, // 或者使用其他需要的属性
  465 + value: user.userName, // 作为value的字段
  466 + }))
  467 + : [];
  468 + return userOptions;
  469 + }}
  470 + ></ProFormSelect>,
  471 + <>
  472 + <ProFormUploadDragger
  473 + key={'filePaths' + listMeta.index}
  474 + label="附件"
  475 + name="filePaths"
  476 + action="/api/service/order/fileProcess"
  477 + fieldProps={{
  478 + headers: {
  479 + Authorization: localStorage.getItem('token'),
  480 + },
  481 + // headers: {
  482 + // 'Content-Type':
  483 + // 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  484 + // },
  485 + itemFileList,
  486 + }}
  487 + max={1}
  488 + />
  489 + </>,
  490 + ]}
  491 + </ProCard>
  492 + );
  493 + }}
  494 + actionRef={actionRef}
  495 + ></ProFormList>
  496 + {/* <div className="styled-text">
318 497 <div className="vertical-line"></div>
319 498 <span className="text">工单指派</span>
320 499 </div>
... ... @@ -342,7 +521,7 @@ export default ({ data, type, reloadTable }) =&gt; {
342 521 placeholder="请输入工单详情"
343 522 initialValue={data?.ticketsDetail ? data?.ticketsDetail + '' : null}
344 523 readonly={optType[type].readOnly}
345   - ></ProFormTextArea>
  524 + ></ProFormTextArea> */}
346 525 {/* <ProFormUploadDragger
347 526 label="工单附件"
348 527 name="ticketsAttachment"
... ... @@ -371,7 +550,7 @@ export default ({ data, type, reloadTable }) =&gt; {
371 550 }}
372 551 max={1}
373 552 /> */}
374   - <Row>
  553 + {/* <Row>
375 554 <Col span={4}>附件:</Col>
376 555 <Col span={20}>
377 556 <UploadC
... ... @@ -426,13 +605,13 @@ export default ({ data, type, reloadTable }) =&gt; {
426 605  
427 606 const userOptions = res.data.records
428 607 ? res.data.records.map((user) => ({
429   - label: user.userName, // 或者使用其他需要的属性
430   - value: user.userName, // 作为value的字段
431   - }))
  608 + label: user.userName, // 或者使用其他需要的属性
  609 + value: user.userName, // 作为value的字段
  610 + }))
432 611 : [];
433 612 return userOptions;
434 613 }}
435   - ></ProFormSelect>
  614 + ></ProFormSelect> */}
436 615 </ModalForm>
437 616 );
438 617 };
... ...
src/pages/Client/Client/Components/ClientModal.tsx
1 1 import { RESPONSE_CODE } from '@/constants/enum';
2   -import UploadC from '@/pages/Invoice/waitProcessRecord/components/UploadSingleImg';
3 2 import {
4 3 postAdminClientAddOrModifyClientComunicationInfo,
5 4 postAdminClientQueryClientPage,
... ... @@ -10,74 +9,43 @@ import {
10 9 } from '@/services';
11 10 import { enumToSelect } from '@/utils';
12 11 import {
  12 + FormListActionType,
13 13 ModalForm,
  14 + ProCard,
14 15 ProFormDateTimePicker,
  16 + ProFormList,
15 17 ProFormSelect,
16 18 ProFormText,
17 19 ProFormTextArea,
18 20 ProFormUploadDragger,
19 21 } from '@ant-design/pro-components';
20   -import { Button, Col, Form, Row, message } from 'antd';
  22 +import { Button, Form, message } from 'antd';
21 23 import { RcFile } from 'antd/es/upload';
  24 +import { useRef } from 'react';
22 25 import './style.css';
23   -// import { useRef, useState } from 'react';
24 26 export default ({ data, type, reloadTable }) => {
25   - // const initialItems = [
26   - // { label: 'Tab 1', children: 'Content of Tab 1', key: '1' },
27   - // ];
28   - // const [activeKey, setActiveKey] = useState(initialItems[0].key);
29   - // const [items, setItems] = useState(initialItems);
30   - // const newTabIndex = useRef(1);
31   -
32   - // const onChange = (newActiveKey: string) => {
33   - // setActiveKey(newActiveKey);
34   - // };
35   -
36   - // const add = () => {
37   - // // 生成新的标签名,格式为 'Tab {num}'
38   - // const newTabLabel = `Tab ${newTabIndex.current + 1}`;
39   - // // 增加索引,保证下次生成新的Tab名称时,num会递增
40   - // newTabIndex.current++;
41   - // // 创建新的panes,追加新Tab
42   - // const newActiveKey = `newTab${newTabIndex.current}`;
43   - // const newPanes = [...items];
44   - // newPanes.push({ label: newTabLabel, children: 'Content of new Tab', key: newActiveKey });
45   - // setItems(newPanes);
46   - // setActiveKey(newActiveKey);
47   - // };
48   -
49   - // const remove = (targetKey: TargetKey) => {
50   - // let newActiveKey = activeKey;
51   - // let lastIndex = -1;
52   - // items.forEach((item, i) => {
53   - // if (item.key === targetKey) {
54   - // lastIndex = i - 1;
55   - // }
56   - // });
57   - // const newPanes = items.filter((item) => item.key !== targetKey);
58   - // if (newPanes.length && newActiveKey === targetKey) {
59   - // if (lastIndex >= 0) {
60   - // newActiveKey = newPanes[lastIndex].key;
61   - // } else {
62   - // newActiveKey = newPanes[0].key;
63   - // }
64   - // }
65   - // setItems(newPanes);
66   - // setActiveKey(newActiveKey);
67   - // };
68   -
69   - // const onEdit = (
70   - // targetKey: React.MouseEvent | React.KeyboardEvent | string,
71   - // action: 'add' | 'remove',
72   - // ) => {
73   - // if (action === 'add') {
74   - // add();
75   - // } else {
76   - // remove(targetKey);
77   - // }
78   - // };
79 27 const [form] = Form.useForm();
  28 + const actionRef = useRef<
  29 + FormListActionType<{
  30 + name: string;
  31 + }>
  32 + >();
  33 + const fileList: any = [];
80 34 const onfinish = async (values) => {
  35 + let list = values.list;
  36 +
  37 + list = list.map((item) => {
  38 + let itemAnnexUrl = null;
  39 + // 尝试从 item.filePaths 中获取 annexUrl
  40 + if (item?.filePaths) {
  41 + itemAnnexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null;
  42 + } else {
  43 + itemAnnexUrl = null;
  44 + }
  45 + item.annexUrl = itemAnnexUrl;
  46 + return item;
  47 + });
  48 + values.list = list;
81 49 const res = await postAdminClientAddOrModifyClientComunicationInfo({
82 50 data: {
83 51 ...values,
... ... @@ -184,7 +152,7 @@ export default ({ data, type, reloadTable }) =&gt; {
184 152 label="联系人"
185 153 width="sm"
186 154 placeholder="请输入联系人"
187   - initialValue={data?.content}
  155 + initialValue={data?.contact}
188 156 readonly={optType[type].readOnly}
189 157 rules={[
190 158 {
... ... @@ -198,7 +166,7 @@ export default ({ data, type, reloadTable }) =&gt; {
198 166 label="联系电话"
199 167 width="sm"
200 168 placeholder="请输入联系电话"
201   - initialValue={data?.content}
  169 + initialValue={data?.contactPhone}
202 170 readonly={optType[type].readOnly}
203 171 rules={[
204 172 {
... ... @@ -318,132 +286,140 @@ export default ({ data, type, reloadTable }) =&gt; {
318 286 hidden
319 287 ></ProFormText>
320 288 <ProFormText initialValue={data?.id} name="id" hidden></ProFormText>
321   - {/* <Tabs
322   - type="editable-card"
323   - onChange={onChange}
324   - activeKey={activeKey}
325   - onEdit={onEdit}
326   - items={items}
327   - /> */}
328   - <div className="styled-text">
329   - <div className="vertical-line"></div>
330   - <span className="text">工单指派</span>
331   - </div>
332   - <ProFormSelect
333   - name="ticketsType"
334   - label="工单类型"
335   - width="sm"
336   - placeholder="请输入工单类型"
337   - readonly={optType[type].readOnly}
338   - fieldProps={{
339   - labelInValue: false,
340   - }}
341   - initialValue={data?.type ? data?.type + '' : null}
342   - request={async () => {
343   - return [
344   - { label: '问题', value: 'QUESTION' },
345   - { label: '需求', value: 'DEMAND' },
346   - { label: '建议', value: 'ADVICE' },
347   - ];
348   - }}
349   - />
350   - <ProFormTextArea
351   - name="ticketsDetail"
352   - label="工单详情"
353   - placeholder="请输入工单详情"
354   - initialValue={data?.detailText ? data?.detailText + '' : null}
355   - readonly={optType[type].readOnly}
356   - ></ProFormTextArea>
357   - <Row>
358   - <Col span={4}>附件:</Col>
359   - <Col span={20}>
360   - <UploadC
361   - onFilesChange={async (newFileList) => {
362   - if (newFileList.length > 0) {
363   - const urls = []; // 创建一个数组来存储所有的 URL
364   -
365   - for (const file of newFileList) {
366   - const formData = new FormData();
367   - formData.append('file', file.originFileObj as RcFile);
368   -
369   - const res = await postOrderErpOrderStagesUpload({
370   - data: formData,
371   - headers: {
372   - 'Content-Type':
373   - 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
374   - },
375   - });
376   -
377   - const url = res.data; // 获取响应中的 URL
378   - urls.push(url); // 将每个 URL 追加到数组中
379   - }
380   -
381   - // 将所有 URL 使用 ',' 进行拼接
382   - const combinedUrl = urls.join(',');
383   - form.setFieldValue('ticketsAttachments', combinedUrl); // 设置表单字段值为拼接后的 URL
384   - } else {
385   - form.setFieldValue('ticketsAttachments', null); // 如果没有文件,则清空 URL
  289 + <h2>工单指派</h2>
  290 + <ProFormList
  291 + // creatorButtonProps={{ disabled: optType('after-sales-check') }}
  292 + // deleteIconProps={!optType('after-sales-check')}
  293 + deleteIconProps={true}
  294 + name="list"
  295 + label=""
  296 + copyIconProps={false} //复制按钮不显示
  297 + initialValue={[
  298 + {
  299 + type: '',
  300 + detailText: '',
  301 + assignPeople: '',
  302 + annexUrl: '',
  303 + },
  304 + ]}
  305 + actionGuard={{
  306 + beforeRemoveRow: async () => {
  307 + return new Promise((resolve) => {
  308 + let list = form.getFieldValue('list');
  309 + if (list && list.length === 1) {
  310 + message.error('至少需要保留一个工单');
  311 + resolve(false);
  312 + return;
386 313 }
387   - }}
388   - ></UploadC>
389   - </Col>
390   - </Row>
391   - {/* <ProFormUploadDragger
392   - label="工单附件"
393   - name="ticketsAttachment"
394   - action="upload.do"
395   - hidden={optType[type].readOnly}
396   - onChange={(info) => {
397   - const uploadFile = async ({ fileList: newFileList }) => {
398   - if (newFileList.length > 0) {
399   - const formData = new FormData();
400   - formData.append('file', newFileList[0].originFileObj as RcFile);
401   - const res = await postOrderErpOrderStagesUpload({
402   - data: formData,
403   - headers: {
404   - 'Content-Type':
405   - 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
406   - },
407   - });
408   - const url = res.data;
409   - console.log('ticketsAttachments' + JSON.stringify(url));
410   - form.setFieldValue('ticketsAttachments', url);
411   - } else {
412   - form.setFieldValue('ticketsAttachments', null);
413   - }
414   - };
415   - uploadFile(info);
416   - }}
417   - max={1}
418   - /> */}
419   - <a hidden={!optType[type].readOnly} href={data?.orderAttachment} download>
420   - 下载附件
421   - </a>
422   - <ProFormSelect
423   - name="assignPeople"
424   - width="sm"
425   - readonly={optType[type].readOnly}
426   - fieldProps={{
427   - labelInValue: false,
  314 + resolve(true);
  315 + });
  316 + },
428 317 }}
429   - initialValue={data?.assignPeople ? data?.assignPeople + '' : null}
430   - label="指派人员"
431   - request={async () => {
432   - const res = await postOrderErpUsersListByPage({
433   - data: {
434   - pageSize: 10000,
435   - },
436   - });
  318 + itemRender={(doms, listMeta) => {
  319 + if (optType[type].modify || optType[type].add || optType[type].add2) {
  320 + let i = 0;
  321 + let defaultFileList = listMeta.record?.listAnnex?.map((annex) => {
  322 + return {
  323 + uid: i++,
  324 + name: annex,
  325 + status: 'uploaded',
  326 + url: annex,
  327 + response: { data: [annex] },
  328 + };
  329 + });
  330 + fileList[listMeta.index] = defaultFileList;
  331 + }
  332 + let itemFileList = fileList[listMeta.index];
  333 + return (
  334 + <ProCard
  335 + bordered
  336 + extra={doms.action}
  337 + title={'工单指派' + (listMeta.index + 1)}
  338 + style={{
  339 + marginBlockEnd: 8,
  340 + }}
  341 + >
  342 + {[
  343 + <ProFormSelect
  344 + key={'type' + listMeta.index}
  345 + name="type"
  346 + label="工单类型"
  347 + width="sm"
  348 + placeholder="请输入工单类型"
  349 + readonly={optType[type].readOnly}
  350 + fieldProps={{
  351 + labelInValue: false,
  352 + }}
  353 + initialValue={data?.type ? data?.type + '' : null}
  354 + request={async () => {
  355 + return [
  356 + { label: '问题', value: 'QUESTION' },
  357 + { label: '需求', value: 'DEMAND' },
  358 + { label: '建议', value: 'ADVICE' },
  359 + ];
  360 + }}
  361 + />,
  362 + <ProFormTextArea
  363 + key={'detailText' + listMeta.index}
  364 + name="detailText"
  365 + label="工单详情"
  366 + placeholder="请输入工单详情"
  367 + initialValue={data?.detailText ? data?.detailText + '' : null}
  368 + readonly={optType[type].readOnly}
  369 + ></ProFormTextArea>,
  370 + <ProFormSelect
  371 + key={'assignPeople' + listMeta.index}
  372 + name="assignPeople"
  373 + width="sm"
  374 + readonly={optType[type].readOnly}
  375 + fieldProps={{
  376 + labelInValue: false,
  377 + }}
  378 + initialValue={
  379 + data?.assignPeople ? data?.assignPeople + '' : null
  380 + }
  381 + label="指派人员"
  382 + request={async () => {
  383 + const res = await postOrderErpUsersListByPage({
  384 + data: {
  385 + pageSize: 10000,
  386 + },
  387 + });
437 388  
438   - const userOptions = res.data.records
439   - ? res.data.records.map((user) => ({
440   - label: user.userName, // 或者使用其他需要的属性
441   - value: user.userName, // 作为value的字段
442   - }))
443   - : [];
444   - return userOptions;
  389 + const userOptions = res.data.records
  390 + ? res.data.records.map((user) => ({
  391 + label: user.userName, // 或者使用其他需要的属性
  392 + value: user.userName, // 作为value的字段
  393 + }))
  394 + : [];
  395 + return userOptions;
  396 + }}
  397 + ></ProFormSelect>,
  398 + <>
  399 + <ProFormUploadDragger
  400 + key={'filePaths' + listMeta.index}
  401 + label="附件"
  402 + name="filePaths"
  403 + action="/api/service/order/fileProcess"
  404 + fieldProps={{
  405 + headers: {
  406 + Authorization: localStorage.getItem('token'),
  407 + },
  408 + // headers: {
  409 + // 'Content-Type':
  410 + // 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  411 + // },
  412 + itemFileList,
  413 + }}
  414 + max={1}
  415 + />
  416 + </>,
  417 + ]}
  418 + </ProCard>
  419 + );
445 420 }}
446   - ></ProFormSelect>
  421 + actionRef={actionRef}
  422 + ></ProFormList>
447 423 </ModalForm>
448 424 );
449 425 };
... ...
src/pages/Client/Client/Components/CommunicationHistoryModal.tsx
... ... @@ -138,6 +138,7 @@ export default ({ record }) =&gt; {
138 138 const [createByName, setCreateByName] = useState(''); // 创建人
139 139 const [latestObject, setLatestObject] = useState(); // 最新跟进时间
140 140 const [recordSave, setRecordSave] = useState(); //新增跟进
  141 + const [affiliateSales, setAffiliateSales] = useState([]);
141 142 useEffect(() => {
142 143 const request = async () => {
143 144 const resShow = await postAdminClientQueryClientComunicationInfo({
... ... @@ -175,6 +176,7 @@ export default ({ record }) =&gt; {
175 176 setAddress(record.address);
176 177 setNotes(record.notes);
177 178 setCreateByName(record.createByName);
  179 + setAffiliateSales(record.affiliateSales.join(','));
178 180 };
179 181 request();
180 182 }, []);
... ... @@ -249,6 +251,11 @@ export default ({ record }) =&gt; {
249 251 label: '最新跟进时间',
250 252 children: latestObject, // 最新跟进时间
251 253 },
  254 + {
  255 + key: '16',
  256 + label: '关联销售',
  257 + children: affiliateSales, // 创建人
  258 + },
252 259 ];
253 260 return (
254 261 <ModalForm
... ...
src/pages/Client/Client/Components/InformationHistoryModal.tsx
... ... @@ -27,10 +27,6 @@ export default ({ data, reloadTable }) =&gt; {
27 27 const [createTime, setCreateTime] = useState(null); // 创建时间
28 28 const [attachments, setAttachments] = useState(); //附件
29 29 const [attachmentsName, setAttachmentsName] = useState(''); // 附件名称
30   - const [ticketsType, setTicketsType] = useState(''); // 附件名称
31   - const [ticketsDetail, setTicketsDetail] = useState(''); // 附件名称
32   - const [ticketsAttachment, setTicketsAttachment] = useState(''); // 附件名称
33   - const [assignPeople, setAssignPeople] = useState(''); // 附件名称
34 30 const [comment, setComment] = useState(''); // 客户评价
35 31  
36 32 useEffect(() => {
... ... @@ -56,10 +52,6 @@ export default ({ data, reloadTable }) =&gt; {
56 52 }
57 53 }
58 54 setComment(dataSearch.comment);
59   - setTicketsType(dataSearch.ticketsTypeText);
60   - setTicketsDetail(dataSearch.ticketsDetail);
61   - setTicketsAttachment(dataSearch.ticketsAttachments);
62   - setAssignPeople(dataSearch.assignPeople);
63 55 setDatetime(dataSearch.datetime); // 设置跟进日期
64 56 // setDateRange(data.dateRange || []); // 设置跟进时间范围
65 57 setCreateByName(dataSearch.createByName); // 设置跟进人员
... ... @@ -115,26 +107,6 @@ export default ({ data, reloadTable }) =&gt; {
115 107 },
116 108 {
117 109 key: '8',
118   - label: '工单类型',
119   - children: ticketsType, // 跟进状态
120   - },
121   - {
122   - key: '9',
123   - label: '工单详情',
124   - children: ticketsDetail, // 跟进状态
125   - },
126   - // {
127   - // key: '10',
128   - // label: '工单附件',
129   - // children: ticketsAttachment, // 跟进状态
130   - // },
131   - {
132   - key: '10',
133   - label: '指派人员',
134   - children: assignPeople, // 跟进状态
135   - },
136   - {
137   - key: '11',
138 110 label: '客户评价',
139 111 children: comment, // 跟进状态
140 112 },
... ... @@ -151,6 +123,40 @@ export default ({ data, reloadTable }) =&gt; {
151 123 actionRef?.current?.reload(); // 重新加载表格数据
152 124 }
153 125 };
  126 + const TicketDescriptions = ({ data }) => {
  127 + return (
  128 + <div>
  129 + {data.ticketsList && data.ticketsList.length > 0 ? (
  130 + data.ticketsList.map((ticket, index) => (
  131 + <div key={index} style={{ marginBottom: '20px' }}>
  132 + <Descriptions title={`工单 ${index + 1}`} column={1}>
  133 + <Descriptions.Item label="工单类型">
  134 + {ticket.typeText}
  135 + </Descriptions.Item>
  136 + <Descriptions.Item label="工单详情">
  137 + {ticket.detailText}
  138 + </Descriptions.Item>
  139 + <Descriptions.Item label="指派人员">
  140 + {ticket.assignPeople}
  141 + </Descriptions.Item>
  142 + </Descriptions>
  143 + {ticket.annexUrl && (
  144 + <a
  145 + href={ticket.annexUrl}
  146 + target="_blank"
  147 + rel="noopener noreferrer"
  148 + >
  149 + 下载工单附件
  150 + </a>
  151 + )}
  152 + </div>
  153 + ))
  154 + ) : (
  155 + <p></p>
  156 + )}
  157 + </div>
  158 + );
  159 + };
154 160 return (
155 161 <Space>
156 162 <ModalForm
... ... @@ -215,11 +221,6 @@ export default ({ data, reloadTable }) =&gt; {
215 221 }}
216 222 >
217 223 <Descriptions items={items} column={1} />
218   - {/* {attachmentsName && (
219   - <a href={attachments} download>
220   - 附件:{attachmentsName}
221   - </a>
222   - )} */}
223 224 {attachmentsName && (
224 225 <div>
225 226 {attachmentsName.endsWith('.png') ||
... ... @@ -242,6 +243,12 @@ export default ({ data, reloadTable }) =&gt; {
242 243 )}
243 244 </div>
244 245 )}
  246 + <TicketDescriptions data={data} />
  247 + {/* {attachmentsName && (
  248 + <a href={attachments} download>
  249 + 附件:{attachmentsName}
  250 + </a>
  251 + )} */}
245 252  
246 253 <div></div>
247 254 {/* {ticketsAttachment && (
... ... @@ -271,12 +278,12 @@ export default ({ data, reloadTable }) =&gt; {
271 278 )}
272 279 </div>
273 280 )} */}
274   - {ticketsAttachment && (
  281 + {/* {ticketsAttachment && (
275 282 <div>
276 283 {ticketsAttachment.split(',').map((ticketsAttachment, index) => (
277 284 <div key={index}>
278 285 {ticketsAttachment.includes('jpg') ||
279   - ticketsAttachment.includes('png') ? (
  286 + ticketsAttachment.includes('png') ? (
280 287 <>
281 288 <img
282 289 src={ticketsAttachment}
... ... @@ -292,7 +299,7 @@ export default ({ data, reloadTable }) =&gt; {
292 299 </div>
293 300 ))}
294 301 </div>
295   - )}
  302 + )} */}
296 303 </ModalForm>
297 304 </Space>
298 305 );
... ...
src/pages/Client/Client/index.tsx
... ... @@ -105,6 +105,12 @@ const columns = [
105 105 },
106 106 },
107 107 {
  108 + title: '关联销售',
  109 + hideInSearch: true,
  110 + hideInTable: true,
  111 + dataIndex: 'affiliateSales',
  112 + },
  113 + {
108 114 title: '部门',
109 115 width: 150,
110 116 ellipsis: true,
... ... @@ -296,46 +302,57 @@ const columns = [
296 302 }
297 303 };
298 304 return [
299   - <CommunicationHistoryModal
300   - key={'communicationHistory'}
301   - record={record}
302   - />,
303   - <ClientDrawer
304   - key={'detail'}
305   - record={record}
306   - optType={'detail'}
307   - onFinish={() => {
308   - action.reload();
309   - }}
310   - ></ClientDrawer>,
311   - <ClientModal
312   - key={'add'}
313   - data={record}
314   - reloadTable={() => {
315   - action?.reload();
316   - }}
317   - type={'add'}
318   - />,
319   - <ClientDrawer
320   - key={'edit'}
321   - record={record}
322   - optType={'edit'}
323   - onFinish={() => {
324   - action.reload();
325   - }}
326   - ></ClientDrawer>,
327   - // <a key={'delete'} onClick={handleDelete}>
328   - // 删除
329   - // </a>,
330   - <ButtonConfirm
331   - key="delete"
332   - className="p-0"
333   - title={'删除该客户,客户下的跟进记录会一并删除'}
334   - text="删除"
335   - onConfirm={async () => {
336   - handleDelete();
337   - }}
338   - />,
  305 + record?.paths?.includes('DETAIL') && (
  306 + <CommunicationHistoryModal
  307 + key={'communicationHistory'}
  308 + record={record}
  309 + />
  310 + ),
  311 +
  312 + record?.paths?.includes('DETAIL') && (
  313 + <ClientDrawer
  314 + key={'detail'}
  315 + record={record}
  316 + optType={'detail'}
  317 + onFinish={() => {
  318 + action.reload();
  319 + }}
  320 + />
  321 + ),
  322 +
  323 + record?.paths?.includes('DETAIL') && (
  324 + <ClientModal
  325 + key={'add'}
  326 + data={record}
  327 + reloadTable={() => {
  328 + action?.reload();
  329 + }}
  330 + type={'add'}
  331 + />
  332 + ),
  333 +
  334 + record?.paths?.includes('EDIT') && (
  335 + <ClientDrawer
  336 + key={'edit'}
  337 + record={record}
  338 + optType={'edit'}
  339 + onFinish={() => {
  340 + action.reload();
  341 + }}
  342 + />
  343 + ),
  344 +
  345 + record?.paths?.includes('DELETE') && (
  346 + <ButtonConfirm
  347 + key="delete"
  348 + className="p-0"
  349 + title={'删除该客户,客户下的跟进记录会一并删除'}
  350 + text="删除"
  351 + onConfirm={async () => {
  352 + handleDelete();
  353 + }}
  354 + />
  355 + ),
339 356 ];
340 357 },
341 358 },
... ...
src/pages/Client/FollowRecord/Components/ClientModal.tsx
1 1 import { RESPONSE_CODE } from '@/constants/enum';
2   -import UploadC from '@/pages/Invoice/waitProcessRecord/components/UploadSingleImg';
3 2 import {
4 3 postAdminClientAddOrModifyClientComunicationInfo,
5 4 postAdminClientQueryClientPage,
... ... @@ -10,21 +9,43 @@ import {
10 9 } from '@/services';
11 10 import { enumToSelect } from '@/utils';
12 11 import {
  12 + FormListActionType,
13 13 ModalForm,
  14 + ProCard,
14 15 ProFormDateTimePicker,
  16 + ProFormList,
15 17 ProFormSelect,
16 18 ProFormText,
17 19 ProFormTextArea,
18 20 ProFormUploadDragger,
19 21 } from '@ant-design/pro-components';
20   -import { Button, Col, Form, Row, message } from 'antd';
  22 +import { Button, Form, message } from 'antd';
21 23 import { RcFile } from 'antd/es/upload';
22   -import { useEffect } from 'react';
  24 +import { useEffect, useRef } from 'react';
23 25 import './style.css';
24 26 export default ({ data, type, reloadTable }) => {
25 27 useEffect(() => {});
26 28 const [form] = Form.useForm();
  29 + const actionRef = useRef<
  30 + FormListActionType<{
  31 + name: string;
  32 + }>
  33 + >();
  34 + const fileList: any = [];
27 35 const onfinish = async (values) => {
  36 + let list = values.ticketsList;
  37 + list = list.map((item) => {
  38 + let itemAnnexUrl = null;
  39 + // 尝试从 item.filePaths 中获取 annexUrl
  40 + if (item?.filePaths) {
  41 + itemAnnexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null;
  42 + } else {
  43 + itemAnnexUrl = null;
  44 + }
  45 + item.annexUrl = itemAnnexUrl;
  46 + return item;
  47 + });
  48 + values.list = list;
28 49 const resSearchId = await postAdminClientQueryClientPage({
29 50 data: {
30 51 groupFilter: 'all',
... ... @@ -43,7 +64,7 @@ export default ({ data, type, reloadTable }) =&gt; {
43 64 const res = await postAdminClientAddOrModifyClientComunicationInfo({
44 65 data: {
45 66 ...values,
46   - ticketsAttachments: form.getFieldValue('ticketsAttachments'),
  67 + // ticketsAttachments: form.getFieldValue('ticketsAttachments'),
47 68 clientId: matchedId,
48 69 },
49 70 });
... ... @@ -58,24 +79,35 @@ export default ({ data, type, reloadTable }) =&gt; {
58 79 const editOnfinish = async (values) => {
59 80 // setEditClientId(data.clientId);
60 81 values.clientId = data.clientId;
61   -
62   - // const resSearchId = await postAdminClientQueryClientPage({
63   - // data: {
64   - // groupFilter: 'all',
65   - // },
  82 + let list = values.ticketsList;
  83 + // list = list.map((item) => {
  84 + // // item.annexUrl = item.filePaths?.map((file) => {
  85 + // // return { url: file.response.data[0] };
  86 + // // });
  87 + // // item.annexUrl = item?.filePaths[0].response?.data[0];
  88 + // item.annexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null; // 使用 null 或默认值
  89 + // return item;
66 90 // });
67   - // const matchingItem = resSearchId.data.data.find(
68   - // (item) => item.id === values.name,
69   - // );
  91 + list = list.map((item, index) => {
  92 + // 尝试从 item.filePaths 中获取 annexUrl
  93 + const itemAnnexUrl = item?.filePaths?.[0]?.response?.data?.[0] || null;
  94 +
  95 + // 如果 itemAnnexUrl 为 null,则使用 data.ticketsList 对应的 annexUrl
  96 + if (
  97 + itemAnnexUrl === null &&
  98 + data.ticketsList[index]?.annexUrl &&
  99 + index < data.ticketsList.length
  100 + ) {
  101 + item.annexUrl = data.ticketsList[index].annexUrl; // 确保对比中的 annesUrl 也是 null
  102 + } else {
  103 + item.annexUrl = itemAnnexUrl; // 否则,使用从 item.filePaths 中获取的值
  104 + }
  105 + return item;
  106 + });
  107 + values.list = list;
70 108 const res = await postAdminClientAddOrModifyClientComunicationInfo({
71 109 data: {
72 110 ...values,
73   - // ticketsAttachments: form.getFieldValue("ticketsAttachments"),
74   - ticketsAttachments:
75   - form.getFieldValue('ticketsAttachments') !== undefined
76   - ? form.getFieldValue('ticketsAttachments')
77   - : data?.ticketsAttachments,
78   - // ticketsAttachments: data?.annexUrl,
79 111 },
80 112 });
81 113 if (res.result === RESPONSE_CODE.SUCCESS) {
... ... @@ -315,7 +347,168 @@ export default ({ data, type, reloadTable }) =&gt; {
315 347 hidden
316 348 ></ProFormText>
317 349 <ProFormText initialValue={data?.id} name="id" hidden></ProFormText>
318   - <div className="styled-text">
  350 + <h2>工单指派</h2>
  351 + <ProFormList
  352 + // creatorButtonProps={{ disabled: optType('after-sales-check') }}
  353 + // deleteIconProps={!optType('after-sales-check')}
  354 + deleteIconProps={true}
  355 + name="ticketsList"
  356 + label=""
  357 + copyIconProps={false} //复制按钮不显示
  358 + // initialValue={[
  359 + // {
  360 + // ticketsType: '',
  361 + // ticketsDetail: '',
  362 + // assignPeople: '',
  363 + // ticketsAttachment: '',
  364 + // ticketsAttachments: '',
  365 + // },
  366 + // ]}
  367 + initialValue={
  368 + optType[type].title === '修改跟进记录'
  369 + ? data.ticketsList || []
  370 + : [
  371 + {
  372 + type: '',
  373 + detailText: '',
  374 + assignPeople: '',
  375 + annexUrl: '',
  376 + },
  377 + ]
  378 + }
  379 + actionGuard={{
  380 + beforeRemoveRow: async () => {
  381 + return new Promise((resolve) => {
  382 + let list = form.getFieldValue('ticketsList');
  383 + if (list && list.length === 1) {
  384 + message.error('至少需要保留一个工单');
  385 + resolve(false);
  386 + return;
  387 + }
  388 + resolve(true);
  389 + });
  390 + },
  391 + }}
  392 + itemRender={(doms, listMeta) => {
  393 + if (optType[type].title === '修改跟进记录') {
  394 + // let i = 0;
  395 + // // let defaultFileList = data.ticketsList?.annexUrl?.map((annex) => {
  396 + // // return {
  397 + // // uid: i++,
  398 + // // name: annex,
  399 + // // status: 'uploaded',
  400 + // // url: annex,
  401 + // // response: { data: [annex] },
  402 + // // };
  403 + // // });
  404 + let defaultFileList = [
  405 + {
  406 + uid: listMeta.index,
  407 + name: data.ticketsList[listMeta.index]?.annexUrl,
  408 + status: 'uploaded',
  409 + url: data.ticketsList[listMeta.index]?.annexUrl,
  410 + response: {
  411 + data: [data.ticketsList[listMeta.index]?.annexUrl],
  412 + },
  413 + },
  414 + ];
  415 + fileList[listMeta.index] = defaultFileList;
  416 + }
  417 + let itemFileList = fileList[listMeta.index];
  418 + return (
  419 + <ProCard
  420 + bordered
  421 + extra={doms.action}
  422 + title={'工单指派' + (listMeta.index + 1)}
  423 + style={{
  424 + marginBlockEnd: 8,
  425 + }}
  426 + >
  427 + {[
  428 + <ProFormSelect
  429 + key={'type' + listMeta.index}
  430 + name="type"
  431 + label="工单类型"
  432 + width="sm"
  433 + placeholder="请输入工单类型"
  434 + readonly={optType[type].readOnly}
  435 + fieldProps={{
  436 + labelInValue: false,
  437 + }}
  438 + // initialValue={data?.ticketsList?.type ? data?.ticketsList?.type + '' : null}
  439 + initialValue={
  440 + data?.ticketsList ? data?.ticketsList?.type + '' : null
  441 + }
  442 + request={async () => {
  443 + return [
  444 + { label: '问题', value: 'QUESTION' },
  445 + { label: '需求', value: 'DEMAND' },
  446 + { label: '建议', value: 'ADVICE' },
  447 + ];
  448 + }}
  449 + />,
  450 + <ProFormTextArea
  451 + key={'detailText' + listMeta.index}
  452 + name="detailText"
  453 + label="工单详情"
  454 + placeholder="请输入工单详情"
  455 + initialValue={data?.detailText ? data?.detailText + '' : null}
  456 + readonly={optType[type].readOnly}
  457 + ></ProFormTextArea>,
  458 + <ProFormSelect
  459 + key={'assignPeople' + listMeta.index}
  460 + name="assignPeople"
  461 + width="sm"
  462 + readonly={optType[type].readOnly}
  463 + fieldProps={{
  464 + labelInValue: false,
  465 + }}
  466 + initialValue={
  467 + data?.assignPeople ? data?.assignPeople + '' : null
  468 + }
  469 + label="指派人员"
  470 + request={async () => {
  471 + const res = await postOrderErpUsersListByPage({
  472 + data: {
  473 + pageSize: 10000,
  474 + },
  475 + });
  476 +
  477 + const userOptions = res.data.records
  478 + ? res.data.records.map((user) => ({
  479 + label: user.userName, // 或者使用其他需要的属性
  480 + value: user.userName, // 作为value的字段
  481 + }))
  482 + : [];
  483 + return userOptions;
  484 + }}
  485 + ></ProFormSelect>,
  486 + <>
  487 + <ProFormUploadDragger
  488 + key={'filePaths' + listMeta.index}
  489 + label="附件"
  490 + name="filePaths"
  491 + action="/api/service/order/fileProcess"
  492 + fieldProps={{
  493 + headers: {
  494 + Authorization: localStorage.getItem('token'),
  495 + },
  496 + // headers: {
  497 + // 'Content-Type':
  498 + // 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  499 + // },
  500 + itemFileList,
  501 + }}
  502 + max={1}
  503 + />
  504 + </>,
  505 + ]}
  506 + </ProCard>
  507 + );
  508 + }}
  509 + actionRef={actionRef}
  510 + ></ProFormList>
  511 + {/* <div className="styled-text">
319 512 <div className="vertical-line"></div>
320 513 <span className="text">工单指派</span>
321 514 </div>
... ... @@ -348,7 +541,7 @@ export default ({ data, type, reloadTable }) =&gt; {
348 541 }}
349 542 initialValue={data?.ticketsDetail ? data?.ticketsDetail + '' : null}
350 543 readonly={optType[type].readOnly}
351   - ></ProFormTextArea>
  544 + ></ProFormTextArea> */}
352 545 {/* <ProFormUploadDragger
353 546 label="工单附件"
354 547 name="ticketsAttachment"
... ... @@ -377,7 +570,7 @@ export default ({ data, type, reloadTable }) =&gt; {
377 570 }}
378 571 max={1}
379 572 /> */}
380   - <Row>
  573 + {/* <Row>
381 574 <Col span={4}>附件:</Col>
382 575 <Col span={20}>
383 576 <UploadC
... ... @@ -416,7 +609,6 @@ export default ({ data, type, reloadTable }) =&gt; {
416 609 </Col>
417 610 </Row>
418 611 <a hidden={!optType[type].readOnly} href={data?.orderAttachment} download>
419   - {/* <a href={data?.annexUrl} download> */}
420 612 下载附件
421 613 </a>
422 614 <ProFormSelect
... ... @@ -443,7 +635,7 @@ export default ({ data, type, reloadTable }) =&gt; {
443 635 : [];
444 636 return userOptions;
445 637 }}
446   - ></ProFormSelect>
  638 + ></ProFormSelect> */}
447 639 </ModalForm>
448 640 );
449 641 };
... ...
src/pages/Client/FollowRecord/Components/CommunicationHistoryModal.tsx
... ... @@ -15,26 +15,15 @@ export default ({ data, reloadTable }) =&gt; {
15 15 const [datetime, setDatetime] = useState(); // 跟进日期
16 16 const [createByName, setCreateByName] = useState(''); // 跟进人员
17 17 const [clientName, setClientName] = useState(''); // 客户名称
18   - // const [clientAddress, setClientAddress] = useState(''); // 客户地址
19 18 const [way, setWay] = useState(); // 跟进方式
20 19 const [tradeStatusText, setTradeStatusText] = useState(); // 状态
21   - // const [clientNameLike, setClientNameLike] = useState(''); // 客户名称模糊查询
22   - // const [clientAddressLike, setClientAddressLike] = useState(''); // 客户地址模糊查询
23   - // const [tradeStatus, setTradeStatus] = useState(''); // 客户状态
24   - // const [tradeStatusLike, setTradeStatusLike] = useState(''); // 客户状态模糊查询
25 20 const [content, setContent] = useState(''); // 跟进详情
26 21 const [createTime, setCreateTime] = useState(null); // 创建时间
27 22 const [attachments, setAttachments] = useState(); //附件
28 23 const [attachmentsName, setAttachmentsName] = useState(''); // 附件名称
29   - const [ticketsType, setTicketsType] = useState(''); // 附件名称
30   - const [ticketsDetail, setTicketsDetail] = useState(''); // 附件名称
31   - const [ticketsAttachment, setTicketsAttachment] = useState(); // 附件名称
32   - // const [ticketsAttachmentName, setTicketsAttachmentName] = useState(''); // 附件名称
33 24 const [comment, setComment] = useState(''); // 客户评价
34   - const [assignPeople, setAssignPeople] = useState(''); // 附件名称
35 25 useEffect(() => {
36 26 const request = async () => {
37   - console.log(data, '5656datatatatata');
38 27 const res = await postAdminClientQueryClientComunicationInfo({
39 28 data: {
40 29 id: data.id,
... ... @@ -82,10 +71,6 @@ export default ({ data, reloadTable }) =&gt; {
82 71 // }
83 72 // }
84 73 setComment(dataSearch.comment);
85   - setTicketsType(dataSearch.ticketsTypeText);
86   - setTicketsDetail(dataSearch.ticketsDetail);
87   - setTicketsAttachment(dataSearch.ticketsAttachments);
88   - setAssignPeople(dataSearch.assignPeople);
89 74 setComment(dataSearch.comment);
90 75 setDatetime(dataSearch.datetime); // 设置跟进日期
91 76 // setDateRange(data.dateRange || []); // 设置跟进时间范围
... ... @@ -142,29 +127,24 @@ export default ({ data, reloadTable }) =&gt; {
142 127 },
143 128 {
144 129 key: '8',
145   - label: '工单类型',
146   - children: ticketsType, // 跟进状态
147   - },
148   - {
149   - key: '9',
150   - label: '工单详情',
151   - children: ticketsDetail, // 跟进状态
152   - },
153   - // {
154   - // key: '12',
155   - // label: '工单附件',
156   - // children: ticketsAttachment, // 跟进状态
157   - // },
158   - {
159   - key: '10',
160   - label: '指派人员',
161   - children: assignPeople, // 跟进状态
162   - },
163   - {
164   - key: '11',
165 130 label: '客户评价',
166 131 children: comment, // 跟进状态
167 132 },
  133 + // {
  134 + // key: '9',
  135 + // label: '工单类型',
  136 + // children: ticketsType, // 跟进状态
  137 + // },
  138 + // {
  139 + // key: '10',
  140 + // label: '工单详情',
  141 + // children: ticketsDetail, // 跟进状态
  142 + // },
  143 + // {
  144 + // key: '11',
  145 + // label: '指派人员',
  146 + // children: assignPeople, // 跟进状态
  147 + // },
168 148 ];
169 149 const handleDelete = async () => {
170 150 // 调用删除接口
... ... @@ -178,6 +158,40 @@ export default ({ data, reloadTable }) =&gt; {
178 158 actionRef?.current?.reload(); // 重新加载表格数据
179 159 }
180 160 };
  161 + const TicketDescriptions = ({ data }) => {
  162 + return (
  163 + <div>
  164 + {data.ticketsList && data.ticketsList.length > 0 ? (
  165 + data.ticketsList.map((ticket, index) => (
  166 + <div key={index} style={{ marginBottom: '20px' }}>
  167 + <Descriptions title={`工单 ${index + 1}`} column={1}>
  168 + <Descriptions.Item label="工单类型">
  169 + {ticket.typeText}
  170 + </Descriptions.Item>
  171 + <Descriptions.Item label="工单详情">
  172 + {ticket.detailText}
  173 + </Descriptions.Item>
  174 + <Descriptions.Item label="指派人员">
  175 + {ticket.assignPeople}
  176 + </Descriptions.Item>
  177 + </Descriptions>
  178 + {ticket.annexUrl && (
  179 + <a
  180 + href={ticket.annexUrl}
  181 + target="_blank"
  182 + rel="noopener noreferrer"
  183 + >
  184 + 下载工单附件
  185 + </a>
  186 + )}
  187 + </div>
  188 + ))
  189 + ) : (
  190 + <p></p>
  191 + )}
  192 + </div>
  193 + );
  194 + };
181 195 return (
182 196 <Space>
183 197 <ModalForm
... ... @@ -242,17 +256,6 @@ export default ({ data, reloadTable }) =&gt; {
242 256 }}
243 257 >
244 258 <Descriptions items={items} column={1} />
245   - {/* {attachmentsName && (
246   - <a href={attachments} download>
247   - 附件:{attachmentsName}
248   - </a>
249   - )}
250   - <div></div>
251   - {ticketsAttachment && (
252   - <a href={ticketsAttachment} download>
253   - 工单附件:{ticketsAttachment}
254   - </a>
255   - )} */}
256 259 {attachmentsName && (
257 260 <div>
258 261 {attachmentsName.endsWith('.png') ||
... ... @@ -275,6 +278,18 @@ export default ({ data, reloadTable }) =&gt; {
275 278 )}
276 279 </div>
277 280 )}
  281 + <TicketDescriptions data={data} />
  282 + {/* {attachmentsName && (
  283 + <a href={attachments} download>
  284 + 附件:{attachmentsName}
  285 + </a>
  286 + )}
  287 + <div></div>
  288 + {ticketsAttachment && (
  289 + <a href={ticketsAttachment} download>
  290 + 工单附件:{ticketsAttachment}
  291 + </a>
  292 + )} */}
278 293  
279 294 <div></div>
280 295 {/* {ticketsAttachment && (
... ... @@ -304,28 +319,6 @@ export default ({ data, reloadTable }) =&gt; {
304 319 )}
305 320 </div>
306 321 )} */}
307   - {ticketsAttachment && (
308   - <div>
309   - {ticketsAttachment.split(',').map((ticketsAttachment, index) => (
310   - <div key={index}>
311   - {ticketsAttachment.includes('jpg') ||
312   - ticketsAttachment.includes('png') ? (
313   - <>
314   - <img
315   - src={ticketsAttachment}
316   - alt={`附件 ${index + 1}`}
317   - style={{ maxWidth: '300px', height: 'auto' }}
318   - />
319   - <div></div>
320   - </>
321   - ) : null}
322   - <a href={ticketsAttachment} download>
323   - 工单附件:{ticketsAttachment}
324   - </a>
325   - </div>
326   - ))}
327   - </div>
328   - )}
329 322 </ModalForm>
330 323 </Space>
331 324 );
... ...
src/pages/Client/FollowRecord/index.tsx
... ... @@ -23,7 +23,6 @@ export default () =&gt; {
23 23  
24 24 // 更新 refreshKey,强制刷新 CommunicationHistoryModal
25 25 setRefreshKey((prevKey) => prevKey + 1);
26   - console.log('5656refresh');
27 26 };
28 27 //biaoji
29 28 const columns = [
... ... @@ -247,7 +246,6 @@ export default () =&gt; {
247 246 data={record}
248 247 // reloadTable={() => {
249 248 // actionRef.current.reload();
250   - // console.log('5656flush');
251 249 // }}
252 250 reloadTable={reload}
253 251 />,
... ...
src/pages/Order/FeedBack/AfterSalesDrawer.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceOrderApplyAfterSales } from '@/services';
  3 +import { enumToSelect } from '@/utils';
  4 +import {
  5 + DrawerForm,
  6 + ProFormDigit,
  7 + ProFormSelect,
  8 + ProFormTextArea,
  9 + ProFormUploadDragger,
  10 +} from '@ant-design/pro-components';
  11 +import { Form, message } from 'antd';
  12 +import { AFTE_SALES_PLAN_OPTIONS } from '../constant';
  13 +export default ({ setVisible, mainOrder, subOrders, onClose }) => {
  14 + let subOrderIds = subOrders?.map((item: { id: any }) => {
  15 + return item.id;
  16 + });
  17 +
  18 + let mainOrderId = mainOrder.id;
  19 + const [form] = Form.useForm<{
  20 + afterSalesNotes: string;
  21 + afterSalesPlan: string;
  22 + ids: [];
  23 + totalPayment: number;
  24 + filePaths: any[];
  25 + }>();
  26 +
  27 + return (
  28 + <DrawerForm<{
  29 + afterSalesNotes: string;
  30 + afterSalesPlan: string;
  31 + subOrderIds: [];
  32 + totalPayment: number;
  33 + mainId: number;
  34 + filePaths: any[];
  35 + }>
  36 + title="申请售后"
  37 + open
  38 + resize={{
  39 + onResize() {
  40 + console.log('resize!');
  41 + },
  42 + maxWidth: window.innerWidth * 0.8,
  43 + minWidth: 500,
  44 + }}
  45 + form={form}
  46 + autoFocusFirstInput
  47 + drawerProps={{
  48 + destroyOnClose: true,
  49 + onClose: () => {
  50 + setVisible(false);
  51 + },
  52 + }}
  53 + onFinish={async (values) => {
  54 + values.subOrderIds = subOrderIds;
  55 + values.mainId = mainOrderId;
  56 + values.filePaths = values.filePaths?.map((file) => {
  57 + return { url: file.response.data[0] };
  58 + });
  59 + let res = await postServiceOrderApplyAfterSales({ data: values });
  60 + if (res?.result === RESPONSE_CODE.SUCCESS) {
  61 + message.success(res.message);
  62 + onClose();
  63 + }
  64 + }}
  65 + >
  66 + <ProFormSelect
  67 + key="key"
  68 + label="售后方案"
  69 + width="lg"
  70 + showSearch
  71 + name="afterSalesPlan"
  72 + options={enumToSelect(AFTE_SALES_PLAN_OPTIONS)}
  73 + placeholder="请搜索"
  74 + rules={[{ required: true, message: '售后方案必填' }]}
  75 + ></ProFormSelect>
  76 + <ProFormDigit
  77 + width="lg"
  78 + name="totalPayment"
  79 + label="总金额调整"
  80 + min={0}
  81 + initialValue={mainOrder.totalPayment}
  82 + rules={[{ required: true, message: '总金额必填' }]}
  83 + />
  84 + <ProFormTextArea
  85 + width="lg"
  86 + label="售后原因"
  87 + name="afterSalesNotes"
  88 + rules={[{ required: true, message: '售后原因必填' }]}
  89 + />
  90 + <ProFormUploadDragger
  91 + key="filePaths"
  92 + label="附件"
  93 + name="filePaths"
  94 + action="/api/service/order/fileProcess"
  95 + fieldProps={{
  96 + headers: { Authorization: localStorage.getItem('token') },
  97 + }}
  98 + />
  99 + </DrawerForm>
  100 + );
  101 +};
... ...
src/pages/Order/FeedBack/ApplyForInvoicingModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceOrderApplyInvoicing } from '@/services';
  3 +import { enumToSelect, getAliYunOSSFileNameFromUrl } from '@/utils';
  4 +import {
  5 + ModalForm,
  6 + ProFormSelect,
  7 + ProFormText,
  8 + ProFormTextArea,
  9 + ProFormUploadDragger,
  10 +} from '@ant-design/pro-components';
  11 +import { Form, message } from 'antd';
  12 +import { useEffect, useState } from 'react';
  13 +import { PAYEE_OPTIONS } from '../constant';
  14 +export default ({
  15 + setCheckVisible,
  16 + isEdit,
  17 + subOrders,
  18 + isMainOrder,
  19 + totalPayment,
  20 + onClose,
  21 +}) => {
  22 + const [isUrgent, setIsUrgent] = useState('');
  23 + let sumPrice = totalPayment;
  24 +
  25 + let ids = subOrders?.map((item) => {
  26 + return item.id;
  27 + });
  28 +
  29 + let mainIdSet = new Set();
  30 + subOrders?.forEach((item: { mainOrderId: unknown }) => {
  31 + mainIdSet.add(item.mainOrderId);
  32 + });
  33 +
  34 + let mainIds = Array.from(mainIdSet).join(',');
  35 +
  36 + let newListAnnex = [];
  37 +
  38 + //回显,子订单可以编辑备注跟附件
  39 + if (isEdit) {
  40 + newListAnnex = subOrders.afterAnnexList?.map((path) => {
  41 + let i = 0;
  42 + return {
  43 + uid: i++,
  44 + name: getAliYunOSSFileNameFromUrl(path),
  45 + status: 'uploaded',
  46 + url: path,
  47 + response: { data: [path] },
  48 + };
  49 + });
  50 + subOrders.filePaths = newListAnnex;
  51 + }
  52 +
  53 + const [form] = Form.useForm<{
  54 + applyInvoicingNotes: string;
  55 + filePaths: any;
  56 + subIds: any[];
  57 + afterInvoicingUpdate: boolean;
  58 + receivingCompany: string;
  59 + isUrgent: boolean;
  60 + deadline: string;
  61 + }>();
  62 +
  63 + useEffect(() => {
  64 + //显示拼接的主订单id
  65 + form.setFieldValue('applyInvoicingNotes', mainIds);
  66 + }, []);
  67 +
  68 + return (
  69 + <ModalForm<{
  70 + applyInvoicingNotes: string;
  71 + filePaths: any;
  72 + subIds: any[];
  73 + afterInvoicingUpdate: boolean;
  74 + }>
  75 + width={500}
  76 + open
  77 + title={isEdit ? '修改信息' : '申请开票'}
  78 + initialValues={subOrders}
  79 + form={form}
  80 + autoFocusFirstInput
  81 + modalProps={{
  82 + okText: '确认',
  83 + cancelText: '取消',
  84 + destroyOnClose: true,
  85 + onCancel: () => {
  86 + setCheckVisible(false);
  87 + },
  88 + }}
  89 + submitter={{
  90 + render: (props, defaultDoms) => {
  91 + return defaultDoms;
  92 + },
  93 + }}
  94 + submitTimeout={2000}
  95 + onFinish={async (values) => {
  96 + values.subIds = ids;
  97 + //附件处理
  98 + values.filePaths = values.filePaths?.map((item) => {
  99 + return { url: item.response.data[0] };
  100 + });
  101 +
  102 + if (isEdit) {
  103 + values.afterInvoicingUpdate = true;
  104 + } else {
  105 + values.afterInvoicingUpdate = false;
  106 + }
  107 +
  108 + const res = await postServiceOrderApplyInvoicing({ data: values });
  109 +
  110 + if (res.result === RESPONSE_CODE.SUCCESS) {
  111 + message.success(res.message);
  112 + onClose();
  113 + }
  114 + }}
  115 + onOpenChange={setCheckVisible}
  116 + >
  117 + {isMainOrder ? (
  118 + <div className="mb-[24px]">
  119 + <span>选中子订单金额之和:</span>
  120 + <span className="text-red-500">{sumPrice}¥</span>
  121 + </div>
  122 + ) : (
  123 + ''
  124 + )}
  125 +
  126 + <div className="mb-1">
  127 + 如果需要合并订单,请将需要合并的订单id写在备注中,id之间用英文逗号隔开。
  128 + </div>
  129 + <ProFormTextArea
  130 + width="lg"
  131 + name="applyInvoicingNotes"
  132 + key="applyInvoicingNotes"
  133 + placeholder="请输入备注"
  134 + onMetaChange={(val) => {
  135 + console.log(val);
  136 + }}
  137 + proFieldProps={{
  138 + onchange: () => {
  139 + message.info('change');
  140 + },
  141 + }}
  142 + />
  143 + <ProFormText
  144 + width="lg"
  145 + name="purchaser"
  146 + label="抬头名称"
  147 + key="purchaser"
  148 + placeholder="请输入抬头名称"
  149 + rules={[{ required: true, message: '抬头名称必填' }]}
  150 + />
  151 + <ProFormSelect
  152 + placeholder="选择收款单位"
  153 + name="receivingCompany"
  154 + width="lg"
  155 + key="receivingCompany"
  156 + label={
  157 + <div>
  158 + <span>开票收款单位</span>
  159 + <span className="pl-2 text-xs text-gray-400">
  160 + 财务开票将依据这个字段,选择对应的公司开票(若对[收款单位]没有要求,请任意选择一个)
  161 + </span>
  162 + </div>
  163 + }
  164 + options={enumToSelect(PAYEE_OPTIONS)}
  165 + rules={[{ required: true, message: '开票收款单位必填' }]}
  166 + />
  167 + <ProFormSelect
  168 + placeholder="选择是否加急"
  169 + name="isUrgent"
  170 + width="lg"
  171 + key="isUrgent"
  172 + label="是否加急"
  173 + options={[
  174 + { label: '是', value: 'true' },
  175 + { label: '否', value: 'false' },
  176 + ]}
  177 + rules={[{ required: true, message: '是否加急必填' }]}
  178 + onChange={(val: any) => {
  179 + setIsUrgent(val);
  180 + }}
  181 + />
  182 +
  183 + {/* <ProFormDatePicker
  184 + key="deadline"
  185 + label="期望开票时间"
  186 + name="deadline"
  187 + rules={[{ required: isUrgent === 'true', message: '期望开票时间必填' }]}
  188 + hidden={isUrgent !== 'true'}
  189 + /> */}
  190 +
  191 + <ProFormTextArea
  192 + key="invoicingUrgentCause"
  193 + label="加急开票原因"
  194 + name="invoicingUrgentCause"
  195 + rules={[{ required: isUrgent === 'true', message: '加急开票原因' }]}
  196 + hidden={isUrgent !== 'true'}
  197 + />
  198 +
  199 + <ProFormUploadDragger
  200 + key="2"
  201 + label={
  202 + <div>
  203 + <span>开票明细确认表</span>
  204 + <span className="pl-2 text-xs text-gray-400">
  205 + 如果开票信息有变更,如开票内容跟下单内容不一致、下单抬头和付款抬头不一致,请上传开票明细确认表。
  206 + </span>
  207 + </div>
  208 + }
  209 + name="filePaths"
  210 + action="/api/service/order/fileProcess"
  211 + fieldProps={{
  212 + headers: { Authorization: localStorage.getItem('token') },
  213 + }}
  214 + />
  215 + </ModalForm>
  216 + );
  217 +};
... ...
src/pages/Order/FeedBack/AttachmentModal.tsx 0 → 100644
  1 +import { getAliYunOSSFileNameFromUrl, isImageName } from '@/utils';
  2 +import { ModalForm } from '@ant-design/pro-components';
  3 +import { Button, Card, Divider, Empty, Form, Image, List, message } from 'antd';
  4 +import Base64 from 'base-64';
  5 +import { cloneDeep } from 'lodash';
  6 +import React, { useEffect, useState } from 'react';
  7 +
  8 +export default ({ data, onClose }) => {
  9 + let newData = cloneDeep(data);
  10 + const [fileList, setFileList] = useState<[]>([]);
  11 + console.log(fileList);
  12 + const [form] = Form.useForm<{
  13 + subOrderId: '';
  14 + listAnnex: [];
  15 + }>();
  16 +
  17 + let newListAnnex = newData.listAnnex?.map((path) => {
  18 + let i = 0;
  19 + return {
  20 + uid: i++,
  21 + name: getAliYunOSSFileNameFromUrl(path),
  22 + status: 'uploaded',
  23 + url: path,
  24 + response: { data: [path] },
  25 + };
  26 + });
  27 + newData.listAnnex = newListAnnex;
  28 +
  29 + //将图片和其他文件区分开
  30 + let images: any[] = [];
  31 + let otherAnnex: any[] = [];
  32 + newListAnnex.forEach((item: any) => {
  33 + if (isImageName(item.name)) {
  34 + images.push(item);
  35 + } else {
  36 + otherAnnex.push(item);
  37 + }
  38 + });
  39 +
  40 + useEffect(() => {
  41 + setFileList(newData.listAnnex);
  42 + }, []);
  43 +
  44 + return (
  45 + <ModalForm
  46 + width={800}
  47 + open
  48 + title="查看附件"
  49 + initialValues={newData}
  50 + form={form}
  51 + modalProps={{
  52 + onCancel: onClose,
  53 + }}
  54 + submitter={{
  55 + render: () => {
  56 + return [
  57 + <Button
  58 + key="back"
  59 + onClick={() => {
  60 + onClose();
  61 + }}
  62 + >
  63 + 返回
  64 + </Button>,
  65 + ];
  66 + },
  67 + }}
  68 + >
  69 + {newListAnnex?.length <= 0 ? (
  70 + <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
  71 + ) : (
  72 + // <ProFormUploadDragger
  73 + // name="listAnnex"
  74 + // action="/api/service/order/fileProcess"
  75 + // disabled
  76 + // fieldProps={{
  77 + // headers: { Authorization: localStorage.getItem('token') },
  78 + // // onRemove: (file) => {
  79 + // // const index = fileList[listMeta.index].indexOf(file);
  80 + // // console.log(index);
  81 + // // const newFileList = fileList.slice();
  82 + // // newFileList.splice(index, 1);
  83 + // // setFileList(newFileList);
  84 + // // },
  85 + // // beforeUpload: (file) => {
  86 + // // fileList[listMeta.index] = [...fileList[listMeta.index], file as RcFile];
  87 + // // setFileList(fileList);
  88 + // // return true;
  89 + // // },
  90 + // fileList,
  91 + // // defaultFileList: itemFileList
  92 + // }}
  93 + // />
  94 + <>
  95 + <Card>
  96 + <Image.PreviewGroup
  97 + className="mr-10"
  98 + preview={{
  99 + onChange: (current, prev) =>
  100 + console.log(`current index: ${current}, prev index: ${prev}`),
  101 + }}
  102 + >
  103 + {images.map((item, index) => (
  104 + <React.Fragment key={index}>
  105 + <Image
  106 + className="max-h-[200px] max-w-[200px]"
  107 + src={item.url}
  108 + title={item.name}
  109 + />{' '}
  110 + <Divider type="vertical" />
  111 + </React.Fragment>
  112 + ))}
  113 + </Image.PreviewGroup>
  114 + </Card>
  115 + <Divider />
  116 +
  117 + <div>
  118 + <List
  119 + size="small"
  120 + header={<div>其他类型文件</div>}
  121 + bordered
  122 + dataSource={otherAnnex}
  123 + renderItem={(item) => (
  124 + <List.Item
  125 + actions={[
  126 + <Button
  127 + type="link"
  128 + key="key"
  129 + href={item.url}
  130 + target="blank"
  131 + className="py-1"
  132 + >
  133 + 下载
  134 + </Button>,
  135 + <Button
  136 + type="link"
  137 + key="key"
  138 + className="py-1"
  139 + onClick={() => {
  140 + message.info(item.url);
  141 + window.open(
  142 + '/previewApi/onlinePreview?url=' +
  143 + encodeURIComponent(Base64.encode(item.url)),
  144 + );
  145 + }}
  146 + >
  147 + 预览
  148 + </Button>,
  149 + ]}
  150 + >
  151 + <div>
  152 + <span>{item.name}</span>
  153 + </div>
  154 + </List.Item>
  155 + )}
  156 + />
  157 + </div>
  158 + </>
  159 + )}
  160 + </ModalForm>
  161 + );
  162 +};
... ...
src/pages/Order/FeedBack/BaseModal.tsx 0 → 100644
  1 +import { ModalForm } from '@ant-design/pro-components';
  2 +import { Form } from 'antd';
  3 +
  4 +// import { cloneDeep } from 'lodash';
  5 +export default ({ setVisible, onClose }) => {
  6 + const [form] = Form.useForm<{ name: string; company: string }>();
  7 +
  8 + return (
  9 + <>
  10 + <ModalForm<{
  11 + name: string;
  12 + company: string;
  13 + }>
  14 + width={500}
  15 + open
  16 + title="标题"
  17 + form={form}
  18 + autoFocusFirstInput
  19 + modalProps={{
  20 + okText: '通过',
  21 + cancelText: '取消',
  22 + destroyOnClose: true,
  23 + onCancel: () => {
  24 + setVisible(false);
  25 + },
  26 + }}
  27 + onFinish={async (values) => {
  28 + console.log(values);
  29 + onClose();
  30 + }}
  31 + onOpenChange={setVisible}
  32 + ></ModalForm>
  33 + </>
  34 + );
  35 +};
... ...
src/pages/Order/FeedBack/CheckModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import {
  3 + postPrepaidAudit,
  4 + postServiceOrderAfterSalesCheck,
  5 + postServiceOrderAudit,
  6 + postServiceOrderFileProcess,
  7 + postServiceOrderFinanceCheckOrder,
  8 + postServiceOrderLeaderAudit,
  9 + postServiceOrderToProcureAudit,
  10 +} from '@/services';
  11 +import {
  12 + ModalForm,
  13 + ProFormText,
  14 + ProFormTextArea,
  15 + ProList,
  16 +} from '@ant-design/pro-components';
  17 +import {
  18 + Button,
  19 + Col,
  20 + Divider,
  21 + Form,
  22 + Image,
  23 + Modal,
  24 + Row,
  25 + Space,
  26 + Tag,
  27 + UploadFile,
  28 + message,
  29 +} from 'antd';
  30 +import Upload, { RcFile, UploadProps } from 'antd/es/upload';
  31 +import { useEffect, useRef, useState } from 'react';
  32 +import {
  33 + AFTE_SALES_PLAN_OPTIONS,
  34 + CHECK_TYPE,
  35 + COMFIR_RECEIPT_IMAGES_NUMBER,
  36 +} from './constant';
  37 +// import { cloneDeep } from 'lodash';
  38 +import InvoiceSubOrderInfoTable from '@/pages/Order/Order/components/InvoiceSubOrderInfoTable';
  39 +import { enumValueToLabel, transImageFile } from '@/utils';
  40 +import { PlusOutlined } from '@ant-design/icons';
  41 +import { cloneDeep } from 'lodash';
  42 +
  43 +export default ({
  44 + setCheckVisible,
  45 + data,
  46 + subOrders,
  47 + orderCheckType,
  48 + openOrderDrawer,
  49 + onClose,
  50 +}) => {
  51 + const [previewOpen, setPreviewOpen] = useState(false);
  52 + const [aPopoverTitle, setAPopoverTitle] = useState('审核');
  53 + const [previewImage, setPreviewImage] = useState('');
  54 + const [previewTitle, setPreviewTitle] = useState('');
  55 + const [paymentReceiptsImages, setPymentReceiptsImages] = useState<any[]>([]);
  56 + const fileListObj = useRef<UploadFile[]>([]); //使用引用类型,使得在useEffect里面设置监听事件后,不用更新监听事件也能保持obj与外界一致
  57 + const getBase64 = (file: RcFile): Promise<string> =>
  58 + new Promise((resolve, reject) => {
  59 + const reader = new FileReader();
  60 + reader.readAsDataURL(file);
  61 + reader.onload = () => resolve(reader.result as string);
  62 + reader.onerror = (error) => reject(error);
  63 + });
  64 + const [fileList, setFileList] = useState<UploadFile[]>([]);
  65 + const handleCancel = () => setPreviewOpen(false);
  66 + const [messageApi, contextHolder] = message.useMessage();
  67 + const [form] = Form.useForm<{ name: string; company: string }>();
  68 + let subOrderIds: any[] = subOrders?.map((subOrder) => subOrder.id);
  69 + const [mainOrderId] = useState(data.id);
  70 +
  71 + const [afterSalesInfo, setAfterSalesInfo] = useState<any>();
  72 + const [prepaidProofImages, setPrepaidProofImages] = useState<any[]>([]);
  73 + /**
  74 + * 审核类型
  75 + */
  76 + function checkType(check: string) {
  77 + if (orderCheckType === check) {
  78 + return true;
  79 + }
  80 + return false;
  81 + }
  82 +
  83 + const getOrderAfterSalesInfo = async () => {
  84 + // let res = await postServiceOrderQueryAfterSalesInfoSnapshot({
  85 + // data: { subOrderIds: subOrderIds },
  86 + // });
  87 +
  88 + //附件
  89 + let annex = subOrders[0].afterSalesAnnexList;
  90 + let index = 1;
  91 + let annexLinks = annex?.map((f) => {
  92 + return (
  93 + <Button className="p-0 pr-1" type="link" key="key" href={f}>
  94 + {'附件' + index++}
  95 + </Button>
  96 + );
  97 + });
  98 +
  99 + console.log(annexLinks);
  100 +
  101 + setAfterSalesInfo(
  102 + <div className="my-5">
  103 + <Row gutter={[16, 24]}>
  104 + <Col span={6}>
  105 + <span className="text-[#333333]">售后方案</span>
  106 + </Col>
  107 + <Col span={18}>
  108 + {enumValueToLabel(
  109 + subOrders[0]?.afterSalesPlan,
  110 + AFTE_SALES_PLAN_OPTIONS,
  111 + )}
  112 + </Col>
  113 + <Col span={6}>
  114 + <span className="className='text-[#333333]'">售后原因</span>
  115 + </Col>
  116 + <Col span={18}>{subOrders[0]?.afterSalesNotes}</Col>
  117 + <Col span={6}>
  118 + <span className="className='text-[#333333]'">附件</span>
  119 + </Col>
  120 + <Col span={18}>{annexLinks}</Col>
  121 + </Row>
  122 + </div>,
  123 + );
  124 + };
  125 +
  126 + useEffect(() => {
  127 + if (checkType(CHECK_TYPE.CONFIRM_DELIVER)) {
  128 + setAPopoverTitle('确认发货');
  129 + }
  130 + getOrderAfterSalesInfo();
  131 +
  132 + let paymentReceiptsImagesList: any[] = [];
  133 + subOrders?.forEach((item: any) => {
  134 + if (item.paymentReceiptAnnexList) {
  135 + paymentReceiptsImagesList.push(...item.paymentReceiptAnnexList);
  136 + }
  137 + });
  138 + //去重
  139 + paymentReceiptsImagesList = [...new Set(paymentReceiptsImagesList)];
  140 + setPymentReceiptsImages(paymentReceiptsImagesList);
  141 +
  142 + //预存审核的凭证
  143 + let proofImages: any[] = [];
  144 + subOrders?.forEach((item) => {
  145 + let images = item.proofImages;
  146 + if (images !== null && images !== undefined) {
  147 + proofImages.push(...images);
  148 + }
  149 + });
  150 + setPrepaidProofImages(proofImages);
  151 + }, []);
  152 +
  153 + const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
  154 + //fileListObj得在change里变化,change的参数是已经处理过的file数组
  155 + //beforeUpload中的参数file是未处理过,还需要Base64拿到文件数据处理
  156 + fileListObj.current = newFileList;
  157 + setFileList(newFileList);
  158 + };
  159 +
  160 + /** 粘贴快捷键的回调 */
  161 + const onPaste = async (e: any) => {
  162 + /** 获取剪切板的数据clipboardData */
  163 + let clipboardData = e.clipboardData,
  164 + i = 0,
  165 + items,
  166 + item,
  167 + types;
  168 +
  169 + /** 为空判断 */
  170 + if (clipboardData) {
  171 + items = clipboardData.items;
  172 + if (!items) {
  173 + message.info('您的剪贴板中没有照片');
  174 + return;
  175 + }
  176 +
  177 + item = items[0];
  178 + types = clipboardData.types || [];
  179 + /** 遍历剪切板的数据 */
  180 + for (; i < types.length; i++) {
  181 + if (types[i] === 'Files') {
  182 + item = items[i];
  183 + break;
  184 + }
  185 + }
  186 +
  187 + /** 判断文件是否为图片 */
  188 + if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
  189 + const imgItem = item.getAsFile();
  190 + const newFileList = cloneDeep(fileListObj.current);
  191 + let filteredArray = newFileList.filter(
  192 + (obj) => obj.status !== 'removed',
  193 + ); //过滤掉状态为已删除的照片
  194 + const listItem = {
  195 + ...imgItem,
  196 + status: 'done',
  197 + url: await getBase64(imgItem),
  198 + originFileObj: imgItem,
  199 + };
  200 +
  201 + if (filteredArray.length >= COMFIR_RECEIPT_IMAGES_NUMBER) {
  202 + message.info('发货照片数量不能超过3');
  203 + return;
  204 + }
  205 + fileListObj.current = filteredArray;
  206 + filteredArray.push(listItem);
  207 + setFileList(filteredArray);
  208 + return;
  209 + }
  210 + }
  211 +
  212 + message.info('您的剪贴板中没有照片');
  213 + };
  214 + useEffect(() => {
  215 + //回显售后信息
  216 + // if (checkType(CHECK_TYPE.AFTER_SALES)) {
  217 + // getOrderAfterSalesInfo();
  218 + // }
  219 +
  220 + document.addEventListener('paste', onPaste);
  221 + return () => {
  222 + document.removeEventListener('paste', onPaste);
  223 + };
  224 + }, []);
  225 + const uploadButton = (
  226 + <div>
  227 + <PlusOutlined />
  228 + <div style={{ marginTop: 8 }}>上传凭证</div>
  229 + </div>
  230 + );
  231 + const handlePreview = async (file: UploadFile) => {
  232 + if (!file.url && !file.preview) {
  233 + file.preview = await getBase64(file.originFileObj as RcFile);
  234 + }
  235 + setPreviewImage(file.url || (file.preview as string));
  236 + setPreviewOpen(true);
  237 + setPreviewTitle(
  238 + file.name ||
  239 + file.originFileObj?.name ||
  240 + file.url!.substring(file.url!.lastIndexOf('/') + 1),
  241 + );
  242 + };
  243 +
  244 + const handleBeforeUpload = (file: any) => {
  245 + setFileList([...fileList, file]);
  246 + return false;
  247 + };
  248 +
  249 + const props: UploadProps = {
  250 + onRemove: (file) => {
  251 + const index = fileList.indexOf(file);
  252 + const newFileList = fileList.slice();
  253 + newFileList.splice(index, 1);
  254 + setFileList(newFileList);
  255 + },
  256 + beforeUpload: handleBeforeUpload,
  257 + listType: 'picture-card',
  258 + onPreview: handlePreview,
  259 + fileList,
  260 + onChange: handleChange,
  261 + accept: 'image/png, image/jpeg, image/png',
  262 + // action: '/api/service/order/fileProcess',
  263 + name: 'files',
  264 + headers: { Authorization: localStorage.getItem('token') },
  265 + };
  266 +
  267 + //仓库审核
  268 + async function doCheck(body: object) {
  269 + const data = await postServiceOrderAudit({
  270 + data: body,
  271 + });
  272 + if (data.result === RESPONSE_CODE.SUCCESS) {
  273 + message.success(data.message);
  274 + onClose();
  275 + }
  276 + }
  277 +
  278 + /**
  279 + *
  280 + * @param body 财务审核
  281 + */
  282 + async function doFinancailCheck(values: any, isAgree: boolean) {
  283 + if (fileList.length <= 0) {
  284 + message.error('凭证不能为空');
  285 + return;
  286 + }
  287 + messageApi.open({
  288 + type: 'loading',
  289 + content: '正在上传图片...',
  290 + duration: 0,
  291 + });
  292 + //附件处理
  293 + let formData = new FormData();
  294 + //附件处理
  295 + for (let file of fileList) {
  296 + if (file.originFileObj) {
  297 + formData.append('files', file.originFileObj as RcFile);
  298 + } else {
  299 + //有url的话取url(源文件),没url取thumbUrl。有url的时候thumbUrl是略缩图
  300 + if (file?.url === undefined || file?.url === null) {
  301 + formData.append(
  302 + 'files',
  303 + transImageFile(file?.thumbUrl),
  304 + file?.originFileObj?.name,
  305 + );
  306 + } else {
  307 + formData.append(
  308 + 'files',
  309 + transImageFile(file?.url),
  310 + file?.originFileObj?.name,
  311 + );
  312 + }
  313 + }
  314 + }
  315 + let res = await postServiceOrderFileProcess({
  316 + data: formData,
  317 + });
  318 + messageApi.destroy();
  319 + if (res.result === RESPONSE_CODE.SUCCESS) {
  320 + message.success('上传成功!');
  321 +
  322 + let fileUrls = res?.data?.map((item) => {
  323 + return { url: item };
  324 + });
  325 + //财务审核
  326 + const data = await postServiceOrderFinanceCheckOrder({
  327 + data: {
  328 + ...values,
  329 + checkNotes: values.name,
  330 + ids: subOrderIds,
  331 + checkPassOrReject: isAgree,
  332 + invoicingCheckAnnex: fileUrls,
  333 + },
  334 + });
  335 + if (data.result === RESPONSE_CODE.SUCCESS) {
  336 + message.success(data.message);
  337 + onClose();
  338 + }
  339 + } else {
  340 + message.success('上传失败');
  341 + }
  342 + }
  343 +
  344 + /**
  345 + *
  346 + * @param body 售后审核
  347 + */
  348 + async function doAfterSalesCheck(body: object) {
  349 + const data = await postServiceOrderAfterSalesCheck({
  350 + data: body,
  351 + });
  352 + if (data.result === RESPONSE_CODE.SUCCESS) {
  353 + message.success(data.message);
  354 + onClose();
  355 + }
  356 + }
  357 +
  358 + /**
  359 + *
  360 + * @param body 领导审核
  361 + */
  362 + async function doLeaderCheck(body: object) {
  363 + const data = await postServiceOrderLeaderAudit({
  364 + data: body,
  365 + });
  366 + if (data.result === RESPONSE_CODE.SUCCESS) {
  367 + message.success(data.message);
  368 + onClose();
  369 + }
  370 + }
  371 +
  372 + /**
  373 + * 预存审核
  374 + * @param body
  375 + */
  376 + async function doPrepaidAudit(body: any) {
  377 + const data = await postPrepaidAudit({
  378 + data: body,
  379 + });
  380 + if (data.result === RESPONSE_CODE.SUCCESS) {
  381 + message.success(data.message);
  382 + onClose();
  383 + }
  384 + }
  385 +
  386 + function computeType() {
  387 + let type: string = '';
  388 + if (checkType(CHECK_TYPE.CONFIRM_DELIVER)) {
  389 + type = 'confirm_deliver';
  390 + }
  391 + if (checkType(CHECK_TYPE.WEARHOUSE_KEEPER)) {
  392 + type = 'warehouse_audit';
  393 + }
  394 + if (checkType(CHECK_TYPE.WAITING_FOR_POST_AUDIT)) {
  395 + type = 'post_audit';
  396 + }
  397 + if (checkType(CHECK_TYPE.NODE_OPERATING_AUDIT)) {
  398 + type = 'node_operating_audit';
  399 + }
  400 + if (checkType(CHECK_TYPE.MODIFY_LEADER_AUDIT)) {
  401 + type = 'modify_leader_audit';
  402 + }
  403 + if (checkType(CHECK_TYPE.URGENT_INVOICE_AUDITING)) {
  404 + type = 'urgent_invoice_audit';
  405 + }
  406 + if (checkType(CHECK_TYPE.PAYMENT_RECEIPTS_AUDIT)) {
  407 + type = 'payment_receipt_audit';
  408 + }
  409 + if (checkType(CHECK_TYPE.CONFIRM_REISSUE)) {
  410 + type = 'confirm_reissue';
  411 + }
  412 + if (checkType(CHECK_TYPE.CREDIT_AUDIT)) {
  413 + type = 'credit_audit';
  414 + }
  415 + if (checkType(CHECK_TYPE.URGENT_INVOICE_AUDITING_OLD)) {
  416 + type = 'urgent_invoice_audit_old';
  417 + }
  418 + if (checkType(CHECK_TYPE.CONFIRM_REISSUE_OLD)) {
  419 + type = 'confirm_reissue_old';
  420 + }
  421 + return type;
  422 + }
  423 +
  424 + return (
  425 + <>
  426 + <ModalForm<{
  427 + name: string;
  428 + company: string;
  429 + }>
  430 + width={500}
  431 + open
  432 + title={aPopoverTitle}
  433 + form={form}
  434 + autoFocusFirstInput
  435 + modalProps={{
  436 + okText: '通过',
  437 + cancelText: '驳回',
  438 + destroyOnClose: true,
  439 + onCancel: () => {
  440 + setCheckVisible(false);
  441 + },
  442 + }}
  443 + submitter={{
  444 + render: (props, defaultDoms) => {
  445 + let myDoms = [];
  446 + if (!checkType(CHECK_TYPE.CONFIRM_DELIVER)) {
  447 + myDoms.push(
  448 + <Button
  449 + key="驳回"
  450 + onClick={async () => {
  451 + if (checkType(CHECK_TYPE.AFTER_SALES)) {
  452 + doAfterSalesCheck({
  453 + applyType: 'after-sales',
  454 + isAfterSalesSuccess: false,
  455 + subOrderIds: subOrderIds,
  456 + mainId: mainOrderId,
  457 + afterSalesRejectionNotes: form.getFieldValue('name'),
  458 + });
  459 + return;
  460 + }
  461 +
  462 + if (checkType(CHECK_TYPE.FINALCIAL)) {
  463 + let values = { name: form.getFieldValue('name') };
  464 + doFinancailCheck(values, false);
  465 + return;
  466 + }
  467 +
  468 + if (checkType(CHECK_TYPE.LEADER_AUDIT)) {
  469 + doLeaderCheck({
  470 + pass: false,
  471 + subOrderIds: subOrderIds,
  472 + reason: form.getFieldValue('name'),
  473 + });
  474 + return;
  475 + }
  476 +
  477 + if (checkType(CHECK_TYPE.MODIFY_APPLY_WAIT_FOR_AUDIT)) {
  478 + doAfterSalesCheck({
  479 + applyType: 'order-change-normal',
  480 + isAfterSalesSuccess: false,
  481 + subOrderIds: subOrderIds,
  482 + mainId: mainOrderId,
  483 + afterSalesRejectionNotes: form.getFieldValue('name'),
  484 + });
  485 + return;
  486 + }
  487 +
  488 + //预存审核,先暂时共用同一个审核弹窗
  489 + if (checkType(CHECK_TYPE.PREPAID_AUDIT)) {
  490 + return doPrepaidAudit({
  491 + pass: false,
  492 + ids: subOrderIds,
  493 + auditNotes: form.getFieldValue('name'),
  494 + });
  495 + }
  496 +
  497 + let type = '';
  498 + type = computeType();
  499 + console.log('type:' + type);
  500 + doCheck({
  501 + pass: false,
  502 + subOrderIds: subOrderIds,
  503 + type: type,
  504 + notes: form.getFieldValue('name'),
  505 + });
  506 + }}
  507 + >
  508 + 驳回
  509 + </Button>,
  510 + );
  511 + }
  512 +
  513 + //如果是仓库审核,那么显示这个外部采购
  514 + if (checkType(CHECK_TYPE.WEARHOUSE_KEEPER)) {
  515 + myDoms.push(
  516 + <Button
  517 + key="外部采购"
  518 + onClick={async () => {
  519 + let res = await postServiceOrderToProcureAudit({
  520 + data: {
  521 + subOrderIds: subOrderIds,
  522 + },
  523 + });
  524 +
  525 + if (res && res.result === RESPONSE_CODE.SUCCESS) {
  526 + message.success(res.message);
  527 + onClose();
  528 + }
  529 + }}
  530 + >
  531 + 外部采购
  532 + </Button>,
  533 + );
  534 + }
  535 +
  536 + //确认
  537 + myDoms.push(defaultDoms[1]);
  538 + return myDoms;
  539 + },
  540 + }}
  541 + submitTimeout={2000}
  542 + onFinish={async (values) => {
  543 + if (checkType(CHECK_TYPE.AFTER_SALES)) {
  544 + //审核通过
  545 + return doAfterSalesCheck({
  546 + applyType: 'after-sales',
  547 + isAfterSalesSuccess: true,
  548 + subOrderIds: subOrderIds,
  549 + mainId: mainOrderId,
  550 + afterSalesRejectionNotes: values.name,
  551 + });
  552 + }
  553 + console.log('h');
  554 + if (checkType(CHECK_TYPE.FINALCIAL)) {
  555 + doFinancailCheck(values, true);
  556 + return;
  557 + }
  558 +
  559 + if (checkType(CHECK_TYPE.LEADER_AUDIT)) {
  560 + doLeaderCheck({
  561 + pass: true,
  562 + subOrderIds: subOrderIds,
  563 + reason: values.name,
  564 + });
  565 + return;
  566 + }
  567 +
  568 + if (checkType(CHECK_TYPE.MODIFY_APPLY_WAIT_FOR_AUDIT)) {
  569 + //审核通过
  570 + return doAfterSalesCheck({
  571 + applyType: 'order-change-normal',
  572 + isAfterSalesSuccess: true,
  573 + subOrderIds: subOrderIds,
  574 + mainId: mainOrderId,
  575 + afterSalesRejectionNotes: values.name,
  576 + });
  577 + }
  578 +
  579 + //预存审核,先暂时共用同一个审核弹窗
  580 + if (checkType(CHECK_TYPE.PREPAID_AUDIT)) {
  581 + return doPrepaidAudit({
  582 + pass: true,
  583 + ids: subOrderIds,
  584 + auditNotes: form.getFieldValue('name'),
  585 + });
  586 + }
  587 +
  588 + let type = '';
  589 + type = computeType();
  590 + doCheck({
  591 + ...values,
  592 + pass: true,
  593 + subOrderIds: subOrderIds,
  594 + type: type,
  595 + notes: form.getFieldValue('name'),
  596 + });
  597 + }}
  598 + onOpenChange={setCheckVisible}
  599 + >
  600 + {checkType(CHECK_TYPE.AFTER_SALES) ? (
  601 + <>
  602 + {afterSalesInfo}
  603 + <Button
  604 + className="px-0"
  605 + type="link"
  606 + onClick={() => {
  607 + console.log(data);
  608 + openOrderDrawer('after-sales-check', mainOrderId);
  609 + }}
  610 + >
  611 + 查看旧订单
  612 + </Button>
  613 + </>
  614 + ) : (
  615 + ''
  616 + )}
  617 +
  618 + {checkType(CHECK_TYPE.PAYMENT_RECEIPTS_AUDIT) ? (
  619 + <>
  620 + <Divider orientation="center">
  621 + <span className="text-sm">回款凭证</span>
  622 + </Divider>
  623 + <Image.PreviewGroup
  624 + className="mr-10"
  625 + preview={{
  626 + onChange: (current, prev) =>
  627 + console.log(`current index: ${current}, prev index: ${prev}`),
  628 + }}
  629 + >
  630 + {paymentReceiptsImages.map((url) => (
  631 + <>
  632 + <Image width={120} src={url} /> <Divider type="vertical" />
  633 + </>
  634 + ))}
  635 + </Image.PreviewGroup>
  636 + <Divider></Divider>
  637 + </>
  638 + ) : (
  639 + ''
  640 + )}
  641 +
  642 + {checkType(CHECK_TYPE.PREPAID_AUDIT) && (
  643 + <>
  644 + <Divider orientation="center">
  645 + <span className="text-sm">凭证</span>
  646 + </Divider>
  647 + <Image.PreviewGroup
  648 + className="mr-10"
  649 + preview={{
  650 + onChange: (current, prev) =>
  651 + console.log(`current index: ${current}, prev index: ${prev}`),
  652 + }}
  653 + >
  654 + {prepaidProofImages.map((url) => (
  655 + <>
  656 + <Image width={120} src={url} /> <Divider type="vertical" />
  657 + </>
  658 + ))}
  659 + </Image.PreviewGroup>
  660 + <Divider></Divider>
  661 + </>
  662 + )}
  663 +
  664 + {checkType('prepaidAudit') ? (
  665 + <div>请特别注意手机号码和充值金额。</div>
  666 + ) : (
  667 + <div>请特别注意订单总金额与订单金额。</div>
  668 + )}
  669 + {!checkType(CHECK_TYPE.CONFIRM_DELIVER) ? (
  670 + <ProFormTextArea
  671 + width="lg"
  672 + name="name"
  673 + placeholder="若驳回,请填写驳回理由"
  674 + />
  675 + ) : (
  676 + <></>
  677 + )}
  678 + {checkType(CHECK_TYPE.FINALCIAL) ? (
  679 + <>
  680 + <ProFormText
  681 + width="md"
  682 + name="bankStatementSerialNumbersText"
  683 + label="流水号"
  684 + rules={[
  685 + {
  686 + required: true,
  687 + message: '请输入流水号!',
  688 + },
  689 + ]}
  690 + placeholder={'多个流水号用逗号隔开'}
  691 + />
  692 + <div className="pb-4 text-xs decoration-gray-50">
  693 + 可复制照片粘贴
  694 + </div>
  695 + <Upload {...props}>
  696 + {fileList.length < COMFIR_RECEIPT_IMAGES_NUMBER
  697 + ? uploadButton
  698 + : ''}
  699 + </Upload>
  700 + </>
  701 + ) : (
  702 + ''
  703 + )}
  704 + {checkType(CHECK_TYPE.CONFIRM_REISSUE) && (
  705 + <>
  706 + <InvoiceSubOrderInfoTable
  707 + subOrderIds={subOrderIds}
  708 + ></InvoiceSubOrderInfoTable>
  709 + </>
  710 + )}
  711 + {checkType(CHECK_TYPE.URGENT_INVOICE_AUDITING) ? (
  712 + <>
  713 + <ProList
  714 + rowKey="id"
  715 + headerTitle="发票信息"
  716 + metas={{
  717 + title: {
  718 + dataIndex: 'name',
  719 + },
  720 + avatar: {
  721 + dataIndex: 'image',
  722 + editable: false,
  723 + },
  724 + description: {
  725 + dataIndex: 'desc',
  726 + },
  727 + subTitle: {
  728 + render: () => {
  729 + return (
  730 + <Space size={0}>
  731 + <Tag color="blue">Ant Design</Tag>
  732 + <Tag color="#5BD8A6">TechUI</Tag>
  733 + </Space>
  734 + );
  735 + },
  736 + },
  737 + actions: {
  738 + render: (text, row, index, action) => [
  739 + <a
  740 + onClick={() => {
  741 + action?.startEditable(row.id);
  742 + }}
  743 + key="link"
  744 + >
  745 + 编辑
  746 + </a>,
  747 + ],
  748 + },
  749 + }}
  750 + ></ProList>
  751 + </>
  752 + ) : (
  753 + ''
  754 + )}
  755 + </ModalForm>
  756 +
  757 + <Modal
  758 + open={previewOpen}
  759 + title={previewTitle}
  760 + footer={null}
  761 + onCancel={handleCancel}
  762 + >
  763 + <img alt="图片预览" style={{ width: '100%' }} src={previewImage} />
  764 + </Modal>
  765 + {contextHolder}
  766 + </>
  767 + );
  768 +};
... ...
src/pages/Order/FeedBack/ConfirmReceiptModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceOrderConfirmReceipt } from '@/services';
  3 +import { PlusOutlined } from '@ant-design/icons';
  4 +import { Button, Modal, Upload, message } from 'antd';
  5 +import { RcFile, UploadFile, UploadProps } from 'antd/es/upload';
  6 +import { cloneDeep } from 'lodash';
  7 +import { useEffect, useRef, useState } from 'react';
  8 +import { COMFIR_RECEIPT_IMAGES_NUMBER } from './constant';
  9 +export default ({ data, onClose }) => {
  10 + const subIds = data?.map((item) => {
  11 + return item.id;
  12 + });
  13 + // const [form] = Form.useForm<{ name: string; company: string }>();
  14 + const [previewOpen, setPreviewOpen] = useState(false);
  15 + const [previewImage, setPreviewImage] = useState('');
  16 + const [previewTitle, setPreviewTitle] = useState('');
  17 + const fileListObj = useRef<UploadFile[]>([]); //使用引用类型,使得在useEffect里面设置监听事件后,不用更新监听事件也能保持obj与外界一致
  18 + const getBase64 = (file: RcFile): Promise<string> =>
  19 + new Promise((resolve, reject) => {
  20 + const reader = new FileReader();
  21 + reader.readAsDataURL(file);
  22 + reader.onload = () => resolve(reader.result as string);
  23 + reader.onerror = (error) => reject(error);
  24 + });
  25 + const [fileList, setFileList] = useState<UploadFile[]>([]);
  26 + const [uploading, setUploading] = useState(false);
  27 + const handleCancel = () => setPreviewOpen(false);
  28 +
  29 + const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
  30 + //fileListObj得在change里变化,change的参数是已经处理过的file数组
  31 + //beforeUpload中的参数file是未处理过,还需要Base64拿到文件数据处理
  32 + fileListObj.current = newFileList;
  33 + setFileList(newFileList);
  34 + };
  35 +
  36 + /** 粘贴快捷键的回调 */
  37 + const onPaste = async (e: any) => {
  38 + /** 获取剪切板的数据clipboardData */
  39 + let clipboardData = e.clipboardData,
  40 + i = 0,
  41 + items,
  42 + item,
  43 + types;
  44 +
  45 + /** 为空判断 */
  46 + if (clipboardData) {
  47 + items = clipboardData.items;
  48 + if (!items) {
  49 + message.info('您的剪贴板中没有照片');
  50 + return;
  51 + }
  52 +
  53 + item = items[0];
  54 + types = clipboardData.types || [];
  55 + /** 遍历剪切板的数据 */
  56 + for (; i < types.length; i++) {
  57 + if (types[i] === 'Files') {
  58 + item = items[i];
  59 + break;
  60 + }
  61 + }
  62 +
  63 + /** 判断文件是否为图片 */
  64 + if (item && item.kind === 'file' && item.type.match(/^image\//i)) {
  65 + const imgItem = item.getAsFile();
  66 + const newFileList = cloneDeep(fileListObj.current);
  67 + let filteredArray = newFileList.filter(
  68 + (obj) => obj.status !== 'removed',
  69 + ); //过滤掉状态为已删除的照片
  70 + const listItem = {
  71 + ...imgItem,
  72 + status: 'done',
  73 + url: await getBase64(imgItem),
  74 + originFileObj: imgItem,
  75 + };
  76 +
  77 + if (filteredArray.length >= COMFIR_RECEIPT_IMAGES_NUMBER) {
  78 + message.info('发货凭证照片数量不能超过3');
  79 + return;
  80 + }
  81 + fileListObj.current = filteredArray;
  82 + filteredArray.push(listItem);
  83 + setFileList(filteredArray);
  84 + return;
  85 + }
  86 + }
  87 +
  88 + message.info('您的剪贴板中没有照片');
  89 + };
  90 + useEffect(() => {
  91 + document.addEventListener('paste', onPaste);
  92 + return () => {
  93 + document.removeEventListener('paste', onPaste);
  94 + };
  95 + }, []);
  96 + const uploadButton = (
  97 + <div>
  98 + <PlusOutlined />
  99 + <div style={{ marginTop: 8 }}>上传凭证</div>
  100 + </div>
  101 + );
  102 + const handlePreview = async (file: UploadFile) => {
  103 + if (!file.url && !file.preview) {
  104 + file.preview = await getBase64(file.originFileObj as RcFile);
  105 + }
  106 + setPreviewImage(file.url || (file.preview as string));
  107 + setPreviewOpen(true);
  108 + setPreviewTitle(
  109 + file.name ||
  110 + file.originFileObj?.name ||
  111 + file.url!.substring(file.url!.lastIndexOf('/') + 1),
  112 + );
  113 + };
  114 +
  115 + const handleUpload = async () => {
  116 + const formData = new FormData();
  117 + fileList.forEach((file) => {
  118 + //originFileObj二进制文件
  119 + formData.append('files', file.originFileObj as RcFile);
  120 + });
  121 + // console.log(fileList[0] as RcFile)
  122 + // formData.append('file', fileList[0] as RcFile);
  123 + formData.append('subIds', subIds);
  124 + setUploading(true);
  125 + // You can use any AJAX library you like
  126 + const res = await postServiceOrderConfirmReceipt({
  127 + data: formData,
  128 + headers: {
  129 + 'Content-Type':
  130 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  131 + },
  132 + });
  133 +
  134 + if (res.result === RESPONSE_CODE.SUCCESS) {
  135 + message.success(res.message);
  136 + onClose();
  137 + }
  138 +
  139 + setUploading(false);
  140 + };
  141 +
  142 + const props: UploadProps = {
  143 + onRemove: (file) => {
  144 + const index = fileList.indexOf(file);
  145 + const newFileList = fileList.slice();
  146 + newFileList.splice(index, 1);
  147 + setFileList(newFileList);
  148 + },
  149 + beforeUpload: (file) => {
  150 + setFileList([...fileList, file]);
  151 + return false;
  152 + },
  153 + listType: 'picture-card',
  154 + onPreview: handlePreview,
  155 + fileList,
  156 + onChange: handleChange,
  157 + accept: 'image/png, image/jpeg, image/png',
  158 + };
  159 +
  160 + return (
  161 + <>
  162 + <Modal
  163 + width={500}
  164 + open
  165 + title="确认收货"
  166 + footer={[
  167 + <Button key="cancel" onClick={onClose}>
  168 + 取消
  169 + </Button>,
  170 + <Button
  171 + type="primary"
  172 + key="ok"
  173 + onClick={handleUpload}
  174 + disabled={fileList.length === 0}
  175 + loading={uploading}
  176 + >
  177 + {uploading ? '上传中' : '提交'}
  178 + </Button>,
  179 + ]}
  180 + onCancel={async () => {
  181 + onClose();
  182 + }}
  183 + >
  184 + <div className="pt-4 font-semibold">请将买家确认收货的凭证照片上传</div>
  185 + <div className="pb-4 text-xs decoration-gray-50">可复制照片粘贴</div>
  186 + <Upload {...props}>
  187 + {fileList.length < COMFIR_RECEIPT_IMAGES_NUMBER ? uploadButton : ''}
  188 + </Upload>
  189 + </Modal>
  190 + <Modal
  191 + open={previewOpen}
  192 + title={previewTitle}
  193 + footer={null}
  194 + onCancel={handleCancel}
  195 + >
  196 + <img alt="图片预览" style={{ width: '100%' }} src={previewImage} />
  197 + </Modal>
  198 + </>
  199 + );
  200 +};
... ...
src/pages/Order/FeedBack/DeliverInfoDrawer.tsx 0 → 100644
  1 +import { postDistrictSelOrderProvince } from '@/services';
  2 +import { enumValueToLabel } from '@/utils';
  3 +import { getReceivingCompanyOptions } from '@/utils/order';
  4 +import { Col, Drawer, Row } from 'antd';
  5 +import { useEffect, useState } from 'react';
  6 +import { PAYEE_OPTIONS } from './constant';
  7 +
  8 +export default ({ data, onClose }) => {
  9 + const [province, setProvince] = useState('');
  10 + const [city, setCity] = useState('');
  11 + const [district, setDistrict] = useState('');
  12 +
  13 + useEffect(() => {
  14 + const fetchData = async () => {
  15 + if (data.id !== undefined) {
  16 + const resp = await postDistrictSelOrderProvince({
  17 + data: data.id,
  18 + });
  19 + if (resp && resp.data) {
  20 + if (resp.data.province) {
  21 + setProvince(resp.data.province);
  22 + }
  23 + if (resp.data.city) {
  24 + setCity(resp.data.city);
  25 + }
  26 + if (resp.data.district) {
  27 + setDistrict(resp.data.district);
  28 + }
  29 + }
  30 + }
  31 + };
  32 +
  33 + fetchData();
  34 + }, [data.id]);
  35 + return (
  36 + <>
  37 + <Drawer
  38 + width={500}
  39 + title="基本信息"
  40 + placement="right"
  41 + onClose={onClose}
  42 + open
  43 + >
  44 + <Row gutter={[16, 24]}>
  45 + <Col span={6}>
  46 + <span className="text-[#333333]">收货人</span>
  47 + </Col>
  48 + <Col span={18}>{data.customerName}</Col>
  49 + <Col span={6}>
  50 + <span className="className='text-[#333333]'">联系方式</span>
  51 + </Col>
  52 + <Col span={18}>{data.customerContactNumber}</Col>
  53 + <Col span={6}>
  54 + <span className="className='text-[#333333]'">省市区</span>
  55 + </Col>
  56 + <Col span={18}>
  57 + {province}&nbsp;{city}&nbsp;{district}
  58 + </Col>
  59 + <Col span={6}>
  60 + <span className="className='text-[#333333]'">收货地址</span>
  61 + </Col>
  62 + <Col span={18}>{data.customerShippingAddress}</Col>
  63 + <Col span={6}>
  64 + <span className="className='text-[#333333]'">课题组老师</span>
  65 + </Col>
  66 + <Col span={18}>{data.institutionContactName}</Col>
  67 + <Col span={6}>
  68 + <span className="className='text-[#333333]'">单位名称</span>
  69 + </Col>
  70 + <Col span={18}>{data.institution}</Col>
  71 + <Col span={6}>
  72 + <span className="className='text-[#333333]'">开户银行</span>
  73 + </Col>
  74 + <Col span={18}>{data.bank}</Col>
  75 +
  76 + <Col span={6}>
  77 + <span className="className='text-[#333333]'">开票收款单位</span>
  78 + </Col>
  79 + <Col span={18}>
  80 + {enumValueToLabel(
  81 + data.receivingCompany,
  82 + getReceivingCompanyOptions(PAYEE_OPTIONS),
  83 + )}
  84 + </Col>
  85 +
  86 + <Col span={6}>
  87 + <span className="className='text-[#333333]'">银行账号</span>
  88 + </Col>
  89 + <Col span={18}>{data.bankAccountNumber}</Col>
  90 + <Col span={6}>
  91 + <span className="className='text-[#333333]'">开票识别号</span>
  92 + </Col>
  93 + <Col span={18}>{data.invoiceIdentificationNumber}</Col>
  94 + </Row>
  95 + </Drawer>
  96 + </>
  97 + );
  98 +};
... ...
src/pages/Order/FeedBack/DeliverModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import {
  3 + postServiceOrderProcureSend,
  4 + postServiceOrderSendProduct,
  5 + postServiceOrderSupplierSendOrder,
  6 +} from '@/services';
  7 +import { enumToSelect } from '@/utils';
  8 +import {
  9 + ProColumns,
  10 + ProForm,
  11 + ProFormSelect,
  12 + ProFormText,
  13 + ProTable,
  14 +} from '@ant-design/pro-components';
  15 +import {
  16 + Button,
  17 + Col,
  18 + Flex,
  19 + Input,
  20 + InputNumber,
  21 + Modal,
  22 + Row,
  23 + Select,
  24 + message,
  25 +} from 'antd';
  26 +import { cloneDeep } from 'lodash';
  27 +import { useEffect, useRef, useState } from 'react';
  28 +import { CHECK_TYPE, LOGISTICS_STATUS_OPTIONS } from '../constant';
  29 +
  30 +const DeliverModal = ({
  31 + data: propsData,
  32 + isSendProduct,
  33 + setVisible,
  34 + sendType,
  35 + onClose,
  36 +}) => {
  37 + const [data, setData] = useState(propsData || {});
  38 + const form = useRef();
  39 +
  40 + /**
  41 + * 是供应商发货还是普通发货
  42 + * @param typeString
  43 + * @returns
  44 + */
  45 + function optType(typeString: string) {
  46 + if (sendType === typeString) {
  47 + return true;
  48 + }
  49 +
  50 + return false;
  51 + }
  52 +
  53 + useEffect(() => {
  54 + setData(propsData);
  55 + }, [propsData]);
  56 +
  57 + const handleChange = (key: string, index: number, obj: any) => {
  58 + const newData = cloneDeep(data);
  59 + if (typeof obj !== 'object') {
  60 + newData[index][key] = obj;
  61 + } else {
  62 + newData[index][key] = obj.target?.value;
  63 + }
  64 + setData(newData);
  65 + };
  66 + const columns: ProColumns<any>[] = [
  67 + {
  68 + title: 'ID',
  69 + dataIndex: 'id',
  70 + width: 120,
  71 + render: (_, record) => <Input value={record.id} disabled />,
  72 + },
  73 + {
  74 + title: '商品编号',
  75 + dataIndex: 'productCode',
  76 + width: 120,
  77 + render: (_, record) => <Input value={record.productCode} disabled />,
  78 + },
  79 + {
  80 + title: '商品名称',
  81 + dataIndex: 'productName',
  82 + width: 120,
  83 + render: (_, record) => <Input value={record.productName} disabled />,
  84 + },
  85 + {
  86 + title: '商品参数',
  87 + dataIndex: 'parameters',
  88 + width: 80,
  89 + render: (_, record) => <Input value={record.parameters} disabled />,
  90 + },
  91 + {
  92 + title: '商品数量',
  93 + dataIndex: 'status',
  94 + render: (_, record) => <InputNumber value={record.quantity} disabled />,
  95 + },
  96 + {
  97 + title: '包裹数量',
  98 + dataIndex: 'packageNumber',
  99 + render: (_, record, index) => (
  100 + <InputNumber
  101 + min={1}
  102 + value={record.packageNumber}
  103 + defaultValue={1}
  104 + onChange={(value) => handleChange('packageNumber', index, value)}
  105 + />
  106 + ),
  107 + },
  108 + {
  109 + title: '物流方式',
  110 + key: 'logisticsMethod',
  111 + render: (_, record, index) => (
  112 + <Select
  113 + style={{ minWidth: 150 }}
  114 + placeholder="请输入物流方式"
  115 + value={record.logisticsMethod}
  116 + options={enumToSelect(LOGISTICS_STATUS_OPTIONS)}
  117 + onChange={(value) => {
  118 + handleChange('logisticsMethod', index, value); //修改时更改record数据
  119 + if (value === 'OTHER_LOGISTICS') {
  120 + message.info(
  121 + '您选择的是[其他物流方式],请将该物流方式写在备注中',
  122 + );
  123 + }
  124 + }}
  125 + />
  126 + ),
  127 + },
  128 + {
  129 + title: '物流单号',
  130 + key: 'serialNumber',
  131 + render: (_, record, index) => (
  132 + <Input
  133 + placeholder="请输入物流单号"
  134 + value={record.serialNumber}
  135 + onChange={(value) => {
  136 + handleChange('serialNumber', index, value);
  137 + }}
  138 + />
  139 + ),
  140 + },
  141 + {
  142 + title: '物流备注',
  143 + dataIndex: 'packageNumber',
  144 + render: (_, record, index) => (
  145 + <Input.TextArea
  146 + value={record.logisticsNotes}
  147 + onChange={(value) => handleChange('logisticsNotes', index, value)}
  148 + />
  149 + ),
  150 + },
  151 + ];
  152 +
  153 + return (
  154 + <Modal
  155 + open
  156 + width={1000}
  157 + title={isSendProduct ? '发货' : '修改发货信息'}
  158 + onOk={async () => {
  159 + //请求体封装
  160 + let list = data.map((item) => {
  161 + return {
  162 + id: item.id,
  163 + logisticsMethod: item.logisticsMethod,
  164 + serialNumber: item.serialNumber,
  165 + packageNumber:
  166 + item.packageNumber === null || item.packageNumber === undefined
  167 + ? 1
  168 + : item.packageNumber,
  169 + logisticsNotes: item.logisticsNotes,
  170 + };
  171 + });
  172 +
  173 + for (let item of list) {
  174 + let method = item.logisticsMethod;
  175 + let notes = item.logisticsNotes;
  176 + if (
  177 + method === 'OTHER_LOGISTICS' &&
  178 + (notes === '' || notes === undefined)
  179 + ) {
  180 + message.error(
  181 + '请检查:物流方式为[其他物流方式]的记录中,物流备注不能为空!请将实际的物流方式填写在备注中!',
  182 + );
  183 + return;
  184 + }
  185 + }
  186 + let body = { id: data[0].mainOrderId, list: list, flag: false };
  187 + if (isSendProduct) {
  188 + body.flag = true;
  189 + }
  190 + //发货请求
  191 + let res;
  192 + if (optType(CHECK_TYPE.SUPPLIER)) {
  193 + res = await postServiceOrderSupplierSendOrder({ data: body });
  194 + } else if (optType(CHECK_TYPE.PROCURE)) {
  195 + res = await postServiceOrderProcureSend({ data: body });
  196 + } else {
  197 + res = await postServiceOrderSendProduct({ data: body });
  198 + }
  199 +
  200 + if (res.result === RESPONSE_CODE.SUCCESS) {
  201 + message.success(res.message);
  202 + onClose();
  203 + }
  204 + }}
  205 + onCancel={() => {
  206 + setVisible(false);
  207 + }}
  208 + footer={[
  209 + <Button
  210 + key="back"
  211 + onClick={() => {
  212 + setVisible(false);
  213 + }}
  214 + >
  215 + 取消
  216 + </Button>,
  217 + <Button
  218 + key="selfDeliver"
  219 + type="primary"
  220 + onClick={async () => {
  221 + //请求体封装
  222 + let list = data.map((item) => {
  223 + return {
  224 + id: item.id,
  225 + deliverType: 'SELF_DELIVER',
  226 + };
  227 + });
  228 +
  229 + let body = { id: data[0].mainOrderId, list: list, flag: false };
  230 + if (isSendProduct) {
  231 + body.flag = true;
  232 + }
  233 + //发货请求
  234 + let res;
  235 + if (optType(CHECK_TYPE.SUPPLIER)) {
  236 + res = await postServiceOrderSupplierSendOrder({ data: body });
  237 + } else if (optType(CHECK_TYPE.PROCURE)) {
  238 + res = await postServiceOrderProcureSend({ data: body });
  239 + } else {
  240 + res = await postServiceOrderSendProduct({ data: body });
  241 + }
  242 +
  243 + if (res.result === RESPONSE_CODE.SUCCESS) {
  244 + message.success(res.message);
  245 + onClose();
  246 + }
  247 + }}
  248 + >
  249 + 自行派送
  250 + </Button>,
  251 + <Button
  252 + key="submit"
  253 + type="primary"
  254 + onClick={async () => {
  255 + //请求体封装
  256 + let list = data.map((item) => {
  257 + return {
  258 + id: item.id,
  259 + logisticsMethod: item.logisticsMethod,
  260 + serialNumber: item.serialNumber,
  261 + packageNumber:
  262 + item.packageNumber === null ||
  263 + item.packageNumber === undefined
  264 + ? 1
  265 + : item.packageNumber,
  266 + logisticsNotes: item.logisticsNotes,
  267 + };
  268 + });
  269 +
  270 + for (let item of list) {
  271 + let method = item.logisticsMethod;
  272 + let notes = item.logisticsNotes;
  273 + if (
  274 + method === 'OTHER_LOGISTICS' &&
  275 + (notes === '' || notes === undefined)
  276 + ) {
  277 + message.error(
  278 + '请检查:物流方式为[其他物流方式]的记录中,物流备注不能为空!请将实际的物流方式填写在备注中!',
  279 + );
  280 + return;
  281 + }
  282 + }
  283 + let body = { id: data[0].mainOrderId, list: list, flag: false };
  284 + if (isSendProduct) {
  285 + body.flag = true;
  286 + }
  287 + //发货请求
  288 + let res;
  289 + if (optType(CHECK_TYPE.SUPPLIER)) {
  290 + res = await postServiceOrderSupplierSendOrder({ data: body });
  291 + } else if (optType(CHECK_TYPE.PROCURE)) {
  292 + res = await postServiceOrderProcureSend({ data: body });
  293 + } else {
  294 + res = await postServiceOrderSendProduct({ data: body });
  295 + }
  296 +
  297 + if (res.result === RESPONSE_CODE.SUCCESS) {
  298 + message.success(res.message);
  299 + onClose();
  300 + }
  301 + }}
  302 + >
  303 + 确认
  304 + </Button>,
  305 + ]}
  306 + >
  307 + <Flex vertical>
  308 + <strong>将物流方式和物流单号更新到下方所有订单</strong>
  309 + <span className="text-[red] py-1">
  310 + 选择【其他物流方式】时,需要将对应的物流方式填写在备注中。例如:如果发圆通快递,系统上没有这个选项,就需要选【其他物流方式】,然后把“圆通快递”填在备注上。
  311 + </span>
  312 + </Flex>
  313 +
  314 + <ProForm
  315 + layout="inline"
  316 + submitter={false}
  317 + className="mb-8"
  318 + formRef={form}
  319 + >
  320 + <Row gutter={[0, 6]}>
  321 + <Col>
  322 + <ProFormSelect
  323 + placeholder="请输入物流方式"
  324 + name="logisticsMethod"
  325 + width="sm"
  326 + label="物流方式"
  327 + options={enumToSelect(LOGISTICS_STATUS_OPTIONS)}
  328 + />
  329 + <ProFormText name="logisticsNotes" label="物流备注"></ProFormText>
  330 + </Col>
  331 + <Col>
  332 + <ProFormText name="serialNumber" label="物流单号"></ProFormText>
  333 + </Col>
  334 + </Row>
  335 +
  336 + <Button
  337 + type="primary"
  338 + onClick={() => {
  339 + const values = form.current.getFieldsValue();
  340 + if (values.logisticsMethod === 'OTHER_LOGISTICS') {
  341 + message.info(
  342 + '自动填充成功!您选择的是其他物流方式,请将物流方式写在物流备注中!',
  343 + );
  344 + }
  345 + let newData = cloneDeep(data);
  346 + newData = newData.map((item) => ({
  347 + ...item,
  348 + logisticsMethod: values.logisticsMethod,
  349 + serialNumber: values.serialNumber,
  350 + logisticsNotes: values.logisticsNotes,
  351 + }));
  352 + setData(newData);
  353 + }}
  354 + >
  355 + 批量更新
  356 + </Button>
  357 + </ProForm>
  358 + <ProTable<any>
  359 + className="px-0"
  360 + dataSource={data}
  361 + rowKey="id"
  362 + pagination={false}
  363 + columns={columns}
  364 + search={false}
  365 + dateFormatter="string"
  366 + options={false}
  367 + scroll={{ x: 1400 }}
  368 + />
  369 + </Modal>
  370 + );
  371 +};
  372 +
  373 +export default DeliverModal;
... ...
src/pages/Order/FeedBack/FeedbackRegistrationModal.tsx 0 → 100644
  1 +import { postServiceOrderFeedbackRegistration } from '@/services/request';
  2 +import { Input, Modal } from 'antd';
  3 +import { useState } from 'react';
  4 +
  5 +// import { cloneDeep } from 'lodash';
  6 +export default ({ setVisible, subOrders, mainOrder, onClose }) => {
  7 + const [isModalOpen] = useState(true);
  8 + const { TextArea } = Input;
  9 + const [textValue, setTextValue] = useState('');
  10 + console.log(mainOrder);
  11 +
  12 + const handleOk = async () => {
  13 + await postServiceOrderFeedbackRegistration({
  14 + data: {
  15 + id: subOrders[0].id,
  16 + feedbackRegistrationContent: textValue,
  17 + },
  18 + });
  19 + onClose();
  20 + // setIsModalOpen(false);
  21 + // onClose();
  22 + };
  23 + const handleCancel = () => {
  24 + setVisible(false);
  25 + onClose();
  26 + // setIsModalOpen(false);
  27 + // onClose();
  28 + };
  29 + const handleChange = (e) => {
  30 + setTextValue(e.target.value);
  31 + };
  32 + return (
  33 + <>
  34 + {/* <ModalForm<{
  35 + filePaths: any;
  36 + }>
  37 + width={500}
  38 + open
  39 + title="回访登记"
  40 + form={form}
  41 + autoFocusFirstInput
  42 + modalProps={{
  43 + okText: '提交',
  44 + cancelText: '取消',
  45 + destroyOnClose: true,
  46 + onCancel: () => {
  47 + setVisible(false);
  48 + },
  49 + }}
  50 + onFinish={async () => {
  51 + onClose();
  52 + }}
  53 + onOpenChange={setVisible}
  54 + >
  55 + <TextArea rows={6} placeholder="请输入" />
  56 + </ModalForm> */}
  57 + <Modal
  58 + title="回访登记"
  59 + open={isModalOpen}
  60 + onOk={handleOk}
  61 + onCancel={handleCancel}
  62 + >
  63 + <TextArea
  64 + rows={6}
  65 + placeholder="请输入"
  66 + onChange={handleChange}
  67 + value={textValue}
  68 + />
  69 + </Modal>
  70 + </>
  71 + );
  72 +};
... ...
src/pages/Order/FeedBack/FinancialDrawer.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import {
  3 + postServiceOrderEditOrder,
  4 + postServiceOrderInvoicing,
  5 +} from '@/services';
  6 +import { FloatAdd, enumToSelect, enumValueToLabel } from '@/utils';
  7 +import { getReceivingCompanyOptions } from '@/utils/order';
  8 +import {
  9 + DrawerForm,
  10 + ProFormDatePicker,
  11 + ProFormDigit,
  12 + ProFormSelect,
  13 + ProFormText,
  14 + ProFormTextArea,
  15 +} from '@ant-design/pro-components';
  16 +import { Button, Form, message } from 'antd';
  17 +import { useEffect, useState } from 'react';
  18 +import { INVOCING_STATUS_OPTIONS_OLD, PAYEE_OPTIONS } from './constant';
  19 +
  20 +export default ({
  21 + mainOrder,
  22 + subOrders,
  23 + isEdit,
  24 + isMainOrder,
  25 + cancel,
  26 + onClose,
  27 +}) => {
  28 + const [invoicingStatus, setInvoicingStatus] = useState('');
  29 + const subIds = subOrders.map((item) => item.id);
  30 + useEffect(() => {
  31 + // 在组件挂载或数据变化时,更新组件状态
  32 + if (mainOrder) {
  33 + setInvoicingStatus(subOrders[0]?.invoicingStatus);
  34 + }
  35 + }, [mainOrder]);
  36 + useEffect(() => {
  37 + console.log(JSON.stringify(subOrders));
  38 + }, []);
  39 +
  40 + const [form] = Form.useForm<{ name: string; company: string }>();
  41 +
  42 + /**
  43 + * 自动选择收款公司
  44 + * @param receivingCompany
  45 + */
  46 + function chooseReceivingCompany(receivingCompany: any) {
  47 + form.setFieldValue('payee', receivingCompany);
  48 + }
  49 +
  50 + /**
  51 + * 计算选中子订单的主订单金额之和
  52 + */
  53 + function computeTotalPayment() {
  54 + let distinctMap = new Map();
  55 +
  56 + subOrders?.forEach((item: any) => {
  57 + distinctMap.set(item.mainOrderId, item.totalPayment);
  58 + });
  59 +
  60 + let sum = 0;
  61 + for (let p of distinctMap.values()) {
  62 + sum = FloatAdd(p, sum);
  63 + }
  64 +
  65 + form.setFieldValue('money', sum);
  66 + }
  67 +
  68 + return (
  69 + <DrawerForm<{
  70 + name: string;
  71 + company: string;
  72 + }>
  73 + open
  74 + title="财务信息"
  75 + resize={{
  76 + onResize() {
  77 + console.log('resize!');
  78 + },
  79 + maxWidth: window.innerWidth * 0.8,
  80 + minWidth: 400,
  81 + }}
  82 + initialValues={mainOrder}
  83 + form={form}
  84 + autoFocusFirstInput
  85 + drawerProps={{
  86 + destroyOnClose: true,
  87 + }}
  88 + submitTimeout={2000}
  89 + onFinish={async (values) => {
  90 + let res;
  91 + let body = values;
  92 + body.subIds = subIds;
  93 + if (isEdit) {
  94 + res = await postServiceOrderEditOrder({ data: body });
  95 + } else {
  96 + res = await postServiceOrderInvoicing({ data: body });
  97 + }
  98 + if (res.result === RESPONSE_CODE.SUCCESS) {
  99 + message.success(res.message);
  100 + onClose();
  101 + }
  102 + }}
  103 + onOpenChange={(val) => {
  104 + return !val && cancel();
  105 + }}
  106 + >
  107 + {isMainOrder ? (
  108 + <ProFormSelect
  109 + placeholder="选择是否需要开票"
  110 + name="invoicingStatus"
  111 + width="lg"
  112 + label="是否需要开票"
  113 + options={enumToSelect(INVOCING_STATUS_OPTIONS_OLD)}
  114 + onChange={setInvoicingStatus}
  115 + initialValue={subOrders[0]?.invoicingStatus}
  116 + // disabled={mainInfoDisbled}
  117 + rules={[{ required: true, message: '是否需要开票必填' }]}
  118 + />
  119 + ) : (
  120 + ''
  121 + )}
  122 +
  123 + <ProFormTextArea
  124 + width="lg"
  125 + name="invoiceIdentificationNumber"
  126 + label="开票信息"
  127 + placeholder="请输入开票信息"
  128 + disabled
  129 + />
  130 + <ProFormText
  131 + width="lg"
  132 + name="bank"
  133 + label="开户银行"
  134 + placeholder="请输入开户银行"
  135 + disabled
  136 + />
  137 + <ProFormText
  138 + width="lg"
  139 + name="bankAccountNumber"
  140 + label="开户银行账号"
  141 + placeholder="请输入开户银行账号"
  142 + disabled
  143 + />
  144 +
  145 + {invoicingStatus !== 'UN_INVOICE'
  146 + ? [
  147 + <ProFormDatePicker
  148 + key="invoicingTime"
  149 + width="lg"
  150 + name="invoicingTime"
  151 + label="开票时间"
  152 + disabled={isEdit}
  153 + rules={[
  154 + { required: !isEdit ? true : false, message: '这是必填项' },
  155 + ]}
  156 + initialValue={subOrders[0]?.invoicingTime}
  157 + />,
  158 + <ProFormText
  159 + key="purchaser"
  160 + width="lg"
  161 + name="purchaser"
  162 + label="抬头名称"
  163 + disabled={isEdit}
  164 + rules={[
  165 + { required: !isEdit ? true : false, message: '这是必填项' },
  166 + ]}
  167 + initialValue={subOrders[0]?.purchaser}
  168 + />,
  169 + <ProFormDatePicker
  170 + key="financialReceiptIssuanceTime"
  171 + width="lg"
  172 + name="financialReceiptIssuanceTime"
  173 + label="开收据时间"
  174 + initialValue={subOrders[0]?.financialReceiptIssuanceTime}
  175 + />,
  176 + <ProFormDatePicker
  177 + key="collectMoneyTime"
  178 + width="lg"
  179 + name="collectMoneyTime"
  180 + label="收款时间"
  181 + initialValue={subOrders[0]?.collectMoneyTime}
  182 + />,
  183 + <ProFormText
  184 + width="lg"
  185 + key="invoiceNumber"
  186 + name="invoiceNumber"
  187 + label="发票号码"
  188 + initialValue={subOrders[0]?.invoiceNumber}
  189 + rules={[{ required: true, message: '发票号码必填' }]}
  190 + />,
  191 + <div
  192 + key="salesChooseReceivingCompany"
  193 + hidden={subOrders[0].receivingCompany === null}
  194 + >
  195 + <span className={'pl-2 text-xs text-gray-400'}>
  196 + 销售申请开票时选择了:
  197 + {enumValueToLabel(
  198 + subOrders[0].receivingCompany,
  199 + getReceivingCompanyOptions(PAYEE_OPTIONS),
  200 + )}
  201 + </span>
  202 + <span
  203 + hidden={subOrders[0].receivingCompany === 'ANY'}
  204 + className={
  205 + 'pl-2 text-xs text-[#1677ff] cursor-pointer hover:text-[#64abf7]'
  206 + }
  207 + onClick={() => {
  208 + chooseReceivingCompany(subOrders[0].receivingCompany);
  209 + }}
  210 + >
  211 + 选择
  212 + </span>
  213 + </div>,
  214 + <ProFormSelect
  215 + key="payee"
  216 + placeholder="选择收款单位"
  217 + name="payee"
  218 + width="lg"
  219 + showSearch
  220 + label="收款单位"
  221 + options={enumToSelect(PAYEE_OPTIONS)}
  222 + initialValue={subOrders[0]?.payee}
  223 + rules={[{ required: true, message: '收款单位必填' }]}
  224 + />,
  225 +
  226 + <div id="total-payment" key="money">
  227 + <ProFormDigit
  228 + key="money"
  229 + name="money"
  230 + width="lg"
  231 + label="金额"
  232 + rules={[{ required: true, message: '金额必填' }]}
  233 + tooltip="点击计算,合计所有子订单对应主订单总额"
  234 + fieldProps={{
  235 + addonAfter: (
  236 + <Button
  237 + className="rounded-l-none"
  238 + type="primary"
  239 + onClick={computeTotalPayment}
  240 + >
  241 + 计算
  242 + </Button>
  243 + ),
  244 + }}
  245 + />
  246 + </div>,
  247 + ]
  248 + : ''}
  249 +
  250 + <ProFormSelect
  251 + placeholder="是否完全开票"
  252 + name="afterInvoicingStatus"
  253 + width="lg"
  254 + label="是否完全开票"
  255 + options={[
  256 + { label: '完全开票', value: 'COMPLETE_INVOICING' },
  257 + { label: '部分开票', value: 'PARTIAL_INVOICING' },
  258 + ]}
  259 + // disabled={mainInfoDisbled}
  260 + initialValue={
  261 + subOrders[0]?.afterInvoicingStatus === 'APPLY_FOR_INVOICING'
  262 + ? 'COMPLETE_INVOICING'
  263 + : subOrders[0]?.afterInvoicingStatus
  264 + }
  265 + rules={[{ required: true, message: '是否完全开票必填' }]}
  266 + />
  267 + <ProFormTextArea
  268 + width="lg"
  269 + name="invoicingNotes"
  270 + label="备注"
  271 + initialValue={subOrders[0]?.invoicingNotes}
  272 + />
  273 + </DrawerForm>
  274 + );
  275 +};
... ...
src/pages/Order/FeedBack/FinancialEditDrawer.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceOrderNoNeedInvoicingEdit } from '@/services';
  3 +import { enumToSelect } from '@/utils';
  4 +import {
  5 + DrawerForm,
  6 + ProFormDatePicker,
  7 + ProFormSelect,
  8 + ProFormText,
  9 +} from '@ant-design/pro-components';
  10 +import { Form, message } from 'antd';
  11 +import { useEffect, useState } from 'react';
  12 +import { INVOCING_STATUS_OPTIONS_OLD } from './constant';
  13 +
  14 +export default ({ mainOrder, subOrders, setVisible, isMainOrder, onClose }) => {
  15 + const [invoicingStatus, setInvoicingStatus] = useState('');
  16 + useEffect(() => {
  17 + setInvoicingStatus(subOrders[0]?.invoicingStatus);
  18 + }, []);
  19 + const subOrderIds = subOrders?.map((subOrder) => {
  20 + return subOrder?.id;
  21 + });
  22 + const mainOrderId = mainOrder.id;
  23 + const [form] = Form.useForm<{
  24 + collectMoneyTime: string;
  25 + subIds: [];
  26 + financialReceiptIssuanceTime: string;
  27 + }>();
  28 +
  29 + //回显开收据时间和收款时间
  30 + if (!isMainOrder) {
  31 + form.setFieldValue('collectMoneyTime', subOrders[0].collectMoneyTime);
  32 + form.setFieldValue(
  33 + 'financialReceiptIssuanceTime',
  34 + subOrders[0].financialReceiptIssuanceTime,
  35 + );
  36 + }
  37 +
  38 + return (
  39 + <DrawerForm<{
  40 + collectMoneyTime: string;
  41 + financialReceiptIssuanceTime: string;
  42 + subIds: [];
  43 + }>
  44 + open
  45 + title={isMainOrder ? '编辑开票信息' : '编辑收款时间'}
  46 + resize={{
  47 + onResize() {
  48 + console.log('resize!');
  49 + },
  50 + maxWidth: window.innerWidth * 0.8,
  51 + minWidth: 400,
  52 + }}
  53 + initialValues={mainOrder}
  54 + form={form}
  55 + autoFocusFirstInput
  56 + drawerProps={{
  57 + destroyOnClose: true,
  58 + }}
  59 + submitTimeout={2000}
  60 + onFinish={async (values) => {
  61 + let body = {
  62 + ...values,
  63 + mainOrderId: mainOrderId,
  64 + subIds: subOrderIds,
  65 + };
  66 +
  67 + if (!isMainOrder) {
  68 + body.invoicingStatus = 'UN_INVOICE';
  69 + }
  70 +
  71 + let res = await postServiceOrderNoNeedInvoicingEdit({
  72 + data: body,
  73 + });
  74 + if (res.result === RESPONSE_CODE.SUCCESS) {
  75 + message.success(res.message);
  76 + onClose();
  77 + }
  78 + }}
  79 + onOpenChange={(val) => {
  80 + return !val && setVisible(val);
  81 + }}
  82 + >
  83 + {isMainOrder ? (
  84 + <ProFormSelect
  85 + placeholder="选择是否需要开票"
  86 + name="invoicingStatus"
  87 + width="lg"
  88 + label="是否需要开票"
  89 + options={enumToSelect(INVOCING_STATUS_OPTIONS_OLD)}
  90 + onChange={setInvoicingStatus}
  91 + initialValue={subOrders[0]?.invoicingStatus}
  92 + // disabled={mainInfoDisbled}
  93 + rules={[{ required: true, message: '是否需要开票必填' }]}
  94 + />
  95 + ) : (
  96 + ''
  97 + )}
  98 +
  99 + {invoicingStatus !== 'UN_INVOICE' ? (
  100 + <>
  101 + <ProFormText
  102 + key="invoiceIdentificationNumber"
  103 + width="lg"
  104 + name="invoiceIdentificationNumber"
  105 + label="开票信息"
  106 + placeholder="请输入开票信息"
  107 + rules={[{ required: true, message: '开票信息必填' }]}
  108 + />
  109 + <ProFormText
  110 + key="bank"
  111 + width="lg"
  112 + name="bank"
  113 + label="开户银行"
  114 + placeholder="请输入开户银行"
  115 + />
  116 + <ProFormText
  117 + key="bankAccountNumber"
  118 + width="lg"
  119 + name="bankAccountNumber"
  120 + label="开户银行账号"
  121 + placeholder="请输入开户银行账号"
  122 + />
  123 + </>
  124 + ) : (
  125 + ''
  126 + )}
  127 +
  128 + <ProFormDatePicker
  129 + key="financialReceiptIssuanceTime"
  130 + width="lg"
  131 + name="financialReceiptIssuanceTime"
  132 + label="开收据时间"
  133 + // rules={[
  134 + // {
  135 + // required: !isMainOrder && invoicingStatus === 'UN_INVOICE',
  136 + // message: '开收据时间必填',
  137 + // },
  138 + // ]}
  139 + />
  140 + <ProFormDatePicker
  141 + key="collectMoneyTime"
  142 + width="lg"
  143 + name="collectMoneyTime"
  144 + label="收款时间"
  145 + // rules={[
  146 + // {
  147 + // required: !isMainOrder && invoicingStatus === 'UN_INVOICE',
  148 + // message: '收款时间必填',
  149 + // },
  150 + // ]}
  151 + />
  152 + </DrawerForm>
  153 + );
  154 +};
... ...
src/pages/Order/FeedBack/FinancialMergeDrawer.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { enumToSelect } from '@/utils';
  3 +import {
  4 + DrawerForm,
  5 + ProFormDatePicker,
  6 + ProFormDigit,
  7 + ProFormSelect,
  8 + ProFormText,
  9 + ProFormTextArea,
  10 +} from '@ant-design/pro-components';
  11 +import { Form, message } from 'antd';
  12 +import { PAYEE_OPTIONS } from './constant';
  13 +
  14 +export default ({ dataList, setVisible, onClose }) => {
  15 + // let subOrderIds = dataList?.map((item) => {
  16 + // return item.id;
  17 + // });
  18 + let firstMainOrder = dataList[0];
  19 + let bank = firstMainOrder?.bank;
  20 + let bankAccountNumber = firstMainOrder?.bankAccountNumber;
  21 + let invoiceIdentificationNumber = firstMainOrder?.invoiceIdentificationNumber;
  22 +
  23 + const [form] = Form.useForm<{
  24 + invoicingTime: string;
  25 + financialReceiptIssuanceTime: string;
  26 + invoicingNotes: string;
  27 + afterInvoicingStatus: string;
  28 + collectMoneyTime: string;
  29 + }>();
  30 + return (
  31 + <DrawerForm
  32 + open
  33 + title="合并开票"
  34 + resize={{
  35 + onResize() {
  36 + console.log('resize!');
  37 + },
  38 + maxWidth: window.innerWidth * 0.8,
  39 + minWidth: 400,
  40 + }}
  41 + form={form}
  42 + autoFocusFirstInput
  43 + drawerProps={{
  44 + destroyOnClose: true,
  45 + }}
  46 + submitTimeout={2000}
  47 + onFinish={async (values) => {
  48 + console.log(values);
  49 + let res;
  50 + let body = values;
  51 + body.subIds = subIds;
  52 + body.mainOrderId = mainOrder.id;
  53 + body.mainorderOrSubOrderInvoicing = isMainOrder;
  54 + if (isEdit) {
  55 + res = await postServiceOrderEditOrder({ data: body });
  56 + } else {
  57 + res = await postServiceOrderInvoicing({ data: body });
  58 + }
  59 + if (res.result === RESPONSE_CODE.SUCCESS) {
  60 + message.success(res.message);
  61 + onClose();
  62 + }
  63 + }}
  64 + onOpenChange={(val) => {
  65 + return !val && setVisible();
  66 + }}
  67 + >
  68 + <ProFormText
  69 + width="lg"
  70 + name="invoiceIdentificationNumber"
  71 + label="开票信息"
  72 + placeholder="请输入开票信息"
  73 + initialValue={invoiceIdentificationNumber}
  74 + disabled
  75 + />
  76 + <ProFormText
  77 + width="lg"
  78 + name="bank"
  79 + label="开户银行"
  80 + placeholder="请输入开户银行"
  81 + initialValue={bank}
  82 + disabled
  83 + />
  84 + <ProFormText
  85 + width="lg"
  86 + name="bankAccountNumber"
  87 + label="开户银行账号"
  88 + placeholder="请输入开户银行账号"
  89 + initialValue={bankAccountNumber}
  90 + disabled
  91 + />
  92 +
  93 + <ProFormDatePicker
  94 + key="invoicingTime"
  95 + width="lg"
  96 + name="invoicingTime"
  97 + label="开票时间"
  98 + rules={[{ required: true, message: '这是必填项' }]}
  99 + />
  100 + <ProFormDatePicker
  101 + key="financialReceiptIssuanceTime"
  102 + width="lg"
  103 + name="financialReceiptIssuanceTime"
  104 + label="开收据时间"
  105 + />
  106 + <ProFormDatePicker
  107 + key="collectMoneyTime"
  108 + width="lg"
  109 + name="collectMoneyTime"
  110 + label="收款时间"
  111 + />
  112 + <ProFormText
  113 + width="lg"
  114 + key="invoiceNumber"
  115 + name="invoiceNumber"
  116 + label="发票号码"
  117 + rules={[{ required: true, message: '发票号码必填' }]}
  118 + />
  119 + <ProFormSelect
  120 + key="payee"
  121 + placeholder="选择收款单位"
  122 + name="payee"
  123 + width="lg"
  124 + label="收款单位"
  125 + options={enumToSelect(PAYEE_OPTIONS)}
  126 + rules={[{ required: true, message: '收款单位必填' }]}
  127 + />
  128 +
  129 + <ProFormDigit
  130 + key="money"
  131 + name="money"
  132 + width="lg"
  133 + label="金额"
  134 + rules={[{ required: true, message: '金额必填' }]}
  135 + />
  136 + <ProFormSelect
  137 + placeholder="是否完全开票"
  138 + name="afterInvoicingStatus"
  139 + width="lg"
  140 + label="是否完全开票"
  141 + options={[
  142 + { label: '完全开票', value: 'COMPLETE_INVOICING' },
  143 + { label: '部分开票', value: 'PARTIAL_INVOICING' },
  144 + ]}
  145 + initialValue={'COMPLETE_INVOICING'}
  146 + />
  147 + <ProFormTextArea width="lg" name="invoicingNotes" label="备注" />
  148 + </DrawerForm>
  149 + );
  150 +};
... ...
src/pages/Order/FeedBack/FinancialReceiptsModal.tsx 0 → 100644
  1 +import { postServiceOrderUpdateHirePurchase } from '@/services';
  2 +import {
  3 + EditableProTable,
  4 + ModalForm,
  5 + ProColumns,
  6 + ProForm,
  7 +} from '@ant-design/pro-components';
  8 +import { Form } from 'antd';
  9 +import { useState } from 'react';
  10 +
  11 +// import { cloneDeep } from 'lodash';
  12 +export default ({ setVisible, datas, onClose }) => {
  13 + const [form] = Form.useForm<{ name: string; company: string }>();
  14 + type DataSourceType = {
  15 + id: React.Key;
  16 + hirePurchaseMethod?: string;
  17 + hirePurchaseMethodName?: string;
  18 + money?: number;
  19 + updateTime?: string;
  20 + notes?: string;
  21 + };
  22 + const defaultData: DataSourceType[] = [
  23 + {
  24 + id: 1,
  25 + hirePurchaseMethod: 'ADVANCE_CHARGE',
  26 + hirePurchaseMethodName: '预付款',
  27 + money: undefined,
  28 + updateTime: undefined,
  29 + notes: undefined,
  30 + },
  31 + {
  32 + id: 2,
  33 + hirePurchaseMethod: 'PAYMENT_FOR_SHIPMENT',
  34 + hirePurchaseMethodName: '发货款',
  35 + money: undefined,
  36 + updateTime: undefined,
  37 + notes: undefined,
  38 + },
  39 + {
  40 + id: 3,
  41 + hirePurchaseMethod: 'ACCEPTANCE_PAYMENT',
  42 + hirePurchaseMethodName: '验收款',
  43 + money: undefined,
  44 + updateTime: undefined,
  45 + notes: undefined,
  46 + },
  47 + {
  48 + id: 4,
  49 + hirePurchaseMethod: 'BALANCE_PAYMENT',
  50 + hirePurchaseMethodName: '尾款',
  51 + money: undefined,
  52 + updateTime: undefined,
  53 + notes: undefined,
  54 + },
  55 + ];
  56 + const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
  57 + // defaultData.map((item) => item.id),
  58 + [1, 2, 3, 4],
  59 + );
  60 + const columns: ProColumns<DataSourceType>[] = [
  61 + {
  62 + title: '款项',
  63 + dataIndex: 'hirePurchaseMethodName',
  64 + editable: false,
  65 + width: '10%',
  66 + },
  67 + {
  68 + title: '已收金额',
  69 + dataIndex: 'money',
  70 + valueType: 'digit',
  71 + width: '15%',
  72 + },
  73 + {
  74 + title: '收款时间',
  75 + dataIndex: 'updateTime',
  76 + valueType: 'dateTime',
  77 + width: '25%',
  78 + },
  79 + {
  80 + title: '备注',
  81 + dataIndex: 'receiptsNotes',
  82 + },
  83 + ];
  84 + return (
  85 + <>
  86 + <ModalForm<{
  87 + name: string;
  88 + company: string;
  89 + }>
  90 + width={1100}
  91 + open
  92 + title="收款记录"
  93 + form={form}
  94 + autoFocusFirstInput
  95 + modalProps={{
  96 + okText: '保存',
  97 + cancelText: '取消',
  98 + destroyOnClose: true,
  99 + onCancel: () => {
  100 + setVisible(false);
  101 + },
  102 + }}
  103 + onFinish={async (values) => {
  104 + let res = await postServiceOrderUpdateHirePurchase({
  105 + data: {
  106 + mainOrderId: datas[0].id,
  107 + list: values.dataSource,
  108 + },
  109 + });
  110 + console.log(res);
  111 + onClose();
  112 + }}
  113 + onOpenChange={setVisible}
  114 + >
  115 + <ProForm.Item
  116 + label=""
  117 + name="dataSource"
  118 + initialValue={defaultData}
  119 + trigger="onValuesChange"
  120 + >
  121 + <EditableProTable<DataSourceType>
  122 + rowKey="id"
  123 + toolBarRender={false}
  124 + columns={columns}
  125 + recordCreatorProps={{
  126 + newRecordType: 'dataSource',
  127 + position: 'top',
  128 + record: () => ({
  129 + id: Date.now(),
  130 + addonBefore: 'ccccccc',
  131 + decs: 'testdesc',
  132 + }),
  133 + style: {
  134 + display: 'none',
  135 + },
  136 + }}
  137 + editable={{
  138 + type: 'multiple',
  139 + editableKeys,
  140 + onChange: setEditableRowKeys,
  141 + actionRender: (row, _, dom) => {
  142 + return [dom.delete];
  143 + },
  144 + }}
  145 + />
  146 + </ProForm.Item>
  147 +
  148 + {/* <ProForm.Group>
  149 + <ProFormText
  150 + width="sm"
  151 + name="name1"
  152 + label="款项"
  153 + tooltip="最长为 24 位"
  154 + initialValue={"预付款"}
  155 + disabled
  156 + placeholder="请输入名称"
  157 + />
  158 +
  159 + <ProFormText
  160 + width="sm"
  161 + name="company"
  162 + label="收款时间"
  163 + placeholder="请输入名称"
  164 + />
  165 +
  166 + <ProFormText
  167 + width="sm"
  168 + name="price"
  169 + label="收款金额"
  170 + placeholder="请输入名称"
  171 + />
  172 +
  173 + <ProFormText
  174 + width="sm"
  175 + name="notes"
  176 + label="备注"
  177 + placeholder="请输入名称"
  178 + />
  179 + </ProForm.Group>
  180 +
  181 + <ProForm.Group>
  182 + <ProFormText
  183 + width="sm"
  184 + name="name2"
  185 + initialValue={"发货款"}
  186 + disabled
  187 + tooltip="最长为 24 位"
  188 + placeholder="请输入名称"
  189 + />
  190 +
  191 + <ProFormText
  192 + width="sm"
  193 + name="company"
  194 + placeholder="请输入名称"
  195 + />
  196 +
  197 + <ProFormText
  198 + width="sm"
  199 + name="price"
  200 + placeholder="请输入名称"
  201 + />
  202 +
  203 + <ProFormText
  204 + width="sm"
  205 + name="notes"
  206 + placeholder="请输入名称"
  207 + />
  208 + </ProForm.Group>
  209 +
  210 + <ProForm.Group>
  211 + <ProFormText
  212 + width="sm"
  213 + name="name3"
  214 + initialValue={"验收款"}
  215 + disabled
  216 + tooltip="最长为 24 位"
  217 + placeholder="请输入名称"
  218 + />
  219 +
  220 + <ProFormText
  221 + width="sm"
  222 + name="company"
  223 + placeholder="请输入名称"
  224 + />
  225 +
  226 + <ProFormText
  227 + width="sm"
  228 + name="price"
  229 + placeholder="请输入名称"
  230 + />
  231 +
  232 + <ProFormText
  233 + width="sm"
  234 + name="notes"
  235 + placeholder="请输入名称"
  236 + />
  237 + </ProForm.Group>
  238 +
  239 + <ProForm.Group>
  240 + <ProFormText
  241 + width="sm"
  242 + name="name4"
  243 + disabled
  244 + initialValue={"尾款"}
  245 + tooltip="最长为 24 位"
  246 + placeholder="请输入名称"
  247 + />
  248 +
  249 + <ProFormText
  250 + width="sm"
  251 + name="company"
  252 + placeholder="请输入名称"
  253 + />
  254 +
  255 + <ProFormText
  256 + width="sm"
  257 + name="price"
  258 + placeholder="请输入名称"
  259 + />
  260 +
  261 + <ProFormText
  262 + width="sm"
  263 + name="notes"
  264 + placeholder="请输入名称"
  265 + />
  266 + </ProForm.Group> */}
  267 + </ModalForm>
  268 + </>
  269 + );
  270 +};
... ...
src/pages/Order/FeedBack/HistoryModal.tsx 0 → 100644
  1 +import { postServiceOrderQueryHistoryOrderRecord } from '@/services';
  2 +import { formatDateTime } from '@/utils';
  3 +import { Button, Col, Empty, Flex, Modal, Row, Spin } from 'antd';
  4 +import { useEffect, useState } from 'react';
  5 +
  6 +export default ({ subOrders, isCancelledOrder, onClose }) => {
  7 + let subOrderIds = subOrders?.map((subOrder: any) => {
  8 + return subOrder.id;
  9 + });
  10 +
  11 + const [data, setData] = useState([]);
  12 + const [loading, setLoading] = useState(true);
  13 + let i = 0;
  14 +
  15 + const handleOk = () => {
  16 + onClose();
  17 + };
  18 +
  19 + /**
  20 + * 获取历史记录
  21 + */
  22 + const getHistory = async () => {
  23 + let res = await postServiceOrderQueryHistoryOrderRecord({
  24 + data: { ids: subOrderIds, isDeleteQueryOrder: isCancelledOrder },
  25 + });
  26 + setData(res.data);
  27 + setLoading(false);
  28 + };
  29 +
  30 + const getRecord = (history: any) => {
  31 + let record = [];
  32 + record.push(
  33 + <span className="pr-2 text-[#5E5E5E]">
  34 + {formatDateTime(history.createTime)}
  35 + </span>,
  36 + );
  37 +
  38 + record.push(<span className="text-[#3b83e5]">{history.createByName}</span>);
  39 +
  40 + record.push(<span>进行了</span>);
  41 +
  42 + // let label = enumValueToLabel(history.status, ORDER_STATUS_OPTIONS);
  43 +
  44 + // if (
  45 + // history.record !== 'INVOICING' && history.record !== 'order-change-normal' && history.record !== 'order-change-normal-CHECK' &&
  46 + // history.record?.indexOf(':') === -1 &&
  47 + // label !== undefined &&
  48 + // label !== ''
  49 + // ) {
  50 + // record.push(
  51 + // <>
  52 + // <span>,订单状态为:</span>
  53 + // <span className="text-[#3b83e5]">{label}</span>
  54 + // </>,
  55 + // );
  56 + // }
  57 +
  58 + if (history.record?.indexOf(':') !== -1) {
  59 + let values = history.record?.split(':');
  60 + let type = values[0];
  61 + let target = values[1];
  62 + if (target === 'null') {
  63 + target = '未指定';
  64 + }
  65 + //采购转发
  66 + if (type === 'PROCURE_CONVERT_PROCURE') {
  67 + record.push(
  68 + <>
  69 + <span>采购转发,{history.createByName}将订单转发给了</span>
  70 + <span className="text-[#3b83e5]">{target}</span>
  71 + </>,
  72 + );
  73 + }
  74 + } else {
  75 + record.push(
  76 + <span className="text-[#3b83e5]">
  77 + {history.recordText +
  78 + (history.record === 'INVOICING'
  79 + ? '(开票号码:' + history.invoiceNumber + ')'
  80 + : '')}
  81 + </span>,
  82 + );
  83 + }
  84 +
  85 + if (history.notes !== null) {
  86 + record.push(<span className="pl-1">{'备注:' + history.notes}</span>);
  87 + }
  88 +
  89 + if (history.description !== null) {
  90 + record.push(
  91 + <span className="pl-1">{'描述:' + history.description}</span>,
  92 + );
  93 + }
  94 +
  95 + return record;
  96 + };
  97 +
  98 + useEffect(() => {
  99 + getHistory();
  100 + }, []);
  101 +
  102 + const handleCancel = () => {
  103 + onClose();
  104 + };
  105 +
  106 + return (
  107 + <>
  108 + <Modal
  109 + title="订单历史记录"
  110 + open
  111 + width={650}
  112 + onOk={handleOk}
  113 + onCancel={handleCancel}
  114 + footer={() => (
  115 + <>
  116 + <Button onClick={handleCancel}>返回</Button>
  117 + </>
  118 + )}
  119 + >
  120 + <Spin tip="加载中" spinning={loading}>
  121 + <Row className="max-h-[500px] overflow-auto" gutter={[0, 14]}>
  122 + {data.map((item) => {
  123 + return (
  124 + <Col span={24} key={i}>
  125 + <Flex vertical>
  126 + <div>
  127 + <span className="py-2 text-[#5E5E5E]">
  128 + {'商品' + ++i}
  129 + </span>
  130 + <span className="text-[#8C8C8C]">
  131 + -【{item.productName}】
  132 + </span>
  133 + </div>
  134 +
  135 + <Flex vertical>
  136 + {item.historySubOrderRecordDto?.map((history) => {
  137 + return (
  138 + <div className="py-1" key={history.id}>
  139 + {getRecord(history)}
  140 + </div>
  141 + );
  142 + })}
  143 + </Flex>
  144 + </Flex>
  145 + </Col>
  146 + );
  147 + })}
  148 + </Row>
  149 + {data?.length <= 0 ? (
  150 + <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
  151 + ) : (
  152 + ''
  153 + )}
  154 + </Spin>
  155 + </Modal>
  156 + </>
  157 + );
  158 +};
... ...
src/pages/Order/FeedBack/ImagesViewerModal.tsx 0 → 100644
  1 +import { postServiceOrderViewImages } from '@/services';
  2 +import { Button, Divider, Image, Modal } from 'antd';
  3 +import { useEffect, useState } from 'react';
  4 +export default ({ setVisible, optType, onClose, orderRow }) => {
  5 + const [images, setImages] = useState<any[]>([]);
  6 + const [title, setTitle] = useState('收货凭证');
  7 + const handleOk = () => {
  8 + onClose();
  9 + setVisible(false);
  10 + };
  11 +
  12 + const handleCancel = () => {
  13 + onClose();
  14 + setVisible(false);
  15 + };
  16 +
  17 + async function getImages() {
  18 + const res = await postServiceOrderViewImages({
  19 + data: { subId: orderRow.id },
  20 + });
  21 + const images = res.data;
  22 + setImages(images);
  23 + }
  24 + useEffect(() => {
  25 + if (optType === 'shippingReceipt') {
  26 + setTitle('收货凭证');
  27 + getImages();
  28 + } else if (optType === 'paymentReceipt') {
  29 + let paymentReceiptsImagesList: any[] = [];
  30 + if (orderRow.paymentReceiptAnnexList) {
  31 + paymentReceiptsImagesList.push(...orderRow.paymentReceiptAnnexList);
  32 + }
  33 + //去重
  34 + paymentReceiptsImagesList = [...new Set(paymentReceiptsImagesList)];
  35 + setImages(paymentReceiptsImagesList);
  36 + }
  37 + }, []);
  38 +
  39 + return (
  40 + <>
  41 + <Modal
  42 + title={title}
  43 + open
  44 + onOk={handleOk}
  45 + onCancel={handleCancel}
  46 + footer={[
  47 + <Button key="back" onClick={handleCancel}>
  48 + 返回
  49 + </Button>,
  50 + ]}
  51 + >
  52 + <Image.PreviewGroup
  53 + className="mr-10"
  54 + preview={{
  55 + onChange: (current, prev) =>
  56 + console.log(`current index: ${current}, prev index: ${prev}`),
  57 + }}
  58 + >
  59 + {images.map((url) => (
  60 + <>
  61 + <Image width={120} src={url} /> <Divider type="vertical" />
  62 + </>
  63 + ))}
  64 + </Image.PreviewGroup>
  65 + </Modal>
  66 + </>
  67 + );
  68 +};
... ...
src/pages/Order/FeedBack/ImportExpressBillModal copy.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postOrderImportImportWeightAndVolume } from '@/services';
  3 +import { ModalForm, ProFormUploadDragger } from '@ant-design/pro-components';
  4 +import { Button, Form, message } from 'antd';
  5 +
  6 +export default () => {
  7 + const [form] = Form.useForm();
  8 + const [messageApi, contextHolder] = message.useMessage();
  9 + return (
  10 + <>
  11 + <ModalForm
  12 + title="导入重量、体积"
  13 + trigger={<Button type="primary">导入重量、体积</Button>}
  14 + form={form}
  15 + autoFocusFirstInput
  16 + modalProps={{
  17 + destroyOnClose: true,
  18 + }}
  19 + submitTimeout={2000}
  20 + onFinish={async (values) => {
  21 + console.log(values);
  22 + const formData = new FormData();
  23 + formData.append('file', values.express[0].originFileObj);
  24 + messageApi.open({
  25 + type: 'loading',
  26 + content: '正在导入...',
  27 + duration: 0,
  28 + });
  29 + const res = await postOrderImportImportWeightAndVolume({
  30 + data: formData,
  31 + headers: {
  32 + 'Content-Type':
  33 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  34 + },
  35 + });
  36 + if (res.result === RESPONSE_CODE.SUCCESS) {
  37 + message.error('导入成功');
  38 + } else {
  39 + message.error('导入失败');
  40 + }
  41 + messageApi.destroy();
  42 + return true;
  43 + }}
  44 + >
  45 + <ProFormUploadDragger max={1} label="上传快递单" name="express" />
  46 + </ModalForm>
  47 + {contextHolder}
  48 + </>
  49 + );
  50 +};
... ...
src/pages/Order/FeedBack/ImportExpressBillModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postOrderImportImportWeightAndVolume } from '@/services';
  3 +import { ModalForm, ProFormUploadDragger } from '@ant-design/pro-components';
  4 +import { Button, Form, message } from 'antd';
  5 +
  6 +export default () => {
  7 + const [form] = Form.useForm();
  8 + const [messageApi, contextHolder] = message.useMessage();
  9 + return (
  10 + <>
  11 + <ModalForm
  12 + title="导入重量、体积"
  13 + trigger={<Button type="primary">导入重量、体积</Button>}
  14 + form={form}
  15 + autoFocusFirstInput
  16 + modalProps={{
  17 + destroyOnClose: true,
  18 + }}
  19 + submitTimeout={2000}
  20 + onFinish={async (values) => {
  21 + console.log(values);
  22 + const formData = new FormData();
  23 + formData.append('file', values.express[0].originFileObj);
  24 + messageApi.open({
  25 + type: 'loading',
  26 + content: '正在导入...',
  27 + duration: 0,
  28 + });
  29 + const res = await postOrderImportImportWeightAndVolume({
  30 + data: formData,
  31 + headers: {
  32 + 'Content-Type':
  33 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  34 + },
  35 + });
  36 + if (res.result === RESPONSE_CODE.SUCCESS) {
  37 + message.error('导入成功');
  38 + } else {
  39 + message.error('导入失败');
  40 + }
  41 + messageApi.destroy();
  42 + return true;
  43 + }}
  44 + >
  45 + <ProFormUploadDragger max={1} label="上传快递单" name="express" />
  46 + </ModalForm>
  47 + {contextHolder}
  48 + </>
  49 + );
  50 +};
... ...