完善客服聊天功能

This commit is contained in:
al 2024-07-11 23:57:26 +08:00
parent cf2d08373f
commit fc2253f59b
9 changed files with 561 additions and 116 deletions

View File

@ -123,7 +123,11 @@ body{
background-color: #07050A;
color: #ffffff88;
}
.adm-jumbo-tabs-header .adm-jumbo-tabs-tab-list{
.spaceBoxTwo .adm-jumbo-tabs-header .adm-jumbo-tabs-tab-list{
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.spaceBoxThree .adm-jumbo-tabs-header .adm-jumbo-tabs-tab-list{
display: grid;
grid-template-columns: repeat(3, 1fr);
}

View File

@ -1,22 +1,17 @@
"use client";
import React, { useState, useCallback, useEffect } from "react";
import React, { useState, useRef, useEffect, useCallback } 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,
Toast
} from "antd-mobile";
import { Input, Button, Toast, Avatar } from "antd-mobile";
import { useRouter } from "next/navigation";
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
import { get } from "@/utils/storeInfo";
import require from "@/utils/require";
import { formatDeadline } from "@/utils/tools";
/*
params格式
{
@ -25,32 +20,117 @@ params格式
*/
export default function MessageDetail({}) {
const [hasMore, setHasMore] = useState(true);
// 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 [oldMessages, setOldMessages] = useState([]);
const [messages, setMessages] = useState([]);
const [handledmessages, setHandledmessages] = useState([]);
const [sessionId, setSessionId] = useState();
const [userInfo, setUserInfo] = useState(null);
const [newMessage, setNewMessage] = useState("");
const [offset, setOffset] = useState(0);
const [more, setMore] = useState(1);
const scrollBox = useRef();
const toScrollBottom = useRef(0);
useEffect(() => {
const userData = get("account");
setUserInfo(userData);
getSession(userData.mid);
}, []);
useEffect(() => {
if (toScrollBottom.current) {
scrollBox.current?.scrollTo(0, scrollBox.current.scrollHeight);
toScrollBottom.current = 0;
}
}, [toScrollBottom.current]);
// useEffect(() => {
// if (offset == 12) {
// console.log("offset--------", offset);
// scrollBox.current?.scrollTo(0, scrollBox.current.scrollHeight);
// // console.log("scrollBox.current",scrollBox.current.scrollHeight,scrollBox.current.scrollTop)
// }
// }, [offset]);
useEffect(() => {
if (sessionId && userInfo.mid) {
loadEarlierHistory().then((res) => {
setMessages((old) => {
toScrollBottom.current = 1;
return res;
});
});
const detailResponse = await fetch(
`${apiUrl}/api/contact_customer_service_session/list_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
}
const intervalId = setInterval(() => {
updateMessages();
}, 3000); // 间隔时间为3秒
// 在组件卸载时清除定时器
return () => {
clearInterval(intervalId);
};
}, [sessionId]);
//查询session
const getSession = async (mid) => {
try {
const data =
await require("POST", "/api/contact_customer_service_session/list_by_mid", {
body: {
mid: Number(mid),
},
body: JSON.stringify({
mid: account.mid,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
}
if (data.data.session) {
setSessionId(data.data.session.id);
return;
}
} catch (error) {
console.error(error);
}
};
//创建session
const createSession = async () => {
try {
const data =
await require("POST", "/api/contact_customer_service_session/create", {
body: {
sub_mid: Number(mid),
obj_mid: 0,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
}
setSessionId(data.data.session_id);
} catch (error) {
console.error(error);
}
};
//请求历史记录
const loadEarlierHistory = async () => {
if (!more) return;
try {
const data =
await require("POST", "/api/contact_customer_service/list_by_session_id", {
body: {
session_id: sessionId,
offset: offset,
limit: 12,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
@ -58,29 +138,198 @@ export default function MessageDetail({}) {
});
return;
}
if (detailData.data.session) {
setSessionId(detailData.data.session.id);
return;
}
setOffset(data.data.offset);
setMore(data.data.more);
let mathNewMessages = handleData(data.data.list);
// setMessages((prev) => [...prev, ...temMessages]);
return mathNewMessages;
} catch (error) {
console.error(error);
}
};
async function doRefresh() {
await sleep(1000);
Toast.show({
icon: "fail",
content: "刷新失败",
});
throw new Error("刷新失败");
}
//发送私信功能
const onSend = useCallback(
async (message, lastId, oldArr) => {
if (message == "") {
Toast.show({
icon: "error",
content: "不可发送空内容",
position: "top",
});
return;
}
//如果是第一次发送需要创建session
if (!sessionId) await createSession();
//查询历史记录的时候后移一位,防止记录重复
setOffset((prev) => prev + 1);
//请求接口发送私信
try {
const data =
await require("POST", "/api/contact_customer_service/create", {
body: {
session_id: sessionId,
predicate: 0,
message,
},
});
if (data.ret === -1) {
Toast.show({
icon: "error",
content: data.msg,
position: "top",
});
return;
}
// updateLatestHistory();
console.log("oldArr", oldArr);
updateMessages(lastId, 0, oldArr);
} catch (error) {
console.error(error);
}
// //每次发送都缓存信息到本地
// addArr(`${selfData.mid}_to_${params.mid}_messages`, messages);
},
[userInfo, sessionId]
);
// 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);
if (sessionId && userInfo.mid && offset && more) {
const append = await loadEarlierHistory();
if (append) {
// setMessages((val) => [...val, ...append]);
setMessages(append);
// setHasMore(append.length > 0);
}
}
}
const handleData = (list) => {
// console.log("list", list);
const account = get("account");
const temMessages = list.map((item) => {
if (item.predicate === 0) {
return {
predicate: item.predicate,
_id: item.id,
// createdAt: new Date(item.ct * 1000).toISOString(),
createdAt: item.ct,
text: item.message,
user: {
_id: account?.mid,
name: account?.name,
avatar: account?.avatar?.images[0]?.urls[0],
},
};
} else {
return {
predicate: item.predicate,
_id: item.id,
createdAt: item.ct,
text: item.message,
user: {
_id: 0,
name: "客服",
avatar: "images/icon.png",
},
};
}
});
// console.log("[...messages, ...temMessages]", [
// ...handledmessages,
// ...temMessages,
// ]);
setHandledmessages([...handledmessages, ...temMessages]);
setOldMessages([...oldMessages, ...list]);
let newMessages = [...handledmessages, ...temMessages].reverse();
let mathNewMessages = newMessages.reduce(
(accumulator, currentValue, index, sourceArray) => {
// console.log(
// accumulator.time,
// "----",
// currentValue.createdAt,
// "---",
// index
// );
if (accumulator.time > currentValue.createdAt) {
let newData = {
...accumulator,
time: accumulator.time,
messages: [...accumulator.messages, currentValue],
totalArr:
index == sourceArray.length - 1
? [
...accumulator.totalArr,
[...accumulator.messages, currentValue],
]
: [...accumulator.totalArr],
};
return newData;
} else {
let newData = {
...accumulator,
time: currentValue.createdAt + 60 * 3,
messages: [currentValue],
totalArr:
index == sourceArray.length - 1
? [
...accumulator.totalArr,
accumulator.messages,
[currentValue],
]
: [...accumulator.totalArr, accumulator.messages],
};
return newData;
}
},
{ time: newMessages[0]?.createdAt + 60 * 3, messages: [], totalArr: [] }
);
return mathNewMessages.totalArr;
};
const updateMessages = async (lastId, currentOffset, oldArr) => {
console.log("lastId", lastId);
try {
const data =
await require("POST", "/api/contact_customer_service/list_by_session_id", {
body: {
session_id: sessionId,
offset: currentOffset,
limit: 12,
},
});
if (data.ret === -1) {
Toast.show({
icon: "fail",
content: data.msg,
position: "top",
});
return;
}
let newData = data.data.list.filter((element) => {
return element.id > lastId;
});
console.log("[...messages,...newData]", [...newData, ...oldArr]);
let mathNewMessages = handleData([...newData, ...oldArr]);
setMessages((old) => {
toScrollBottom.current = 1;
setNewMessage("");
return mathNewMessages;
});
} catch (error) {
console.error(error);
}
};
return (
<div className="bg-[#13121F] h-screen">
<div className="bg-[#13121F] h-screen overflow-y-auto" ref={scrollBox}>
<div className="p-4 fixed top-0 z-10 w-full bg-black">
<div className="flex items-center justify-center absolute">
<FontAwesomeIcon
@ -94,24 +343,81 @@ export default function MessageDetail({}) {
<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 className="my-[57px]">
<div className="flex justify-center py-2">
<p
className="px-3 py-2 rounded-full bg-[#FFFFFF1A]"
onClick={loadMore}
>
{more ? "查看更早" : "无更早消息"}
</p>
</div>
<ul className="py-2">
{messages?.map((item, index) => (
<li key={index}>
<p className="my-2 text-center">
{formatDeadline(item[0].createdAt)}
</p>
<ul className="px-4 overflow-y-auto scrollbarBox_hidden">
{item.map((it) => (
<li key={it?._id} className="py-3 rounded-lg ">
<div className="flex w-full">
{it?.predicate == 1 ? (
<div className="flex justify-start w-full items-center">
<Avatar
className="mr-2"
style={{ "--border-radius": "50px" }}
width={32}
height={32}
src={it?.user.avatar}
/>
<div
className="block rounded-lg py-2 px-3 bg-blue-500"
style={{ borderTopLeftRadius: 0 }}
>
{it?.text}
</div>
</div>
) : (
<div className="flex justify-end w-full items-center">
<div
className="block rounded-lg py-2 px-3 bg-blue-500"
style={{ borderTopRightRadius: 0 }}
>
{it?.text}
</div>
<Avatar
className="ml-2"
style={{ "--border-radius": "50px" }}
width={32}
height={32}
src={it?.user?.avatar}
/>
</div>
)}
</div>
</li>
))}
</ul>
</li>
))}
{}
{/* <InfiniteScroll loadMore={loadMore} hasMore={more} /> */}
</ul>
</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="w-full h-16 fixed bottom-0 grid grid-cols-[1fr_68px] bg-black items-center p-2 border-t-2 border-[#ffffff2a]">
<div className="rounded bg-[#222036] px-4 py-2 mr-2">
<Input placeholder="输入新消息" className="" />
<Input
placeholder="输入新消息"
className=""
value={newMessage}
onChange={setNewMessage}
/>
</div>
<Button
size="middle"
block
// onClick={handleSubmit}
onClick={() => onSend(newMessage, oldMessages[0].id, oldMessages)}
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
>
发送

View File

@ -112,11 +112,11 @@ export default function PersonSpace() {
/>
</div>
<Popover
style={{ "--background": "#1E1C29"}}
content={
<div
className="text-black"
onClick={() => {
router.push("messageDetail");
router.push("/messageDetail");
}}
>
<FontAwesomeIcon

View File

@ -85,9 +85,8 @@ export default function ShareSpace({ data }) {
<Divider />
<div
onClick={() => {
router.push("WebWithoutHeader", {
uri: webUrl + "/zone/share/" + data?.streamer_ext?.user_id,
})
console.log(webUrl + "/zone/share/" + streamerInfo?.streamer_ext?.user_id)
router.push(webUrl + "/zone/share/" + streamerInfo?.streamer_ext?.user_id)
}}
className="flex justify-between pt-4 pb-2"
>

View File

@ -160,7 +160,7 @@ export default function PersonSpace() {
}
};
return (
<div className="">
<div className={`${!!streamerInfo?.is_superfanship_enabled ? "spaceBoxThree":"spaceBoxTwo"}`}>
<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
@ -176,7 +176,7 @@ export default function PersonSpace() {
height={42}
src="/icons/setting.png"
placeholder=""
onClick={() => router.push("setting")}
onClick={() => router.push("setting?data="+encodeURIComponent(JSON.stringify(streamerInfo?.streamer_ext)))}
/>
</div>
{/* 内容 */}
@ -277,7 +277,7 @@ export default function PersonSpace() {
10
)}/${parseInt(streamerInfo?.ironfanship_price / 100, 10)}`}</p>
</li>
{streamerInfo?.is_superfanship_enabled && (
{!!streamerInfo?.is_superfanship_enabled && (
<li
className="flex flex-col items-center mr-6"
onClick={() => {
@ -304,7 +304,9 @@ export default function PersonSpace() {
)}
<li
className="flex flex-col items-center"
// onClick={() => setMaskVisible(true)}
onClick={() => {
router.push("/messageDetail");
}}
>
<div className="w-9 h-9 flex items-center justify-center bg-[#1d1d1d71] rounded-full mb-1">
<Image
@ -398,7 +400,7 @@ export default function PersonSpace() {
}}
/>
</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]">
<ul className="flex justify-around 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" })}
@ -434,7 +436,7 @@ export default function PersonSpace() {
</p>
{/* <p className="text-[#ffffff54] text-[10px]">0/299</p> */}
</li>
{streamerInfo?.is_superfanship_enabled && (
{!!streamerInfo?.is_superfanship_enabled && (
<li
className="flex flex-col items-center"
onClick={() => {

View File

@ -1,14 +1,56 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { Image, Avatar } from "antd-mobile";
import { useRouter } from "next/navigation";
import { Image, Avatar, Divider, Dialog, Toast } from "antd-mobile";
import { useRouter, useSearchParams } from "next/navigation";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
import {
faAngleLeft,
faAngleRight,
faCalendar,
} from "@fortawesome/free-solid-svg-icons";
export default function Setting() {
const router = useRouter();
const searchParams = useSearchParams();
const [streamerInfo, setStreamerInfo] = useState(null);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
let data = JSON.parse(decodeURIComponent(searchParams.get("data")));
setStreamerInfo(data);
}, []);
const handleShowVideo = () => {
Dialog.className = "videoMask";
Dialog.show({
title: "是否确认退出空间?",
content: (
<div className="flex flex-col gap-2">
一旦退出您的空间成员身份将会被取消若当前空间为付费空间下次加入时需要再次付费请确保知晓以上内容后谨慎选择退出
</div>
),
bodyStyle: {
// background: "none",
maxHeight: "none",
width: "80vw",
position: "absolute",
top: "calc(50% - 50px)",
left: "10vw",
},
actions: [
{
key: "submit",
text: "确定",
onClick: () => {
i;
},
},
{
key: "cancel",
text: "取消",
onClick: () => {},
},
],
});
};
return (
<div className="">
<div className="p-4 fixed top-0 z-10 w-full">
@ -24,29 +66,93 @@ export default function Setting() {
<p className="text-base text-center leading-9">空间设置</p>
</div>
{/* 内容 */}
<div className="flex items-center p-4 pt-16">
<div className="p-4 pt-20">
<div className="flex items-center">
<Avatar
rounded-full
mr-4
src="https://picsum.photos/seed/picsum/200/300"
src={streamerInfo?.cover?.images[0]?.urls[0]}
className="mr-4"
style={{ "--size": "64px", "--border-radius": "50%" }}
style={{ "--size": "52px", "--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>
<p className="text-xl font-bold">{streamerInfo?.name}</p>
<ul className="flex">
<li className="h-4 mr-1 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mt-1 w-max">
<Image
src="/icons/info/ID.png"
width={14}
height={14}
className="w-4 h-full mr-1"
placeholder=""
/>
<span>{streamerInfo?.user_id}</span>
</li>
<li className="h-4 flex items-center text-xs bg-[#ffffff18] rounded-full px-2 py-2.5 mt-1 w-max">
<FontAwesomeIcon
icon={faCalendar}
size="sm"
className="mr-1"
onClick={() => {
router.back();
}}
/>
<span>{streamerInfo?.user_id}</span>
</li>
</ul>
</div>
</div>
<div>
<ul className="mt-6">
<li>
<div
onClick={() => router.push("/share/" + streamerInfo.mid)}
className="flex justify-between"
>
<span className="text-base text-white">分享空间</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="mr-1"
onClick={() => {
router.back();
}}
/>
</div>
<Divider />
</li>
<li
onClick={async () => {
const result = await Dialog.confirm({
title: "是否确认退出空间?",
content: "一旦退出,您的空间成员身份将会被取消,若当前空间为付费空间,下次加入时,需要再次付费。请确保知晓以上内容后谨慎选择退出。",
bodyStyle: {
maxHeight: "none",
width: "80vw",
position: "fixed",
top: "200px",
left: "10vw",
},
});
if (result) {
Toast.show({ content: "点击了确认", position: "bottom" });
}
}}
>
<div className="flex justify-between">
<span className="text-base text-white">退出空间</span>
<FontAwesomeIcon
icon={faAngleRight}
size="xl"
className="mr-1"
onClick={() => {
router.back();
}}
/>
</div>
<Divider />
</li>
</ul>
</div>
</div>
);

View File

@ -67,7 +67,7 @@ export default function Photos({ media }) {
<div
className="flex w-12 h-12 justify-center items-center bg-[#33333348] rounded-full"
key="closeBtn"
onClick={handleSeeAllPhotos}
// onClick={handleSeeAllPhotos}
>
<FontAwesomeIcon icon={faSave} size="2xl"/>
</div>
@ -116,8 +116,8 @@ export default function Photos({ media }) {
placeholder={
<div className="w-full h-full bg-[#1d1d1d] rounded"></div>
}
width={currentPhotos.length>1 ? "25vw":"100%"}
height={currentPhotos.length>1 ? "25vw":"100%"}
width={currentPhotos.length>1 ? "24vw":"100%"}
height={currentPhotos.length>1 ? "24vw":"100%"}
className={`rounded max-w-full`}
fit="cover"
src={item.url}

View File

@ -4,11 +4,11 @@ import React, { useEffect, useState, useMemo } from "react";
import Photos from "../Photos";
import { useRouter } from "next/navigation";
import PaySpacePost from "../PaySpacePost";
import { Image } from "antd-mobile";
import { Image, Popover, Divider } 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"
import { get } from "@/utils/storeInfo";
export default function PostItem({
type,
follow,
@ -18,14 +18,16 @@ 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 || data?.is_zone_moment_thumbed_up);
const [isThumbsUp, setIsThumbsUp] = useState(
data?.is_thumbed_up || data?.is_zone_moment_thumbed_up
);
//判断是否是发帖人
const [isCreator, setIsCreator] = useState(false);
useEffect(() => {
const account = get("account");
if (account.mid === data.mid) setIsCreator(true);
if (account.mid === data.mid) setIsCreator(true);
return () => {
router.prefetch("/profile/"+data.mid);
router.prefetch("/profile/" + data.mid);
};
}, []);
const getDays = useMemo(() => {
@ -35,7 +37,7 @@ export default function PostItem({
}, []);
return (
<div>
{type == "space" && data?.is_headed === 1 && (
{type == "space" && data?.is_headed === 1 && (
<Image src="/images/top_post.png" width={76} className="mb-2" />
)}
<div className="flex">
@ -43,7 +45,7 @@ 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/"+data.mid)}
onClick={() => router.push("/profile/" + data.mid)}
/>
<div className="flex-1">
@ -72,23 +74,23 @@ export default function PostItem({
)}
</div>
<div className="mr-8">
{data.media_component && <Photos media={data.media_component} />}
{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}
/>
)}
{data.media_component && <Photos media={data.media_component} />}
{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>
<div className="flex justify-between items-center mt-2">
{type == "post" ? (
<div
className="flex items-center"
onClick={() => router.push("/profile/"+data.mid)}
onClick={() => router.push("/profile/" + data.mid)}
>
{data.is_active_within_a_week ? (
<>
@ -141,7 +143,9 @@ export default function PostItem({
<div className="flex items-center">
<div
className="flex items-center mr-4 h-[32px]"
onClick={() => thumbsUp(data.id, isThumbsUp, setIsThumbsUp,type == "space")}
onClick={() =>
thumbsUp(data.id, isThumbsUp, setIsThumbsUp, type == "space")
}
>
<Image
src={
@ -153,9 +157,33 @@ export default function PostItem({
className="w-4 h-full"
placeholder=""
/>
<span className={`text-xs ${isThumbsUp == 1?"text-[#FF669E]":"text-[#FFFFFF80]"}`}>{isThumbsUp == 1 ? "已赞" : "点赞"}</span>
<span
className={`text-xs ${
isThumbsUp == 1 ? "text-[#FF669E]" : "text-[#FFFFFF80]"
}`}
>
{isThumbsUp == 1 ? "已赞" : "点赞"}
</span>
</div>
<span className="mr-2">···</span>
<Popover
style={{ "--background": "#1E1C29" }}
content={
<ul>
<li
className="py-1 px-4"
onClick={() => {
router.push("/messageDetail");
}}
>
举报
</li>
</ul>
}
trigger="click"
placement="left"
>
<span className="mr-2">···</span>
</Popover>
</div>
</div>
{/* <div className="rounded-full h-px bg-gray-200 mt-2"></div> */}

BIN
public/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB