Ver código fonte

运营端小程序页面打磨

skyline 2 semanas atrás
pai
commit
5668075fd4

+ 304 - 0
haha-admin-mp/.impeccable/design.json

@@ -0,0 +1,304 @@
+{
+  "schemaVersion": 2,
+  "generatedAt": "2026-05-13T00:00:00.000Z",
+  "title": "Design System: haha-admin-mp",
+  "extensions": {
+    "colorMeta": {
+      "amber-primary": {
+        "role": "primary",
+        "displayName": "Amber Primary",
+        "canonical": "oklch(78% 0.17 85)",
+        "tonalRamp": [
+          "oklch(20% 0.04 85)",
+          "oklch(35% 0.08 85)",
+          "oklch(48% 0.12 85)",
+          "oklch(60% 0.15 85)",
+          "oklch(70% 0.17 85)",
+          "oklch(78% 0.17 85)",
+          "oklch(88% 0.12 85)",
+          "oklch(95% 0.04 85)"
+        ]
+      },
+      "amber-bg": {
+        "role": "primary-bg",
+        "displayName": "Amber Background",
+        "canonical": "oklch(97% 0.02 85)",
+        "tonalRamp": [
+          "oklch(98% 0.01 85)",
+          "oklch(97% 0.02 85)",
+          "oklch(96% 0.025 85)",
+          "oklch(95% 0.03 85)",
+          "oklch(94% 0.035 85)",
+          "oklch(92% 0.05 85)",
+          "oklch(90% 0.07 85)",
+          "oklch(85% 0.1 85)"
+        ]
+      },
+      "orange-accent": {
+        "role": "secondary",
+        "displayName": "Orange Accent",
+        "canonical": "oklch(62% 0.2 50)",
+        "tonalRamp": [
+          "oklch(18% 0.05 50)",
+          "oklch(30% 0.08 50)",
+          "oklch(42% 0.12 50)",
+          "oklch(52% 0.16 50)",
+          "oklch(62% 0.2 50)",
+          "oklch(72% 0.18 50)",
+          "oklch(84% 0.1 50)",
+          "oklch(94% 0.03 50)"
+        ]
+      },
+      "green-success": {
+        "role": "semantic",
+        "displayName": "Green Success",
+        "canonical": "oklch(62% 0.17 160)",
+        "tonalRamp": [
+          "oklch(18% 0.05 160)",
+          "oklch(32% 0.08 160)",
+          "oklch(45% 0.12 160)",
+          "oklch(55% 0.15 160)",
+          "oklch(62% 0.17 160)",
+          "oklch(72% 0.15 160)",
+          "oklch(84% 0.08 160)",
+          "oklch(94% 0.03 160)"
+        ]
+      },
+      "red-error": {
+        "role": "semantic",
+        "displayName": "Red Error",
+        "canonical": "oklch(55% 0.21 25)",
+        "tonalRamp": [
+          "oklch(18% 0.06 25)",
+          "oklch(28% 0.1 25)",
+          "oklch(40% 0.15 25)",
+          "oklch(50% 0.18 25)",
+          "oklch(55% 0.21 25)",
+          "oklch(65% 0.18 25)",
+          "oklch(78% 0.1 25)",
+          "oklch(93% 0.03 25)"
+        ]
+      },
+      "amber-warning": {
+        "role": "semantic",
+        "displayName": "Amber Warning",
+        "canonical": "oklch(68% 0.16 75)",
+        "tonalRamp": [
+          "oklch(18% 0.04 75)",
+          "oklch(30% 0.07 75)",
+          "oklch(44% 0.1 75)",
+          "oklch(56% 0.13 75)",
+          "oklch(68% 0.16 75)",
+          "oklch(78% 0.14 75)",
+          "oklch(86% 0.08 75)",
+          "oklch(95% 0.03 75)"
+        ]
+      },
+      "slate-text-primary": {
+        "role": "neutral-text",
+        "displayName": "Slate Text Primary",
+        "canonical": "oklch(25% 0.01 260)",
+        "tonalRamp": [
+          "oklch(15% 0.01 260)",
+          "oklch(20% 0.01 260)",
+          "oklch(25% 0.01 260)",
+          "oklch(35% 0.01 260)",
+          "oklch(45% 0.01 260)",
+          "oklch(60% 0.01 260)",
+          "oklch(80% 0.005 85)",
+          "oklch(94% 0.005 85)"
+        ]
+      },
+      "ash-page-bg": {
+        "role": "neutral-bg",
+        "displayName": "Ash Page Background",
+        "canonical": "oklch(97% 0.005 85)",
+        "tonalRamp": [
+          "oklch(99% 0.002 85)",
+          "oklch(97% 0.005 85)",
+          "oklch(95% 0.006 85)",
+          "oklch(92% 0.007 85)",
+          "oklch(88% 0.008 85)",
+          "oklch(82% 0.008 85)",
+          "oklch(72% 0.007 85)",
+          "oklch(58% 0.006 85)"
+        ]
+      },
+      "white-card-bg": {
+        "role": "neutral-surface",
+        "displayName": "White Card Background",
+        "canonical": "oklch(100% 0.002 85)",
+        "tonalRamp": [
+          "oklch(100% 0 0)",
+          "oklch(100% 0.002 85)",
+          "oklch(98% 0.003 85)",
+          "oklch(96% 0.004 85)",
+          "oklch(93% 0.005 85)",
+          "oklch(87% 0.006 85)",
+          "oklch(76% 0.007 85)",
+          "oklch(62% 0.006 85)"
+        ]
+      },
+      "ash-border": {
+        "role": "neutral-border",
+        "displayName": "Ash Border",
+        "canonical": "oklch(90% 0.005 260)",
+        "tonalRamp": [
+          "oklch(96% 0.003 260)",
+          "oklch(93% 0.004 260)",
+          "oklch(90% 0.005 260)",
+          "oklch(85% 0.005 260)",
+          "oklch(78% 0.005 260)",
+          "oklch(68% 0.004 260)",
+          "oklch(52% 0.003 260)",
+          "oklch(32% 0.002 260)"
+        ]
+      }
+    },
+    "typographyMeta": {
+      "display": { "displayName": "Display", "purpose": "订单金额、统计大数。仅在关键数据上出现。" },
+      "headline": { "displayName": "Headline", "purpose": "页面标题、卡片标题、弹窗标题。" },
+      "title": { "displayName": "Title", "purpose": "主要正文、列表项名称、标签。最广泛的字号。" },
+      "body": { "displayName": "Body", "purpose": "正文信息、表单输入、描述文字。28rpx + 1.6 line-height。" },
+      "label": { "displayName": "Label", "purpose": "标签、徽章、meta 文字。22rpx + 0.02em letter-spacing。" }
+    },
+    "shadows": [
+      { "name": "card-rest", "value": "0 2rpx 12rpx rgba(0,0,0,0.05)", "purpose": "所有卡片和可交互表面在静止态的标准阴影。" },
+      { "name": "card-subtle", "value": "0 2rpx 8rpx rgba(0,0,0,0.04)", "purpose": "轻量阴影,用于列表卡片之间的细微区分。" },
+      { "name": "bottom-bar", "value": "0 -2rpx 12rpx rgba(0,0,0,0.05)", "purpose": "固定底部栏的方向性阴影,指向上方。" },
+      { "name": "primary-glow", "value": "0 6rpx 20rpx rgba(255,193,7,0.4)", "purpose": "唯一彩色阴影。仅用于扫码FAB,暖金散射强调首要地位。" }
+    ],
+    "motion": [
+      { "name": "ease-press", "value": "opacity 0.15s ease", "purpose": "所有按钮和可点击元素的 active 态反馈。通过 opacity 变化而非 layout property 动画。" },
+      { "name": "ease-pulse", "value": "1.2s ease-in-out infinite", "purpose": "加载 pulse dots 的序列动画。不适用 ease-out-quart 因其为循环动画。" },
+      { "name": "ease-spin", "value": "1s linear infinite", "purpose": "加载 spinner 的旋转动画。" }
+    ],
+    "breakpoints": [
+      { "name": "mobile", "value": "375px-414px", "purpose": "微信小程序标准视口宽度。" }
+    ]
+  },
+  "components": [
+    {
+      "name": "Primary Button",
+      "kind": "button",
+      "refersTo": "button-primary",
+      "description": "页面级主操作:保存、退款、确认。暖金填充胶囊。",
+      "html": "<view class=\"ds-btn-primary\"><text class=\"ds-btn-primary-text\">保存</text></view>",
+      "css": ".ds-btn-primary { background: #FFC107; border-radius: 48rpx; padding: 28rpx 0; display: flex; align-items: center; justify-content: center; transition: opacity 0.15s ease; cursor: pointer; } .ds-btn-primary:active { opacity: 0.8; } .ds-btn-primary-text { font-size: 32rpx; font-weight: 600; color: #1e293b; }"
+    },
+    {
+      "name": "Secondary Action Button",
+      "kind": "button",
+      "refersTo": "button-secondary",
+      "description": "卡片内次级操作:查看详情、筛选、复制。填充底色无边框。",
+      "html": "<view class=\"ds-btn-secondary\"><text class=\"ds-btn-secondary-text\">查看详情</text></view>",
+      "css": ".ds-btn-secondary { background: #f1f5f9; border-radius: 8rpx; padding: 8rpx 24rpx; display: inline-flex; align-items: center; justify-content: center; transition: opacity 0.15s ease; } .ds-btn-secondary:active { opacity: 0.7; } .ds-btn-secondary-text { font-size: 24rpx; color: #475569; font-weight: 500; }"
+    },
+    {
+      "name": "Surface Card",
+      "kind": "card",
+      "refersTo": "card-surface",
+      "description": "信息容器。白色背景,微阴影,中等圆角。用于列表项和内容区。",
+      "html": "<view class=\"ds-card\"><text class=\"ds-card-heading\">标题</text><text class=\"ds-card-body\">内容文本行,展示关键信息。</text></view>",
+      "css": ".ds-card { background: #ffffff; border-radius: 16rpx; padding: 24rpx 32rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); margin-bottom: 16rpx; } .ds-card:active { opacity: 0.7; } .ds-card-heading { display: block; font-size: 30rpx; font-weight: 600; color: #1e293b; margin-bottom: 8rpx; } .ds-card-body { display: block; font-size: 28rpx; color: #475569; line-height: 1.6; }"
+    },
+    {
+      "name": "Status Tag - Success",
+      "kind": "chip",
+      "refersTo": "status-tag-success",
+      "description": "成功/正常/完成状态标签。绿底绿字。",
+      "html": "<view class=\"ds-tag ds-tag-success\"><text>已完成</text></view>",
+      "css": ".ds-tag { padding: 4rpx 16rpx; border-radius: 8rpx; font-size: 22rpx; font-weight: 500; display: inline-flex; align-items: center; justify-content: center; } .ds-tag-success { background: #ecfdf5; color: #10b981; }"
+    },
+    {
+      "name": "Status Tag - Warning",
+      "kind": "chip",
+      "refersTo": "status-tag-warning",
+      "description": "待处理/未支付状态标签。琥珀底琥珀字。",
+      "html": "<view class=\"ds-tag ds-tag-warning\"><text>待支付</text></view>",
+      "css": ".ds-tag { padding: 4rpx 16rpx; border-radius: 8rpx; font-size: 22rpx; font-weight: 500; display: inline-flex; align-items: center; justify-content: center; } .ds-tag-warning { background: #fffbeb; color: #f59e0b; }"
+    },
+    {
+      "name": "Status Tag - Error",
+      "kind": "chip",
+      "refersTo": "status-tag-error",
+      "description": "失败/退款/故障状态标签。红底红字。",
+      "html": "<view class=\"ds-tag ds-tag-error\"><text>已退款</text></view>",
+      "css": ".ds-tag { padding: 4rpx 16rpx; border-radius: 8rpx; font-size: 22rpx; font-weight: 500; display: inline-flex; align-items: center; justify-content: center; } .ds-tag-error { background: #fef2f2; color: #ef4444; }"
+    },
+    {
+      "name": "Navigation Bar",
+      "kind": "nav",
+      "refersTo": null,
+      "description": "固定顶部导航栏。居中标题,左侧返回箭头,支持安全区域。",
+      "html": "<view class=\"ds-navbar\"><view class=\"ds-navback\"><view class=\"ds-navback-arrow\"></view></view><text class=\"ds-navtitle\">页面标题</text></view>",
+      "css": ".ds-navbar { position: fixed; top: 0; left: 0; right: 0; z-index: 999; background: #ffffff; padding-top: env(safe-area-inset-top); } .ds-navback { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background 0.15s; } .ds-navback:active { background: rgba(0,0,0,0.05); } .ds-navback-arrow { width: 20rpx; height: 20rpx; border-left: 4rpx solid #1e293b; border-bottom: 4rpx solid #1e293b; transform: rotate(45deg); margin-left: 8rpx; } .ds-navtitle { font-size: 34rpx; font-weight: 600; color: #1e293b; }"
+    },
+    {
+      "name": "Tab Bar",
+      "kind": "nav",
+      "refersTo": null,
+      "description": "固定底部标签栏。4 个标准 Tab 围绕中央扫码 FAB。激活态变暖金。",
+      "html": "<view class=\"ds-tabbar\"><view class=\"ds-tab ds-tab-active\"><view class=\"ds-tab-icon\"></view><text class=\"ds-tab-label\">工作台</text></view><view class=\"ds-tab\"><view class=\"ds-tab-icon\"></view><text class=\"ds-tab-label\">订单</text></view><view class=\"ds-tabbar-fab\"><view class=\"ds-fab-circle\"></view><text class=\"ds-fab-label\">扫码</text></view><view class=\"ds-tab\"><view class=\"ds-tab-icon\"></view><text class=\"ds-tab-label\">设备</text></view><view class=\"ds-tab\"><view class=\"ds-tab-icon\"></view><text class=\"ds-tab-label\">我的</text></view></view>",
+      "css": ".ds-tabbar { position: fixed; bottom: 0; left: 0; right: 0; height: 120rpx; background: #ffffff; display: flex; align-items: center; justify-content: space-around; border-top: 1rpx solid #e2e8f0; padding-bottom: env(safe-area-inset-bottom); z-index: 999; box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.05); } .ds-tab { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; } .ds-tab-label { font-size: 20rpx; color: #1e293b; } .ds-tab-active .ds-tab-label { color: #FFC107; font-weight: 600; } .ds-tabbar-fab { position: relative; width: 120rpx; height: 120rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; } .ds-fab-circle { width: 80rpx; height: 80rpx; background: linear-gradient(135deg, #FFC107, #FFA000); border-radius: 50%; box-shadow: 0 6rpx 20rpx rgba(255,193,7,0.4); transition: transform 0.2s; } .ds-fab-circle:active { transform: scale(0.92); } .ds-fab-label { font-size: 18rpx; color: #1e293b; font-weight: 500; }"
+    },
+    {
+      "name": "Text Input Field",
+      "kind": "input",
+      "refersTo": null,
+      "description": "标准输入框。灰底,圆角,暖金 focus 边框。",
+      "html": "<input class=\"ds-input\" placeholder=\"请输入姓名\" />",
+      "css": ".ds-input { background: #f1f5f9; border: 1rpx solid #e2e8f0; border-radius: 12rpx; padding: 24rpx 28rpx; font-size: 28rpx; color: #1e293b; height: 80rpx; width: 100%; box-sizing: border-box; transition: border-color 0.15s; } .ds-input:focus { border-color: #FFC107; } .ds-input::placeholder { color: #cbd5e1; font-size: 24rpx; }"
+    },
+    {
+      "name": "Loading Pulse Dots",
+      "kind": "custom",
+      "refersTo": null,
+      "description": "页面级加载指示器。三个序列脉冲圆点。",
+      "html": "<view class=\"ds-loading\"><view class=\"ds-pulse-dot\"></view><view class=\"ds-pulse-dot\"></view><view class=\"ds-pulse-dot\"></view></view>",
+      "css": ".ds-loading { display: flex; justify-content: center; gap: 8rpx; padding: 60rpx 0; } .ds-pulse-dot { width: 9rpx; height: 9rpx; background: #cbd5e1; border-radius: 50%; animation: ds-pulse 1.2s ease-in-out infinite; } .ds-pulse-dot:nth-child(2) { animation-delay: 0.2s; } .ds-pulse-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes ds-pulse { 0%, 80%, 100% { transform: scale(0.5); opacity: 0.4; } 40% { transform: scale(1); opacity: 1; } }"
+    }
+  ],
+  "narrative": {
+    "northStar": "The Warm Workshop",
+    "overview": "haha-admin-mp 是 AI 智能零售柜系统的运营管理端。品牌语言与 haha-mp 消费者端一脉相承——琥珀暖金作为身份色,大面积留白托底,轻阴影赋予卡片可触感。设计不追求传统后台的功能堆砌,而是在移动端上呈现清晰、温暖、高效的信息结构。暖金不是装饰色,是信号色。它在关键操作(主按钮、激活态、扫码入口)上集中出现,其余区域用冷灰中性色后退,让操作者凭色彩就能感知界面优先级。",
+    "keyCharacteristics": [
+      "暖金信号:琥珀色承载关键操作和选中态,其余区域用冷灰后退",
+      "轻提感:卡片统一使用 2rpx 模糊 12rpx 偏移的微阴影,暗示可点击性",
+      "移动优先文字:最小字号不低於 22rpx,触控区域不小于 44x44pt",
+      "8rpx 基准间距:所有间距为该基准的整数倍,拒绝随意值",
+      "与 haha-mp 共享色彩基因"
+    ],
+    "rules": [
+      { "name": "The Golden Signal Rule", "body": "暖金仅在交互关键点出现:主按钮、选中态、激活 Tab。其稀缺性制造信号效应。切勿将暖金用于大面积装饰或非交互内容。", "section": "colors" },
+      { "name": "The Tinted Neutral Rule", "body": "所有中性色向暖金方向偏移 0.005-0.01 chroma。纯黑 #000000 和纯白 #ffffff 被禁用。视频背景使用 --bg-secondary 而非 #000。", "section": "colors" },
+      { "name": "The Financial Data Rule", "body": "金额、统计数字永远使用 slate-text-primary,不使用红色或绿色。颜色仅携带语义状态(成功/失败),不携带价值判断。", "section": "colors" },
+      { "name": "The 28rpx Anchor Rule", "body": "正文基线和主要控件字号均锚定在 28rpx。向上取 30/32/36rpx 做标题,向下取 22/24rpx 做辅助。绝不使用 26rpx——它作为无意义的中间值被禁用。", "section": "typography" },
+      { "name": "The One Font Rule", "body": "整个系统使用单一字体栈。层次差异通过字号和 weight 表达,不通过字体切换表达。", "section": "typography" },
+      { "name": "The Shadows-As-State Rule", "body": "阴影在静止态统一微弱,在交互态通过 opacity 而非 elevation 变化反馈。不做悬浮升起动画。FAB 的暖金 glow shadow 是唯一的例外。", "section": "elevation" }
+    ],
+    "dos": [
+      "使用 8rpx 基准间距。所有 padding/margin/gap 必须是 8 的整数倍。",
+      "使用 $spacing-N / $radius-* / $font-size-* SCSS token,不硬编码数值。",
+      "为每个异步数据加载提供 loading 状态(pulse dots 或 spinner)。",
+      "将暖金仅用于交互关键点。其力量来自克制。",
+      "所有可点击卡片的 active 态使用 opacity: 0.7。",
+      "页面使用 height: 100vh + flex column 布局,scroll-view 用 flex: 1; height: 0 填充剩余空间。",
+      "固定底部元素使用 env(safe-area-inset-bottom) 留出安全区域。",
+      "状态指示同时使用颜色 + 文字,不单纯依赖颜色(WCAG AA 兼容)。",
+      "字体栈在所有页面 style 中统一声明为一行的 $font-stack 变量。"
+    ],
+    "donts": [
+      "使用 #000000 或 #ffffff。中性色向暖金方向偏移 0.005-0.01 chroma。",
+      "对财务数字使用红色或绿色。金额统计永远用 #1e293b。",
+      "使用 border-left 或 border-right 大于 1px 做彩色侧边条纹装饰。",
+      "使用渐变文字 (background-clip: text)。",
+      "使用 26rpx 字号。字号体系中没有这个中间值。",
+      "在页面中使用原生 <button> 组件 + &::after { border: none } hack。用 <view> 构建按钮。",
+      "显示不完整功能。'开发中' 的 UI 要么隐藏,要么用 v-if 按数据可用性控制。",
+      "硬编码背景色为 #000(如视频区)。使用 $bg-color-secondary。",
+      "使用 min-height: 100vh 做页面布局。用 height: 100vh + flex column + flex:1 scroll-view。",
+      "泄露传统 ERP 后台的痕迹:无密集表格、无多级菜单、无蓝白配色、无功能入口堆砌。"
+    ]
+  }
+}

