Files
TeyvatGuide/src/views/t-post.vue
2024-02-29 15:14:33 +08:00

407 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<TSwitchTheme />
<TShareBtn
v-show="!loadingEmpty"
v-model="postRef"
v-model:loading="loadShare"
:title="shareTitle"
/>
<ToLoading v-model="loading" :empty="loadingEmpty" :title="loadingTitle" :subtitle="loadingSub" />
<div class="tp-post-body" v-if="postData">
<div class="tp-post-info">
<div class="tp-post-version">
PostID{{ postId }} | Render by TeyvatGuide v{{ appVersion }}
</div>
<div class="tp-post-meta">
<div class="mpm-forum" v-if="postData.forum">
<img :src="postData.forum.icon" alt="forumIcon" />
<span>{{ postData.forum.name }}</span>
</div>
<div class="mpm-item" :title="`浏览数:${postData.stat.view_num}`">
<v-icon>mdi-eye</v-icon>
<span>{{ postData.stat.view_num }}</span>
</div>
<div class="mpm-item" :title="`收藏数:${postData.stat.bookmark_num}`">
<v-icon>mdi-star</v-icon>
<span>{{ postData.stat.bookmark_num }}</span>
</div>
<div class="mpm-item" :title="`回复数:${postData.stat.reply_num}`">
<v-icon>mdi-comment</v-icon>
<span>{{ postData.stat.reply_num }}</span>
</div>
<div class="mpm-item" :title="`点赞数:${postData.stat.like_num}`">
<v-icon>mdi-thumb-up</v-icon>
<span>{{ postData.stat.like_num }}</span>
</div>
<div class="mpm-item" :title="`转发数:${postData.stat.forward_num}`">
<v-icon>mdi-share-variant</v-icon>
<span>{{ postData.stat.forward_num }}</span>
</div>
</div>
<TpAvatar :data="postData.user" position="right" />
</div>
<div class="tp-post-title" @click="toPost()" title="点击查看评论">
<span class="mpt-official" v-if="postData.post.post_status.is_official"></span>
<span>{{ postData.post.subject }}</span>
</div>
<!-- 一些附加信息比如 topiccollection -->
<div class="tp-post-extra">
<div
class="tp-post-collection"
:title="`合集ID${postData.collection.collection_id}`"
v-if="postData.collection"
@click="showOverlayC()"
>
<v-icon size="12">mdi-widgets</v-icon>
<span>{{ postData.collection.collection_title }}</span>
<span>{{ postData.collection.cur }}/{{ postData.collection.total }}</span>
</div>
<div v-for="topic in postData.topics" :key="topic.id" class="tp-post-topic">
<v-icon size="12">mdi-tag</v-icon>
<span>{{ topic.name }}</span>
</div>
</div>
<div class="tp-post-subtitle">
<span :title="`更新时间: ${getDate(postData.post.updated_at)}`">
创建时间{{ getDate(postData.post.created_at) }}
</span>
<span>分享时间{{ getDate(shareTime) }}</span>
<span v-if="postData.post.republish_authorization !== 0" class="tp-post-copyright">
<v-icon size="20">mdi-copyright</v-icon>
<span>{{ getRepublishAuthorization(postData.post.republish_authorization) }}</span>
</span>
</div>
<TpParser v-model:data="renderPost" />
</div>
<TpoCollection
v-model="showCollection"
:collection="postData.collection"
v-if="postData?.collection"
/>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { appWindow } from "@tauri-apps/api/window";
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import TSwitchTheme from "../components/app/t-switchTheme.vue";
import TShareBtn from "../components/main/t-shareBtn.vue";
import ToLoading from "../components/overlay/to-loading.vue";
import TpAvatar from "../components/post/tp-avatar.vue";
import TpParser from "../components/post/tp-parser.vue";
import TpoCollection from "../components/post/tpo-collection.vue";
import Mys from "../plugins/Mys";
import { useAppStore } from "../store/modules/app";
import TGClient from "../utils/TGClient";
import TGLogger from "../utils/TGLogger";
import { createTGWindow } from "../utils/TGWindow";
// loading
const loading = ref<boolean>(true);
const loadShare = ref<boolean>(false);
const loadingTitle = ref<string>("正在加载");
const loadingSub = ref<string>();
const loadingEmpty = ref<boolean>(false);
// share
const postRef = ref<HTMLElement>(<HTMLElement>{});
const shareTitle = ref<string>("");
// 数据
const appVersion = ref<string>();
const postId = Number(useRoute().params.post_id);
const renderPost = ref<TGApp.Plugins.Mys.SctPost.Base[]>([]);
const postData = ref<TGApp.Plugins.Mys.Post.FullData>();
// 当前时间,秒级时间戳
const shareTime = ref<number>(Math.floor(Date.now() / 1000));
// 时间更新定时器
const shareTimeTimer = ref<any>();
// 合集
const showCollection = ref<boolean>(false);
onMounted(async () => {
await appWindow.show();
appVersion.value = await app.getVersion();
// 检查数据
if (!postId) {
loadingEmpty.value = true;
loadingTitle.value = "未找到数据";
await appWindow.setTitle("未找到数据");
await TGLogger.Error("[t-post][onMounted] PostID 不存在");
return;
}
// 获取数据
loadingTitle.value = "正在获取数据...";
try {
postData.value = await Mys.Post.get(postId);
loadingTitle.value = "正在渲染数据...";
renderPost.value = getRenderPost(postData.value);
shareTitle.value = `Post_${postId}`;
await appWindow.setTitle(`Post_${postId} ${postData.value.post.subject}`);
} catch (error) {
if (error instanceof Error) {
await TGLogger.Error(`[t-post][${postId}] ${error.name}: ${error.message}`);
loadingTitle.value = error.name;
loadingSub.value = error.message;
} else {
console.error(error);
loadingTitle.value = "帖子不存在或解析失败";
loadingSub.value = "请检查帖子是否存在或者是否为合法的帖子";
}
loadingEmpty.value = true;
await appWindow.setTitle(`Post_${postId} Parsing Error`);
return;
}
await TGLogger.Info(`[t-post][${postId}][onMounted] ${postData.value.post.subject}`);
// 打开 json
const isDev = useAppStore().devMode ?? false;
if (isDev) {
await TGLogger.Info(`[t-post][${postId}][onMounted] 打开 JSON 窗口`);
createPostJson(postId);
}
await nextTick(() => {
shareTimeTimer.value = setInterval(() => {
shareTime.value = Math.floor(Date.now() / 1000);
}, 1000);
loading.value = false;
postRef.value = <HTMLElement>document.querySelector(".tp-post-body");
});
});
watch(loadShare, async (value) => {
if (value) {
shareTime.value = Math.floor(Date.now() / 1000);
loadingTitle.value = "正在生成分享图片";
loadingSub.value = `${shareTitle.value}.png`;
loading.value = true;
await TGLogger.Info(`[t-post.vue][${postId}][share] 生成分享图片:${shareTitle.value}.png`);
} else {
loadingSub.value = "";
loading.value = false;
await TGLogger.Info(`[t-post.vue][${postId}][share] 生成分享图片完成:${shareTitle.value}.png`);
}
});
function showOverlayC() {
showCollection.value = true;
}
function getDate(date: number): string {
return new Date(date * 1000).toLocaleString().replace(/\//g, "-");
}
function getRepublishAuthorization(type: number): string {
switch (type) {
case 1:
return "已开启创作声明,禁止转载或摘编";
case 2:
return "已开启创作声明,允许规范转载";
default:
return "未知";
}
}
function getRenderPost(data: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.SctPost.Base[] {
const postContent = data.post.content;
let jsonParse: string;
if (postContent.startsWith("<")) {
jsonParse = data.post.structured_content;
} else {
try {
jsonParse = parseContent(data.post.content);
} catch (e) {
if (e instanceof SyntaxError) {
TGLogger.Warn(`[t-post][${postId}] ${e.name}: ${e.message}`);
}
jsonParse = data.post.structured_content;
}
}
return JSON.parse(jsonParse);
}
function parseContent(content: string): string {
const data: TGApp.Plugins.Mys.SctPost.Other = JSON.parse(content);
const result: TGApp.Plugins.Mys.SctPost.Base[] = [];
const keys = Object.keys(data);
keys.forEach((key) => {
switch (key) {
case "describe":
result.push({
insert: data.describe,
});
break;
case "imgs":
data.imgs.forEach((item) => {
result.push({
insert: {
image: item,
},
});
});
break;
default:
TGLogger.Warn(`[t-post][${postId}][parseContent] Unknown key: ${key}`);
result.push({
insert: data[key],
});
break;
}
});
return JSON.stringify(result);
}
function createPostJson(postId: number): void {
const jsonPath = `/post_detail_json/${postId}`;
const jsonTitle = `Post_${postId}_JSON`;
createTGWindow(jsonPath, "Dev_JSON", jsonTitle, 960, 720, false, false);
}
async function toPost(): Promise<void> {
const url = `https://m.miyoushe.com/ys/#/article/${postId}`;
await TGClient.open("web_thin", url);
}
onUnmounted(() => {
clearInterval(shareTimeTimer.value);
});
</script>
<style lang="css" scoped>
.tp-post-body {
width: 800px;
margin: 0 auto;
font-family: var(--font-text);
}
/* title */
.tp-post-title {
display: flex;
width: fit-content;
align-items: center;
justify-content: start;
color: var(--common-text-title);
cursor: pointer;
font-family: var(--font-title);
font-size: 20px;
}
.mpt-official {
display: inline-block;
width: 24px;
align-items: center;
justify-content: center;
border-radius: 5px;
margin-right: 2px;
background: var(--common-shadow-1);
color: var(--box-text-5);
font-size: 16px;
text-align: center;
}
/* subtitle */
.tp-post-subtitle {
display: flex;
align-items: center;
justify-content: flex-start;
column-gap: 10px;
font-size: 16px;
opacity: 0.6;
}
.tp-post-copyright {
color: var(--common-text-title);
font-family: var(--font-title);
}
/* info */
.tp-post-info {
position: relative;
display: flex;
align-items: end;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px dashed var(--common-shadow-2);
margin-bottom: 10px;
}
.tp-post-version {
position: absolute;
top: 0;
left: 0;
color: var(--box-text-4);
font-family: var(--font-title);
font-size: 14px;
opacity: 0.6;
}
/* meta */
.tp-post-meta {
display: flex;
align-items: center;
justify-content: start;
color: var(--box-text-4);
column-gap: 10px;
font-size: 14px;
}
.mpm-forum {
display: flex;
align-items: center;
justify-content: center;
}
.mpm-forum img {
width: 30px;
height: 30px;
object-fit: cover;
}
.mpm-forum span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mpm-item {
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
column-gap: 2px;
opacity: 0.8;
}
/* extra */
.tp-post-extra {
display: flex;
align-items: center;
justify-content: start;
column-gap: 10px;
}
/* collection */
.tp-post-collection {
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
color: var(--box-text-3);
column-gap: 5px;
cursor: pointer;
font-family: var(--font-title);
font-size: 12px;
}
/* topic */
.tp-post-topic {
display: flex;
align-items: center;
justify-content: center;
color: var(--box-text-5);
font-family: var(--font-title);
font-size: 12px;
}
</style>