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