请求接口

This commit is contained in:
al 2024-07-03 19:59:39 +08:00
parent c36206fe02
commit 2a81f3fe93
21 changed files with 680 additions and 129 deletions

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,10 +1,12 @@
import { Inter } from "next/font/google";
import React, { useEffect } from 'react';
import "./globals.css";
import BottomNav from "../components/BottomNav";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "铁粉空间",
description: "与Ta永不失联",
keywords: [
"铁粉空间",
@ -26,12 +28,12 @@ export const viewport = {
userScalable: 0,
};
export default function RootLayout({ children }) {
return (
<html
lang="zh-CN"
className="bg-deepBg"
data-prefers-color-scheme="dark"
>
<body className={inter.className}>
{children}

View File

@ -0,0 +1,47 @@
.customTabs>div>div:last-child>div:first-child {
z-index: 1;
background: none;
&::after{
content: '';
background-image: url(http://localhost:3000/icons/tabindicator.png);
height: 44px;
background-size: cover;
background-repeat: no-repeat;
width: 50px !important;
position: absolute;
top: -48px;
left: calc(50% - 25px);
}
}
.customTabs {
border: none;
--active-line-color: #ff8383;
--active-title-color: #fff;
--title-color: #a0a0a0;
--inactive-title-color: #5c5c5c;
--tab-border-color: #ff8383;
}
/* .loginBox .adm-tabs .adm-tabs-tab {
color: #a0a0a0;
} */
.customTabs>div>div:last-child>div>div {
font-weight: bold;
font-size: 18px;
}
.customTabs>div {
border-bottom: none!important;
}
.adm-list-body-inner {
margin-top: 8px;
}
.adm-image-viewer-indicator{
color: #fff;
}
.adm-list-item-content{
padding-right: 0 !important;
}

197
app/login/page.js Normal file
View File

@ -0,0 +1,197 @@
"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 { useRouter } from "next/navigation";
import styles from "./index.module.css";
/*
params格式
{
mid: item.mid,
}
*/
const tabItems = [
{ key: "code", title: "验证码登录" },
{ key: "password", title: "帐号密码登录" },
];
export default function Login({}) {
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 swiperRef = useRef(null);
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">
<Tabs
activeKey={tabItems[activeIndex].key}
onChange={(key) => {
const index = tabItems.findIndex((item) => item.key === key);
setActiveIndex(index);
swiperRef.current?.swipeTo(index);
}}
className={`w-full ${styles.customTabs}`}
>
{tabItems.map((item) => (
<Tabs.Tab
forceRender={false}
title={item.title}
key={item.key}
className="text-left"
/>
))}
</Tabs>
</div>
<Swiper
className="overflow-visible mt-6 "
direction="horizontal"
loop
indicator={() => null}
ref={swiperRef}
defaultIndex={activeIndex}
onIndexChange={(index) => {
setActiveIndex(index);
}}
>
<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>
<Input
placeholder="请输入手机号"
// disabled={true}
type="number"
maxLength={11}
onChangeText={(value) => setMobilePhone(value)}
value={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
placeholder="请输入验证码"
onChangeText={(value) => setVeriCode(value)}
value={veriCode}
type="number"
style={{
"--placeholder-color": "#FFFFFF80",
"--font-size": "16px",
}}
/>
<Button
shape="rounded"
size="mini"
disabled={isCounting}
// onClick={handleSubmit}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
className="whitespace-nowrap"
>
{isCounting ? `(${seconds})重新发送` : "获取验证码"}
</Button>
</div>
</div>
<LoginBtn />
</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>
<Input
placeholder="请输入手机号"
// disabled={true}
type="number"
maxLength={11}
onChangeText={(value) => setMobilePhone(value)}
value={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
placeholder="请输入密码"
onChangeText={(value) => setVeriCode(value)}
value={password}
type="number"
style={{
"--placeholder-color": "#FFFFFF80",
"--font-size": "16px",
}}
/>
</div>
</div>
<LoginBtn />
</Swiper.Item>
</Swiper>
</div>
);
}
const LoginBtn = () => {
const router = useRouter();
return (
<div className="mt-16">
<div className="flex items-center">
<Checkbox
style={{
"--icon-size": "14px",
"--font-size": "14px",
"--gap": "6px",
}}
></Checkbox>
<span className="text-[#FFFFFF80] font-medium text-xs ml-2">
我确认已满18周岁并同意
<span
onClick={() =>
router.push(
`${process.env.NEXT_PUBLIC_WEB_URL}/doc/useragreement`
)
}
className="text-[#FF669E] text-xs"
>
用户协议
</span>
<span
onClick={() =>
router.push(
`${process.env.NEXT_PUBLIC_WEB_URL}/doc/useragreement`
)
}
className="text-[#FF669E] text-xs"
>
隐私政策
</span>
</span>
</div>
<Button
shape="rounded"
size="middle"
block
// onClick={handleSubmit}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
className="mt-2"
>
登录
</Button>
</div>
);
};

124
app/messageDetail/page.js Normal file
View File

@ -0,0 +1,124 @@
"use client";
import React, { useState, useCallback, useEffect } 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,
PullToRefresh,
List,
InfiniteScroll,
} from "antd-mobile";
import { useRouter } from "next/navigation";
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
/*
params格式
{
mid: item.mid,
}
*/
export default function MessageDetail({}) {
const [hasMore, setHasMore] = useState(true);
const router = useRouter();
const getSession = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = baseRequest();
const account = await get("account");
const signature = generateSignature({
mid: account.mid,
...base,
});
const detailResponse = await fetch(
`${apiUrl}/api/contact_customer_service_session/list_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: account.mid,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
});
return;
}
if (detailData.data.session) {
setSessionId(detailData.data.session.id);
return;
}
} catch (error) {
console.error(error);
}
};
async function doRefresh() {
await sleep(1000);
Toast.show({
icon: "fail",
content: "刷新失败",
});
throw new Error("刷新失败");
}
async function loadMore() {
const append = await mockRequest();
setData((val) => [...val, ...append]);
setHasMore(append.length > 0);
}
return (
<div className="bg-[#13121F] h-screen">
<div className="p-4 fixed top-0 z-10 w-full bg-black">
<div className="flex items-center justify-center absolute">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center">在线服务</p>
</div>
<div>
<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>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>
</PullToRefresh>
</div>
<div className="w-full h-16 fixed bottom-0 grid grid-cols-[1fr_68px] items-center p-2 border-t-2 border-[#ffffff2a]">
<div className="rounded bg-[#222036] px-4 py-2 mr-2">
<Input placeholder="输入新消息" className="" />
</div>
<Button
size="middle"
block
// onClick={handleSubmit}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
>
发送
</Button>
</div>
</div>
</div>
);
}

View File

@ -5,8 +5,8 @@ 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";
export default function My() {
import withAuth from "@/components/WithAuth";
const My = () => {
const searchParams = useSearchParams();
const router = useRouter();
return (
@ -29,7 +29,7 @@ export default function My() {
/>
</div>
</div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center justify-between mb-4" onClick={()=>router.push("profile")}>
<div className="flex items-center">
<Avatar
rounded-full
@ -211,9 +211,6 @@ export default function My() {
icon={faAngleRight}
size="sm"
className="h-4 text-gray-300"
onClick={() => {
router.back();
}}
/>
</li>
<li className="flex justify-between items-center p-3 py-2">
@ -230,12 +227,9 @@ export default function My() {
icon={faAngleRight}
size="sm"
className="h-4 text-gray-300"
onClick={() => {
router.back();
}}
/>
</li>
{/* <li className="flex justify-between items-center p-3 py-2" onClick={()=>router.push("my/streamerVerification")}>
<li className="flex justify-between items-center p-3 py-2" onClick={()=>router.push("my/settleIn")}>
<div className="flex items-center">
<Image
className="mr-2"
@ -249,11 +243,8 @@ export default function My() {
icon={faAngleRight}
size="sm"
className="h-4 text-gray-300"
onClick={() => {
router.back();
}}
/>
</li> */}
</li>
<li className="flex justify-between items-center p-3 py-2">
<div className="flex items-center">
<Image
@ -268,9 +259,6 @@ export default function My() {
icon={faAngleRight}
size="sm"
className="h-4 text-gray-300"
onClick={() => {
router.back();
}}
/>
</li>
</ul>
@ -278,3 +266,6 @@ export default function My() {
</div>
);
}
export default withAuth(My)

View File

@ -25,8 +25,11 @@ export default function Setting() {
<div className="pt-16 ">
<List mode="card">
<List.Item arrow onClick={() => {
router.push("/login");
}}>退出账号</List.Item>
{/* <List.Item arrow onClick={() => {
router.push("/my/setting/switchAccount");
}}>切换账号</List.Item>
}}>切换账号</List.Item> */}
<List.Item arrow onClick={() => {
router.push("/my/setting/editPassword");
}}>修改密码</List.Item>

43
app/my/settleIn/page.js Normal file
View File

@ -0,0 +1,43 @@
"use client";
import React from "react";
import { Button } from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
} from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/navigation";
export default function SettleIn() {
const router = useRouter();
return (
<div>
<div className="p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full absolute">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center leading-9">申请入驻</p>
</div>
{/* 内容 */}
<div className="pt-16 p-4 flex flex-col justify-center items-center">
<div className="text-center">申请入驻需要在APP中进行请前往下载铁粉空间APP应用程序</div>
<div className="mt-16 w-full">
<Button
shape="rounded"
size="middle"
block
// onClick={handleSubmit}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
>
前往下载
</Button>
</div>
</div>
</div>
);
}

View File

@ -16,6 +16,9 @@ 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";
const variables = {
"@active-line-color": "#f00", // 将主题色改为红色
};
@ -31,53 +34,46 @@ 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);
// getData(0)
}, []);
async function mockRequest() {
if (count >= 5) {
return [];
}
await sleep(2000);
count++;
return [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
];
}
async function doRefresh() {
await sleep(1000);
Toast.show({
icon: "fail",
content: "刷新失败",
});
throw new Error("刷新失败");
// await sleep(1000);
// Toast.show({
// icon: "fail",
// content: "刷新失败",
// });
// throw new Error("刷新失败");
getPostList(1);
}
async function loadMore() {
const append = await mockRequest();
setData((val) => [...val, ...append]);
setHasMore(append.length > 0);
// 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">
@ -98,7 +94,12 @@ export default function Home() {
/>
))}
</Tabs>
<Link href="search" className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full"><Image src="/icons/search.png"/></Link>
<Link
href="search"
className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full"
>
<Image src="/icons/search.png" />
</Link>
</div>
<Swiper
className="overflow-visible"
@ -114,25 +115,13 @@ export default function Home() {
<Swiper.Item>
<PullToRefresh onRefresh={doRefresh}>
<List className="px-4 overflow-y-auto scrollbarBox_hidden">
<PostItemSkeleton />
<List.Item className="!p-0">
<PostItem type="post" 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"},
]}/>
</List.Item>
<List.Item className="!p-0">
<PostItem type="post" photos={[
{url:"https://picsum.photos/seed/picsum/200/300",type:"img"},
]}/>
</List.Item>
<List.Item className="!p-0">
<PostItem type="post" photos={[
{url:"https://picsum.photos/seed/picsum/200/300",type:"img"},
{url:"https://picsum.photos/seed/picsum/200/300",type:"img"},
]}/>
</List.Item>
{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>
@ -144,7 +133,7 @@ export default function Home() {
style={{ maxHeight: `${scrollHeight}px` }}
>
<List.Item className="!p-0">
<PostItem follow={true} type="post"/>
<PostItem follow={true} type="post" />
</List.Item>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</List>

View File

@ -1,7 +1,7 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Image, Swiper, Divider, ImageViewer } from "antd-mobile";
import { Image, Swiper, Divider, ImageViewer, Popover } from "antd-mobile";
import { useRouter } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
@ -9,6 +9,7 @@ import {
faAngleRight,
faEllipsisVertical,
faCopy,
faWarning,
} from "@fortawesome/free-solid-svg-icons";
export default function PersonSpace() {
const router = useRouter();
@ -22,7 +23,7 @@ export default function PersonSpace() {
useEffect(() => {}, []);
const showPhotos = (photos, index) => {
ImageViewer.Multi.show({
images: photos.map(item=>item.url),
images: photos.map((item) => item.url),
defaultIndex: index,
});
};
@ -38,14 +39,35 @@ export default function PersonSpace() {
}}
/>
</div>
<Popover
content={
<div
className="text-black"
onClick={() => {
router.push("messageDetail");
}}
>
<FontAwesomeIcon
icon={faWarning}
// size="xl"
<FontAwesomeIcon
icon={faEllipsisVertical}
size="xl"
onClick={() => {
// router.back();
}}
/>
color="#3B69B8"
className="mr-2"
/>
<span>举报</span>
</div>
}
trigger="click"
placement="left"
>
<FontAwesomeIcon
icon={faEllipsisVertical}
size="xl"
onClick={() => {
// router.back();
}}
/>
</Popover>
</div>
{/* 内容 */}
<div>

View File

@ -15,7 +15,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faAngleLeft,
faRefresh,
faAngleRight,
} from "@fortawesome/free-solid-svg-icons";
import PostItem from "@/components/PostItem";
import PostItemSkeleton from "@/components/skeletons/PostItemSkeleton";
@ -62,7 +61,7 @@ export default function PersonSpace() {
}}
/>
</div>
<Image width={42} height={42} src="/icons/setting.png" placeholder="" />
<Image width={42} height={42} src="/icons/setting.png" placeholder="" onClick={()=>router.push("setting")}/>
</div>
{/* 内容 */}
<div>
@ -115,6 +114,7 @@ export default function PersonSpace() {
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -131,6 +131,7 @@ export default function PersonSpace() {
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -146,6 +147,7 @@ export default function PersonSpace() {
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -160,6 +162,7 @@ export default function PersonSpace() {
<Image
src="/icons/report.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -372,12 +375,12 @@ export default function PersonSpace() {
</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">
<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.back();
router.refresh();
}}
/>
</div>
@ -390,6 +393,7 @@ export default function PersonSpace() {
<Image
src="/images/wechat.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -404,6 +408,7 @@ export default function PersonSpace() {
<Image
src="/icons/tiefen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>
@ -419,6 +424,7 @@ export default function PersonSpace() {
<Image
src="/icons/chaofen.png"
width={22}
height={22}
className="w-4 h-full"
placeholder=""
/>

53
app/space/setting/page.js Normal file
View File

@ -0,0 +1,53 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { Image, Avatar } 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 Setting() {
const router = useRouter();
useEffect(() => {
}, []);
return (
<div className="">
<div className="p-4 fixed top-0 z-10 w-full">
<div className="w-9 h-9 flex items-center justify-center bg-[#FFFFFF1A] rounded-full absolute">
<FontAwesomeIcon
icon={faAngleLeft}
size="xl"
onClick={() => {
router.back();
}}
/>
</div>
<p className="text-base text-center leading-9">空间设置</p>
</div>
{/* 内容 */}
<div className="flex items-center p-4 pt-16">
<Avatar
rounded-full
mr-4
src="https://picsum.photos/seed/picsum/200/300"
className="mr-4"
style={{ "--size": "64px", "--border-radius": "50%" }}
/>
<div>
<p className="text-xl font-bold">测试账号</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>
</div>
</div>
</div>
<div>
</div>
</div>
);
}

View File

@ -9,7 +9,7 @@ export default function PaySpacePost({
status = 0,
}) {
return (
<div className={`rounded-md bg-${type === "ironFan" ? "primary" : "super"}-500 px-2 py-2 my-2 text-sm`}>
<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="flex items-center">
<Image

View File

@ -9,19 +9,29 @@ const tabItems = [
{ key: "follow", title: "关注" },
];
export default function Photos({ photos = [] }) {
export default function Photos({ media }) {
const [seeAllPhotos, setSeeAllPhotos] = useState(false);
const [currentPhotos, setCurrentPhotos] = useState(photos);
const [currentPhotos, setCurrentPhotos] = useState([]);
const [photos, setPhotos] = useState([]);
const [currentVideos, setCurrentVideos] = useState([]);
const [videoVisible, setVideoVisible] = useState({
video: "",
visible: false,
});
useEffect(() => {
if (photos.length > 9) {
const newPhotos = [...photos];
setCurrentPhotos(newPhotos.slice(0, 9));
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 arr=[...imgArr,...videoArr]
let newPhotos = [...arr];
setPhotos(arr);
if (arr.length > 9) {
newPhotos = newPhotos.slice(0, 9);
}
setCurrentPhotos(newPhotos);
}
}, []);
}, [media]);
const showPhotos = (photos, index) => {
ImageViewer.Multi.show({
images: photos.map((item) => item.url),
@ -38,7 +48,7 @@ export default function Photos({ photos = [] }) {
<div
className="flex w-12 h-12 justify-center items-center bg-[#33333348] rounded-full"
key="closeBtn"
onClick={()=>Dialog.clear()}
onClick={() => Dialog.clear()}
>
<FontAwesomeIcon icon={faClose} size="2xl" className="h-12" />
</div>
@ -49,10 +59,10 @@ export default function Photos({ photos = [] }) {
height="100%"
controls
className="w-screen h-[70vh] rounded-lg object-cover"
poster="https://picsum.photos/seed/picsum/200/300"
poster={video.url}
>
<source
src="https://www.runoob.com/try/demo_source/movie.mp4"
src={video.mp4}
type="video/mp4"
/>
您的浏览器不支持 Video 标签
@ -95,11 +105,11 @@ export default function Photos({ photos = [] }) {
{currentPhotos.map((item, index) => {
return (
<div
className="relative "
className="relative w-max"
key={index}
onClick={() => {
if (item.type == "video") {
handleShowVideo(item.url);
handleShowVideo(item);
} else {
showPhotos(photos, index);
}
@ -111,7 +121,7 @@ export default function Photos({ photos = [] }) {
<div className="w-full h-full bg-[#1d1d1d] rounded"></div>
}
width={currentPhotos.length > 1 ? "100%" : 150}
height={currentPhotos.length > 1 ? "24vw" : "auto"}
height={currentPhotos.length > 1 ? "24vw" : 200}
className="rounded"
fit="cover"
src={item.url}

View File

@ -5,11 +5,13 @@ import Photos from "../Photos";
import { useRouter } from "next/navigation";
import PaySpacePost from "../PaySpacePost";
import { Image } from "antd-mobile";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleRight } from "@fortawesome/free-solid-svg-icons";
export default function PostItem({
type,
follow,
date = new Date(),
photos = [],
data = {},
}) {
const router = useRouter();
const [isOpenText, setIsOpenText] = useState(false);
@ -31,83 +33,85 @@ export default function PostItem({
<div className="flex">
<img
className="flex-none w-8 h-8 rounded-full mr-2"
src="https://picsum.photos/seed/picsum/200/300"
src={data.streamer_ext?.avatar.images[0].urls[0]}
alt=""
onClick={() => router.push("/profile")}
/>
<div className="flex-1">
<div className="flex justify-between items-center">
<span className="font-bold text-md">用户名</span>
<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">
{follow ? "已关注" : "关注"}
{data.is_followed ? "已关注" : "关注"}
</span>
)}
</div>
<div>
<p className={`mb-2 mt-2 ${!isOpenText ? "text-ellipsis-3" : ""}`}>
御姐风细跟高跟鞋太绝了御姐风细跟高跟鞋太绝了御姐风细跟高跟鞋太绝了御姐风细跟高跟鞋太绝了御姐风细跟高跟鞋太绝了
{data.text}
</p>
<div
{
data.text?.length>50 &&
<div
className="font-bold text-btn my-4 text-base"
onClick={() => setIsOpenText(!isOpenText)}
>
{isOpenText ? "收起" : "全文"}
</div>
}
</div>
<Photos photos={photos} />
{data.media_component && <Photos media={data.media_component} />}
{type == "space" && (
<PaySpacePost type="superFan" price="19.89" status={0}/>
<PaySpacePost type="superFan" price="19.89" status={0} />
)}
<div className="flex justify-between items-center mt-2">
{type == "post" ? (
<div className="flex items-center">
<Image
{
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-2 text-primary text-xs">
{getDays < 7
<span className="mr-1 text-primary text-xs">
{data.days_elapsed_since_the_last_zones_update < 7
? `空间${
getDays === 0
data.days_elapsed_since_the_last_zones_update === 0
? "今日"
: "new" === 1
? "昨日"
: "new" === 2
? "前天"
: getDays + "天前"
: data.days_elapsed_since_the_last_zones_update + "天前"
}有更新`
: "1" === 2
? "空间今日有更新"
: ""}
</span>
<FontAwesomeIcon icon={faAngleRight} color="#FF669E" size="sm" />
</>
}
</div>
) : (
<div className="text-[#ffffff88] text-xs">
<span className="mr-2">
{getDays < 3
? `${
getDays === 0
? "今日"
: "new" === 1
? "昨日"
: "前天"
getDays === 0 ? "今日" : "new" === 1 ? "昨日" : "前天"
}`
: date.getMonth() + 1 + "月" + date.getDate() + "日"
}
</span>
<span>
{date.getHours() + ":" + date.getMinutes()}
: date.getMonth() + 1 + "月" + date.getDate() + "日"}
</span>
<span>{date.getHours() + ":" + date.getMinutes()}</span>
</div>
)}
<div className="flex items-center">
<div className="flex items-center mr-4">
<Image
src="/icons/notthumbup.png"
src={data.is_thumbed_up ? "/icons/thumbup.png":"/icons/notthumbup.png"}
width={32}
className="w-4 h-full"
placeholder=""

View File

@ -0,0 +1,7 @@
import { checkAuth } from "@/utils/auth";
import { redirect } from "next/navigation";
export default function WithAuth(WrappedComponent) {
return checkAuth() ? WrappedComponent : redirect("/login");
}

View File

@ -4,7 +4,7 @@ const nextConfig = {
return [
{
source: "/api/:path*",
destination: "https://api.tiefen.fun/api/:path*",
destination: "https://testapi.tiefen.fun/api/:path*",
},
];
},

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"

41
utils/require.js Normal file
View File

@ -0,0 +1,41 @@
import baseRequest from "./baseRequest";
// import webviewBaseRequest from "@/utils/webviewBaseRequest";
const base = baseRequest();
// 创建一个封装 fetch 的函数
export default function customFetch(method, url, options = {}) {
// 默认选项
const defaultOptions = {
method: method,
headers: {
'Content-Type': 'application/json',
'X-Req-Source-TF': 'wittgenstein',
...options.headers
// 可以添加其他默认头部信息
}
// 可以添加其他默认选项
};
const body=JSON.stringify({...base,...options.body})
// 合并选项
const mergedOptions = { ...defaultOptions, body};
console.log("mergedOptions",mergedOptions)
// 返回 Promise 对象
return new Promise((resolve, reject) => {
fetch(url, mergedOptions)
.then(response => {
// 检查响应状态码
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 解析 JSON 响应
return response.json();
})
.then(data => {
// 解析成功,返回数据
resolve(data);
})
.catch(error => {
// 请求失败,拒绝 Promise
reject(error);
});
});
}

12
utils/storeInfo.js Normal file
View File

@ -0,0 +1,12 @@
export function save(key,value){
localStorage.setItem(key,value)
}
export function get(key){
localStorage.getItem(key)
}
export function remove(key){
localStorage.removeItem(key)
}
export function clear(){
localStorage.clear()
}