mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-15 09:48:14 +08:00
feat(parser): 帖子跳转不用写入文件做中转了
This commit is contained in:
@@ -28,7 +28,7 @@
|
|||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import TSidebar from "./components/t-sidebar.vue";
|
import TSidebar from "./components/t-sidebar.vue";
|
||||||
// tauri
|
// tauri
|
||||||
import { fs,window } from "@tauri-apps/api";
|
import { fs, window } from "@tauri-apps/api";
|
||||||
// store
|
// store
|
||||||
import useAppStore from "./store/modules/app";
|
import useAppStore from "./store/modules/app";
|
||||||
// utils
|
// utils
|
||||||
|
|||||||
@@ -1 +1,29 @@
|
|||||||
@import "fonts/index.css";
|
@import "fonts/index.css";
|
||||||
|
|
||||||
|
/* 米游社解析 css */
|
||||||
|
.mys-post-body {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mys-post-div {
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mys-post-cover {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mys-post-img {
|
||||||
|
width: 800px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mys-post-vod {
|
||||||
|
width: 800px;
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mys-post-span {
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 卡池封面 -->
|
<!-- 卡池封面 -->
|
||||||
<v-row class="Home-pool">
|
<v-row class="Home-pool">
|
||||||
<div class="Home-pool-cover" @click="toPost(pool.post_id)">
|
<div class="Home-pool-cover" @click="toPost(pool)">
|
||||||
<img :src="pool.cover" alt="cover" />
|
<img :src="pool.cover" alt="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div class="Home-pool-character">
|
<div class="Home-pool-character">
|
||||||
@@ -57,20 +57,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// vue
|
// vue
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import TLoading from "../components/t-loading.vue";
|
import TLoading from "../components/t-loading.vue";
|
||||||
// tauri
|
|
||||||
import { fs } from "@tauri-apps/api";
|
|
||||||
// store
|
|
||||||
import useAppStore from "../store/modules/app";
|
|
||||||
// plugin
|
// plugin
|
||||||
import MysOper from "../plugins/Mys";
|
import MysOper from "../plugins/Mys";
|
||||||
// utils
|
// utils
|
||||||
import { createTGWindow } from "../utils/TGWindow";
|
import { createTGWindow } from "../utils/TGWindow";
|
||||||
// interface
|
// interface
|
||||||
import { GachaData, GachaCard } from "../plugins/Mys/interface/gacha";
|
import { GachaData, GachaCard } from "../plugins/Mys/interface/gacha";
|
||||||
import { Post } from "../plugins/Mys/interface/post";
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const router = useRouter();
|
||||||
const poolInfo = ref([] as GachaCard[]);
|
const poolInfo = ref([] as GachaCard[]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const empty = ref(false);
|
const empty = ref(false);
|
||||||
@@ -116,18 +112,16 @@ function toOuter(url: string, title: string) {
|
|||||||
createTGWindow(url, "祈愿", title, 1200, 800, true);
|
createTGWindow(url, "祈愿", title, 1200, 800, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toPost(post_id: number) {
|
async function toPost(pool: GachaCard) {
|
||||||
// 获取帖子内容
|
// 获取路由路径
|
||||||
const post: Post = (await MysOper.Post.get(post_id)).post;
|
const path = router.resolve({
|
||||||
// 结构化渲染
|
name: "帖子详情",
|
||||||
const parseDoc = MysOper.Post.parser(post.structured_content);
|
params: {
|
||||||
// 将解析后的 doc 保存到 文件
|
post_id: pool.post_id.toString(),
|
||||||
await fs.writeTextFile(
|
},
|
||||||
`${appStore.dataPath.temp}\\${post_id}_home.html`,
|
}).href;
|
||||||
parseDoc.documentElement.outerHTML
|
// 打开新窗口
|
||||||
);
|
createTGWindow(path, "祈愿", pool.title, 960, 720, false);
|
||||||
const postUrl = `file:\\\\\\${appStore.dataPath.temp}\\${post.post_id}_home.html`;
|
|
||||||
createTGWindow(postUrl, "祈愿卡池", post.subject, 960, 720, false);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,10 @@
|
|||||||
class="justify-space-between flex-nowrap"
|
class="justify-space-between flex-nowrap"
|
||||||
width="320"
|
width="320"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img :src="item.cover" cover style="height: 150px" @click="toPost(item)"></v-img>
|
||||||
:src="item.cover"
|
|
||||||
cover
|
|
||||||
style="height: 150px"
|
|
||||||
@click="toPost(item.post_id)"
|
|
||||||
></v-img>
|
|
||||||
<v-card-title>{{ item.title }}</v-card-title>
|
<v-card-title>{{ item.title }}</v-card-title>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn @click="toPost(item.post_id)" class="ms-2 card-btn">
|
<v-btn @click="toPost(item)" class="ms-2 card-btn">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
||||||
</template>
|
</template>
|
||||||
@@ -56,16 +51,11 @@
|
|||||||
class="justify-space-between flex-nowrap"
|
class="justify-space-between flex-nowrap"
|
||||||
width="320"
|
width="320"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img :src="item.cover" cover style="height: 150px" @click="toPost(item)"></v-img>
|
||||||
:src="item.cover"
|
|
||||||
cover
|
|
||||||
style="height: 150px"
|
|
||||||
@click="toPost(item.post_id)"
|
|
||||||
></v-img>
|
|
||||||
<v-card-title>{{ item.title }}</v-card-title>
|
<v-card-title>{{ item.title }}</v-card-title>
|
||||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn @click="toPost(item.post_id)" class="ms-2 card-btn">
|
<v-btn @click="toPost(item)" class="ms-2 card-btn">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
||||||
</template>
|
</template>
|
||||||
@@ -107,15 +97,10 @@
|
|||||||
class="justify-space-between flex-nowrap"
|
class="justify-space-between flex-nowrap"
|
||||||
width="320"
|
width="320"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img :src="item.cover" cover style="height: 150px" @click="toPost(item)"></v-img>
|
||||||
:src="item.cover"
|
|
||||||
cover
|
|
||||||
style="height: 150px"
|
|
||||||
@click="toPost(item.post_id)"
|
|
||||||
></v-img>
|
|
||||||
<v-card-title>{{ item.title }}</v-card-title>
|
<v-card-title>{{ item.title }}</v-card-title>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn @click="toPost(item.post_id)" class="ms-2 card-btn">
|
<v-btn @click="toPost(item)" class="ms-2 card-btn">
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
<img src="../assets/icons/arrow-right.svg" alt="right" onload="SVGInject(this)" />
|
||||||
</template>
|
</template>
|
||||||
@@ -132,7 +117,7 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="load-news">
|
<div class="load-news">
|
||||||
<v-btn @click="loadMore('notice')">
|
<v-btn @click="loadMore('news')">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<img src="../assets/icons/arrow-left.svg" alt="right" onload="SVGInject(this)" />
|
<img src="../assets/icons/arrow-left.svg" alt="right" onload="SVGInject(this)" />
|
||||||
</template>
|
</template>
|
||||||
@@ -160,17 +145,17 @@ import MysOper from "../plugins/Mys";
|
|||||||
// utils
|
// utils
|
||||||
import { createTGWindow } from "../utils/TGWindow";
|
import { createTGWindow } from "../utils/TGWindow";
|
||||||
// interface
|
// interface
|
||||||
import { Post } from "../plugins/Mys/interface/post";
|
|
||||||
import { NewsCard, NewsData } from "../plugins/Mys/interface/news";
|
import { NewsCard, NewsData } from "../plugins/Mys/interface/news";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
// Store
|
// Store
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
// 渲染模式
|
|
||||||
const renderMode = ref(appStore.structureRender);
|
|
||||||
// loading
|
// loading
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const loadingTitle = ref("正在加载");
|
const loadingTitle = ref("正在加载");
|
||||||
|
// 路由
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const tab = ref("");
|
const tab = ref("");
|
||||||
@@ -242,25 +227,16 @@ async function loadMore(data: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toPost(post_id: number) {
|
async function toPost(item: NewsCard) {
|
||||||
// 获取帖子内容
|
// 获取路由路径
|
||||||
const post: Post = (await MysOper.Post.get(post_id)).post;
|
const path = router.resolve({
|
||||||
let parseDoc: Document;
|
name: "帖子详情",
|
||||||
// 获取渲染模式
|
params: {
|
||||||
if (renderMode.value) {
|
post_id: item.post_id.toString(),
|
||||||
// 结构化渲染
|
},
|
||||||
parseDoc = MysOper.Post.parser(post.structured_content);
|
}).href;
|
||||||
} else {
|
// 打开新窗口
|
||||||
// 原始渲染
|
createTGWindow(path, "帖子", item.title, 960, 720, false);
|
||||||
parseDoc = new DOMParser().parseFromString(post.content, "text/html");
|
|
||||||
}
|
|
||||||
// 将解析后的 doc 保存到 文件
|
|
||||||
await fs.writeTextFile(
|
|
||||||
`${appStore.dataPath.temp}\\${post_id}.html`,
|
|
||||||
parseDoc.documentElement.outerHTML
|
|
||||||
);
|
|
||||||
const postUrl = `file:\\\\\\${appStore.dataPath.temp}\\${post.post_id}.html`;
|
|
||||||
createTGWindow(postUrl, "MysPost", post.subject, 960, 720, false);
|
|
||||||
}
|
}
|
||||||
async function toJson(post_id: number) {
|
async function toJson(post_id: number) {
|
||||||
const post: string = (await MysOper.Post.get(post_id)).post.structured_content;
|
const post: string = (await MysOper.Post.get(post_id)).post.structured_content;
|
||||||
|
|||||||
@@ -10,40 +10,37 @@ import { PostStructuredContent } from "../interface/post";
|
|||||||
* @description 解析Mys数据
|
* @description 解析Mys数据
|
||||||
* @param {string} data Mys数据
|
* @param {string} data Mys数据
|
||||||
* @description 为了安全考虑,不会解析所有的属性,只会解析几个常用的属性
|
* @description 为了安全考虑,不会解析所有的属性,只会解析几个常用的属性
|
||||||
* @returns {Document} 解析后的 HTML 文档
|
* @returns {string} 解析后的HTML,可作为 v-html 使用
|
||||||
*/
|
*/
|
||||||
export function PostParser(data: string): Document {
|
export function PostParser(data: string): string {
|
||||||
// Json 化
|
// Json 化
|
||||||
let jsonData: PostStructuredContent[] = JSON.parse(data);
|
let jsonData: PostStructuredContent[] = JSON.parse(data);
|
||||||
// 创建 HTML 文档
|
// 创建 div
|
||||||
const doc = document.implementation.createHTMLDocument();
|
const doc = document.createElement("div");
|
||||||
// cover flag
|
// cover flag
|
||||||
let coverFlag = false;
|
let coverFlag = false;
|
||||||
// 遍历 Json 数据
|
// 遍历 Json 数据
|
||||||
jsonData.forEach((item: any) => {
|
jsonData.forEach((item: any) => {
|
||||||
if (typeof item.insert === "string") {
|
if (typeof item.insert === "string") {
|
||||||
const text = TextParser(item);
|
const text = TextParser(item);
|
||||||
doc.body.appendChild(text);
|
doc.appendChild(text);
|
||||||
} else if (item.insert.image) {
|
} else if (item.insert.image) {
|
||||||
const img = ImageParser(item, coverFlag);
|
const img = ImageParser(item, coverFlag);
|
||||||
coverFlag = img[1];
|
coverFlag = img[1];
|
||||||
doc.body.appendChild(img[0]);
|
doc.appendChild(img[0]);
|
||||||
} else if (item.insert.vod) {
|
} else if (item.insert.vod) {
|
||||||
// 创建 div
|
// 创建 div
|
||||||
const video = VideoParser(item);
|
const video = VideoParser(item);
|
||||||
// 插入 div
|
// 插入 div
|
||||||
doc.body.appendChild(video);
|
doc.appendChild(video);
|
||||||
} else if (item.insert.backup_text) {
|
} else if (item.insert.backup_text) {
|
||||||
// 创建 div
|
// 创建 div
|
||||||
const backup = BackupTextParser(item);
|
const backup = BackupTextParser(item);
|
||||||
// 插入 div
|
// 插入 div
|
||||||
doc.body.appendChild(backup);
|
doc.appendChild(backup);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// doc 宽度设为 800,居中
|
return doc.innerHTML;
|
||||||
doc.body.style.width = "800px";
|
|
||||||
doc.body.style.margin = "20px auto";
|
|
||||||
return doc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,8 +68,8 @@ function TextParser(data: PostStructuredContent): HTMLSpanElement {
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 行间距
|
// 添加 class
|
||||||
text.style.lineHeight = "2";
|
text.classList.add("mys-post-span");
|
||||||
// 设置 span 内容
|
// 设置 span 内容
|
||||||
text.innerText = data.insert;
|
text.innerText = data.insert;
|
||||||
// 返回文本
|
// 返回文本
|
||||||
@@ -107,21 +104,19 @@ function ImageParser(data: PostStructuredContent, coverFlag: boolean): [HTMLDivE
|
|||||||
// 创建图片
|
// 创建图片
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = data.insert.image;
|
img.src = data.insert.image;
|
||||||
// 设置图片属性,窗口宽度 900,页面宽度 800
|
// 添加 class
|
||||||
img.style.height = "auto"; // 高度自适应
|
img.classList.add("mys-post-img");
|
||||||
img.width = 800; // 设置宽度
|
|
||||||
// 判断是否是 cover
|
// 判断是否是 cover
|
||||||
if (!coverFlag) {
|
if (!coverFlag) {
|
||||||
// 添加 border-radius
|
// 添加 class
|
||||||
img.style.borderRadius = "10px";
|
img.classList.add("mys-post-cover");
|
||||||
// 设置 coverFlag
|
// 设置 coverFlag
|
||||||
coverFlag = true;
|
coverFlag = true;
|
||||||
}
|
}
|
||||||
// 插入图片
|
// 插入图片
|
||||||
div.appendChild(img);
|
div.appendChild(img);
|
||||||
// 设置 div 属性
|
// 添加 class
|
||||||
div.style.display = "center"; // 居中
|
div.classList.add("mys-post-div");
|
||||||
div.style.margin = "20px auto"; // 设置 margin
|
|
||||||
// 返回 div
|
// 返回 div
|
||||||
return [div, coverFlag];
|
return [div, coverFlag];
|
||||||
}
|
}
|
||||||
@@ -151,9 +146,9 @@ function VideoParser(data: PostStructuredContent): HTMLDivElement {
|
|||||||
});
|
});
|
||||||
// 设置视频属性
|
// 设置视频属性
|
||||||
video.poster = data.insert.vod.cover; // 设置封面
|
video.poster = data.insert.vod.cover; // 设置封面
|
||||||
video.width = 800; // 设置宽度
|
|
||||||
video.height = 450; // 设置高度
|
|
||||||
video.controls = true; // 设置 controls
|
video.controls = true; // 设置 controls
|
||||||
|
// 添加 class
|
||||||
|
video.classList.add("mys-post-vod");
|
||||||
// 添加 source
|
// 添加 source
|
||||||
const source = document.createElement("source");
|
const source = document.createElement("source");
|
||||||
source.src = resolution.url;
|
source.src = resolution.url;
|
||||||
@@ -162,9 +157,8 @@ function VideoParser(data: PostStructuredContent): HTMLDivElement {
|
|||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
// 插入 video
|
// 插入 video
|
||||||
div.appendChild(video);
|
div.appendChild(video);
|
||||||
// 设置 div 属性
|
// 添加 class
|
||||||
div.style.display = "center"; // 居中
|
div.classList.add("mys-post-div");
|
||||||
div.style.margin = "20px auto"; // 设置 margin
|
|
||||||
// 返回 div
|
// 返回 div
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
@@ -213,19 +207,20 @@ function BackupTextParser(data: PostStructuredContent): HTMLDivElement {
|
|||||||
// 解析内容
|
// 解析内容
|
||||||
contentJson.forEach(item => {
|
contentJson.forEach(item => {
|
||||||
// 数据检查
|
// 数据检查
|
||||||
if (typeof item.insert !== "string") {
|
if (typeof item.insert === "string") {
|
||||||
throw new Error("item.insert is not a string");
|
// 解析
|
||||||
|
content.appendChild(TextParser(item));
|
||||||
|
} else if (item.insert.image) {
|
||||||
|
// 解析
|
||||||
|
content.appendChild(ImageParser(item, false)[0]);
|
||||||
}
|
}
|
||||||
// 解析
|
|
||||||
content.appendChild(TextParser(item));
|
|
||||||
});
|
});
|
||||||
// 插入标题
|
// 插入标题
|
||||||
div.appendChild(title);
|
div.appendChild(title);
|
||||||
// 插入内容
|
// 插入内容
|
||||||
div.appendChild(content);
|
div.appendChild(content);
|
||||||
// 设置 div 属性
|
// 添加 class
|
||||||
div.style.display = "center"; // 居中
|
div.classList.add("mys-post-div");
|
||||||
div.style.margin = "20px auto"; // 设置 margin
|
|
||||||
// 返回 div
|
// 返回 div
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>TEST {{ $route.meta.isMain }}</div>
|
<div v-if="loading">
|
||||||
|
<t-loading :empty="loadingEmpty" :title="loadingTitle" />
|
||||||
|
</div>
|
||||||
|
<div v-else v-html="postHtml" class="mys-post-body" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// 获取路由传参
|
// vue
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import TLoading from "../components/t-loading.vue";
|
||||||
|
// plugins
|
||||||
|
import MysOper from "../plugins/Mys";
|
||||||
|
|
||||||
const route = useRoute();
|
// loading
|
||||||
|
const loading = ref(true as boolean);
|
||||||
|
const loadingTitle = ref("正在加载");
|
||||||
|
const loadingEmpty = ref(false as boolean);
|
||||||
|
|
||||||
// 获取路由参数
|
// 数据
|
||||||
const id = route.params.post_id;
|
const post_id = Number(useRoute().params.post_id);
|
||||||
console.log(id);
|
const postHtml = ref("");
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// 检查数据
|
||||||
|
if (!post_id) {
|
||||||
|
loadingEmpty.value = true;
|
||||||
|
loadingTitle.value = "未找到数据";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取数据
|
||||||
|
loadingTitle.value = "正在获取数据...";
|
||||||
|
const postData = await MysOper.Post.get(post_id);
|
||||||
|
loadingTitle.value = "正在渲染数据...";
|
||||||
|
postHtml.value = MysOper.Post.parser(postData.post.structured_content);
|
||||||
|
setInterval(() => {
|
||||||
|
loading.value = false;
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="css"></style>
|
<style lang="css"></style>
|
||||||
|
|||||||
Reference in New Issue
Block a user