refactor file tree

This commit is contained in:
Timi
2026-08-11 15:24:36 +08:00
parent 575d41b2e2
commit 819c1623eb
42 changed files with 86 additions and 86 deletions
+299
View File
@@ -0,0 +1,299 @@
<template>
<div v-can="permissionCodes.user.read" class="user-list">
<div class="store-menu">
<t-dropdown-menu v-if="isGlobalManager">
<t-dropdown-item
v-model="selectedStoreId"
:label="storeFilterLabel"
:options="storeOptions"
@change="selectStoreFilter"
/>
</t-dropdown-menu>
</div>
<t-pull-down-refresh
v-model="isRefreshing"
:refresh-timeout="30000"
@refresh="refresh"
@scrolltolower="fetch"
>
<t-cell-group theme="card">
<t-cell
class="item"
v-for="item in pageResult.list"
:key="item.id"
:description="`可登记事件:${getActionScope(item)}`"
arrow
@click="toDetail(item)"
>
<template #left-icon>
<t-image :src="buildUserAvatar(item)" :alt="getUserName(item)" fit="cover" class="avatar" />
</template>
<template #title>
<div class="header">
<span class="name" v-text="getUserName(item)" />
<t-tag class="meta" size="small" theme="success" variant="outline" :content="getRoleName(item)" />
</div>
</template>
</t-cell>
</t-cell-group>
<div v-if="!pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载用户' : '暂无用户'" />
<div v-if="pageResult.list.length" class="load-state" v-text="isNoMore ? '已经到底了' : '上拉加载更多'" />
</t-pull-down-refresh>
<t-button v-can="permissionCodes.user.create" class="add" theme="primary" type="button" @click="toCreate">
<template #icon>
<t-icon name="add" />
</template>
新增
</t-button>
</div>
</template>
<script setup lang="ts">
import { Toast, type DropdownValue } from "tdesign-mobile-vue";
import { type Page, type PageResult, UserAPI, type User, useUserStore } from "timi-web";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { ManagedUser } from "@/types/ManagedUser";
import type { EventItem } from "@/types/EventItem";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { buildFallbackAvatar } from "@/utils/avatar";
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({
name: "UserList"
});
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const permissionStore = usePermissionSettingsStore();
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
// ---------- 门店筛选 ----------
const allStoreFilterValue = "__ALL_STORES__";
const selectedStoreId = ref(allStoreFilterValue);
const loginStoreId = ref("");
const storeOptions = computed(() => [
{ label: "全部门店", value: allStoreFilterValue },
...currentStoreStore.storeList
.map(item => ({
label: item.name || item.code || "未命名门店",
value: item.id || ""
}))
.filter(item => item.value)
]);
const storeFilterLabel = computed(() => storeOptions.value.find(item => item.value === selectedStoreId.value)?.label || "全部门店");
const userListOptions = computed(() => {
if (isGlobalManager.value) {
return selectedStoreId.value === allStoreFilterValue
? { allStores: true }
: { storeId: selectedStoreId.value };
}
return { storeId: loginStoreId.value };
});
// ---------- 列表数据 ----------
const eventList = ref<EventItem[]>([]);
const isRefreshing = ref(false);
const isLoading = ref(false);
const isNoMore = ref(false);
const page = reactive<Page<ManagedUser>>({
index: 0,
size: 20
});
const pageResult = reactive<PageResult<ManagedUser>>({
total: 0,
list: []
});
// ---------- 展示文案 ----------
function getUserName(item: ManagedUser): string {
return item.user?.nick || item.user?.name || "未命名用户";
}
function getRoleName(item: ManagedUser): string {
return getRoleDisplayName(getManagedUserRoleList(item)[0], permissionStore.roleList);
}
function getActionScope(item: ManagedUser): string {
const nameList = eventList.value
.filter(action => canEventItem(action, item))
.map(action => action.name);
return nameList.join("、") || "未配置";
}
// ---------- 事件范围 ----------
function canEventItem(action: EventItem, item: ManagedUser): boolean {
const userRoleCodeSet = buildEffectiveRoleCodeSet(item);
const roleCodeList = action.roleCodeList || [];
if (!roleCodeList.length) {
return false;
}
return roleCodeList.some(code => userRoleCodeSet.has(code));
}
// ---------- 头像 ----------
function buildUserAvatar(item: ManagedUser): string {
return isUser(item.user) ? UserAPI.getAvatarURL(item.user) || buildFallbackAvatar(getUserName(item)) : buildFallbackAvatar(getUserName(item));
}
function isUser(user?: Partial<User>): user is User {
return typeof user?.name === "string";
}
// ---------- 页面数据 ----------
async function loadStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
return;
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store?.id || "";
}
async function loadData(): Promise<void> {
await loadStoreScope();
const [userResult, actions] = await Promise.all([
ManagedUserAPI.list(page, userListOptions.value),
EventAPI.listValid(),
permissionStore.loadAll()
]);
pageResult.total = userResult.total;
pageResult.list = userResult.list || [];
page.index = 1;
isNoMore.value = pageResult.list.length >= pageResult.total || pageResult.list.length < page.size;
eventList.value = actions || [];
}
async function refresh(): Promise<void> {
isRefreshing.value = true;
try {
page.index = 0;
pageResult.total = 0;
pageResult.list = [];
isNoMore.value = false;
await fetch();
} finally {
isRefreshing.value = false;
}
}
async function fetch(): Promise<void> {
if (isLoading.value || isNoMore.value) {
return;
}
isLoading.value = true;
try {
const result = await ManagedUserAPI.list(page, userListOptions.value);
pageResult.total = result.total;
pageResult.list = pageResult.list.concat(result.list || []);
page.index += 1;
isNoMore.value = pageResult.list.length >= pageResult.total || (result.list || []).length < page.size;
} finally {
isLoading.value = false;
}
}
async function selectStoreFilter(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
selectedStoreId.value = String(value);
await refresh();
}
// ---------- 导航 ----------
async function toCreate(): Promise<void> {
await router.push({
path: "/management/users/create",
query: selectedStoreId.value === allStoreFilterValue ? undefined : { storeId: selectedStoreId.value }
});
}
async function toDetail(item: ManagedUser): Promise<void> {
if (!item.id) {
return;
}
await router.push(`/management/users/${item.id}`);
}
onMounted(async () => {
try {
await loadData();
} catch {
Toast.error("用户列表加载失败");
}
});
</script>
<style scoped lang="less">
.user-list {
padding: calc(var(--app-nav-height) + 4rem) 0 6rem 0;
display: grid;
gap: .75rem;
.store-menu {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 20;
position: fixed;
background: #fff;
}
.item {
padding: 10px;
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
}
.header {
flex: 1;
gap: .5rem;
display: flex;
align-items: center;
justify-content: space-between;
}
}
.add {
left: 1rem;
right: 1rem;
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
position: fixed;
}
}
:global(html.theme-dark) {
.user-list {
.item {
background: rgba(30, 41, 59, .9);
.name {
color: #e2e8f0;
}
.meta {
color: #94a3b8;
}
}
}
}
</style>