Commit 386bf8b4d3ac6c6637f40861ed42aab056a11905

Authored by 曾国涛
1 parent a46cc9ad

feat(product): 优化采购管理功能

- 重构采购管理页面,增加更多筛选条件和操作功能- 新增采购单号、创建人等搜索条件
- 添加采购明细的可编辑表格功能
- 集成产品选择接口,实现产品信息自动填充
-优化附件展示方式,支持多附件查看
- 新增审核功能,支持采购单审核操作
src/pages/product/procure/index.tsx
1 1 import ButtonConfirm from '@/components/ButtomConfirm';
2 2 import { RESPONSE_CODE } from '@/constants/enum';
3 3 import AddOrUpdate from '@/pages/product/procure/components/AddOrUpdate';
4   -import { postProcureBillDelete, postProcureBillPage } from '@/services';
  4 +import {
  5 + postProcureBillDelete,
  6 + postProcureBillPage,
  7 + postServiceConstProcureBillAuditStatus,
  8 +} from '@/services';
  9 +import { enumToSelect } from '@/utils';
5 10 import { ProTable, type ActionType } from '@ant-design/pro-components';
6 11 import { message } from 'antd';
7 12 import { useRef } from 'react';
  13 +import Audit from './components/Audit';
8 14  
9 15 export default () => {
10 16 const actionRef = useRef<ActionType>();
11 17 const columns = [
12 18 {
  19 + title: '单号',
  20 + dataIndex: 'id',
  21 + ellipsis: true,
  22 + hideInSearch: true,
  23 + },
  24 + {
13 25 title: '创建时间',
14 26 dataIndex: 'createTime',
15 27 ellipsis: true,
... ... @@ -52,32 +64,84 @@ export default () =&gt; {
52 64 hideInSearch: true,
53 65 },
54 66 {
  67 + title: '采购单号',
  68 + dataIndex: 'id',
  69 + ellipsis: true,
  70 + hideInTable: true,
  71 + },
  72 + {
  73 + title: '创建人',
  74 + dataIndex: 'createByNameLike',
  75 + ellipsis: true,
  76 + hideInTable: true,
  77 + },
  78 + {
  79 + title: '创建时间',
  80 + valueType: 'dateTimeRange',
  81 + hideInTable: true,
  82 + search: {
  83 + transform: (value) => {
  84 + if (value) {
  85 + return {
  86 + createTimeGe: value[0],
  87 + createTimeLe: value[1],
  88 + };
  89 + }
  90 + },
  91 + },
  92 + },
  93 + {
  94 + title: '审核状态',
  95 + valueType: 'select',
  96 + key: 'auditStatus',
  97 + dataIndex: 'auditStatus',
  98 + filters: true,
  99 + onFilter: true,
  100 + hideInTable: true,
  101 + request: async () => {
  102 + const res = await postServiceConstProcureBillAuditStatus();
  103 + return enumToSelect(res.data);
  104 + },
  105 + },
  106 + {
55 107 title: '操作',
56 108 valueType: 'option',
57 109 key: 'option',
58 110 render: (text, record) => [
59   - <AddOrUpdate
60   - key="update"
61   - record={record}
62   - onfinish={() => {
63   - actionRef.current?.reload();
64   - }}
65   - />,
66   - <ButtonConfirm
67   - key="delete"
68   - className="p-0"
69   - title={'确认删除该记录?'}
70   - text="删除"
71   - onConfirm={async () => {
72   - let res = await postProcureBillDelete({
73   - query: { id: record.id },
74   - });
75   - if (res) {
76   - message.success(res.message);
  111 + record.paths?.includes('UPDATE') && (
  112 + <AddOrUpdate
  113 + key="update"
  114 + record={record}
  115 + onfinish={() => {
77 116 actionRef.current?.reload();
78   - }
79   - }}
80   - />,
  117 + }}
  118 + />
  119 + ),
  120 + record.paths?.includes('UPDATE') && (
  121 + <ButtonConfirm
  122 + key="delete"
  123 + className="p-0"
  124 + title={'确认删除该记录?'}
  125 + text="删除"
  126 + onConfirm={async () => {
  127 + let res = await postProcureBillDelete({
  128 + query: { id: record.id },
  129 + });
  130 + if (res) {
  131 + message.success(res.message);
  132 + actionRef.current?.reload();
  133 + }
  134 + }}
  135 + />
  136 + ),
  137 + record.paths?.includes('AUDIT') && (
  138 + <Audit
  139 + recordId={record.id}
  140 + onClose={() => {
  141 + actionRef.current?.reload();
  142 + }}
  143 + ></Audit>
  144 + ),
81 145 ],
82 146 },
83 147 ];
... ... @@ -128,7 +192,41 @@ export default () =&gt; {
128 192 },
129 193 { title: '数量', dataIndex: 'number', key: 'number' },
130 194 { title: '小计', dataIndex: 'totalPrice', key: 'totalPrice' },
131   - { title: '附件', dataIndex: 'annex', key: 'annex' },
  195 + {
  196 + title: '附件',
  197 + dataIndex: 'annex',
  198 + key: 'annex',
  199 + render: (_, record) => (
  200 + <div>
  201 + {record.annexList?.map((url, index) => {
  202 + const shortName =
  203 + url.split('/').pop()?.slice(0, 15) ||
  204 + `附件 ${index + 1}`;
  205 + return (
  206 + <a
  207 + key={index}
  208 + href={url}
  209 + target="_blank"
  210 + rel="noopener noreferrer"
  211 + title={url} // 悬停显示完整链接
  212 + style={{
  213 + display: 'block',
  214 + marginBottom: '4px',
  215 + whiteSpace: 'nowrap',
  216 + overflow: 'hidden',
  217 + textOverflow: 'ellipsis',
  218 + maxWidth: '200px', // 限制显示宽度
  219 + }}
  220 + >
  221 + {shortName.length < url.split('/').pop()?.length
  222 + ? `${shortName}...`
  223 + : shortName}
  224 + </a>
  225 + );
  226 + })}
  227 + </div>
  228 + ),
  229 + },
132 230 { title: '备注', dataIndex: 'notes', key: 'notes' },
133 231 ]}
134 232 headerTitle={false}
... ... @@ -139,7 +237,6 @@ export default () =&gt; {
139 237 />
140 238 ),
141 239 }}
142   - search={false}
143 240 dateFormatter="string"
144 241 headerTitle="采购管理"
145 242 options={false}
... ...
src/pages/product/productCollect/components/AddOrUpdate.tsx
1 1 import { postProductCollectBillAddOrModify } from '@/services';
2 2 import { useModel } from '@@/exports';
3 3 import {
  4 + ActionType,
  5 + EditableProTable,
4 6 ModalForm,
5   - ProFormDigit,
6   - ProFormSelect,
7   - ProFormText,
  7 + ProCard,
  8 + ProColumns,
  9 + ProForm,
  10 + ProFormDependency,
  11 + ProFormField,
  12 + ProFormSwitch,
8 13 ProFormTextArea,
9 14 } from '@ant-design/pro-components';
10 15 import { Button, Form, message } from 'antd';
  16 +import React, { useEffect, useRef, useState } from 'react';
  17 +
  18 +export default ({ record, onfinish }) => {
  19 + const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
  20 + const [controlled, setControlled] = useState<boolean>(false);
  21 + const [processedRecord, setProcessedRecord] = useState(record);
  22 + const formRef = useRef(null);
  23 + const editorFormRef = useRef(null);
  24 + const { getProducts } = useModel('enum');
  25 + const actionRef = useRef<ActionType>();
  26 + useEffect(() => {
  27 + if (record?.details) {
  28 + const updateddetails = record.details.map((item) => ({
  29 + ...item,
  30 + key: item.key || `key-${Math.random().toString(36).substr(2, 9)}`, // 动态生成唯一 key
  31 + }));
  32 + setProcessedRecord({
  33 + ...record,
  34 + details: updateddetails,
  35 + });
  36 + }
  37 + }, [record]);
  38 +
  39 + const columns: ProColumns[] = [
  40 + {
  41 + title: '商品',
  42 + dataIndex: 'productId',
  43 + valueType: 'select',
  44 + request: async () => {
  45 + const res = await getProducts();
  46 + return res.map((item) => ({
  47 + ...item,
  48 + label: item.name,
  49 + value: item.id,
  50 + productUnitName: item.baseUnitName,
  51 + productUnitPrice: item.unitPrice,
  52 + }));
  53 + },
  54 + fieldProps: (_, { rowIndex }) => ({
  55 + onSelect: (value, option) => {
  56 + const currentTableData = editorFormRef.current?.getRowsData?.();
  57 + if (currentTableData) {
  58 + const updatedData = [...currentTableData];
  59 + updatedData[rowIndex] = {
  60 + ...updatedData[rowIndex],
  61 + productUnitName: option.productUnitName,
  62 + productUnitPrice: option.productUnitPrice,
  63 + };
  64 + formRef.current?.setFieldsValue({
  65 + details: updatedData,
  66 + });
  67 + }
  68 + },
  69 + }),
  70 + },
  71 + {
  72 + title: '单位',
  73 + dataIndex: 'productUnitName',
  74 + valueType: 'text',
  75 + editable: false,
  76 + },
  77 + {
  78 + title: '单价',
  79 + dataIndex: 'productUnitPrice',
  80 + valueType: 'digit',
  81 + editable: false,
  82 + },
  83 + {
  84 + title: '数量',
  85 + dataIndex: 'number',
  86 + valueType: 'digit',
  87 + },
  88 + {
  89 + title: '备注',
  90 + dataIndex: 'notes',
  91 + valueType: 'textarea',
  92 + },
  93 + {
  94 + title: '操作',
  95 + valueType: 'option',
  96 + render: (text, record, _, action) => [
  97 + <a
  98 + key="editable"
  99 + onClick={() => {
  100 + action?.startEditable?.(record.key);
  101 + }}
  102 + >
  103 + 编辑
  104 + </a>,
  105 + <a
  106 + key="delete"
  107 + onClick={() => {
  108 + const tableDataSource = formRef.current?.getFieldValue('details');
  109 + formRef.current?.setFieldsValue({
  110 + table: tableDataSource.filter((item) => item.key !== record.key),
  111 + });
  112 + }}
  113 + >
  114 + 删除
  115 + </a>,
  116 + ],
  117 + },
  118 + ];
11 119  
12   -export default ({ record, onFinish }) => {
13 120 const [form] = Form.useForm();
14   - const { getWarehouse } = useModel('enum');
15 121 return (
16 122 <ModalForm
  123 + formRef={formRef}
  124 + initialValues={processedRecord}
  125 + validateTrigger="onBlur"
17 126 title="新建表单"
18 127 trigger={
19 128 record?.id ? (
... ... @@ -24,66 +133,99 @@ export default ({ record, onFinish }) =&gt; {
24 133 }
25 134 form={form}
26 135 autoFocusFirstInput
  136 + width={1500}
27 137 modalProps={{
28 138 destroyOnClose: true,
29 139 onCancel: () => console.log('run'),
30 140 }}
31 141 submitTimeout={2000}
32 142 onFinish={async (values) => {
33   - let res = await postProductCollectBillAddOrModify({
34   - data: values,
  143 + const res = await postProductCollectBillAddOrModify({
  144 + data: {
  145 + ...record,
  146 + ...values,
  147 + },
35 148 });
36 149 if (res) {
37 150 message.success(res.message);
38   - onFinish();
  151 + onfinish();
39 152 }
40 153 return true;
41 154 }}
42 155 >
43   - <ProFormDigit
44   - label="id"
45   - name="id"
46   - initialValue={record?.id}
47   - width="sm"
48   - hidden={true}
49   - />
50   - <ProFormText
51   - width="md"
52   - initialValue={record?.productName}
53   - name="productName"
54   - label="申领物品"
55   - rules={[{ required: true, message: '申领物品必填' }]}
56   - />
57   - <ProFormDigit
58   - label="申领数量"
59   - name="productNumber"
60   - initialValue={record?.productNumber}
61   - width="sm"
62   - min={1}
63   - rules={[{ required: true, message: '申领数量必填' }]}
64   - />
65   - <ProFormSelect
66   - width="md"
67   - request={async () => {
68   - const res = await getWarehouse();
69   - console.log('options:' + res);
70   - let options = Object.entries(res).map(([value, label]) => ({
71   - label,
72   - value,
73   - }));
74   - console.log('options:' + options);
75   - return options;
  156 + <EditableProTable
  157 + rowKey="key"
  158 + scroll={{
  159 + x: 960,
  160 + }}
  161 + editableFormRef={editorFormRef}
  162 + headerTitle="可编辑表格"
  163 + maxLength={5}
  164 + name="details"
  165 + controlled={controlled}
  166 + recordCreatorProps={{
  167 + position: 'bottom',
  168 + record: () => ({
  169 + key: `key-${Math.random().toString(36).substr(2, 9)}`,
  170 + }),
  171 + }}
  172 + actionRef={actionRef}
  173 + toolBarRender={() => [
  174 + <ProFormSwitch
  175 + key="render"
  176 + fieldProps={{
  177 + style: {
  178 + marginBlockEnd: 0,
  179 + },
  180 + checked: controlled,
  181 + onChange: (value) => {
  182 + setControlled(value);
  183 + },
  184 + }}
  185 + checkedChildren="数据更新通知 Form"
  186 + unCheckedChildren="保存后通知 Form"
  187 + noStyle
  188 + />,
  189 + <Button
  190 + key="rows"
  191 + onClick={() => {
  192 + const rows = editorFormRef.current?.getRowsData?.();
  193 + console.log(rows);
  194 + }}
  195 + >
  196 + 获取 table 的数据
  197 + </Button>,
  198 + ]}
  199 + columns={columns}
  200 + editable={{
  201 + type: 'multiple',
  202 + editableKeys,
  203 + onChange: setEditableRowKeys,
  204 + actionRender: (row, config, defaultDom) => {
  205 + return [defaultDom.save, defaultDom.delete, defaultDom.cancel];
  206 + },
76 207 }}
77   - // initialValue={record?.warehouseCode}
78   - name="warehouseCode"
79   - label="申领仓库"
80   - rules={[{ required: true, message: '申领仓库为必填项' }]}
81   - />
82   - <ProFormTextArea
83   - initialValue={record?.applyRemarks}
84   - name="applyRemarks"
85   - label="申领备注"
86 208 />
  209 + <ProForm.Item>
  210 + <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
  211 + <ProFormDependency name={['details']}>
  212 + {({ details }) => (
  213 + <ProFormField
  214 + ignoreFormItem
  215 + fieldProps={{
  216 + style: {
  217 + width: '100%',
  218 + },
  219 + }}
  220 + mode="read"
  221 + valueType="jsonCode"
  222 + text={JSON.stringify(details)}
  223 + />
  224 + )}
  225 + </ProFormDependency>
  226 + </ProCard>
  227 + </ProForm.Item>
  228 + <ProFormTextArea name="auditRemarks" label="备注" />
87 229 </ModalForm>
88 230 );
89 231 };
... ...