Pārlūkot izejas kodu

用户端小程序页面打磨

skyline 1 mēnesi atpakaļ
vecāks
revīzija
f47edc16ba

+ 201 - 0
DESIGN.md

@@ -0,0 +1,201 @@
+---
+name: AI零售柜
+description: AI智能零售柜小程序 — 扫码开门、即拿即走、自动扣款
+colors:
+  primary-yellow: "#FFC107"
+  primary-yellow-light: "#FFE082"
+  primary-yellow-dark: "#FFA000"
+  primary-yellow-bg: "#FFF8E1"
+  neutral-ink: "#2C2C2C"
+  neutral-ash: "#8C8C8C"
+  neutral-cloud: "#BDBDBD"
+  neutral-white: "#FFFFFF"
+  neutral-cream: "#FAFAFA"
+  neutral-mist: "#F5F5F5"
+  neutral-border: "#EEEEEE"
+  success-green: "#4CAF50"
+  warning-orange: "#FF9800"
+  error-red: "#F44336"
+  info-blue: "#2196F3"
+typography:
+  body:
+    fontFamily: "-apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif"
+    fontSize: "28rpx"
+    fontWeight: 400
+    lineHeight: 1.6
+rounded:
+  sm: "8rpx"
+  md: "16rpx"
+  lg: "24rpx"
+  xl: "32rpx"
+  full: "50%"
+spacing:
+  xs: "8rpx"
+  sm: "16rpx"
+  md: "24rpx"
+  lg: "32rpx"
+  xl: "48rpx"
+  xxl: "64rpx"
+components:
+  button-primary:
+    backgroundColor: "{colors.primary-yellow}"
+    textColor: "#1A1A1A"
+    rounded: "54rpx"
+    padding: "24rpx 0"
+    height: "108rpx"
+  button-primary-hover:
+    backgroundColor: "{colors.primary-yellow-dark}"
+  button-ghost:
+    backgroundColor: "{colors.neutral-white}"
+    textColor: "{colors.neutral-ash}"
+    rounded: "{rounded.xl}"
+    padding: "16rpx 32rpx"
+  card-default:
+    backgroundColor: "{colors.neutral-white}"
+    rounded: "{rounded.lg}"
+  input-field:
+    backgroundColor: "{colors.neutral-white}"
+    textColor: "{colors.neutral-ink}"
+    rounded: "{rounded.sm}"
+  chip-tag:
+    backgroundColor: "rgba(255,255,255,0.9)"
+    textColor: "#555555"
+    rounded: "40rpx"
+---
+
+# Design System: AI零售柜
+
+## 1. Overview
+
+**Creative North Star: "The Yellow Mark"**
+
+AI零售柜 is a consumer mini-program where the yellow accent works like a brand marker — instantly recognizable, like 美团's yellow or 闲鱼's yellow. The surface is clean and white, the yellow appears with purpose: a scan button, a selected state, a price. Its presence signals action; its absence signals rest.
+
+The system is built on restraint, not because less is more, but because every element must justify itself against the physical context: a user standing in front of a cabinet under bright office lights, phone in one hand, products in the other. Speed reads as intelligence. Friction reads as cheapness.
+
+The aesthetic rejects the busy, promo-heavy energy of discount retail mini-programs. No banner carousels, no red flash-sale tags, no cluttered grids of identically-sized product cards. Instead: generous negative space, deliberate typography, one shade of yellow doing one job at a time.
+
+**Key Characteristics:**
+- Yellow as brand marker, not surface decoration
+- White-dominant backgrounds with warm tint
+- Precision over abundance — every pixel is intentional
+- Fast transitions that feel mechanical, not theatrical
+- Physical-digital coherence: the phone UI mirrors the cabinet interaction
+
+## 2. Colors
+
+The palette is built around a single committed yellow, supported by warm-tinted neutrals and four functional signal colors. This is a **Committed** strategy: yellow carries 30-40% of the visual weight (primary buttons, brand marks, active states), while the rest of the surface stays in neutral territory.
+
+### Primary
+- **Brand Yellow** (#FFC107): The only accent. Used on the scan button, primary action buttons, active states, selected indicators, and the brand mark. Never used as a background fill on text-heavy surfaces; its job is to signal, not to decorate.
+- **Yellow Light** (#FFE082): Transitions and subtle fills only. The gradient midpoint on the scan button.
+- **Yellow Dark** (#FFA000): Hover/press states on yellow buttons, link text.
+- **Yellow Background** (#FFF8E1): Lightest tint, used for highlight overlays and the top of page gradients.
+
+### Neutral
+- **Ink** (#2C2C2C): Primary text. Not pure black; slightly warm.
+- **Ash** (#8C8C8C): Secondary text, descriptions, meta information.
+- **Cloud** (#BDBDBD): Tertiary text, placeholder content, disabled states.
+- **White** (#FFFFFF): Card and surface backgrounds.
+- **Cream** (#FAFAFA): Page backgrounds, section dividers.
+- **Mist** (#F5F5F5): Depressed surfaces, skeleton loading states.
+- **Border** (#EEEEEE): Dividers, card borders, input strokes at rest.
+
+### Functional
+- **Green** (#4CAF50): Success, completed orders, payment confirmed.
+- **Orange** (#FF9800): Warnings, pending status, attention-needed states.
+- **Red** (#F44336): Errors, failed payments, expired states.
+- **Blue** (#2196F3): Informational highlights, expand-tip text, links.
+
+### Named Rules
+**The One Yellow Rule.** Brand Yellow appears on at most 30-40% of any given screen. It is the brand marker, not the brand blanket. When everything is yellow, nothing is.
+
+**The White Canvas Rule.** White (#FFFFFF) is the default card surface; Cream (#FAFAFA) is the default page background. The difference is subtle but structural — cards float on cream; content that belongs to the page sits directly on cream.
+
+## 3. Typography
+
+**Primary Font:** PingFang SC (with system fallback: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', system-ui, sans-serif)
+
+**Character:** A single sans-serif stack optimized for Chinese text on small screens. PingFang SC's generous x-height and clean geometry work at the small sizes required by mini-program interfaces. No display face needed — this is a utility, not a magazine.
+
+### Hierarchy
+- **Page Title** (700, 48rpx, 1.2): Page-level headers like "AI零售柜" brand mark or modal titles. Used sparingly, 1-2 per screen.
+- **Section Title** (600, 32-36rpx, 1.3): Card headers, section labels, status messages. The workhorse heading.
+- **Body** (400-500, 28rpx, 1.6): Product names, prices, menu labels, form content. The default size for most text.
+- **Caption** (400, 22-24rpx, 1.5): Secondary metadata, timestamps, helper text, version info.
+- **Button** (600, 28-36rpx, 1.0): Action labels in buttons. Letter-spacing of 2-8rpx on primary CTAs for presence.
+
+### Named Rules
+**The Scale Jump Rule.** Adjacent text sizes differ by at least 1.25x. No 28rpx next to 30rpx; the scale skips intentionally: 22 → 24 → 28 → 32 → 36 → 48 → 56.
+
+## 4. Elevation
+
+This system uses intentional, restrained shadows. Surfaces are flat at rest; elevation is a response to state — a button lifts on hover, a card lifts when interactive. The shadow vocabulary is minimal: four levels from ambient to prominent.
+
+Shadows are never decorative. They exist to communicate: "this is tappable," "this is above," "this is important right now."
+
+### Shadow Vocabulary
+- **Ambient** (`0 2rpx 8rpx rgba(0,0,0,0.04)`): Subtle grounding for cards at rest. Barely visible; more felt than seen.
+- **Low** (`0 4rpx 16rpx rgba(0,0,0,0.06)`): Default card elevation. Separates a card from the cream background.
+- **Mid** (`0 8rpx 32rpx rgba(0,0,0,0.08)`): Elevated surfaces like the login card or active modal.
+- **Brand Glow** (`0 8rpx 24rpx rgba(255,193,7,0.25)`): The scan button's halo. The only colored shadow; reserved for the primary CTA.
+
+### Named Rules
+**The Shadow Has a Job Rule.** Never use a shadow without answering: what does this elevation communicate? If the answer is "looks nice," remove it.
+
+**The Flat-By-Default Rule.** Menu items, list rows, and non-interactive surfaces carry no shadow. Shadow = affordance, not decoration.
+
+## 5. Components
+
+### Buttons
+- **Shape:** Fully rounded pill (54rpx radius) for primary actions; 32rpx for secondary.
+- **Primary:** Brand Yellow fill (#FFC107), near-black text (#1A1A1A), Brand Glow shadow. Used for the scan button, "开门购物," and the payment confirmation button. Height: 108rpx for the scan/home buttons, 88rpx for inline actions.
+- **Hover:** Darkens to Yellow Dark (#FFA000), lifts 4rpx with expanded shadow. 300ms ease-out.
+- **Ghost:** White fill, 2rpx Border (#EEEEEE) stroke, Ash text. Used for "返回" and secondary actions alongside primary buttons. Active state: background shifts to Cream.
+
+### Cards
+- **Corner Style:** 24rpx radius (`$radius-lg`). Consistent across all card types.
+- **Background:** White (#FFFFFF) with Ambient or Low shadow depending on context.
+- **Internal Padding:** 32rpx (`$spacing-lg`) as default; varies to 24rpx for compact info cards.
+- **Border:** Cards use shadow for separation, not borders. The only bordered surface is the login card (1rpx solid rgba yellow tint).
+
+### Inputs & Fields
+- **Style:** White fill, 2rpx Border stroke at rest, 2rpx Brand Yellow or Blue on focus.
+- **Focus:** Border-color shift to #1890FF or Brand Yellow. No glow; the border transition is enough.
+- **Error:** Border shifts to Error Red. Error text in Red below the field.
+
+### Navigation
+- **Tab bar:** Text labels with icon above. Active tab in Brand Yellow, inactive in Cloud (#BDBDBD).
+- **Door tabs (left/right):** Horizonally split, active tab underlined with 4rpx Brand Yellow bar.
+- **Page header:** Centered title in Body weight, optional back arrow on left.
+
+### Chips & Tags
+- **Style:** White background (90% opacity), 1rpx border with yellow tint, 40rpx rounded pill.
+- **Usage:** Feature tags on login page (秒级开门, 安全支付, AI识别).
+- **Badge:** Error Red fill, white text, 32rpx height, 16rpx radius. Used for coupon counts and notification dots.
+
+### Status Indicators
+- **Success:** Green circle with white checkmark, 56rpx diameter.
+- **Pending:** Orange circle with clock or spinner.
+- **Error:** Red circle with cross mark.
+- **Each has a 4rpx 12rpx colored shadow matching its fill.**
+
+## 6. Do's and Don'ts
+
+### Do:
+- **Do** use Brand Yellow (#FFC107) exclusively for interactive elements: buttons, selected states, brand marks, and active indicators.
+- **Do** use White (#FFFFFF) for cards and Cream (#FAFAFA) for page backgrounds. The distinction creates structure without borders.
+- **Do** maintain at least 32rpx of breathing room around primary CTAs. The scan button needs space to own the center of the screen.
+- **Do** use the Shadow vocabulary intentionally: Ambient for at-rest cards, Brand Glow only for the primary CTA.
+- **Do** cap body text at 65-75 characters per line on the widest screen.
+
+### Don't:
+- **Don't** use Brand Yellow as a background fill on text-heavy surfaces. Yellow's job is signaling action, not hosting reading.
+- **Don't** introduce a second accent color. Green, orange, red, and blue are functional signals; they are not decorative accents.
+- **Don't** use nested cards. A card inside a card is always wrong.
+- **Don't** use side-stripe borders (border-left/border-right > 1px) as colored accents. Use full borders, background tints, or nothing.
+- **Don't** use gradient text (background-clip: text). Solid colors only.
+- **Don't** use glassmorphism, decorative blurs, or translucent overlays as a default aesthetic.
+- **Don't** use em dashes in copy. Commas, colons, and periods only.
+- **Don't** use the hero-metric template (big number + small label + supporting stats + gradient accent).
+- **Don't** let the interface feel like a discount retail mini-program: no red flash-sale badges, no banner carousels, no cluttered grids of same-sized product cards.

+ 47 - 0
PRODUCT.md

@@ -0,0 +1,47 @@
+# Product
+
+## Register
+
+hybrid
+
+Product-first with brand expression. The design serves the grab-and-go shopping workflow, but the surface must feel like a premium retail space, not a utility tool.
+
+## Users
+
+Office workers in business districts who grab snacks and drinks during breaks or overtime. They interact with a physical AI vending cabinet in a hallway or lobby, scan a QR code, and shop via the mini-program. Context: standing in front of the cabinet, often in a hurry, with ambient indoor lighting. They want speed and confidence — no friction, no surprises.
+
+## Product Purpose
+
+AI Smart Retail Cabinet mini-program. Scan a QR code, the cabinet door opens, take products, close the door — AI vision recognizes what was taken and WeChat Pay Score auto-charges. The feeling is "grab and go" — seamless, trustable, effortless.
+
+## Brand Personality
+
+**简洁、克制、质感** — Simple, restrained, textured.
+
+Like a high-end retail space: white walls, warm gold accents, precise typography, intentional negative space. Not loud, not busy, not cheap. Quality whispers.
+
+## Anti-references
+
+- Do NOT look like a low-end food-delivery or discount retail mini-program (no red-banner promos, no cluttered grids of cheap product cards, no 9.9-yuan flash-sale energy)
+- Do NOT look like a traditional enterprise SaaS dashboard (no table-heavy layouts, no blue-and-white default, no form-cramped screens)
+- No stock-photo aesthetic, no generic illustration placeholders
+
+## Design Principles
+
+1. **Clarity over decoration** — Every element earns its place. The user is standing in front of a cabinet, likely in a hurry. Remove, don't add.
+
+2. **Restraint builds trust** — Subtle motion, deliberate spacing, one accent doing one job. Loudness reads as cheapness; quiet reads as quality.
+
+3. **Texture through precision** — Richness comes from typography, spacing rhythm, and micro-interaction, not from adding color or decoration.
+
+4. **Speed as a feature** — The interface must feel fast. No heavy animations that delay interaction. The door opens; the recognition happens; the payment completes. Each transition should feel instant.
+
+5. **Physical-digital coherence** — The mini-program is an extension of the physical cabinet experience. The gold accent on screen matches the cabinet branding. The interaction model mirrors the physical act of opening and closing a door.
+
+## Accessibility & Inclusion
+
+- Target: WCAG AA compliance
+- Touch targets ≥ 44×44px for all interactive elements in the shopping flow
+- Color contrast ≥ 4.5:1 for text on backgrounds
+- Support reduced-motion preferences where practical
+- Consider office corridor lighting conditions (bright overhead fluorescent) — avoid low-contrast text that washes out in glare

+ 10 - 10
haha-mp/src/pages/agreement/privacyPolicy.vue

@@ -269,7 +269,7 @@ onLoad(() => {
 <style lang="scss" scoped>
 .container {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFF9E6 0%, #FFFFFF 30%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-primary 30%);
 }
 
 .content-scroll {
@@ -290,7 +290,7 @@ onLoad(() => {
     display: block;
     font-size: 44rpx;
     font-weight: 700;
-    color: #1A1A1A;
+    color: $color-text-primary;
     margin-bottom: 20rpx;
     letter-spacing: 2rpx;
   }
@@ -298,7 +298,7 @@ onLoad(() => {
   .update-time {
     display: block;
     font-size: 24rpx;
-    color: #999999;
+    color: $color-text-secondary;
     margin-top: 8rpx;
   }
 }
@@ -310,7 +310,7 @@ onLoad(() => {
   padding: 24rpx;
   background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%);
   border-radius: 16rpx;
-  border-left: 6rpx solid #2196F3;
+  border-top: 3rpx solid $color-info;
   margin-bottom: 48rpx;
 
   .notice-icon {
@@ -334,24 +334,24 @@ onLoad(() => {
     display: block;
     font-size: 32rpx;
     font-weight: 700;
-    color: #1A1A1A;
+    color: $color-text-primary;
     margin-bottom: 24rpx;
-    padding-left: 16rpx;
-    border-left: 6rpx solid #FFC107;
-    line-height: 1.4;
+    padding-left: 0;
+    border-bottom: 3rpx solid $color-primary;
+    padding-bottom: 8rpx;
   }
 
   .section-content {
     display: block;
     font-size: 28rpx;
-    color: #555555;
+    color: $color-text-primary;
     line-height: 1.8;
     margin-bottom: 16rpx;
     text-align: justify;
 
     &.sub-item {
       padding-left: 32rpx;
-      color: #666666;
+      color: $color-text-secondary;
       font-size: 26rpx;
     }
   }

+ 10 - 10
haha-mp/src/pages/agreement/serviceAgreement.vue

@@ -208,7 +208,7 @@ onLoad(() => {
 <style lang="scss" scoped>
 .container {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFF9E6 0%, #FFFFFF 30%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-primary 30%);
 }
 
 .content-scroll {
@@ -229,7 +229,7 @@ onLoad(() => {
     display: block;
     font-size: 44rpx;
     font-weight: 700;
-    color: #1A1A1A;
+    color: $color-text-primary;
     margin-bottom: 20rpx;
     letter-spacing: 2rpx;
   }
@@ -237,7 +237,7 @@ onLoad(() => {
   .update-time {
     display: block;
     font-size: 24rpx;
-    color: #999999;
+    color: $color-text-secondary;
     margin-top: 8rpx;
   }
 }
@@ -249,7 +249,7 @@ onLoad(() => {
   padding: 24rpx;
   background: linear-gradient(135deg, #FFF3CD 0%, #FFE69C 100%);
   border-radius: 16rpx;
-  border-left: 6rpx solid #FFC107;
+  border-top: 3rpx solid $color-primary;
   margin-bottom: 48rpx;
 
   .notice-icon {
@@ -273,24 +273,24 @@ onLoad(() => {
     display: block;
     font-size: 32rpx;
     font-weight: 700;
-    color: #1A1A1A;
+    color: $color-text-primary;
     margin-bottom: 24rpx;
-    padding-left: 16rpx;
-    border-left: 6rpx solid #FFC107;
-    line-height: 1.4;
+    padding-left: 0;
+    border-bottom: 3rpx solid $color-primary;
+    padding-bottom: 8rpx;
   }
 
   .section-content {
     display: block;
     font-size: 28rpx;
-    color: #555555;
+    color: $color-text-primary;
     line-height: 1.8;
     margin-bottom: 16rpx;
     text-align: justify;
 
     &.sub-item {
       padding-left: 32rpx;
-      color: #666666;
+      color: $color-text-secondary;
       font-size: 26rpx;
     }
   }

+ 20 - 26
haha-mp/src/pages/announcement/announcement.vue

@@ -35,53 +35,47 @@
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
 import { getAnnouncementList, type Announcement } from '../../api/announcement';
+import { logger } from '../../utils/logger';
 
 const loading = ref(true);
 const announcementList = ref<Announcement[]>([]);
 
-// 格式化时间
 const formatTime = (time: string | null): string => {
   if (!time) return '';
   const date = new Date(time);
   const now = new Date();
   const diff = now.getTime() - date.getTime();
-  
-  // 一小时内
+
   if (diff < 3600000) {
     const minutes = Math.floor(diff / 60000);
     return minutes <= 1 ? '刚刚' : `${minutes}分钟前`;
   }
-  
-  // 今天
+
   if (date.toDateString() === now.toDateString()) {
     return `今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
   }
-  
-  // 昨天
+
   const yesterday = new Date(now);
   yesterday.setDate(yesterday.getDate() - 1);
   if (date.toDateString() === yesterday.toDateString()) {
     return `昨天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
   }
-  
-  // 其他
+
   return `${date.getMonth() + 1}月${date.getDate()}日`;
 };
 
-// 获取公告列表
 const fetchList = async () => {
   loading.value = true;
   try {
     const list = await getAnnouncementList(20);
     announcementList.value = list;
   } catch (error) {
-    console.error('获取公告列表失败:', error);
+    logger.error('获取公告列表失败:', error);
   } finally {
     loading.value = false;
   }
 };
 
-// 跳转详情
 const goDetail = (item: Announcement) => {
   uni.navigateTo({
     url: `/pages/announcementDetail/announcementDetail?id=${item.id}`
@@ -96,7 +90,7 @@ onMounted(() => {
 <style lang="scss" scoped>
 .announcement-list {
   min-height: 100vh;
-  background-color: #f5f5f5;
+  background-color: $color-bg-secondary;
   padding: 20rpx;
 }
 
@@ -106,17 +100,17 @@ onMounted(() => {
   justify-content: center;
   align-items: center;
   height: 400rpx;
-  color: #999;
+  color: $color-text-secondary;
   font-size: 28rpx;
 }
 
 .list-wrap {
   .announcement-item {
-    background-color: #fff;
-    border-radius: 16rpx;
+    background-color: $color-bg-primary;
+    border-radius: $radius-md;
     padding: 24rpx;
     margin-bottom: 20rpx;
-    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+    box-shadow: $shadow-sm;
 
     .item-header {
       display: flex;
@@ -131,22 +125,22 @@ onMounted(() => {
 
         &.type-1 {
           background-color: #e6f4ff;
-          color: #1890ff;
+          color: $color-info;
         }
 
         &.type-2 {
           background-color: #f6ffed;
-          color: #52c41a;
+          color: $color-success;
         }
 
         &.type-3 {
           background-color: #fffbe6;
-          color: #faad14;
+          color: $color-warning;
         }
 
         &.type-4 {
-          background-color: #f5f5f5;
-          color: #999;
+          background-color: $color-bg-tertiary;
+          color: $color-text-secondary;
         }
       }
 
@@ -155,14 +149,14 @@ onMounted(() => {
         padding: 4rpx 12rpx;
         border-radius: 8rpx;
         background-color: #fff1f0;
-        color: #ff4d4f;
+        color: $color-error;
       }
     }
 
     .item-title {
       font-size: 32rpx;
       font-weight: 500;
-      color: #333;
+      color: $color-text-primary;
       line-height: 1.4;
       margin-bottom: 16rpx;
       display: -webkit-box;
@@ -178,12 +172,12 @@ onMounted(() => {
 
       .publish-time {
         font-size: 24rpx;
-        color: #999;
+        color: $color-text-secondary;
       }
 
       .read-count {
         font-size: 24rpx;
-        color: #999;
+        color: $color-text-secondary;
       }
     }
   }

+ 13 - 15
haha-mp/src/pages/announcementDetail/announcementDetail.vue

@@ -38,15 +38,15 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue';
+import { ref } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import { getAnnouncementDetail, type Announcement } from '../../api/announcement';
+import { logger } from '../../utils/logger';
 
 const loading = ref(true);
 const announcement = ref<Announcement | null>(null);
 let announcementId = 0;
 
-// 格式化时间
 const formatTime = (time: string | null): string => {
   if (!time) return '';
   const date = new Date(time);
@@ -58,7 +58,6 @@ const formatTime = (time: string | null): string => {
   return `${year}-${month}-${day} ${hours}:${minutes}`;
 };
 
-// 获取公告详情
 const fetchDetail = async () => {
   if (!announcementId) return;
 
@@ -67,12 +66,11 @@ const fetchDetail = async () => {
     const detail = await getAnnouncementDetail(announcementId);
     announcement.value = detail;
 
-    // 设置页面标题
     uni.setNavigationBarTitle({
       title: detail.title || '公告详情'
     });
   } catch (error) {
-    console.error('获取公告详情失败:', error);
+    logger.error('获取公告详情失败:', error);
   } finally {
     loading.value = false;
   }
@@ -89,7 +87,7 @@ onLoad((options?: any) => {
 <style lang="scss" scoped>
 .announcement-detail {
   min-height: 100vh;
-  background-color: #fff;
+  background-color: $color-bg-primary;
 }
 
 .loading-wrap,
@@ -98,7 +96,7 @@ onLoad((options?: any) => {
   justify-content: center;
   align-items: center;
   height: 400rpx;
-  color: #999;
+  color: $color-text-secondary;
   font-size: 28rpx;
 }
 
@@ -108,7 +106,7 @@ onLoad((options?: any) => {
   .detail-title {
     font-size: 40rpx;
     font-weight: 600;
-    color: #333;
+    color: $color-text-primary;
     line-height: 1.4;
     margin-bottom: 24rpx;
   }
@@ -127,28 +125,28 @@ onLoad((options?: any) => {
 
       &.type-1 {
         background-color: #e6f4ff;
-        color: #1890ff;
+        color: $color-info;
       }
 
       &.type-2 {
         background-color: #f6ffed;
-        color: #52c41a;
+        color: $color-success;
       }
 
       &.type-3 {
         background-color: #fffbe6;
-        color: #faad14;
+        color: $color-warning;
       }
 
       &.type-4 {
-        background-color: #f5f5f5;
-        color: #999;
+        background-color: $color-bg-tertiary;
+        color: $color-text-secondary;
       }
     }
 
     .meta-item {
       font-size: 24rpx;
-      color: #999;
+      color: $color-text-secondary;
       margin-right: 16rpx;
     }
   }
@@ -161,7 +159,7 @@ onLoad((options?: any) => {
 
   .detail-content {
     font-size: 30rpx;
-    color: #333;
+    color: $color-text-primary;
     line-height: 1.8;
     white-space: pre-wrap;
   }

+ 71 - 86
haha-mp/src/pages/couponCenter/couponCenter.vue

@@ -47,7 +47,7 @@
               <text class="ticket-scope">{{ getScopeLabel(item.applyScope) }}</text>
             </view>
             <text v-if="item.couponDesc" class="ticket-desc">{{ item.couponDesc }}</text>
-            
+
             <!-- 库存和领取信息 -->
             <view class="ticket-stock-row">
               <text class="ticket-stock">
@@ -57,7 +57,7 @@
                 每人限领{{ item.receiveLimit }}张
               </text>
             </view>
-            
+
             <!-- 有效期 -->
             <text class="ticket-period">
               <text v-if="item.validType === 1">{{ formatTime(item.validStartTime) }} ~ {{ formatTime(item.validEndTime) }}</text>
@@ -115,8 +115,8 @@ import { getAvailableCoupons, receiveCoupon } from '../../api/coupon';
 import type { CouponTemplateInfo } from '../../api/coupon';
 import { checkAuth } from '../../utils/auth';
 import { getCouponTypeLabel, getScopeLabel, formatTime } from '../../utils/coupon';
+import { logger } from '../../utils/logger';
 
-// 扩展接口,添加前端需要的字段
 interface CouponTemplateInfoExtended extends CouponTemplateInfo {
   alreadyReceived?: boolean;
 }
@@ -126,9 +126,6 @@ const loading = ref(false);
 const refreshing = ref(false);
 const receiving = reactive<Record<string, boolean>>({});
 
-/**
- * 格式化优惠值
- */
 const formatDiscountValue = (item: CouponTemplateInfoExtended): string => {
   if (item.couponType === 2) {
     return item.discountValue ? `${item.discountValue}折` : '0';
@@ -136,16 +133,10 @@ const formatDiscountValue = (item: CouponTemplateInfoExtended): string => {
   return item.discountValue ? `${item.discountValue}` : '0';
 };
 
-/**
- * 判断是否可领取
- */
 const canReceive = (item: CouponTemplateInfoExtended): boolean => {
   return item.remainCount > 0 && item.status === 1 && !item.alreadyReceived;
 };
 
-/**
- * 领取优惠券
- */
 const handleReceive = async (item: CouponTemplateInfoExtended) => {
   if (!canReceive(item) || receiving[item.id]) return;
 
@@ -156,20 +147,15 @@ const handleReceive = async (item: CouponTemplateInfoExtended) => {
       title: '领取成功',
       icon: 'success'
     });
-    // 更新剩余数量
     item.remainCount = Math.max(0, item.remainCount - 1);
     item.alreadyReceived = true;
   } catch (error: any) {
-    console.error('领取优惠券失败:', error);
-    // 错误信息已在 request 工具中展示
+    logger.error('领取优惠券失败:', error);
   } finally {
     receiving[item.id] = false;
   }
 };
 
-/**
- * 下拉刷新
- */
 const onRefresh = async () => {
   refreshing.value = true;
   try {
@@ -179,27 +165,21 @@ const onRefresh = async () => {
   }
 };
 
-/**
- * 跳转到我的优惠券
- */
 const goToMyCoupons = () => {
   uni.navigateTo({
     url: '/pages/coupons/coupons'
   });
 };
 
-/**
- * 加载可领取优惠券列表
- */
 const loadTemplates = async () => {
   try {
     const result = await getAvailableCoupons();
     templates.value = result.map(item => ({
       ...item,
-      alreadyReceived: false // 初始化为未领取
+      alreadyReceived: false
     }));
   } catch (error) {
-    console.error('加载可领取优惠券失败:', error);
+    logger.error('加载可领取优惠券失败:', error);
   }
 };
 
@@ -211,10 +191,10 @@ onMounted(() => {
 });
 </script>
 
-<style>
+<style lang="scss">
 .page {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFF1F0 0%, #F5F5F5 30%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-secondary 100%);
 }
 
 .coupon-scroll {
@@ -228,14 +208,14 @@ onMounted(() => {
 
 .header-title {
   font-size: 36rpx;
-  font-weight: 800;
-  color: #1A1A1A;
+  font-weight: 700;
+  color: $color-text-primary;
   display: block;
 }
 
 .header-desc {
   font-size: 24rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
   margin-top: 4rpx;
   display: block;
 }
@@ -251,8 +231,8 @@ onMounted(() => {
   margin-bottom: 24rpx;
   border-radius: 20rpx;
   overflow: hidden;
-  background: #ffffff;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05), 0 1rpx 4rpx rgba(0, 0, 0, 0.03);
+  background: $color-bg-primary;
+  box-shadow: $shadow-md;
 }
 
 /* --- Left: Amount Face --- */
@@ -265,30 +245,30 @@ onMounted(() => {
   padding: 36rpx 12rpx;
   flex-shrink: 0;
   position: relative;
-}
 
-.ticket-face::after {
-  content: '';
-  position: absolute;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  width: 1rpx;
-  background: rgba(255, 255, 255, 0.25);
+  &::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    width: 1rpx;
+    background: rgba(255, 255, 255, 0.2);
+  }
 }
 
-/* 类型色系 */
+/* 类型色系 — 功能性颜色区分券种 */
 .ticket-face--type1 {
-  background: linear-gradient(150deg, #FF8A00 0%, #FFA940 100%);
+  background: linear-gradient(150deg, #FF8A00, #FFA940);
 }
 .ticket-face--type2 {
-  background: linear-gradient(150deg, #E8533E 0%, #FF7B6B 100%);
+  background: linear-gradient(150deg, $color-error, #FF7B6B);
 }
 .ticket-face--type3 {
-  background: linear-gradient(150deg, #7B61FF 0%, #A78BFA 100%);
+  background: linear-gradient(150deg, #7B61FF, #A78BFA);
 }
 .ticket-face--type4 {
-  background: linear-gradient(150deg, #0EA5E9 0%, #38BDF8 100%);
+  background: linear-gradient(150deg, $color-info, #38BDF8);
 }
 
 .ticket-amount {
@@ -333,7 +313,7 @@ onMounted(() => {
 .serration-gap {
   width: 36rpx;
   height: 18rpx;
-  background: #ffffff;
+  background: $color-bg-primary;
   position: absolute;
   z-index: 2;
 }
@@ -350,7 +330,7 @@ onMounted(() => {
 
 .serration-rail {
   flex: 1;
-  border-left: 2rpx dashed #E0E0E0;
+  border-left: 2rpx dashed $color-border;
   margin: 18rpx 0;
 }
 
@@ -367,7 +347,7 @@ onMounted(() => {
 
 .ticket-title {
   font-size: 28rpx;
-  color: #1A1A1A;
+  color: $color-text-primary;
   font-weight: 700;
   margin-bottom: 12rpx;
   line-height: 1.4;
@@ -396,12 +376,12 @@ onMounted(() => {
 
 .ticket-scope {
   font-size: 20rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .ticket-desc {
   font-size: 22rpx;
-  color: #999999;
+  color: $color-text-secondary;
   margin-bottom: 10rpx;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -418,22 +398,22 @@ onMounted(() => {
 
 .ticket-stock {
   font-size: 20rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .stock-num {
-  color: #E8533E;
+  color: $color-error;
   font-weight: 600;
 }
 
 .ticket-limit {
   font-size: 20rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
 }
 
 .ticket-period {
   font-size: 22rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
 }
 
 /* --- Grab Button --- */
@@ -442,30 +422,30 @@ onMounted(() => {
   top: 28rpx;
   right: 28rpx;
   padding: 10rpx 28rpx;
-  background: #1A1A1A;
+  background: $color-primary;
   border-radius: 28rpx;
 }
 
 .ticket-grab-text {
   font-size: 24rpx;
-  color: #FFD700;
-  font-weight: 700;
+  color: #1A1A1A;
+  font-weight: 600;
 }
 
 .ticket-grab--out {
-  background: #F0F0F0;
-}
+  background: $color-bg-tertiary;
 
-.ticket-grab--out .ticket-grab-text {
-  color: #CCCCCC;
+  .ticket-grab-text {
+    color: $color-text-tertiary;
+  }
 }
 
 .ticket-grab--received {
-  background: #F0F0F0;
-}
+  background: $color-bg-tertiary;
 
-.ticket-grab--received .ticket-grab-text {
-  color: #999999;
+  .ticket-grab-text {
+    color: $color-text-secondary;
+  }
 }
 
 /* ========== Empty State ========== */
@@ -489,7 +469,7 @@ onMounted(() => {
   left: 4rpx;
   right: 4rpx;
   bottom: -8rpx;
-  background: #E0E0E0;
+  background: $color-border;
   border-radius: 16rpx;
 }
 
@@ -497,7 +477,7 @@ onMounted(() => {
   display: flex;
   width: 320rpx;
   height: 140rpx;
-  background: #F5F5F5;
+  background: $color-bg-tertiary;
   border-radius: 16rpx;
   overflow: hidden;
   position: relative;
@@ -509,13 +489,14 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: center;
-}
 
-.empty-ticket-left::before {
-  content: '¥';
-  font-size: 44rpx;
-  font-weight: 800;
-  color: #DCDCDC;
+  &::before {
+    content: '¥';
+    font-size: 44rpx;
+    font-weight: 800;
+    color: $color-text-tertiary;
+    opacity: 0.4;
+  }
 }
 
 .empty-ticket-dots {
@@ -531,8 +512,8 @@ onMounted(() => {
   width: 14rpx;
   height: 14rpx;
   border-radius: 50%;
-  background: #F5F5F5;
-  border: 2rpx solid #E0E0E0;
+  background: $color-bg-tertiary;
+  border: 2rpx solid $color-border;
 }
 
 .empty-ticket-right {
@@ -559,28 +540,32 @@ onMounted(() => {
 }
 
 .empty-title {
-  font-size: 30rpx;
-  color: #999999;
+  font-size: 28rpx;
+  color: $color-text-secondary;
   font-weight: 600;
   margin-bottom: 12rpx;
 }
 
 .empty-desc {
   font-size: 24rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
   margin-bottom: 36rpx;
 }
 
 .empty-cta {
   padding: 18rpx 64rpx;
-  background: #1A1A1A;
+  background: $color-primary;
   border-radius: 44rpx;
+
+  &:active {
+    opacity: 0.85;
+  }
 }
 
 .empty-cta-text {
   font-size: 28rpx;
-  color: #FFD700;
-  font-weight: 700;
+  color: #1A1A1A;
+  font-weight: 600;
 }
 
 /* ========== Loading ========== */
@@ -596,8 +581,8 @@ onMounted(() => {
 .loading-spinner {
   width: 48rpx;
   height: 48rpx;
-  border: 4rpx solid #E0E0E0;
-  border-top-color: #FFD700;
+  border: 4rpx solid $color-border;
+  border-top-color: $color-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
 }
@@ -608,6 +593,6 @@ onMounted(() => {
 
 .loading-label {
   font-size: 24rpx;
-  color: #CCCCCC;
+  color: $color-text-tertiary;
 }
 </style>

+ 71 - 101
haha-mp/src/pages/coupons/coupons.vue

@@ -103,6 +103,7 @@ import { getMyCoupons } from '../../api/coupon';
 import type { UserCouponInfo, PageResult } from '../../api/coupon';
 import { checkAuth } from '../../utils/auth';
 import { getCouponTypeLabel, getScopeLabel, formatTime } from '../../utils/coupon';
+import { logger } from '../../utils/logger';
 
 const tabs = [
   { label: '未使用', value: 0 },
@@ -129,9 +130,6 @@ const emptyText = computed(() => {
   return map[currentTab.value] || '暂无优惠券';
 });
 
-/**
- * 切换Tab
- */
 const switchTab = (tab: number) => {
   if (currentTab.value === tab) return;
   currentTab.value = tab;
@@ -140,9 +138,6 @@ const switchTab = (tab: number) => {
   loadCoupons();
 };
 
-/**
- * 加载优惠券列表
- */
 const loadCoupons = async (isLoadMore = false) => {
   if (isLoadMore) {
     if (loadingMore.value) return;
@@ -161,24 +156,18 @@ const loadCoupons = async (isLoadMore = false) => {
     }
     total.value = result.total;
   } catch (error) {
-    console.error('加载优惠券列表失败:', error);
+    logger.error('加载优惠券列表失败:', error);
   } finally {
     loading.value = false;
     loadingMore.value = false;
   }
 };
 
-/**
- * 加载更多
- */
 const loadMore = () => {
   currentPage.value++;
   loadCoupons(true);
 };
 
-/**
- * 格式化优惠面值(纯数字部分)
- */
 const formatValue = (item: UserCouponInfo): string => {
   if (item.couponType === 2) {
     const val = item.discountValue;
@@ -188,9 +177,6 @@ const formatValue = (item: UserCouponInfo): string => {
   return val ? `${val}` : '--';
 };
 
-/**
- * 跳转领券中心
- */
 const goToCouponCenter = () => {
   uni.navigateTo({
     url: '/pages/couponCenter/couponCenter'
@@ -208,7 +194,7 @@ onMounted(() => {
 <style scoped lang="scss">
 .page {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFF8E1 0%, #F5F5F5 30%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-secondary 100%);
 }
 
 /* ========== Tab Bar ========== */
@@ -220,35 +206,32 @@ onMounted(() => {
   position: sticky;
   top: 0;
   z-index: 10;
-  animation: slideDown 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .tab-pill {
   padding: 12rpx 40rpx;
   border-radius: 32rpx;
   background: rgba(0, 0, 0, 0.04);
-  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
-  
+
   &:active {
     transform: scale(0.95);
   }
 }
 
 .tab-pill--active {
-  background: #1A1A1A;
-  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+  background: $color-primary;
+  box-shadow: $shadow-sm;
 }
 
 .tab-pill-text {
   font-size: 26rpx;
-  color: #999999;
+  color: $color-text-secondary;
   font-weight: 500;
-  transition: color 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .tab-pill--active .tab-pill-text {
-  color: #FFD700;
-  font-weight: 700;
+  color: #1A1A1A;
+  font-weight: 600;
 }
 
 /* ========== Coupon List ========== */
@@ -262,21 +245,27 @@ onMounted(() => {
   margin-bottom: 24rpx;
   border-radius: 20rpx;
   overflow: hidden;
-  background: #ffffff;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05), 0 1rpx 4rpx rgba(0, 0, 0, 0.03);
+  background: $color-bg-primary;
+  box-shadow: $shadow-md;
   position: relative;
-  animation: slideUp 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) both;
-  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), box-shadow 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
-  
+
   &:active {
     transform: scale(0.98);
-    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
+    box-shadow: $shadow-sm;
   }
 }
 
 .ticket--dim {
   opacity: 0.5;
-  filter: saturate(0.3);
+
+  .ticket-face {
+    background: linear-gradient(150deg, #B0B0B0, #C8C8C8);
+  }
+
+  .ticket-badge {
+    color: $color-text-secondary;
+    background: $color-bg-tertiary;
+  }
 }
 
 /* --- Left: Amount Face --- */
@@ -289,40 +278,34 @@ onMounted(() => {
   padding: 36rpx 12rpx;
   flex-shrink: 0;
   position: relative;
-}
 
-.ticket-face::after {
-  content: '';
-  position: absolute;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  width: 1rpx;
-  background: rgba(255, 255, 255, 0.25);
+  &::after {
+    content: '';
+    position: absolute;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    width: 1rpx;
+    background: rgba(255, 255, 255, 0.2);
+  }
 }
 
-/* 类型色系 */
 .ticket-face--type1 {
-  background: linear-gradient(150deg, #FF8A00 0%, #FFA940 100%);
+  background: linear-gradient(150deg, #FF8A00, #FFA940);
 }
 .ticket-face--type2 {
-  background: linear-gradient(150deg, #E8533E 0%, #FF7B6B 100%);
+  background: linear-gradient(150deg, $color-error, #FF7B6B);
 }
 .ticket-face--type3 {
-  background: linear-gradient(150deg, #7B61FF 0%, #A78BFA 100%);
+  background: linear-gradient(150deg, #7B61FF, #A78BFA);
 }
 .ticket-face--type4 {
-  background: linear-gradient(150deg, #0EA5E9 0%, #38BDF8 100%);
-}
-
-.ticket--dim .ticket-face {
-  background: linear-gradient(150deg, #B0B0B0 0%, #C8C8C8 100%);
+  background: linear-gradient(150deg, $color-info, #38BDF8);
 }
 
 .ticket-amount {
   display: flex;
   align-items: baseline;
-  animation: scaleIn 0.3s ease 0.1s both;
 }
 
 .ticket-currency {
@@ -362,8 +345,7 @@ onMounted(() => {
 .serration-gap {
   width: 36rpx;
   height: 18rpx;
-  background: #ffffff;
-  border-radius: 0 0 50% 50%;
+  background: $color-bg-primary;
   position: absolute;
   z-index: 2;
 }
@@ -380,7 +362,7 @@ onMounted(() => {
 
 .serration-rail {
   flex: 1;
-  border-left: 2rpx dashed #E0E0E0;
+  border-left: 2rpx dashed $color-border;
   margin: 18rpx 0;
 }
 
@@ -397,7 +379,7 @@ onMounted(() => {
 
 .ticket-title {
   font-size: 28rpx;
-  color: #1A1A1A;
+  color: $color-text-primary;
   font-weight: 700;
   margin-bottom: 12rpx;
   line-height: 1.4;
@@ -424,19 +406,14 @@ onMounted(() => {
   flex-shrink: 0;
 }
 
-.ticket--dim .ticket-badge {
-  color: #999;
-  background: #F0F0F0;
-}
-
 .ticket-scope {
   font-size: 20rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .ticket-desc {
   font-size: 22rpx;
-  color: #999999;
+  color: $color-text-secondary;
   margin-bottom: 10rpx;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -445,7 +422,7 @@ onMounted(() => {
 
 .ticket-period {
   font-size: 22rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
 }
 
 /* --- Watermark --- */
@@ -453,7 +430,7 @@ onMounted(() => {
   position: absolute;
   top: 20rpx;
   right: 20rpx;
-  border: 3rpx solid #CCCCCC;
+  border: 3rpx solid $color-text-tertiary;
   border-radius: 8rpx;
   padding: 2rpx 14rpx;
   transform: rotate(-12deg);
@@ -462,7 +439,7 @@ onMounted(() => {
 
 .watermark-text {
   font-size: 22rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
   font-weight: 700;
 }
 
@@ -474,7 +451,7 @@ onMounted(() => {
 
 .load-more-text {
   font-size: 24rpx;
-  color: #CCCCCC;
+  color: $color-text-tertiary;
 }
 
 /* ========== Empty State ========== */
@@ -485,13 +462,11 @@ onMounted(() => {
   justify-content: center;
   min-height: 55vh;
   padding: 40rpx;
-  animation: fadeIn 0.3s ease;
 }
 
 .empty-visual {
   margin-bottom: 40rpx;
   position: relative;
-  animation: bounce 0.3s ease 0.1s both;
 }
 
 .empty-ticket-shadow {
@@ -500,7 +475,7 @@ onMounted(() => {
   left: 4rpx;
   right: 4rpx;
   bottom: -8rpx;
-  background: #E0E0E0;
+  background: $color-border;
   border-radius: 16rpx;
 }
 
@@ -508,7 +483,7 @@ onMounted(() => {
   display: flex;
   width: 320rpx;
   height: 140rpx;
-  background: #F5F5F5;
+  background: $color-bg-tertiary;
   border-radius: 16rpx;
   overflow: hidden;
   position: relative;
@@ -520,13 +495,14 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: center;
-}
 
-.empty-ticket-left::before {
-  content: '¥';
-  font-size: 44rpx;
-  font-weight: 800;
-  color: #DCDCDC;
+  &::before {
+    content: '¥';
+    font-size: 44rpx;
+    font-weight: 800;
+    color: $color-text-tertiary;
+    opacity: 0.4;
+  }
 }
 
 .empty-ticket-dots {
@@ -542,8 +518,8 @@ onMounted(() => {
   width: 14rpx;
   height: 14rpx;
   border-radius: 50%;
-  background: #F5F5F5;
-  border: 2rpx solid #E0E0E0;
+  background: $color-bg-tertiary;
+  border: 2rpx solid $color-border;
 }
 
 .empty-ticket-right {
@@ -570,29 +546,26 @@ onMounted(() => {
 }
 
 .empty-title {
-  font-size: 30rpx;
-  color: #999999;
+  font-size: 28rpx;
+  color: $color-text-secondary;
   font-weight: 600;
   margin-bottom: 36rpx;
-  animation: slideUp 0.3s ease 0.1s both;
 }
 
 .empty-cta {
   padding: 18rpx 64rpx;
-  background: #1A1A1A;
+  background: $color-primary;
   border-radius: 44rpx;
-  animation: slideUp 0.3s ease 0.15s both;
-  transition: transform 0.3s ease;
-  
+
   &:active {
-    transform: scale(0.95);
+    opacity: 0.85;
   }
 }
 
 .empty-cta-text {
   font-size: 28rpx;
-  color: #FFD700;
-  font-weight: 700;
+  color: #1A1A1A;
+  font-weight: 600;
 }
 
 /* ========== Loading ========== */
@@ -603,14 +576,13 @@ onMounted(() => {
   justify-content: center;
   min-height: 55vh;
   gap: 20rpx;
-  animation: fadeIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .loading-spinner {
   width: 48rpx;
   height: 48rpx;
-  border: 4rpx solid #E0E0E0;
-  border-top-color: #FFD700;
+  border: 4rpx solid $color-border;
+  border-top-color: $color-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
 }
@@ -621,7 +593,7 @@ onMounted(() => {
 
 .loading-label {
   font-size: 24rpx;
-  color: #CCCCCC;
+  color: $color-text-tertiary;
 }
 
 /* ========== Bottom CTA ========== */
@@ -629,25 +601,23 @@ onMounted(() => {
   padding: 20rpx 64rpx 56rpx;
   display: flex;
   justify-content: center;
-  animation: slideUp 0.3s ease 0.1s both;
 }
 
 .bottom-btn {
   padding: 22rpx 0;
   width: 100%;
-  background: #1A1A1A;
+  background: $color-primary;
   border-radius: 44rpx;
   text-align: center;
-  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
-  
+
   &:active {
-    transform: scale(0.98);
+    opacity: 0.85;
   }
 }
 
 .bottom-btn-label {
-  font-size: 30rpx;
-  color: #FFD700;
-  font-weight: 700;
+  font-size: 28rpx;
+  color: #1A1A1A;
+  font-weight: 600;
 }
 </style>

+ 6 - 6
haha-mp/src/pages/index/index.vue

@@ -116,7 +116,7 @@ const scanCode = async () => {
       return
     }
   } catch (error: any) {
-    console.error('检查支付分状态失败:', error)
+    logger.error('检查支付分状态失败:', error)
     // 如果检查失败,也跳转到开通页面
     uni.navigateTo({
       url: '/pages/payscore/enable'
@@ -159,7 +159,7 @@ const scanCode = async () => {
           throw new Error('无法解析设备ID');
         }
       } catch (error) {
-        console.error('解析设备ID失败:', error);
+        logger.error('解析设备ID失败:', error);
         uni.showToast({
           title: '二维码格式错误',
           icon: 'none'
@@ -227,7 +227,7 @@ const doOpenDoor = async (deviceId: string) => {
     }, 1000);
   } catch (error: any) {
     uni.hideLoading();
-    console.error('开门失败:', error);
+    logger.error('开门失败:', error);
   }
 };
 
@@ -364,7 +364,7 @@ const onContactSuccess = () => {
 .scan-text {
   font-size: 40rpx;
   font-weight: 600;
-  color: #1A1A1A;
+  color: $color-text-primary;
   letter-spacing: 4rpx;
 }
 
@@ -396,14 +396,14 @@ const onContactSuccess = () => {
   .action-icon {
     width: 120rpx;
     height: 120rpx;
-    background: #ffffff;
+    background: $color-bg-primary;
     border-radius: $radius-lg;
     display: flex;
     align-items: center;
     justify-content: center;
     box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
     margin-bottom: $spacing-sm;
-    border: 2rpx solid #F0F0F0;
+    border: 2rpx solid $color-border;
     flex-shrink: 0;
 
     image {

+ 183 - 374
haha-mp/src/pages/login/login.vue

@@ -1,14 +1,8 @@
 <template>
   <view class="container">
-    <!-- 动态背景层 -->
+    <!-- 背景层 -->
     <view class="bg-layer">
-      <view class="gradient-orb orb-1"></view>
-      <view class="gradient-orb orb-2"></view>
-      <view class="gradient-orb orb-3"></view>
-      <view class="wave-container">
-        <view class="wave wave-1"></view>
-        <view class="wave wave-2"></view>
-      </view>
+      <view class="gradient-orb"></view>
     </view>
 
     <!-- 主内容区 -->
@@ -16,35 +10,25 @@
       <!-- 品牌展示区 -->
       <view class="brand-showcase">
         <view class="logo-wrapper">
-          <!-- 外层装饰环 -->
-          <view class="logo-outer-ring">
-            <!-- 内层装饰环 -->
-            <view class="logo-inner-ring">
-              <!-- 光晕效果 -->
-              <view class="logo-glow"></view>
-              <!-- Logo主体 -->
-              <view class="logo-circle">
-                <image class="logo" src="/static/brand-logo.svg" mode="aspectFit"></image>
-              </view>
+          <view class="logo-ring">
+            <view class="logo-glow"></view>
+            <view class="logo-circle">
+              <image class="logo" src="/static/brand-logo.svg" mode="aspectFit"></image>
             </view>
           </view>
         </view>
-        
-        <view class="brand-text-group">
-          <text class="brand-subtitle">AI智能零售柜 · 即拿即走</text>
-        </view>
-        
+
+        <text class="brand-name">AI零售柜</text>
+        <text class="brand-desc">扫码开门 · 即拿即走</text>
+
         <view class="feature-tags">
           <view class="tag">
-            <text class="tag-icon">⚡</text>
             <text class="tag-text">秒级开门</text>
           </view>
           <view class="tag">
-            <text class="tag-icon">🔒</text>
             <text class="tag-text">安全支付</text>
           </view>
           <view class="tag">
-            <text class="tag-icon">🤖</text>
             <text class="tag-text">AI识别</text>
           </view>
         </view>
@@ -58,38 +42,32 @@
           <view class="header-line"></view>
         </view>
 
-        <!-- 微信手机号快速登录按钮 -->
         <button
-          class="wechat-login-btn"
+          class="login-btn"
           open-type="getPhoneNumber"
           @getphonenumber="onGetPhoneNumber"
           :loading="isLoading"
-          hover-class="btn-hover"
+          hover-class="login-btn-hover"
         >
-          <view class="btn-shine"></view>
-          <text class="btn-text">一键登录</text>
+          <text class="login-btn-text">一键登录</text>
         </button>
       </view>
 
       <!-- 用户协议 -->
-      <view class="agreement-section">
-        <view class="agreement-card">
-          <text class="agreement-prefix">登录即表示您已阅读并同意</text>
-          <view class="agreement-links">
-            <text class="link-item" @tap="goToServiceAgreement">《用户服务协议》</text>
-            <text class="link-sep">与</text>
-            <text class="link-item" @tap="goToPrivacyPolicy">《隐私政策》</text>
-          </view>
+      <view class="agreement">
+        <text class="agreement-text">登录即表示您已阅读并同意</text>
+        <view class="agreement-links">
+          <text class="link" @tap="goToServiceAgreement">《用户服务协议》</text>
+          <text class="link-sep">与</text>
+          <text class="link" @tap="goToPrivacyPolicy">《隐私政策》</text>
         </view>
       </view>
 
       <!-- 底部装饰 -->
-      <view class="footer-decoration">
-        <view class="dot-pattern">
-          <view class="dot"></view>
-          <view class="dot"></view>
-          <view class="dot"></view>
-        </view>
+      <view class="footer-dots">
+        <view class="dot"></view>
+        <view class="dot"></view>
+        <view class="dot"></view>
       </view>
     </view>
   </view>
@@ -105,10 +83,8 @@ import { logger } from '@/utils/logger';
 
 const isLoading = ref(false);
 
-// 登录后跳转的页面
 const redirectUrl = ref('');
 
-// 页面加载时获取redirect参数
 onLoad((options: any) => {
   if (options.redirect) {
     redirectUrl.value = decodeURIComponent(options.redirect);
@@ -117,21 +93,20 @@ onLoad((options: any) => {
 
 const onGetPhoneNumber = async (e: any) => {
   logger.log('[登录] 获取手机号事件:', e.detail);
-  
+
   if (e.detail.errMsg !== 'getPhoneNumber:ok') {
-    console.error('[登录] 获取手机号失败:', e.detail.errMsg);
+    logger.error('[登录] 获取手机号失败:', e.detail.errMsg);
     uni.showToast({
       title: '获取手机号失败,请重试',
       icon: 'none'
     });
     return;
   }
-  
+
   isLoading.value = true;
   uni.showLoading({ title: '登录中...' });
-  
+
   try {
-    // 获取登录code
     logger.log('[登录] 开始获取login code...');
     const loginRes = await new Promise<any>((resolve, reject) => {
       uni.login({
@@ -141,25 +116,25 @@ const onGetPhoneNumber = async (e: any) => {
           resolve(res);
         },
         fail: (err) => {
-          console.error('[登录] login失败:', err);
+          logger.error('[登录] login失败:', err);
           reject(err);
         }
       });
     });
-    
+
     logger.log('[登录] loginRes:', loginRes);
     logger.log('[登录] phoneCode:', e.detail.code);
-    
+
     const { code, encryptedData, iv } = e.detail;
-    const res = await loginByMiniappPhone({ 
+    const res = await loginByMiniappPhone({
       code: loginRes.code,
       phoneCode: code
     });
     logger.log('[登录] 登录成功:', res);
     handleLoginSuccess(res);
   } catch (error: any) {
-    console.error('[登录] 微信手机号登录失败', error);
-    console.error('[登录] 错误详情:', error.message || error);
+    logger.error('[登录] 微信手机号登录失败', error);
+    logger.error('[登录] 错误详情:', error.message || error);
     uni.showToast({
       title: error.message || '登录失败,请重试',
       icon: 'none',
@@ -183,7 +158,7 @@ const handleLoginSuccess = (res: LoginResult) => {
   logger.log('[登录成功] 保存后验证 - token:', savedToken ? '已保存' : '保存失败');
 
   uni.vibrateShort({ type: 'medium' });
-  
+
   uni.showToast({
     title: '登录成功',
     icon: 'success'
@@ -203,14 +178,12 @@ const handleLoginSuccess = (res: LoginResult) => {
   }, 1000);
 };
 
-// 跳转到用户服务协议
 const goToServiceAgreement = () => {
   uni.navigateTo({
     url: '/pages/agreement/serviceAgreement'
   });
 };
 
-// 跳转到隐私政策
 const goToPrivacyPolicy = () => {
   uni.navigateTo({
     url: '/pages/agreement/privacyPolicy'
@@ -221,12 +194,12 @@ const goToPrivacyPolicy = () => {
 <style lang="scss">
 .container {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFFBF0 0%, #FFF9E6 30%, #FFFFFF 70%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, #FFF9E6 30%, $color-bg-secondary 100%);
   position: relative;
   overflow: hidden;
 }
 
-/* ========== 动态背景层 ========== */
+/* ========== 背景层 ========== */
 .bg-layer {
   position: fixed;
   top: 0;
@@ -235,66 +208,17 @@ const goToPrivacyPolicy = () => {
   bottom: 0;
   z-index: 0;
   pointer-events: none;
-  contain: layout style paint;
 }
 
 .gradient-orb {
   position: absolute;
+  width: 500rpx;
+  height: 500rpx;
   border-radius: 50%;
-  will-change: transform, opacity;
-  transform: translateZ(0);
-  backface-visibility: hidden;
-
-  &.orb-1 {
-    width: 500rpx;
-    height: 500rpx;
-    background: radial-gradient(circle, rgba(255, 215, 0, 0.2) 0%, transparent 70%);
-    top: -150rpx;
-    right: -100rpx;
-    animation: float 8s ease-in-out infinite;
-  }
-
-  &.orb-2 {
-    width: 400rpx;
-    height: 400rpx;
-    background: radial-gradient(circle, rgba(255, 193, 7, 0.15) 0%, transparent 70%);
-    top: 200rpx;
-    left: -120rpx;
-    animation: float 10s ease-in-out infinite reverse;
-  }
-
-  &.orb-3 {
-    width: 350rpx;
-    height: 350rpx;
-    background: radial-gradient(circle, rgba(255, 235, 59, 0.12) 0%, transparent 70%);
-    bottom: 200rpx;
-    right: -80rpx;
-    animation: float 12s ease-in-out infinite 2s;
-  }
-}
-
-.wave-container {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  height: 400rpx;
-  overflow: hidden;
-}
-
-.wave {
-  position: absolute;
-  bottom: 0;
-  left: -50%;
-  width: 200%;
-  height: 400rpx;
-  background: linear-gradient(180deg, transparent 0%, rgba(255, 215, 0, 0.05) 100%);
-  border-radius: 45% 48% 43% 47%;
-  animation: wave 15s linear infinite;
-
-  &.wave-2 {
-    display: none; /* 移除第二道波浪以提升性能 */
-  }
+  background: radial-gradient(circle, rgba(255, 215, 0, 0.15) 0%, transparent 70%);
+  top: -120rpx;
+  right: -80rpx;
+  animation: orb-float 8s ease-in-out infinite;
 }
 
 /* ========== 主内容区 ========== */
@@ -303,9 +227,10 @@ const goToPrivacyPolicy = () => {
   z-index: 1;
   display: flex;
   flex-direction: column;
-  padding: 0 40rpx;
-  padding-bottom: calc(160rpx + constant(safe-area-inset-bottom));
-  padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
+  align-items: center;
+  padding: 0 48rpx;
+  padding-bottom: calc(80rpx + constant(safe-area-inset-bottom));
+  padding-bottom: calc(80rpx + env(safe-area-inset-bottom));
 }
 
 /* ========== 品牌展示区 ========== */
@@ -314,113 +239,78 @@ const goToPrivacyPolicy = () => {
   flex-direction: column;
   align-items: center;
   padding-top: 160rpx;
-  padding-bottom: 80rpx;
-  animation: slideDown 0.3s ease;
+  padding-bottom: 72rpx;
+  animation: slide-down 0.35s $ease-out;
 }
 
 .logo-wrapper {
-  margin-bottom: 56rpx;
-  will-change: transform;
-  transform: translateZ(0);
+  margin-bottom: 48rpx;
 
-  .logo-outer-ring {
-    width: 280rpx;
-    height: 280rpx;
+  .logo-ring {
+    width: 220rpx;
+    height: 220rpx;
     border-radius: 50%;
-    border: 2rpx solid rgba(255, 215, 0, 0.25);
+    border: 2rpx solid rgba(255, 193, 7, 0.2);
     display: flex;
     align-items: center;
     justify-content: center;
-    animation: ringRotate 20s linear infinite;
+    position: relative;
+    animation: ring-rotate 30s linear infinite;
+
+    .logo-glow {
+      position: absolute;
+      width: 200rpx;
+      height: 200rpx;
+      border-radius: 50%;
+      background: radial-gradient(circle, rgba(255, 193, 7, 0.12) 0%, transparent 70%);
+      animation: glow-pulse 3s ease-in-out infinite;
+    }
 
-    .logo-inner-ring {
-      width: 256rpx;
-      height: 256rpx;
+    .logo-circle {
+      width: 170rpx;
+      height: 170rpx;
+      background: $color-bg-primary;
       border-radius: 50%;
-      border: 2rpx dashed rgba(255, 215, 0, 0.3);
       display: flex;
       align-items: center;
       justify-content: center;
+      box-shadow: $shadow-md, inset 0 2rpx 8rpx rgba(255, 255, 255, 0.6);
+      padding: 32rpx;
       position: relative;
+      z-index: 1;
 
-      .logo-glow {
-        position: absolute;
-        width: 240rpx;
-        height: 240rpx;
-        border-radius: 50%;
-        background: radial-gradient(circle, rgba(255, 215, 0, 0.15) 0%, transparent 70%);
-        /* 移除filter: blur()以提升性能 */
-        opacity: 0.8;
-        animation: pulse 4s ease-in-out infinite;
-      }
-
-      .logo-circle {
-        width: 220rpx;
-        height: 220rpx;
-        background: linear-gradient(135deg, #FFFFFF 0%, #FFFEF5 100%);
-        border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        box-shadow:
-          0 16rpx 40rpx rgba(255, 193, 7, 0.2),
-          inset 0 2rpx 8rpx rgba(255, 255, 255, 0.6);
-        padding: 28rpx;
-        position: relative;
-        z-index: 1;
-
-        .logo {
-          width: 100%;
-          height: 100%;
-          /* 移除drop-shadow滤镜 */
-        }
+      .logo {
+        width: 100%;
+        height: 100%;
       }
     }
   }
 }
 
-.brand-text-group {
-  text-align: center;
-  margin-bottom: 48rpx;
-  
-  .brand-title {
-    font-size: 56rpx;
-    font-weight: 700;
-    background: linear-gradient(135deg, #333333 0%, #666666 100%);
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    background-clip: text;
-    letter-spacing: 4rpx;
-    display: block;
-    margin-bottom: 16rpx;
-  }
-  
-  .brand-subtitle {
-    font-size: 26rpx;
-    color: #888888;
-    letter-spacing: 2rpx;
-    font-weight: 400;
-  }
+.brand-name {
+  font-size: 48rpx;
+  font-weight: 700;
+  color: $color-text-primary;
+  letter-spacing: 6rpx;
+  margin-bottom: 12rpx;
+}
+
+.brand-desc {
+  font-size: 26rpx;
+  color: $color-text-secondary;
+  letter-spacing: 2rpx;
+  margin-bottom: 40rpx;
 }
 
 .feature-tags {
   display: flex;
   gap: 20rpx;
-  justify-content: center;
 
   .tag {
-    display: flex;
-    align-items: center;
-    gap: 8rpx;
-    padding: 12rpx 20rpx;
+    padding: 12rpx 24rpx;
     background: rgba(255, 255, 255, 0.9);
     border-radius: 40rpx;
-    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-    border: 1rpx solid rgba(255, 215, 0, 0.2);
-
-    .tag-icon {
-      font-size: 24rpx;
-    }
+    box-shadow: $shadow-sm;
 
     .tag-text {
       font-size: 22rpx;
@@ -432,238 +322,157 @@ const goToPrivacyPolicy = () => {
 
 /* ========== 登录卡片 ========== */
 .login-card {
-  background: rgba(255, 255, 255, 0.98);
-  border-radius: 32rpx;
-  padding: 56rpx 40rpx;
-  margin-bottom: 40rpx;
-  box-shadow:
-    0 16rpx 48rpx rgba(0, 0, 0, 0.06),
-    inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
-  border: 1rpx solid rgba(255, 215, 0, 0.15);
-  animation: slideUp 0.3s ease 0.1s both;
-  will-change: transform, opacity;
-  transform: translateZ(0);
+  width: 100%;
+  background: $color-bg-primary;
+  border-radius: $radius-xl;
+  padding: 48rpx 40rpx 40rpx;
+  box-shadow: $shadow-lg;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-sizing: border-box;
+  animation: slide-up 0.35s $ease-out 0.1s both;
 }
 
 .card-header {
   display: flex;
   align-items: center;
   justify-content: center;
-  gap: 24rpx;
-  margin-bottom: 60rpx;
-  
+  gap: 20rpx;
+  margin-bottom: 48rpx;
+
   .header-line {
-    width: 80rpx;
+    width: 64rpx;
     height: 2rpx;
-    background: linear-gradient(90deg, transparent 0%, rgba(255, 215, 0, 0.4) 100%);
-    
+    background: linear-gradient(90deg, transparent, $color-primary-light);
+
     &:last-child {
-      background: linear-gradient(90deg, rgba(255, 215, 0, 0.4) 0%, transparent 100%);
+      background: linear-gradient(90deg, $color-primary-light, transparent);
     }
   }
-  
+
   .card-title {
-    font-size: 30rpx;
+    font-size: 28rpx;
     font-weight: 600;
-    color: #333333;
+    color: $color-text-primary;
     letter-spacing: 2rpx;
   }
 }
 
-.wechat-login-btn {
+.login-btn {
   width: 100%;
-  min-height: 108rpx;
-  background: linear-gradient(135deg, #FFD700 0%, #FFC107 50%, #FFB300 100%);
+  height: 108rpx;
+  background: linear-gradient(135deg, #FFD700, $color-primary, $color-primary-dark);
   border-radius: 54rpx;
   border: none;
   display: flex;
   align-items: center;
   justify-content: center;
-  position: relative;
-  overflow: hidden;
-  box-shadow:
-    0 12rpx 40rpx rgba(255, 193, 7, 0.35),
-    0 4rpx 16rpx rgba(255, 193, 7, 0.2),
-    inset 0 2rpx 0 rgba(255, 255, 255, 0.3);
-  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  box-shadow: $shadow-primary, 0 4rpx 16rpx rgba(255, 193, 7, 0.2);
 
   &::after {
     border: none;
   }
 
-  .btn-shine {
-    position: absolute;
-    top: 0;
-    left: -100%;
-    width: 100%;
-    height: 100%;
-    background: linear-gradient(
-      90deg,
-      transparent 0%,
-      rgba(255, 255, 255, 0.3) 50%,
-      transparent 100%
-    );
-    transition: left 0.6s ease;
-  }
-
-  &.btn-hover {
-    transform: translateY(-4rpx) scale(1.02);
-    box-shadow:
-      0 16rpx 48rpx rgba(255, 193, 7, 0.45),
-      0 6rpx 20rpx rgba(255, 193, 7, 0.25),
-      inset 0 2rpx 0 rgba(255, 255, 255, 0.4);
-
-    .btn-shine {
-      left: 100%;
-    }
+  &:active {
+    transform: scale(0.98);
   }
+}
 
-  .btn-text {
-    font-size: 36rpx;
-    font-weight: 600;
-    color: #1A1A1A;
-    letter-spacing: 8rpx;
-    position: relative;
-    z-index: 1;
-    white-space: nowrap;
-  }
+.login-btn-hover {
+  transform: translateY(-2rpx) scale(1.01);
+  box-shadow: 0 12rpx 36rpx rgba(255, 193, 7, 0.4), 0 6rpx 20rpx rgba(255, 193, 7, 0.25);
 }
 
-/* ========== 用户协议 ========== */
-.agreement-section {
-  animation: fadeIn 0.35s ease 0.15s both;
+.login-btn-text {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #1A1A1A;
+  letter-spacing: 4rpx;
 }
 
-.agreement-card {
+/* ========== 用户协议 ========== */
+.agreement {
   text-align: center;
-  padding: 28rpx 36rpx;
-  background: rgba(255, 255, 255, 0.85);
-  border-radius: 20rpx;
-  border: 1rpx solid rgba(0, 0, 0, 0.04);
-
-  .agreement-prefix {
-    font-size: 24rpx;
-    color: #AAAAAA;
-    display: block;
-    margin-bottom: 8rpx;
-  }
+  padding: 40rpx 32rpx 0;
+  animation: fade-in 0.4s $ease-out 0.2s both;
+}
 
-  .agreement-links {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 8rpx;
-    flex-wrap: wrap;
-
-    .link-item {
-      font-size: 24rpx;
-      color: #FFB300;
-      font-weight: 600;
-      text-decoration: underline;
-      text-decoration-color: rgba(255, 179, 0, 0.3);
-      cursor: pointer;
-
-      &:active {
-        opacity: 0.6;
-      }
-    }
-    
-    .link-sep {
-      font-size: 24rpx;
-      color: #CCCCCC;
-    }
-  }
+.agreement-text {
+  font-size: 24rpx;
+  color: $color-text-tertiary;
+  display: block;
+  margin-bottom: 8rpx;
 }
 
-/* ========== 底部装饰 ========== */
-.footer-decoration {
+.agreement-links {
   display: flex;
+  align-items: center;
   justify-content: center;
-  margin-top: auto;
-  padding-top: 60rpx;
+  gap: 4rpx;
+  flex-wrap: wrap;
+}
 
-  .dot-pattern {
-    display: flex;
-    gap: 12rpx;
+.link {
+  font-size: 24rpx;
+  color: $color-primary-dark;
+  font-weight: 500;
 
-    .dot {
-      width: 12rpx;
-      height: 12rpx;
-      border-radius: 50%;
-      background: linear-gradient(135deg, #FFD700 0%, #FFC107 100%);
-      opacity: 0.4;
-      /* 移除脉冲动画以提升性能 */
-    }
+  &:active {
+    opacity: 0.6;
   }
 }
 
-/* ========== 动画关键帧 ========== */
-@keyframes float {
-  0%, 100% {
-    transform: translateY(0) scale(1);
-  }
-  50% {
-    transform: translateY(-30rpx) scale(1.05);
-  }
+.link-sep {
+  font-size: 24rpx;
+  color: $color-text-tertiary;
 }
 
-@keyframes wave {
-  0% {
-    transform: translateX(0) rotate(0deg);
-  }
-  100% {
-    transform: translateX(50%) rotate(360deg);
+/* ========== 底部装饰 ========== */
+.footer-dots {
+  display: flex;
+  gap: 12rpx;
+  justify-content: center;
+  margin-top: auto;
+  padding-top: 64rpx;
+
+  .dot {
+    width: 10rpx;
+    height: 10rpx;
+    border-radius: 50%;
+    background: $color-primary-light;
+    opacity: 0.5;
   }
 }
 
-@keyframes slideDown {
-  from {
-    opacity: 0;
-    transform: translateY(-60rpx);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
+/* ========== 动画 ========== */
+@keyframes orb-float {
+  0%, 100% { transform: translateY(0) scale(1); }
+  50% { transform: translateY(-24rpx) scale(1.04); }
 }
 
-@keyframes slideUp {
-  from {
-    opacity: 0;
-    transform: translateY(60rpx);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
+@keyframes ring-rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
-@keyframes fadeIn {
-  from {
-    opacity: 0;
-  }
-  to {
-    opacity: 1;
-  }
+@keyframes glow-pulse {
+  0%, 100% { opacity: 0.6; transform: scale(1); }
+  50% { opacity: 1; transform: scale(1.08); }
 }
 
-@keyframes pulse {
-  0%, 100% {
-    opacity: 0.25;
-    transform: scale(1);
-  }
-  50% {
-    opacity: 0.4;
-    transform: scale(1.05);
-  }
+@keyframes slide-down {
+  from { opacity: 0; transform: translateY(-40rpx); }
+  to { opacity: 1; transform: translateY(0); }
 }
 
-@keyframes ringRotate {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
+@keyframes slide-up {
+  from { opacity: 0; transform: translateY(40rpx); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes fade-in {
+  from { opacity: 0; }
+  to { opacity: 1; }
 }
 </style>

+ 1 - 1
haha-mp/src/pages/my/my.vue

@@ -406,7 +406,7 @@ const handleLogout = () => {
     border-radius: 16rpx;
     padding: 0 8rpx;
     margin-right: $spacing-sm;
-    animation: bounce 0.3s ease;
+    animation: pop-in 0.3s ease;
   }
   
   .service-phone {

+ 92 - 224
haha-mp/src/pages/orderDetail/orderDetail.vue

@@ -24,7 +24,6 @@
           <text class="card-title">订单明细</text>
         </view>
 
-        <!-- 商品列表 -->
         <view v-for="(product, index) in order.products" :key="index" class="product-item">
           <view class="product-image">
             <image v-if="product.image" :src="product.image" mode="aspectFill" lazy-load></image>
@@ -37,7 +36,6 @@
           <text class="product-quantity">x{{ product.quantity }}</text>
         </view>
 
-        <!-- 优惠金额(仅当有优惠时显示) -->
         <view v-if="order.discountAmount && order.discountAmount > 0" class="amount-row discount-row">
           <text class="amount-label">优惠金额</text>
           <view class="amount-value-wrapper">
@@ -45,8 +43,7 @@
             <text class="amount-discount">{{ order.discountAmount.toFixed(2) }}</text>
           </view>
         </view>
-        
-        <!-- 实付款 -->
+
         <view class="amount-row">
           <text class="amount-label">实付款</text>
           <view class="amount-value-wrapper">
@@ -101,6 +98,10 @@
             <text class="info-label">支付方式</text>
             <text class="info-value">微信</text>
           </view>
+          <view class="info-row" v-if="order.storeName">
+            <text class="info-label">门店</text>
+            <text class="info-value">{{ order.storeName }}</text>
+          </view>
           <view class="info-row">
             <text class="info-label">设备编号</text>
             <text class="info-value">{{ order.deviceId }}</text>
@@ -139,14 +140,12 @@ import { getOrderDetail } from '../../api/order';
 import type { OrderInfo } from '../../api/order';
 import { checkAuth } from '../../utils/auth';
 import { getStatusText, canRefund } from '../../utils/order';
+import { logger } from '../../utils/logger';
 
 const order = ref<OrderInfo | null>(null);
 const loading = ref(true);
 const orderId = ref<string | null>(null);
 
-/**
- * 获取状态样式
- */
 const getStatusClass = (status: number) => {
   if (status === 1) return 'status-success';
   if (status === 0) return 'status-pending';
@@ -154,81 +153,30 @@ const getStatusClass = (status: number) => {
   return '';
 };
 
-/**
- * 判断订单是否超过退款期限(7天)
- */
-const isRefundExpired = (order: OrderInfo) => {
-  if (order.status !== 1 || !order.payTime) {
-    return false;
-  }
-  
-  const payTime = new Date(order.payTime);
-  const now = new Date();
-  const diffTime = now.getTime() - payTime.getTime();
-  const diffDays = diffTime / (1000 * 60 * 60 * 24);
-  
-  return diffDays > 7;
-};
-
-/**
- * 获取退款期限提示文本
- */
-const getRefundExpireText = (order: OrderInfo) => {
-  if (!order.payTime) return '';
-  
-  const payTime = new Date(order.payTime);
-  const now = new Date();
-  const diffTime = now.getTime() - payTime.getTime();
-  const diffDays = diffTime / (1000 * 60 * 60 * 24);
-  
-  if (diffDays > 7) {
-    return '已超过退款期限(7天)';
-  } else {
-    const remainingDays = Math.ceil(7 - diffDays);
-    return `剩余 ${remainingDays} 天可申请退款`;
-  }
-};
-
-/**
- * 格式化时间
- */
 const formatTime = (time: string | undefined) => {
   if (!time) return '-';
-  // 如果已经是格式化的时间字符串,直接返回
   if (typeof time === 'string' && time.includes('-')) {
     return time.replace('T', ' ').substring(0, 19);
   }
   return time;
 };
 
-/**
- * 加载订单详情
- */
 const loadOrderDetail = async () => {
   if (!orderId.value) {
-    uni.showToast({
-      title: '订单ID不存在',
-      icon: 'none'
-    });
+    uni.showToast({ title: '订单ID不存在', icon: 'none' });
     return;
   }
 
   loading.value = true;
 
   try {
-    uni.showLoading({
-      title: '加载中...'
-    });
-
-    // 调用真实接口获取订单详情
+    uni.showLoading({ title: '加载中...' });
     const orderDetail = await getOrderDetail({ orderId: orderId.value });
     order.value = orderDetail;
-
     uni.hideLoading();
   } catch (error: any) {
     uni.hideLoading();
-    console.error('加载订单详情失败:', error);
-    // 错误已在request工具中显示
+    logger.error('加载订单详情失败:', error);
     setTimeout(() => {
       uni.navigateBack();
     }, 1500);
@@ -237,11 +185,7 @@ const loadOrderDetail = async () => {
   }
 };
 
-/**
- * 页面加载时检查登录状态并获取订单详情
- */
 onMounted(() => {
-  // 检查登录状态
   if (!checkAuth()) {
     return;
   }
@@ -251,82 +195,41 @@ onMounted(() => {
   const options = currentPage.options || {};
 
   if (options.orderId) {
-    // 直接使用字符串类型的订单ID,避免精度丢失
     orderId.value = String(options.orderId);
     loadOrderDetail();
   } else {
-    uni.showToast({
-      title: '订单ID不存在',
-      icon: 'none'
-    });
+    uni.showToast({ title: '订单ID不存在', icon: 'none' });
     setTimeout(() => {
       uni.navigateBack();
     }, 1500);
   }
 });
 
-/**
- * 申请退款
- */
 const applyRefund = () => {
   if (!order.value) return;
-  // 确保订单ID以字符串形式传递,避免精度丢失
   uni.navigateTo({
     url: '/pages/refund/refund?orderId=' + String(order.value.id)
   });
 };
 
-/**
- * 打印订单
- */
-const printOrder = () => {
-  uni.showToast({
-    title: '打印订单功能开发中',
-    icon: 'none'
-  });
-};
-
-/**
- * 导出图片
- */
-const exportImage = () => {
-  uni.showToast({
-    title: '导出图片功能开发中',
-    icon: 'none'
-  });
-};
-
-/**
- * 复制文本
- */
 const copyText = (text: string) => {
   uni.setClipboardData({
     data: text,
     success: () => {
-      uni.showToast({
-        title: '复制成功',
-        icon: 'success'
-      });
+      uni.showToast({ title: '复制成功', icon: 'success' });
     }
   });
 };
 
-/**
- * 查看视频
- */
 const viewVideo = (url: string) => {
-  // TODO: [video] 实现视频播放功能(需后端提供视频URL)
-  uni.showToast({
-    title: '视频播放功能开发中',
-    icon: 'none'
-  });
+  uni.showToast({ title: '视频播放功能开发中', icon: 'none' });
 };
 </script>
 
 <style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: linear-gradient(180deg, #FFF8E1 0%, #F5F5F5 30%);
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-secondary 30%);
   padding: 24rpx;
   box-sizing: border-box;
 }
@@ -337,18 +240,16 @@ const viewVideo = (url: string) => {
   justify-content: center;
   min-height: 400rpx;
   font-size: 28rpx;
-  color: #999;
-  animation: fadeIn 0.2s ease;
+  color: $color-text-secondary;
 }
 
 /* 通用卡片样式 */
 .card {
-  background-color: #ffffff;
-  border-radius: 24rpx;
+  background-color: $color-bg-primary;
+  border-radius: $radius-lg;
   padding: 32rpx;
   margin-bottom: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
-  animation: slideUp 0.25s ease both;
+  box-shadow: $shadow-md;
 }
 
 /* 订单状态卡片 */
@@ -366,29 +267,26 @@ const viewVideo = (url: string) => {
 .status-icon-wrapper {
   width: 56rpx;
   height: 56rpx;
-  background: linear-gradient(135deg, #00CC66 0%, #00B359 100%);
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   margin-right: 20rpx;
-  box-shadow: 0 4rpx 12rpx rgba(0, 204, 102, 0.3);
-  animation: scaleIn 0.3s ease 0.1s both;
-}
 
-.status-icon-wrapper.status-success {
-  background: linear-gradient(135deg, #00CC66 0%, #00B359 100%);
-  box-shadow: 0 4rpx 12rpx rgba(0, 204, 102, 0.3);
-}
+  &.status-success {
+    background: linear-gradient(135deg, $color-success, #43A047);
+    box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.3);
+  }
 
-.status-icon-wrapper.status-pending {
-  background: linear-gradient(135deg, #FFA000 0%, #FF8C00 100%);
-  box-shadow: 0 4rpx 12rpx rgba(255, 160, 0, 0.3);
-}
+  &.status-pending {
+    background: linear-gradient(135deg, $color-warning, #F57C00);
+    box-shadow: 0 4rpx 12rpx rgba(255, 152, 0, 0.3);
+  }
 
-.status-icon-wrapper.status-cancelled {
-  background: linear-gradient(135deg, #999999 0%, #757575 100%);
-  box-shadow: 0 4rpx 12rpx rgba(153, 153, 153, 0.3);
+  &.status-cancelled {
+    background: linear-gradient(135deg, $color-text-secondary, #9E9E9E);
+    box-shadow: 0 4rpx 12rpx rgba(140, 140, 140, 0.3);
+  }
 }
 
 .status-icon {
@@ -399,15 +297,15 @@ const viewVideo = (url: string) => {
 
 .status-text {
   font-size: 34rpx;
-  color: #1A1A1A;
+  color: $color-text-primary;
   font-weight: 700;
 }
 
 .invoice-status {
   font-size: 24rpx;
-  color: #FF6B6B;
+  color: $color-error;
   padding: 6rpx 16rpx;
-  background: #FFF0F0;
+  background: rgba(244, 67, 54, 0.06);
   border-radius: 12rpx;
 }
 
@@ -419,46 +317,45 @@ const viewVideo = (url: string) => {
 
 .card-header {
   padding: 32rpx;
-  border-bottom: 1rpx solid #F5F5F5;
+  border-bottom: 1rpx solid $color-bg-secondary;
 }
 
 .card-title {
   font-size: 32rpx;
   font-weight: 700;
-  color: #1A1A1A;
+  color: $color-text-primary;
 }
 
 .product-item {
   display: flex;
   padding: 32rpx;
   align-items: flex-start;
-  transition: background 0.2s ease;
-  
+
   &:active {
-    background: #FAFAFA;
+    background: $color-bg-secondary;
   }
 }
 
 .product-image {
   width: 140rpx;
   height: 140rpx;
-  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
-  border-radius: 16rpx;
+  background: $color-bg-tertiary;
+  border-radius: $radius-md;
   margin-right: 24rpx;
   overflow: hidden;
-}
 
-.product-image image {
-  width: 100%;
-  height: 100%;
-  border-radius: 16rpx;
+  image {
+    width: 100%;
+    height: 100%;
+    border-radius: $radius-md;
+  }
 }
 
 .image-placeholder {
   width: 100%;
   height: 100%;
-  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
-  border-radius: 16rpx;
+  background: $color-bg-tertiary;
+  border-radius: $radius-md;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -470,7 +367,7 @@ const viewVideo = (url: string) => {
 
 .product-name {
   font-size: 30rpx;
-  color: #1A1A1A;
+  color: $color-text-primary;
   font-weight: 600;
   margin-bottom: 16rpx;
   display: block;
@@ -479,12 +376,12 @@ const viewVideo = (url: string) => {
 
 .product-price {
   font-size: 26rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .product-quantity {
   font-size: 26rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
   margin-top: 60rpx;
   font-weight: 500;
 }
@@ -494,20 +391,19 @@ const viewVideo = (url: string) => {
   justify-content: space-between;
   align-items: center;
   padding: 32rpx;
-  border-top: 1rpx solid #F5F5F5;
-  background: linear-gradient(180deg, #FFFFFF 0%, #FFFDF5 100%);
+  border-top: 1rpx solid $color-bg-secondary;
+  background: $color-bg-primary;
 }
 
-/* 优惠金额行样式 */
 .discount-row {
-  background: linear-gradient(180deg, #F0F9FF 0%, #E0F2FE 100%);
+  background: rgba(76, 175, 80, 0.04);
   border-top: none;
   padding: 24rpx 32rpx;
 }
 
 .amount-label {
   font-size: 28rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .amount-value-wrapper {
@@ -517,26 +413,25 @@ const viewVideo = (url: string) => {
 
 .amount-prefix {
   font-size: 26rpx;
-  color: #666666;
+  color: $color-text-secondary;
   margin-right: 10rpx;
 }
 
 .amount-symbol {
   font-size: 28rpx;
-  color: #FF8C00;
+  color: $color-warning;
   font-weight: 700;
 }
 
 .amount-integer {
   font-size: 40rpx;
-  color: #FF8C00;
+  color: $color-warning;
   font-weight: 800;
 }
 
-/* 优惠金额数值样式 */
 .amount-discount {
   font-size: 32rpx;
-  color: #10B981;
+  color: $color-success;
   font-weight: 700;
 }
 
@@ -548,48 +443,25 @@ const viewVideo = (url: string) => {
 }
 
 .action-btn-outline {
-  background-color: #ffffff;
-  border: 2rpx solid #E0E0E0;
-  border-radius: 32rpx;
+  background-color: $color-bg-primary;
+  border: 2rpx solid $color-border;
+  border-radius: $radius-xl;
   font-size: 26rpx;
-  color: #666666;
+  color: $color-text-secondary;
   margin: 0;
   padding: 16rpx 32rpx;
   line-height: 1;
-  transition: all 0.2s ease;
-  
-  &:active {
-    transform: scale(0.95);
-    background-color: #F5F5F5;
-  }
-}
 
-.action-btn-outline::after {
-  border: none;
-}
+  &::after {
+    border: none;
+  }
 
-.action-btn-primary {
-  background: linear-gradient(135deg, #FFC107 0%, #FFB300 100%);
-  border-radius: 32rpx;
-  font-size: 26rpx;
-  color: #1A1A1A;
-  margin: 0;
-  padding: 16rpx 32rpx;
-  line-height: 1;
-  font-weight: 600;
-  box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.25);
-  transition: all 0.2s ease;
-  
   &:active {
     transform: scale(0.95);
-    box-shadow: 0 2rpx 8rpx rgba(255, 193, 7, 0.3);
+    background-color: $color-bg-secondary;
   }
 }
 
-.action-btn-primary::after {
-  border: none;
-}
-
 /* 订单信息卡片 */
 .info-card {
   padding: 0;
@@ -604,21 +476,20 @@ const viewVideo = (url: string) => {
   justify-content: space-between;
   align-items: center;
   padding: 28rpx 0;
-  border-bottom: 1rpx solid #F5F5F5;
-  transition: background 0.2s ease;
-  
+  border-bottom: 1rpx solid $color-bg-secondary;
+
   &:active {
-    background: #FAFAFA;
+    background: $color-bg-secondary;
   }
-}
 
-.info-row:last-child {
-  border-bottom: none;
+  &:last-child {
+    border-bottom: none;
+  }
 }
 
 .info-label {
   font-size: 28rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .info-value-group {
@@ -628,15 +499,14 @@ const viewVideo = (url: string) => {
 
 .info-value {
   font-size: 28rpx;
-  color: #1A1A1A;
+  color: $color-text-primary;
   font-weight: 500;
 }
 
 .copy-btn {
   margin-left: 12rpx;
   padding: 8rpx;
-  transition: transform 0.2s ease;
-  
+
   &:active {
     transform: scale(0.9);
   }
@@ -645,28 +515,27 @@ const viewVideo = (url: string) => {
 .copy-icon-box {
   width: 28rpx;
   height: 32rpx;
-  border: 3rpx solid #999999;
+  border: 3rpx solid $color-text-secondary;
   position: relative;
   border-radius: 4rpx;
-}
 
-.copy-icon-box::after {
-  content: '';
-  position: absolute;
-  width: 20rpx;
-  height: 24rpx;
-  border: 3rpx solid #999999;
-  background-color: #ffffff;
-  top: -8rpx;
-  right: -8rpx;
-  border-radius: 4rpx;
+  &::after {
+    content: '';
+    position: absolute;
+    width: 20rpx;
+    height: 24rpx;
+    border: 3rpx solid $color-text-secondary;
+    background-color: $color-bg-primary;
+    top: -8rpx;
+    right: -8rpx;
+    border-radius: 4rpx;
+  }
 }
 
 .info-link {
   display: flex;
   align-items: center;
-  transition: opacity 0.2s ease;
-  
+
   &:active {
     opacity: 0.6;
   }
@@ -674,25 +543,24 @@ const viewVideo = (url: string) => {
 
 .link-text {
   font-size: 28rpx;
-  color: #FF8C00;
+  color: $color-warning;
   font-weight: 600;
   margin-right: 6rpx;
 }
 
 .link-arrow {
   font-size: 28rpx;
-  color: #FFB300;
+  color: $color-primary-dark;
 }
 
 /* 底部提示 */
 .footer-note {
   text-align: center;
   padding: 60rpx 0 40rpx;
-  animation: fadeIn 0.3s ease 0.15s both;
-}
 
-.footer-note text {
-  font-size: 24rpx;
-  color: #CCCCCC;
+  text {
+    font-size: 24rpx;
+    color: $color-text-tertiary;
+  }
 }
 </style>

+ 59 - 116
haha-mp/src/pages/orders/orders.vue

@@ -4,13 +4,9 @@
     <view v-if="!loading && orders.length === 0" class="empty-container">
       <view class="empty-icon">
         <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
-          <!-- 空箱子 -->
           <rect x="60" y="80" width="80" height="70" rx="4" fill="#E8E8E8" stroke="#CCCCCC" stroke-width="2"/>
-          <!-- 箱子盖子 -->
           <path d="M 55 80 L 100 60 L 145 80" fill="#F5F5F5" stroke="#CCCCCC" stroke-width="2"/>
-          <!-- 箱子细节线 -->
           <line x1="100" y1="80" x2="100" y2="150" stroke="#CCCCCC" stroke-width="1" stroke-dasharray="3,3"/>
-          <!-- 表情 -->
           <circle cx="85" cy="110" r="3" fill="#999999"/>
           <circle cx="115" cy="110" r="3" fill="#999999"/>
           <path d="M 85 125 Q 100 120 115 125" stroke="#999999" stroke-width="2" fill="none" stroke-linecap="round"/>
@@ -19,7 +15,7 @@
       <text class="empty-text">暂无订单记录</text>
       <text class="empty-tip">快去购物吧~</text>
     </view>
-    
+
     <!-- 订单列表 -->
     <view v-else class="order-list">
       <view v-for="order in orders" :key="order.id" class="order-item">
@@ -60,81 +56,49 @@ import { ref, onMounted } from 'vue';
 import { getOrderList } from '../../api/order';
 import type { OrderInfo } from '../../api/order';
 import { checkAuth } from '../../utils/auth';
-import { getStatusText, canRefund, isRefundExpired, getRefundExpireText } from '../../utils/order';
+import { getStatusText, canRefund } from '../../utils/order';
+import { logger } from '../../utils/logger';
 
 const orders = ref<OrderInfo[]>([]);
 const loading = ref(false);
 
-/**
- * 计算订单商品总数量
- */
 const getQuantity = (order: OrderInfo) => {
   if (!order.products) return 0;
   return order.products.reduce((acc: number, p: any) => acc + p.quantity, 0);
 };
 
-/**
- * 加载订单列表
- */
 const loadOrders = async () => {
   if (loading.value) return;
-  
+
   loading.value = true;
-  
+
   try {
-    uni.showLoading({
-      title: '加载中...'
-    });
-    
-    // 调用真实接口获取订单列表
+    uni.showLoading({ title: '加载中...' });
     const orderList = await getOrderList();
     orders.value = orderList;
-    
     uni.hideLoading();
-    
-    // 无数据时不显示toast,由空状态提示展示
-    // if (orderList.length === 0) {
-    //   uni.showToast({
-    //     title: '暂无订单',
-    //     icon: 'none'
-    //   });
-    // }
   } catch (error: any) {
     uni.hideLoading();
-    console.error('加载订单列表失败:', error);
-    // 错误已在request工具中显示
+    logger.error('加载订单列表失败:', error);
   } finally {
     loading.value = false;
   }
 };
 
-/**
- * 页面加载时检查登录状态并获取订单列表
- */
 onMounted(() => {
-  // 检查登录状态,未登录会自动跳转到登录页
   if (!checkAuth('/pages/orders/orders')) {
     return;
   }
-  
-  // 已登录,加载订单列表
   loadOrders();
 });
 
-/**
- * 申请退款
- */
 const applyRefund = (order: OrderInfo) => {
   uni.navigateTo({
     url: '/pages/refund/refund?orderId=' + order.id
   });
 };
 
-/**
- * 查看订单详情
- */
 const viewOrderDetail = (order: OrderInfo) => {
-  // 确保订单ID以字符串形式传递,避免精度丢失
   uni.navigateTo({
     url: '/pages/orderDetail/orderDetail?orderId=' + String(order.id)
   });
@@ -144,9 +108,7 @@ const viewOrderDetail = (order: OrderInfo) => {
 <style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background: #F5F5F5;
-  position: relative;
-  padding-top: 0;
+  background: $color-bg-secondary;
 }
 
 /* 无数据提示 */
@@ -157,37 +119,29 @@ const viewOrderDetail = (order: OrderInfo) => {
   justify-content: center;
   min-height: 80vh;
   padding: 40rpx;
-  animation: fadeIn 0.3s ease;
-  will-change: transform, opacity;
-  transform: translateZ(0);
 }
 
 .empty-icon {
   width: 300rpx;
   height: 300rpx;
   margin-bottom: 40rpx;
-  animation: fadeIn 0.3s ease 0.1s both;
-  will-change: transform, opacity;
-  transform: translateZ(0);
-}
 
-.empty-icon svg {
-  width: 100%;
-  height: 100%;
+  svg {
+    width: 100%;
+    height: 100%;
+  }
 }
 
 .empty-text {
   font-size: 32rpx;
-  color: #666666;
+  color: $color-text-secondary;
   margin-bottom: 16rpx;
   font-weight: 500;
-  animation: fadeIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1) 0.3s both;
 }
 
 .empty-tip {
   font-size: 28rpx;
-  color: #999999;
-  animation: fadeIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1) 0.4s both;
+  color: $color-text-secondary;
 }
 
 /* 订单列表 */
@@ -197,14 +151,11 @@ const viewOrderDetail = (order: OrderInfo) => {
 
 /* 订单项 */
 .order-item {
-  background-color: #ffffff;
-  border-radius: 24rpx;
+  background-color: $color-bg-primary;
+  border-radius: $radius-lg;
   margin-bottom: 24rpx;
   overflow: hidden;
-  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
-  transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
-  will-change: transform;
-  transform: translateZ(0);
+  box-shadow: $shadow-sm;
 
   &:active {
     transform: scale(0.98);
@@ -217,12 +168,12 @@ const viewOrderDetail = (order: OrderInfo) => {
   justify-content: space-between;
   align-items: center;
   padding: 28rpx 32rpx;
-  border-bottom: 1rpx solid #F5F5F5;
+  border-bottom: 1rpx solid $color-bg-secondary;
 }
 
 .order-time {
   font-size: 24rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .time-invoice-container {
@@ -233,7 +184,7 @@ const viewOrderDetail = (order: OrderInfo) => {
 
 .invoice-status {
   font-size: 22rpx;
-  color: #FF6B6B;
+  color: $color-error;
   margin-left: 16rpx;
   padding: 4rpx 12rpx;
   background: #FFF0F0;
@@ -243,26 +194,26 @@ const viewOrderDetail = (order: OrderInfo) => {
 .order-status {
   font-size: 26rpx;
   font-weight: 600;
-}
 
-.order-status.paid,
-.order-status.completed {
-  color: #00CC66;
-}
+  &.paid,
+  &.completed {
+    color: $color-success;
+  }
 
-.order-status.refunded {
-  color: #999999;
-}
+  &.refunded {
+    color: $color-text-secondary;
+  }
 
-.order-status.pending {
-  color: #FFA000;
+  &.pending {
+    color: $color-warning;
+  }
 }
 
 /* 订单内容 */
 .order-content {
   display: flex;
   padding: 32rpx;
-  border-bottom: 1rpx solid #F5F5F5;
+  border-bottom: 1rpx solid $color-bg-secondary;
 }
 
 .product-image {
@@ -270,20 +221,20 @@ const viewOrderDetail = (order: OrderInfo) => {
   height: 160rpx;
   margin-right: 24rpx;
   flex-shrink: 0;
-  background: #F5F5F5;
-  border-radius: 16rpx;
+  background: $color-bg-tertiary;
+  border-radius: $radius-md;
   overflow: hidden;
-}
 
-.product-image image {
-  width: 100%;
-  height: 100%;
+  image {
+    width: 100%;
+    height: 100%;
+  }
 }
 
 .image-placeholder {
   width: 100%;
   height: 100%;
-  background: #F5F5F5;
+  background: $color-bg-tertiary;
 }
 
 .order-info {
@@ -295,11 +246,9 @@ const viewOrderDetail = (order: OrderInfo) => {
 }
 
 .product-name {
-  font-size: 30rpx;
-  color: #1A1A1A;
+  font-size: 28rpx;
+  color: $color-text-primary;
   font-weight: 600;
-  width: 100%;
-  text-align: left;
   margin-bottom: 12rpx;
   line-height: 1.5;
   word-break: break-all;
@@ -311,18 +260,18 @@ const viewOrderDetail = (order: OrderInfo) => {
 }
 
 .price-label {
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .price-value {
-  color: #FF8C00;
+  color: $color-warning;
   font-weight: 700;
   font-size: 32rpx;
 }
 
 .quantity-info {
   font-size: 24rpx;
-  color: #BBBBBB;
+  color: $color-text-tertiary;
 }
 
 /* 订单底部 */
@@ -330,18 +279,16 @@ const viewOrderDetail = (order: OrderInfo) => {
   display: flex;
   justify-content: flex-end;
   padding: 24rpx 32rpx;
-  width: 100%;
   box-sizing: border-box;
   gap: 16rpx;
 }
 
-/* 重置按钮默认样式 */
 .action-btn {
-  background-color: #ffffff;
-  color: #666666;
-  border: 2rpx solid #E0E0E0;
+  background-color: $color-bg-primary;
+  color: $color-text-secondary;
+  border: 2rpx solid $color-border;
   padding: 16rpx 32rpx;
-  border-radius: 32rpx;
+  border-radius: $radius-xl;
   font-size: 26rpx;
   flex-shrink: 0;
   text-align: center;
@@ -353,33 +300,29 @@ const viewOrderDetail = (order: OrderInfo) => {
   justify-content: center;
   margin: 0;
   width: auto;
-  transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
-  will-change: transform;
-  transform: translateZ(0);
+
+  &::after {
+    border: none;
+  }
 
   &:active {
     transform: scale(0.95);
   }
 }
 
-.action-btn::after {
-  border: none;
-}
-
-/* 详情按钮高亮 */
 .detail-btn {
-  background: linear-gradient(135deg, #FFC107 0%, #FFB300 100%);
-  border-color: #FFC107;
-  color: #1A1A1A;
+  background: linear-gradient(135deg, $color-primary-light, $color-primary);
+  border-color: $color-primary;
+  color: $color-text-primary;
   font-weight: 600;
-  box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.25);
+  box-shadow: $shadow-primary;
 }
 
 .refund-btn {
-  color: #999999;
-  
+  color: $color-text-secondary;
+
   &:active {
-    background-color: #F5F5F5;
+    background-color: $color-bg-secondary;
   }
 }
-</style>
+</style>

+ 39 - 50
haha-mp/src/pages/payscore/enable.vue

@@ -207,7 +207,7 @@ const handleEnablePayscore = async () => {
         pollEnableStatus(outOrderNo)
       },
       fail: (err: any) => {
-        console.error('调起支付分失败:', err)
+        logger.error('调起支付分失败:', err)
         // 用户取消或失败,仍然可以手动确认
         uni.showModal({
           title: '提示',
@@ -218,7 +218,7 @@ const handleEnablePayscore = async () => {
     })
     
   } catch (error: any) {
-    console.error('开通支付分失败:', error)
+    logger.error('开通支付分失败:', error)
     uni.showModal({
       title: '开通失败',
       content: error.message || '开通过程中出现错误,请稍后重试',
@@ -276,7 +276,7 @@ const pollEnableStatus = (outOrderNo: string) => {
         })
       }
     } catch (error) {
-      console.error('轮询开通状态失败:', error)
+      logger.error('轮询开通状态失败:', error)
     }
   }, 2000) // 每 2 秒轮询一次
 }
@@ -304,7 +304,7 @@ const checkPayscoreStatus = async () => {
       })
     }
   } catch (error) {
-    console.error('检查支付分状态失败:', error)
+    logger.error('检查支付分状态失败:', error)
   }
 }
 
@@ -313,29 +313,18 @@ checkPayscoreStatus()
 </script>
 
 <style lang="scss" scoped>
-/* 微信品牌色彩 */
-$wx-green: #07C160;
-$wx-green-dark: #06AD56;
-$wx-green-light: #09D66B;
-$wx-bg: #F7F7F7;
-$wx-card-bg: #FFFFFF;
-$wx-text-primary: #191919;
-$wx-text-secondary: #7D7D7D;
-$wx-text-hint: #B2B2B2;
-$wx-border: #E5E5E5;
-$wx-red: #FA5555;
-$wx-orange: #FF9F00;
+/* 微信支付分页面 — 使用设计令牌 */
 
 .container {
   min-height: 100vh;
-  background: $wx-bg;
+  background: $color-bg-secondary;
   padding-bottom: 200rpx;
 }
 
 /* 微信风格头部 - 纯色背景 */
 .wx-header {
   width: 100%;
-  background: $wx-green;
+  background: $color-success;
   padding: 32rpx 40rpx 28rpx;
   box-sizing: border-box;
   
@@ -377,7 +366,7 @@ $wx-orange: #FF9F00;
 
 /* 分数要求卡片 - 突出显示 */
 .score-card {
-  background: $wx-card-bg;
+  background: $color-bg-primary;
   border-radius: 20rpx;
   padding: 40rpx 32rpx;
   margin-bottom: 30rpx;
@@ -389,7 +378,7 @@ $wx-orange: #FF9F00;
     position: absolute;
     top: 20rpx;
     right: 20rpx;
-    background: $wx-red;
+    background: $color-error;
     color: #FFFFFF;
     font-size: 22rpx;
     padding: 8rpx 16rpx;
@@ -406,13 +395,13 @@ $wx-orange: #FF9F00;
     .score-number {
       font-size: 96rpx;
       font-weight: bold;
-      color: $wx-green;
+      color: $color-success;
       line-height: 1;
     }
     
     .score-unit {
       font-size: 28rpx;
-      color: $wx-text-secondary;
+      color: $color-text-secondary;
       margin-left: 16rpx;
     }
   }
@@ -421,19 +410,19 @@ $wx-orange: #FF9F00;
     display: flex;
     align-items: center;
     justify-content: center;
-    background: rgba(7, 193, 96, 0.05);
+    background: rgba(76, 175, 80, 0.05);
     padding: 16rpx 24rpx;
     border-radius: 12rpx;
     
     .desc-icon {
       font-size: 28rpx;
-      color: $wx-green;
+      color: $color-success;
       margin-right: 12rpx;
     }
     
     .desc-text {
       font-size: 24rpx;
-      color: $wx-text-secondary;
+      color: $color-text-secondary;
     }
   }
 }
@@ -445,7 +434,7 @@ $wx-orange: #FF9F00;
   .section-title {
     font-size: 32rpx;
     font-weight: 600;
-    color: $wx-text-primary;
+    color: $color-text-primary;
     margin-bottom: 24rpx;
     display: block;
   }
@@ -458,7 +447,7 @@ $wx-orange: #FF9F00;
   
   .benefit-card {
     flex: 1;
-    background: $wx-card-bg;
+    background: $color-bg-primary;
     border-radius: 16rpx;
     padding: 32rpx 20rpx;
     display: flex;
@@ -475,7 +464,7 @@ $wx-orange: #FF9F00;
       align-items: center;
       justify-content: center;
       margin-bottom: 20rpx;
-      background: $wx-green;
+      background: $color-success;
         
       .icon-emoji {
         font-size: 40rpx;
@@ -486,20 +475,20 @@ $wx-orange: #FF9F00;
     .benefit-title {
       font-size: 28rpx;
       font-weight: 600;
-      color: $wx-text-primary;
+      color: $color-text-primary;
       margin-bottom: 8rpx;
     }
     
     .benefit-desc {
       font-size: 22rpx;
-      color: $wx-text-secondary;
+      color: $color-text-secondary;
     }
   }
 }
 
 /* 信息卡片 */
 .info-card {
-  background: $wx-card-bg;
+  background: $color-bg-primary;
   border-radius: 20rpx;
   padding: 24rpx 32rpx;
   margin-bottom: 30rpx;
@@ -508,7 +497,7 @@ $wx-orange: #FF9F00;
   .card-title {
     font-size: 32rpx;
     font-weight: 600;
-    color: $wx-text-primary;
+    color: $color-text-primary;
     margin-bottom: 16rpx;
     display: block;
   }
@@ -516,7 +505,7 @@ $wx-orange: #FF9F00;
   .card-content {
     .desc-text {
       font-size: 28rpx;
-      color: $wx-text-secondary;
+      color: $color-text-secondary;
       line-height: 1.6;
       display: block;
     }
@@ -525,7 +514,7 @@ $wx-orange: #FF9F00;
 
 /* 开通流程 */
 .steps-section {
-  background: $wx-card-bg;
+  background: $color-bg-primary;
   border-radius: 20rpx;
   padding: 40rpx 32rpx;
   margin-bottom: 30rpx;
@@ -534,7 +523,7 @@ $wx-orange: #FF9F00;
   .section-title {
     font-size: 32rpx;
     font-weight: 600;
-    color: $wx-text-primary;
+    color: $color-text-primary;
     margin-bottom: 32rpx;
     display: block;
   }
@@ -560,13 +549,13 @@ $wx-orange: #FF9F00;
       color: #FFFFFF;
       margin-right: 24rpx;
       flex-shrink: 0;
-      background: $wx-green;
+      background: $color-success;
       
       &.step-1,
       &.step-2,
       &.step-3,
       &.step-4 {
-        background: $wx-green;
+        background: $color-success;
       }
     }
     
@@ -576,14 +565,14 @@ $wx-orange: #FF9F00;
       .step-title {
         font-size: 28rpx;
         font-weight: 600;
-        color: $wx-text-primary;
+        color: $color-text-primary;
         display: block;
         margin-bottom: 8rpx;
       }
       
       .step-desc {
         font-size: 24rpx;
-        color: $wx-text-secondary;
+        color: $color-text-secondary;
         display: block;
         line-height: 1.4;
       }
@@ -596,8 +585,8 @@ $wx-orange: #FF9F00;
   margin-bottom: 30rpx;
   
   .tips-card {
-    background: rgba(7, 193, 96, 0.05);
-    border: 2rpx solid rgba(7, 193, 96, 0.2);
+    background: rgba(76, 175, 80, 0.05);
+    border: 2rpx solid rgba(76, 175, 80, 0.2);
     border-radius: 16rpx;
     padding: 32rpx 28rpx;
     display: flex;
@@ -605,7 +594,7 @@ $wx-orange: #FF9F00;
     
     .tips-icon {
       font-size: 40rpx;
-      color: $wx-green;
+      color: $color-success;
       margin-right: 20rpx;
       flex-shrink: 0;
     }
@@ -616,7 +605,7 @@ $wx-orange: #FF9F00;
       .tips-title {
         font-size: 28rpx;
         font-weight: 600;
-        color: $wx-text-primary;
+        color: $color-text-primary;
         display: block;
         margin-bottom: 16rpx;
       }
@@ -631,7 +620,7 @@ $wx-orange: #FF9F00;
         }
         
         .tip-dot {
-          color: $wx-green;
+          color: $color-success;
           font-size: 28rpx;
           margin-right: 12rpx;
           flex-shrink: 0;
@@ -639,7 +628,7 @@ $wx-orange: #FF9F00;
         
         .tip-content {
           font-size: 24rpx;
-          color: $wx-text-secondary;
+          color: $color-text-secondary;
           line-height: 1.6;
         }
       }
@@ -653,7 +642,7 @@ $wx-orange: #FF9F00;
   bottom: 0;
   left: 0;
   right: 0;
-  background: $wx-card-bg;
+  background: $color-bg-primary;
   padding: 32rpx 40rpx;
   padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
   box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.05);
@@ -661,7 +650,7 @@ $wx-orange: #FF9F00;
   .enable-button {
     width: 100%;
     height: 88rpx;
-    background: $wx-green;
+    background: $color-success;
     border-radius: 44rpx;
     border: none;
     display: flex;
@@ -670,7 +659,7 @@ $wx-orange: #FF9F00;
     margin-bottom: 20rpx;
     
     &[disabled] {
-      background: $wx-border;
+      background: $color-border;
       opacity: 0.6;
     }
 
@@ -698,13 +687,13 @@ $wx-orange: #FF9F00;
     
     .service-text {
       font-size: 24rpx;
-      color: $wx-text-hint;
+      color: $color-text-tertiary;
       margin-right: 8rpx;
     }
     
     .service-link {
       font-size: 24rpx;
-      color: $wx-green;
+      color: $color-success;
       font-weight: 500;
     }
   }

+ 107 - 98
haha-mp/src/pages/products/products.vue

@@ -103,7 +103,7 @@
     <view v-if="!loading && !errorMsg" class="bottom-bar">
       <view class="btn-back" @click="goBack">返回</view>
       <view class="bottom-bar-center">
-        <BrandSlogan type="standard" service-type="先享后付" :compact="true" />
+        <BrandSlogan type="standard" serviceType="先享后付" :compact="true" />
         <view class="btn-open" @click="handleOpenDoor">开门购物</view>
       </view>
     </view>
@@ -120,10 +120,8 @@ import { logger } from '@/utils/logger';
 import BrandSlogan from '@/components/BrandSlogan.vue';
 import type { FloorConfig } from '@/api/device';
 
-// 页面参数
 const deviceId = ref('');
 
-// 数据状态
 const loading = ref(true);
 const errorMsg = ref('');
 const templateName = ref('');
@@ -133,18 +131,10 @@ const leftFloors = ref<FloorConfig[]>([]);
 const rightFloors = ref<FloorConfig[]>([]);
 const opening = ref(false);
 
-// 双门柜切换
 const activeDoor = ref<'left' | 'right'>('left');
 
-// 是否双门柜
 const hasRightDoor = computed(() => rightFloors.value.length > 0);
 
-// 设备类型文字
-const deviceTypeText = computed(() => {
-  return hasRightDoor.value ? '双门柜' : '单门柜';
-});
-
-// 当前显示的层数据
 const currentFloors = computed(() => {
   if (activeDoor.value === 'right' && hasRightDoor.value) {
     return rightFloors.value;
@@ -152,26 +142,21 @@ const currentFloors = computed(() => {
   return leftFloors.value;
 });
 
-// 页面加载
 onLoad((options: any) => {
   let id = '';
 
   if (options?.deviceId) {
-    // 普通页面跳转传参
     id = options.deviceId;
   } else if (options?.q) {
-    // 微信扫码调起小程序,链接通过 q 参数传入(URL编码)
-    // 链接格式: https://dev-haha.kuaiyuman.cn/B150534
     try {
       const url = decodeURIComponent(options.q);
       logger.log('扫码链接:', url);
-      // 从 URL 路径中提取设备ID
       const match = url.match(/\/([A-Za-z0-9]+)(?:\?|$)/);
       if (match && match[1]) {
         id = match[1];
       }
     } catch (e) {
-      console.error('解析扫码链接失败:', e);
+      logger.error('解析扫码链接失败:', e);
     }
   }
 
@@ -185,7 +170,6 @@ onLoad((options: any) => {
   }
 });
 
-// 加载商品数据
 const loadProducts = async () => {
   loading.value = true;
   errorMsg.value = '';
@@ -198,18 +182,16 @@ const loadProducts = async () => {
     leftFloors.value = data.leftFloors || [];
     rightFloors.value = data.rightFloors || [];
   } catch (error: any) {
-    console.error('加载商品数据失败:', error);
+    logger.error('加载商品数据失败:', error);
     errorMsg.value = error.message || '加载失败,请稍后重试';
   } finally {
     loading.value = false;
   }
 };
 
-// 开门购物
 const handleOpenDoor = async () => {
   if (opening.value) return;
 
-  // 1. 登录校验
   if (!isLoggedIn()) {
     uni.showModal({
       title: '提示',
@@ -224,7 +206,6 @@ const handleOpenDoor = async () => {
     return;
   }
 
-  // 2. 支付分校验
   try {
     const payscoreResult = await checkPayscoreEnabled();
     if (!payscoreResult.enabled) {
@@ -234,14 +215,13 @@ const handleOpenDoor = async () => {
       return;
     }
   } catch (error: any) {
-    console.error('检查支付分状态失败:', error);
+    logger.error('检查支付分状态失败:', error);
     uni.navigateTo({
       url: '/pages/payscore/enable'
     });
     return;
   }
 
-  // 3. 执行开门
   opening.value = true;
 
   uni.showLoading({
@@ -258,13 +238,11 @@ const handleOpenDoor = async () => {
       icon: 'success'
     });
 
-    // 存储设备信息
     uni.setStorageSync('currentDeviceId', response.deviceId);
     uni.setStorageSync('currentOutTradeNo', response.outTradeNo);
     uni.setStorageSync('currentOrderNo', response.orderNo);
     uni.removeStorageSync('shoppingPollingActive');
 
-    // 跳转到购物页面
     setTimeout(() => {
       uni.redirectTo({
         url: '/pages/shopping/shopping'
@@ -273,13 +251,11 @@ const handleOpenDoor = async () => {
   } catch (error: any) {
     uni.hideLoading();
     opening.value = false;
-    console.error('开门失败:', error);
+    logger.error('开门失败:', error);
   }
 };
 
-// 返回
 const goBack = () => {
-  // 校验登录状态,未登录则跳转到登录页
   if (!isLoggedIn()) {
     uni.reLaunch({ url: '/pages/login/login' });
     return;
@@ -295,7 +271,7 @@ const goBack = () => {
 <style lang="scss" scoped>
 .products-page {
   min-height: 100vh;
-  background: #f5f5f5;
+  background: $color-bg-secondary;
 }
 
 /* 加载状态 */
@@ -306,21 +282,24 @@ const goBack = () => {
   justify-content: center;
   height: 60vh;
 }
+
 .loading-spinner {
   width: 60rpx;
   height: 60rpx;
-  border: 4rpx solid #e0e0e0;
-  border-top-color: #FFD700;
+  border: 4rpx solid $color-border;
+  border-top-color: $color-primary;
   border-radius: 50%;
   animation: spin 0.8s linear infinite;
 }
+
 @keyframes spin {
   to { transform: rotate(360deg); }
 }
+
 .loading-text {
   margin-top: 20rpx;
   font-size: 28rpx;
-  color: #999;
+  color: $color-text-secondary;
 }
 
 /* 错误状态 */
@@ -332,28 +311,31 @@ const goBack = () => {
   height: 60vh;
   padding: 0 60rpx;
 }
+
 .error-icon {
   width: 80rpx;
   height: 80rpx;
   line-height: 80rpx;
   text-align: center;
-  background: #ff4d4f;
+  background: $color-error;
   color: #fff;
   border-radius: 50%;
   font-size: 40rpx;
   font-weight: bold;
 }
+
 .error-text {
   margin-top: 24rpx;
   font-size: 28rpx;
-  color: #666;
+  color: $color-text-secondary;
   text-align: center;
 }
+
 .error-btn {
   margin-top: 40rpx;
   padding: 16rpx 60rpx;
-  background: #FFD700;
-  color: #333;
+  background: $color-primary;
+  color: $color-text-primary;
   border-radius: 40rpx;
   font-size: 28rpx;
   font-weight: bold;
@@ -362,71 +344,79 @@ const goBack = () => {
 /* 门Tab切换 */
 .door-tabs {
   display: flex;
-  background: #fff;
-  border-bottom: 1rpx solid #eee;
+  background: $color-bg-primary;
+  border-bottom: 1rpx solid $color-border;
 }
+
 .door-tab {
   flex: 1;
   text-align: center;
   padding: 24rpx 0;
   font-size: 28rpx;
-  color: #666;
+  color: $color-text-secondary;
   position: relative;
 }
+
 .door-tab.active {
-  color: #333;
+  color: $color-text-primary;
   font-weight: bold;
-}
-.door-tab.active::after {
-  content: '';
-  position: absolute;
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 60rpx;
-  height: 4rpx;
-  background: #FFD700;
-  border-radius: 2rpx;
+
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 60rpx;
+    height: 4rpx;
+    background: $color-primary;
+    border-radius: 2rpx;
+  }
 }
 
 /* 楼层列表 */
 .floors-list {
   padding: 20rpx;
 }
+
 .floor-card {
-  background: #fff;
-  border-radius: 16rpx;
+  background: $color-bg-primary;
+  border-radius: $radius-md;
   margin-bottom: 20rpx;
   overflow: hidden;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
+  box-shadow: $shadow-sm;
 }
+
 .floor-header {
   display: flex;
   align-items: center;
   padding: 20rpx 24rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  border-bottom: 1rpx solid $color-border;
 }
+
 .floor-badge {
   width: 44rpx;
   height: 44rpx;
   line-height: 44rpx;
   text-align: center;
-  background: #FFD700;
-  color: #333;
+  background: $color-primary;
+  color: $color-text-primary;
   border-radius: 10rpx;
   font-size: 24rpx;
   font-weight: bold;
   margin-right: 16rpx;
 }
+
 .floor-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: $color-text-primary;
   flex: 1;
 }
+
 .floor-count {
   font-size: 24rpx;
-  color: #999;
+  color: $color-text-secondary;
 }
 
 /* 空楼层 */
@@ -434,22 +424,25 @@ const goBack = () => {
   padding: 40rpx 0;
   text-align: center;
 }
+
 .empty-text {
   font-size: 26rpx;
-  color: #ccc;
+  color: $color-text-tertiary;
 }
 
 /* 商品列表 */
 .goods-list {
   padding: 0 24rpx;
 }
+
 .goods-item {
   display: flex;
   padding: 20rpx 0;
-  border-bottom: 1rpx solid #f5f5f5;
-}
-.goods-item:last-child {
-  border-bottom: none;
+  border-bottom: 1rpx solid $color-bg-secondary;
+
+  &:last-child {
+    border-bottom: none;
+  }
 }
 
 /* 商品图片 */
@@ -459,23 +452,26 @@ const goBack = () => {
   border-radius: 12rpx;
   overflow: hidden;
   flex-shrink: 0;
-  background: #f9f9f9;
+  background: $color-bg-tertiary;
 }
+
 .goods-image {
   width: 100%;
   height: 100%;
 }
+
 .goods-image-placeholder {
   width: 100%;
   height: 100%;
   display: flex;
   align-items: center;
   justify-content: center;
-  background: #f0f0f0;
+  background: $color-bg-tertiary;
 }
+
 .placeholder-text {
   font-size: 20rpx;
-  color: #ccc;
+  color: $color-text-tertiary;
 }
 
 /* 商品信息 */
@@ -486,9 +482,10 @@ const goBack = () => {
   flex-direction: column;
   justify-content: center;
 }
+
 .goods-name {
   font-size: 28rpx;
-  color: #333;
+  color: $color-text-primary;
   font-weight: 500;
   line-height: 1.4;
   display: -webkit-box;
@@ -496,39 +493,47 @@ const goBack = () => {
   -webkit-line-clamp: 2;
   overflow: hidden;
 }
+
 .goods-type {
   font-size: 22rpx;
-  color: #999;
+  color: $color-text-secondary;
   margin-top: 6rpx;
 }
+
 .goods-price-row {
   display: flex;
   align-items: baseline;
   gap: 12rpx;
   margin-top: 8rpx;
 }
+
 .goods-price {
   font-size: 32rpx;
-  color: #ff4d4f;
+  color: $color-error;
   font-weight: bold;
 }
+
 .goods-discount-price {
   font-size: 24rpx;
-  color: #999;
+  color: $color-text-secondary;
   text-decoration: line-through;
 }
+
 .goods-stock {
   margin-top: 6rpx;
   font-size: 22rpx;
 }
+
 .stock-label {
-  color: #999;
+  color: $color-text-secondary;
 }
+
 .stock-normal {
-  color: #52c41a;
+  color: $color-success;
 }
+
 .stock-empty {
-  color: #ff4d4f;
+  color: $color-error;
 }
 
 /* 无数据 */
@@ -538,19 +543,22 @@ const goBack = () => {
   align-items: center;
   padding: 80rpx 0;
 }
+
 .empty-icon {
   font-size: 80rpx;
 }
+
 .empty-main-text {
   margin-top: 20rpx;
-  font-size: 30rpx;
-  color: #333;
+  font-size: 28rpx;
+  color: $color-text-primary;
   font-weight: bold;
 }
+
 .empty-sub-text {
   margin-top: 8rpx;
-  font-size: 26rpx;
-  color: #999;
+  font-size: 24rpx;
+  color: $color-text-secondary;
 }
 
 /* 底部占位 */
@@ -569,19 +577,25 @@ const goBack = () => {
   gap: 20rpx;
   padding: 20rpx 30rpx;
   padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-  background: #fff;
-  box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.08);
+  background: $color-bg-primary;
+  box-shadow: $shadow-lg;
 }
+
 .btn-back {
   flex: 1;
   text-align: center;
   padding: 22rpx 0;
   border-radius: 44rpx;
-  font-size: 30rpx;
+  font-size: 28rpx;
   font-weight: bold;
-  color: #666;
-  background: #f0f0f0;
+  color: $color-text-secondary;
+  background: $color-bg-tertiary;
+
+  &:active {
+    background: $color-border;
+  }
 }
+
 .bottom-bar-center {
   flex: 2;
   display: flex;
@@ -589,26 +603,21 @@ const goBack = () => {
   align-items: center;
   gap: 10rpx;
 }
+
 .btn-open {
   width: 100%;
   text-align: center;
   padding: 22rpx 0;
   border-radius: 44rpx;
-  font-size: 30rpx;
+  font-size: 28rpx;
   font-weight: bold;
-  color: #333;
-  background: linear-gradient(135deg, #FFD700, #FFC107);
-  box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.4);
-  animation: breathe 2s ease-in-out infinite;
-}
-@keyframes breathe {
-  0%, 100% {
-    transform: scale(1);
-    box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.4);
-  }
-  50% {
-    transform: scale(1.04);
-    box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.7);
+  color: $color-text-primary;
+  background: linear-gradient(135deg, $color-primary-light, $color-primary);
+  box-shadow: $shadow-primary;
+
+  &:active {
+    opacity: 0.9;
+    transform: scale(0.98);
   }
 }
 </style>

+ 125 - 255
haha-mp/src/pages/refund/refund.vue

@@ -4,12 +4,11 @@
     <view class="section-block product-section">
       <text class="section-title">选择退款商品</text>
       <view class="product-list">
-        <view 
-          v-for="(product, index) in orderInfo.products" 
-          :key="index" 
+        <view
+          v-for="(product, index) in orderInfo.products"
+          :key="index"
           :class="['product-item', { 'product-item-selected': selectedProducts.includes(index) }]"
         >
-          <!-- 商品主体信息 -->
           <view class="product-main" @click="toggleProductSelect(index)">
             <view class="product-checkbox">
               <view :class="['checkbox-icon', { checked: selectedProducts.includes(index) }]">
@@ -31,14 +30,13 @@
               </view>
             </view>
           </view>
-          
-          <!-- 退款数量选择器(仅当商品被选中时显示,独立一行) -->
+
           <view v-if="selectedProducts.includes(index)" class="refund-quantity-selector">
             <view class="selector-row">
               <text class="selector-label">退款数量</text>
               <view class="quantity-control">
-                <view 
-                  :class="['control-btn', (product.refundQuantity || 1) > 1 ? '' : 'disabled']" 
+                <view
+                  :class="['control-btn', (product.refundQuantity || 1) > 1 ? '' : 'disabled']"
                   @click.stop="decreaseQuantity(index)"
                 >
                   <text>-</text>
@@ -46,8 +44,8 @@
                 <view class="quantity-display">
                   <text class="quantity-text">{{ product.refundQuantity || 1 }}</text>
                 </view>
-                <view 
-                  :class="['control-btn', (product.refundQuantity || 1) < product.quantity ? '' : 'disabled']" 
+                <view
+                  :class="['control-btn', (product.refundQuantity || 1) < product.quantity ? '' : 'disabled']"
                   @click.stop="increaseQuantity(index)"
                 >
                   <text>+</text>
@@ -65,7 +63,7 @@
         已选择 {{ selectedProducts.length }} 件商品,共 {{ refundQuantity }} 件
       </view>
     </view>
-    
+
     <!-- 退款原因 -->
     <view class="section-block reason-section">
       <text class="section-title">退款原因</text>
@@ -77,11 +75,10 @@
           <text class="radio-label">{{ reason.label }}</text>
         </label>
       </radio-group>
-      
-      <!-- 其他原因的文本输入框 -->
+
       <view v-if="selectedReason === '4'" class="remark-input-wrapper">
-        <textarea 
-          v-model="refundRemark" 
+        <textarea
+          v-model="refundRemark"
           class="remark-textarea"
           placeholder="请详细描述您的问题或投诉内容"
           maxlength="100"
@@ -90,7 +87,7 @@
         <text class="remark-count">{{ refundRemark.length }}/100</text>
       </view>
     </view>
-    
+
     <!-- 退款信息 -->
     <view class="section-block info-section">
       <text class="section-title">退款信息</text>
@@ -115,14 +112,14 @@
         <text :class="['info-value', isRefundExpired ? 'expired' : '']">{{ refundExpireText }}</text>
       </view>
     </view>
-    
+
     <!-- 提示信息 -->
     <view class="section-block tip-section">
       <text class="tip-text">• 请选择需要退款的商品</text>
       <text class="tip-text">• 退款申请提交后,工作人员将尽快处理</text>
       <text class="tip-text">• 退款金额将原路返回至您的支付账户</text>
     </view>
-    
+
     <!-- 确认提交按钮 -->
     <button class="submit-btn" @click="submitRefund">确认提交</button>
 
@@ -146,11 +143,10 @@ import { checkAuth } from '../../utils/auth';
 import { getOrderDetail } from '../../api/order';
 import type { OrderInfo } from '../../api/order';
 import { post } from '../../utils/request';
+import { logger } from '../../utils/logger';
 
-// 路由参数
 const orderId = ref<string>('');
 
-// 订单信息
 const orderInfo = ref<OrderInfo>({
   id: '',
   orderNo: '',
@@ -164,10 +160,8 @@ const orderInfo = ref<OrderInfo>({
   products: []
 });
 
-// 选中的商品索引列表
 const selectedProducts = ref<number[]>([]);
 
-// 退款数量(根据选中商品的退款数量计算)
 const refundQuantity = computed(() => {
   return selectedProducts.value.reduce((sum, index) => {
     const product = orderInfo.value.products[index];
@@ -175,7 +169,6 @@ const refundQuantity = computed(() => {
   }, 0);
 });
 
-// 退款金额(根据选中商品的退款数量计算)
 const refundAmount = computed(() => {
   return selectedProducts.value.reduce((sum, index) => {
     const product = orderInfo.value.products[index];
@@ -186,27 +179,21 @@ const refundAmount = computed(() => {
   }, 0);
 });
 
-// 判断是否超过退款期限
 const isRefundExpired = computed(() => {
   if (!orderInfo.value.payTime) return false;
-  
   const payTime = new Date(orderInfo.value.payTime);
   const now = new Date();
   const diffTime = now.getTime() - payTime.getTime();
   const diffDays = diffTime / (1000 * 60 * 60 * 24);
-  
   return diffDays > 7;
 });
 
-// 退款期限提示文本
 const refundExpireText = computed(() => {
   if (!orderInfo.value.payTime) return '';
-  
   const payTime = new Date(orderInfo.value.payTime);
   const now = new Date();
   const diffTime = now.getTime() - payTime.getTime();
   const diffDays = diffTime / (1000 * 60 * 60 * 24);
-  
   if (diffDays > 7) {
     return '已超过退款期限';
   } else {
@@ -227,23 +214,17 @@ const refundReasons = [
 
 const handleReasonChange = (e: any) => {
   selectedReason.value = e.detail.value;
-  // 如果切换到非"其他"原因,清空备注内容
   if (selectedReason.value !== '4') {
     refundRemark.value = '';
   }
 };
 
-/**
- * 切换商品选中状态
- */
 const toggleProductSelect = (index: number) => {
   const selectedIndex = selectedProducts.value.indexOf(index);
   if (selectedIndex > -1) {
-    // 取消选中:清除退款数量
     orderInfo.value.products[index].refundQuantity = undefined;
     selectedProducts.value.splice(selectedIndex, 1);
   } else {
-    // 选中:初始化退款数量为 1
     if (!orderInfo.value.products[index].refundQuantity) {
       orderInfo.value.products[index].refundQuantity = 1;
     }
@@ -251,9 +232,6 @@ const toggleProductSelect = (index: number) => {
   }
 };
 
-/**
- * 显示商品全称
- */
 const showProductName = (productName: string) => {
   uni.showModal({
     title: '商品名称',
@@ -263,9 +241,6 @@ const showProductName = (productName: string) => {
   });
 };
 
-/**
- * 减少退款数量
- */
 const decreaseQuantity = (index: number) => {
   const product = orderInfo.value.products[index];
   if (product.refundQuantity && product.refundQuantity > 1) {
@@ -273,9 +248,6 @@ const decreaseQuantity = (index: number) => {
   }
 };
 
-/**
- * 增加退款数量
- */
 const increaseQuantity = (index: number) => {
   const product = orderInfo.value.products[index];
   if ((product.refundQuantity || 1) < product.quantity) {
@@ -287,130 +259,73 @@ const increaseQuantity = (index: number) => {
   }
 };
 
-/**
- * 显示商品详情
- */
-const showProductDetail = () => {
-  // TODO: [sku-detail] 跳转到商品详情页或弹出商品详情弹窗
-  uni.showToast({
-    title: '商品详情功能开发中',
-    icon: 'none'
-  });
-};
-
-/**
- * 加载订单详情
- */
 const loadOrderDetail = async () => {
   if (!orderId.value) {
-    uni.showToast({
-      title: '订单 ID 缺失',
-      icon: 'none'
-    });
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 1500);
+    uni.showToast({ title: '订单 ID 缺失', icon: 'none' });
+    setTimeout(() => { uni.navigateBack(); }, 1500);
     return;
   }
 
   try {
     const detail = await getOrderDetail({ orderId: orderId.value });
     orderInfo.value = detail;
-    
-    // 检查订单是否超过退款期限(7天)
+
     if (detail.payTime) {
       const payTime = new Date(detail.payTime);
       const now = new Date();
       const diffTime = now.getTime() - payTime.getTime();
       const diffDays = diffTime / (1000 * 60 * 60 * 24);
-      
+
       if (diffDays > 7) {
         uni.showModal({
           title: '退款期限已过',
           content: '该订单已完成超过7天,不支持申请退款',
           showCancel: false,
           confirmText: '知道了',
-          success: () => {
-            uni.navigateBack();
-          }
+          success: () => { uni.navigateBack(); }
         });
         return;
       }
     }
-    
-    // 默认不选中任何商品(移除之前的默认全选逻辑)
+
     selectedProducts.value = [];
   } catch (error) {
-    console.error('加载订单详情失败:', error);
-    uni.showToast({
-      title: '加载订单详情失败',
-      icon: 'none'
-    });
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 1500);
+    logger.error('加载订单详情失败:', error);
+    uni.showToast({ title: '加载订单详情失败', icon: 'none' });
+    setTimeout(() => { uni.navigateBack(); }, 1500);
   }
 };
 
-/**
- * 页面加载时检查登录状态并加载订单详情
- */
 onMounted(() => {
-  // 检查登录状态,未登录会自动跳转到登录页
   checkAuth();
 });
 
-/**
- * 监听页面加载参数
- */
 onLoad((options) => {
   if (options && options.orderId) {
-    // 直接使用字符串类型的订单ID,避免精度丢失
     orderId.value = String(options.orderId);
     loadOrderDetail();
   } else {
-    uni.showToast({
-      title: '订单 ID 缺失',
-      icon: 'none'
-    });
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 1500);
+    uni.showToast({ title: '订单 ID 缺失', icon: 'none' });
+    setTimeout(() => { uni.navigateBack(); }, 1500);
   }
 });
 
-/**
- * 提交退款申请
- */
 const submitRefund = async () => {
-  // 校验是否选择了商品
   if (selectedProducts.value.length === 0) {
-    uni.showToast({
-      title: '请选择要退款的商品',
-      icon: 'none'
-    });
+    uni.showToast({ title: '请选择要退款的商品', icon: 'none' });
     return;
   }
 
-  // 校验退款原因
   if (!selectedReason.value) {
-    uni.showToast({
-      title: '请选择退款原因',
-      icon: 'none'
-    });
+    uni.showToast({ title: '请选择退款原因', icon: 'none' });
     return;
   }
 
-  // 如果选择"其他"原因,校验备注内容
   if (selectedReason.value === '4' && !refundRemark.value.trim()) {
-    uni.showToast({
-      title: '请填写投诉内容',
-      icon: 'none'
-    });
+    uni.showToast({ title: '请填写投诉内容', icon: 'none' });
     return;
   }
 
-  // 映射前端退款原因到后端描述
   const reasonMap: Record<string, string> = {
     '1': '订单扣款错误',
     '2': '扣款金额与实际金额不符',
@@ -419,55 +334,44 @@ const submitRefund = async () => {
   };
 
   let reasonText = reasonMap[selectedReason.value] || '用户申请退款';
-  
-  // 如果是"其他"原因,将备注内容附加到原因后面
+
   if (selectedReason.value === '4' && refundRemark.value.trim()) {
     reasonText = `其他:${refundRemark.value.trim()}`;
   }
 
-  // 构建选中的商品信息(包含退款数量)
   const selectedProductIds = selectedProducts.value.map(index => {
     const product = orderInfo.value.products[index];
     return {
       productId: product.id,
       productName: product.name,
-      quantity: product.refundQuantity || 1, // 使用用户选择的退款数量
+      quantity: product.refundQuantity || 1,
       price: product.price
     };
   });
 
   try {
-    // 调用后端退款 API
-    const result = await post('/payment/refund', {
+    await post('/payment/refund', {
       orderId: orderId.value,
       reason: reasonText,
-      products: selectedProductIds // 传递选中的商品信息
+      products: selectedProductIds
     });
 
-    uni.showToast({
-      title: '退款申请成功',
-      icon: 'success'
-    });
-
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 1500);
+    uni.showToast({ title: '退款申请成功', icon: 'success' });
+    setTimeout(() => { uni.navigateBack(); }, 1500);
   } catch (error: any) {
-    console.error('退款申请失败:', error);
-    // 错误提示已在 request 拦截器中处理
+    logger.error('退款申请失败:', error);
   }
 };
 </script>
 
-<style>
+<style lang="scss">
 .container {
   min-height: 100vh;
-  background-color: #f5f5f5;
+  background-color: $color-bg-secondary;
   padding-top: 20rpx;
   padding-bottom: 40rpx;
 }
 
-/* 商品选择区域 */
 .product-section {
   margin-bottom: 20rpx;
   padding-bottom: 20rpx;
@@ -482,7 +386,7 @@ const submitRefund = async () => {
 .product-item {
   display: flex;
   flex-direction: column;
-  background-color: #ffffff;
+  background-color: $color-bg-primary;
   border-radius: 12rpx;
   padding: 24rpx;
   border: 2rpx solid transparent;
@@ -490,12 +394,12 @@ const submitRefund = async () => {
 }
 
 .product-item-selected {
-  border-color: #07c160;
+  border-color: $color-success;
   background-color: #f6ffed;
 }
 
 .product-item:active {
-  background-color: #fafafa;
+  background-color: $color-bg-secondary;
 }
 
 .product-main {
@@ -513,23 +417,23 @@ const submitRefund = async () => {
   width: 44rpx;
   height: 44rpx;
   border-radius: 50%;
-  border: 3rpx solid #dddddd;
+  border: 3rpx solid $color-border;
   display: flex;
   align-items: center;
   justify-content: center;
   transition: all 0.3s ease;
-}
 
-.checkbox-icon.checked {
-  background-color: #07c160;
-  border-color: #07c160;
-  box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.3);
-}
+  &.checked {
+    background-color: $color-success;
+    border-color: $color-success;
+    box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.3);
 
-.checkbox-icon text {
-  color: #ffffff;
-  font-size: 26rpx;
-  font-weight: bold;
+    text {
+      color: #ffffff;
+      font-size: 26rpx;
+      font-weight: bold;
+    }
+  }
 }
 
 .product-image {
@@ -539,23 +443,23 @@ const submitRefund = async () => {
   overflow: hidden;
   flex-shrink: 0;
   margin-right: 20rpx;
-  background-color: #f5f5f5;
-  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
-}
+  background-color: $color-bg-tertiary;
+  box-shadow: $shadow-sm;
 
-.product-image image {
-  width: 100%;
-  height: 100%;
+  image {
+    width: 100%;
+    height: 100%;
+  }
 }
 
 .image-placeholder {
   width: 100%;
   height: 100%;
-  background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
+  background: linear-gradient(135deg, $color-bg-tertiary, $color-border);
   display: flex;
   align-items: center;
   justify-content: center;
-  color: #999999;
+  color: $color-text-secondary;
   font-size: 40rpx;
 }
 
@@ -572,13 +476,12 @@ const submitRefund = async () => {
   position: relative;
   display: flex;
   align-items: center;
-  cursor: pointer;
   max-width: 100%;
 }
 
 .product-name {
   font-size: 28rpx;
-  color: #333333;
+  color: $color-text-primary;
   line-height: 1.4;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -589,7 +492,7 @@ const submitRefund = async () => {
 
 .expand-tip {
   font-size: 26rpx;
-  color: #1890ff;
+  color: $color-info;
   margin-left: 8rpx;
   flex-shrink: 0;
   padding: 4rpx 12rpx;
@@ -607,19 +510,14 @@ const submitRefund = async () => {
 
 .product-price {
   font-size: 32rpx;
-  color: #ff4400;
+  color: $color-error;
   font-weight: 600;
 }
 
-.product-quantity {
-  font-size: 24rpx;
-  color: #999999;
-}
-
 .product-quantity-label {
   font-size: 24rpx;
-  color: #999999;
-  background-color: #f5f5f5;
+  color: $color-text-secondary;
+  background-color: $color-bg-tertiary;
   padding: 4rpx 12rpx;
   border-radius: 6rpx;
 }
@@ -642,7 +540,7 @@ const submitRefund = async () => {
 
 .selector-label {
   font-size: 28rpx;
-  color: #52c41a;
+  color: $color-success;
   font-weight: 500;
   flex-shrink: 0;
 }
@@ -657,31 +555,30 @@ const submitRefund = async () => {
   width: 60rpx;
   height: 60rpx;
   border-radius: 12rpx;
-  background-color: #ffffff;
-  border: 2rpx solid #e0e0e0;
+  background-color: $color-bg-primary;
+  border: 2rpx solid $color-border;
   display: flex;
   align-items: center;
   justify-content: center;
   font-size: 36rpx;
-  color: #333333;
+  color: $color-text-primary;
   transition: all 0.2s;
-}
 
-.control-btn:active {
-  background-color: #f0f0f0;
-  transform: scale(0.95);
-}
+  &:active {
+    background-color: $color-bg-tertiary;
+    transform: scale(0.95);
+  }
 
-.control-btn.disabled {
-  background-color: #fafafa;
-  border-color: #f0f0f0;
-  color: #cccccc;
-  cursor: not-allowed;
-}
+  &.disabled {
+    background-color: $color-bg-secondary;
+    border-color: $color-bg-tertiary;
+    color: $color-text-tertiary;
 
-.control-btn.disabled:active {
-  background-color: #fafafa;
-  transform: none;
+    &:active {
+      background-color: $color-bg-secondary;
+      transform: none;
+    }
+  }
 }
 
 .quantity-display {
@@ -691,15 +588,15 @@ const submitRefund = async () => {
   display: flex;
   align-items: center;
   justify-content: center;
-  background-color: #ffffff;
-  border: 2rpx solid #07c160;
+  background-color: $color-bg-primary;
+  border: 2rpx solid $color-success;
   border-radius: 12rpx;
   padding: 0 16rpx;
 }
 
 .quantity-text {
   font-size: 32rpx;
-  color: #07c160;
+  color: $color-success;
   font-weight: 600;
 }
 
@@ -715,45 +612,25 @@ const submitRefund = async () => {
 
 .preview-label {
   font-size: 26rpx;
-  color: #999999;
+  color: $color-text-secondary;
 }
 
 .preview-value {
   font-size: 30rpx;
-  color: #ff4400;
+  color: $color-error;
   font-weight: 600;
 }
 
-/* 退款商品选择器 */
-.section-item {
-  background-color: #ffffff;
-  padding: 28rpx 30rpx;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 20rpx;
-}
-
-.section-label {
-  font-size: 30rpx;
-  color: #333333;
-}
-
-.arrow-right {
-  font-size: 32rpx;
-  color: #cccccc;
-}
-
 /* 区域块 */
 .section-block {
-  background-color: #ffffff;
+  background-color: $color-bg-primary;
   padding: 30rpx;
   margin-bottom: 20rpx;
 }
 
 .section-title {
   font-size: 30rpx;
-  color: #333333;
+  color: $color-text-primary;
   font-weight: 500;
   margin-bottom: 30rpx;
   display: block;
@@ -768,25 +645,25 @@ radio-group {
   display: flex;
   align-items: center;
   margin-bottom: 32rpx;
-}
 
-.radio-item:last-child {
-  margin-bottom: 0;
+  &:last-child {
+    margin-bottom: 0;
+  }
 }
 
 .radio-wrapper {
   margin-right: 16rpx;
   display: flex;
   align-items: center;
-}
 
-.radio-wrapper radio {
-  transform: scale(1.1);
+  radio {
+    transform: scale(1.1);
+  }
 }
 
 .radio-label {
   font-size: 28rpx;
-  color: #333333;
+  color: $color-text-primary;
   line-height: 1.5;
 }
 
@@ -803,24 +680,24 @@ radio-group {
   width: 100%;
   min-height: 160rpx;
   padding: 20rpx;
-  background-color: #ffffff;
+  background-color: $color-bg-primary;
   border-radius: 8rpx;
-  border: 2rpx solid #e0e0e0;
+  border: 2rpx solid $color-border;
   font-size: 28rpx;
-  color: #333333;
+  color: $color-text-primary;
   line-height: 1.6;
   box-sizing: border-box;
-}
 
-.remark-textarea:focus {
-  border-color: #1890ff;
+  &:focus {
+    border-color: $color-info;
+  }
 }
 
 .remark-count {
   display: block;
   text-align: right;
   font-size: 24rpx;
-  color: #999999;
+  color: $color-text-secondary;
   margin-top: 12rpx;
 }
 
@@ -837,55 +714,48 @@ radio-group {
 
 .info-label {
   font-size: 28rpx;
-  color: #999999;
+  color: $color-text-secondary;
   min-width: 200rpx;
 }
 
 .info-value {
   font-size: 28rpx;
-  color: #333333;
+  color: $color-text-primary;
   flex: 1;
   text-align: right;
+
+  &.expired {
+    color: $color-error;
+    font-weight: 600;
+  }
 }
 
-.info-value.expired {
-  color: #ff4444;
-  font-weight: 600;
+.refund-amount {
+  color: $color-error;
+  font-weight: 500;
 }
 
 /* 提交按钮 */
 .submit-btn {
   margin: 40rpx 30rpx;
-  background-color: #f8f8f8;
-  border: 1rpx solid #e8e8e8;
+  background-color: $color-bg-secondary;
+  border: 1rpx solid $color-border;
   border-radius: 10rpx;
   font-size: 32rpx;
-  color: #333333;
+  color: $color-text-primary;
   padding: 24rpx 0;
   line-height: 1;
   font-weight: 500;
-}
-
-.submit-btn::after {
-  border: none;
-}
 
-/* 商品信息 */
-.product-info {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-}
-
-.refund-amount {
-  color: #ff4400;
-  font-weight: 500;
+  &::after {
+    border: none;
+  }
 }
 
 /* 提示信息 */
 .tip-section {
-  background-color: #fffbea;
-  border: 1rpx solid #ffe58f;
+  background-color: $color-primary-bg;
+  border: 1rpx solid $color-primary-light;
   border-radius: 8rpx;
   padding: 24rpx 30rpx;
   margin: 30rpx;
@@ -894,14 +764,14 @@ radio-group {
 .tip-text {
   display: block;
   font-size: 24rpx;
-  color: #faad14;
+  color: $color-warning;
   line-height: 1.8;
 }
 
 .selected-tip {
   text-align: right;
   font-size: 26rpx;
-  color: #07c160;
+  color: $color-success;
   margin-top: 20rpx;
   padding: 16rpx 20rpx;
   background-color: #f6ffed;
@@ -914,4 +784,4 @@ radio-group {
   padding: 20rpx 0 60rpx;
   font-size: 26rpx;
 }
-</style>
+</style>

+ 4 - 4
haha-mp/src/pages/replenish/bind.vue

@@ -164,7 +164,7 @@ const handleBind = async () => {
           resolve(res);
         },
         fail: (err) => {
-          console.error('[补货员绑定] 微信授权失败:', err);
+          logger.error('[补货员绑定] 微信授权失败:', err);
           reject(err);
         }
       });
@@ -198,7 +198,7 @@ const handleBind = async () => {
       uni.reLaunch({ url: '/pages/index/index' });
     }, 2000);
   } catch (error: any) {
-    console.error('[补货员绑定] 绑定失败:', error);
+    logger.error('[补货员绑定] 绑定失败:', error);
     step.value = 1;
     // 错误已在 request.ts 中统一 toast
   } finally {
@@ -469,7 +469,7 @@ const handleBind = async () => {
     align-items: center;
     justify-content: center;
     margin-bottom: 32rpx;
-    animation: bounceIn 0.3s $ease-out;
+    animation: pop-in 0.3s $ease-out;
 
     .success-check {
       font-size: 72rpx;
@@ -539,7 +539,7 @@ const handleBind = async () => {
   to { opacity: 1; }
 }
 
-@keyframes bounceIn {
+@keyframes pop-in {
   0% {
     transform: scale(0);
     opacity: 0;

+ 1 - 1
haha-mp/src/pages/shopping/shopping.vue

@@ -267,7 +267,7 @@ const startStatusPolling = async () => {
       }
     }
   } catch (error: any) {
-    console.error('状态轮询失败:', error);
+    logger.error('状态轮询失败:', error);
     
     // 再次检查组件是否仍然活跃
     if (!isComponentActive) {