帖子搜索集成

fix #103
This commit is contained in:
目棃
2024-04-04 18:48:35 +08:00
parent 623d137457
commit 6bf24bd8c2
10 changed files with 290 additions and 36 deletions

View File

@@ -38,7 +38,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tpc-forum" :title="`频道: ${card.forum.name}`"> <div class="tpc-forum" v-if="card.forum.icon !== ''" :title="`频道: ${card.forum.name}`">
<img :src="card.forum.icon" :alt="card.forum.name" /> <img :src="card.forum.icon" :alt="card.forum.name" />
<span>{{ card.forum.name }}</span> <span>{{ card.forum.name }}</span>
</div> </div>
@@ -179,7 +179,11 @@ function getCommonCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys
function getPostCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.News.RenderCard { function getPostCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.News.RenderCard {
const commonCard = getCommonCard(item); const commonCard = getCommonCard(item);
if (item.news_meta !== null && item.news_meta.start_at_sec !== "0") { if (
item.news_meta !== undefined &&
item.news_meta !== null &&
item.news_meta.start_at_sec !== "0"
) {
isAct.value = true; isAct.value = true;
const startTime = new Date(Number(item.news_meta.start_at_sec) * 1000).toLocaleDateString(); const startTime = new Date(Number(item.news_meta.start_at_sec) * 1000).toLocaleDateString();
const endTime = new Date(Number(item.news_meta.end_at_sec) * 1000).toLocaleDateString(); const endTime = new Date(Number(item.news_meta.end_at_sec) * 1000).toLocaleDateString();

View File

@@ -4,7 +4,9 @@
<slot name="left"></slot> <slot name="left"></slot>
<div class="toai-box"> <div class="toai-box">
<div class="toai-top"> <div class="toai-top">
<span class="toai-click" @click="search(props.data.name)">{{ props.data.name }}</span> <span class="toai-click" @click="searchDirect(props.data.name)">{{
props.data.name
}}</span>
<span>{{ props.data.description }}</span> <span>{{ props.data.description }}</span>
</div> </div>
<div v-if="achiLC"> <div v-if="achiLC">
@@ -24,7 +26,7 @@
</div> </div>
<div class="toai-mid-item" v-for="item in achiLC.trigger.task" :key="item.questId"> <div class="toai-mid-item" v-for="item in achiLC.trigger.task" :key="item.questId">
<v-icon>mdi-alert-decagram</v-icon> <v-icon>mdi-alert-decagram</v-icon>
<span class="toai-click" @click="search(item.name)">{{ item.name }}</span> <span class="toai-click" @click="searchDirect(item.name)">{{ item.name }}</span>
<span>{{ item.type }}</span> <span>{{ item.type }}</span>
</div> </div>
</div> </div>
@@ -49,6 +51,7 @@
<slot name="right"></slot> <slot name="right"></slot>
</div> </div>
</TOverlay> </TOverlay>
<ToPostSearch gid="2" v-model="showSearch" :keyword="search" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref, watch } from "vue"; import { computed, onMounted, ref, watch } from "vue";
@@ -56,6 +59,7 @@ import { computed, onMounted, ref, watch } from "vue";
import { AppAchievementsData, AppAchievementSeriesData } from "../../data"; import { AppAchievementsData, AppAchievementSeriesData } from "../../data";
import TGLogger from "../../utils/TGLogger"; import TGLogger from "../../utils/TGLogger";
import TOverlay from "../main/t-overlay.vue"; import TOverlay from "../main/t-overlay.vue";
import ToPostSearch from "../post/to-postSearch.vue";
interface ToAchiInfoProps { interface ToAchiInfoProps {
modelValue: boolean; modelValue: boolean;
@@ -70,6 +74,8 @@ interface ToAchiInfoEmits {
const props = defineProps<ToAchiInfoProps>(); const props = defineProps<ToAchiInfoProps>();
const emits = defineEmits<ToAchiInfoEmits>(); const emits = defineEmits<ToAchiInfoEmits>();
const showSearch = ref(false);
const search = ref("");
const visible = computed({ const visible = computed({
get: () => props.modelValue, get: () => props.modelValue,
@@ -98,11 +104,10 @@ function onCancel() {
} }
// 查找 // 查找
async function search(word: string): Promise<void> { async function searchDirect(word: string): Promise<void> {
await TGLogger.Info(`[ToAchiInfo][${props.data?.id}][Search] 查询 ${word}`); await TGLogger.Info(`[ToAchiInfo][${props.data?.id}][Search] 查询 ${word}`);
const str = encodeURIComponent(word); search.value = word;
const url = `https://www.miyoushe.com/ys/search?keyword=${str}`; showSearch.value = true;
window.open(url, "_blank");
} }
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>

View File

@@ -0,0 +1,161 @@
<template>
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
<div class="tops-box">
<div class="tops-top">查找{{ search }}</div>
<div class="tops-act">
<span>分区{{ getGidLabel() }}</span>
<v-btn size="small" class="tops-btn" @click="searchPosts()" rounded>
加载更多({{ results.length }})
</v-btn>
</div>
<div class="tops-list">
<div v-for="item in results" :key="item.post.post_id">
<TPostCard :model-value="item" />
</div>
</div>
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from "vue";
import Mys from "../../plugins/Mys";
import TOverlay from "../main/t-overlay.vue";
import TPostCard from "../main/t-postcard.vue";
// data
const search = ref("");
const game = ref("2");
const lastId = ref("");
const isLast = ref(false);
const results = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
interface ToPostSearchProps {
modelValue: boolean;
gid: string;
keyword: string;
}
interface ToPostSearchEmits {
(e: "update:modelValue", value: boolean): void;
(e: "cancel"): void;
}
const props = defineProps<ToPostSearchProps>();
const emits = defineEmits<ToPostSearchEmits>();
const gameList: Record<string, string> = {
"1": "崩坏3",
"2": "原神",
"3": "崩坏2",
"4": "未定事件簿",
"5": "大别野",
"6": "崩坏:星穹铁道",
"8": "绝区零",
};
// overlay
const visible = computed({
get: () => props.modelValue,
set: (value) => {
emits("update:modelValue", value);
},
});
function onCancel() {
visible.value = false;
}
onMounted(async () => {
search.value = props.keyword;
game.value = props.gid;
});
watch(
() => props.modelValue,
async (value) => {
if (value && results.value.length === 0) {
await searchPosts();
} else {
visible.value = value;
}
},
);
watch(
() => props.keyword,
async (value) => {
search.value = value;
results.value = [];
lastId.value = "";
isLast.value = false;
},
);
async function searchPosts() {
if (!props.gid || !props.keyword) {
return;
}
if (isLast.value) {
return;
}
const res = await Mys.Posts.search(game.value, search.value, lastId.value);
if (lastId.value === "") {
results.value = res.posts;
} else {
results.value = results.value.concat(res.posts);
}
lastId.value = res.last_id;
isLast.value = res.is_last;
}
function getGidLabel(): string {
if (gameList[game.value]) {
return gameList[game.value];
}
return "未知";
}
</script>
<style lang="css" scoped>
.tops-box {
padding: 10px;
border-radius: 5px;
background-color: var(--box-bg-1);
}
.tops-top {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
word-break: break-all;
}
.tops-act {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 5px;
border-bottom: 1px solid var(--common-shadow-2);
margin-bottom: 10px;
}
.tops-list {
display: flex;
width: 400px;
max-height: 400px;
flex-direction: column;
padding-right: 10px;
overflow-y: auto;
row-gap: 10px;
}
.tops-btn {
width: fit-content;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
</style>

View File

@@ -4,7 +4,7 @@
<div>{{ props.data.nickname }}</div> <div>{{ props.data.nickname }}</div>
<div :title="getAuthorDesc()">{{ getAuthorDesc() }}</div> <div :title="getAuthorDesc()">{{ getAuthorDesc() }}</div>
</div> </div>
<div class="tpa-img" @click="toAuthor()" title="点击前往用户主页"> <div class="tpa-img">
<img :src="props.data.avatar_url" alt="avatar" class="tpa-icon" /> <img :src="props.data.avatar_url" alt="avatar" class="tpa-icon" />
<img <img
:src="props.data.pendant" :src="props.data.pendant"
@@ -34,11 +34,6 @@ function getAuthorDesc(): string {
return props.data.introduce; return props.data.introduce;
} }
async function toAuthor(): Promise<void> {
const url = `https://www.miyoushe.com/ys/#/accountCenter/0?id=${props.data.uid}`;
window.open(url, "_blank");
}
const flexAlign = props.position === "left" ? "flex-start" : "flex-end"; const flexAlign = props.position === "left" ? "flex-start" : "flex-end";
const textAlign = props.position; const textAlign = props.position;
</script> </script>
@@ -83,7 +78,6 @@ const textAlign = props.position;
position: relative; position: relative;
width: 50px; width: 50px;
height: 50px; height: 50px;
cursor: pointer;
} }
.tpa-icon { .tpa-icon {

View File

@@ -8,7 +8,7 @@
v-model="search" v-model="search"
class="news-search" class="news-search"
append-icon="mdi-magnify" append-icon="mdi-magnify"
label="请输入米游社帖子 ID" label="请输入帖子 ID 或搜索词"
:single-line="true" :single-line="true"
hide-details hide-details
@click:append="searchPost" @click:append="searchPost"
@@ -40,6 +40,7 @@
</v-window-item> </v-window-item>
</v-window> </v-window>
<ToChannel v-model="showList" :gid="gid" /> <ToChannel v-model="showList" :gid="gid" />
<ToPostSearch :gid="gid" v-model="showSearch" :keyword="search" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -50,6 +51,7 @@ import showSnackbar from "../../components/func/snackbar";
import TPostCard from "../../components/main/t-postcard.vue"; import TPostCard from "../../components/main/t-postcard.vue";
import ToChannel from "../../components/overlay/to-channel.vue"; import ToChannel from "../../components/overlay/to-channel.vue";
import ToLoading from "../../components/overlay/to-loading.vue"; import ToLoading from "../../components/overlay/to-loading.vue";
import ToPostSearch from "../../components/post/to-postSearch.vue";
import Mys from "../../plugins/Mys"; import Mys from "../../plugins/Mys";
import { useAppStore } from "../../store/modules/app"; import { useAppStore } from "../../store/modules/app";
import TGLogger from "../../utils/TGLogger"; import TGLogger from "../../utils/TGLogger";
@@ -87,6 +89,7 @@ const loadingSub = ref<boolean>(false);
const appStore = useAppStore(); const appStore = useAppStore();
const tab = ref<NewsKey>("notice"); const tab = ref<NewsKey>("notice");
const showList = ref<boolean>(false); const showList = ref<boolean>(false);
const showSearch = ref<boolean>(false);
const tabValues = ref<Array<NewsKey>>(["notice", "activity", "news"]); const tabValues = ref<Array<NewsKey>>(["notice", "activity", "news"]);
// 渲染数据 // 渲染数据
@@ -193,13 +196,11 @@ function searchPost(): void {
}); });
return; return;
} }
if (!isNaN(Number(search.value))) { const numCheck = Number(search.value);
createPost(search.value); if (isNaN(numCheck)) {
showSearch.value = true;
} else { } else {
showSnackbar({ createPost(search.value);
text: "请输入搜索内容",
color: "error",
});
} }
} }
</script> </script>

View File

@@ -27,7 +27,7 @@
v-model="search" v-model="search"
class="post-switch-item" class="post-switch-item"
append-inner-icon="mdi-magnify" append-inner-icon="mdi-magnify"
label="请输入帖子 ID" label="请输入帖子 ID 或搜索词"
variant="outlined" variant="outlined"
:single-line="true" :single-line="true"
hide-details hide-details
@@ -51,6 +51,7 @@
</div> </div>
</div> </div>
</div> </div>
<ToPostSearch :gid="curGid.toString()" v-model="showSearch" :keyword="search" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, ref, watch } from "vue"; import { nextTick, onMounted, ref, watch } from "vue";
@@ -59,6 +60,7 @@ import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar"; import showSnackbar from "../../components/func/snackbar";
import TPostCard from "../../components/main/t-postcard.vue"; import TPostCard from "../../components/main/t-postcard.vue";
import ToLoading from "../../components/overlay/to-loading.vue"; import ToLoading from "../../components/overlay/to-loading.vue";
import ToPostSearch from "../../components/post/to-postSearch.vue";
import Mys from "../../plugins/Mys"; import Mys from "../../plugins/Mys";
import TGClient from "../../utils/TGClient"; import TGClient from "../../utils/TGClient";
import TGLogger from "../../utils/TGLogger"; import TGLogger from "../../utils/TGLogger";
@@ -162,7 +164,8 @@ const curSortType = ref<number>(0);
// 渲染数据 // 渲染数据
const posts = ref<TGApp.Plugins.Mys.Post.FullData[]>([]); const posts = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
const nav = ref<TGApp.BBS.Navigator.Navigator[]>([]); const nav = ref<TGApp.BBS.Navigator.Navigator[]>([]);
const search = ref<string>(); const search = ref<string>("");
const showSearch = ref<boolean>(false);
onMounted(async () => { onMounted(async () => {
await TGLogger.Info( await TGLogger.Info(
@@ -281,20 +284,18 @@ function freshCurForum(newVal: string): void {
// 查询帖子 // 查询帖子
function searchPost(): void { function searchPost(): void {
if (search.value === undefined || search.value === "") { if (search.value === "") {
showSnackbar({ showSnackbar({
text: "请输入搜索内容", text: "请输入搜索内容",
color: "error", color: "error",
}); });
return; return;
} }
if (!isNaN(Number(search.value))) { const numCheck = Number(search.value);
createPost(search.value); if (isNaN(numCheck)) {
showSearch.value = true;
} else { } else {
showSnackbar({ createPost(search.value);
text: "请输入搜索内容",
color: "error",
});
} }
} }
</script> </script>
@@ -351,7 +352,7 @@ function searchPost(): void {
} }
.post-switch-item { .post-switch-item {
max-width: 200px; width: fit-content;
height: 50px; height: 50px;
} }

View File

@@ -15,6 +15,7 @@ import getNewsList from "./request/getNewsList";
import { getPositionData } from "./request/getPositionData"; import { getPositionData } from "./request/getPositionData";
import getPostData from "./request/getPostData"; import getPostData from "./request/getPostData";
import { getVoteInfo, getVoteResult } from "./request/getVoteData"; import { getVoteInfo, getVoteResult } from "./request/getVoteData";
import searchPosts from "./request/searchPost";
import { getGachaCard } from "./utils/getGachaCard"; import { getGachaCard } from "./utils/getGachaCard";
import getLotteryCard from "./utils/getLotteryCard"; import getLotteryCard from "./utils/getLotteryCard";
import getPositionCard from "./utils/getPositionCard"; import getPositionCard from "./utils/getPositionCard";
@@ -31,6 +32,7 @@ const Mys = {
Posts: { Posts: {
get: getForumList, get: getForumList,
nav: getHomeNavigator, nav: getHomeNavigator,
search: searchPosts,
}, },
Gacha: { Gacha: {
get: getGachaData, get: getGachaData,

View File

@@ -0,0 +1,42 @@
/**
* @file plugins/Mys/request/searchPost.ts
* @description 帖子搜索
* @since Beta v0.4.5
*/
import { http } from "@tauri-apps/api";
/**
* @description 搜索帖子
* @since Beta v0.4.5
* @param {string} gid 游戏分区 ID
* @param {string} keyword 关键词
* @param {string} last_id 最后一条帖子 ID
* @return {Promise<TGApp.Plugins.Mys.Search.PostsResponseData>} 返回帖子列表
*/
async function searchPosts(
gid: string = "2",
keyword: string,
last_id: string,
): Promise<TGApp.Plugins.Mys.Search.PostsResponseData> {
const url = "https://bbs-api.miyoushe.com/post/wapi/searchPosts";
const params = {
gids: gid,
keyword,
last_id,
size: "20",
};
return await http
.fetch<TGApp.Plugins.Mys.Search.PostsResponse>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
query: params,
})
.then((res) => {
return res.data.data;
});
}
export default searchPosts;

View File

@@ -1,12 +1,12 @@
/** /**
* @file plugins/Mys/types/post.d.ts * @file plugins/Mys/types/post.d.ts
* @description Mys 插件帖子类型定义文件 * @description Mys 插件帖子类型定义文件
* @since Beta v0.4.0 * @since Beta v0.4.5
*/ */
/** /**
* @description Mys 插件帖子类型 * @description Mys 插件帖子类型
* @since Beta v0.4.0 * @since Beta v0.4.5
* @namespace TGApp.Plugins.Mys.Post * @namespace TGApp.Plugins.Mys.Post
* @memberof TGApp.Plugins.Mys * @memberof TGApp.Plugins.Mys
*/ */
@@ -27,7 +27,7 @@ declare namespace TGApp.Plugins.Mys.Post {
/** /**
* @description 帖子数据 * @description 帖子数据
* @since Beta v0.4.0 * @since Beta v0.4.5
* @interface FullData * @interface FullData
* @property {Post} post 帖子信息 * @property {Post} post 帖子信息
* @property {Forum} forum 所属版块 * @property {Forum} forum 所属版块
@@ -73,7 +73,7 @@ declare namespace TGApp.Plugins.Mys.Post {
is_block_on: boolean; is_block_on: boolean;
forum_rank_info: unknown | null; forum_rank_info: unknown | null;
link_card_list: unknown[]; link_card_list: unknown[];
news_meta: TGApp.Plugins.Mys.News.Meta | null; news_meta: TGApp.Plugins.Mys.News.Meta | null | undefined;
recommend_reason: unknown | null; recommend_reason: unknown | null;
villa_card: unknown | null; villa_card: unknown | null;
is_mentor: boolean; is_mentor: boolean;

44
src/plugins/Mys/types/Search.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
/**
* @file plugins/Mys/types/Search.d.ts
* @description Mys 插件搜索类型声明
* @since Beta v0.4.5
*/
/**
* @description 搜索帖子返回
* @since Beta v0.4.5
* @namespace TGApp.Plugins.Mys.Search
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.Search {
/**
* @description 搜索帖子返回
* @since Beta v0.4.5
* @interface PostsResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {PostsResponseData} data 返回数据
* @return PostsResponse
*/
interface PostsResponse extends TGApp.BBS.Response.BaseWithData {
data: PostsResponseData;
}
/**
* @description 搜索帖子返回数据
* @since Beta v0.4.5
* @interface PostsResponseData
* @property {TGApp.Plugins.Mys.Post.FullData[]} posts 帖子列表
* @property {string} last_id 索引
* @property {boolean} is_last 是否最后一页
* @property {string[]} token_list token 列表
* @property {Record<string,string>} databox 数据盒
* @return PostsResponseData
*/
interface PostsResponseData {
posts: TGApp.Plugins.Mys.Post.FullData[];
last_id: string;
is_last: boolean;
token_list: string[];
databox: Record<string, string>;
}
}