Ver código fonte

Add new menu item for "推荐权重" and update type exports for playmate recommendations

- Introduced a new menu entry for "推荐权重" with the corresponding path, sort order, and permissions.
- Updated the type exports in the API index to include new types related to playmate recommend manual weight, enhancing type safety and clarity in the application.
0es 2 semanas atrás
pai
commit
ed0460ca1f

+ 530 - 0
src/app/(dashboard)/playmate/recommend-manual-weight/page.tsx

@@ -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;

+ 9 - 0
src/config/menus.tsx

@@ -5,6 +5,7 @@ import {
   BarChartOutlined,
   BarChartOutlined,
   ControlOutlined,
   ControlOutlined,
   DollarOutlined,
   DollarOutlined,
+  FieldNumberOutlined,
   FileOutlined,
   FileOutlined,
   FileTextOutlined,
   FileTextOutlined,
   GlobalOutlined,
   GlobalOutlined,
@@ -135,6 +136,14 @@ export const menuConfig: MenuItem[] = [
         icon: <UnorderedListOutlined />,
         icon: <UnorderedListOutlined />,
         permission: "/playmate/list",
         permission: "/playmate/list",
       },
       },
+      {
+        key: "playmate-recommend-manual-weight",
+        label: "推荐权重",
+        path: "/playmate/recommend-manual-weight",
+        sortOrder: 2,
+        icon: <FieldNumberOutlined />,
+        permission: "/playmate/recommend-manual-weight",
+      },
     ],
     ],
   },
   },
   {
   {

+ 50 - 0
src/services/playmateRecommendManualWeight.ts

@@ -0,0 +1,50 @@
+/**
+ * Playmate recommend manual weight API services
+ */
+
+import { post } from "@/lib/request";
+import type {
+  PagerPlaymateRecommendManualWeightAdminDTO,
+  PlaymateRecommendManualWeightAdminDTO,
+  PlaymateRecommendManualWeightAdminQuery,
+  PlaymateRecommendManualWeightCreateAdminDTO,
+  PlaymateRecommendManualWeightIdListReq,
+  PlaymateRecommendManualWeightIdReq,
+  PlaymateRecommendManualWeightUpdateAdminDTO,
+} from "@/types/api";
+
+export async function getPlaymateRecommendManualWeightPage(
+  query: PlaymateRecommendManualWeightAdminQuery,
+): Promise<PagerPlaymateRecommendManualWeightAdminDTO> {
+  return post<PagerPlaymateRecommendManualWeightAdminDTO>(
+    "/config/playmate-recommend-manual-weight/page",
+    query,
+  );
+}
+
+export async function getPlaymateRecommendManualWeight(
+  req: PlaymateRecommendManualWeightIdReq,
+): Promise<PlaymateRecommendManualWeightAdminDTO> {
+  return post<PlaymateRecommendManualWeightAdminDTO>(
+    "/config/playmate-recommend-manual-weight/get",
+    req,
+  );
+}
+
+export async function createPlaymateRecommendManualWeight(
+  data: PlaymateRecommendManualWeightCreateAdminDTO,
+): Promise<void> {
+  return post<void>("/config/playmate-recommend-manual-weight/create", data);
+}
+
+export async function updatePlaymateRecommendManualWeight(
+  data: PlaymateRecommendManualWeightUpdateAdminDTO,
+): Promise<void> {
+  return post<void>("/config/playmate-recommend-manual-weight/update", data);
+}
+
+export async function deletePlaymateRecommendManualWeights(
+  req: PlaymateRecommendManualWeightIdListReq,
+): Promise<void> {
+  return post<void>("/config/playmate-recommend-manual-weight/delete", req);
+}

+ 10 - 0
src/types/api/index.ts

@@ -160,6 +160,16 @@ export type {
   SkillInfoAdminDto,
   SkillInfoAdminDto,
   UserPlaymateSetSub,
   UserPlaymateSetSub,
 } from "./playmate";
 } from "./playmate";
+// Playmate recommend manual weight types
+export type {
+  PagerPlaymateRecommendManualWeightAdminDTO,
+  PlaymateRecommendManualWeightAdminDTO,
+  PlaymateRecommendManualWeightCreateAdminDTO,
+  PlaymateRecommendManualWeightAdminQuery,
+  PlaymateRecommendManualWeightIdListReq,
+  PlaymateRecommendManualWeightIdReq,
+  PlaymateRecommendManualWeightUpdateAdminDTO,
+} from "./playmateRecommendManualWeight";
 // Playmate apply (approval) types
 // Playmate apply (approval) types
 export type {
 export type {
   PagerPlaymateApplyAdminDTO,
   PagerPlaymateApplyAdminDTO,

+ 101 - 0
src/types/api/playmateRecommendManualWeight.ts

@@ -0,0 +1,101 @@
+/**
+ * Playmate recommend manual weight API type definitions
+ */
+
+import type { QuerySort } from "./common";
+
+/**
+ * 陪玩师推荐人工权重配置传输对象
+ * ObjectId fields are represented as string.
+ */
+export interface PlaymateRecommendManualWeightAdminDTO {
+  id?: string;
+  createdAt?: number;
+  updatedAt?: number;
+  playmateUserId?: string;
+  userNo?: string;
+  nickname?: string;
+  avatar?: string;
+  /**
+   * 人工权重值(可正可负)
+   */
+  weightValue?: number;
+  /**
+   * 是否启用
+   */
+  enabled?: boolean;
+  /**
+   * 生效开始时间(毫秒)
+   */
+  effectiveStartAt?: number;
+  /**
+   * 生效结束时间(毫秒,null/undefined 表示长期生效)
+   */
+  effectiveEndAt?: number | null;
+  /**
+   * 备注
+   */
+  remark?: string;
+  operatorUserId?: string;
+  deleted?: boolean;
+}
+
+/**
+ * 陪玩师推荐人工权重配置批量新增入参
+ */
+export interface PlaymateRecommendManualWeightCreateAdminDTO {
+  /**
+   * 陪玩师用户编号列表
+   */
+  userNos: string[];
+  weightValue: number;
+  enabled?: boolean;
+  effectiveStartAt: number;
+  effectiveEndAt?: number | null;
+  remark?: string;
+}
+
+/**
+ * 陪玩师推荐人工权重配置更新入参
+ * ObjectId fields are represented as string.
+ */
+export interface PlaymateRecommendManualWeightUpdateAdminDTO {
+  id: string;
+  weightValue: number;
+  enabled?: boolean;
+  effectiveStartAt: number;
+  effectiveEndAt?: number | null;
+  remark?: string;
+}
+
+/**
+ * 陪玩师推荐人工权重配置分页请求
+ */
+export interface PlaymateRecommendManualWeightAdminQuery {
+  pageSize?: number;
+  pageIndex?: number;
+  sort?: QuerySort;
+  userNo?: string;
+  enabled?: boolean;
+  effectiveStartAt?: number;
+  effectiveEndAt?: number;
+  skip?: number;
+}
+
+/**
+ * 分页响应
+ */
+export interface PagerPlaymateRecommendManualWeightAdminDTO {
+  total: number;
+  pageIndex: number;
+  pageSize: number;
+  items: PlaymateRecommendManualWeightAdminDTO[];
+}
+
+export interface PlaymateRecommendManualWeightIdReq {
+  id: string;
+}
+
+export interface PlaymateRecommendManualWeightIdListReq {
+  ids: string[];
+}