新增:空间管理、空间动态管理、mid/user_id互查、空间成员退款查询、广场动态发帖人 #8

Merged
yezian merged 1 commits from zone into main 2024-05-01 04:55:21 +08:00
9 changed files with 1427 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import ImageUploader from "../../components/ImageUploader";
import VideoUploader from "../../components/VideoUploader";
const CreateAndEditPostContent = (props) => {
const { TextArea } = Input;
//
const columns = [
{
@ -182,7 +183,6 @@ const CreateAndEditPostContent = (props) => {
}),
});
const temData = await response.json();
console.log(temData);
if (temData.ret === -1) {
alert(temData.msg);
return;
@ -487,7 +487,7 @@ const CreateAndEditPostContent = (props) => {
]}
style={{ margin: 0 }}
>
<Input />
<TextArea />
</Form.Item>
</Space>
<ImageUploader setIds={setImageId} />
@ -502,7 +502,7 @@ const CreateAndEditPostContent = (props) => {
{isEditModalOpen && (
<Modal footer={null} open={isEditModalOpen} onCancel={handleCancel}>
<p className="text-sm text-red-400 font-bold">
*选中主播后再发布确保文案不为空图片数不超过9视频数不超过1图片视频不同时存在
*确保文案不为空图片数不超过9视频数不超过1图片视频不同时存在
</p>
<Form className="mt-4 flex flex-col" onFinish={handleChange}>
<Space>
@ -518,7 +518,7 @@ const CreateAndEditPostContent = (props) => {
]}
style={{ margin: 0 }}
>
<Input />
<TextArea />
</Form.Item>
</Space>
{defaultMedia.image_ids.length !== 0 && (

View File

@ -0,0 +1,451 @@
import React, { useState, useRef, useEffect } from "react";
import { Form, Input, Table, Image, Space, Button, Modal, message } from "antd";
import baseRequest from "../../utils/baseRequest";
import ImageUploader from "../../components/ImageUploader";
import VideoUploader from "../../components/VideoUploader";
import { useSearchParams } from "react-router-dom";
const EditSpacePostContent = (props) => {
const { TextArea } = Input;
const [searchParams, setSearchParams] = useSearchParams();
const userId = searchParams.get("user_id");
//
const columns = [
{
title: "发帖人",
dataIndex: "creator",
key: "creator",
render: (data) => (
<div>
<Image src={data.avatar.images[0].urls[0]} width={50} />
<p>
ID<span className="text-red-400">{data.user_id}</span>
</p>
<p>
昵称<span className="text-red-400">{data.name}</span>
</p>
</div>
),
},
{
title: "动态内容",
dataIndex: "content",
key: "content",
render: (data) => (
<div className="flex flex-col">
<div>
<p className="text-red-400">
当前状态
<span className="text-black">
{data.status === 0 && "机审中"}
{data.status === 1 && "运营待审核"}
{data.status === 2 && "可见"}
{data.status === 3 && "审核不通过"}
</span>
</p>
<hr />
<p className="text-red-400">
文案<span className="text-black">{data.content}</span>
</p>
</div>
<p className="text-red-400">媒体</p>
<div className="flex flex-wrap gap-1">
{data.media.images.map((item, index) => (
<Image key={index} src={item.urls[0]} width={100} />
))}
{data.media.videos.map((item, index) => (
<video key={index} src={item.urls[0]} width={150} controls />
))}
</div>
</div>
),
},
{
title: "审核结果",
dataIndex: "info",
key: "info",
render: (data) => (
<div className="flex flex-col">
<p>文案审核结果</p>
<p className="text-green-400">{data.text_audit_opinion}</p>
<hr className="w-full" />
<p>媒体审核结果</p>
<p className="text-green-400">{data.image_audit_opinion}</p>
</div>
),
},
{
title: "发布时间",
dataIndex: "submitTime",
key: "submitTime",
},
{
title: "备注",
dataIndex: "remarks",
key: "remarks",
render: (_, record) => (
<div className="flex flex-col">
<Form.Item
name={record.momentId}
initialValue={record.remarks.manually_review_opinion}
>
<TextArea rows={4} />
</Form.Item>
</div>
),
},
{
title: "操作",
dataIndex: "opeartion",
key: "opeartion",
render: (_, record) => (
<div>
<Space>
<Space.Compact direction="vertical">
<Button type="primary" onClick={() => onClickEdit(record)}>
编辑
</Button>
<Button onClick={() => onClickInvisible(record)}>设为违规</Button>
<Button onClick={() => onClickDelete(record)}>删除</Button>
</Space.Compact>
</Space>
</div>
),
},
];
//modal
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
//modal
const [defaultValues, setDefaultValues] = useState({});
const [defaultMedia, setDefaultMedia] = useState({});
//modal
const handleCancel = () => {
setDefaultValues({});
setDefaultMedia({});
setEditImageId([]);
setEditVideoId([]);
setIsEditModalOpen(false);
};
//
const onClickEdit = (record) => {
setDefaultValues(record);
setDefaultMedia(record.content.media);
setIsEditModalOpen(true);
};
//id
const [editImageId, setEditImageId] = useState([]);
const [editVideoId, setEditVideoId] = useState([]);
//
const handleDeleteMedia = (type, id) => {
if (type === "video") {
setDefaultMedia({
...defaultMedia,
video_ids: [],
});
return;
}
if (type === "image") {
setDefaultMedia({
...defaultMedia,
image_ids: defaultMedia.image_ids.filter((item) => item !== id),
images: defaultMedia.images.filter((item) => item.id !== id),
});
return;
}
};
//
const handleChange = async (value) => {
const imageIds = [...defaultMedia.image_ids, ...editImageId];
const videoIds = [...defaultMedia.video_ids, ...editVideoId];
if (imageIds.length === 0 && videoIds.length === 0) {
alert("图片或视频不可为空");
return;
}
if (imageIds.length !== 0 && videoIds.length !== 0) {
alert("不可同时上传图片和视频");
return;
}
if (imageIds.length > 30) {
alert("图片不可超过30张");
return;
}
if (videoIds.length > 1) {
alert("视频不可超过1个");
return;
}
try {
const base = baseRequest();
const response = await fetch("/op/zone_moment/update", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: defaultValues.momentId,
text: value.content,
media_component: {
image_ids: imageIds,
video_ids: videoIds,
},
c_type: defaultValues.data.c_type,
m_type: defaultValues.data.m_type,
...base,
}),
});
const temData = await response.json();
if (temData.ret === -1) {
alert(temData.msg);
return;
}
setShowData(
showData.filter((item) => item.momentId !== defaultValues.momentId)
);
handleCancel();
} catch (error) {
console.error(error);
}
};
//
const onClickDelete = async (record) => {
try {
const base = baseRequest();
const response = await fetch("/op/zone_moment/delete", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: record.momentId,
...base,
}),
});
const temData = await response.json();
if (temData.ret === -1) {
alert(temData.msg);
return;
}
setShowData(showData.filter((item) => item.momentId !== record.momentId));
} catch (error) {
console.error(error);
}
};
//ref
const formRef = useRef(null);
//
const onClickInvisible = (record) => {
formRef.current.record = record;
formRef.current.type = 0;
formRef.current.submit();
};
//
const handleSubmit = async (value) => {
try {
const base = baseRequest();
const response = await fetch("/op/zone_moment/set_private", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
zone_moment_id: parseInt(formRef.current.record.momentId, 10),
manually_review_opinion: value[formRef.current.record.momentId]
? value[formRef.current.record.momentId]
: "",
...base,
}),
});
const temData = await response.json();
if (temData.ret === -1) {
alert(temData.msg);
return;
}
setShowData(
showData.filter(
(item) => item.momentId !== formRef.current.record.momentId
)
);
} catch (error) {
console.error(error);
}
};
//
const [showData, setShowData] = useState([]);
//
const search = async (value) => {
try {
const base = baseRequest();
const detailResponse = await fetch(`/op/zone_moment/list_by_user_id`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: parseInt(value.userId, 10),
offset: 0,
limit: 1000,
...base,
}),
});
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
alert(detailData.msg);
return;
}
//
const structedData = detailData.data.list.map((item, index) => {
return {
key: index,
data: item,
momentId: item.id,
creator: item.streamer_ext,
content: {
status: item.status,
media: item.media_component,
content: item.text,
},
info: {
image_audit_status: item.image_audit_status,
image_audit_opinion: item.image_audit_opinion,
text_audit_status: item.text_audit_status,
text_audit_opinion: item.text_audit_opinion,
},
submitTime: new Date(item.ct * 1000).toLocaleString(),
remarks: {
manually_review_status: item.manually_review_status,
manually_review_opinion: item.manually_review_opinion,
manually_review_operator: item.manually_review_operator,
},
};
});
setShowData(structedData);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
if (!userId) return;
search({ userId });
}, []);
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<div className="mt-4" style={{ marginLeft: 20, marginRight: 20 }}>
<Form name="search" onFinish={search} onFinishFailed={onFinishFailed}>
<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>
<Form
ref={formRef}
name="remarks"
onFinish={handleSubmit}
onFinishFailed={onFinishFailed}
>
<Table
columns={columns}
dataSource={showData}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>
</Form>
{/* 编辑动态内容的弹窗重复判断isEditModalOpen是为了重新渲染ImageUploader和VideoUploader组件 */}
{isEditModalOpen && (
<Modal footer={null} open={isEditModalOpen} onCancel={handleCancel}>
<p className="text-sm text-red-400 font-bold">
*请确保文案不为空图片数不超过30视频数不超过1图片视频不同时存在
</p>
<Form className="mt-4 flex flex-col" onFinish={handleChange}>
<Space>
<Form.Item
label="文案"
name="content"
initialValue={defaultValues.content.content}
rules={[
{
required: true,
message: "请填写文案",
},
]}
style={{ margin: 0 }}
>
<TextArea />
</Form.Item>
</Space>
{defaultMedia.image_ids.length !== 0 && (
<div className="flex flex-row flex-wrap gap-2 mt-4">
{defaultMedia.images.map((item) => (
<div key={item.urls[0]} className="relative">
<Image src={item.urls[0]} width={100} />
<Button
className="absolute top-0 left-0 w-full"
danger
type="primary"
onClick={() => handleDeleteMedia("image", item.id)}
>
删除
</Button>
</div>
))}
</div>
)}
{defaultMedia.video_ids.length !== 0 && (
<div className="flex flex-row mt-4">
<div className="relative">
<video
src={defaultMedia.videos[0].urls[0]}
width={150}
controls
className="mr-auto"
/>
<Button
className="absolute top-0 left-0 w-full"
danger
type="primary"
onClick={() => handleDeleteMedia("video")}
>
删除
</Button>
</div>
</div>
)}
{defaultValues.data.m_type === 1 && (
<ImageUploader setIds={setEditImageId} />
)}
{defaultValues.data.m_type === 2 && (
<VideoUploader setIds={setEditVideoId} />
)}
<Button className="ml-8" type="primary" htmlType="submit">
确认
</Button>
</Form>
</Modal>
)}
</div>
);
};
export default function EditSpacePost() {
return <EditSpacePostContent />;
}

