fix tag style
This commit is contained in:
+15
-2
@@ -4,8 +4,21 @@ import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/u
|
|||||||
|
|
||||||
const BASE_URI = "/gao/customer";
|
const BASE_URI = "/gao/customer";
|
||||||
|
|
||||||
async function list(page: Page<Customer>): Promise<PageResult<Customer>> {
|
export type CustomerPage = Page<Customer> & {
|
||||||
return await axios.post(`${BASE_URI}/list`, withCurrentStorePage(page));
|
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> {
|
async function detail(id: string): Promise<Customer> {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="item"
|
class="item"
|
||||||
:theme="isSelected(item) ? 'primary' : 'default'"
|
:theme="isSelected(item) ? 'primary' : 'default'"
|
||||||
size="extra-large"
|
size="large"
|
||||||
:variant="isSelected(item) ? 'dark' : 'light-outline'"
|
:variant="isSelected(item) ? 'dark' : 'light-outline'"
|
||||||
@click="toggleTag(item)"
|
@click="toggleTag(item)"
|
||||||
>
|
>
|
||||||
@@ -219,7 +219,7 @@ watch(() => props.visible, async (value) => {
|
|||||||
.search-row {
|
.search-row {
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: .5rem 1rem;
|
padding: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@@ -245,7 +245,6 @@ watch(() => props.visible, async (value) => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
min-width: 5rem;
|
min-width: 5rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: .75rem;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,11 +59,14 @@
|
|||||||
import PageTransition from "@/components/PageTransition.vue";
|
import PageTransition from "@/components/PageTransition.vue";
|
||||||
import {useNavBarStore} from "@/store/navBarStore";
|
import {useNavBarStore} from "@/store/navBarStore";
|
||||||
import {useTabBarStore} from "@/store/tabBarStore";
|
import {useTabBarStore} from "@/store/tabBarStore";
|
||||||
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
import { useUserStore } from "timi-web";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const tabBarStore = useTabBarStore();
|
const tabBarStore = useTabBarStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// ---------- 导航高度 ----------
|
// ---------- 导航高度 ----------
|
||||||
|
|
||||||
@@ -113,17 +116,32 @@ const navBarHeight = computed(() => {
|
|||||||
|
|
||||||
// ---------- Tab 切换 ----------
|
// ---------- Tab 切换 ----------
|
||||||
|
|
||||||
|
type TabItem = {
|
||||||
|
value: string;
|
||||||
|
icon: string;
|
||||||
|
text: string;
|
||||||
|
permission?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const tabVal = ref(route.path);
|
const tabVal = ref(route.path);
|
||||||
const list = [
|
const tabList: TabItem[] = [
|
||||||
{ value: "/customers", icon: "usergroup", text: "客户" },
|
{ 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: "管理" }
|
{ value: "/management", icon: "setting-1", text: "管理" }
|
||||||
];
|
];
|
||||||
|
const list = computed(() => tabList.filter(item => !item.permission || userStore.hasPermissions(item.permission)));
|
||||||
|
|
||||||
watch(() => route.path, (newPath: string) => {
|
watch(() => route.path, (newPath: string) => {
|
||||||
tabVal.value = resolveTabValue(newPath);
|
tabVal.value = resolveTabValue(newPath);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
|
watch(list, () => {
|
||||||
|
const visiblePath = resolveVisibleTabValue(route.path);
|
||||||
|
if (tabVal.value !== visiblePath) {
|
||||||
|
tabVal.value = visiblePath;
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
function resolveTabValue(path: string): string {
|
function resolveTabValue(path: string): string {
|
||||||
if (path.startsWith("/customers")) {
|
if (path.startsWith("/customers")) {
|
||||||
return "/customers";
|
return "/customers";
|
||||||
@@ -140,6 +158,15 @@ function resolveTabValue(path: string): string {
|
|||||||
return path;
|
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 {
|
function onChangeTab(value: string | number): void {
|
||||||
const path = String(value);
|
const path = String(value);
|
||||||
if (resolveTabValue(route.path) === path) {
|
if (resolveTabValue(route.path) === path) {
|
||||||
|
|||||||
@@ -16,6 +16,28 @@
|
|||||||
clearable
|
clearable
|
||||||
borderless
|
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>
|
</div>
|
||||||
<t-pull-down-refresh
|
<t-pull-down-refresh
|
||||||
class="list-wrapper"
|
class="list-wrapper"
|
||||||
@@ -86,6 +108,11 @@
|
|||||||
添加
|
添加
|
||||||
</t-button>
|
</t-button>
|
||||||
</div>
|
</div>
|
||||||
|
<tag-selector
|
||||||
|
v-model="selectedTagIdList"
|
||||||
|
v-model:visible="tagSelectorVisible"
|
||||||
|
@change="handleTagFilterChanged"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -95,10 +122,12 @@ import { type DropdownValue } from "tdesign-mobile-vue";
|
|||||||
import { type Page, type PageResult, Time, Toolkit, useUserStore } from "timi-web";
|
import { type Page, type PageResult, Time, Toolkit, useUserStore } from "timi-web";
|
||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
import GenderIcon from "@/components/GenderIcon.vue";
|
import GenderIcon from "@/components/GenderIcon.vue";
|
||||||
|
import TagSelector from "@/components/TagSelector.vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
|
import type { Tag } from "@/types/Tag";
|
||||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "CustomerList"
|
name: "CustomerList"
|
||||||
@@ -110,6 +139,7 @@ const currentStoreStore = useCurrentStoreStore();
|
|||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
const isExporting = ref(false);
|
const isExporting = ref(false);
|
||||||
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
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();
|
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 keyword = ref();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const isNoMore = ref(false);
|
const isNoMore = ref(false);
|
||||||
|
let keywordTimer: number | undefined;
|
||||||
const pageResult = reactive<PageResult<Customer>>({
|
const pageResult = reactive<PageResult<Customer>>({
|
||||||
total: 0,
|
total: 0,
|
||||||
list: []
|
list: []
|
||||||
@@ -150,11 +204,9 @@ const page = reactive<Page<Customer>>({
|
|||||||
const fetch = Toolkit.debounce(async () => {
|
const fetch = Toolkit.debounce(async () => {
|
||||||
const result = await CustomerAPI.list({
|
const result = await CustomerAPI.list({
|
||||||
...page,
|
...page,
|
||||||
likesExample: {
|
keyword: keyword.value,
|
||||||
code: keyword.value,
|
storeId: selectedStoreId.value,
|
||||||
name: keyword.value,
|
tagIdList: selectedTagIdList.value
|
||||||
telephone: keyword.value
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
pageResult.total = result.total;
|
pageResult.total = result.total;
|
||||||
pageResult.list = pageResult.list.concat(result.list);
|
pageResult.list = pageResult.list.concat(result.list);
|
||||||
@@ -187,6 +239,17 @@ async function loadData(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
|
|
||||||
|
watch(keyword, async () => {
|
||||||
|
window.clearTimeout(keywordTimer);
|
||||||
|
keywordTimer = window.setTimeout(async () => {
|
||||||
|
await refresh();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.clearTimeout(keywordTimer);
|
||||||
|
});
|
||||||
|
|
||||||
// ---------- 导航 ----------
|
// ---------- 导航 ----------
|
||||||
|
|
||||||
async function toCreate(): Promise<void> {
|
async function toCreate(): Promise<void> {
|
||||||
@@ -242,6 +305,27 @@ async function doExport(): Promise<void> {
|
|||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
border-radius: 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 {
|
.list-wrapper {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const emit = defineEmits<{
|
|||||||
edit: [];
|
edit: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const maxRowCount = 5;
|
const maxRowCount = 3;
|
||||||
const tagRowList = computed(() => {
|
const tagRowList = computed(() => {
|
||||||
const rowCount = Math.min(maxRowCount, props.tagList.length);
|
const rowCount = Math.min(maxRowCount, props.tagList.length);
|
||||||
const rowList = Array.from({ length: rowCount }, () => [] as Tag[]);
|
const rowList = Array.from({ length: rowCount }, () => [] as Tag[]);
|
||||||
|
|||||||
@@ -216,12 +216,12 @@ onMounted(loadTags);
|
|||||||
display: flex;
|
display: flex;
|
||||||
min-width: 5rem;
|
min-width: 5rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: .5rem .75rem;
|
padding: .25rem .75rem;
|
||||||
font-size: .875rem;
|
font-size: 1rem;
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
border: 1px solid var(--td-brand-color);
|
border: 1px solid var(--td-brand-color);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: .75rem;
|
border-radius: .25rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
|
|||||||
Reference in New Issue
Block a user