diff --git a/CLAUDE.md b/CLAUDE.md index b85977e..4416ed3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,3 +97,54 @@ Prisma schema 位于 `packages/server/prisma/schema.prisma`,关键约定: - **异常处理**:使用 NestJS 内置异常(BadRequestException、NotFoundException 等) - **分页**:统一使用 `PaginatedResponse`,包含 data、total、page、limit - **pnpm**:使用 `shamefully-hoist=true`(.npmrc),为 Uni-app 兼容所需 + +## 前端样式规范 + +### 主题色变量(必用) + +所有色值必须使用 `packages/app/src/uni.scss` 中定义的 SCSS 变量,禁止在 Vue/Scss 文件中硬编码色值。 + +**主题色系:** + +```scss +$primary-color: #a9bfcc; /* 主色-柔雾蓝灰 */ +$primary-dark: #7ba5be; /* 主色-深蓝灰 */ +$primary-light: #c8d8e4; /* 主色-浅蓝灰 */ +$primary-bg: #f0f6f9; /* 页面背景-冷白蓝 */ +$primary-border: #d8eaf4; /* 边框-淡蓝灰 */ +$primary-selected-bg: #EFF6F9; /* 选中态背景 */ +``` + +**通用语义变量(已同步主题色):** + +| 变量 | 值 | 用途 | +|------|----|------| +| `$accent-color` | `#7ba5be` | 强调色 | +| `$warning-color` | `#e8a87c` | 警告色 | +| `$brand-light` | `#c8d8e4` | 品牌浅色 | +| `$border-color` | `rgba(180,160,130,0.2)` | 边框(中性) | +| `$text-primary` | `#4A4035` | 主文字(深棕灰) | +| `$text-secondary` | `#7A6A5A` | 次文字 | +| `$text-hint` | `#A09080` | 弱提示文字 | + +### 变量替换规则 + +| 旧硬编码 | 替换为 | +|---------|--------| +| `#c9a87c`(旧暖棕金) | `$primary-dark` | +| `#d4b896`(旧浅棕金) | `$primary-color` | +| `#C4956A`(旧警告橙棕) | `$warning-color` | +| `#B08050`(旧深棕) | `$accent-color` | +| `#7d6608`(旧深暖绿) | `#5a7a8a`(冷青灰) | +| `#e8c88a`、`#b49868`(旧暖渐变) | `$primary-color` / `$primary-dark` | + +### CSS 变量规范 + +组件内部的多处共用颜色(如阴影、遮罩)若无法用 SCSS 变量,需用 `rgba($primary-dark, 0.x)` 形式动态构造,不可直接写死十六进制值。 + +### 新增页面/组件 + +新增页面或组件时: +1. 优先查阅 `uni.scss` 已有变量 +2. 若需要新增语义化变量,先更新 `uni.scss`,再在组件中引用 +3. 禁止在 ` diff --git a/packages/app/src/pages/admin/index.vue b/packages/app/src/pages/admin/index.vue index 72288ed..32e4620 100644 --- a/packages/app/src/pages/admin/index.vue +++ b/packages/app/src/pages/admin/index.vue @@ -1,41 +1,147 @@ @@ -53,15 +159,6 @@ const adminStore = useAdminStore() const statsLoading = ref(false) const stats = ref({ todayBookings: 0, totalOrders: 0, totalBookings: 0 }) -const navItems = [ - { icon: '📅', label: '排课管理', path: '/pages/admin/schedule' }, - { icon: '📋', label: '排课模板', path: '/pages/admin/week-template' }, - { icon: '👥', label: '会员管理', path: '/pages/admin/members' }, - { icon: '📋', label: '订单管理', path: '/pages/admin/orders' }, - { icon: '💳', label: '卡种管理', path: '/pages/admin/card-types' }, - { icon: '🏢', label: '工作室设置', path: '/pages/admin/studio' }, -] - function navigate(path: string) { uni.navigateTo({ url: path }) } @@ -84,101 +181,168 @@ onMounted(() => { diff --git a/packages/app/src/pages/admin/members.vue b/packages/app/src/pages/admin/members.vue index fc1b3af..b11d1ac 100644 --- a/packages/app/src/pages/admin/members.vue +++ b/packages/app/src/pages/admin/members.vue @@ -273,11 +273,6 @@ onMounted(() => loadMembers(true)) animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty ───────────────────────────────── */ .empty-state { display: flex; diff --git a/packages/app/src/pages/admin/orders.vue b/packages/app/src/pages/admin/orders.vue index fdb7b1b..551cca7 100644 --- a/packages/app/src/pages/admin/orders.vue +++ b/packages/app/src/pages/admin/orders.vue @@ -293,7 +293,7 @@ onMounted(() => { } .stat-num.paid { color: #7A9E7E; } -.stat-num.pending { color: #C4956A; } +.stat-num.pending { color: $warning-color; } .stat-label { font-size: 22rpx; @@ -359,7 +359,7 @@ onMounted(() => { width: 6rpx; height: 6rpx; border-radius: 50%; - background: #B08050; + background: $accent-color; } /* ── List ───────────────────────────────────── */ @@ -384,11 +384,6 @@ onMounted(() => { animation: shimmer 1.6s ease infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty ──────────────────────────────────── */ .empty-state { display: flex; @@ -527,7 +522,7 @@ onMounted(() => { .info-value.price { font-size: 30rpx; font-weight: 700; - color: #B08050; + color: $accent-color; font-family: 'DIN Alternate', 'Helvetica Neue', Arial, sans-serif; } @@ -556,7 +551,7 @@ onMounted(() => { .load-more-text { font-size: 26rpx; - color: #B08050; + color: $accent-color; font-weight: 500; } diff --git a/packages/app/src/pages/admin/schedule.vue b/packages/app/src/pages/admin/schedule.vue index bfbc2be..c7b70e0 100644 --- a/packages/app/src/pages/admin/schedule.vue +++ b/packages/app/src/pages/admin/schedule.vue @@ -445,11 +445,6 @@ onMounted(() => { animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty state ─────────────────────────── */ .empty-state { display: flex; @@ -485,7 +480,7 @@ onMounted(() => { &--template { border-style: dashed; - border-color: #c9a87c; + border-color: $primary-dark; background: rgba(201, 168, 124, 0.04); } @@ -624,7 +619,7 @@ onMounted(() => { .add-wrap { margin: 24rpx; padding: 24rpx; - border: 2rpx dashed #c9a87c; + border: 2rpx dashed $primary-dark; border-radius: 16rpx; display: flex; align-items: center; @@ -636,7 +631,7 @@ onMounted(() => { .add-text { font-size: 28rpx; font-weight: 600; - color: #c9a87c; + color: $primary-dark; } /* ── Action bar ──────────────────────────── */ @@ -667,7 +662,7 @@ onMounted(() => { .publish-btn-text { font-size: 30rpx; font-weight: 700; - color: #c9a87c; + color: $primary-dark; } /* ── Modal ───────────────────────────────── */ @@ -758,5 +753,5 @@ onMounted(() => { justify-content: center; } -.modal-confirm-text { font-size: 28rpx; font-weight: 700; color: #c9a87c; } +.modal-confirm-text { font-size: 28rpx; font-weight: 700; color: $primary-dark; } diff --git a/packages/app/src/pages/admin/slot-adjust.vue b/packages/app/src/pages/admin/slot-adjust.vue index 9486736..a6278c4 100644 --- a/packages/app/src/pages/admin/slot-adjust.vue +++ b/packages/app/src/pages/admin/slot-adjust.vue @@ -286,7 +286,7 @@ onMounted(() => { } .tab--active { - border-bottom: 4rpx solid #c9a87c; + border-bottom: 4rpx solid $primary-dark; } /* ── Panel ───────────────────────────────── */ @@ -343,11 +343,6 @@ onMounted(() => { animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty ───────────────────────────────── */ .empty-state { display: flex; @@ -431,5 +426,5 @@ onMounted(() => { &--loading { opacity: 0.6; } } -.action-btn-text { font-size: 30rpx; font-weight: 700; color: #c9a87c; } +.action-btn-text { font-size: 30rpx; font-weight: 700; color: $primary-dark; } diff --git a/packages/app/src/pages/admin/studio.vue b/packages/app/src/pages/admin/studio.vue index 8f5bc23..5d8caef 100644 --- a/packages/app/src/pages/admin/studio.vue +++ b/packages/app/src/pages/admin/studio.vue @@ -268,11 +268,6 @@ onMounted(fetchStudioInfo) animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Banner preview ──────────────────────── */ .banner-preview { height: 260rpx; @@ -303,7 +298,7 @@ onMounted(fetchStudioInfo) .banner-logo-placeholder { width: 100%; height: 100%; - background: #c9a87c; + background: $primary-dark; display: flex; align-items: center; justify-content: center; @@ -398,7 +393,7 @@ onMounted(fetchStudioInfo) .save-btn-text { font-size: 32rpx; font-weight: 700; - color: #c9a87c; + color: $primary-dark; letter-spacing: 2rpx; } diff --git a/packages/app/src/pages/admin/week-template.vue b/packages/app/src/pages/admin/week-template.vue index 83f95e5..d17a0e9 100644 --- a/packages/app/src/pages/admin/week-template.vue +++ b/packages/app/src/pages/admin/week-template.vue @@ -351,7 +351,7 @@ onMounted(() => { padding: 12rpx 28rpx; } -.add-btn-text { font-size: 26rpx; font-weight: 600; color: #c9a87c; } +.add-btn-text { font-size: 26rpx; font-weight: 600; color: $primary-dark; } /* ── Skeleton ────────────────────────────── */ .skeleton-list { padding: 0 24rpx; } @@ -365,11 +365,6 @@ onMounted(() => { animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty ───────────────────────────────── */ .empty-state { display: flex; @@ -456,7 +451,7 @@ onMounted(() => { &--loading { opacity: 0.6; } } -.save-btn-text { font-size: 30rpx; font-weight: 700; color: #c9a87c; } +.save-btn-text { font-size: 30rpx; font-weight: 700; color: $primary-dark; } /* ── Modal ───────────────────────────────── */ .modal-mask { @@ -529,5 +524,5 @@ onMounted(() => { justify-content: center; } -.modal-confirm-text { font-size: 28rpx; font-weight: 700; color: #c9a87c; } +.modal-confirm-text { font-size: 28rpx; font-weight: 700; color: $primary-dark; } diff --git a/packages/app/src/pages/booking/index.vue b/packages/app/src/pages/booking/index.vue index 10adff4..091200d 100644 --- a/packages/app/src/pages/booking/index.vue +++ b/packages/app/src/pages/booking/index.vue @@ -281,7 +281,7 @@ onMounted(async () => { diff --git a/packages/app/src/pages/home/index.vue b/packages/app/src/pages/home/index.vue index d3e5859..5109d02 100644 --- a/packages/app/src/pages/home/index.vue +++ b/packages/app/src/pages/home/index.vue @@ -135,7 +135,7 @@ function scrollToCardShop() { diff --git a/packages/app/src/pages/profile/membership.vue b/packages/app/src/pages/profile/membership.vue index 8bc3c1a..af14a77 100644 --- a/packages/app/src/pages/profile/membership.vue +++ b/packages/app/src/pages/profile/membership.vue @@ -272,11 +272,6 @@ onMounted(() => { animation: shimmer 1.4s infinite; } -@keyframes shimmer { - 0% { background-position: 100% 0; } - 100% { background-position: -100% 0; } -} - /* ── Empty ───────────────────────────────────────────── */ .empty-wrap { display: flex; @@ -306,7 +301,7 @@ onMounted(() => { margin-top: 12rpx; padding: 22rpx 60rpx; border-radius: 44rpx; - background: #c9a87c; + background: $primary-dark; box-shadow: 0 4rpx 16rpx rgba(201, 168, 124, 0.35); } @@ -377,7 +372,7 @@ onMounted(() => { &--times { background: linear-gradient(90deg, #1a1a2e, #2d2d5e); } &--duration { background: linear-gradient(90deg, #6c3483, #9b59b6); } - &--trial { background: linear-gradient(90deg, #7d6608, #c9a87c); } + &--trial { background: linear-gradient(90deg, #5a7a8a, $primary-dark); } &--inactive { background: #ccc; } } @@ -391,7 +386,7 @@ onMounted(() => { &--times { background: linear-gradient(90deg, #1a1a2e, #2d2d5e); } &--duration { background: linear-gradient(90deg, #6c3483, #9b59b6); } - &--trial { background: linear-gradient(90deg, #7d6608, #c9a87c); } + &--trial { background: linear-gradient(90deg, #5a7a8a, $primary-dark); } &--inactive { background: #888; } } @@ -469,13 +464,13 @@ onMounted(() => { .highlight-number { font-size: 44rpx; font-weight: 800; - color: #c9a87c; + color: $primary-dark; line-height: 1; } .highlight-unit { font-size: 22rpx; - color: #c9a87c; + color: $primary-dark; font-weight: 500; } @@ -514,7 +509,7 @@ onMounted(() => { .progress-fill { height: 100%; - background: linear-gradient(90deg, #c9a87c, #e8c88a); + background: linear-gradient(90deg, $primary-dark, $primary-color); border-radius: 4rpx; transition: width 0.4s ease; } @@ -547,7 +542,7 @@ onMounted(() => { .fab-icon { font-size: 36rpx; - color: #c9a87c; + color: $primary-dark; font-weight: 300; line-height: 1; } @@ -555,7 +550,7 @@ onMounted(() => { .fab-text { font-size: 28rpx; font-weight: 700; - color: #c9a87c; + color: $primary-dark; letter-spacing: 1rpx; } diff --git a/packages/app/src/uni.scss b/packages/app/src/uni.scss index d5c6066..abc7116 100644 --- a/packages/app/src/uni.scss +++ b/packages/app/src/uni.scss @@ -1,16 +1,26 @@ /* uni.scss - 全局样式变量 */ -$brand-color: #1a1a2e; -$brand-light: #e2d1c3; -$accent-color: #c9a87c; -$text-primary: #333333; -$text-secondary: #666666; -$text-hint: #999999; -$bg-page: #f5f5f5; + +/* ── 主题色系 ───────────────────────────────────────────── */ +$primary-color: #a9bfcc; +$primary-dark: #7ba5be; +$primary-light: #c8d8e4; +$primary-bg: #f0f6f9; +$primary-border: #d8eaf4; +$primary-selected-bg: #EFF6F9; + +/* ── 通用 ─────────────────────────────────────────────── */ +$brand-color: #4A4035; +$brand-light: #c8d8e4; +$accent-color: #7ba5be; +$text-primary: #4A4035; +$text-secondary: #7A6A5A; +$text-hint: #A09080; +$bg-page: #FAF8F5; $bg-card: #ffffff; -$border-color: #eeeeee; -$success-color: #52c41a; -$warning-color: #faad14; -$error-color: #ff4d4f; +$border-color: rgba(180, 160, 130, 0.2); +$success-color: #7A9E7E; +$warning-color: #e8a87c; +$error-color: #C47A7A; $radius-sm: 8rpx; $radius-md: 16rpx; $radius-lg: 24rpx; @@ -19,3 +29,9 @@ $spacing-sm: 16rpx; $spacing-md: 24rpx; $spacing-lg: 32rpx; $spacing-xl: 48rpx; + +/* ── Shimmer animation ──────────────────────────────────── */ +@keyframes shimmer { + 0% { background-position: 100% 0; } + 100% { background-position: -100% 0; } +}