View File

@ -1,13 +1,10 @@
import React, { useEffect } from "react";
import "./index.css";
import {
FireOutlined,
ShopOutlined,
UsergroupAddOutlined,
SoundOutlined,
PhoneOutlined,
ToolOutlined,
SearchOutlined,
SafetyCertificateOutlined,
PoweroffOutlined,
MoneyCollectOutlined,
@ -43,13 +40,9 @@ export default function Op() {
};
}
const items = [
// getItem("", "management", <FireOutlined />, [
// getItem("", "feed"),
// ]),
getItem("审核专区", "review", <SafetyCertificateOutlined />, [
getItem("网红入驻审核", "streamerJoin"),
getItem("网红实名认证审核", "streamerVerification"),
// getItem("", "streamerProfileReview"),
getItem("图片机审回查", "imageMachineReview"),
getItem("文本机审回查", "textMachineReview"),
getItem("动态机审回查", "postMachineReview"),
@ -58,19 +51,21 @@ export default function Op() {
]),
getItem("网红管理", "streamerManagement", <ShopOutlined />, [
getItem("网红资料", "streamerInformation"),
// getItem("", "streamerPlatform"),
getItem("空间管理", "streamerSpace"),
]),
getItem("动态管理", "postManagement", <FormOutlined />, [
getItem("发布与编辑", "createAndEditPost"),
getItem("置顶动态", "topPosts"),
getItem("广场动态管理", "createAndEditPost"),
getItem("广场置顶动态", "topPosts"),
getItem("空间动态管理", "EditSpacePost"),
]),
getItem("信息查询", "search", <FormOutlined />, [
getItem("手机号查询", "getPhoneNumber"),
getItem("mid/user_id互查", "querryMidAndUserId"),
]),
// getItem("", "userManagement", <UsergroupAddOutlined />),
getItem("订单管理", "orderManagement", <FileSearchOutlined />, [
getItem("查询", "ordersQuerry"),
getItem("退款", "refund"),
getItem("空间成员退款查询", "spaceMemberRefundQuerry"),
]),
getItem("社区治理", "communityManagement", <UsergroupAddOutlined />, [
getItem("用户封禁", "blockUser"),
@ -82,9 +77,6 @@ export default function Op() {
),
getItem("意见反馈", "feedback", <SoundOutlined />),
getItem("客服回复", "contact", <PhoneOutlined />),
// getItem("", "tools", <ToolOutlined />, [
// getItem("", "uploadMedia"),
// ]),
getItem("退出登录", "signOut", <PoweroffOutlined />),
];
const onClick = (e) => {

View File

@ -14,6 +14,22 @@ const PostMachineReviewContent = (props) => {
const current = props.current;
//
const columns = [
{
title: "发帖人",
dataIndex: "creator",
key: "creator",
render: (data) => (
<div>
<Image src={data.avatar.images[0].urls[0]} width={50} />
<p>
ID<span className="text-red-400">{data.user_id}</span>
</p>
<p>
昵称<span className="text-red-400">{data.name}</span>
</p>
</div>
),
},
{
title: "现内容",
dataIndex: "newMedia",
@ -347,6 +363,7 @@ const PostMachineReviewContent = (props) => {
return {
key: index,
momentId: item.moment_audit_task.associative_table_id,
creator: item?.streamer_ext,
id: {
media: item.image_audit_task_vo.id,
text: item.text_audit_task_vo.id,

View File

@ -0,0 +1,67 @@
import React, { useState } from "react";
import { Form, Input, Space, Button } from "antd";
import baseRequest from "../../utils/baseRequest";
export default function QuerryMidAndUserId() {
const [result, setResult] = useState({});
//
const search = async (value) => {
if (value.mid && value.user_id) {
alert("不可同时查询mid和user_id");
return;
}
let type = "list_by_mid";
if (!value.mid) type = "list_by_user_id";
try {
const base = baseRequest();
const response = await fetch(`/op/account/${type}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: value.user_id ? parseInt(value.user_id, 10) : 0,
mid: value.mid ? parseInt(value.mid, 10) : 0,
...base,
}),
});
const data = await response.json();
if (data.ret === -1) {
alert(data.msg);
return;
}
if (type === "list_by_user_id") {
setResult({ user_id: null, mid: data.data.account.mid });
return;
}
setResult({ user_id: data.data.account.user_id, mid: null });
} catch (error) {
console.error(error);
}
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<div className="mt-4" style={{ marginLeft: 20, marginRight: 20 }}>
<Form name="search" onFinish={search} onFinishFailed={onFinishFailed}>
<Space direction="vertical" style={{ marginBottom: 20 }}>
<Form.Item label="请输入user_id" name="user_id" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Form.Item label="请输入mid" name="mid" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<Button type="primary" htmlType="submit">
搜索
</Button>
</Space>
</Form>
{result?.user_id && (
<p className="text-lg mt-4">user_id{result?.user_id}</p>
)}
{result?.mid && <p className="text-lg mt-4">mid{result?.mid}</p>}
</div>
);
}

View File

@ -0,0 +1,192 @@
import React, { useState } from "react";
import baseRequest from "../../utils/baseRequest";
import { DatePicker, Table, Form, Space, Button, Input, Radio } from "antd";
export default function SpaceMemberRefundQuerry() {
const { RangePicker } = DatePicker;
const [data, setData] = useState();
//
const columns = [
{
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>
);
},
},
{
title: "金额",
dataIndex: "cost",
key: "cost",
render: (data) => {
return (
<div>{data.money !== undefined && <p>¥{data.money / 100}</p>}</div>
);
},
},
{
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 !== 0 && (
<p>
<span className="text-green-400">主播id</span>
{data.streamer_user_id}
</p>
)}
</div>
);
},
},
{
title: "退款备注",
dataIndex: "refundNote",
key: "refundNote",
render: (data) => {
return (
<div>
<p>
<span className="text-green-400">联系人</span>
{data.contact_name}
</p>
<p>
<span className="text-green-400">联系电话</span>
{data.contact_phone}
</p>
<p>
<span className="text-green-400">退款原因</span>
{data.note}
</p>
</div>
);
},
},
];
//
const handleQuerry = async (value) => {
try {
const base = baseRequest();
//
const response = await fetch(`op/vas/zone_refund_list`, {
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();
if (_data.ret === -1) {
alert(_data.msg);
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(),
},
cost: {
money: item?.money,
},
userAndStreamer: {
user_user_id: item?.user_user_id,
streamer_user_id: item?.streamer_user_id,
},
refundNote: {
contact_name: item?.contact_name,
contact_phone: item?.contact_phone,
note: item?.note,
},
};
});
setData(structedData);
} catch (error) {
console.error(error);
}
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
return (
<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="mid(开发使用,运营勿用)"
name="mid"
style={{ margin: 0 }}
>
<Input type="number" />
</Form.Item>
<Form.Item label="订单号" name="order_id" style={{ margin: 0 }}>
<Input type="number" />
</Form.Item>
<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">
搜索
</Button>
</Space>
</Form>
<Table
columns={columns}
dataSource={data}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>
</div>
);
}

View File

@ -0,0 +1,669 @@
import React, { useState, useEffect } from "react";
import {
Form,
Input,
Button,
Space,
Table,
Checkbox,
InputNumber,
Image,
} from "antd";
import Modal from "../../components/Modal";
import baseRequest from "../../utils/baseRequest";
import { useNavigate } from "react-router-dom";
//tab
const StreamerSpaceContent = () => {
const navigate = useNavigate();
const { TextArea } = Input;
//
const [showColumns, setShowColumns] = useState([
"baseInfo",
"profile",
"paymentSettings",
"active",
"ratio",
]);
//
const dynamicColumns = showColumns.map((item) => {
switch (item) {
case "baseInfo":
return {
title: "基础信息",
dataIndex: "baseInfo",
key: "baseInfo",
render: (data) => (
<div>
<Image src={data.avatar} width={50} />
<p>
ID<span className="text-red-400">{data.id}</span>
</p>
<p>
昵称<span className="text-red-400">{data.name}</span>
</p>
<p>
创建时间<span className="text-red-400">{data.ct}</span>
</p>
</div>
),
};
case "profile":
return {
title: "空间介绍",
dataIndex: "profile",
key: "profile",
};
case "paymentSettings":
return {
title: "付费设置",
dataIndex: "paymentSettings",
key: "paymentSettings",
render: (data) => (
<div>
<p>
解锁空间价格
<span className="text-red-400">¥{data.admission_price}</span>
</p>
<p>
铁粉价格
<span className="text-red-400">¥{data.ironfanship_price}</span>
</p>
<p>
是否开启超粉功能
<span className="text-red-400">
{data.is_superfanship_enabled === 1 ? "是" : "否"}
</span>
</p>
{data.is_superfanship_enabled === 1 && (
<>
<p>
超粉价格
<span className="text-red-400">
¥{data.superfanship_price}
</span>
</p>
<p>
超粉有效期
{data.superfanship_valid_period === 0 && (
<span className="text-red-400">永久</span>
)}
{data.superfanship_valid_period === 1 && (
<span className="text-red-400">月度</span>
)}
{data.superfanship_valid_period === 2 && (
<span className="text-red-400">季度</span>
)}
{data.superfanship_valid_period === 3 && (
<span className="text-red-400">半年</span>
)}
{data.superfanship_valid_period === 4 && (
<span className="text-red-400">年度</span>
)}
</p>
<p>
开通超粉是否送微信
<span className="text-red-400">
{data.is_superfanship_give_wechat === 1 ? "是" : "否"}
</span>
</p>
</>
)}
</div>
),
};
case "active":
return {
title: "活跃情况",
dataIndex: "active",
key: "active",
render: (data) => (
<div>
<p>
动态数量
<span className="text-red-400">{data.zone_moment_count}</span>
</p>
<p>
图片数量
<span className="text-red-400">{data.image_count}</span>
</p>
<p>
视频数量
<span className="text-red-400">{data.video_count}</span>
</p>
<p>
最后发帖时间
<span className="text-red-400">{data.last_zone_moment_ct}</span>
</p>
</div>
),
};
case "ratio":
return {
title: "分成情况",
dataIndex: "ratio",
key: "ratio",
render: (data) => (
<div>
{data.zone_third_partner?.third_partner_account?.mid && (
<>
<p>
代运营ID
<span className="text-red-400">
{data.zone_third_partner?.third_partner_account?.user_id}
</span>
</p>
<p>
代运营比例
<span className="text-red-400">
{(data.zone_third_partner.sharing_ratio * 100).toFixed()}%
</span>
</p>
</>
)}
<hr />
{data.zone_collaborator_list.length !== 0 &&
data.zone_collaborator_list?.map((item, index) => (
<div key={index}>
<p>
合伙人ID
<span className="text-red-400">
{item?.collaborator_account?.user_id}
</span>
</p>
<p>
合伙人比例
<span className="text-red-400">
{(item.sharing_ratio * 100).toFixed()}%
</span>
</p>
<br />
</div>
))}
</div>
),
};
default:
return {};
}
});
dynamicColumns.push({
title: "操作",
dataIndex: "opeartion",
key: "opeartion",
render: (_, record) => (
<div>
<Space>
<Space.Compact direction="vertical">
<Button type="primary" onClick={() => handleModal(record)}>
修改
</Button>
<Button
onClick={() =>
navigate(`/editSpacePost?user_id=${record.baseInfo.id}`)
}
>
查看动态
</Button>
</Space.Compact>
</Space>
</div>
),
});
//checkbox
const showColumnsOptions = [
{
label: "基础信息",
value: "baseInfo",
},
{
label: "空间介绍",
value: "profile",
},
{
label: "付费设置",
value: "paymentSettings",
},
{
label: "活跃情况",
value: "active",
},
{
label: "分成情况",
value: "ratio",
},
];
//
const [showData, setShowData] = useState([]);
//
const search = async (value) => {
if (!value.id) {
getAllSpace();
return;
}
try {
const base = baseRequest();
const detailResponse = await fetch(`/op/zone/list_by_user_id`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: parseInt(value.id, 10),
...base,
}),
});
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
alert(detailData.msg);
return;
}
const zone = detailData.data.list[0];
setShowData([
{
key: zone.id,
baseInfo: {
id: zone.streamer_ext.user_id,
name: zone.streamer_ext.name,
avatar: zone.streamer_ext.avatar.images[0].urls[0],
ct: new Date(zone.ct * 1000).toLocaleString(),
},
profile: zone.profile,
paymentSettings: {
admission_price: zone.admission_price / 100,
ironfanship_price: zone.ironfanship_price / 100,
is_superfanship_enabled: zone.is_superfanship_enabled,
superfanship_price: zone.superfanship_price / 100,
superfanship_valid_period: zone.superfanship_valid_period,
is_superfanship_give_wechat: zone.is_superfanship_give_wechat,
},
active: {
zone_moment_count: zone.zone_moment_count,
image_count: zone.image_count,
video_count: zone.video_count,
last_zone_moment_ct: new Date(
zone?.last_zone_moment_ct * 1000
).toLocaleString(),
},
ratio: {
zone_third_partner: zone.zone_third_partner,
zone_collaborator_list: zone.zone_collaborator_list,
},
},
]);
} catch (error) {
console.error(error);
}
};
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
//
const getAllSpace = async () => {
try {
const base = baseRequest();
const detailResponse = await fetch(`/op/zone/list`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
offset: 0,
limit: 2000,
...base,
}),
});
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
alert(detailData.msg);
return;
}
//
const structedData = detailData.data.list.map((item, index) => {
return {
key: item.id,
baseInfo: {
id: item.streamer_ext.user_id,
name: item.streamer_ext.name,
avatar: item.streamer_ext.avatar.images[0].urls[0],
ct: new Date(item.ct * 1000).toLocaleString(),
},
profile: item.profile,
paymentSettings: {
admission_price: item.admission_price / 100,
ironfanship_price: item.ironfanship_price / 100,
is_superfanship_enabled: item.is_superfanship_enabled,
superfanship_price: item.superfanship_price / 100,
superfanship_valid_period: item.superfanship_valid_period,
is_superfanship_give_wechat: item.is_superfanship_give_wechat,
},
active: {
zone_moment_count: item.zone_moment_count,
image_count: item.image_count,
video_count: item.video_count,
last_zone_moment_ct: new Date(
item?.last_zone_moment_ct * 1000
).toLocaleString(),
},
ratio: {
zone_third_partner: item.zone_third_partner,
zone_collaborator_list: item.zone_collaborator_list,
},
};
});
setShowData(structedData);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
getAllSpace();
}, []);
//modal
const [isModalOpen, setIsModalOpen] = useState(false);
//modal
const handleCancel = () => {
setShowData([]);
setDefaultValues({});
setIsModalOpen(false);
};
//modal
const [defaultValues, setDefaultValues] = useState({});
//
const handleModal = (record) => {
setDefaultValues(record);
setIsModalOpen(true);
};
//
const onModalFormFinish = async (value) => {
//...
if (
!value.admission_price?.toString() ||
!value.ironfanship_price?.toString() ||
!value.is_superfanship_enabled?.toString() ||
!value.profile
) {
alert("请完善表单信息");
return;
}
if (
parseInt(value.is_superfanship_enabled) === 1 &&
(!value.superfanship_price?.toString() ||
!value.superfanship_valid_period?.toString() ||
!value.is_superfanship_give_wechat?.toString())
) {
alert("请完善表单信息");
return;
}
const _spacePrice = parseInt(value.admission_price * 100, 10);
if (isNaN(_spacePrice) || _spacePrice < 0) {
alert("请输入有效的解锁空间价格");
return;
}
const _ironFanPrice = parseInt(value.ironfanship_price * 100, 10);
if (isNaN(_ironFanPrice) || _ironFanPrice < 100 || _ironFanPrice > 388800) {
alert("请输入有效的铁粉价格");
return;
}
const _superFanPrice = parseInt(value.superfanship_price * 100, 10);
if (
parseInt(value.is_superfanship_enabled) === 1 &&
(isNaN(_superFanPrice) || _superFanPrice < 100 || _superFanPrice > 388800)
) {
alert("请输入有效的超粉价格");
return;
}
if (
parseInt(value.is_superfanship_enabled) === 1 &&
_superFanPrice <= _ironFanPrice
) {
alert("请输入大于铁粉价格的超粉价格");
return;
}
try {
const base = baseRequest();
const detailResponse = await fetch(`/op/zone/update`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: defaultValues.key,
admission_price: parseInt(value.admission_price * 100, 10),
ironfanship_price: parseInt(value.ironfanship_price * 100, 10),
is_superfanship_enabled: parseInt(value.is_superfanship_enabled),
superfanship_price: parseInt(value.superfanship_price * 100, 10),
superfanship_valid_period: parseInt(
value.superfanship_valid_period,
10
),
is_superfanship_give_wechat: parseInt(
value.is_superfanship_give_wechat,
10
),
...base,
}),
});
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
alert(detailData.msg);
return;
}
} catch (error) {
console.error(error);
}
//
setShowData([]);
setDefaultValues({});
setIsModalOpen(false);
};
//
const onModalFormFinishFailed = (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="ID" name="id" style={{ margin: 0 }}>
<Input />
</Form.Item>
<Button type="primary" htmlType="submit">
搜索
</Button>
</Space>
</Form>
<p>{showData.length}条结果</p>
<Table
columns={dynamicColumns}
dataSource={showData}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>
{/* 模态框是否显示 */}
{isModalOpen && (
<Form
name="change"
onFinish={onModalFormFinish}
onFinishFailed={onModalFormFinishFailed}
autoComplete="off"
>
<Modal
title="修改空间设置"
cancel={handleCancel}
content={
<div
className="flex flex-col overflow-y-scroll"
style={{ height: (window.innerHeight * 2) / 3 }}
>
<div className="flex flex-row">
<Image
width={80}
height={80}
className="rounded-full"
src={defaultValues.baseInfo.avatar}
/>
<div className="flex flex-col justify-between ml-2">
<p className="font-bold">ID{defaultValues.baseInfo.id}</p>
<p className="font-bold">
昵称{defaultValues.baseInfo.name}
</p>
<p className="font-bold">
创建时间{defaultValues.baseInfo.ct}
</p>
</div>
</div>
<div
className="flex flex-row flex-wrap"
style={{ width: (window.innerWidth * 2) / 3 }}
>
<Form.Item
className="basis-1/2 px-2"
name="profile"
label="空间介绍"
initialValue={defaultValues.profile}
rules={[
{
required: true,
message: "请输入空间介绍",
},
]}
>
<TextArea />
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="admission_price"
label="解锁空间价格(元)"
initialValue={defaultValues.paymentSettings.admission_price}
rules={[
{
required: true,
message: "请输入解锁空间价格",
},
]}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="ironfanship_price"
label="铁粉价格(元)"
initialValue={
defaultValues.paymentSettings.ironfanship_price
}
rules={[
{
required: true,
message: "请输入铁粉价格",
},
]}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="is_superfanship_enabled"
label="是否开通超粉功能"
initialValue={
defaultValues.paymentSettings.is_superfanship_enabled
}
rules={[
{
required: true,
message: "请选择是否开通超粉功能",
},
]}
>
<select
style={{
height: 32,
padding: "4px 11px",
border: "1px solid #d9d9d9",
borderRadius: 4,
outline: "none",
}}
>
<option value={1}></option>
<option value={0}></option>
</select>
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="superfanship_price"
label="超粉价格(元)"
initialValue={
defaultValues.paymentSettings.superfanship_price
}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="superfanship_valid_period"
label="超粉有效期"
initialValue={
defaultValues.paymentSettings.superfanship_valid_period
}
>
<select
style={{
height: 32,
padding: "4px 11px",
border: "1px solid #d9d9d9",
borderRadius: 4,
outline: "none",
}}
>
<option value={0}>永久</option>
<option value={1}>月度</option>
<option value={2}>季度</option>
<option value={3}>半年</option>
<option value={4}>年度</option>
</select>
</Form.Item>
<Form.Item
className="basis-1/2 px-2"
name="is_superfanship_give_wechat"
label="开通超粉是否送微信"
initialValue={
defaultValues.paymentSettings.is_superfanship_give_wechat
}
>
<select
style={{
height: 32,
padding: "4px 11px",
border: "1px solid #d9d9d9",
borderRadius: 4,
outline: "none",
}}
>
<option value={1}></option>
<option value={0}></option>
</select>
</Form.Item>
</div>
</div>
}
/>
</Form>
)}
</div>
);
};
export default function StreamerSpace() {
return <StreamerSpaceContent />;
}

View File

@ -189,6 +189,7 @@ const ZonePostMachineReviewContent = (props) => {
}),
});
const temData = await response.json();
console.log(temData);
if (temData.ret === -1) {
alert(temData.msg);
return;

View File

@ -23,6 +23,10 @@ import Refund from "../pages/Refund";
import BlockUser from "../pages/BlockUser";
import DeletedPostReview from "../pages/DeletedPostReview";
import ZonePostMachineReview from "../pages/ZonePostMachineReview";
import StreamerSpace from "../pages/StreamerSpace";
import SpaceMemberRefundQuerry from "../pages/SpaceMemberRefundQuerry";
import QuerryMidAndUserId from "../pages/QuerryMidAndUserId";
import EditSpacePost from "../pages/EditSpacePost";
const routes = [
{
@ -125,6 +129,22 @@ const routes = [
path: "zonePostMachineReview/*",
element: <ZonePostMachineReview />,
},
{
path: "streamerSpace/*",
element: <StreamerSpace />,
},
{
path: "spaceMemberRefundQuerry/*",
element: <SpaceMemberRefundQuerry />,
},
{
path: "querryMidAndUserId/*",
element: <QuerryMidAndUserId />,
},
{
path: "editSpacePost/*",
element: <EditSpacePost />,
},
],
},
];