增加订单查询、订单退款、用户封禁功能;修复bug #1

Merged
yezian merged 1 commits from test into main 2024-03-13 00:03:56 +08:00
8 changed files with 713 additions and 341 deletions

View File

@ -0,0 +1,456 @@
import React, { useState, useRef, useEffect } from "react";
import { Form, Input, Button, Space, Table, Menu, Image, Modal } from "antd";
import {
Routes,
Route,
Navigate,
useNavigate,
useLocation,
} from "react-router-dom";
import baseRequest from "../../utils/baseRequest";
const BlockUserContent = (props) => {
const { TextArea } = Input;
const current = props.current;
//
const columns = [
{
title: "用户信息",
dataIndex: "user",
key: "user",
onCell: (_, index) => ({
rowSpan: _.rowSpan,
}),
render: (data) => (
<div>
<Image
src={data.avatar}
width={50}
height={50}
className="rounded-full object-cover"
/>
<p>
昵称<span className="text-red-400">{data.name}</span>
</p>
<p>
ID<span className="text-red-400">{data.user_id}</span>
</p>
</div>
),
},
{
title: "封禁内容",
dataIndex: "type",
key: "type",
render: (data) => <p>{data === 0 && "主播发帖封禁"}</p>,
},
{
title: "起始时间",
dataIndex: "ct",
key: "ct",
},
{
title: "结束时间",
dataIndex: "end_time",
key: "end_time",
},
{
title: "状态",
dataIndex: "status",
key: "status",
render: (data, record) => (
<div>
<p className="text-green-400">{data === 0 && "处罚中"}</p>
<p className="text-green-400">{data === 1 && "正常结束"}</p>
<p className="text-red-400">
{data === 2 && `提前终止:${record.ut}`}
</p>
</div>
),
},
{
title: "操作",
dataIndex: "opeartion",
key: "opeartion",
render: (_, record) => (
<div>
<Space>
<Space.Compact direction="vertical">
{current === "blocking" && (
<Button type="primary" onClick={() => unblock(record)}>
{current === "blocking" && "解封"}
</Button>
)}
</Space.Compact>
</Space>
</div>
),
},
];
//ref
const formRef = useRef(null);
//
const unblock = (record) => {
formRef.current.id = record.id;
formRef.current.btn = "unblock";
formRef.current.submit();
};
//
const handleSubmit = async (value) => {
//
try {
const base = baseRequest();
const _response = await fetch("/op/account_punishment/unblock", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: formRef.current.id,
...base,
}),
});
const _data = await _response.json();
if (_data.ret === -1) {
alert(_data.msg);
return;
}
} catch (error) {
console.error(error);
}
//
window.location.reload();
};
//
const [data, setData] = useState([]);
const getData = async () => {
let querryUrl;
switch (current) {
case "blocking":
querryUrl = "/op/account_punishment/list";
break;
case "terminated":
querryUrl = "/op/account_punishment/list_terminated";
break;
default:
break;
}
try {
const base = baseRequest();
const _response = await fetch(querryUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
offset: 0,
limit: 10000,
...base,
}),
});
const _data = await _response.json();
console.log(_data);
if (_data.ret === -1) {
alert(_data.msg);
return;
}
//
const blockList = _data.data.list
.map((item1, index1) =>
item1.account_punishments.map((item2, index2) => ({
rowSpan: index2 === 0 ? item1.account_punishments.length : 0,
account: item1.account,
account_punishments: item2,
}))
)
.flat();
//
const structedData = blockList.map((item, index) => {
return {
key: index,
id: item.account_punishments.id,
rowSpan: item.rowSpan,
user: {
avatar: item.account.avatar?.images[0].urls[0],
user_id: item.account.user_id,
name: item.account.name,
},
type: item.account_punishments.type,
ct: new Date(item.account_punishments.ct * 1000).toLocaleString(),
end_time: new Date(
item.account_punishments.end_time * 1000
).toLocaleString(),
status: item.account_punishments.status,
ut: new Date(item.account_punishments.ut * 1000).toLocaleString(),
};
});
setData([...data, ...structedData]);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
getData();
}, [current]);
//
const [showData, setShowData] = useState([]);
useEffect(() => {
setShowData(data);
}, [data]);
//Modal
const [isModalVisible, setIsModalVisible] = useState(false);
//modal
const [userInfo, setUserInfo] = useState();
const modalSearch = async (value) => {
try {
const base = baseRequest();
const _response = await fetch(`/op/account/list_by_user_id`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: parseInt(value.userId, 10),
...base,
}),
});
const _data = await _response.json();
console.log(_data);
if (_data.ret === -1) {
alert(_data.msg);
return;
}
setSelectedUser();
setUserInfo(_data.data.account);
} catch (error) {
console.error(error);
}
};
//
const [selectedUser, setSelectedUser] = useState();
const handleSelected = () => {
if (selectedUser) {
setSelectedUser();
return;
}
setSelectedUser(userInfo.mid);
};
//
const [form] = Form.useForm();
const block = async (value) => {
if (!selectedUser) {
alert("还未选中用户");
return;
}
try {
const base = baseRequest();
const _response = await fetch("/op/account_punishment/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: selectedUser,
type: parseInt(value.type, 10),
duration: parseInt(value.duration, 10),
...base,
}),
});
const _data = await _response.json();
if (_data.ret === -1) {
alert(_data.msg);
return;
}
handleCancelModal();
//
window.location.reload();
} catch (error) {
console.error(error);
}
};
//
const handleCancelModal = () => {
form.resetFields();
setUserInfo();
setSelectedUser();
setIsModalVisible(false);
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<div style={{ marginLeft: 20, marginRight: 20, marginTop: 20 }}>
<Button
className="mb-4"
type="primary"
onClick={() => setIsModalVisible(true)}
>
添加封禁
</Button>
<Form
ref={formRef}
name="remarks"
onFinish={handleSubmit}
onFinishFailed={onFinishFailed}
>
<Table
columns={columns}
dataSource={showData}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>
</Form>
{isModalVisible && (
<Modal footer={null} open={isModalVisible} onCancel={handleCancelModal}>
<p className="text-sm text-red-400 font-bold">
*请选中用户后再执行操作
</p>
<Form name="modal_search" onFinish={modalSearch}>
<Space style={{ marginBottom: 20 }}>
<Form.Item label="ID" name="userId" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Button type="primary" htmlType="submit">
搜索
</Button>
</Space>
</Form>
{userInfo && (
<div
className={`flex flex-row items-center p-2 rounded-xl ${
selectedUser ? "bg-green-300" : "bg-gray-300"
}`}
>
<Image
src={userInfo?.avatar?.images[0].urls[0]}
width={80}
height={80}
/>
<div className="flex flex-col justify-center mx-4">
<p className="text-lg font-bold">ID{userInfo.user_id}</p>
<p className="text-lg font-bold">昵称{userInfo.name}</p>
</div>
<Button
type={selectedUser ? "default" : "primary"}
onClick={handleSelected}
>
{selectedUser ? "取消选中" : "选中用户"}
</Button>
</div>
)}
<Form className="mt-4 flex flex-col" form={form} onFinish={block}>
<Space>
<Form.Item
name="type"
label="封禁内容"
initialValue={0}
rules={[
{
required: true,
message: "请选择封禁内容",
},
]}
>
<select
style={{
height: 32,
padding: "4px 11px",
border: "1px solid #d9d9d9",
borderRadius: 4,
outline: "none",
}}
>
<option value={0}>限制发帖</option>
</select>
</Form.Item>
<Form.Item
name="duration"
label="封禁时长"
initialValue={86400}
rules={[
{
required: true,
message: "请选择封禁时长",
},
]}
>
<select
style={{
height: 32,
padding: "4px 11px",
border: "1px solid #d9d9d9",
borderRadius: 4,
outline: "none",
}}
>
<option value={86400}>1</option>
<option value={259200}>3</option>
<option value={604800}>7</option>
<option value={1296000}>15</option>
<option value={2592000}>30</option>
<option value={3155760000}>永久</option>
</select>
</Form.Item>
</Space>
<Button className="ml-8" type="primary" htmlType="submit">
确认
</Button>
</Form>
</Modal>
)}
</div>
);
};
export default function BlockUser() {
const navigate = useNavigate();
//tab
const location = useLocation();
const pathname = location.pathname.split("/")[2] || "blocking";
const [current, setCurrent] = useState(pathname);
//tab
const items = [
{
label: "封禁中",
key: "blocking",
},
{
label: "已结束",
key: "terminated",
},
];
const onClick = (e) => {
setCurrent(e.key);
navigate(e.key);
window.location.reload();
};
return (
<div>
<Menu
onClick={onClick}
selectedKeys={[current]}
mode="horizontal"
items={items}
/>
<Routes>
<Route path="/" element={<Navigate to="/blockUser/blocking" />} />
<Route
path="/blocking"
element={<BlockUserContent current="blocking" />}
/>
<Route
path="/terminated"
element={<BlockUserContent current="terminated" />}
/>
</Routes>
</div>
);
}

