feat: 新增公司管理和用户管理模块及相关功能
refactor: 优化订单转让功能和大礼包订单状态显示
fix: 修复订单详情页布局和状态显示问题
chore: 移除无用导出功能并统一使用utils工具类
docs: 更新路由配置添加新页面路径
style: 调整确认弹窗样式和订单表格宽度
perf: 优化订单转让记录查询和详情展示
test: 添加批量修改积分组件测试用例
build: 新增公司详情和用户列表相关服务接口
ci: 更新CI配置支持新模块构建
| | |
| | | "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" |
| | |
| | | "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" |
| | | } |
| | | ] |
| | | }, |
| | | ]; |
| | |
| | | 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'; |
| | |
| | | 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 ( |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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); |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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); |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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', |
| | | }); |
| | | }; |
| New file |
| | |
| | | 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); |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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', |
| | | }); |
| | | }; |
| | |
| | | label: '用户手机号', |
| | | children: orderDetail.phone || '-', |
| | | }, |
| | | { |
| | | label: '当前状态', |
| | | children: statusMap[status] || '-', |
| | | }, |
| | | { |
| | | label: '订单金额', |
| | | children: `¥${orderDetail.orderPrice || 0}`, |
| | | }, |
| | | ]; |
| | | |
| | | // 根据不同状态和支付类型添加特定字段 |
| | |
| | | |
| | | 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}`, |
| | |
| | | label: '备注', |
| | | children: orderDetail.remark || '-', |
| | | }, |
| | | ]; |
| | | ]]; |
| | | } else if (status === 3 && paymentType === 1) { |
| | | // 已支付定金+尾款 |
| | | specificItems = [ |
| | | specificItems = [...specificItems, ...[ |
| | | { |
| | | label: '已付定金', |
| | | children: `¥${orderDetail.deposit || 0}`, |
| | |
| | | label: '备注', |
| | | children: orderDetail.remark || '-', |
| | | }, |
| | | ]; |
| | | ]]; |
| | | } else if (status === 3 && paymentType === 2) { |
| | | // 已支付全款 |
| | | specificItems = [ |
| | | specificItems = [...specificItems, ...[ |
| | | { |
| | | label: '支付金额', |
| | | children: `¥${orderDetail.paymentPrice || 0}`, |
| | |
| | | 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> |
| | |
| | | |
| | | {/* 商品信息 */} |
| | | <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 && ( |
| | |
| | | onCancel={onCancel} |
| | | footer={null} |
| | | width={800} |
| | | destroyOnClose |
| | | destroyOnHidden |
| | | > |
| | | {renderDetailContent()} |
| | | </Modal> |
| | |
| | | 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; |
| | | |
| | |
| | | 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, |
| | | }, |
| | | { |
| | |
| | | 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> |
| | | ), |
| | | }, |
| | | ]; |
| | |
| | | |
| | | 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); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 订单数据源 |
| | |
| | | onCancel={onCancel} |
| | | footer={null} |
| | | width={1200} |
| | | destroyOnClose |
| | | destroyOnHidden |
| | | > |
| | | {/* 上部分:订单信息 */} |
| | | <div style={{ marginBottom: 24 }}> |
| | |
| | | </div> |
| | | |
| | | {/* 中部分:查询字段 */} |
| | | <div style={{ marginBottom: 24, textAlign: 'center' }}> |
| | | <div style={{ marginBottom: 24 }}> |
| | | <Space size="middle"> |
| | | <Search |
| | | placeholder="请输入推广号/电话号码" |
| | |
| | | label: '用户手机号', |
| | | children: orderDetail.phone || '-', |
| | | }, |
| | | { |
| | | label: '当前状态', |
| | | children: statusMap[status] || '-', |
| | | }, |
| | | { |
| | | label: '订单金额', |
| | | children: `¥${orderDetail.orderPrice || 0}`, |
| | | }, |
| | | ]; |
| | | |
| | | // 根据不同状态和支付类型添加特定字段 |
| | |
| | | |
| | | 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}`, |
| | |
| | | label: '备注', |
| | | children: orderDetail.remark || '-', |
| | | }, |
| | | ]; |
| | | ]]; |
| | | } else if (status === 3 && paymentType === 1) { |
| | | // 已支付定金+尾款 |
| | | specificItems = [ |
| | | specificItems = [...specificItems, ...[ |
| | | { |
| | | label: '已付定金', |
| | | children: `¥${orderDetail.deposit || 0}`, |
| | |
| | | label: '备注', |
| | | children: orderDetail.remark || '-', |
| | | }, |
| | | ]; |
| | | ]]; |
| | | } else if (status === 3 && paymentType === 2) { |
| | | // 已支付全款 |
| | | specificItems = [ |
| | | specificItems = [...specificItems, ...[ |
| | | { |
| | | label: '支付金额', |
| | | children: `¥${orderDetail.paymentPrice || 0}`, |
| | |
| | | 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> |
| | |
| | | |
| | | {/* 商品信息 */} |
| | | <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 }}> |
| | |
| | | const [transferVisible, setTransferVisible] = useState(false); |
| | | const [transferOrderData, setTransferOrderData] = useState(null); |
| | | |
| | | // 暴露刷新函数给子组件使用 |
| | | window.refreshParentList = () => { |
| | | actionRef.current?.reload(); |
| | | }; |
| | | |
| | | // 处理详情弹窗 |
| | | const handleDetail = (id) => { |
| | | setCurrentOrderId(id); |
| | |
| | | } |
| | | }; |
| | | |
| | | // 订单状态映射 |
| | | const statusMap = { |
| | | 1: '待付款', |
| | | 2: '已付订金', |
| | | 3: '完成支付', |
| | | 4: '确认收货', |
| | | 5: '已退款', |
| | | }; |
| | | |
| | | // 支付类型映射 |
| | | const paymentTypeMap = { |
| | | 1: '订金+尾款', |
| | | 2: '全款', |
| | | }; |
| | | |
| | | |
| | | |
| | | const columns = [ |
| | | { |
| | | title: '序号', |
| | |
| | | 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),不显示任何操作按钮 |
| | |
| | | }} |
| | | toolBarRender={() => [ |
| | | <Button |
| | | key="export" |
| | | type="primary" |
| | | onClick={handleExport} |
| | | > |
| | | 导出Excel |
| | |
| | | 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, |
| | | }); |
| | | } |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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', |
| | | }); |
| | | }; |
| | |
| | | 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 }) => { |
| | |
| | | } |
| | | }, [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 = { |
| | |
| | | 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> |
| | | </> |
| | | ) : ( |
| | | <> |
| | |
| | | )} |
| | | </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 > |
| | | ); |
| | | }; |
| | | |
| | |
| | | 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(); |
| | |
| | | |
| | | // 导出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') |
| | | }; |
| | | |
| | | // 查看详情 |
| | |
| | | title: '订单编号', |
| | | dataIndex: 'code', |
| | | key: 'code', |
| | | copyable: true, |
| | | }, |
| | | { |
| | | title: '下单时间', |
| | |
| | | title: '商品名称', |
| | | dataIndex: 'physicalGoodsName', |
| | | key: 'physicalGoodsName', |
| | | width: 200, |
| | | }, |
| | | { |
| | | title: '支付方式', |
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | </Button>, |
| | | ]} |
| | | width={800} |
| | | destroyOnClose |
| | | destroyOnHidden |
| | | > |
| | | {renderDetailContent()} |
| | | </Modal> |
| | |
| | | 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(); |
| | |
| | | }); |
| | | } |
| | | |
| | | // 导出退款申请列表 |
| | | 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}`, { |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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; |
| New file |
| | |
| | | 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 }, |
| | | }); |
| | | } |
| | |
| | | method: 'GET', |
| | | }); |
| | | } |
| | | |
| | | // 获取用户信息 |
| | | export async function getUserInfoFromUserId() { |
| | | return request(`/system/user/queryDetail`, { |
| | | method: 'GET', |
| | | }); |
| | | } |
| | |
| | | |
| | | 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]]; |
| | | }, |