add store tab
This commit is contained in:
@@ -116,6 +116,7 @@ const navBarHeight = computed(() => {
|
||||
const tabVal = ref(route.path);
|
||||
const list = [
|
||||
{ value: "/customers", icon: "usergroup", text: "客户" },
|
||||
{ value: "/store", icon: "shop", text: "门店" },
|
||||
{ value: "/management", icon: "setting-1", text: "管理" }
|
||||
];
|
||||
|
||||
@@ -128,6 +129,10 @@ function resolveTabValue(path: string): string {
|
||||
return "/customers";
|
||||
}
|
||||
|
||||
if (path.startsWith("/store")) {
|
||||
return "/store";
|
||||
}
|
||||
|
||||
if (path.startsWith("/management")) {
|
||||
return "/management";
|
||||
}
|
||||
|
||||
@@ -26,19 +26,6 @@
|
||||
/>
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell
|
||||
v-if="canViewStore"
|
||||
class="cell"
|
||||
title="当前门店"
|
||||
:note="currentStoreStore.currentStoreName"
|
||||
:arrow="canSelectStore"
|
||||
:hover="canSelectStore"
|
||||
@click="openStorePicker"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<t-icon name="shop" size="1.75rem" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<t-cell-group v-for="group in visibleMenuGroupList" :key="group.title" :title="group.title" theme="card">
|
||||
<t-cell
|
||||
@@ -60,34 +47,20 @@
|
||||
<p>技术支持:夜雨</p>
|
||||
<p>Copyright © 2026 imyeyu.com</p>
|
||||
</footer>
|
||||
<t-popup v-model="storePickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="storePickerDraft"
|
||||
:columns="storePickerColumns"
|
||||
title="当前门店"
|
||||
@cancel="storePickerVisible = false"
|
||||
@confirm="confirmStorePicker"
|
||||
/>
|
||||
</t-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { UserAPI, useUserStore } from "timi-web";
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
import { corePermissionCodes, gaoRoleCodes, gaoStoreScopePermissionCodes, permissionCodes } from "@/utils/permission";
|
||||
import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
|
||||
type PickerValue = string | number;
|
||||
|
||||
type MenuItem = {
|
||||
icon: string;
|
||||
path: string;
|
||||
title: string;
|
||||
visible: boolean;
|
||||
action?: "store";
|
||||
};
|
||||
|
||||
type MenuGroup = {
|
||||
@@ -101,15 +74,10 @@ defineOptions({
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
|
||||
const currentUser = computed(() => userStore.loginUser.user);
|
||||
|
||||
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
|
||||
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||
const canLoadLoginStore = computed(() => userStore.hasPermissions(gaoStoreScopePermissionCodes, "or"));
|
||||
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
|
||||
const canManageStore = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
|
||||
const canManagePermission = computed(() => userStore.hasPermissions([
|
||||
corePermissionCodes.role.read,
|
||||
corePermissionCodes.permission.read
|
||||
@@ -122,53 +90,10 @@ const visibleMenuGroupList = computed<MenuGroup[]>(() => menuGroupList.value
|
||||
}))
|
||||
.filter(group => group.items.length));
|
||||
|
||||
// ---------- 当前门店 ----------
|
||||
|
||||
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)
|
||||
]);
|
||||
|
||||
function openStorePicker(): void {
|
||||
if (!canSelectStore.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentStoreStore.storeList.length) {
|
||||
Toast.warning("暂无可选门店");
|
||||
return;
|
||||
}
|
||||
|
||||
storePickerDraft.value = [currentStoreStore.currentStoreId];
|
||||
storePickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmStorePicker(): void {
|
||||
const storeId = String(storePickerDraft.value[0] || "");
|
||||
if (!storeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentStoreStore.selectStore(storeId);
|
||||
storePickerVisible.value = false;
|
||||
Toast.success("当前门店已切换");
|
||||
}
|
||||
|
||||
const menuGroupList = computed<MenuGroup[]>(() => [
|
||||
{
|
||||
title: "客户",
|
||||
items: [
|
||||
{
|
||||
icon: "shop",
|
||||
path: "/management/stores",
|
||||
title: "门店管理",
|
||||
action: "store",
|
||||
visible: canManageStore.value && userStore.hasPermissions(permissionCodes.store.read)
|
||||
},
|
||||
{
|
||||
icon: "usergroup",
|
||||
path: "/management/customers",
|
||||
@@ -192,12 +117,6 @@ const menuGroupList = computed<MenuGroup[]>(() => [
|
||||
path: "/management/customer-data-charts",
|
||||
title: "客户统计数据",
|
||||
visible: userStore.hasPermissions(permissionCodes.stat.read)
|
||||
},
|
||||
{
|
||||
icon: "chart",
|
||||
path: "/management/data-charts",
|
||||
title: "全局统计数据",
|
||||
visible: userStore.hasPermissions(permissionCodes.stat.read)
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -242,27 +161,8 @@ async function go(item: MenuItem | string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.action === "store" && !canSelectStore.value) {
|
||||
const store = await currentStoreStore.loadLoginStore();
|
||||
if (!store?.id) {
|
||||
Toast.warning("未绑定所属门店");
|
||||
return;
|
||||
}
|
||||
|
||||
await router.push(`/management/stores/${store.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await router.push(item.path);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (canSelectStore.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
} else if (canLoadLoginStore.value) {
|
||||
await currentStoreStore.loadLoginStore();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -291,13 +191,6 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.store-card {
|
||||
|
||||
.cell {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 1.25rem 1rem calc(1rem + env(safe-area-inset-bottom, 0));
|
||||
text-align: center;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="store-tab">
|
||||
<section v-if="canViewStore || canManageStore" class="store-header">
|
||||
<t-cell-group class="store-card" theme="card">
|
||||
<t-cell
|
||||
v-if="canViewStore"
|
||||
class="info"
|
||||
title="当前门店"
|
||||
:note="currentStoreStore.currentStoreName"
|
||||
>
|
||||
<template #description>
|
||||
<div v-if="currentStore?.code" v-text="`编码:${currentStore.code}`" />
|
||||
<div v-if="currentStore?.address" v-text="currentStore.address" />
|
||||
<div v-if="currentStore?.telephone" v-text="`电话:${currentStore.telephone}`" />
|
||||
</template>
|
||||
<template #leftIcon>
|
||||
<t-icon name="shop" size="1.75rem" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell
|
||||
v-if="canSelectStore"
|
||||
class="action"
|
||||
title="切换门店"
|
||||
note="选择数据统计门店"
|
||||
arrow
|
||||
hover
|
||||
@click="openStorePicker"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<t-icon name="swap" size="1.75rem" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell
|
||||
v-if="canManageStore"
|
||||
class="action"
|
||||
title="门店管理"
|
||||
note="管理门店资料和营业日"
|
||||
arrow
|
||||
hover
|
||||
@click="goStoreManagement"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<t-icon name="shop" size="1.75rem" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
</section>
|
||||
<store-data-charts :key="currentStoreStore.currentStoreId" />
|
||||
<t-popup v-model="storePickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
v-model="storePickerDraft"
|
||||
:columns="storePickerColumns"
|
||||
title="切换门店"
|
||||
@cancel="storePickerVisible = false"
|
||||
@confirm="confirmStorePicker"
|
||||
/>
|
||||
</t-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { gaoRoleCodes, gaoStoreScopePermissionCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
type PickerValue = string | number;
|
||||
|
||||
const StoreDataCharts = defineAsyncComponent(() => import("@/pages/store/StoreDataCharts.vue"));
|
||||
|
||||
defineOptions({
|
||||
name: "StoreTab"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
|
||||
const currentStore = computed(() => currentStoreStore.currentStore);
|
||||
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||
const canLoadLoginStore = computed(() => userStore.hasPermissions(gaoStoreScopePermissionCodes, "or"));
|
||||
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
|
||||
const canManageStore = computed(() => userStore.hasRoles([
|
||||
gaoRoleCodes.globalManager,
|
||||
gaoRoleCodes.storeManager
|
||||
], "or") && userStore.hasPermissions(permissionCodes.store.read));
|
||||
|
||||
// ---------- 门店切换 ----------
|
||||
|
||||
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)
|
||||
]);
|
||||
|
||||
function openStorePicker(): void {
|
||||
if (!canSelectStore.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentStoreStore.storeList.length) {
|
||||
Toast.warning("暂无可选门店");
|
||||
return;
|
||||
}
|
||||
|
||||
storePickerDraft.value = [currentStoreStore.currentStoreId];
|
||||
storePickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmStorePicker(): void {
|
||||
const storeId = String(storePickerDraft.value[0] || "");
|
||||
if (!storeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentStoreStore.selectStore(storeId);
|
||||
storePickerVisible.value = false;
|
||||
Toast.success("当前门店已切换");
|
||||
}
|
||||
|
||||
// ---------- 门店管理 ----------
|
||||
|
||||
async function goStoreManagement(): Promise<void> {
|
||||
if (canSelectStore.value) {
|
||||
await router.push("/management/stores");
|
||||
return;
|
||||
}
|
||||
|
||||
const store = await currentStoreStore.loadLoginStore();
|
||||
if (!store?.id) {
|
||||
Toast.warning("未绑定所属门店");
|
||||
return;
|
||||
}
|
||||
|
||||
await router.push(`/management/stores/${store.id}`);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (canSelectStore.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
} else if (canLoadLoginStore.value) {
|
||||
await currentStoreStore.loadLoginStore();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.store-tab {
|
||||
.store-header {
|
||||
padding: calc(var(--app-nav-height) + 1rem) 1rem 0 1rem;
|
||||
|
||||
.store-card {
|
||||
.info {
|
||||
:deep(.t-cell__description) {
|
||||
gap: .25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.data-charts-page) {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.store-tab {
|
||||
.store-card {
|
||||
.info {
|
||||
:deep(.t-cell__description) {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import HomeTab from "@/pages/tabs/HomeTab.vue";
|
||||
import SettingTab from "@/pages/tabs/SettingTab.vue";
|
||||
import StoreTab from "@/pages/tabs/StoreTab.vue";
|
||||
|
||||
const tabRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
@@ -17,6 +18,20 @@ const tabRoutes: RouteRecordRaw[] = [
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/store",
|
||||
name: "StoreTab",
|
||||
component: StoreTab,
|
||||
meta: {
|
||||
depth: 2,
|
||||
navBarVisible: true,
|
||||
navBarCanBack: false,
|
||||
navBarTitle: "门店",
|
||||
tabBarVisible: true,
|
||||
bodyBackground: "#F5F5F8",
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management",
|
||||
name: "SettingTab",
|
||||
|
||||
Reference in New Issue
Block a user