View File

@ -323,6 +323,7 @@ const CreateAndEditPostContent = (props) => {
alert(data.msg);
return;
}
setSelectedUser();
setUserInfo(data.data.account);
} catch (error) {
console.error(error);

View File

@ -149,6 +149,7 @@ const ManualRechargeAndWithdrawalContent = (props) => {
alert(data.msg);
return;
}
setSelectedUser();
setUserInfo(data.data.account);
} catch (error) {
console.error(error);

View File

@ -35,6 +35,10 @@ export default function Data() {
}),
});
const _data = await response.json();
if (_data.ret === -1) {
alert(_data.msg);
return;
}
const structedData = _data.data.list.map((item, index) => {
return {
key: index,
@ -54,10 +58,6 @@ export default function Data() {
};
});
setData(structedData);
if (_data.ret === -1) {
alert(_data.msg);
return;
}
} catch (error) {
console.error(error);
}

View File

@ -66,9 +66,13 @@ export default function Op() {
getItem("手机号查询", "getPhoneNumber"),
]),
// getItem("", "userManagement", <UsergroupAddOutlined />),
// getItem("", "orderManagement", <FileSearchOutlined />, [
// getItem("", "ordersQuerry"),
// ]),
getItem("订单管理", "orderManagement", <FileSearchOutlined />, [
getItem("查询", "ordersQuerry"),
getItem("退款", "refund"),
]),
getItem("社区治理", "communityManagement", <UsergroupAddOutlined />, [
getItem("用户封禁", "blockUser"),
]),
getItem(
"人工充值/提现",
"manualRechargeAndWithdrawal",

View File

@ -1,354 +1,167 @@
import React, { useState } from "react";
import { Form, Input, Button, Space, Table, Checkbox, Image } from "antd";
import React, { useState, useEffect } from "react";
import baseRequest from "../../utils/baseRequest";
import { DatePicker, Table, Form, Space, Button, Input, Radio } from "antd";
export default function OrdersQuerry() {
//
const [showColumns, setShowColumns] = useState([
"orderId",
"orderStatus",
"orderTime",
"creatorId",
"creatorName",
"userId",
"userName",
"requirement",
"payment",
"comment",
]);
//
const dynamicColumns = showColumns.map((item) => {
switch (item) {
case "orderId":
return {
title: "订单号",
dataIndex: "orderId",
key: "orderId",
};
case "orderStatus":
return {
title: "订单状态",
dataIndex: "orderStatus",
key: "orderStatus",
};
case "orderTime":
return {
title: "订单时间",
dataIndex: "orderTime",
key: "orderTime",
render: (orderTime) => (
<ul>
<li>
<span style={{ color: "red" }}>创建时间</span>
{orderTime.createTime}
</li>
<li>
<span style={{ color: "red" }}>付款时间</span>
{orderTime.payTime}
</li>
<li>
<span style={{ color: "red" }}>发货时间</span>
{orderTime.deliveryTime}
</li>
<li>
<span style={{ color: "red" }}>完成时间</span>
{orderTime.finishTime}
</li>
</ul>
),
};
case "creatorId":
return {
title: "名人ID",
dataIndex: "creatorId",
key: "creatorId",
};
case "creatorName":
return {
title: "名人昵称",
dataIndex: "creatorName",
key: "creatorName",
};
case "userId":
return {
title: "用户ID",
dataIndex: "userId",
key: "userId",
};
case "userName":
return {
title: "用户昵称",
dataIndex: "userName",
key: "userName",
};
case "requirement":
return {
title: "拍摄要求",
dataIndex: "requirement",
key: "requirement",
render: (requirement) => (
<ul>
<li>
<span style={{ color: "red" }}>给谁</span>
{requirement.to}
</li>
<li>
<span style={{ color: "red" }}>拍摄要求</span>
{requirement.requirement}
</li>
<li>
<span style={{ color: "red" }}>备注</span>
{requirement.remark}
</li>
</ul>
),
};
case "payment":
return {
title: "实付款",
dataIndex: "payment",
key: "payment",
};
case "media":
return {
title: "成品",
dataIndex: "media",
key: "media",
render: (media) =>
media.type === "img" ? (
<Image
key={media.url}
src={media.url}
width={150}
style={{ marginBottom: 10 }}
/>
) : (
<video key={media.url} src={media.url} width={150} controls />
),
};
case "comment":
return {
title: "评价",
dataIndex: "comment",
key: "comment",
render: (comment) => (
<ul>
<li>
<span style={{ color: "red" }}>评分</span>
{comment.rate}
</li>
<li>
<span style={{ color: "red" }}>评语</span>
{comment.comment}
</li>
</ul>
),
};
case "reaction":
return {
title: "Reaction",
dataIndex: "reaction",
key: "reaction",
render: (reaction) => (
<div>
{reaction
? reaction.map((item) =>
item.type === "img" ? (
<Image
key={item.url}
src={item.url}
width={150}
style={{ marginBottom: 10 }}
/>
) : (
<video
key={item.url}
src={item.url}
width={150}
controls
/>
)
)
: "无"}
</div>
),
};
default:
return {};
}
});
//
const data = [
const { RangePicker } = DatePicker;
const [data, setData] = useState();
//
const columns = [
{
key: "1",
orderId: "666666666",
orderStatus: "已完成",
orderTime: {
createTime: "2022-07-25 12:00:00",
payTime: "2022-07-25 12:01:00",
deliveryTime: "2022-07-26 12:02:00",
finishTime: "2022-07-25 12:03:00",
title: "基本信息",
dataIndex: "base",
key: "base",
render: (data) => {
return (
<div>
<p>
<span className="text-green-400">订单号</span>
{data.order_id}
</p>
<p>
<span className="text-green-400">订单状态</span>
{data.order_status_desc}
</p>
<p>
<span className="text-green-400">创建时间</span>
{data.ct}
</p>
</div>
);
},
creatorId: "123456",
creatorName: "马牛逼",
userId: "888888",
userName: "路人甲",
requirement: {
to: "路人A",
requirement: "马牛逼的拍摄要求",
remark: "马牛逼的备注",
},
payment: 60,
media: {
type: "img",
url: "https://s2.loli.net/2023/07/25/fjouqVLAlTn2s58.png",
},
comment: { rate: "5.0", comment: "马牛逼的评价" },
reaction: [
{
type: "img",
url: "https://s2.loli.net/2023/07/25/fjouqVLAlTn2s58.png",
},
],
},
{
key: "2",
orderId: "999999999",
orderStatus: "已完成",
orderTime: {
createTime: "2023-07-25 12:00:00",
payTime: "2023-07-25 12:01:00",
deliveryTime: "2023-07-26 12:02:00",
finishTime: "2023-07-25 12:03:00",
title: "商品名称",
dataIndex: "product",
key: "product",
},
{
title: "金额",
dataIndex: "cost",
key: "cost",
render: (data) => {
return (
<div>
{data.coins && <p>{data.coins}</p>}
{data.money && <p>{data.money}</p>}
</div>
);
},
creatorId: "10086",
creatorName: "迪迦",
userId: "111111",
userName: "路人乙",
requirement: {
to: "路人B",
requirement: "迪迦的拍摄要求",
remark: "迪迦的备注",
},
{
title: "购买(双)方",
dataIndex: "userAndStreamer",
key: "userAndStreamer",
render: (data) => {
return (
<div>
<p>
<span className="text-green-400">用户id</span>
{data.user_user_id}
</p>
{data.streamer_user_id && (
<p>
<span className="text-green-400">主播id</span>
{data.streamer_user_id}
</p>
)}
</div>
);
},
payment: 50,
media: {
type: "video",
url: "https://media.w3.org/2010/05/sintel/trailer.mp4",
},
comment: { rate: "4.0", comment: "迪迦的评价" },
reaction: [
{
type: "img",
url: "https://s2.loli.net/2023/07/25/1daCqmZGQNUzoJ8.png",
},
{
type: "video",
url: "https://media.w3.org/2010/05/sintel/trailer.mp4",
},
],
},
];
//checkbox
const showColumnsOptions = [
{
label: "订单号",
value: "orderId",
},
{
label: "订单状态",
value: "orderStatus",
},
{
label: "订单时间",
value: "orderTime",
},
{
label: "名人ID",
value: "creatorId",
},
{
label: "名人昵称",
value: "creatorName",
},
{
label: "用户ID",
value: "userId",
},
{
label: "用户昵称",
value: "userName",
},
{
label: "拍摄要求",
value: "requirement",
},
{
label: "实付款",
value: "payment",
},
{
label: "成品",
value: "media",
},
{
label: "评价",
value: "comment",
},
{
label: "Reaction",
value: "reaction",
},
];
//
const [showData, setShowData] = useState(data);
//
const search = (value) => {
value.creatorName ||
value.creatorId ||
value.orderId ||
value.userId ||
value.userName
? setShowData(
data.filter(
(item) =>
item.creatorName === value.creatorName ||
item.creatorId === value.creatorId ||
item.orderId === value.orderId ||
item.userId === value.userId ||
item.userName === value.userName
)
)
: setShowData(data);
//
const handleQuerry = async (value) => {
const ordersQuerry = async (type) => {
try {
const base = baseRequest();
//
const response = await fetch(`/op/vas/${type}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
st: value.time ? Math.floor(value.time[0].valueOf() / 1000) : 0,
et: value.time ? Math.floor(value.time[1].valueOf() / 1000) : 0,
user_id: value.user_id ? parseInt(value.user_id, 10) : 0,
mid: value.mid ? parseInt(value.mid, 10) : 0,
order_id: value.order_id ? value.order_id : "",
...base,
}),
});
const _data = await response.json();
console.log(_data);
if (_data.ret === -1) {
return false;
}
const structedData = _data.data.list.map((item, index) => {
return {
key: index,
base: {
order_id: item.order_id,
order_status_desc: item.order_status_desc,
ct: new Date(item.ct * 1000).toLocaleString(),
},
product: item.product_name,
cost: {
coins: `${item?.coins}金币`,
money: `¥${item?.money / 100}`,
},
userAndStreamer: {
user_user_id: item?.user_user_id,
streamer_user_id: item?.streamer_user_id,
},
};
});
return structedData;
} catch (error) {
console.error(error);
}
};
let result = await ordersQuerry("coin_order_list");
if (!result) result = await ordersQuerry("order_list");
setData(result);
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<div style={{ marginLeft: 20, marginRight: 20 }}>
<div style={{ marginTop: 20, marginBottom: 10 }}>
<p style={{ display: "inline" }}>数据可见性</p>
<Checkbox.Group
options={showColumnsOptions}
defaultValue={showColumns}
onChange={(value) => setShowColumns(value)}
/>
</div>
<Form name="search" onFinish={search} onFinishFailed={onFinishFailed}>
<Space style={{ marginBottom: 20 }}>
<Form.Item label="订单号" name="orderId" style={{ margin: 0 }}>
<Input />
<div className="px-4">
<h1>订单查询</h1>
<p className="text-red-400">*以下字段择一填写进行查询</p>
<Form
name="querry"
onFinish={handleQuerry}
onFinishFailed={onFinishFailed}
>
<Space direction="vertical" style={{ marginBottom: 20 }}>
<Form.Item label="ID" name="user_id" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Form.Item label="名人ID" name="creatorId" style={{ margin: 0 }}>
<Input />
<Form.Item
label="mid(开发使用,运营勿用)"
name="mid"
style={{ margin: 0 }}
>
<Input type="number" />
</Form.Item>
<Form.Item label="名人昵称" name="creatorName" style={{ margin: 0 }}>
<Input />
<Form.Item label="订单号" name="order_id" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Form.Item label="用户ID" name="userId" style={{ margin: 0 }}>
<Input />
</Form.Item>
<Form.Item label="用户昵称" name="userName" style={{ margin: 0 }}>
<Input />
<Form.Item label="时间段" name="time" style={{ margin: 0 }}>
<RangePicker
showTime={{
format: "HH:mm",
}}
showNow
format="YYYY-MM-DD HH:mm"
/>
</Form.Item>
<Button type="primary" htmlType="submit">
搜索
@ -356,8 +169,8 @@ export default function OrdersQuerry() {
</Space>
</Form>
<Table
columns={dynamicColumns}
dataSource={showData}
columns={columns}
dataSource={data}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>

View File

@ -0,0 +1,87 @@
import React from "react";
import { Form, Input, Space, Button, message, Radio } from "antd";
import baseRequest from "../../utils/baseRequest";
const RefundContent = (props) => {
const [form] = Form.useForm();
//退
const handleRefund = async (value) => {
try {
const base = baseRequest();
const _response = await fetch(
`op/vas/${
value.type === "coin" ? "refund_coin_order" : "refund_order"
}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
order_id: value.orderId,
operator: value.operator,
...base,
}),
}
);
const _data = await _response.json();
console.log(_data);
if (_data.ret === -1) {
alert(_data.msg);
return;
}
form.resetFields();
message.success("退款成功");
} catch (error) {
console.error(error);
}
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<div className="mt-4" style={{ marginLeft: 20, marginRight: 20 }}>
<Form
name="refund"
form={form}
onFinish={handleRefund}
onFinishFailed={onFinishFailed}
>
<Space direction="vertical" style={{ marginBottom: 20 }}>
<Form.Item label="订单号" name="orderId" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Form.Item label="操作人" name="operator" style={{ margin: 0 }}>
<Input type="text" />
</Form.Item>
<Form.Item
label=""
name="type"
rules={[
{
required: true,
message: "未选择",
},
]}
style={{ margin: 0 }}
>
<Radio.Group>
<Radio.Button value="coin">退金币</Radio.Button>
<Radio.Button value="cash">退现金</Radio.Button>
</Radio.Group>
</Form.Item>
<Button type="primary" htmlType="submit">
确认退款
</Button>
</Space>
</Form>
</div>
);
};
export default function Refund() {
return <RefundContent />;
}

View File

@ -19,6 +19,8 @@ import CreateAndEditPost from "../pages/CreateAndEditPost";
import TopPosts from "../pages/TopPosts";
import PostMachineReview from "../pages/PostMachineReview";
import GetPhoneNumber from "../pages/GetPhoneNumber";
import Refund from "../pages/Refund";
import BlockUser from "../pages/BlockUser";
const routes = [
{
@ -105,6 +107,14 @@ const routes = [
path: "getPhoneNumber/*",
element: <GetPhoneNumber />,
},
{
path: "refund/*",
element: <Refund />,
},
{
path: "blockUser/*",
element: <BlockUser />,
},
],
},
];