Compare commits
31 Commits
596de7b73c
...
ada774a142
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada774a142 | ||
|
|
cbb0cf0ac7 | ||
|
|
87784a78c6 | ||
|
|
b6d55e7698 | ||
|
|
f295b53f72 | ||
|
|
6d42ebb513 | ||
|
|
b3f7609494 | ||
|
|
02bfefa58c | ||
|
|
d9e14b09c6 | ||
|
|
7ef0e46cc8 | ||
|
|
da6fb97da4 | ||
|
|
f5534632ff | ||
|
|
8dfe94453b | ||
|
|
f8f7d80922 | ||
|
|
553d546c6e | ||
|
|
1138e1f0de | ||
|
|
07a568c878 | ||
|
|
0270ba084f | ||
|
|
1b7dc31916 | ||
|
|
b778216afb | ||
|
|
b349e606fc | ||
|
|
1107d296ce | ||
|
|
c03a699ab9 | ||
|
|
48c7cebac1 | ||
|
|
0143a08af7 | ||
|
|
7a5afff119 | ||
|
|
50728c0211 | ||
|
|
30c62c1b10 | ||
|
|
84ae386223 | ||
|
|
d40cc64fa8 | ||
|
|
e432da9c75 |
42
api/topic.js
Normal file
42
api/topic.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { request } from "@/utils/request.js";
|
||||||
|
|
||||||
|
// 发布主题
|
||||||
|
export const releaseTopic = async (data) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/rating/topic/release",
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取主题分类列表
|
||||||
|
export const fetchTopicCategories = async (data) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/rating/topic/category/list",
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据分类ID获取主题列表
|
||||||
|
export const fetchTopicList = async (categoryId, page = 1) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/rating/topic/list/${categoryId}?page=${page}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取主题详情
|
||||||
|
export const fetchTopicDetail = async (topicId) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/rating/topic/detail/${topicId}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取主题下的评分项
|
||||||
|
export const fetchTopicRatingItems = async (topicId, page = 1) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/rating/topic/item/list/${topicId}?page=${page}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
45
api/topicItem.js
Normal file
45
api/topicItem.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { request } from "@/utils/request.js";
|
||||||
|
|
||||||
|
// 打分
|
||||||
|
export const topicItemScore = async (data) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/rating/topic/item/score",
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评论
|
||||||
|
export const topicItemComment = async (data) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/rating/topic/comment/item",
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评论点赞
|
||||||
|
// data = { commentId: 'xxx' }
|
||||||
|
export const topicItemCommentLike = async (data) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/rating/topic/comment/item/like",
|
||||||
|
method: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取评论列表
|
||||||
|
export const topicItemCommentList = async (topicId, itemId, page = 1, orderBy = 1) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/rating/topic/comment/item/list?topicId=${topicId}&itemId=${itemId}&page=${page}&orderBy=${orderBy}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取评分项详情
|
||||||
|
export const fetchTopicRatingItems = async (itemId) => {
|
||||||
|
return request({
|
||||||
|
url: `/api/rating/topic/item/detail/${itemId}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
96
components/ActionBadge/ActionBadge.vue
Normal file
96
components/ActionBadge/ActionBadge.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<view class="action-badge" :class="badgeClass" v-if="action">
|
||||||
|
<text class="badge-icon">{{ badgeIcon }}</text>
|
||||||
|
<text class="badge-text">{{ action }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const badgeClass = computed(() => {
|
||||||
|
switch (props.action) {
|
||||||
|
case '夯': return 'badge-god';
|
||||||
|
case '顶级': return 'badge-good';
|
||||||
|
case '人上人': return 'badge-ok';
|
||||||
|
case 'NPC': return 'badge-bad';
|
||||||
|
case '拉': return 'badge-terrible';
|
||||||
|
default: return 'badge-default';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const badgeIcon = computed(() => {
|
||||||
|
switch (props.action) {
|
||||||
|
case '夯': return '👑';
|
||||||
|
case '顶级': return '🔥';
|
||||||
|
case '人上人': return '🙂';
|
||||||
|
case 'NPC': return '😐';
|
||||||
|
case '拉': return '👎';
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.action-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-icon {
|
||||||
|
margin-right: 6rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 夯 - 金色/黄色 */
|
||||||
|
.badge-god {
|
||||||
|
background: linear-gradient(135deg, #fff8e1, #ffe082);
|
||||||
|
color: #b07d00;
|
||||||
|
border: 1rpx solid #ffecb3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶级 - 红色/橙色 */
|
||||||
|
.badge-good {
|
||||||
|
background: linear-gradient(135deg, #ffebee, #ffcdd2);
|
||||||
|
color: #c62828;
|
||||||
|
border: 1rpx solid #ffcdd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 人上人 - 蓝色/绿色 */
|
||||||
|
.badge-ok {
|
||||||
|
background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
|
||||||
|
color: #2e7d32;
|
||||||
|
border: 1rpx solid #c8e6c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NPC - 灰色 */
|
||||||
|
.badge-bad {
|
||||||
|
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
|
||||||
|
color: #616161;
|
||||||
|
border: 1rpx solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拉 - 深灰/暗红 */
|
||||||
|
.badge-terrible {
|
||||||
|
background: linear-gradient(135deg, #eceff1, #cfd8dc);
|
||||||
|
color: #455a64;
|
||||||
|
border: 1rpx solid #cfd8dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-default {
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
168
components/AttitudePanel/AttitudePanel.vue
Normal file
168
components/AttitudePanel/AttitudePanel.vue
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<view class="attitude-panel card">
|
||||||
|
<text class="section-title">你的态度</text>
|
||||||
|
<view class="action-group">
|
||||||
|
<view
|
||||||
|
class="action-item"
|
||||||
|
v-for="(action, index) in actions"
|
||||||
|
:key="index"
|
||||||
|
:class="{ active: currentAction === action.label }"
|
||||||
|
@tap="handleAction(action)"
|
||||||
|
>
|
||||||
|
<view class="emoji-wrap">
|
||||||
|
<text class="emoji">{{ action.emoji }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-name">{{ action.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="quick-comment">
|
||||||
|
<input
|
||||||
|
class="comment-input"
|
||||||
|
type="text"
|
||||||
|
v-model="commentText"
|
||||||
|
placeholder="说点什么吧..."
|
||||||
|
placeholder-class="input-placeholder"
|
||||||
|
/>
|
||||||
|
<button class="send-btn" @tap="handleSend">发送</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
initialAction: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['action-change', 'send-comment']);
|
||||||
|
|
||||||
|
const currentAction = ref(props.initialAction);
|
||||||
|
|
||||||
|
// 监听父组件传入的 initialAction 的变化
|
||||||
|
watch(() => props.initialAction, (newVal) => {
|
||||||
|
currentAction.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentText = ref('');
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{ label: '拉', value: 'terrible', emoji: '👎', score: 2, scoreType:1 },
|
||||||
|
{ label: 'NPC', value: 'bad', emoji: '😐', score: 4, scoreType: 2},
|
||||||
|
{ label: '人上人', value: 'ok', emoji: '🙂', score: 6, scoreType: 3},
|
||||||
|
{ label: '顶级', value: 'good', emoji: '🔥', score: 8, scoreType: 4},
|
||||||
|
{ label: '夯', value: 'god', emoji: '👑', score: 10, scoreType: 5 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleAction = (action) => {
|
||||||
|
emit('action-change', action);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSend = () => {
|
||||||
|
if (!commentText.value.trim()) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入评论内容',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit('send-comment', {
|
||||||
|
action: currentAction.value,
|
||||||
|
content: commentText.value
|
||||||
|
});
|
||||||
|
commentText.value = ''; // 发送后清空
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
display: block;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-wrap {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
font-size: 56rpx;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-name {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item.active .emoji {
|
||||||
|
filter: grayscale(0%);
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item.active .action-name {
|
||||||
|
color: #2953ff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-comment {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f0f2f7;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 8rpx 8rpx 8rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 72rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
background-color: #4d44f1;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
line-height: 64rpx;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
197
components/CommentItem/CommentItem.vue
Normal file
197
components/CommentItem/CommentItem.vue
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<view class="comment-item">
|
||||||
|
<image class="c-avatar" :src="comment.avatar" mode="aspectFill" />
|
||||||
|
<view class="c-content-wrap">
|
||||||
|
<view class="c-header">
|
||||||
|
<view class="c-user-info">
|
||||||
|
<text class="c-name">{{ comment.name }}</text>
|
||||||
|
<!-- 用户态度徽章 -->
|
||||||
|
<ActionBadge v-if="comment.userAction" :action="comment.userAction" />
|
||||||
|
<view class="c-badge" v-if="comment.badge">
|
||||||
|
<text class="b-icon">{{ comment.badgeIcon }}</text>
|
||||||
|
<text class="b-text">{{ comment.badge }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="c-like" @tap="handleLike">
|
||||||
|
<text class="like-icon" :class="{ 'is-liked': isLiked }"></text>
|
||||||
|
<text class="like-count" :class="{ 'liked-text': isLiked }">{{ currentLikes }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="c-time">{{ comment.time }}</text>
|
||||||
|
<text class="c-text">{{ comment.content }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import ActionBadge from "@/components/ActionBadge/ActionBadge.vue";
|
||||||
|
import { topicItemCommentLike } from "@/api/topicItem";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
comment: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLiked = ref(props.comment.isLiked || false);
|
||||||
|
const currentLikes = ref(props.comment.likes || 0);
|
||||||
|
let isLiking = false; // 防抖标志
|
||||||
|
|
||||||
|
watch(() => props.comment.likes, (newVal) => {
|
||||||
|
currentLikes.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.comment.isLiked, (newVal) => {
|
||||||
|
isLiked.value = newVal || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLike = async () => {
|
||||||
|
if (isLiking) return;
|
||||||
|
isLiking = true;
|
||||||
|
|
||||||
|
// 乐观更新 UI
|
||||||
|
isLiked.value = !isLiked.value;
|
||||||
|
if (isLiked.value) {
|
||||||
|
currentLikes.value += 1;
|
||||||
|
uni.vibrateShort({ type: 'light' });
|
||||||
|
} else {
|
||||||
|
currentLikes.value -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用点赞接口 (点赞和取消点赞为同一个接口)
|
||||||
|
await topicItemCommentLike({ commentId: props.comment.id });
|
||||||
|
} catch (error) {
|
||||||
|
// 接口调用失败,回滚 UI 状态
|
||||||
|
isLiked.value = !isLiked.value;
|
||||||
|
if (isLiked.value) {
|
||||||
|
currentLikes.value += 1;
|
||||||
|
} else {
|
||||||
|
currentLikes.value -= 1;
|
||||||
|
}
|
||||||
|
uni.showToast({ title: '操作失败', icon: 'none' });
|
||||||
|
} finally {
|
||||||
|
isLiking = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.comment-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 32rpx 0;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item:active {
|
||||||
|
background-color: #fafbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 104rpx; /* 头像宽度(80) + 间距(24) */
|
||||||
|
right: 0;
|
||||||
|
height: 2rpx;
|
||||||
|
background-color: #f0f2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-avatar {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 2rpx solid #f0f2f7;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-content-wrap {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700; /* 减轻字重,显得更精致 */
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(90deg, #f5f6fa, #f0f2f7);
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-icon { font-size: 20rpx; margin-right: 6rpx; }
|
||||||
|
.b-text { font-size: 20rpx; color: #666; font-weight: bold; }
|
||||||
|
|
||||||
|
.c-like {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8rpx;
|
||||||
|
margin: -8rpx; /* 增加点击区域 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3'%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-icon.is-liked {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff4d4f' stroke='%23ff4d4f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3'%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.liked-text {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-like:active .like-icon {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-time {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #a1a1a1;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #2c2c2c;
|
||||||
|
line-height: 1.65;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
176
components/RatingCard/RatingCard.vue
Normal file
176
components/RatingCard/RatingCard.vue
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<view class="rating-card" @tap="handleCardTap">
|
||||||
|
<view class="card-header">
|
||||||
|
<image class="item-avatar" :src="FILE_BASE_URL + item.avatarUrl" mode="aspectFill" />
|
||||||
|
<text class="item-name">{{ item.name }}</text>
|
||||||
|
<view class="rank-tag" v-if="item.rank <= 3">NO.{{ item.rank }}</view>
|
||||||
|
<view class="flex-spacer"></view>
|
||||||
|
<text class="item-score">{{ item.scoreAvg }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="quote-box" v-if="item.quote">
|
||||||
|
<text class="quote-text">“{{ item.quote }}”</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-group">
|
||||||
|
<view
|
||||||
|
class="action-item"
|
||||||
|
v-for="(action, aIndex) in actions"
|
||||||
|
:key="aIndex"
|
||||||
|
:class="{ active: item.userAction === action.label }"
|
||||||
|
@tap.stop="handleAction(action)"
|
||||||
|
>
|
||||||
|
<view class="action-icon-wrap" :class="'icon-' + action.value">
|
||||||
|
<text class="emoji">{{ action.emoji }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="action-name">{{ action.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{ label: '拉', value: 'terrible', emoji: '👎', score: 2, scoreType:1 },
|
||||||
|
{ label: 'NPC', value: 'bad', emoji: '😐', score: 4, scoreType: 2},
|
||||||
|
{ label: '人上人', value: 'ok', emoji: '🙂', score: 6, scoreType: 3},
|
||||||
|
{ label: '顶级', value: 'good', emoji: '🔥', score: 8, scoreType: 4},
|
||||||
|
{ label: '夯', value: 'god', emoji: '👑', score: 10, scoreType: 5 }
|
||||||
|
];
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['item-click', 'action-click']);
|
||||||
|
|
||||||
|
const handleCardTap = () => {
|
||||||
|
emit('item-click', props.item.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAction = async (action) => {
|
||||||
|
emit('action-click', props.item, action);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.rating-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
width: 90rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-tag {
|
||||||
|
background-color: #eef1ff;
|
||||||
|
color: #2953ff;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-score {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #2953ff;
|
||||||
|
font-family: 'DIN Alternate', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-box {
|
||||||
|
background-color: #f9fafe;
|
||||||
|
border-radius: 0 20rpx 20rpx 20rpx;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
margin-bottom: 36rpx;
|
||||||
|
border-left: 6rpx solid #c9d4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #555;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon-wrap {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
font-size: 44rpx;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-name {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item.active .action-icon-wrap {
|
||||||
|
background-color: #eef1ff;
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: 0 8rpx 16rpx rgba(41, 83, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item.active .emoji {
|
||||||
|
filter: grayscale(0%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item.active .action-name {
|
||||||
|
color: #2953ff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
186
components/SuccessPopup/SuccessPopup.vue
Normal file
186
components/SuccessPopup/SuccessPopup.vue
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
<view class="success-popup" v-if="visible" @touchmove.stop.prevent>
|
||||||
|
<view class="mask" :class="{ 'mask-show': showAnim }" @tap="close"></view>
|
||||||
|
<view class="popup-content" :class="{ 'content-show': showAnim }">
|
||||||
|
<view class="icon-wrap">
|
||||||
|
<text class="emoji">{{ actionData?.emoji || '✨' }}</text>
|
||||||
|
<!-- 环形扩散特效 -->
|
||||||
|
<view class="ring ring-1"></view>
|
||||||
|
<view class="ring ring-2"></view>
|
||||||
|
<!-- 星星点缀 -->
|
||||||
|
<view class="sparkle s-1">✨</view>
|
||||||
|
<view class="sparkle s-2">✨</view>
|
||||||
|
<view class="sparkle s-3">✨</view>
|
||||||
|
</view>
|
||||||
|
<text class="title">{{ type === 'comment' ? '评论成功' : '评分成功' }}</text>
|
||||||
|
<text class="subtitle" v-if="type === 'comment'">{{ actionData?.label || '你的锐评已发布' }}</text>
|
||||||
|
<text class="subtitle" v-else>你已将TA评价为「{{ actionData?.label || '...' }}」</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const showAnim = ref(false);
|
||||||
|
const actionData = ref(null);
|
||||||
|
const type = ref('rating');
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
const show = (action, popupType = 'rating') => {
|
||||||
|
actionData.value = action;
|
||||||
|
type.value = popupType;
|
||||||
|
visible.value = true;
|
||||||
|
|
||||||
|
// 留一点时间让DOM渲染,然后添加动画类
|
||||||
|
setTimeout(() => {
|
||||||
|
showAnim.value = true;
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
// 2秒后自动关闭
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
close();
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
showAnim.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false;
|
||||||
|
}, 300); // 等待淡出动画结束
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
close
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.success-popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask.mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content {
|
||||||
|
position: relative;
|
||||||
|
width: 460rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 64rpx 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(20rpx);
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content.content-show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
font-size: 100rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
animation: emojiBounce 1.2s infinite alternate ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% { transform: translateY(0) scale(1); }
|
||||||
|
100% { transform: translateY(-12rpx) scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #4d44f1;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-1 {
|
||||||
|
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-2 {
|
||||||
|
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite 0.8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0% { width: 100rpx; height: 100rpx; opacity: 0.8; }
|
||||||
|
100% { width: 260rpx; height: 260rpx; opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkle {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 32rpx;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-1 { top: -10rpx; right: -20rpx; animation: popStar 1.4s ease-out infinite 0.1s; }
|
||||||
|
.s-2 { bottom: 10rpx; left: -30rpx; animation: popStar 1.4s ease-out infinite 0.6s; font-size: 24rpx; }
|
||||||
|
.s-3 { top: 60rpx; left: -40rpx; animation: popStar 1.4s ease-out infinite 1.1s; font-size: 28rpx; }
|
||||||
|
|
||||||
|
@keyframes popStar {
|
||||||
|
0% { transform: scale(0) rotate(0deg); opacity: 0; }
|
||||||
|
30% { transform: scale(1.2) rotate(45deg); opacity: 1; }
|
||||||
|
80% { transform: scale(0) rotate(90deg); opacity: 0; }
|
||||||
|
100% { transform: scale(0) rotate(90deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #111;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
247
components/TopicCard/TopicCard.vue
Normal file
247
components/TopicCard/TopicCard.vue
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<template>
|
||||||
|
<view class="topic-card">
|
||||||
|
<!-- 卡片头部:标题与统计信息 -->
|
||||||
|
<view class="topic-header">
|
||||||
|
<text class="topic-title">{{ topic.title }}</text>
|
||||||
|
<view class="topic-stats">
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-icon icon-group"></text>
|
||||||
|
<text>{{ topic.participantCount }}人参与</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item heat">
|
||||||
|
<text class="stat-icon icon-fire"></text>
|
||||||
|
<text>热度 {{ topic.hotScore }} w</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 排行榜列表 -->
|
||||||
|
<view class="rank-list">
|
||||||
|
<view
|
||||||
|
class="rank-item"
|
||||||
|
v-for="(item, index) in topic.items"
|
||||||
|
:key="item.id"
|
||||||
|
@tap="handleItemTap(item.id)"
|
||||||
|
>
|
||||||
|
<text class="rank-num" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
|
||||||
|
<image class="item-avatar" :src="FILE_BASE_URL + item.avatarUrl" mode="aspectFill"></image>
|
||||||
|
<text class="item-name">{{ item.name }}</text>
|
||||||
|
<text class="item-score" :class="'score-' + (index + 1)">{{ item.scoreAvg }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- AI 总结 -->
|
||||||
|
<view class="ai-summary" v-if="topic.aiSummary">
|
||||||
|
<text class="ai-icon icon-robot"></text>
|
||||||
|
<view class="summary-text">
|
||||||
|
<text class="summary-label">AI总结:</text>
|
||||||
|
<text class="summary-content">{{ topic.aiSummary }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 标签列表 -->
|
||||||
|
<view class="tag-list">
|
||||||
|
<view class="tag" v-for="(tag, tIndex) in topic.tags" :key="tIndex">
|
||||||
|
<text>#{{ tag }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
topic: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['item-click']);
|
||||||
|
|
||||||
|
const handleItemTap = (itemId) => {
|
||||||
|
emit('item-click', props.topic.id, itemId);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.topic-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 36rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.04);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-card:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-header {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-title {
|
||||||
|
font-size: 38rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #222;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
display: block;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
display: inline-block;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-group {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999999'%3E%3Cpath d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-fire {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff4d4f'%3E%3Cpath d='M19.48 13.03c-.02-.19-.13-.36-.29-.46-.17-.11-.38-.13-.56-.05-.98.42-2.12.3-2.92-.31-1.07-.81-1.55-2.22-1.28-3.75.12-.66.1-1.32-.06-1.97-.24-.95-1.1-1.6-2.09-1.58-1 .02-1.83.74-2 1.72-.25 1.55-.95 2.94-2 4.02-1.38 1.41-2.05 3.39-1.85 5.4.2 2.05 1.42 3.86 3.25 4.82 1.37.72 2.94 1.01 4.5.83 2.74-.32 5.06-2.28 5.86-4.95.23-.75.29-1.52.19-2.28-.06-.5-.21-.99-.41-1.45l-.34-.99zm-4.98 6.47c-1.37 0-2.5-1.13-2.5-2.5s1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5-1.13 2.5-2.5 2.5z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-list {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
background-color: #fcfcfd;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
border-bottom: 2rpx dashed #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-num {
|
||||||
|
width: 44rpx;
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-1 {
|
||||||
|
color: #2953ff;
|
||||||
|
font-size: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-2, .rank-3 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border: 4rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-score {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-1 {
|
||||||
|
color: #2953ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-2, .score-3 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fbfaff, #f4efff);
|
||||||
|
border: 2rpx solid #e0d6ff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(123, 70, 241, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237b46f1'%3E%3Cpath d='M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h5a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V5.73A2 2 0 0 1 10 4a2 2 0 0 1 2-2zm-3 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm6 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
color: #7b46f1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-content {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background-color: #f4eaff;
|
||||||
|
padding: 6rpx 24rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag text {
|
||||||
|
color: #9d5bfe;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
26
pages.json
26
pages.json
@@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "祝福 壁纸 头像",
|
"navigationBarTitleText": "全民评分",
|
||||||
"enablePullDownRefresh": true,
|
"enablePullDownRefresh": true,
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "custom",
|
||||||
"backgroundColor": "#FFFFFF"
|
"backgroundColor": "#FFFFFF"
|
||||||
@@ -42,9 +42,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/creation/index",
|
"path": "pages/release/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "创作中心",
|
"navigationBarTitleText": "发布中心",
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "custom",
|
||||||
"backgroundColor": "#fbfbfb"
|
"backgroundColor": "#fbfbfb"
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,22 @@
|
|||||||
"enablePullDownRefresh": false,
|
"enablePullDownRefresh": false,
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/rating/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "全民评分",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/rating/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "人物详情",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
@@ -88,8 +104,8 @@
|
|||||||
"selectedIconPath": "static/images/tabBar/home_s.png"
|
"selectedIconPath": "static/images/tabBar/home_s.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "创作",
|
"text": "发布",
|
||||||
"pagePath": "pages/creation/index",
|
"pagePath": "pages/release/index",
|
||||||
"iconPath": "static/images/tabBar/creation.png",
|
"iconPath": "static/images/tabBar/creation.png",
|
||||||
"selectedIconPath": "static/images/tabBar/creation_s.png"
|
"selectedIconPath": "static/images/tabBar/creation_s.png"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,66 +3,46 @@
|
|||||||
<!-- 顶部固定栏 (使用 padding-top 避开状态栏) -->
|
<!-- 顶部固定栏 (使用 padding-top 避开状态栏) -->
|
||||||
<view class="header-section" :style="{ paddingTop: statusBarHeight + 'px' }">
|
<view class="header-section" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
<view class="header-content">
|
<view class="header-content">
|
||||||
<view class="header-texts">
|
|
||||||
<text class="title">全民评分</text>
|
|
||||||
<text class="subtitle">看看全国网友怎么评分</text>
|
|
||||||
</view>
|
|
||||||
<image
|
<image
|
||||||
class="user-avatar"
|
class="user-avatar"
|
||||||
:src="userInfo.avatarUrl || '/static/images/default-avatar.png'"
|
:src="userInfo.avatarUrl || '/static/images/default-avatar.png'"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
@tap="handleLogin"
|
@tap="handleLogin"
|
||||||
/>
|
/>
|
||||||
|
<view class="header-texts">
|
||||||
|
<text class="title">全民评分</text>
|
||||||
|
<text class="subtitle">看看全国网友怎么评分</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 顶部切换 tab -->
|
||||||
|
<scroll-view class="category-tabs" scroll-x :show-scrollbar="false">
|
||||||
|
<view class="tabs-wrapper">
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: currentCategoryId === category.id }"
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
@tap="switchCategory(category.id)"
|
||||||
|
>
|
||||||
|
<text class="tab-text">{{ category.title }}</text>
|
||||||
|
<view class="tab-line" v-if="currentCategoryId === category.id"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 占位,防止固定头部遮挡内容 -->
|
<!-- 占位,防止固定头部遮挡内容 (增加 tab 的高度) -->
|
||||||
<view :style="{ height: statusBarHeight + 60 + 'px' }"></view>
|
<view :style="{ height: statusBarHeight + 104 + 'px' }"></view>
|
||||||
|
|
||||||
<!-- 列表内容 -->
|
<!-- 列表内容 -->
|
||||||
<view class="topic-list">
|
<view class="topic-list">
|
||||||
<view class="topic-card" v-for="topic in topicList" :key="topic.id">
|
<TopicCard
|
||||||
<!-- 卡片头部:标题与统计信息 -->
|
v-for="topic in topicList"
|
||||||
<view class="topic-header">
|
:key="topic.id"
|
||||||
<text class="topic-title">{{ topic.title }}</text>
|
:topic="topic"
|
||||||
<view class="topic-stats">
|
@item-click="goToRating"
|
||||||
<view class="stat-item">
|
/>
|
||||||
<text class="stat-icon icon-group"></text>
|
|
||||||
<text>{{ topic.participants }}人参与</text>
|
|
||||||
</view>
|
|
||||||
<view class="stat-item heat">
|
|
||||||
<text class="stat-icon icon-fire"></text>
|
|
||||||
<text>热度 {{ topic.heat }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 排行榜列表 -->
|
|
||||||
<view class="rank-list">
|
|
||||||
<view class="rank-item" v-for="(item, index) in topic.items" :key="item.id">
|
|
||||||
<text class="rank-num" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
|
|
||||||
<image class="item-avatar" :src="item.avatar" mode="aspectFill"></image>
|
|
||||||
<text class="item-name">{{ item.name }}</text>
|
|
||||||
<text class="item-score" :class="'score-' + (index + 1)">{{ item.score }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- AI 总结 -->
|
|
||||||
<view class="ai-summary">
|
|
||||||
<text class="ai-icon icon-robot"></text>
|
|
||||||
<view class="summary-text">
|
|
||||||
<text class="summary-label">AI总结:</text>
|
|
||||||
<text class="summary-content">{{ topic.aiSummary }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 标签列表 -->
|
|
||||||
<view class="tag-list">
|
|
||||||
<view class="tag" v-for="(tag, tIndex) in topic.tags" :key="tIndex">
|
|
||||||
<text>#{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 加载更多 -->
|
<!-- 加载更多 -->
|
||||||
<view class="load-more">
|
<view class="load-more">
|
||||||
@@ -88,8 +68,10 @@ import {
|
|||||||
} from "@dcloudio/uni-app";
|
} from "@dcloudio/uni-app";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { getShareReward } from "@/api/system";
|
import { getShareReward } from "@/api/system";
|
||||||
|
import { fetchTopicList, fetchTopicCategories } from "@/api/topic";
|
||||||
import { getShareToken, saveViewRequest } from "@/utils/common.js";
|
import { getShareToken, saveViewRequest } from "@/utils/common.js";
|
||||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||||
|
import TopicCard from "@/components/TopicCard/TopicCard.vue";
|
||||||
import adManager from "@/utils/adManager.js";
|
import adManager from "@/utils/adManager.js";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -98,6 +80,68 @@ const userInfo = computed(() => userStore?.userInfo || {});
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const hasMore = ref(true);
|
const hasMore = ref(true);
|
||||||
|
const currentCategoryId = ref('');
|
||||||
|
const categories = ref([]);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
|
const loadCategories = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchTopicCategories();
|
||||||
|
// 提取真实数据数组
|
||||||
|
const list = res?.data?.list || res?.list || res || [];
|
||||||
|
|
||||||
|
// 增加一个“全部”的默认选项,id 置空
|
||||||
|
categories.value = [{ id: 'recommend', title: '推荐' }, ...list];
|
||||||
|
|
||||||
|
// 初始化选中的ID为“全部”
|
||||||
|
if (!currentCategoryId.value && categories.value.length > 0) {
|
||||||
|
currentCategoryId.value = categories.value[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载初始列表
|
||||||
|
// loadTopicList(currentCategoryId.value, currentPage.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取分类失败:", error);
|
||||||
|
// 兜底数据
|
||||||
|
categories.value = [
|
||||||
|
{ id: '', title: '全部' },
|
||||||
|
{ id: 1, title: '历史' },
|
||||||
|
{ id: 2, title: '数码' },
|
||||||
|
];
|
||||||
|
// 使用兜底也尝试加载一次
|
||||||
|
loadTopicList(currentCategoryId.value, currentPage.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadTopicList = async (categoryId, page = 1) => {
|
||||||
|
if (page === 1) {
|
||||||
|
topicList.value = [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await fetchTopicList(categoryId, page);
|
||||||
|
// 提取真实数据数组
|
||||||
|
const list = res?.list || [];
|
||||||
|
// 合并数据
|
||||||
|
topicList.value = [...topicList.value, ...list];
|
||||||
|
hasMore.value = list.length >= 5;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取主题列表失败:", error);
|
||||||
|
hasMore.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchCategory = (categoryId) => {
|
||||||
|
if (currentCategoryId.value === categoryId) return;
|
||||||
|
currentCategoryId.value = categoryId;
|
||||||
|
|
||||||
|
// 切换分类时重新加载数据
|
||||||
|
currentPage.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
loadTopicList(categoryId, currentPage.value);
|
||||||
|
};
|
||||||
|
|
||||||
const mockData = [
|
const mockData = [
|
||||||
{
|
{
|
||||||
@@ -143,6 +187,12 @@ const mockData = [
|
|||||||
|
|
||||||
const topicList = ref([...mockData]);
|
const topicList = ref([...mockData]);
|
||||||
|
|
||||||
|
const goToRating = (topicId, itemId) => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/rating/index?topicId=${topicId}&itemId=${itemId}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
if (!userInfo.value.nickName) {
|
if (!userInfo.value.nickName) {
|
||||||
uni.$emit("show-login-popup");
|
uni.$emit("show-login-popup");
|
||||||
@@ -155,7 +205,7 @@ const initData = () => {
|
|||||||
if (adManager.isAdShowing) return; // 防止广告页返回时立即重新渲染引发 insertTextView:fail
|
if (adManager.isAdShowing) return; // 防止广告页返回时立即重新渲染引发 insertTextView:fail
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad(async (options) => {
|
||||||
uni.$trackRecord({
|
uni.$trackRecord({
|
||||||
eventName: "index_page_visit",
|
eventName: "index_page_visit",
|
||||||
eventType: `visit`,
|
eventType: `visit`,
|
||||||
@@ -163,6 +213,8 @@ onLoad((options) => {
|
|||||||
if (options.shareToken) {
|
if (options.shareToken) {
|
||||||
saveViewRequest(options.shareToken, "index");
|
saveViewRequest(options.shareToken, "index");
|
||||||
}
|
}
|
||||||
|
await loadCategories(); // 初始化时加载分类
|
||||||
|
await loadTopicList(currentCategoryId.value); // 初始化时加载主题列表
|
||||||
initData();
|
initData();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,26 +230,18 @@ const handleLoginSuccess = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
onPullDownRefresh(() => {
|
onPullDownRefresh(async () => {
|
||||||
setTimeout(() => {
|
currentPage.value = 1;
|
||||||
topicList.value = [...mockData];
|
hasMore.value = true;
|
||||||
hasMore.value = true;
|
await loadTopicList(currentCategoryId.value, currentPage.value);
|
||||||
uni.stopPullDownRefresh();
|
uni.stopPullDownRefresh();
|
||||||
}, 1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 上拉加载更多
|
// 上拉加载更多
|
||||||
onReachBottom(() => {
|
onReachBottom(() => {
|
||||||
if (!hasMore.value || loading.value) return;
|
if (!hasMore.value || loading.value) return;
|
||||||
loading.value = true;
|
currentPage.value += 1;
|
||||||
setTimeout(() => {
|
loadTopicList(currentCategoryId.value, currentPage.value);
|
||||||
const newData = mockData.map(item => ({ ...item, id: item.id + topicList.value.length }));
|
|
||||||
topicList.value.push(...newData);
|
|
||||||
loading.value = false;
|
|
||||||
if (topicList.value.length >= 10) {
|
|
||||||
hasMore.value = false;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onShareAppMessage(async () => {
|
onShareAppMessage(async () => {
|
||||||
@@ -240,7 +284,6 @@ onShareTimeline(async () => {
|
|||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 32rpx;
|
padding: 0 32rpx;
|
||||||
height: 120rpx;
|
height: 120rpx;
|
||||||
@@ -270,165 +313,59 @@ onShareTimeline(async () => {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2rpx solid #fff;
|
border: 2rpx solid #fff;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
margin-right: 24rpx; /* 头像在左边时,增加右侧间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab 样式 */
|
||||||
|
.category-tabs {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 88rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active .tab-text {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-line {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 12rpx;
|
||||||
|
width: 32rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
background-color: #2953ff;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-list {
|
.topic-list {
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-card {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 32rpx;
|
|
||||||
margin-bottom: 32rpx;
|
|
||||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
.topic-header {
|
|
||||||
margin-bottom: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topic-title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #222;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topic-stats {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
margin-right: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-icon {
|
|
||||||
width: 24rpx;
|
|
||||||
height: 24rpx;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
display: inline-block;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-group {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999999'%3E%3Cpath d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-fire {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff4d4f'%3E%3Cpath d='M19.48 13.03c-.02-.19-.13-.36-.29-.46-.17-.11-.38-.13-.56-.05-.98.42-2.12.3-2.92-.31-1.07-.81-1.55-2.22-1.28-3.75.12-.66.1-1.32-.06-1.97-.24-.95-1.1-1.6-2.09-1.58-1 .02-1.83.74-2 1.72-.25 1.55-.95 2.94-2 4.02-1.38 1.41-2.05 3.39-1.85 5.4.2 2.05 1.42 3.86 3.25 4.82 1.37.72 2.94 1.01 4.5.83 2.74-.32 5.06-2.28 5.86-4.95.23-.75.29-1.52.19-2.28-.06-.5-.21-.99-.41-1.45l-.34-.99zm-4.98 6.47c-1.37 0-2.5-1.13-2.5-2.5s1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5-1.13 2.5-2.5 2.5z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-list {
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-num {
|
|
||||||
width: 40rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-1 {
|
|
||||||
color: #2953ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-2, .rank-3 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-avatar {
|
|
||||||
width: 72rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 24rpx;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-name {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-score {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-1 {
|
|
||||||
color: #2953ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-2, .score-3 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ai-summary {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 2rpx solid #e0d6ff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ai-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
margin-top: 4rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234d44f1'%3E%3Cpath d='M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h5a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V5.73A2 2 0 0 1 10 4a2 2 0 0 1 2-2zm-3 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm6 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-text {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-label {
|
|
||||||
color: #4d44f1;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-content {
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
background-color: #f4eaff;
|
|
||||||
padding: 6rpx 20rpx;
|
|
||||||
border-radius: 100rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag text {
|
|
||||||
color: #9d5bfe;
|
|
||||||
font-size: 22rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more {
|
.load-more {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 30rpx 0;
|
padding: 30rpx 0;
|
||||||
|
|||||||
661
pages/rating/detail.vue
Normal file
661
pages/rating/detail.vue
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<!-- 顶部导航栏 -->
|
||||||
|
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-content">
|
||||||
|
<view class="nav-left" @tap="goBack">
|
||||||
|
<text class="back-icon"></text>
|
||||||
|
<text class="nav-title">全民评分</text>
|
||||||
|
</view>
|
||||||
|
<view class="nav-right">
|
||||||
|
<text class="search-icon"></text>
|
||||||
|
<image class="nav-avatar" src="/static/images/default-avatar.png" mode="aspectFill" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view :style="{ height: statusBarHeight + 44 + 'px' }"></view>
|
||||||
|
|
||||||
|
<view class="content-wrap">
|
||||||
|
<!-- 头部信息卡片 -->
|
||||||
|
<view class="hero-card card">
|
||||||
|
<image class="hero-avatar" :src="detailData.avatar" mode="aspectFill" />
|
||||||
|
<view class="hero-info">
|
||||||
|
<view class="hero-name-row">
|
||||||
|
<text class="hero-name">{{ detailData.name }}</text>
|
||||||
|
<text class="hero-score">{{ detailData.score }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="hero-meta">
|
||||||
|
<text class="meta-rank">🥇 No.1</text>
|
||||||
|
<text class="meta-heat">🔥 {{ detailData.hotScore }}w 热度</text>
|
||||||
|
<text class="meta-count" style="margin-left: 20rpx; color: #999;">{{ detailData.ratingCount }} 人评分</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- AI 观察员 -->
|
||||||
|
<view class="ai-card">
|
||||||
|
<view class="ai-header">
|
||||||
|
<text class="ai-icon"></text>
|
||||||
|
<text class="ai-title">AI观察员</text>
|
||||||
|
</view>
|
||||||
|
<text class="ai-content">{{ detailData.aiSummary }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 你的态度 -->
|
||||||
|
<AttitudePanel
|
||||||
|
:initial-action="currentAction"
|
||||||
|
@action-change="handleActionChange"
|
||||||
|
@send-comment="handleSendComment"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 大家都在 PK -->
|
||||||
|
<view class="pk-panel card">
|
||||||
|
<view class="pk-header">
|
||||||
|
<text class="section-title">大家都在 PK</text>
|
||||||
|
<view class="go-compare">去对比<text class="arrow-right"></text></view>
|
||||||
|
</view>
|
||||||
|
<view class="pk-content">
|
||||||
|
<view class="pk-person">
|
||||||
|
<image class="pk-avatar blue-border" :src="detailData.avatar" mode="aspectFill" />
|
||||||
|
<text class="pk-name">{{ detailData.name }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="vs-text">VS</text>
|
||||||
|
<view class="pk-person">
|
||||||
|
<image class="pk-avatar yellow-border" src="https://api.dicebear.com/7.x/avataaars/svg?seed=13" mode="aspectFill" />
|
||||||
|
<text class="pk-name">康熙</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="pk-bar-wrap">
|
||||||
|
<view class="pk-bar blue-bar" style="width: 63%;"></view>
|
||||||
|
<view class="pk-bar yellow-bar" style="width: 37%;"></view>
|
||||||
|
</view>
|
||||||
|
<view class="pk-data-text">
|
||||||
|
<text class="blue-text">63% 支持</text>
|
||||||
|
<text class="yellow-text">37% 支持</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评论区 -->
|
||||||
|
<view class="comments-section card">
|
||||||
|
<view class="tabs-header">
|
||||||
|
<view class="tabs">
|
||||||
|
<text class="tab" :class="{ active: currentTab === 'hot' }" @tap="switchTab('hot')">最热</text>
|
||||||
|
<text class="tab" :class="{ active: currentTab === 'new' }" @tap="switchTab('new')">最新</text>
|
||||||
|
</view>
|
||||||
|
<!-- <text class="filter-icon"></text> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="comment-list" style="min-height: 400rpx;">
|
||||||
|
<CommentItem
|
||||||
|
v-for="(comment, index) in comments"
|
||||||
|
:key="comment.id"
|
||||||
|
:comment="comment"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view class="load-more">
|
||||||
|
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评分成功特效弹窗 -->
|
||||||
|
<SuccessPopup ref="successPopupRef" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { getStatusBarHeight } from "@/utils/system";
|
||||||
|
import { onLoad, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
|
||||||
|
import AttitudePanel from "@/components/AttitudePanel/AttitudePanel.vue";
|
||||||
|
import SuccessPopup from "@/components/SuccessPopup/SuccessPopup.vue";
|
||||||
|
import ActionBadge from "@/components/ActionBadge/ActionBadge.vue";
|
||||||
|
import CommentItem from "@/components/CommentItem/CommentItem.vue";
|
||||||
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
|
import { fetchTopicRatingItems, topicItemScore, topicItemComment, topicItemCommentList } from "@/api/topicItem";
|
||||||
|
import { formatDate } from "@/utils/date";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const statusBarHeight = ref(getStatusBarHeight() || 44);
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasMore = ref(true);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const currentAction = ref('');
|
||||||
|
const successPopupRef = ref(null);
|
||||||
|
const currentTab = ref('hot'); // 'hot' 或 'new'
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailData = ref({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
score: '0.0',
|
||||||
|
avatar: '',
|
||||||
|
aiSummary: '加载中...',
|
||||||
|
userAction: "",
|
||||||
|
topicId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const comments = ref([]);
|
||||||
|
const itemId = ref('');
|
||||||
|
|
||||||
|
const handleActionChange = async (action) => {
|
||||||
|
const originAction = detailData.value.userAction;
|
||||||
|
const originScore = detailData.value.score;
|
||||||
|
const isCancel = detailData.value.userAction === action.label;
|
||||||
|
|
||||||
|
try {
|
||||||
|
detailData.value.userAction = isCancel ? '' : action.label;
|
||||||
|
currentAction.value = detailData.value.userAction;
|
||||||
|
|
||||||
|
const res = await topicItemScore({
|
||||||
|
topicId: detailData.value.topicId,
|
||||||
|
itemId: itemId.value,
|
||||||
|
scoreType: action.scoreType
|
||||||
|
});
|
||||||
|
detailData.value.score = res?.scoreAvg || originScore;
|
||||||
|
|
||||||
|
// 如果是成功评分(非取消),展示特效弹窗
|
||||||
|
if (!isCancel && successPopupRef.value) {
|
||||||
|
successPopupRef.value.show(action);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
detailData.value.userAction = originAction;
|
||||||
|
detailData.value.score = originScore;
|
||||||
|
currentAction.value = originAction;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendComment = async (payload) => {
|
||||||
|
if (!detailData.value.topicId || !itemId.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '发送中...' });
|
||||||
|
await topicItemComment({
|
||||||
|
topicId: detailData.value.topicId,
|
||||||
|
itemId: itemId.value,
|
||||||
|
content: payload.content
|
||||||
|
});
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
// 弹出成功弹窗
|
||||||
|
if (successPopupRef.value) {
|
||||||
|
successPopupRef.value.show({
|
||||||
|
emoji: '💬',
|
||||||
|
label: '你的锐评已发布'
|
||||||
|
}, 'comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新页面数据以获取最新评论
|
||||||
|
currentPage.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
comments.value = [];
|
||||||
|
getCommentsData();
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: '发送失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchTab = (tab) => {
|
||||||
|
if (currentTab.value === tab) return;
|
||||||
|
currentTab.value = tab;
|
||||||
|
currentPage.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
// comments.value = []; // 移除这行,避免数据清空导致高度塌陷
|
||||||
|
getCommentsData(true); // 传入标识表示是切换榜单
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommentsData = async (isSwitchTab = false) => {
|
||||||
|
if (!detailData.value.topicId || !itemId.value || loading.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const orderBy = currentTab.value;
|
||||||
|
const res = await topicItemCommentList(detailData.value.topicId, itemId.value, currentPage.value, orderBy);
|
||||||
|
const list = res?.list || [];
|
||||||
|
|
||||||
|
const formattedList = list.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.user?.nickname || '匿名用户',
|
||||||
|
avatar: item.user?.avatar ? item.user.avatar : 'https://api.dicebear.com/7.x/avataaars/svg?seed=fallback',
|
||||||
|
time: formatDate(item.createdAt) || '刚刚', // 如果后端返回了时间字段可以替换
|
||||||
|
content: item.content,
|
||||||
|
likes: item.likeCount || 0,
|
||||||
|
isLiked: item.isLiked || false,
|
||||||
|
badge: '', // 预留徽章字段
|
||||||
|
badgeIcon: '',
|
||||||
|
userAction: item.userAction || "" // 夯/顶级/人上人/NPC/拉
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (currentPage.value === 1 || isSwitchTab) {
|
||||||
|
comments.value = formattedList;
|
||||||
|
} else {
|
||||||
|
comments.value = [...comments.value, ...formattedList];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 假设每页10条,如果返回的少于10条则没有更多了
|
||||||
|
hasMore.value = list.length >= 6;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取评论列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetailData = async () => {
|
||||||
|
if (!itemId.value) return;
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '加载中' });
|
||||||
|
const res = await fetchTopicRatingItems(itemId.value);
|
||||||
|
const data = res?.data || res || {};
|
||||||
|
|
||||||
|
detailData.value = {
|
||||||
|
id: itemId.value,
|
||||||
|
name: data.name || '未知',
|
||||||
|
score: data.scoreAvg ? Number(data.scoreAvg).toFixed(1) : '0.0',
|
||||||
|
avatar: data.avatarUrl ? `${FILE_BASE_URL}${data.avatarUrl}` : '',
|
||||||
|
aiSummary: data.description || '暂无总结',
|
||||||
|
hotScore: data.hotScore || '0.00',
|
||||||
|
topicId: data.topicId || detailData.value.topicId || '',
|
||||||
|
ratingCount: data.scoreCount || 0,
|
||||||
|
userAction: data.userAction || ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果接口返回了当前用户的态度,初始化它
|
||||||
|
if (data.userAction) {
|
||||||
|
currentAction.value = data.userAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有评论数据,更新评论 (兼容老接口一并返回的情况,但推荐通过 getCommentsData 单独获取)
|
||||||
|
if (data.comments && Array.isArray(data.comments) && currentPage.value === 1) {
|
||||||
|
// 如果后端在详情里依然返回了一部分评论
|
||||||
|
// comments.value = data.comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拉取真实的评论列表
|
||||||
|
if (currentPage.value === 1) {
|
||||||
|
getCommentsData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取详情失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '获取详情失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
uni.$trackRecord({
|
||||||
|
eventName: "rating_item_detail_page_visit",
|
||||||
|
eventType: `visit`,
|
||||||
|
elementId: options.itemId
|
||||||
|
})
|
||||||
|
if (options.topicId) {
|
||||||
|
detailData.value.topicId = options.topicId;
|
||||||
|
}
|
||||||
|
if (options.itemId) {
|
||||||
|
itemId.value = options.itemId;
|
||||||
|
getDetailData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onPullDownRefresh(() => {
|
||||||
|
currentPage.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
comments.value = [];
|
||||||
|
getDetailData();
|
||||||
|
});
|
||||||
|
|
||||||
|
onReachBottom(() => {
|
||||||
|
if (!hasMore.value || loading.value) return;
|
||||||
|
currentPage.value += 1;
|
||||||
|
getCommentsData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
padding-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏 */
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='19' y1='12' x2='5' y2='12'%3E%3C/line%3E%3Cpolyline points='12 19 5 12 12 5'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232953ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-avatar {
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrap {
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 英雄卡片 */
|
||||||
|
.hero-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-avatar {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 32rpx;
|
||||||
|
border: 4rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-name {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-score {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #2953ff;
|
||||||
|
font-family: 'DIN Alternate', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-rank {
|
||||||
|
margin-right: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #d9a000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-heat {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AI 观察员 */
|
||||||
|
.ai-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2rpx solid #e0d6ff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237B46F1'%3E%3Cpath d='M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h5a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V5.73A2 2 0 0 1 10 4a2 2 0 0 1 2-2zm-3 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm6 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z'/%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #7b46f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-content {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 你的态度面板 */
|
||||||
|
|
||||||
|
/* 大家都在 PK */
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
display: block;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-header .section-title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.go-compare {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #2953ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232953ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-person {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-avatar {
|
||||||
|
width: 110rpx;
|
||||||
|
height: 110rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid transparent;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-border { border-color: #2953ff; }
|
||||||
|
.yellow-border { border-color: #ffc107; }
|
||||||
|
|
||||||
|
.pk-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-text {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #a1a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-bar-wrap {
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pk-bar { height: 100%; }
|
||||||
|
.blue-bar { background-color: #4d44f1; }
|
||||||
|
.yellow-bar { background-color: #ffe066; }
|
||||||
|
|
||||||
|
.pk-data-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-text { color: #4d44f1; }
|
||||||
|
.yellow-text { color: #333; }
|
||||||
|
|
||||||
|
/* 评论区 */
|
||||||
|
.comments-section {
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: #111;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700; /* 稍微减轻字重,从800改为700,显得不那么生硬 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 32rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background: linear-gradient(90deg, #4d44f1, #8a78ff);
|
||||||
|
border-radius: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' y1='21' x2='4' y2='14'%3E%3C/line%3E%3Cline x1='4' y1='10' x2='4' y2='3'%3E%3C/line%3E%3Cline x1='12' y1='21' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='8' x2='12' y2='3'%3E%3C/line%3E%3Cline x1='20' y1='21' x2='20' y2='16'%3E%3C/line%3E%3Cline x1='20' y1='12' x2='20' y2='3'%3E%3C/line%3E%3Cline x1='1' y1='14' x2='7' y2='14'%3E%3C/line%3E%3Cline x1='9' y1='8' x2='15' y2='8'%3E%3C/line%3E%3Cline x1='17' y1='16' x2='23' y2='16'%3E%3C/line%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-icon:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40rpx 0 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
866
pages/rating/index.vue
Normal file
866
pages/rating/index.vue
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-content">
|
||||||
|
<view class="nav-back" @tap="goBack">
|
||||||
|
<text class="back-icon"></text>
|
||||||
|
</view>
|
||||||
|
<view class="nav-logo-wrap">
|
||||||
|
<image class="nav-logo" src="/static/images/default-avatar.png" mode="aspectFill" />
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">全民评分</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 顶部占位 -->
|
||||||
|
<view :style="{ height: statusBarHeight + 44 + 'px' }"></view>
|
||||||
|
|
||||||
|
<view class="content-wrap">
|
||||||
|
<!-- 头部信息 -->
|
||||||
|
<view class="topic-header">
|
||||||
|
<text class="topic-title">{{ topicData.title }}</text>
|
||||||
|
<view class="topic-stats">
|
||||||
|
<text class="stat-icon fire-icon"></text>
|
||||||
|
<text class="stat-text">热度 {{ topicData.hotScore }} w</text>
|
||||||
|
<text class="stat-text margin-left">{{ topicData.participantCount }}人参与</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- AI 实时观察 -->
|
||||||
|
<view class="ai-observe">
|
||||||
|
<view class="ai-header">
|
||||||
|
<text class="ai-icon"></text>
|
||||||
|
<text class="ai-title">AI 实时观察</text>
|
||||||
|
</view>
|
||||||
|
<text class="ai-content">{{ topicData.aiSummary }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 领奖台 (Top 3) -->
|
||||||
|
<view class="podium-section" v-if="ratingItems && ratingItems.length >= 3">
|
||||||
|
<!-- 第二名 -->
|
||||||
|
<view class="podium-item rank-2" @tap="goToDetail(ratingItems[1].id)">
|
||||||
|
<view class="avatar-wrap">
|
||||||
|
<image class="avatar" :src="FILE_BASE_URL + ratingItems[1].avatarUrl" mode="aspectFill" />
|
||||||
|
<text class="rank-badge badge-2">2</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-box box-2">
|
||||||
|
<text class="name">{{ ratingItems[1].name }}</text>
|
||||||
|
<text class="score">分数 {{ ratingItems[1].scoreAvg }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 第一名 -->
|
||||||
|
<view class="podium-item rank-1" @tap="goToDetail(ratingItems[0].id)">
|
||||||
|
<view class="avatar-wrap">
|
||||||
|
<image class="avatar avatar-1" :src="FILE_BASE_URL + ratingItems[0].avatarUrl" mode="aspectFill" />
|
||||||
|
<text class="rank-badge badge-1">1</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-box box-1">
|
||||||
|
<text class="name name-1">{{ ratingItems[0].name }}</text>
|
||||||
|
<text class="score score-1">分数 {{ ratingItems[0].scoreAvg }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 第三名 -->
|
||||||
|
<view class="podium-item rank-3" @tap="goToDetail(ratingItems[2].id)">
|
||||||
|
<view class="avatar-wrap">
|
||||||
|
<image class="avatar" :src="FILE_BASE_URL + ratingItems[2].avatarUrl" mode="aspectFill" />
|
||||||
|
<text class="rank-badge badge-3">3</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-box box-3">
|
||||||
|
<text class="name">{{ ratingItems[2].name }}</text>
|
||||||
|
<text class="score">分数 {{ ratingItems[2].scoreAvg }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评分项列表 -->
|
||||||
|
<view class="rating-list">
|
||||||
|
<RatingCard
|
||||||
|
v-for="(item, index) in ratingItems"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
@item-click="goToDetail"
|
||||||
|
@action-click="handleAction"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 热门锐评 -->
|
||||||
|
<!-- <view class="comments-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="comment-icon"></text>
|
||||||
|
<text class="section-title">热门锐评</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="comment-input-box">
|
||||||
|
<text class="edit-icon"></text>
|
||||||
|
<text class="placeholder">发表你的锐评...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="comment-list">
|
||||||
|
<view class="comment-item" v-for="comment in comments" :key="comment.id">
|
||||||
|
<view class="comment-user">
|
||||||
|
<image class="comment-avatar" :src="comment.avatar" mode="aspectFill" />
|
||||||
|
<text class="comment-name">{{ comment.name }}</text>
|
||||||
|
<text class="comment-time">{{ comment.time }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="comment-content">
|
||||||
|
<text>{{ comment.content }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view class="load-more">
|
||||||
|
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右下角悬浮添加按钮 -->
|
||||||
|
<view class="fab-btn" @tap="showAddPopup = true">
|
||||||
|
<text class="plus-icon">+</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 添加评分项弹窗 -->
|
||||||
|
<view class="popup-mask" v-if="showAddPopup" @tap="showAddPopup = false">
|
||||||
|
<view class="popup-content" @tap.stop>
|
||||||
|
<text class="popup-title">添加评分项</text>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">上传</text>
|
||||||
|
<view class="avatar-uploader" @tap="chooseAvatar">
|
||||||
|
<image v-if="newItem.avatar" :src="newItem.avatar" class="preview-avatar" mode="aspectFill" />
|
||||||
|
<view v-else class="upload-placeholder">
|
||||||
|
<text class="camera-icon"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">名称</text>
|
||||||
|
<input class="form-input" type="text" v-model="newItem.name" placeholder="请输入名称" placeholder-class="input-placeholder" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-actions">
|
||||||
|
<button class="btn-cancel" @tap="showAddPopup = false">取消</button>
|
||||||
|
<button class="btn-confirm" @tap="confirmAddItem">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评分成功特效弹窗 -->
|
||||||
|
<SuccessPopup ref="successPopupRef" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { getStatusBarHeight } from "@/utils/system";
|
||||||
|
import { onPullDownRefresh, onReachBottom, onLoad } from "@dcloudio/uni-app";
|
||||||
|
import { fetchTopicDetail, fetchTopicRatingItems } from "@/api/topic";
|
||||||
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
|
import RatingCard from "@/components/RatingCard/RatingCard.vue";
|
||||||
|
import SuccessPopup from "@/components/SuccessPopup/SuccessPopup.vue";
|
||||||
|
import { topicItemScore } from "@/api/topicItem";
|
||||||
|
// 状态栏高度处理
|
||||||
|
const statusBarHeight = ref(getStatusBarHeight() || 44);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasMore = ref(true);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const currentTopicId = ref(null);
|
||||||
|
const successPopupRef = ref(null);
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const topicData = ref({
|
||||||
|
title: '',
|
||||||
|
heat: '0',
|
||||||
|
participants: '0',
|
||||||
|
aiSummary: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadDetail = async (topicId) => {
|
||||||
|
try {
|
||||||
|
const res = await fetchTopicDetail(topicId);
|
||||||
|
const data = res?.data || res || {};
|
||||||
|
topicData.value = {
|
||||||
|
title: data.title || '',
|
||||||
|
heat: data.heat || '0',
|
||||||
|
participants: data.participants || '0',
|
||||||
|
aiSummary: data.aiSummary || '暂无总结',
|
||||||
|
participantCount: data.participantCount || '0',
|
||||||
|
hotScore: data.hotScore || '0'
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("获取主题详情失败:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAddPopup = ref(false);
|
||||||
|
const newItem = ref({
|
||||||
|
name: '',
|
||||||
|
avatar: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const chooseAvatar = () => {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
success: (res) => {
|
||||||
|
newItem.value.avatar = res.tempFilePaths[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmAddItem = () => {
|
||||||
|
if (!newItem.value.name.trim()) {
|
||||||
|
uni.showToast({ title: '请输入名称', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟添加
|
||||||
|
const newRatingItem = {
|
||||||
|
id: Date.now(),
|
||||||
|
rank: ratingItems.value.length + 1,
|
||||||
|
name: newItem.value.name,
|
||||||
|
avatar: newItem.value.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${Date.now()}`,
|
||||||
|
score: '0.0',
|
||||||
|
quote: '刚刚加入讨论,快来发表你的看法吧。',
|
||||||
|
userAction: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
ratingItems.value.push(newRatingItem);
|
||||||
|
|
||||||
|
// 重置并关闭
|
||||||
|
newItem.value = { name: '', avatar: '' };
|
||||||
|
showAddPopup.value = false;
|
||||||
|
|
||||||
|
uni.showToast({ title: '添加成功', icon: 'success' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const ratingItems = ref([]);
|
||||||
|
|
||||||
|
const loadItems = async (topicId, page = 1) => {
|
||||||
|
if (page === 1) {
|
||||||
|
ratingItems.value = [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await fetchTopicRatingItems(topicId, page);
|
||||||
|
const list = res?.data?.records || res?.records || res?.list || [];
|
||||||
|
|
||||||
|
ratingItems.value = [...ratingItems.value, ...list];
|
||||||
|
hasMore.value = list.length >= 10; // 假设 pageSize 是 10
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取评分项列表失败:", error);
|
||||||
|
hasMore.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
uni.$trackRecord({
|
||||||
|
eventName: "rating_topic_detail_page_visit",
|
||||||
|
eventType: `visit`,
|
||||||
|
elementId: options.topicId
|
||||||
|
})
|
||||||
|
if (options.topicId) {
|
||||||
|
currentTopicId.value = options.topicId;
|
||||||
|
loadDetail(options.topicId);
|
||||||
|
loadItems(options.topicId, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockComments = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '史学家阿强',
|
||||||
|
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=c1',
|
||||||
|
time: '刚刚',
|
||||||
|
content: '李世民唯一缺点是儿子太抽象。他在位时那种包容力和大局观,放眼整个历史都是炸裂的存在。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '逻辑帝',
|
||||||
|
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=c2',
|
||||||
|
time: '2小时前',
|
||||||
|
content: '秦始皇是开辟者,李世民是完善者。一个是0到1的惊天动地,一个是1到100的极致巅峰。'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const comments = ref([...mockComments]);
|
||||||
|
|
||||||
|
const handleAction = async (item, action) => {
|
||||||
|
const originAction = item.userAction;
|
||||||
|
const originScore = item.scoreAvg;
|
||||||
|
const isCancel = item.userAction === action.label;
|
||||||
|
|
||||||
|
try {
|
||||||
|
item.userAction = isCancel ? '' : action.label;
|
||||||
|
const res = await topicItemScore({
|
||||||
|
topicId: currentTopicId.value,
|
||||||
|
itemId: item.id,
|
||||||
|
scoreType: action.scoreType
|
||||||
|
});
|
||||||
|
item.scoreAvg = res?.scoreAvg || originScore;
|
||||||
|
|
||||||
|
// 如果是成功评分(非取消),展示特效弹窗
|
||||||
|
if (!isCancel && successPopupRef.value) {
|
||||||
|
successPopupRef.value.show(action);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
item.userAction = originAction;
|
||||||
|
item.scoreAvg = originScore;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToDetail = (itemId) => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/rating/detail?itemId=${itemId}&topicId=${currentTopicId.value}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
onPullDownRefresh(async () => {
|
||||||
|
if (currentTopicId.value) {
|
||||||
|
currentPage.value = 1;
|
||||||
|
hasMore.value = true;
|
||||||
|
await Promise.all([
|
||||||
|
loadDetail(currentTopicId.value),
|
||||||
|
loadItems(currentTopicId.value, currentPage.value)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// 模拟评论刷新(此处未接接口,暂时保留原有模拟刷新逻辑,去掉 mockRatingItems 的重置)
|
||||||
|
setTimeout(() => {
|
||||||
|
comments.value = [...mockComments];
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上拉加载更多
|
||||||
|
onReachBottom(() => {
|
||||||
|
if (!hasMore.value || loading.value) return;
|
||||||
|
if (currentTopicId.value) {
|
||||||
|
currentPage.value += 1;
|
||||||
|
loadItems(currentTopicId.value, currentPage.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有的评论模拟加载(根据需要决定是否保留)
|
||||||
|
setTimeout(() => {
|
||||||
|
const moreComments = mockComments.map(c => ({...c, id: c.id + comments.value.length}));
|
||||||
|
comments.value.push(...moreComments);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #fafbfe; /* 浅蓝灰色背景 */
|
||||||
|
padding-bottom: 120rpx; /* 留出底部 tabBar 空间 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏 */
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: rgba(250, 251, 254, 0.95);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
padding: 10rpx 24rpx 10rpx 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 18 9 12 15 6'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo-wrap {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #001f3f, #0088a9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2953ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrap {
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头部信息 */
|
||||||
|
.topic-header {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-title {
|
||||||
|
font-size: 46rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #111;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon.fire-icon {
|
||||||
|
width: 26rpx;
|
||||||
|
height: 26rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff4d4f'%3E%3Cpath d='M19.48 13.03c-.02-.19-.13-.36-.29-.46-.17-.11-.38-.13-.56-.05-.98.42-2.12.3-2.92-.31-1.07-.81-1.55-2.22-1.28-3.75.12-.66.1-1.32-.06-1.97-.24-.95-1.1-1.6-2.09-1.58-1 .02-1.83.74-2 1.72-.25 1.55-.95 2.94-2 4.02-1.38 1.41-2.05 3.39-1.85 5.4.2 2.05 1.42 3.86 3.25 4.82 1.37.72 2.94 1.01 4.5.83 2.74-.32 5.06-2.28 5.86-4.95.23-.75.29-1.52.19-2.28-.06-.5-.21-.99-.41-1.45l-.34-.99zm-4.98 6.47c-1.37 0-2.5-1.13-2.5-2.5s1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5-1.13 2.5-2.5 2.5z'/%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #FF5A5F;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-text.margin-left {
|
||||||
|
margin-left: 32rpx;
|
||||||
|
color: #555;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AI 实时观察 */
|
||||||
|
.ai-observe {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2rpx solid rgba(77, 68, 241, 0.2);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(77, 68, 241, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234d44f1'%3E%3Cpath d='M12 2l2.4 5.6L20 10l-5.6 2.4L12 18l-2.4-5.6L4 10l5.6-2.4L12 2zm0 0'/%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4d44f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-content {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #444;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 领奖台 (Top 3) */
|
||||||
|
.podium-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
height: 360rpx;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-bottom: -44rpx; /* 将头像下移,覆盖在底座上 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #fff;
|
||||||
|
box-shadow: 0 8rpx 16rpx rgba(0,0,0,0.1);
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-1 {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
border-color: #ffd700; /* 冠军金边 */
|
||||||
|
border-width: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4rpx;
|
||||||
|
right: -8rpx;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #111;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 4rpx solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-1 { background-color: #ffd700; width: 44rpx; height: 44rpx; right: -10rpx; bottom: 0; font-size: 26rpx; }
|
||||||
|
.badge-2 { background-color: #e2e8f0; }
|
||||||
|
.badge-3 { background-color: #e2e8f0; }
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
width: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 56rpx;
|
||||||
|
padding-bottom: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-1 {
|
||||||
|
width: 220rpx;
|
||||||
|
height: 190rpx;
|
||||||
|
background: linear-gradient(180deg, #6c5ce7 0%, #5a48eb 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 12rpx 32rpx rgba(108, 92, 231, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-2 {
|
||||||
|
height: 150rpx;
|
||||||
|
background-color: #e4e7f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-3 {
|
||||||
|
height: 150rpx; /* 从 130rpx 调整到 150rpx */
|
||||||
|
background-color: #eef1f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-1 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 34rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-1 {
|
||||||
|
color: rgba(255,255,255,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 评分项列表 */
|
||||||
|
.rating-list {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 热门锐评 */
|
||||||
|
.comments-section {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232953ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-box {
|
||||||
|
background-color: #f0f2f7;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23666'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
background-color: #f5f7fb;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-user {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬浮添加按钮 */
|
||||||
|
.fab-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 40rpx;
|
||||||
|
bottom: 160rpx; /* 留出 tabBar 的距离 */
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
background: linear-gradient(135deg, #4d44f1, #2953ff);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(41, 83, 255, 0.4);
|
||||||
|
z-index: 99;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-btn:active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plus-icon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 60rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: -8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗样式 */
|
||||||
|
.popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content {
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes popIn {
|
||||||
|
from { transform: scale(0.8); opacity: 0; }
|
||||||
|
to { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-uploader {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
border: 2rpx dashed #d1d5db;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-icon {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Ccircle cx='12' cy='13' r='4'%3E%3C/circle%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
height: 80rpx;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 48rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel, .btn-confirm {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel::after, .btn-confirm::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #f0f2f7;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
background: linear-gradient(135deg, #4d44f1, #2953ff);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
759
pages/release/index.vue
Normal file
759
pages/release/index.vue
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-content">
|
||||||
|
<view class="nav-side left" @tap="goBack">
|
||||||
|
<text class="back-icon"></text>
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">发起评分</text>
|
||||||
|
<view class="nav-side right" @tap="handlePreview">
|
||||||
|
<text class="preview-text">预览</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view :style="{ height: statusBarHeight + 44 + 'px' }"></view>
|
||||||
|
|
||||||
|
<view class="content-wrap">
|
||||||
|
<view class="topic-editor card-block">
|
||||||
|
<textarea
|
||||||
|
v-model="formData.title"
|
||||||
|
class="topic-input"
|
||||||
|
maxlength="40"
|
||||||
|
placeholder="输入一个有趣的话题..."
|
||||||
|
placeholder-class="topic-placeholder"
|
||||||
|
auto-height
|
||||||
|
/>
|
||||||
|
<!-- <textarea
|
||||||
|
v-model="formData.description"
|
||||||
|
class="desc-input"
|
||||||
|
maxlength="120"
|
||||||
|
placeholder="简单介绍一下这个评分话题..."
|
||||||
|
placeholder-class="desc-placeholder"
|
||||||
|
auto-height
|
||||||
|
/> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- <view class="cover-card" :class="{ 'has-cover': formData.coverUrl }" @tap="chooseCover">
|
||||||
|
<image
|
||||||
|
v-if="formData.coverUrl"
|
||||||
|
:src="FILE_BASE_URL + formData.coverUrl"
|
||||||
|
class="cover-image"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view v-else class="cover-placeholder">
|
||||||
|
<view class="cover-icon"></view>
|
||||||
|
<text class="cover-text">上传封面图</text>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<view class="category-group">
|
||||||
|
<view
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
class="category-chip"
|
||||||
|
:class="{ active: formData.categoryId === category.id }"
|
||||||
|
@tap="formData.categoryId = category.id"
|
||||||
|
>
|
||||||
|
{{ category.title }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="section-title">评分对象</text>
|
||||||
|
<text class="section-count">{{ participants.length }}/20</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="participant-list">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in participants"
|
||||||
|
:key="item.id"
|
||||||
|
class="participant-card"
|
||||||
|
>
|
||||||
|
<view class="participant-main">
|
||||||
|
<view class="participant-avatar-wrap" @tap="chooseParticipantAvatar(index)">
|
||||||
|
<image
|
||||||
|
v-if="item.avatar"
|
||||||
|
:src="FILE_BASE_URL + item.avatar"
|
||||||
|
class="participant-avatar"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view v-else class="participant-avatar empty">
|
||||||
|
<view class="camera-small"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="participant-info">
|
||||||
|
<input
|
||||||
|
v-model="item.name"
|
||||||
|
class="participant-name"
|
||||||
|
maxlength="12"
|
||||||
|
placeholder="输入评价对象"
|
||||||
|
placeholder-class="participant-placeholder"
|
||||||
|
/>
|
||||||
|
<!-- <input
|
||||||
|
v-model="item.desc"
|
||||||
|
class="participant-desc"
|
||||||
|
maxlength="24"
|
||||||
|
placeholder="一句话简介"
|
||||||
|
placeholder-class="participant-placeholder"
|
||||||
|
/> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="participant-actions">
|
||||||
|
<!-- <text class="drag-icon">≡</text> -->
|
||||||
|
<text class="delete-icon" @tap="removeParticipant(item.id)">🗑</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="add-participant" @tap="addParticipant">
|
||||||
|
<text class="add-plus">+</text>
|
||||||
|
<text class="add-text">添加评分对象</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="settings-card card-block">
|
||||||
|
<view class="setting-row">
|
||||||
|
<view class="setting-info">
|
||||||
|
<text class="setting-title">允许用户锐评</text>
|
||||||
|
<text class="setting-desc">开启后用户可以对评分对象发表评论</text>
|
||||||
|
</view>
|
||||||
|
<switch
|
||||||
|
:checked="formData.allowComment"
|
||||||
|
color="#4d44f1"
|
||||||
|
@change="formData.allowComment = $event.detail.value"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="setting-row">
|
||||||
|
<view class="setting-info">
|
||||||
|
<text class="setting-title">允许开启PK</text>
|
||||||
|
<text class="setting-desc">启用后系统会自动生成对象间的对比PK</text>
|
||||||
|
</view>
|
||||||
|
<switch
|
||||||
|
:checked="formData.allowPk"
|
||||||
|
color="#4d44f1"
|
||||||
|
@change="formData.allowPk = $event.detail.value"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="setting-row last">
|
||||||
|
<view class="setting-info">
|
||||||
|
<text class="setting-title">匿名评分</text>
|
||||||
|
<text class="setting-desc">用户可以隐藏个人身份进行打分</text>
|
||||||
|
</view>
|
||||||
|
<switch
|
||||||
|
:checked="formData.anonymous"
|
||||||
|
color="#4d44f1"
|
||||||
|
@change="formData.anonymous = $event.detail.value"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer-bar">
|
||||||
|
<button class="submit-btn" @tap="handleSubmit">发布全民评分</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
import { getStatusBarHeight } from "@/utils/system";
|
||||||
|
import { releaseTopic, fetchTopicCategories } from "@/api/topic";
|
||||||
|
import { smartNavigateBack } from "@/utils/page";
|
||||||
|
import {
|
||||||
|
uploadImage,
|
||||||
|
chooseAndUploadImage,
|
||||||
|
} from "@/utils/common.js";
|
||||||
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
|
|
||||||
|
const statusBarHeight = ref(getStatusBarHeight() || 0);
|
||||||
|
|
||||||
|
const categories = ref([]);
|
||||||
|
|
||||||
|
const loadCategories = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchTopicCategories();
|
||||||
|
// 假设返回结构为 { data: { list: [{ id: 1, title: '历史' }, ...] } }
|
||||||
|
// 或者直接是一个数组 [{ id: 1, title: '历史' }, ...]
|
||||||
|
// 根据实际返回结构调整
|
||||||
|
const list = res?.data?.list || res?.list || res || [];
|
||||||
|
categories.value = list;
|
||||||
|
|
||||||
|
// 如果存在分类且未选择,则默认选中第一个
|
||||||
|
if (categories.value.length > 0 && !formData.categoryId) {
|
||||||
|
formData.categoryId = categories.value[0].id;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取分类失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
loadCategories();
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
coverUrl: "",
|
||||||
|
categoryId: "",
|
||||||
|
allowComment: true,
|
||||||
|
allowPk: true,
|
||||||
|
anonymous: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tagInput = ref("");
|
||||||
|
|
||||||
|
const participants = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "",
|
||||||
|
desc: "一句话简介",
|
||||||
|
avatar: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
smartNavigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreview = () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: "预览功能待接入",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseCover = async () => {
|
||||||
|
try {
|
||||||
|
const urls = await chooseAndUploadImage({ count: 1 });
|
||||||
|
if (urls && urls.length > 0) {
|
||||||
|
formData.coverUrl = urls[0];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("选择封面失败", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseParticipantAvatar = async (index) => {
|
||||||
|
try {
|
||||||
|
const urls = await chooseAndUploadImage({ count: 1 });
|
||||||
|
if (urls && urls.length > 0) {
|
||||||
|
participants.value[index].avatar = urls[0];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("选择评分对象头像失败", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddTag = () => {
|
||||||
|
const value = tagInput.value.trim();
|
||||||
|
if (!value) return;
|
||||||
|
if (formData.tags.includes(value)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "标签已存在",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formData.tags.push(value);
|
||||||
|
tagInput.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTag = (index) => {
|
||||||
|
formData.tags.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addParticipant = () => {
|
||||||
|
if (participants.value.length >= 20) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "最多添加20个对象",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
participants.value.push({
|
||||||
|
id: Date.now(),
|
||||||
|
name: "",
|
||||||
|
desc: "",
|
||||||
|
avatar: "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeParticipant = (id) => {
|
||||||
|
if (participants.value.length <= 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "至少保留1个对象",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
participants.value = participants.value.filter((item) => item.id !== id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formData.title.trim()) {
|
||||||
|
uni.showToast({ title: '请输入话题', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if (!formData.coverUrl) {
|
||||||
|
// uni.showToast({ title: '请上传封面图', icon: 'none' });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
if (!formData.categoryId) {
|
||||||
|
uni.showToast({ title: '请选择话题分类', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有空的对象名称
|
||||||
|
const hasEmptyName = participants.value.some(p => !p.name.trim());
|
||||||
|
if (hasEmptyName) {
|
||||||
|
uni.showToast({ title: '请完善评分对象名称', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({ title: '发布中...' });
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
...formData,
|
||||||
|
participants: participants.value,
|
||||||
|
};
|
||||||
|
await releaseTopic(payload);
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "发布成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
smartNavigateBack({ delay: 1500 });
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "发布失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
console.error("发布失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8f9fc; /* 整体背景调亮 */
|
||||||
|
padding-bottom: 180rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: rgba(244, 245, 251, 0.96);
|
||||||
|
backdrop-filter: blur(18rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-side {
|
||||||
|
width: 120rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-side.right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23222' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 18 9 12 15 6'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #5f63ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrap {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统一的卡片风格 */
|
||||||
|
.card-block {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 36rpx; /* 增大圆角,与设计图更统一 */
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(72, 82, 130, 0.03); /* 柔和的阴影 */
|
||||||
|
margin-bottom: 32rpx; /* 增加块级间距 */
|
||||||
|
border: 2rpx solid #eef0f7; /* 统一的极淡边框 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-editor {
|
||||||
|
padding: 32rpx; /* 增加内边距 */
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-input,
|
||||||
|
.desc-input {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 52rpx;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-input {
|
||||||
|
font-size: 44rpx; /* 稍微减小标题字号,显得更精致 */
|
||||||
|
font-weight: 800; /* 加粗 */
|
||||||
|
margin-bottom: 16rpx; /* 增加行距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-input {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-placeholder {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #c1c2d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-placeholder {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #b8b9c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-card {
|
||||||
|
height: 340rpx;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
background: #e9ecf5;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
border: 2rpx dashed #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-card.has-cover {
|
||||||
|
border: none; /* 有图片时去掉虚线框 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #77728b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-icon {
|
||||||
|
width: 80rpx; /* 图标稍微放大 */
|
||||||
|
height: 80rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2377728b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E"); /* 替换为更像图片的占位图标 */
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-chip {
|
||||||
|
min-width: 88rpx;
|
||||||
|
padding: 14rpx 32rpx; /* 增加内边距 */
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #e9ecf5; /* 背景调浅一些,更接近设计图 */
|
||||||
|
color: #5c6176; /* 字体颜色调浅 */
|
||||||
|
font-size: 28rpx; /* 字体调大 */
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-chip.active {
|
||||||
|
background: linear-gradient(135deg, #6c4af2, #883cf3); /* 更接近设计图的紫色渐变 */
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(108, 74, 242, 0.3); /* 调整阴影颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-box {
|
||||||
|
padding: 24rpx 32rpx; /* 增加内边距 */
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chip {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background-color: #f0f4ff; /* 浅蓝色背景 */
|
||||||
|
border-radius: 8rpx;
|
||||||
|
color: #4e46e5;
|
||||||
|
font-size: 26rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chip text {
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-chip::after {
|
||||||
|
content: "×";
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #8c89e8;
|
||||||
|
margin-left: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 180rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-input-placeholder {
|
||||||
|
color: #b9b7c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24rpx; /* 增加底部间距 */
|
||||||
|
padding: 0 8rpx; /* 标题与内容对齐 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx; /* 调整大小 */
|
||||||
|
font-weight: 800; /* 加粗 */
|
||||||
|
color: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-count {
|
||||||
|
min-width: 78rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 14rpx;
|
||||||
|
background: #e6e9f7;
|
||||||
|
color: #5c6176;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-list {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-card {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 36rpx; /* 更圆润的边角 */
|
||||||
|
padding: 32rpx 28rpx; /* 增加内边距 */
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
border: 2rpx solid #eef0f7; /* 增加细微的描边,更接近设计图质感 */
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(72, 82, 130, 0.03); /* 阴影调弱 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-avatar-wrap {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-avatar {
|
||||||
|
width: 92rpx;
|
||||||
|
height: 92rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e8ebf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-avatar.empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2rpx dashed #b5bad0; /* 加深虚线颜色 */
|
||||||
|
background: #f8f9fc; /* 空状态背景色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-small {
|
||||||
|
width: 38rpx;
|
||||||
|
height: 38rpx;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a90a6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Ccircle cx='12' cy='13' r='4'%3E%3C/circle%3E%3C/svg%3E");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-name,
|
||||||
|
.participant-desc {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-name {
|
||||||
|
height: 42rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #202020;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-desc {
|
||||||
|
height: 38rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #676c7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-placeholder {
|
||||||
|
color: #b6b9c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-actions {
|
||||||
|
width: 56rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-icon {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #b2b4c5;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
font-size: 32rpx; /* 图标放大 */
|
||||||
|
color: #ff6b6b; /* 明确的删除红色 */
|
||||||
|
line-height: 1;
|
||||||
|
padding: 8rpx; /* 增加点击区域 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-participant {
|
||||||
|
height: 120rpx; /* 增加高度 */
|
||||||
|
border: 2rpx dashed #c0c4d6; /* 边框颜色更明显 */
|
||||||
|
border-radius: 36rpx; /* 圆角一致 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #888d9e; /* 字体颜色微调 */
|
||||||
|
gap: 16rpx; /* 间距变大 */
|
||||||
|
background: #f8f9fc; /* 添加底色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-plus {
|
||||||
|
font-size: 38rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
padding: 16rpx 32rpx; /* 增加内边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32rpx 0; /* 增加行间距 */
|
||||||
|
border-bottom: 2rpx solid #f0f1f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row.last {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx; /* 字体稍微调小 */
|
||||||
|
font-weight: 700; /* 加粗 */
|
||||||
|
color: #181818;
|
||||||
|
margin-bottom: 12rpx; /* 增加与描述的间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx; /* 描述字体稍微调大 */
|
||||||
|
color: #8c92a4; /* 颜色调浅 */
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
background: linear-gradient(180deg, rgba(244, 245, 251, 0) 0%, rgba(244, 245, 251, 0.98) 34%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
height: 100rpx;
|
||||||
|
line-height: 100rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: linear-gradient(135deg, #5c43f5, #7a2cf3); /* 更接近设计图的深紫色 */
|
||||||
|
color: #fff;
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 16rpx 32rpx rgba(100, 60, 240, 0.3); /* 加强底部按钮阴影 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -29,8 +29,7 @@ export const uploadImage = (filePath) => {
|
|||||||
if (keyJson?.data?.result === "auditFailed") {
|
if (keyJson?.data?.result === "auditFailed") {
|
||||||
reject("图片不符合发布规范,请稍作修改后再试");
|
reject("图片不符合发布规范,请稍作修改后再试");
|
||||||
} else {
|
} else {
|
||||||
const url = `https://file.lihailezzc.com/${keyJson?.data.key}`;
|
resolve(keyJson?.data.key);
|
||||||
resolve(url);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
@@ -46,6 +45,54 @@ export const uploadImage = (filePath) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的选择并上传图片的方法
|
||||||
|
* @param {Object} options 配置项
|
||||||
|
* @param {number} options.count 最多可选择的图片数量,默认为 1
|
||||||
|
* @param {string} options.loadingTitle 上传中提示文案,默认为 '上传中...'
|
||||||
|
* @returns {Promise<string[]>} 返回一个包含所有上传成功图片 URL 的数组 Promise
|
||||||
|
*/
|
||||||
|
export const chooseAndUploadImage = (options = {}) => {
|
||||||
|
const { count = 1, loadingTitle = '上传中...' } = options;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseImage({
|
||||||
|
count,
|
||||||
|
success: async (res) => {
|
||||||
|
const filePaths = res.tempFilePaths;
|
||||||
|
if (!filePaths || filePaths.length === 0) {
|
||||||
|
return resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({ title: loadingTitle });
|
||||||
|
try {
|
||||||
|
// 并发上传所有选中的图片
|
||||||
|
const uploadPromises = filePaths.map(path => uploadImage(path));
|
||||||
|
const urls = await Promise.all(uploadPromises);
|
||||||
|
uni.hideLoading();
|
||||||
|
resolve(urls);
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: "上传失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
console.error("图片上传异常:", error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
// 用户取消选择不作为异常抛出,返回空数组
|
||||||
|
if (err.errMsg && err.errMsg.indexOf('cancel') !== -1) {
|
||||||
|
resolve([]);
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const saveRecordRequest = async (path, targetId, scene, imageUrl) => {
|
export const saveRecordRequest = async (path, targetId, scene, imageUrl) => {
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
imageUrl = await uploadImage(path);
|
imageUrl = await uploadImage(path);
|
||||||
|
|||||||
1
utils/constants.js
Normal file
1
utils/constants.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const FILE_BASE_URL = "https://file.lihailezzc.com/";
|
||||||
111
utils/page.js
Normal file
111
utils/page.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 页面跳转相关工具方法
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能返回方法:默认返回上一页,如果页面栈只有1层(没有上一页),则返回到首页
|
||||||
|
*
|
||||||
|
* @param {Object} options 配置参数
|
||||||
|
* @param {number} options.delay 延迟执行的时间,单位毫秒,默认 0
|
||||||
|
* @param {string} options.homePath 首页的路径,默认为 '/pages/index/index'
|
||||||
|
*/
|
||||||
|
export const smartNavigateBack = (options = {}) => {
|
||||||
|
const { delay = 0, homePath = '/pages/index/index' } = options;
|
||||||
|
|
||||||
|
const executeBack = () => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
// 有上一页,直接返回
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 页面栈只有一层,返回首页
|
||||||
|
uni.switchTab({
|
||||||
|
url: homePath,
|
||||||
|
fail: () => {
|
||||||
|
// 如果首页不是 tabBar 页面,则使用 reLaunch
|
||||||
|
uni.reLaunch({
|
||||||
|
url: homePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
setTimeout(executeBack, delay);
|
||||||
|
} else {
|
||||||
|
executeBack();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象转 URL 参数
|
||||||
|
* @param {Object} obj 要转换的对象
|
||||||
|
* @returns {string} 返回 a=1&b=2 格式的字符串
|
||||||
|
*/
|
||||||
|
export const objToUrlParams = (obj) => {
|
||||||
|
if (!obj || typeof obj !== 'object') return '';
|
||||||
|
const params = [];
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key) && obj[key] !== undefined && obj[key] !== null) {
|
||||||
|
// 过滤掉 undefined 和 null 的参数
|
||||||
|
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.join('&');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装带参数的跳转方法
|
||||||
|
* @param {string} url 跳转的页面路径
|
||||||
|
* @param {Object} params 要传递的参数对象
|
||||||
|
*/
|
||||||
|
export const navigateToWithParams = (url, params = {}) => {
|
||||||
|
const queryStr = objToUrlParams(params);
|
||||||
|
const finalUrl = queryStr ? `${url}${url.includes('?') ? '&' : '?'}${queryStr}` : url;
|
||||||
|
|
||||||
|
uni.navigateTo({
|
||||||
|
url: finalUrl
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前页面的实例
|
||||||
|
* @returns {Object|null} 当前页面实例
|
||||||
|
*/
|
||||||
|
export const getCurrentPage = () => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
return pages.length > 0 ? pages[pages.length - 1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新当前页面(重新调用 onLoad 和 onShow)
|
||||||
|
* 注意:部分复杂页面可能需要根据具体业务逻辑单独实现刷新机制
|
||||||
|
*/
|
||||||
|
export const refreshCurrentPage = () => {
|
||||||
|
const currentPage = getCurrentPage();
|
||||||
|
if (currentPage) {
|
||||||
|
if (typeof currentPage.onLoad === 'function') {
|
||||||
|
currentPage.onLoad(currentPage.options || {});
|
||||||
|
}
|
||||||
|
if (typeof currentPage.onShow === 'function') {
|
||||||
|
currentPage.onShow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前是否是指定页面
|
||||||
|
* @param {string} route 页面路由,例如 'pages/index/index'
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isCurrentPage = (route) => {
|
||||||
|
const currentPage = getCurrentPage();
|
||||||
|
if (!currentPage) return false;
|
||||||
|
// 去除可能的首字符 '/'
|
||||||
|
const checkRoute = route.startsWith('/') ? route.substring(1) : route;
|
||||||
|
return currentPage.route === checkRoute;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const BASE_URL = "https://api.ai-meng.com";
|
// const BASE_URL = "https://api.ai-meng.com";
|
||||||
// const BASE_URL = 'http://127.0.0.1:3999'
|
// const BASE_URL = 'http://127.0.0.1:3999'
|
||||||
// const BASE_URL = "http://192.168.1.2:3999";
|
// const BASE_URL = "http://192.168.1.2:3999";
|
||||||
// const BASE_URL = "http://192.168.31.253:3999";
|
const BASE_URL = "http://192.168.31.253:3999";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { getPlatform } from "./system.js";
|
import { getPlatform } from "./system.js";
|
||||||
const platform = getPlatform();
|
const platform = getPlatform();
|
||||||
@@ -35,7 +35,7 @@ function hideLoading() {
|
|||||||
function getHeaders() {
|
function getHeaders() {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const headers = {
|
const headers = {
|
||||||
"x-app-id": "69665538a49b8ae3be50fe5d",
|
"x-app-id": "6a0d7dbe4c5de50f2ba66475",
|
||||||
"x-platform": platform,
|
"x-platform": platform,
|
||||||
"x-user-id": userStore?.userInfo?.id || "",
|
"x-user-id": userStore?.userInfo?.id || "",
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user