Files
PC-vote/pages/index.html/index.vue
DESKTOP-RQ919RC\Pc a584a2771b fix(DetailsArea): 修复举报功能并更新相关样式
修复举报功能,区分评论和主题举报类型。更新讨论图标和投币文本样式。移除不必要的背景色和注释代码。

refactor(api): 重构举报相关API接口
将评论举报和主题举报分离为独立接口,提高代码可维护性。

style: 清理多余空格和注释
统一代码格式,删除无用注释和空白行。
2025-11-12 19:10:49 +08:00

430 lines
13 KiB
Vue

<template>
<Head>
<Title>投票 - 寄托天下出国留学网</Title>
</Head>
<TopHead></TopHead>
<div class="search-info flexacenter" v-if="keyword">
<div class="flexacenter" @click="closeKeyword">
{{ keyword }}
<img class="round-fork-fork" src="@/assets/img/round-fork-fork.png" />
</div>
<div class="halving-line"></div>
<div class="search-result"> {{ count }} 条搜索数据</div>
</div>
<div class="vote-list-box" :class="{ firstdata: firstdataState }" ref="gridContainer" v-loading="loading">
<a class="vote-item" target="_blank" :href="`/details/${item['uniqid']}?colorI=${index % 6}`" v-for="(item, index) in list" :key="index" :class="{ isvote: item['isvote'] == 1 || item['status'] == 0 }" :style="{ '--main-color': colourValue[index % 6]['main'], '--bg-color': colourValue[index % 6]['bg'], '--bc-color': colourValue[index % 6]['bc'] }">
<div class="vote-title">
<div class="vote-state" v-if="item['status'] == 1">进行中</div>
<div class="vote-state finish" v-else>已结束</div>
{{ item["title"] }}
</div>
<div class="vote-explain">{{ item["message"] }}</div>
<div class="vote-option-list flexflex">
<div class="vote-option-item flexflex" :class="{ pitch: item.selected == 1 }" v-for="(item, index) in item?.option" :key="index">
<div class="flexflex" style="padding: 2px 0">
<div class="vote-option-number flexcenter">{{ index + 1 }}</div>
<img class="tick-icon" src="@/assets/img/tick-black.svg" />
<div class="vote-option-content flex1">{{ item["value"] }}</div>
</div>
<div class="vote-option-progress flexacenter">
<div class="vote-option-progress-step" :style="{ width: item['percentage'] + '%' }"></div>
<div class="vote-option-progress-value">{{ item["count"] }}</div>
</div>
</div>
</div>
<div class="vote-data flexacenter">
<div class="vote-data-left flexacenter">
{{ item.votes }}人参与 <template v-if="item['deadline']">| {{ handleDeadline(item["deadline"]) }}结束</template>
</div>
<div class="vote-data-right flexacenter">
<div class="vote-data-item flexacenter"><img class="vote-data-icon" src="@/assets/img/eye-icon.svg" />&nbsp; {{ item.views }}</div>
<!-- <div class="vote-data-item flexacenter" @click.stop.prevent="handleLike(item['token'], index)"> -->
<div class="vote-data-item flexacenter">
<img v-if="item['islike'] == 0" class="vote-data-icon" src="@/assets/img/like-icon-gray.png" />
<img v-else class="vote-data-icon" src="@/assets/img/like-red-pitch.png" />&nbsp; {{ item["likes"] }}
</div>
<div class="vote-data-item flexacenter"><img class="vote-data-icon" src="@/assets/img/comment-icon.svg" />&nbsp; {{ item["comments"] }}</div>
</div>
</div>
</a>
<div class="empty-box flexcenter" v-if="keyword && list.length == 0 && !loading">
<Empty :isNeedIssue="true"></Empty>
</div>
</div>
<Like v-if="isLikeGif"></Like>
</template>
<script setup>
useHead({ script: [{ src: "https://app.gter.net/bottom?tpl=header&menukey=vote" }, { src: "https://app.gter.net/bottom?tpl=footer,popupnotification", body: true }] });
let isNeedLogin = inject("isNeedLogin");
const goLogin = inject("goLogin");
import { useRoute } from "vue-router";
const route = useRoute();
const router = useRouter();
let keyword = ref("");
let page = ref(1); // 投票页数从 1 开始
let count = ref(0);
let list = ref([]);
let loading = ref(false); // 加载中
const firstdataState = ref(true);
keyword.value = route.query["keyword"];
const gridContainer = ref(null);
let masonryInstance = null;
onMounted(async () => {
let Masonry = await import("masonry-layout");
masonryInstance = new Masonry.default(gridContainer.value, {
itemSelector: ".vote-item",
gutter: 22.5,
});
getList();
window.addEventListener("scroll", handleScroll);
});
const getList = () => {
if (page.value == 0 || loading.value) return;
loading.value = true;
getListHttp({ page: page.value, keyword: keyword.value, limit: 20 })
.then((res) => {
if (res.code != 200) {
page.value = 0;
ElMessage.error(res.message);
return;
}
let data = res.data;
list.value = list.value.concat(data.data);
count.value = data.count;
if (data.count > list.value.length) page.value++;
else page.value = 0;
firstdataState.value = false;
// data.data.forEach((element, index) => {
// // let uniqidEnd = element["uniqid"].charAt(element["uniqid"].length - 1)
// // element["uniqidIndex"] = base62ToDecimal(uniqidEnd) % 6
// element["uniqidIndex"] = index % 6
// })
nextTick(() => {
masonryInstance.reloadItems();
masonryInstance.layout();
});
})
.finally(() => (loading.value = false));
};
const handleScroll = () => {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
// 列表下 滑动到底部 获取新数据
if (scrollTop + clientHeight >= scrollHeight - 40) getList();
};
const userInfoWin = inject("userInfoWin");
let openAttest = inject("openAttest");
const realname = inject("realname");
let isLikeGif = ref(false);
let isLikeTimer = null;
// 处理点赞
const handleLike = (token, index) => {
if (realname.value == 0 && userInfoWin.value.uin > 0) {
openAttest();
return;
}
if (isNeedLogin.value) {
goLogin();
return;
}
if (list.value[index].islike) {
ElMessage.error("不可取消点赞");
return;
}
operateLikeHttp({ token }).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
const status = data["status"];
list.value[index].likes = data["count"];
list.value[index].islike = status;
ElMessage.success(res.message);
if (status) {
isLikeGif.value = false;
clearTimeout(isLikeTimer);
nextTick(() => {
isLikeGif.value = true;
isLikeTimer = setTimeout(() => (isLikeGif.value = false), 2000);
});
}
});
};
// 使用 process.client 判断是否在浏览器环境下
const isServer = computed(() => {
return process.server; // 使用 process.client 判断是否在浏览器环境下
});
// 点击关闭搜索
const closeKeyword = () => router.push("./index.html");
watch(
() => route.query,
() => {
keyword.value = route.query["keyword"];
page.value = 1;
list.value = [];
count.value = 0;
getList();
}
);
try {
if (process.env.NODE_ENV === "development") {
} else if (process.server) {
await getListHttp({ page: 1, keyword: keyword.value }).then((res) => {
let data = res.data;
list.value = list.value.concat(data.data);
count.value = data.count;
});
}
} catch (error) { }
</script>
<style lang="less" scoped>
.vote-item {
--main-color: rgba(44, 186, 230, 1);
--bg-color: rgba(234, 245, 248, 1);
--bc-color: rgba(213, 235, 242, 1);
}
.search-info {
font-size: 14px;
color: #72db86;
width: 1200px;
margin: 0 auto 31px;
.round-fork-fork {
width: 14px;
height: 14px;
margin-left: 8px;
cursor: pointer;
}
.halving-line {
width: 1px;
height: 13px;
background-color: #d7d7d7;
margin: 0 20px;
}
.search-result {
font-size: 13px;
color: #7f7f7f;
}
}
.vote-list-box {
width: 1200px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
min-height: 100vh;
&.firstdata {
.vote-item {
margin-right: 22.5px;
&:nth-of-type(3n) {
margin-right: 0;
}
}
}
.vote-item {
width: 385px;
background-color: rgba(255, 255, 255, 1);
border-radius: 16px;
padding: 25px 22px 24px;
margin-bottom: 20px;
cursor: pointer;
&:hover {
-moz-box-shadow: 0px 0px 5px 2px rgba(216, 216, 216, 0.48);
-webkit-box-shadow: 0px 0px 5px 2px rgba(216, 216, 216, 0.48);
box-shadow: 0px 0px 5px 2px rgba(216, 216, 216, 0.48);
}
&.isvote {
.vote-option-list {
.vote-option-item {
.vote-option-progress {
display: flex;
}
}
}
}
.vote-title {
.vote-state {
background-color: var(--main-color);
border-radius: 25px;
font-size: 12px;
margin-right: 6px;
height: 20px;
color: #ffffff;
padding: 0 6px;
display: inline-flex;
justify-content: center;
align-items: center;
&.finish {
color: #ffffff;
background: #000;
}
}
font-weight: 650;
font-style: normal;
font-size: 16px;
color: #000000;
line-height: 26px;
margin-bottom: 10px;
word-break: break-all;
}
.vote-explain {
color: #555555;
line-height: 22px;
font-size: 13px;
word-break: break-word;
margin-bottom: 14px;
}
.vote-option-list {
width: 340px;
background-color: var(--bg-color);
border: 1px solid var(--bc-color);
border-radius: 13px;
flex-direction: column;
padding: 8px 0;
margin-bottom: 16px;
.vote-option-item {
padding: 10px 15px;
flex-direction: column;
&:not(:last-of-type) {
border-bottom: 1px solid var(--bc-color);
}
&.pitch {
.vote-option-number {
display: none;
}
.tick-icon {
display: block;
}
.vote-option-content {
color: #000;
font-weight: 650;
}
}
.vote-option-number {
font-size: 11px;
color: #ffffff;
width: 14px;
height: 14px;
background-color: var(--main-color);
border-radius: 50%;
margin-right: 6px;
margin-top: 3px;
}
.tick-icon {
width: 14px;
height: 14px;
margin-top: 3px;
margin-right: 6px;
display: none;
}
.vote-option-content {
font-size: 14px;
color: #333;
line-height: 20px;
word-break: break-word;
}
.vote-option-progress {
height: 5px;
width: 100%;
justify-content: flex-end;
display: none;
margin-top: 5px;
.vote-option-progress-step {
// background-color: rgba(49, 215, 46, 0.498039215686275);
background-color: var(--main-color);
opacity: 0.49039;
// width: 150px;
height: 4px;
border-radius: 66px;
margin-right: 14px;
}
.vote-option-progress-value {
font-size: 12px;
color: var(--main-color);
line-height: 20px;
}
}
}
}
.vote-data {
justify-content: space-between;
font-size: 12px;
color: #aaaaaa;
line-height: 22px;
.vote-data-item {
margin-left: 16px;
.vote-data-icon {
width: 14px;
cursor: pointer;
}
}
}
}
}
.empty-box {
width: 1200px;
height: 540px;
background-color: rgba(255, 255, 255, 1);
border-radius: 16px;
margin: 0 auto;
}
</style>