|
|
@@ -0,0 +1,530 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import {
|
|
|
+ DeleteOutlined,
|
|
|
+ EditOutlined,
|
|
|
+ PlusOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ SearchOutlined,
|
|
|
+ UndoOutlined,
|
|
|
+} from "@ant-design/icons";
|
|
|
+import {
|
|
|
+ App,
|
|
|
+ Avatar,
|
|
|
+ Button,
|
|
|
+ DatePicker,
|
|
|
+ Form,
|
|
|
+ Input,
|
|
|
+ InputNumber,
|
|
|
+ Modal,
|
|
|
+ Popconfirm,
|
|
|
+ Select,
|
|
|
+ Space,
|
|
|
+ Switch,
|
|
|
+ Table,
|
|
|
+ Tag,
|
|
|
+} from "antd";
|
|
|
+import type { ColumnsType, TablePaginationConfig } from "antd/es/table";
|
|
|
+import type { Dayjs } from "dayjs";
|
|
|
+import type React from "react";
|
|
|
+import { useEffect, useState } from "react";
|
|
|
+import {
|
|
|
+ createPlaymateRecommendManualWeight,
|
|
|
+ deletePlaymateRecommendManualWeights,
|
|
|
+ getPlaymateRecommendManualWeight,
|
|
|
+ getPlaymateRecommendManualWeightPage,
|
|
|
+ updatePlaymateRecommendManualWeight,
|
|
|
+} from "@/services/playmateRecommendManualWeight";
|
|
|
+import type {
|
|
|
+ PlaymateRecommendManualWeightAdminDTO,
|
|
|
+ PlaymateRecommendManualWeightAdminQuery,
|
|
|
+ PlaymateRecommendManualWeightCreateAdminDTO,
|
|
|
+ PlaymateRecommendManualWeightUpdateAdminDTO,
|
|
|
+} from "@/types/api";
|
|
|
+import { formatTimestamp } from "@/utils/date";
|
|
|
+import dayjs from "@/utils/dayjs";
|
|
|
+
|
|
|
+const enabledOptions = [
|
|
|
+ { label: "全部", value: "all" },
|
|
|
+ { label: "启用", value: "true" },
|
|
|
+ { label: "禁用", value: "false" },
|
|
|
+];
|
|
|
+
|
|
|
+const PlaymateRecommendManualWeightPage: React.FC = () => {
|
|
|
+ const { message } = App.useApp();
|
|
|
+ const [searchForm] = Form.useForm();
|
|
|
+ const [editForm] = Form.useForm();
|
|
|
+
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [dataSource, setDataSource] = useState<
|
|
|
+ PlaymateRecommendManualWeightAdminDTO[]
|
|
|
+ >([]);
|
|
|
+ const [total, setTotal] = useState(0);
|
|
|
+ const [queryParams, setQueryParams] =
|
|
|
+ useState<PlaymateRecommendManualWeightAdminQuery>({
|
|
|
+ pageSize: 20,
|
|
|
+ pageIndex: 1,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [modalVisible, setModalVisible] = useState(false);
|
|
|
+ const [editMode, setEditMode] = useState(false);
|
|
|
+ const [editLoading, setEditLoading] = useState(false);
|
|
|
+ const [currentRecord, setCurrentRecord] =
|
|
|
+ useState<PlaymateRecommendManualWeightAdminDTO | null>(null);
|
|
|
+ const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
|
|
|
+
|
|
|
+ const loadPageData = async () => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const response = await getPlaymateRecommendManualWeightPage(queryParams);
|
|
|
+ setDataSource(response.items || []);
|
|
|
+ setTotal(response.total || 0);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to load playmate recommend manual weights:", error);
|
|
|
+ message.error("加载推荐权重失败");
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // biome-ignore lint/correctness/useExhaustiveDependencies: loadPageData is stable and doesn't need to be in dependencies
|
|
|
+ useEffect(() => {
|
|
|
+ loadPageData();
|
|
|
+ }, [queryParams]);
|
|
|
+
|
|
|
+ const handleSearch = () => {
|
|
|
+ const values = searchForm.getFieldsValue();
|
|
|
+ const effectiveRange = values.effectiveRange as [Dayjs, Dayjs] | undefined;
|
|
|
+
|
|
|
+ setQueryParams({
|
|
|
+ ...queryParams,
|
|
|
+ pageIndex: 1,
|
|
|
+ userNo: values.userNo?.trim() || undefined,
|
|
|
+ enabled:
|
|
|
+ values.enabled === "true"
|
|
|
+ ? true
|
|
|
+ : values.enabled === "false"
|
|
|
+ ? false
|
|
|
+ : undefined,
|
|
|
+ effectiveStartAt: effectiveRange?.[0]?.valueOf(),
|
|
|
+ effectiveEndAt: effectiveRange?.[1]?.valueOf(),
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleReset = () => {
|
|
|
+ searchForm.resetFields();
|
|
|
+ setQueryParams({
|
|
|
+ pageSize: 20,
|
|
|
+ pageIndex: 1,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRefresh = () => {
|
|
|
+ loadPageData();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleTableChange = (pagination: TablePaginationConfig) => {
|
|
|
+ setQueryParams({
|
|
|
+ ...queryParams,
|
|
|
+ pageIndex: pagination.current || 1,
|
|
|
+ pageSize: pagination.pageSize || 20,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const openCreateModal = () => {
|
|
|
+ setEditMode(false);
|
|
|
+ setCurrentRecord(null);
|
|
|
+ editForm.resetFields();
|
|
|
+ editForm.setFieldsValue({
|
|
|
+ userNos: [],
|
|
|
+ enabled: true,
|
|
|
+ });
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const openEditModal = async (
|
|
|
+ record: PlaymateRecommendManualWeightAdminDTO,
|
|
|
+ ) => {
|
|
|
+ if (!record.id) {
|
|
|
+ message.warning("缺少记录 ID,无法编辑");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setEditMode(true);
|
|
|
+ setEditLoading(true);
|
|
|
+ try {
|
|
|
+ const detail = await getPlaymateRecommendManualWeight({ id: record.id });
|
|
|
+ setCurrentRecord(detail);
|
|
|
+ editForm.resetFields();
|
|
|
+ editForm.setFieldsValue({
|
|
|
+ weightValue: detail.weightValue,
|
|
|
+ enabled: detail.enabled ?? true,
|
|
|
+ effectiveStartAt: detail.effectiveStartAt
|
|
|
+ ? dayjs(detail.effectiveStartAt)
|
|
|
+ : undefined,
|
|
|
+ effectiveEndAt: detail.effectiveEndAt
|
|
|
+ ? dayjs(detail.effectiveEndAt)
|
|
|
+ : undefined,
|
|
|
+ remark: detail.remark,
|
|
|
+ });
|
|
|
+ setModalVisible(true);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to load manual weight detail:", error);
|
|
|
+ message.error("加载记录详情失败");
|
|
|
+ } finally {
|
|
|
+ setEditLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDelete = async (
|
|
|
+ record: PlaymateRecommendManualWeightAdminDTO,
|
|
|
+ ) => {
|
|
|
+ if (!record.id) {
|
|
|
+ message.warning("缺少记录 ID,无法删除");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await deletePlaymateRecommendManualWeights({ ids: [record.id] });
|
|
|
+ message.success("删除成功");
|
|
|
+ loadPageData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to delete manual weight:", error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleBatchDelete = async () => {
|
|
|
+ if (selectedRowIds.length === 0) {
|
|
|
+ message.warning("请先选择要删除的记录");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await deletePlaymateRecommendManualWeights({ ids: selectedRowIds });
|
|
|
+ message.success("批量删除成功");
|
|
|
+ setSelectedRowIds([]);
|
|
|
+ loadPageData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to batch delete manual weights:", error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmit = async () => {
|
|
|
+ try {
|
|
|
+ const values = await editForm.validateFields();
|
|
|
+ setEditLoading(true);
|
|
|
+
|
|
|
+ if (editMode) {
|
|
|
+ if (!currentRecord?.id) {
|
|
|
+ message.error("缺少记录 ID,无法更新");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const submitData: PlaymateRecommendManualWeightUpdateAdminDTO = {
|
|
|
+ id: currentRecord.id,
|
|
|
+ weightValue: values.weightValue,
|
|
|
+ enabled: Boolean(values.enabled),
|
|
|
+ effectiveStartAt: dayjs(values.effectiveStartAt).valueOf(),
|
|
|
+ effectiveEndAt: values.effectiveEndAt
|
|
|
+ ? dayjs(values.effectiveEndAt).valueOf()
|
|
|
+ : null,
|
|
|
+ remark: values.remark?.trim() || undefined,
|
|
|
+ };
|
|
|
+ await updatePlaymateRecommendManualWeight(submitData);
|
|
|
+ message.success("更新成功");
|
|
|
+ } else {
|
|
|
+ const submitData: PlaymateRecommendManualWeightCreateAdminDTO = {
|
|
|
+ userNos: (values.userNos || [])
|
|
|
+ .map((item: string) => item.trim())
|
|
|
+ .filter((item: string) => item.length > 0),
|
|
|
+ weightValue: values.weightValue,
|
|
|
+ enabled: Boolean(values.enabled),
|
|
|
+ effectiveStartAt: dayjs(values.effectiveStartAt).valueOf(),
|
|
|
+ effectiveEndAt: values.effectiveEndAt
|
|
|
+ ? dayjs(values.effectiveEndAt).valueOf()
|
|
|
+ : null,
|
|
|
+ remark: values.remark?.trim() || undefined,
|
|
|
+ };
|
|
|
+ await createPlaymateRecommendManualWeight(submitData);
|
|
|
+ message.success("创建成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ setModalVisible(false);
|
|
|
+ loadPageData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to submit manual weight:", error);
|
|
|
+ } finally {
|
|
|
+ setEditLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const columns: ColumnsType<PlaymateRecommendManualWeightAdminDTO> = [
|
|
|
+ {
|
|
|
+ title: "陪玩师编号",
|
|
|
+ dataIndex: "userNo",
|
|
|
+ key: "userNo",
|
|
|
+ width: 140,
|
|
|
+ render: (v?: string) => v || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "昵称",
|
|
|
+ dataIndex: "nickname",
|
|
|
+ key: "nickname",
|
|
|
+ width: 160,
|
|
|
+ render: (v?: string) => v || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "头像",
|
|
|
+ dataIndex: "avatar",
|
|
|
+ key: "avatar",
|
|
|
+ width: 90,
|
|
|
+ render: (url?: string) =>
|
|
|
+ url ? <Avatar src={url} size={36} alt="avatar" /> : "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "人工权重",
|
|
|
+ dataIndex: "weightValue",
|
|
|
+ key: "weightValue",
|
|
|
+ width: 110,
|
|
|
+ render: (v?: number) =>
|
|
|
+ typeof v === "number" ? (
|
|
|
+ <Tag color={v >= 0 ? "success" : "error"}>{v}</Tag>
|
|
|
+ ) : (
|
|
|
+ "-"
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "状态",
|
|
|
+ dataIndex: "enabled",
|
|
|
+ key: "enabled",
|
|
|
+ width: 90,
|
|
|
+ render: (v?: boolean) =>
|
|
|
+ v ? <Tag color="success">启用</Tag> : <Tag color="default">禁用</Tag>,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "生效开始时间",
|
|
|
+ dataIndex: "effectiveStartAt",
|
|
|
+ key: "effectiveStartAt",
|
|
|
+ width: 180,
|
|
|
+ render: (ts?: number) => (ts ? formatTimestamp(ts) : "-"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "生效结束时间",
|
|
|
+ dataIndex: "effectiveEndAt",
|
|
|
+ key: "effectiveEndAt",
|
|
|
+ width: 180,
|
|
|
+ render: (ts?: number | null) => (ts ? formatTimestamp(ts) : "长期生效"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "备注",
|
|
|
+ dataIndex: "remark",
|
|
|
+ key: "remark",
|
|
|
+ width: 260,
|
|
|
+ ellipsis: true,
|
|
|
+ render: (v?: string) => v || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "更新时间",
|
|
|
+ dataIndex: "updatedAt",
|
|
|
+ key: "updatedAt",
|
|
|
+ width: 180,
|
|
|
+ render: (ts?: number) => (ts ? formatTimestamp(ts) : "-"),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "操作",
|
|
|
+ key: "action",
|
|
|
+ width: 160,
|
|
|
+ fixed: "right",
|
|
|
+ render: (_, record) => (
|
|
|
+ <Space size="small">
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon={<EditOutlined />}
|
|
|
+ onClick={() => openEditModal(record)}
|
|
|
+ >
|
|
|
+ 编辑
|
|
|
+ </Button>
|
|
|
+ <Popconfirm
|
|
|
+ title="确定删除该记录吗?"
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ onConfirm={() => handleDelete(record)}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const rowSelection = {
|
|
|
+ selectedRowKeys: selectedRowIds,
|
|
|
+ onChange: (selectedRowKeys: React.Key[]) => {
|
|
|
+ setSelectedRowIds(
|
|
|
+ selectedRowKeys.filter((key): key is string => typeof key === "string"),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ getCheckboxProps: (record: PlaymateRecommendManualWeightAdminDTO) => ({
|
|
|
+ disabled: !record.id,
|
|
|
+ }),
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ <div className="bg-white p-4 rounded-lg shadow mb-4">
|
|
|
+ <Form form={searchForm} layout="inline" className="gap-x-2 gap-y-4">
|
|
|
+ <Form.Item label="陪玩师编号" name="userNo">
|
|
|
+ <Input placeholder="请输入" allowClear style={{ width: 180 }} />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="状态" name="enabled">
|
|
|
+ <Select
|
|
|
+ placeholder="请选择"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 140 }}
|
|
|
+ options={enabledOptions}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="生效区间" name="effectiveRange">
|
|
|
+ <DatePicker.RangePicker
|
|
|
+ showTime
|
|
|
+ allowClear
|
|
|
+ style={{ width: 360 }}
|
|
|
+ placeholder={["开始时间", "结束时间"]}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item style={{ marginLeft: "auto" }}>
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<SearchOutlined />}
|
|
|
+ onClick={handleSearch}
|
|
|
+ >
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ <Button icon={<UndoOutlined />} onClick={handleReset}>
|
|
|
+ 重置
|
|
|
+ </Button>
|
|
|
+ <Button icon={<ReloadOutlined />} onClick={handleRefresh}>
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<PlusOutlined />}
|
|
|
+ onClick={openCreateModal}
|
|
|
+ style={{ backgroundColor: "#52c41a" }}
|
|
|
+ >
|
|
|
+ 新增
|
|
|
+ </Button>
|
|
|
+ <Popconfirm
|
|
|
+ title={`确定删除选中的 ${selectedRowIds.length} 条记录吗?`}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ onConfirm={handleBatchDelete}
|
|
|
+ disabled={selectedRowIds.length === 0}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
+ disabled={selectedRowIds.length === 0}
|
|
|
+ >
|
|
|
+ 批量删除
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ </Space>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="bg-white p-4 rounded-lg shadow">
|
|
|
+ <Table
|
|
|
+ rowSelection={rowSelection}
|
|
|
+ columns={columns}
|
|
|
+ dataSource={dataSource}
|
|
|
+ rowKey={(record) =>
|
|
|
+ record.id ??
|
|
|
+ `${record.playmateUserId ?? ""}-${record.createdAt ?? ""}-${record.userNo ?? ""}`
|
|
|
+ }
|
|
|
+ loading={loading}
|
|
|
+ pagination={{
|
|
|
+ current: queryParams.pageIndex,
|
|
|
+ pageSize: queryParams.pageSize,
|
|
|
+ total,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showTotal: (t) => `共 ${t} 条`,
|
|
|
+ pageSizeOptions: ["10", "20", "30", "40", "50", "100", "200"],
|
|
|
+ }}
|
|
|
+ onChange={handleTableChange}
|
|
|
+ scroll={{ x: 1500 }}
|
|
|
+ size="small"
|
|
|
+ bordered
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ title={editMode ? "编辑推荐权重" : "新增推荐权重"}
|
|
|
+ open={modalVisible}
|
|
|
+ onOk={handleSubmit}
|
|
|
+ onCancel={() => setModalVisible(false)}
|
|
|
+ confirmLoading={editLoading}
|
|
|
+ width={700}
|
|
|
+ destroyOnHidden
|
|
|
+ forceRender
|
|
|
+ >
|
|
|
+ <Form form={editForm} layout="vertical" style={{ marginTop: 16 }}>
|
|
|
+ {!editMode && (
|
|
|
+ <Form.Item
|
|
|
+ label="陪玩师编号列表(userNos)"
|
|
|
+ name="userNos"
|
|
|
+ rules={[{ required: true, message: "请至少输入一个陪玩师编号" }]}
|
|
|
+ tooltip="支持批量输入,回车或逗号分隔"
|
|
|
+ >
|
|
|
+ <Select mode="tags" tokenSeparators={[",", " ", "\n", "\t"]} />
|
|
|
+ </Form.Item>
|
|
|
+ )}
|
|
|
+ <Form.Item
|
|
|
+ label="人工权重值"
|
|
|
+ name="weightValue"
|
|
|
+ rules={[{ required: true, message: "请输入人工权重值" }]}
|
|
|
+ >
|
|
|
+ <InputNumber
|
|
|
+ style={{ width: "100%" }}
|
|
|
+ placeholder="可填正数或负数"
|
|
|
+ step={0.1}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item
|
|
|
+ label="生效开始时间"
|
|
|
+ name="effectiveStartAt"
|
|
|
+ rules={[{ required: true, message: "请选择生效开始时间" }]}
|
|
|
+ >
|
|
|
+ <DatePicker showTime style={{ width: "100%" }} />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="生效结束时间" name="effectiveEndAt">
|
|
|
+ <DatePicker
|
|
|
+ showTime
|
|
|
+ style={{ width: "100%" }}
|
|
|
+ placeholder="不填表示长期生效"
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="是否启用" name="enabled" valuePropName="checked">
|
|
|
+ <Switch />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item label="备注" name="remark">
|
|
|
+ <Input.TextArea
|
|
|
+ placeholder="请输入备注"
|
|
|
+ autoSize={{ minRows: 2, maxRows: 6 }}
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </Modal>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default PlaymateRecommendManualWeightPage;
|