feat(app): implement all sub-pages and admin management pages

Sub-pages: card purchase with WeChat Pay flow, my memberships with
progress bars, my bookings with tabs, personal info editor
Admin: management center grid, week template CRUD, slot adjustment,
member management with search, order list with filters, card type
CRUD with form modal, studio settings editor
Admin Pinia store for all admin API calls
This commit is contained in:
richarjiang
2026-04-02 15:25:57 +08:00
parent 3a29aca0db
commit 7a06b5e336
12 changed files with 1809 additions and 1680 deletions

View File

@@ -21,7 +21,7 @@
</view>
</view>
<!-- Form card -->
<!-- Basic info card -->
<view class="form-card">
<text class="form-card-title">基本信息</text>
@@ -150,10 +150,10 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { get, put } from '../../utils/request'
import type { StudioConfig } from '@mp-pilates/shared'
import { useAdminStore } from '../../stores/admin'
const adminStore = useAdminStore()
// Form state
const form = ref({
name: '',
address: '',
@@ -183,7 +183,7 @@ const bannerStyle = computed(() => {
async function fetchStudioInfo() {
loading.value = true
try {
const data = await get<StudioConfig>('/studio/info')
const data = await adminStore.fetchStudioConfig()
const initial = {
name: data.name ?? '',
address: data.address ?? '',
@@ -228,7 +228,7 @@ async function handleSave() {
if (!isNaN(lat)) payload.latitude = lat
if (!isNaN(lng)) payload.longitude = lng
await put('/admin/studio/info', payload)
await adminStore.saveStudioConfig(payload as any)
original.value = { ...form.value }
uni.showToast({ title: '保存成功', icon: 'success' })
} catch (e: any) {
@@ -249,10 +249,7 @@ onMounted(fetchStudioInfo)
}
/* ── Skeleton ────────────────────────────── */
.skeleton-page {
padding: 0 24rpx;
padding-top: 280rpx;
}
.skeleton-page { padding: 0 24rpx; padding-top: 280rpx; }
.skeleton-section {
height: 200rpx;
@@ -277,7 +274,7 @@ onMounted(fetchStudioInfo)
.banner-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
background: rgba(0,0,0,0.35);
display: flex;
flex-direction: column;
align-items: center;
@@ -290,13 +287,10 @@ onMounted(fetchStudioInfo)
height: 96rpx;
border-radius: 50%;
overflow: hidden;
border: 4rpx solid rgba(255, 255, 255, 0.4);
border: 4rpx solid rgba(255,255,255,0.4);
}
.banner-logo {
width: 96rpx;
height: 96rpx;
}
.banner-logo { width: 96rpx; height: 96rpx; }
.banner-logo-placeholder {
width: 100%;
@@ -307,17 +301,9 @@ onMounted(fetchStudioInfo)
justify-content: center;
}
.banner-logo-text {
font-size: 40rpx;
font-weight: 700;
color: #1a1a2e;
}
.banner-logo-text { font-size: 40rpx; font-weight: 700; color: #1a1a2e; }
.banner-name {
font-size: 32rpx;
font-weight: 700;
color: #ffffff;
}
.banner-name { font-size: 32rpx; font-weight: 700; color: #ffffff; }
/* ── Form card ───────────────────────────── */
.form-card {
@@ -325,7 +311,7 @@ onMounted(fetchStudioInfo)
border-radius: 20rpx;
margin: 24rpx 24rpx 0;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
}
.form-card-title {
@@ -345,9 +331,7 @@ onMounted(fetchStudioInfo)
padding: 28rpx;
border-bottom: 1rpx solid #f5f5f5;
&--last {
border-bottom: none;
}
&--last { border-bottom: none; }
}
.form-label {
@@ -365,10 +349,7 @@ onMounted(fetchStudioInfo)
margin-top: 4rpx;
}
.label-group {
width: 240rpx;
flex-shrink: 0;
}
.label-group { width: 240rpx; flex-shrink: 0; }
.form-input {
flex: 1;
@@ -385,9 +366,7 @@ onMounted(fetchStudioInfo)
}
/* ── Save button ─────────────────────────── */
.save-wrap {
padding: 40rpx 24rpx;
}
.save-wrap { padding: 40rpx 24rpx; }
.save-btn {
width: 100%;
@@ -397,7 +376,7 @@ onMounted(fetchStudioInfo)
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(26, 26, 46, 0.3);
box-shadow: 0 4rpx 20rpx rgba(26,26,46,0.3);
&:active { opacity: 0.85; }