support search for tag list

This commit is contained in:
Timi
2026-08-20 22:16:45 +08:00
parent 5ee7bf7980
commit 1f37d36901
+90 -11
View File
@@ -1,9 +1,17 @@
<template> <template>
<div v-can="corePermissionCodes.tag.read" class="tag-list"> <div v-can="corePermissionCodes.tag.read" class="tag-list">
<t-input
v-model.trim="keyword"
class="search"
type="search"
borderless
placeholder="搜索标签"
/>
<t-pull-down-refresh <t-pull-down-refresh
v-model="isRefreshing" v-model="isRefreshing"
:refresh-timeout="30000" :refresh-timeout="30000"
@refresh="refresh" @refresh="refresh"
@scrolltolower="loadMore"
> >
<div class="tag-panel"> <div class="tag-panel">
<div <div
@@ -23,7 +31,8 @@
</button> </button>
</div> </div>
</div> </div>
<div v-if="!tagList.length" class="empty" v-text="isLoading ? '正在加载标签' : '暂无标签'" /> <div v-if="!tagList.length" class="empty" v-text="emptyText" />
<div v-if="tagList.length" class="load-state" v-text="isLoading ? '正在加载更多' : isNoMore ? '已经到底了' : '上拉加载更多'" />
</t-pull-down-refresh> </t-pull-down-refresh>
<t-button v-can="corePermissionCodes.tag.create" class="create" theme="primary" @click="openCreateDialog"> <t-button v-can="corePermissionCodes.tag.create" class="create" theme="primary" @click="openCreateDialog">
<template #icon> <template #icon>
@@ -54,7 +63,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { DialogPlugin, Toast } from "tdesign-mobile-vue"; import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web"; import { type Page, useUserStore } from "timi-web";
import TagAPI from "@/api/TagAPI"; import TagAPI from "@/api/TagAPI";
import type { Tag } from "@/types/Tag"; import type { Tag } from "@/types/Tag";
import { corePermissionCodes } from "@/utils/permission"; import { corePermissionCodes } from "@/utils/permission";
@@ -66,11 +75,20 @@ defineOptions({
const userStore = useUserStore(); const userStore = useUserStore();
const pageSize = 100; const pageSize = 100;
const keyword = ref("");
const tagList = ref<Tag[]>([]); const tagList = ref<Tag[]>([]);
const isLoading = ref(false); const isLoading = ref(false);
const isSaving = ref(false); const isSaving = ref(false);
const isRefreshing = ref(false); const isRefreshing = ref(false);
const isNoMore = ref(false);
const editDialogVisible = ref(false); const editDialogVisible = ref(false);
const total = ref(0);
const page = reactive<Page<Tag>>({
index: 0,
size: pageSize
});
let keywordTimer: number | undefined;
let requestToken = 0;
const formModel = reactive({ const formModel = reactive({
id: "", id: "",
name: "" name: ""
@@ -79,35 +97,71 @@ const formModel = reactive({
const canUpdateTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.update)); const canUpdateTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.update));
const canDeleteTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.delete)); const canDeleteTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.delete));
const isCreateMode = computed(() => !formModel.id); const isCreateMode = computed(() => !formModel.id);
const emptyText = computed(() => {
if (isLoading.value) {
return "正在加载标签";
}
return keyword.value.trim() ? "未找到匹配标签" : "暂无标签";
});
function getTagName(tag: Tag): string { function getTagName(tag: Tag): string {
return tag.name || tag.zhCN || "未命名标签"; return tag.name || tag.zhCN || "未命名标签";
} }
async function loadTags(): Promise<void> { async function fetch(reset = false): Promise<void> {
if (!reset && (isLoading.value || isNoMore.value)) {
return;
}
if (reset) {
page.index = 0;
tagList.value = [];
total.value = 0;
isNoMore.value = false;
}
const token = ++requestToken;
const currentKeyword = keyword.value.trim();
isLoading.value = true; isLoading.value = true;
try { try {
const result = await TagAPI.list({ const result = await TagAPI.list({
index: 0, ...page,
size: pageSize likesExample: currentKeyword ? { name: currentKeyword } : undefined
}); });
tagList.value = result.list || []; if (token !== requestToken) {
return;
}
const list = result.list || [];
tagList.value = reset ? list : tagList.value.concat(list);
total.value = result.total;
page.index += 1;
isNoMore.value = list.length < page.size || total.value <= tagList.value.length;
} catch { } catch {
if (token === requestToken) {
Toast.error("标签加载失败"); Toast.error("标签加载失败");
}
} finally { } finally {
if (token === requestToken) {
isLoading.value = false; isLoading.value = false;
} }
} }
}
async function loadMore(): Promise<void> {
await fetch();
}
async function refresh(): Promise<void> { async function refresh(): Promise<void> {
isRefreshing.value = true; isRefreshing.value = true;
try { try {
await loadTags(); await reload();
} finally { } finally {
isRefreshing.value = false; isRefreshing.value = false;
} }
} }
async function reload(): Promise<void> {
await fetch(true);
}
function openCreateDialog(): void { function openCreateDialog(): void {
formModel.id = ""; formModel.id = "";
formModel.name = ""; formModel.name = "";
@@ -152,7 +206,7 @@ async function saveTag(): Promise<void> {
await TagAPI.update(payload); await TagAPI.update(payload);
} }
editDialogVisible.value = false; editDialogVisible.value = false;
await loadTags(); await reload();
Toast.success("标签已保存"); Toast.success("标签已保存");
} catch { } catch {
Toast.error("标签保存失败"); Toast.error("标签保存失败");
@@ -188,14 +242,25 @@ async function removeTag(id: string): Promise<void> {
} }
try { try {
await TagAPI.remove(id); await TagAPI.remove(id);
tagList.value = tagList.value.filter(item => item.id !== id); await reload();
Toast.success("标签已删除"); Toast.success("标签已删除");
} catch { } catch {
Toast.error("标签删除失败"); Toast.error("标签删除失败");
} }
} }
onMounted(loadTags); onMounted(reload);
watch(keyword, () => {
window.clearTimeout(keywordTimer);
keywordTimer = window.setTimeout(async () => {
await reload();
}, 300);
});
onUnmounted(() => {
window.clearTimeout(keywordTimer);
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@@ -245,12 +310,25 @@ onMounted(loadTags);
} }
} }
.search {
margin: 0 1rem 1rem;
padding: .5rem 1rem;
border: 1px solid var(--td-component-border);
border-radius: .875rem;
}
.empty { .empty {
color: #52607a; color: #52607a;
padding: 2rem 1rem; padding: 2rem 1rem;
text-align: center; text-align: center;
} }
.load-state {
color: #52607a;
padding: .75rem 1rem 1rem;
text-align: center;
}
.create { .create {
left: 1rem; left: 1rem;
right: 1rem; right: 1rem;
@@ -273,7 +351,8 @@ onMounted(loadTags);
:global(html.theme-dark) { :global(html.theme-dark) {
.tag-list { .tag-list {
.empty { .empty,
.load-state {
color: #94a3b8; color: #94a3b8;
} }
} }