Commit 9501262e22f29cfb95f06a646a1f74cad4c51c58

Authored by PurelzMgnead
1 parent 0cbf10f0

feat: 售后

Too many changes to show.

To preserve performance only 25 of 45 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 },
... ... @@ -59,6 +59,11 @@ export default defineConfig({
59 59 access: 'canReadAdminAndSalesManager',
60 60 component: './Order/WarningWhitelist',
61 61 },
  62 + {
  63 + name: '订单售后',
  64 + path: 'FeedBack',
  65 + component: './Order/FeedBack',
  66 + },
62 67 ],
63 68 },
64 69 {
... ...
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 +
  11 + const handleOk = async () => {
  12 + console.log(subOrders, '5656subOrders', mainOrder);
  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 +};
... ...
src/pages/Order/FeedBack/ImportModal.tsx 0 → 100644
  1 +import { RESPONSE_CODE } from '@/constants/enum';
  2 +import { postServiceOrderImportExcel } from '@/services';
  3 +import { downloadFile } from '@/services/order';
  4 +import { UploadOutlined } from '@ant-design/icons';
  5 +import { Button, Modal, Upload, message } from 'antd';
  6 +import { RcFile, UploadFile, UploadProps } from 'antd/es/upload';
  7 +import { useState } from 'react';
  8 +export default ({ onClose }) => {
  9 + // const [form] = Form.useForm<{ name: string; company: string }>();
  10 + const [messageApi, contextHolder] = message.useMessage();
  11 + const [fileList, setFileList] = useState<UploadFile[]>([]);
  12 + const [uploading, setUploading] = useState(false);
  13 + const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
  14 + setFileList(newFileList);
  15 +
  16 + const exportLoading = (content: string) => {
  17 + messageApi.open({
  18 + type: 'loading',
  19 + content: content,
  20 + duration: 0,
  21 + });
  22 + };
  23 +
  24 + const exportLoadingDestory = () => {
  25 + messageApi.destroy();
  26 + };
  27 + const downloadTemplate = async () => {
  28 + exportLoading('正在下载模板...');
  29 + downloadFile(
  30 + '/api/service/order/exportTemplate',
  31 + '订单.xlsx',
  32 + 'post',
  33 + {},
  34 + exportLoadingDestory,
  35 + );
  36 + };
  37 +
  38 + const handleUpload = async () => {
  39 + const formData = new FormData();
  40 + fileList.forEach((file) => {
  41 + //originFileObj二进制文件
  42 + formData.append('file', file.originFileObj as RcFile);
  43 + });
  44 + // console.log(fileList[0] as RcFile)
  45 + // formData.append('file', fileList[0] as RcFile);
  46 + setUploading(true);
  47 + // You can use any AJAX library you like
  48 + const res = await postServiceOrderImportExcel({
  49 + data: formData,
  50 + headers: {
  51 + 'Content-Type':
  52 + 'multipart/form-data; boundary=----WebKitFormBoundarynl6gT1BKdPWIejNq',
  53 + },
  54 + });
  55 +
  56 + if (res.result === RESPONSE_CODE.SUCCESS) {
  57 + message.success(res.message);
  58 + onClose();
  59 + } else {
  60 + if (res.message === '表格中没有数据') {
  61 + setUploading(false);
  62 + return;
  63 + }
  64 + //存在错误信息,下载错误信息模板
  65 + exportLoading('正在下载错误信息...');
  66 + downloadFile(
  67 + '/api/service/order/errorExcelInformation',
  68 + '订单.xlsx',
  69 + 'post',
  70 + formData,
  71 + exportLoadingDestory,
  72 + );
  73 + }
  74 +
  75 + setUploading(false);
  76 + };
  77 +
  78 + const props: UploadProps = {
  79 + onRemove: (file) => {
  80 + const index = fileList.indexOf(file);
  81 + const newFileList = fileList.slice();
  82 + newFileList.splice(index, 1);
  83 + setFileList(newFileList);
  84 + },
  85 + beforeUpload: (file) => {
  86 + setFileList([...fileList, file]);
  87 +
  88 + return false;
  89 + },
  90 + fileList,
  91 + onChange: handleChange,
  92 + accept: '.xlsx',
  93 + };
  94 +
  95 + return (
  96 + <>
  97 + <Modal
  98 + width={500}
  99 + open
  100 + title="批量发货"
  101 + footer={[
  102 + <Button key="cancel" onClick={onClose}>
  103 + 取消
  104 + </Button>,
  105 + <Button
  106 + type="primary"
  107 + key="ok"
  108 + onClick={handleUpload}
  109 + disabled={fileList.length === 0}
  110 + loading={uploading}
  111 + >
  112 + {uploading ? '上传中' : '提交'}
  113 + </Button>,
  114 + ]}
  115 + onCancel={async () => {
  116 + onClose();
  117 + }}
  118 + >
  119 + <div className="py-4 font-semibold">
  120 + 导入发货信息
  121 + <Button type="link" onClick={downloadTemplate}>
  122 + 下载模板
  123 + </Button>
  124 + </div>
  125 + <Upload {...props}>
  126 + <Button icon={<UploadOutlined />} disabled={fileList.length > 0}>
  127 + 点击选择文件
  128 + </Button>
  129 + </Upload>
  130 + </Modal>
  131 + {contextHolder}
  132 + </>
  133 + );
  134 +};
... ...
src/pages/Order/FeedBack/InvoiceSubOrderInfoTable.tsx 0 → 100644
  1 +import { postServiceOrderGetReissueInfo } from '@/services';
  2 +import type { ProColumns } from '@ant-design/pro-components';
  3 +import { ProTable } from '@ant-design/pro-components';
  4 +import { Button, Divider } from 'antd';
  5 +import { useEffect, useState } from 'react';
  6 +
  7 +const columns: ProColumns[] = [
  8 + {
  9 + title: '发票号码',
  10 + width: 80,
  11 + dataIndex: 'invoiceNumber',
  12 + render: (_) => _,
  13 + },
  14 + {
  15 + title: '关联订单',
  16 + dataIndex: 'subOrderIds',
  17 + render: (_, { subOrderIds }) => {
  18 + console.log(JSON.stringify(_));
  19 + console.log(JSON.stringify(subOrderIds));
  20 + return (
  21 + <>
  22 + {subOrderIds.map((subOrderId, index) => {
  23 + return (
  24 + <>
  25 + <Button
  26 + key={index}
  27 + className="pl-1 pr-0"
  28 + type="link"
  29 + target="_blank"
  30 + href={'/order/order?id=' + subOrderId}
  31 + >
  32 + {subOrderId}
  33 + </Button>
  34 + <Divider type="vertical" />
  35 + </>
  36 + );
  37 + })}
  38 + </>
  39 + );
  40 + },
  41 + },
  42 +];
  43 +
  44 +export default ({ subOrderIds }) => {
  45 + const [reissueInfos, setReissueInfos] = useState([]);
  46 + useEffect(() => {
  47 + console.log('info');
  48 + const getReissueInfo = async () => {
  49 + let res = await postServiceOrderGetReissueInfo({
  50 + data: subOrderIds,
  51 + });
  52 + setReissueInfos(res.data);
  53 + };
  54 + getReissueInfo();
  55 + }, []);
  56 + return (
  57 + <ProTable
  58 + dataSource={reissueInfos}
  59 + rowKey="key"
  60 + pagination={false}
  61 + size={'small'}
  62 + //设置左右下边距为0
  63 + options={false}
  64 + columns={columns}
  65 + search={false}
  66 + dateFormatter="string"
  67 + headerTitle="发票信息"
  68 + />
  69 + );
  70 +};
... ...
src/pages/Order/FeedBack/InvoicingDrawerForm.tsx 0 → 100644
  1 +// import { PlusOutlined } from '@ant-design/icons';
  2 +import InvoiceModal from '@/pages/Invoice/waitProcessRecord/components/InvoiceModal';
  3 +import {
  4 + postServiceConstGetPayeeEnum,
  5 + postServiceConstInitInvoiceDetailNames,
  6 + postServiceConstInvoiceType,
  7 + postServiceConstInvoicingType,
  8 + postServiceConstListInvoiceDetailNames,
  9 + postServiceInvoiceApplyInvoice,
  10 + postServiceInvoiceQueryCompanyInfo,
  11 + postServiceInvoiceWaitReissueInvoices,
  12 +} from '@/services';
  13 +import {
  14 + FloatAdd,
  15 + FloatMul,
  16 + FloatSub,
  17 + enum2ReverseSelect,
  18 + enumToSelect,
  19 +} from '@/utils';
  20 +import { convertCurrency } from '@/utils/numberUtil';
  21 +import {
  22 + DrawerForm,
  23 + FormListActionType,
  24 + ProCard,
  25 + ProFormDigit,
  26 + ProFormGroup,
  27 + ProFormInstance,
  28 + ProFormList,
  29 + ProFormMoney,
  30 + ProFormSelect,
  31 + ProFormText,
  32 + ProFormTextArea,
  33 +} from '@ant-design/pro-components';
  34 +import { Button, Divider, Form, Space, Tooltip, message } from 'antd';
  35 +import { useEffect, useRef, useState } from 'react';
  36 +
  37 +export default ({ dataList, setVisible, mainOrder, onClose }) => {
  38 + // let subOrderIds = dataList?.map((item) => {
  39 + // return item.id;
  40 + // })
  41 + const [form] = Form.useForm();
  42 + const [projectOptions] = useState();
  43 + const [dataListCopy] = useState(dataList);
  44 + const listActionRef = useRef<FormListActionType>();
  45 + const formRef = useRef<ProFormInstance>();
  46 + useEffect(() => {
  47 + const initOptions = async () => {
  48 + const res = await postServiceConstInitInvoiceDetailNames({
  49 + data: dataListCopy.map((item) => {
  50 + return item.productName;
  51 + }),
  52 + });
  53 + const options = res.data;
  54 + const datas = dataListCopy.map((item) => {
  55 + return {
  56 + ...item,
  57 + projectName: options[item.productName],
  58 + };
  59 + });
  60 + const initialValue = datas.map((item) => {
  61 + return {
  62 + productName: item.productName,
  63 + projectName: item.projectName,
  64 + subOrderId: item.id,
  65 + specification: item.parameters,
  66 + unit: item.unit,
  67 + quantity: item.quantity,
  68 + price: item.productPrice,
  69 + totalPrice: item.quantity * item.productPrice,
  70 + };
  71 + });
  72 + form.setFieldValue('invoiceDetails', initialValue);
  73 + form.setFieldValue('contacts', mainOrder.customerName);
  74 + };
  75 + initOptions();
  76 + }, []);
  77 +
  78 + useEffect(() => {}, [projectOptions]);
  79 +
  80 + function copyToClipboard(text: string) {
  81 + // 创建一个临时的textarea元素
  82 + const textarea = document.createElement('textarea');
  83 + textarea.value = text;
  84 +
  85 + // 将textarea元素添加到DOM中
  86 + document.body.appendChild(textarea);
  87 +
  88 + // 选中textarea中的文本
  89 + textarea.select();
  90 +
  91 + try {
  92 + // 尝试执行复制命令
  93 + document.execCommand('copy');
  94 + return true;
  95 + } catch (err) {
  96 + return false;
  97 + } finally {
  98 + // 移除临时的textarea元素
  99 + document.body.removeChild(textarea);
  100 + }
  101 + }
  102 + // 定义一个计算总金额的函数
  103 + const calculateTotalPrice = (index: number) => {
  104 + const invoiceDetails = form.getFieldValue('invoiceDetails');
  105 + if (invoiceDetails && invoiceDetails[index]) {
  106 + const quantity = invoiceDetails[index].quantity || 0;
  107 + const price = invoiceDetails[index].price || 0;
  108 + const totalPrice = FloatMul(quantity, price);
  109 + const newInvoiceDetails = [...invoiceDetails];
  110 + newInvoiceDetails[index] = { ...newInvoiceDetails[index], totalPrice };
  111 + form.setFieldsValue({ invoiceDetails: newInvoiceDetails });
  112 + }
  113 + const totalPrice = invoiceDetails.reduce((accumulator, currentValue) => {
  114 + return FloatAdd(accumulator, currentValue.quantity * currentValue.price);
  115 + }, 0);
  116 + form.setFieldValue('price', totalPrice);
  117 + };
  118 + useEffect(() => {}, []);
  119 + return (
  120 + <DrawerForm
  121 + open
  122 + title="申请开票"
  123 + resize={{
  124 + maxWidth: window.innerWidth * 0.8,
  125 + minWidth: 500,
  126 + }}
  127 + form={form}
  128 + formRef={formRef}
  129 + autoFocusFirstInput
  130 + drawerProps={{
  131 + destroyOnClose: true,
  132 + }}
  133 + submitter={{
  134 + render: (props, defaultDoms) => {
  135 + return [
  136 + <InvoiceModal
  137 + key={'invoicePreview'}
  138 + button={<Button type="primary"> 发票预览 </Button>}
  139 + getRecord={() => {
  140 + const totalPrice = form
  141 + .getFieldValue('invoiceDetails')
  142 + .reduce((accumulator, currentValue) => {
  143 + return FloatAdd(accumulator, currentValue.totalPrice);
  144 + }, 0);
  145 + const partyBName = form.getFieldValue('partyBName');
  146 + const openBank = form.getFieldValue('openBank');
  147 + const bankAccount = form.getFieldValue('bankAccount');
  148 + const bankCode = form.getFieldValue('bankCode');
  149 + return {
  150 + ...form.getFieldsValue(),
  151 + totalPrice: totalPrice,
  152 + totalPriceText: convertCurrency(totalPrice),
  153 + comment:
  154 + '开户名称: ' +
  155 + partyBName +
  156 + '\n' +
  157 + '开户行: ' +
  158 + openBank +
  159 + '\n' +
  160 + '账号: ' +
  161 + bankAccount +
  162 + '\n' +
  163 + '银行联行号: ' +
  164 + bankCode,
  165 + };
  166 + }}
  167 + />,
  168 + ...defaultDoms,
  169 + ];
  170 + },
  171 + }}
  172 + submitTimeout={2000}
  173 + onFinish={async (values) => {
  174 + postServiceInvoiceApplyInvoice({
  175 + data: {
  176 + ...values,
  177 + subOrderIds: dataListCopy.map((item) => {
  178 + return item.id;
  179 + }),
  180 + },
  181 + });
  182 + onClose();
  183 + }}
  184 + onOpenChange={(val) => {
  185 + return !val && setVisible();
  186 + }}
  187 + >
  188 + <ProFormList
  189 + name="subOrderIdObjs"
  190 + readonly={true}
  191 + label="开票订单"
  192 + initialValue={dataListCopy.map((item) => {
  193 + return {
  194 + value: item.id,
  195 + };
  196 + })}
  197 + deleteIconProps={false}
  198 + copyIconProps={false}
  199 + >
  200 + <ProFormGroup key="group">
  201 + <ProFormText readonly={true} name="value" label="" />
  202 + </ProFormGroup>
  203 + </ProFormList>
  204 + <ProFormSelect
  205 + name="ReissueInvoiceRecordIds"
  206 + label="重开的发票"
  207 + fieldProps={{
  208 + mode: 'multiple',
  209 + }}
  210 + placeholder="请选择重开发票"
  211 + request={async () => {
  212 + let reissueIds = dataListCopy.map((item) => {
  213 + return item.id;
  214 + });
  215 + let res = await postServiceInvoiceWaitReissueInvoices({
  216 + data: reissueIds,
  217 + });
  218 + return enum2ReverseSelect(res.data);
  219 + }}
  220 + />
  221 + <ProFormSelect
  222 + key="key"
  223 + label="购方名称"
  224 + width="lg"
  225 + showSearch
  226 + name="partyAName"
  227 + placeholder="请搜索购方"
  228 + rules={[{ required: true, message: '购方名称必填' }]}
  229 + onChange={(_, option) => {
  230 + form.setFieldValue('partyATaxid', option.taxId);
  231 + }}
  232 + fieldProps={{
  233 + optionItemRender(item) {
  234 + if (item.type === 'add') {
  235 + return <>{item.name}</>;
  236 + }
  237 + return (
  238 + <>
  239 + {item.name}
  240 + <Divider type="vertical" />
  241 + {item.taxId}
  242 + </>
  243 + );
  244 + },
  245 + }}
  246 + debounceTime={1000}
  247 + request={async (value) => {
  248 + const keywords = value.keyWords;
  249 + const res = await postServiceInvoiceQueryCompanyInfo({
  250 + data: {
  251 + nameLike: keywords,
  252 + taxIdIsNotNull: true,
  253 + },
  254 + });
  255 + let options = res?.data?.map((company) => {
  256 + return {
  257 + ...company,
  258 + label: company.name,
  259 + value: company.name,
  260 + key: company.id,
  261 + };
  262 + });
  263 +
  264 + //第一个商品默认为要新增的商品
  265 + if (keywords.trim() !== '') {
  266 + options.unshift({
  267 + name: keywords,
  268 + type: 'add',
  269 + label: keywords,
  270 + value: keywords,
  271 + key: keywords,
  272 + });
  273 + }
  274 + return options;
  275 + }}
  276 + />
  277 + <ProFormText
  278 + width="md"
  279 + name="partyATaxid"
  280 + label="购方税号"
  281 + rules={[{ required: true, message: '购方税号必填' }]}
  282 + placeholder="请输入名称"
  283 + />
  284 + <ProFormText
  285 + width="md"
  286 + name="partyAOpenBank"
  287 + label="开户银行"
  288 + placeholder="请输入名称"
  289 + />
  290 + <ProFormText
  291 + width="md"
  292 + name="partyABankAccount"
  293 + label="开户行账号"
  294 + placeholder="请输入名称"
  295 + />
  296 + <ProFormText
  297 + name="contacts"
  298 + label="联系人"
  299 + rules={[{ required: true, message: '请选择银行联行号!' }]}
  300 + />
  301 + <ProFormSelect
  302 + name="invoicingType"
  303 + label="开具类型"
  304 + request={async () => {
  305 + let invoicingTypeRet = await postServiceConstInvoicingType();
  306 + let options = enumToSelect(invoicingTypeRet.data);
  307 + return options;
  308 + }}
  309 + placeholder="请选择开具类型"
  310 + rules={[{ required: true, message: '请选择开具类型!' }]}
  311 + />
  312 + <ProFormSelect
  313 + name="type"
  314 + label="开票类型"
  315 + placeholder="请选择开票类型"
  316 + rules={[{ required: true, message: '请选择开票类型!' }]}
  317 + request={async () => {
  318 + let invoiceTypeRet = await postServiceConstInvoiceType();
  319 + let options = enumToSelect(invoiceTypeRet.data);
  320 + return options;
  321 + }}
  322 + />
  323 + <ProFormSelect
  324 + name="partyB"
  325 + label="开票收款单位"
  326 + request={async () => {
  327 + const res = await postServiceConstGetPayeeEnum();
  328 + let options = res?.data?.map((payee: any) => {
  329 + return {
  330 + ...payee,
  331 + label: payee.payeeName,
  332 + value: payee.name,
  333 + };
  334 + });
  335 + return options;
  336 + }}
  337 + onChange={(_, option) => {
  338 + if (option) {
  339 + form.setFieldsValue({
  340 + partyBName: option.payeeName,
  341 + partyBTaxid: option.taxId,
  342 + bankAccount: option.bankAccount,
  343 + openBank: option.openBank,
  344 + bankCode: option.bankCode,
  345 + });
  346 + }
  347 + }}
  348 + placeholder="请选择收款单位"
  349 + rules={[{ required: true, message: '请选择收款单位!' }]}
  350 + />
  351 + <ProFormText
  352 + name="partyBName"
  353 + label="开票收款单位名称"
  354 + hidden
  355 + rules={[{ required: true, message: '请选择收款单位!' }]}
  356 + />
  357 + <ProFormText
  358 + name="partyBTaxid"
  359 + label="开票收款单位税号"
  360 + hidden
  361 + rules={[{ required: true, message: '请选择收款单位!' }]}
  362 + />
  363 + <ProFormText
  364 + name="bankAccount"
  365 + label="账号"
  366 + hidden
  367 + rules={[{ required: true, message: '请选择账号!' }]}
  368 + />
  369 + <ProFormText
  370 + name="openBank"
  371 + label="开户行"
  372 + hidden
  373 + rules={[{ required: true, message: '请选择开户行!' }]}
  374 + />
  375 + <ProFormText
  376 + name="bankCode"
  377 + label="银行联行号"
  378 + hidden
  379 + rules={[{ required: true, message: '请选择银行联行号!' }]}
  380 + />
  381 + <ProFormSelect
  382 + name="isUrgent"
  383 + label="是否加急"
  384 + valueEnum={{
  385 + true: '是',
  386 + false: '否',
  387 + }}
  388 + placeholder="请选择是否加急"
  389 + rules={[{ required: true, message: '请选择是否加急!' }]}
  390 + />
  391 + <ProFormMoney
  392 + label="开票金额"
  393 + name="price"
  394 + locale="zh-CN"
  395 + disabled={true}
  396 + rules={[{ required: true, message: '请填写开票金额!' }]}
  397 + initialValue={dataListCopy.reduce((accumulator, currentValue) => {
  398 + return accumulator + currentValue.subOrderPayment;
  399 + }, 0)}
  400 + />
  401 + <ProFormList
  402 + name="invoiceDetails"
  403 + label="开票明细"
  404 + actionRef={listActionRef}
  405 + actionGuard={{
  406 + beforeRemoveRow: async (index) => {
  407 + const list = listActionRef.current?.getList();
  408 + const totalPrice = list[index].totalPrice;
  409 + form.setFieldValue(
  410 + 'price',
  411 + FloatSub(form.getFieldValue('price'), totalPrice),
  412 + );
  413 + return true;
  414 + },
  415 + }}
  416 + rules={[
  417 + {
  418 + required: true,
  419 + validator: async (_, value) => {
  420 + if (value && value.length > 0) {
  421 + return;
  422 + }
  423 + throw new Error('至少要有一项!');
  424 + },
  425 + },
  426 + ]}
  427 + itemRender={(doms, listMeta) => {
  428 + return (
  429 + <ProCard
  430 + bordered
  431 + extra={doms.action}
  432 + title={'明细' + (listMeta.index + 1)}
  433 + style={{
  434 + marginBlockEnd: 8,
  435 + }}
  436 + >
  437 + <Tooltip title="点击复制商品名称">
  438 + <Space
  439 + className="hover:cursor-pointer"
  440 + style={{
  441 + margin: 16,
  442 + marginTop: 4,
  443 + marginLeft: 0,
  444 + fontSize: 15,
  445 + }}
  446 + onClick={() => {
  447 + copyToClipboard(listMeta.record.productName);
  448 + message.info('商品名称复制成功!');
  449 + }}
  450 + >
  451 + 商品名称:{listMeta.record.productName}
  452 + </Space>
  453 + </Tooltip>
  454 + <ProFormSelect
  455 + key={'projectName' + listMeta.index}
  456 + width="md"
  457 + showSearch
  458 + name="projectName"
  459 + rules={[{ required: true, message: '请输入开票项目名称!' }]}
  460 + request={async (value) => {
  461 + const keywords = value.keyWords;
  462 + const res = await postServiceConstListInvoiceDetailNames({
  463 + data: {
  464 + nameLike: keywords,
  465 + },
  466 + });
  467 + let options = res?.data?.map((c: any) => {
  468 + return {
  469 + ...c,
  470 + label:
  471 + '*' +
  472 + c.productAndServiceCatagoryAbbreviation +
  473 + '*' +
  474 + c.name,
  475 + value:
  476 + '*' +
  477 + c.productAndServiceCatagoryAbbreviation +
  478 + '*' +
  479 + c?.name,
  480 + key: c.id,
  481 + };
  482 + });
  483 + return options;
  484 + }}
  485 + fieldProps={{
  486 + filterOption() {
  487 + return true;
  488 + },
  489 + }}
  490 + onChange={(_, option) => {
  491 + let index = listMeta.index;
  492 + let copyList = form.getFieldValue('invoiceDetails');
  493 + let currentData = copyList[index];
  494 + currentData.projectName =
  495 + '*' +
  496 + option.productAndServiceCatagoryAbbreviation +
  497 + '*' +
  498 + option.name;
  499 + form.setFieldValue('invoiceDetails', copyList);
  500 + }}
  501 + debounceTime={1000}
  502 + label="项目名称"
  503 + initialValue={listMeta.record.projectName}
  504 + placeholder="请输入名称"
  505 + />
  506 + <ProFormText
  507 + key={'specification' + listMeta.index}
  508 + name="specification"
  509 + label="规格型号"
  510 + rules={[
  511 + {
  512 + message: '规格型号不能为空!',
  513 + required: true,
  514 + },
  515 + {
  516 + validator: (_, value) => {
  517 + let len = 0;
  518 + // 判断是否为全角字符
  519 + for (let i = 0; i < value.length; i++) {
  520 + // 获取字符的Unicode值
  521 + const code = value.charCodeAt(i);
  522 + // 判断是否为全角字符
  523 + if (
  524 + (code >= 0xff01 && code <= 0xff5e) ||
  525 + (code >= 0x4e00 && code <= 0x9fff)
  526 + ) {
  527 + len += 2; // 全角字符
  528 + } else {
  529 + len += 1; // 半角字符
  530 + }
  531 + }
  532 + if (len <= 40) {
  533 + return Promise.resolve();
  534 + }
  535 + return Promise.reject(
  536 + new Error('规格型号不能超过40个字符!'),
  537 + );
  538 + },
  539 + },
  540 + ]}
  541 + placeholder="请输入名称"
  542 + />
  543 + <ProFormText
  544 + key={'unit' + listMeta.index}
  545 + name="unit"
  546 + label="单位"
  547 + placeholder="请输入名称"
  548 + />
  549 + <ProFormDigit
  550 + key={'quantity' + listMeta.index}
  551 + label="数量"
  552 + name="quantity"
  553 + rules={[
  554 + {
  555 + validator: (_, value) => {
  556 + if (value === undefined || value > 0) {
  557 + return Promise.resolve();
  558 + }
  559 + return Promise.reject(new Error('数量必须大于0'));
  560 + },
  561 + },
  562 + ]}
  563 + onChange={() => calculateTotalPrice(listMeta.index)}
  564 + />
  565 +
  566 + <ProFormDigit
  567 + key={'price' + listMeta.index}
  568 + label="单价"
  569 + name="price"
  570 + rules={[
  571 + {
  572 + validator: (_, value) => {
  573 + if (value === undefined || value > 0) {
  574 + return Promise.resolve();
  575 + }
  576 + return Promise.reject(new Error('单价必须大于0'));
  577 + },
  578 + },
  579 + ]}
  580 + onChange={() => calculateTotalPrice(listMeta.index)}
  581 + />
  582 +
  583 + <ProFormMoney
  584 + key={'totalPrice' + listMeta.index}
  585 + label="金额"
  586 + name="totalPrice"
  587 + rules={[
  588 + {
  589 + validator: (_, value) => {
  590 + if (value === undefined || value > 0) {
  591 + return Promise.resolve();
  592 + }
  593 + return Promise.reject(new Error('金额必须大于0'));
  594 + },
  595 + },
  596 + ]}
  597 + readonly={true}
  598 + locale="zh-CN"
  599 + />
  600 + </ProCard>
  601 + );
  602 + }}
  603 + ></ProFormList>
  604 + <ProFormTextArea
  605 + name="applyInvoicingNotes"
  606 + label="备注"
  607 + placeholder="请输入名称"
  608 + />
  609 + </DrawerForm>
  610 + );
  611 +};
... ...
src/pages/Order/FeedBack/KingdeeCustomerModal.tsx 0 → 100644
  1 +import {
  2 + postKingdeeRepCustomerDetail,
  3 + postKingdeeRepCustomerSave,
  4 +} from '@/services';
  5 +import { getTeacherCustomFieldNumber } from '@/utils/kingdee';
  6 +import { CloseCircleOutlined } from '@ant-design/icons';
  7 +import {
  8 + ModalForm,
  9 + ProFormGroup,
  10 + ProFormList,
  11 + ProFormText,
  12 +} from '@ant-design/pro-components';
  13 +import { Form, message } from 'antd';
  14 +
  15 +// import { cloneDeep } from 'lodash';
  16 +export default ({ setVisible, data, onClose }) => {
  17 + const [form] = Form.useForm();
  18 +
  19 + /**
  20 + * 回显客户信息
  21 + * @param id
  22 + */
  23 + async function queryAndShowCustomer(id: any) {
  24 + //查询客户信息
  25 + let res = await postKingdeeRepCustomerDetail({
  26 + data: {
  27 + id: id,
  28 + },
  29 + });
  30 +
  31 + if (res) {
  32 + form.setFieldValue('name', res.name);
  33 + form.setFieldValue('id', res.id);
  34 + form.setFieldValue('contact_persons', res.bomentity);
  35 + let customFiledNumber = await getTeacherCustomFieldNumber();
  36 + form.setFieldValue('teacherName', res.custom_field[customFiledNumber]);
  37 + }
  38 + }
  39 +
  40 + if (data) {
  41 + //修改
  42 + if (data.id) {
  43 + queryAndShowCustomer(data.id);
  44 + } else {
  45 + //新增
  46 + form.setFieldValue('name', data.name);
  47 + form.setFieldValue('contact_persons', [{}]);
  48 + }
  49 + }
  50 + return (
  51 + <>
  52 + <ModalForm
  53 + width={900}
  54 + open
  55 + title="客户信息"
  56 + form={form}
  57 + autoFocusFirstInput
  58 + modalProps={{
  59 + okText: '保存',
  60 + cancelText: '取消',
  61 + destroyOnClose: true,
  62 + onCancel: () => {
  63 + setVisible(false);
  64 + },
  65 + }}
  66 + onFinish={async (values) => {
  67 + //查询客户自定义字段,课题组
  68 + let custom_field_umber = await getTeacherCustomFieldNumber();
  69 +
  70 + if (custom_field_umber) {
  71 + let customFieldObj = {};
  72 + customFieldObj[custom_field_umber] = values.teacherName;
  73 + values.custom_field = customFieldObj;
  74 + }
  75 + let customSaveRes = await postKingdeeRepCustomerSave({
  76 + data: values,
  77 + });
  78 + if (customSaveRes) {
  79 + let id_number_map = customSaveRes.id_number_map;
  80 + let ids = customSaveRes.ids;
  81 + if (id_number_map && ids) {
  82 + message.success('保存成功');
  83 + let id = ids[0];
  84 + onClose(id);
  85 + }
  86 + }
  87 + }}
  88 + onOpenChange={setVisible}
  89 + >
  90 + <ProFormText key="key" name="id" label="id" placeholder="id" hidden />
  91 + <ProFormGroup key="group">
  92 + <ProFormText
  93 + name="name"
  94 + width="md"
  95 + label={
  96 + <>
  97 + <span>客户名称</span>
  98 + <span className="pl-2 text-xs text-gray-400">
  99 + 请按照[单位]-[姓名][手机号]命名,例如:清华大学-张三12345678919
  100 + </span>
  101 + </>
  102 + }
  103 + initialValue={data}
  104 + placeholder="请输入客户名称"
  105 + rules={[
  106 + {
  107 + required: true,
  108 + pattern: new RegExp('^.+-.+$'),
  109 + message:
  110 + '格式错误,若无单位可写:“无-姓名手机号”,例如:无-张三12345678919',
  111 + },
  112 + { required: true, message: '客户名称必填' },
  113 + ]}
  114 + />
  115 + <ProFormText
  116 + name="teacherName"
  117 + width="md"
  118 + label="课题组老师"
  119 + placeholder="请输入课题组老师"
  120 + rules={[
  121 + { required: true, message: '课题组老师必填,若没有请填“无”' },
  122 + ]}
  123 + />
  124 + </ProFormGroup>
  125 +
  126 + <ProFormList
  127 + creatorButtonProps={{ disabled: false }}
  128 + name="contact_persons"
  129 + label="联系人信息"
  130 + actionGuard={{
  131 + beforeRemoveRow: async () => {
  132 + return new Promise((resolve) => {
  133 + let contactPersons = form.getFieldValue('contact_persons');
  134 + if (contactPersons.length === 1) {
  135 + message.error('至少要有一个联系人');
  136 + resolve(false);
  137 + return;
  138 + }
  139 + resolve(true);
  140 + });
  141 + },
  142 + beforeAddRow: (defaultValue) => {
  143 + defaultValue.id = undefined; //复制的时候要把id去掉
  144 + return defaultValue;
  145 + },
  146 + }}
  147 + deleteIconProps={{
  148 + Icon: CloseCircleOutlined,
  149 + tooltipText: '不需要这行了',
  150 + }}
  151 + >
  152 + <ProFormGroup key="group">
  153 + {[
  154 + <ProFormText
  155 + key="key"
  156 + name="id"
  157 + label="id"
  158 + placeholder="id"
  159 + hidden
  160 + />,
  161 + <ProFormText
  162 + key="key"
  163 + name="contact_person"
  164 + label="收货人姓名"
  165 + placeholder="联系人姓名"
  166 + rules={[{ required: true, message: '收货人姓名必填' }]}
  167 + />,
  168 + <ProFormText
  169 + key="key"
  170 + name="mobile"
  171 + label="联系方式"
  172 + placeholder="联系方式"
  173 + rules={[{ required: true, message: '联系方式必填' }]}
  174 + />,
  175 + <ProFormText
  176 + key="key"
  177 + name="contact_address"
  178 + label="收货地址"
  179 + width="md"
  180 + placeholder="请输入收货地址"
  181 + rules={[{ required: true, message: '收货地址必填' }]}
  182 + />,
  183 + ]}
  184 + </ProFormGroup>
  185 + </ProFormList>
  186 + </ModalForm>
  187 + </>
  188 + );
  189 +};
... ...
src/pages/Order/FeedBack/LazySelect.tsx 0 → 100644
  1 +import { Select, Spin } from 'antd';
  2 +import debounce from 'lodash/debounce';
  3 +import difference from 'lodash/difference';
  4 +import { useEffect, useState } from 'react';
  5 +
  6 +/**
  7 + * 懒加载 Select ,适用于数据超大的下拉框
  8 + * @param {Select 官方属性} props
  9 + */
  10 +const LazySelect = (props) => {
  11 + const { value, onChange, query, pageSize } = props;
  12 + // 清除 porps 中 query,避免控制台警告
  13 + const selectProps = { ...props, query: undefined };
  14 +
  15 + const [selected, setSelected] = useState(value);
  16 + const [data, setData] = useState([]);
  17 + const [loading, setLoading] = useState(false);
  18 + const [current, setCurrent] = useState(1);
  19 + const [total, setTotal] = useState(0);
  20 + const [searchText] = useState('');
  21 +
  22 + const getSelectedArray = (obj) => {
  23 + let selectedValues = obj;
  24 + // 如果是单选,将值封装为数组
  25 + if (obj && obj instanceof Array === false) {
  26 + selectedValues = [obj];
  27 + }
  28 + return selectedValues;
  29 + };
  30 +
  31 + // 添加 300 毫秒防抖
  32 + const handleQuery = debounce(async (param) => {
  33 + setLoading(true);
  34 + const resp = await query(param);
  35 + const resData = resp.data;
  36 + let options = resData?.data?.map((p: any) => {
  37 + return {
  38 + ...p,
  39 + label: p.productName,
  40 + value: p.productName,
  41 + key: p.id,
  42 + };
  43 + });
  44 + // 搜索服务端异步加载
  45 + // const handleSearch = (filter) => {
  46 + // setSearchText(filter);
  47 + // handleQuery({
  48 + // filter,
  49 + // selectedValues: getSelectedArray(selected),
  50 + // pageSize,
  51 + // current
  52 + // });
  53 + // };
  54 +
  55 + //第一个商品默认为要新增的商品
  56 + // if (searchValue?.trim() !== "") {
  57 + // options.unshift({ productName: searchValue, type: "add", label: searchValue, value: searchValue })
  58 + // }
  59 + // return options;
  60 + setTotal(resData.total);
  61 + setData(data.concat(options));
  62 + setLoading(false);
  63 + }, 300);
  64 +
  65 + // 组件初始化时加载一次数据
  66 + useEffect(() => {
  67 + handleQuery({
  68 + filter: '',
  69 + selectedValues: getSelectedArray(value),
  70 + });
  71 + }, []);
  72 +
  73 + // 外部注入的 value 变化后,如果 value 在 data 中不存在,则加载数据
  74 + useEffect(() => {
  75 + setSelected(value);
  76 + const dataKeys = data.map((item) => item.value);
  77 + const diff = difference(getSelectedArray(value), dataKeys);
  78 + if (diff && diff.length > 0) {
  79 + handleQuery({
  80 + filter: '',
  81 + selectedValues: getSelectedArray(value),
  82 + });
  83 + }
  84 + }, [value]);
  85 +
  86 + const keywordChange = (newWord: string) => {
  87 + setData([]);
  88 + setTotal(0);
  89 + setCurrent(1);
  90 + handleSearch(newWord);
  91 + };
  92 +
  93 + const handleChange = (newValue, option) => {
  94 + setSelected(newValue);
  95 + if (onChange) {
  96 + // 将值通过 onChange 传递到外部
  97 + onChange(newValue, option);
  98 + }
  99 + };
  100 +
  101 + const scrollEnd = (e) => {
  102 + e.persist();
  103 + const { target } = e;
  104 + // 滚动 触底 看接口是否还有剩余的值没传过来
  105 + if (target.scrollTop + target.offsetHeight === target.scrollHeight) {
  106 + if (current * pageSize < total) {
  107 + setCurrent(current + 1);
  108 + handleSearch(searchText);
  109 + }
  110 + }
  111 + };
  112 +
  113 + return (
  114 + <Select
  115 + {...selectProps}
  116 + value={selected}
  117 + loading={loading}
  118 + onSearch={keywordChange}
  119 + onChange={handleChange}
  120 + onPopupScroll={scrollEnd}
  121 + filterOption={false}
  122 + options={data}
  123 + showSearch
  124 + showArrow
  125 + notFoundContent={loading ? <Spin size="small" /> : null}
  126 + >
  127 + {/* {data.map(d => (
  128 + <Option key={d.value} title={d.label}>
  129 + {d.label}
  130 + </Option>
  131 + ))} */}
  132 + </Select>
  133 + );
  134 +};
  135 +
  136 +export default LazySelect;
... ...
src/pages/Order/FeedBack/MessageListDrawer.tsx 0 → 100644
  1 +import ButtonConfirm from '@/components/ButtomConfirm';
  2 +import { RESPONSE_CODE } from '@/constants/enum';
  3 +import {
  4 + postOrderErpMessageQueryMyMessage,
  5 + postOrderErpMessageRead,
  6 + postOrderErpMessageReadAll,
  7 +} from '@/services';
  8 +import { formatDateTime, getUserInfo } from '@/utils';
  9 +import { UserOutlined } from '@ant-design/icons';
  10 +import {
  11 + Avatar,
  12 + Badge,
  13 + Button,
  14 + Checkbox,
  15 + Drawer,
  16 + Flex,
  17 + List,
  18 + message,
  19 +} from 'antd';
  20 +import { useEffect, useState } from 'react';
  21 +
  22 +export default ({ setVisible }) => {
  23 + const userInfo = getUserInfo();
  24 + const [current, setCurrent] = useState<number>(1); //当前页码
  25 + const [total, setTotal] = useState(0);
  26 + const [messageListData, setMessageListData] = useState<any[]>([]); //列表数据
  27 + const [loading, setLoading] = useState(false);
  28 + const [initLoading, setInitLoading] = useState(true);
  29 + const [onlyUnread, setOnlyUnread] = useState(false);
  30 + const [readFlagList, setReadFlagList] = useState<any[]>([]); //消息未读标识
  31 +
  32 + /**
  33 + * 获取消息列表
  34 + */
  35 + async function getMessageListData() {
  36 + setLoading(true);
  37 + let res = await postOrderErpMessageQueryMyMessage({
  38 + data: {
  39 + username: userInfo.username,
  40 + current: current,
  41 + isReaded: onlyUnread ? onlyUnread : null,
  42 + },
  43 + }).finally(() => {
  44 + setLoading(false);
  45 + });
  46 + if (res && res.result === RESPONSE_CODE.SUCCESS) {
  47 + setMessageListData([...messageListData, ...res?.data?.data]);
  48 + let reqReadNumList = res?.data?.data?.map((item: any) => {
  49 + return item.isReaded;
  50 + });
  51 +
  52 + setReadFlagList([...readFlagList, ...reqReadNumList]);
  53 + setTotal(res?.data?.total);
  54 + }
  55 + }
  56 +
  57 + /**
  58 + * 跳转到订单列表
  59 + */
  60 + function toOrderList(mainOrderIds: any) {
  61 + window.open('/order/order?id=' + mainOrderIds.join(','), '_blank');
  62 + }
  63 +
  64 + /**
  65 + * 仅展示未读
  66 + */
  67 + function onlyUnreadChange() {
  68 + setOnlyUnread(!onlyUnread);
  69 + setCurrent(1);
  70 + setMessageListData([]);
  71 + setReadFlagList([]);
  72 + }
  73 +
  74 + /**
  75 + * 仅展示未读
  76 + */
  77 + async function read(ids: any[]) {
  78 + let res = await postOrderErpMessageRead({
  79 + data: ids,
  80 + });
  81 +
  82 + if (res && res.result === RESPONSE_CODE.SUCCESS) {
  83 + message.success('已读');
  84 + return true;
  85 + }
  86 +
  87 + return false;
  88 + }
  89 +
  90 + /**
  91 + * 全部标记为已读
  92 + */
  93 + async function readAll() {
  94 + let res = await postOrderErpMessageReadAll();
  95 + if (res && res.result === RESPONSE_CODE.SUCCESS) {
  96 + message.success(res.message);
  97 + let newReadFlagList = readFlagList.map(() => true);
  98 + setReadFlagList(newReadFlagList);
  99 + }
  100 + }
  101 +
  102 + const loadMore =
  103 + !initLoading && !loading && messageListData.length < total ? (
  104 + <div
  105 + style={{
  106 + textAlign: 'center',
  107 + marginTop: 12,
  108 + height: 32,
  109 + lineHeight: '32px',
  110 + }}
  111 + >
  112 + <Button
  113 + onClick={() => {
  114 + setCurrent(current + 1);
  115 + }}
  116 + >
  117 + 加载更多
  118 + </Button>
  119 + </div>
  120 + ) : messageListData.length > 0 && messageListData.length >= total ? (
  121 + <span>没有更多消息了</span>
  122 + ) : null;
  123 +
  124 + useEffect(() => {
  125 + getMessageListData().finally(() => {
  126 + setInitLoading(false);
  127 + });
  128 + }, [onlyUnread, current]);
  129 +
  130 + return (
  131 + <>
  132 + <Drawer
  133 + title="消息列表"
  134 + onClose={() => {
  135 + setVisible(false);
  136 + }}
  137 + open={true}
  138 + width={450}
  139 + styles={{ body: { paddingTop: 0 } }}
  140 + >
  141 + <Flex align="center" justify="space-between">
  142 + <div>
  143 + <Checkbox onChange={onlyUnreadChange} checked={onlyUnread}>
  144 + 仅展示未读
  145 + </Checkbox>
  146 + </div>
  147 +
  148 + <ButtonConfirm
  149 + title={'是否全部标记为已读?'}
  150 + text={'全部标记为已读'}
  151 + className="p-0"
  152 + onConfirm={readAll}
  153 + placement="bottomLeft"
  154 + />
  155 + </Flex>
  156 +
  157 + <List
  158 + className="demo-loadmore-list"
  159 + loading={initLoading}
  160 + itemLayout="horizontal"
  161 + loadMore={loadMore}
  162 + dataSource={messageListData}
  163 + renderItem={(item: any, index: any) => (
  164 + <List.Item key={item.id}>
  165 + <List.Item.Meta
  166 + avatar={
  167 + <Badge dot={!readFlagList[index]}>
  168 + <Avatar shape="square" icon={<UserOutlined />} />
  169 + </Badge>
  170 + }
  171 + />
  172 + <Flex
  173 + vertical
  174 + className="w-full hover:cursor-pointer"
  175 + onClick={async () => {
  176 + toOrderList(item.mainOrderIds);
  177 +
  178 + let readSuccess = await read([item.mesUsrRelId]);
  179 + if (readSuccess) {
  180 + let newUnreadNumList = [...readFlagList];
  181 + newUnreadNumList[index] = true;
  182 + setReadFlagList([...newUnreadNumList]);
  183 + }
  184 + }}
  185 + >
  186 + <Flex>
  187 + <div>
  188 + {item.content}
  189 + {/* <span className="text-[#8C8C8C]">
  190 + (点击跳转到对应主订单)
  191 + </span> */}
  192 + </div>
  193 + </Flex>
  194 + <Flex>
  195 + <span className="text-xs text-[#8C8C8C] pt-1">
  196 + {formatDateTime(item.createTime)}
  197 + </span>
  198 + </Flex>
  199 + </Flex>
  200 + </List.Item>
  201 + )}
  202 + />
  203 + </Drawer>
  204 + </>
  205 + );
  206 +};
... ...
src/pages/Order/FeedBack/ModifiedDiffModal.tsx 0 → 100644
  1 +import { postServiceOrderModifiedDiff } from '@/services';
  2 +import { enumValueToLabel, getAliYunOSSFileNameFromUrl } from '@/utils';
  3 +import { getReceivingCompanyOptions, isSupplier } from '@/utils/order';
  4 +import { Button, Divider, Modal, Space, Table, TableProps } from 'antd';
  5 +import { useEffect, useState } from 'react';
  6 +import {
  7 + PAYEE_OPTIONS,
  8 + PAYMENT_CHANNEL_OPTIONS,
  9 + PAYMENT_METHOD_OPTIONS,
  10 + PRODUCT_BELONG_DEPARTMENT_OPTIONS,
  11 + SHIPPING_WAREHOUSE_OPTIONS,
  12 +} from './constant';
  13 +import './table.less';
  14 +
  15 +export default ({ setVisible, subOrders, mainOrder, onClose }) => {
  16 + let subIds = subOrders?.map((item: any) => {
  17 + return item.id;
  18 + });
  19 +
  20 + let mainId = mainOrder?.id;
  21 +
  22 + const [subOrderDiffs, setSubOrderDiffs] = useState([]);
  23 + const [mainOrderDiffs, setMainOrderDiffs] = useState([]);
  24 +
  25 + function isSupplierUnvisibleField(field: any) {
  26 + //主订单字段
  27 + let unvisibleFields = [
  28 + 'receivingCompany',
  29 + 'invoiceIdentificationNumber',
  30 + 'bankAccountNumber',
  31 + 'bank',
  32 + 'totalPayment',
  33 + 'institution',
  34 + 'institutionContactName',
  35 + ];
  36 + //子订单字段
  37 + unvisibleFields.push(
  38 + ...[
  39 + 'listAnnex',
  40 + 'shippingWarehouse',
  41 + 'productBelongBusiness',
  42 + 'subOrderPayment',
  43 + 'productPrice',
  44 + ],
  45 + );
  46 +
  47 + return isSupplier() && unvisibleFields.includes(field);
  48 + }
  49 +
  50 + async function loadData() {
  51 + let res = await postServiceOrderModifiedDiff({
  52 + data: {
  53 + subOrderIds: subIds,
  54 + mainOrderId: mainId,
  55 + },
  56 + });
  57 + let subOrderDiffs = res?.data?.subOrderDiffs;
  58 + let mainOrderDiffs = res?.data?.mainOrderDiffs;
  59 +
  60 + setSubOrderDiffs(subOrderDiffs);
  61 + setMainOrderDiffs(mainOrderDiffs);
  62 + }
  63 +
  64 + useEffect(() => {
  65 + loadData();
  66 + }, []);
  67 +
  68 + function toChineseName(key: any, text: any) {
  69 + let newText = text;
  70 + if (key === '所属事业部') {
  71 + newText = enumValueToLabel(text, PRODUCT_BELONG_DEPARTMENT_OPTIONS);
  72 + }
  73 + if (key === '发货仓库') {
  74 + newText = enumValueToLabel(text, SHIPPING_WAREHOUSE_OPTIONS);
  75 + }
  76 + if (key === '支付渠道') {
  77 + newText = enumValueToLabel(text, PAYMENT_CHANNEL_OPTIONS);
  78 + }
  79 + if (key === '支付方式') {
  80 + newText = enumValueToLabel(text, PAYMENT_METHOD_OPTIONS);
  81 + }
  82 + if (key === '单价' || key === '合计') {
  83 + newText = '¥' + newText;
  84 + }
  85 + if (key === '开票收款单位') {
  86 + newText = enumValueToLabel(
  87 + text,
  88 + getReceivingCompanyOptions(PAYEE_OPTIONS),
  89 + );
  90 + }
  91 + return newText;
  92 + }
  93 +
  94 + function cellRender(value: any, record: any) {
  95 + if (record.fieldName === '附件') {
  96 + return (
  97 + <Space className="max-w-[300px]" wrap>
  98 + {value?.map((item: any, index: any) => {
  99 + let fileName = getAliYunOSSFileNameFromUrl(item);
  100 + return (
  101 + <Button
  102 + className="p-0 pr-2"
  103 + key={index}
  104 + danger={record.isDiff}
  105 + type="link"
  106 + onClick={() => {
  107 + window.open(
  108 + '/previewApi/onlinePreview?url=' +
  109 + encodeURIComponent(Base64.encode(item)),
  110 + );
  111 + }}
  112 + >
  113 + {fileName}
  114 + </Button>
  115 + );
  116 + })}
  117 + </Space>
  118 + );
  119 + }
  120 + return (
  121 + <div
  122 + title={toChineseName(record.fieldName, value)}
  123 + className="max-w-[250px] whitespace-no-wrap overflow-hidden overflow-ellipsis"
  124 + >
  125 + <span className={record.isDiff ? 'text-[red]' : ''}>
  126 + {toChineseName(record.fieldName, value)}
  127 + </span>
  128 + </div>
  129 + );
  130 + }
  131 +
  132 + interface DataType {
  133 + fieldName: string;
  134 + oldValue: string;
  135 + newValue: string;
  136 + isDiff: boolean;
  137 + }
  138 +
  139 + const columns: TableProps<DataType>['columns'] = [
  140 + {
  141 + title: '字段名',
  142 + dataIndex: 'fieldName',
  143 + key: 'fieldName',
  144 + render(value) {
  145 + return (
  146 + <div
  147 + title={value}
  148 + className="max-w-[80px] whitespace-no-wrap overflow-hidden overflow-ellipsis"
  149 + >
  150 + {value}
  151 + </div>
  152 + );
  153 + },
  154 + },
  155 + {
  156 + title: '修改前字段值',
  157 + dataIndex: 'oldValue',
  158 + key: 'oldValue',
  159 + render(value, record) {
  160 + return cellRender(value, record);
  161 + },
  162 + },
  163 + {
  164 + title: '修改后(当前)字段值',
  165 + dataIndex: 'newValue',
  166 + key: 'newValue',
  167 + render(value, record) {
  168 + return cellRender(value, record);
  169 + },
  170 + },
  171 + ];
  172 +
  173 + function loadSubOrderDiffTable(item: any, index: any) {
  174 + //转换为表格数据
  175 + let oldDatas = item[0];
  176 + let curDatas = item[1];
  177 + let diffFiledNames = oldDatas?.diffFieldsName;
  178 +
  179 + let tableData = [];
  180 + let visibleFields = [
  181 + ['productName', '商品名称'],
  182 + ['productCode', '商品编码'],
  183 + ['parameters', '商品参数'],
  184 + ['quantity', '数量'],
  185 + ['productPrice', '单价'],
  186 + ['unit', '单位'],
  187 + ['subOrderPayment', '合计'],
  188 + ['productBelongBusiness', '所属事业部'],
  189 + ['shippingWarehouse', '发货仓库'],
  190 + ['notes', '备注'],
  191 + ['paymentChannel', '支付渠道'],
  192 + ['paymentMethod', '支付方式'],
  193 + ['listAnnex', '附件'],
  194 + ];
  195 + for (let field of visibleFields) {
  196 + let filedKey = field[0];
  197 + let filedName = field[1];
  198 +
  199 + if (!isSupplierUnvisibleField(filedKey)) {
  200 + tableData.push({
  201 + fieldName: filedName,
  202 + oldValue: oldDatas[filedKey],
  203 + newValue: curDatas[filedKey],
  204 + isDiff: diffFiledNames?.includes(filedKey),
  205 + });
  206 + }
  207 + }
  208 + return (
  209 + <>
  210 + <Divider orientation="left">商品{index + 1}:</Divider>
  211 + <Table
  212 + className="myTable"
  213 + size="small"
  214 + pagination={false}
  215 + key={index}
  216 + columns={columns}
  217 + dataSource={tableData}
  218 + />
  219 + </>
  220 + );
  221 + }
  222 +
  223 + function loadMainOrderDiffTable(item: any, index: any) {
  224 + if (!item || item.length <= 0) {
  225 + return;
  226 + }
  227 + //转换为表格数据
  228 + let oldDatas = item[0];
  229 + let curDatas = item[1];
  230 + let diffFiledNames = oldDatas?.diffFieldsName;
  231 +
  232 + let tableData = [];
  233 + let visibleFields = [
  234 + ['salesCode', '销售代号'],
  235 + ['customerName', '收货人姓名'],
  236 + ['customerContactNumber', '收货人联系手机号'],
  237 + ['customerShippingAddress', '收货人地址信息'],
  238 + ['institutionContactName', '单位联系人'],
  239 + ['institution', '单位'],
  240 + ['totalPayment', '支付总金额'],
  241 + ['notes', '备注'],
  242 + ['bank', '开户银行'],
  243 + ['bankAccountNumber', '银行账号'],
  244 + ['invoiceIdentificationNumber', '开票识别号'],
  245 + ['receivingCompany', '开票收款单位'],
  246 + ];
  247 + for (let field of visibleFields) {
  248 + let filedKey = field[0];
  249 + let filedName = field[1];
  250 +
  251 + if (!isSupplierUnvisibleField(filedKey)) {
  252 + tableData.push({
  253 + fieldName: filedName,
  254 + oldValue: oldDatas[filedKey],
  255 + newValue: curDatas[filedKey],
  256 + isDiff: diffFiledNames?.includes(filedKey),
  257 + });
  258 + }
  259 + }
  260 + return (
  261 + <Table
  262 + className="myTable"
  263 + size="small"
  264 + pagination={false}
  265 + key={index}
  266 + columns={columns}
  267 + dataSource={tableData}
  268 + />
  269 + );
  270 + }
  271 +
  272 + return (
  273 + <>
  274 + <Modal
  275 + width={700}
  276 + open
  277 + title="信息对比"
  278 + okText="返回"
  279 + cancelText={false}
  280 + onOk={() => {
  281 + setVisible(false);
  282 + onClose();
  283 + }}
  284 + onCancel={() => {
  285 + setVisible(false);
  286 + }}
  287 + cancelButtonProps={{
  288 + hidden: true,
  289 + }}
  290 + destroyOnClose={true}
  291 + >
  292 + <Divider>主订单信息:</Divider>
  293 + {loadMainOrderDiffTable(mainOrderDiffs, 0)}
  294 +
  295 + <Divider>子订单信息:</Divider>
  296 + {subOrderDiffs?.map((item: any, index) => {
  297 + return loadSubOrderDiffTable(item, index);
  298 + })}
  299 + </Modal>
  300 + </>
  301 + );
  302 +};
... ...