更改广场动态审核功能

This commit is contained in:
al 2024-11-21 18:37:48 +08:00
parent a02e57e404
commit 647bbe9ff3
13 changed files with 1161 additions and 101 deletions

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { uploadImage } from "../../utils/upload";
const ImageUploader = ({ setIds }) => {
const ImageUploader = ({ setIds, uploadOne }) => {
const [selectedImages, setSelectedImages] = useState([]);
const [uploading, setUploading] = useState(false);
@ -86,40 +86,42 @@ const ImageUploader = ({ setIds }) => {
)}
</div>
))}
<label
htmlFor="fileInput"
style={{
width: "100px",
height: "100px",
margin: "5px",
background: "#9ca3af",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
position: "relative",
borderRadius: "8px",
}}
>
<span className="text-white">
{uploading ? "正在上传..." : "添加图片"}
</span>
<input
id="fileInput"
type="file"
multiple
onChange={handleFileChange}
{uploadOne && selectedImages.length ? null : (
<label
htmlFor="fileInput"
style={{
opacity: 0,
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
width: "100px",
height: "100px",
margin: "5px",
background: "#9ca3af",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
position: "relative",
borderRadius: "8px",
}}
/>
</label>
>
<span className="text-white">
{uploading ? "正在上传..." : "添加图片"}
</span>
<input
id="fileInput"
type="file"
multiple
onChange={handleFileChange}
style={{
opacity: 0,
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
cursor: "pointer",
}}
/>
</label>
)}
</div>
</div>
);

View File

@ -0,0 +1,93 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
List,
Input,
Button,
message,
// Spin,
} from "antd";
import baseRequest from "../../utils/baseRequest";
export default function PagesManage() {
const [pages, setPages] = useState([
{ title: "主播空间", name: "streamerSpace", params: "name=text" },
{ title: "主播主页", name: "streamerProfile", params: "" },
{ title: "充值页面", name: "payCenter", params: "" },
{ title: "主播空间", name: "streamerSpace", params: "" },
{ title: "主播空间", name: "streamerSpace", params: "" },
{ title: "主播空间", name: "streamerSpace", params: "" },
]);
//创建页面路由
const handleCreate = async ({ id, route_path, action, desc }) => {
try {
const base = baseRequest();
const response = await fetch("/op/frontend_route/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id,
route_path,
action,
desc,
...base,
}),
});
const _data = await response.json();
if (_data.ret === -1) {
alert(_data.msg);
return;
}
message.success("创建成功");
} catch (error) {
console.error(error);
}
};
return (
<div className="my-4">
<List split={false}>
<div className="my-6 flex items-center justify-between">
<div className="flex-1 grid grid-cols-[20%,20%,auto] gap-2">
<Input placeholder="页面名称" />
<Input placeholder="页面值" />
<Input placeholder="页面参数" />
</div>
<Button type="primary" className="ml-2">
新增+
</Button>
</div>
{pages.map((it) => (
<div className="mt-2 ">
<List.Item style={{ padding: 0 }}>
<div className="w-full">
<div className="flex items-center justify-between rounded-t-lg px-4 py-2 bg-[#dcdcdc80]">
<p className="flex items-center">
<span className="text-base">{it.title}</span>
<span className="mx-1">|</span>
<span className="text-[gray]">{it.name}</span>
</p>
<div>
<span className="px-2 py-1 rounded-lg cursor-pointer">
编辑
</span>
<span className="px-2 py-1 rounded-lg text-[#ff000064] cursor-pointer">
删除
</span>
</div>
</div>
<div className="flex rounded-b-lg px-4 py-0.5 bg-[#dcdcdc41] text-xs text-[#9d9d9d]">
{it.params && (
<div className="py-2">
<span>参数</span>
<span>{`?${it.params}`}</span>
</div>
)}
</div>
</div>
</List.Item>
</div>
))}
</List>
</div>
);
}

View File