+ 9 - 0
haha-admin-mp/src/pages.json

@@ -54,6 +54,15 @@
         "navigationStyle": "custom"
       }
     },
+    {
+      "path": "pages/device/replenisher-config",
+      "style": {
+        "navigationBarTitleText": "配置补货员",
+        "navigationBarBackgroundColor": "#ffffff",
+        "navigationBarTextStyle": "black",
+        "navigationStyle": "custom"
+      }
+    },
     {
       "path": "pages/shop/list",
       "style": {

+ 4 - 0
haha-admin-mp/src/pages/checkin/index.vue

@@ -844,6 +844,8 @@ const handleSuccessClose = () => {
 .submit-btn {
   width: 100%;
   height: 96rpx;
+  line-height: 96rpx;
+  padding: 0;
   background: linear-gradient(135deg, $primary-color 0%, $primary-color-dark 100%);
   border-radius: 48rpx;
   display: flex;
@@ -937,6 +939,8 @@ const handleSuccessClose = () => {
 .success-btn {
   width: 100%;
   height: 88rpx;
+  line-height: 88rpx;
+  padding: 0;
   background: $primary-color;
   border-radius: 44rpx;
   font-size: 30rpx;

+ 2 - 0
haha-admin-mp/src/pages/customer/detail.vue

@@ -528,10 +528,12 @@ onMounted(() => {
     button {
       flex: 1;
       height: 80rpx;
+      line-height: 80rpx;
       border-radius: 12rpx;
       font-size: 28rpx;
       font-weight: 500;
       border: none;
+      padding: 0;
 
       &::after {
         border: none;

+ 2 - 0
haha-admin-mp/src/pages/customer/list.vue

@@ -518,10 +518,12 @@ onMounted(() => {
     button {
       flex: 1;
       height: 80rpx;
+      line-height: 80rpx;
       border-radius: 12rpx;
       font-size: 28rpx;
       font-weight: 500;
       border: none;
+      padding: 0;
 
       &::after {
         border: none;

+ 2 - 0
haha-admin-mp/src/pages/device/detail.vue

@@ -827,10 +827,12 @@ onMounted(() => {
     button {
       flex: 1;
       height: 80rpx;
+      line-height: 80rpx;
       border-radius: 12rpx;
       font-size: 28rpx;
       font-weight: 500;
       border: none;
+      padding: 0;
 
       &::after {
         border: none;

+ 4 - 3
haha-admin-mp/src/pages/device/list.vue

@@ -124,7 +124,7 @@
               </view>
               <text class="foot-label">上架商品</text>
             </view>
-            <view class="foot-item" @click.stop="goRestockerConfig(device.deviceId)">
+            <view class="foot-item" @click.stop="goRestockerConfig(device)">
               <view class="foot-icon">
                 <view class="fi-person">
                   <view class="fi-head"></view>
@@ -304,8 +304,9 @@ const goProductConfig = (deviceId: string) => {
   uni.navigateTo({ url: `/pages/products/list?deviceId=${deviceId}` });
 };
 
-const goRestockerConfig = (deviceId: string) => {
-  uni.showToast({ title: '配置补货员功能开发中', icon: 'none' });
+const goRestockerConfig = (device: any) => {
+  const name = encodeURIComponent(device.name || '');
+  uni.navigateTo({ url: `/pages/device/replenisher-config?deviceId=${device.deviceId}&deviceName=${name}` });
 };
 
 onMounted(() => {

+ 507 - 0
haha-admin-mp/src/pages/device/replenisher-config.vue

@@ -0,0 +1,507 @@
+<template>
+  <view class="page">
+    <NavBar title="配置补货员" :showBack="true" />
+
+    <!-- 设备信息 -->
+    <view class="header-card">
+      <view class="hc-icon">
+        <view class="icon-device"></view>
+      </view>
+      <view class="hc-info">
+        <text class="hc-name">{{ deviceName || deviceId }}</text>
+        <text class="hc-id">{{ deviceId }}</text>
+      </view>
+    </view>
+
+    <!-- 已选统计 -->
+    <view class="stats-bar" v-if="!loading">
+      <text>已选 <text class="sb-num">{{ selectedIds.length }}</text> 名补货员</text>
+    </view>
+
+    <scroll-view class="page-scroll" scroll-y>
+      <view class="list" v-if="!loading">
+        <view
+          class="item"
+          v-for="r in replenisherList"
+          :key="r.id"
+          @click="toggleReplenisher(r.id)"
+        >
+          <view class="item-avatar" :class="r.status === 0 ? 'off' : ''">
+            <text>{{ (r.name || '?')[0] }}</text>
+          </view>
+          <view class="item-info">
+            <text class="item-name">{{ r.name }}</text>
+            <text class="item-sub" v-if="r.phone">{{ r.phone }}</text>
+          </view>
+          <view class="item-check" :class="{ on: selectedIds.includes(r.id) }">
+            <text v-if="selectedIds.includes(r.id)">✓</text>
+          </view>
+        </view>
+      </view>
+
+      <view class="load-tip" v-if="loading">
+        <view class="dot-row">
+          <view class="pulse-dot"></view>
+          <view class="pulse-dot"></view>
+          <view class="pulse-dot"></view>
+        </view>
+      </view>
+
+      <view class="empty-state" v-if="!loading && replenisherList.length === 0">
+        <view class="empty-icon">
+          <view class="empty-person">
+            <view class="ep-head"></view>
+            <view class="ep-body"></view>
+          </view>
+        </view>
+        <text class="empty-title">暂无可用补货员</text>
+        <text class="empty-desc">请先在补货员管理中新增补货员</text>
+      </view>
+
+      <view class="bottom-safe"></view>
+    </scroll-view>
+
+    <!-- 底部操作栏 -->
+    <view class="bottom-bar" v-if="!loading">
+      <view class="bb-info">
+        <text>变更:新增 {{ toBind.length }} 名,移除 {{ toUnbind.length }} 名</text>
+      </view>
+      <view
+        class="bb-btn"
+        :class="{ disabled: (toBind.length === 0 && toUnbind.length === 0) || saving }"
+        @click="handleSave"
+      >
+        <view class="bb-spinner" v-if="saving"></view>
+        <text class="bb-btn-text" v-else>保存</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import NavBar from '@/components/NavBar.vue';
+import {
+  getReplenisherList,
+  getBoundDevices,
+  bindDevices,
+  unbindDevice
+} from '@/api/replenisher';
+
+const deviceId = ref('');
+const deviceName = ref('');
+const loading = ref(true);
+const saving = ref(false);
+const replenisherList = ref<any[]>([]);
+const selectedIds = ref<string[]>([]);
+const origBoundIds = ref<string[]>([]);
+
+const toBind = computed(() => selectedIds.value.filter(id => !origBoundIds.value.includes(id)));
+const toUnbind = computed(() => origBoundIds.value.filter(id => !selectedIds.value.includes(id)));
+
+const toggleReplenisher = (id: string) => {
+  const idx = selectedIds.value.indexOf(id);
+  if (idx >= 0) {
+    selectedIds.value.splice(idx, 1);
+  } else {
+    selectedIds.value.push(id);
+  }
+};
+
+const handleSave = async () => {
+  if (saving.value) return;
+  if (toBind.value.length === 0 && toUnbind.value.length === 0) {
+    uni.showToast({ title: '未做任何变更', icon: 'none' });
+    return;
+  }
+  saving.value = true;
+  uni.showLoading({ title: '保存中...', mask: true });
+  try {
+    if (toBind.value.length > 0) {
+      await Promise.all(toBind.value.map(id => bindDevices(id, [deviceId.value])));
+    }
+    if (toUnbind.value.length > 0) {
+      await Promise.all(toUnbind.value.map(id => unbindDevice(id, deviceId.value)));
+    }
+    uni.hideLoading();
+    uni.showToast({ title: '保存成功', icon: 'success' });
+    setTimeout(() => {
+      uni.navigateBack();
+    }, 1200);
+  } catch (e) {
+    uni.hideLoading();
+    uni.showToast({ title: '保存失败', icon: 'none' });
+  } finally {
+    saving.value = false;
+  }
+};
+
+onMounted(async () => {
+  const pages = getCurrentPages();
+  const page = pages[pages.length - 1];
+  const options = (page as Record<string, any>).options || {};
+  deviceId.value = options.deviceId || '';
+  deviceName.value = decodeURIComponent(options.deviceName || '');
+
+  if (!deviceId.value) {
+    uni.showToast({ title: '参数错误', icon: 'none' });
+    return;
+  }
+
+  try {
+    const res = await getReplenisherList({ page: 1, pageSize: 200, status: 1 });
+    const list = res.list || [];
+    replenisherList.value = list;
+
+    const boundResults = await Promise.all(
+      list.map(async (r: any) => {
+        try {
+          const devices = await getBoundDevices(r.id);
+          return { id: r.id, bound: (devices || []).includes(deviceId.value) };
+        } catch {
+          return { id: r.id, bound: false };
+        }
+      })
+    );
+
+    origBoundIds.value = boundResults.filter(r => r.bound).map(r => r.id);
+    selectedIds.value = [...origBoundIds.value];
+  } catch (e) {
+    uni.showToast({ title: '加载失败', icon: 'none' });
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+$font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif;
+
+.page {
+  height: 100vh;
+  width: 100vw;
+  overflow-x: hidden;
+  background: $bg-color-page;
+  display: flex;
+  flex-direction: column;
+  font-family: $font-stack;
+}
+
+// ====== Header ======
+.header-card {
+  display: flex;
+  align-items: center;
+  background: $bg-color-card;
+  padding: $spacing-3 $spacing-4;
+  gap: $spacing-2;
+  flex-shrink: 0;
+  border-bottom: 1rpx solid $border-color-light;
+}
+
+.hc-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 18rpx;
+  background: $success-color-bg;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.icon-device {
+  width: 36rpx;
+  height: 28rpx;
+  border: 3rpx solid $primary-color;
+  border-radius: 6rpx;
+  position: relative;
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: -8rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 16rpx;
+    height: 8rpx;
+    background: $primary-color;
+    border-radius: 0 0 4rpx 4rpx;
+  }
+}
+
+.hc-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.hc-name {
+  display: block;
+  font-size: 30rpx;
+  font-weight: 600;
+  color: $text-color-primary;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.hc-id {
+  font-size: 24rpx;
+  color: $text-color-muted;
+  margin-top: 4rpx;
+  display: block;
+  font-family: monospace;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+// ====== Stats ======
+.stats-bar {
+  padding: $spacing-2 $spacing-4;
+  font-size: 24rpx;
+  color: $text-color-muted;
+  flex-shrink: 0;
+
+  .sb-num {
+    font-weight: 600;
+    color: $primary-color-dark;
+  }
+}
+
+// ====== Scroll ======
+.page-scroll {
+  flex: 1;
+  height: 0;
+  padding: 0 $spacing-3;
+  overflow-x: hidden;
+}
+
+.list {
+  display: flex;
+  flex-direction: column;
+  gap: $spacing-2;
+  padding-top: $spacing-2;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+// ====== Item ======
+.item {
+  display: flex;
+  align-items: center;
+  background: $bg-color-card;
+  border-radius: $radius-md;
+  padding: $spacing-3 $spacing-3;
+  gap: $spacing-2;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+  transition: opacity 0.15s ease;
+  width: 100%;
+  box-sizing: border-box;
+
+  &:active { opacity: 0.7; }
+}
+
+.item-avatar {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  background: $primary-color-bg;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+
+  text {
+    font-size: 28rpx;
+    font-weight: 700;
+    color: $primary-color-dark;
+  }
+
+  &.off {
+    background: $bg-color-page;
+    text { color: $text-color-placeholder; }
+  }
+}
+
+.item-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.item-name {
+  display: block;
+  font-size: 28rpx;
+  font-weight: 500;
+  color: $text-color-primary;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.item-sub {
+  font-size: 24rpx;
+  color: $text-color-muted;
+  margin-top: 4rpx;
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.item-check {
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: $radius-sm;
+  border: 2rpx solid $border-color;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  font-size: 24rpx;
+  color: transparent;
+  transition: all 0.15s ease;
+
+  &.on {
+    background: $primary-color;
+    border-color: $primary-color;
+    color: $text-color-primary;
+  }
+}
+
+// ====== Bottom bar ======
+.bottom-bar {
+  display: flex;
+  align-items: center;
+  padding: $spacing-2 $spacing-3;
+  padding-bottom: calc($spacing-2 + constant(safe-area-inset-bottom));
+  padding-bottom: calc($spacing-2 + env(safe-area-inset-bottom));
+  background: $bg-color-card;
+  border-top: 1rpx solid $border-color-light;
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.05);
+  gap: $spacing-2;
+  flex-shrink: 0;
+}
+
+.bb-info {
+  flex: 1;
+  font-size: 22rpx;
+  color: $text-color-muted;
+}
+
+.bb-btn {
+  background: $primary-color;
+  border-radius: $radius-full;
+  padding: 18rpx 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 120rpx;
+  transition: opacity 0.15s ease;
+
+  &:active { opacity: 0.8; }
+
+  &.disabled {
+    opacity: 0.4;
+    pointer-events: none;
+  }
+}
+
+.bb-btn-text {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: $text-color-primary;
+}
+
+.bb-spinner {
+  width: 28rpx;
+  height: 28rpx;
+  border: 3rpx solid rgba(30, 41, 59, 0.2);
+  border-top-color: $text-color-primary;
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+// ====== Load / Empty ======
+.load-tip {
+  display: flex;
+  justify-content: center;
+  padding: 60rpx 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; }
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 100rpx 0 60rpx;
+  gap: $spacing-2;
+}
+
+.empty-icon {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 24rpx;
+  background: $bg-color-secondary;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 4rpx;
+}
+
+.empty-person {
+  position: relative;
+  width: 40rpx;
+  height: 48rpx;
+
+  .ep-head {
+    position: absolute; top: 0; left: 50%;
+    transform: translateX(-50%);
+    width: 18rpx; height: 18rpx;
+    background: $text-color-placeholder;
+    border-radius: 50%;
+  }
+  .ep-body {
+    position: absolute; bottom: 0; left: 50%;
+    transform: translateX(-50%);
+    width: 34rpx; height: 20rpx;
+    background: $text-color-placeholder;
+    border-radius: 17rpx 17rpx 4rpx 4rpx;
+  }
+}
+
+.empty-title {
+  font-size: 28rpx;
+  color: $text-color-secondary;
+  font-weight: 500;
+}
+
+.empty-desc {
+  font-size: 24rpx;
+  color: $text-color-muted;
+}
+
+.bottom-safe {
+  height: $spacing-5;
+}
+</style>

+ 4 - 0
haha-admin-mp/src/pages/distribution/index.vue

@@ -279,6 +279,8 @@ const goToRecords = () => {
 .btn-primary {
   width: 100%;
   height: 96rpx;
+  line-height: 96rpx;
+  padding: 0;
   background: linear-gradient(135deg, $primary-color 0%, $primary-color-dark 100%);
   border-radius: 48rpx;
   display: flex;
@@ -298,6 +300,8 @@ const goToRecords = () => {
 .btn-secondary {
   width: 100%;
   height: 88rpx;
+  line-height: 88rpx;
+  padding: 0;
   background: $bg-color-card;
   border: 2rpx solid $border-color;
   border-radius: 44rpx;

+ 2 - 0
haha-admin-mp/src/pages/distribution/withdrawal.vue

@@ -280,6 +280,8 @@ const handleSubmit = async () => {
 .btn-submit {
   width: 100%;
   height: 96rpx;
+  line-height: 96rpx;
+  padding: 0;
   background: linear-gradient(135deg, $primary-color 0%, $primary-color-dark 100%);
   border-radius: 48rpx;
   display: flex;

+ 5 - 1
haha-admin-mp/src/pages/index/index.vue

@@ -64,7 +64,7 @@
       <view class="device-card">
         <view class="card-title">
           <text>设备概况</text>
-          <text class="card-more" @click="navigateTo('/pages/device/list')">查看全部 ›</text>
+          <text class="card-more" @click="switchToTab('/pages/device/list')">查看全部 ›</text>
         </view>
         <view class="device-row">
           <view class="device-box online">
@@ -167,6 +167,10 @@ const navigateTo = (url: string) => {
   uni.navigateTo({ url });
 };
 
+const switchToTab = (url: string) => {
+  uni.switchTab({ url });
+};
+
 const handleNoticeClick = () => {
   uni.navigateTo({ url: '/pages/announcement/list' });
 };

+ 7 - 0
haha-admin-mp/src/pages/invite/index.vue

@@ -538,6 +538,7 @@ const goToRecords = () => {
 
 .copy-btn {
   padding: 16rpx 32rpx;
+  line-height: 1;
   background: $info-color;
   border-radius: 12rpx;
   font-size: 26rpx;
@@ -557,6 +558,8 @@ const goToRecords = () => {
 .btn-primary {
   width: 100%;
   height: 96rpx;
+  line-height: 96rpx;
+  padding: 0;
   background: linear-gradient(135deg, $primary-color 0%, $primary-color-dark 100%);
   border-radius: 48rpx;
   display: flex;
@@ -591,6 +594,8 @@ const goToRecords = () => {
 .btn-secondary {
   width: 100%;
   height: 96rpx;
+  line-height: 96rpx;
+  padding: 0;
   background: $bg-color-card;
   border: 2rpx solid $info-color;
   border-radius: 48rpx;
@@ -615,6 +620,8 @@ const goToRecords = () => {
 .btn-link {
   width: 100%;
   height: 88rpx;
+  line-height: 88rpx;
+  padding: 0;
   background: transparent;
   border: none;
   display: flex;

+ 6 - 0
haha-admin-mp/src/pages/login/login.vue

@@ -449,6 +449,8 @@ const handleBindWechat = async () => {
 .login-btn {
   width: 100%;
   height: 100rpx;
+  line-height: 100rpx;
+  padding: 0;
   background: $primary-color;
   color: #fff;
   font-size: 32rpx;
@@ -554,6 +556,8 @@ const handleBindWechat = async () => {
 .wechat-btn {
   width: 100%;
   height: 100rpx;
+  line-height: 100rpx;
+  padding: 0;
   background: $bg-color-card;
   color: $text-color-primary;
   font-size: 30rpx;
@@ -669,6 +673,8 @@ const handleBindWechat = async () => {
   .bind-btn {
     width: 100%;
     height: 90rpx;
+    line-height: 90rpx;
+    padding: 0;
     background: $primary-color;
     color: #fff;
     font-size: 30rpx;

+ 309 - 347
haha-admin-mp/src/pages/my/my.vue

@@ -1,192 +1,171 @@
 <template>
   <view class="page">
-    <!-- 导航栏 -->
     <NavBar title="我的" />
-    
-    <!-- 用户卡片 -->
-    <view class="user-section">
-      <view class="user-card">
-        <view class="user-header">
-          <view class="avatar">
-            <view class="avatar-icon">
-              <text class="avatar-text">{{ userInfo.nickname?.charAt(0) || 'A' }}</text>
-            </view>
-          </view>
-          <view class="user-info">
-            <text class="user-name">{{ userInfo.nickname || '未登录' }}</text>
-            <text class="user-role">{{ userInfo.roleName || '管理员' }}</text>
-          </view>
-        </view>
-        
-        <view class="user-stats">
-          <view class="stat-item">
-            <text class="stat-value">12</text>
-            <text class="stat-label">管理门店</text>
-          </view>
-          <view class="stat-divider"></view>
-          <view class="stat-item">
-            <text class="stat-value">48</text>
-            <text class="stat-label">设备数量</text>
-          </view>
-          <view class="stat-divider"></view>
-          <view class="stat-item">
-            <text class="stat-value">156</text>
-            <text class="stat-label">今日订单</text>
-          </view>
-        </view>
+
+    <!-- Loading -->
+    <view class="load-tip" v-if="loading">
+      <view class="dot-row">
+        <view class="pulse-dot"></view>
+        <view class="pulse-dot"></view>
+        <view class="pulse-dot"></view>
       </view>
     </view>
-    
-    <!-- 菜单区域 -->
-    <view class="menu-section">
-      <!-- 补货员菜单 -->
-      <view class="menu-card" v-if="isReplenisherUser">
-        <text class="menu-title">补货管理</text>
-        <view class="menu-list">
-          <view class="menu-item" @click="handleReplenisherHome">
-            <view class="menu-icon green">
-              <view class="icon-device"></view>
+
+    <template v-else>
+      <!-- User card -->
+      <view class="user-section">
+        <view class="user-card">
+          <view class="user-header">
+            <view class="avatar">
+              <text class="avatar-text">{{ userInfo.nickname?.charAt(0) || 'A' }}</text>
             </view>
-            <view class="menu-content">
-              <text class="menu-name">补货首页</text>
-              <text class="menu-desc">查看设备和执行补货</text>
+            <view class="user-info">
+              <text class="user-name">{{ userInfo.nickname || '未登录' }}</text>
+              <text class="user-role">{{ userInfo.roleName || '管理员' }}</text>
             </view>
-            <view class="menu-arrow"></view>
           </view>
         </view>
       </view>
 
-      <!-- 管理员菜单 -->
-      <view class="menu-card" v-if="!isReplenisherUser">
-        <text class="menu-title">业务管理</text>
-        <view class="menu-list">
-          <view class="menu-item" @click="navigateTo('/pages/shop/list')">
-            <view class="menu-icon amber">
-              <view class="icon-shop"></view>
-            </view>
-            <view class="menu-content">
-              <text class="menu-name">门店管理</text>
-              <text class="menu-desc">查看和管理门店信息</text>
+      <!-- Menu sections -->
+      <view class="menu-section">
+        <!-- Replenisher menu -->
+        <view class="menu-card" v-if="isReplenisherUser">
+          <text class="menu-title">补货管理</text>
+          <view class="menu-list">
+            <view class="menu-item" @click="handleReplenisherHome">
+              <view class="menu-icon green">
+                <view class="icon-device"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">补货首页</text>
+                <text class="menu-desc">查看设备和执行补货</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-arrow"></view>
           </view>
+        </view>
 
-          <view class="menu-item" @click="navigateTo('/pages/products/list')">
-            <view class="menu-icon purple">
-              <view class="icon-product"></view>
-            </view>
-            <view class="menu-content">
-              <text class="menu-name">商品管理</text>
-              <text class="menu-desc">管理商品和价格</text>
+        <!-- Admin menu -->
+        <view class="menu-card" v-if="!isReplenisherUser">
+          <text class="menu-title">业务管理</text>
+          <view class="menu-list">
+            <view class="menu-item" @click="navigateTo('/pages/shop/list')">
+              <view class="menu-icon amber">
+                <view class="icon-shop"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">门店管理</text>
+                <text class="menu-desc">查看和管理门店信息</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-arrow"></view>
-          </view>
 
-          <view class="menu-item" @click="navigateTo('/pages/inventory/query')">
-            <view class="menu-icon cyan">
-              <view class="icon-chart"></view>
+            <view class="menu-item" @click="navigateTo('/pages/products/list')">
+              <view class="menu-icon purple">
+                <view class="icon-product"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">商品管理</text>
+                <text class="menu-desc">管理商品和价格</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-content">
-              <text class="menu-name">库存查询</text>
-              <text class="menu-desc">查看库存情况</text>
-            </view>
-            <view class="menu-arrow"></view>
-          </view>
 
-          <view class="menu-item" @click="navigateTo('/pages/inventory/warning')">
-            <view class="menu-icon rose">
-              <view class="icon-warning"></view>
+            <view class="menu-item" @click="navigateTo('/pages/inventory/query')">
+              <view class="menu-icon cyan">
+                <view class="icon-chart"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">库存查询</text>
+                <text class="menu-desc">查看库存情况</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-content">
-              <text class="menu-name">库存预警</text>
-              <text class="menu-desc">低库存和缺货提醒</text>
+
+            <view class="menu-item" @click="navigateTo('/pages/inventory/warning')">
+              <view class="menu-icon rose">
+                <view class="icon-warning"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">库存预警</text>
+                <text class="menu-desc">低库存和缺货提醒</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-arrow"></view>
-          </view>
 
-          <view class="menu-item" @click="navigateTo('/pages/customer/list')">
-            <view class="menu-icon green">
-              <view class="icon-user"></view>
+            <view class="menu-item" @click="navigateTo('/pages/customer/list')">
+              <view class="menu-icon green">
+                <view class="icon-user"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">客户管理</text>
+                <text class="menu-desc">查看客户信息和信用分</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-content">
-              <text class="menu-name">客户管理</text>
-              <text class="menu-desc">查看客户信息和信用分</text>
+
+            <view class="menu-item" @click="navigateTo('/pages/statistics/overview')">
+              <view class="menu-icon blue">
+                <view class="icon-stats"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">数据统计</text>
+                <text class="menu-desc">经营数据和销售排行</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-arrow"></view>
           </view>
+        </view>
 
-          <view class="menu-item" @click="navigateTo('/pages/statistics/overview')">
-            <view class="menu-icon blue">
-              <view class="icon-stats"></view>
+        <!-- Replenisher system menu -->
+        <view class="menu-card" v-if="isReplenisherUser">
+          <text class="menu-title">系统设置</text>
+          <view class="menu-list">
+            <view class="menu-item" @click="handleAbout">
+              <view class="menu-icon blue">
+                <view class="icon-info"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">关于我们</text>
+                <text class="menu-desc">版本信息</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-content">
-              <text class="menu-name">数据统计</text>
-              <text class="menu-desc">经营数据和销售排行</text>
-            </view>
-            <view class="menu-arrow"></view>
           </view>
         </view>
-      </view>
-      
-      <!-- 补货员设置的关闭按钮 -->
-      <view class="menu-card" v-if="isReplenisherUser">
-        <text class="menu-title">系统设置</text>
-        <view class="menu-list">
-          <view class="menu-item" @click="handleAbout">
-            <view class="menu-icon blue">
-              <view class="icon-info"></view>
-            </view>
-            <view class="menu-content">
-              <text class="menu-name">关于我们</text>
-              <text class="menu-desc">版本信息</text>
+
+        <!-- Admin system menu -->
+        <view class="menu-card" v-if="!isReplenisherUser">
+          <text class="menu-title">系统设置</text>
+          <view class="menu-list">
+            <view class="menu-item" @click="handleAbout">
+              <view class="menu-icon blue">
+                <view class="icon-info"></view>
+              </view>
+              <view class="menu-content">
+                <text class="menu-name">关于我们</text>
+                <text class="menu-desc">版本信息</text>
+              </view>
+              <view class="menu-arrow"></view>
             </view>
-            <view class="menu-arrow"></view>
           </view>
         </view>
       </view>
-      
-      <!-- 管理员菜单 -->
-      <view class="menu-card" v-if="!isReplenisherUser">
-        <text class="menu-title">系统设置</text>
-        <view class="menu-list">
-          <view class="menu-item" @click="handleChangePassword">
-            <view class="menu-icon green">
-              <view class="icon-lock"></view>
-            </view>
-            <view class="menu-content">
-              <text class="menu-name">修改密码</text>
-              <text class="menu-desc">更改登录密码</text>
-            </view>
-            <view class="menu-arrow"></view>
-          </view>
-          
-          <view class="menu-item" @click="handleAbout">
-            <view class="menu-icon blue">
-              <view class="icon-info"></view>
-            </view>
-            <view class="menu-content">
-              <text class="menu-name">关于我们</text>
-              <text class="menu-desc">版本信息</text>
-            </view>
-            <view class="menu-arrow"></view>
-          </view>
+
+      <!-- Logout -->
+      <view class="logout-section">
+        <view class="logout-btn" @click="handleLogout">
+          <text>退出登录</text>
         </view>
       </view>
-    </view>
-    
-    <!-- 退出登录 -->
-    <view class="logout-section">
-      <button class="logout-btn" @click="handleLogout">
-        <text>退出登录</text>
-      </button>
-    </view>
-    
-    <!-- 版本信息 -->
-    <view class="version-info">
-      <text>版本 1.0.0</text>
-    </view>
-    
-    <!-- 自定义TabBar -->
+
+      <!-- Version -->
+      <view class="version-info">
+        <text>版本 1.0.0</text>
+      </view>
+    </template>
+
     <CustomTabBar />
   </view>
 </template>
@@ -196,17 +175,22 @@ import { ref, onMounted } from 'vue';
 import NavBar from '@/components/NavBar.vue';
 import CustomTabBar from '@/components/CustomTabBar.vue';
 import { getUserInfo, getReplenisherInfo, isReplenisher, clearAuth } from '@/utils/auth';
-import { showConfirm, showToast } from '@/utils/common';
+import { showConfirm } from '@/utils/common';
+
+interface UserInfo {
+  nickname?: string;
+  roleName?: string;
+  [key: string]: any;
+}
 
-const userInfo = ref<any>({});
+const userInfo = ref<UserInfo>({});
 const isReplenisherUser = ref(false);
+const loading = ref(true);
 
 const loadUserInfo = () => {
-  // 判断用户类型
   isReplenisherUser.value = isReplenisher();
-  
+
   if (isReplenisherUser.value) {
-    // 补货员使用补货员信息
     const info = getReplenisherInfo();
     if (info) {
       userInfo.value = {
@@ -215,7 +199,6 @@ const loadUserInfo = () => {
       };
     }
   } else {
-    // 管理员使用原有用户信息
     const info = getUserInfo();
     if (info) {
       userInfo.value = info;
@@ -227,12 +210,8 @@ const navigateTo = (url: string) => {
   uni.navigateTo({ url });
 };
 
-const handleChangePassword = () => {
-  showToast('修改密码功能开发中');
-};
-
 const handleAbout = () => {
-  showToast('哈哈运营平台 v1.0.0');
+  uni.showToast({ title: '哈哈运营平台 v1.0.0', icon: 'none' });
 };
 
 const handleReplenisherHome = () => {
@@ -249,131 +228,118 @@ const handleLogout = async () => {
 
 onMounted(() => {
   loadUserInfo();
+  loading.value = false;
 });
 </script>
 
 <style lang="scss" scoped>
+$font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif;
+
 .page {
   min-height: 100vh;
+  width: 100vw;
+  overflow-x: hidden;
   background: $bg-color-page;
-  padding-bottom: 200rpx;
+  padding-bottom: calc(40rpx + constant(safe-area-inset-bottom));
+  padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
+  font-family: $font-stack;
+}
+
+/* Loading */
+.load-tip {
+  display: flex;
+  justify-content: center;
+  padding: 120rpx 0;
 }
 
-/* 用户区域 */
+.dot-row {
+  display: flex;
+  gap: $spacing-1;
+}
+
+.pulse-dot {
+  width: 9rpx;
+  height: 9rpx;
+  background: $text-color-placeholder;
+  border-radius: $radius-full;
+  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; }
+}
+
+/* User section */
 .user-section {
-  padding: 16rpx 24rpx;
+  padding: $spacing-2 $spacing-3;
 }
 
 .user-card {
   background: $bg-color-card;
-  border: 1rpx solid $border-color;
-  border-radius: 20rpx;
-  padding: 32rpx;
+  border-radius: $radius-lg;
+  padding: $spacing-4;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
 }
 
 .user-header {
   display: flex;
   align-items: center;
-  margin-bottom: 24rpx;
-  
+
   .avatar {
     width: 96rpx;
     height: 96rpx;
-    background: $success-color-bg;
-    border-radius: 24rpx;
+    background: $primary-color-bg;
+    border-radius: $radius-xl;
     display: flex;
     align-items: center;
     justify-content: center;
-    margin-right: 24rpx;
-    border: 2rpx solid $primary-color;
-    
-    .avatar-icon {
-      width: 100%;
-      height: 100%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-    
+    margin-right: $spacing-3;
+
     .avatar-text {
-      font-size: 40rpx;
+      font-size: $font-size-2xl;
       font-weight: 700;
       color: $primary-color;
     }
   }
-  
+
   .user-info {
     flex: 1;
-    
+
     .user-name {
       display: block;
-      font-size: 34rpx;
+      font-size: $font-size-lg;
       font-weight: 700;
       color: $text-color-primary;
       margin-bottom: 4rpx;
     }
-    
-    .user-role {
-      font-size: 26rpx;
-      color: $text-color-tertiary;
-    }
-  }
-}
 
-.user-stats {
-  display: flex;
-  background: $bg-color-page;
-  border-radius: 12rpx;
-  padding: 20rpx 0;
-  
-  .stat-item {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    
-    .stat-value {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 32rpx;
-      font-weight: 700;
-      color: $text-color-primary;
-      margin-bottom: 4rpx;
-    }
-    
-    .stat-label {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 22rpx;
+    .user-role {
+      font-size: $font-size-sm;
       color: $text-color-tertiary;
     }
   }
-  
-  .stat-divider {
-    width: 1rpx;
-    background: $border-color;
-  }
 }
 
-/* 菜单区域 */
+/* Menu section */
 .menu-section {
-  padding: 0 24rpx;
+  padding: 0 $spacing-3;
 }
 
 .menu-card {
   background: $bg-color-card;
-  border: 1rpx solid $border-color;
-  border-radius: 20rpx;
-  margin-bottom: 16rpx;
+  border-radius: $radius-lg;
+  margin-bottom: $spacing-2;
   overflow: hidden;
-  
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
   .menu-title {
     display: block;
-    padding: 20rpx 24rpx 12rpx;
-    font-size: 24rpx;
+    padding: $spacing-3 $spacing-3 $spacing-1;
+    font-size: $font-size-sm;
     color: $text-color-muted;
     font-weight: 600;
   }
@@ -383,42 +349,43 @@ onMounted(() => {
   .menu-item {
     display: flex;
     align-items: center;
-    padding: 20rpx 24rpx;
-    border-bottom: 1rpx solid $bg-color-secondary;
-    transition: background 0.15s;
-    
+    padding: $spacing-3;
+    border-bottom: 1rpx solid $border-color-light;
+    transition: opacity 0.15s ease;
+
     &:last-child {
       border-bottom: none;
     }
-    
+
     &:active {
-      background: $bg-color-page;
+      opacity: 0.7;
     }
   }
 }
 
+/* Menu icons */
 .menu-icon {
   width: 56rpx;
   height: 56rpx;
-  border-radius: 14rpx;
+  border-radius: $radius-md;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-right: 16rpx;
-  
-  &.green { background: $success-color-bg; }
-  &.blue { background: $info-color-bg; }
-  &.amber { background: $accent-color-bg; }
+  margin-right: $spacing-2;
+
+  &.green  { background: $success-color-bg; }
+  &.blue   { background: $info-color-bg; }
+  &.amber  { background: $primary-color-bg; }
   &.purple { background: $primary-color-bg; }
-  &.cyan { background: $primary-color-bg; }
-  
-  /* 图标 - 纯CSS几何图形 */
+  &.cyan   { background: $info-color-bg; }
+  &.rose   { background: $warning-color-bg; }
+
   .icon-shop {
     width: 22rpx;
     height: 22rpx;
     background: $accent-color;
     position: relative;
-    
+
     &::before {
       content: '';
       position: absolute;
@@ -432,14 +399,14 @@ onMounted(() => {
       border-bottom: 10rpx solid $accent-color;
     }
   }
-  
+
   .icon-product {
     width: 20rpx;
     height: 18rpx;
     background: $primary-color;
-    border-radius: 4rpx;
+    border-radius: $radius-xs;
     position: relative;
-    
+
     &::before {
       content: '';
       position: absolute;
@@ -453,13 +420,13 @@ onMounted(() => {
       border-bottom: 10rpx solid $primary-color;
     }
   }
-  
+
   .icon-chart {
     display: flex;
     align-items: flex-end;
     width: 24rpx;
     height: 20rpx;
-    
+
     &::before, &, &::after {
       content: '';
       width: 6rpx;
@@ -471,65 +438,12 @@ onMounted(() => {
     & { height: 18rpx; }
     &::after { height: 14rpx; margin-right: 0; }
   }
-  
-  .icon-lock {
-    width: 16rpx;
-    height: 20rpx;
-    background: $primary-color;
-    border-radius: 4rpx;
-    position: relative;
-    
-    &::before {
-      content: '';
-      position: absolute;
-      top: -6rpx;
-      left: 50%;
-      transform: translateX(-50%);
-      width: 10rpx;
-      height: 8rpx;
-      border: 3rpx solid $primary-color;
-      border-bottom: none;
-      border-radius: 8rpx 8rpx 0 0;
-    }
-  }
-  
-  .icon-info {
-    width: 20rpx;
-    height: 20rpx;
-    border: 3rpx solid $info-color;
-    border-radius: 50%;
-    position: relative;
-
-    &::before {
-      content: '';
-      position: absolute;
-      top: 4rpx;
-      left: 50%;
-      transform: translateX(-50%);
-      width: 3rpx;
-      height: 3rpx;
-      background: $info-color;
-      border-radius: 50%;
-    }
-
-    &::after {
-      content: '';
-      position: absolute;
-      top: 10rpx;
-      left: 50%;
-      transform: translateX(-50%);
-      width: 3rpx;
-      height: 6rpx;
-      background: $info-color;
-      border-radius: 2rpx;
-    }
-  }
 
   .icon-warning {
     width: 24rpx;
     height: 24rpx;
-    border: 3rpx solid $error-color;
-    border-radius: 50%;
+    border: 3rpx solid $warning-color;
+    border-radius: $radius-full;
     position: relative;
 
     &::before {
@@ -540,7 +454,7 @@ onMounted(() => {
       transform: translate(-50%, -50%);
       width: 3rpx;
       height: 10rpx;
-      background: $error-color;
+      background: $warning-color;
     }
 
     &::after {
@@ -551,16 +465,16 @@ onMounted(() => {
       transform: translateX(-50%);
       width: 3rpx;
       height: 3rpx;
-      background: $error-color;
-      border-radius: 50%;
+      background: $warning-color;
+      border-radius: $radius-full;
     }
   }
 
   .icon-user {
     width: 20rpx;
     height: 20rpx;
-    border: 3rpx solid $primary-color;
-    border-radius: 50%;
+    border: 3rpx solid $success-color;
+    border-radius: $radius-full;
     position: relative;
 
     &::before {
@@ -571,8 +485,8 @@ onMounted(() => {
       transform: translateX(-50%);
       width: 10rpx;
       height: 10rpx;
-      border: 3rpx solid $primary-color;
-      border-radius: 50%;
+      border: 3rpx solid $success-color;
+      border-radius: $radius-full;
     }
   }
 
@@ -594,11 +508,43 @@ onMounted(() => {
     &::after { height: 12rpx; }
   }
 
+  .icon-info {
+    width: 20rpx;
+    height: 20rpx;
+    border: 3rpx solid $info-color;
+    border-radius: $radius-full;
+    position: relative;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 4rpx;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 3rpx;
+      height: 3rpx;
+      background: $info-color;
+      border-radius: $radius-full;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 10rpx;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 3rpx;
+      height: 6rpx;
+      background: $info-color;
+      border-radius: 2rpx;
+    }
+  }
+
   .icon-device {
     width: 20rpx;
     height: 16rpx;
-    border: 3rpx solid $primary-color;
-    border-radius: 4rpx;
+    border: 3rpx solid $success-color;
+    border-radius: $radius-xs;
     position: relative;
 
     &::before {
@@ -609,25 +555,46 @@ onMounted(() => {
       transform: translateX(-50%);
       width: 12rpx;
       height: 6rpx;
-      background: $primary-color;
+      background: $success-color;
       border-radius: 0 0 4rpx 4rpx;
     }
   }
+
+  .icon-lock {
+    width: 16rpx;
+    height: 20rpx;
+    background: $primary-color;
+    border-radius: $radius-xs;
+    position: relative;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: -6rpx;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 10rpx;
+      height: 8rpx;
+      border: 3rpx solid $primary-color;
+      border-bottom: none;
+      border-radius: 8rpx 8rpx 0 0;
+    }
+  }
 }
 
 .menu-content {
   flex: 1;
-  
+
   .menu-name {
     display: block;
-    font-size: 30rpx;
+    font-size: $font-size-md;
     color: $text-color-primary;
     font-weight: 500;
     margin-bottom: 2rpx;
   }
-  
+
   .menu-desc {
-    font-size: 24rpx;
+    font-size: $font-size-sm;
     color: $text-color-muted;
   }
 }
@@ -640,42 +607,37 @@ onMounted(() => {
   transform: rotate(45deg);
 }
 
-/* 退出登录 */
+/* Logout */
 .logout-section {
-  padding: 24rpx 24rpx 0;
-  
+  padding: $spacing-3 $spacing-3 0;
+
   .logout-btn {
     width: 100%;
     height: 100rpx;
+    line-height: 100rpx;
+    padding: 0;
     background: $error-color-bg;
-    border: 1rpx solid $error-color-bg;
-    border-radius: 16rpx;
-    font-size: 32rpx;
+    border-radius: $radius-md;
+    font-size: $font-size-lg;
     font-weight: 600;
     color: $error-color;
     display: flex;
     align-items: center;
     justify-content: center;
-    transition: all 0.2s;
-    
-    &::after {
-      border: none;
-    }
-    
+
     &:active {
-      background: $error-color-bg;
-      transform: scale(0.98);
+      opacity: 0.8;
     }
   }
 }
 
-/* 版本信息 */
+/* Version */
 .version-info {
   text-align: center;
-  padding: 24rpx 0;
-  
+  padding: $spacing-3 0;
+
   text {
-    font-size: 24rpx;
+    font-size: $font-size-sm;
     color: $text-color-placeholder;
   }
 }

+ 2 - 0
haha-admin-mp/src/pages/replenish/bind.vue

@@ -308,6 +308,8 @@ const handleBind = async () => {
 .bind-btn {
   width: 100%;
   height: 100rpx;
+  line-height: 100rpx;
+  padding: 0;
   background: $primary-color;
   color: #fff;
   font-size: 32rpx;

+ 2 - 0
haha-admin-mp/src/pages/replenish/operation.vue

@@ -559,6 +559,8 @@ onMounted(async () => {
   .submit-btn {
     flex: 1;
     height: 88rpx;
+    line-height: 88rpx;
+    padding: 0;
     background: $primary-color;
     color: #fff;
     font-size: 30rpx;