fix tag style

This commit is contained in:
Timi
2026-08-13 20:49:50 +08:00
parent 6ebd83edbc
commit 4f8b992350
6 changed files with 140 additions and 17 deletions
+15 -2
View File
@@ -4,8 +4,21 @@ import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/u
const BASE_URI = "/gao/customer";
async function list(page: Page<Customer>): Promise<PageResult<Customer>> {
return await axios.post(`${BASE_URI}/list`, withCurrentStorePage(page));
export type CustomerPage = Page<Customer> & {
keyword?: string;
storeId?: string;
tagIdList?: string[];
};
async function list(page: CustomerPage): Promise<PageResult<Customer>> {
const req = withCurrentStorePage(page);
return await axios.post(`${BASE_URI}/list`, {
index: req.index,
size: req.size,
keyword: page.keyword || page.likesExample?.code || page.likesExample?.name || page.likesExample?.telephone,
storeId: page.storeId || req.equalsExample?.storeId,
tagIdList: page.tagIdList || []
});
}
async function detail(id: string): Promise<Customer> {
+2 -3
View File
@@ -39,7 +39,7 @@
:key="item.id"
class="item"
:theme="isSelected(item) ? 'primary' : 'default'"
size="extra-large"
size="large"
:variant="isSelected(item) ? 'dark' : 'light-outline'"
@click="toggleTag(item)"
>
@@ -219,7 +219,7 @@ watch(() => props.visible, async (value) => {
.search-row {
gap: .5rem;
display: flex;
padding: .5rem 1rem;
padding: 1rem;
align-items: center;
.input {
@@ -245,7 +245,6 @@ watch(() => props.visible, async (value) => {
margin: 0;
min-width: 5rem;
max-width: 100%;
border-radius: .75rem;
justify-content: center;
}
+29 -2
View File
@@ -59,11 +59,14 @@
import PageTransition from "@/components/PageTransition.vue";
import {useNavBarStore} from "@/store/navBarStore";
import {useTabBarStore} from "@/store/tabBarStore";
import { permissionCodes } from "@/utils/permission";
import { useUserStore } from "timi-web";
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const tabBarStore = useTabBarStore();
const userStore = useUserStore();
// ---------- 导航高度 ----------
@@ -113,17 +116,32 @@ const navBarHeight = computed(() => {
// ---------- Tab 切换 ----------
type TabItem = {
value: string;
icon: string;
text: string;
permission?: string;
};
const tabVal = ref(route.path);
const list = [
const tabList: TabItem[] = [
{ value: "/customers", icon: "usergroup", text: "客户" },
{ value: "/store", icon: "shop-3", text: "门店" },
{ value: "/store", icon: "shop-3", text: "门店", permission: permissionCodes.stat.read },
{ value: "/management", icon: "setting-1", text: "管理" }
];
const list = computed(() => tabList.filter(item => !item.permission || userStore.hasPermissions(item.permission)));
watch(() => route.path, (newPath: string) => {
tabVal.value = resolveTabValue(newPath);
}, { immediate: true });
watch(list, () => {
const visiblePath = resolveVisibleTabValue(route.path);
if (tabVal.value !== visiblePath) {
tabVal.value = visiblePath;
}
}, { immediate: true });
function resolveTabValue(path: string): string {
if (path.startsWith("/customers")) {
return "/customers";
@@ -140,6 +158,15 @@ function resolveTabValue(path: string): string {
return path;
}
function resolveVisibleTabValue(path: string): string {
const tabPath = resolveTabValue(path);
if (list.value.some(item => item.value === tabPath)) {
return tabPath;
}
return list.value[0]?.value || tabPath;
}
function onChangeTab(value: string | number): void {
const path = String(value);
if (resolveTabValue(route.path) === path) {
+90 -6
View File
@@ -16,6 +16,28 @@
clearable
borderless
/>
<t-button
v-if="canReadTag"
class="tag-filter"
theme="primary"
variant="outline"
@click="tagSelectorVisible = true"
>
<template #icon>
<t-icon name="tag" />
</template>
<span v-text="tagFilterText" />
</t-button>
</div>
<div v-if="canReadTag && selectedTagList.length" class="selected-tags bg-white">
<t-tag
v-for="item in selectedTagList"
:key="item.id"
theme="primary"
variant="light-outline"
:content="getTagName(item)"
/>
<t-button size="extra-small" variant="text" @click="clearTagFilter">清空</t-button>
</div>
<t-pull-down-refresh
class="list-wrapper"
@@ -86,6 +108,11 @@
添加
</t-button>
</div>
<tag-selector
v-model="selectedTagIdList"
v-model:visible="tagSelectorVisible"
@change="handleTagFilterChanged"
/>
</div>
</template>
@@ -95,10 +122,12 @@ import { type DropdownValue } from "tdesign-mobile-vue";
import { type Page, type PageResult, Time, Toolkit, useUserStore } from "timi-web";
import CustomerAPI from "@/api/CustomerAPI";
import GenderIcon from "@/components/GenderIcon.vue";
import TagSelector from "@/components/TagSelector.vue";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { Customer } from "@/types/Customer";
import type { Tag } from "@/types/Tag";
import { getCustomerPhotoURL } from "@/utils/customer";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({
name: "CustomerList"
@@ -110,6 +139,7 @@ const currentStoreStore = useCurrentStoreStore();
const isRefreshing = ref(false);
const isExporting = ref(false);
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const canReadTag = computed(() => userStore.hasPermissions(corePermissionCodes.tag.read));
// ---------- 门店筛选 ----------
@@ -133,11 +163,35 @@ async function selectStoreFilter(value: DropdownValue): Promise<void> {
await refresh();
}
// ---------- 标签筛选 ----------
const selectedTagList = ref<Tag[]>([]);
const selectedTagIdList = ref<string[]>([]);
const tagSelectorVisible = ref(false);
const tagFilterText = computed(() => selectedTagList.value.length ? `${selectedTagList.value.length} 个标签` : "标签");
function getTagName(tag: Tag): string {
return tag.name || tag.zhCN || "未命名标签";
}
async function handleTagFilterChanged(tagList: Tag[]): Promise<void> {
selectedTagList.value = tagList;
selectedTagIdList.value = tagList.map(item => item.id || "").filter(Boolean);
await refresh();
}
async function clearTagFilter(): Promise<void> {
selectedTagList.value = [];
selectedTagIdList.value = [];
await refresh();
}
// ---------- 分页 ----------
const keyword = ref();
const isLoading = ref(false);
const isNoMore = ref(false);
let keywordTimer: number | undefined;
const pageResult = reactive<PageResult<Customer>>({
total: 0,
list: []
@@ -150,11 +204,9 @@ const page = reactive<Page<Customer>>({
const fetch = Toolkit.debounce(async () => {
const result = await CustomerAPI.list({
...page,
likesExample: {
code: keyword.value,
name: keyword.value,
telephone: keyword.value
}
keyword: keyword.value,
storeId: selectedStoreId.value,
tagIdList: selectedTagIdList.value
});
pageResult.total = result.total;
pageResult.list = pageResult.list.concat(result.list);
@@ -187,6 +239,17 @@ async function loadData(): Promise<void> {
onMounted(loadData);
watch(keyword, async () => {
window.clearTimeout(keywordTimer);
keywordTimer = window.setTimeout(async () => {
await refresh();
}, 300);
});
onUnmounted(() => {
window.clearTimeout(keywordTimer);
});
// ---------- 导航 ----------
async function toCreate(): Promise<void> {
@@ -242,6 +305,27 @@ async function doExport(): Promise<void> {
padding: .5rem 1rem;
border-radius: 1rem;
}
.tag-filter {
--td-button-border-radius: .875rem;
flex: 0 0 auto;
}
}
.selected-tags {
gap: .5rem;
z-index: 9;
display: flex;
padding: .5rem 1rem;
overflow: auto;
position: sticky;
top: calc(var(--app-nav-height) + 3.5rem);
box-shadow: var(--td-shadow-1);
:deep(.t-tag) {
flex: 0 0 auto;
}
}
.list-wrapper {
@@ -38,7 +38,7 @@ const emit = defineEmits<{
edit: [];
}>();
const maxRowCount = 5;
const maxRowCount = 3;
const tagRowList = computed(() => {
const rowCount = Math.min(maxRowCount, props.tagList.length);
const rowList = Array.from({ length: rowCount }, () => [] as Tag[]);
+3 -3
View File
@@ -216,12 +216,12 @@ onMounted(loadTags);
display: flex;
min-width: 5rem;
max-width: 100%;
padding: .5rem .75rem;
font-size: .875rem;
padding: .25rem .75rem;
font-size: 1rem;
min-height: 2rem;
border: 1px solid var(--td-brand-color);
align-items: center;
border-radius: .75rem;
border-radius: .25rem;
justify-content: center;
.name {