fix user profile update

This commit is contained in:
Timi
2026-08-07 18:20:29 +08:00
parent 82e717e3c6
commit 16fa5fdd36
10 changed files with 386 additions and 73 deletions
+20 -1
View File
@@ -4,7 +4,26 @@ import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/u
const BASE_URI = "/gao/event";
async function list(req: Page<EventItem>): Promise<PageResult<EventItem>> {
type ListOptions = {
allStores?: boolean;
storeId?: string;
};
async function list(req: Page<EventItem>, options?: ListOptions): Promise<PageResult<EventItem>> {
if (options?.allStores) {
return await axios.post(`${BASE_URI}/list`, req);
}
if (options?.storeId) {
return await axios.post(`${BASE_URI}/list`, {
...req,
equalsExample: {
...(req.equalsExample || {}),
storeId: options.storeId
}
});
}
return await axios.post(`${BASE_URI}/list`, withCurrentStorePage(req));
}
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<t-icon
v-if="iconName"
v-if="value && iconName"
:name="iconName"
:size="size"
:class="['gender-icon', `gender-icon--${tone}`]"
+78 -3
View File
@@ -18,6 +18,9 @@
</template>
</t-input>
</t-form-item>
<t-form-item v-if="isGlobalManager" label="门店" required-mark arrow @click="openStorePicker">
<div class="picker-text" v-text="selectedStoreName" />
</t-form-item>
<div v-if="scannerVisible && scannerTarget === 'code'" class="code-scanner-wrapper">
<code-scanner
visible
@@ -153,31 +156,46 @@
@change="handleIntroducerChange"
/>
</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 { AttachmentAPI } from "timi-web";
import { AttachmentAPI, useUserStore } from "timi-web";
import { Toast } from "tdesign-mobile-vue";
import CustomerAPI from "@/api/CustomerAPI";
import BodyAreaSelector from "@/components/BodyAreaSelector.vue";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerSelector from "@/components/CustomerSelector.vue";
import PhotoField from "@/components/PhotoField.vue";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { useSettingStore } from "@/store/settingStore";
import type { BodyAreaValue, Customer } from "@/types/Customer";
import { createBodyAreaValue, normalizeBodyAreaValue } from "@/utils/customer";
import { permissionCodes } from "@/utils/permission";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
import { compressUploadImage } from "@/utils/imageCompression";
type PickerValue = string | number;
defineOptions({
name: "CustomerEdit"
});
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const settingStore = useSettingStore();
const currentStoreStore = useCurrentStoreStore();
const isCreateMode = computed(() => route.name === "CustomerCreate");
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.customer.create : permissionCodes.customer.update);
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
let latestDecodedText = "";
@@ -268,9 +286,60 @@ function openPhotoPicker(): void {
photoInputRef.value?.click();
}
const canSubmit = computed(() => formData.name?.trim());
const canSubmit = computed(() => formData.name?.trim() && formData.storeId);
// ---------- 门店选择 ----------
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 === formData.storeId);
return store?.name || store?.code || "请选择门店";
});
function openStorePicker(): void {
if (!currentStoreStore.storeList.length) {
Toast.warning("暂无可选门店");
return;
}
storePickerDraft.value = [formData.storeId || ""];
storePickerVisible.value = true;
}
function confirmStorePicker(): void {
const storeId = String(storePickerDraft.value[0] || "");
if (!storeId) {
return;
}
formData.storeId = storeId;
currentStoreStore.selectStore(storeId);
storePickerVisible.value = false;
}
async function prepareStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
const queryStoreId = String(route.query.storeId || "");
formData.storeId = currentStoreStore.storeList.some(item => item.id === queryStoreId)
? queryStoreId
: currentStoreStore.currentStoreId;
return;
}
const store = await currentStoreStore.loadLoginStore();
formData.storeId = store?.id || "";
}
async function loadDetail() {
await prepareStoreScope();
const id = String(route.params.id || "");
if (!id) {
return;
@@ -278,6 +347,7 @@ async function loadDetail() {
try {
const customer = await CustomerAPI.detail(id);
Object.assign(formData, customer);
formData.storeId = customer.storeId || formData.storeId;
selectedAreas.value = normalizeBodyAreaValue(customer.conditioningPart);
introducerCustomerId.value = customer.introducerCustomer?.id || "";
} catch (err) {
@@ -334,6 +404,11 @@ async function handlePhotoSelected(event: Event): Promise<void> {
const isSubmitting = ref(false);
async function doSubmit(): Promise<void> {
if (!formData.storeId) {
Toast.warning("请选择门店");
return;
}
isSubmitting.value = true;
try {
formData.conditioningPart = selectedAreas.value;
+64 -7
View File
@@ -1,6 +1,14 @@
<template>
<div v-can="permissionCodes.customer.read" class="customer-list">
<div class="search-bar">
<div class="filter-bar">
<t-dropdown-menu v-if="isGlobalManager">
<t-dropdown-item
v-model="selectedStoreId"
:label="storeFilterLabel"
:options="storeOptions"
@change="selectStoreFilter"
/>
</t-dropdown-menu>
<t-input
v-model.trim="keyword"
class="text-input"
@@ -70,7 +78,7 @@
v-can="permissionCodes.customer.create"
theme="primary"
class="create"
@click="router.push('/management/customers/create')"
@click="toCreate"
>
<template #icon>
<t-icon name="add" />
@@ -83,20 +91,47 @@
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { type Page, type PageResult, Time, Toolkit } from "timi-web";
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 { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { Customer } from "@/types/Customer";
import { getCustomerPhotoURL } from "@/utils/customer";
import { permissionCodes } from "@/utils/permission";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({
name: "CustomerList"
});
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const isRefreshing = ref(false);
const isExporting = ref(false);
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
// ---------- 门店筛选 ----------
const selectedStoreId = ref(currentStoreStore.currentStoreId);
const storeOptions = computed(() => 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 || "选择门店");
async function selectStoreFilter(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
const storeId = String(value);
selectedStoreId.value = storeId;
currentStoreStore.selectStore(storeId);
await refresh();
}
// ---------- 分页 ----------
@@ -140,7 +175,26 @@ async function refresh(): Promise<void> {
}
}
onMounted(fetch);
async function loadData(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
selectedStoreId.value = currentStoreStore.currentStoreId;
} else {
await currentStoreStore.loadLoginStore();
}
await fetch();
}
onMounted(loadData);
// ---------- 导航 ----------
async function toCreate(): Promise<void> {
await router.push({
path: "/management/customers/create",
query: isGlobalManager.value && selectedStoreId.value ? { storeId: selectedStoreId.value } : undefined
});
}
// ---------- 导出 ----------
@@ -163,13 +217,16 @@ async function doExport(): Promise<void> {
.customer-list {
padding: calc(var(--app-nav-height)) 0 5.5rem 0;
.search-bar {
.filter-bar {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 10;
gap: .5rem;
display: flex;
padding: .75rem 1rem;
position: sticky;
flex-direction: column;
background: #F5F5F8;
box-shadow: var(--td-shadow-1);
@@ -234,7 +291,7 @@ async function doExport(): Promise<void> {
:global(html.theme-dark) {
.customer-list {
.search-bar {
.filter-bar {
background: #10161f;
.text-input {
+82 -2
View File
@@ -22,6 +22,9 @@
borderless
/>
</t-form-item>
<t-form-item v-if="isGlobalManager" label="门店" :arrow="isCreateMode" @click="openStorePicker">
<div class="picker-text" v-text="selectedStoreName" />
</t-form-item>
<t-form-item label="说明">
<t-textarea
class="description"
@@ -140,6 +143,15 @@
@confirm="confirmLimitPicker"
/>
</t-popup>
<t-popup v-model="storePickerVisible" placement="bottom">
<t-picker
v-model="storePickerDraft"
:columns="storePickerColumns"
title="选择门店"
@cancel="storePickerVisible = false"
@confirm="confirmStorePicker"
/>
</t-popup>
<t-popup v-model="statusPickerVisible" placement="bottom">
<t-picker
v-model="statusPickerDraft"
@@ -183,11 +195,14 @@
import { Time } from "timi-web";
import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import type { ColorObject } from "tdesign-mobile-vue";
import type { Role } from "timi-web";
import { useUserStore, type Role } from "timi-web";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { EventItem, EventStatus, EventLimitType } from "@/types/EventItem";
import { permissionCodes } from "@/utils/permission";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
type PickerValue = string | number;
type LimitOption = {
label: string;
@@ -200,9 +215,12 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const routeId = computed(() => String(route.params.id || ""));
const isCreateMode = computed(() => !routeId.value);
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.event.create : permissionCodes.event.update);
const deletePermissionCode = permissionCodes.event.delete;
const pageAccess = computed(() => ({
@@ -217,6 +235,7 @@ const isSaving = ref(false);
const isRemoving = ref(false);
const formModel = reactive({
id: "",
storeId: "",
name: "",
description: "",
color: "#0f766e",
@@ -229,6 +248,46 @@ const formModel = reactive({
roleCodeList: [] as string[]
});
// ---------- 门店选择 ----------
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 (!isCreateMode.value) {
return;
}
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;
}
// ---------- 颜色 ----------
const colorPickerVisible = ref(false);
@@ -388,6 +447,10 @@ function resolveTimestamp(valueText: string): number | undefined {
}
function validateForm(): boolean {
if (!formModel.storeId) {
Toast.warning("请选择门店");
return false;
}
if (!formModel.name.trim()) {
Toast.warning("请输入登记事件名称");
return false;
@@ -453,6 +516,7 @@ async function saveEventItem(): Promise<void> {
try {
const payload: EventItem = {
id: formModel.id || undefined,
storeId: formModel.storeId,
name: formModel.name.trim(),
description: formModel.description,
color: formModel.color,
@@ -519,7 +583,22 @@ async function removeEventItem(id: string): Promise<void> {
// ---------- 页面数据 ----------
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();
roleOptions.value = await ManagedUserAPI.roleList();
if (isCreateMode.value) {
return;
@@ -527,6 +606,7 @@ async function fillForm(): Promise<void> {
const current = await EventAPI.detail(routeId.value);
Object.assign(formModel, {
id: current.id || "",
storeId: current.storeId || formModel.storeId,
name: current.name || "",
description: current.description || "",
color: current.color || "#2f88ff",
+102 -9
View File
@@ -1,5 +1,20 @@
<template>
<div v-can="{ value: [permissionCodes.event.read, permissionCodes.user.read], mode: 'and' }" class="registration-event-list">
<div
v-can="{ value: [permissionCodes.event.read, permissionCodes.user.read], mode: 'and' }"
:class="{ 'has-filter': hasFilter }"
class="registration-event-list"
>
<div v-if="hasFilter" class="filter-menu">
<t-dropdown-menu>
<t-dropdown-item
v-if="isGlobalManager"
v-model="selectedStoreId"
:label="storeFilterLabel"
:options="storeOptions"
@change="selectStoreFilter"
/>
</t-dropdown-menu>
</div>
<t-pull-down-refresh
v-model="isRefreshing"
:refresh-timeout="30000"
@@ -9,7 +24,7 @@
<t-cell-group theme="card">
<t-cell
class="item"
v-for="item in pageResult.list"
v-for="item in visibleEventList"
:key="item.id"
:arrow="canUpdateEventItem"
:hover="canUpdateEventItem"
@@ -46,10 +61,10 @@
</template>
</t-cell>
</t-cell-group>
<div v-if="!pageResult.list.length" class="empty-block" v-text="isLoading ? '正在加载登记事件' : '暂无登记事件'" />
<div v-if="pageResult.list.length" class="load-state" v-text="isNoMore ? '已经到底了' : '上拉加载更多'" />
<div v-if="!visibleEventList.length" class="empty-block" v-text="emptyText" />
<div v-if="visibleEventList.length" class="load-state" v-text="isNoMore ? '已经到底了' : '上拉加载更多'" />
</t-pull-down-refresh>
<t-button v-can="permissionCodes.event.create" class="create" theme="primary" @click="router.push('/management/registration-events/create')">
<t-button v-can="permissionCodes.event.create" class="create" theme="primary" @click="toCreate">
<template #icon>
<t-icon name="add" />
</template>
@@ -59,13 +74,14 @@
</template>
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import { Toast, type DropdownValue } from "tdesign-mobile-vue";
import { type Page, type PageResult, type Role, useUserStore } from "timi-web";
import EventAPI from "@/api/EventAPI";
import ManagedUserAPI from "@/api/ManagedUserAPI";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { useNavBarStore } from "@/store/navBarStore";
import type { EventItem, EventLimitType, EventStatus } from "@/types/EventItem";
import { permissionCodes } from "@/utils/permission";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({
name: "eventList"
@@ -74,6 +90,34 @@ defineOptions({
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const isGlobalManager = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const hasFilter = computed(() => isGlobalManager.value);
// ---------- 门店筛选 ----------
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 eventListOptions = computed(() => {
if (isGlobalManager.value) {
return selectedStoreId.value === allStoreFilterValue
? { allStores: true }
: { storeId: selectedStoreId.value };
}
return { storeId: loginStoreId.value };
});
// ---------- 列表数据 ----------
@@ -91,6 +135,13 @@ const pageResult = reactive<PageResult<EventItem>>({
list: []
});
const canUpdateEventItem = computed(() => userStore.hasPermissions(permissionCodes.event.update));
const visibleEventList = computed(() => pageResult.list);
const emptyText = computed(() => {
if (isLoading.value) {
return "正在加载登记事件";
}
return "暂无登记事件";
});
type StatusTag = {
text: string;
@@ -154,13 +205,23 @@ function getRoleName(code: string): string {
// ---------- 页面数据 ----------
async function loadStoreScope(): Promise<void> {
if (isGlobalManager.value) {
await currentStoreStore.loadStoreList();
return;
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store?.id || "";
}
async function fetch(): Promise<void> {
if (isLoading.value || isNoMore.value) {
return;
}
isLoading.value = true;
try {
const result = await EventAPI.list(page);
const result = await EventAPI.list(page, eventListOptions.value);
pageResult.total = result.total;
pageResult.list = pageResult.list.concat(result.list || []);
page.index += 1;
@@ -184,8 +245,9 @@ async function refresh(): Promise<void> {
}
async function loadData(): Promise<void> {
await loadStoreScope();
const [eventResult, roles] = await Promise.all([
EventAPI.list(page),
EventAPI.list(page, eventListOptions.value),
ManagedUserAPI.roleList()
]);
roleOptions.value = roles || [];
@@ -195,6 +257,24 @@ async function loadData(): Promise<void> {
isNoMore.value = pageResult.list.length >= pageResult.total || pageResult.list.length < page.size;
}
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/registration-events/create",
query: isGlobalManager.value && selectedStoreId.value !== allStoreFilterValue
? { storeId: selectedStoreId.value }
: undefined
});
}
onMounted(async () => {
navBarStore.clearRight();
if (canUpdateEventItem.value) {
@@ -218,6 +298,19 @@ onUnmounted(() => {
.registration-event-list {
padding: calc(var(--app-nav-height) + 1rem) 0 6rem 0;
&.has-filter {
padding-top: calc(var(--app-nav-height) + 4rem);
}
.filter-menu {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 20;
position: fixed;
background: var(--td-bg-color-container);
}
.item {
.header {
+9 -29
View File
@@ -21,14 +21,14 @@
</t-form-item>
<t-form-item label="性别">
<t-radio-group class="sex" v-model="formModel.gender" variant="default-filled">
<t-radio :block="false" borderless value="男">男</t-radio>
<t-radio :block="false" borderless value="女">女</t-radio>
<t-radio :block="false" borderless value="未知">未知</t-radio>
<t-radio :block="false" borderless value="MALE">男</t-radio>
<t-radio :block="false" borderless value="FEMALE">女</t-radio>
<t-radio :block="false" borderless value="">未知</t-radio>
</t-radio-group>
</t-form-item>
<t-form-item label="手机号">
<div class="mobile-value">
<telephone-link v-if="formModel.mobile" :value="formModel.mobile" />
<div v-if="formModel.mobile" v-text="formModel.mobile" />
<span v-else v-text="'未填写'" />
</div>
</t-form-item>
@@ -50,7 +50,7 @@
<script setup lang="ts">
import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import { AttachmentAPI, UserAPI, useUserStore } from "timi-web";
import { AttachmentAPI, type Gender, UserAPI, useUserStore } from "timi-web";
import PhotoField from "@/components/PhotoField.vue";
import { buildFallbackAvatar } from "@/utils/avatar";
import { compressUploadImage } from "@/utils/imageCompression";
@@ -59,7 +59,7 @@ type UserAvatarSource = NonNullable<Parameters<typeof UserAPI.getAvatarURL>[0]>
name?: string;
nick?: string;
};
type ProfileGender = Gender | "";
defineOptions({
name: "ManagementProfile"
});
@@ -83,7 +83,7 @@ const formModel = reactive({
avatar: "",
nickname: "",
role: "",
gender: "未知",
gender: "" as ProfileGender,
mobile: "",
password: "",
office: "",
@@ -102,7 +102,7 @@ function fillForm(): void {
avatar: buildUserAvatar(user),
nickname: user?.nick || "",
role: formatRoleCode(userStore.getRoleList()[0]),
gender: sexToText(user?.sex),
gender: user?.gender || "",
mobile: user?.phoneNo || "",
password: "",
office: "",
@@ -159,7 +159,7 @@ async function saveProfile(): Promise<void> {
id: formModel.id,
name: formModel.name,
nick: formModel.nickname,
sex: textToSex(formModel.gender),
gender: formModel.gender,
tempFileIdList: avatarTempFileId.value ? [avatarTempFileId.value] : undefined
});
await userStore.updateLoginUser(result);
@@ -195,16 +195,6 @@ async function logout(): Promise<void> {
// ---------- 格式化 ----------
function sexToText(sex?: number): "男" | "女" | "未知" {
if (sex === 1) {
return "男";
}
if (sex === 2) {
return "女";
}
return "未知";
}
function formatRoleCode(roleCode?: string): string {
if (!roleCode) {
return "";
@@ -214,16 +204,6 @@ function formatRoleCode(roleCode?: string): string {
return codePartList[codePartList.length - 1] || roleCode;
}
function textToSex(text: string): number {
if (text === "男") {
return 1;
}
if (text === "女") {
return 2;
}
return 0;
}
function buildUserAvatar(user?: UserAvatarSource): string {
const name = user?.nick || user?.name || "用户";
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(name, "#0f766e");
+24 -18
View File
@@ -2,32 +2,37 @@
<div class="management-tab">
<t-cell-group class="user-card" theme="card">
<t-cell
class="cell"
:title="currentUserName"
v-if="currentUser"
class="info"
arrow
hover
@click="go('/management/profile')"
>
<template #description>
<telephone-link v-if="currentUser?.phoneNo" :value="currentUser.phoneNo" />
<span v-else v-text="currentUserDesc" />
<div v-if="currentUser?.phoneNo" v-text="currentUser.phoneNo" />
</template>
<template #title>
<div class="header">
<div class="name" v-text="currentUser.name" />
<gender-icon :value="currentUser.gender" />
</div>
</template>
<template #leftIcon>
<t-image
class="avatar"
:src="currentUserAvatar"
:alt="currentUserName"
:alt="currentUser.name"
fit="cover"
/>
</template>
</t-cell>
<t-cell
v-if="canSelectStore"
v-if="canViewStore"
class="cell"
title="当前门店"
:note="currentStoreStore.currentStoreName"
arrow
hover
:arrow="canSelectStore"
:hover="canSelectStore"
@click="openStorePicker"
>
<template #leftIcon>
@@ -99,13 +104,10 @@ const currentStoreStore = useCurrentStoreStore();
const currentUser = computed(() => userStore.loginUser.user);
const currentUserName = computed(() => currentUser.value?.nick || currentUser.value?.name || "未登录用户");
const currentUserDesc = computed(() => currentUser.value?.phoneNo || currentUser.value?.email || "暂无联系方式");
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
const canSelectStore = computed(() => userStore.hasPermissions(permissionCodes.store.read));
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
const canLoadLoginStore = computed(() => userStore.hasRoles([gaoRoleCodes.storeManager, gaoRoleCodes.employee], "or"));
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
const canManagePermission = computed(() => (
userStore.hasRoles(coreRoleCodes.admin) || userStore.hasRoles(coreRoleCodes.userRoleAdmin)
) && userStore.hasPermissions([
@@ -132,6 +134,10 @@ const storePickerColumns = computed(() => [
]);
function openStorePicker(): void {
if (!canSelectStore.value) {
return;
}
if (!currentStoreStore.storeList.length) {
Toast.warning("暂无可选门店");
return;
@@ -160,7 +166,7 @@ const menuGroupList = computed<MenuGroup[]>(() => [
icon: "shop",
path: "/management/stores",
title: "门店管理",
visible: userStore.hasPermissions(permissionCodes.store.read)
visible: canSelectStore.value && userStore.hasPermissions(permissionCodes.store.read)
},
{
icon: "usergroup",
@@ -226,7 +232,7 @@ const menuGroupList = computed<MenuGroup[]>(() => [
]);
function buildUserAvatar(user?: UserAvatarSource): string {
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(currentUserName.value, "#0f766e");
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(currentUser.value?.name, "#0f766e");
}
async function go(path: string): Promise<void> {
@@ -251,7 +257,7 @@ onMounted(async () => {
.user-card {
.cell {
.info {
padding: 10px;
.avatar {
@@ -260,9 +266,10 @@ onMounted(async () => {
border-radius: .875rem;
}
.title {
.header {
gap: .5rem;
display: flex;
align-items: center;
}
}
}
@@ -299,4 +306,3 @@ onMounted(async () => {
}
}
</style>
+4 -1
View File
@@ -15,7 +15,10 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
let loginStoreRequest: Promise<Store | undefined> | undefined;
let loginStoreRequestUserId = "";
const currentStore = computed(() => storeList.value.find(item => item.id === currentStoreId.value));
const currentStore = computed(() => (
storeList.value.find(item => item.id === currentStoreId.value)
|| (loginStore.value?.id === currentStoreId.value ? loginStore.value : undefined)
));
const currentStoreName = computed(() => currentStore.value?.name || "默认门店");
async function loadStoreList(): Promise<void> {
+2 -2
View File
@@ -4,8 +4,8 @@
* @param name 头像中显示的用户名
* @param backgroundColor 头像背景色
*/
export function buildFallbackAvatar(name: string, backgroundColor = "#2563eb"): string {
const text = encodeURIComponent((name.trim() || "用户").slice(0, 2));
export function buildFallbackAvatar(name?: string, backgroundColor = "#2563eb"): string {
const text = encodeURIComponent((name?.trim() || "用户").slice(0, 2));
const fill = encodeURIComponent(backgroundColor);
return [
"data:image/svg+xml;charset=UTF-8,",