hejianhao
2025-12-22 ead0d2f66337aedf0aa0aedd1bc20f98d17276bd
feat: 新增公司管理和用户管理模块及相关功能

refactor: 优化订单转让功能和大礼包订单状态显示

fix: 修复订单详情页布局和状态显示问题

chore: 移除无用导出功能并统一使用utils工具类

docs: 更新路由配置添加新页面路径

style: 调整确认弹窗样式和订单表格宽度

perf: 优化订单转让记录查询和详情展示

test: 添加批量修改积分组件测试用例

build: 新增公司详情和用户列表相关服务接口

ci: 更新CI配置支持新模块构建
16个文件已添加
15个文件已修改
2898 ■■■■■ 已修改文件
management/config/routes.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/app.tsx 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/components/profitDetail.jsx 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/components/promotionRecord.jsx 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/components/qrCodeModal.jsx 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/components/userManagement.jsx 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/components/userModal.jsx 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/index.jsx 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/detail/service.js 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/list/components/addAndEdit.jsx 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/list/index.jsx 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/company/list/service.js 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/bigGiftPackageOrder/components/OrderDetailModal.jsx 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/bigGiftPackageOrder/components/OrderTransferModal.jsx 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/bigGiftPackageOrder/edit.jsx 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/bigGiftPackageOrder/index.jsx 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/bigGiftPackageOrder/service.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/orderTransfer/components/TransferDetailModal.jsx 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/orderTransfer/index.jsx 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/orderTransfer/service.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/physicalGoodsOrder/components/OrderDetailModal.jsx 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/physicalGoodsOrder/index.jsx 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/physicalGoodsOrder/service.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/refundAudit/components/RefundDetailModal.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/refundAudit/index.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/order/refundAudit/service.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/user/list/components/BatchUpdateIntegralModal.jsx 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/user/list/index.jsx 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/user/list/service.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/services/service.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/utils/antdUtils.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/config/routes.ts
@@ -132,6 +132,17 @@
        "component": "./order/bigGiftPackageOrder/index"
      },
      {
        "name": "大礼包订单编辑",
        "path": "/order/bigGiftPackageOrder/edit",
        "component": "./order/bigGiftPackageOrder/edit",
        "hideInMenu": true
      },
      {
        "name": "订单转让记录",
        "path": "/order/orderTransfer",
        "component": "./order/orderTransfer/index"
      },
      {
        "name": "实物商品订单",
        "path": "/order/physicalGoodsOrder",
        "component": "./order/physicalGoodsOrder/index"
@@ -141,24 +152,34 @@
        "path": "/order/refundAudit",
        "component": "./order/refundAudit/index"
      },
      // {
      //   "name": "大礼包订单详情",
      //   "path": "/order/bigGiftPackageOrder/detail",
      //   "component": "./order/bigGiftPackageOrder/detail",
      //   "hideInMenu": true
      // },
      // {
      //   "name": "大礼包订单编辑",
      //   "path": "/order/bigGiftPackageOrder/edit",
      //   "component": "./order/bigGiftPackageOrder/edit",
      //   "hideInMenu": true
      // },
      // {
      //   "name": "订单转让",
      //   "path": "/order/bigGiftPackageOrder/transfer",
      //   "component": "./order/bigGiftPackageOrder/transfer",
      //   "hideInMenu": true
      // }
    ]
  }
  },
  {
    "name": "公司管理",
    "path": "/company",
    "routes": [
      {
        "name": "公司列表",
        "path": "/company/list",
        "component": "./company/list"
      },
      {
        "name": "公司详情",
        "path": "/company/detail",
        "component": "./company/detail",
        "hideInMenu": true
      }
    ]
  },
  {
    "name": "用户管理",
    "path": "/user",
    "routes": [
      {
        "name": "用户列表",
        "path": "/user/list",
        "component": "./user/list"
      }
    ]
  },
];
management/src/app.tsx
@@ -11,7 +11,7 @@
import logo from '../public/logo/logo.png';
import { AvatarName } from './components/RightContent/AvatarDropdown';
import { errorConfig } from './requestErrorConfig';
import { getRoleInfoFromUserId } from './services/service';
import { getRoleInfoFromUserId, getUserInfoFromUserId } from './services/service';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/login';
@@ -39,20 +39,7 @@
  currentUser?: {};
  permission?: Array<Permissions>;
  loading?: boolean;
  // fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
  // const fetchUserInfo = async () => {
  //   try {
  //     const msg = await queryCurrentUser({
  //       skipErrorHandler: true,
  //     });
  //     return msg.data;
  //   } catch (error) {
  //     history.push(loginPath);
  //   }
  //   return undefined;
  // };
  // 如果不是登录页面,执行
  let permission = [];
  if (
management/src/pages/company/detail/components/profitDetail.jsx
New file
@@ -0,0 +1,181 @@
import {
  buildProTableDataSource,
} from '@/utils/antdUtils';
import { exportExcell } from '@/utils/utils';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Modal, Space } from 'antd';
import { useRef, useState } from 'react';
import { getProfitPageList } from '../service';
import { message } from 'antd';
import OrderDetailModal from '../../../order/bigGiftPackageOrder/components/OrderDetailModal';
const ProfitDetail = ({ companyId }) => {
  const actionRef = useRef();
  const [orderDetailVisible, setOrderDetailVisible] = useState(false);
  const [currentOrderId, setCurrentOrderId] = useState(null);
  const columns = [
    {
      title: '序号',
      dataIndex: 'index',
      align: 'center',
      hideInSearch: true,
      render: (text, record, index) => {
        return index + 1;
      },
    },
    {
      title: '订单编号',
      dataIndex: 'code',
      align: 'center',
    },
    {
      title: '下单时间',
      dataIndex: 'createTime',
      align: 'center',
      valueType: 'dateRange',
      hideInTable: true,
      fieldProps: {
        placeholder: ['开始时间', '结束时间']
      },
    },
    {
      title: '下单用户',
      dataIndex: 'userName',
      align: 'center',
    },
    {
      title: '用户手机号',
      dataIndex: 'phone',
      align: 'center',
    },
    {
      title: '订单价格',
      dataIndex: 'orderPrice',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
    {
      title: '推广员收益',
      dataIndex: 'saleProfit',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
    {
      title: '合伙人收益',
      dataIndex: 'partnerProfit',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
    {
      title: '操作',
      hideInSearch: true,
      render: (text, record) => {
        return (
          <Space>
            <Button
              size="small"
              type="link"
              onClick={() => {
                setCurrentOrderId(record.id);
                setOrderDetailVisible(true);
              }}
            >
              详情
            </Button>
          </Space>
        );
      },
    },
  ];
  // 处理导出功能
  const handleExport = async (params) => {
    try {
      // 构建导出参数
      const exportParams = {
        ...params,
        companyId,
      };
      exportExcell('收益明细.xlsx', exportParams, '/t-company/exportProfit');
      message.success('导出成功');
    } catch (error) {
      message.error('导出失败');
    }
  };
  // 处理时间范围参数
  const handleDateRange = (params) => {
    const newParams = { ...params };
    if (params.createTime && params.createTime.length === 2) {
      newParams.startTime = params.createTime[0];
      newParams.endTime = params.createTime[1];
      delete newParams.createTime;
    }
    return newParams;
  };
  return (
    <div>
      <ProTable
        rowKey="id"
        columnsState={{
          persistenceKey: 'pro-table-profit-detail',
          persistenceType: 'localStorage',
        }}
        tableAlertRender={false}
        search={{
          labelWidth: 'auto',
          defaultCollapsed: false,
        }}
        actionRef={actionRef}
        columns={columns}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        rowSelection={false}
        request={(params) => {
          const processedParams = handleDateRange(params);
          return buildProTableDataSource(getProfitPageList, { ...processedParams, companyId });
        }}
        toolBarRender={(action, selectRows) => [
          <Button
            key="export"
            type="primary"
            onClick={() => {
              // 获取当前搜索参数
              const currentParams = actionRef.current?.ProTable ?
                actionRef.current.ProTable.getState().proTableSearchParams : {};
              handleExport(currentParams);
            }}
          >
            导出Excel
          </Button>,
        ]}
      />
      {/* 订单详情弹窗 */}
      <OrderDetailModal
        visible={orderDetailVisible}
        onCancel={() => {
          setOrderDetailVisible(false);
          setCurrentOrderId(null);
        }}
        orderId={currentOrderId}
      />
    </div>
  );
};
export default ProfitDetail;
management/src/pages/company/detail/components/promotionRecord.jsx
New file
@@ -0,0 +1,197 @@
import {
  buildProTableDataSource
} from '@/utils/antdUtils';
import { exportExcell } from '@/utils/utils'
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import { useRef } from 'react';
import { getPromotionPageList } from '../service';
import { message } from 'antd';
const PromotionRecord = ({ companyId }) => {
  const actionRef = useRef();
  const columns = [
    {
      title: '上级用户',
      dataIndex: 'lastUserName',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '上级用户电话',
      dataIndex: 'lastPhone',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '上级用户角色',
      dataIndex: 'lastRoleName',
      align: 'center',
      hideInSearch: true,
      render: (text, record) => {
        const roleMap = {
          1: { text: '游客', color: 'default' },
          2: { text: '营销员', color: 'blue' },
          3: { text: '合伙人', color: 'green' },
        };
        const role = roleMap[record.lastRoleName] || { text: '未知', color: 'default' };
        return <Tag color={role.color}>{role.text}</Tag>;
      },
    },
    {
      title: '上级用户',
      dataIndex: 'lastUserName',
      align: 'center',
      hideInTable: true,
      fieldProps: {
        placeholder: '请输入上级用户姓名',
      },
    },
    {
      title: '上级手机号',
      dataIndex: 'lastPhone',
      align: 'center',
      valueType: 'text',
      hideInTable: true,
      fieldProps: {
        placeholder: '请输入上级手机号',
      },
    },
    {
      title: '头像',
      dataIndex: 'lastAvatar',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? (
          <img
            src={text}
            alt="上级头像"
            style={{ width: 40, height: 40, borderRadius: '50%', objectFit: 'cover' }}
          />
        ) : (
          <div style={{ width: 40, height: 40, borderRadius: '50%', background: '#f0f0f0' }} />
        );
      },
    },
    {
      title: '用户名',
      dataIndex: 'userName',
      align: 'center',
    },
    {
      title: '联系电话',
      dataIndex: 'phone',
      align: 'center',
    },
    {
      title: '当前角色',
      dataIndex: 'roleName',
      align: 'center',
      hideInSearch: true,
      render: (text, record) => {
        const roleMap = {
          1: { text: '游客', color: 'default' },
          2: { text: '营销员', color: 'blue' },
          3: { text: '合伙人', color: 'green' },
        };
        const role = roleMap[record.roleName] || { text: '未知', color: 'default' };
        return <Tag color={role.color}>{role.text}</Tag>;
      },
    },
    {
      title: '状态',
      dataIndex: 'status',
      align: 'center',
      hideInSearch: true,
      valueEnum: {
        1: { text: '正常', status: 'Success' },
        2: { text: '冻结', status: 'Error' },
      },
    },
    {
      title: '合计消费',
      dataIndex: 'totalConsume',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
    {
      title: '合计收益',
      dataIndex: 'totalIncome',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
    {
      title: '已结算收益',
      dataIndex: 'totalSettledIncome',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return text ? `¥${Number(text).toFixed(2)}` : '¥0.00';
      },
    },
  ];
  // 处理导出功能
  const handleExport = async (params) => {
    try {
      // 构建导出参数
      const exportParams = {
        ...params,
        companyId,
      };
      exportExcell('推广记录.xlsx', exportParams, '/t-company/export');
      message.success('导出成功');
    } catch (error) {
      message.error('导出失败');
    }
  };
  return (
    <div>
      <ProTable
        rowKey="userId"
        columnsState={{
          persistenceKey: 'pro-table-promotion-record',
          persistenceType: 'localStorage',
        }}
        tableAlertRender={false}
        search={{
          labelWidth: 'auto',
          defaultCollapsed: false,
        }}
        actionRef={actionRef}
        columns={columns}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        rowSelection={false}
        request={(params) => {
          return buildProTableDataSource(getPromotionPageList, { ...params, companyId });
        }}
        toolBarRender={(action, selectRows) => [
          <Button
            key="export"
            type="primary"
            onClick={() => {
              handleExport(params);
            }}
          >
            导出Excel
          </Button>,
        ]}
      />
    </div>
  );
};
export default PromotionRecord;
management/src/pages/company/detail/components/qrCodeModal.jsx
New file
@@ -0,0 +1,87 @@
import { Button, Modal, Space, Typography } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { DownloadOutlined } from '@ant-design/icons';
import { request } from '@umijs/max';
const { Title, Paragraph } = Typography;
const QrCodeModal = ({ visible, onCancel, userInfo }, ref) => {
  const [loading, setLoading] = useState(false);
  const [qrCodeUrl, setQrCodeUrl] = useState('');
  useImperativeHandle(ref, () => {
    return {
      showQrCode: (url) => {
        setQrCodeUrl(url);
        setVisible(true);
      },
    };
  });
  const downloadQrCode = async () => {
    if (!qrCodeUrl) return;
    setLoading(true);
    try {
      const response = await request(qrCodeUrl, {
        method: 'GET',
        responseType: 'blob',
      });
      const url = window.URL.createObjectURL(new Blob([response]));
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute('download', `推广二维码_${userInfo.nickName || userInfo.code || '未命名'}.png`);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      window.URL.revokeObjectURL(url);
    } catch (error) {
      console.error('下载二维码失败:', error);
    } finally {
      setLoading(false);
    }
  };
  return (
    <Modal
      getContainer={false}
      width={500}
      centered
      destroyOnHidden
      title="推广二维码"
      open={visible}
      onCancel={onCancel}
      footer={
        <Space>
          <Button onClick={onCancel}>关闭</Button>
          {qrCodeUrl && <Button
            type="primary"
            icon={<DownloadOutlined />}
            loading={loading}
            onClick={downloadQrCode}
          >
            下载二维码
          </Button>}
        </Space>
      }
    >
      <div style={{ textAlign: 'center', padding: '20px 0' }}>
        <Title level={4}>{userInfo.nickName || userInfo.code} 的推广二维码</Title>
        {qrCodeUrl ? (
          <div style={{ marginTop: 20 }}>
            <img
              src={qrCodeUrl}
              alt="推广二维码"
              style={{ maxWidth: '100%', height: 'auto', border: '1px solid #d9d9d9', borderRadius: 4 }}
            />
          </div>
        ) : (
          <Paragraph type="warning">暂无推广二维码</Paragraph>
        )}
      </div>
    </Modal>
  );
};
export default forwardRef(QrCodeModal);
management/src/pages/company/detail/components/userManagement.jsx
New file
@@ -0,0 +1,258 @@
import {
  buildProTableDataSource,
  sendRequest,
  showConfirm,
  showDelConfirm,
} from '@/utils/antdUtils';
import { importFile } from '@/utils/utils';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Space, Tag, Upload, message } from 'antd';
import { useRef, useState } from 'react';
import UserModal from './userModal';
import QrCodeModal from './qrCodeModal';
import { getCompanyUserList, addUser, editUser, deleteUser } from '../service';
const UserManagement = ({ companyId }) => {
  const actionRef = useRef();
  const userModalRef = useRef();
  const qrCodeModalRef = useRef();
  const [userModalVisible, setUserModalVisible] = useState(false);
  const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false);
  const [currentUser, setCurrentUser] = useState({});
  const [importLoading, setImportLoading] = useState(false);
  const [fileList, setFileList] = useState([]);
  // 处理文件导入
  const handleImport = async (info) => {
    const { file } = info;
    try {
      const response = await importFile(file, '/t-company/import', {
        companyId
      });
      if (response && response.code && response.code !== 200) {
        throw response;
      }
      message.success('导入成功');
      actionRef.current?.reload();
      setFileList([]);
    } catch (error) {
      let errorMsg = '导入失败,请重试';
      if (error && (error.msg || error.message)) {
        errorMsg = error.msg || error.message;
      }
      message.error(errorMsg);
    } finally {
      setImportLoading(false);
    }
  };
  // 上传前校验
  const beforeUpload = (file) => {
    const fileExtension = file.name.split('.').pop()?.toLowerCase();
    const allowedExtensions = ['xlsx', 'xls'];
    if (!allowedExtensions.includes(fileExtension)) {
      message.error('请选择 Excel 文件(.xlsx 或 .xls)');
      return false;
    }
    const fileSizeMB = file.size / 1024 / 1024;
    if (fileSizeMB > 10) {
      message.error('文件大小不能超过 10MB');
      return false;
    }
    setFileList([file]);
    return false; // 阻止自动上传
  };
  const columns = [
    {
      title: '序号',
      dataIndex: 'index',
      align: 'center',
      hideInSearch: true,
      render: (text, record, index) => {
        return index + 1;
      },
    },
    {
      title: '人员姓名',
      dataIndex: 'nickName',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '推广ID',
      dataIndex: 'code',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '手机号',
      dataIndex: 'phonenumber',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '创建时间',
      dataIndex: 'createTime',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '角色',
      dataIndex: 'roleName',
      align: 'center',
      hideInSearch: true,
      render: (text) => {
        return <Tag color="blue">{text || '默认角色'}</Tag>;
      },
    },
    {
      title: '操作',
      hideInSearch: true,
      render: (text, record) => {
        return (
          <Space>
            <Button
              size="small"
              type="link"
              onClick={() => {
                setCurrentUser(record);
                userModalRef.current.setViewMode('view');
                userModalRef.current.refreshData(record);
                setUserModalVisible(true);
              }}
            >
              详情
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                setCurrentUser(record);
                userModalRef.current.setViewMode('edit');
                userModalRef.current.refreshData(record);
                setUserModalVisible(true);
              }}
            >
              编辑
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                setCurrentUser(record);
                setQrCodeModalVisible(true);
              }}
            >
              推广二维码
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                showDelConfirm(async () => {
                  let status = await sendRequest(deleteUser, record.userId);
                  if (status) {
                    actionRef.current.reset();
                  }
                });
              }}
            >
              删除
            </Button>
          </Space>
        );
      },
    },
  ];
  return (
    <div>
      <ProTable
        rowKey="userId"
        columnsState={{
          persistenceKey: 'pro-table-company-user',
          persistenceType: 'localStorage',
        }}
        tableAlertRender={false}
        search={false}
        actionRef={actionRef}
        columns={columns}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        rowSelection={false}
        request={(params) => {
          return buildProTableDataSource(getCompanyUserList, { ...params, companyId });
        }}
        toolBarRender={(action, selectRows) => [
          <>
            <Upload
              accept=".xlsx,.xls"
              beforeUpload={beforeUpload}
              fileList={fileList}
              onChange={handleImport}
              showUploadList={false}
            >
              <Button
                type='primary'
                loading={importLoading}
              >
                导入
              </Button>
            </Upload>
            <Button
              type='primary'
              onClick={() => {
                setCurrentUser({});
                userModalRef.current.setViewMode('add');
                userModalRef.current.refreshData({});
                setUserModalVisible(true);
              }}
            >
              新增
            </Button>
          </>,
        ]}
      />
      {/* 新增/编辑/详情人员弹窗 */}
      <UserModal
        ref={userModalRef}
        companyId={companyId}
        visible={userModalVisible}
        onCancel={() => setUserModalVisible(false)}
        onSave={async (fields) => {
          const success = await sendRequest(addUser, { ...fields, companyId });
          if (success) {
            setUserModalVisible(false);
            actionRef.current.reload();
          }
        }}
        onUpdate={async (fields) => {
          const success = await sendRequest(editUser, { ...fields, companyId });
          if (success) {
            setUserModalVisible(false);
            actionRef.current.reload();
          }
        }}
      />
      {/* 推广二维码弹窗 */}
      <QrCodeModal
        ref={qrCodeModalRef}
        visible={qrCodeModalVisible}
        onCancel={() => setQrCodeModalVisible(false)}
        userInfo={currentUser}
      />
    </div>
  );
};
export default UserManagement;
management/src/pages/company/detail/components/userModal.jsx
New file
@@ -0,0 +1,138 @@
import {
  ProForm,
  ProFormSelect,
  ProFormSwitch,
  ProFormText,
  ProFormTextArea,
} from '@ant-design/pro-components';
import { Divider, Form, Modal } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
const UserModal = ({ visible, onSave, onUpdate, onCancel, companyId }, ref) => {
  const [form] = Form.useForm();
  const [editData, setEditData] = useState({});
  const [viewMode, setViewMode] = useState('add'); // add, edit, view
  const formItemLayout = {
    labelCol: { span: 5 },
  };
  useImperativeHandle(ref, () => {
    return {
      refreshData: (data) => {
        form.resetFields();
        form.setFieldsValue(data);
        setEditData(data);
      },
      setViewMode: (mode) => {
        setViewMode(mode);
      },
    };
  });
  const okHandle = () => {
    if (viewMode === 'view') {
      onCancel();
      return;
    }
    form.validateFields().then((values) => {
      if (editData.userId) {
        onUpdate({ ...values, userId: editData.userId });
      } else {
        onSave(values);
      }
    });
  };
  const modalTitle = {
    add: '新增管理人员',
    edit: '编辑管理人员',
    view: '查看详情'
  }[viewMode];
  const isViewMode = viewMode === 'view';
  return (
    <Modal
      getContainer={false}
      width="40%"
      centered
      destroyOnHidden
      title={modalTitle}
      open={visible}
      onCancel={() => {
        onCancel(false);
        form.resetFields();
      }}
      onOk={okHandle}
      okButtonProps={{ disabled: isViewMode }}
    >
      <ProForm submitter={false} layout="horizontal" {...formItemLayout} form={form}>
        <ProFormText
          name="nickName"
          label="人员姓名"
          placeholder="请输入人员姓名"
          width={240}
          rules={[{ required: true, message: '请输入人员姓名' }]}
          disabled={isViewMode}
        />
        <ProFormText
          name="code"
          label="推广号"
          placeholder="请输入推广号"
          width={240}
          disabled={isViewMode}
        />
        <ProFormText
          name="phonenumber"
          label="联系电话"
          width={240}
          placeholder="请输入联系电话"
          disabled={isViewMode}
          rules={[
            { required: true, message: '请输入联系电话' },
            {
              validator: (_, value) => {
                if (!value) return Promise.resolve();
                const input = String(value).trim();
                const mobilePattern = /^1[3-9]\d{9}$/;
                const landlinePattern = /^(0\d{2,3}-?)?\d{7,8}(-\d{1,4})?$/;
                return mobilePattern.test(input) || landlinePattern.test(input)
                  ? Promise.resolve()
                  : Promise.reject(new Error('请输入正确的联系电话(手机或座机)'));
              },
            },
          ]}
        />
        <Divider />
        <ProFormTextArea
          name="remark"
          label="备注"
          placeholder="请输入备注"
          width={240}
          disabled={isViewMode}
        />
        <Divider />
        <ProFormText
          name="userName"
          label="登录账号"
          placeholder="请输入登录账号"
          width={240}
          disabled={isViewMode}
          rules={[{ required: true, message: '请输入登录账号' }]}
          addonAfter={editData.userId && <span style={{ color: 'red' }}>账号不可重复</span>}
        />
        <ProFormText.Password
          name="password"
          label="初始密码"
          placeholder="请输入初始密码"
          width={240}
          disabled={isViewMode}
          rules={[{ required: true, message: '请输入初始密码' }]}
        />
      </ProForm>
    </Modal>
  );
};
export default forwardRef(UserModal);
management/src/pages/company/detail/index.jsx
New file
@@ -0,0 +1,104 @@
import { history, useSearchParams } from '@umijs/max';
import { Card, Descriptions, Tabs } from 'antd';
import { PageContainer } from '@ant-design/pro-components';
import { useEffect, useState } from 'react';
import { getCompanyDetail } from './service';
import UserManagement from './components/userManagement';
import PromotionRecord from './components/promotionRecord';
import ProfitDetail from './components/profitDetail';
import moment from 'moment';
const CompanyDetail = () => {
  const [companyInfo, setCompanyInfo] = useState({});
  const [searchParams] = useSearchParams();
  const [loading, setLoading] = useState(true);
  const id = searchParams.get('id');
  useEffect(() => {
    if (id) {
      fetchCompanyDetail();
    }
  }, [id]);
  const fetchCompanyDetail = async () => {
    try {
      setLoading(true);
      const res = await getCompanyDetail(id);
      if (res?.code === 200) {
        setCompanyInfo(res.data);
      }
    } catch (error) {
      console.error('获取公司详情失败:', error);
    } finally {
      setLoading(false);
    }
  };
  const handleBack = () => {
    history.push('/company/list');
  };
  return (
    <PageContainer
      loading={loading}
      onBack={handleBack}
    >
      <div style={{ padding: '0 24px' }}>
        <Card title="公司基本信息" style={{ marginBottom: 24 }}>
          <Descriptions column={3} bordered>
            <Descriptions.Item label="公司名称">
              {companyInfo.name || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="负责人">
              {companyInfo.chargePerson || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="联系电话">
              {companyInfo.chargePersonPhone || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="商户号">
              {companyInfo.merchantNumber || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="商户OpenId">
              {companyInfo.merchantOpenid || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="推广号">
              {companyInfo.code || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="所在地址" span={3}>
              {companyInfo.address || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="备注" span={3}>
              {companyInfo.remark || '-'}
            </Descriptions.Item>
          </Descriptions>
        </Card>
        <Card>
          <Tabs
            defaultActiveKey="1"
            destroyOnHidden
            items={[
              {
                key: '1',
                label: '管理人员列表',
                children: <UserManagement companyId={id} />,
              },
              {
                key: '2',
                label: '推广记录',
                children: <PromotionRecord companyId={id} />,
              },
              {
                key: '3',
                label: '收益明细',
                children: <ProfitDetail companyId={id} />,
              },
            ]}
          />
        </Card>
      </div>
    </PageContainer>
  );
};
export default CompanyDetail;
management/src/pages/company/detail/service.js
New file
@@ -0,0 +1,55 @@
import { request } from '@umijs/max';
// 获取公司详情
export const getCompanyDetail = async (id) => {
  return request(`/t-company/detail/${id}`, {
    method: 'POST',
  });
};
// 获取管理人员列表
export const getCompanyUserList = async (data) => {
  return request('/t-company/getCompanyUserList', {
    method: 'POST',
    data,
  });
};
// 获取推广记录列表
export const getPromotionPageList = async (data) => {
  return request('/t-company/promotionPageList', {
    method: 'POST',
    data,
  });
};
// 获取收益明细列表
export const getProfitPageList = async (data) => {
  return request('/t-company/profitPageList', {
    method: 'POST',
    data,
  });
};
// 新增管理人员
export const addUser = async (data) => {
  return request('/t-company/system/user/add', {
    method: 'POST',
    data,
  });
};
// 编辑管理人员
export const editUser = async (data) => {
  return request('/t-company/system/user/edit', {
    method: 'POST',
    data,
  });
};
// 删除管理人员
export const deleteUser = async (id) => {
  return request(`/t-company/system/user/deleteById/${id}`, {
    method: 'DELETE',
  });
};
management/src/pages/company/list/components/addAndEdit.jsx
New file
@@ -0,0 +1,144 @@
import {
  ProForm,
  ProFormSelect,
  ProFormSwitch,
  ProFormText,
  ProFormTextArea,
} from '@ant-design/pro-components';
import { Divider, Form, Modal } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
const AddEditDetail = ({ visible, onSave, onUpdate, onCancel }, ref) => {
  const [form] = Form.useForm();
  const [editData, setEditData] = useState({});
  const formItemLayout = {
    labelCol: { span: 5 },
  };
  useImperativeHandle(ref, () => {
    return {
      refreshData: (data) => {
        form.resetFields();
        form.setFieldsValue(data);
        setEditData(data);
      },
      clean: () => {
        form.resetFields();
      },
    };
  });
  const okHandle = () => {
    form.validateFields().then((values) => {
      if (values.id) {
        onUpdate(values);
      } else {
        onSave(values);
      }
    });
  };
  return (
    <Modal
      getContainer={false}
      width="50%"
      centered
      destroyOnHidden
      title={editData.id ? '编辑公司' : '添加公司'}
      open={visible}
      onCancel={() => {
        onCancel(false);
        form.resetFields();
      }}
      onOk={okHandle}
    >
      <ProForm submitter={false} layout="horizontal" {...formItemLayout} form={form}>
        <ProFormText
          name="name"
          label="公司名称"
          placeholder="请输入公司名称"
          width={240}
          rules={[{ required: true, message: '请输入公司名称' }]}
        />
        <ProFormText
          name="code"
          label="推广号"
          placeholder="请输入推广号"
          width={240}
          rules={[{ required: true, message: '请输入推广号' }]}
        />
        <ProFormText
          name="chargePerson"
          label="负责人"
          placeholder="请输入负责人"
          width={240}
          rules={[{ required: true, message: '请输入负责人' }]}
        />
        <ProFormText
          name="chargePersonPhone"
          label="联系电话"
          width={240}
          placeholder="请输入联系电话"
          rules={[
            { required: true, message: '请输入联系电话' },
            {
              validator: (_, value) => {
                if (!value) return Promise.resolve();
                const input = String(value).trim();
                const mobilePattern = /^1[3-9]\d{9}$/;
                const landlinePattern = /^(0\d{2,3}-?)?\d{7,8}(-\d{1,4})?$/;
                return mobilePattern.test(input) || landlinePattern.test(input)
                  ? Promise.resolve()
                  : Promise.reject(new Error('请输入正确的联系电话(手机或座机)'));
              },
            },
          ]}
        />
        <ProFormText
          name="merchantNumber"
          label="商户号"
          placeholder="请输入商户号"
          width={240}
          rules={[{ required: true, message: '请输入商户号' }]}
        />
        <ProFormText
          name="merchantOpenid"
          label="商户openId"
          placeholder="请输入商户openId"
          width={240}
          rules={[{ required: true, message: '请输入商户openId' }]}
        />
        <Divider />
        <ProFormTextArea
          name="address"
          label="经销商所在地址"
          placeholder="请输入经销商所在地址"
          width={240}
        />
        <ProFormTextArea
          name="remark"
          label="备注"
          placeholder="请输入备注"
          width={240}
        />
        <Divider />
        <ProFormText
          name="account"
          label="登录账号"
          placeholder="请输入登录账号"
          width={240}
          rules={[{ required: true, message: '请输入登录账号' }]}
        />
        <ProFormText.Password
          name="password"
          label="初始密码"
          placeholder="请输入初始密码"
          width={240}
          rules={[{ required: true, message: '请输入初始密码' }]}
        />
      </ProForm>
    </Modal>
  );
};
export default forwardRef(AddEditDetail);
management/src/pages/company/list/index.jsx
New file
@@ -0,0 +1,169 @@
import {
  buildProTableDataSource,
  sendRequest,
  showConfirm,
  showDelConfirm,
} from '@/utils/antdUtils';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Badge, Button, Space } from 'antd';
import { useRef, useState } from 'react';
import AddAndEdit from './components/addAndEdit';
import { history } from '@umijs/max';
import { add, del, edit, getList } from './service';
import moment from 'moment';
const Company = () => {
  const actionRef = useRef();
  const addViewRef = useRef();
  const [modalVisible, handleModalVisible] = useState(false);
  const columns = [
    {
      title: '序号',
      dataIndex: 'index',
      align: 'center',
      hideInSearch: true,
      render: (text, record, index) => {
        return index + 1;
      },
    },
    {
      title: '公司名称',
      dataIndex: 'name',
      align: 'center',
    },
    {
      title: '负责人',
      dataIndex: 'chargePerson',
      align: 'center',
    },
    {
      title: '负责人联系方式',
      dataIndex: 'chargePersonPhone',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '商户号',
      dataIndex: 'merchantNumber',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '创建时间',
      dataIndex: 'createTime',
      align: 'center',
      hideInSearch: true,
      render: (text, record) => {
        return record.createTime ? moment(record.createTime).format('YYYY-MM-DD HH:mm') : '-'
      }
    },
    {
      title: '操作',
      hideInSearch: true,
      render: (text, record) => {
        return (
          <Space>
            <Button
              size="small"
              type="link"
              onClick={() => {
                history.push(`/company/detail?id=${record.id}`);
              }}
            >
              详情
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                addViewRef.current.refreshData(record);
                handleModalVisible(true);
              }}
            >
              编辑
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                showDelConfirm(async () => {
                  let status = await sendRequest(del, record.id);
                  if (status) {
                    actionRef.current.reset();
                  }
                });
              }}
            >
              删除
            </Button>
          </Space>
        );
      },
    },
  ];
  return (
    <PageContainer>
      <ProTable
        rowKey="id"
        columnsState={{
          persistenceKey: 'pro-table-company',
          persistenceType: 'localStorage',
        }}
        tableAlertRender={false}
        search={{
          labelWidth: 'auto',
          defaultCollapsed: false,
        }}
        actionRef={actionRef}
        columns={columns}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        rowSelection={false}
        request={(params) => {
          return buildProTableDataSource(getList, params);
        }}
        toolBarRender={(action, selectRows) => [
          <>
            <Button
              type="primary"
              onClick={() => {
                addViewRef.current.refreshData({});
                handleModalVisible(true);
              }}
            >
              新增公司
            </Button>
          </>,
        ]}
      />
      {/* 新增/编辑公司弹窗 */}
      <AddAndEdit
        ref={addViewRef}
        visible={modalVisible}
        onCancel={() => handleModalVisible(false)}
        onSave={async (fields) => {
          const success = await sendRequest(add, fields);
          if (success) {
            handleModalVisible(false);
            actionRef.current.reload();
          }
        }}
        onUpdate={async (fields) => {
          const success = await sendRequest(edit, fields);
          if (success) {
            handleModalVisible(false);
            actionRef.current.reload();
          }
        }}
      />
    </PageContainer>
  );
};
export default Company;
management/src/pages/company/list/service.js
New file
@@ -0,0 +1,39 @@
import { request } from '@umijs/max';
// 列表
export const getList = async (data) => {
  return request('/t-company/pageList', {
    method: 'POST',
    data,
  });
};
// 新增
export const add = async (data) => {
  return request('/t-company/add', {
    method: 'POST',
    data,
  });
};
// 修改
export const edit = async (data) => {
  return request('/t-company/edit', {
    method: 'POST',
    data,
  });
};
// 删除
export const del = async (id) => {
  return request(`/t-company/delete/${id}`, {
    method: 'DELETE',
  });
};
// 获取详情
export const getDetail = async (id) => {
  return request(`/t-company/detail/${id}`, {
    method: 'GET',
  });
};
management/src/pages/order/bigGiftPackageOrder/components/OrderDetailModal.jsx
@@ -120,14 +120,6 @@
        label: '用户手机号',
        children: orderDetail.phone || '-',
      },
      {
        label: '当前状态',
        children: statusMap[status] || '-',
      },
      {
        label: '订单金额',
        children: `¥${orderDetail.orderPrice || 0}`,
      },
    ];
    // 根据不同状态和支付类型添加特定字段
