refactor: 调整 admin 目录结构并精简个人中心快捷区

- 将 admin 相关的 store/utils 移入对应子目录(pages/admin/stores、pages/admin/utils)
- 更新 manifest.json、pages.json 路由与配置
- 个人中心 ProfileMenu 移除顶部「我的会员卡 / 我的预约」快捷卡片,与 UserCard 会员卡入口合并
- 「我的预约」下移至菜单列表,紧邻「个人信息」
- 清理 profile/index.vue 中不再使用的 bookingStore / 计算属性 / 透传
This commit is contained in:
richarjiang
2026-09-09 14:36:34 +08:00
parent 57107c02dc
commit 75ef5e94a6
22 changed files with 138 additions and 183 deletions

View File

@@ -1,21 +1,9 @@
<template>
<view class="profile-menu">
<view class="profile-menu__shortcuts">
<view v-for="item in primaryItems" :key="item.key" class="profile-menu__shortcut"
hover-class="profile-menu__item--hover" @tap="handleTap(item)">
<view class="profile-menu__shortcut-head">
<view class="profile-menu__icon" :class="'profile-menu__icon--' + item.key" />
<text class="profile-menu__arrow"></text>
</view>
<text class="profile-menu__shortcut-title">{{ item.title }}</text>
<text class="profile-menu__shortcut-note">{{ !requireAuth ? '登录后查看' : item.key === 'membership' ? `${activeMembershipCount || 0} 张可用` : `${upcomingBookingCount || 0} 节待上` }}</text>
</view>
</view>
<slot />
<view class="profile-menu__links">
<template v-for="item in secondaryItems" :key="item.key">
<template v-for="item in menuItems" :key="item.key">
<view v-if="item.type === 'separator'" class="profile-menu__separator" />
<view v-else class="profile-menu__item" :class="{ 'profile-menu__item--admin': item.isAdmin }"
hover-class="profile-menu__item--hover" hover-stay-time="150" @tap="handleTap(item)">
@@ -36,7 +24,6 @@ interface MenuItem {
title?: string
path?: string
isAdmin?: boolean
badge?: string
action?: 'clear'
requireAuth?: boolean
}
@@ -44,9 +31,6 @@ interface MenuItem {
const props = defineProps<{
isAdmin: boolean
requireAuth?: boolean
activeMembershipCount?: number
upcomingBookingCount?: number
inviteShareEligible?: boolean
}>()
const emit = defineEmits<{
@@ -55,30 +39,7 @@ const emit = defineEmits<{
}>()
const menuItems = computed<MenuItem[]>(() => {
const membershipBadge = props.activeMembershipCount && props.activeMembershipCount > 0
? `${props.activeMembershipCount}`
: undefined
const bookingBadge = props.upcomingBookingCount && props.upcomingBookingCount > 0
? `${props.upcomingBookingCount}`
: undefined
const items: MenuItem[] = [
{
key: 'membership',
type: 'item',
title: '我的会员卡',
path: '/pages/profile/membership',
badge: membershipBadge,
requireAuth: true,
},
{
key: 'bookings',
type: 'item',
title: '我的预约',
path: '/pages/profile/bookings',
badge: bookingBadge,
requireAuth: true,
},
...(props.isAdmin
? [{
key: 'teaching-schedule',
@@ -88,16 +49,13 @@ const menuItems = computed<MenuItem[]>(() => {
requireAuth: true,
}]
: []),
// 临时隐藏邀请好友入口,后续恢复时直接取消这段注释即可。
// ...(props.inviteShareEligible
// ? [{
// key: 'invite',
// type: 'item' as const,
// title: '邀请好友',
// path: '/pages/profile/invite',
// requireAuth: true,
// }]
// : []),
{
key: 'bookings',
type: 'item',
title: '我的预约',
path: '/pages/profile/bookings',
requireAuth: true,
},
{
key: 'info',
type: 'item',
@@ -132,9 +90,6 @@ const menuItems = computed<MenuItem[]>(() => {
return items
})
const primaryItems = computed(() => menuItems.value.filter(item => item.key === 'membership' || item.key === 'bookings'))
const secondaryItems = computed(() => menuItems.value.filter(item => item.key !== 'membership' && item.key !== 'bookings'))
function handleTap(item: MenuItem) {
if (item.requireAuth && !props.requireAuth) {
emit('require-login')
@@ -150,14 +105,6 @@ function handleTap(item: MenuItem) {
<style lang="scss" scoped>
.profile-menu {
&__shortcuts { display: flex; gap: 20rpx; margin: 24rpx 32rpx 0; }
&__shortcut { flex: 1; min-width: 0; padding: 24rpx; box-sizing: border-box; border-radius: 26rpx; background: #fff; border: 1rpx solid #eee8e3; }
&__shortcut-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
&__shortcut-title { display: block; font-size: 27rpx; font-weight: 500; color: #514943; }
&__shortcut-note { display: block; font-size: 22rpx; color: #8b817b; margin-top: 8rpx; }
&__icon { width: 36rpx; height: 30rpx; position: relative; box-sizing: border-box; border: 2rpx solid #9b8878; border-radius: 6rpx; }
&__icon--membership::after { content: ''; position: absolute; top: 8rpx; left: 0; right: 0; border-top: 2rpx solid #9b8878; }
&__icon--bookings { border-color: #829a8b; border-top-width: 7rpx; &::after { content: ''; position: absolute; left: 8rpx; top: 8rpx; width: 12rpx; border-top: 2rpx solid #829a8b; } }
&__links { margin: 24rpx 32rpx 0; border-radius: 26rpx; background: #fff; overflow: hidden; }
&__item { display: flex; align-items: center; gap: 20rpx; min-height: 96rpx; padding: 0 28rpx; box-sizing: border-box; border-bottom: 1rpx solid #f2ede8; &:last-child { border-bottom: none; } }
&__item--hover { background: #f4f1ec; }

View File

@@ -13,6 +13,10 @@
"minified": true
},
"usingComponents": true,
"lazyCodeLoading": "requiredComponents",
"optimization": {
"subPackages": true
},
"permission": {
"scope.userLocation": {
"desc": "用于获取工作室位置导航"

View File

@@ -12,13 +12,19 @@
{
"path": "pages/booking/index",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom",
"componentPlaceholder": {
"booking-confirm-popup": "view"
}
}
},
{
"path": "pages/booking/detail",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom",
"componentPlaceholder": {
"booking-confirm-popup": "view"
}
}
},
{
@@ -69,83 +75,6 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/index",
"style": {
"navigationStyle": "custom"
}
},
{ "path": "pages/admin/analytics", "style": { "navigationStyle": "custom", "enablePullDownRefresh": true } },
{
"path": "pages/admin/bookings",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/schedule",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/slot-adjust",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/members",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/member-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/member-edit",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/member-supplement",
"style": { "navigationStyle": "custom" }
},
{
"path": "pages/admin/member-arrange",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/orders",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/card-types",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/studio",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/admin/flash-sales",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/flash-sale/detail",
"style": {
@@ -153,6 +82,98 @@
}
}
],
"subPackages": [
{
"root": "pages/admin",
"pages": [
{
"path": "index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "analytics",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
},
{
"path": "bookings",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "schedule",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "slot-adjust",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "members",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "member-detail",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "member-edit",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "member-supplement",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "member-arrange",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "card-types",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "studio",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "flash-sales",
"style": {
"navigationStyle": "custom"
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "普拉提约课",

View File

@@ -103,7 +103,7 @@ import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import type { TeachingAnalytics } from '@mp-pilates/shared'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { useUserStore } from '../../stores/user'
import { getSystemLayout } from '../../utils/system'
import { getErrorMessage } from '../../utils/auth'

View File

@@ -239,9 +239,9 @@
import { ref, onMounted } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { formatPrice } from '../../utils/format'
import { uploadStudioAsset } from '../../utils/studio-upload'
import { uploadStudioAsset } from './utils/studio-upload'
import { CardTypeCategory } from '@mp-pilates/shared'
import type { CardType } from '@mp-pilates/shared'

View File

@@ -264,7 +264,7 @@
import { ref, onMounted, computed } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { formatPrice, formatDateTime, getFlashSalePhaseLabel, getStockPercent, formatDateLocal, formatTimeLocal } from '../../utils/format'
import { FlashSaleStatus, FlashSalePhase } from '@mp-pilates/shared'
import type { FlashSaleAdminItem, CardType } from '@mp-pilates/shared'

View File

@@ -190,9 +190,9 @@ import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { useUserStore } from '../../stores/user'
import type { AdminStats } from '../../stores/admin'
import type { AdminStats } from './stores/admin'
import { requestAdminBookingSubscriptionCount } from '../../utils/wechat-subscription'
import { getErrorMessage } from '../../utils/auth'

View File

@@ -115,7 +115,7 @@ import TimePeriodFilter from '../../components/TimePeriodFilter.vue'
import { getSystemLayout } from '../../utils/system'
import { formatDate, isSlotPast } from '../../utils/format'
import { getErrorMessage } from '../../utils/auth'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
type PeriodKey = keyof typeof TIME_PERIODS | null

View File

@@ -170,7 +170,7 @@ import {
} from '../../utils/format'
import { BOOKING_STATUS_LABELS } from '../../utils/booking-helpers'
import { getErrorMessage } from '../../utils/auth'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
const adminStore = useAdminStore()
const navBarHeight = ref('64px')

View File

@@ -111,7 +111,7 @@ import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { formatDateLocal } from '../../utils/format'
import { getErrorMessage } from '../../utils/auth'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
const adminStore = useAdminStore()
const navBarHeight = ref('64px')

View File

@@ -60,7 +60,7 @@ import { onLoad } from '@dcloudio/uni-app'
import type { AdminMemberDetail, CreateLessonSupplementDto, LessonSupplementRecord } from '@mp-pilates/shared'
import CustomNavBar from '../../components/CustomNavBar.vue'
import LessonSupplementList from '../../components/LessonSupplementList.vue'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { getSystemLayout } from '../../utils/system'
import { HttpRequestError } from '../../utils/request'
import { getErrorMessage } from '../../utils/auth'

View File

@@ -103,8 +103,8 @@ import { onReachBottom, onShow } from '@dcloudio/uni-app'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { getCardTypeLabel } from '../../utils/format'
import { useAdminStore } from '../../stores/admin'
import type { MemberSummary } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import type { MemberSummary } from './stores/admin'
const adminStore = useAdminStore()

View File

@@ -137,7 +137,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { formatPrice, formatDateTime } from '../../utils/format'
import { OrderStatus } from '@mp-pilates/shared'
import type { OrderWithDetails } from '@mp-pilates/shared'

View File

@@ -162,7 +162,7 @@ import { ref, computed, onMounted } from 'vue'
import type { ScheduleSlotPreview } from '@mp-pilates/shared'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { formatDate } from '../../utils/format'
import DateSelector from '../../components/DateSelector.vue'
import {
@@ -170,7 +170,7 @@ import {
timeToPickerIndex,
pickerIndexToTime,
addOneHourCapped,
} from '../../utils/schedule-time'
} from './utils/schedule-time'
const timePickerRange = SCHEDULE_TIME_PICKER_RANGE

View File

@@ -152,14 +152,14 @@
import { ref, onMounted } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { formatDate } from '../../utils/format'
import type { TimeSlot } from '@mp-pilates/shared'
import {
SCHEDULE_TIME_PICKER_RANGE,
timeToPickerIndex,
pickerIndexToTime,
} from '../../utils/schedule-time'
} from './utils/schedule-time'
const timePickerRange = SCHEDULE_TIME_PICKER_RANGE

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { get, post, put, del } from '../utils/request'
import { get, post, put, del } from '../../../utils/request'
import type {
TeachingAnalytics,
CardType,

View File

@@ -197,9 +197,9 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue'
import { useAdminStore } from '../../stores/admin'
import { useAdminStore } from './stores/admin'
import { getErrorMessage } from '../../utils/auth'
import { uploadStudioAsset } from '../../utils/studio-upload'
import { uploadStudioAsset } from './utils/studio-upload'
import { getSystemLayout } from '../../utils/system'
type FormState = {

View File

@@ -179,8 +179,8 @@
</view>
<BookingConfirmPopup
v-if="isSlotMode"
:visible="showConfirmPopup"
v-if="showConfirmPopup"
:visible="true"
:time-slot="slotData"
:memberships="userStore.activeMemberships as MembershipWithCardType[]"
@confirm="onConfirmBooking"

View File

@@ -68,7 +68,8 @@
<!-- Confirm popup -->
<BookingConfirmPopup
:visible="showConfirmPopup"
v-if="showConfirmPopup"
:visible="true"
:time-slot="pendingSlot"
:memberships="userStore.activeMemberships as MembershipWithCardType[]"
@confirm="onConfirmBooking"

View File

@@ -15,9 +15,6 @@
<ProfileMenu
:is-admin="isAdmin"
:require-auth="loggedIn"
:active-membership-count="activeMembershipCount"
:upcoming-booking-count="upcomingBookingCount"
:invite-share-eligible="!!user?.inviteShareEligible"
@clear-cache="handleClearCache"
@require-login="handleLogin"
>
@@ -34,11 +31,10 @@
<script setup lang="ts">
import InviteCard from '../../components/InviteCard.vue'
import { useInviteStore } from '../../stores/invite'
import { ref, computed, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { useUserStore } from '../../stores/user'
import { useBookingStore } from '../../stores/booking'
import { getSystemLayout } from '../../utils/system'
import { getErrorMessage } from '../../utils/auth'
import PracticeActivityCard from '../../components/PracticeActivityCard.vue'
@@ -47,9 +43,7 @@ import ProfileMenu from '../../components/ProfileMenu.vue'
const invite = useInviteStore()
const userStore = useUserStore()
const bookingStore = useBookingStore()
const { loggedIn, hasProfile, user, memberships, membershipsLoading, membershipsLoaded, membershipsError, isAdmin } = storeToRefs(userStore)
const { upcomingBookings } = storeToRefs(bookingStore)
const activityRefreshKey = ref(0)
const membershipNow = ref(Date.now())
@@ -57,14 +51,6 @@ const loginLoading = ref(false)
const navBarHeight = ref(getSystemLayout().navBarHeight)
const statusBarHeight = getSystemLayout().statusBarHeight
const activeMembershipCount = computed(
() => user.value?.activeMembershipCount ?? userStore.activeMemberships.length,
)
const upcomingBookingCount = computed(
() => (loggedIn.value ? upcomingBookings.value.length : 0),
)
// ─── 微信分享 ───────────────────────────────────────────────
onShareAppMessage(() => {
return {
@@ -94,7 +80,6 @@ onShow(async () => {
invite.refreshActivity().catch(() => {}),
userStore.fetchProfile(),
userStore.fetchMemberships(),
bookingStore.fetchUpcomingBookings(),
])
}
})
@@ -105,10 +90,7 @@ async function handleLogin() {
try {
const { isNewUser } = await userStore.loginWithSetup()
if (!isNewUser) {
await Promise.all([
invite.refreshActivity().catch(() => {}),
bookingStore.fetchUpcomingBookings(),
])
await invite.refreshActivity().catch(() => {})
}
} catch (err: unknown) {
uni.showToast({ title: getErrorMessage(err, '登录失败,请重试'), icon: 'none' })