@ -0,0 +1,141 @@
import React, { useEffect, useState } from "react";
import { Spin, Image, ImageViewer } from "antd";
import { uploadImage } from "../../utils/upload";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { faAdd, faClose } from "@fortawesome/free-solid-svg-icons";
import { FileAddFilled, CloseCircleFilled } from "@ant-design/icons";
export default function UploadImgs({ assets, getImgs, multiple }) {
const maxCount = 6;
const [fileList, setFileList] = useState([]);
const [loading, setLoading] = useState(false);
// useEffect(() => {
// if (!assets) {
// setFileList([]);
// }
// }, assets);
const handleUploadImage = async (e) => {
let file = e.target.files[0];
if (!file) return;
setLoading(true);
if (file.type.indexOf("image/") != -1) {
// const image = await uploadImage(file);
// getImgs((old) => [...old, image.id]);
const newFiles = [file];
const newAssets = newFiles.map((item) => ({
type: "img",
src: URL.createObjectURL(item),
}));
const uploadPromise = uploadImage(file);
const uploadResult = await Promise.race([
uploadPromise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Upload timeout")), 30000)
),
]);
console.log("uploadResult", uploadResult);
setFileList(newAssets);
getImgs(uploadResult);
} else if (file.type.indexOf("video/") != -1) {
if (typeof window == "undefined") return;
const videoD = document.getElementById("videoD");
const videoC = document.getElementById("videoC");
// console.log("videoC", videoC);
const ctx = videoC?.getContext("2d");
// 设置Canvas的宽度和高度与视频一致
videoC.width = videoD.videoWidth;
videoC.height = videoD.videoHeight;
// 在Canvas上绘制当前视频帧
ctx.drawImage(videoD, 0, 0, videoC.width, videoC.height);
// 将Canvas转换为图片URL
const frameImageUrl = videoC.toDataURL();
// 输出图片URL
// console.log(frameImageUrl);
// console.log("ddddd", file, {
// type: "video",
// src: frameImageUrl,
// });
// const video = await uploadVideo(file);
// getImgs((old) => [...old, video.id]);
// setFileList((old) => [...old, video]);
const newFiles = [...assets, file];
const newAssets = newFiles.map((item) => ({
type: "video",
src: URL.createObjectURL(item),
}));
setFileList(newAssets);
}
setLoading(false);
};
const handleRemoveItem = (index) => {
// console.log(index);
let newArr = [...fileList];
newArr.splice(index, 1);
setFileList(newArr);
getImgs(newArr);
};
return (
<div>
<div className="grid grid-cols-4 gap-1 h-28">
{fileList.map((item, index) => {
return (
<div key={index} className="rounded relative">
<Image
src={item?.src}
width="100%"
height="100%"
className="rounded"
/>
<div
className="h-4 w-4 bg-[#33333380] absolute top-0 right-0 flex justify-center items-center"
onClick={() => handleRemoveItem(index)}
>
<CloseCircleFilled />
</div>
</div>
);
})}
{loading && (
<div className="rounded border-[#00000040] text-[#00000040] flex flex-col justify-center items-center">
<Spin />
<p>上传中</p>
</div>
)}
{(multiple || (!multiple && !fileList.length)) && !loading && (
<label htmlFor="uploadAvatarBtn">
<div
className="border-2 border-[#00000040] text-[#00000040] rounded border-dashed w-full h-full flex justify-center items-center"
// style={{ minHeight: "calc(25vw - 0.75rem)" }}
>
<FileAddFilled />
</div>
</label>
)}
<input
type="file"
id="uploadAvatarBtn"
multiple={multiple}
style={{ display: "none" }}
// accept="image/png, image/jpeg, video/*"
accept="image/png, image/jpeg, image/jpg"
// capture="camera"
onChange={handleUploadImage}
/>
</div>
<div className="hidden">
<video id="videoD">
<source src={fileList[0]?.src} />
您的浏览器不支持 Video 标签
</video>
<canvas id="videoC" />
</div>
</div>
);
}
// export async function mockUpload(file) {
// await sleep(3000)
// return {
// url: URL.createObjectURL(file),
// }
// }

View File

