Merge pull request 'version_1.1' (#6) from version_1.1 into main

Reviewed-on: https://git.wishpal.cn/wishpal_ironfan/tiefen_space_h5/pulls/6
This commit is contained in:
yezian 2024-11-21 18:39:59 +08:00
commit 43c8c75e6c
12 changed files with 1400 additions and 58 deletions

View File

@ -57,9 +57,9 @@ input {
font-family: inherit;
font-size: inherit;
color: inherit;
}
input,textarea{
input,
textarea {
-webkit-user-select: auto; /* Safari */
-moz-user-select: auto; /* Firefox */
-ms-user-select: auto; /* IE/Edge */
@ -273,6 +273,18 @@ input,textarea{
.adm-dialog .adm-dialog-content {
max-height: none;
height: 100%;
color: #ffffff85;
}
.adm-dialog .adm-dialog-header {
font-size: large;
}
.adm-dialog .adm-dialog-footer .adm-dialog-action-row {
& > :first-child {
color: #ffffff80;
}
& > :last-child {
color: #ffffff;
}
}
.adm-toast-icon {
display: flex;

View File

@ -652,6 +652,7 @@ export default function EditHome() {
<TextArea
value={formData.info}
placeholder="点此输入,文案将在“主页”展示"
style={{ "--font-size": "16px" }}
onChange={(value) =>
setFormData((old) => ({ ...old, info: value }))
}
@ -670,6 +671,7 @@ export default function EditHome() {
<TextArea
value={formData.autoResponse}
placeholder="文案将自动发送给点开私信的用户"
style={{ "--font-size": "16px" }}
onChange={(value) =>
setFormData((old) => ({ ...old, autoResponse: value }))
}

View File

@ -190,6 +190,28 @@ export default function Wallet() {
style={{ maxWidth: "12px" }}
/>
</div>
<div
onClick={() => router.push("/bill/income/income_querry")}
className="flex justify-between items-center py-4"
>
<div className="flex flex-row items-center">
<FontAwesomeIcon
icon={faPrint}
size="xl"
color="#60a5fa"
className="w-[28px]"
style={{ maxWidth: "20px" }}
/>
<span className="text-base text-white font-medium ml-2">
近一周收益
</span>
</div>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
style={{ maxWidth: "12px" }}
/>
</div>
</div>
</div>
</div>

View File

@ -186,13 +186,7 @@ export default function PersonSpace() {
}}
/>
</div>
<div
onClick={() =>
router.push(
"setting/" + streamerInfo?.mid
)
}
>
<div onClick={() => router.push("setting/" + streamerInfo?.mid)}>
<Image
width={42}
height={42}

View File

@ -155,7 +155,6 @@ export default function CreateImagePost() {
...imageAssets.filter((it) => it.id != undefined).map((it) => it.id),
...media.image_ids,
];
debugger;
try {
const body = {
c_type: parseInt(price) ? 1 : 0,

View File

@ -1,13 +1,12 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { Image, Toast } from "antd-mobile";
import { Image, Toast, Dialog } from "antd-mobile";
import { useRouter, useParams } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faAngleRight,
faClose,
// faSave,
} from "@fortawesome/free-solid-svg-icons";
import AddWeChat from "@/components/AddWeChat";
@ -20,6 +19,7 @@ export default function PersonSpaceIntroduce() {
const base = baseRequest();
const router = useRouter();
const contentBox = useRef();
const handler = useRef(null);
// 获取屏幕高度
// const scrollHeight = 600;
const [visible, setVisible] = useState(false);
@ -249,17 +249,34 @@ export default function PersonSpaceIntroduce() {
if (data?.admission_price === 0) {
handleJoinFreeSpace();
} else {
// router.push("/pay");
router.push(
"/webView/" +
encodeURIComponent(
"/zone/pay/" +
data?.id +
"/h5_zone_admission/0" +
"?base=" +
encodeURIComponent(JSON.stringify(base))
)
);
handler.current = Dialog.confirm({
header: "提醒",
content:
"本空间内容为达人创建并维护,属于虚拟服务,不可退款,且空间内存在部分内容需要另外付费,请再次确认是否付费加入",
bodyStyle: {
maxHeight: "none",
width: "80vw",
position: "fixed",
top: "200px",
left: "10vw",
"--text-color": "#fff",
},
onAction: (res) => {
if (res.key === "confirm") {
// router.push("/pay");
router.push(
"/webView/" +
encodeURIComponent(
"/zone/pay/" +
data?.id +
"/h5_zone_admission/0" +
"?base=" +
encodeURIComponent(JSON.stringify(base))
)
);
}
},
});
}
}}
>

View File

@ -19,6 +19,8 @@ export default function Setting() {
const router = useRouter();
const params = useParams();
const [streamerInfo, setStreamerInfo] = useState(null);
//是否对主播展示代运营设置
const [isAgencyHided, setIsAgencyHided] = useState(false);
useEffect(() => {
(async () => {
const { mid } = params;
@ -31,6 +33,7 @@ export default function Setting() {
...info,
refund_enable: res?.refund_enable,
});
isShowThird(parseInt(info.id));
});
}
})();
@ -112,6 +115,28 @@ export default function Setting() {
// console.error(error);
}
};
const isShowThird = async (id) => {
console.log(id);
try {
const _data = await requireAPI("POST", "/api/zone_third_partner/list", {
body: {
zid: id,
},
});
if (_data.ret === -1) {
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setIsAgencyHided(_data.data.zone_third_partner?.is_hided != 1);
} catch (error) {
// console.error(error);
}
};
return (
<div className="">
<div className="p-4 fixed top-0 z-10 w-full">
@ -201,22 +226,6 @@ export default function Setting() {
</li>
{streamerInfo?.visitor_role === 3 && (
<>
<li>
<div
onClick={() =>
router.push("spaceMember?zid=" + streamerInfo.id)
}
className="flex justify-between"
>
<span className="text-base text-white">空间成员</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
style={{ maxWidth: "12px" }}
/>
</div>
<Divider />
</li>
<li>
<div
onClick={() =>
@ -233,8 +242,61 @@ export default function Setting() {
</div>
<Divider />
</li>
{isAgencyHided && (
<li>
<div
onClick={() =>
router.push("agencySetting?zid=" + streamerInfo.id)
}
className="flex justify-between"
>
<span className="text-base text-white">代运营设置</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
style={{ maxWidth: "12px" }}
/>
</div>
<Divider />
</li>
)}
</>
)}
{(streamerInfo?.visitor_role === 1 ||
streamerInfo?.visitor_role === 2) && (
<li>
<div
onClick={() =>
router.push(
`collaboratorSetting?zid=${streamerInfo.id}&visitor_role=${streamerInfo.visitor_role}`
)
}
className="flex justify-between"
>
<span className="text-base text-white">合伙人设置</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
style={{ maxWidth: "12px" }}
/>
</div>
<Divider />
</li>
)}
<li>
<div
onClick={() => router.push("spaceMember?zid=" + streamerInfo.id)}
className="flex justify-between"
>
<span className="text-base text-white">空间成员</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
style={{ maxWidth: "12px" }}
/>
</div>
<Divider />
</li>
{streamerInfo?.refund_enable === 1 &&
streamerInfo?.admission_price > 0 &&
streamerInfo?.visitor_role === 0 && (
@ -260,7 +322,7 @@ export default function Setting() {
<Divider />
</li>
)}
{streamerInfo?.visitor_role != 3 && (
{streamerInfo?.visitor_role === 0 && (
<li onClick={handleShowDialog}>
<div className="flex justify-between">
<span className="text-base text-white">退出空间</span>

View File

@ -0,0 +1,461 @@
"use client";
import React, {
useState,
useEffect,
useMemo,
useCallback,
useRef,
} from "react";
import {
Image,
Toast,
Dialog,
Picker,
Avatar,
Space,
SpinLoading,
} from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faAdd,
faSortDown,
} from "@fortawesome/free-solid-svg-icons";
import requireAPI from "@/utils/requireAPI";
import { useRouter, useSearchParams } from "next/navigation";
import OwnInput from "@/components/OwnInput";
import { get } from "@/utils/storeInfo";
import { JSEncrypt } from "jsencrypt";
export default function AgencySetting() {
const [data, setData] = useState(null);
const [modalVisible, setModalVisible] = useState(false);
const [isloading, setIsloading] = useState([]);
const searchParams = useSearchParams();
const router = useRouter();
const handler = useRef(null);
const getData = async () => {
const zid = Number(searchParams.get("zid"));
try {
setIsloading(true);
const body = {
zid,
};
const _data = await requireAPI("POST", "/api/zone_third_partner/list", {
body,
});
if (_data.ret === -1) {
setIsloading(false);
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setData(_data.data.zone_third_partner);
} catch (error) {
console.error(error);
}
setIsloading(false);
};
useEffect(() => {
getData();
}, []);
useEffect(() => {
if (!modalVisible && handler.current) {
handler.current?.close();
}
}, [modalVisible]);
const handleAddAgency = () => {
setModalVisible(!modalVisible);
const zid = Number(searchParams.get("zid"));
handler.current = Dialog.show({
content: (
<ModalMask
setModalVisible={setModalVisible}
zid={zid}
router={router}
/>
),
bodyStyle: {
backgroundColor: "#17161A",
maxHeight: "none",
width: "90vw",
position: "fixed",
top: "200px",
left: "5vw",
"--text-color": "#fff",
color: "#fff",
},
});
};
return (
<div>
{isloading && (
<div
className="bg-[#00000099] fixed top-0 w-full text-center flex items-center justify-center h-screen"
// style={{ height: scrollHeight - 60 + "px" }}
>
<SpinLoading />
</div>
)}
{/* 头部标题 */}
<div className="p-4 fixed top-0 z-10 w-full bg-black">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full absolute">
<FontAwesomeIcon
icon={faAngleLeft}
style={{ maxWidth: "12px" }}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center leading-9">代运营设置</p>
</div>
{/* 内容 */}
<div className="p-4 pt-20">
<div className="flex justify-center items-center">
{data ? (
<div className="flex-1 flex flex-col justify-center items-center">
<Image
src={data?.third_partner_account.avatar.images[0].urls[0]}
width={74}
height={74}
className="h-full mr-1"
placeholder=""
/>
<div className="flex flex-col border-2 border-[#2c2b2f] rounded-2xl p-4 w-full mt-6 text-base font-medium">
<p>代运营昵称{data.third_partner_account.name}</p>
<p>ID{data.third_partner_account.user_id}</p>
<p>分成比例{(data.sharing_ratio * 100).toFixed()}%</p>
</div>
</div>
) : (
<div
onClick={handleAddAgency}
className="border border-white rounded-full w-[4.6rem] h-[4.6rem] flex items-center justify-center"
>
<FontAwesomeIcon
icon={faAdd}
style={{ maxWidth: "20px" }}
size="2xl"
/>
</div>
)}
</div>
<div className="mt-8 text-[#FFFFFF80] text-sm">
<p className="text-base font-medium">注意事项</p>
<p>
1一个空间仅可设置一个代运营若您的代运营团队为多人请设置代运营主账号后让代运营主账号进入当前空间设置合伙人
</p>
<p>
2设置完成后无法再次修改人员和分成比例请确认后再提交后续如需修改请联系人工客服
</p>
<p>
3您获得的收益会按照您设置的分成比例直接转移至代运营及协作者账户您将不会得到这部分的收益如有疑问请咨询人工客服
</p>
</div>
</div>
</div>
);
}
const ModalMask = ({ setModalVisible, zid, router }) => {
const [isloading, setIsloading] = useState([]);
const [isSelected, setIsSelected] = useState(false);
const [seconds, setSeconds] = useState(60);
const [agencyData, setAgencyData] = useState(null);
const [mobilePhone, setMobilePhone] = useState("");
const [regionCode, setRegionCode] = useState("");
const [veriCode, setVeriCode] = useState("");
const [userId, setUserId] = useState("");
const [isCounting, setIsCounting] = useState(false);
const [rate, setRate] = useState([null]);
// const router = useRouter();
//
const generateItems = useCallback((min, max) => {
const items = [];
for (let i = min; i <= max; i++) {
items.push({ label: `${i.toString()}%`, value: i.toString() });
}
return items;
}, []);
const rates = useMemo(() => generateItems(1, 50), []);
useEffect(() => {
async function getMobilePhone() {
setMobilePhone(await get("mobile_phone"));
setRegionCode(await get("region_code"));
}
getMobilePhone();
}, []);
useEffect(() => {
let interval;
if (isCounting && seconds > 0) {
interval = setInterval(() => {
setSeconds(seconds - 1);
}, 1000);
} else {
setIsCounting(false);
setSeconds(60);
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [isCounting, seconds]);
const handleSearch = async (id) => {
try {
setIsloading(true);
const body = {
user_id: parseInt(id, 10),
};
const _data = await requireAPI(
"POST",
"/api/account/list_others_by_user_id",
{
body,
}
);
if (_data.ret === -1) {
setIsloading(false);
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setAgencyData(_data.data.account);
setIsSelected(false);
} catch (error) {
console.error(error);
}
};
//
const handleVerification = async () => {
if (!isSelected) {
Toast.show({
icon: "fail",
content: "请先选中用户",
position: "top",
});
return;
}
//
setIsCounting(true);
//RSA
const encrypt = new JSEncrypt();
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
const mobile_phone = encrypt.encrypt(mobilePhone);
//
try {
const data = await fetch(`/api/veri_code/send`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mobile_phone,
region_code: regionCode,
}),
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
} catch (error) {
// console.error(error);
}
};
const handleSubmit = async () => {
if (!isSelected) {
Toast.show({
icon: "fail",
content: "请先选中用户",
position: "top",
});
return;
}
if (!veriCode) {
Toast.show({
icon: "fail",
content: "请输入验证码",
position: "top",
});
return;
}
if (!rate[0]) {
Toast.show({
icon: "fail",
content: "请选择分成比例",
position: "top",
});
return;
}
try {
//RSA
const encrypt = new JSEncrypt();
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
const mobile_phone = encrypt.encrypt(mobilePhone);
const body = {
zid,
third_partner_mid: agencyData.mid,
region_code: regionCode,
mobile_phone: mobile_phone,
veri_code: veriCode,
sharing_ratio: parseInt(rate[0], 10) / 100,
};
const _data = await requireAPI("POST", "/api/zone_third_partner/create", {
body,
});
if (_data.ret === -1) {
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setModalVisible(false);
router.back();
} catch (error) {
console.error(error);
}
};
return (
<div className="flex flex-col w-full rounded-3xl text-white">
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">搜索用户:</span>
<OwnInput
placeholder="请输入用户ID"
type="number"
onChange={(value) => {
setUserId(value);
}}
value={userId}
className="flex-1 bg-[#FFFFFF1A] text-white rounded-2xl px-4 h-8 mx-2"
/>
<span
onClick={() => handleSearch(userId)}
className="text-[#FF669E] text-base font-medium"
>
搜索
</span>
</div>
{agencyData && (
<div
className="flex flex-row items-center rounded-2xl p-4 mb-4 border"
style={{ borderColor: isSelected ? "#FF669E" : "#2c2b2f" }}
>
<Avatar
src={agencyData?.avatar?.images[0]?.urls[0]}
fit="cover"
style={{ "--border-radius": "100px", "--size": "42px" }}
/>
<div className="flex flex-1 flex-col justify-around items-start ml-2">
<span className="text-base font-medium whitespace-nowrap">
{agencyData?.name}
</span>
<div className="flex flex-row items-center py-0.5 px-2 bg-[#FFFFFF1A] rounded-full">
<Image
src={
process.env.NEXT_PUBLIC_WEB_ASSETS_URL + "/icons/info/ID.png"
}
width={14}
height={14}
className="w-4 h-full mr-1"
/>
<span className="text-xs font-medium ml-0.5 whitespace-nowrap">
{agencyData?.user_id}
</span>
</div>
</div>
<div
className="px-4 py-2 text-sm font-medium rounded-full"
style={{
backgroundColor: isSelected ? "#FFFFFF1A" : "#FF669E",
}}
onClick={() => setIsSelected(!isSelected)}
>
{isSelected ? "取消" : "选择"}
</div>
</div>
)}
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">
手机号:
<span className="text-[#FFFFFF80] ml-2">
{mobilePhone?.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2")}
</span>
</span>
</div>
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">验证码:</span>
<OwnInput
placeholder="请输入验证码"
type="number"
onChange={(value) => setVeriCode(value)}
value={veriCode}
className="flex-1 bg-[#FFFFFF1A] rounded-2xl px-4 h-8 mx-2"
/>
<div
className="bg-[#FF669E] px-2 py-1 text-sm font-medium rounded-full"
disabled={isCounting}
onClick={handleVerification}
>
{isCounting ? `(${seconds})重新发送` : "获取验证码"}
</div>
</div>
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">分成比例</span>
<div className="w-1/3">
<Picker columns={[rates]} onConfirm={setRate} value={rate}>
{(items, { open }) => {
return (
<Space
align="center"
direction="horizontal"
justify="center"
onClick={open}
>
{items.every((item) => item === null)
? "未选择"
: items.map((item) => item?.label ?? "未选择").join("")}
<FontAwesomeIcon
icon={faSortDown}
style={{ maxWidth: "12px", marginBottom: 6 }}
size="lg"
/>
</Space>
);
}}
</Picker>
</div>
</div>
<span className="text-[#F53030] text-xs font-medium">
注意事项分成比例不得超过50%且确认后无法修改
</span>
<div className="grid grid-cols-2 gap-2 mt-2">
<div
className="bg-[#FF669E] text-center w-full py-2 text-md font-medium rounded-full"
onClick={handleSubmit}
>
确认
</div>
<div
className="bg-[#FFFFFF1A] text-center w-full py-2 text-md font-medium rounded-full"
onClick={() => setModalVisible(false)}
>
取消
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,510 @@
"use client";
import React, {
useState,
useEffect,
useMemo,
useCallback,
useRef,
} from "react";
import {
Image,
Toast,
SpinLoading,
Picker,
Avatar,
Space,
List,
Dialog,
} from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faAdd,
faSortDown,
faMoneyBill,
} from "@fortawesome/free-solid-svg-icons";
import requireAPI from "@/utils/requireAPI";
import { useRouter, useSearchParams } from "next/navigation";
import OwnInput from "@/components/OwnInput";
import { get } from "@/utils/storeInfo";
export default function CollaboratorSetting() {
const [data, setData] = useState(null);
const [selfMid, setSelfMid] = useState();
const [modalVisible, setModalVisible] = useState(false);
const [isloading, setIsloading] = useState([]);
const searchParams = useSearchParams();
const router = useRouter();
const handler = useRef(null);
useEffect(() => {
getData();
}, []);
useEffect(() => {
if (!modalVisible && handler.current) {
handler.current?.close();
}
}, [modalVisible]);
//
const remainingRate = useMemo(() => {
if (!data) return;
const totalRate = data?.list?.reduce((acc, cur) => {
return acc + cur.sharing_ratio || 0;
}, 0);
return (data?.zone_third_partner?.sharing_ratio - (totalRate || 0)).toFixed(
2
);
}, [data]);
const getData = async () => {
const zid = Number(searchParams.get("zid"));
const visitor_role = Number(searchParams.get("visitor_role"));
try {
setIsloading(true);
setSelfMid(zid);
const body = {
zid,
visitor_role,
};
const _data = await requireAPI("POST", "/api/zone_collaborator/list", {
body,
});
if (_data.ret === -1) {
setIsloading(false);
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setData(_data.data);
} catch (error) {
console.error(error);
}
setIsloading(false);
};
const handleAddCollaborator = () => {
setModalVisible(!modalVisible);
const zid = Number(searchParams.get("zid"));
handler.current = Dialog.show({
content: (
<ModalMask
setModalVisible={setModalVisible}
zid={zid}
handleRefresh={getData}
maxRate={remainingRate * 100}
/>
),
bodyStyle: {
backgroundColor: "#17161A",
maxHeight: "none",
width: "80vw",
position: "fixed",
top: "200px",
left: "10vw",
"--text-color": "#fff",
color: "#fff",
},
});
};
//
const handleDelete = async (id) => {
const visitor_role = Number(searchParams.get("visitor_role"));
if (visitor_role !== 1) return;
try {
const body = {
id,
};
const _data = await requireAPI("POST", "/api/zone_collaborator/delete", {
body,
});
if (_data.ret === -1) {
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
Toast.show({
icon: "success",
content: "移除合伙人成功",
position: "top",
});
getData();
} catch (error) {
console.error(error);
}
};
//
const CollaboratorItem = useCallback(({ item }) => {
return (
<div className="border-b border-[#ffffff80]">
<List.Item className="p-0 bg-transparent">
<div className="flex-1">
<div className="flex items-center">
<Avatar
src={item?.collaborator_account?.avatar.images[0].urls[0]}
fit="cover"
style={{ "--border-radius": "100px", "--size": "42px" }}
/>
<div className="ml-2 justify-around flex-1">
<p className="text-base font-medium whitespace-nowrap">
{item?.collaborator_account?.name}
</p>
<div className="flex">
<div className="h-4 mr-1 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mt-1 w-max">
<Image
src={
process.env.NEXT_PUBLIC_WEB_ASSETS_URL +
"/icons/info/ID.png"
}
width={14}
height={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>{item?.collaborator_account?.user_id}</span>
</div>
<div className="h-4 mr-1 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mt-1 w-max">
<div className="mr-1">
<FontAwesomeIcon icon={faMoneyBill} size="sm" />
</div>
<span>
{item?.sharing_ratio
? (item?.sharing_ratio * 100).toFixed()
: 0}
%
</span>
</div>
</div>
</div>
{!item?.isDeleteBtnInvisible && (
<div
className="bg-[#F53030] ml-2 h-8 flex justify-center items-center text-sm font-medium px-3 rounded-full"
onClick={() => handleDelete(item.id)}
>
移除
</div>
)}
</div>
</div>
</List.Item>
</div>
);
}, []);
const selfData = data?.list?.filter(
(item) => item.collaborator_mid === selfMid
);
return (
<div>
{isloading && (
<div
className="bg-[#00000099] fixed top-0 w-full text-center flex items-center justify-center h-screen"
// style={{ height: scrollHeight - 60 + "px" }}
>
<SpinLoading />
</div>
)}
{/* 头部标题 */}
<div className="p-4 fixed top-0 z-10 w-full bg-black">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full absolute">
<FontAwesomeIcon
icon={faAngleLeft}
style={{ maxWidth: "12px" }}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center leading-9">合伙人设置</p>
</div>
{/* 内容 */}
<div className="p-4 pt-20">
{searchParams.get("visitor_role") === 2 ? (
<div className="p-4 pt-20">
<div className="flex flex-col border-2 border-[#2c2b2f] rounded-2xl p-4 w-full mt-6">
<p>合伙人昵称{selfData[0]?.collaborator_account?.name}</p>
<p>ID{selfData[0]?.collaborator_account?.user_id}</p>
<p>
分成比例
{selfData[0]?.sharing_ratio
? (selfData[0]?.sharing_ratio * 100).toFixed()
: 0}
%
</p>
</div>
</div>
) : (
<div className="flex flex-col items-center p-4">
<div className="flex flex-col items-center">
<p className="text-base font-medium">总分成比例</p>
<p className="text-[#F53030] text-3xl font-medium my-2">
{data?.zone_third_partner?.sharing_ratio
? (data?.zone_third_partner?.sharing_ratio * 100).toFixed()
: 0}
%
</p>
<p className="text-[#FFFFFF80] text-sm">修改比例请联系平台客服</p>
</div>
<div className="h-[3px] rounded-full w-full bg-[#FFFFFF26] mt-4"></div>
<div className="flex flex-col w-full mb-4">
<List style={{ "--padding-left": "0" }}>
<CollaboratorItem
item={{
collaborator_account:
data?.zone_third_partner?.third_partner_account,
sharing_ratio: remainingRate,
isDeleteBtnInvisible: true,
}}
/>
{data?.list?.map((item, index) => (
<CollaboratorItem key={index} item={item} />
))}
</List>
</div>
<div
onClick={handleAddCollaborator}
className="border border-white rounded-full w-[4.6rem] h-[4.6rem] flex items-center justify-center"
>
<FontAwesomeIcon
icon={faAdd}
style={{ maxWidth: "20px" }}
size="2xl"
/>
</div>
</div>
)}
<div className="mt-8 text-[#FFFFFF80] text-sm">
<p className="text-base font-medium">注意事项</p>
<p>
1总分成比例由当前空间主人设置您的个人分成比例由代运营进行设置
</p>
<p>
2您的个人收益计算公式为空间收益x个人分成比例=个人收益收益将以钻石形式发放至您的钱包
</p>
<p>3若您对收益情况存在任何疑问请联系人工客服</p>
</div>
</div>
</div>
);
}
const ModalMask = ({ setModalVisible, zid, handleRefresh, maxRate }) => {
const [isloading, setIsloading] = useState([]);
const [isSelected, setIsSelected] = useState(false);
const [seconds, setSeconds] = useState(60);
const [mobilePhone, setMobilePhone] = useState("");
const [regionCode, setRegionCode] = useState("");
const [userId, setUserId] = useState("");
const [isCounting, setIsCounting] = useState(false);
const [rate, setRate] = useState([null]);
const [collaboratorData, setCollaboratorData] = useState();
//
const generateItems = useCallback((min, max) => {
const items = [];
for (let i = min; i <= max; i++) {
items.push({ label: `${i.toString()}%`, value: i.toString() });
}
return items;
}, []);
const rates = useMemo(() => generateItems(1, maxRate), [maxRate]);
useEffect(() => {
async function getMobilePhone() {
setMobilePhone(await get("mobile_phone"));
setRegionCode(await get("region_code"));
}
getMobilePhone();
}, []);
useEffect(() => {
let interval;
if (isCounting && seconds > 0) {
interval = setInterval(() => {
setSeconds(seconds - 1);
}, 1000);
} else {
setIsCounting(false);
setSeconds(60);
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [isCounting, seconds]);
const handleSearch = async (id) => {
try {
setIsloading(true);
const body = {
user_id: parseInt(id, 10),
};
const _data = await requireAPI(
"POST",
"/api/account/list_others_by_user_id",
{
body,
}
);
if (_data.ret === -1) {
setIsloading(false);
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setCollaboratorData(_data.data.account);
setIsSelected(false);
} catch (error) {
console.error(error);
}
};
const handleSubmit = async () => {
if (!isSelected) {
Toast.show({
icon: "fail",
content: "请先选中用户",
position: "top",
});
return;
}
if (!rate[0]) {
Toast.show({
icon: "fail",
content: "请选择分成比例",
position: "top",
});
return;
}
try {
const body = {
zid,
collaborator_mid: collaboratorData.mid,
sharing_ratio: parseInt(rate[0], 10) / 100,
};
const data = await requireAPI("POST", "/api/zone_collaborator/create", {
body,
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
Toast.show({
icon: "success",
content: "添加合伙人成功",
position: "top",
});
handleRefresh();
setModalVisible(false);
} catch (error) {
console.error(error);
}
};
return (
<div className="flex flex-col w-full bg-[#17161A] rounded-3xl text-white">
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">搜索用户</span>
<OwnInput
placeholder="请输入用户ID"
type="number"
onChange={(value) => setUserId(value)}
value={userId}
className="flex-1 bg-[#FFFFFF1A] text-white rounded-2xl px-4 h-8 mx-2"
/>
<span
onClick={() => handleSearch(userId)}
className="text-[#FF669E] text-base font-medium"
>
搜索
</span>
</div>
{collaboratorData && (
<div
className="flex flex-row items-center rounded-2xl p-4 mb-4 border"
style={{ borderColor: isSelected ? "#FF669E" : "#2c2b2f" }}
>
<Avatar
src={collaboratorData?.avatar?.images[0]?.urls[0]}
fit="cover"
style={{ "--border-radius": "100px", "--size": "42px" }}
/>
<div className="flex flex-1 flex-col justify-around items-start ml-2">
<span className="text-base font-medium whitespace-nowrap">
{collaboratorData?.name}
</span>
<div className="flex flex-row items-center py-0.5 px-2 bg-[#FFFFFF1A] rounded-full">
<Image
src={
process.env.NEXT_PUBLIC_WEB_ASSETS_URL + "/icons/info/ID.png"
}
width={14}
height={14}
className="w-4 h-full mr-1"
/>
<span className="text-xs font-medium ml-0.5 whitespace-nowrap">
{collaboratorData?.user_id}
</span>
</div>
</div>
<div
className="px-4 py-2 text-sm font-medium rounded-full"
style={{
backgroundColor: isSelected ? "#FFFFFF1A" : "#FF669E",
}}
onClick={() => setIsSelected(!isSelected)}
>
{isSelected ? "取消" : "选择"}
</div>
</div>
)}
<div className="flex flex-row items-center mb-4">
<span className="text-base font-medium">分成比例</span>
<div className="w-1/3">
<Picker columns={[rates]} onConfirm={setRate} value={rate}>
{(items, { open }) => {
return (
<Space
align="center"
direction="horizontal"
justify="center"
onClick={open}
>
{items.every((item) => item === null)
? "未选择"
: items.map((item) => item?.label ?? "未选择").join("")}
<FontAwesomeIcon
icon={faSortDown}
style={{ maxWidth: "12px", marginBottom: 6 }}
size="lg"
/>
</Space>
);
}}
</Picker>
</div>
</div>
<p className="text-[#F53030] text-xs font-medium">
注意事项分成比例不得超过50%且确认后无法修改
</p>
<div className="grid grid-cols-2 gap-2 mt-2">
<div
className="bg-[#FF669E] text-center w-full py-2 text-md font-medium rounded-full"
onClick={handleSubmit}
>
确认
</div>
<div
className="bg-[#FFFFFF1A] text-center w-full py-2 text-md font-medium rounded-full"
onClick={() => setModalVisible(false)}
>
取消
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,262 @@
"use client";
import React, { useState, useEffect } from "react";
import { DotLoading, Popup, Toast, TextArea, Switch } from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons";
import requireAPI from "@/utils/requireAPI";
import { useRouter, useParams } from "next/navigation";
import { multiUploadImage } from "@/utils/upload";
import UploadImgs from "@/components/UploadImgs";
export default function EditPost() {
const params = useParams();
const [isSubmitting, setIsSubmitting] = useState(false);
const [oldPhotos, setOldPhotos] = useState([]);
const [data, setData] = useState(null);
//
const [formData, setFormData] = useState({
content: "",
imageAssets: [],
videoAssets: [],
});
const router = useRouter();
useEffect(() => {
getData();
}, []);
const getData = async () => {
const ids = [parseInt(params.zid, 10)];
try {
const body = {
ids,
};
const _data = await requireAPI(
"POST",
"/api/moment/list_by_ids_from_creater",
{
body,
},
true
);
if (_data.ret === -1) {
Toast.show({
icon: "fail",
content: _data.msg,
position: "top",
});
return;
}
setData(_data.data.list[0]);
const { text, media_component } = _data.data.list[0];
setFormData({
content: text,
imageAssets: media_component.images.map((it) => ({
url: it.urls[0],
id: it.id,
})),
videoAssets: media_component.videos.map((it) => ({
url: it.cover_urls[0],
id: it.id,
})),
});
} catch (error) {
console.error(error);
} finally {
setIsSubmitting(false);
}
};
//
const handleSubmit = async () => {
if (formData.content == "") {
Toast.show({
icon: "fail",
content: "动态内容不能为空",
position: "top",
});
return;
}
if (
formData.imageAssets.length === 0 &&
formData.videoAssets.length === 0
) {
Toast.show({
icon: "fail",
content: "请上传图片或视频",
position: "top",
});
return;
}
//
if (isSubmitting) return;
setIsSubmitting(true);
const { content, imageAssets, videoAssets } = formData;
const type = formData.imageAssets.length > 0 ? 1 : 2;
const newMedia = [...(type == 1 ? imageAssets : videoAssets)].filter(
(it) => it.id == undefined
);
const media = await multiUploadImage(newMedia, type);
// const media = await multiUploadImage(imageAssets);
if (type == 1) {
media.image_ids = [
...imageAssets.filter((it) => it.id != undefined).map((it) => it.id),
...media.image_ids,
];
} else {
media.video_ids = [
...videoAssets.filter((it) => it.id != undefined).map((it) => it.id),
...media.video_ids,
];
}
try {
const body = {
id: parseInt(params.zid, 10),
text: content,
media_component: media,
status: 2,
};
const data = await requireAPI(
"POST",
"/api/moment/update",
{
body,
},
true,
100000
);
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
//Toast
Toast.show({
icon: "success",
content: "更新成功",
position: "top",
});
router.back();
} catch (error) {
console.error(error);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="flex-1">
{/* 头部标题 */}
<div className="p-4 fixed top-0 z-10 w-full flex justify-between items-center bg-black">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
style={{ maxWidth: "12px" }}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center leading-9">广场动态</p>
{isSubmitting ? (
<DotLoading />
) : (
<span className="text-primary text-lg" onClick={handleSubmit}>
发布
</span>
)}
</div>
{/* 内容 */}
<div className="pt-16 p-4">
{data?.status === 6 &&
(data.text_audit_opinion ||
data.image_audit_opinion ||
data.manually_review_opinion) && (
<div className="mb-4">
<span className="text-base font-medium text-[#F53030] mb-2">
违规详情
</span>
<div className="mt-2 p-4 rounded-2xl bg-[#FFFFFF1A]">
{data.text_audit_opinion && (
<p className="text-sm font-medium text-white">
<span className="text-[#F53030]">文案违规原因</span>
{data.text_audit_opinion}
</p>
)}
{data.image_audit_opinion && (
<p className="text-sm font-medium text-white">
<span className="text-[#F53030]">图片/视频违规原因</span>
{data.image_audit_opinion}
</p>
)}
{data.manually_review_opinion && (
<p className="text-sm font-medium text-white">
<span className="text-[#F53030]">运营追加</span>
{data.manually_review_opinion}
</p>
)}
</div>
</div>
)}
<div className="mt-2">
<p className="text-base font-medium text-white">动态内容</p>
<div className="h-32">
<TextArea
placeholder="请遵守平台准则,严禁发布违规内容"
value={formData.content}
autoSize={{ minRows: 6, maxRows: 15 }}
onChange={(value) =>
setFormData((old) => ({ ...old, content: value }))
}
style={{ "--font-size": "16px" }}
className="h-full bg-[#FFFFFF1A] rounded-2xl mt-2 mb-4 p-2"
/>
</div>
</div>
<div className="mt-4">
<p className="text-base font-medium mt-4 mb-1">
图片
<span className="text-[#FFFFFF80] text-sm">
添加图片后不可添加视频
</span>
</p>
<UploadImgs
type={1}
id="uploadAvatarBtn1"
existImages={oldPhotos}
assets={formData.imageAssets}
getImgs={(imgs) => {
setFormData((old) => ({
...old,
imageAssets: imgs,
videoAssets: [],
}));
}}
/>
</div>
<div className="mt-4">
<p className="text-base font-medium mt-4 mb-1">
视频
<span className="text-[#FFFFFF80] text-sm">
添加视频后不可添加图片
</span>
</p>
<UploadImgs
type={2}
accept="video/*"
id="uploadAvatarBtn2"
assets={formData.videoAssets}
videoSrc={data?.media_component?.videos[0]?.urls[0]}
maxLength={1}
getImgs={(imgs) => {
setFormData((old) => ({
...old,
videoAssets: imgs,
imageAssets: [],
}));
}}
/>
</div>
</div>
</div>
);
}

View File

@ -189,23 +189,24 @@ export default function PostItem({
)}
{(type == "space"
? data?.status === 3
: data?.status === 5 || data?.status === 6) &&
(type == "space" ? (
<p
className="py-1 px-2 inline-block bg-[#F53030] rounded"
onClick={() => {
router.push(`/space/editSpacePost/${data.id}`);
}}
>
<span className="text-sm">
审核未通过<span className="underline">重新编辑</span>
</span>
</p>
) : (
<p className="py-1 px-2 inline-block bg-[#F53030] rounded">
<span className="text-sm">审核未通过请删除后重新提交</span>
</p>
))}
: data?.status === 5 || data?.status === 6) && (
<p
className="py-1 px-2 inline-block bg-[#F53030] rounded"
onClick={() => {
router.push(
`${
type == "space"
? "/space/editSpacePost"
: "/streamerPosts/editPost"
}/${data.id}`
);
}}
>
<span className="text-sm">
审核未通过<span className="underline">重新编辑</span>
</span>
</p>
)}
<div>
{!data?.is_zone_moment_unlocked ? (
<span

View File

@ -46,7 +46,7 @@ export default function customFetch(
// 合并选项
const mergedOptions = { ...defaultOptions, body };
// console.log("body", body);
console.log("body", body);
// 返回 Promise 对象
return new Promise((resolve, reject) => {
fetch(url, mergedOptions)