Commit 9ceaf867959d415ebf780e6f6c1d81e1b253da8f
1 parent
379c80fa
fix: 修改积分兑换弹窗样式
Showing
8 changed files
with
908 additions
and
55 deletions
src/pages/Prepaid/components/PointsExchangeModal.tsx
... | ... | @@ -5,7 +5,7 @@ import { |
5 | 5 | ProFormTextArea, |
6 | 6 | } from '@ant-design/pro-components'; |
7 | 7 | import { Form, Input, message } from 'antd'; |
8 | -import React, { useRef } from 'react'; | |
8 | +import React, { useEffect, useRef, useState } from 'react'; | |
9 | 9 | import '../index.less'; |
10 | 10 | |
11 | 11 | interface PointsExchangeModalProps { |
... | ... | @@ -28,6 +28,28 @@ const PointsExchangeModal: React.FC<PointsExchangeModalProps> = ({ |
28 | 28 | const formRef = useRef<ProFormInstance>(); |
29 | 29 | const uid = userInfoObj?.uid; |
30 | 30 | const userName = userInfoObj?.nickname || userInfoObj?.realName || ''; |
31 | + const [accountPoints, setAccountPoints] = useState<number>(0); | |
32 | + | |
33 | + useEffect(() => { | |
34 | + if (userInfoObj && userInfoObj.delta) { | |
35 | + setAccountPoints(Number(userInfoObj.delta) || 0); | |
36 | + } | |
37 | + }, [userInfoObj]); | |
38 | + | |
39 | + // Validate that delta is not greater than available points | |
40 | + const validateDelta = (rule: any, value: string) => { | |
41 | + const deltaValue = Number(value); | |
42 | + if (isNaN(deltaValue)) { | |
43 | + return Promise.reject('请输入有效的积分数值'); | |
44 | + } | |
45 | + if (deltaValue <= 0) { | |
46 | + return Promise.reject('兑换积分必须大于0'); | |
47 | + } | |
48 | + if (deltaValue > accountPoints) { | |
49 | + return Promise.reject('兑换积分不能大于账户积分'); | |
50 | + } | |
51 | + return Promise.resolve(); | |
52 | + }; | |
31 | 53 | |
32 | 54 | return ( |
33 | 55 | <div className="prepaid-index"> |
... | ... | @@ -61,6 +83,7 @@ const PointsExchangeModal: React.FC<PointsExchangeModalProps> = ({ |
61 | 83 | id: Number(uid), // 使用用户的uid作为id参数 |
62 | 84 | delta: values.delta, // 将delta设为负数 |
63 | 85 | remark: values.remark, // 兑换说明 |
86 | + relationEntityType: 'USER', // 关联实体类型 | |
64 | 87 | createByName: userName, // 使用昵称或真实姓名作为createByName |
65 | 88 | }, |
66 | 89 | }); |
... | ... | @@ -79,7 +102,10 @@ const PointsExchangeModal: React.FC<PointsExchangeModalProps> = ({ |
79 | 102 | <Form.Item |
80 | 103 | label="兑换积分" |
81 | 104 | name="delta" |
82 | - rules={[{ required: true, message: '请输入兑换积分' }]} | |
105 | + rules={[ | |
106 | + { required: true, message: '请输入兑换积分' }, | |
107 | + { validator: validateDelta }, | |
108 | + ]} | |
83 | 109 | > |
84 | 110 | <Input style={{ height: '30px' }} placeholder="请输入兑换积分数量" /> |
85 | 111 | </Form.Item> |
... | ... |
src/pages/Prepaid/components/PointsExchangeRecordsModal.tsx
1 | 1 | import { postIntegralUserExchangeRecords } from '@/services'; |
2 | 2 | import { formatDateTime } from '@/utils'; |
3 | -import { Empty, Modal, Table, Tabs } from 'antd'; | |
3 | +import { Button, DatePicker, Empty, Modal, Space, Table, Tabs } from 'antd'; | |
4 | 4 | import React, { useEffect, useState } from 'react'; |
5 | 5 | import '../index.less'; |
6 | 6 | |
... | ... | @@ -43,26 +43,71 @@ const PointsExchangeRecordsModal: React.FC<PointsExchangeRecordsModalProps> = ({ |
43 | 43 | pendingRecords: [], |
44 | 44 | }); |
45 | 45 | |
46 | + // Add date range state for each tab | |
47 | + const [exchangeStartTime, setExchangeStartTime] = useState<string | null>( | |
48 | + null, | |
49 | + ); | |
50 | + const [exchangeEndTime, setExchangeEndTime] = useState<string | null>(null); | |
51 | + const [collectedStartTime, setCollectedStartTime] = useState<string | null>( | |
52 | + null, | |
53 | + ); | |
54 | + const [collectedEndTime, setCollectedEndTime] = useState<string | null>(null); | |
55 | + const [pendingStartTime, setPendingStartTime] = useState<string | null>(null); | |
56 | + const [pendingEndTime, setPendingEndTime] = useState<string | null>(null); | |
57 | + | |
46 | 58 | const uid = userInfoObj?.uid; |
47 | 59 | const phone = userInfoObj?.phone; |
48 | 60 | |
49 | - // Function to fetch records data from API | |
50 | - const fetchRecordsData = async () => { | |
61 | + // Function to fetch records data from API with date parameters | |
62 | + const fetchRecordsData = async ( | |
63 | + params: { | |
64 | + startTime?: string | null; | |
65 | + endTime?: string | null; | |
66 | + tabKey?: string; | |
67 | + } = {}, | |
68 | + ) => { | |
51 | 69 | try { |
52 | 70 | setLoading(true); |
71 | + const { startTime, endTime, tabKey } = params; | |
72 | + | |
73 | + // Prepare API request data | |
74 | + const requestData: any = { | |
75 | + id: Number(uid), | |
76 | + phone: phone, | |
77 | + }; | |
78 | + | |
79 | + // Add date range if provided | |
80 | + if (startTime) requestData.startTime = startTime; | |
81 | + if (endTime) requestData.endTime = endTime; | |
82 | + | |
53 | 83 | const response = await postIntegralUserExchangeRecords({ |
54 | - data: { | |
55 | - id: Number(uid), | |
56 | - phone: phone, | |
57 | - }, | |
84 | + data: requestData, | |
58 | 85 | }); |
59 | 86 | |
60 | 87 | if (response && response.data) { |
61 | - setRecordsData({ | |
62 | - exchangeRecords: response.data.exchangeRecords || [], | |
63 | - collectedRecords: response.data.collectedRecords || [], | |
64 | - pendingRecords: response.data.pendingRecords || [], | |
65 | - }); | |
88 | + // Update only the data for the active tab if tabKey is provided | |
89 | + if (tabKey) { | |
90 | + setRecordsData((prevData) => { | |
91 | + const newData = { ...prevData }; | |
92 | + | |
93 | + if (tabKey === '1') { | |
94 | + newData.exchangeRecords = response.data.exchangeRecords || []; | |
95 | + } else if (tabKey === '2') { | |
96 | + newData.collectedRecords = response.data.collectedRecords || []; | |
97 | + } else if (tabKey === '3') { | |
98 | + newData.pendingRecords = response.data.pendingRecords || []; | |
99 | + } | |
100 | + | |
101 | + return newData; | |
102 | + }); | |
103 | + } else { | |
104 | + // Update all data if no specific tab is targeted | |
105 | + setRecordsData({ | |
106 | + exchangeRecords: response.data.exchangeRecords || [], | |
107 | + collectedRecords: response.data.collectedRecords || [], | |
108 | + pendingRecords: response.data.pendingRecords || [], | |
109 | + }); | |
110 | + } | |
66 | 111 | } |
67 | 112 | } catch (error) { |
68 | 113 | console.error('Failed to fetch exchange records:', error); |
... | ... | @@ -77,26 +122,30 @@ const PointsExchangeRecordsModal: React.FC<PointsExchangeRecordsModalProps> = ({ |
77 | 122 | fetchRecordsData(); |
78 | 123 | } |
79 | 124 | }, [uid, phone]); |
125 | + | |
80 | 126 | // Exchange records columns |
81 | 127 | const exchangeRecordsColumns = [ |
82 | 128 | { |
83 | 129 | title: '兑换日期', |
84 | 130 | dataIndex: 'createTime', |
85 | 131 | key: 'createTime', |
132 | + width: 160, | |
86 | 133 | render: (text: string) => formatDateTime(text), |
87 | 134 | }, |
88 | 135 | { |
89 | 136 | title: '扣除积分', |
90 | 137 | dataIndex: 'delta', |
91 | 138 | key: 'delta', |
139 | + width: 100, | |
92 | 140 | render: (delta: number) => ( |
93 | - <span style={{ color: delta < 0 ? 'red' : 'green' }}>{delta}</span> | |
141 | + <span style={{ color: delta < 0 ? 'green' : 'red' }}>{delta}</span> | |
94 | 142 | ), |
95 | 143 | }, |
96 | 144 | { |
97 | 145 | title: '操作人', |
98 | 146 | dataIndex: 'createByName', |
99 | 147 | key: 'createByName', |
148 | + width: 100, | |
100 | 149 | render: (text: string | null) => text || '-', |
101 | 150 | }, |
102 | 151 | { |
... | ... | @@ -161,54 +210,151 @@ const PointsExchangeRecordsModal: React.FC<PointsExchangeRecordsModalProps> = ({ |
161 | 210 | key: '1', |
162 | 211 | label: '兑换记录', |
163 | 212 | children: ( |
164 | - <Table | |
165 | - columns={exchangeRecordsColumns} | |
166 | - dataSource={recordsData.exchangeRecords.map((item, index) => ({ | |
167 | - ...item, | |
168 | - key: `exchange-${index}`, | |
169 | - }))} | |
170 | - pagination={{ pageSize: 5 }} | |
171 | - loading={loading && activeTab === '1'} | |
172 | - locale={{ | |
173 | - emptyText: <Empty description="暂无兑换记录" />, | |
174 | - }} | |
175 | - /> | |
213 | + <> | |
214 | + <div | |
215 | + style={{ marginBottom: 16, display: 'flex', alignItems: 'center' }} | |
216 | + > | |
217 | + <span>兑换日期:</span> | |
218 | + <Space> | |
219 | + <DatePicker | |
220 | + placeholder="开始日期" | |
221 | + onChange={(date, dateString) => | |
222 | + setExchangeStartTime(dateString) | |
223 | + } | |
224 | + style={{ width: 150 }} | |
225 | + /> | |
226 | + <DatePicker | |
227 | + placeholder="结束日期" | |
228 | + onChange={(date, dateString) => setExchangeEndTime(dateString)} | |
229 | + style={{ width: 150 }} | |
230 | + /> | |
231 | + <Button | |
232 | + type="primary" | |
233 | + onClick={() => | |
234 | + fetchRecordsData({ | |
235 | + startTime: exchangeStartTime, | |
236 | + endTime: exchangeEndTime, | |
237 | + tabKey: '1', | |
238 | + }) | |
239 | + } | |
240 | + > | |
241 | + 查询 | |
242 | + </Button> | |
243 | + </Space> | |
244 | + </div> | |
245 | + <Table | |
246 | + columns={exchangeRecordsColumns} | |
247 | + dataSource={recordsData.exchangeRecords.map((item, index) => ({ | |
248 | + ...item, | |
249 | + key: `exchange-${index}`, | |
250 | + }))} | |
251 | + pagination={{ pageSize: 5 }} | |
252 | + loading={loading && activeTab === '1'} | |
253 | + locale={{ | |
254 | + emptyText: <Empty description="暂无兑换记录" />, | |
255 | + }} | |
256 | + /> | |
257 | + </> | |
176 | 258 | ), |
177 | 259 | }, |
178 | 260 | { |
179 | 261 | key: '2', |
180 | 262 | label: '已领取积分', |
181 | 263 | children: ( |
182 | - <Table | |
183 | - columns={collectedRecordsColumns} | |
184 | - dataSource={recordsData.collectedRecords.map((item, index) => ({ | |
185 | - ...item, | |
186 | - key: `collected-${index}`, | |
187 | - }))} | |
188 | - pagination={{ pageSize: 5 }} | |
189 | - loading={loading && activeTab === '2'} | |
190 | - locale={{ | |
191 | - emptyText: <Empty description="暂无已领取积分记录" />, | |
192 | - }} | |
193 | - /> | |
264 | + <> | |
265 | + <div | |
266 | + style={{ marginBottom: 16, display: 'flex', alignItems: 'center' }} | |
267 | + > | |
268 | + <span>领取日期:</span> | |
269 | + <Space> | |
270 | + <DatePicker | |
271 | + placeholder="开始日期" | |
272 | + onChange={(date, dateString) => | |
273 | + setCollectedStartTime(dateString) | |
274 | + } | |
275 | + style={{ width: 150 }} | |
276 | + /> | |
277 | + <DatePicker | |
278 | + placeholder="结束日期" | |
279 | + onChange={(date, dateString) => setCollectedEndTime(dateString)} | |
280 | + style={{ width: 150 }} | |
281 | + /> | |
282 | + <Button | |
283 | + type="primary" | |
284 | + onClick={() => | |
285 | + fetchRecordsData({ | |
286 | + startTime: collectedStartTime, | |
287 | + endTime: collectedEndTime, | |
288 | + tabKey: '2', | |
289 | + }) | |
290 | + } | |
291 | + > | |
292 | + 查询 | |
293 | + </Button> | |
294 | + </Space> | |
295 | + </div> | |
296 | + <Table | |
297 | + columns={collectedRecordsColumns} | |
298 | + dataSource={recordsData.collectedRecords.map((item, index) => ({ | |
299 | + ...item, | |
300 | + key: `collected-${index}`, | |
301 | + }))} | |
302 | + pagination={{ pageSize: 5 }} | |
303 | + loading={loading && activeTab === '2'} | |
304 | + locale={{ | |
305 | + emptyText: <Empty description="暂无已领取积分记录" />, | |
306 | + }} | |
307 | + /> | |
308 | + </> | |
194 | 309 | ), |
195 | 310 | }, |
196 | 311 | { |
197 | 312 | key: '3', |
198 | 313 | label: '待领取积分', |
199 | 314 | children: ( |
200 | - <Table | |
201 | - columns={pendingRecordsColumns} | |
202 | - dataSource={recordsData.pendingRecords.map((item, index) => ({ | |
203 | - ...item, | |
204 | - key: `pending-${index}`, | |
205 | - }))} | |
206 | - pagination={{ pageSize: 5 }} | |
207 | - loading={loading && activeTab === '3'} | |
208 | - locale={{ | |
209 | - emptyText: <Empty description="暂无待领取积分记录" />, | |
210 | - }} | |
211 | - /> | |
315 | + <> | |
316 | + <div | |
317 | + style={{ marginBottom: 16, display: 'flex', alignItems: 'center' }} | |
318 | + > | |
319 | + <span>过期日期:</span> | |
320 | + <Space> | |
321 | + <DatePicker | |
322 | + placeholder="开始日期" | |
323 | + onChange={(date, dateString) => setPendingStartTime(dateString)} | |
324 | + style={{ width: 150 }} | |
325 | + /> | |
326 | + <DatePicker | |
327 | + placeholder="结束日期" | |
328 | + onChange={(date, dateString) => setPendingEndTime(dateString)} | |
329 | + style={{ width: 150 }} | |
330 | + /> | |
331 | + <Button | |
332 | + type="primary" | |
333 | + onClick={() => | |
334 | + fetchRecordsData({ | |
335 | + startTime: pendingStartTime, | |
336 | + endTime: pendingEndTime, | |
337 | + tabKey: '3', | |
338 | + }) | |
339 | + } | |
340 | + > | |
341 | + 查询 | |
342 | + </Button> | |
343 | + </Space> | |
344 | + </div> | |
345 | + <Table | |
346 | + columns={pendingRecordsColumns} | |
347 | + dataSource={recordsData.pendingRecords.map((item, index) => ({ | |
348 | + ...item, | |
349 | + key: `pending-${index}`, | |
350 | + }))} | |
351 | + pagination={{ pageSize: 5 }} | |
352 | + loading={loading && activeTab === '3'} | |
353 | + locale={{ | |
354 | + emptyText: <Empty description="暂无待领取积分记录" />, | |
355 | + }} | |
356 | + /> | |
357 | + </> | |
212 | 358 | ), |
213 | 359 | }, |
214 | 360 | ]; |
... | ... | @@ -217,7 +363,7 @@ const PointsExchangeRecordsModal: React.FC<PointsExchangeRecordsModalProps> = ({ |
217 | 363 | <Modal |
218 | 364 | open={true} |
219 | 365 | title="积分兑换记录" |
220 | - width={800} | |
366 | + width={1000} | |
221 | 367 | onCancel={() => { |
222 | 368 | setVisible(false); |
223 | 369 | onClose(); |
... | ... |
src/pages/ResearchGroup/components/PointsExchangeModal.tsx
0 → 100644
1 | +import { postIntegralExchangeIntegral } from '@/services/request'; | |
2 | +import { getUserInfo } from '@/utils'; | |
3 | +import { | |
4 | + ModalForm, | |
5 | + ProFormInstance, | |
6 | + ProFormTextArea, | |
7 | +} from '@ant-design/pro-components'; | |
8 | +import { Form, Input, message } from 'antd'; | |
9 | +import React, { useEffect, useRef, useState } from 'react'; | |
10 | +import '../index.less'; | |
11 | + | |
12 | +interface PointsExchangeModalProps { | |
13 | + setVisible: (visible: boolean) => void; | |
14 | + record: any; | |
15 | + onClose: () => void; | |
16 | +} | |
17 | +const userInfo = getUserInfo(); | |
18 | +const PointsExchangeModal: React.FC<PointsExchangeModalProps> = ({ | |
19 | + setVisible, | |
20 | + record, | |
21 | + onClose, | |
22 | +}) => { | |
23 | + const [form] = Form.useForm<{ delta: string; remark: string }>(); | |
24 | + const formRef = useRef<ProFormInstance>(); | |
25 | + const [accountPoints, setAccountPoints] = useState<number>(0); | |
26 | + | |
27 | + // Get account points from record | |
28 | + useEffect(() => { | |
29 | + if (record && record.delta) { | |
30 | + setAccountPoints(Number(record.delta) || 0); | |
31 | + } | |
32 | + }, [record]); | |
33 | + | |
34 | + // Validate that delta is not greater than available points | |
35 | + const validateDelta = (rule: any, value: string) => { | |
36 | + const deltaValue = Number(value); | |
37 | + if (isNaN(deltaValue)) { | |
38 | + return Promise.reject('请输入有效的积分数值'); | |
39 | + } | |
40 | + if (deltaValue <= 0) { | |
41 | + return Promise.reject('兑换积分必须大于0'); | |
42 | + } | |
43 | + if (deltaValue > accountPoints) { | |
44 | + return Promise.reject('兑换积分不能大于账户积分'); | |
45 | + } | |
46 | + return Promise.resolve(); | |
47 | + }; | |
48 | + | |
49 | + return ( | |
50 | + <div className="prepaid-index"> | |
51 | + <ModalForm<{ | |
52 | + delta: string; | |
53 | + remark: string; | |
54 | + }> | |
55 | + width={600} | |
56 | + open | |
57 | + title="确认兑换" | |
58 | + form={form} | |
59 | + formRef={formRef} | |
60 | + autoFocusFirstInput | |
61 | + submitter={{ | |
62 | + searchConfig: { | |
63 | + submitText: '确认兑换', | |
64 | + resetText: '取消', | |
65 | + }, | |
66 | + }} | |
67 | + modalProps={{ | |
68 | + destroyOnClose: true, | |
69 | + onCancel: () => { | |
70 | + setVisible(false); | |
71 | + }, | |
72 | + }} | |
73 | + onFinish={async (values) => { | |
74 | + try { | |
75 | + // 调用积分兑换API | |
76 | + await postIntegralExchangeIntegral({ | |
77 | + data: { | |
78 | + id: Number(record.id), | |
79 | + delta: Number(values.delta), | |
80 | + remark: values.remark, | |
81 | + relationEntityType: 'RESEARCH_GROUP', | |
82 | + createByName: userInfo?.username, | |
83 | + }, | |
84 | + }); | |
85 | + | |
86 | + message.success('积分兑换成功'); | |
87 | + setVisible(false); | |
88 | + onClose(); | |
89 | + return true; | |
90 | + } catch (error) { | |
91 | + console.error(error); | |
92 | + return false; | |
93 | + } | |
94 | + }} | |
95 | + onOpenChange={setVisible} | |
96 | + > | |
97 | + <Form.Item | |
98 | + label="兑换积分" | |
99 | + name="delta" | |
100 | + rules={[ | |
101 | + { required: true, message: '请输入兑换积分' }, | |
102 | + { validator: validateDelta }, | |
103 | + ]} | |
104 | + > | |
105 | + <Input style={{ height: '30px' }} placeholder="请输入兑换积分数量" /> | |
106 | + </Form.Item> | |
107 | + | |
108 | + <Form.Item | |
109 | + label="兑换说明" | |
110 | + name="remark" | |
111 | + rules={[{ required: true, message: '请输入兑换说明' }]} | |
112 | + > | |
113 | + <ProFormTextArea | |
114 | + style={{ height: '100px' }} | |
115 | + placeholder="请输入兑换说明" | |
116 | + /> | |
117 | + </Form.Item> | |
118 | + </ModalForm> | |
119 | + </div> | |
120 | + ); | |
121 | +}; | |
122 | + | |
123 | +export default PointsExchangeModal; | |
... | ... |
src/pages/ResearchGroup/components/PointsExchangeRecordsModal.tsx
0 → 100644
1 | +import { postIntegralGroupExchangeRecords } from '@/services/request'; | |
2 | +import { ModalForm, ProTable } from '@ant-design/pro-components'; | |
3 | +import { Button, DatePicker, Space, Tabs, message } from 'antd'; | |
4 | +import dayjs from 'dayjs'; | |
5 | +import React, { useState } from 'react'; | |
6 | +import '../index.less'; | |
7 | + | |
8 | +interface PointsExchangeRecordsModalProps { | |
9 | + setVisible: (visible: boolean) => void; | |
10 | + record: any; | |
11 | +} | |
12 | + | |
13 | +// Define a type for the records data | |
14 | +type RecordsDataType = { | |
15 | + exchangeRecords: any[]; | |
16 | + collectedRecords: any[]; | |
17 | + pendingRecords: any[]; | |
18 | +}; | |
19 | + | |
20 | +const PointsExchangeRecordsModal: React.FC<PointsExchangeRecordsModalProps> = ({ | |
21 | + setVisible, | |
22 | + record, | |
23 | +}) => { | |
24 | + const [activeTab, setActiveTab] = useState('1'); | |
25 | + | |
26 | + // Separate date ranges for each tab | |
27 | + const [exchangeDateRange, setExchangeDateRange] = useState<any[]>([]); | |
28 | + const [collectedDateRange, setCollectedDateRange] = useState<any[]>([]); | |
29 | + const [pendingDateRange, setPendingDateRange] = useState<any[]>([]); | |
30 | + | |
31 | + const [loading, setLoading] = useState(false); | |
32 | + const [recordsData, setRecordsData] = useState<RecordsDataType>({ | |
33 | + exchangeRecords: [], // 兑换记录 | |
34 | + collectedRecords: [], // 已领取积分 | |
35 | + pendingRecords: [], // 待领取积分 | |
36 | + }); | |
37 | + | |
38 | + // Function to fetch records data from API with date parameters | |
39 | + const fetchRecordsData = async (params: { | |
40 | + startTime?: string | null; | |
41 | + endTime?: string | null; | |
42 | + tabKey: string; | |
43 | + }) => { | |
44 | + try { | |
45 | + setLoading(true); | |
46 | + const { startTime, endTime, tabKey } = params; | |
47 | + | |
48 | + // Prepare API request data | |
49 | + const requestDto: any = { | |
50 | + id: Number(record.id), | |
51 | + }; | |
52 | + | |
53 | + // Add date range if provided | |
54 | + if (startTime) requestDto.startTime = startTime; | |
55 | + if (endTime) requestDto.endTime = endTime; | |
56 | + | |
57 | + console.log(`Fetching data for tab: ${tabKey}`); | |
58 | + | |
59 | + // Use postIntegralGroupExchangeRecords instead of postIntegralUserExchangeRecords | |
60 | + const response = await postIntegralGroupExchangeRecords({ | |
61 | + data: requestDto, | |
62 | + }); | |
63 | + | |
64 | + if (response && response.data) { | |
65 | + // Update only the data for the active tab | |
66 | + setRecordsData((prevData: RecordsDataType) => { | |
67 | + const newData = { ...prevData }; | |
68 | + | |
69 | + switch (tabKey) { | |
70 | + case '1': // 兑换记录 | |
71 | + newData.exchangeRecords = response.data.exchangeRecords || []; | |
72 | + break; | |
73 | + case '2': // 已领取积分 | |
74 | + newData.collectedRecords = response.data.collectedRecords || []; | |
75 | + break; | |
76 | + case '3': // 待领取积分 | |
77 | + newData.pendingRecords = response.data.pendingRecords || []; | |
78 | + break; | |
79 | + } | |
80 | + | |
81 | + return newData; | |
82 | + }); | |
83 | + } else { | |
84 | + message.error('获取积分记录失败'); | |
85 | + } | |
86 | + } catch (error) { | |
87 | + console.error(error); | |
88 | + message.error('获取积分记录失败'); | |
89 | + } finally { | |
90 | + setLoading(false); | |
91 | + } | |
92 | + }; | |
93 | + | |
94 | + // Initial data fetch for all tabs | |
95 | + React.useEffect(() => { | |
96 | + const loadAllTabs = async () => { | |
97 | + try { | |
98 | + setLoading(true); | |
99 | + const requestDto = { | |
100 | + id: Number(record.id), | |
101 | + }; | |
102 | + | |
103 | + const response = await postIntegralGroupExchangeRecords({ | |
104 | + data: requestDto, | |
105 | + }); | |
106 | + | |
107 | + if (response && response.data) { | |
108 | + setRecordsData({ | |
109 | + exchangeRecords: response.data.exchangeRecords || [], | |
110 | + collectedRecords: response.data.collectedRecords || [], | |
111 | + pendingRecords: response.data.pendingRecords || [], | |
112 | + }); | |
113 | + } else { | |
114 | + message.error('获取积分记录失败'); | |
115 | + } | |
116 | + } catch (error) { | |
117 | + console.error(error); | |
118 | + message.error('获取积分记录失败'); | |
119 | + } finally { | |
120 | + setLoading(false); | |
121 | + } | |
122 | + }; | |
123 | + | |
124 | + loadAllTabs(); | |
125 | + }, [record.id]); | |
126 | + | |
127 | + const handleTabChange = (key: string) => { | |
128 | + setActiveTab(key); | |
129 | + }; | |
130 | + | |
131 | + // Get current date range based on active tab | |
132 | + const getCurrentDateRange = () => { | |
133 | + switch (activeTab) { | |
134 | + case '1': | |
135 | + return exchangeDateRange; | |
136 | + case '2': | |
137 | + return collectedDateRange; | |
138 | + case '3': | |
139 | + return pendingDateRange; | |
140 | + default: | |
141 | + return []; | |
142 | + } | |
143 | + }; | |
144 | + | |
145 | + // Set date range for current tab | |
146 | + const handleDateRangeChange = (dates: any) => { | |
147 | + switch (activeTab) { | |
148 | + case '1': | |
149 | + setExchangeDateRange(dates); | |
150 | + break; | |
151 | + case '2': | |
152 | + setCollectedDateRange(dates); | |
153 | + break; | |
154 | + case '3': | |
155 | + setPendingDateRange(dates); | |
156 | + break; | |
157 | + } | |
158 | + }; | |
159 | + | |
160 | + const handleSearchClick = () => { | |
161 | + const dateRange = getCurrentDateRange(); | |
162 | + // Only get the dates that are actually selected | |
163 | + const startTime = dateRange?.[0] | |
164 | + ? dayjs(dateRange[0]).format('YYYY-MM-DD') | |
165 | + : null; | |
166 | + const endTime = dateRange?.[1] | |
167 | + ? dayjs(dateRange[1]).format('YYYY-MM-DD') | |
168 | + : null; | |
169 | + | |
170 | + fetchRecordsData({ | |
171 | + startTime, | |
172 | + endTime, | |
173 | + tabKey: activeTab, | |
174 | + }); | |
175 | + }; | |
176 | + | |
177 | + const handleClearClick = () => { | |
178 | + // Clear date range for current tab | |
179 | + switch (activeTab) { | |
180 | + case '1': | |
181 | + setExchangeDateRange([]); | |
182 | + break; | |
183 | + case '2': | |
184 | + setCollectedDateRange([]); | |
185 | + break; | |
186 | + case '3': | |
187 | + setPendingDateRange([]); | |
188 | + break; | |
189 | + } | |
190 | + | |
191 | + // Fetch data without date filters | |
192 | + fetchRecordsData({ | |
193 | + startTime: null, | |
194 | + endTime: null, | |
195 | + tabKey: activeTab, | |
196 | + }); | |
197 | + }; | |
198 | + | |
199 | + // 兑换记录表格列定义 | |
200 | + const exchangeColumns = [ | |
201 | + { | |
202 | + title: '兑换日期', | |
203 | + dataIndex: 'createTime', | |
204 | + key: 'createTime', | |
205 | + width: 30, | |
206 | + }, | |
207 | + { | |
208 | + title: '扣除积分', | |
209 | + dataIndex: 'delta', | |
210 | + key: 'delta', | |
211 | + width: 20, | |
212 | + }, | |
213 | + { | |
214 | + title: '操作人', | |
215 | + dataIndex: 'createByName', | |
216 | + key: 'createByName', | |
217 | + width: 20, | |
218 | + }, | |
219 | + { | |
220 | + title: '积分用途', | |
221 | + dataIndex: 'remark', | |
222 | + key: 'remark', | |
223 | + width: 100, | |
224 | + }, | |
225 | + ]; | |
226 | + | |
227 | + // 已领取积分表格列定义 | |
228 | + const collectedColumns = [ | |
229 | + { | |
230 | + title: '订单号', | |
231 | + dataIndex: 'sourceId', | |
232 | + key: 'sourceId', | |
233 | + }, | |
234 | + { | |
235 | + title: '领取积分', | |
236 | + dataIndex: 'delta', | |
237 | + key: 'delta', | |
238 | + }, | |
239 | + { | |
240 | + title: '领取日期', | |
241 | + dataIndex: 'createTime', | |
242 | + key: 'createTime', | |
243 | + }, | |
244 | + ]; | |
245 | + | |
246 | + // 待领取积分表格列定义 | |
247 | + const pendingColumns = [ | |
248 | + { | |
249 | + title: '订单号', | |
250 | + dataIndex: 'sourceId', | |
251 | + key: 'sourceId', | |
252 | + }, | |
253 | + { | |
254 | + title: '领取积分', | |
255 | + dataIndex: 'delta', | |
256 | + key: 'delta', | |
257 | + }, | |
258 | + { | |
259 | + title: '过期日期', | |
260 | + dataIndex: 'createTime', | |
261 | + key: 'createTime', | |
262 | + }, | |
263 | + ]; | |
264 | + | |
265 | + // Render date picker and search buttons for current tab | |
266 | + const renderDateRangePicker = () => { | |
267 | + return ( | |
268 | + <Space style={{ marginBottom: 16 }}> | |
269 | + <DatePicker.RangePicker | |
270 | + value={getCurrentDateRange()} | |
271 | + onChange={handleDateRangeChange} | |
272 | + allowEmpty={[true, true]} | |
273 | + /> | |
274 | + <Button type="primary" onClick={handleSearchClick}> | |
275 | + 搜索 | |
276 | + </Button> | |
277 | + <Button onClick={handleClearClick}>重置</Button> | |
278 | + </Space> | |
279 | + ); | |
280 | + }; | |
281 | + | |
282 | + return ( | |
283 | + <div className="prepaid-index"> | |
284 | + <ModalForm | |
285 | + width={1000} | |
286 | + open | |
287 | + title="积分兑换记录" | |
288 | + submitter={false} | |
289 | + modalProps={{ | |
290 | + destroyOnClose: true, | |
291 | + onCancel: () => { | |
292 | + setVisible(false); | |
293 | + }, | |
294 | + }} | |
295 | + > | |
296 | + <Tabs activeKey={activeTab} onChange={handleTabChange}> | |
297 | + <Tabs.TabPane tab="兑换记录" key="1"> | |
298 | + {renderDateRangePicker()} | |
299 | + <ProTable | |
300 | + headerTitle={false} | |
301 | + search={false} | |
302 | + options={false} | |
303 | + pagination={{ | |
304 | + pageSize: 10, | |
305 | + }} | |
306 | + loading={loading && activeTab === '1'} | |
307 | + dataSource={recordsData.exchangeRecords} | |
308 | + columns={exchangeColumns} | |
309 | + rowKey="id" | |
310 | + /> | |
311 | + </Tabs.TabPane> | |
312 | + <Tabs.TabPane tab="已领取积分" key="2"> | |
313 | + {renderDateRangePicker()} | |
314 | + <ProTable | |
315 | + headerTitle={false} | |
316 | + search={false} | |
317 | + options={false} | |
318 | + pagination={{ | |
319 | + pageSize: 10, | |
320 | + }} | |
321 | + loading={loading && activeTab === '2'} | |
322 | + dataSource={recordsData.collectedRecords} | |
323 | + columns={collectedColumns} | |
324 | + rowKey="id" | |
325 | + /> | |
326 | + </Tabs.TabPane> | |
327 | + <Tabs.TabPane tab="待领取积分" key="3"> | |
328 | + {renderDateRangePicker()} | |
329 | + <ProTable | |
330 | + headerTitle={false} | |
331 | + search={false} | |
332 | + options={false} | |
333 | + pagination={{ | |
334 | + pageSize: 10, | |
335 | + }} | |
336 | + loading={loading && activeTab === '3'} | |
337 | + dataSource={recordsData.pendingRecords} | |
338 | + columns={pendingColumns} | |
339 | + rowKey="id" | |
340 | + /> | |
341 | + </Tabs.TabPane> | |
342 | + </Tabs> | |
343 | + </ModalForm> | |
344 | + </div> | |
345 | + ); | |
346 | +}; | |
347 | + | |
348 | +export default PointsExchangeRecordsModal; | |
... | ... |
src/pages/ResearchGroup/constant.tsx
... | ... | @@ -55,6 +55,27 @@ export const RESEARCH_GROUP_COLUMNS = [ |
55 | 55 | hideInSearch: true, |
56 | 56 | }, |
57 | 57 | { |
58 | + title: '账户积分', | |
59 | + dataIndex: 'delta', | |
60 | + key: 'delta', | |
61 | + valueType: 'number', | |
62 | + hideInSearch: true, | |
63 | + }, | |
64 | + { | |
65 | + title: '待领取积分', | |
66 | + dataIndex: 'pendingDelta', | |
67 | + key: 'pendingDelta', | |
68 | + valueType: 'number', | |
69 | + hideInSearch: true, | |
70 | + }, | |
71 | + { | |
72 | + title: '已扣除积分', | |
73 | + dataIndex: 'deleteDelta', | |
74 | + key: 'deleteDelta', | |
75 | + valueType: 'number', | |
76 | + hideInSearch: true, | |
77 | + }, | |
78 | + { | |
58 | 79 | title: '状态', |
59 | 80 | dataIndex: 'statusText', |
60 | 81 | key: 'statusText', |
... | ... | @@ -224,11 +245,11 @@ export const RESEARCH_GROUP_MEMBER_REQUEST_COLUMNS = [ |
224 | 245 | valueEnum: { |
225 | 246 | ADD_MEMBER: { |
226 | 247 | text: '新增成员', |
227 | - status: 'ADD_MEMBER' | |
248 | + status: 'ADD_MEMBER', | |
228 | 249 | }, |
229 | 250 | ADD_ACCOUNT: { |
230 | 251 | text: '新增课题组', |
231 | - status: 'ADD_ACCOUNT' | |
252 | + status: 'ADD_ACCOUNT', | |
232 | 253 | }, |
233 | 254 | }, |
234 | 255 | hideInTable: true, |
... | ... |
src/pages/ResearchGroup/index.tsx
... | ... | @@ -29,6 +29,8 @@ import { |
29 | 29 | import React, { useRef, useState } from 'react'; |
30 | 30 | import AuditModal from './components/AuditModal'; |
31 | 31 | import ImportModal from './components/ImportModal'; |
32 | +import PointsExchangeModal from './components/PointsExchangeModal'; | |
33 | +import PointsExchangeRecordsModal from './components/PointsExchangeRecordsModal'; | |
32 | 34 | import ResearchGroupAddModal from './components/ResearchGroupAddModal'; |
33 | 35 | import ResearchGroupMemberRequestAddModal from './components/ResearchGroupMemberRequestAddModal'; |
34 | 36 | import { |
... | ... | @@ -60,6 +62,13 @@ const PrepaidPage = () => { |
60 | 62 | const [accountInfoLoading, setAccountInfoLoading] = useState(false); |
61 | 63 | const [perms, setPerms] = useState<string[]>([]); |
62 | 64 | const [optRecordId, setOptRecordId] = useState<any>(null); |
65 | + const [pointsExchangeModalVisible, setPointsExchangeModalVisible] = | |
66 | + useState<boolean>(false); | |
67 | + const [ | |
68 | + pointsExchangeRecordsModalVisible, | |
69 | + setPointsExchangeRecordsModalVisible, | |
70 | + ] = useState<boolean>(false); | |
71 | + const [currentRecord, setCurrentRecord] = useState<any>(null); | |
63 | 72 | |
64 | 73 | const reloadResearchGroupTable = () => { |
65 | 74 | researchGroupActionRef.current?.reload(); |
... | ... | @@ -306,9 +315,39 @@ const PrepaidPage = () => { |
306 | 315 | valueType: 'option', |
307 | 316 | key: 'option', |
308 | 317 | fixed: 'right', |
309 | - width: 120, | |
318 | + width: 240, | |
310 | 319 | render: (text, record) => { |
311 | 320 | let btns = []; |
321 | + | |
322 | + // Add Points Exchange button if there are accounts | |
323 | + btns.push( | |
324 | + <Button | |
325 | + className="p-0" | |
326 | + key="points-exchange" | |
327 | + type="link" | |
328 | + onClick={() => { | |
329 | + setCurrentRecord(record); | |
330 | + setPointsExchangeModalVisible(true); | |
331 | + }} | |
332 | + > | |
333 | + 积分兑换 | |
334 | + </Button>, | |
335 | + ); | |
336 | + | |
337 | + btns.push( | |
338 | + <Button | |
339 | + className="p-0" | |
340 | + key="points-records" | |
341 | + type="link" | |
342 | + onClick={() => { | |
343 | + setCurrentRecord(record); | |
344 | + setPointsExchangeRecordsModalVisible(true); | |
345 | + }} | |
346 | + > | |
347 | + 积分兑换记录 | |
348 | + </Button>, | |
349 | + ); | |
350 | + | |
312 | 351 | if (perms?.includes('modify')) { |
313 | 352 | btns.push( |
314 | 353 | <Button |
... | ... | @@ -814,6 +853,24 @@ const PrepaidPage = () => { |
814 | 853 | }} |
815 | 854 | /> |
816 | 855 | )} |
856 | + | |
857 | + {pointsExchangeModalVisible && currentRecord && ( | |
858 | + <PointsExchangeModal | |
859 | + setVisible={setPointsExchangeModalVisible} | |
860 | + record={currentRecord} | |
861 | + onClose={() => { | |
862 | + reloadResearchGroupTable(); | |
863 | + }} | |
864 | + /> | |
865 | + )} | |
866 | + | |
867 | + {pointsExchangeRecordsModalVisible && currentRecord && ( | |
868 | + <PointsExchangeRecordsModal | |
869 | + setVisible={setPointsExchangeRecordsModalVisible} | |
870 | + record={currentRecord} | |
871 | + onClose={() => {}} | |
872 | + /> | |
873 | + )} | |
817 | 874 | </div> |
818 | 875 | ); |
819 | 876 | }; |
... | ... |
src/services/definition.ts
... | ... | @@ -1526,6 +1526,7 @@ export interface ExchangeIntegralDto { |
1526 | 1526 | delta?: number; |
1527 | 1527 | /** @format int64 */ |
1528 | 1528 | id?: number; |
1529 | + relationEntityType?: string; | |
1529 | 1530 | remark?: string; |
1530 | 1531 | } |
1531 | 1532 | |
... | ... | @@ -1540,6 +1541,8 @@ export interface ExchangeRecordsRequestDto { |
1540 | 1541 | current?: number; |
1541 | 1542 | /** @format int32 */ |
1542 | 1543 | end?: number; |
1544 | + /** @format date-time */ | |
1545 | + endTime?: string; | |
1543 | 1546 | /** @format int32 */ |
1544 | 1547 | id?: number; |
1545 | 1548 | /** @format int32 */ |
... | ... | @@ -1547,6 +1550,8 @@ export interface ExchangeRecordsRequestDto { |
1547 | 1550 | phone?: string; |
1548 | 1551 | /** @format int32 */ |
1549 | 1552 | start?: number; |
1553 | + /** @format date-time */ | |
1554 | + startTime?: string; | |
1550 | 1555 | /** @format int32 */ |
1551 | 1556 | total?: number; |
1552 | 1557 | } |
... | ... | @@ -1555,6 +1560,31 @@ export interface FilePathDto { |
1555 | 1560 | url?: string; |
1556 | 1561 | } |
1557 | 1562 | |
1563 | +export interface GroupExchangeRecordsRequestDto { | |
1564 | + createByName?: string; | |
1565 | + createByNameLike?: string; | |
1566 | + /** @format date-time */ | |
1567 | + createTimeGe?: string; | |
1568 | + /** @format date-time */ | |
1569 | + createTimeLe?: string; | |
1570 | + /** @format int32 */ | |
1571 | + current?: number; | |
1572 | + /** @format int32 */ | |
1573 | + end?: number; | |
1574 | + /** @format date-time */ | |
1575 | + endTime?: string; | |
1576 | + /** @format int64 */ | |
1577 | + id?: number; | |
1578 | + /** @format int32 */ | |
1579 | + pageSize?: number; | |
1580 | + /** @format int32 */ | |
1581 | + start?: number; | |
1582 | + /** @format date-time */ | |
1583 | + startTime?: string; | |
1584 | + /** @format int32 */ | |
1585 | + total?: number; | |
1586 | +} | |
1587 | + | |
1558 | 1588 | export type InputStream = any; |
1559 | 1589 | |
1560 | 1590 | export interface InventoryMaterialStockReq { |
... | ... | @@ -3731,6 +3761,16 @@ export interface ResearchGroupEditRequest { |
3731 | 3761 | createTime?: string; |
3732 | 3762 | /** |
3733 | 3763 | * @description |
3764 | + * 已扣除积分 | |
3765 | + */ | |
3766 | + deleteDelta?: number; | |
3767 | + /** | |
3768 | + * @description | |
3769 | + * 账户积分 | |
3770 | + */ | |
3771 | + delta?: number; | |
3772 | + /** | |
3773 | + * @description | |
3734 | 3774 | * 课题组名称 |
3735 | 3775 | */ |
3736 | 3776 | groupName?: string; |
... | ... | @@ -3748,6 +3788,11 @@ export interface ResearchGroupEditRequest { |
3748 | 3788 | logicDelete?: boolean; |
3749 | 3789 | members?: Array<ResearchGroupMembers>; |
3750 | 3790 | paths?: Array<string>; |
3791 | + /** | |
3792 | + * @description | |
3793 | + * 待领取积分 | |
3794 | + */ | |
3795 | + pendingDelta?: number; | |
3751 | 3796 | requestStatus?: string; |
3752 | 3797 | /** |
3753 | 3798 | * @description |
... | ... | @@ -4066,6 +4111,16 @@ export interface ResearchGroupsDTO { |
4066 | 4111 | createTime?: string; |
4067 | 4112 | /** |
4068 | 4113 | * @description |
4114 | + * 已扣除积分 | |
4115 | + */ | |
4116 | + deleteDelta?: number; | |
4117 | + /** | |
4118 | + * @description | |
4119 | + * 账户积分 | |
4120 | + */ | |
4121 | + delta?: number; | |
4122 | + /** | |
4123 | + * @description | |
4069 | 4124 | * 课题组名称 |
4070 | 4125 | */ |
4071 | 4126 | groupName?: string; |
... | ... | @@ -4083,6 +4138,11 @@ export interface ResearchGroupsDTO { |
4083 | 4138 | logicDelete?: boolean; |
4084 | 4139 | members?: Array<ResearchGroupMembers>; |
4085 | 4140 | paths?: Array<string>; |
4141 | + /** | |
4142 | + * @description | |
4143 | + * 待领取积分 | |
4144 | + */ | |
4145 | + pendingDelta?: number; | |
4086 | 4146 | requestStatus?: string; |
4087 | 4147 | /** |
4088 | 4148 | * @description |
... | ... |
src/services/request.ts
... | ... | @@ -59,6 +59,7 @@ import type { |
59 | 59 | ExchangeIntegralDto, |
60 | 60 | ExchangeRecordsRequestDto, |
61 | 61 | FeedbackRegistrationDTO, |
62 | + GroupExchangeRecordsRequestDto, | |
62 | 63 | InventoryMaterialStockReq, |
63 | 64 | InvoiceBatchDownloadDto, |
64 | 65 | InvoiceDto, |
... | ... | @@ -4378,6 +4379,77 @@ export const postIntegralGetIntegralRecordTypes = /* #__PURE__ */ (() => { |
4378 | 4379 | return request; |
4379 | 4380 | })(); |
4380 | 4381 | |
4382 | +/** @description request parameter type for postIntegralGroupExchangeRecords */ | |
4383 | +export interface PostIntegralGroupExchangeRecordsOption { | |
4384 | + /** | |
4385 | + * @description | |
4386 | + * dto | |
4387 | + */ | |
4388 | + body: { | |
4389 | + /** | |
4390 | + @description | |
4391 | + dto */ | |
4392 | + dto: GroupExchangeRecordsRequestDto; | |
4393 | + }; | |
4394 | +} | |
4395 | + | |
4396 | +/** @description response type for postIntegralGroupExchangeRecords */ | |
4397 | +export interface PostIntegralGroupExchangeRecordsResponse { | |
4398 | + /** | |
4399 | + * @description | |
4400 | + * OK | |
4401 | + */ | |
4402 | + 200: ServerResult; | |
4403 | + /** | |
4404 | + * @description | |
4405 | + * Created | |
4406 | + */ | |
4407 | + 201: any; | |
4408 | + /** | |
4409 | + * @description | |
4410 | + * Unauthorized | |
4411 | + */ | |
4412 | + 401: any; | |
4413 | + /** | |
4414 | + * @description | |
4415 | + * Forbidden | |
4416 | + */ | |
4417 | + 403: any; | |
4418 | + /** | |
4419 | + * @description | |
4420 | + * Not Found | |
4421 | + */ | |
4422 | + 404: any; | |
4423 | +} | |
4424 | + | |
4425 | +export type PostIntegralGroupExchangeRecordsResponseSuccess = | |
4426 | + PostIntegralGroupExchangeRecordsResponse[200]; | |
4427 | +/** | |
4428 | + * @description | |
4429 | + * 课题组积分兑换记录 | |
4430 | + * @tags 积分接口 | |
4431 | + * @produces * | |
4432 | + * @consumes application/json | |
4433 | + */ | |
4434 | +export const postIntegralGroupExchangeRecords = /* #__PURE__ */ (() => { | |
4435 | + const method = 'post'; | |
4436 | + const url = '/integral/groupExchangeRecords'; | |
4437 | + function request( | |
4438 | + option: PostIntegralGroupExchangeRecordsOption, | |
4439 | + ): Promise<PostIntegralGroupExchangeRecordsResponseSuccess> { | |
4440 | + return requester(request.url, { | |
4441 | + method: request.method, | |
4442 | + ...option, | |
4443 | + }) as unknown as Promise<PostIntegralGroupExchangeRecordsResponseSuccess>; | |
4444 | + } | |
4445 | + | |
4446 | + /** http method */ | |
4447 | + request.method = method; | |
4448 | + /** request url */ | |
4449 | + request.url = url; | |
4450 | + return request; | |
4451 | +})(); | |
4452 | + | |
4381 | 4453 | /** @description request parameter type for postIntegralMergeUserIntegralToGroup */ |
4382 | 4454 | export interface PostIntegralMergeUserIntegralToGroupOption { |
4383 | 4455 | /** |
... | ... |