@ -1,11 +1,18 @@
import React, { useState } from "react";
import { Select } from "antd";
import ReactPlayer from "react-player";
export default function VideoPlayer({url,key,style}) {
export default function VideoPlayer({ url, key, style }) {
const [rate, setRate] = useState(1);
return (
<div key={key} >
<ReactPlayer playbackRate={rate} url={url} width={150} controls style={style}/>
<div key={key}>
<ReactPlayer
playbackRate={rate}
url={url}
width="100%"
height="auto"
controls
style={style}
/>
<Select onChange={(e) => setRate(e)} value={rate}>
<Select.Option value={1}>x1</Select.Option>
<Select.Option value={2}>x2</Select.Option>

752
src/pages/Notices/index.jsx Normal file
View File

@ -0,0 +1,752 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
Form,
Input,
Button,
Space,
Table,
Modal,
Image,
Select,
message,
// Upload,
DatePicker,
Radio,
Tag,
// Spin,
} from "antd";
import { LoadingOutlined, PlusOutlined, LayoutFilled } from "@ant-design/icons";
import zh from "antd/es/date-picker/locale/zh_CN";
import baseRequest from "../../utils/baseRequest";
import UploadImgs from "../../components/UploadImgs";
import { formatDeadline } from "../../utils/tools";
import PagesManage from "../../components/PagesManage";
const NoticesContent = (props) => {
//modal
const [isModalOpen, setIsModalOpen] = useState(false);
const [isPagesModalOpen, setIsPagesModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
// const [assets, setAssets] = useState([]);
const [isSubmitting, setIsSubmitting] = useState(false);
//
const [form] = Form.useForm();
const { TextArea } = Input;
const current = props.current;
//
const columns = [
{
title: "推送消息类型",
dataIndex: "n_type",
key: "n_type",
filters: [
{
text: "系统通知",
value: 0,
},
{
text: "付费通知",
value: 1,
},
{
text: "活动通知",
value: 2,
},
{
text: "审核通知",
value: 3,
},
],
filterMode: "tree",
onFilter: (value, record) => record.status == value,
render: (data) => {
return (
<div>
{data == 0
? "系统通知"
: data == 1
? "付费通知"
: data == 2
? "活动通知"
: "审核通知"}
</div>
);
},
},
{
title: "推送消息正文",
dataIndex: "message",
key: "message",
},
{
title: "推送消息主图",
dataIndex: "thumbnail",
key: "thumbnail",
render: (data) => (
<div>
{data
? data.images?.map((item, index) => (
<Image
key={index}
src={item.urls[0]}
style={{ marginBottom: 10 }}
/>
))
: "-"}
</div>
),
},
{
title: "链接标语",
dataIndex: "link_text",
key: "link_text",
render: (data) => <div>{data || "-"}</div>,
},
{
title: "链接",
dataIndex: "params",
key: "params",
render: (data) => <div>{data || "-"}</div>,
},
{
title: "推送时间",
dataIndex: "push_time",
key: "push_time",
render: (data) => {
return <div>{formatDeadline(data)}</div>;
},
},
{
title: "推送角色",
dataIndex: "obj_mids",
key: "obj_mids",
render: (data) => (
<div>
{data?.length > 0 ? data.map((it) => <span>{it}</span>) : "-"}
</div>
),
},
{
title: "推送角色类型",
dataIndex: "obj_type",
key: "obj_type",
render: (data) => {
return (
<div>
{data == 0
? "所有主播"
: data == 1
? "所有个人"
: data == 2
? "所有主播和个人"
: "自定义角色"}
</div>
);
},
},
{
title: "推送状态",
dataIndex: "status",
key: "status",
filters: [
{
text: "待推送",
value: 0,
},
{
text: "已推送",
value: 1,
},
{
text: "推送失败",
value: 2,
},
{
text: "已撤销",
value: 3,
},
],
filterMode: "tree",
onFilter: (value, record) => record.status == value,
render: (data) => (
<div>
{data == 0 ? (
<Tag color="green">待推送</Tag>
) : data == 1 ? (
<Tag color="blue">已推送</Tag>
) : data == 2 ? (
<Tag color="red">推送失败</Tag>
) : (
<Tag color="orange">已撤销</Tag>
)}
</div>
),
},
{
title: "操作",
dataIndex: "opeartion",
key: "opeartion",
render: (_, record) => {
console.log("eeeeee", record);
return (
<div>
<Space>
<Space.Compact direction="vertical">
{record.status == 0 && (
<Button
style={{ background: "#ff4d4f", color: "#fff" }}
onClick={() => handleCancleNotice(record?.id)}
>
撤销通知
</Button>
)}
</Space.Compact>
</Space>
</div>
);
},
},
];
//ref
const formRef = useRef(null);
//
const onClickSubmit = (record) => {
formRef.current.key = record.key;
formRef.current.btn = "submit";
formRef.current.submit();
};
//
const onClickInvalid = (record) => {
formRef.current.key = record.key;
formRef.current.btn = "invalid";
formRef.current.submit();
};
//
const onClickIteration = (record) => {
formRef.current.key = record.key;
formRef.current.btn = "iteration";
formRef.current.submit();
};
//
const handleCancleNotice = async (nid) => {
try {
const base = baseRequest();
const response = await fetch("/op/notification/cancel", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nid,
...base,
}),
});
const temData = await response.json();
if (temData.ret === -1) {
alert(temData.msg);
return;
}
message.success("撤销成功");
} catch (error) {
console.error(error);
}
};
const buddhistLocale = {
...zh,
lang: {
...zh.lang,
fieldDateFormat: "YYYY-MM-DD",
fieldDateTimeFormat: "YYYY-MM-DD HH:mm:ss",
yearFormat: "YYYY",
cellYearFormat: "YYYY",
},
};
//
const [data, setData] = useState([]);
const [offset, setOffset] = useState(0);
const [more, setMore] = useState(1);
const [formData, setFormData] = useState({
message: "",
n_type: 0,
obj_mids: [],
// push_time: 1111111111111,
params: null,
pageName: null,
link: null,
action: null,
link_text: null,
thumbnail: null,
obj_type: "0",
});
const getData = async () => {
if (!more) return;
let querryStatus;
switch (current) {
case "pushing":
querryStatus = 0;
break;
case "pushed":
querryStatus = 1;
break;
case "fail":
querryStatus = 2;
break;
case "backed":
querryStatus = 3;
break;
default:
break;
}
try {
const base = baseRequest();
const response = await fetch("/op/notification/list", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
n_type: 0,
// push_time: 0,
// status: querryStatus,
// offset: offset,
// limit: 200,
...base,
}),
});
const temData = await response.json();
console.log("response", temData);
if (temData.ret === -1) {
alert(temData.msg);
return;
}
setShowData([...temData.data.list]);
setOffset(temData.data.offset);
setMore(temData.data.more);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
getData();
}, [current]);
//
const [showData, setShowData] = useState(data);
//
const onFinishFailed = (errorInfo) => {
console.log("Failed:", errorInfo);
};
//
const handleCancelModal = () => {
form.resetFields();
setIsModalOpen(false);
};
const handleSubmit = async (value) => {
// if (!selectedUser) {
// alert("");
// return;
// }
const newValue = { ...value };
newValue["thumbnail"] = formData.thumbnail;
newValue["obj_mids"] = newValue.obj_mids
?.split(" ")
.map((it) => Number(it));
newValue["push_time"] = Math.ceil(
new Date(newValue.push_time).getTime() / 1000
);
newValue["obj_type"] = parseInt(newValue.obj_type, 10);
console.log("newValue", newValue);
debugger;
try {
const base = baseRequest();
const response = await fetch(`/op/notification/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
sub_mid: 0,
...base,
...newValue,
}),
});
const data = await response.json();
if (data.ret === -1) {
alert(data.msg);
return;
}
message.success("发布成功");
} catch (error) {
console.error(error);
}
form.resetFields();
};
const onTimeOk = (value) => {
console.log("onOk: ", value);
};
const uploadButton = (
<button
style={{
border: 0,
background: "none",
}}
type="button"
>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div
style={{
marginTop: 8,
}}
>
上传图片
</div>
</button>
);
return (
<div style={{ marginLeft: 20, marginRight: 20 }}>
<div className="flex justify-between items-center">
<Button
type="default"
className="mt-4 mb-4 float-right"
onClick={() => setIsPagesModalOpen(true)}
icon={<LayoutFilled className="text-primary" />}
>
页面管理
</Button>
<Button
type="primary"
className="mt-4 mb-4 float-right"
onClick={() => setIsModalOpen(true)}
>
新建推送+
</Button>
</div>
<Table
columns={columns}
dataSource={showData}
pagination={{ pageSize: 20 }}
scroll={{ y: window.innerHeight - 300 }}
/>
{/* 重复判断isModalOpen是为了重新渲染ImageUploader和VideoUploader组件 */}
<Modal
title="新建消息推送通知"
footer={null}
open={isModalOpen}
onCancel={handleCancelModal}
>
<Form
className="mt-4 flex flex-col"
form={form}
onFinish={handleSubmit}
layout="vertical"
initialValues={formData}
>
<Form.Item
label="添加活动消息正文上限250字"
name="message"
rules={[
{
required: true,
message: "请填写活动消息内容",
},
]}
className="mb-6"
>
<TextArea maxLength={250} showCount />
</Form.Item>
<Form.Item
label="推送消息类型"
name="n_type"
rules={[
{
required: true,
message: "请选择推送消息类型",
},
]}
className="mb-6"
>
<Select
// defaultValue={1}
style={{
width: 120,
}}
// onChange={handleChange}
options={[
{
value: 0,
label: "系统通知",
},
{
value: 1,
label: "付费通知",
},
{
value: 2,
label: "活动通知",
},
{
value: 3,
label: "审核通知",
},
]}
/>
</Form.Item>
<Form.Item
label="推送角色"
name="obj_type"
rules={[
{
required: true,
message: "请填写推送角色",
},
]}
className={formData.obj_type == 3 ? "mb-2" : "mb-6"}
>
<div>
<Radio.Group
defaultValue={0}
onChange={(e) => {
console.log(e.target.value);
setFormData((old) => {
return {
...old,
obj_type: e.target.value,
};
});
}}
>
<Radio value={0}>所有主播</Radio>
<Radio value={1}>所有个人</Radio>
<Radio value={2}>所有主播和个人</Radio>
<Radio value={3}>自定义角色</Radio>
</Radio.Group>
</div>
</Form.Item>
{formData.obj_type == 3 && (
<Form.Item
name="obj_mids"
rules={[
{
required: true,
message: "请填写推送角色",
},
]}
className="mb-6"
>
<div>
<TextArea
value={formData.obj_mids}
onChange={(e) =>
setFormData((old) => ({
...old,
obj_mids: e.target.value,
}))
}
placeholder="用户ID以空格隔开"
/>
</div>
</Form.Item>
)}
<Form.Item
label="推送时间"
name="push_time"
format="YYYY-MM-DD HH:mm"
rules={[
{
required: true,
message: "请选择时间",
},
]}
className="mb-6"
>
<DatePicker
showTime
// locale={{
// lang: { locale: "zh_CN", placeholder: "" },
// }}
locale={buddhistLocale}
onChange={(value, dateString) => {
// console.log("Time: ", new Date());
console.log("Selected Time: ", value);
console.log("Formatted Selected Time: ", dateString);
}}
onOk={onTimeOk}
/>
</Form.Item>
<p className="mb-2">添加跳转内容</p>
<Space
direction="vertical"
className="bg-[#00000011] rounded-md p-4 mb-4"
>
<Form.Item label="跳转形式" name="action" className="mb-2">
<Select
placeholder="选择跳转形式"
style={{
width: 120,
}}
// onChange={handleChange}
options={[
{
value: "app",
label: "APP内跳转",
},
{
value: "h5",
label: "内部H5跳转",
},
{
value: "out",
label: "外部浏览器跳转",
},
]}
onChange={(value) => {
setFormData((old) => ({
...old,
action: value,
}));
}}
/>
</Form.Item>
{formData.action && (
<div>
<div>
{formData.action == "out" ? (
<Form.Item
rules={[
{
required: true,
message: "请填写具体链接地址",
},
]}
label="添加链接"
name="link"
className="mb-2"
>
<Input
onChange={(value) => {
console.log("value", value);
setFormData((old) => ({
...old,
link: value.target.value,
}));
}}
/>
</Form.Item>
) : (
<Space direction="vertical" className="w-full">
<label>
<span className="text-[red] mr-1">*</span>添加链接
</label>
<div className="flex items-center">
<Form.Item name="pageName" className="mb-2">
<Select
placeholder="选择页面"
style={{
width: 120,
}}
// onChange={handleChange}
options={[
{
value: "app",
label: "空间主页",
},
{
value: "h5",
label: "活动页",
},
]}
/>
</Form.Item>
<div className="-mt-2 mx-2">?</div>
<Form.Item
layout="horizontal"
name="params"
className="mb-2 w-full flex-1"
style={{ width: "100%" }}
>
<Input
className="w-full"
placeholder="填写连接参数"
onChange={(value) => {
console.log("value", value);
setFormData((old) => ({
...old,
params: value.target.value,
}));
}}
/>
</Form.Item>
</div>
</Space>
)}
</div>
<Form.Item
label="添加链接标题"
name="link_text"
rules={[
{
required: true,
message: "请填写链接标题",
},
]}
className="mb-2"
>
<Input placeholder="链接标题" />
</Form.Item>
<Form.Item
label="添加主图"
name="thumbnail"
// rules={[
// {
// required: true,
// message: "",
// },
// ]}
className="mb-2"
>
<UploadImgs
assets={formData.thumbnail}
getImgs={(value) => {
setFormData((old) => ({
...old,
thumbnail: { image_ids: [value] },
}));
}}
multiple={false}
/>
</Form.Item>
</div>
)}
</Space>
<Button type="primary" htmlType="submit">
确认
</Button>
</Form>
</Modal>
<Modal
title="页面管理"
footer={null}
open={isPagesModalOpen}
style={{ minWidth: 800 }}
onCancel={() => setIsPagesModalOpen(false)}
>
<PagesManage />
</Modal>
</div>
);
};
export default function Notices() {
return (
<div>
<NoticesContent />
</div>
);
}

View File

@ -4,6 +4,7 @@ import {
ShopOutlined,
UsergroupAddOutlined,
SoundOutlined,
// NotificationOutlined,
PhoneOutlined,
SafetyCertificateOutlined,
PoweroffOutlined,
@ -50,7 +51,7 @@ export default function Op() {
getItem("主播文本机审回查", "streamerTextMachineReview"),
getItem("头像机审回查", "imageMachineReview"),
getItem("昵称机审回查", "textMachineReview"),
getItem("动态机审回查", "postMachineReview"),
getItem("广场动态审核", "postMachineReview"),
getItem("空间动态审核", "zonePostMachineReview"),
getItem("已删除动态回捞", "deletedPostReview"),
]),
@ -81,6 +82,7 @@ export default function Op() {
"manualRechargeAndWithdrawal",
<MoneyCollectOutlined />
),
// getItem("", "notices", <NotificationOutlined />),
getItem("意见反馈", "feedback", <SoundOutlined />),
getItem("客服回复", "contact", <PhoneOutlined />),
getItem("退出登录", "signOut", <PoweroffOutlined />),

View File

@ -8,7 +8,7 @@ import {
useLocation,
} from "react-router-dom";
import baseRequest from "../../utils/baseRequest";
import VideoPlayer from '../../components/VideoPlayer';
import VideoPlayer from "../../components/VideoPlayer";
const PostMachineReviewContent = (props) => {
const { TextArea } = Input;
const current = props.current;
@ -31,7 +31,7 @@ const PostMachineReviewContent = (props) => {
),
},
{
title: "内容",
title: "动态内容",
dataIndex: "newMedia",
key: "newMedia",
render: (data) => (
@ -47,7 +47,7 @@ const PostMachineReviewContent = (props) => {
<Image key={index} src={item.urls[0]} width={100} />
))}
{data.media?.videos?.map((item, index) => (
<VideoPlayer key={index} url={item.urls[0]} width={150} />
<VideoPlayer key={index} url={item.urls[0]} width={150} />
))}
</div>
</div>
@ -68,7 +68,7 @@ const PostMachineReviewContent = (props) => {
<Image key={index} src={item.urls[0]} width={100} />
))}
{data.media?.videos?.map((item, index) => (
<VideoPlayer key={index} url={item.urls[0]} width={150} />
<VideoPlayer key={index} url={item.urls[0]} width={150} />
))}
</div>
</div>
@ -247,16 +247,10 @@ const PostMachineReviewContent = (props) => {
render: (_, record) => (
<div className="flex flex-col">
<Form.Item
name={record.id.text}
initialValue={`文案:${record.remarks.text}`}
name={record.momentId}
initialValue={record.remarks.manually_review_opinion}
>
<TextArea disabled rows={4} />
</Form.Item>
<Form.Item
name={record.id.media}
initialValue={`媒体:${record.remarks.media}`}
>
<TextArea disabled rows={4} />
<TextArea rows={4} disabled={current != "operatorReviewing"} />
</Form.Item>
</div>
),
@ -268,12 +262,14 @@ const PostMachineReviewContent = (props) => {
render: (_, record) => (
<div>
<Space>
<Space.Compact direction="vertical">
<Button type="primary" onClick={() => onClickPass(record)}>
通过
</Button>
<Button onClick={() => onClickReject(record)}>拒绝</Button>
</Space.Compact>
{current === "operatorReviewing" && (
<Space.Compact direction="vertical">
<Button type="primary" onClick={() => onClickPass(record)}>
通过
</Button>
<Button onClick={() => onClickReject(record)}>拒绝</Button>
</Space.Compact>
)}
</Space>
</div>
),
@ -294,7 +290,14 @@ const PostMachineReviewContent = (props) => {
formRef.current.submit();
};
//
const handleSubmit = async () => {
const handleSubmit = async (value) => {
const cc = {
moment_ids: [parseInt(formRef.current.record.momentId, 10)],
op_type: formRef.current.type,
manually_review_opinion: value[formRef.current.record.momentId]
? value[formRef.current.record.momentId]
: "",
};
try {
//
const base = baseRequest();
@ -306,6 +309,9 @@ const PostMachineReviewContent = (props) => {
body: JSON.stringify({
moment_ids: [parseInt(formRef.current.record.momentId, 10)],
op_type: formRef.current.type,
manually_review_opinion: value[formRef.current.record.momentId]
? value[formRef.current.record.momentId]
: "",
...base,
}),
});
@ -332,14 +338,14 @@ const PostMachineReviewContent = (props) => {
if (!more) return;
let querryStatus;
switch (current) {
case "rollbackbymachine":
case "operatorReviewing":
querryStatus = 4;
break;
case "machinereviewfail":
querryStatus = 10;
case "machineReviewing":
querryStatus = 3;
break;
case "passbymachine":
querryStatus = 2;
case "onlySelfCanSee":
querryStatus = 6;
break;
default:
break;
@ -386,23 +392,31 @@ const PostMachineReviewContent = (props) => {
media: item.moment_audit_task.audited_media,
text: item.moment_audit_task.audited_text,
},
info: {
mediaType:
item.moment_audit_task.audited_media.videos.length === 0
? "image"
: "video",
media:
item.moment_audit_task.audited_media.videos.length === 0
? item.image_audit_task_vo.image_audits
: item.moment_audit_task.image_audit_opinion,
text: item.text_audit_task_vo.text_audit,
},
info: item.moment_audit_task.audited_media
? {
mediaType:
item.moment_audit_task.audited_media.videos.length === 0
? "image"
: "video",
media:
item.moment_audit_task.audited_media.videos.length === 0
? item.image_audit_task_vo.image_audits
: item.moment_audit_task.image_audit_opinion,
text: item.text_audit_task_vo.text_audit,
}
: { text: item.text_audit_task_vo.text_audit },
submitTime: new Date(
item.moment_audit_task.ct * 1000
).toLocaleString(),
remarks: {
media: item.image_audit_task_vo.remarks,
text: item.text_audit_task_vo.remarks,
// media: item.image_audit_task_vo.remarks,
// text: item.moment_audit_task.remarks,
manually_review_status:
item.moment_audit_task?.manually_review_status,
manually_review_opinion:
item.moment_audit_task?.manually_review_opinion,
manually_review_operator:
item.moment_audit_task?.manually_review_operator,
},
others: {
media: {
@ -455,8 +469,8 @@ const PostMachineReviewContent = (props) => {
return (
<div style={{ marginLeft: 20, marginRight: 20 }}>
<p>
{current === "rollbackbymachine" && `机审核违规${showData.length}`}
{current === "passbymachine" && `机审核通过${showData.length}`}
{current === "operatorReviewing" && `运营待审核${showData.length}`}
{current === "onlySelfCanSee" && `审核未通过${showData.length}`}
</p>
<Form
ref={formRef}
@ -479,21 +493,21 @@ export default function PostMachineReview() {
const navigate = useNavigate();
//tab
const location = useLocation();
const pathname = location.pathname.split("/")[2] || "rollbackbymachine";
const pathname = location.pathname.split("/")[2] || "onlySelfCanSee";
const [current, setCurrent] = useState(pathname);
//tab
const items = [
{
label: "机审回退",
key: "rollbackbymachine",
label: "运营待审核",
key: "operatorReviewing",
},
{
label: "机审失败",
key: "machinereviewfail",
label: "审核中",
key: "machineReviewing",
},
{
label: "审通过",
key: "passbymachine",
label: "核未通过",
key: "onlySelfCanSee",
},
];
const onClick = (e) => {
@ -513,19 +527,19 @@ export default function PostMachineReview() {
<Routes>
<Route
path="/"
element={<Navigate to="/postMachineReview/rollbackbymachine" />}
element={<Navigate to="/postMachineReview/operatorReviewing" />}
/>
<Route
path="/rollbackbymachine"
element={<PostMachineReviewContent current="rollbackbymachine" />}
path="/operatorReviewing"
element={<PostMachineReviewContent current="operatorReviewing" />}
/>
<Route
path="/machinereviewfail"
element={<PostMachineReviewContent current="machinereviewfail" />}
path="/machineReviewing"
element={<PostMachineReviewContent current="machineReviewing" />}
/>
<Route
path="/passbymachine"
element={<PostMachineReviewContent current="passbymachine" />}
path="/onlySelfCanSee"
element={<PostMachineReviewContent current="onlySelfCanSee" />}
/>
</Routes>
</div>

View File

@ -13,7 +13,7 @@ import Modal from "../../components/Modal";
import VideoUploader from "../../components/VideoUploader";
import ImageUploader from "../../components/ImageUploader";
import baseRequest from "../../utils/baseRequest";
import VideoPlayer from '../../components/VideoPlayer';
import VideoPlayer from "../../components/VideoPlayer";
//tab
const StreamerInformationContent = () => {
const { TextArea } = Input;
@ -119,9 +119,7 @@ const StreamerInformationContent = () => {
title: "封面视频",
dataIndex: "displayVideo",
key: "displayVideo",
render: (data) => (
<VideoPlayer url={data[0].urls[0]} width={150} />
),
render: (data) => <VideoPlayer url={data[0].urls[0]} width={150} />,
};
case "displayGallery":
return {
@ -753,7 +751,10 @@ const StreamerInformationContent = () => {
</p>
{defaultMedia.displayVideo.length !== 0 && (
<div className="relative">
<VideoPlayer url={defaultMedia.displayVideo[0].urls[0]} width={150} />
<VideoPlayer
url={defaultMedia.displayVideo[0].urls[0]}
width={150}
/>
<Button
className="absolute top-0 left-0 w-full"
danger

View File

@ -8,7 +8,7 @@ import {
useLocation,
} from "react-router-dom";
import baseRequest from "../../utils/baseRequest";
import VideoPlayer from '../../components/VideoPlayer';
import VideoPlayer from "../../components/VideoPlayer";
const StreamerVideoMachineReviewContent = (props) => {
const { TextArea } = Input;
const current = props.current;
@ -45,10 +45,11 @@ const StreamerVideoMachineReviewContent = (props) => {
title: "新内容",
dataIndex: "newMedia",
key: "newMedia",
width: "18%",
render: (data) => (
<div className="flex flex-wrap gap-1">
{data?.map((item, index) => (
<VideoPlayer url={item.urls[0]}width={150}/>
<VideoPlayer url={item.urls[0]} width={150} />
))}
</div>
),
@ -57,10 +58,11 @@ const StreamerVideoMachineReviewContent = (props) => {
title: "旧内容",
dataIndex: "oldMedia",
key: "oldMedia",
width: "18%",
render: (data) => (
<div className="flex flex-wrap gap-1">
{data?.map((item, index) => (
<VideoPlayer key={index} url={item.urls[0]} width={150} />
<VideoPlayer key={index} url={item.urls[0]} />
))}
</div>
),

View File

@ -48,7 +48,9 @@ const ZonePostMachineReviewContent = (props) => {
</div>
<p className="text-red-400">媒体</p>
<div className="flex flex-wrap gap-1">
<Image.PreviewGroup items={data.media.images?.map((item) => item.urls[0])}>
<Image.PreviewGroup
items={data.media.images?.map((item) => item.urls[0])}
>
{data.media.images.map((item, index) => (
<Image key={index} src={item.urls[0]} width={100} />
))}
@ -146,7 +148,7 @@ const ZonePostMachineReviewContent = (props) => {
name={record.momentId}
initialValue={record.remarks.manually_review_opinion}
>
<TextArea rows={4} />
<TextArea rows={4} disabled={current != "operatorReviewing"} />
</Form.Item>
</div>
),

View File

@ -7,6 +7,7 @@ import StreamerJoin from "../pages/StreamerJoin";
import StreamerJoinNew from "../pages/StreamerJoinNew";
import StreamerInformationCompleteNew from "../pages/StreamerInformationCompleteNew";
import Feedback from "../pages/Feedback";
import Notices from "../pages/Notices";
import Contact from "../pages/Contact";
import UploadMedia from "../pages/UploadMedia";
import GoodsReview from "../pages/GoodsReview";
@ -71,6 +72,10 @@ const routes = [
path: "streamerVerification/*",
element: <StreamerVerification />,
},
{
path: "notices/*",
element: <Notices />,
},
{
path: "feedback/*",
element: <Feedback />,

12
src/utils/tools.js Normal file
View File

@ -0,0 +1,12 @@
//格式化时间戳
export function formatDeadline(timestamp) {
const date = new Date(timestamp); // 时间戳以秒为单位需要乘以1000转换成毫秒
const year = date.getFullYear();
const month = ("0" + (date.getMonth() + 1)).slice(-2);
const day = ("0" + date.getDate()).slice(-2);
const hours = ("0" + date.getHours()).slice(-2);
const minutes = ("0" + date.getMinutes()).slice(-2);
const seconds = ("0" + date.getSeconds()).slice(-2);
return `${year}${month}${day}${hours}:${minutes}:${seconds}`;
}

View File

@ -2,7 +2,34 @@
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
extend: {
colors: {
primary: { DEFAULT: "#FF669E", 500: "#FF669E50" },
secondary: { DEFAULT: "#838284" },
neutral: { DEFAULT: "#2c2b2f" },
info: { DEFAULT: "#3B69B8" },
success: { DEFAULT: "#27F5B7" },
warning: { DEFAULT: "#FFF04C" },
error: { DEFAULT: "#F53030" },
deepBg: { DEFAULT: "#07050A" },
btn: { DEFAULT: "#3763b7" },
super: {
DEFAULT: "#FFD685",
500: "#FFD68550",
},
},
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
backgroundSize: {
auto: "auto",
cover: "cover",
contain: "contain",
"40%": "40%",
},
},
plugins: [],
corePlugins: {