Commit 386bf8b4d3ac6c6637f40861ed42aab056a11905
1 parent
a46cc9ad
feat(product): 优化采购管理功能
- 重构采购管理页面,增加更多筛选条件和操作功能- 新增采购单号、创建人等搜索条件 - 添加采购明细的可编辑表格功能 - 集成产品选择接口,实现产品信息自动填充 -优化附件展示方式,支持多附件查看 - 新增审核功能,支持采购单审核操作
Showing
2 changed files
with
313 additions
and
74 deletions
src/pages/product/procure/index.tsx
1 | import ButtonConfirm from '@/components/ButtomConfirm'; | 1 | import ButtonConfirm from '@/components/ButtomConfirm'; |
2 | import { RESPONSE_CODE } from '@/constants/enum'; | 2 | import { RESPONSE_CODE } from '@/constants/enum'; |
3 | import AddOrUpdate from '@/pages/product/procure/components/AddOrUpdate'; | 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 | import { ProTable, type ActionType } from '@ant-design/pro-components'; | 10 | import { ProTable, type ActionType } from '@ant-design/pro-components'; |
6 | import { message } from 'antd'; | 11 | import { message } from 'antd'; |
7 | import { useRef } from 'react'; | 12 | import { useRef } from 'react'; |
13 | +import Audit from './components/Audit'; | ||
8 | 14 | ||
9 | export default () => { | 15 | export default () => { |
10 | const actionRef = useRef<ActionType>(); | 16 | const actionRef = useRef<ActionType>(); |
11 | const columns = [ | 17 | const columns = [ |
12 | { | 18 | { |
19 | + title: '单号', | ||
20 | + dataIndex: 'id', | ||
21 | + ellipsis: true, | ||
22 | + hideInSearch: true, | ||
23 | + }, | ||
24 | + { | ||
13 | title: '创建时间', | 25 | title: '创建时间', |
14 | dataIndex: 'createTime', | 26 | dataIndex: 'createTime', |
15 | ellipsis: true, | 27 | ellipsis: true, |
@@ -52,32 +64,84 @@ export default () => { | @@ -52,32 +64,84 @@ export default () => { | ||
52 | hideInSearch: true, | 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 | title: '操作', | 107 | title: '操作', |
56 | valueType: 'option', | 108 | valueType: 'option', |
57 | key: 'option', | 109 | key: 'option', |
58 | render: (text, record) => [ | 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 | actionRef.current?.reload(); | 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 () => { | @@ -128,7 +192,41 @@ export default () => { | ||
128 | }, | 192 | }, |
129 | { title: '数量', dataIndex: 'number', key: 'number' }, | 193 | { title: '数量', dataIndex: 'number', key: 'number' }, |
130 | { title: '小计', dataIndex: 'totalPrice', key: 'totalPrice' }, | 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 | { title: '备注', dataIndex: 'notes', key: 'notes' }, | 230 | { title: '备注', dataIndex: 'notes', key: 'notes' }, |
133 | ]} | 231 | ]} |
134 | headerTitle={false} | 232 | headerTitle={false} |
@@ -139,7 +237,6 @@ export default () => { | @@ -139,7 +237,6 @@ export default () => { | ||
139 | /> | 237 | /> |
140 | ), | 238 | ), |
141 | }} | 239 | }} |
142 | - search={false} | ||
143 | dateFormatter="string" | 240 | dateFormatter="string" |
144 | headerTitle="采购管理" | 241 | headerTitle="采购管理" |
145 | options={false} | 242 | options={false} |
src/pages/product/productCollect/components/AddOrUpdate.tsx
1 | import { postProductCollectBillAddOrModify } from '@/services'; | 1 | import { postProductCollectBillAddOrModify } from '@/services'; |
2 | import { useModel } from '@@/exports'; | 2 | import { useModel } from '@@/exports'; |
3 | import { | 3 | import { |
4 | + ActionType, | ||
5 | + EditableProTable, | ||
4 | ModalForm, | 6 | ModalForm, |
5 | - ProFormDigit, | ||
6 | - ProFormSelect, | ||
7 | - ProFormText, | 7 | + ProCard, |
8 | + ProColumns, | ||
9 | + ProForm, | ||
10 | + ProFormDependency, | ||
11 | + ProFormField, | ||
12 | + ProFormSwitch, | ||
8 | ProFormTextArea, | 13 | ProFormTextArea, |
9 | } from '@ant-design/pro-components'; | 14 | } from '@ant-design/pro-components'; |
10 | import { Button, Form, message } from 'antd'; | 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 | const [form] = Form.useForm(); | 120 | const [form] = Form.useForm(); |
14 | - const { getWarehouse } = useModel('enum'); | ||
15 | return ( | 121 | return ( |
16 | <ModalForm | 122 | <ModalForm |
123 | + formRef={formRef} | ||
124 | + initialValues={processedRecord} | ||
125 | + validateTrigger="onBlur" | ||
17 | title="新建表单" | 126 | title="新建表单" |
18 | trigger={ | 127 | trigger={ |
19 | record?.id ? ( | 128 | record?.id ? ( |
@@ -24,66 +133,99 @@ export default ({ record, onFinish }) => { | @@ -24,66 +133,99 @@ export default ({ record, onFinish }) => { | ||
24 | } | 133 | } |
25 | form={form} | 134 | form={form} |
26 | autoFocusFirstInput | 135 | autoFocusFirstInput |
136 | + width={1500} | ||
27 | modalProps={{ | 137 | modalProps={{ |
28 | destroyOnClose: true, | 138 | destroyOnClose: true, |
29 | onCancel: () => console.log('run'), | 139 | onCancel: () => console.log('run'), |
30 | }} | 140 | }} |
31 | submitTimeout={2000} | 141 | submitTimeout={2000} |
32 | onFinish={async (values) => { | 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 | if (res) { | 149 | if (res) { |
37 | message.success(res.message); | 150 | message.success(res.message); |
38 | - onFinish(); | 151 | + onfinish(); |
39 | } | 152 | } |
40 | return true; | 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 | </ModalForm> | 229 | </ModalForm> |
88 | ); | 230 | ); |
89 | }; | 231 | }; |