@@ -135,10 +127,19 @@
    if (status === 1) {
      // 待付款
      specificItems = [];
      specificItems = [
        {
          label: '当前状态',
          children: statusMap[status] || '-',
        },
        {
          label: '订单金额',
          children: `¥${orderDetail.orderPrice || 0}`,
        }
      ];
    } else if (status === 2 && paymentType === 1) {
      // 已付定金
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '已付定金',
          children: `¥${orderDetail.deposit || 0}`,
@@ -151,10 +152,10 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    } else if (status === 3 && paymentType === 1) {
      // 已支付定金+尾款
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '已付定金',
          children: `¥${orderDetail.deposit || 0}`,
@@ -175,10 +176,10 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    } else if (status === 3 && paymentType === 2) {
      // 已支付全款
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '支付金额',
          children: `¥${orderDetail.paymentPrice || 0}`,
@@ -195,13 +196,13 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    }
    return (
      <div>
        <Descriptions column={2} bordered loading={loading}>
          {[...baseItems, ...specificItems].map((item, index) => (
        <Descriptions column={2} bordered>
          {baseItems.map((item, index) => (
            <Descriptions.Item key={index} label={item.label}>
              {item.children}
            </Descriptions.Item>
@@ -210,11 +211,18 @@
        {/* 商品信息 */}
        <div style={{ marginTop: 24 }}>
          <Text strong>商品信息</Text>
          <div style={{ marginTop: 8 }}>
            {renderProductTable()}
          </div>
        </div>
        <Descriptions column={2} bordered style={{ marginTop: 24 }}>
          {specificItems.map((item, index) => (
            <Descriptions.Item key={index} label={item.label}>
              {item.children}
            </Descriptions.Item>
          ))}
        </Descriptions>
        {/* 营销明细(仅已支付定金+尾款时显示) */}
        {status === 3 && paymentType === 1 && (
@@ -244,7 +252,7 @@
      onCancel={onCancel}
      footer={null}
      width={800}
      destroyOnClose
      destroyOnHidden
    >
      {renderDetailContent()}
    </Modal>
management/src/pages/order/bigGiftPackageOrder/components/OrderTransferModal.jsx
@@ -1,5 +1,6 @@
import { Modal, Table, Input, Button, Space, message } from 'antd';
import { Modal, Table, Input, Button, Space, message, Tag, Avatar } from 'antd';
import { useState } from 'react';
import { getTransferUserList, confirmTransfer } from '../service';
const { Search } = Input;
@@ -82,20 +83,26 @@
  const transferColumns = [
    {
      title: '头像',
      dataIndex: 'avatar',
      dataIndex: 'profilePhoto',
      width: 80,
      render: (text) => (
        <div style={{ width: 40, height: 40, background: '#f5f5f5' }} />
      render: (text, record) => (
        <Avatar
          src={text}
          size="small"
          style={{ backgroundColor: '#f5f5f5', color: '#000' }}
        >
          {text ? '' : record.name?.substring(-1)}
        </Avatar>
      ),
    },
    {
      title: '用户名',
      dataIndex: 'userName',
      width: 120,
      dataIndex: 'name',
      width: 100,
    },
    {
      title: '推广号',
      dataIndex: 'promotionCode',
      dataIndex: 'code',
      width: 120,
    },
    {
@@ -110,23 +117,36 @@
      render: (text) => (text === 1 ? '男' : text === 2 ? '女' : '-'),
    },
    {
      title: '出生日期',
      dataIndex: 'birthDate',
      width: 120,
    },
    {
      title: '注册时间',
      dataIndex: 'registerTime',
      width: 180,
      dataIndex: 'createTime',
      width: 150,
    },
    {
      title: '状态',
      dataIndex: 'userStatus',
      dataIndex: 'status',
      width: 100,
      render: (text) => (
        <span style={{ color: text === 1 ? '#52c41a' : '#8c8c8c' }}>
          {text === 1 ? '正常' : '禁用'}
        </span>
      render: (text) => {
        const statusMap = {
          1: { text: '正常', color: 'success' },
          2: { text: '冻结', color: 'warning' },
          3: { text: '删除', color: 'error' },
        };
        const status = statusMap[text] || { text: '未知', color: 'default' };
        return <Tag color={status.color}>{status.text}</Tag>;
      },
    },
    {
      title: '操作',
      dataIndex: 'operation',
      width: 100,
      render: (_, record) => (
        <Button
          type="link"
          size="small"
          onClick={() => handleConfirmTransfer(record)}
        >
          确认转让
        </Button>
      ),
    },
  ];
@@ -140,25 +160,61 @@
    setTransferLoading(true);
    try {
      // TODO: 调用查询接口
      // const res = await queryTransferUser(searchValue.trim());
      console.log('查询转让用户:', searchValue.trim());
      const res = await getTransferUserList({
        phoneNumber: searchValue.trim(),
        pageNum: 1,
        pageSize: 10,
      });
      // 模拟数据,实际使用时需要替换为接口调用
      const mockData = [];
      setTransferList(mockData);
      // if (res.code === 200 && res.data) {
      //   setTransferList(res.data);
      // } else {
      //   message.error(res.message || '查询失败');
      // }
      if (res.code === 200 && res.data) {
        setTransferList(res.data.records || []);
      } else {
        message.error(res.message || '查询失败');
      }
    } catch (error) {
      console.error('查询转让用户失败:', error);
      message.error('查询失败');
    } finally {
      setTransferLoading(false);
    }
  };
  // 处理确认转让
  const handleConfirmTransfer = async (record) => {
    Modal.confirm({
      title: '确认转让',
      content: `确定要将订单 ${orderData?.code} 转让给用户 ${record.name} 吗?`,
      okText: '确认',
      cancelText: '取消',
      okButtonProps: {
        loading: transferLoading,
      },
      onOk: async () => {
        try {
          setTransferLoading(true);
          const res = await confirmTransfer({
            appUserId: record.id,
            orderId: orderData?.id,
          });
          if (res.code === 200) {
            message.success('转让成功');
            onCancel();
            // 刷新父页面列表
            if (window.refreshParentList) {
              window.refreshParentList();
            }
          } else {
            message.error(res.message || '转让失败');
          }
        } catch (error) {
          console.error('确认转让失败:', error);
          message.error('转让失败');
        } finally {
          setTransferLoading(false);
        }
      },
    });
  };
  // 订单数据源
@@ -171,7 +227,7 @@
      onCancel={onCancel}
      footer={null}
      width={1200}
      destroyOnClose
      destroyOnHidden
    >
      {/* 上部分:订单信息 */}
      <div style={{ marginBottom: 24 }}>
@@ -186,7 +242,7 @@
      </div>
      {/* 中部分:查询字段 */}
      <div style={{ marginBottom: 24, textAlign: 'center' }}>
      <div style={{ marginBottom: 24 }}>
        <Space size="middle">
          <Search
            placeholder="请输入推广号/电话号码"
management/src/pages/order/bigGiftPackageOrder/edit.jsx
@@ -127,14 +127,6 @@
        label: '用户手机号',
        children: orderDetail.phone || '-',
      },
      {
        label: '当前状态',
        children: statusMap[status] || '-',
      },
      {
        label: '订单金额',
        children: `¥${orderDetail.orderPrice || 0}`,
      },
    ];
    // 根据不同状态和支付类型添加特定字段
@@ -142,10 +134,19 @@
    if (status === 1) {
      // 待付款
      specificItems = [];
      specificItems = [
        {
          label: '当前状态',
          children: statusMap[status] || '-',
        },
        {
          label: '订单金额',
          children: `¥${orderDetail.orderPrice || 0}`,
        }
      ];
    } else if (status === 2 && paymentType === 1) {
      // 已付定金
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '已付定金',
          children: `¥${orderDetail.deposit || 0}`,
@@ -158,10 +159,10 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    } else if (status === 3 && paymentType === 1) {
      // 已支付定金+尾款
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '已付定金',
          children: `¥${orderDetail.deposit || 0}`,
@@ -182,10 +183,10 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    } else if (status === 3 && paymentType === 2) {
      // 已支付全款
      specificItems = [
      specificItems = [...specificItems, ...[
        {
          label: '支付金额',
          children: `¥${orderDetail.paymentPrice || 0}`,
@@ -202,13 +203,13 @@
          label: '备注',
          children: orderDetail.remark || '-',
        },
      ];
      ]];
    }
    return (
      <div>
        <Descriptions column={2} bordered>
          {[...baseItems, ...specificItems].map((item, index) => (
          {baseItems.map((item, index) => (
            <Descriptions.Item key={index} label={item.label}>
              {item.children}
            </Descriptions.Item>
@@ -217,12 +218,19 @@
        {/* 商品信息 */}
        <div style={{ marginTop: 24 }}>
          <Text strong>商品信息</Text>
          <div style={{ marginTop: 8 }}>
            {renderProductTable()}
          </div>
        </div>
        <Descriptions column={2} bordered style={{ marginTop: 24 }}>
          {specificItems.map((item, index) => (
            <Descriptions.Item key={index} label={item.label}>
              {item.children}
            </Descriptions.Item>
          ))}
        </Descriptions>
        {/* 营销明细(仅已支付定金+尾款时显示) */}
        {status === 3 && paymentType === 1 && (
          <div style={{ marginTop: 24 }}>
management/src/pages/order/bigGiftPackageOrder/index.jsx
@@ -18,6 +18,11 @@
  const [transferVisible, setTransferVisible] = useState(false);
  const [transferOrderData, setTransferOrderData] = useState(null);
  // 暴露刷新函数给子组件使用
  window.refreshParentList = () => {
    actionRef.current?.reload();
  };
  // 处理详情弹窗
  const handleDetail = (id) => {
    setCurrentOrderId(id);
@@ -57,22 +62,13 @@
    }
  };
  // 订单状态映射
  const statusMap = {
    1: '待付款',
    2: '已付订金',
    3: '完成支付',
    4: '确认收货',
    5: '已退款',
  };
  // 支付类型映射
  const paymentTypeMap = {
    1: '订金+尾款',
    2: '全款',
  };
  const columns = [
    {
      title: '序号',
@@ -169,19 +165,29 @@
      order: 1,
      width: 120,
      valueEnum: {
        1: { text: '待付款', status: 'Default' },
        2: { text: '已付订金', status: 'Processing' },
        3: { text: '完成支付', status: 'Success' },
        4: { text: '确认收货', status: 'Success' },
        5: { text: '已退款', status: 'Error' },
        1: { text: '待付款' },
        2: { text: '已付订金' },
        3: { text: '完成支付' },
        4: { text: '确认收货' },
        5: { text: '已退款' },
      },
      render: (text) => statusMap[text] || '-',
      render: (text, record) => {
        const statusMap = {
          1: { text: '待付款', color: '#faad14' },
          2: { text: '已付订金', color: '' },
          3: { text: '完成支付', color: '#52c41a' },
          4: { text: '确认收货', color: '#1890ff' },
          5: { text: '已退款', color: '#722ed1' },
        };
        const status = statusMap[record.status] || { text: '未知', color: '#d9d9d9' };
        return <span style={{ color: status.color }}>{status.text}</span>;
      },
    },
    {
      title: '操作',
      dataIndex: 'action',
      valueType: 'option',
      width: 200,
      width: 300,
      fixed: 'right',
      render: (_, record) => {
        // 如果订单状态为已退款(5),不显示任何操作按钮
@@ -241,7 +247,7 @@
        }}
        toolBarRender={() => [
          <Button
            key="export"
            type="primary"
            onClick={handleExport}
          >
            导出Excel
management/src/pages/order/bigGiftPackageOrder/service.js
@@ -29,4 +29,20 @@
    method: 'POST',
    data: params,
  });
}
// 查询转让用户列表
export async function getTransferUserList(params) {
  return request(`/t-big-gift-package-order/transferPageUserList`, {
    method: 'POST',
    data: params,
  });
}
// 确认转让订单
export async function confirmTransfer(params) {
  return request(`/t-big-gift-package-order/transfer`, {
    method: 'POST',
    data: params,
  });
}
management/src/pages/order/orderTransfer/components/TransferDetailModal.jsx
New file
@@ -0,0 +1,224 @@
import { Modal, Table, Typography, Avatar, Descriptions } from 'antd';
import { useState, useEffect } from 'react';
import { getTransferDetail } from '../service';
const { Text } = Typography;
const TransferDetailModal = ({ visible, onCancel, transferId }) => {
  const [transferDetail, setTransferDetail] = useState(null);
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    if (visible && transferId) {
      fetchTransferDetail();
    }
  }, [visible, transferId]);
  const fetchTransferDetail = async () => {
    setLoading(true);
    try {
      const res = await getTransferDetail(transferId);
      if (res.code === 200 && res.data) {
        setTransferDetail(res.data);
      }
    } catch (error) {
      console.error('获取转让详情失败:', error);
    } finally {
      setLoading(false);
    }
  };
  // 原订单信息表格列定义
  const orderColumns = [
    {
      title: '订单编号',
      dataIndex: 'code',
      width: 180,
    },
    {
      title: '下单时间',
      dataIndex: 'createTime',
      width: 180,
    },
    {
      title: '下单用户',
      dataIndex: 'oldUser',
      width: 120,
    },
    {
      title: '订单价格',
      dataIndex: 'orderPrice',
      width: 120,
      render: (text) => `¥${text || 0}`,
    },
    {
      title: '商品名称',
      dataIndex: 'productName',
      width: 200,
      ellipsis: true,
    },
    {
      title: '支付方式',
      dataIndex: 'payType',
      width: 120,
      render: () => '微信支付',
    },
    {
      title: '定金',
      dataIndex: 'deposit',
      width: 100,
      render: (text) => `¥${text || 0}`,
    },
    {
      title: '尾款',
      dataIndex: 'finalPayment',
      width: 100,
      render: (text) => `¥${text || 0}`,
    },
    {
      title: '订单状态',
      dataIndex: 'status',
      width: 120,
      render: (text) => {
        const statusMap = {
          1: '待付款',
          2: '已付订金',
          3: '完成支付',
          4: '确认收货',
          5: '已退款',
        };
        return statusMap[text] || '-';
      },
    },
  ];
  // 转让目标用户表格列定义
  const userColumns = [
    {
      title: '头像',
      dataIndex: 'newUserAvatar',
      width: 80,
      render: (text, record) => (
        <Avatar
          src={text}
          size="small"
          style={{ backgroundColor: '#f5f5f5', color: '#000' }}
        >
          {text ? '' : record.name?.substring(-1)}
        </Avatar>
      ),
    },
    {
      title: '用户名',
      dataIndex: 'newUser',
      width: 120,
    },
    {
      title: '推广号',
      dataIndex: 'newUserCode',
      width: 120,
    },
    {
      title: '联系电话',
      dataIndex: 'newUserPhone',
      width: 130,
    },
    {
      title: '性别',
      dataIndex: 'newUserGender',
      width: 80,
      render: (text) => (text === 1 ? '男' : text === 2 ? '女' : '-'),
    },
    {
      title: '出生日期',
      dataIndex: 'newUserBirthday',
      width: 120,
    },
    {
      title: '注册时间',
      dataIndex: 'newUserRegisterTime',
      width: 150,
    },
    {
      title: '状态',
      dataIndex: 'newUserStatus',
      width: 100,
      render: (text) => {
        const statusMap = {
          1: { text: '使用中', color: 'success' },
          2: { text: '冻结', color: 'warning' },
          3: { text: '删除', color: 'error' },
        };
        const status = statusMap[text] || { text: '未知', color: 'default' };
        return <Text style={{ color: status.color }}>{status.text}</Text>;
      },
    },
  ];
  // 订单数据源
  const orderDataSource = transferDetail ? [{
    key: transferDetail.id,
    ...transferDetail,
  }] : [];
  // 用户数据源
  const userDataSource = transferDetail ? [{
    key: transferDetail.id,
    ...transferDetail,
  }] : [];
  return (
    <Modal
      title="订单转让详情"
      open={visible}
      onCancel={onCancel}
      footer={null}
      width={1200}
      destroyOnClose
    >
      {loading ? (
        <div style={{ textAlign: 'center', padding: '50px' }}>加载中...</div>
      ) : transferDetail ? (
        <>
          {/* 第一部分:原订单信息 */}
          <div style={{ marginBottom: 24 }}>
            <h4>原订单信息</h4>
            <Table
              columns={orderColumns}
              dataSource={orderDataSource}
              pagination={false}
              size="small"
              scroll={{ x: 1400 }}
            />
          </div>
          {/* 第二部分:转让目标用户 */}
          <div style={{ marginBottom: 24 }}>
            <h4>转让目标用户</h4>
            <Table
              columns={userColumns}
              dataSource={userDataSource}
              pagination={false}
              size="small"
              scroll={{ x: 1000 }}
            />
          </div>
          {/* 第三部分:操作信息 */}
          <Descriptions title="操作信息" column={2} bordered>
            <Descriptions.Item label="操作时间">
              {transferDetail.operateTime || '-'}
            </Descriptions.Item>
            <Descriptions.Item label="操作人">
              {transferDetail.operateUser || '-'}
            </Descriptions.Item>
          </Descriptions>
        </>
      ) : (
        <div style={{ textAlign: 'center', padding: '50px' }}>暂无数据</div>
      )}
    </Modal>
  );
};
export default TransferDetailModal;
management/src/pages/order/orderTransfer/index.jsx
New file
@@ -0,0 +1,164 @@
import {
  buildProTableDataSource,
  sendRequest,
  showDelConfirm,
} from '@/utils/antdUtils';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Space } from 'antd';
import { useRef, useState } from 'react';
import TransferDetailModal from './components/TransferDetailModal';
import { getTransferList, deleteTransfer, exportTransfer } from './service';
import { exportExcell } from '@/utils/utils';
import { message } from 'antd';
const OrderTransfer = () => {
  const actionRef = useRef();
  const [searchParams, setSearchParams] = useState({});
  const [transferDetailVisible, setTransferDetailVisible] = useState(false);
  const [currentTransferId, setCurrentTransferId] = useState(null);
  const columns = [
    {
      title: '序号',
      dataIndex: 'index',
      align: 'center',
      hideInSearch: true,
      render: (text, record, index) => {
        return index + 1;
      },
    },
    {
      title: '订单编号',
      dataIndex: 'code',
      align: 'center',
    },
    {
      title: '原所属公司',
      dataIndex: 'oldCompany',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '原下单用户',
      dataIndex: 'oldUser',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '原用户工号',
      dataIndex: 'oldUserCode',
      align: 'center',
    },
    {
      title: '原用户手机号',
      dataIndex: 'oldUserPhone',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '转让所属公司',
      dataIndex: 'newCompany',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '转让用户',
      dataIndex: 'newUser',
      align: 'center',
      hideInSearch: true,
    },
    {
      title: '转让用户工号',
      dataIndex: 'newUserCode',
      align: 'center',
    },
    {
      title: '操作',
      hideInSearch: true,
      render: (text, record) => {
        return (
          <Space>
            <Button
              size="small"
              type="link"
              onClick={() => {
                setCurrentTransferId(record.id);
                setTransferDetailVisible(true);
              }}
            >
              详情
            </Button>
            <Button
              size="small"
              type="link"
              onClick={() => {
                showDelConfirm(async () => {
                  let status = await sendRequest(deleteTransfer, record.id);
                  if (status) {
                    actionRef.current.reset();
                  }
                });
              }}
            >
              删除
            </Button>
          </Space>
        );
      },
    },
  ];
  // 导出Excel
  const handleExport = async () => {
    exportExcell('订单转让记录.xlsx', searchParams, '/t-big-gift-package-order/transferExport');
  };
  return (
    <PageContainer>
      <ProTable
        rowKey="id"
        columnsState={{
          persistenceKey: 'pro-table-order-transfer',
          persistenceType: 'localStorage',
        }}
        tableAlertRender={false}
        search={{
          labelWidth: 'auto',
          defaultCollapsed: false,
        }}
        actionRef={actionRef}
        columns={columns}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        rowSelection={false}
        request={(params) => {
          setSearchParams(params);
          return buildProTableDataSource(getTransferList, params);
        }}
        toolBarRender={() => [
          <Button
            type="primary"
            onClick={handleExport}
          >
            导出Excel
          </Button>,
        ]}
      />
      {/* 转让详情弹窗 */}
      <TransferDetailModal
        visible={transferDetailVisible}
        onCancel={() => {
          setTransferDetailVisible(false);
          setCurrentTransferId(null);
        }}
        transferId={currentTransferId}
      />
    </PageContainer>
  );
};
export default OrderTransfer;
management/src/pages/order/orderTransfer/service.js
New file
@@ -0,0 +1,31 @@
import { request } from '@umijs/max';
// 获取订单转让记录列表
export const getTransferList = async (data) => {
  return request('/t-big-gift-package-order/transferList', {
    method: 'POST',
    data,
  });
};
// 删除订单转让记录
export const deleteTransfer = async (id) => {
  return request(`/t-big-gift-package-order/transferDelete/${id}`, {
    method: 'DELETE',
  });
};
// 导出订单转让记录
export const exportTransfer = async (data) => {
  return request('/t-big-gift-package-order/transferExport', {
    method: 'POST',
    data,
  });
};
// 获取订单转让详情
export const getTransferDetail = async (id) => {
  return request(`/t-big-gift-package-order/transferDetail/${id}`, {
    method: 'POST',
  });
};
management/src/pages/order/physicalGoodsOrder/components/OrderDetailModal.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Modal, Descriptions, Image, Typography, message } from 'antd';
import { Modal, Descriptions, Image, Typography, message, Table } from 'antd';
import { getPhysicalGoodsOrderDetail } from '../service';
const OrderDetailModal = ({ orderId, visible, onClose }) => {
@@ -26,6 +26,62 @@
    }
  }, [visible, orderId]);
  // 渲染商品信息表格
  const renderProductTable = () => {
    const columns = [
      {
        title: '商品名称',
        dataIndex: 'physicalGoodsName',
        width: 200,
      },
      {
        title: '封面图',
        dataIndex: 'physicalGoodsCover',
        width: 100,
        render: (text) => (
          <Image
            width={60}
            height={60}
            src={text ? IMAGE_URL + text : null}
            style={{ objectFit: 'cover' }}
            placeholder={<div style={{ width: 60, height: 60, background: '#f5f5f5' }} />}
            fallback="/images/default-image.png"
          />
        ),
      },
      {
        title: '商品价格',
        dataIndex: 'physicalGoodsPrice',
        width: 120,
        render: (text) => `¥${text || 0}`,
      },
      {
        title: '商品简介',
        dataIndex: 'physicalGoodsIntro',
        ellipsis: true,
      },
      {
        title: '销量',
        dataIndex: 'physicalGoodsSales',
        width: 80,
      },
    ];
    const dataSource = [{
      key: orderDetail?.id,
      ...orderDetail,
    }];
    return (
      <Table
        columns={columns}
        dataSource={dataSource}
        pagination={false}
        size="small"
      />
    );
  };
  if (!visible) return null;
  const statusMap = {
@@ -40,28 +96,24 @@
  return (
    <Modal
      title="订单详情"
      width={800}
      width='60%'
      visible={visible}
      onCancel={onClose}
      footer={null}
      destroyOnClose
      destroyOnHidden
    >
      {loading ? (
        <div style={{ textAlign: 'center', padding: '50px' }}>加载中...</div>
      ) : orderDetail ? (
        <>
          <Descriptions title="订单信息" column={2} bordered>
          <Descriptions column={2} bordered>
            {orderDetail.status === 1 ? (
              <>
                <Descriptions.Item label="订单编号">{orderDetail.code}</Descriptions.Item>
                <Descriptions.Item label="下单时间">{orderDetail.createTime}</Descriptions.Item>
                <Descriptions.Item label="订单价格">¥{orderDetail.orderPrice || 0}</Descriptions.Item>
                <Descriptions.Item label="当前状态">
                  <span style={{ color: currentStatus.color }}>{currentStatus.text}</span>
                </Descriptions.Item>
                <Descriptions.Item label="用户昵称">{orderDetail.userName}</Descriptions.Item>
                <Descriptions.Item label="用户手机号">{orderDetail.phone}</Descriptions.Item>
                <Descriptions.Item label="订单金额">¥{orderDetail.orderPrice || 0}</Descriptions.Item>
              </>
            ) : (
              <>
@@ -79,34 +131,34 @@
            )}
          </Descriptions>
          {/* 商品信息 */}
          <div style={{ marginTop: 24 }}>
            <Typography.Title level={4}>商品信息</Typography.Title>
            <Descriptions bordered>
              <Descriptions.Item label="商品名称">
                {orderDetail.physicalGoodsName}
              </Descriptions.Item>
              <Descriptions.Item label="商品价格">
                ¥{orderDetail.physicalGoodsPrice}
              </Descriptions.Item>
              <Descriptions.Item label="商品简介">
                {orderDetail.physicalGoodsIntro}
              </Descriptions.Item>
              <Descriptions.Item label="商品封面">
                <Image
                  width={100}
                  height={100}
                  src={orderDetail.physicalGoodsCover ? IMAGE_URL + orderDetail.physicalGoodsCover : '/images/default-image.png'}
                  style={{ objectFit: 'cover' }}
                  fallback="/images/default-image.png"
                />
              </Descriptions.Item>
            </Descriptions>
            <div style={{ marginTop: 8 }}>
              {renderProductTable()}
            </div>
          </div>
          <Descriptions column={2} bordered style={{ marginTop: 24 }}>
            <>
              <Descriptions.Item label="当前状态">
                <span style={{ color: currentStatus.color }}>{currentStatus.text}</span>
              </Descriptions.Item>
              <Descriptions.Item label="订单金额">¥{orderDetail.orderPrice || 0}</Descriptions.Item>
              {
                orderDetail.status === 2 && <>
                  <Descriptions.Item label="已支付">¥{orderDetail.paymentPrice || 0}</Descriptions.Item>
                  <Descriptions.Item label="支付时间">{orderDetail.paymentTime}</Descriptions.Item>
                  <Descriptions.Item label="支付方式">微信支付</Descriptions.Item>
                </>
              }
            </>
          </Descriptions>
        </>
      ) : (
        <div style={{ textAlign: 'center', padding: '50px' }}>暂无数据</div>
      )}
    </Modal>
      )
      }
    </Modal >
  );
};
management/src/pages/order/physicalGoodsOrder/index.jsx
@@ -1,8 +1,9 @@
import React, { useRef, useState } from 'react';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Space, Popconfirm, message } from 'antd';
import { getPhysicalGoodsOrderList, deletePhysicalGoodsOrder, exportPhysicalGoodsOrder } from './service';
import { getPhysicalGoodsOrderList, deletePhysicalGoodsOrder } from './service';
import OrderDetailModal from './components/OrderDetailModal';
import { exportExcell } from '@/utils/utils'
const PhysicalGoodsOrder = () => {
  const actionRef = useRef();
@@ -23,21 +24,7 @@
  // 导出Excel
  const handleExport = async () => {
    const res = await exportPhysicalGoodsOrder(searchParams);
    if (res.code === 200) {
      const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = '实物商品订单.xlsx';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
      message.success('导出成功');
    } else {
      message.error(res.message || '导出失败');
    }
    exportExcell('实物商品订单.xlsx', searchParams, '/t-physical-goods-order/export')
  };
  // 查看详情
@@ -66,7 +53,6 @@
      title: '订单编号',
      dataIndex: 'code',
      key: 'code',
      copyable: true,
    },
    {
      title: '下单时间',
@@ -96,6 +82,7 @@
      title: '商品名称',
      dataIndex: 'physicalGoodsName',
      key: 'physicalGoodsName',
      width: 200,
    },
    {
      title: '支付方式',
management/src/pages/order/physicalGoodsOrder/service.js
@@ -20,12 +20,4 @@
  return request(`/t-physical-goods-order/delete/${id}`, {
    method: 'DELETE',
  });
}
// 导出实物商品订单列表
export async function exportPhysicalGoodsOrder(params) {
  return request('/t-physical-goods-order/export', {
    method: 'POST',
    data: params,
  });
}
management/src/pages/order/refundAudit/components/RefundDetailModal.jsx
@@ -408,7 +408,7 @@
        </Button>,
      ]}
      width={800}
      destroyOnClose
      destroyOnHidden
    >
      {renderDetailContent()}
    </Modal>
management/src/pages/order/refundAudit/index.jsx
@@ -4,7 +4,7 @@
import { useRef, useState } from 'react';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import RefundDetailModal from './components/RefundDetailModal';
import { deleteRefundApplication, exportRefundApplication, pageRefundList } from './service';
import { deleteRefundApplication, pageRefundList } from './service';
const RefundAudit = () => {
  const actionRef = useRef();
management/src/pages/order/refundAudit/service.js
@@ -15,14 +15,6 @@
  });
}
// 导出退款申请列表
export async function exportRefundApplication(params) {
  return request('/t-refund-application/export', {
    method: 'POST',
    data: params,
  });
}
// 获取退款申请详情
export async function getRefundDetail(id) {
  return request(`/t-refund-application/refundDetail/${id}`, {
management/src/pages/user/list/components/BatchUpdateIntegralModal.jsx
New file
@@ -0,0 +1,84 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Select, message } from 'antd';
import { batchUpdateIntegral } from '../service';
const BatchUpdateIntegralModal = ({ visible, type, data, onCancel, onSuccess }) => {
  const [form] = Form.useForm();
  const [loading, setLoading] = useState(false);
  // 当props变化时更新表单
  useEffect(() => {
    form.resetFields();
  }, [visible, type, data, form]);
  const handleCancel = () => {
    onCancel();
  };
  const handleSubmit = async () => {
    try {
      const values = await form.validateFields();
      setLoading(true);
      let updateParams = {
        userIds: type === 'single' ? [data.userId] : data.userIds,
        integral: Number(values.integral),
      };
      const res = await batchUpdateIntegral(updateParams);
      if (res.code === 200) {
        message.success(`${type === 'single' ? '单个用户' : '批量'}修改积分成功`);
        onSuccess();
      } else {
        message.error(res.message || '修改失败');
      }
    } catch (error) {
      console.error('修改积分失败:', error);
      message.error('修改积分失败');
    } finally {
      setLoading(false);
    }
  };
  return (
    <Modal
      title={type === 'single' ? '修改积分' : '批量修改积分'}
      open={visible}
      onCancel={handleCancel}
      onOk={handleSubmit}
      okButtonProps={{ loading }}
      width={500}
      destroyOnHidden
    >
      {type === 'single' && data.userId && (
        <div style={{ marginBottom: 16, padding: 12, backgroundColor: '#f5f5f5', borderRadius: 4 }}>
          <div style={{ marginBottom: 8 }}>
            <strong>用户:</strong> {data.name}
          </div>
          <div>
            <strong>当前剩余积分:</strong> {data.accumulativeIntegral || 0}
          </div>
        </div>
      )}
      <Form form={form} layout="vertical">
        <Form.Item
          label="积分数量"
          name="integral"
          rules={[
            { required: true, message: '请输入积分数量' },
            { pattern: /^\+?[1-9]\d*$/, message: '积分数量必须为正整数' }
          ]}
        >
          <Input
            type="number"
            placeholder="请输入积分数量"
          />
        </Form.Item>
      </Form>
    </Modal>
  );
};
export default BatchUpdateIntegralModal;
management/src/pages/user/list/index.jsx
New file
@@ -0,0 +1,451 @@
import React, { useRef, useState } from 'react';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Space, Popconfirm, message, Modal, Input, Form, Avatar } from 'antd';
import { getUserList, exportUserExcel, importUser, importGold, clearAllGold, batchUpdateIntegral, changeStatus } from './service';
import { importFile, exportExcell } from '@/utils/utils';
import BatchUpdateIntegralModal from './components/BatchUpdateIntegralModal';
const UserList = () => {
  const actionRef = useRef();
  const [searchParams, setSearchParams] = useState({});
  const [batchUpdateVisible, setBatchUpdateVisible] = useState(false);
  const [selectedUsers, setSelectedUsers] = useState([]);
  const [integralModalVisible, setIntegralModalVisible] = useState(false);
  const [integralModalType, setIntegralModalType] = useState('single'); // single, batch
  const [integralModalData, setIntegralModalData] = useState({});
  const fileInputRef = useRef(null);
  const goldFileInputRef = useRef(null);
  // 处理批量修改积分
  const handleBatchUpdate = () => {
    if (selectedUsers.length === 0) {
      message.warning('请先选择要修改的用户');
      return;
    }
    setIntegralModalType('batch');
    setIntegralModalData({
      userIds: selectedUsers,
      accumulativeIntegral: record.accumulativeIntegral,
    });
    setIntegralModalVisible(true);
  };
  // 处理单个用户修改积分
  const handleSingleUpdate = (record) => {
    setIntegralModalType('single');
    setIntegralModalData({
      userId: record.id,
      accumulativeIntegral: record.accumulativeIntegral,
      name: record.name
    });
    setIntegralModalVisible(true);
  };
  // 处理金币一键清空
  const handleClearGold = async () => {
    try {
      const res = await clearAllGold();
      if (res.code === 200) {
        message.success('金币清空成功');
        actionRef.current?.reload();
      } else {
        message.error(res.message || '清空失败');
      }
    } catch (error) {
      message.error('清空失败');
    }
  };
  // 触发用户导入
  const triggerUserImport = () => {
    fileInputRef.current?.click();
  };
  // 触发金币导入
  const triggerGoldImport = () => {
    goldFileInputRef.current?.click();
  };
  // 处理用户导入
  const handleUserImport = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    // 验证文件类型
    const fileExtension = file.name.split('.').pop()?.toLowerCase();
    const allowedExtensions = ['xlsx', 'xls'];
    if (!allowedExtensions.includes(fileExtension)) {
      message.error('请选择 Excel 文件(.xlsx 或 .xls)');
      e.target.value = '';
      return;
    }
    // 验证文件大小
    const fileSizeMB = file.size / 1024 / 1024;
    if (fileSizeMB > 10) {
      message.error('文件大小不能超过 10MB');
      e.target.value = '';
      return;
    }
    const response = await importUser(file);
    if (response && response.code && response.code !== 200) {
      throw response;
    }
    e.target.value = '';
    actionRef.current?.reload();
  };
  // 处理金币导入
  const handleGoldImport = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    // 验证文件类型
    const fileExtension = file.name.split('.').pop()?.toLowerCase();
    const allowedExtensions = ['xlsx', 'xls'];
    if (!allowedExtensions.includes(fileExtension)) {
      message.error('请选择 Excel 文件(.xlsx 或 .xls)');
      e.target.value = '';
      return;
    }
    // 验证文件大小
    const fileSizeMB = file.size / 1024 / 1024;
    if (fileSizeMB > 10) {
      message.error('文件大小不能超过 10MB');
      e.target.value = '';
      return;
    }
    const response = await importGold(file);
    if (response && response.code && response.code !== 200) {
      throw response;
    }
    e.target.value = '';
    actionRef.current?.reload();
  };
  // 导出Excel
  const handleExport = () => {
    exportExcell('用户列表.xlsx', searchParams, '/t-app-user/exportAppUser');
  };
  // 角色映射
  const userTypeMap = {
    1: '游客',
    2: '营销员',
    3: '合伙人',
  };
  // 状态映射
  const statusMap = {
    1: { text: '使用中', color: 'success' },
    2: { text: '冻结', color: 'warning' },
    3: { text: '删除', color: 'error' },
  };
  const columns = [
    {
      title: '序号',
      dataIndex: 'index',
      hideInSearch: true,
      align: 'center',
      width: 60,
      render: (text, record, index) => index + 1,
    },
    {
      title: '头像',
      dataIndex: 'profilePhoto',
      hideInSearch: true,
      width: 80,
      render: (text, record) => (
        <Avatar
          src={text}
          size="small"
          style={{ backgroundColor: '#f5f5f5', color: '#000' }}
        >
          {text ? '' : record.name?.substring(-1)}
        </Avatar>
      ),
    },
    {
      title: '用户名',
      dataIndex: 'name',
      width: 120,
      ellipsis: true,
    },
    {
      title: '推广号',
      dataIndex: 'code',
      width: 120,
      ellipsis: true,
      search: false,
    },
    {
      title: '联系电话',
      dataIndex: 'phone',
      width: 130,
      ellipsis: true,
    },
    {
      title: '性别',
      dataIndex: 'gender',
      width: 80,
      hideInSearch: true,
      render: (text) => (text === 1 ? '男' : text === 2 ? '女' : '-'),
    },
    {
      title: '出生日期',
      dataIndex: 'birthday',
      width: 120,
      hideInSearch: true,
    },
    {
      title: '注册时间',
      dataIndex: 'createTime',
      width: 180,
      hideInSearch: true,
    },
    {
      title: '角色',
      dataIndex: 'userType',
      width: 100,
      hideInSearch: true,
      valueEnum: {
        1: { text: '游客' },
        2: { text: '营销员' },
        3: { text: '合伙人' },
      },
      render: (text) => userTypeMap[text] || '-',
    },
    {
      title: '状态',
      dataIndex: 'status',
      width: 100,
      valueType: 'select',
      valueEnum: {
        1: { text: '使用中', color: '#52c41a' },
        2: { text: '冻结', color: 'red' },
      },
      render: (text, record) => {
        const status = statusMap[record.status] || { text: '未知', color: 'default' };
        return <span style={{ color: status.color }}>{status.text}</span>;
      },
    },
    {
      title: '操作',
      dataIndex: 'action',
      valueType: 'option',
      width: 500,
      fixed: 'right',
      render: (_, record) => (
        <Space>
          <Button
            type="link"
            size="small"
            onClick={() => {
              Modal.confirm({
                title: '操作确认',
                content: `确定要${record.status === 1 ? '冻结' : '解冻'}用户 ${record.name} 吗?`,
                onOk: async () => {
                  try {
                    const res = await changeStatus(record.id);
                    if (res.code === 200) {
                      message.success(`${record.status === 1 ? '冻结' : '解冻'}成功`);
                      actionRef.current?.reload();
                    } else {
                      message.error(res.message || '操作失败');
                    }
                  } catch (error) {
                    console.error('冻结/解冻失败:', error);
                    message.error('操作失败');
                  }
                },
              });
            }}
          >
            {record.status === 1 ? '冻结' : '解冻'}
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 跳转到详情页面
              // history.push(`/user/detail?id=${record.id}`);
              message.info('详情功能开发中');
            }}
          >
            详情
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 跳转到编辑页面
              // history.push(`/user/edit?id=${record.id}`);
              message.info('编辑功能开发中');
            }}
          >
            编辑
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 跳转到购买记录页面
              // history.push(`/user/purchase-record?id=${record.id}`);
              message.info('购买记录功能开发中');
            }}
          >
            购买记录
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 跳转到推广记录页面
              // history.push(`/user/promotion-record?id=${record.id}`);
              message.info('推广记录功能开发中');
            }}
          >
            推广记录
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 金币充值功能
              // 这里应该打开充值弹窗
              message.info('金币充值功能开发中');
            }}
          >
            金币充值
          </Button>
          <Button
            type="link"
            size="small"
            onClick={() => {
              // 单个用户修改积分
              handleSingleUpdate(record);
            }}
          >
            修改积分
          </Button>
        </Space>
      ),
    },
  ];
  return (
    <PageContainer>
      <ProTable
        actionRef={actionRef}
        rowKey="id"
        search={{
          labelWidth: 'auto',
          defaultCollapsed: false,
        }}
        toolBarRender={() => [
          <input
            ref={fileInputRef}
            type="file"
            accept=".xlsx,.xls"
            style={{ display: 'none' }}
            onChange={handleUserImport}
          />,
          <input
            ref={goldFileInputRef}
            type="file"
            accept=".xlsx,.xls"
            style={{ display: 'none' }}
            onChange={handleGoldImport}
          />,
          <Button
            key="importUser"
            onClick={triggerUserImport}
          >
            导入用户
          </Button>,
          <Button
            key="importGold"
            onClick={triggerGoldImport}
          >
            批量导入金币
          </Button>,
          <Popconfirm
            title="确定要清空所有用户的金币吗?"
            onConfirm={handleClearGold}
            okText="确定"
            cancelText="取消"
          >
            <Button key="clearGold" danger>
              金币一键清空
            </Button>
          </Popconfirm>,
          <Button
            key="batchUpdate"
            onClick={handleBatchUpdate}
          >
            批量修改积分
          </Button>,
          <Button
            key="export"
            type="primary"
            onClick={handleExport}
          >
            导出Excel
          </Button>,
        ]}
        request={async (params) => {
          const { current, pageSize, ...rest } = params;
          // 保存搜索参数,用于导出
          setSearchParams(rest);
          const res = await getUserList({
            ...rest,
            pageNum: current,
            pageSize,
          });
          return {
            data: res.data?.records || [],
            success: res.code === 200,
            total: res.data?.total || 0,
          };
        }}
        pagination={{
          showSizeChanger: true,
          showQuickJumper: true,
          defaultPageSize: 10,
          showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条/共 ${total} 条`,
        }}
        columns={columns}
        rowSelection={{
          onChange: (_, selectedRows) => {
            setSelectedUsers(selectedRows.map(row => row.id));
          },
        }}
        scroll={{ x: 1500 }}
      />
      <BatchUpdateIntegralModal
        visible={integralModalVisible}
        type={integralModalType}
        data={integralModalData}
        onCancel={() => setIntegralModalVisible(false)}
        onSuccess={() => {
          setIntegralModalVisible(false);
          setSelectedUsers([]);
          actionRef.current?.reload();
        }}
      />
    </PageContainer>
  );
};
export default UserList;
management/src/pages/user/list/service.js
New file
@@ -0,0 +1,65 @@
import { request } from '@umijs/max';
// 获取用户列表
export async function getUserList(params) {
  return request(`/t-app-user/pageList`, {
    method: 'POST',
    data: params,
  });
}
// 导入用户
export async function importUser(file, companyId) {
  const formData = new FormData();
  formData.append('file', file);
  if (companyId) {
    formData.append('companyId', companyId);
  }
  return request(`/t-app-user/importUser`, {
    method: 'POST',
    data: formData,
  });
}
// 导入金币
export async function importGold(file) {
  const formData = new FormData();
  formData.append('file', file);
  return request(`/t-app-user/importGold`, {
    method: 'POST',
    data: formData,
  });
}
// 导出Excel
export async function exportUserExcel(params) {
  return request(`/t-app-user/exportAppUser`, {
    method: 'POST',
    data: params,
  });
}
// 批量修改积分
export async function batchUpdateIntegral(params) {
  return request(`/t-app-user/batchUpdateGold`, {
    method: 'POST',
    data: params,
  });
}
// 金币一键清空
export async function clearAllGold() {
  return request(`/t-app-user/clearGold`, {
    method: 'GET',
  });
}
// 修改用户状态(冻结/解冻)
export async function changeStatus(id) {
  return request(`/t-app-user/changeStatus`, {
    method: 'GET',
    params: { id },
  });
}
management/src/services/service.js
@@ -59,3 +59,10 @@
    method: 'GET',
  });
}
// 获取用户信息
export async function getUserInfoFromUserId() {
  return request(`/system/user/queryDetail`, {
    method: 'GET',
  });
}
management/src/utils/antdUtils.js
@@ -16,14 +16,11 @@
export function showDelConfirm(okHandler, content, confirmText, cancelText, titleText) {
  confirm({
    className: 'del-confirm',
    title: titleText || '确定要删除这条数据?',
    content: content || '删除后不可恢复,请谨慎操作!',
    okText: confirmText || '删除',
    cancelText: cancelText || '取消',
    okType: 'default',
    centered: true,
    icon: <div className='del-icon'>?</div>,
    footer: (params) => {
      return [params.props.children[0], params.props.children[1]];
    },