瀏覽代碼

运营端小程序页面优化

skyline 2 周之前
父節點
當前提交
02fd12659e
共有 4 個文件被更改,包括 632 次插入306 次删除
  1. 251 0
      haha-admin-mp/DESIGN.md
  2. 35 0
      haha-admin-mp/PRODUCT.md
  3. 199 178
      haha-admin-mp/src/pages/orders/detail.vue
  4. 147 128
      haha-admin-mp/src/pages/orders/list.vue

+ 251 - 0
haha-admin-mp/DESIGN.md

@@ -0,0 +1,251 @@
+---
+name: haha-admin-mp
+description: AI 智能零售柜运营管理端,与 haha-mp 消费者端共享暖金黄品牌语言
+colors:
+  amber-primary: "#FFC107"
+  amber-light: "#FFE082"
+  amber-dark: "#FFA000"
+  amber-bg: "#FFF8E1"
+  orange-accent: "#f97316"
+  orange-accent-bg: "#fff7ed"
+  sky-info: "#0ea5e9"
+  sky-info-bg: "#f0f9ff"
+  green-success: "#10b981"
+  green-success-bg: "#ecfdf5"
+  amber-warning: "#f59e0b"
+  amber-warning-bg: "#fffbeb"
+  red-error: "#ef4444"
+  red-error-bg: "#fef2f2"
+  slate-text-primary: "#1e293b"
+  slate-text-secondary: "#475569"
+  slate-text-tertiary: "#64748b"
+  slate-text-muted: "#94a3b8"
+  slate-text-placeholder: "#cbd5e1"
+  ash-page-bg: "#f8fafc"
+  white-card-bg: "#ffffff"
+  ash-secondary-bg: "#f1f5f9"
+  ash-border: "#e2e8f0"
+  ash-border-light: "#f1f5f9"
+typography:
+  body:
+    fontFamily: "-apple-system, BlinkMacSystemFont, PingFang SC, Helvetica Neue, system-ui, sans-serif"
+    fontSize: "28rpx"
+    fontWeight: 400
+    lineHeight: 1.6
+  title:
+    fontWeight: 600
+    fontSize: "28rpx"
+  headline:
+    fontWeight: 600
+    fontSize: "30rpx"
+  display:
+    fontWeight: 700
+    fontSize: "32rpx"
+  label:
+    fontWeight: 500
+    fontSize: "22rpx"
+    letterSpacing: "0.02em"
+rounded:
+  xs: "4rpx"
+  sm: "8rpx"
+  base: "12rpx"
+  md: "16rpx"
+  lg: "20rpx"
+  xl: "24rpx"
+  full: "9999rpx"
+spacing:
+  "1": "8rpx"
+  "2": "16rpx"
+  "3": "24rpx"
+  "4": "32rpx"
+  "5": "40rpx"
+  "6": "48rpx"
+  "7": "56rpx"
+  "8": "64rpx"
+components:
+  button-primary:
+    backgroundColor: "{colors.amber-primary}"
+    textColor: "{colors.slate-text-primary}"
+    rounded: "{rounded.xl}"
+    padding: "28rpx 48rpx"
+  button-primary-hover:
+    backgroundColor: "{colors.amber-dark}"
+  button-secondary:
+    backgroundColor: "{colors.ash-secondary-bg}"
+    textColor: "{colors.slate-text-primary}"
+    rounded: "{rounded.base}"
+  card-surface:
+    backgroundColor: "{colors.white-card-bg}"
+    rounded: "{rounded.md}"
+  status-tag-success:
+    backgroundColor: "{colors.green-success-bg}"
+    textColor: "{colors.green-success}"
+    rounded: "{rounded.sm}"
+    padding: "8rpx 16rpx"
+  status-tag-warning:
+    backgroundColor: "{colors.amber-warning-bg}"
+    textColor: "{colors.amber-warning}"
+    rounded: "{rounded.sm}"
+    padding: "8rpx 16rpx"
+  status-tag-error:
+    backgroundColor: "{colors.red-error-bg}"
+    textColor: "{colors.red-error}"
+    rounded: "{rounded.sm}"
+    padding: "8rpx 16rpx"
+---
+
+# Design System: haha-admin-mp
+
+## 1. Overview
+
+**Creative North Star: "The Warm Workshop"**
+
+haha-admin-mp 是 AI 智能零售柜系统的运营管理端。品牌语言与 haha-mp 消费者端一脉相承——琥珀暖金作为身份色,大面积留白托底,轻阴影赋予卡片可触感。设计不追求传统后台的功能堆砌,而是在移动端上呈现清晰、温暖、高效的信息结构。
+
+暖金不是装饰色,是信号色。它在关键操作(主按钮、激活态、扫码入口)上集中出现,其余区域用冷灰中性色后退,让操作者凭色彩就能感知界面优先级。物理场景锚定:运营人员在自然光或室内照明下使用手机,需要快速扫读——对比度、间距和字体层级比装饰更重要。
+
+PRODUCT.md 的反参考体系直接生效:纯黑纯白被禁用,所有中性色向暖金方向偏移 0.01 chroma;无传统 ERP 的密集表格和多级菜单;无 ThemeForest 式的过度阴影和杂色装饰。
+
+**Key Characteristics:**
+- 暖金信号:琥珀色承载关键操作和选中态,其余区域用冷灰后退
+- 轻提感:卡片统一使用 2rpx 模糊 12rpx 偏移的微阴影,暗示可点击性
+- 移动优先文字:最小字号不低於 22rpx,触控区域不小于 44x44pt
+- 8rpx 基准间距:所有间距为该基准的整数倍,拒绝随意值
+- 与 haha-mp 共享色彩基因:相同的暖金主色、相同的中性色标尺、相同的圆角体系
+
+## 2. Colors
+
+暖金与冷灰的双轨系统。琥珀色系承担品牌身份和功能信号,石板灰色系承担文字层级和背景区分。功能色(绿/红/橙/蓝)各司其职,不混用。
+
+### Primary
+- **Amber Primary** (#FFC107): 主按钮背景、选中态文字、激活的 Tab 下划线、扫码 FAB 背景。作为品牌身份色,出现在约 30% 的交互表面。
+- **Amber Light** (#FFE082): 次级暖黄点缀。极少独立使用,主要用于渐变过渡。
+- **Amber Dark** (#FFA000): 按钮 hover/active 态、强调文字。Primary 的加重版本。
+- **Amber Background** (#FFF8E1): 头像背景、选中态标记背景、status tag 的暖调底。
+
+### Secondary
+- **Orange Accent** (#f97316): 数据高亮(设备数量、任务次数等元数据值)。用量控制在 5% 以内。
+- **Orange Accent Background** (#fff7ed): Accent 的衬底,用于 subtle 的高亮区域。
+
+### Semantic
+- **Green Success** (#10b981): 已完成/正常/在线状态。仅用于状态指示,不作为主按钮色。
+- **Red Error** (#ef4444): 退款标签、故障状态、删除按钮文字。不用于大面积的统计数字(避免财务数据误读为负值)。
+- **Amber Warning** (#f59e0b): 待支付/未完成状态。与 Primary 同色系但偏红,形成功能区分。
+- **Sky Info** (#0ea5e9): 中性信息提示。极少使用。
+
+### Neutral
+- **Slate Text Primary** (#1e293b): 主文字。用于标题、金额、关键信息。
+- **Slate Text Secondary** (#475569): 次要文字。用于标签、辅助说明。
+- **Slate Text Muted** (#94a3b8): 弱化文字。用于 meta 信息、时间戳、占位符。
+- **Slate Text Placeholder** (#cbd5e1): 输入框占位文字、禁用态文字。
+- **Ash Page Background** (#f8fafc): 全局页面背景。不与卡片混用。
+- **White Card Background** (#ffffff): 所有卡片、列表项、导航栏、底部栏的表面色。
+- **Ash Secondary Background** (#f1f5f9): 输入框填充、空状态图标背景、加载占位。
+
+### Named Rules
+**The Golden Signal Rule.** 暖金仅在交互关键点出现:主按钮、选中态、激活 Tab。其稀缺性制造信号效应。切勿将暖金用于大面积装饰或非交互内容。
+
+**The Tinted Neutral Rule.** 所有中性色向暖金方向偏移 0.005-0.01 chroma。纯黑 `#000000` 和纯白 `#ffffff` 被禁用。视频背景使用 `--bg-secondary` 而非 `#000`。
+
+**The Financial Data Rule.** 金额、统计数字永远使用 `slate-text-primary`,不使用红色或绿色。颜色仅携带语义状态(成功/失败),不携带价值判断。
+
+## 3. Typography
+
+**Body Font:** -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif
+
+系统字体栈,利用各平台的优化渲染。中文优先使用苹方,西文和数字使用 SF Pro / Segoe UI。不引入外部 Web Font,避免小程序加载开销。
+
+**Character:** 工具感中性无衬线。字号层次通过 weight 和 size 的 ≥1.25 比例拉开,避免扁平淡薄。
+
+### Hierarchy
+- **Display** (700, 32rpx, 1.3): 订单金额、统计大数。仅在关键数据上出现。
+- **Headline** (600, 30rpx, 1.4): 页面标题、卡片标题、弹窗标题。
+- **Title** (600, 28rpx, 1.5): 主要正文、列表项名称、标签。使用最广泛的字号。
+- **Body** (400, 28rpx, 1.6): 正文信息、表单输入、描述文字。行高 1.6 确保中文可读性。
+- **Label** (500, 22rpx, 0.02em letter-spacing, 1.4): 标签、徽章、meta 文字。全小写或全大写均可,保持一致性。
+
+### Named Rules
+**The 28rpx Anchor Rule.** 正文基线和主要控件字号均锚定在 28rpx。向上取 30/32/36rpx 做标题,向下取 22/24rpx 做辅助。绝不使用 26rpx——它作为无意义的中间值被禁用。
+
+**The One Font Rule.** 整个系统使用单一字体栈。层次差异通过字号和 weight 表达,不通过字体切换表达。
+
+## 4. Elevation
+
+Subtle lift 策略。所有可交互的卡片和表面在静止态带有统一的微阴影 `0 2rpx 12rpx rgba(0,0,0,0.05)`,暗示可点击性和信息块边界。阴影的角色是结构的而非装饰的——区分表面层次,而非制造浮动效果。
+
+固定元素(底部操作栏、TabBar)使用方向性阴影指向上方 `0 -2rpx 12rpx rgba(0,0,0,0.05)`。
+
+### Shadow Vocabulary
+- **Card Rest** (`box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05)`): 所有卡片、列表卡片在静止态。最常用的层级。
+- **Card Hover/Press** (`box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04)`): 原阴影去除了 elevation,active 态通过 `opacity: 0.7` 补充反馈。
+- **Bottom Bar** (`box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.05)`): 固定在底部的操作栏。
+- **Primary Glow** (`box-shadow: 0 6rpx 20rpx rgba(255,193,7,0.4)`): 仅用于扫码 FAB 按钮。唯一的彩色阴影——暖金散射,强调该按钮的首要地位。
+- **Page Background**: `#f8fafc` 无阴影。通过色值与白色卡片自然区分。
+
+### Named Rules
+**The Shadows-As-State Rule.** 阴影在静止态统一微弱,在交互态通过 opacity 而非 elevation 变化反馈。不做悬浮升起动画。FAB 的暖金 glow shadow 是唯一的例外,其存在理由是该按钮是整个 TabBar 的视觉锚点。
+
+## 5. Components
+
+### Buttons
+- **Shape:** 全圆角胶囊形(24rpx-48rpx 半径),无直角。
+- **Primary:** 琥珀填充 `#FFC107`,文字 `#1e293b` 60% weight,padding 28rpx 48rpx。Active 态 opacity 降至 0.8。用于页面级主操作(保存、退款、确认)。
+- **Secondary/Ghost:** `#f1f5f9` 填充,无边框,padding 8-16rpx。用于卡片内的次级操作(查看详情、筛选、复制)。
+- **Call-to-Action Fab:** 80rpx 圆形,暖黄渐变背景,带 `0 6rpx 20rpx rgba(255,193,7,0.4)` 暖金辉光阴影。Tap 时缩至 0.92x。
+- **Danger:** 无独立样式。删除类操作使用红色文字 `#ef4444`,置于页面底部,与主按钮间用间距分隔。
+
+### Cards / Containers
+- **Corner Style:** 16rpx-20rpx 圆角(页面级卡片 20rpx,列表卡片 16rpx)
+- **Background:** 白色 `#ffffff`
+- **Shadow:** `0 2rpx 12rpx rgba(0,0,0,0.05)`
+- **Border:** 无。通过阴影和色差区分边界
+- **Internal Padding:** 24rpx-32rpx
+- **Active:** `opacity: 0.7`(可点击卡片)
+
+### Status Tags
+- **Shape:** 8rpx 圆角,inline-flex 容器
+- **Size:** padding 4-8rpx 16rpx,字号 22-24rpx,weight 500
+- **Variants:** 使用语义色背景 + 语义色文字组合。Success(绿底绿字)、Warning(橙底橙字)、Danger(红底红字)、Info(蓝底蓝字)
+
+### Inputs / Fields
+- **Style:** `#f1f5f9` 填充背景,`1rpx solid #e2e8f0` 边框,12rpx 圆角
+- **Internal Padding:** 24rpx 28rpx,高度 80rpx
+- **Placeholder:** 字号 24rpx,颜色 `#cbd5e1`
+- **Focus:** 边框色变为 `#FFC107`
+
+### Navigation
+- **NavBar:** 固定在顶部,白色背景。标题在中间 (34rpx, 600 weight)。返回按钮为 CSS 旋转正方形的左箭头 (64rpx 圆形 tap target)。支持安全区域延伸。
+- **CustomTabBar:** 固定在底部,白色背景,120rpx 高度。4 个标准 Tab(线框 CSS 图标 + 20rpx 文字)围绕中央扫码 FAB(80rpx 暖金渐变圆形,唯一带彩阴影的元素)。激活 Tab 的图标和文字变为暖金色。
+
+### Loading States
+- **Pulse Dots:** 三个 9rpx 圆点,`#cbd5e1` 色,1.2s ease-in-out 序列脉冲动画 (0s / 0.2s / 0.4s 延迟)。用于页面级加载和骨架屏。
+- **Spinner:** 32rpx 圆环,`#e2e8f0` 底色 + `#FFC107` 顶色,1s 线性旋转。用于列表加载更多和按钮异步反馈。
+
+### Empty States
+- 居中排列:120rpx 图标容器(`#f1f5f9` 背景,24rpx 圆角)+ 28rpx 标题 `#475569` + 24rpx 描述 `#94a3b8`
+- 无纯文字空态。每个空态都有视觉元素 + 引导文案。
+
+## 6. Do's and Don'ts
+
+### Do:
+- **Do** 使用 8rpx 基准间距。所有 padding/margin/gap 必须是 8 的整数倍。
+- **Do** 使用 `$spacing-N` / `$radius-*` / `$font-size-*` SCSS token,不硬编码数值。
+- **Do** 为每个异步数据加载提供 loading 状态(pulse dots 或 spinner)。
+- **Do** 将暖金仅用于交互关键点。其力量来自克制。
+- **Do** 所有可点击卡片的 active 态使用 `opacity: 0.7`。
+- **Do** 页面使用 `height: 100vh` + flex column 布局,scroll-view 用 `flex: 1; height: 0` 填充剩余空间。
+- **Do** 固定底部元素使用 `env(safe-area-inset-bottom)` 留出安全区域。
+- **Do** 状态指示同时使用颜色 + 文字,不单纯依赖颜色(WCAG AA 兼容)。
+- **Do** 字体栈在所有页面 style 中统一声明为一行的 `$font-stack` 变量。
+
+### Don't:
+- **Don't** 使用 `#000000` 或 `#ffffff`。中性色向暖金方向偏移 0.005-0.01 chroma。
+- **Don't** 对财务数字使用红色或绿色。金额统计永远用 `#1e293b`。
+- **Don't** 使用 `border-left` 或 `border-right` 大于 1px 做彩色侧边条纹装饰。
+- **Don't** 使用渐变文字 (`background-clip: text`)。
+- **Don't** 使用 26rpx 字号。字号体系中没有这个中间值。
+- **Don't** 在页面中使用原生 `<button>` 组件 + `&::after { border: none }` hack。用 `<view>` 构建按钮。
+- **Don't** 显示不完整功能。"开发中"、"功能开发中" 的 UI 要么隐藏,要么用 `v-if` 按数据可用性控制。
+- **Don't** 硬编码背景色为 `#000`(如视频区)。使用 `$bg-color-secondary`。
+- **Don't** 使用 `min-height: 100vh` 做页面布局。用 `height: 100vh` + flex column + flex:1 scroll-view。
+- **Don't** 泄露传统 ERP 后台的痕迹:无密集表格、无多级菜单、无蓝白配色、无功能入口堆砌。

+ 35 - 0
haha-admin-mp/PRODUCT.md

@@ -0,0 +1,35 @@
+# Product
+
+## Register
+
+product
+
+## Users
+
+运营团队、门店管理员、补货员三种角色共用一个小程序。使用场景为移动端(手机),在工作中随时查看和处理信息——运营人员在办公室或巡店时处理订单和退款,门店管理员查看本店设备和订单状态,补货员在柜机前扫码补货。
+
+## Product Purpose
+
+haha-admin-mp 是 AI 智能零售柜系统的运营管理端。支撑 haha-mp 消费者端小程序的正常运转:管理订单(查看、退款)、设备(状态监控)、补货员(账号绑定、设备分配)、商品库存、门店。存在意义是让运营工作高效、可追溯,而非成为负担。
+
+## Brand Personality
+
+清晰高效的工具感。与 haha-mp 消费者端保持一致的视觉语言——极简留白、暖黄主色、克制装饰。有高级感但不冰冷,让操作者感到精准可控而非复杂难懂。不追求传统后台的功能堆砌,信息呈现有呼吸感。
+
+## Anti-references
+
+- 传统 ERP 后台:密集表格、多级菜单、蓝白配色、功能入口过多
+- ThemeForest 风格 SaaS 模板:过度渐变、装饰阴影、杂乱配色
+- 任何与 haha-mp 消费者端视觉语言不一致的设计
+
+## Design Principles
+
+1. **清晰优先**:信息可扫读,不堆砌。操作者 3 秒内能找到关键信息。
+2. **路径最短**:完成任务所需点击次数最小化。常见动作用长按/手势触发。
+3. **高级克制**:品质感来自间距、排版和微动效,而非装饰元素。
+4. **与 haha-mp 一致**:共享色系、间距体系、组件模式。操作者从消费者端切换到管理端应该感到熟悉。
+5. **每个元素有存在理由**:无不完整功能、无占位 UI、无 "开发中" 死界面。
+
+## Accessibility & Inclusion
+
+WCAG AA 合规。触控区域不小于 44x44pt。文字颜色对比度满足 AA 标准。支持 reduced motion 偏好。考虑红绿色盲场景——状态指示不单纯依赖颜色。

+ 199 - 178
haha-admin-mp/src/pages/orders/detail.vue

@@ -3,6 +3,15 @@
     <!-- 黄色导航栏 -->
     <NavBar :title="navTitle" :showBack="true" />
 
+    <!-- 加载状态 -->
+    <view class="load-tip" v-if="!order">
+      <view class="dot-row">
+        <view class="pulse-dot"></view>
+        <view class="pulse-dot"></view>
+        <view class="pulse-dot"></view>
+      </view>
+    </view>
+
     <scroll-view class="detail-scroll" scroll-y v-if="order">
       <!-- 状态区域 -->
       <view class="status-section">
@@ -25,7 +34,7 @@
       <!-- 订单明细 -->
       <view class="section">
         <text class="section-title">订单明细</text>
-        <view class="product-list">
+        <view class="product-list" v-if="order.products && order.products.length > 0">
           <view class="product-item" v-for="item in order.products" :key="item.productId">
             <image
               class="product-img"
@@ -34,17 +43,20 @@
             />
             <view class="product-main">
               <text class="product-name">{{ item.productName }}</text>
-              <text class="product-meta">量 x{{ item.productNum }}</text>
-              <text class="product-meta">销售单价 ¥{{ formatMoney(item.price) }}</text>
+              <text class="product-meta">量 x{{ item.productNum }}</text>
+              <text class="product-meta">单价 ¥{{ formatMoney(item.price) }}</text>
             </view>
             <view class="product-right">
-              <text class="deal-price">成交单价 ¥{{ formatMoney(item.price) }}</text>
+              <text class="deal-price">¥{{ formatMoney(item.price * item.productNum) }}</text>
             </view>
           </view>
         </view>
+        <view class="product-empty" v-else>
+          <text>暂无商品明细</text>
+        </view>
         <view class="amount-row">
           <text class="amount-label">实付款</text>
-          <text class="amount-value">合计¥{{ formatMoney(order.paidAmount) }}</text>
+          <text class="amount-value">¥{{ formatMoney(order.paidAmount) }}</text>
         </view>
       </view>
 
@@ -53,14 +65,16 @@
         <text class="section-title">订单信息</text>
         <view class="info-row">
           <text class="info-label">订单编号</text>
-          <view class="info-value-wrapper">
+          <view class="info-value-row">
             <text class="info-value">{{ order.orderNo }}</text>
-            <view class="copy-icon" @click="copyText(order.orderNo)"></view>
+            <view class="copy-btn" @click="copyText(order.orderNo)">
+              <text class="copy-btn-text">复制</text>
+            </view>
           </view>
         </view>
         <view class="info-row">
           <text class="info-label">下单时间</text>
-          <text class="info-value">{{ order.createTime }}</text>
+          <text class="info-value">{{ order.createTime || '-' }}</text>
         </view>
         <view class="info-row" v-if="order.payTime">
           <text class="info-label">支付时间</text>
@@ -78,53 +92,73 @@
           <text class="info-label">设备编号</text>
           <text class="info-value">{{ order.deviceId || '-' }}</text>
         </view>
-        <view class="info-row">
+        <view class="info-row" v-if="order.deviceAddress">
           <text class="info-label">设备地址</text>
-          <text class="info-value">-</text>
+          <text class="info-value">{{ order.deviceAddress }}</text>
         </view>
-        <view class="info-row">
+        <view class="info-row" v-if="order.phone">
           <text class="info-label">联系消费者</text>
-          <view class="info-value-wrapper">
-            <view class="copy-icon" @click="copyText(order.phone || '')"></view>
-            <view class="phone-icon" @click="callPhone(order.phone)"></view>
-          </view>
-        </view>
-        <view class="info-row blacklist-row">
-          <view class="blacklist-label">
-            <text class="info-label">黑名单</text>
-            <text class="blacklist-tip">移入黑名单后,消费者将无法在"此设备所属子商家"的所有设备中消费</text>
+          <view class="info-value-row">
+            <text class="info-value">{{ formatPhone(order.phone) }}</text>
+            <view class="action-btn call-btn" @click="callPhone(order.phone)">
+              <text class="action-btn-text">呼叫</text>
+            </view>
           </view>
-          <switch class="blacklist-switch" :checked="false" @change="toggleBlacklist" />
-        </view>
-        <view class="info-row">
-          <text class="info-label">已发送短信催缴次数</text>
-          <text class="info-value">0次</text>
         </view>
       </view>
     </scroll-view>
 
     <!-- 底部操作 -->
     <view class="bottom-bar" v-if="order && canRefund(order)">
-      <button class="refund-btn" @click="handleRefund">退款</button>
+      <view class="refund-btn" @click="handleRefund">
+        <text class="refund-btn-text">退款</text>
+      </view>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, computed } from 'vue';
+import { ref, computed } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import NavBar from '@/components/NavBar.vue';
 import { getOrderDetail, handleRefund as refundOrder } from '@/api/order';
-import { formatMoney as formatMoneyUtil, showToast, showConfirm } from '@/utils/common';
+import { formatMoney as formatMoneyUtil, formatPhone as formatPhoneUtil, showToast, showConfirm } from '@/utils/common';
+
+interface OrderProduct {
+  productId: string;
+  productName: string;
+  productNum: number;
+  price: number;
+  pic?: string;
+}
+
+interface OrderDetail {
+  id: string;
+  orderNo: string;
+  status: number;
+  payStatus: string;
+  payStatusLabel?: string;
+  paidAmount: number;
+  createTime: string;
+  payTime?: string;
+  shopName?: string;
+  deviceId?: string;
+  deviceName?: string;
+  deviceAddress?: string;
+  phone?: string;
+  videoUrl?: string;
+  products?: OrderProduct[];
+}
 
-const order = ref<any>(null);
+const order = ref<OrderDetail | null>(null);
 const formatMoney = formatMoneyUtil;
+const formatPhone = formatPhoneUtil;
 
 const navTitle = computed(() => {
   return `${order.value?.deviceId || '订单'}-订单详情`;
 });
 
-const getPayStatusText = (payStatus: string) => {
+const getPayStatusText = (payStatus: string): string => {
   const map: Record<string, string> = {
     'UNPAID': '未支付',
     'PAID': '已支付',
@@ -133,13 +167,13 @@ const getPayStatusText = (payStatus: string) => {
   return map[payStatus] || '未知';
 };
 
-const getPayStatusClass = (payStatus: string) => {
+const getPayStatusClass = (payStatus: string): 'success' | 'danger' | 'warning' => {
   if (payStatus === 'PAID') return 'success';
   if (payStatus === 'REFUND') return 'danger';
   return 'warning';
 };
 
-const getStatusDesc = (status: number, payStatus: string) => {
+const getStatusDesc = (status: number, payStatus: string): string => {
   if (payStatus === 'PAID') return '交易成功';
   if (payStatus === 'REFUND') return '已退款';
   if (status === 1) return '等待支付';
@@ -148,8 +182,8 @@ const getStatusDesc = (status: number, payStatus: string) => {
   return '';
 };
 
-const canRefund = (order: any) => {
-  return order.payStatus === 'PAID' && order.status === 2;
+const canRefund = (o: OrderDetail): boolean => {
+  return o.payStatus === 'PAID' && o.status === 2;
 };
 
 const copyText = (text: string) => {
@@ -164,17 +198,9 @@ const copyText = (text: string) => {
 };
 
 const callPhone = (phone: string) => {
-  if (!phone) {
-    showToast('暂无联系方式');
-    return;
-  }
   uni.makePhoneCall({ phoneNumber: phone });
 };
 
-const toggleBlacklist = () => {
-  showToast('功能开发中');
-};
-
 const loadDetail = async (id?: string) => {
   if (!id) {
     showToast('订单ID不存在');
@@ -182,7 +208,7 @@ const loadDetail = async (id?: string) => {
   }
   try {
     order.value = await getOrderDetail(id);
-  } catch (error) {
+  } catch {
     showToast('加载订单详情失败');
   }
 };
@@ -196,29 +222,29 @@ const handleRefund = async () => {
     showToast('退款处理成功', 'success');
     setTimeout(() => {
       const pages = getCurrentPages();
-      const currentPage = pages[pages.length - 1] as any;
+      const currentPage = pages[pages.length - 1] as { options?: Record<string, string> };
       loadDetail(currentPage?.options?.id);
     }, 1000);
-  } catch (error) {
+  } catch {
     showToast('退款处理失败');
   }
 };
 
-onLoad((options: any) => {
+onLoad((options?: Record<string, string>) => {
   loadDetail(options?.id);
 });
-
-onMounted(() => {
-  // onLoad 中已处理加载
-});
 </script>
 
 <style lang="scss" scoped>
+$font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif;
+
 .page {
-  min-height: 100vh;
+  height: 100vh;
   background: $bg-color-page;
   display: flex;
   flex-direction: column;
+  font-family: $font-stack;
+  overflow-x: hidden;
 }
 
 .detail-scroll {
@@ -227,27 +253,53 @@ onMounted(() => {
   padding-bottom: 140rpx;
 }
 
-/* 状态区域 */
+// ====== Loading ======
+.load-tip {
+  display: flex;
+  justify-content: center;
+  padding: 120rpx 0;
+}
+
+.dot-row {
+  display: flex;
+  gap: 8rpx;
+}
+
+.pulse-dot {
+  width: 9rpx; height: 9rpx;
+  background: $text-color-placeholder;
+  border-radius: 50%;
+  animation: pulse 1.2s ease-in-out infinite;
+  &:nth-child(2) { animation-delay: 0.2s; }
+  &:nth-child(3) { animation-delay: 0.4s; }
+}
+
+@keyframes pulse {
+  0%, 80%, 100% { transform: scale(0.5); opacity: 0.4; }
+  40% { transform: scale(1); opacity: 1; }
+}
+
+// ====== Status section ======
 .status-section {
   background: $bg-color-card;
-  padding: 24rpx 32rpx;
-  margin-bottom: 16rpx;
+  padding: $spacing-3 $spacing-4;
+  margin-bottom: $spacing-2;
 
   .status-row {
     display: flex;
     align-items: center;
-    margin-bottom: 12rpx;
+    margin-bottom: $spacing-1;
 
     .status-tag {
-      padding: 6rpx 16rpx;
-      border-radius: 6rpx;
-      font-size: 24rpx;
+      padding: 4rpx $spacing-2;
+      border-radius: $radius-sm;
+      font-size: $font-size-sm;
       font-weight: 500;
-      margin-right: 16rpx;
+      margin-right: $spacing-2;
 
       &.success {
-        background: $primary-color-bg;
-        color: $primary-color;
+        background: $success-color-bg;
+        color: $success-color;
       }
 
       &.danger {
@@ -256,13 +308,13 @@ onMounted(() => {
       }
 
       &.warning {
-        background: $accent-color-bg;
-        color: $accent-color;
+        background: $warning-color-bg;
+        color: $warning-color;
       }
     }
 
     .order-no-text {
-      font-size: 30rpx;
+      font-size: $font-size-md;
       font-weight: 600;
       color: $text-color-primary;
     }
@@ -271,56 +323,48 @@ onMounted(() => {
   .status-desc {
     font-size: 26rpx;
 
-    &.success {
-      color: $primary-color;
-    }
-
-    &.danger {
-      color: $error-color;
-    }
-
-    &.warning {
-      color: $accent-color;
-    }
+    &.success { color: $success-color; }
+    &.danger { color: $error-color; }
+    &.warning { color: $warning-color; }
   }
 }
 
-/* 视频区域 */
+// ====== Video ======
 .video-section {
   background: $bg-color-card;
-  padding: 20rpx 32rpx;
-  margin-bottom: 16rpx;
+  padding: 20rpx $spacing-4;
+  margin-bottom: $spacing-2;
 
   .order-video {
     width: 100%;
     height: 400rpx;
-    border-radius: 12rpx;
-    background: #000000;
+    border-radius: $radius-base;
+    background: $bg-color-secondary;
   }
 }
 
-/* 通用卡片 */
+// ====== Section card ======
 .section {
   background: $bg-color-card;
-  margin-bottom: 16rpx;
-  padding: 24rpx 32rpx;
+  margin-bottom: $spacing-2;
+  padding: $spacing-3 $spacing-4;
 
   .section-title {
     display: block;
-    font-size: 30rpx;
+    font-size: $font-size-md;
     font-weight: 600;
     color: $text-color-primary;
-    margin-bottom: 20rpx;
+    margin-bottom: $spacing-2;
   }
 }
 
-/* 商品列表 */
+// ====== Product list ======
 .product-list {
   .product-item {
     display: flex;
     align-items: flex-start;
-    padding: 16rpx 0;
-    border-bottom: 1rpx solid $bg-color-page;
+    padding: $spacing-2 0;
+    border-bottom: 1rpx solid $border-color-light;
 
     &:last-child {
       border-bottom: none;
@@ -329,7 +373,7 @@ onMounted(() => {
     .product-img {
       width: 120rpx;
       height: 120rpx;
-      border-radius: 8rpx;
+      border-radius: $radius-sm;
       background: $bg-color-page;
       margin-right: 20rpx;
       flex-shrink: 0;
@@ -340,14 +384,14 @@ onMounted(() => {
 
       .product-name {
         display: block;
-        font-size: 28rpx;
+        font-size: $font-size-base;
         color: $text-color-primary;
-        margin-bottom: 8rpx;
+        margin-bottom: $spacing-1;
       }
 
       .product-meta {
         display: block;
-        font-size: 24rpx;
+        font-size: $font-size-sm;
         color: $text-color-muted;
         margin-bottom: 4rpx;
       }
@@ -358,155 +402,132 @@ onMounted(() => {
       align-items: flex-end;
 
       .deal-price {
-        font-size: 24rpx;
+        font-size: $font-size-sm;
         color: $text-color-muted;
       }
     }
   }
 }
 
+.product-empty {
+  padding: $spacing-5 0;
+  text-align: center;
+  font-size: $font-size-sm;
+  color: $text-color-placeholder;
+}
+
 .amount-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 20rpx 0 0;
-  border-top: 1rpx solid $bg-color-page;
-  margin-top: 16rpx;
+  padding: $spacing-2 0 0;
+  border-top: 1rpx solid $border-color-light;
+  margin-top: $spacing-2;
 
   .amount-label {
-    font-size: 28rpx;
+    font-size: $font-size-base;
     color: $text-color-secondary;
   }
 
   .amount-value {
-    font-size: 32rpx;
+    font-size: $font-size-lg;
     font-weight: 700;
     color: $text-color-primary;
   }
 }
 
-/* 订单信息 */
+// ====== Info rows ======
 .info-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 16rpx 0;
-  border-bottom: 1rpx solid $bg-color-page;
+  padding: $spacing-3 0;
+  border-bottom: 1rpx solid $border-color-light;
 
   &:last-child {
     border-bottom: none;
   }
 
   .info-label {
-    font-size: 28rpx;
+    font-size: $font-size-base;
     color: $text-color-muted;
+    flex-shrink: 0;
   }
 
   .info-value {
-    font-size: 28rpx;
+    font-size: $font-size-base;
     color: $text-color-primary;
+    text-align: right;
   }
 
-  .info-value-wrapper {
+  .info-value-row {
     display: flex;
     align-items: center;
-    gap: 16rpx;
+    gap: $spacing-2;
+  }
+}
 
-    .copy-icon {
-      width: 32rpx;
-      height: 32rpx;
-      background: $bg-color-page;
-      border-radius: 4rpx;
-      position: relative;
-
-      &::before {
-        content: '';
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        width: 16rpx;
-        height: 20rpx;
-        border: 2rpx solid $text-color-muted;
-        border-radius: 2rpx;
-      }
+// ====== Action buttons ======
+.copy-btn, .action-btn {
+  padding: 8rpx $spacing-3;
+  border-radius: $radius-sm;
+  flex-shrink: 0;
 
-      &::after {
-        content: '';
-        position: absolute;
-        top: 4rpx;
-        right: 4rpx;
-        width: 8rpx;
-        height: 8rpx;
-        background: $bg-color-card;
-      }
-    }
+  &:active { opacity: 0.7; }
+}
 
-    .phone-icon {
-      width: 40rpx;
-      height: 40rpx;
-      background: $primary-color;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      position: relative;
-
-      &::before {
-        content: '';
-        position: absolute;
-        width: 16rpx;
-        height: 20rpx;
-        background: $bg-color-card;
-        border-radius: 4rpx 4rpx 8rpx 8rpx;
-      }
-    }
+.copy-btn {
+  background: $bg-color-page;
+
+  .copy-btn-text {
+    font-size: $font-size-xs;
+    color: $text-color-secondary;
   }
 }
 
-.blacklist-row {
-  align-items: flex-start;
+.action-btn {
+  .action-btn-text {
+    font-size: $font-size-xs;
+    font-weight: 500;
+  }
+}
 
-  .blacklist-label {
-    display: flex;
-    flex-direction: column;
+.call-btn {
+  background: $primary-color-bg;
 
-    .blacklist-tip {
-      font-size: 22rpx;
-      color: $text-color-muted;
-      margin-top: 4rpx;
-      line-height: 1.4;
-    }
+  .action-btn-text {
+    color: $primary-color-dark;
   }
 }
 
-/* 底部操作 */
+// ====== Bottom bar ======
 .bottom-bar {
   position: fixed;
   bottom: 0;
   left: 0;
   right: 0;
   background: $bg-color-card;
-  padding: 20rpx 32rpx;
-  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+  padding: $spacing-3 $spacing-4;
+  padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+  border-top: 1rpx solid $border-color-light;
+  flex-shrink: 0;
 
   .refund-btn {
     width: 100%;
-    height: 96rpx;
     background: $primary-color;
-    color: $text-color-primary;
-    font-size: 32rpx;
-    font-weight: 600;
-    border-radius: 16rpx;
-    border: none;
+    border-radius: $radius-xl;
+    padding: 28rpx 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: opacity 0.15s ease;
 
-    &::after {
-      border: none;
-    }
+    &:active { opacity: 0.8; }
 
-    &:active {
-      background: $primary-color-dark;
+    .refund-btn-text {
+      font-size: $font-size-lg;
+      font-weight: 600;
+      color: $text-color-primary;
     }
   }
 }

+ 147 - 128
haha-admin-mp/src/pages/orders/list.vue

@@ -30,24 +30,18 @@
       </view>
       <view class="stat-item">
         <text class="stat-value">{{ stats.wechatAmount || '0.00' }}</text>
-        <text class="stat-label">微信总金额</text>
+        <text class="stat-label">微信支付</text>
       </view>
       <view class="stat-item">
         <text class="stat-value">{{ stats.alipayAmount || '0.00' }}</text>
-        <text class="stat-label">支付宝总金额</text>
+        <text class="stat-label">支付宝</text>
       </view>
       <view class="stat-item">
         <text class="stat-value">{{ stats.otherAmount || '0.00' }}</text>
-        <text class="stat-label">其他</text>
+        <text class="stat-label">其他支付</text>
       </view>
     </view>
 
-    <!-- 说明文字 -->
-    <view class="tips-section">
-      <text class="tips-text">已支付订单:消费者支付完成的订单;</text>
-      <text class="tips-text">其他订单:包括未支付订单、刷脸支付、余额支付、扶贫、API商家、刷卡等订单;</text>
-    </view>
-
     <!-- 列表头部 -->
     <view class="list-header">
       <text class="list-info">{{ dateRangeShortText }} 共{{ total }}条</text>
@@ -68,14 +62,14 @@
           <!-- 头部 -->
           <view class="card-header">
             <text class="order-time">{{ formatDateTime(order.createTime) }}</text>
-            <text class="order-status" :style="{ color: getStatusColor(order.status) }">
+            <text class="order-status" :class="getStatusClass(order.status)">
               {{ order.statusLabel || getStatusText(order.status) }}
             </text>
           </view>
 
           <!-- 商品区域 -->
           <view class="card-product">
-            <image class="product-img" :src="getFirstProductPic(order)" mode="aspectFill" />
+            <image class="product-img" :src="getProductPic(order)" mode="aspectFill" />
             <view class="product-right">
               <text class="product-amount">¥{{ formatMoney(order.paidAmount || order.totalAmount) }}</text>
               <text class="product-count">共{{ getProductCount(order) }}件</text>
@@ -85,19 +79,19 @@
           <!-- 信息区域 -->
           <view class="card-info">
             <view class="info-line">
-              <text class="info-label">订单编号:</text>
+              <text class="info-label">订单编号</text>
               <text class="info-value">{{ order.orderNo }}</text>
             </view>
             <view class="info-line">
-              <text class="info-label">设备信息:</text>
+              <text class="info-label">设备信息</text>
               <text class="info-value">{{ order.deviceId }}</text>
             </view>
-            <view class="info-line">
-              <text class="info-label">门店名称:</text>
-              <text class="info-value">{{ order.shopName || '-' }}</text>
+            <view class="info-line" v-if="order.shopName">
+              <text class="info-label">门店名称</text>
+              <text class="info-value">{{ order.shopName }}</text>
             </view>
             <view class="info-line">
-              <text class="info-label">支付方式:</text>
+              <text class="info-label">支付方式</text>
               <text class="info-value">{{ order.payType || '-' }}</text>
             </view>
           </view>
@@ -105,7 +99,7 @@
           <!-- 操作按钮 -->
           <view class="card-action">
             <view class="view-btn" @click.stop="goDetail(order.id)">
-              <text>查看</text>
+              <text>查看详情</text>
             </view>
           </view>
         </view>
@@ -157,12 +151,25 @@
 
 <script setup lang="ts">
 import { ref, onMounted, computed } from 'vue';
-import { logger } from '@/utils/logger';
 import NavBar from '@/components/NavBar.vue';
 import CustomTabBar from '@/components/CustomTabBar.vue';
 import { getOrderList, getOrderStats } from '@/api/order';
-import { OrderStatusText, OrderStatusColor } from '@/utils/constants';
-import { formatMoney as formatMoneyUtil, showToast } from '@/utils/common';
+import { OrderStatusText } from '@/utils/constants';
+import { formatMoney as formatMoneyUtil } from '@/utils/common';
+
+interface OrderItem {
+  id: string;
+  orderNo: string;
+  status: number;
+  statusLabel?: string;
+  paidAmount?: number;
+  totalAmount?: number;
+  createTime: string;
+  deviceId?: string;
+  shopName?: string;
+  payType?: string;
+  products?: { pic?: string; productNum?: number }[];
+}
 
 const tabs = [
   { key: 'sales', label: '销售订单', params: { status: 2 } },
@@ -172,7 +179,7 @@ const tabs = [
 ];
 
 const currentTab = ref('sales');
-const orderList = ref<any[]>([]);
+const orderList = ref<OrderItem[]>([]);
 const loading = ref(false);
 const hasMore = ref(true);
 const page = ref(1);
@@ -192,8 +199,7 @@ const startDate = ref(formatDateStr(thirtyDaysAgo));
 const endDate = ref(formatDateStr(today));
 const showDatePicker = ref(false);
 
-// 统计
-const stats = ref<any>({});
+const stats = ref<Record<string, string | number>>({});
 
 const dateRangeFullText = computed(() => {
   return `${startDate.value} 00:00:00 - ${endDate.value} 23:59:59`;
@@ -205,11 +211,16 @@ const dateRangeShortText = computed(() => {
 
 const formatMoney = formatMoneyUtil;
 
-const getStatusText = (status: number) => OrderStatusText[status] || '未知';
+const getStatusText = (status: number): string => OrderStatusText[status] || '未知';
 
-const getStatusColor = (status: number) => OrderStatusColor[status] || '$text-color-muted';
+const getStatusClass = (status: number): string => {
+  if (status === 2) return 'completed';
+  if (status === 1) return 'pending';
+  if (status === 0) return 'cancelled';
+  return 'closed';
+};
 
-const formatDateTime = (datetime: string) => {
+const formatDateTime = (datetime: string): string => {
   if (!datetime) return '-';
   if (datetime.length > 16) {
     return datetime.substring(0, 16);
@@ -217,20 +228,22 @@ const formatDateTime = (datetime: string) => {
   return datetime;
 };
 
-const getFirstProductPic = (_order?: any) => {
-  return '/static/images/default-product.png';
+const getProductPic = (order: OrderItem): string => {
+  const first = order.products?.[0];
+  return first?.pic || '/static/images/default-product.png';
 };
 
-const getProductCount = (_order?: any) => {
-  return 1;
+const getProductCount = (order: OrderItem): number => {
+  if (!order.products || order.products.length === 0) return 0;
+  return order.products.reduce((sum, p) => sum + (p.productNum || 0), 0);
 };
 
 const loadStats = async () => {
   try {
     const res = await getOrderStats({ startDate: startDate.value, endDate: endDate.value });
     stats.value = res || {};
-  } catch (error) {
-    logger.warn('加载统计失败', error);
+  } catch {
+    // stats are non-critical
   }
 };
 
@@ -238,7 +251,7 @@ const loadOrders = async () => {
   loading.value = true;
   try {
     const tab = tabs.find(t => t.key === currentTab.value);
-    const params: any = {
+    const params: Record<string, unknown> = {
       page: page.value,
       pageSize,
       startDate: startDate.value,
@@ -260,8 +273,8 @@ const loadOrders = async () => {
 
     total.value = res.total || 0;
     hasMore.value = orderList.value.length < total.value;
-  } catch (error) {
-    logger.warn('加载订单失败', error);
+  } catch {
+    // fail silently, list remains as-is
   } finally {
     loading.value = false;
   }
@@ -285,11 +298,11 @@ const goDetail = (orderId: string) => {
   uni.navigateTo({ url: `/pages/orders/detail?id=${orderId}` });
 };
 
-const onStartDateChange = (e: any) => {
+const onStartDateChange = (e: { detail: { value: string } }) => {
   startDate.value = e.detail.value;
 };
 
-const onEndDateChange = (e: any) => {
+const onEndDateChange = (e: { detail: { value: string } }) => {
   endDate.value = e.detail.value;
 };
 
@@ -308,19 +321,23 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
+$font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif;
+
 .page {
-  min-height: 100vh;
+  height: 100vh;
   background: $bg-color-page;
   display: flex;
   flex-direction: column;
-  padding-bottom: 200rpx;
+  overflow-x: hidden;
+  padding-bottom: env(safe-area-inset-bottom);
+  font-family: $font-stack;
 }
 
-/* Tab 栏 */
+// ====== Tab bar ======
 .tab-bar {
   display: flex;
   background: $bg-color-card;
-  border-bottom: 1rpx solid $border-color;
+  border-bottom: 1rpx solid $border-color-light;
 
   .tab-item {
     flex: 1;
@@ -328,11 +345,11 @@ onMounted(() => {
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    padding: 24rpx 0 16rpx;
+    padding: $spacing-3 0 $spacing-2;
     position: relative;
 
     text {
-      font-size: 28rpx;
+      font-size: $font-size-base;
       color: $text-color-secondary;
     }
 
@@ -356,22 +373,22 @@ onMounted(() => {
   }
 }
 
-/* 时间筛选 */
+// ====== Time filter ======
 .time-filter {
-  padding: 16rpx 24rpx;
+  padding: $spacing-2 $spacing-3;
   background: $bg-color-card;
 
   .time-label {
-    font-size: 24rpx;
+    font-size: $font-size-sm;
     color: $text-color-muted;
   }
 }
 
-/* 统计卡片 */
+// ====== Stats bar ======
 .stats-bar {
   display: flex;
   background: $bg-color-card;
-  padding: 20rpx 0;
+  padding: $spacing-2 0;
 
   .stat-item {
     flex: 1;
@@ -380,98 +397,94 @@ onMounted(() => {
     align-items: center;
 
     .stat-value {
-      font-size: 36rpx;
+      font-size: $font-size-xl;
       font-weight: 700;
-      color: $error-color;
-      margin-bottom: 8rpx;
+      color: $text-color-primary;
+      margin-bottom: $spacing-1;
     }
 
     .stat-label {
-      font-size: 24rpx;
-      color: $text-color-primary;
+      font-size: $font-size-sm;
+      color: $text-color-muted;
     }
   }
 }
 
-/* 说明文字 */
-.tips-section {
-  background: $bg-color-card;
-  padding: 16rpx 24rpx;
-  margin-bottom: 16rpx;
-
-  .tips-text {
-    display: block;
-    font-size: 22rpx;
-    color: $text-color-muted;
-    line-height: 1.6;
-  }
-}
-
-/* 列表头部 */
+// ====== List header ======
 .list-header {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 16rpx 24rpx;
+  padding: $spacing-2 $spacing-3;
 
   .list-info {
-    font-size: 26rpx;
+    font-size: $font-size-sm;
     color: $text-color-muted;
   }
 
   .filter-btn {
     display: flex;
     align-items: center;
-    padding: 8rpx 16rpx;
+    padding: $spacing-1 $spacing-2;
     background: $bg-color-card;
-    border-radius: 8rpx;
-    font-size: 26rpx;
+    border-radius: $radius-sm;
+    font-size: $font-size-sm;
     color: $text-color-secondary;
+
+    &:active { opacity: 0.7; }
   }
 }
 
-/* 订单列表 */
+// ====== Order list ======
 .order-scroll {
   flex: 1;
   height: 0;
 }
 
 .order-list {
-  padding: 0 24rpx;
+  padding: $spacing-2 $spacing-3;
 }
 
 .order-card {
   background: $bg-color-card;
-  border-radius: 12rpx;
-  margin-bottom: 16rpx;
-  padding: 20rpx;
+  border-radius: $radius-md;
+  margin-bottom: $spacing-2;
+  padding: $spacing-2;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
+  &:active { opacity: 0.7; }
 
   .card-header {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    margin-bottom: 16rpx;
+    margin-bottom: $spacing-2;
 
     .order-time {
-      font-size: 28rpx;
+      font-size: $font-size-base;
       color: $text-color-primary;
     }
 
     .order-status {
-      font-size: 28rpx;
+      font-size: $font-size-sm;
       font-weight: 500;
+
+      &.completed { color: $success-color; }
+      &.pending { color: $warning-color; }
+      &.cancelled { color: $text-color-muted; }
+      &.closed { color: $text-color-tertiary; }
     }
   }
 
   .card-product {
     display: flex;
     align-items: center;
-    margin-bottom: 16rpx;
+    margin-bottom: $spacing-2;
 
     .product-img {
       width: 120rpx;
       height: 120rpx;
-      border-radius: 8rpx;
+      border-radius: $radius-sm;
       background: $bg-color-page;
       margin-right: 20rpx;
     }
@@ -483,34 +496,38 @@ onMounted(() => {
       align-items: flex-end;
 
       .product-amount {
-        font-size: 32rpx;
+        font-size: $font-size-lg;
         font-weight: 600;
         color: $text-color-primary;
-        margin-bottom: 8rpx;
+        margin-bottom: $spacing-1;
       }
 
       .product-count {
-        font-size: 24rpx;
+        font-size: $font-size-sm;
         color: $text-color-muted;
       }
     }
   }
 
   .card-info {
-    margin-bottom: 16rpx;
+    margin-bottom: $spacing-2;
 
     .info-line {
       display: flex;
-      margin-bottom: 8rpx;
+      align-items: center;
+      margin-bottom: $spacing-1;
+
+      &:last-child { margin-bottom: 0; }
 
       .info-label {
-        font-size: 26rpx;
+        font-size: $font-size-sm;
         color: $text-color-secondary;
-        margin-right: 8rpx;
+        margin-right: $spacing-1;
+        flex-shrink: 0;
       }
 
       .info-value {
-        font-size: 26rpx;
+        font-size: $font-size-sm;
         color: $text-color-primary;
       }
     }
@@ -521,27 +538,30 @@ onMounted(() => {
     justify-content: flex-end;
 
     .view-btn {
-      padding: 10rpx 32rpx;
-      border: 1rpx solid $text-color-placeholder;
-      border-radius: 8rpx;
+      padding: $spacing-1 $spacing-4;
+      background: $bg-color-page;
+      border-radius: $radius-sm;
+
+      &:active { opacity: 0.7; }
 
       text {
-        font-size: 26rpx;
+        font-size: $font-size-sm;
         color: $text-color-secondary;
+        font-weight: 500;
       }
     }
   }
 }
 
-/* 加载状态 */
+// ====== Loading ======
 .loading-more {
   display: flex;
   align-items: center;
   justify-content: center;
-  gap: 12rpx;
-  padding: 32rpx;
+  gap: $spacing-1;
+  padding: $spacing-4;
   color: $text-color-muted;
-  font-size: 24rpx;
+  font-size: $font-size-sm;
 
   .loading-spinner {
     width: 32rpx;
@@ -559,8 +579,8 @@ onMounted(() => {
 
 .no-more {
   text-align: center;
-  padding: 32rpx;
-  font-size: 24rpx;
+  padding: $spacing-4;
+  font-size: $font-size-sm;
   color: $text-color-placeholder;
 }
 
@@ -571,84 +591,83 @@ onMounted(() => {
   padding: 100rpx 0;
 
   .empty-text {
-    font-size: 28rpx;
+    font-size: $font-size-base;
     color: $text-color-muted;
   }
 }
 
-/* 日期弹窗 */
+// ====== Date modal ======
 .date-modal {
   position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
+  inset: 0;
+  background: rgba(0, 0, 0, 0.45);
   z-index: 100;
   display: flex;
   align-items: center;
   justify-content: center;
-  padding: 0 48rpx;
+  padding: 0 $spacing-6;
 }
 
 .modal-content {
   width: 100%;
   background: $bg-color-card;
-  border-radius: 20rpx;
+  border-radius: $radius-xl;
   overflow: hidden;
 
   .modal-header {
-    padding: 24rpx;
-    border-bottom: 1rpx solid $bg-color-page;
+    padding: $spacing-3;
+    border-bottom: 1rpx solid $border-color-light;
 
     .modal-title {
-      font-size: 32rpx;
+      font-size: $font-size-lg;
       font-weight: 600;
       color: $text-color-primary;
     }
   }
 
   .modal-body {
-    padding: 24rpx;
+    padding: $spacing-3;
 
     .date-row {
       display: flex;
       align-items: center;
       justify-content: space-between;
-      padding: 20rpx 0;
-      border-bottom: 1rpx solid $bg-color-page;
+      padding: $spacing-2 0;
+      border-bottom: 1rpx solid $border-color-light;
+
+      &:last-child { border-bottom: none; }
 
       text {
-        font-size: 28rpx;
+        font-size: $font-size-base;
         color: $text-color-primary;
       }
 
       .picker-value {
-        font-size: 28rpx;
+        font-size: $font-size-base;
         color: $text-color-primary;
-        padding: 8rpx 16rpx;
+        padding: $spacing-1 $spacing-2;
         background: $bg-color-page;
-        border-radius: 8rpx;
+        border-radius: $radius-sm;
       }
     }
   }
 
   .modal-footer {
     display: flex;
-    padding: 16rpx 24rpx 24rpx;
-    gap: 12rpx;
+    padding: $spacing-2 $spacing-3 $spacing-3;
+    gap: $spacing-1;
 
     button {
       flex: 1;
       height: 80rpx;
-      border-radius: 12rpx;
-      font-size: 28rpx;
+      line-height: 80rpx;
+      border-radius: $radius-base;
+      font-size: $font-size-base;
       font-weight: 500;
       border: none;
+      padding: 0;
 
-      &::after {
-        border: none;
-      }
+      &::after { border: none; }
     }
 
     .btn-cancel {