diff --git a/src/components/app/t-setCollect.vue b/src/components/app/t-setCollect.vue new file mode 100644 index 00000000..ea84774f --- /dev/null +++ b/src/components/app/t-setCollect.vue @@ -0,0 +1,103 @@ + + + + + {{ isCollected ? "mdi-star" : "mdi-star-outline" }} + + + + + + diff --git a/src/components/app/t-sidebar.vue b/src/components/app/t-sidebar.vue index 294584aa..0b1d6ef7 100644 --- a/src/components/app/t-sidebar.vue +++ b/src/components/app/t-sidebar.vue @@ -205,6 +205,11 @@ + + + + + + + + + + 获取用户收藏 + + + + + + + + + + + + diff --git a/src/plugins/Sqlite/index.ts b/src/plugins/Sqlite/index.ts index 1b518c5d..581bc7dc 100644 --- a/src/plugins/Sqlite/index.ts +++ b/src/plugins/Sqlite/index.ts @@ -1,19 +1,19 @@ /** * @file plugins/Sqlite/index.ts * @description Sqlite 数据库操作类 - * @since Beta v0.4.4 + * @since Beta v0.4.5 */ import { app } from "@tauri-apps/api"; import Database from "tauri-plugin-sql-api"; import initDataSql from "./sql/initData"; -import initTableSql from "./sql/initTable"; import { importAbyssData, insertAbyssData, insertAppData, insertGameAccountData, + insertPostCollectData, insertRecordData, insertRoleData, } from "./sql/insertData"; @@ -66,12 +66,12 @@ class Sqlite { /** * @description 初始化数据库 - * @since Beta v0.3.3 + * @since Beta v0.4.5 * @returns {Promise} */ public async initDB(): Promise { const db = await this.getDB(); - const sql = [...initTableSql(), ...(await initDataSql())]; + const sql = await initDataSql(); for (const item of sql) { await db.execute(item); } @@ -181,7 +181,6 @@ class Sqlite { let isVerified = false; const sqlT = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"; const res: Array<{ name: string }> = await db.select(sqlT); - // 只检测已有的表是否具备,不检测总表数目 if (this.tables.every((item) => res.map((i) => i.name).includes(item))) { isVerified = true; } @@ -491,6 +490,70 @@ class Sqlite { if (res.length === 0) return undefined; return res[0].id; } + + /** + * @description 检测特定表是否存在 + * @since Beta v0.4.5 + * @param {string} table 表名 + * @returns {Promise} + */ + async checkTableExist(table: string): Promise { + const db = await this.getDB(); + const sql = `SELECT name + FROM sqlite_master + WHERE type='table' + AND name='${table}';`; + const res: Array<{ name: string }> = await db.select(sql); + return res.length > 0; + } + + /** + * @description 检测帖子是否已收藏 + * @since Beta v0.4.5 + * @param {string} postId 帖子 id + * @returns {Promise} 当该帖子被归到多个分类时,返回分类数量 + */ + async checkPostCollect(postId: string): Promise { + const db = await this.getDB(); + const sql = `SELECT collect + FROM UserCollection + WHERE postId = '${postId}';`; + const res: Array<{ collect: string }> = await db.select(sql); + if (res.length === 0) return false; + return res[0].collect; + } + + /** + * @description 收藏单个帖子 + * @since Beta v0.4.5 + * @param {TGApp.Plugins.Mys.Post.FullData} post 帖子 + * @param {Array} collect 分类 + * @param {string} uid 用户 uid + * @returns {Promise} + */ + async collectPost( + post: TGApp.Plugins.Mys.Post.FullData, + collect: string[], + uid?: string, + ): Promise { + const db = await this.getDB(); + const sql = insertPostCollectData(post, collect, uid); + await db.execute(sql); + } + + /** + * @description 取消收藏 + * @since Beta v0.4.5 + * @param {string} postId 帖子 id + * @returns {Promise} + */ + async cancelCollect(postId: string): Promise { + const db = await this.getDB(); + const sql = `DELETE + FROM UserCollection + WHERE postId = '${postId}';`; + await db.execute(sql); + } } const TGSqlite = new Sqlite(); diff --git a/src/plugins/Sqlite/sql/initData.ts b/src/plugins/Sqlite/sql/initData.ts index 816b224c..f53e9c6a 100644 --- a/src/plugins/Sqlite/sql/initData.ts +++ b/src/plugins/Sqlite/sql/initData.ts @@ -1,11 +1,12 @@ /** - * @file plugins Sqlite sql initData.ts + * @file plugins/Sqlite/sql/initData.ts * @description Sqlite 初始化数据 sql 语句 - * @since Alpha v0.2.0 + * @since Beta v0.4.5 */ import { app } from "@tauri-apps/api"; +import initTableSql from "./initTable"; import { insertAchievementData, insertAchievementSeriesData, @@ -31,19 +32,21 @@ async function initAppData(): Promise { const buildTime: string = getBuildTime(); // 初始化应用版本 sqlRes.push(` - INSERT INTO AppData (key, value, updated) - VALUES ('appVersion', '${appVersion}', datetime('now', 'localtime')) - ON CONFLICT(key) DO UPDATE SET value = '${appVersion}', updated = datetime('now', 'localtime');`); + INSERT INTO AppData (key, value, updated) + VALUES ('appVersion', '${appVersion}', datetime('now', 'localtime')) + ON CONFLICT(key) DO UPDATE SET value = '${appVersion}', + updated = datetime('now', 'localtime');`); // 初始化应用数据更新时间 sqlRes.push(` - INSERT INTO AppData (key, value, updated) - VALUES ('dataUpdated', '${buildTime}', datetime('now', 'localtime')) - ON CONFLICT(key) DO UPDATE SET value = '${buildTime}', updated = datetime('now', 'localtime');`); + INSERT INTO AppData (key, value, updated) + VALUES ('dataUpdated', '${buildTime}', datetime('now', 'localtime')) + ON CONFLICT(key) DO UPDATE SET value = '${buildTime}', + updated = datetime('now', 'localtime');`); // 初始化 cookie sqlRes.push(` - INSERT INTO AppData (key, value, updated) - VALUES ('cookie', '{}', datetime('now', 'localtime')) - ON CONFLICT(key) DO NOTHING;`); + INSERT INTO AppData (key, value, updated) + VALUES ('cookie', '{}', datetime('now', 'localtime')) + ON CONFLICT(key) DO NOTHING;`); return sqlRes; } @@ -85,11 +88,12 @@ function initCharacterData(): string[] { /** * @description 初始化数据 - * @since Alpha v0.2.0 + * @since Beta v0.4.5 * @returns {Promise} sql */ async function initDataSql(): Promise { const sqlRes: string[] = []; + sqlRes.push(...initTableSql()); sqlRes.push(...(await initAppData())); sqlRes.push(...initAchievementSeriesData()); sqlRes.push(...initAchievementData()); diff --git a/src/plugins/Sqlite/sql/insertData.ts b/src/plugins/Sqlite/sql/insertData.ts index f7f8428d..87d5e3b6 100644 --- a/src/plugins/Sqlite/sql/insertData.ts +++ b/src/plugins/Sqlite/sql/insertData.ts @@ -269,3 +269,28 @@ export function insertRoleData(uid: string, data: TGApp.Game.Character.ListItem[ }); return sql.join(""); } + +/** + * @description 插入帖子收藏数据 + * @since Beta v0.4.5 + * @param {TGApp.Plugins.Mys.Post.FullData} data 帖子数据 + * @param {Array} collect 合集 + * @param {string} uid 用户 UID + * @returns {string} sql + */ +export function insertPostCollectData( + data: TGApp.Plugins.Mys.Post.FullData, + collect: string[], + uid?: string, +): string { + return ` + INSERT INTO UserCollection (postId, title, content, collect, uid, updated) + VALUES (${data.post.post_id}, '${data.post.subject}', '${JSON.stringify(data)}', + '${JSON.stringify(collect)}', '${uid}', datetime('now', 'localtime')) + ON CONFLICT DO UPDATE + SET title = '${data.post.subject}', + content = '${JSON.stringify(data)}', + collect = '${JSON.stringify(collect)}', + updated = datetime('now', 'localtime'); + `; +} diff --git a/src/router/modules/main.ts b/src/router/modules/main.ts index 87c53346..df3e61bd 100644 --- a/src/router/modules/main.ts +++ b/src/router/modules/main.ts @@ -1,7 +1,7 @@ /** * @file router/modules/main.ts * @description 主路由模块 - * @since Beta v0.4.4 + * @since Beta v0.4.5 */ const mainRoutes = [ @@ -30,6 +30,11 @@ const mainRoutes = [ name: "成就", component: async () => await import("../../pages/common/Achievements.vue"), }, + { + path: "/collection", + name: "收藏", + component: async () => await import("../../pages/common/PostCollect.vue"), + }, { path: "/test", name: "测试页", diff --git a/src/types/BBS/Collection.d.ts b/src/types/BBS/Collection.d.ts new file mode 100644 index 00000000..05a1140f --- /dev/null +++ b/src/types/BBS/Collection.d.ts @@ -0,0 +1,40 @@ +/** + * @file types/BBS/Collection.d.ts + * @description BBS 收藏相关类型定义文件 + * @since Beta v0.4.5 + */ + +/** + * @description BBS 收藏命名空间 + * @since Beta v0.4.5 + * @namespace TGApp.BBS.Collection + * @memberof TGApp.BBS + */ +declare namespace TGApp.BBS.Collection { + /** + * @description 用户收藏帖子数据返回 + * @since Beta v0.4.5 + * @interface PostResponse + * @extends TGApp.BBS.Response.BaseWithData + * @property {PostRespData} data - 响应数据 + * @return PostResponse + */ + interface PostResponse extends TGApp.BBS.Response.BaseWithData { + data: PostRespData; + } + + /** + * @description 用户收藏帖子响应数据 + * @since Beta v0.4.5 + * @interface PostRespData + * @property {boolean} is_last - 是否最后一页 + * @property {string} next_offset - 下一页偏移量 + * @property {Array} list - 帖子列表 + * @return PostRespData + */ + interface PostRespData { + is_last: boolean; + next_offset: string; + list: TGApp.Plugins.Mys.Post.FullData[]; + } +} diff --git a/src/types/Sqlite/Collection.d.ts b/src/types/Sqlite/Collection.d.ts new file mode 100644 index 00000000..2f3bc949 --- /dev/null +++ b/src/types/Sqlite/Collection.d.ts @@ -0,0 +1,41 @@ +/** + * @file types/Sqlite/Collection.d.ts + * @description Sqlite UserCollection 类型定义文件 + * @since Beta v0.4.5 + */ + +/** + * @description 用户收藏命名空间 + * @since Beta v0.4.5 + * @namespace TGApp.Sqlite.UserCollection + * @memberof TGApp.Sqlite + */ +declare namespace TGApp.Sqlite.UserCollection { + /** + * @description 数据库-用户收藏表 + * @since Beta v0.4.5 + * @interface SingleTable + * @property {string} postId - 帖子 ID + * @property {string} title - 标题 + * @property {string} content - 内容 + * @property {string} collect - 合集 + * @property {string} uid - 用户 UID + * @property {string} updated - 更新时间 + * @return SingleTable + */ + interface SingleTable { + postId: string; + title: string; + content: string; + collect: string; + uid: string; + updated: string; + } + + /** + * @description 渲染卡片 + * @since Beta v0.4.5 + * @interface RenderCard + */ + type RenderCard = TGApp.Plugins.Mys.Forum.RenderCard; +} diff --git a/src/views/t-post.vue b/src/views/t-post.vue index 005bdbfb..06c27d28 100644 --- a/src/views/t-post.vue +++ b/src/views/t-post.vue @@ -1,5 +1,6 @@ + (Math.floor(Date.now() / 1000)); const shareTimeTimer = ref(); // 合集 const showCollection = ref(false); +const collectExist = ref(false); onMounted(async () => { await appWindow.show(); @@ -160,6 +164,7 @@ onMounted(async () => { await TGLogger.Info(`[t-post][${postId}][onMounted] 打开 JSON 窗口`); createPostJson(postId); } + collectExist.value = await TGSqlite.checkTableExist("UserCollection"); await nextTick(() => { shareTimeTimer.value = setInterval(() => { shareTime.value = Math.floor(Date.now() / 1000); diff --git a/src/web/request/TGRequest.ts b/src/web/request/TGRequest.ts index bc6ea4ea..c4064b6c 100644 --- a/src/web/request/TGRequest.ts +++ b/src/web/request/TGRequest.ts @@ -1,7 +1,7 @@ /** * @file web/request/TGRequest.ts * @description 应用用到的请求函数 - * @since Beta v0.4.3 + * @since Beta v0.4.5 */ import { genAuthkey, genAuthkey2 } from "./genAuthkey"; @@ -20,6 +20,7 @@ import { getStokenByGameToken, getTokenBySToken } from "./getStoken"; import getSyncAvatarDetail from "./getSyncAvatarDetail"; import getSyncAvatarListAll from "./getSyncAvatarListAll"; import { getTokensByLoginTicket } from "./getTokens"; +import { getUserCollect } from "./getUserCollect"; import { getUserInfoByCookie } from "./getUserInfo"; import { verifyLToken } from "./verifyLToken"; @@ -43,6 +44,7 @@ const TGRequest = { getAbyss, getAccounts: getGameAccountsByCookie, getUserInfo: getUserInfoByCookie, + getCollect: getUserCollect, }, byLToken: { verify: verifyLToken, diff --git a/src/web/request/getUserCollect.ts b/src/web/request/getUserCollect.ts new file mode 100644 index 00000000..92442305 --- /dev/null +++ b/src/web/request/getUserCollect.ts @@ -0,0 +1,35 @@ +/** + * @file web/request/getUserCollect.ts + * @description 获取用户收藏请求模块 + * @since Beta v0.4.5 + */ + +import { http } from "@tauri-apps/api"; + +import TGUtils from "../utils/TGUtils"; + +/** + * @description 获取用户收藏帖子 + * @since Beta v0.4.5 + * @param {Record} cookie - 用户 cookie + * @param {string} offset - 偏移量 + * @returns {Promise} 用户收藏帖子 + */ +export async function getUserCollect( + cookie: Record, + offset: string = "", +): Promise { + const url = "https://bbs-api.miyoushe.com/post/wapi/userFavouritePost"; + const params = { size: "20", offset }; + const header = TGUtils.User.getHeader(cookie, "GET", params, "common"); + return await http + .fetch(url, { + method: "GET", + headers: header, + query: params, + }) + .then((res) => { + if (res.data.retcode !== 0) return res.data; + return res.data.data; + }); +}