增加了悬赏,标签查看,评论页面,标签上传后端有问题,评论还没跟后端连,优化了一些小界面

Change-Id: I44f5ef2eb0a8ebd91a4b3b3b446f897bea41435f
diff --git a/react-ui/src/pages/Reward/components/UpdateForm.tsx b/react-ui/src/pages/Reward/components/UpdateForm.tsx
new file mode 100644
index 0000000..526b946
--- /dev/null
+++ b/react-ui/src/pages/Reward/components/UpdateForm.tsx
@@ -0,0 +1,122 @@
+import { Modal, Form, Input, InputNumber, Select } from 'antd';
+import React from 'react';
+import { useIntl } from 'umi';
+import { RewardItem } from '../data';
+export type FormValueType = {
+    rewardId?: number;
+    title?: string;
+    description?: string;
+    amount?: number;
+    status?: string;
+    remark?: string;
+} & Partial<RewardItem>;
+
+export type UpdateFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    values: Partial<RewardItem>;
+    statusOptions: any;
+    readOnly?: boolean;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    const { statusOptions } = props;
+
+    const intl = useIntl();
+
+    React.useEffect(() => {
+        form.resetFields();
+        form.setFieldsValue({
+            rewardId: props.values.rewardId,
+            title: props.values.title,
+            description: props.values.description,
+            amount: props.values.amount,
+            status: props.values.status,
+            remark: props.values.remark,
+        });
+    }, [form, props]);
+
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+        form.resetFields();
+    };
+    const handleFinish = async (values: FormValueType) => {
+        props.onSubmit(values);
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={props.readOnly ? '查看悬赏' : (props.values.rewardId ? '编辑悬赏' : '新增悬赏')}
+            open={props.open}
+            onOk={handleOk}
+            footer={props.readOnly ? null : undefined}
+            onCancel={handleCancel}
+        >
+            <Form
+                form={form}
+                onFinish={handleFinish}
+                initialValues={props.values}
+                labelCol={{ span: 6 }}
+                wrapperCol={{ span: 18 }}
+            >
+                <Form.Item name="rewardId" hidden={true}>
+                    <Input />
+                </Form.Item>
+                <Form.Item
+                    name="title"
+                    label="悬赏标题"
+                    rules={[{ required: true, message: '请输入悬赏标题!' }]}
+                >
+                    <Input placeholder="请输入悬赏标题" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="description"
+                    label="悬赏描述"
+                    rules={[{ required: true, message: '请输入悬赏描述!' }]}
+                >
+                    <Input.TextArea rows={4} placeholder="请输入悬赏描述" disabled={props.readOnly} />
+                </Form.Item>
+                <Form.Item
+                    name="amount"
+                    label="悬赏金额"
+                    rules={[{ required: true, message: '请输入悬赏金额!' }]}
+                >
+                    <InputNumber
+                        style={{ width: '100%' }}
+                        min={0}
+                        precision={2}
+                        placeholder="请输入悬赏金额"
+                        disabled={props.readOnly}
+                    />
+                </Form.Item>
+                {/* <Form.Item
+                    name="status"
+                    label="悬赏状态"
+                >
+                    <Select
+                        placeholder="请选择悬赏状态"
+                        options={statusOptions.map((item: any) => ({
+                            value: item.dictValue,
+                            label: item.dictLabel,
+                        }))}
+                    />
+                </Form.Item> */}
+                <Form.Item
+                    name="remark"
+                    label="备注"
+                >
+                    <Input.TextArea rows={2} placeholder="请输入备注" disabled={props.readOnly} />
+                </Form.Item>
+            </Form>
+        </Modal>
+    );
+};
+
+export default UpdateForm;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/data.d.ts b/react-ui/src/pages/Reward/data.d.ts
new file mode 100644
index 0000000..6dbec7b
--- /dev/null
+++ b/react-ui/src/pages/Reward/data.d.ts
@@ -0,0 +1,38 @@
+export interface RewardItem {
+    /** 悬赏ID */
+    rewardId?: number;
+    /** 悬赏标题 */
+    title: string;
+    /** 悬赏描述 */
+    description: string;
+    /** 悬赏金额 */
+    amount: number;
+    /** 悬赏状态(0进行中 1已完成 2已取消) */
+    status?: string;
+    /** 发布者ID */
+    publisherId?: number;
+    /** 接受者ID */
+    accepterId?: number;
+    /** 创建时间 */
+    createTime?: string;
+    /** 更新时间 */
+    updateTime?: string;
+    /** 备注 */
+    remark?: string;
+}
+
+export interface RewardListParams {
+    /** 当前的页码 */
+    pageNum?: number;
+    /** 页面的容量 */
+    pageSize?: number;
+    /** 悬赏标题 */
+    title?: string;
+    /** 悬赏状态 */
+    status?: string;
+    /** 开始时间 */
+    'params[beginTime]'?: string;
+    /** 结束时间 */
+    'params[endTime]'?: string;
+}
+
diff --git a/react-ui/src/pages/Reward/index.tsx b/react-ui/src/pages/Reward/index.tsx
new file mode 100644
index 0000000..231415c
--- /dev/null
+++ b/react-ui/src/pages/Reward/index.tsx
@@ -0,0 +1,338 @@
+import { ExclamationCircleOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Button, message, Modal, Switch } from 'antd';
+import React, { useRef, useState, useEffect } from 'react';
+import { FormattedMessage, useIntl } from 'umi';
+import { FooterToolbar, PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@ant-design/pro-table';
+import ProTable from '@ant-design/pro-table';
+import type { FormInstance } from 'antd';
+import { getRewardList, removeReward, updateReward, addReward } from './service';
+import UpdateForm from './components/UpdateForm';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import { useAccess } from 'umi';
+import { RewardItem, RewardListParams } from './data';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: RewardItem[]) => {
+    const hide = message.loading('正在删除');
+    if (!selectedRows) return true;
+    try {
+        await removeReward(selectedRows.map((row) => row.rewardId).join(','));
+        hide();
+        message.success('删除成功');
+        return true;
+    } catch (error) {
+        hide();
+        message.error('删除失败,请重试');
+        return false;
+    }
+};
+
+const handleUpdate = async (fields: RewardItem) => {
+    const hide = message.loading('正在更新');
+    try {
+        const resp = await updateReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('更新成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const handleAdd = async (fields: RewardItem) => {
+    const hide = message.loading('正在添加');
+    try {
+        const resp = await addReward(fields);
+        hide();
+        if (resp.code === 200) {
+            message.success('添加成功');
+        } else {
+            message.error(resp.msg);
+        }
+        return true;
+    } catch (error) {
+        hide();
+        message.error('配置失败请重试!');
+        return false;
+    }
+};
+
+const RewardTableList: React.FC = () => {
+    const formTableRef = useRef<FormInstance>();
+
+    const [modalVisible, setModalVisible] = useState<boolean>(false);
+    const [readOnly, setReadOnly] = useState<boolean>(false);
+
+    const actionRef = useRef<ActionType>();
+    const [currentRow, setCurrentRow] = useState<RewardItem>();
+    const [selectedRows, setSelectedRows] = useState<RewardItem[]>([]);
+
+    const [statusOptions, setStatusOptions] = useState<any>([]);
+
+    const access = useAccess();
+
+    /** 国际化配置 */
+    const intl = useIntl();
+
+    useEffect(() => {
+        getDictValueEnum('reward_status').then((data) => {
+            setStatusOptions(data);
+        });
+    }, []);
+
+    const columns: ProColumns<RewardItem>[] = [
+        {
+            title: '悬赏ID',
+            dataIndex: 'rewardId',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: '悬赏标题',
+            dataIndex: 'title',
+            valueType: 'text',
+        },
+        {
+            title: '悬赏金额',
+            dataIndex: 'amount',
+            valueType: 'money',
+            hideInSearch: true,
+        },
+        // {
+        //     title: '悬赏状态',
+        //     dataIndex: 'status',
+        //     valueType: 'select',
+        //     valueEnum: statusOptions,
+        //     render: (_, record) => {
+        //         return (<DictTag enums={statusOptions} value={record.status} />);
+        //     },
+        // },
+        {
+            title: '发布时间',
+            dataIndex: 'createTime',
+            valueType: 'dateRange',
+            render: (_, record) => {
+                return (<span>{record.createTime?.toString()}</span>);
+            },
+            search: {
+                transform: (value) => {
+                    return {
+                        'params[beginTime]': value[0],
+                        'params[endTime]': value[1],
+                    };
+                },
+            },
+        },
+        {
+            title: '备注',
+            dataIndex: 'remark',
+            valueType: 'text',
+            hideInSearch: true,
+        },
+        {
+            title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+            dataIndex: 'option',
+            width: '220px',
+            valueType: 'option',
+            render: (_, record) => [
+                <Button
+                    type="link"
+                    size="small"
+                    key="view"
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        // 设置只读模式
+                        setReadOnly(true);
+                    }}
+                >
+                    查看
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    key="edit"
+                    hidden={!access.hasPerms('reward:reward:edit')}
+                    onClick={() => {
+                        setModalVisible(true);
+                        setCurrentRow(record);
+                        setReadOnly(false);
+                    }}
+                >
+                    编辑
+                </Button>,
+                <Button
+                    type="link"
+                    size="small"
+                    danger
+                    key="batchRemove"
+                    hidden={!access.hasPerms('reward:reward:remove')}
+                    onClick={async () => {
+                        Modal.confirm({
+                            title: '删除',
+                            content: '确定删除该项吗?',
+                            okText: '确认',
+                            cancelText: '取消',
+                            onOk: async () => {
+                                const success = await handleRemove([record]);
+                                if (success) {
+                                    if (actionRef.current) {
+                                        actionRef.current.reload();
+                                    }
+                                }
+                            },
+                        });
+                    }}
+                >
+                    删除
+                </Button>,
+            ],
+        },
+    ];
+
+    return (
+        <PageContainer>
+            <div style={{ width: '100%', float: 'right' }}>
+                <ProTable<RewardItem>
+                    headerTitle={intl.formatMessage({
+                        id: 'pages.searchTable.title',
+                        defaultMessage: '信息',
+                    })}
+                    actionRef={actionRef}
+                    formRef={formTableRef}
+                    rowKey="rewardId"
+                    key="rewardList"
+                    search={{
+                        labelWidth: 120,
+                    }}
+                    toolBarRender={() => [
+                        <Button
+                            type="primary"
+                            key="add"
+                            hidden={!access.hasPerms('reward:reward:add')}
+                            onClick={async () => {
+                                setCurrentRow(undefined);
+                                setModalVisible(true);
+                            }}
+                        >
+                            <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+                        </Button>,
+                        <Button
+                            type="primary"
+                            key="remove"
+                            danger
+                            hidden={selectedRows?.length === 0 || !access.hasPerms('reward:reward:remove')}
+                            onClick={async () => {
+                                Modal.confirm({
+                                    title: '是否确认删除所选数据项?',
+                                    icon: <ExclamationCircleOutlined />,
+                                    content: '请谨慎操作',
+                                    async onOk() {
+                                        const success = await handleRemove(selectedRows);
+                                        if (success) {
+                                            setSelectedRows([]);
+                                            actionRef.current?.reloadAndRest?.();
+                                        }
+                                    },
+                                    onCancel() { },
+                                });
+                            }}
+                        >
+                            <DeleteOutlined />
+                            <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+                        </Button>,
+                    ]}
+                    request={(params) =>
+                        getRewardList({ ...params } as RewardListParams).then((res) => {
+                            return {
+                                data: res.rows,
+                                total: res.total,
+                                success: true,
+                            };
+                        })
+                    }
+                    columns={columns}
+                    rowSelection={{
+                        onChange: (_, selectedRows) => {
+                            setSelectedRows(selectedRows);
+                        },
+                    }}
+                />
+            </div>
+            {selectedRows?.length > 0 && (
+                <FooterToolbar
+                    extra={
+                        <div>
+                            <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+                            <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+                            <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+                        </div>
+                    }
+                >
+                    <Button
+                        key="remove"
+                        danger
+                        hidden={!access.hasPerms('reward:reward:remove')}
+                        onClick={async () => {
+                            Modal.confirm({
+                                title: '删除',
+                                content: '确定删除该项吗?',
+                                okText: '确认',
+                                cancelText: '取消',
+                                onOk: async () => {
+                                    const success = await handleRemove(selectedRows);
+                                    if (success) {
+                                        setSelectedRows([]);
+                                        actionRef.current?.reloadAndRest?.();
+                                    }
+                                },
+                            });
+                        }}
+                    >
+                        <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+                    </Button>
+                </FooterToolbar>
+            )}
+            <UpdateForm
+                readOnly={readOnly}
+                onSubmit={async (values) => {
+                    let success = false;
+                    if (values.rewardId) {
+                        success = await handleUpdate({ ...values } as RewardItem);
+                    } else {
+                        success = await handleAdd({ ...values } as RewardItem);
+                    }
+                    if (success) {
+                        setModalVisible(false);
+                        setCurrentRow(undefined);
+                        if (actionRef.current) {
+                            actionRef.current.reload();
+                        }
+                    }
+                }}
+                onCancel={() => {
+                    setModalVisible(false);
+                    setCurrentRow(undefined);
+                    setReadOnly(false);
+                }}
+                open={modalVisible}
+                values={currentRow || {}}
+                statusOptions={statusOptions}
+            />
+        </PageContainer>
+    );
+};
+
+export default RewardTableList;
\ No newline at end of file
diff --git a/react-ui/src/pages/Reward/service.ts b/react-ui/src/pages/Reward/service.ts
new file mode 100644
index 0000000..7550cf9
--- /dev/null
+++ b/react-ui/src/pages/Reward/service.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import type {
+    RewardItem,
+    RewardListParams,
+} from '@/pages/Reward/data'; // 假设你把 data.d.ts 放这里
+
+/** 获取悬赏任务列表 */
+export async function getRewardList(params?: RewardListParams) {
+    const queryString = params
+        ? `?${new URLSearchParams(params as Record<string, any>).toString()}`
+        : '';
+    const response = await request(`/api/reward/list${queryString}`, {
+        method: 'get',
+    });
+    if (!response || response.length === 0) {
+        return [{ id: 1, name: '虚假任务1', description: '这是一个虚假的任务描述' }, { id: 2, name: '虚假任务2', description: '这是另一个虚假的任务描述' }];
+    }
+    return response;
+}
+
+/** 获取悬赏任务详细信息 */
+export async function getReward(rewardId: number) {
+    return request(`/api/reward/${rewardId}`, {
+        method: 'get',
+    });
+}
+
+/** 新增悬赏任务 */
+export async function addReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'post',
+        data: params,
+    });
+}
+
+/** 修改悬赏任务 */
+export async function updateReward(params: RewardItem) {
+    return request('/api/reward', {
+        method: 'put',
+        data: params,
+    });
+}
+
+/** 删除悬赏任务 */
+export async function removeReward(ids: string) {
+    return request(`/api/reward/${ids}`, {
+        method: 'delete',
+    });
+}
\ No newline at end of file