update user detail

This commit is contained in:
Timi
2026-08-07 16:02:29 +08:00
parent 1b1121f052
commit 28b789eb86
13 changed files with 421 additions and 147 deletions
+2 -13
View File
@@ -52,6 +52,7 @@
import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import { AttachmentAPI, UserAPI, useUserStore } from "timi-web";
import PhotoField from "@/components/PhotoField.vue";
import { buildFallbackAvatar } from "@/utils/avatar";
import { compressUploadImage } from "@/utils/imageCompression";
type UserAvatarSource = NonNullable<Parameters<typeof UserAPI.getAvatarURL>[0]> & {
@@ -223,21 +224,9 @@ function textToSex(text: string): number {
return 0;
}
function buildFallbackAvatar(name: string): string {
const text = encodeURIComponent((name.trim() || "用户").slice(0, 2));
return [
"data:image/svg+xml;charset=UTF-8,",
"%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E",
"%3Crect width='120' height='120' rx='24' fill='%230f766e'/%3E",
"%3Ctext x='50%25' y='54%25' fill='white' font-size='36' font-family='Arial,sans-serif' text-anchor='middle' dominant-baseline='middle'%3E",
text,
"%3C/text%3E%3C/svg%3E"
].join("");
}
function buildUserAvatar(user?: UserAvatarSource): string {
const name = user?.nick || user?.name || "用户";
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(name);
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(name, "#0f766e");
}
onMounted(() => {
+21 -29
View File
@@ -1,28 +1,34 @@
<template>
<div v-can="permissionCodes.user.read" class="user-edit">
<t-cell-group v-if="currentUser" theme="card">
<div
v-if="managedUser"
v-can="permissionCodes.user.read"
class="user-edit"
>
<t-cell-group v-if="managedUser.user" theme="card">
<t-cell
:title="currentUser.name"
:description="currentUser.role"
:title="managedUser.user.name"
:description="getRoleDisplayName(managedUser.user.roleList?.[0], permissionStore.roleList)"
>
<template #leftIcon>
<t-image
class="avatar"
:src="currentUser.avatar || buildFallbackAvatar(currentUser.name)"
:alt="currentUser.name"
:src="buildUserAvatar(managedUser.user)"
:alt="managedUser.user.name"
fit="cover"
/>
</template>
</t-cell>
</t-cell-group>
<t-cell-group v-if="currentUser" theme="card">
<t-cell-group v-if="managedUser.user" theme="card">
<t-cell title="状态" :note="managedUser.enabled ? '启用' : '禁用'" />
<t-cell title="备注" :note="managedUser.remark" />
<t-cell title="手机号">
<template #description>
<telephone-link :value="currentUser.mobile" placeholder="未填写" />
<template #note>
<telephone-link :value="managedUser.user.phoneNo" placeholder="未填写" />
</template>
</t-cell>
<t-cell title="上次登录时间" :description="currentUser.lastLoginAt || '暂无记录'" />
<t-cell title="上次登录时间" :note="Time.toPassedDateTime(managedUser.user.lastLoginAt)" />
</t-cell-group>
<t-cell-group v-if="currentUser" theme="card">
@@ -61,12 +67,13 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { UserAPI, type User } from "timi-web";
import { Time, type User, UserAPI } from "timi-web";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import type { ManagedUser } from "@/types/ManagedUser";
import type { EventItem } from "@/types/EventItem";
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
import { buildFallbackAvatar } from "@/utils/avatar";
import { buildEffectiveRoleCodeSet, getRoleDisplayName } from "@/utils/role";
import { permissionCodes } from "@/utils/permission";
@@ -95,7 +102,7 @@ const currentUser = computed(() => {
role: getRoleDisplayName(item.roleList?.[0], permissionStore.roleList),
avatar: buildUserAvatar(user),
mobile: user?.phoneNo || "",
lastLoginAt: user?.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : ""
lastLoginAt: user?.lastLoginAt
};
});
const scopeList = computed(() => {
@@ -114,7 +121,6 @@ async function toEdit(): Promise<void> {
if (!currentUser.value) {
return;
}
await router.push(`/management/users/${currentUser.value.id}/edit`);
}
@@ -122,26 +128,12 @@ async function toRoleAssign(): Promise<void> {
if (!currentUser.value) {
return;
}
await router.push({
path: `/management/users/${currentUser.value.id}/roles`
path: `/management/users/${currentUser.value.id}/roles`,
query: managedUser.value?.storeId ? { storeId: managedUser.value.storeId } : undefined
});
}
// ---------- 头像 ----------
function buildFallbackAvatar(name: string): string {
const text = encodeURIComponent((name.trim() || "用户").slice(0, 2));
return [
"data:image/svg+xml;charset=UTF-8,",
"%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E",
"%3Crect width='120' height='120' rx='24' fill='%232563eb'/%3E",
"%3Ctext x='50%25' y='54%25' fill='white' font-size='36' font-family='Arial,sans-serif' text-anchor='middle' dominant-baseline='middle'%3E",
text,
"%3C/text%3E%3C/svg%3E"
].join("");
}
function buildUserAvatar(user?: Partial<User>): string {
const name = user?.nick || user?.name || "用户";
return isUser(user) ? UserAPI.getAvatarURL(user) || buildFallbackAvatar(name) : buildFallbackAvatar(name);
+101 -3
View File
@@ -10,16 +10,32 @@
<t-form-item v-if="isCreateMode" label="密码">
<t-input v-model.trim="formModel.password" type="password" placeholder="密码" borderless />
</t-form-item>
<t-form-item v-if="isGlobalManager" label="门店" arrow @click="openStorePicker">
<div class="picker-text" v-text="selectedStoreName" />
</t-form-item>
</t-form>
<t-button class="save" theme="primary" :loading="isSaving" @click="saveUser">保存</t-button>
</div>
<t-popup v-model="storePickerVisible" placement="bottom">
<t-picker
v-model="storePickerDraft"
:columns="storePickerColumns"
title="选择门店"
@cancel="storePickerVisible = false"
@confirm="confirmStorePicker"
/>
</t-popup>
</template>
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { ManagedUser } from "@/types/ManagedUser";
import { permissionCodes } from "@/utils/permission";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
type PickerValue = string | number;
defineOptions({
name: "UserEdit"
@@ -27,6 +43,10 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
// ---------- 表单 ----------
@@ -35,6 +55,7 @@ const isSaving = ref(false);
const formModel = reactive({
id: "",
userId: "",
storeId: "",
name: "",
mobile: "",
password: ""
@@ -42,9 +63,59 @@ const formModel = reactive({
const isCreateMode = computed(() => !String(route.params.id || ""));
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.user.create : permissionCodes.user.update);
// ---------- 门店选择 ----------
const storePickerVisible = ref(false);
const storePickerDraft = ref<PickerValue[]>([]);
const storePickerColumns = computed(() => [
currentStoreStore.storeList.map(item => ({
label: item.name || item.code || "未命名门店",
value: item.id || ""
})).filter(item => item.value)
]);
const selectedStoreName = computed(() => {
const store = currentStoreStore.storeList.find(item => item.id === formModel.storeId);
return store?.name || store?.code || "请选择门店";
});
function openStorePicker(): void {
if (!currentStoreStore.storeList.length) {
Toast.warning("暂无可选门店");
return;
}
storePickerDraft.value = [formModel.storeId];
storePickerVisible.value = true;
}
function confirmStorePicker(): void {
const storeId = String(storePickerDraft.value[0] || "");
if (!storeId) {
return;
}
formModel.storeId = storeId;
storePickerVisible.value = false;
}
// ---------- 页面数据 ----------
async function prepareStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || "");
formModel.storeId = currentStoreStore.storeList.some(item => item.id === queryStoreId)
? queryStoreId
: currentStoreStore.currentStoreId;
return;
}
const store = await currentStoreStore.loadLoginStore();
formModel.storeId = store.id || "";
}
async function fillForm(): Promise<void> {
await prepareStoreScope();
const routeId = String(route.params.id || "");
if (routeId) {
const current = await ManagedUserAPI.detail(routeId);
@@ -52,6 +123,7 @@ async function fillForm(): Promise<void> {
Object.assign(formModel, {
id: current.id,
userId: current.userId || "",
storeId: current.storeId || formModel.storeId,
name: current.user?.nick || current.user?.name || "",
mobile: current.user?.phoneNo || "",
password: ""
@@ -62,6 +134,7 @@ async function fillForm(): Promise<void> {
Object.assign(formModel, {
id: "",
userId: "",
storeId: formModel.storeId,
name: "",
mobile: "",
password: ""
@@ -80,6 +153,7 @@ async function saveUser(): Promise<void> {
const formData: ManagedUser = {
id: formModel.id || undefined,
userId: formModel.userId || undefined,
storeId: formModel.storeId || undefined,
enabled: currentManagedUser.value?.enabled ?? true,
remark: currentManagedUser.value?.remark || "",
user: {
@@ -90,9 +164,25 @@ async function saveUser(): Promise<void> {
password: isCreateMode.value ? formModel.password : undefined
}
};
const saved = isCreateMode.value ? await ManagedUserAPI.create(formData) : await ManagedUserAPI.update(formData);
let saved: ManagedUser;
if (isCreateMode.value) {
saved = await ManagedUserAPI.create(formData);
} else {
saved = await ManagedUserAPI.update(formData);
if (isGlobalManager.value && currentManagedUser.value?.storeId !== formModel.storeId) {
await ManagedUserAPI.transferStore({
id: formModel.id || undefined,
storeId: formModel.storeId || undefined
});
}
}
Toast.success("用户已保存");
await router.push(`/management/users/${saved.id}`);
const detailPath = `/management/users/${saved.id || formModel.id}`;
if (isCreateMode.value) {
await router.replace(detailPath);
} else {
await router.push(detailPath);
}
} finally {
isSaving.value = false;
}
@@ -103,6 +193,10 @@ function validateForm(): boolean {
Toast.warning("请填写姓名");
return false;
}
if (!formModel.storeId) {
Toast.warning("请选择门店");
return false;
}
if (isCreateMode.value && !formModel.password) {
Toast.warning("请填写密码");
return false;
@@ -127,6 +221,10 @@ onMounted(async () => {
border-radius: 1rem;
}
.picker-text {
color: var(--td-text-color-primary);
}
.save {
left: 1rem;
right: 1rem;
+109 -59
View File
@@ -1,25 +1,45 @@
<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"
>
<div v-for="item in pageResult.list" :key="item.id" class="item" @click="toDetail(item)">
<t-image :src="buildUserAvatar(item)" :alt="getUserName(item)" fit="cover" class="avatar" />
<div class="content">
<div class="header">
<span class="name" v-text="getUserName(item)" />
<t-tag class="meta" size="small" theme="success" variant="outline" :content="getRoleName(item)" />
</div>
<span class="events gray" v-text="`可登记事件:${getActionScope(item)}`" />
</div>
</div>
<div v-if="!pageResult.list.length" class="empty-block" v-text="isLoading ? '正在加载用户' : '暂无用户'" />
<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="router.push('/management/users/create')">
<t-button v-can="permissionCodes.user.create" class="add" theme="primary" type="button" @click="toCreate">
<template #icon>
<t-icon name="add" />
</template>
@@ -29,23 +49,53 @@
</template>
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { type Page, type PageResult, UserAPI, type User } from "timi-web";
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, getRoleDisplayName } from "@/utils/role";
import { permissionCodes } from "@/utils/permission";
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[]>([]);
@@ -99,23 +149,22 @@ function isUser(user?: Partial<User>): user is User {
return typeof user?.name === "string";
}
function buildFallbackAvatar(name: string): string {
const text = encodeURIComponent((name.trim() || "用户").slice(0, 2));
return [
"data:image/svg+xml;charset=UTF-8,",
"%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E",
"%3Crect width='120' height='120' rx='24' fill='%232563eb'/%3E",
"%3Ctext x='50%25' y='54%25' fill='white' font-size='36' font-family='Arial,sans-serif' text-anchor='middle' dominant-baseline='middle'%3E",
text,
"%3C/text%3E%3C/svg%3E"
].join("");
}
// ---------- 页面数据 ----------
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),
ManagedUserAPI.list(page, userListOptions.value),
EventAPI.listValid(),
permissionStore.loadAll()
]);
@@ -146,7 +195,7 @@ async function fetch(): Promise<void> {
isLoading.value = true;
try {
const result = await ManagedUserAPI.list(page);
const result = await ManagedUserAPI.list(page, userListOptions.value);
pageResult.total = result.total;
pageResult.list = pageResult.list.concat(result.list || []);
page.index += 1;
@@ -156,8 +205,24 @@ async function fetch(): Promise<void> {
}
}
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;
@@ -177,49 +242,34 @@ onMounted(async () => {
<style scoped lang="less">
.user-list {
padding: calc(var(--app-nav-height) + 1rem) 1rem 6rem 1rem;
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 {
gap: .5rem;
display: flex;
padding: .5rem;
align-items: center;
justify-content: space-between;
border: 1px solid var(--td-border-level-1-color);
background: #f7faff;
border-radius: 1rem;
padding: 10px;
.avatar {
width: 3.5rem;
height: 3.5rem;
flex: none;
border-radius: .5rem;
}
.content {
gap: 1rem;
.header {
flex: 1;
gap: .5rem;
display: flex;
min-width: 0;
flex-direction: column;
.header {
gap: .5rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
font-size: 1rem;
font-weight: 700;
}
.events {
font-size: .875rem;
}
align-items: center;
justify-content: space-between;
}
}
+81 -19
View File
@@ -2,6 +2,13 @@
<div v-can="corePermissionCodes.userRole.read" class="user-role-assign">
<div class="user-menu">
<t-dropdown-menu>
<t-dropdown-item
v-if="isGlobalManager"
v-model="selectedStoreId"
:label="storeFilterLabel"
:options="storeOptions"
@change="handleStoreChange"
/>
<t-dropdown-item
:label="activeUserLabel"
:options="userOptions"
@@ -42,10 +49,15 @@
<script setup lang="ts">
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
import { Toast } from "tdesign-mobile-vue";
import { RoleAPI, type ModuleCode, type Role } from "timi-web";
import { RoleAPI, type ModuleCode, type Role, useUserStore } from "timi-web";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { ManagedUser } from "@/types/ManagedUser";
import { corePermissionCodes } from "@/utils/permission";
import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission";
type ManagedRole = Role & {
id: string;
};
defineOptions({
name: "UserRoleAssign"
@@ -54,18 +66,43 @@ defineOptions({
const route = useRoute();
const moduleCode: ModuleCode = "GAO";
const pageSize = 1000;
const allStoreFilterValue = "__ALL_STORES__";
// ---------- 用户选择 ----------
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const userList = ref<ManagedUser[]>([]);
const roleList = ref<Role[]>([]);
const roleList = ref<ManagedRole[]>([]);
const routeManagedUserId = computed(() => String(route.params.id || route.query.managedUserId || ""));
const activeManagedUserId = ref(routeManagedUserId.value);
const selectedStoreId = ref(allStoreFilterValue);
const loginStoreId = ref("");
const checkedRoleIds = ref<string[]>([]);
const isLoading = ref(false);
const isSaving = ref(false);
let roleSyncSeq = 0;
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
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 userRoleWriteAccess = {
@@ -95,29 +132,54 @@ async function handleUserChange(value: DropdownValue): Promise<void> {
await loadActiveUserRoles();
}
async function handleStoreChange(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
selectedStoreId.value = String(value);
await loadUserList();
}
// ---------- 页面数据 ----------
async function loadStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || "");
selectedStoreId.value = currentStoreStore.storeList.some(item => item.id === queryStoreId)
? queryStoreId
: allStoreFilterValue;
return;
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store.id || "";
}
async function loadData(): Promise<void> {
isLoading.value = true;
try {
const [userResult, roles] = await Promise.all([
ManagedUserAPI.list({
index: 0,
size: 100
}),
loadRoleList()
]);
userList.value = userResult.list || [];
roleList.value = roles || [];
if (!activeManagedUserId.value) {
activeManagedUserId.value = userList.value[0]?.id || "";
}
await loadActiveUserRoles();
await loadStoreScope();
roleList.value = await loadRoleList();
await loadUserList();
} finally {
isLoading.value = false;
}
}
async function loadUserList(): Promise<void> {
const result = await ManagedUserAPI.list({
index: 0,
size: 100
}, userListOptions.value);
userList.value = result.list || [];
if (!userList.value.some(item => item.id === activeManagedUserId.value)) {
activeManagedUserId.value = userList.value[0]?.id || "";
}
await loadActiveUserRoles();
}
// ---------- 角色同步 ----------
async function loadActiveUserRoles(): Promise<void> {
@@ -168,7 +230,7 @@ function getUserName(item: ManagedUser): string {
// ---------- 角色列表 ----------
async function loadRoleList(): Promise<Role[]> {
async function loadRoleList(): Promise<ManagedRole[]> {
const result = await RoleAPI.list({
index: 0,
size: pageSize,
@@ -179,7 +241,7 @@ async function loadRoleList(): Promise<Role[]> {
code: "ASC"
}
});
return result.list || [];
return (result.list || []).filter((role): role is ManagedRole => Boolean(role.id));
}
// ---------- 角色勾选 ----------
@@ -190,7 +252,7 @@ function isRoleChecked(roleId: string): boolean {
function getRoleId(role: Role | string): string {
if (typeof role !== "string") {
return role.id;
return role.id || "";
}
const fullRole = roleList.value.find(item => item.code === role || `${item.moduleCode}:${item.code}` === role);
+9 -17
View File
@@ -21,9 +21,8 @@
/>
</template>
</t-cell>
</t-cell-group>
<t-cell-group v-if="canSelectStore" class="store-card" theme="card">
<t-cell
v-if="canSelectStore"
class="cell"
title="当前门店"
:note="currentStoreStore.currentStoreName"
@@ -72,7 +71,8 @@
import { UserAPI, useUserStore } from "timi-web";
import { Toast } from "tdesign-mobile-vue";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { corePermissionCodes, coreRoleCodes, permissionCodes } from "@/utils/permission";
import { buildFallbackAvatar } from "@/utils/avatar";
import { corePermissionCodes, coreRoleCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
type PickerValue = string | number;
@@ -105,12 +105,14 @@ const currentUserDesc = computed(() => currentUser.value?.phoneNo || currentUser
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
const canSelectStore = computed(() => userStore.hasPermissions(permissionCodes.store.read));
const canLoadLoginStore = computed(() => userStore.hasRoles([gaoRoleCodes.storeManager, gaoRoleCodes.employee], "or"));
const canManagePermission = computed(() => (
userStore.hasRoles(coreRoleCodes.admin) || userStore.hasRoles(coreRoleCodes.userRoleAdmin)
) && userStore.hasPermissions([
corePermissionCodes.role.read,
corePermissionCodes.permission.read
], "and"));
const canManageUser = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
const visibleMenuGroupList = computed<MenuGroup[]>(() => menuGroupList.value
.map(group => ({
...group,
@@ -211,7 +213,7 @@ const menuGroupList = computed<MenuGroup[]>(() => [
icon: "root-list",
path: "/management/users",
title: "用户管理",
visible: userStore.hasPermissions(permissionCodes.user.read)
visible: canManageUser.value && userStore.hasPermissions(permissionCodes.user.read)
},
{
icon: "qrcode",
@@ -223,20 +225,8 @@ const menuGroupList = computed<MenuGroup[]>(() => [
}
]);
function buildFallbackAvatar(name: string): string {
const text = encodeURIComponent((name.trim() || "用户").slice(0, 2));
return [
"data:image/svg+xml;charset=UTF-8,",
"%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E",
"%3Crect width='120' height='120' rx='24' fill='%230f766e'/%3E",
"%3Ctext x='50%25' y='54%25' fill='white' font-size='36' font-family='Arial,sans-serif' text-anchor='middle' dominant-baseline='middle'%3E",
text,
"%3C/text%3E%3C/svg%3E"
].join("");
}
function buildUserAvatar(user?: UserAvatarSource): string {
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(currentUserName.value);
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(currentUserName.value, "#0f766e");
}
async function go(path: string): Promise<void> {
@@ -246,6 +236,8 @@ async function go(path: string): Promise<void> {
onMounted(async () => {
if (canSelectStore.value) {
await currentStoreStore.loadStoreList();
} else if (canLoadLoginStore.value) {
await currentStoreStore.loadLoginStore();
}
});
</script>