Files
TeyvatGuide/src/pages/common/PostNews.vue
2025-12-24 17:17:40 +08:00

256 lines
7.6 KiB
Vue

<!-- 咨讯页面 -->
<template>
<v-app-bar>
<template #prepend>
<v-tabs v-model="recentNewsType" align-tabs="center" class="news-tab">
<v-tab
v-for="(value, index) in bbsEnum.post.newsTypeList"
:key="index"
:disabled="loading"
:value
@click="firstLoad()"
>
{{ rawData[value].name }}
</v-tab>
</v-tabs>
</template>
<template #title>
<v-text-field
v-model="search"
:hide-details="true"
:single-line="true"
append-icon="mdi-magnify"
class="news-search"
density="compact"
label="请输入帖子 ID 或搜索词"
@keydown.enter="searchPost()"
@click:append="searchPost()"
/>
</template>
<template #append>
<v-btn
:loading="loading"
class="post-news-btn"
size="small"
variant="elevated"
@click="firstLoad(true)"
>
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-btn class="post-news-btn" size="small" variant="elevated" @click="handleList()">
<v-icon>mdi-view-list</v-icon>
</v-btn>
<v-btn
v-if="gid === '2'"
class="post-news-btn"
prepend-icon="mdi-bullhorn"
rounded
variant="elevated"
@click="switchAnno"
>
切换游戏内公告
</v-btn>
</template>
</v-app-bar>
<v-window v-model="recentNewsType">
<v-window-item v-for="(value, index) in bbsEnum.post.newsTypeList" :key="index" :value="value">
<div class="news-grid">
<div v-for="item in postData[value]" :key="item.post.post_id">
<TPostCard :model-value="item" />
</div>
</div>
</v-window-item>
</v-window>
<ToChannel v-model="showList" :gid="gid" />
<VpOverlaySearch v-model="showSearch" :gid="gid" :keyword="search" />
</template>
<script lang="ts" setup>
import TPostCard from "@comp/app/t-postcard.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToChannel from "@comp/pageNews/to-channel.vue";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import bbsEnum from "@enum/bbs.js";
import { usePageReachBottom } from "@hooks/reachBottom.js";
import painterReq from "@req/painterReq.js";
import useAppStore from "@store/app.js";
import useBBSStore from "@store/bbs.js";
import TGLogger from "@utils/TGLogger.js";
import { createPost } from "@utils/TGWindow.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, reactive, Ref, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
type PostData = Record<TGApp.BBS.Post.NewsTypeEnum, Ref<Array<TGApp.BBS.Post.FullData>>>;
type RawItem = { isLast: boolean; name: string; lastId: number };
type RawData = Record<TGApp.BBS.Post.NewsTypeEnum, Ref<RawItem>>;
const router = useRouter();
const { recentNewsType } = storeToRefs(useAppStore());
const { gameList } = storeToRefs(useBBSStore());
const { gid } = <{ gid: string }>useRoute().params;
const { isReachBottom } = usePageReachBottom();
const label = computed<string>(() => {
const game = gameList.value.find((v) => v.id.toString() === gid);
return game?.name || "未知分区";
});
const loading = ref<boolean>(false);
const showList = ref<boolean>(false);
const showSearch = ref<boolean>(false);
const search = ref<string>("");
const postData = reactive<PostData>(
<PostData>(
Object.fromEntries(
bbsEnum.post.newsTypeList.map((v) => [v, shallowRef<Array<TGApp.BBS.Post.FullData>>([])]),
)
),
);
const rawData = reactive<RawData>(
<RawData>Object.fromEntries(
bbsEnum.post.newsTypeList.map((type) => [
type,
shallowRef<RawItem>({
isLast: false,
name: bbsEnum.post.newsTypeDesc(type),
lastId: 0,
}),
]),
),
);
onMounted(async () => await firstLoad());
watch(
() => isReachBottom.value,
async () => {
if (!isReachBottom.value) return;
await loadMore();
},
);
async function firstLoad(refresh: boolean = false): Promise<void> {
if (loading.value) return;
loading.value = true;
let key: TGApp.BBS.Post.NewsTypeEnum = bbsEnum.post.newsType.NOTICE;
if (bbsEnum.post.newsTypeList.includes(recentNewsType.value)) key = recentNewsType.value;
if (rawData[key].lastId !== 0) {
if (!refresh) {
loading.value = false;
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
return;
}
postData[key] = [];
rawData[key].lastId = 0;
}
await showLoading.start(`正在获取${label.value}${rawData[key].name}数据`);
const getData = await painterReq.news(gid, key);
await showLoading.update(`数量:${getData.list.length},是否最后一页:${getData.is_last}`);
rawData[key] = { isLast: getData.is_last, name: rawData[key].name, lastId: getData.list.length };
postData[key] = getData.list;
await showLoading.end();
await TGLogger.Info(`[News][${gid}][firstLoad] 获取${rawData[key].name}数据成功`);
showSnackbar.success(
`获取${label.value}${rawData[key].name}数据成功,共 ${getData.list.length}`,
);
loading.value = false;
}
async function switchAnno(): Promise<void> {
await TGLogger.Info(`[News][${gid}][switchAnno] 切换公告`);
await router.push("/announcements");
}
function handleList(): void {
if (showSearch.value === true) showSearch.value = false;
showList.value = true;
}
// 加载更多
async function loadMore(): Promise<void> {
if (loading.value) return;
loading.value = true;
let key: TGApp.BBS.Post.NewsTypeEnum = bbsEnum.post.newsType.NOTICE;
if (bbsEnum.post.newsTypeList.includes(recentNewsType.value)) key = recentNewsType.value;
if (rawData[key].isLast) {
showSnackbar.warn("已经是最后一页了");
loading.value = false;
return;
}
await showLoading.start(`正在获取${label.value}${rawData[key].name}数据`);
const mod = rawData[key].lastId % 20;
const pageSize = mod === 0 ? 20 : 20 - mod;
const getData = await painterReq.news(gid, key, pageSize, rawData[key].lastId);
await showLoading.update(`数量:${getData.list.length},是否最后一页:${getData.is_last}`);
rawData[key].lastId = rawData[key].lastId + getData.list.length;
rawData[key].isLast = getData.is_last;
postData[key] = postData[key].concat(getData.list);
if (rawData[key].isLast) {
await showLoading.end();
showSnackbar.warn("已经是最后一页了");
loading.value = false;
return;
}
await showLoading.end();
loading.value = false;
showSnackbar.success(`加载成功,共加载 ${getData.list.length}`);
}
async function searchPost(): Promise<void> {
if (search.value === "") {
showSnackbar.warn("请输入搜索内容");
return;
}
const numCheck = Number(search.value);
if (isNaN(numCheck) || numCheck % 1 !== 0) {
if (showList.value === true) showList.value = false;
showSearch.value = true;
return;
}
await createPost(search.value);
showSearch.value = false;
}
</script>
<style lang="scss" scoped>
.news-tab {
margin-bottom: 8px;
color: var(--common-text-title);
font-family: var(--font-title);
&:first-child {
margin-left: 12px;
}
}
.post-news-btn {
height: 40px;
background: var(--tgc-btn-1);
color: var(--btn-text);
font-family: var(--font-title);
&:last-child {
margin-right: 12px;
}
}
.post-news-btn + .post-news-btn {
margin-left: 8px;
}
.news-search {
margin: 0 16px;
color: var(--box-text-1);
}
.news-grid {
display: grid;
font-family: var(--font-title);
gap: 8px;
grid-auto-rows: auto;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
</style>