提交 dcce05d6 作者: ZhangLingKun

功能:订单列表

上级 a5a4279b
# 请求接口地址
VITE_REQUEST_BASE_URL = 'https://testapi.sharefly.mmcuav.cn/'
NODE_ENV = 'development'
VERSION = '2.2.4.230217.Release'
# 请求接口地址
VITE_REQUEST_BASE_URL = 'https://testapi.sharefly.mmcuav.cn/'
NODE_ENV = 'development'
VERSION = '2.2.4.230217.Release'
/** 扩展环境变量import.meta.env */
interface ImportMetaEnv {
VITE_REQUEST_BASE_URL: string;
NODE_ENV: string;
VERSION: string;
}
......@@ -13,35 +13,36 @@
"format": "npm run prettier:fix && npm run lint:fix"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.2",
"@ant-design/icons": "^5.0.1",
"@reduxjs/toolkit": "^1.9.2",
"antd": "^5.4.2",
"axios": "^1.4.0",
"dayjs": "^1.11.7",
"js-base64": "^3.7.3",
"js-cookie": "^3.0.1",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"match-sorter": "^6.3.1",
"pinyin-pro": "^3.14.0",
"qs": "^6.10.3",
"query-string": "^8.1.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0",
"sort-by": "^1.2.0",
"xlsx": "^0.18.5",
"axios": "^1.4.0",
"js-base64": "^3.7.3",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"qs": "^6.10.3",
"react-redux": "^8.0.5"
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/js-cookie": "^3.0.1",
"@types/lodash": "^4.14.182",
"@types/node": "^20.1.3",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.5",
"@types/react-redux": "^7.1.25",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"@vitejs/plugin-react": "^1.3.0",
"@types/js-cookie": "^3.0.1",
"@types/lodash": "^4.14.182",
"@types/react-redux": "^7.1.25",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
......
......@@ -3,12 +3,12 @@ import { message } from 'antd';
import Cookies from 'js-cookie';
import { Base64 } from 'js-base64';
const { REACT_APP_BASE_URL } = process.env;
export const uploadImgURL = `${REACT_APP_BASE_URL}ossservlet/upload/imgOss`;
export const uploadVideoURL = `${REACT_APP_BASE_URL}ossservlet/upload/videoOss`;
export const uploadFileURL = `${REACT_APP_BASE_URL}ossservlet/upload/oss`;
const { VITE_REQUEST_BASE_URL } = import.meta.env;
export const uploadImgURL = `${VITE_REQUEST_BASE_URL}ossservlet/upload/imgOss`;
export const uploadVideoURL = `${VITE_REQUEST_BASE_URL}ossservlet/upload/videoOss`;
export const uploadFileURL = `${VITE_REQUEST_BASE_URL}ossservlet/upload/oss`;
const service = axios.create({
baseURL: REACT_APP_BASE_URL,
baseURL: VITE_REQUEST_BASE_URL,
timeout: 6000,
});
service.interceptors.request.use(
......
.point-list-head-number {
width: 100%;
height: 40px;
display: flex;
justify-content: space-around;
align-items: center;
.number-label {
margin-right: 10px;
font-weight: bold;
font-size: 14px;
}
.ant-btn-link{
padding: 0;
font-size: 16px !important;
font-weight: bold !important;
span {
font-size: 16px !important;
font-weight: bold !important;
}
}
}
import { useEffect, useState } from "react";
import { RoleAPI } from "@/api";
import { limitEntity } from "@/api/modules/role";
function useOptionShow(id: number) {
// const [show, setShow] = useState<boolean>(false);
return JSON.parse(localStorage.getItem("routeList") as string).some(
(v: limitEntity) => v.id === id
);
// useEffect(() => {
// RoleAPI.getListCuserMenuInfo().then((res: any) => {
// const bol: boolean = res.result.some((v: limitEntity) => v.id === id);
// setShow(bol);
// });
// }, []);
// return show;
}
export default useOptionShow;
......@@ -164,7 +164,7 @@ $page-background: #F6F7FB;
margin-right: 10px;
width: 24px;
height: 24px;
border:1px solid #3a4252;
border:1px solid #1677ff;
border-radius: 2px;
padding: 0;
line-height: normal;
......
......@@ -35,7 +35,7 @@ export function MenuView() {
};
// 递归将路由转换为侧边栏数据
const getItem = (routerList: RouteObjectType[]) => {
const list: MenuItem[] = routerList.map((i) => {
const list: Array<MenuItem> = routerList.map((i) => {
if (i.children?.length) {
return {
label: i.meta?.title,
......@@ -44,14 +44,16 @@ export function MenuView() {
children: getItem(i.children || []),
};
} else {
return {
label: i.meta?.title,
key: i.meta?.id,
icon: i.meta?.icon,
};
if (!i.meta.hidden) {
return {
label: i.meta?.title,
key: i.meta?.id,
icon: i.meta?.icon,
};
}
}
});
return list;
}) as MenuItem[];
return list?.filter((i) => i !== undefined);
};
// 组件挂载
useEffect(() => {
......
.header {
padding: 15px 20px 5px 20px;
background-color: #fff;
margin: 0 0 10px 0;
border-radius: 10px;
//min-width: 768px;
width: 100%;
//.ant-select-selector {
// min-width: 200px;
//}
.ant-row {
//margin-bottom:10px;
}
.add-button {
margin-right: 15px;
}
.search-children {
position: relative;
display: flex;
justify-content: flex-start;
align-items: center;
box-sizing: border-box;
margin-bottom: 15px;
padding-right: 15px;
div, .ant-btn {
margin-right: 10px;
}
div:last-child, .ant-btn:last-child {
margin-right: 0 !important;
}
.backButton{
position: absolute;
top: 0;
right: 0;
}
}
.search-sufFixBtn{
margin-bottom: 10px;
div, .ant-btn {
margin-right: 10px;
}
}
}
import { Form, Input, Button, Select, DatePicker } from "antd";
import moment from "dayjs";
import {
SearchOutlined,
ReloadOutlined,
ExportOutlined,
} from "@ant-design/icons";
import React, { useImperativeHandle } from "react";
import "./index.scss";
const { RangePicker } = DatePicker;
// 搜索列表的类型
export interface searchColumns {
type: "input" | "select" | "rangePicker" | "DatePicker" | "Select";
label?: string;
name: string;
placeholder: string;
maxlength?: number;
width?: number;
options?: {
id?: string | number | boolean | null;
name?: string;
value?: string | number | boolean | null;
label?: string;
}[];
onSelect?: (value: any) => void;
disable?: boolean;
onChange?: any;
showTime?: { format: "HH:mm:ss" };
}
// 组件的类型
interface propsType {
search?: searchColumns[];
searchData?: any;
preFixBtn?: React.ReactNode;
sufFixBtn?: React.ReactNode;
child?: React.ReactNode;
children?: React.ReactNode;
isExport?: boolean;
exportEvent?: any;
baseRef?: any;
otherChild?: React.ReactNode;
}
const Index: React.FC<propsType> = (props) => {
// 组件的默认值
Index.defaultProps = {
search: [],
searchData: null,
preFixBtn: null,
sufFixBtn: null,
child: null,
children: null,
isExport: false,
exportEvent: null,
baseRef: null,
otherChild: null,
};
// 表单ref
const [form] = Form.useForm();
// 重置表格
const handleRest = () => {
form.resetFields();
handleSubmit({});
};
// 提交数据
const handleSubmit = (data: any) => {
// console.log("提交数据 --->", data);
if (data.startTime) {
data.startTime = moment(data.startTime).format("YYYY-MM-DD");
} else {
data.startTime = undefined;
}
Object.keys(data).forEach((k) => {
const isRangPicker: boolean | undefined =
props.search &&
props.search.some((v) => v.name === k && v.type === "rangePicker");
if (Array.isArray(data[k]) && isRangPicker) {
data[k] = [
`${moment(data[k][0]).format("YYYY-MM-DD")} 00:00:00`,
`${moment(data[k][1]).format("YYYY-MM-DD")} 23:59:59`,
];
}
});
props.searchData(data);
};
useImperativeHandle(props.baseRef, () => ({
getForm: () => form,
}));
// 导出事件点击
const exportClick = () => {
props.exportEvent();
};
return (
<div className="header">
{props?.preFixBtn && (
<div className="search-children">{props?.preFixBtn}</div>
)}
<Form
layout="inline"
form={form}
initialValues={{ layout: "inline" }}
onFinish={handleSubmit}
>
{props?.child && <div className="search-children">{props?.child}</div>}
{props?.children && (
<div className="search-children">{props?.children}</div>
)}
{props?.search?.map((item: any) => {
return (
<Form.Item
name={item.name}
label={item.label}
key={item.name}
style={{ marginBottom: "10px" }}
>
{item.type === "input" ? (
<Input
placeholder={item.placeholder}
allowClear
style={{ width: item.width ? `${item.width}px` : "150px" }}
maxLength={50}
/>
) : item.type === "select" ? (
<Select
style={{ width: item.width ? `${item.width}px` : "200px" }}
placeholder={item.placeholder}
allowClear
>
{item.options.map((option: any) => {
return (
<Select.Option value={option.id} key={option.id}>
{option.name}
</Select.Option>
);
})}
</Select>
) : item.type === "Select" ? (
<Select
style={{ width: item.width ? `${item.width}px` : "200px" }}
placeholder={item.placeholder}
allowClear
options={item.options}
/>
) : item.type === "rangePicker" ? (
<RangePicker
// showTime={{
// // hideDisabledOptions: true,
// defaultValue: [
// moment("00:00:00", "HH:mm:ss"),
// moment("23:59:59", "HH:mm:ss"),
// ],
// }}
format="YYYY-MM-DD"
style={{ width: item.width ? `${item.width}px` : "220px" }}
/>
) : item.type === "DatePicker" ? (
<DatePicker
showTime={item.showTime}
style={{ width: item.width ? `${item.width}px` : "180px" }}
/>
) : (
""
)}
</Form.Item>
);
})}
{props?.search && props?.search?.length > 0 && (
<>
<Form.Item style={{ marginBottom: "10px" }}>
<Button
type="primary"
htmlType="submit"
icon={<SearchOutlined />}
>
搜索
</Button>
</Form.Item>
<Form.Item style={{ marginBottom: "10px" }}>
<Button
type="primary"
onClick={handleRest}
icon={<ReloadOutlined />}
>
重置
</Button>
</Form.Item>
{!!props.otherChild && (
<Form.Item style={{ marginBottom: "10px" }}>
{props.otherChild}
</Form.Item>
)}
</>
)}
{props.isExport ? (
<Form.Item style={{ marginBottom: "10px" }}>
<Button
type="primary"
onClick={exportClick}
icon={<ExportOutlined />}
>
导出
</Button>
</Form.Item>
) : (
""
)}
</Form>
{!!props?.sufFixBtn && (
<div className="search-sufFixBtn">{props?.sufFixBtn}</div>
)}
</div>
);
};
export default Index;
import { Button } from 'antd';
import React from 'react';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
interface PropsType {
isBack?: boolean;
isFooter?: boolean;
children: React.ReactNode;
onCancel?: React.MouseEventHandler<HTMLAnchorElement> &
React.MouseEventHandler<HTMLButtonElement>;
onOK?: React.MouseEventHandler<HTMLAnchorElement> & React.MouseEventHandler<HTMLButtonElement>;
}
export const TableDetailView: React.FC<PropsType> = (props) => {
// 组件默认值
TableDetailView.defaultProps = {
isBack: false,
isFooter: false,
onCancel: undefined,
onOK: undefined,
};
// 参数解构
const { children, isBack, isFooter, onCancel, onOK } = props;
// 路由钩子
const navigate = useNavigate();
// Dom
return (
<div className='table-detail-view'>
{isBack && (
<Button
type='primary'
icon={<ArrowLeftOutlined />}
style={{
position: 'absolute',
top: '10px',
right: '10px',
width: '100px',
}}
onClick={() => {
navigate(-1);
}}
>
返回
</Button>
)}
{children}
{isFooter && (
<div className='detail-footer'>
<Button onClick={onCancel}>取消</Button>
<Button onClick={onOK} type='primary'>
确认
</Button>
</div>
)}
</div>
);
};
import { Form, Input, message, Modal, Radio, Select } from "antd";
import React, { useEffect, useState } from "react";
import { debounce } from "lodash";
import { PointManageAPI } from "@/api";
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: any;
push: any;
orderData?: any;
}
const DivideAddEditModal: React.FC<propType> = (props: propType) => {
// 组件默认值
DivideAddEditModal.defaultProps = {
data: null,
orderData: null,
};
// 参数
const { title, open, closed, data, orderData, push } = props;
// 表单钩子
const [form] = Form.useForm();
// 分成方式
const [divideMethod, setDivideMethod] = React.useState(0);
// 用户列表
const [mallUserList, setMallUserList] = React.useState<
{
value: number;
label: string;
uid: string;
userName: string;
}[]
>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
setMallUserList([]);
setPagination({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
closed();
};
// 获取分成用户
const getListKBTMallUser = async (value = {}) => {
const res = await PointManageAPI.MallUserBySearchKey({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
});
if (res && res.code === "200") {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
const arr = list?.map((i) => ({
value: i.id,
label: `${i.userName || i.nickName}(${i.uid}) ${i.phoneNum}`,
uid: i.uid,
userName: i.userName || i.nickName,
}));
setMallUserList([...mallUserList, ...arr]);
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
} else {
message.warning(res.message);
}
};
// 滚动翻页(防抖)
const handlePopupScroll = debounce(async (e) => {
if (pagination.totalPage <= pagination.current) return;
await getListKBTMallUser({ pageNo: pagination.current + 1 });
// console.log("handlePopupScroll -->", e.target.scrollTop);
}, 500);
// 校验数据
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
// 处理数据并准备提交数据
await handleSubmit({
...values,
proportionRate: Number(values.proportionRate) / 100,
});
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (values: any) => {
const res = await PointManageAPI.CalculateOrderBonusVO({
...data,
...values,
orderId: orderData.orderId,
orderNO: orderData.orderNo,
uid: mallUserList.find((i) => i.value === values.mallUserId)?.uid || "",
userName:
mallUserList.find((i) => i.value === values.mallUserId)?.userName || "",
ruleType: 1,
});
if (res && res.code === "200") {
push({
...data,
...res?.result,
});
message.success(data?.id ? "修改成功" : "添加成功").then();
handleCancel();
}
};
// componentDidMount
useEffect(() => {
if (!open) return;
(async () => {
await getListKBTMallUser();
})();
if (!data) return;
form.setFieldsValue({
...data,
proportionRate: (Number(data.proportionRate) * 100).toFixed(1),
});
setDivideMethod(data.proportionMode);
}, [open]);
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
>
<Form
name="addForm"
form={form}
labelAlign="right"
labelCol={{ span: 5 }}
wrapperCol={{ span: 10 }}
>
<Form.Item
label="其它分成对象"
name="proportionObjName"
rules={[{ required: true, message: "请输入其它分成对象" }]}
>
<Input placeholder="请输入其它分成对象" maxLength={20} />
</Form.Item>
<Form.Item
label="分成方式"
name="proportionMode"
rules={[{ required: true, message: "请选择分成方式" }]}
initialValue={divideMethod}
>
<Radio.Group
options={[
{ label: "分成比例", value: 0 },
{ label: "差价分成", value: 1, disabled: true },
]}
onChange={({ target: { value } }) => setDivideMethod(value)}
/>
</Form.Item>
{divideMethod === 0 && (
<>
<Form.Item
label="分成参数"
name="proportionRate"
rules={[
{ required: true, message: "请输入分成额度百分比" },
// 只能输入0.1到100.0的一位小数,不能大于100.0
{
pattern: /^(100(\.0)?|[1-9]?\d(\.\d)?)$/,
message: "只能输入0.1到100.0的一位小数",
},
]}
>
<Input
placeholder="请输入分成额度百分比"
maxLength={20}
type="number"
suffix="%"
/>
</Form.Item>
<div style={{ margin: "0 0 10px 60px" }}>
说明:分成比例基数以订单金额减去相关渠道利润的得数为基准
</div>
</>
)}
<Form.Item
label="分成用户"
name="mallUserId"
rules={[{ required: true, message: "请选择分成用户" }]}
wrapperCol={{ span: 14 }}
>
<Select
placeholder="请选择分成用户"
options={mallUserList}
allowClear
showSearch
filterOption={(input, option) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
optionFilterProp="label"
onPopupScroll={handlePopupScroll}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default DivideAddEditModal;
import React, { useEffect, useState } from "react";
import { Button, Form, message, Modal, Select, Table } from "antd";
import { ColumnsType } from "antd/es/table";
import { PlusOutlined } from "@ant-design/icons";
import DivideAddEditModal from "@/pages/pointManage/divideOrder/comp/divideAddEditModal";
import { PointManageAPI } from "@/api";
import { OrderBonusListByOrderIdType } from "@/api/interface/pointManageType";
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: any;
isEdit: boolean;
}
// 列表的类型
type TableType = (ReturnType<OrderBonusListByOrderIdType> extends Promise<
infer T
>
? T
: never)["result"]["defaultList"];
export const DivideSetting: React.FC<propType> = (props: propType) => {
// 组件默认值
DivideSetting.defaultProps = {
data: null,
};
// 参数
const { title, open, closed, data, isEdit } = props;
// 表单钩子
const [form] = Form.useForm();
// 规则列表
const [ruleNameList, setRuleNameList] = useState<
{ label: string; value: number }[]
>([]);
// 默认表格数据
const [defaultTableData, setDefaultTableData] = useState<TableType>([]);
// 扩展表格数据
const [extendTableData, setExtendTableData] = useState<TableType>([]);
// 需要编辑的数据
const [editData, setEditData] = useState<TableType[0]>();
// 添加编辑弹窗
const [addEditVisible, setAddEditVisible] = useState<boolean>(false);
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
setRuleNameList([]);
setExtendTableData([]);
setDefaultTableData([]);
closed();
};
// 校验数据
const handleOk = () => {
// 编辑状态下直接发放积分
if (isEdit) {
return handleConfirm();
}
form
.validateFields()
.then(async (values) => {
await handleSubmit();
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 确定发放积分
const handleConfirm = () => {
Modal.confirm({
title: "提示",
content: "确认一键发放积分给相关用户吗?操作不可逆,请谨慎操作",
onOk: async () => {
const res = await PointManageAPI.DistributeMallOrderBonus({
orderId: data.orderId,
});
if (res && res.code === "200") {
message.success("发放成功").then();
handleCancel();
}
},
});
};
// 删除数据
const handleDelete = (record: any) => {
Modal.confirm({
title: "提示",
content: "删除后此数据将会丢失,确定删除吗?",
onOk: () => {
setExtendTableData(
extendTableData?.filter((i) => i.index !== record.index)
);
message.success("操作成功").then();
},
});
};
// 获取分成对象列表
const getOrderBonusListRule = async () => {
const res = await PointManageAPI.OrderBonusListRule();
if (res && res.code === "200") {
setRuleNameList(
res.result?.map((i) => {
return { label: i.ruleName, value: i.id };
})
);
}
};
// 获取订单详情
const getBonusRuleListQuery = async () => {
const res = await PointManageAPI.OrderBonusListByOrderId({
orderId: data?.orderId,
});
if (res && res.code === "200") {
const { bonusRuleDTO, extendList } = res.result;
// 设置扩展规则数据
setExtendTableData(extendList?.map((i, j) => ({ ...i, index: j })));
// 判断是否有默认规则
if (!bonusRuleDTO?.id) return;
// 设置规则名称回显
form.setFieldValue("ruleName", bonusRuleDTO.id);
// 获取规则列表
await getOrderBonusBuildMall(bonusRuleDTO.id);
}
};
// 获取订单规则列表
const getOrderBonusBuildMall = async (proportionRuleId: number) => {
const res = await PointManageAPI.OrderBonusBuildMall({
proportionRuleId,
orderId: data?.orderId,
});
if (res && res.code === "200") {
// 强行设置索引
setDefaultTableData(res.result.map((i, j) => ({ ...i, index: j })));
}
};
// 追加列表数据
const handlePush = (val: TableType[0]) => {
// 原来列表中没有的数据就追加,有的话就替换
if (extendTableData.some((i) => val?.index === i.index)) {
setExtendTableData(
extendTableData.map((i) => {
if (i.index === val?.index) {
return val;
}
return i;
})
);
} else {
setExtendTableData(
[...extendTableData, val].map((i, j) => ({ ...i, index: j }))
);
}
};
// 提交数据
const handleSubmit = async () => {
const res = await PointManageAPI.UpdateMallOrderBonus([
...defaultTableData,
...extendTableData,
]);
if (res && res.code === "200") {
message.success("操作成功").then();
handleCancel();
}
};
// componentDidMount
useEffect(() => {
if (!open) return;
if (!data) return;
(async () => {
await getOrderBonusListRule();
await getBonusRuleListQuery();
})();
}, [open]);
// 通用表格列
const defaultColumns: ColumnsType<TableType[0]> = [
{
title: "分成对象",
dataIndex: "proportionObjName",
align: "center",
},
{
title: "分成方式",
dataIndex: "proportionMode",
align: "center",
render: (text) => (text === 0 ? "比例分成" : "差额分成"),
},
{
title: "分成参数",
dataIndex: "proportionRate",
align: "center",
render: (text, record) =>
record.proportionMode === 0
? `${(Number(text) * 100).toFixed(1)}%` || "/"
: "/",
},
{
title: "分成用户名称",
dataIndex: "userName",
align: "center",
},
{
title: "分成用户UID",
dataIndex: "uid",
align: "center",
},
{
title: "渠道等级",
dataIndex: "channelName",
align: "center",
render: (text) => text || "无",
},
{
title: "分成积分数额",
dataIndex: "scoreAmount",
align: "center",
},
];
// 扩展表格列
const extendColumns: ColumnsType<TableType[0]> = [
{
title: "其它分成对象",
dataIndex: "proportionObjName",
align: "center",
},
...defaultColumns.slice(1, 5),
...defaultColumns.slice(6),
{
title: "操作",
align: "center",
render: (text, record) => (
<div style={{ display: "flex", justifyContent: "center" }}>
<Button
type="link"
onClick={() => {
setEditData(record);
setAddEditVisible(true);
}}
>
编辑
</Button>
<Button type="link" onClick={() => handleDelete(record)}>
删除
</Button>
</div>
),
},
];
return (
<>
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
width={768}
wrapClassName="divide-setting-modal"
okText={!isEdit ? "确定" : "确定一键发放积分"}
>
<Form
name="addForm"
form={form}
labelAlign="right"
disabled={isEdit}
// layout="inline"
>
<div className="divide-title">一、通用规则</div>
<Form.Item
label="规则名称"
name="ruleName"
rules={[{ required: true, message: "请选择规则名称" }]}
wrapperCol={{ span: 6 }}
>
<Select
placeholder="请选择规则名称"
options={ruleNameList}
allowClear
onChange={(value) => {
if (value) {
getOrderBonusBuildMall(value).then();
} else {
setDefaultTableData([]);
}
}}
/>
</Form.Item>
<Table
size="small"
dataSource={defaultTableData}
columns={defaultColumns}
rowKey="index"
bordered
style={{ marginBottom: "-40px" }}
/>
<div className="divide-title">二、扩展规则</div>
{!isEdit && (
<Button
icon={<PlusOutlined />}
type="primary"
onClick={() => setAddEditVisible(true)}
>
添加分成对象
</Button>
)}
<Table
size="small"
dataSource={extendTableData}
columns={!isEdit ? extendColumns : extendColumns.slice(0, -1)}
rowKey="index"
bordered
style={{ marginTop: "10px" }}
/>
</Form>
</Modal>
<DivideAddEditModal
title={editData ? "编辑分成对象" : "添加分成对象"}
open={addEditVisible}
data={editData}
orderData={data}
closed={() => {
setAddEditVisible(false);
setEditData(undefined);
}}
push={handlePush}
/>
</>
);
};
.divide-setting-modal{
.divide-title {
font-size: 14px;
margin-bottom: 10px;
}
}
import { useEffect, useState } from 'react';
import { ColumnsType } from 'antd/es/table';
import { Button, message, Table } from 'antd';
import SearchView from '~/components/search-box';
import { PointManageAPI } from '~/api';
import { OrderBonusListPageType } from '~/api/interface/pointManageType';
import { DivideSetting } from '~/pages/pointManage/divideOrder/comp/divideSetting';
import './index.scss';
// 列表的类型
type TableType = (ReturnType<OrderBonusListPageType> extends Promise<infer T>
? T
: never)['result']['list'];
type ReqType = Parameters<OrderBonusListPageType>[0];
// 搜索表单的数据
let query: ReqType = {};
// 分成状态
const orderStatus = [
{ label: '未分成', value: 0 },
{ label: '已分成', value: 1 },
];
function DivideOrder() {
// 分成设置弹窗
const [divideSettingVisible, setDivideSettingVisible] = useState(false);
// 分成确认弹窗
const [divideConfirmVisible, setDivideConfirmVisible] = useState(false);
// 编辑数据
const [editData, setEditData] = useState<TableType[0]>();
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.OrderBonusListPage({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
...query,
});
if (res && res.code === '200') {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
// console.log("getTableList --->", list);
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// 表单提交
const onFinish = (data: ReqType) => {
pagination.current = 1;
query = data;
getTableList(data).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
const handleClosed = () => {
setDivideConfirmVisible(false);
setDivideSettingVisible(false);
paginationChange(pagination.current, pagination.pageSize);
};
// 表格结构
const columns: ColumnsType<TableType[0]> = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
width: 80,
render: (_text, _record, index) => (pagination.current - 1) * pagination.pageSize + index + 1,
},
{
title: '订单编号',
dataIndex: 'orderNo',
align: 'center',
},
{
title: '订单名称',
dataIndex: 'orderName',
align: 'center',
},
{
title: '订单金额(元)',
dataIndex: 'realityAmount',
align: 'center',
render: (text) => text.toLocaleString(),
},
{
title: '分成状态',
dataIndex: 'divide',
align: 'center',
render: (text: string) => orderStatus.find((i) => i.value === Number(text))?.label || text,
},
{
title: '买家账号',
dataIndex: 'userName',
align: 'center',
render: (_text, record) => `${record.userName}(${record.uid})`,
},
{
title: '认证企业',
dataIndex: 'entName',
align: 'center',
},
{
title: '操作',
dataIndex: 'id',
align: 'center',
fixed: 'right',
width: 180,
render: (_text, record) => (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button
type='link'
onClick={() => {
setDivideSettingVisible(true);
setEditData(record);
}}
disabled={record.divide === 1}
>
分成设置
</Button>
<Button
type='link'
onClick={() => {
setDivideConfirmVisible(true);
setEditData(record);
}}
disabled={record.divide === 1}
>
确认分成
</Button>
</div>
),
},
];
// componentDidMount
useEffect(() => {
query = {};
(async () => {
await getTableList();
})();
}, []);
return (
<>
<SearchView
search={[
{
label: '订单编号',
name: 'orderNo',
type: 'input',
placeholder: '请输入订单编号',
},
{
label: '分成状态',
name: 'divide',
type: 'Select',
placeholder: '请选择分成状态',
options: orderStatus,
width: 160,
},
{
label: '买家账号',
name: 'keyword',
type: 'input',
placeholder: '请输入买家姓名、昵称、UID',
width: 220,
},
{
label: '买家认证企业',
name: 'entName',
type: 'input',
placeholder: '请输入买家认证企业',
width: 180,
},
]}
searchData={onFinish}
/>
<Table
size='small'
dataSource={tableData}
columns={columns}
rowKey='orderId'
scroll={{ x: 1500 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
<DivideSetting
open={divideSettingVisible || divideConfirmVisible}
title='分成设置'
closed={handleClosed}
data={editData}
isEdit={divideConfirmVisible}
/>
</>
);
}
export default DivideOrder;
import React, { useEffect, useState } from "react";
import {
Button,
Form,
Input,
message,
Modal,
Select,
Switch,
Table,
} from "antd";
import { ColumnsType } from "antd/es/table";
import { PlusOutlined } from "@ant-design/icons";
import DivideAddEditObject from "../divideAddEditObject";
import {
AddAndEditBonusRuleType,
BonusRuleListQueryType,
} from "@/api/interface/pointManageType";
import { PointManageAPI } from "@/api";
// 编辑的数据类型
type DataType = (ReturnType<BonusRuleListQueryType> extends Promise<infer T>
? T
: never)["result"]["list"][0];
type ReqType = Parameters<AddAndEditBonusRuleType>[0];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
isEdit: boolean;
data?: DataType;
}
export const DivideAddEditModal: React.FC<propType> = (props: propType) => {
// 组件默认值
DivideAddEditModal.defaultProps = {
data: { bonusSet: [], id: undefined, ruleName: "" },
};
// 参数
const { title, open, closed, data, isEdit } = props;
// 表单钩子
const [form] = Form.useForm();
// 表格数据
const [tableData, setTableData] = useState<DataType["bonusSet"]>([]);
// 需要编辑的数据
const [editData, setEditData] = useState<any>();
// 添加编辑弹窗
const [addEditVisible, setAddEditVisible] = useState<boolean>(false);
// 分成对象列表
const [propObjCode, setPropObjCode] = useState<
{ label: string; value: number }[]
>([]);
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
closed();
};
// 删除数据
const handleDelete = (record: any) => {
Modal.confirm({
title: "提示",
content: "删除后此数据将会丢失,确定删除吗?",
onOk: () => {
// 删除数据
setTableData(tableData.filter((i) => i.id !== record.id));
message.success("操作成功").then();
},
});
};
// 获取分成对象列表
const getListProportionObject = async () => {
const res = await PointManageAPI.ListProportionObject();
if (res && res.code === "200") {
setPropObjCode(
res.result?.map((i) => {
return { label: i.name, value: i.code };
})
);
}
};
// 追加列表数据
const handlePush = (val: DataType["bonusSet"][0]) => {
// 原来列表中没有的数据就追加,有的话就替换
if (tableData.some((i) => val?.id === i.id)) {
setTableData(
tableData.map((i) => {
if (i.id === val.id) {
return val;
}
return i;
})
);
} else {
setTableData([...tableData, val]);
}
};
// 校验数据
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
await handleSubmit({
...data,
...values,
bonusSetList: tableData.map((i) => {
const id = data?.bonusSet?.some((j) => j.id === i.id);
return id ? i : { ...i, id: null };
}),
});
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (val: ReqType) => {
const res = await PointManageAPI.AddAndEditBonusRule(val);
if (res && res.code === "200") {
message.success("操作成功").then();
closed();
}
};
// 通用表格列
const defaultColumns: ColumnsType<DataType["bonusSet"][0]> = [
{
title: "分成对象",
dataIndex: "propObjCode",
align: "center",
render: (text) =>
propObjCode.find((i) => i.value === text)?.label || text,
},
{
title: "分成方式",
dataIndex: "proportionMode",
align: "center",
render: (text) => (text === 0 ? "比例分成" : "差价分成"),
},
{
title: "分成参数",
dataIndex: "proportionParam",
align: "center",
render: (text, record) =>
record.proportionMode === 0
? `${(Number(text) * 100).toFixed(1)}%` || "/"
: "/",
},
{
title: "操作",
dataIndex: "id",
align: "center",
width: 100,
render: (text, record) => (
<div style={{ display: "flex", justifyContent: "center" }}>
<Button
type="link"
onClick={() => {
setEditData(record);
setAddEditVisible(true);
}}
>
编辑
</Button>
<Button type="link" onClick={() => handleDelete(record)}>
删除
</Button>
</div>
),
},
];
useEffect(() => {
if (!open) return;
(async () => {
await getListProportionObject();
})();
if (!data) return;
form.setFieldsValue({ ...data, defaultRule: data.defaultRule === 0 });
setTableData(data.bonusSet);
}, [open]);
return (
<>
<Modal
open={open}
title={title}
onCancel={handleCancel}
// onOk={handleOk}
destroyOnClose
width={768}
wrapClassName="divide-setting-modal"
footer={
!isEdit ? (
<>
<Button onClick={handleCancel}>取消</Button>
<Button type="primary" onClick={handleOk}>
确定
</Button>
</>
) : null
}
>
<Form
name="addForm"
form={form}
labelAlign="right"
disabled={isEdit}
// layout="inline"
>
<div className="divide-title">一、基本信息</div>
<Form.Item
label="规则名称"
name="ruleName"
rules={[{ required: true, message: "请输入规则名称" }]}
wrapperCol={{ span: 6 }}
>
<Input placeholder="请输入规则名称" maxLength={20} allowClear />
</Form.Item>
<div className="divide-title">二、分成设置</div>
{!isEdit && (
<Button
icon={<PlusOutlined />}
type="primary"
onClick={() => setAddEditVisible(true)}
>
添加分成对象
</Button>
)}
<Table
size="small"
dataSource={tableData}
columns={!isEdit ? defaultColumns : defaultColumns.slice(0, -1)}
rowKey="id"
bordered
style={{ margin: "10px 0 -40px 0" }}
/>
<Form.Item
label="是否默认规则"
name="defaultRule"
wrapperCol={{ span: 6 }}
valuePropName="checked"
initialValue={false}
>
<Switch checkedChildren="是" unCheckedChildren="否" />
</Form.Item>
</Form>
</Modal>
<DivideAddEditObject
title={editData ? "编辑分成对象" : "添加分成对象"}
open={addEditVisible}
data={editData}
closed={() => {
setAddEditVisible(false);
setEditData(null);
}}
state={{ propObjCode }}
push={handlePush}
selected={tableData}
/>
</>
);
};
import { Form, Input, message, Modal, Radio, Select } from "antd";
import React, { useEffect, useState } from "react";
import { BonusRuleListQueryType } from "@/api/interface/pointManageType";
// 编辑的数据类型
type DataType = (ReturnType<BonusRuleListQueryType> extends Promise<infer T>
? T
: never)["result"]["list"][0];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
push: any;
data?: any;
state?: { propObjCode?: { label: string; value: number }[] };
selected: DataType["bonusSet"];
}
const DivideAddEditObject: React.FC<propType> = (props: propType) => {
// 组件默认值
DivideAddEditObject.defaultProps = {
data: null,
state: {},
};
// 参数
const { title, open, closed, data, state, push, selected } = props;
// 表单钩子
const [form] = Form.useForm();
// 分成方式
const [divideMethod, setDivideMethod] = useState(0);
// 分成方式列表
const [proportionModeList, setProportionModeList] = useState<
{ label: string; value: number; disabled?: true }[]
>([
{ label: "分成比例", value: 0 },
{ label: "差价分成", value: 1, disabled: true },
]);
// 关闭弹窗
const handleCancel = () => {
// 重置表单
form.resetFields();
// 重置分成方式
setDivideMethod(proportionModeList[0].value);
// 重置分成对象
form.setFieldValue("proportionMode", 0);
// 重置分成方式列表
setProportionModeList([
{ label: "分成比例", value: 0 },
{ label: "差价分成", value: 1, disabled: true },
]);
// 关闭弹窗
closed();
};
// 校验数据
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
// 处理数据并准备提交数据
await handleSubmit({
...values,
proportionParam: Number(values.proportionParam) / 100,
});
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (values: any) => {
// 添加数据的时候用时间戳当id
push({
id: new Date().getTime(),
...data,
...values,
});
message.success(data?.id ? "修改成功" : "添加成功").then();
handleCancel();
};
// 分成对象选择渠道监听
const handleChange = (e: number) => {
if (e === 300) {
setProportionModeList([
{ label: "分成比例", value: 0, disabled: true },
{ label: "差价分成", value: 1 },
]);
form.setFieldValue("proportionMode", proportionModeList[1].value);
setDivideMethod(proportionModeList[1].value);
} else {
setProportionModeList([
{ label: "分成比例", value: 0 },
{ label: "差价分成", value: 1, disabled: true },
]);
form.setFieldValue("proportionMode", proportionModeList[0].value);
setDivideMethod(proportionModeList[0].value);
}
};
// componentDidMount
useEffect(() => {
if (!open) return;
// console.log("selected --->", selected);
if (!data) return;
form.setFieldsValue({
...data,
proportionParam: (Number(data.proportionParam) * 100).toFixed(1),
});
setDivideMethod(data.proportionMode);
}, [open]);
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
>
<Form
name="addForm"
form={form}
labelAlign="right"
labelCol={{ span: 5 }}
wrapperCol={{ span: 10 }}
>
<Form.Item
label="分成对象"
name="propObjCode"
rules={[{ required: true, message: "请选择分成对象" }]}
>
<Select
placeholder="请选择分成对象"
options={state?.propObjCode?.map((i) => ({
label: i.label,
value: i.value,
disabled: selected.some((j) => j.propObjCode === i.value),
}))}
onChange={handleChange}
/>
</Form.Item>
<Form.Item
label="分成方式"
name="proportionMode"
rules={[{ required: true, message: "请选择分成方式" }]}
initialValue={divideMethod}
>
<Radio.Group
options={proportionModeList}
onChange={({ target: { value } }) => setDivideMethod(value)}
/>
</Form.Item>
{divideMethod === 0 && (
<>
<Form.Item
label="分成参数"
name="proportionParam"
rules={[
{ required: true, message: "请输入分成额度百分比" },
// 只能输入0.1到100.0的一位小数,不能大于100.0
{
pattern: /^(100(\.0)?|[1-9]?\d(\.\d)?)$/,
message: "只能输入0.1到100.0的一位小数",
},
]}
>
<Input
placeholder="请输入分成额度百分比"
maxLength={10}
type="number"
suffix="%"
/>
</Form.Item>
<div style={{ margin: "0 0 10px 60px" }}>
说明:分成比例基数以订单金额减去相关渠道利润的得数为基准
</div>
</>
)}
</Form>
</Modal>
);
};
export default DivideAddEditObject;
import React, { useEffect, useState } from 'react';
import { Button, message, Modal, Table } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/es/table';
import SearchView from '~/components/search-box';
import { BonusRuleListQueryType } from '~/api/interface/pointManageType';
import { PointManageAPI } from '~/api';
import '../divideOrder/index.scss';
import { DivideAddEditModal } from '~/pages/pointManage/divideRules/comp/divideAddEditModal';
// 列表的类型
type TableType = (ReturnType<BonusRuleListQueryType> extends Promise<infer T>
? T
: never)['result']['list'];
type ReqType = Parameters<BonusRuleListQueryType>[0];
// 搜索表单的数据
let query: ReqType = {};
function DivideRules() {
// 查看规则弹窗
const [divideViewVisible, setDivideViewVisible] = useState(false);
// 查看规则弹窗
const [divideEditVisible, setDivideEditVisible] = useState(false);
// 编辑数据
const [editData, setEditData] = useState<TableType[0]>();
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.BonusRuleListQuery({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
...query,
});
if (res && res.code === '200') {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
// console.log("getTableList --->", list);
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// 表单提交
const onFinish = (data: ReqType) => {
pagination.current = 1;
query = data;
getTableList(data).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
const handleClosed = () => {
paginationChange(pagination.current, pagination.pageSize);
setDivideEditVisible(false);
setDivideViewVisible(false);
setEditData(undefined);
};
// 删除数据
const handleDelete = (record: TableType[0]) => {
Modal.confirm({
title: '提示',
content: '删除后此数据将会丢失,确定删除吗?',
onOk: async () => {
const res = await PointManageAPI.RemoveBonusRule({ id: record.id });
if (res && res.code === '200') {
message.success('操作成功').then();
paginationChange(
tableData.length === 1 ? pagination.current - 1 : pagination.current,
pagination.pageSize,
);
}
},
});
};
// 表格结构
const columns: ColumnsType<TableType[0]> = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
width: 80,
render: (_text, _record, index) => (pagination.current - 1) * pagination.pageSize + index + 1,
},
{
title: '规则名称',
dataIndex: 'ruleName',
align: 'center',
width: 300,
},
{
title: '分成对象',
dataIndex: 'proportionObject',
align: 'center',
ellipsis: true,
},
{
title: '是否为默认规则',
dataIndex: 'defaultRule',
align: 'center',
width: 200,
render: (text) => (text === 1 ? '否' : '是'),
},
{
title: '操作',
dataIndex: 'id',
align: 'center',
fixed: 'right',
width: 180,
render: (_text, record) => (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button
type='link'
onClick={() => {
setEditData(record);
setDivideViewVisible(true);
}}
>
查看
</Button>
<Button
type='link'
onClick={() => {
setEditData(record);
setDivideEditVisible(true);
}}
>
编辑
</Button>
<Button type='link' onClick={() => handleDelete(record)}>
删除
</Button>
</div>
),
},
];
// componentDidMount
useEffect(() => {
query = {};
(async () => {
await getTableList();
})();
}, []);
return (
<>
<SearchView
search={[
{
label: '规则名称',
name: 'ruleName',
type: 'input',
placeholder: '请输入规则名称',
width: 180,
},
]}
sufFixBtn={
<Button icon={<PlusOutlined />} type='primary' onClick={() => setDivideEditVisible(true)}>
新建规则
</Button>
}
searchData={onFinish}
/>
<Table
size='small'
dataSource={tableData}
columns={columns}
rowKey='id'
scroll={{ x: 1000 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
<DivideAddEditModal
title={divideViewVisible ? '查看规则' : editData ? '编辑规则' : '新建规则'}
open={divideViewVisible || divideEditVisible}
closed={handleClosed}
data={editData}
isEdit={divideViewVisible}
/>
</>
);
}
export default DivideRules;
import { Descriptions, Modal } from "antd";
import React from "react";
import { GetScoreListType } from "@/api/interface/pointManageType";
// 列表的类型
type TableType = (ReturnType<GetScoreListType> extends Promise<infer T>
? T
: never)["result"]["list"];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: TableType[0];
}
const BankCardModal: React.FC<propType> = (props) => {
BankCardModal.defaultProps = {
data: undefined,
};
const { title, open, closed, data } = props;
// 关闭弹窗
const handleCancel = () => {
closed();
};
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
// onOk={handleOk}
footer={null}
destroyOnClose
>
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="银行卡号">
{data?.bankCardNumber}
</Descriptions.Item>
<Descriptions.Item label="开户银行">
{data?.accountBank}
</Descriptions.Item>
<Descriptions.Item label="支行">{data?.branch}</Descriptions.Item>
<Descriptions.Item label="开户姓名">
{data?.accountName}
</Descriptions.Item>
</Descriptions>
</Modal>
);
};
export default BankCardModal;
import { useEffect, useState } from 'react';
import qs from 'query-string';
import { Button, message, Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { TableDetailView } from '~/components/tableDetailView';
import { GetScoreListType } from '~/api/interface/pointManageType';
import { PointManageAPI } from '~/api';
import { ApproveModal } from '~/pages/pointManage/pointList/comp/approveModal';
import SearchView from '~/components/search-box';
import BankCardModal from './comp/bankCardModal';
// 列表的类型
type TableType = (ReturnType<GetScoreListType> extends Promise<infer T>
? T
: never)['result']['list'];
// 请求的参数
type ReqType = Parameters<GetScoreListType>[0];
// 表单提交
let query: ReqType = {};
// 提现状态
const withdrawStatus = [
{ label: '处理中', value: 0 },
{ label: '已提现', value: 1 },
{ label: '提现失败', value: 2 },
];
function PointDetailList(props: { location: { search: string } }) {
// 参数解析
const option = qs.parse(props.location.search);
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// 查看参数
const [recordData, setRecordData] = useState<TableType[0]>();
// 审批弹窗
const [approveVisible, setApproveVisible] = useState(false);
// 银行卡弹窗
const [bankCardVisible, setBankCardVisible] = useState(false);
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.GetScoreList({
type: option.type ? Number(option.type) : 0,
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
...query,
});
if (res && res.code === '200') {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// 表单提交
const onFinish = (data: any) => {
const obj = {
...data,
};
pagination.current = 1;
query = obj;
getTableList({
type: option.type ? option.type : 0,
...obj,
}).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// componentDidMount
useEffect(() => {
query = {};
getTableList().then();
}, []);
// 类型列表
const typeList: {
label: string;
value: string;
columns: ColumnsType<TableType[0]>;
}[] = [
{
label: '积分发放明细',
value: '0',
columns: [
{ title: 'UID', dataIndex: 'uid', align: 'center' },
{
title: '企业名称',
dataIndex: 'entName',
align: 'center',
render: (text: string, record) => text || (record.phoneNum ? '微信用户' : '游客用户'),
},
{ title: '手机号', dataIndex: 'phoneNum', align: 'center' },
{ title: '发放积分', dataIndex: 'releaseScore', align: 'center' },
{ title: '发放时间', dataIndex: 'releaseTime', align: 'center' },
],
},
{
label: '积分兑换明细',
value: '1',
columns: [
{ title: 'UID', dataIndex: 'uid', align: 'center' },
{
title: '企业名称',
dataIndex: 'entName',
align: 'center',
render: (text: string, record) => text || (record.phoneNum ? '微信用户' : '游客用户'),
},
{ title: '手机号', dataIndex: 'phoneNum', align: 'center' },
{ title: '兑换积分', dataIndex: 'convertScore', align: 'center' },
{
title: '兑换比例',
align: 'center',
render: (_text, record) => `${record.score}:${record.coupon}`,
},
{ title: '发放时间', dataIndex: 'convertTime', align: 'center' },
],
},
{
label: '积分提现明细',
value: '2',
columns: [
{ title: 'UID', dataIndex: 'uid', align: 'center' },
{
title: '用户名称',
dataIndex: 'userName',
align: 'center',
render: (text, record) => text || record?.nickName || `游客用户`,
width: 80,
},
{
title: '企业名称',
dataIndex: 'entName',
align: 'center',
render: (text: string, record) => text || (record.phoneNum ? '微信用户' : '游客用户'),
},
{ title: '手机号', dataIndex: 'phoneNum', align: 'center' },
{ title: '提现积分', dataIndex: 'withdrawScore', align: 'center' },
{
title: '银行卡',
dataIndex: 'accountBank',
align: 'center',
render: (text: string, record) => (
<>
{text ? (
<Button
type='link'
title='查看银行卡'
onClick={() => {
setRecordData(JSON.parse(JSON.stringify(record)));
setBankCardVisible(true);
}}
>
{`${text} (${record?.bankCardNumber?.slice(-4)})`}
</Button>
) : (
'未绑定'
)}
</>
),
width: 100,
},
{
title: '状态',
dataIndex: 'status',
align: 'center',
render: (text: number) => withdrawStatus?.find((i) => i.value === text)?.label || text,
},
{ title: '提现时间', dataIndex: 'withdrawTime', align: 'center' },
{
title: '操作',
align: 'center',
render: (_text, record) => (
<>
<Button
type='link'
onClick={() => {
setRecordData(JSON.parse(JSON.stringify(record)));
setApproveVisible(true);
}}
disabled={record.status !== 0}
>
审批
</Button>
</>
),
},
],
},
];
return (
<>
<TableDetailView
isBack
onCancel={() => {
history.go(-1);
}}
>
<div className='detail-table-label'>
{typeList?.find((i) => i.value === (option.type || 0))?.label || '积分发放明细'}
</div>
{option && option.type === '2' && (
<SearchView
search={[
{
label: '兑换状态',
name: 'status',
type: 'select',
placeholder: '请选择状态',
options: withdrawStatus.map((i) => {
return { id: i.value, name: i.label };
}),
},
]}
searchData={onFinish}
/>
)}
<Table
size='small'
dataSource={tableData}
columns={
typeList?.find((i) => i.value === (option.type || 0))?.columns || typeList[0].columns
}
rowKey='id'
// scroll={{ x: 1500 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
</TableDetailView>
<ApproveModal
open={approveVisible}
data={{
withdrawId: recordData?.id,
...recordData,
}}
title='提现审批'
closed={() => {
setApproveVisible(false);
setRecordData(undefined);
getTableList({
type: option.type ? option.type : 0,
}).then();
}}
/>
<BankCardModal
open={bankCardVisible}
data={recordData}
title='查看银行卡'
closed={() => {
setBankCardVisible(false);
setRecordData(undefined);
}}
/>
</>
);
}
export default PointDetailList;
import React, { useEffect, useState } from "react";
import {
Descriptions,
Form,
Input,
InputNumber,
message,
Modal,
Select,
Space,
} from "antd";
import {
GetUserScoreDetailsType,
UserScoreDetailsListType,
} from "@/api/interface/pointManageType";
import { PointManageAPI } from "@/api";
import { phoneNumber } from "@/utils/validateUtils";
// 列表的类型
type TableType = (ReturnType<UserScoreDetailsListType> extends Promise<infer T>
? T
: never)["result"]["list"]["userScoreList"];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: TableType[0];
}
// 状态类型
const statusList = [
{ value: 0, label: "提现中" },
{ value: 1, label: "提现成功" },
{ value: 2, label: "提现失败" },
];
export const ApproveModal: React.FC<propType> = (props) => {
ApproveModal.defaultProps = {
data: undefined,
};
// 参数
const { title, open, closed, data } = props;
/// 表单钩子
const [form] = Form.useForm();
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
closed();
};
// 提交弹窗
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
await handleSubmit(values);
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (values: any) => {
const res = await PointManageAPI.CheckUserScore({
withdrawDetailsId: data?.withdrawId,
flag: values.flag,
});
if (res && res.code === "200") {
message.success("操作成功");
handleCancel();
} else {
message.warning(res.message);
}
};
// 转换状态
const transStatus = (id: number) => {
return statusList.find((i) => i.value === id)?.label || id;
};
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
width={400}
>
<Form
name="addForm"
form={form}
labelAlign="right"
labelCol={{ span: 8 }}
// layout="inline"
>
<Form.Item label="当前状态">
<div>{transStatus(data?.status as number)}</div>
</Form.Item>
<Form.Item
label="审批结果"
name="flag"
rules={[{ required: true, message: "请选择审批结果" }]}
>
<Select
placeholder="请选择审批结果"
options={[
{ value: true, label: "通过" },
{ value: false, label: "不通过" },
]}
allowClear
/>
</Form.Item>
</Form>
</Modal>
);
};
import React, { useEffect, useState } from "react";
import { Descriptions, Modal } from "antd";
import {
GetUserScoreDetailsType,
UserScoreDetailsListType,
} from "@/api/interface/pointManageType";
import { PointManageAPI } from "@/api";
// 列表的类型
type TableType = (ReturnType<UserScoreDetailsListType> extends Promise<infer T>
? T
: never)["result"]["list"]["userScoreList"];
type DetailType = (ReturnType<GetUserScoreDetailsType> extends Promise<infer T>
? T
: never)["result"];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: TableType[0];
}
const contentStyle = { width: "180px" };
// 状态
const statusList = [
{ value: 0, label: "提现中" },
{ value: 1, label: "提现成功" },
{ value: 2, label: "提现失败" },
];
export const DetailModal: React.FC<propType> = (props) => {
DetailModal.defaultProps = {
data: undefined,
};
// 参数
const { title, open, closed, data } = props;
// 用户数据
const [detailData, setDetailData] = useState<DetailType>();
// 关闭弹窗
const handleCancel = () => {
closed();
};
// 状态转换
const transStatus = (id: number) => {
return statusList.find((i) => i.value === id)?.label || id;
};
// 获取详情数据
const getUserScoreDetails = async () => {
const res = await PointManageAPI.GetUserScoreDetails({
id: Number(data?.id),
});
if (res && res.code === "200") {
setDetailData(res.result);
// console.log(res.result);
}
};
// 获取使用时间
const getUseTime = (record: DetailType) => {
if (detailData?.type === "积分提现" && detailData?.status === 0) {
return "";
}
if (detailData?.type === "积分提现" && detailData?.status === 2) {
return "";
}
if (detailData?.type === "积分提现" && detailData?.status === 1) {
return record?.approvalTime;
}
return record?.useTime;
};
// 获取申请提现时间
const getApplyTime = (record: DetailType) => {
if (detailData?.type === "积分提现" && detailData?.status === 0) {
return record?.useTime;
}
if (["积分转赠", "积分兑换"].includes(detailData?.type || "")) {
return "";
}
return record?.useTime;
};
useEffect(() => {
if (!data) return;
(async () => {
await getUserScoreDetails();
})();
}, [data]);
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
footer={null}
destroyOnClose
width={650}
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item contentStyle={contentStyle} label="积分数值">
{detailData?.scoreNum &&
(detailData?.scoreNum > 0
? `+${detailData?.scoreNum}`
: detailData?.scoreNum)}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="流通方式">
{detailData?.type}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="订单编号">
{detailData?.orderNo}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="兑换比例">
{detailData?.convertRatio}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="领取时间">
{detailData?.getTime}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="使用时间">
{detailData && getUseTime(detailData)}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="状态">
{transStatus(detailData?.status as number)}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="申请提现时间">
{detailData && getApplyTime(detailData)}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="获赠方UID">
{detailData?.giveAwayToUid}
</Descriptions.Item>
<Descriptions.Item contentStyle={contentStyle} label="转赠方UID">
{detailData?.gainFormUid}
</Descriptions.Item>
</Descriptions>
</Modal>
);
};
import React, { useEffect, useState } from "react";
import {
Descriptions,
Form,
Input,
InputNumber,
message,
Modal,
Select,
Space,
} from "antd";
import { UserScoreDetailsListType } from "@/api/interface/pointManageType";
import { PointManageAPI } from "@/api";
import { maxLength9, noSpaceFront } from "@/utils/validateUtils";
// 列表的类型
type DetailType = (ReturnType<UserScoreDetailsListType> extends Promise<infer T>
? T
: never)["result"]["list"];
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: DetailType;
}
export const DistributionModal: React.FC<propType> = (props) => {
DistributionModal.defaultProps = {
data: undefined,
};
// 参数
const { title, open, closed, data } = props;
/// 表单钩子
const [form] = Form.useForm();
// 流通方式选择
const [circulateId, setCirculateId] = useState<number>();
// 流通方式列表
const [circulateIdList, setCirculateIdList] =
useState<{ label: string; value: number }[]>();
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
setCirculateId(undefined);
closed();
};
// 提交弹窗
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
if (Number(values.score) === 0) {
return message.warning("积分数值不能为0");
}
if (values.orderNo) {
// 从订单列表查询订单
const res = await PointManageAPI.GetOrderInfoByOrderNo({
orderNo: values.orderNo,
});
if (res && res.code === "200") {
const { id } = res.result;
if (!id) {
return message.warning("订单编号不存在");
}
await handleSubmit({
...values,
orderId: id,
});
}
} else {
await handleSubmit(values);
}
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (values: any) => {
const res = await PointManageAPI.OrderScoreRelease({
circulateId: values.circulateId,
mallUserId: data?.mallUserId as number,
orderNo: values.orderNo,
orderId: values.orderId,
score: Number(`${values.symbol}${values.score}`),
});
if (res && res.code === "200") {
message.success("操作成功");
handleCancel();
} else {
message.warning(res.message);
}
};
// 获取流通数据列表
const getListCirculateInfo = async () => {
const res = await PointManageAPI.ListCirculateInfo({});
if (res && res.code === "200") {
setCirculateIdList(
res.result
.map((i) => {
return { label: i.type, value: i.id };
})
.filter((i) => [5, 7].includes(i.value))
);
}
};
// 流通方式点击事件
const handleCirculateId = (e: number) => {
setCirculateId(e);
};
// 组件加载
useEffect(() => {
if (!open) return;
(async () => {
await getListCirculateInfo();
})();
}, [open]);
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
width={400}
>
<Form
name="addForm"
form={form}
labelAlign="right"
labelCol={{ span: 8 }}
// layout="inline"
>
<Form.Item label="发放积分数值" required>
<Space.Compact style={{ width: "100%" }}>
<Form.Item name="symbol" noStyle initialValue="+">
<Select>
<Select.Option value="+">+</Select.Option>
<Select.Option value="-">-</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="score"
noStyle
rules={[{ required: true, message: "请输入发放积分数值" }]}
>
<Input
style={{ width: "250%" }}
placeholder="请输入发放积分数值"
onInput={maxLength9}
/>
</Form.Item>
</Space.Compact>
</Form.Item>
<Form.Item
label="流通方式"
name="circulateId"
rules={[{ required: true, message: "请选择流通方式" }]}
>
<Select
placeholder="请选择流通方式"
options={circulateIdList}
allowClear
value={circulateId}
onChange={handleCirculateId}
/>
</Form.Item>
{circulateId === 5 && (
<Form.Item
label="订单编号"
name="orderNo"
rules={[{ required: true, message: "请输入订单编号" }]}
>
<Input
placeholder="请输入订单编号"
maxLength={20}
allowClear
onInput={noSpaceFront}
/>
</Form.Item>
)}
</Form>
</Modal>
);
};
import React, { useEffect, useState } from 'react';
import { Button, message, Table } from 'antd';
import qs from 'query-string';
import { PlusOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/es/table';
import { useNavigate } from 'react-router-dom';
import SearchView from '~/components/search-box';
import { UserScoreDetailsListType } from '~/api/interface/pointManageType';
import { PointManageAPI } from '~/api';
import { DetailModal } from '../comp/detailModal';
import { DistributionModal } from '~/pages/pointManage/pointList/comp/distributionModal';
import { ApproveModal } from '~/pages/pointManage/pointList/comp/approveModal';
import useOperate from '~/common/hook/optionHook';
import { useSearchParams } from 'react-router-dom';
// 列表的类型
type TableType = (ReturnType<UserScoreDetailsListType> extends Promise<infer T>
? T
: never)['result']['list']['userScoreList'];
// 后台返回的类型,但不是列表
type ListType = (ReturnType<UserScoreDetailsListType> extends Promise<infer T>
? T
: never)['result']['list'];
// 状态类型
const statusList = [
{ value: 0, label: '提现中' },
{ value: 1, label: '提现成功' },
{ value: 2, label: '提现失败' },
];
export function PointDetail(props: { location: { search: string } }) {
const [searchParams] = useSearchParams();
// 参数解析
const option = qs.parse(props.location.search);
// 路由操作
const navigate = useNavigate();
// 金额数据
const [pointData, setPointData] = useState<ListType>();
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// 查看参数
const [recordData, setRecordData] = useState<TableType[0]>();
// 显示查看弹窗
const [detailVisible, setDetailVisible] = useState(false);
// 积分发放弹窗
const [distributionVisible, setDistributionVisible] = useState(false);
// 审批弹窗
const [approveVisible, setApproveVisible] = useState(false);
// 按钮权限
const isDistributePointBtnShow = useOperate(25101);
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.UserScoreDetailsList({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
mallUserId: Number(option.id),
});
if (res && res.code === '200') {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(list.userScoreList);
setPointData(res?.result?.list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 表格结构
const columns: ColumnsType<TableType[0]> = [
{
title: '积分数值',
dataIndex: 'scoreNum',
align: 'center',
render: (text) => (text > 0 ? `+${text}` : text),
},
{ title: '流通方式', dataIndex: 'type', align: 'center' },
{ title: '兑换比例', dataIndex: 'convertRatio', align: 'center' },
{ title: '领取时间', dataIndex: 'getTime', align: 'center' },
{
title: '使用时间',
dataIndex: 'useTime',
align: 'center',
render: (_text, record) => {
if (record?.type === '积分提现' && record?.status === 0) {
return '';
}
if (record?.type === '积分提现' && record?.status === 2) {
return '';
}
if (record?.type === '积分提现' && record?.status === 1) {
return record?.approvalTime;
}
return record?.useTime;
},
},
{
title: '订单编号',
dataIndex: 'orderNo',
align: 'center',
render: (text, record) =>
!!text && (
<Button
type='link'
onClick={() => {
// 跳转到订单列表
navigate({
pathname: '/orderManage/list/detail',
search: qs.stringify({ id: record.orderId, activeTabKey: 1 }),
});
}}
>
{text}
</Button>
),
},
{
title: '状态',
dataIndex: 'status',
align: 'center',
render: (text) => statusList.find((i) => i.value === text)?.label || text,
},
{
title: '操作',
align: 'center',
fixed: 'right',
width: 180,
render: (_text, record) => {
return (
<>
{record.status === 0 && (
<Button
type='link'
onClick={() => {
setRecordData(JSON.parse(JSON.stringify(record)));
setApproveVisible(true);
}}
>
审批
</Button>
)}
<Button
type='link'
onClick={() => {
setRecordData(JSON.parse(JSON.stringify(record)));
setDetailVisible(true);
}}
>
查看
</Button>
</>
);
},
},
];
// 一键关闭全部弹窗
const handleCloseAll = () => {
setDetailVisible(false);
setDistributionVisible(false);
setApproveVisible(false);
setRecordData(undefined);
getTableList().then();
};
// 组件加载
useEffect(() => {
console.log('获取参数 --->', searchParams.get('id'));
(async () => {
await getTableList();
})();
}, []);
return (
<>
<SearchView
preFixBtn={
<div>
{pointData?.uid}&nbsp;{pointData?.userName}({pointData?.nickName})
</div>
}
sufFixBtn={
<div className='point-list-head-number'>
<div className='head-number'>
<span className='number-label'>积分总额</span>
<Button type='link'>{pointData?.scoreTotal}</Button>
</div>
<div className='head-number'>
<span className='number-label'>已使用积分</span>
<Button type='link'>{pointData?.useScore}</Button>
</div>
<div className='head-number'>
<span className='number-label'>已兑换积分</span>
<Button type='link'>{pointData?.convertScore}</Button>
</div>
<div className='head-number'>
<Button
type='primary'
icon={<PlusOutlined />}
onClick={() => {
setDistributionVisible(true);
}}
disabled={!isDistributePointBtnShow}
>
积分发放
</Button>
</div>
</div>
}
/>
<Table
size='small'
dataSource={tableData}
columns={columns}
rowKey='id'
scroll={{ x: 1500 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
<DetailModal
open={detailVisible}
data={recordData}
title='查看详情'
closed={handleCloseAll}
/>
<DistributionModal
open={distributionVisible}
data={pointData}
title='发放积分'
closed={handleCloseAll}
/>
<ApproveModal
open={approveVisible}
data={recordData}
title='提现审批'
closed={handleCloseAll}
/>
</>
);
}
import React, { useEffect, useState } from 'react';
import { Button, message, Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
import SearchView from '~/components/search-box/index';
import { PointManageAPI } from '~/api';
import { UserScorePageListType } from '~/api/interface/pointManageType';
import { useNavigate } from 'react-router-dom';
// 列表的类型
type TableType = (ReturnType<UserScorePageListType> extends Promise<infer T>
? T
: never)['result']['list']['userBasicInfo'];
// 后台返回的类型,但不是列表
type ListType = (ReturnType<UserScorePageListType> extends Promise<infer T>
? T
: never)['result']['list'];
// 搜索表单的类型
type ReqType = Parameters<UserScorePageListType>[0];
// 搜索表单的数据
let query: ReqType = {};
export const PointList = () => {
// 路由操作
const navigation = useNavigate();
// 金额数据
const [pointData, setPointData] = useState<ListType>();
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// 跳转指定明细详情
const handleDetailList = (type: number) => {
navigation(`/pointManage/pointList/list?type=${type}`);
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.UserScorePageList({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
...query,
});
if (res && res.code === '200') {
const {
list: { convertScore, releaseScore, userBasicInfo, withdrawScore, withdrawingScore },
pageNo,
totalCount,
pageSize,
totalPage,
} = res.result; // 解构
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(userBasicInfo);
setPointData(res?.result?.list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// 表单提交
const onFinish = (data: ReqType) => {
pagination.current = 1;
query = data;
getTableList(data).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 跳转详情
const handleDetail = (record: TableType[0]) => {
navigation({
pathname: '/pointManage/pointList/detail',
search: `id=${record?.mallUserId}&uid=${record?.uid}`,
});
};
// 表格结构
const columns: ColumnsType<TableType[0]> = [
{ title: 'UID', dataIndex: 'uid', align: 'center' },
{
title: '用户名称',
dataIndex: 'userName',
align: 'center',
render: (text, record) => text || record?.nickName || `游客用户`,
},
{
title: '企业名称',
dataIndex: 'entName',
align: 'center',
render: (text: string, record) => text || (record.phoneNum ? '微信用户' : '游客用户'),
},
{
title: '手机号',
dataIndex: 'phoneNum',
align: 'center',
},
{
title: '积分总额',
dataIndex: 'score',
align: 'center',
render: (text: string, record) => {
return (
<>
<Button type='link' onClick={() => handleDetail(record)}>
{text}
</Button>
</>
);
},
},
];
useEffect(() => {
query = {};
(async () => {
await getTableList();
})();
}, []);
return (
<>
<SearchView
search={[
{
label: '用户UID',
name: 'uid',
type: 'input',
placeholder: '请输入UID',
width: 180,
},
{
label: '手机号',
name: 'phoneNum',
type: 'input',
placeholder: '请输入手机号',
},
{
label: '企业名称',
name: 'entName',
type: 'input',
placeholder: '请输入企业名称',
},
]}
searchData={onFinish}
sufFixBtn={
<div className='point-list-head-number'>
<div className='head-number'>
<span className='number-label'>积分发放总额</span>
<Button type='link' onClick={() => handleDetailList(0)}>
{pointData?.releaseScore}
</Button>
</div>
<div className='head-number'>
<span className='number-label'>积分兑换总额</span>
<Button type='link' onClick={() => handleDetailList(1)}>
{pointData?.convertScore}
</Button>
</div>
<div className='head-number'>
<span className='number-label'>积分提现总额</span>
<Button type='link' onClick={() => handleDetailList(2)}>
{pointData?.withdrawScore}
</Button>
</div>
<div className='head-number'>
<span className='number-label'>待处理提现积分</span>
<Button type='link' onClick={() => handleDetailList(2)}>
{pointData?.withdrawingScore}
</Button>
</div>
</div>
}
/>
<Table
size='small'
dataSource={tableData}
columns={columns}
rowKey='mallUserId'
// scroll={{ x: 1500 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / ${total} 条数据`,
}}
/>
</>
);
};
import React, { useState } from "react";
import { DatePicker, Form, Input, message, Modal, Radio } from "antd";
import moment from "dayjs";
import { PointManageAPI } from "@/api";
import { maxLength8, maxString8 } from "@/utils/validateUtils";
// 传参类型
interface propType {
title: string;
open: boolean;
closed: any;
data?: any;
}
export const AddEditModal: React.FC<propType> = (props: propType) => {
// 组件默认值
AddEditModal.defaultProps = {
data: null,
};
// 参数
const { title, open, closed, data } = props;
// 表单钩子
const [form] = Form.useForm();
// 生效时间单选
const [radioValue, setRadioValue] = useState(0);
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
setRadioValue(0);
closed();
};
// 提交弹窗
const handleOk = () => {
form
.validateFields()
.then(async (values) => {
await handleSubmit(values);
})
.catch((err) => {
message
.warning({
content: err.errorFields[0].errors[0],
})
.then();
});
};
// 提交数据
const handleSubmit = async (values: any) => {
const res = await PointManageAPI.AddConvertRule({
coupon: Number(values.coupon),
entryIntoForceTime:
values.ruleSetting === 1
? moment(values.entryIntoForceTime.$d).format("YYYY-MM-DD HH:mm:ss")
: undefined,
ruleName: values.ruleName,
score: Number(values.score),
});
if (res && res.code === "200") {
handleCancel();
}
};
return (
<Modal
open={open}
title={title}
onCancel={handleCancel}
onOk={handleOk}
destroyOnClose
>
<Form
name="addForm"
form={form}
labelAlign="right"
// layout="inline"
>
<Form.Item
label="规则名称"
name="ruleName"
rules={[{ required: true, message: "请输入规则名称" }]}
>
<Input placeholder="请输入规则名称" maxLength={15} allowClear />
</Form.Item>
<Form.Item label="兑换比例(积分:券额)" required>
<Form.Item
name="score"
rules={[{ required: true, message: "请输入积分比例" }]}
style={{ display: "inline-block", width: "calc(50% - 8px)" }}
>
<Input
placeholder="请输入积分比例"
type="number"
onInput={maxLength8}
/>
</Form.Item>
<Form.Item
name="coupon"
rules={[
{ required: true, message: "请输入券额比例" },
{
pattern: /^(\d|[1-9]\d+)(\.\d{1,2})?$/,
message: "只能输入两位小数",
},
]}
style={{
display: "inline-block",
width: "calc(50% - 8px)",
margin: "0 8px",
}}
>
<Input
placeholder="请输入券额比例"
type="number"
onInput={maxString8}
/>
</Form.Item>
</Form.Item>
<div style={{ transform: "translateY(-10px)" }}>
说明:若兑换比例为1:20,则1积分可兑20元VIP优惠券,且为无门槛优惠券
</div>
<Form.Item
label="生效时间"
name="ruleSetting"
rules={[{ required: true, message: "请选择生效时间" }]}
initialValue={0}
>
<Radio.Group
options={[
{ label: "立即生效", value: 0 },
{ label: "手动设置", value: 1 },
]}
value={radioValue}
onChange={({ target: { value } }) => {
setRadioValue(value);
}}
/>
</Form.Item>
{radioValue === 1 && (
<Form.Item
label="生效时间"
name="entryIntoForceTime"
rules={[{ required: radioValue === 1, message: "请设置生效时间" }]}
>
<DatePicker
placeholder="请选择生效时间"
allowClear
showTime={{ format: "HH:mm:ss" }}
format="YYYY-MM-DD HH:mm:ss"
style={{ width: "100%" }}
disabledDate={(current) => {
// 限制时间不可早于当日
return current && current <= moment();
}}
/>
</Form.Item>
)}
</Form>
</Modal>
);
};
import { useEffect, useState } from 'react';
import { Button, message, Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { PlusOutlined } from '@ant-design/icons';
import SearchView from '~/components/search-box/index';
import { PointManageAPI } from '~/api';
import { ListConvertRuleType } from '~/api/interface/pointManageType';
import { AddEditModal } from './comp/addEditModal/index';
import useOperate from '~/common/hook/optionHook';
// 列表的类型
type TableType = (ReturnType<ListConvertRuleType> extends Promise<infer T>
? T
: never)['result']['list'];
type ReqType = Parameters<ListConvertRuleType>[0];
// 搜索表单的数据
let query: ReqType = {};
// 规则状态
const ruleOptions = [
// { id: undefined, name: "全部状态" },
{ id: 0, name: '生效中' },
{ id: 1, name: '已失效' },
{ id: 2, name: '未生效' },
];
export const PointRules = () => {
// 新增弹窗是否显示
const [addEditVisible, setAddEditVisible] = useState(false);
// 表格数据
const [tableData, setTableData] = useState<TableType>([]);
// 表格分页配置
const [pagination, setPagination] = useState({
total: 0,
pageSize: 10,
current: 1,
totalPage: 0,
});
// 按钮权限
const isPointRuleBtn = useOperate(25201);
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 新版通用部分(ES6+ for React) ZhangLK 2022/08/30 Start
// 加载列表
const getTableList = async (value = {}) => {
// 只需要修改这个地方的接口即可
const res = await PointManageAPI.ListConvertRule({
pageNo: pagination.current,
pageSize: pagination.pageSize,
...value,
...query,
});
if (res && res.code === '200') {
const { list, pageNo, totalCount, pageSize, totalPage } = res.result; // 解构
// console.log("getTableList --->", list);
setPagination({
total: totalCount,
current: pageNo,
pageSize,
totalPage,
});
setTableData(list);
} else {
message.warning(res.message);
}
};
// 翻页
const paginationChange = (pageNo: number, pageSize: number) => {
getTableList({ pageNo, pageSize }).then();
};
// 表单提交
const onFinish = (data: ReqType) => {
pagination.current = 1;
query = data;
getTableList(data).then();
};
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// 表格结构
const columns: ColumnsType<TableType[0]> = [
{
title: '规则名称',
dataIndex: 'ruleName',
align: 'center',
},
{
title: '兑换比例(积分:券额)',
dataIndex: 'convertRatio',
align: 'center',
},
{
title: '生效时间',
dataIndex: 'entryIntoForceTime',
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
align: 'center',
render: (text: string) => ruleOptions.find((i) => i.id === Number(text))?.name || text,
},
];
useEffect(() => {
query = {};
(async () => {
await getTableList();
})();
}, []);
return (
<>
<SearchView
search={[
{
label: '规则状态',
name: 'status',
type: 'select',
placeholder: '请选择规则状态',
options: ruleOptions,
},
]}
searchData={onFinish}
child={
<Button
icon={<PlusOutlined />}
type='primary'
onClick={() => {
setAddEditVisible(true);
}}
disabled={!isPointRuleBtn}
>
新增规则
</Button>
}
/>
<Table
size='small'
dataSource={tableData}
columns={columns}
rowKey='id'
// scroll={{ x: 1500 }}
bordered
pagination={{
total: pagination.total,
pageSize: pagination.pageSize,
current: pagination.current,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
<AddEditModal
open={addEditVisible}
title='新增兑换规则'
closed={() => {
setAddEditVisible(false);
setTimeout(() => {
paginationChange(pagination.current, pagination.pageSize);
}, 500);
}}
/>
</>
);
};
......@@ -2,19 +2,25 @@ import React from 'react';
import { Navigate, RouteObject } from 'react-router-dom';
import ErrorPage from '~/pages/common/error';
import LayoutView from '~/components/layout';
import { MacCommandOutlined } from '@ant-design/icons';
import { AccountBookOutlined, MacCommandOutlined } from '@ant-design/icons';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { AgnosticIndexRouteObject } from '@remix-run/router';
import { Spin } from 'antd';
import { PointList } from '~/pages/pointManage/pointList';
import { PointDetail } from '~/pages/pointManage/pointList/detail';
import { PointRules } from '~/pages/pointManage/pointRules';
import PointDetailList from '~/pages/pointManage/pointDetail';
import DivideOrder from '~/pages/pointManage/divideOrder';
import DivideRules from '~/pages/pointManage/divideRules';
const ProductOrderView = React.lazy(() => import('src/pages/orderManage/productOrder')); //销售订单
const EquipmentOrderView = React.lazy(() => import('src/pages/orderManage/equipmentOrder')); //设备订单
const ServiceOrderView = React.lazy(() => import('src/pages/orderManage/serviceOrder')); //服务订单
export interface RouteObjectType {
path?: AgnosticIndexRouteObject['path'];
element?: React.ReactNode | null;
path: AgnosticIndexRouteObject['path'];
element: any;
errorElement?: React.ReactNode | null;
children?: Array<RouteObject & RouteObjectType>;
meta: {
......@@ -79,6 +85,75 @@ export const routerList: Array<RouteObjectType> = [
},
],
},
{
path: '/pointManage',
element: <LayoutView />,
errorElement: <ErrorPage />,
meta: {
id: 25000,
icon: <AccountBookOutlined />,
title: '积分管理',
},
children: [
{
path: '/pointManage/pointList',
element: withLoadingComponent(<PointList />),
meta: {
id: 25100,
title: '积分列表',
icon: <MacCommandOutlined />,
},
},
{
path: '/pointManage/pointList/detail',
element: withLoadingComponent(<PointDetail />),
meta: {
id: 25100,
title: '个人积分明细',
icon: <MacCommandOutlined />,
hidden: true,
},
},
{
path: '/pointManage/pointRule',
element: withLoadingComponent(<PointRules />),
meta: {
id: 25200,
title: '兑换规则',
icon: <MacCommandOutlined />,
},
},
{
path: '/pointManage/pointList/list',
element: withLoadingComponent(<PointDetailList />),
meta: {
id: 25100,
title: '积分明细',
icon: <MacCommandOutlined />,
hidden: true,
},
},
{
path: '/pointManage/divideOrder',
element: withLoadingComponent(<DivideOrder />),
meta: {
id: 25200,
title: '订单分成',
icon: <MacCommandOutlined />,
},
},
{
path: '/pointManage/divideRules',
element: withLoadingComponent(<DivideRules />),
meta: {
id: 25200,
title: '分成规则',
icon: <MacCommandOutlined />,
hidden: true,
},
},
],
},
];
// 路由白名单
export const whiteRouterList: Array<RouteObject & RouteObjectType> = [
......
import { useSelector, useDispatch, TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "../index";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import { configureStore } from "@reduxjs/toolkit";
import stateSlice from "@/store/slice";
const store = configureStore({
reducer: {
state: stateSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
const stateSlice = createSlice({
name: "ghj",
initialState: {
num: 1,
},
reducers: {
add: (state) => {
state.num += 1;
},
},
});
export default stateSlice;
const TOKENKEY = "mmc-token";
const USERKEY = "mmc-custManage";
const getToken = () => {
return localStorage.getItem(TOKENKEY);
};
const setToken = (token: string) => {
localStorage.setItem(TOKENKEY, token);
};
const getUser = () => {
return localStorage.getItem(USERKEY);
};
const setUser = (user: Object) => {
localStorage.setItem(USERKEY, JSON.stringify(user));
};
export { getToken, setToken, getUser, setUser };
// 合同状态
export const signStatusList = [
{ val: 0, label: "待用户签署合同" },
{ val: 1, label: "用户签署失败" },
{ val: 2, label: "待平台签署合同" },
{ val: 3, label: "平台签署失败" },
{ val: 4, label: "平台签署成功" },
{ val: 5, label: "合同归档完成" },
];
// 裂变优惠券门槛
export const splitCouponType = [
{
val: 1,
label: "有门槛",
},
{
val: 2,
label: "减免券",
},
{
val: 3,
label: "无门槛",
},
];
// 裂变优惠券使用类型
export const splitCouponUseType = [
{
val: 2,
label: "品牌券",
},
{
val: 1,
label: "商品券",
},
{
val: 3,
label: "店铺券",
},
];
export const base64ToFile = (dataUrl: string, name?: string) => {
const arr: string[] = dataUrl.split(",");
// const mime = arr[0].match(/:(.*?);/)[1];
const bstr: string = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], `${name}.png`, { type: "image/png" });
};
export const urlToBase64 = (url: string) => {
return new Promise((resolve) => {
const timeStamp = new Date().getTime();
// 通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
const Img = new Image();
// 处理缓存,fix缓存bug,有缓存,浏览器会报错;
Img.src = `${url}?${timeStamp}`;
// 解决控制台跨域报错的问题
Img.crossOrigin = "Anonymous";
// 获取后缀
const ext = Img.src.substring(Img.src.lastIndexOf(".") + 1).toLowerCase();
Img.onload = () => {
// 要先确保图片完整获取到,这是个异步事件
const canvas: any = document.createElement("canvas"); // 创建canvas元素
// 确保canvas的尺寸和图片一样
canvas.width = Img.width;
canvas.height = Img.height;
// 将图片绘制到canvas中
canvas.getContext("2d").drawImage(Img, 0, 0, Img.width, Img.height);
// 转换图片为dataURL
resolve(canvas.toDataURL(`image/${ext}`));
};
});
};
// 过滤路由
import { limitEntity } from "@/api/modules/role";
export const filterRouter = (list: any, routeList: limitEntity[]) => {
return list.reduce((pre: any, cur: any) => {
const obj = { ...cur };
const bool: boolean = routeList.some((v) => v.id === obj.id);
if (bool) {
if (obj.children) {
obj.children = filterRouter(obj.children, routeList);
}
pre.push(obj);
}
return pre;
}, []);
};
export const isRoute = (list: any, pathname: string) => {
return list.some(
(v: any) =>
v.path === pathname ||
(v.alias && pathname.includes(v.alias)) ||
(v.children && isRoute(v.children, pathname))
);
};
// 格式化千分位并补零
export const moneyFormat = (num: number) => {
if (Number(num).toString() !== "NaN") {
// 添加千分符
let _n = Number(num).toLocaleString();
if (_n.indexOf(".") !== -1) {
_n += "00";
} else {
_n += ".00";
}
// 因为有千分符所以,返回数据为字符串格式,无法做运算操作,适合做直接显示使用
return _n.substring(0, _n.indexOf(".") + 3);
}
};
// 是否允许操作
const isAllowOption = (record: any) => {
const userInfo = JSON.parse(localStorage.getItem("user_info") as string);
return (
userInfo.userAccountId === record.operationId ||
(userInfo.roleInfo.pmc &&
(record.statusCode === 710 || record.statusCode === 500))
);
};
// 设置状态
export const setStatus = (record: any, deliverBtn: boolean) => {
if (record.statusCode !== 50 && !isAllowOption(record)) {
return "";
}
// 新版
if (record.statusCode === 200) {
return record.signStatus === 2 ? "合同签署" : "";
}
switch (record.statusCode) {
case 50:
return JSON.parse(localStorage.getItem("user_info") as string).roleInfo
.admin
? "分配订单"
: "";
case 100:
return "确认订单";
case 300:
return "确认付款";
case 400:
return "确认库存";
case 710:
case 500:
return deliverBtn ? "发货" : "";
case 660:
return "确认尾款";
default:
return null;
}
};
// 不能输入数字,其他可惜输入
export const exceptNumber = (val: any) => {
val.target.value = val.target.value
.replace(/1?(\d|([1-9]\d+))(.\d+)?$/g, "")
.replace(/\s/g, "");
};
// 只能输入正整数
export const onlyNumberPositive = (val: any) => {
// eslint-disable-next-line eqeqeq
if (val.target.value == 0) {
val.target.value = val.target.value.replace(/0/g, "");
}
val.target.value = val.target.value.replace(/\D/g, "");
};
// 不能输入汉字,其他可输入
export const exceptChinese = (val: any) => {
val.target.value = val.target.value
.replace(/[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/g, "")
.replace(/\s/g, "");
};
// 只能输入字母和中文,不能输入数字和符号
export const onlyCharacter = (val: any) => {
val.target.value = val.target.value
.replace(/[^a-zA-Z\u4E00-\u9FA5]/g, "")
.replace(/\s/g, "");
};
// 手机号输入,限制11位
export const phoneNumber = (val: any) => {
if (val.target.value.length > 11) {
val.target.value = val.target.value.slice(0, 11);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 开头不能输入空格
export const noSpaceFront = (val: any) => {
val.target.value = val.target.value.replace(/^\s/g, "");
};
// 数字限制长度
export const maxLength = (val: any) => {
if (val.target.value.length > 10) {
val.target.value = val.target.value.slice(0, 10);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 数字限制长度
export const maxString = (val: any) => {
if (val.target.value.length > 10) {
val.target.value = val.target.value.slice(0, 10);
}
};
// 最大不能超过100000
export const maxNumber = (val: any) => {
if (val.target.value > 100000) {
val.target.value = 100000;
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 字符串限制长度
export const maxString8 = (val: any) => {
if (val.target.value.length > 8) {
val.target.value = val.target.value.slice(0, 8);
}
};
// 数字限制长度
export const maxLength8 = (val: any) => {
if (val.target.value.length > 8) {
val.target.value = val.target.value.slice(0, 8);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 数字限制长度
export const maxLength9 = (val: any) => {
if (val.target.value.length > 9) {
val.target.value = val.target.value.slice(0, 9);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 数字限制长度
export const maxLength7 = (val: any) => {
if (val.target.value.length > 7) {
val.target.value = val.target.value.slice(0, 7);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
// 数字限制长度
export const maxLength6 = (val: any) => {
if (val.target.value.length > 6) {
val.target.value = val.target.value.slice(0, 6);
} else {
val.target.value = val.target.value.replace(/\D/g, "");
}
};
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
......@@ -9,4 +10,11 @@ export default defineConfig({
server: {
host: '0.0.0.0',
},
envDir: resolve(__dirname, 'env'),
resolve: {
// 配置别名
alias: {
'@': resolve(__dirname, 'src'),
},
},
});
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论