广场和空间接口调用

This commit is contained in:
anln 2024-07-06 11:05:19 +08:00
parent 2a81f3fe93
commit 41fd2f7a7c
36 changed files with 2424 additions and 1284 deletions

View File

@ -1 +0,0 @@
import '@fortawesome/fontawesome-svg-core/styles.css'; // 引入Font Awesome CSS

81
api/public.js Normal file
View File

@ -0,0 +1,81 @@
import { get } from "@/utils/storeInfo";
import require from "@/utils/require";
import { Toast } from "antd-mobile";
//关注和取关功能
export const handleFollow = async (isFollowed, followedID, callback) => {
const account = get("account");
let body = {
[!isFollowed ? "account_relations" : "sentences"]: [
{ sub_mid: account.mid, obj_mid: followedID, predicate: 0 },
{ sub_mid: followedID, obj_mid: account.mid, predicate: 1 },
],
};
try {
const data = await require("POST", `/api/account_relation/${
!isFollowed ? "create" : "delete"
}`, {
body,
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
} else {
callback(!isFollowed);
}
} catch (error) {
console.error(error);
}
};
//点赞和取消点赞功能
export const thumbsUp = async (id, times = 1,callback) => {
console.log("times", times);
try {
const body = {
moment_id: id,
times: times==1?-1:1,
};
const data = await require("POST", `/api/moment/thumbs_up`, {
body,
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}else{
callback(times==1?-1:1);
}
} catch (error) {
console.error(error);
}
};
export async function checkRelation(subMid, objMid, predicate) {
try {
const data = await require("POST", `/api/account_relation/list_by_sentence`, {
body:{
sub_mid: subMid,
obj_mid: objMid,
predicate: predicate,
},
});
if (data.ret === -1) {
Toast.show({
type: "error",
text1: data.msg,
topOffset: 60,
});
return;
}
return data.data.is_account_relation_existed;
} catch (error) {
console.error(error);
}
}

65
api/space.js Normal file
View File

@ -0,0 +1,65 @@
import require from "@/utils/require";
export const getStreamerInfo = async (mid) => {
try {
const data = await require("POST", "/api/zone/list_by_mid", {
body: {
mid,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
return {
...data.data.list[0],
refund_enable: data.data.refund_enable,
refund_status: data.data.refund_status,
};
} catch (error) {
console.error(error);
}
};
//获取空间数据并将该空间标为已读
export const getSpaceData = async (mid) => {
try {
const data = await require("POST", `/api/zone/list_by_mid`, {
body: {
mid,
},
});
if (data.ret === -1) {
Toast.show({
type: "error",
text1: data.msg,
topOffset: 60,
});
return;
}
//将空间标为已读
const data2 = await require("POST", `/api/zone_session/upsert`, {
body: {
zid: data.data.list[0].id,
},
});
if (data2.ret === -1) {
Toast.show({
type: "error",
text1: data2.msg,
topOffset: 60,
});
return;
}
return {
isRefunding: data.data.refund_status === 1,
noRole: data.data.list[0].visitor_role === 4,
};
} catch (error) {
console.error(error);
}
};

View File

@ -151,4 +151,9 @@ body{
.adm-dialog .adm-dialog-content{
max-height: none;
height: 100%;
}
.adm-toast-icon{
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,12 +1,15 @@
"use client";
import { Inter } from "next/font/google";
import React, { useEffect } from 'react';
import React, { useEffect } from "react";
import "./globals.css";
import BottomNav from "../components/BottomNav";
import { Provider } from 'react-redux';
import store from '../store';
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "铁粉空间",
description: "与Ta永不失联",
keywords: [
"铁粉空间",
@ -29,18 +32,14 @@ export const viewport = {
};
export default function RootLayout({ children }) {
return (
<html
lang="zh-CN"
className="bg-deepBg"
data-prefers-color-scheme="dark"
>
<html lang="zh-CN" className="bg-deepBg" data-prefers-color-scheme="dark">
<body className={inter.className}>
{children}
<main
className={`fixed bottom-0 left-0 w-full bg-deepBg border-t-[1px] border-[#333333]`}
className={`h-screen fixed bottom-0 left-0 w-full bg-deepBg `}
>
<BottomNav />
<Provider store={store}>{children}</Provider>
<div className="fixed bottom-0 left-0 w-full bg-black"><BottomNav /></div>
</main>
</body>
</html>

View File

@ -4,7 +4,7 @@ import React from "react";
import { SpinLoading } from "antd-mobile";
export default function Loading() {
return (
<section className="fixed top-0 w-full h-screen flex flex-col justify-center items-center bg-deepBg z-50">
<section className="w-full h-screen flex flex-col justify-center items-center bg-deepBg z-50">
<SpinLoading />
</section>
);

View File

@ -1,15 +1,24 @@
"use client";
import React, { useState, useRef } from "react";
import baseRequest from "@/utils/baseRequest";
import { generateSignature } from "@/utils/crypto";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons";
import { Input, Button, Swiper, Tabs, Divider, Checkbox } from "antd-mobile";
import React, { useState, useRef, useEffect } from "react";
import {
Input,
Button,
Swiper,
Tabs,
Divider,
Checkbox,
Toast,
} from "antd-mobile";
import { useRouter } from "next/navigation";
import styles from "./index.module.css";
import { JSEncrypt } from "jsencrypt";
import { handleLogin } from "@/store/actions";
import { saveUserInfo,removeUserInfo } from "@/utils/storeInfo";
import { connect } from "react-redux";
import { cryptoPassword } from "@/utils/crypto";
import require from "@/utils/require";
import {signOut,signIn} from "@/utils/auth";
/*
params格式
{
@ -20,15 +29,171 @@ const tabItems = [
{ key: "code", title: "验证码登录" },
{ key: "password", title: "帐号密码登录" },
];
export default function Login({}) {
function Login({ handleLogin }) {
const [activeIndex, setActiveIndex] = useState(0);
const [regionCode, setRegionCode] = useState("");
const [mobilePhone, setMobilePhone] = useState("");
const [veriCode, setVeriCode] = useState("");
const [password, setPassword] = useState("");
const [isCounting, setIsCounting] = useState(false);
const [seconds, setSeconds] = useState(60);
const [loginInfo, setLoginInfo] = useState({
mobilePhone: "",
regionCode: "86",
password: "",
checked: false,
});
const router = useRouter();
const swiperRef = useRef(null);
useEffect(() => {
handleLogin({ isSignin: false, userToken: null });
signOut()
removeUserInfo();
},[])
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 handleSubmit = async (type) => {
const { mobilePhone, password, regionCode, checked } = loginInfo;
//验证数据格式
if (!checked) {
Toast.show({
icon: "fail",
content: "请先阅读并同意《用户协议》和《隐私政策》后登录",
position: "top",
});
return;
}
if (!mobilePhone.match(/^1[3456789]\d{9}$/)) {
Toast.show({
icon: "fail",
content: "手机号码格式错误",
position: "top",
});
return;
}
if (type === "password") {
if (password.length < 8) {
Toast.show({
icon: "fail",
content: "密码不得小于8位",
position: "top",
});
return;
}
if (password.length > 15) {
Toast.show({
icon: "fail",
content: "密码不得大于15位",
position: "top",
});
return;
}
} else {
if (veriCode.length !== 6) {
Toast.show({
icon: "fail",
content: "请输入正确的验证码",
position: "top",
});
return;
}
}
//对手机号进行RSA加密
const encrypt = new JSEncrypt();
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
const mobile_phone = encrypt.encrypt(mobilePhone);
//MD5加密password
const encryptedPassword = cryptoPassword(password);
//发送登录请求
let body = {
mobile_phone,
region_code: regionCode,
};
body =
type === "password"
? {
...body,
password: encryptedPassword,
}
: {
...body,
code: veriCode,
};
try {
const data = await require("POST", `/api/login/${
type === "password" ? "login_by_pswd" : "login_by_veri_code"
}`, {
body,
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
//登录
saveUserInfo(data, mobilePhone, regionCode);
signIn(data);
handleLogin({ isSignin: true, userToken: data.data.token });
router.push("/");
} catch (error) {
console.error(error);
}
};
//点击获取验证码
const handleVerification = async () => {
const { mobilePhone, regionCode, checked } = loginInfo;
if (!checked) {
Toast.show({
icon: "fail",
content: "请先阅读并同意《用户协议》和《隐私政策》后登录",
position: "top",
});
return;
}
//手机号校验
if (!mobilePhone.match(/^1[3456789]\d{9}$/)) {
Toast.show({
type: "error",
text1: "手机号码格式错误",
topOffset: 60,
});
return;
}
//开始倒计时
setIsCounting(true);
//对手机号进行RSA加密
const encrypt = new JSEncrypt();
encrypt.setPublicKey(process.env.NEXT_PUBLIC_RSA_KEY);
const mobile_phone = encrypt.encrypt(mobilePhone);
//发送短信验证码
try {
await fetch(`/api/veri_code/send`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mobile_phone,
region_code: regionCode,
}),
});
} catch (error) {
console.error(error);
}
};
return (
<div className={`${styles.loginBox}`}>
<div className="mt-32 flex justify-between items-center px-2 text-gray-400 sticky top-0 z-10 bg-deepBg">
@ -65,14 +230,33 @@ export default function Login({}) {
<Swiper.Item className="px-10">
<div className="border-2 border-[#2c2b2f] rounded-2xl p-4">
<div className="flex flex-row flex-nowrap items-center mb-4">
<p className="text-base text-white mr-4">+{regionCode}</p>
<p className="text-base text-white mr-4">
+{loginInfo.regionCode}
</p>
<Input
clearable
placeholder="请输入手机号"
// disabled={true}
type="number"
maxLength={11}
onChangeText={(value) => setMobilePhone(value)}
value={mobilePhone}
onChange={(value) =>
setLoginInfo({ ...loginInfo, mobilePhone: value })
}
value={loginInfo.mobilePhone}
style={{ "--color": "#FFFFFF", "--font-size": "16px" }}
/>
</div>
<Divider />
<div className="flex flex-row flex-nowrap items-center">
<p className="text-base text-white mr-4 whitespace-nowrap">
密码
</p>
<Input
clearable
placeholder="请输入密码"
onChange={(value) => setLoginInfo({ ...loginInfo, password: value})}
value={loginInfo.password}
type="password"
style={{ "--color": "#FFFFFF", "--font-size": "16px" }}
/>
</div>
@ -83,7 +267,7 @@ export default function Login({}) {
</p>
<Input
placeholder="请输入验证码"
onChangeText={(value) => setVeriCode(value)}
onChange={(value) => setVeriCode(value)}
value={veriCode}
type="number"
style={{
@ -95,7 +279,7 @@ export default function Login({}) {
shape="rounded"
size="mini"
disabled={isCounting}
// onClick={handleSubmit}
onClick={handleVerification}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
className="whitespace-nowrap"
>
@ -104,19 +288,29 @@ export default function Login({}) {
</div>
</div>
<LoginBtn />
<LoginBtn
loginInfo={loginInfo}
setLoginInfo={setLoginInfo}
handleSubmit={handleSubmit}
type={activeIndex ? "password" : "mobile"}
/>
</Swiper.Item>
<Swiper.Item className="px-10">
<div className="border-2 border-[#2c2b2f] rounded-2xl p-4">
<div className="flex flex-row flex-nowrap items-center mb-4">
<p className="text-base text-white mr-4">+{regionCode}</p>
<p className="text-base text-white mr-4">
+{loginInfo.regionCode}
</p>
<Input
clearable
placeholder="请输入手机号"
// disabled={true}
type="number"
maxLength={11}
onChangeText={(value) => setMobilePhone(value)}
value={mobilePhone}
onChange={(value) =>
setLoginInfo({ ...loginInfo, mobilePhone: value })
}
value={loginInfo.mobilePhone}
style={{ "--color": "#FFFFFF", "--font-size": "16px" }}
/>
</div>
@ -126,10 +320,13 @@ export default function Login({}) {
密码
</p>
<Input
clearable
placeholder="请输入密码"
onChangeText={(value) => setVeriCode(value)}
value={password}
type="number"
onChange={(value) =>
setLoginInfo({ ...loginInfo, password: value })
}
value={loginInfo.password}
type="password"
style={{
"--placeholder-color": "#FFFFFF80",
"--font-size": "16px",
@ -137,20 +334,29 @@ export default function Login({}) {
/>
</div>
</div>
<LoginBtn />
<LoginBtn
loginInfo={loginInfo}
setLoginInfo={setLoginInfo}
handleSubmit={handleSubmit}
type={activeIndex ? "password" : "mobile"}
/>
</Swiper.Item>
</Swiper>
</div>
);
}
const LoginBtn = () => {
const LoginBtn = ({ loginInfo, setLoginInfo, type, handleSubmit }) => {
const router = useRouter();
useEffect(() => {
console.log("loginInfo", loginInfo);
}, []);
return (
<div className="mt-16">
<div className="flex items-center">
<Checkbox
value={loginInfo?.checked}
onChange={(value) => setLoginInfo({ ...loginInfo, checked: value })}
style={{
"--icon-size": "14px",
"--font-size": "14px",
@ -186,7 +392,7 @@ const LoginBtn = () => {
shape="rounded"
size="middle"
block
// onClick={handleSubmit}
onClick={() => handleSubmit(type)}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
className="mt-2"
>
@ -195,3 +401,8 @@ const LoginBtn = () => {
</div>
);
};
const mapDispatchToProps = {
handleLogin,
};
export default connect(null, mapDispatchToProps)(Login);

View File

@ -12,6 +12,7 @@ import {
PullToRefresh,
List,
InfiniteScroll,
Toast
} from "antd-mobile";
import { useRouter } from "next/navigation";
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
@ -29,9 +30,9 @@ export default function MessageDetail({}) {
const getSession = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = baseRequest();
const base = baseRequest();
const account = await get("account");
const signature = generateSignature({
const signature = generateSignature({
mid: account.mid,
...base,
});
@ -51,9 +52,9 @@ export default function MessageDetail({}) {
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
@ -96,9 +97,7 @@ export default function MessageDetail({}) {
<div>
<PullToRefresh onRefresh={doRefresh}>
<List className="px-4 overflow-y-auto scrollbarBox_hidden">
<List.Item className="!p-0">
</List.Item>
<List.Item className="!p-0"></List.Item>
<List.Item className="!p-0"></List.Item>
<List.Item className="!p-0"></List.Item>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />

View File

@ -1,16 +1,24 @@
"use client";
import React from "react";
import React,{useEffect,useState} from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleRight } from "@fortawesome/free-solid-svg-icons";
import { Avatar, Image } from "antd-mobile";
import { useRouter,useSearchParams } from "next/navigation";
import withAuth from "@/components/WithAuth";
import {get} from "@/utils/storeInfo";
const My = () => {
const [userInfo, setUserInfo] = useState({});
const searchParams = useSearchParams();
const router = useRouter();
useEffect(() => {
const userInfo = get("account");
if (userInfo) {
setUserInfo(userInfo);
}
},[])
return (
<div className="p-4 pb-20 bg-no-repeat bg-contain bg-top bg-[url(/images/profilebackground.png)]">
<div className="h-screen p-4 pb-20 bg-no-repeat bg-contain bg-top bg-[url(/images/profilebackground.png)]">
<div className="flex justify-end items-center z-10 w-full mb-4">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full mr-2" onClick={()=>router.push("my/editUserProfile/selectUserProfileItem")}>
<Image
@ -29,25 +37,26 @@ const My = () => {
/>
</div>
</div>
<div className="flex items-center justify-between mb-4" onClick={()=>router.push("profile")}>
<div className="flex items-center justify-between mb-4" onClick={()=>router.push("profile/"+userInfo.mid)}>
<div className="flex items-center">
<Avatar
rounded-full
mr-4
src="https://picsum.photos/seed/picsum/200/300"
src={userInfo.avatar?.images[0].urls[0]}
className="mr-4"
style={{ "--size": "76px", "--border-radius": "50%" }}
/>
<div>
<p className="text-2xl font-bold">测试账号</p>
<p className="text-2xl font-bold">{userInfo.name}</p>
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mt-1 w-max">
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>213422</span>
<span>{userInfo.user_id}</span>
</div>
</div>
</div>
@ -71,11 +80,11 @@ const My = () => {
<p className="text-[#ffffff88]">粉丝</p>
</li>
<li className="text-center" onClick={()=>router.push("my/wallet")}>
<p className="text-2xl">540</p>
<p className="text-2xl">{userInfo.gold_num}</p>
<p className="text-[#ffffff88]">金币</p>
</li>
<li className="text-center" onClick={()=>router.push("my/wallet")}>
<p className="text-2xl">0</p>
<p className="text-2xl">{userInfo.diamond_num}</p>
<p className="text-[#ffffff88]">钻石</p>
</li>
</ul>

View File

@ -71,6 +71,7 @@ export default function SwitchAccount() {
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
/>
<span>213422</span>

View File

@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useRef, useState, Suspense } from "react";
import React, { useEffect, useRef, useState } from "react";
import {
Tabs,
Swiper,
@ -10,15 +10,15 @@ import {
List,
Image,
} from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRefresh } from "@fortawesome/free-solid-svg-icons";
import PostItem from "../components/PostItem";
import { sleep } from "antd-mobile/es/utils/sleep";
import "./index.css";
import PostItemSkeleton from "@/components/skeletons/PostItemSkeleton";
import Link from "next/link";
import requre from "@/utils/require";
import baseRequest from "@/utils/baseRequest";
import { generateSignature } from "@/utils/crypto";
import require from "@/utils/require";
import Empty from "@/components/Empty";
const variables = {
"@active-line-color": "#f00", // 将主题色改为红色
};
@ -34,46 +34,14 @@ export default function Home() {
const swiperRef = useRef(null);
const [activeIndex, setActiveIndex] = useState(0);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true);
const [scrollHeight, setScrollHeight] = useState(0);
const [commenPostList,setCommenPostList] = useState([])
// 获取屏幕高度
// const scrollHeight = 600;
useEffect(() => {
setScrollHeight(window.innerHeight - 126);
getPostList(2);
setScrollHeight(window.innerHeight - 112);
// getData(0)
}, []);
async function doRefresh() {
// await sleep(1000);
// Toast.show({
// icon: "fail",
// content: "刷新失败",
// });
// throw new Error("刷新失败");
getPostList(1);
}
async function loadMore() {
// const append = await getPostList(0);
// setData((val) => [...val, ...append]);
// setHasMore(append.length > 0);
}
const getPostList = async (type = 0) => {
const data = await requre("POST", "/api/moment/recomm_list", {
body: { op_type: type },
});
setLoading(false)
if (data.ret == -1) {
Toast.show({
icon: "fail",
content: "加载失败",
});
}else{
setCommenPostList(data.data.recomm_list)
}
console.log("res", data);
};
return (
<div className="h-screen">
<div className="flex justify-between items-center px-2 custom-tabs text-gray-400 sticky top-0 z-10 bg-deepBg">
@ -87,6 +55,7 @@ export default function Home() {
>
{tabItems.map((item) => (
<Tabs.Tab
destroyOnClose={true}
forceRender={false}
title={item.title}
key={item.key}
@ -102,9 +71,9 @@ export default function Home() {
</Link>
</div>
<Swiper
className="overflow-visible"
className="overflow-visible h-full"
direction="horizontal"
loop
loop={false}
indicator={() => null}
ref={swiperRef}
defaultIndex={activeIndex}
@ -113,33 +82,199 @@ export default function Home() {
}}
>
<Swiper.Item>
<PullToRefresh onRefresh={doRefresh}>
<List className="px-4 overflow-y-auto scrollbarBox_hidden">
{loading && <div><PostItemSkeleton /><PostItemSkeleton /><PostItemSkeleton /></div>}
{commenPostList.map(item=><List.Item key={item.id} className="!p-0">
<PostItem
type="post"
data={item}
/>
</List.Item>)}
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>
</PullToRefresh>
<RecommPostList scrollHeight={scrollHeight} />
</Swiper.Item>
<Swiper.Item>
<PullToRefresh onRefresh={doRefresh}>
<List
className="p-2 overflow-y-auto scrollbarBox_hidden"
style={{ maxHeight: `${scrollHeight}px` }}
>
<List.Item className="!p-0">
<PostItem follow={true} type="post" />
</List.Item>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>
</PullToRefresh>
<FollowPostList scrollHeight={scrollHeight} />
</Swiper.Item>
</Swiper>
</div>
);
}
const RecommPostList = ({ scrollHeight }) => {
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true);
const [commenPostList, setCommenPostList] = useState([]);
const [currentTime, setCurrentTime] = useState();
useEffect(() => {
getRecommPostList(2).then((res) => {
setCommenPostList(res);
});
}, []);
async function doRefresh() {
// await sleep(1000);
// Toast.show({
// icon: "fail",
// content: "刷新失败",
// });
// throw new Error("刷新失败");
const list = await getRecommPostList(1);
setCommenPostList(list);
}
async function loadMore() {
const list = await getRecommPostList(0);
if (list.length == 0) {
setHasMore(false);
}
setCommenPostList([...commenPostList, ...list]);
}
const getRecommPostList = async (type = 2) => {
setLoading(true);
const data = await require("POST", "/api/moment/recomm_list", {
body: { op_type: type },
});
setLoading(false);
if (data.ret == -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
} else {
return data.data.recomm_list;
}
};
return (
<div
style={{ maxHeight: `${scrollHeight}px` }}
className="px-4 overflow-y-auto"
>
<List>
{loading && (
<div>
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
</div>
)}
{commenPostList.map((item) => (
<List.Item key={item.id} className="!p-0">
<PostItem type="post" data={item} />
</List.Item>
))}
{commenPostList.length == 0 && (
<div
className={`flex flex-col items-center justify-center`}
style={{ height: `${scrollHeight}px` }}
>
<Empty type="nodata" />
</div>
)}
</List>
{!!commenPostList.length && (
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
)}
<div
className={`fixed bottom-[126px] right-4 z-[999] w-10 h-10 flex items-center justify-center bg-[#1d1d1d71] rounded-full text-white ${
loading ? "animate-spin" : ""
}`}
>
<FontAwesomeIcon icon={faRefresh} size="xl" onClick={doRefresh} />
</div>
</div>
);
};
const FollowPostList = ({ scrollHeight }) => {
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(false);
const [followPostList, setFollowPostList] = useState([]);
const [currentTime, setCurrentTime] = useState();
const [offset, setOffset] = useState(0);
useEffect(() => {
getFollowPostList().then((res) => {
setFollowPostList(res);
});
}, []);
async function doRefresh() {
// await sleep(1000);
// Toast.show({
// icon: "fail",
// content: "刷新失败",
// });
// throw new Error("刷新失败");
// getRecommPostList(1);
}
async function loadMore() {
const list = await getFollowPostList();
const newList = [...followPostList, ...list];
setOffset(newList.length / 4);
setFollowPostList(newList);
}
const getFollowPostList = async () => {
setLoading(true);
setCurrentTime(Math.floor(new Date().getTime() / 1000));
const data = await require("POST", "/api/account_relation/list_follow", {
body: { offset, limit: 4 },
}, true);
setHasMore(data.data.list.length > 0);
if (data.data.list.length > 0) {
//查关注主播展示资料
const followsResponse =
await require("POST", "/api/moment/list_by_mids", {
body: {
offset,
limit: 4,
ct_upper_bound: currentTime,
mids: data.data.list.map((item) => item.obj_mid),
},
});
setLoading(false);
if (data.ret == -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
} else {
return followsResponse.data.list;
}
} else {
setLoading(false);
}
};
return (
<div
style={{ maxHeight: `${scrollHeight}px` }}
className="px-4 overflow-y-auto"
>
{/* <PullToRefresh onRefresh={doRefresh}> */}
<List>
{loading && (
<div className="my-[31px]">
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
</div>
)}
{followPostList.map((item, index) => (
<List.Item key={item.id + "_" + index} className="!p-0">
<PostItem type="post" data={item} />
</List.Item>
))}
{followPostList.length == 0 && (
<div
className={`flex flex-col items-center justify-center`}
style={{ height: `${scrollHeight}px` }}
>
<Empty type="nodata" />
</div>
)}
</List>
{!!followPostList.length && (
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
)}
{/* <div
className={`fixed bottom-[126px] right-4 z-[999] w-10 h-10 flex items-center justify-center bg-[#1d1d1d71] rounded-full text-white ${
loading ? "animate-spin" : ""
}`}
>
<FontAwesomeIcon icon={faRefresh} size="xl" onClick={doRefresh} />
</div> */}
{/* </PullToRefresh> */}
</div>
);
};

360
app/profile/[mid]/page.js Normal file
View File

@ -0,0 +1,360 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import {
Image,
Swiper,
Divider,
ImageViewer,
Popover,
Toast,
} from "antd-mobile";
import { useRouter, useParams } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faAngleRight,
faEllipsisVertical,
faCopy,
faWarning,
} from "@fortawesome/free-solid-svg-icons";
import require from "@/utils/require";
import AddWeChat from "@/components/AddWeChat";
import { handleFollow, checkRelation } from "@/api/public";
import { get } from "@/utils/storeInfo";
// import * as Clipboard from "expo-clipboard";
export default function PersonSpace() {
const { mid } = useParams();
const router = useRouter();
const [streamerInfo, setStreamerInfo] = useState(null);
const [spaceData, setSpaceData] = useState(null);
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [isFollow, setIsFollow] = useState(false);
// 获取屏幕高度
// const scrollHeight = 600;
const photos = [
{ url: "https://picsum.photos/seed/picsum/200/300", type: "video" },
{ url: "https://picsum.photos/seed/picsum/200/300", type: "img" },
];
useEffect(() => {
getStreamerInfo();
getSpaceData();
getRelationData();
}, []);
const showPhotos = (photos, index) => {
ImageViewer.Multi.show({
images: photos.map((item) => item.url),
defaultIndex: index,
});
};
const getStreamerInfo = async () => {
try {
setLoading(true);
const data = await require("POST", "/api/streamer/list_ext_by_mid", {
body: {
mid: Number(mid),
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
setStreamerInfo({
...data.data,
});
setLoading(false);
} catch (error) {
console.error(error);
}
};
const getSpaceData = async () => {
try {
const data = await require("POST", "/api/zone/list_by_mid", {
body: {
mid: Number(mid),
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
setSpaceData(data.data.list[0]);
} catch (error) {
console.error(error);
}
};
const getRelationData = async () => {
const account = get("account");
const subMid = account.mid;
const objMid = Number(mid);
const temIsFollowed = await checkRelation(subMid, objMid, 0);
setIsFollow(temIsFollowed);
};
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<Popover
content={
<div
className="text-black"
onClick={() => {
router.push("messageDetail");
}}
>
<FontAwesomeIcon
icon={faWarning}
// size="xl"
color="#3B69B8"
className="mr-2"
/>
<span>举报</span>
</div>
}
trigger="click"
placement="left"
>
<FontAwesomeIcon
icon={faEllipsisVertical}
size="xl"
onClick={() => {
// router.back();
}}
/>
</Popover>
</div>
{/* 内容 */}
<div>
<div>
<Swiper
autoplay
loop
indicatorProps={{
style: {
"--dot-color": "#FF669E30",
"--active-dot-color": "#FF669E",
},
}}
>
{photos.map((photo, index) => (
<Swiper.Item key={index}>
<div
className="relative min-w-max"
key={index}
onClick={() => {
showPhotos(photos, index);
}}
>
<Image
className="h-12 w-full"
fit="cover"
height={320}
src={photo.url}
// onClick={() => {
// Toast.show(`你点击了卡片 ${index + 1}`);
// }}
/>
{photo.type == "video" && (
<div className="absolute top-0 w-full h-full flex justify-center items-center bg-[#33333348]">
<Image
className=""
width={98}
height={98}
src="/icons/play.png"
/>
</div>
)}
</div>
</Swiper.Item>
))}
</Swiper>
</div>
<div className="p-4 pb-24">
<div>
<div className="mb-2">
<div className="flex items-center mb-2">
<p className="text-2xl mr-2">
{streamerInfo?.streamer_ext?.name}
</p>
<div className="w-5 h-5">
<Image src="/icons/verification.png" />
</div>
</div>
<ul className="flex">
{streamerInfo?.streamer_ext?.tag.map((item, index) => (
<li
key={index}
className="rounded-md bg-primary mr-2 px-2 py-1 text-xs mb-1"
>
{item}
</li>
))}
</ul>
</div>
<div>
<ul className="flex mb-1">
<li className="h-4 flex items-center text-xs bg-[#FFFFFF1A] rounded-full px-2 py-2.5 mb-1">
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
/>
<span>{streamerInfo?.streamer_ext?.user_id}</span>
</li>
</ul>
<p>
<span className="text-[#ffffff88]">个性签名</span>
{streamerInfo?.streamer_ext?.bio}
</p>
</div>
</div>
<Divider />
<div>
<div onClick={() => router.push("/space/"+mid)}>
<div className="flex justify-between items-center mb-2">
<span className="font-bold text-base">空间动态</span>
<div className="h-4 text-xs text-[#ffffff88]">
<span className="mr-2">
查看{spaceData?.zone_moment_count}
</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4"
onClick={() => {
router.back();
}}
/>
</div>
</div>
<div className="flex ">
{spaceData?.previews?.images.map((item, index) => (
<div
key={item.id}
className="w-20 h-20 overflow-hidden rounded"
>
<Image
width="20vw"
height="20vw"
className={`rounded mr-2 ${!!index && "imageBlur"}`}
fit="cover"
src={item.urls[0]}
/>
</div>
))}
</div>
</div>
</div>
{streamerInfo?.streamer_ext?.platforms && (
<>
<Divider />
<div>
<p className="font-bold mb-2 text-base">来这找我玩</p>
<ul>
<li className="flex justify-between border-[1.5px] border-[#ffffff43] rounded-xl p-2 mb-2">
<div className="flex justify-between items-center">
<Image
height={32}
width={32}
className="mr-2"
src="/images/platform_wechat.png"
/>
<div className="text-base">
<span>微信</span>
<span>点击查看</span>
</div>
</div>
</li>
{streamerInfo?.streamer_ext?.platforms?.map((item) => (
<li
key={item.id}
className="flex justify-between border-[1.5px] border-[#ffffff43] rounded-xl p-2"
>
<div className="flex justify-between items-center">
<Image
height={32}
width={32}
className="mr-2"
src="/images/platform_douyin.png"
/>
<div className="text-base">
<span>{item?.link_name}</span>
<span>{item?.nickname}</span>
</div>
</div>
<div className="flex text-sm">
<div
className="flex items-center mr-6"
// onClick={() => {
// Clipboard.setStringAsync(item.url);
// }}
>
<FontAwesomeIcon
icon={faCopy}
size="xl"
className="h-3 mr-1"
/>
<span>复制</span>
</div>
<div className="flex items-center">
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-3 mr-1"
/>
<span>前往</span>
</div>
</div>
</li>
))}
</ul>
</div>
</>
)}
</div>
<div className="flex justify-between items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div
className="text-base bg-[#FFFFFF1A] py-1 px-6 rounded-full"
onClick={() => handleFollow(isFollow, Number(mid), setIsFollow)}
>
{isFollow ? "已关注" : "关注"}
</div>
<div
className="bg-primary px-10 py-1 text-base rounded-full"
onClick={() => setVisible(true)}
>
添加微信
</div>
</div>
</div>
<AddWeChat
visible={visible}
closeMask={setVisible}
price={streamerInfo?.streamer_ext?.wechat_coin_price}
name={streamerInfo?.streamer_ext?.name}
streamerMid={streamerInfo?.streamer_ext?.mid}
/>
</div>
);
}

View File

@ -1,263 +0,0 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Image, Swiper, Divider, ImageViewer, Popover } from "antd-mobile";
import { useRouter } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faAngleRight,
faEllipsisVertical,
faCopy,
faWarning,
} from "@fortawesome/free-solid-svg-icons";
export default function PersonSpace() {
const router = useRouter();
// 获取屏幕高度
// const scrollHeight = 600;
const [searchValue, setSearchValue] = useState("");
const photos = [
{ url: "https://picsum.photos/seed/picsum/200/300", type: "video" },
{ url: "https://picsum.photos/seed/picsum/200/300", type: "img" },
];
useEffect(() => {}, []);
const showPhotos = (photos, index) => {
ImageViewer.Multi.show({
images: photos.map((item) => item.url),
defaultIndex: index,
});
};
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<Popover
content={
<div
className="text-black"
onClick={() => {
router.push("messageDetail");
}}
>
<FontAwesomeIcon
icon={faWarning}
// size="xl"
color="#3B69B8"
className="mr-2"
/>
<span>举报</span>
</div>
}
trigger="click"
placement="left"
>
<FontAwesomeIcon
icon={faEllipsisVertical}
size="xl"
onClick={() => {
// router.back();
}}
/>
</Popover>
</div>
{/* 内容 */}
<div>
<div>
<Swiper
autoplay
loop
indicatorProps={{
style: {
"--dot-color": "#FF669E30",
"--active-dot-color": "#FF669E",
},
}}
>
{photos.map((photo, index) => (
<Swiper.Item key={index}>
<div
className="relative min-w-max"
key={index}
onClick={() => {
showPhotos(photos, index);
}}
>
<Image
className="h-12 w-full"
fit="cover"
height={320}
src={photo.url}
// onClick={() => {
// Toast.show(`你点击了卡片 ${index + 1}`);
// }}
/>
{photo.type == "video" && (
<div className="absolute top-0 w-full h-full flex justify-center items-center bg-[#33333348]">
<Image
className=""
width={98}
height={98}
src="/icons/play.png"
/>
</div>
)}
</div>
</Swiper.Item>
))}
</Swiper>
</div>
<div className="p-4 pb-24">
<div>
<div className="mb-2">
<div className="flex items-center mb-2">
<p className="text-2xl mr-2">PUPIHAN</p>
<div className="w-5 h-5">
<Image src="/icons/verification.png" />
</div>
</div>
<ul className="flex">
<li className="rounded-md bg-primary mr-2 px-2 py-1 text-xs mb-1">
颜值高
</li>
<li className="rounded-md bg-primary mr-2 px-2 py-1 text-xs mb-1">
身材好
</li>
<li className="rounded-md bg-primary mr-2 px-2 py-1 text-xs mb-1">
女王范
</li>
</ul>
</div>
<div>
<ul className="flex mb-1">
<li className="h-4 flex items-center text-xs bg-[#FFFFFF1A] rounded-full px-2 py-2.5 mb-1">
<Image
src="/icons/info/ID.png"
width={14}
className="w-4 h-full mr-1"
/>
<span>213422</span>
</li>
</ul>
<p>
<span className="text-[#ffffff88]">个性签名</span>
专属圈内容都在空间里永久更新外面看不到哟
</p>
</div>
</div>
<Divider />
<div>
<div onClick={() => router.push("/space/person_space")}>
<div className="flex justify-between items-center mb-2">
<span className="font-bold text-base">空间动态</span>
<div className="h-4 text-xs text-[#ffffff88]">
<span className="mr-2">查看60条</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4"
onClick={() => {
router.back();
}}
/>
</div>
</div>
<div className="flex ">
<Image
width="20vw"
height="20vw"
className="rounded mr-2"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
<div className="w-20 h-20 overflow-hidden rounded">
<Image
width="20vw"
height="20vw"
className="rounded mr-2 imageBlur"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</div>
</div>
</div>
</div>
<Divider />
<div>
<p className="font-bold mb-2 text-base">来这找我玩</p>
<ul>
<li className="flex justify-between border-[1.5px] border-[#ffffff43] rounded-xl p-2 mb-2">
<div className="flex justify-between items-center">
<Image
height={32}
width={32}
className="mr-2"
src="/images/platform_wechat.png"
/>
<div className="text-base">
<span>微信</span>
<span>点击查看</span>
</div>
</div>
</li>
<li className="flex justify-between border-[1.5px] border-[#ffffff43] rounded-xl p-2">
<div className="flex justify-between items-center">
<Image
height={32}
width={32}
className="mr-2"
src="/images/platform_douyin.png"
/>
<div className="text-base">
<span>抖音</span>
<span>PUPIHAN</span>
</div>
</div>
<div className="flex text-sm">
<div className="flex items-center mr-6">
<FontAwesomeIcon
icon={faCopy}
size="xl"
className="h-3 mr-1"
onClick={() => {
router.back();
}}
/>
<span>复制</span>
</div>
<div className="flex items-center">
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-3 mr-1"
onClick={() => {
router.back();
}}
/>
<span>前往</span>
</div>
</div>
</li>
</ul>
</div>
</div>
<div className="flex justify-between items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div className="text-base bg-[#FFFFFF1A] py-1 px-6 rounded-full">
关注
</div>
<div className="bg-primary px-10 py-1 text-base rounded-full">
添加微信
</div>
</div>
</div>
</div>
);
}

View File

@ -1,31 +1,102 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Input, List } from "antd-mobile";
import { Input, List, DotLoading, Toast } from "antd-mobile";
import { useRouter } from "next/navigation";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons";
import require from "@/utils/require";
import { debounce } from "@/utils/tools";
import Empty from "@/components/Empty";
const newDebounce = debounce(function (fn) {
fn && fn();
}, 500);
export default function Search() {
const router = useRouter();
// 获取屏幕高度
// const scrollHeight = 600;
const [searchValue, setSearchValue] = useState("");
const [loading, setLoading] = useState(false);
const [streamers, setStreamers] = useState([]);
const [zones, setZones] = useState([]);
useEffect(() => {}, []);
const isNumeric = (str) => {
return /^\d+$/.test(str);
};
const getResult = async (value) => {
console.log("searchValue", value);
const isSearchInt = isNumeric(value);
let api = "";
let querryParams = "";
if (isSearchInt) {
api = "/api/streamer/list_ext_fuzzily_by_user_id";
querryParams = { user_id: parseInt(value, 10) };
} else {
api = "/api/streamer/list_ext_fuzzily_by_name";
querryParams = { name: value };
}
try {
setLoading(true);
const data = await require("POST", api, {
body: {
...querryParams,
offset: 0,
limit: 20,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
// if (!ignore) {
const zonesData = data.data.list.filter((item) => item.zones.length > 0);
setStreamers(data.data.list);
setZones(zonesData);
// }
setLoading(false);
} catch (error) {
console.error(error);
}
};
return (
<div className="">
<div className="flex items-center p-4">
<FontAwesomeIcon icon={faAngleLeft} size="xl" className="mr-3"onClick={() => {
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
className="mr-3"
onClick={() => {
router.back();
}}/>
}}
/>
<div className="flex items-center w-full">
<div className="bg-[#FFFFFF1A] rounded-lg px-4 py-1 w-full">
<div className="relative bg-[#FFFFFF1A] rounded-lg px-4 py-1 w-full">
<Input
placeholder="搜索Ta的昵称或id"
value={searchValue}
onChange={(val) => {
setSearchValue(val);
setSearchValue((old) => {
let test = (e) => {
if (val == "") {
setStreamers([]);
setZones([]);
return;
}
getResult(val);
};
newDebounce(test);
return val;
});
}}
/>
<div className="absolute top-1/2 -translate-y-1/2 right-2">
{loading && <DotLoading />}
</div>
</div>
{searchValue && (
<p
@ -40,42 +111,39 @@ export default function Search() {
</div>
</div>
<List className="px-4 overflow-y-auto scrollbarBox_hidden">
<List.Item className="!p-0">
<div className="flex items-center">
<img
className="flex-none w-10 h-10 rounded-full mr-2"
src="https://picsum.photos/seed/picsum/200/300"
alt=""
/>
<div className="flex-1">
<div className="flex items-center">
<span className="text-md mr-2 text-base text-white font-medium">用户名</span>
<span className="py-0.5 px-2 ml-1 bg-[#FFFFFF1A] rounded-full text-white text-xs font-medium">
ID 845457
</span>
</div>
<p className="text-sm text-[#FFFFFF80]">御姐风细跟高跟鞋太绝了</p>
</div>
{!streamers.length && (
<div
className={`h-screen -mt-[57px] flex flex-col items-center justify-center`}
>
<Empty type="nodata" />
</div>
</List.Item>
<List.Item className="!p-0">
<div className="flex items-center">
<img
className="flex-none w-10 h-10 rounded-full mr-2"
src="https://picsum.photos/seed/picsum/200/300"
alt=""
/>
<div className="flex-1">
<div className="flex items-center">
<span className="text-md mr-2 text-base text-white font-medium">用户名</span>
<span className="py-0.5 px-2 ml-1 bg-[#FFFFFF1A] rounded-full text-white text-xs font-medium">
ID 845457
</span>
)}
{streamers.map((item) => (
<List.Item
className="!p-0"
onClick={() => router.push(`/space/${item.mid}`)}
key={item.id}
>
<div className="flex items-center">
<img
className="flex-none w-10 h-10 rounded-full mr-2"
src={item?.avatar?.images[0]?.urls[0]}
alt=""
/>
<div className="flex-1">
<div className="flex items-center">
<span className="text-md mr-2 text-base text-white font-medium">
{item?.name}
</span>
<span className="py-0.5 px-2 ml-1 bg-[#FFFFFF1A] rounded-full text-white text-xs font-medium">
ID {item.user_id}
</span>
</div>
<p className="text-sm text-[#FFFFFF80]">{item?.bio}</p>
</div>
<p className="text-sm text-[#FFFFFF80]">御姐风细跟高跟鞋太绝了</p>
</div>
</div>
</List.Item>
</List.Item>
))}
</List>
</div>
);

474
app/space/[id]/page.js Normal file
View File

@ -0,0 +1,474 @@
"use client";
import React, { useEffect, useState, useRef,useMemo } from "react";
import {
Image,
Mask,
FloatingPanel,
JumboTabs,
List,
InfiniteScroll,
ProgressBar,
Toast,
} from "antd-mobile";
import { useRouter, useParams } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faRefresh } from "@fortawesome/free-solid-svg-icons";
import PostItem from "@/components/PostItem";
import PostItemSkeleton from "@/components/skeletons/PostItemSkeleton";
import Empty from "@/components/Empty";
import require from "@/utils/require";
import AddWeChat from "@/components/AddWeChat";
import SeeTiefen from "@/components/SeeTiefen";
import DefaultMask from "@/components/DefaultMask";
import {getSpaceData,getStreamerInfo} from '@/api/space'
const anchors = [
window.innerHeight - 280,
window.innerHeight - 280,
window.innerHeight - 60,
];
const tabItems = [
{ label: "全部", key: "all" },
{ label: "铁粉专享", key: "ironFan" },
{ label: "超粉专享", key: "chaofen" },
];
export default function PersonSpace() {
const router = useRouter();
const { id } = useParams();
const contentBox = useRef();
const [hasMore, setHasMore] = useState(true);
const [scrollHeight, setScrollHeight] = useState(0);
const [postList, setPostList] = useState([]);
const [offset, setOffset] = useState(0);
const [maskVisible, setMaskVisible] = useState({ visible: false, type: "" });
const [currentKey, setCurrentKey] = useState("all");
const [loading, setLoading] = useState(false);
const [streamerInfo, setStreamerInfo] = useState(null);
const [currentTime, setCurrentTime] = useState();
//退款中Modal是否展示
const [isRefundingModalVisible, setIsRefundingModalVisible] = useState(false);
const ironFanProgress = useMemo(
() => Math.floor((streamerInfo?.expenditure / streamerInfo?.ironfanship_price) * 100),
[streamerInfo]
);
useEffect(() => {
setScrollHeight(window.innerHeight - 126);
if (contentBox.current) {
contentBox.current.style.transform = "translateY(-12px)";
// debugger
}
getStreamerInfo(Number(id));
getCurrentTime();
getSpaceData(Number(id)).then(res=>{
const {isRefunding,noRole}=res;
isRefunding && router.push("/");
noRole && router.push("/person_space_introduce/"+id);
})
}, []);
useEffect(() => {
setOffset(0);
if (streamerInfo) {
getPostList(streamerInfo.id, currentKey, true).then((res) => {
setPostList(res);
});
}
}, [currentKey, streamerInfo]);
const getCurrentTime = async () => {
setCurrentTime(Math.floor(new Date().getTime() / 1000));
};
async function loadMore() {
const append = await getPostList(streamerInfo.id, currentKey, false);
if (append) {
setPostList((val) => [...val, ...append]);
setOffset((val) => val + 1);
setHasMore(append.length > 0);
}
}
const getPostList = async (zid, activeKey, first) => {
try {
setLoading(true);
let body = {
zid: zid,
ct_upper_bound: Math.floor(new Date().getTime() / 1000),
offset: first ? 0 : offset,
limit: 4,
};
switch (activeKey) {
case "all":
body = body;
break;
case "ironFan":
body = {
...body,
c_type: 1,
is_ironfan_visible: 1,
};
break;
case "chaofen":
body = {
...body,
c_type: 1,
};
break;
default:
break;
}
const data = await require("POST", "/api/zone_moment/list_by_zid", {
body,
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
setLoading(false);
return data.data.list;
} catch (error) {
console.error(error);
}
};
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<Image
width={42}
height={42}
src="/icons/setting.png"
placeholder=""
onClick={() => router.push("setting")}
/>
</div>
{/* 内容 */}
<div>
<div
className="bg-no-repeat bg-cover bg-fixed "
style={{
backgroundImage:
streamerInfo?.streamer_ext?.cover?.images[0]?.urls[0],
}}
>
<div className="px-4 pt-24 pb-8 bg-[#181818a9]">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Image
width="64px"
height="64px"
className="rounded-full mr-2 border-2 border-white"
fit="cover"
src={streamerInfo?.streamer_ext?.avatar?.images[0]?.urls[0]}
/>
<div>
<p className="text-2xl mb-1 font-bold">
{streamerInfo?.streamer_ext?.name}
</p>
<div className="flex">
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mb-1 w-max mr-1">
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>{streamerInfo?.streamer_ext?.user_id}</span>
</div>
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mb-1 w-max">
<Image
src="/icons/edit.png"
width={14}
height={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>{streamerInfo?.zone_moment_count}</span>
</div>
</div>
</div>
</div>
<div
className="flex flex-col items-center"
// onClick={() => setMaskVisible(true)}
>
<p className="text-base px-3 py-1 rounded-full bg-primary">
分享
</p>
</div>
</div>
<ul className="flex mt-8">
<li
className="flex flex-col items-center mr-6"
onClick={() =>
setMaskVisible({ visible: true, type: "weChat" })
}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">查看微信</p>
</li>
<li
className="flex flex-col items-center mr-6"
onClick={() =>
setMaskVisible({ visible: true, type: "ironFan" })
}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">
{streamerInfo?.is_ironfanship_unlocked === 1
? "已是铁粉"
: "成为铁粉"}
</p>
<p className="text-[#ffffff54] text-[10px]">{`${parseInt(
streamerInfo?.expenditure / 100,
10
)}/${parseInt(streamerInfo?.ironfanship_price / 100, 10)}`}</p>
</li>
<li
className="flex flex-col items-center mr-6"
onClick={() => {
streamerInfo?.is_superfanship_unlocked === 1 ? setCurrentKey("chaofen")
: router.push("/pay");
}}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">
{streamerInfo?.is_superfanship_unlocked === 1
? "尊贵超粉"
: "成为超粉"}
</p>
</li>
<li
className="flex flex-col items-center"
// onClick={() => setMaskVisible(true)}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/report.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">举报</p>
</li>
{streamerInfo?.visitor_role === 3 && (
<li
onClick={() =>
router.push("VisibleToOneselfSpacePosts")
}
className="flex flex-col items-center"
>
<Image
src="/icons/review_fail_bg.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
<p className="text-xs">审核未通过</p>
</li>
)}
</ul>
</div>
</div>
<FloatingPanel anchors={anchors}>
<JumboTabs
onChange={(key) => setCurrentKey(key)}
activeKey={currentKey}
className="bg-deepBg"
>
{tabItems.map((it) => (
<JumboTabs.Tab
title={it.label}
key={it.key}
description={
currentKey == it.key && (
<div className="titlePinkLine relative w-full"></div>
)
}
destroyOnClose={true}
>
<List className="overflow-y-auto scrollbarBox_hidden px-1">
{loading && (
<>
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
<PostItemSkeleton />
</>
)}
{!postList.length && (
<div
className={`flex flex-col items-center mt-20`}
style={{ height: `${scrollHeight}px` }}
>
<Empty type="nodata" />
</div>
)}
{postList.map((item, index) => (
<List.Item key={item.id + "_" + index} className="!p-0">
<PostItem type="space" data={item} />
</List.Item>
))}
{/* <InfiniteScroll loadMore={loadMore} hasMore={hasMore} /> */}
{!!postList.length && streamerInfo && (
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
)}
</List>
</JumboTabs.Tab>
))}
</JumboTabs>
</FloatingPanel>
<div
className={`fixed bottom-[96px] right-4 z-[999] w-10 h-10 flex items-center justify-center bg-[#1d1d1d71] rounded-full text-white ${
loading ? "animate-spin" : ""
}`}
>
<FontAwesomeIcon
icon={faRefresh}
size="xl"
onClick={() => {
router.refresh();
}}
/>
</div>
<ul className="grid grid-cols-3 mt-8 px-4 py-2 fixed bottom-0 z-[999] bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<li
className="flex flex-col items-center"
onClick={() => setMaskVisible({ visible: true, type: "weChat" })}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">查看微信</p>
</li>
<li
className="flex flex-col items-center"
onClick={() => setMaskVisible({ visible: true, type: "ironFan" })}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">已是铁粉</p>
{/* <p className="text-[#ffffff54] text-[10px]">0/299</p> */}
</li>
<li
className="flex flex-col items-center"
onClick={() => {
setCurrentKey("chaofen");
}}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">尊贵超粉</p>
</li>
</ul>
{maskVisible.type=="weChat" && <AddWeChat
visible={maskVisible.visible}
closeMask={(close) => setMaskVisible({ visible: false, type: "" })}
price={streamerInfo?.streamer_ext?.wechat_coin_price}
name={streamerInfo?.streamer_ext?.name}
streamerMid={streamerInfo?.streamer_ext?.mid}
avatar={streamerInfo?.streamer_ext?.avatar?.images[0]?.urls[0]}
/>}
{maskVisible.type=="ironFan" && <SeeTiefen
visible={maskVisible.visible}
ironFanProgress={ironFanProgress}
expenditure={streamerInfo.expenditure}
ironfanship_price={streamerInfo.ironfanship_price}
closeMask={() => {
setMaskVisible({ visible: false, type: "" });
}}
handleClick={()=>{
setCurrentKey("ironFan");
setMaskVisible({ visible: false, type: "" });
}}
/>}
<DefaultMask title="当前空间正在退款中" content="退款中空间不支持查看,请关注原支付渠道退款进度,退款后无法再次进入当前空间。" visible={isRefundingModalVisible} closeMask={() => {
setIsRefundingModalVisible(false);
// setTimeout(() => navigation.replace("HomeTab"), 500);
}}/>
{/* <div className="flex justify-center items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div className="bg-primary px-10 py-1 text-base rounded-full">
<div
className="flex items-center py-2 text-base"
onClick={() => {
router.push("/pay");
}}
>
<Image
width={18}
height={18}
placeholder=""
className="mr-2"
src="/icons/money_pink.png"
/>
<span>39.9元立即加入</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4 ml-2"
/>
</div>
</div>
</div> */}
</div>
</div>
);
}

View File

@ -79,7 +79,7 @@ export default function Space() {
setHasMore(append.length > 0);
}
return (
<main className="h-screen">
<div className="h-screen">
<div className="flex justify-between items-center p-2 custom-tabs text-gray-400 sticky top-0 z-10 bg-deepBg">
<Tabs
activeKey={tabItems[activeIndex].key}
@ -209,7 +209,7 @@ export default function Space() {
</div>
</Swiper.Item>
</Swiper>
</main>
</div>
);
}

View File

@ -1,537 +0,0 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import {
Image,
Mask,
FloatingPanel,
JumboTabs,
List,
InfiniteScroll,
ProgressBar,
} from "antd-mobile";
import { useRouter } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faRefresh,
} from "@fortawesome/free-solid-svg-icons";
import PostItem from "@/components/PostItem";
import PostItemSkeleton from "@/components/skeletons/PostItemSkeleton";
import Empty from "@/components/Empty";
const anchors = [
window.innerHeight - 280,
window.innerHeight - 280,
window.innerHeight - 60,
];
export default function PersonSpace() {
const router = useRouter();
const contentBox = useRef();
const [hasMore, setHasMore] = useState(true);
const [scrollHeight, setScrollHeight] = useState(0);
// 获取屏幕高度
// const scrollHeight = 600;
const photos = [
"https://picsum.photos/seed/picsum/200/300",
"https://picsum.photos/seed/picsum/200/300",
];
const [maskVisible, setMaskVisible] = useState({ visible: false, type: "" });
const [currentKey, setCurrentKey] = useState("all");
useEffect(() => {
setScrollHeight(window.innerHeight - 126);
if (contentBox.current) {
contentBox.current.style.transform = "translateY(-12px)";
// debugger
}
}, []);
async function loadMore() {
const append = await mockRequest();
setData((val) => [...val, ...append]);
setHasMore(append.length > 0);
}
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<Image width={42} height={42} src="/icons/setting.png" placeholder="" onClick={()=>router.push("setting")}/>
</div>
{/* 内容 */}
<div>
<div
className="bg-no-repeat bg-cover bg-fixed "
style={{
backgroundImage: "url('https://picsum.photos/seed/picsum/200/300')",
}}
>
<div className="px-4 pt-24 pb-8 bg-[#181818a9]">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Image
width="64px"
height="64px"
className="rounded-full mr-2 border-2 border-white"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
<div>
<p className="text-2xl mb-1 font-bold">草莓不可爱</p>
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mb-1 w-max">
<Image
src="/icons/info/ID.png"
width={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>213422</span>
</div>
</div>
</div>
<div
className="flex flex-col items-center"
// onClick={() => setMaskVisible(true)}
>
<p className="text-base px-3 py-1 rounded-full bg-primary">
分享
</p>
</div>
</div>
<ul className="flex mt-8">
<li
className="flex flex-col items-center mr-6"
onClick={() =>
setMaskVisible({ visible: true, type: "weChat" })
}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">查看微信</p>
</li>
<li
className="flex flex-col items-center mr-6"
onClick={() =>
setMaskVisible({ visible: true, type: "ironFan" })
}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">已是铁粉</p>
<p className="text-[#ffffff54] text-[10px]">0/299</p>
</li>
<li
className="flex flex-col items-center mr-6"
onClick={() => setCurrentKey("chaofen")}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">尊贵超粉</p>
</li>
<li
className="flex flex-col items-center"
// onClick={() => setMaskVisible(true)}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/report.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">举报</p>
</li>
</ul>
</div>
</div>
<FloatingPanel anchors={anchors}>
<JumboTabs
onChange={(key) => setCurrentKey(key)}
activeKey={currentKey}
className="bg-deepBg"
>
<JumboTabs.Tab
title="全部"
key="all"
description={
currentKey == "all" && (
<div className="titlePinkLine relative w-full"></div>
)
}
destroyOnClose={true}
>
<List className="overflow-y-auto scrollbarBox_hidden">
<PostItemSkeleton />
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "video",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>
</JumboTabs.Tab>
<JumboTabs.Tab
title="铁粉专享"
key="ironFan"
description={
currentKey == "ironFan" && (
<div className="titlePinkLine relative w-full"></div>
)
}
destroyOnClose={true}
>
<div
className={`flex flex-col items-center mt-20`}
style={{ height: `${scrollHeight}px` }}
>
<Empty type="nodata" />
</div>
</JumboTabs.Tab>
<JumboTabs.Tab
title="超粉专享"
key="chaofen"
description={
currentKey == "chaofen" && (
<div className="titlePinkLine relative w-full"></div>
)
}
destroyOnClose={true}
>
<List className="overflow-y-auto scrollbarBox_hidden">
<PostItemSkeleton />
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "video",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "video",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "video",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<List.Item className="!p-0">
<PostItem
type="space"
photos={[
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
{
url: "https://picsum.photos/seed/picsum/200/300",
type: "img",
},
]}
/>
</List.Item>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>
</JumboTabs.Tab>
</JumboTabs>
</FloatingPanel>
<div className="fixed bottom-[96px] right-4 z-[999] w-10 h-10 flex items-center justify-center bg-[#1d1d1d71] rounded-full text-white animate-spin">
<FontAwesomeIcon
icon={faRefresh}
size="xl"
onClick={() => {
router.refresh();
}}
/>
</div>
<ul className="grid grid-cols-3 mt-8 px-4 py-2 fixed bottom-0 z-[999] bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<li
className="flex flex-col items-center"
onClick={() => setMaskVisible({ visible: true, type: "weChat" })}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">查看微信</p>
</li>
<li
className="flex flex-col items-center"
onClick={() => setMaskVisible({ visible: true, type: "ironFan" })}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">已是铁粉</p>
{/* <p className="text-[#ffffff54] text-[10px]">0/299</p> */}
</li>
<li
className="flex flex-col items-center"
onClick={() => setCurrentKey("chaofen")}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
</div>
<p className="text-xs">尊贵超粉</p>
</li>
</ul>
<Mask visible={maskVisible.visible}>
<div>
<div
className="w-full h-screen flex justify-center items-center"
onClick={() => setMaskVisible({ visible: false, type: "" })}
></div>
<div className="w-full h-screen flex justify-center items-center absolute top-0 pointer-events-none">
<div className="relative w-[70vw] h-max flex flex-col justify-center items-center p-4 bg-[#261e30] rounded-2xl pointer-events-auto">
{maskVisible.type === "weChat" && <SeeWechat />}
{maskVisible.type === "ironFan" && (
<SeeTiefen
handleClick={() => {
setCurrentKey("ironFan");
setMaskVisible({ visible: false, type: "" });
}}
/>
)}
</div>
</div>
</div>
</Mask>
{/* <div className="flex justify-center items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div className="bg-primary px-10 py-1 text-base rounded-full">
<div
className="flex items-center py-2 text-base"
onClick={() => {
router.push("/pay");
}}
>
<Image
width={18}
height={18}
placeholder=""
className="mr-2"
src="/icons/money_pink.png"
/>
<span>39.9元立即加入</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4 ml-2"
/>
</div>
</div>
</div> */}
</div>
</div>
);
}
const SeeTiefen = ({ closeMask, handleClick }) => {
return (
<div className="relative w-[70vw] h-max flex flex-col justify-center items-center p-4 bg-[#261e30] rounded-2xl pointer-events-auto">
<p className="text-base text-left w-full">
当前铁粉解锁进度
<span className="text-2xl font-bold text-primary">50%</span>
</p>
<ProgressBar
percent={50}
style={{
"--fill-color": "#FF669E",
"--track-color": "#FF669E50",
}}
className="w-full mt-2 mb-4"
/>
<p className="text-left my-2 text-sm text-[#FFFFFF40]">
空间内累计消费达到1280即可成为
<span className="text-primary">铁粉</span>
可查看所有铁粉专享内容哟快来成为我的铁粉吧~
</p>
<div
className="px-8 py-2 rounded-full flex items-center mt-2 bg-gradient-to-r from-[#FF668B] to-[#FF66F0]"
onClick={handleClick}
>
<span className="text-base">查看铁粉专享内容</span>
</div>
</div>
);
};
const SeeWechat = ({ closeMask }) => {
return (
<div className="relative w-[70vw] h-max flex flex-col justify-center items-center p-4 pt-10 bg-[#261e30] rounded-2xl pointer-events-auto">
<Image
src="https://picsum.photos/seed/picsum/200/300"
width={64}
height={64}
className="rounded-full absolute -top-8"
/>
<p className="text-2xl font-bold">草莓不可爱</p>
<div className="my-2 bg-[#FFFFFF1A] px-4 py-2 rounded-lg text-base">
解锁后展示
</div>
<p className="text-[red] text-center mb-2">
添加时请备注自己铁粉空间昵称
<br />
若解锁后72小时为通过好友请联系客服
</p>
<div className="bg-primary px-4 py-2 rounded-full flex items-center">
<span className="text-[16px]">解锁微信990金币</span>
</div>
</div>
);
};

View File

@ -0,0 +1,280 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { Image, ImageViewer,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";
import { getStreamerInfo } from "@/api/space";
export default function PersonSpaceIntroduce() {
const router = useRouter();
const contentBox = useRef();
// 获取屏幕高度
// const scrollHeight = 600;
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [isLoading, setIsloading] = useState(true);
const { mid } = useParams();
useEffect(() => {
if (contentBox.current) {
contentBox.current.style.transform = "translateY(-12px)";
// debugger
}
getStreamerInfo(Number(mid)).then((res) => {
setData(res);
console.log("mid", mid);
});
}, []);
const showPhotos = (photos, index) => {
ImageViewer.Multi.show({
images: photos.map((item) => item.urls[0]),
defaultIndex: index,
});
};
const handleShowVideo = (video) => {
Dialog.className = "videoMask";
Dialog.show({
title: "",
content: (
<div className="flex flex-col gap-2">
<div className="flex w-full justify-end">
<div
className="flex w-12 h-12 justify-center items-center bg-[#33333348] rounded-full"
key="closeBtn"
onClick={() => Dialog.clear()}
>
<FontAwesomeIcon icon={faClose} size="2xl" />
</div>
</div>
<div className="my-4">
<video
width="100%"
height="100%"
controls
className="w-screen h-[70vh] rounded-lg object-contain"
poster={video.url}
>
<source src={video.mp4} type="video/mp4" />
您的浏览器不支持 Video 标签
</video>
</div>
<div
className="flex w-12 h-12 justify-center items-center bg-[#33333348] rounded-full"
key="closeBtn"
// onClick={handleSeeAllPhotos}
>
<FontAwesomeIcon icon={faSave} size="2xl" />
</div>
</div>
),
bodyStyle: {
background: "none",
maxHeight: "none",
height: "100%",
},
});
};
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
</div>
{/* 内容 */}
<div>
<div
className="bg-no-repeat bg-cover bg-fixed "
style={{
backgroundImage: "url('https://picsum.photos/seed/picsum/200/300')",
}}
>
<div className="px-4 pt-24 pb-8 bg-[#181818a9]">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Image
width="64px"
height="64px"
className="rounded-full mr-2 border-2 border-white"
fit="cover"
src={data?.streamer_ext?.avatar?.images[0]?.urls[0]}
/>
<div>
<p className="text-2xl mb-1 font-bold">
{data?.streamer_ext?.name}
</p>
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mb-1 w-max">
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
/>
<span>{data?.streamer_ext?.user_id}</span>
</div>
</div>
</div>
<div
className="flex flex-col items-center"
onClick={() => setVisible(true)}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
/>
</div>
<p className="text-xs">查看微信</p>
</div>
</div>
<ul className="flex justify-around items-center mt-8">
<li className="text-center">
<p className="font-bold text-2xl">{data?.zone_moment_count}</p>
<p>动态</p>
</li>
<li className="text-center">
<p className="font-bold text-2xl">{data?.image_count}</p>
<p>照片</p>
</li>
<li className="text-center">
<p className="font-bold text-2xl">{data?.video_count}</p>
<p>视频</p>
</li>
</ul>
</div>
</div>
<div className="rounded-2xl bg-deepBg p-4 pb-24" ref={contentBox}>
<p className="text-lg font-bold tabPinkLine relative inline-block">
空间介绍
</p>
<p className="my-2 text-base">{data?.profile}</p>
<ul className="grid grid-cols-3 gap-1.5">
<li
className="relative"
onClick={() =>
handleShowVideo({
url: data?.streamer_ext?.shorts?.videos[0]?.cover_urls[0],
mp4: data?.streamer_ext?.shorts?.videos[0]?.urls[0],
})
}
>
<div className="absolute top-0 w-full h-full rounded flex justify-center items-center bg-[#33333348]">
<Image
className=""
width={98}
height={98}
src="/icons/play.png"
placeholder=""
/>
</div>
<Image
height="28vw"
className="rounded"
fit="cover"
src={data?.streamer_ext?.shorts?.videos[0]?.cover_urls[0]}
/>
</li>
{data?.streamer_ext?.album?.images.map((item, index) => (
<li
key={item.id}
onClick={() =>
showPhotos(data?.streamer_ext?.album?.images, index)
}
>
<Image
height="28vw"
className="rounded"
fit="cover"
src={item.urls[0]}
/>
</li>
))}
</ul>
{data?.admission_price !== 0 && (
<div className="mt-6">
<p className="text-lg font-bold mb-2 titlePinkLine relative inline-block">
付费须知
</p>
<div>
<p className="text-white text-base mb-1">
1加入后您可以查看空间内相关内容
</p>
<p className="text-white text-base mb-1">
2本空间由空间主人自行创建加入空间前请确认相关风险本平台不提供相关保证请避免上当受骗
</p>
<p className="text-white text-base mb-1">
3虚拟商品一经售出不予退款请确认阅读上述条款并无异议后进行购买
</p>
<p className="text-white text-base mb-1">
4本平台不提供违法及色情内容如您发现空间内存在以上内容请联系人工客服举报处理
</p>
</div>
</div>
)}
</div>
<AddWeChat
visible={visible}
closeMask={() => setVisible(false)}
name={data?.streamer_ext?.name}
price={data?.streamer_ext?.wechat_coin_price}
streamerMid={data?.streamer_ext?.mid}
avatar={data?.streamer_ext?.avatar?.images[0]?.urls[0]}
/>
{/* <Mask visible={visible} >
<div className="w-full h-screen flex justify-center items-center" onClick={() => setVisible(false)}>
<div className="relative w-[70vw] h-max flex flex-col justify-center items-center p-4 pt-10 bg-[#261e30] rounded-2xl">
<Image src="https://picsum.photos/seed/picsum/200/300" width={64} height={64} className="rounded-full absolute -top-8"/>
<p className="text-2xl font-bold">草莓不可爱</p>
<div className="my-2 bg-[#FFFFFF1A] px-4 py-2 rounded-lg text-base">
解锁后展示
</div>
<p className="text-[red] text-center mb-2">
添加时请备注自己铁粉空间昵称
<br />
若解锁后72小时为通过好友请联系客服
</p>
<div className="bg-primary px-4 py-2 rounded-full flex items-center">
<span className="text-[16px]">解锁微信990金币</span>
</div>
</div>
</div>
</Mask> */}
<div className="flex justify-center items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div className="bg-primary px-10 py-1 text-base rounded-full">
{/* <Image/> */}
<div
className="flex items-center py-2 text-base"
onClick={() => {
router.push("/pay");
}}
>
<Image
width={18}
height={18}
placeholder=""
className="mr-2"
src="/icons/money_pink.png"
/>
<span>39.9元立即加入</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4 ml-2"
/>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,200 +0,0 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { Image, Mask } from "antd-mobile";
import { useRouter } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
export default function PersonSpaceIntroduce() {
const router = useRouter();
const contentBox = useRef();
// 获取屏幕高度
// const scrollHeight = 600;
const photos = [
"https://picsum.photos/seed/picsum/200/300",
"https://picsum.photos/seed/picsum/200/300",
];
const [visible, setVisible] = useState(false);
useEffect(() => {
if (contentBox.current) {
contentBox.current.style.transform = "translateY(-12px)";
// debugger
}
}, []);
return (
<div className="">
<div className="flex justify-between items-center p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
</div>
{/* 内容 */}
<div>
<div
className="bg-no-repeat bg-cover bg-fixed "
style={{
backgroundImage: "url('https://picsum.photos/seed/picsum/200/300')",
}}
>
<div className="px-4 pt-24 pb-8 bg-[#181818a9]">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Image
width="64px"
height="64px"
className="rounded-full mr-2 border-2 border-white"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
<div>
<p className="text-2xl mb-1 font-bold">草莓不可爱</p>
<div className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mb-1 w-max">
<Image
src="/icons/info/ID.png"
width={14}
className="w-4 h-full mr-1"
/>
<span>213422</span>
</div>
</div>
</div>
<div
className="flex flex-col items-center"
onClick={() => setVisible(true)}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
src="/images/wechat.png"
width={22}
className="w-4 h-full"
/>
</div>
<p className="text-xs">查看微信</p>
</div>
</div>
<ul className="flex justify-around items-center mt-8">
<li className="text-center">
<p className="font-bold text-2xl">38</p>
<p>动态</p>
</li>
<li className="text-center">
<p className="font-bold text-2xl">38</p>
<p>照片</p>
</li>
<li className="text-center">
<p className="font-bold text-2xl">38</p>
<p>视频</p>
</li>
</ul>
</div>
</div>
<div className="rounded-2xl bg-deepBg p-4 pb-24" ref={contentBox}>
<p className="text-lg font-bold tabPinkLine relative inline-block">空间介绍</p>
<p className="my-2 text-base">
草莓秘密基地一次进入永久权限都是你想看到的哟
</p>
<ul className="grid grid-cols-3 gap-1.5">
<li>
<Image
height="28vw"
className="rounded"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</li>
<li>
<Image
height="28vw"
className="rounded"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</li>
<li>
<Image
height="28vw"
className="rounded"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</li>
<li>
<Image
height="28vw"
className="rounded"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</li>
<li>
<Image
height="28vw"
className="rounded"
fit="cover"
src="https://picsum.photos/seed/picsum/200/300"
/>
</li>
</ul>
<div className="mt-6">
<p className="text-lg font-bold mb-2 titlePinkLine relative inline-block">付费须知</p>
<div>
<p className="text-white text-base mb-1">
1加入后您可以查看空间内相关内容
</p>
<p className="text-white text-base mb-1">
2本空间由空间主人自行创建加入空间前请确认相关风险本平台不提供相关保证请避免上当受骗
</p>
<p className="text-white text-base mb-1">
3虚拟商品一经售出不予退款请确认阅读上述条款并无异议后进行购买
</p>
<p className="text-white text-base mb-1">
4本平台不提供违法及色情内容如您发现空间内存在以上内容请联系人工客服举报处理
</p>
</div>
</div>
</div>
<Mask visible={visible} >
<div className="w-full h-screen flex justify-center items-center" onClick={() => setVisible(false)}>
<div className="relative w-[70vw] h-max flex flex-col justify-center items-center p-4 pt-10 bg-[#261e30] rounded-2xl">
<Image src="https://picsum.photos/seed/picsum/200/300" width={64} height={64} className="rounded-full absolute -top-8"/>
<p className="text-2xl font-bold">草莓不可爱</p>
<div className="my-2 bg-[#FFFFFF1A] px-4 py-2 rounded-lg text-base">
解锁后展示
</div>
<p className="text-[red] text-center mb-2">
添加时请备注自己铁粉空间昵称
<br />
若解锁后72小时为通过好友请联系客服
</p>
<div className="bg-primary px-4 py-2 rounded-full flex items-center">
<span className="text-[16px]">解锁微信990金币</span>
</div>
</div>
</div>
</Mask>
<div className="flex justify-center items-center px-4 py-4 fixed bottom-0 bg-deepBg w-full border-t-2 border-[#FFFFFF1A]">
<div className="bg-primary px-10 py-1 text-base rounded-full">
{/* <Image/> */}
<div className="flex items-center py-2 text-base" onClick={() => {
router.push("/pay");
}}>
<Image width={18} height={18} placeholder="" className="mr-2" src="/icons/money_pink.png"/>
<span>39.9元立即加入</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="h-4 ml-2"
/>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,117 @@
"use client";
import React, { useState } from "react";
import { Image, Mask, Toast } from "antd-mobile";
import require from "@/utils/require";
import { get } from "@/utils/storeInfo";
import { useRouter } from "next/navigation";
export default function AddWeChat({
visible,
closeMask,
name,
price,
streamerMid,
avatar
}) {
const [isMoneyEnough, setIsMoneyEnough] = useState(true);
const router = useRouter();
//点击解锁微信按钮
const unlockWechat = async () => {
//余额不够就显示余额不足前往充值,够就直接购买
//先支付,支付成功后添加解锁关系,再展示解锁界面
//支付金币解锁微信
const account = get("account");
if (account) {
try {
const userResponse = await require("POST", "/api/account/list_by_mid", {
body: {
mid: account.mid,
},
});
if (userResponse.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
if (userResponse?.data.account?.gold_num >= price) {
console.log("余额足够");
const data = await require("POST", "/api/vas/one_step_unlock", {
body: {
contact_product_id: "contact_wechat",
uid: streamerMid,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
//展示解锁界面
// setIsWechatUnlocked(true);
} else {
setIsMoneyEnough(false);
}
} catch (error) {
console.error(error);
}
}
};
return (
<Mask visible={visible}>
<div className="relative w-screen h-screen">
<div className="w-full h-full" onClick={() => closeMask(false)}></div>
<div className="absolute top-1/2 left-1/2 -ml-[35vw] -mt-[35vw] w-[70vw] h-max flex flex-col justify-center items-center p-4 pt-10 bg-[#261e30] rounded-2xl">
{isMoneyEnough ? (
<>
<Image
src={avatar}
width={64}
height={64}
className="rounded-full absolute -top-8"
/>
<p className="text-2xl font-bold">{name}</p>
<div className="my-2 bg-[#FFFFFF1A] px-4 py-2 rounded-lg text-base">
解锁后展示
</div>
<p className="text-[red] text-center mb-2">
添加时请备注自己铁粉空间昵称
<br />
若解锁后72小时为通过好友请联系客服
</p>
<div
className="bg-primary px-4 py-2 rounded-full flex items-center"
onClick={unlockWechat}
>
<span className="text-[16px]">解锁微信{price}金币</span>
</div>
</>
) : (
<>
<p className="text-2xl text-white font-medium mb-4">
余额不足
</p>
<div
onClick={() => {
router.push("/my/wallet");
closeMask(false);
setIsMoneyEnough(true);
}}
className="px-4 py-2 bg-[#FF669E] rounded-full items-center justify-center"
>
<span className="text-white text-base">前往充值</span>
</div>
</>
)}
</div>
</div>
</Mask>
);
}

View File

@ -0,0 +1,31 @@
"use client";
import React from "react";
import { Mask } from "antd-mobile";
export default function DefaultMask({
visible,
closeMask,
handleClick,
title,
content
}) {
return (
<Mask visible={visible}>
<div className="relative w-screen h-screen">
<div className="w-full h-full" onClick={() => closeMask(false)}></div>
<div className="absolute top-1/2 left-1/2 -ml-[35vw] -mt-[35vw] w-[70vw] h-max flex flex-col justify-center items-center p-4 bg-[#261e30] rounded-2xl pointer-events-auto">
<p>{title}</p>
<p>{content}</p>
<div
className="px-8 py-2 rounded-full flex items-center mt-2 bg-gradient-to-r from-[#FF668B] to-[#FF66F0]"
onClick={handleClick}
>
<span className="text-base">确定</span>
</div>
</div>
</div>
</Mask>
);
}

View File

@ -3,14 +3,18 @@
import React, { useRef, useState } from "react";
import { Image } from "antd-mobile";
export default function PaySpacePost({
type = "ironFan",
price = "0.00",
status = 0,
}) {
export default function PaySpacePost({ type = "ironFan", status = 0,data={} }) {
return (
<div className={`rounded-md ${type === "ironFan" ? "bg-primary-500" : "bg-super-500"} px-2 py-2 my-2 text-sm`}>
<div className={`flex justify-between items-center text-sm text-super mb-2 text-${type === "ironFan" ? "primary" : "super"}`}>
<div
className={`rounded-md ${
type === "ironFan" ? "bg-primary-500" : "bg-super-500"
} px-2 py-2 my-2 text-sm`}
>
<div
className={`flex justify-between items-center text-sm ${
type === "ironFan" ? "text-primary" : "text-super"
}`}
>
<div className="flex items-center">
<Image
width={18}
@ -23,21 +27,40 @@ export default function PaySpacePost({
: "/icons/money_gold.png"
}
/>
<span className="font-bold">{price}</span>
<span className="font-bold">{data.price / 100}</span>
</div>
<div className="flex items-center">
<span>{status === 1 ? "已付费解锁" : `${type === "ironFan"?'铁粉':'超粉'}免费查看`}</span>
{!data.is_zone_moment_unlocked ? (
<span>
{status === 1
? "已付费解锁"
: `${type === "ironFan" ? "铁粉" : "超粉"}免费查看`}
</span>
) : (
<span>
{data.is_ironfan_visible === 1
? "铁粉免费查看"
: data.is_superfanship_enabled === 1
? "超粉免费查看"
: "付费解锁"}
</span>
)}
<Image
height={14}
placeholder=""
className="ml-2"
src={type === "ironFan"?"/icons/pinklink.png":"/icons/goldlink.png"}
src={
type === "ironFan" ? "/icons/pinklink.png" : "/icons/goldlink.png"
}
/>
</div>
</div>
{status === 0 && (
<p className="text-xs" style={{color:type === "ironFan" ? "#ff669e54" : "#FFD68554"}}>
空间内任何消费满399元即可成为铁粉
<p
className="text-xs mb-2"
style={{ color: type === "ironFan" ? "#ff669e54" : "#FFD68554" }}
>
空间内任何消费满{data.ironfanship_price}元即可成为铁粉
</p>
)}
</div>

View File

@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
import { Image, ImageViewer, Dialog } from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleUp, faClose, faSave } from "@fortawesome/free-solid-svg-icons";
const tabItems = [
{ key: "commend", title: "推荐" },
{ key: "follow", title: "关注" },
@ -13,15 +14,10 @@ export default function Photos({ media }) {
const [seeAllPhotos, setSeeAllPhotos] = useState(false);
const [currentPhotos, setCurrentPhotos] = useState([]);
const [photos, setPhotos] = useState([]);
const [currentVideos, setCurrentVideos] = useState([]);
const [videoVisible, setVideoVisible] = useState({
video: "",
visible: false,
});
useEffect(() => {
if (media) {
let imgArr = media.images.map(item=>({type:"img",url:item.cover_urls[0]}))
let videoArr = media.videos.map(item=>({type:"video",url:item.cover_urls[0],mp4:item.urls[0]}))
let imgArr = media.images.map(item=>({type:"img",url:item.urls?.[0]}))
let videoArr = media.videos.map(item=>({type:"video",url:item.cover_urls?.[0],mp4:item.urls?.[0]}))
let arr=[...imgArr,...videoArr]
let newPhotos = [...arr];
setPhotos(arr);
@ -50,7 +46,7 @@ export default function Photos({ media }) {
key="closeBtn"
onClick={() => Dialog.clear()}
>
<FontAwesomeIcon icon={faClose} size="2xl" className="h-12" />
<FontAwesomeIcon icon={faClose} size="2xl" />
</div>
</div>
<div className="my-4">
@ -58,7 +54,7 @@ export default function Photos({ media }) {
width="100%"
height="100%"
controls
className="w-screen h-[70vh] rounded-lg object-cover"
className="w-screen h-[70vh] rounded-lg object-contain"
poster={video.url}
>
<source
@ -73,7 +69,7 @@ export default function Photos({ media }) {
key="closeBtn"
onClick={handleSeeAllPhotos}
>
<FontAwesomeIcon icon={faSave} size="xl" className="h-12" />
<FontAwesomeIcon icon={faSave} size="2xl"/>
</div>
</div>
),
@ -120,18 +116,18 @@ export default function Photos({ media }) {
placeholder={
<div className="w-full h-full bg-[#1d1d1d] rounded"></div>
}
width={currentPhotos.length > 1 ? "100%" : 150}
height={currentPhotos.length > 1 ? "24vw" : 200}
className="rounded"
width={currentPhotos.length > 1 ? "26vw" : "100%"}
height={currentPhotos.length > 1 ? "26vw" : "100%"}
className={`rounded max-w-[80vw] max-h-[80vw] `}
fit="cover"
src={item.url}
/>
{item.type == "video" && (
<div className="absolute top-0 w-full h-full flex justify-center items-center bg-[#33333348]">
<div className="absolute top-0 w-full h-full rounded flex justify-center items-center bg-[#33333348]">
<Image
className=""
width={64}
height={64}
width={98}
height={98}
src="/icons/play.png"
placeholder=""
/>

View File

@ -7,6 +7,8 @@ import PaySpacePost from "../PaySpacePost";
import { Image } from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleRight } from "@fortawesome/free-solid-svg-icons";
import { handleFollow, thumbsUp } from "@/api/public";
import {get} from "@/utils/storeInfo"
export default function PostItem({
type,
follow,
@ -15,9 +17,15 @@ export default function PostItem({
}) {
const router = useRouter();
const [isOpenText, setIsOpenText] = useState(false);
const [isFollow, setIsFollow] = useState(data.is_followed);
const [isThumbsUp, setIsThumbsUp] = useState(data.is_thumbed_up);
//判断是否是发帖人
const [isCreator, setIsCreator] = useState(false);
useEffect(() => {
const account = get("account");
if (account.mid === data.mid) setIsCreator(true);
return () => {
router.prefetch("/profile");
router.prefetch("/profile/"+data.mid);
};
}, []);
const getDays = useMemo(() => {
@ -35,15 +43,18 @@ export default function PostItem({
className="flex-none w-8 h-8 rounded-full mr-2"
src={data.streamer_ext?.avatar.images[0].urls[0]}
alt=""
onClick={() => router.push("/profile")}
onClick={() => router.push("/profile/"+data.mid)}
/>
<div className="flex-1">
<div className="flex justify-between items-center">
<div
className="flex justify-between items-center"
onClick={() => handleFollow(isFollow, data?.mid, setIsFollow)}
>
<span className="font-bold text-md">{data.streamer_ext?.name}</span>
{type == "post" && (
<span className="rounded-full bg-[#FFFFFF1A] px-2 py-1 text-xs text-white font-medium">
{data.is_followed ? "已关注" : "关注"}
{isFollow ? "已关注" : "关注"}
</span>
)}
</div>
@ -51,50 +62,67 @@ export default function PostItem({
<p className={`mb-2 mt-2 ${!isOpenText ? "text-ellipsis-3" : ""}`}>
{data.text}
</p>
{
data.text?.length>50 &&
{data.text?.length > 50 && (
<div
className="font-bold text-btn my-4 text-base"
onClick={() => setIsOpenText(!isOpenText)}
>
{isOpenText ? "收起" : "全文"}
</div>
}
className="font-bold text-btn my-4 text-base"
onClick={() => setIsOpenText(!isOpenText)}
>
{isOpenText ? "收起" : "全文"}
</div>
)}
</div>
{data.media_component && <Photos media={data.media_component} />}
{type == "space" && (
<PaySpacePost type="superFan" price="19.89" status={0} />
{type == "space" && !isCreator && !!data.c_type && (
<PaySpacePost
type={data.is_ironfan_visible ? "ironFan" : "superFan"}
price={data.price / 100}
status={data.is_ironfanship_unlocked}
ironfanship_price={data.ironfanship_price / 100}
is_zone_moment_unlocked={data.is_zone_moment_unlocked}
data={data}
/>
)}
<div className="flex justify-between items-center mt-2">
{type == "post" ? (
<div className="flex items-center">
{
data.is_active_within_a_week &&
<div
className="flex items-center"
onClick={() => router.push("/profile/"+data.mid)}
>
{data.is_active_within_a_week ? (
<>
<Image
src="/icons/space_new_post.png"
width={18}
className="w-4 h-full mr-1"
placeholder=""
/>
<span className="mr-1 text-primary text-xs">
{data.days_elapsed_since_the_last_zones_update < 7
? `空间${
data.days_elapsed_since_the_last_zones_update === 0
? "今日"
: "new" === 1
? "昨日"
: "new" === 2
? "前天"
: data.days_elapsed_since_the_last_zones_update + "天前"
}有更新`
: "1" === 2
? "空间今日有更新"
: ""}
</span>
<FontAwesomeIcon icon={faAngleRight} color="#FF669E" size="sm" />
<Image
src="/icons/space_new_post.png"
width={18}
className="w-4 h-full mr-1"
placeholder=""
/>
<span className="mr-1 text-primary text-xs">
{data.days_elapsed_since_the_last_zones_update < 7 &&
`空间${
data.days_elapsed_since_the_last_zones_update === 0
? "今日"
: "new" === 1
? "昨日"
: "new" === 2
? "前天"
: data.days_elapsed_since_the_last_zones_update +
"天前"
}有更新`}
</span>
<FontAwesomeIcon
icon={faAngleRight}
color="#FF669E"
size="sm"
/>
</>
}
) : (
data?.streamer_ext?.zones?.length !== 0 && (
<div className="text-[#FFFFFFB2] font-medium text-xs flex items-center">
<span className="mr-1">查看TA的空间</span>
<FontAwesomeIcon icon={faAngleRight} size="sm" />
</div>
)
)}
</div>
) : (
<div className="text-[#ffffff88] text-xs">
@ -109,9 +137,16 @@ export default function PostItem({
</div>
)}
<div className="flex items-center">
<div className="flex items-center mr-4">
<div
className="flex items-center mr-4 h-[32px]"
onClick={() => thumbsUp(data.id, isThumbsUp, setIsThumbsUp)}
>
<Image
src={data.is_thumbed_up ? "/icons/thumbup.png":"/icons/notthumbup.png"}
src={
isThumbsUp == 1
? "/icons/thumbup.png"
: "/icons/notthumbup.png"
}
width={32}
className="w-4 h-full"
placeholder=""

View File

@ -0,0 +1,52 @@
"use client";
import React, { useState } from "react";
import { Mask, ProgressBar } from "antd-mobile";
import require from "@/utils/require";
import { get } from "@/utils/storeInfo";
import { useRouter } from "next/navigation";
export default function SeeTiefen({
visible,
closeMask,
handleClick,
ironFanProgress,
expenditure,
ironfanship_price
}) {
return (
<Mask visible={visible}>
<div className="relative w-screen h-screen">
<div className="w-full h-full" onClick={() => closeMask(false)}></div>
<div className="absolute top-1/2 left-1/2 -ml-[35vw] -mt-[35vw] w-[70vw] h-max flex flex-col justify-center items-center p-4 bg-[#261e30] rounded-2xl pointer-events-auto">
<p className="text-base text-left w-full">
当前铁粉解锁进度
<span className="text-2xl font-bold text-primary">{ironFanProgress}%</span>
</p>
<ProgressBar
percent={ironFanProgress}
style={{
"--fill-color": "#FF669E",
"--track-color": "#FF669E50",
}}
className="w-full mt-2 mb-4"
/>
<p className="text-sm text-[#FF669E] font-medium">{`${
expenditure / 100
} / ${ironfanship_price / 100}`}</p>
<p className="text-left my-2 text-sm text-[#FFFFFF40]">
空间内累计消费达到1280即可成为
<span className="text-primary">铁粉</span>
可查看所有铁粉专享内容哟快来成为我的铁粉吧~
</p>
<div
className="px-8 py-2 rounded-full flex items-center mt-2 bg-gradient-to-r from-[#FF668B] to-[#FF66F0]"
onClick={handleClick}
>
<span className="text-base">查看铁粉专享内容</span>
</div>
</div>
</div>
</Mask>
);
}

137
package-lock.json generated
View File

@ -11,12 +11,16 @@
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@reduxjs/toolkit": "^2.2.6",
"antd-mobile": "^5.36.1",
"cookies-next": "^4.0.0",
"crypto-js": "^4.2.0",
"jsencrypt": "^3.3.2",
"next": "14.0.2",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"sass": "^1.77.6"
},
"devDependencies": {
@ -445,6 +449,29 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.2.6",
"resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.2.6.tgz",
"integrity": "sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA==",
"dependencies": {
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@swc/helpers": {
"version": "0.5.2",
"resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.2.tgz",
@ -463,6 +490,11 @@
"resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.101.tgz",
"integrity": "sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA=="
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@use-gesture/core": {
"version": "10.3.0",
"resolved": "https://registry.npmmirror.com/@use-gesture/core/-/core-10.3.0.tgz",
@ -1180,6 +1212,15 @@
"node": ">=0.10.0"
}
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": {
"version": "4.3.6",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz",
@ -1323,6 +1364,11 @@
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/jsencrypt": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.3.2.tgz",
"integrity": "sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A=="
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz",
@ -2025,6 +2071,28 @@
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"node_modules/react-redux": {
"version": "9.1.2",
"resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.1.2.tgz",
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
"dependencies": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^18.2.25",
"react": "^18.0",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
@ -2045,11 +2113,29 @@
"node": ">=8.10.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@ -2928,6 +3014,17 @@
"@react-spring/types": "~9.6.1"
}
},
"@reduxjs/toolkit": {
"version": "2.2.6",
"resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.2.6.tgz",
"integrity": "sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA==",
"requires": {
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
}
},
"@swc/helpers": {
"version": "0.5.2",
"resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.2.tgz",
@ -2946,6 +3043,11 @@
"resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.101.tgz",
"integrity": "sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA=="
},
"@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"@use-gesture/core": {
"version": "10.3.0",
"resolved": "https://registry.npmmirror.com/@use-gesture/core/-/core-10.3.0.tgz",
@ -3457,6 +3559,11 @@
"optional": true,
"peer": true
},
"immer": {
"version": "10.1.1",
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw=="
},
"immutable": {
"version": "4.3.6",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz",
@ -3562,6 +3669,11 @@
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"jsencrypt": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.3.2.tgz",
"integrity": "sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A=="
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz",
@ -4000,6 +4112,15 @@
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"react-redux": {
"version": "9.1.2",
"resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.1.2.tgz",
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
"requires": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
}
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
@ -4017,11 +4138,27 @@
"picomatch": "^2.2.1"
}
},
"redux": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"requires": {}
},
"regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",

View File

@ -12,12 +12,16 @@
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@reduxjs/toolkit": "^2.2.6",
"antd-mobile": "^5.36.1",
"cookies-next": "^4.0.0",
"crypto-js": "^4.2.0",
"jsencrypt": "^3.3.2",
"next": "14.0.2",
"react": "^18",
"react-dom": "^18",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"sass": "^1.77.6"
},
"devDependencies": {

BIN
public/icons/edit.png Normal file

Binary file not shown.

After

(image error) Size: 258 B

7
store/actions.js Normal file
View File

@ -0,0 +1,7 @@
"use client";
export const handleLogin = (data) => ({
type: 'HANDLOGIN',
data
});

11
store/index.js Normal file
View File

@ -0,0 +1,11 @@
"use client";
import { configureStore } from '@reduxjs/toolkit'; // 请确保已安装 "@reduxjs/toolkit"
import reducers from './reducers';
const store = configureStore({
reducer: reducers,
});
// export default rootReducer;
export default store;

26
store/reducers.js Normal file
View File

@ -0,0 +1,26 @@
"use client";
import { combineReducers } from "redux";
const initialState = {
authInfo: {
isSignin: false,
userToken: null,
},
};
let text = (data) => {
return data ? data.token : undefined;
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "HANDLOGIN":
return { ...state, authInfo: action.data };
default:
return state;
}
};
const reducers = combineReducers({
reducer,
});
export default reducers;

View File

@ -1,47 +1,23 @@
import { setCookie, deleteCookie, getCookie } from "cookies-next";
import baseRequest from "./baseRequest";
import { generateSignature } from "@/utils/crypto";
import require from "./require";
export async function checkAuth() {
const token = getCookie("token");
const account = getCookie("account");
if (token && account) {
//验证是否过期
try {
const base = baseRequest();
const signature = generateSignature({
...base,
});
const response = await fetch(
`/api/login/validate?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...base,
}),
}
);
const data = await response.json();
if (data.ret === -1) {
return false;
}
return true;
} catch (e) {
console.warn(e);
try {
const data = await require("POST", `/api/login/validate`);
if (data.ret === -1) {
return false;
}
return true;
} catch (e) {
console.warn(e);
}
return false;
}
export function signIn(data) {
setCookie("token", data.data.token);
setCookie("account", data.data.account);
setCookie("mid", data.data.account.mid);
}
export function signOut() {
deleteCookie("token");
deleteCookie("account");
deleteCookie("mid");
}

View File

@ -1,9 +1,9 @@
import { getCookie } from "cookies-next";
import { get } from "./storeInfo";
export default function baseRequest() {
const token = getCookie("token");
const accountCookie = getCookie("account");
const account = accountCookie === undefined ? {} : JSON.parse(accountCookie);
const accountCookie = get("account");
const account = accountCookie === undefined ? {} : accountCookie;
const mid = getCookie("mid");
const b_ts = new Date().getTime();
const baseRequest = {

View File

@ -1,8 +1,9 @@
import baseRequest from "./baseRequest";
import {get} from "./storeInfo";
// import webviewBaseRequest from "@/utils/webviewBaseRequest";
const base = baseRequest();
// 创建一个封装 fetch 的函数
export default function customFetch(method, url, options = {}) {
export default function customFetch(method, url, options = {},mid) {
// 默认选项
const defaultOptions = {
method: method,
@ -14,10 +15,14 @@ export default function customFetch(method, url, options = {}) {
}
// 可以添加其他默认选项
};
const body=JSON.stringify({...base,...options.body})
let newBody = {...options.body}
if(mid){
newBody.mid=get("account").mid
}
const body=JSON.stringify({...base,...newBody})
// 合并选项
const mergedOptions = { ...defaultOptions, body};
console.log("mergedOptions",mergedOptions)
// console.log("mergedOptions",mergedOptions)
// 返回 Promise 对象
return new Promise((resolve, reject) => {
fetch(url, mergedOptions)

View File

@ -2,11 +2,26 @@ export function save(key,value){
localStorage.setItem(key,value)
}
export function get(key){
localStorage.getItem(key)
let data = localStorage.getItem(key);
return data ? JSON.parse(data) : {};
}
export function remove(key){
localStorage.removeItem(key)
}
export function clear(){
localStorage.clear()
}
}
export function saveUserInfo (data, mobilePhone, regionCode){
save("token", data.data.token);
save("account", JSON.stringify(data.data.account));
save("mobile_phone", mobilePhone);
save("region_code", regionCode);
}
export function removeUserInfo (){
remove("token");
remove("account");
remove("mobile_phone");
remove("region_code");
}

View File

@ -1,12 +1,31 @@
//格式化时间戳
export function formatDeadline(timestamp) {
const date = new Date(timestamp * 1000); // 时间戳以秒为单位需要乘以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);
const date = new Date(timestamp * 1000); // 时间戳以秒为单位需要乘以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}`;
}
return `${year}${month}${day}${hours}:${minutes}:${seconds}`;
}
// 防抖函数
export function debounce(fn, delay) {
let timer = null;
return (fnn) => {
//清除上一次的延时器
if (timer) {
clearTimeout(timer);
// return;
console.log(timer);
}
//重新设置新的延时器
timer = setTimeout(() => {
//修改this指向问题
// fn.apply(this,value)
fn(fnn);
}, delay);
};
}