Files
TeyvatGuide/src/pages/common/PostCollect.vue
2024-11-19 14:45:29 +08:00

464 lines
14 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>
<v-app-bar>
<div class="pc-top">
<div class="pc-title">
<img src="/source/UI/posts.png" alt="posts" />
<span>收藏</span>
</div>
<v-select
v-model="curSelect"
class="pc-select"
:items="collections"
:clearable="curSelect !== '未分类'"
variant="outlined"
label="合集"
>
<template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :title="item.raw.title" :subtitle="item.raw.desc" />
</template>
</v-select>
</div>
<template #append>
<v-pagination class="pc-page" v-model="page" :total-visible="view" :length="length" />
</template>
<template #extension>
<div class="pc-btns">
<v-btn
size="small"
class="pc-btn"
icon="mdi-sort"
@click="sortPost(!sortId)"
:title="sortId ? '按更新时间排序' : '按帖子ID排序'"
/>
<v-btn
:disabled="selectedMode"
size="small"
class="pc-btn"
icon="mdi-refresh"
title="获取用户收藏"
@click="freshUser()"
/>
<v-btn
:disabled="selectedMode"
size="small"
class="pc-btn"
icon="mdi-import"
@click="freshOther"
title="导入其他用户收藏"
/>
<v-btn
size="small"
class="pc-btn"
:icon="selectedMode ? 'mdi-folder-move' : 'mdi-pencil'"
@click="toSelect()"
title="编辑收藏"
/>
<v-btn
:disabled="selectedMode"
size="small"
class="pc-btn"
icon="mdi-plus"
@click="addCollect"
title="新建分类"
/>
<v-btn
:disabled="selectedMode || curSelect === '未分类'"
size="small"
class="pc-btn"
icon="mdi-information"
@click="toEdit()"
title="编辑分类"
/>
<v-btn
size="small"
class="pc-btn"
icon="mdi-delete"
@click="deleteOper(false)"
:title="selectedMode ? '删除帖子分类' : '清空合集'"
/>
<v-btn
size="small"
class="pc-btn"
icon="mdi-delete-forever"
@click="deleteOper(true)"
:title="selectedMode ? '删除帖子' : '删除合集'"
/>
</div>
</template>
</v-app-bar>
<div class="pc-posts">
<div v-for="item in curPosts" :key="item.post.post_id">
<TPostCard @onSelected="handleSelected" :model-value="item" :select-mode="selectedMode" />
</div>
</div>
<ToCollectPost @submit="load" :post="selectedPost" v-model="showOverlay" />
</template>
<script lang="ts" setup>
import { event } from "@tauri-apps/api";
import { UnlistenFn } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import TPostCard from "../../components/app/t-postcard.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import ToCollectPost from "../../components/pageCollect/to-collectPost.vue";
import TSUserCollection from "../../plugins/Sqlite/modules/userCollect.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import BBSApi from "../../web/request/bbsReq.js";
const userStore = storeToRefs(useUserStore());
const collections = ref<TGApp.Sqlite.UserCollection.UFCollection[]>([]);
const selected = ref<TGApp.Sqlite.UserCollection.UFPost[]>([]);
const curSelect = ref<string>("未分类");
const page = ref(1);
const length = computed(() => Math.ceil(selected.value.length / 12));
const view = computed(() => {
if (length.value === 1) return 0;
return length.value > 5 ? 5 : length.value;
});
const curPosts = computed<TGApp.Plugins.Mys.Post.FullData[]>(() => {
return selected.value
.slice((page.value - 1) * 12, page.value * 12)
.map((i) => JSON.parse(i.content));
});
const selectedMode = ref<boolean>(false);
const selectedPost = ref<Array<string>>([]);
const showOverlay = ref(false);
const sortId = ref<boolean>(false);
let collectListener: UnlistenFn | null = null;
onMounted(async () => {
collectListener = await event.listen("refreshCollect", async () => await load());
await load();
});
onUnmounted(() => {
if (collectListener !== null) {
collectListener();
collectListener = null;
}
});
function handleSelected(v: string) {
if (selectedPost.value.includes(v)) {
selectedPost.value = selectedPost.value.filter((i) => i !== v);
} else {
selectedPost.value.push(v);
}
}
function sortPost(value: boolean) {
let ori = sortId.value;
sortId.value = value;
selected.value = selected.value.sort((a, b) => {
if (sortId.value) {
return Number(b.id) - Number(a.id);
} else {
return Number(b.updated) - Number(a.updated);
}
});
if (ori !== sortId.value) {
showSnackbar.success(`${sortId.value ? "按帖子ID排序" : "按更新时间排序"}`);
}
}
async function load(): Promise<void> {
showLoading.start("正在加载收藏帖子...", "获取收藏合集");
collections.value = await TSUserCollection.getCollectList();
showLoading.update("正在加载收藏帖子...", "获取未分类帖子");
const postUnCollect = await TSUserCollection.getUnCollectPostList();
if (curSelect.value === "未分类" || collections.value.length === 0) {
selected.value = postUnCollect;
} else if (collections.value.find((c) => c.title === curSelect.value)) {
selected.value = await TSUserCollection.getCollectPostList(curSelect.value);
} else {
selected.value = postUnCollect;
curSelect.value = "未分类";
}
sortPost(sortId.value);
selectedMode.value = false;
selectedPost.value = [];
if (page.value > length.value) page.value = 1;
showLoading.end();
}
function toSelect() {
if (selectedMode.value) {
selectedMode.value = false;
if (selectedPost.value.length === 0) return;
showOverlay.value = true;
return;
}
selectedPost.value = [];
selectedMode.value = true;
}
async function addCollect(): Promise<void> {
let title, desc;
const titleC = await showDialog.input("新建分类", "分类名称:");
if (titleC === undefined || titleC === false) return;
if (titleC === "未分类") {
showSnackbar.warn("分类名不可为未分类");
return;
}
if (collections.value.find((i) => i.title === titleC)) {
showSnackbar.warn("分类已存在");
return;
}
title = titleC;
const descC = await showDialog.input("新建分类", "分类描述:");
if (descC === false) return;
if (descC === undefined) desc = title;
else desc = descC;
const res = await TSUserCollection.createCollect(title, desc);
if (res) {
showSnackbar.success("成功新建分类");
await load();
return;
}
showSnackbar.error("新建失败");
}
async function toEdit(): Promise<void> {
const collect = collections.value.find((c) => c.title === curSelect.value);
if (collect === undefined) {
showSnackbar.warn("未找到合集信息");
return;
}
let cTc = await showDialog.input("修改分类标题", "分类标题:", collect.title);
if (cTc === false) {
showSnackbar.cancel("取消修改分类信息");
return;
}
if (typeof cTc !== "string") cTc = collect.title;
if (cTc === "未分类") {
showSnackbar.warn("分类名不可为未分类");
return;
}
if (cTc !== collect.title && collections.value.find((c) => c.title === cTc)) {
showSnackbar.warn("分类名称重复");
return;
}
let cTd = await showDialog.input("修改分类描述", "分类描述:", collect.desc);
if (typeof cTd !== "string") cTd = collect.desc;
const cc = await showDialog.check("确定修改?", `[${cTc}] ${cTd}`);
if (!cc) {
showSnackbar.cancel("取消修改分类信息");
return;
}
showLoading.start("正在修改分类信息...");
const check = await TSUserCollection.updateCollect(collect.title, cTc, cTd);
showLoading.end();
if (!check) {
showSnackbar.warn("修改分类信息失败");
return;
}
showSnackbar.success("成功修改分类信息!");
await load();
}
async function deleteOper(force: boolean): Promise<void> {
if (selectedMode.value) await deletePost(force);
else await deleteCollect(force);
}
async function deletePost(force: boolean = false): Promise<void> {
if (selectedPost.value.length === 0) {
showSnackbar.warn("未选择帖子");
return;
}
const title = force ? "删除帖子" : "移除帖子分类";
const check = await showDialog.check(`确定${title}?`, `${selectedPost.value.length} 条帖子`);
if (!check) {
showSnackbar.cancel("取消操作");
return;
}
showLoading.start(`正在${title}...`);
let success = 0;
for (const post of selectedPost.value) {
const check = await TSUserCollection.deletePostCollect(post, force);
if (check) {
success++;
continue;
}
showSnackbar.warn(`帖子 ${post} 操作失败`);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
showLoading.end();
showSnackbar.success(`成功${title} ${success}`);
await load();
}
async function deleteCollect(force: boolean): Promise<void> {
if (curSelect.value === "未分类" && force) {
showSnackbar.warn("未分类不可删除");
return;
}
const title = force ? "删除分类" : "清空分类";
const check = await showDialog.check(
`确定${title}?`,
`该分类下 ${selected.value.length} 条帖子将被${force ? "删除" : "移除分类(未分类将被删除)"}`,
);
if (!check) {
showSnackbar.cancel("取消操作");
return;
}
let resD;
if (curSelect.value !== "未分类") {
resD = await TSUserCollection.deleteCollect(curSelect.value, force);
} else {
resD = await TSUserCollection.deleteUnCollectPost();
}
if (resD) {
showSnackbar.success(`成功${title}`);
await load();
return;
}
showSnackbar.warn(`${title} 失败`);
}
// 根据合集筛选
async function freshPost(select: string | null): Promise<void> {
if (select === null) {
curSelect.value = "未分类";
return;
}
showLoading.start("正在获取合集帖子...", `获取合集 ${select}`);
if (select === "未分类") {
curSelect.value = "未分类";
selected.value = await TSUserCollection.getUnCollectPostList();
} else {
selected.value = await TSUserCollection.getCollectPostList(select);
}
page.value = 1;
showLoading.end();
showSnackbar.success(`切换合集 ${select},共 ${selected.value.length} 条帖子`);
}
watch(
() => curSelect.value,
async () => await freshPost(curSelect.value),
);
async function freshOther(): Promise<void> {
const uidInput = await showDialog.input("导入其他用户收藏", "米游社UID");
if (typeof uidInput === "string") {
if (isNaN(Number(uidInput))) {
showSnackbar.warn("UID 格式错误,请输入数字");
return;
}
await freshUser(uidInput);
return;
}
showSnackbar.cancel("取消导入");
}
async function freshUser(uid?: string): Promise<void> {
if (!userStore.cookie.value) {
showSnackbar.warn("请先登录");
return;
}
const uidReal = uid || userStore.briefInfo.value.uid;
showLoading.start("获取用户收藏...", `UID: ${uidReal}`);
let res = await BBSApi.lovePost(userStore.cookie.value, uidReal);
while (true) {
if ("retcode" in res) {
showLoading.end();
if (res.retcode === 1001) {
showSnackbar.warn("用户收藏已设为私密,无法获取");
} else {
showSnackbar.error(`[${res.retcode}] ${res.message}`);
}
break;
}
let posts = res.list;
showLoading.update("获取用户收藏...", `合并收藏帖子 [offset]${res.next_offset}...`);
await mergePosts(posts, uid || userStore.briefInfo.value.uid);
if (res.is_last) break;
showLoading.update("获取用户收藏...", `[offset]${res.next_offset} [is_last]${res.is_last}`);
res = await BBSApi.lovePost(
userStore.cookie.value,
uid || userStore.briefInfo.value.uid,
res.next_offset,
);
}
showLoading.end();
showSnackbar.success("获取用户收藏成功");
window.location.reload();
}
// 合并收藏帖子
async function mergePosts(
posts: TGApp.Plugins.Mys.Post.FullData[],
collect: string,
): Promise<void> {
const title = `用户收藏-${collect}`;
for (const post of posts) {
showLoading.start("获取用户收藏...", `[POST]${post.post.subject} [collection]${title}`);
const res = await TSUserCollection.addCollect(post.post.post_id, post, title, true);
if (!res) {
await TGLogger.Error(`[PostCollect] mergePosts [${post.post.post_id}]`);
}
}
}
</script>
<style lang="css" scoped>
.pc-top {
display: flex;
align-items: flex-end;
justify-content: space-between;
padding: 0 10px;
gap: 10px;
}
.pc-title {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
}
.pc-select {
max-width: 400px;
height: 50px;
}
.pc-btns {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
gap: 10px;
}
.pc-btn {
height: 40px;
border: 1px solid var(--common-shadow-4);
background: var(--btn-bg-1);
color: var(--btn-text-1);
font-family: var(--font-title);
}
.pc-posts {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(4, minmax(320px, 1fr));
}
</style>