mcwl-pc/app/pages/personal-center/index.vue

964 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script setup lang="ts">
import { Close } from "@vicons/ionicons5";
const message = useMessage();
import { commonApi } from "@/api/common";
import EditUserInfo from "@/components/EditUserInfo.vue";
import { useUserStore } from "@/stores/user";
import { formatDate } from "@/utils/index.ts";
import { NConfigProvider, NMessageProvider } from "naive-ui";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
let pollingTimer: ReturnType<typeof setInterval> | undefined
const route = useRoute();
const { type, status } = route.query as { type: string; status: string };
const loading = ref(false);
const finished = ref(false);
const total = ref(0); // 总条数
const loadingTrigger = ref(null);
import { useRouter } from "vue-router";
const router = useRouter();
const observer = ref<IntersectionObserver | null>(null);
definePageMeta({
layout: "default",
});
interface UserInfo {
nickName?: string; // 使用 ? 表示 nickName 是可选的
avatar?: string;
name?: string;
brief?: string;
}
const userStore = useUserStore();
const userInfo: UserInfo = userStore.userInfo;
// 当前是发布还是点赞?
const currentState = ref("mallProduct");
// 当前是模型还是工作流还是图片?
const currentType = ref("0");
const orderOptions = ref([
{
dictLabel: "最新",
dictValue: "create_time",
},
{
dictLabel: "最热",
dictValue: "like_num",
},
]);
const stateList = ref([
{ id: "mallProduct", title: "发布" },
{ id: "like", title: "点赞" },
]);
const typeList = ref([
{ id: "0", title: "模型" },
{ id: "1", title: "工作流" },
{ id: "2", title: "图片" },
]);
// 发布的form查询条件
const publishParams = ref({
pageNum: 1,
pageSize: 20,
status: "0",
orderByColumn: "create_time",
date: null,
endTime: "",
startTime: "",
});
function initPublishParams() {
publishParams.value = {
pageNum: 1,
pageSize: 20,
status: "0",
orderByColumn: "create_time",
date: null,
endTime: "",
startTime: "",
};
}
// 点赞form的查询条件
const likesParams = ref({
pageNum: 1,
pageSize: 20,
orderByColumn: "create_time",
});
function initLikesParams() {
likesParams.value = {
pageNum: 1,
pageSize: 20,
orderByColumn: "create_time",
};
}
const urlList = ref({
mallProduct: {
0: "/personalCenter/selectByUserIdModel",
1: "/personalCenter/selectByUserIdWorkFlow",
2: "/personalCenter/selectByUserIdImage",
},
like: {
0: "/personalCenter/likeModel",
1: "/personalCenter/likeWorkFlow",
2: "/personalCenter/likeImage",
},
});
const showInvitationModal = ref(false);
// 获取数据字典
const statusOptions = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({
type: "mall_product_status",
});
if (res.code === 200 && res.data.length > 0) {
statusOptions.value = res.data;
publishParams.value.status = res.data[0].dictValue;
}
} catch (error) {
console.log(error);
}
}
getDictType();
// 编辑用户信息
interface EditUserInfoType {
isVisible: boolean;
}
const editUserInfoRef = ref<EditUserInfoType | null>(null);
function onEditInfo() {
if (editUserInfoRef.value) {
editUserInfoRef.value.isVisible = true;
}
}
// 实名认证
interface AuthComponentType {
isVisible: boolean;
}
const authenticationRef = ref<AuthComponentType | null>(null);
function onAuth() {
if (authenticationRef.value) {
authenticationRef.value.isVisible = true;
}
}
function initChangeParams() {
if (currentState.value === "mallProduct") {
initPublishParams();
} else {
initLikesParams();
}
finished.value = false; // 重置加载完成状态
getList();
}
// 切换发布/点赞
function changeTabs(id: string) {
currentState.value = id;
currentType.value = "0";
initChangeParams();
}
// 切换模型/工作流/图片
function changeType(id: string) {
if (id === "2") {
statusOptions.value.forEach((item) => {
if (item.dictValue === "2") {
item.disabled = true;
}
});
} else {
statusOptions.value.forEach((item) => {
if (item.dictValue === "2") {
item.disabled = false;
}
});
}
currentType.value = id;
initChangeParams();
}
function initPageNUm() {
if (currentState.value === "mallProduct") {
publishParams.value.pageNum = 1;
} else {
likesParams.value.pageNum = 1;
}
finished.value = false; // 重置加载完成状态
getList();
}
// 切换select全部状态/已发布/
function changeStatus(value: string) {
publishParams.value.status = value;
initPageNUm();
}
// 切换发布的最热/最新
function changeOrder(value: string) {
publishParams.value.orderByColumn = value;
initPageNUm();
}
// 切换点赞的最热/最新
function changeLikeOrder(value: string) {
likesParams.value.orderByColumn = value;
initPageNUm();
}
// 切换日期
async function changeDate(value: string[]) {
publishParams.value.startTime = `${await formatDate(value[0] as string)} 00:00:00`;
publishParams.value.endTime = `${await formatDate(value[1] as string)} 23:59:59`;
initPageNUm();
}
// 获取用户点赞/粉丝/关注数量
interface SelectUserInfo {
likeCount: number;
bean: number;
download: number;
attention: number;
}
const selectUserInfo = ref<SelectUserInfo>({
likeCount: 0,
bean: 0,
download: 0,
attention: 0,
});
async function getAttention() {
try {
const res = await request.get("/attention/selectUserInfo");
if (res.code === 200) {
selectUserInfo.value = res.data;
}
} catch (err) {
console.log(err);
}
}
getAttention();
// Banner 样式
const bannerStyle = {
backgroundImage:
"url('https://img.zcool.cn/community/special_cover/3a9a64d3628c000c2a1000657eec.jpg')",
};
// 定义响应接口
interface ApiResponse<T> {
code: number;
rows: T[];
message: string;
}
// 定义数据接口
interface UserData {
id: number;
name: string;
email: string;
status: number;
}
// 查询发布模型接口
const dataList = ref([]);
async function getList() {
if (loading.value || finished.value) return;
loading.value = true;
let params = {};
if (currentState.value === "mallProduct") {
params = publishParams.value;
} else {
params = likesParams.value;
}
const url = urlList.value[currentState.value][currentType.value];
try {
const res = await request.post<ApiResponse<UserData>>(url, params);
if (res.code === 200) {
// 如果是第一页,直接赋值,否则追加数据
if (params.pageNum === 1) {
dataList.value = res.rows;
} else {
dataList.value = [...dataList.value, ...res.rows];
}
total.value = res.total; // 假设接口返回了总条数
// 判断是否加载完所有数据
if (dataList.value.length >= total.value) {
finished.value = true;
}
// 自动增加页码
if (currentState.value === "mallProduct") {
publishParams.value.pageNum++;
} else {
likesParams.value.pageNum++;
}
}
} catch (err) {
dataList.value = [];
finished.value = true;
console.log(err);
} finally {
loading.value = false;
}
}
onMounted(() => {
window.addEventListener("scroll", topedRefresh);
observer.value = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !loading.value && !finished.value) {
getList();
}
},
{
threshold: 0.1,
}
);
if (loadingTrigger.value) {
observer.value.observe(loadingTrigger.value);
}
if (type && type === "like") {
currentState.value = type;
}
if (status === "0" || status === "1" || status === "2") {
currentType.value = status;
}
getList();
});
onUnmounted(() => {
window.removeEventListener("scroll", topedRefresh);
if (observer.value) {
observer.value.disconnect();
}
});
async function topedRefresh() {
if (import.meta.client) {
await nextTick();
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
initPageNUm();
}
function onClearDate() {
(publishParams.value.startTime = ""), (publishParams.value.endTime = ""), initPageNUm();
}
// 获取粉丝的列表
const isShowFan = ref(false);
const attentionFinished = ref(false);
const attentionList = ref([]);
const attentionListParams = ref({
pageNumber: 1,
pageSize: 20,
});
async function getAttentionList() {
try {
if (attentionFinished.value) return;
const res = await request.post(`/attention/selectToAttention`, {
...attentionListParams.value,
});
if (res.code === 200) {
if (attentionListParams.value.pageNumber === 1) {
attentionList.value = res.data.list;
} else {
attentionList.value = [...attentionList.value, ...res.data.list];
}
total.value = res.data.total; // 假设接口返回了总条数
// 判断是否加载完所有数据
if (attentionList.value.length >= total.value) {
attentionFinished.value = true;
}
attentionListParams.value.pageNumber++;
}
} catch (err) {
attentionList.value = [];
attentionFinished.value = true;
console.log(err);
}
}
getAttentionList();
// 获取关注的列表
const likeFinished = ref(false);
const likeList = ref([]);
const likeListParams = ref({
pageNumber: 1,
pageSize: 20,
type:null
});
async function getLikeList() {
try {
if (likeFinished.value) return;
const res = await request.post(`/attention/selectAttentionList`, {
...likeListParams.value,
});
if (res.code === 200) {
if (likeListParams.value.pageNumber === 1) {
likeList.value = res.data.list;
} else {
likeList.value = [...likeList.value, ...res.data.list];
}
total.value = res.data.total; // 假设接口返回了总条数
// 判断是否加载完所有数据
if (likeList.value.length >= total.value) {
likeFinished.value = true;
}
likeListParams.value.pageNumber++;
}
} catch (err) {
likeList.value = [];
likeFinished.value = true;
console.log(err);
}
}
getLikeList();
function closefanList() {
isShowFan.value = false;
}
// 去关注/取消关注当前用户
async function onAttention(item: any) {
try {
const userId = userStore.userInfo.userId
if(userId === item.userId){
return message.warning('自己不能关注自己')
}
const res = await request.get(`/attention/addAttention?userId=${item.userId}`);
if (res.code === 200) {
if (res.data) {
message.success("关注成功");
} else {
message.success("取消关注成功");
}
item.attention = !item.attention;
}
} catch (err) {
console.log(err);
}
}
// 过去邀请码
const invitationCode = ref("");
async function getInvitation() {
try {
const res = await request.get(`/invitation/getInvitationCode`);
if (res.code === 200) {
invitationCode.value = res.msg;
}
} catch (err) {
console.log(err);
}
}
getInvitation();
// 获取邀请列表
// async function getInvitationList() {
// }
// 复制到粘贴板
function copyToClipboard(text: string) {
const textarea = document.createElement("textarea");
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand("copy");
document.body.removeChild(textarea);
if (success) {
message.success("复制成功!");
} else {
message.success("复制成功!");
}
}
// 复制到粘贴板
function handlePositiveClick() {
copyToClipboard(invitationCode.value);
}
const activeTab = ref('like')
//邀请码列表
const invitationList = ref({});
async function handleNegativeClick() {
try {
const res = await request.get(`/invitation/earningsDisplay`);
if (res.code === 200) {
invitationList.value = res.data
showInvitationModal.value = true;
}
} catch (err) {
console.log(err);
}
}
const showLike = (type:string) =>{
isShowFan.value= true
activeTab.value = type
}
const toWallet = () => {
router.push({
path: `/wallet`,
});
}
// 查询是否已绑定支付宝
const zfbStatus = ref('0')
async function getBindStatus(){
const res = await request.get(`/ali/pay/queryBindStatus`)
if (res.data === '1') { //'1 绑定 0不绑定
zfbStatus.value = res.data
message.success('绑定成功!')
}
}
getBindStatus()
// 绑定支付宝
const qrUrl = ref('')
const isShowBindingModal = ref(false)
const showBinding = async() =>{
try {
const res = await request.get(`/ali/pay/generateQrCode`);
if (res.code === 200) {
qrUrl.value = res.data;
isShowBindingModal.value = true
pollingTimer && clearTimeout(pollingTimer)
pollingTimer = setInterval(async () => {
try {
const res2 = await request.get(`/ali/pay/queryBindStatus`)
if (res2.data === '1') {
closeBindingModal()
message.success('绑定成功!')
}
}
catch (err) {
closeBindingModal()
message.warning('绑定失败,请稍后再试!')
}
}, 2000)
}
} catch (err) {
console.log(err);
}
}
const closeBindingModal = () =>{
pollingTimer && clearTimeout(pollingTimer)
isShowBindingModal.value = false
}
</script>
<template>
<div class="mx-auto relative">
<!-- Banner Section -->
<div class="banner-content h-32 bg-blue bg-cover bg-center" :style="bannerStyle" />
<!-- User Info Section -->
<div class="info-content mt-[-50px] p-5">
<div class="edit-info-content flex items-center">
<!-- Avatar -->
<div
class="mc-head mr-5 h-20 w-20 rounded-full bg-white shadow-lg flex items-center justify-center"
>
<div class="mc-head-inner h-18 w-18 m-1 rounded-full bg-blue-200">
<client-only>
<img
class="head-img m-1 h-16 w-16 rounded-full bg-white"
:src="userStore?.userInfo?.avatar"
alt="User Avatar"
/>
</client-only>
<!-- {{ userStore.userInfo.avatar }} -->
</div>
</div>
<!-- Edit Info Button -->
<div
class="edit-info mr-2 cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
@click="onEditInfo()"
>
编辑资料
</div>
<!-- Real Name Verification -->
<div
v-if="userInfo.name"
class="edit-info rounded-full bg-white px-5 py-2 shadow-md"
>
已经实名
</div>
<div
v-else
class="edit-info cursor-pointer rounded-full bg-white px-5 py-2 shadow-md"
@click="onAuth()"
>
去实名
</div>
<div>
<n-popconfirm
class="bg-white ml-2"
positive-text="复制邀请码"
negative-text="查看列表"
:show-icon="false"
@positive-click="handlePositiveClick"
@negative-click="handleNegativeClick"
>
<template #trigger>
<n-button class="ml-2 rounded-lg" type="info" round>获取邀请码</n-button>
</template>
邀请码: {{ invitationCode }}
</n-popconfirm>
</div>
<div class="ml-4 cursor-pointer" @click="toWallet()">
<img src="@/assets/img/wallet.png" alt="">
</div>
<div v-if="zfbStatus === '1'" class="bg-[#3875f6] rounded-full px-4 py-2">
已绑定支付宝
</div>
<div v-else @click="showBinding" class="text-xs px-3 py-2 border border-solid border-[#ccc] rounded-full ml-4 bg-white cursor-pointer">
绑定支付宝
</div>
</div>
<!-- User Details -->
<div class="user-info mt-4">
<div v-if="userStore.userInfo" class="nickname text-xl font-bold">
{{ userStore.userInfo.nickName }}
<!-- {{ userInfo.nickName }} -->
</div>
<div v-if="userStore.userInfo.brief" class="info-desc mt-1 text-sm text-gray-700">
{{ userStore.userInfo.brief }}
</div>
<div class="production-state mt-4 flex text-sm text-gray-700">
<div
class="production-state-item mr-5 cursor-pointer"
@click="showLike('like')"
>
<span class="production-state-number font-bold">{{
selectUserInfo.bean ? selectUserInfo.bean : 0
}}</span>
粉丝
</div>
<div
class="production-state-item mr-5 cursor-pointer"
@click="showLike('attention')"
>
<span class="production-state-number font-bold">{{
selectUserInfo.attention ? selectUserInfo.attention : 0
}}</span>
关注
</div>
<div class="production-state-item mr-5">
<span class="production-state-number font-bold">{{
selectUserInfo.download ? selectUserInfo.download : 0
}}</span>
作品被使用次数
</div>
<div class="production-state-item">
<span class="production-state-number font-bold">{{
selectUserInfo.likeCount ? selectUserInfo.likeCount : 0
}}</span>
作品被点赞次数
</div>
</div>
</div>
</div>
<div class="mc-tabs flex px-5 pb-3" style="border-bottom: 1px solid #e0e0e0">
<div
v-for="(item, index) in stateList"
:key="index"
class="mc-tabs-btn mr-2 cursor-pointer rounded-full px-5 py-1"
:class="{
'bg-black text-white': currentState === item.id,
'bg-white text-black': currentState !== item.id,
}"
@click="changeTabs(item.id)"
>
{{ item.title }}
</div>
</div>
<div class="select-content mt-4 flex items-center justify-between px-5">
<div class="flex items-center rounded-full bg-gray-100">
<div
v-for="(item, index) in typeList"
:key="index"
class="m-1 mr-2 cursor-pointer rounded-full px-4 py-1 text-sm"
:class="{
'bg-white': item.id === currentType,
}"
@click="changeType(item.id)"
>
{{ item.title }}
</div>
</div>
<!-- Published Works Filters -->
<div v-if="currentState === 'mallProduct'" class="flex items-center">
<div>
<n-select
v-model:value="publishParams.status"
:options="statusOptions"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择"
style="width: 180px"
class="mr-2"
@update:value="changeStatus"
/>
</div>
<div>
<n-select
v-model:value="publishParams.orderByColumn"
:options="orderOptions"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择"
class="mr-2 w-28"
@update:value="changeOrder"
/>
</div>
<div>
<n-date-picker
v-model:value="publishParams.date"
type="daterange"
style="width: 230px"
format="yyyy-MM-dd"
value-format="yyyy.MM.dd"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:on-clear="onClearDate"
clearable
@update:value="changeDate"
/>
</div>
</div>
<!-- Liked Works Filters -->
<div v-if="currentState === 'like'" class="flex items-center">
<NSelect
v-model:value="likesParams.orderByColumn"
:options="orderOptions"
label-field="dictLabel"
value-field="dictValue"
placeholder="请选择"
class="w-32"
@update:value="changeLikeOrder"
/>
</div>
</div>
<NConfigProvider>
<NMessageProvider>
<Authentication ref="authenticationRef" />
<EditUserInfo ref="editUserInfoRef" />
</NMessageProvider>
</NConfigProvider>
<!-- Dialog Components -->
<!-- grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4 p-4 -->
<div class="login-content my-4 grid xl:grid-cols-4 2xl:grid-cols-5 gap-4 px-5">
<PersonalCenterCard
v-for="(item, index) in dataList"
:key="index"
:item="item"
:current-type="currentType"
:current-state="currentState"
@toped-refresh="topedRefresh"
/>
</div>
<div ref="loadingTrigger" class="h-10">
<div v-if="loading" class="text-center text-gray-500">加载中...</div>
<div v-if="finished" class="text-center text-gray-500">没有更多数据了</div>
</div>
<div class="fan-centent" v-if="isShowFan">
<div
class="w-[550px] h-[calc(100vh-100px)] max-h-[700px] m-auto py-0 px-8 pb-[43px] bg-[#fff] rounded-lg relative"
>
<n-icon
size="20"
class="mr-2 cursor-pointer absolute top-4 right-2"
@click="closefanList"
>
<Close />
</n-icon>
<n-tabs
v-model:value="activeTab"
default-value="like"
justify-content="space-evenly"
type="line"
>
<n-tab-pane name="like" :tab="`粉丝${selectUserInfo.bean || 0}`">
<n-infinite-scroll
style="height: calc(100vh - 200px); padding: 0 10px"
:distance="10"
@load="getAttentionList"
>
<!-- <div class="overflow-y-auto h-[calc(100vh-200px)] px-2"> -->
<div
v-for="(item, index) in attentionList"
:key="index"
class="flex justify-between items-center p-2"
>
<div class="flex items-center">
<img
:src="item.avatar || ''"
alt=""
class="w-14 h-14 rounded-full mr-2"
/>
{{ item.nickName }}
</div>
<div
class="bg-[#f4f5f9] px-4 py-2 rounded-full cursor-pointer"
@click="onAttention(item)"
>
{{ item.attention ? "已关注" : "未关注" }}
</div>
</div>
<div
v-if="attentionList.length === 0"
class="w-full text-center text-gray-500 font-bold mt-2"
>
暂无数据
</div>
<!-- </div> -->
</n-infinite-scroll>
</n-tab-pane>
<n-tab-pane name="attention" :tab="`关注${selectUserInfo.attention || 0}`">
<!-- <div class="overflow-y-auto h-[calc(100vh-200px)] px-2"> -->
<n-infinite-scroll
style="height: calc(100vh - 200px); padding: 0 10px"
:distance="10"
@load="getLikeList"
>
<div
v-for="(item, index) in likeList"
:key="index"
class="flex justify-between items-center p-2"
>
<div class="flex items-center">
<img
:src="item.avatar || ''"
alt=""
class="w-14 h-14 rounded-full mr-2"
/>
{{ item.nickName }}
</div>
<div
class="bg-[#f4f5f9] px-4 py-2 rounded-full cursor-pointer"
@click="onAttention(item)"
>
{{ item.attention ? "已关注" : "未关注" }}
</div>
</div>
<div
v-if="attentionList.length === 0"
class="w-full text-center text-gray-500 font-bold mt-2"
>
暂无数据
</div>
<!-- </div> -->
</n-infinite-scroll>
</n-tab-pane>
</n-tabs>
</div>
</div>
<n-modal v-model:show="showInvitationModal">
<n-card
style="width: 600px"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<!-- <template #header-extra>
</template> -->
<div class="py-4 flex justify-between">
<div class="text-xl font-bold">邀请列表</div>
<div class="text-sm text-gray-600">
总收益: {{ invitationList.totalAmount }}
</div>
</div>
<div class="rounded-lg">
<div class="flex w-[100%]">
<div class="w-[180px] flex items-center py-2">头像</div>
<div class="w-[180px] flex items-center py-2">邀请人购买数量</div>
<div class="w-[180px] flex items-center py-2">邀请人名字</div>
</div>
<div class="max-h-[500px] overflow-y-auto">
<div
class="flex w-[100%] hover:bg-[#f3f3f3] px-2"
v-for="(item, index) in invitationList.earningsDisplayList"
:key="index"
>
<div class="w-[180px] flex items-center py-2">
<img class="w-10 h-10 rounded-full" :src="item.user.avatar" alt="" />
</div>
<div class="w-[180px] flex items-center py-2">{{ item.count }}</div>
<div class="w-[180px] flex items-center py-2">{{ item.user.userName }}</div>
</div>
</div>
</div>
</n-card>
</n-modal>
<div v-if="isShowBindingModal" class="fan-centent">
<div class="relative flex flex-col items-center">
<div class="bg-[#000] py-10 px-20 flex flex-col items-center justify-center rounded-lg">
<div class="text-xl text-white mb-4">
扫码绑定
</div>
<n-qr-code :value="qrUrl" :size="150" style="padding: 0;" />
<div class="text-base text-white mt-4">
</div>
</div>
<n-icon
size="30"
class="cursor-pointer mt-4 text-white"
@click="closeBindingModal"
>
<Close />
</n-icon>
</div>
</div>
</div>
</template>
<style scoped>
.fan-centent {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.4);
}
.edit-info {
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
}
</style>