Commit 9ceaf867959d415ebf780e6f6c1d81e1b253da8f

Authored by boyang
1 parent 379c80fa

fix: 修改积分兑换弹窗样式

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&lt;PointsExchangeModalProps&gt; = ({
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&lt;PointsExchangeModalProps&gt; = ({
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&lt;PointsExchangeRecordsModalProps&gt; = ({
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&lt;PointsExchangeRecordsModalProps&gt; = ({
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&lt;PointsExchangeRecordsModalProps&gt; = ({
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&lt;PointsExchangeRecordsModalProps&gt; = ({
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 = () =&gt; {
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 = () =&gt; {
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 = () =&gt; {
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__ */ (() =&gt; {
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 /**
... ...