瀏覽代碼

UI调整 登录修改

skyline 1 月之前
父節點
當前提交
d74dc45277

+ 2 - 2
haha-admin/src/main/resources/application.yml

@@ -4,7 +4,7 @@ spring:
 
   # 数据库配置
   datasource:
-    url: jdbc:mysql://server.kuaiyuman.cn:3306/haha?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048
+    url: jdbc:mysql://server.kuaiyuman.cn:3307/haha?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048
     username: root
     password: KuaiyuMan/*-
     driver-class-name: com.mysql.cj.jdbc.Driver
@@ -29,7 +29,7 @@ spring:
   data:
     redis:
       host: server.kuaiyuman.cn
-      port: 6379
+      port: 6380
       password: KtXA^Zx!TZmLEy(@JjB@2(TVG0kdy5)&
       database: 9
       timeout: 30000

+ 18 - 0
haha-miniapp/src/main/java/com/haha/miniapp/config/RestTemplateConfig.java

@@ -0,0 +1,18 @@
+package com.haha.miniapp.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * RestTemplate配置类
+ * 用于HTTP请求,如调用微信API
+ */
+@Configuration
+public class RestTemplateConfig {
+    
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}

+ 27 - 0
haha-miniapp/src/main/java/com/haha/miniapp/controller/LoginController.java

@@ -64,6 +64,33 @@ public class LoginController {
         return result;
     }
     
+    /**
+     * 微信小程序手机号快速登录(新版)
+     * 使用button open-type="getPhoneNumber"获取手机号
+     */
+    @PostMapping("/miniapp-phone")
+    public Result<LoginVO> miniappPhoneLogin(@RequestBody Map<String, String> params) {
+        String code = params.get("code");         // 用于获取openid
+        String phoneCode = params.get("phoneCode"); // 用于获取手机号
+        
+        log.info("[登录] 微信小程序手机号快速登录请求 - code: {}, hasPhoneCode: {}", 
+                code != null ? code.substring(0, Math.min(8, code.length())) + "..." : "null",
+                phoneCode != null);
+        
+        if (code == null || code.isEmpty()) {
+            log.warn("[登录] 微信小程序手机号登录失败 - code为空");
+            return Result.error(400, "登录凭证不能为空");
+        }
+        if (phoneCode == null || phoneCode.isEmpty()) {
+            log.warn("[登录] 微信小程序手机号登录失败 - phoneCode为空");
+            return Result.error(400, "手机号凭证不能为空");
+        }
+        
+        Result<LoginVO> result = loginService.wechatMiniappPhoneLogin(code, phoneCode);
+            
+        return result;
+    }
+    
     @PostMapping("/logout")
     public Result<Void> logout() {
         try {

+ 3 - 4
haha-miniapp/src/main/resources/application.yml

@@ -85,10 +85,9 @@ wechat:
   #   pay-score-notify-url: http://localhost:7077/api/payment/callback/wechat_payscore
   # 小程序配置
   miniapp:
-    app-id: your_wechat_miniapp_appid
-    secret: your_wechat_miniapp_secret
-    token: your_wechat_miniapp_token
-    aes-key: your_wechat_miniapp_aes_key
+    app-id: wxb1cbe4678e0175f2
+    secret: 26c00fd986c628799653aca98803e5a5
+
 
 # 哈哈零兽配置
 haha:

+ 10 - 1
haha-mp/src/App.vue

@@ -53,4 +53,13 @@ const checkLoginStatus = () => {
   }
 };
 </script>
-<style></style>
+<style>
+/* 全局动画样式 */
+@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
+@keyframes slideUp { from { opacity: 0; transform: translateY(40rpx); } to { opacity: 1; transform: translateY(0); } }
+@keyframes slideDown { from { opacity: 0; transform: translateY(-40rpx); } to { opacity: 1; transform: translateY(0); } }
+@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
+@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 0.3; } 50% { transform: scale(1.05); opacity: 0.5; } }
+@keyframes bounce { 0% { transform: scale(0); opacity: 0; } 50% { transform: scale(1.1); } 100% { transform: scale(1); opacity: 1; } }
+@keyframes skeleton { 0%, 100% { background-color: #F5F5F5; } 50% { background-color: #EEEEEE; } }
+</style>

+ 11 - 0
haha-mp/src/api/user.ts

@@ -37,6 +37,17 @@ export const loginByWechatPhone = (params: { code: string; encryptedData?: strin
   return post<LoginResult>('/login/wechat-phone', params);
 };
 
+/**
+ * 微信小程序手机号快速登录(新版)
+ * 使用button open-type="getPhoneNumber"获取手机号
+ * @param params { code, phoneCode }
+ * code: 微信登录凭证(用于获取openid)
+ * phoneCode: 手机号获取凭证(用于获取手机号)
+ */
+export const loginByMiniappPhone = (params: { code: string; phoneCode: string }): Promise<LoginResult> => {
+  return post<LoginResult>('/login/miniapp-phone', params);
+};
+
 /**
  * 获取用户信息
  */

+ 76 - 0
haha-mp/src/components/Skeleton.vue

@@ -0,0 +1,76 @@
+<template>
+  <view class="skeleton" :class="type">
+    <view 
+      class="skeleton-line" 
+      v-for="i in lines" 
+      :key="i" 
+      :style="{ width: getLineLength(i), height: lineHeight }"
+    ></view>
+  </view>
+</template>
+
+<script setup lang="ts">
+interface Props {
+  type?: 'default' | 'avatar' | 'card' | 'list';
+  lines?: number;
+  lineHeight?: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  type: 'default',
+  lines: 3,
+  lineHeight: '24rpx'
+});
+
+const getLineLength = (index: number) => {
+  if (props.type === 'list') {
+    // 列表类型:长度递减
+    const lengths = ['100%', '90%', '80%', '70%'];
+    return lengths[(index - 1) % lengths.length];
+  }
+  
+  if (props.type === 'card') {
+    // 卡片类型:交替长度
+    return index % 2 === 0 ? '100%' : '75%';
+  }
+  
+  return '100%';
+};
+</script>
+
+<style lang="scss">
+.skeleton {
+  width: 100%;
+  
+  &.avatar {
+    display: flex;
+    align-items: center;
+    gap: $spacing-md;
+    
+    .skeleton-line:first-child {
+      width: 80rpx !important;
+      height: 80rpx !important;
+      border-radius: $radius-circle;
+      flex-shrink: 0;
+    }
+  }
+  
+  &.card {
+    padding: $spacing-md;
+    background: $color-bg-primary;
+    border-radius: $radius-lg;
+  }
+  
+  &-line {
+    height: 24rpx;
+    background: $color-bg-tertiary;
+    border-radius: $radius-sm;
+    margin-bottom: $spacing-sm;
+    animation: skeleton 1.5s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+</style>

+ 1 - 1
haha-mp/src/manifest.json

@@ -12,7 +12,7 @@
         "compilerVersion" : 3,
         "splashscreen" : {
             "alwaysShowBeforeRender" : true,
-            "waiting" : true,
+            "waiting" : false,
             "autoclose" : true,
             "delay" : 0
         },

+ 7 - 2
haha-mp/src/pages.json

@@ -101,7 +101,12 @@
 	"globalStyle": {
 		"navigationBarTextStyle": "black",
 		"navigationBarTitleText": "AI零售柜",
-		"navigationBarBackgroundColor": "#FFD700",
-		"backgroundColor": "#F8F8F8"
+		"navigationBarBackgroundColor": "#FFC107",
+		"backgroundColor": "#FAFAFA",
+		"animationType": "pop-in",
+		"animationDuration": 300,
+		"renderingMode": "seperated",
+		"style": "v2",
+		"sitemapLocation": "sitemap.json"
 	}
 }

+ 37 - 2
haha-mp/src/pages/coupons/coupons.vue

@@ -238,7 +238,7 @@ onMounted(() => {
 });
 </script>
 
-<style>
+<style scoped lang="scss">
 .page {
   min-height: 100vh;
   background: linear-gradient(180deg, #FFF8E1 0%, #F5F5F5 30%);
@@ -249,23 +249,34 @@ onMounted(() => {
   display: flex;
   gap: 16rpx;
   padding: 20rpx 32rpx 12rpx;
-  background: #FFF8E1;
+  background: transparent;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  animation: slideDown 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .tab-pill {
   padding: 12rpx 40rpx;
   border-radius: 32rpx;
   background: rgba(0, 0, 0, 0.04);
+  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.95);
+  }
 }
 
 .tab-pill--active {
   background: #1A1A1A;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
 }
 
 .tab-pill-text {
   font-size: 26rpx;
   color: #999999;
   font-weight: 500;
+  transition: color 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .tab-pill--active .tab-pill-text {
@@ -287,6 +298,13 @@ onMounted(() => {
   background: #ffffff;
   box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05), 0 1rpx 4rpx rgba(0, 0, 0, 0.03);
   position: relative;
+  animation: slideUp 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) both;
+  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), box-shadow 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.98);
+    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
+  }
 }
 
 .ticket--dim {
@@ -337,6 +355,7 @@ onMounted(() => {
 .ticket-amount {
   display: flex;
   align-items: baseline;
+  animation: scaleIn 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.2s both;
 }
 
 .ticket-currency {
@@ -499,11 +518,13 @@ onMounted(() => {
   justify-content: center;
   min-height: 55vh;
   padding: 40rpx;
+  animation: fadeIn 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .empty-visual {
   margin-bottom: 40rpx;
   position: relative;
+  animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.2s both;
 }
 
 .empty-ticket-shadow {
@@ -586,12 +607,19 @@ onMounted(() => {
   color: #999999;
   font-weight: 600;
   margin-bottom: 36rpx;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.3s both;
 }
 
 .empty-cta {
   padding: 18rpx 64rpx;
   background: #1A1A1A;
   border-radius: 44rpx;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.4s both;
+  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.95);
+  }
 }
 
 .empty-cta-text {
@@ -608,6 +636,7 @@ onMounted(() => {
   justify-content: center;
   min-height: 55vh;
   gap: 20rpx;
+  animation: fadeIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .loading-spinner {
@@ -633,6 +662,7 @@ onMounted(() => {
   padding: 20rpx 64rpx 56rpx;
   display: flex;
   justify-content: center;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.3s both;
 }
 
 .bottom-btn {
@@ -641,6 +671,11 @@ onMounted(() => {
   background: #1A1A1A;
   border-radius: 44rpx;
   text-align: center;
+  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.98);
+  }
 }
 
 .bottom-btn-label {

+ 228 - 230
haha-mp/src/pages/index/index.vue

@@ -1,37 +1,54 @@
 <template>
   <view class="container">
-    <!-- 中部内容 -->
-    <view class="middle-section">
-      <view class="app-title">AI零售柜</view>
+    <!-- 顶部品牌区 -->
+    <view class="brand-section">
+      <text class="brand-title">AI零售柜</text>
+      <text class="brand-slogan">智能视觉 · 即拿即走</text>
     </view>
 
-    <!-- 核心扫码按钮区域 -->
-    <view class="scan-area">
-      <!-- 左侧我的按钮 -->
-      <view class="bottom-item" @click="goToMy">
-        <image class="bottom-icon" src="/static/icons/my.svg"></image>
-        <view class="bottom-text">我的</view>
-      </view>
-
-      <!-- 核心扫码按钮 -->
+    <!-- 核心操作区 -->
+    <view class="action-section">
       <button class="scan-button" @click="scanCode">
-        <view class="scan-icon">
-          <image class="scan-svg-icon" src="/static/icons/scan.svg"></image>
+        <view class="scan-button-inner">
+          <image class="scan-icon" src="/static/icons/scan.svg" mode="aspectFill"></image>
+          <text class="scan-text">扫码开门</text>
         </view>
-        <view class="scan-text">扫码开门</view>
+        <view class="scan-button-ripple"></view>
       </button>
+    </view>
 
-      <!-- 右侧退款按钮 -->
-      <view class="bottom-item" @click="goToRefund">
-        <image class="bottom-icon" src="/static/icons/refund.svg"></image>
-        <view class="bottom-text">退款</view>
+    <!-- 快捷功能区 -->
+    <view class="quick-actions">
+      <view class="quick-action-item" @click="goToMy">
+        <view class="action-icon">
+          <image src="/static/icons/my.svg" mode="aspectFit"></image>
+        </view>
+        <text class="action-label">我的</text>
+      </view>
+      <view class="quick-action-item" @click="goToOrders">
+        <view class="action-icon">
+          <image src="/static/icons/orders.svg" mode="aspectFit"></image>
+        </view>
+        <text class="action-label">订单</text>
+      </view>
+      <view class="quick-action-item" @click="goToCouponCenter">
+        <view class="action-icon">
+          <image src="/static/icons/coupon.svg" mode="aspectFit"></image>
+        </view>
+        <text class="action-label">优惠券</text>
       </view>
     </view>
 
-    <!-- 底部信息 -->
-    <view class="info-section">
-      <text class="info-text">💚 微信支付分 | 550分及以上优享</text>
-      <text class="info-text">客服电话:400-123-4567</text>
+    <!-- 底部信息卡片 -->
+    <view class="info-card">
+      <view class="info-item">
+        <text class="info-icon">💚</text>
+        <text class="info-text">微信支付分 550分及以上优享</text>
+      </view>
+      <view class="info-item">
+        <text class="info-icon">📞</text>
+        <text class="info-text">客服: 400-0759-515</text>
+      </view>
     </view>
   </view>
 </template>
@@ -41,6 +58,7 @@ import { ref, onMounted } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import { scanDoor } from '../../api/device'
 import { checkPayscoreEnabled } from '../../api/payscore'
+import { getCouponCount } from '../../api/coupon'
 import { isLoggedIn, getToken } from '../../utils/auth'
 
 // 页面显示时检查 token
@@ -193,254 +211,234 @@ const scanCode = async () => {
 };
 
 const goToMy = () => {
-  // 跳转到个人中心
+  uni.vibrateShort({ type: 'light' })
   uni.navigateTo({
     url: '/pages/my/my'
-  });
-};
+  })
+}
 
-const goToRefund = () => {
-  // 跳转到订单列表页面
+const goToOrders = () => {
+  uni.vibrateShort({ type: 'light' })
   uni.navigateTo({
     url: '/pages/orders/orders'
-  });
-};
+  })
+}
+
+const goToCouponCenter = () => {
+  uni.vibrateShort({ type: 'light' })
+  uni.navigateTo({
+    url: '/pages/couponCenter/couponCenter'
+  })
+}
 </script>
 
-<style>
+<style lang="scss">
 .container {
-  display: flex;
-  flex-direction: column;
   min-height: 100vh;
-  background-color: #ffffff;
-  position: relative;
-  padding-bottom: 100rpx;
-  margin: 0;
-  padding-left: 0;
-  padding-right: 0;
-  box-sizing: border-box;
-}
-
-/* 中部内容区域 */
-.middle-section {
-  flex: 1;
+  background: $color-bg-secondary;
   display: flex;
   flex-direction: column;
-  align-items: center;
-  justify-content: flex-start;
-  padding: 120rpx 0 20rpx;
-  position: relative;
-  z-index: 1;
-  min-height: 40vh;
-  flex-basis: 40vh;
+  padding: $spacing-xxl $spacing-lg;
+  padding-bottom: calc(100rpx + constant(safe-area-inset-bottom));
+  padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
+  box-sizing: border-box;
 }
 
-/* 应用标题 */
-.app-title {
-  font-size: 80rpx;
-  font-weight: bold;
-  color: #333;
+/* ========== 品牌区 ========== */
+.brand-section {
   text-align: center;
-  margin-bottom: 40rpx;
-  letter-spacing: 6rpx;
-  position: relative;
-  animation: titleSlide 0.8s ease-out;
-}
-
-/* 标题动画 */
-@keyframes titleSlide {
-  0% {
-    opacity: 0;
-    transform: translateY(-50rpx);
+  padding: $spacing-xxl 0 $spacing-xl;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  .brand-title {
+    font-size: 56rpx;
+    font-weight: 300;
+    color: $color-text-primary;
+    letter-spacing: 8rpx;
+    display: block;
+    margin-bottom: $spacing-sm;
   }
-  100% {
-    opacity: 1;
-    transform: translateY(0);
+  
+  .brand-slogan {
+    font-size: 24rpx;
+    color: $color-text-secondary;
+    letter-spacing: 4rpx;
   }
 }
 
-/* 标题下方装饰线 */
-.app-title::after {
-  content: '';
-  position: absolute;
-  bottom: -20rpx;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 150rpx;
-  height: 6rpx;
-  background-color: #FFD700;
-  border-radius: 3rpx;
-  animation: lineSlide 0.8s ease-out 0.3s both;
-}
-
-/* 装饰线动画 */
-@keyframes lineSlide {
-  0% {
-    width: 0;
-    opacity: 0;
-  }
-  100% {
-    width: 150rpx;
-    opacity: 1;
-  }
-}
-
-
-
-/* 扫码按钮区域 */
-.scan-area {
+/* ========== 核心操作区 ========== */
+.action-section {
   display: flex;
+  justify-content: center;
   align-items: center;
-  justify-content: space-around;
-  padding: 0 80rpx 50rpx;
-  position: relative;
-  z-index: 1;
+  padding: $spacing-xl 0;
+  animation: scaleIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 }
 
-/* 核心扫码按钮 */
 .scan-button {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  width: 350rpx;
-  height: 350rpx;
+  position: relative;
+  width: 400rpx;
+  height: 400rpx;
+  min-width: 400rpx;
+  min-height: 400rpx;
+  aspect-ratio: 1 / 1;
   border-radius: 50%;
-  background-color: #FFD700;
+  background: transparent;
   border: none;
-  box-shadow: 0 8rpx 30rpx rgba(255, 215, 0, 0.6);
-  z-index: 2;
-  transition: all 0.3s ease;
-  position: relative;
+  padding: 0;
+  margin: 0;
   overflow: hidden;
-}
-
-/* 扫码按钮悬停效果 */
-.scan-button:hover {
-  transform: scale(1.05);
-  box-shadow: 0 12rpx 40rpx rgba(255, 215, 0, 0.8);
-}
-
-/* 扫码按钮点击效果 */
-.scan-button:active {
-  transform: scale(0.95);
-  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.6);
-}
-
-/* 扫码按钮脉冲动画 */
-.scan-button::before {
-  content: '';
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  width: 0;
-  height: 0;
-  border-radius: 50%;
-  background-color: rgba(255, 255, 255, 0.3);
-  transform: translate(-50%, -50%);
-  animation: pulse 2s infinite;
-}
-
-@keyframes pulse {
-  0% {
-    width: 0;
-    height: 0;
-    opacity: 0.5;
+  
+  &::after {
+    border: none;
   }
-  100% {
-    width: 400rpx;
-    height: 400rpx;
-    opacity: 0;
+  
+  &-inner {
+    width: 100%;
+    height: 100%;
+    border-radius: 50%;
+    background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
+    box-shadow: $shadow-primary;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    transition: all $duration-normal $ease-out;
+    
+    &:active {
+      transform: scale(0.95);
+      box-shadow: 0 4rpx 16rpx rgba(255, 193, 7, 0.3);
+    }
+  }
+  
+  &-ripple {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 100%;
+    height: 100%;
+    border-radius: 50%;
+    background: $color-primary;
+    transform: translate(-50%, -50%);
+    animation: pulse 2s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+    opacity: 0.3;
+    z-index: -1;
   }
 }
 
-/* 扫码图标 */
 .scan-icon {
-  width: 200rpx;
-  height: 200rpx;
-  margin-bottom: 20rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-/* 扫码SVG图标 */
-.scan-svg-icon {
-  width: 200rpx;
-  height: 200rpx;
+  width: 160rpx;
+  height: 160rpx;
+  margin-bottom: $spacing-md;
+  flex-shrink: 0;
+  display: block;
 }
 
-/* 扫码文字 */
 .scan-text {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
+  font-size: 40rpx;
+  font-weight: 600;
+  color: #1A1A1A;
+  letter-spacing: 4rpx;
 }
 
-/* 底部操作按钮 */
-.bottom-item {
+/* ========== 快捷功能区 ========== */
+.quick-actions {
   display: flex;
-  flex-direction: column;
+  justify-content: center;
   align-items: center;
-  z-index: 1;
-  transition: all 0.3s ease;
-  padding: 20rpx;
-  border-radius: 15rpx;
-}
-
-/* 底部按钮悬停效果 */
-.bottom-item:hover {
-  transform: translateY(-5rpx);
-  background-color: #f5f5f5;
-}
-
-/* 底部按钮点击效果 */
-.bottom-item:active {
-  transform: translateY(0);
-  background-color: #e8e8e8;
-}
-
-/* 底部图标 */
-.bottom-icon {
-  width: 50rpx;
-  height: 50rpx;
-  margin-bottom: 10rpx;
-}
-
-/* 底部文字 */
-.bottom-text {
-  font-size: 24rpx;
-  color: #666;
-}
-
-/* 底部信息区域 */
-.info-section {
-  padding: 30rpx 0;
-  text-align: center;
-  background-color: #fff;
-  border-top: 1rpx solid #eee;
-  position: relative;
-  z-index: 1;
-  animation: infoSlide 0.8s ease-out 0.5s both;
-}
-
-/* 底部信息动画 */
-@keyframes infoSlide {
-  0% {
-    opacity: 0;
-    transform: translateY(30rpx);
+  gap: $spacing-xl;
+  margin: $spacing-xxl 0;
+  animation: slideUp 1s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: $spacing-md;
+    transition: all $duration-fast $ease-out;
+    position: relative;
+    flex: 0 0 auto;
+    
+    &:active {
+      transform: scale(0.9);
+    }
+  }
+  
+  .action-icon {
+    width: 120rpx;
+    height: 120rpx;
+    background: #ffffff;
+    border-radius: $radius-lg;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+    margin-bottom: $spacing-sm;
+    transition: all $duration-fast $ease-out;
+    border: 2rpx solid #F0F0F0;
+    flex-shrink: 0;
+    
+    image {
+      width: 64rpx;
+      height: 64rpx;
+      display: block;
+    }
+    
+    &:active {
+      box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+      transform: translateY(-4rpx);
+      border-color: $color-primary-light;
+    }
   }
-  100% {
-    opacity: 1;
-    transform: translateY(0);
+  
+  .action-label {
+    font-size: 28rpx;
+    color: $color-text-primary;
+    font-weight: 500;
+    text-align: center;
+    display: block;
+    width: auto;
   }
 }
 
-/* 信息文字 */
-.info-text {
-  font-size: 22rpx;
-  color: #999;
-  margin: 8rpx 0;
-  display: block;
+/* ========== 底部信息卡片 ========== */
+.info-card {
+  background: $color-bg-primary;
+  border-radius: $radius-lg;
+  padding: $spacing-lg;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
+  animation: slideUp 1.2s cubic-bezier(0.25, 0.1, 0.25, 1);
+  margin-top: auto;
+  width: 100%;
+  box-sizing: border-box;
+  
+  .info-item {
+    display: flex;
+    align-items: center;
+    padding: $spacing-sm 0;
+    min-height: 60rpx;
+    
+    &:not(:last-child) {
+      border-bottom: 1rpx solid $color-border;
+      padding-bottom: $spacing-md;
+      margin-bottom: $spacing-md;
+    }
+  }
+  
+  .info-icon {
+    font-size: 28rpx;
+    margin-right: $spacing-sm;
+    flex-shrink: 0;
+    line-height: 1;
+  }
+  
+  .info-text {
+    font-size: 24rpx;
+    color: $color-text-secondary;
+    line-height: 1.5;
+    flex: 1;
+    word-break: break-all;
+  }
 }
 </style>

+ 287 - 91
haha-mp/src/pages/login/login.vue

@@ -1,48 +1,84 @@
 <template>
   <view class="container">
-    <view class="login-header">
-      <image class="logo" src="/static/logo.png" mode="aspectFit"></image>
-      <text class="title">快与慢充电桩</text>
-      <text class="subtitle">智能视觉零售柜</text>
+    <!-- 品牌区 -->
+    <view class="brand-section">
+      <view class="brand-logo">
+        <image class="logo" src="/static/brand-logo.svg" mode="aspectFit"></image>
+      </view>
+      <text class="brand-title">欢迎登录</text>
+      <text class="brand-subtitle">快与慢充电桩 · 智能视觉零售柜</text>
     </view>
 
-    <view class="login-content">
-      <!-- 账号密码登录表单 -->
-      <view class="form-item">
-        <input
-          class="input"
-          type="number"
-          placeholder="请输入手机号"
-          v-model="loginForm.phone"
-          maxlength="11"
-        />
-      </view>
-      <view class="form-item">
-        <input
-          class="input"
-          type="password"
-          placeholder="请输入密码"
-          v-model="loginForm.password"
-        />
-      </view>
+    <!-- 登录表单 -->
+    <view class="form-section">
+      <view class="form-card">
+        <!-- 微信手机号快速登录按钮 -->
+        <button 
+          class="wechat-login-btn" 
+          open-type="getPhoneNumber" 
+          @getphonenumber="onGetPhoneNumber"
+          :loading="isLoading"
+        >
+          <text class="btn-icon">📱</text>
+          <text class="btn-text">微信手机号快速登录</text>
+        </button>
 
-      <button class="login-btn" @click="onPasswordLogin">
-        立即登录
-      </button>
-
-      <!-- 微信手机号一键登录 (个人版小程序暂不可用,已注释) -->
-      <!--
-      <button
-        class="login-btn wechat-btn"
-        open-type="getPhoneNumber"
-        @getphonenumber="onGetPhoneNumber"
-      >
-        微信手机号一键登录
-      </button>
-      -->
+        <!-- 分隔线 -->
+        <view class="divider">
+          <view class="divider-line"></view>
+          <text class="divider-text">或</text>
+          <view class="divider-line"></view>
+        </view>
+
+        <!-- 传统账号密码登录 -->
+        <view class="form-item">
+          <view class="input-wrapper">
+            <view class="input-icon">
+              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path d="M20 21V19C20 17.8954 19.1046 17 18 17H6C4.89543 17 4 17.8954 4 19V21" stroke="#8C8C8C" stroke-width="2" stroke-linecap="round"/>
+                <circle cx="12" cy="7" r="4" stroke="#8C8C8C" stroke-width="2"/>
+              </svg>
+            </view>
+            <input
+              class="form-input"
+              type="number"
+              placeholder="请输入手机号"
+              v-model="loginForm.phone"
+              maxlength="11"
+              placeholder-class="input-placeholder"
+            />
+          </view>
+        </view>
+        
+        <view class="form-item">
+          <view class="input-wrapper">
+            <view class="input-icon">
+              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <rect x="3" y="11" width="18" height="11" rx="2" stroke="#8C8C8C" stroke-width="2"/>
+                <path d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11" stroke="#8C8C8C" stroke-width="2" stroke-linecap="round"/>
+              </svg>
+            </view>
+            <input
+              class="form-input"
+              type="password"
+              placeholder="请输入密码"
+              v-model="loginForm.password"
+              placeholder-class="input-placeholder"
+            />
+          </view>
+        </view>
+
+        <button class="login-btn" @click="onPasswordLogin" :loading="isLoading">
+          <text class="btn-text">账号密码登录</text>
+        </button>
+      </view>
 
+      <!-- 用户协议 -->
       <view class="agreement">
-        登录即代表您同意<text class="link">《用户协议》</text>和<text class="link">《隐私政策》</text>
+        <text class="agreement-text">登录即代表您同意</text>
+        <text class="agreement-link">《用户协议》</text>
+        <text class="agreement-text">和</text>
+        <text class="agreement-link">《隐私政策》</text>
       </view>
     </view>
   </view>
@@ -51,15 +87,16 @@
 <script setup lang="ts">
 import { ref, reactive } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
-import { loginByPassword } from '@/api/user';
+import { loginByPassword, loginByMiniappPhone } from '@/api/user';
 import { setToken, setUserInfo } from '@/utils/auth';
-// import { loginByWechatPhone } from '@/api/user';
 
 const loginForm = reactive({
   phone: '',
   password: ''
 });
 
+const isLoading = ref(false);
+
 // 登录后跳转的页面
 const redirectUrl = ref('');
 
@@ -80,7 +117,9 @@ const onPasswordLogin = async () => {
     return;
   }
 
+  isLoading.value = true;
   uni.showLoading({ title: '登录中...' });
+  
   try {
     const res = await loginByPassword({
       phone: loginForm.phone,
@@ -91,6 +130,46 @@ const onPasswordLogin = async () => {
     console.error('登录失败', error);
   } finally {
     uni.hideLoading();
+    isLoading.value = false;
+  }
+};
+
+/**
+ * 微信手机号快捷登录
+ */
+const onGetPhoneNumber = async (e: any) => {
+  if (e.detail.errMsg !== 'getPhoneNumber:ok') {
+    uni.showToast({
+      title: '获取手机号失败,请重试',
+      icon: 'none'
+    });
+    return;
+  }
+  
+  isLoading.value = true;
+  uni.showLoading({ title: '登录中...' });
+  
+  try {
+    // 获取登录code
+    const loginRes = await new Promise<any>((resolve, reject) => {
+      uni.login({
+        provider: 'weixin',
+        success: resolve,
+        fail: reject
+      });
+    });
+    
+    const { code, encryptedData, iv } = e.detail;
+    const res = await loginByMiniappPhone({ 
+      code: loginRes.code,  // 用于获取openid
+      phoneCode: code       // 用于获取手机号
+    });
+    handleLoginSuccess(res);
+  } catch (error) {
+    console.error('微信手机号登录失败', error);
+  } finally {
+    uni.hideLoading();
+    isLoading.value = false;
   }
 };
 
@@ -132,6 +211,8 @@ const handleLoginSuccess = (res: any) => {
   const savedToken = uni.getStorageSync('accessToken');
   console.log('[登录成功] 保存后验证 - token:', savedToken ? '已保存' : '保存失败');
 
+  uni.vibrateShort({ type: 'medium' });
+  
   uni.showToast({
     title: '登录成功',
     icon: 'success'
@@ -158,87 +239,202 @@ const handleLoginSuccess = (res: any) => {
 .container {
   display: flex;
   flex-direction: column;
-  align-items: center;
-  padding: 100rpx 40rpx;
   min-height: 100vh;
-  background-color: #fff;
+  background: linear-gradient(180deg, $color-primary-bg 0%, $color-bg-secondary 40%);
+  padding: 0;
 }
 
-.login-header {
+/* ========== 品牌区 ========== */
+.brand-section {
   display: flex;
   flex-direction: column;
   align-items: center;
-  margin-top: 100rpx;
-  margin-bottom: 150rpx;
+  padding: 120rpx 40rpx 80rpx;
+  animation: slideDown 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
 
-  .logo {
+  .brand-logo {
     width: 160rpx;
     height: 160rpx;
-    margin-bottom: 30rpx;
+    margin-bottom: $spacing-lg;
+    animation: scaleIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+    
+    .logo {
+      width: 100%;
+      height: 100%;
+    }
   }
 
-  .title {
-    font-size: 40rpx;
-    font-weight: bold;
-    color: #333;
-    margin-bottom: 10rpx;
+  .brand-title {
+    font-size: 48rpx;
+    font-weight: 300;
+    color: $color-text-primary;
+    margin-bottom: $spacing-sm;
+    letter-spacing: 4rpx;
   }
 
-  .subtitle {
-    font-size: 28rpx;
-    color: #999;
+  .brand-subtitle {
+    font-size: 24rpx;
+    color: $color-text-secondary;
+    letter-spacing: 2rpx;
   }
 }
 
-.login-content {
-  width: 100%;
+/* ========== 表单区 ========== */
+.form-section {
+  flex: 1;
+  padding: 0 40rpx;
+  animation: slideUp 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
+}
 
-  .form-item {
-    width: 100%;
-    margin-bottom: 30rpx;
+.form-card {
+  background: $color-bg-primary;
+  border-radius: $radius-xl;
+  padding: $spacing-xl;
+  box-shadow: $shadow-md;
+  margin-bottom: $spacing-lg;
+}
 
-    .input {
-      width: 100%;
-      height: 90rpx;
-      padding: 0 30rpx;
-      background-color: #f5f5f5;
-      border-radius: 45rpx;
+.form-item {
+  margin-bottom: $spacing-lg;
+
+  .input-wrapper {
+    display: flex;
+    align-items: center;
+    background: $color-bg-secondary;
+    border-radius: $radius-lg;
+    padding: 0 $spacing-md;
+    transition: all $duration-fast $ease-out;
+    border: 2rpx solid transparent;
+
+    &:focus-within {
+      background: $color-bg-primary;
+      border-color: $color-primary;
+      box-shadow: 0 0 0 4rpx rgba(255, 193, 7, 0.1);
+    }
+
+    .input-icon {
+      width: 40rpx;
+      height: 40rpx;
+      margin-right: $spacing-sm;
+      flex-shrink: 0;
+      
+      svg {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .form-input {
+      flex: 1;
+      height: 96rpx;
       font-size: 28rpx;
-      box-sizing: border-box;
+      color: $color-text-primary;
+      background: transparent;
+      border: none;
+    }
+
+    .input-placeholder {
+      color: $color-text-tertiary;
     }
   }
+}
+
+.wechat-login-btn {
+  width: 100%;
+  height: 96rpx;
+  background: linear-gradient(135deg, #07C160 0%, #06AD56 100%);
+  border-radius: $radius-lg;
+  border: none;
+  margin-bottom: $spacing-lg;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.3);
+  transition: all $duration-normal $ease-out;
+
+  &::after {
+    border: none;
+  }
 
-  .login-btn {
-    width: 100%;
-    height: 90rpx;
-    line-height: 90rpx;
-    background-color: #FFD700;
-    color: #333;
-    border-radius: 45rpx;
+  &:active {
+    transform: scale(0.98);
+    box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.4);
+  }
+
+  .btn-icon {
+    font-size: 32rpx;
+    margin-right: $spacing-sm;
+  }
+
+  .btn-text {
     font-size: 32rpx;
-    font-weight: bold;
-    margin-top: 40rpx;
-    margin-bottom: 40rpx;
+    font-weight: 500;
+    color: #FFFFFF;
+  }
+}
+
+.divider {
+  display: flex;
+  align-items: center;
+  margin: $spacing-lg 0;
+  
+  .divider-line {
+    flex: 1;
+    height: 1rpx;
+    background: $color-border-light;
+  }
+  
+  .divider-text {
+    font-size: 24rpx;
+    color: $color-text-tertiary;
+    padding: 0 $spacing-md;
+  }
+}
+
+.login-btn {
+  width: 100%;
+  height: 96rpx;
+  background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
+  border-radius: $radius-lg;
+  border: none;
+  margin-top: $spacing-xl;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: $shadow-primary;
+  transition: all $duration-normal $ease-out;
+
+  &::after {
     border: none;
+  }
 
-    &::after {
-      border: none;
-    }
+  &:active {
+    transform: scale(0.98);
+    box-shadow: 0 4rpx 16rpx rgba(255, 193, 7, 0.3);
+  }
 
-    &.wechat-btn {
-      background-color: #07c160;
-      color: #fff;
-    }
+  .btn-text {
+    font-size: 32rpx;
+    font-weight: 500;
+    color: $color-text-primary;
   }
+}
+
+/* ========== 用户协议 ========== */
+.agreement {
+  text-align: center;
+  padding: 0 $spacing-lg;
+  animation: fadeIn 1s cubic-bezier(0.25, 0.1, 0.25, 1);
 
-  .agreement {
+  .agreement-text {
     font-size: 24rpx;
-    color: #999;
-    text-align: center;
+    color: $color-text-tertiary;
+  }
 
-    .link {
-      color: #576b95;
-    }
+  .agreement-link {
+    font-size: 24rpx;
+    color: $color-primary-dark;
+    font-weight: 500;
   }
 }
 </style>

+ 233 - 181
haha-mp/src/pages/my/my.vue

@@ -1,9 +1,9 @@
 <template>
   <view class="container">
-    <!-- 用户信息区域 -->
-    <view class="user-info">
-      <view class="avatar">
-        <image class="avatar-icon" src="/static/icons/user.svg"></image>
+    <!-- 用户信息卡片 -->
+    <view class="user-card">
+      <view class="user-avatar">
+        <image class="avatar-icon" src="/static/icons/user.svg" mode="aspectFit"></image>
       </view>
       <view class="user-details">
         <view class="user-name">微信用户</view>
@@ -11,75 +11,90 @@
       </view>
     </view>
 
-    <!-- 菜单列表 -->
-    <view class="menu-list">
-      <view class="menu-item" @click="goToOrders">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/orders.svg"></image>
+    <!-- 菜单分组 -->
+    <view class="menu-section">
+      <view class="section-title">订单与服务</view>
+      <view class="menu-card">
+        <view class="menu-item" @click="goToOrders">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/orders.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">我的订单</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">我的订单</view>
-        <view class="menu-arrow"></view>
-      </view>
-      <view class="menu-item" @click="goToCoupons">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/coupon.svg"></image>
+        <view class="menu-item" @click="goToCoupons">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/coupon.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">我的优惠券</view>
+          <view v-if="availableCouponCount > 0" class="menu-badge">{{ availableCouponCount }}</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">我的优惠券</view>
-        <view v-if="availableCouponCount > 0" class="menu-badge">{{ availableCouponCount }}</view>
-        <view class="menu-arrow"></view>
-      </view>
-      <view class="menu-item" @click="goToCouponCenter">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/coupon.svg"></image>
+        <view class="menu-item" @click="goToCouponCenter">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/coupon.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">领券中心</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">领券中心</view>
-        <view class="menu-arrow"></view>
       </view>
-      <view class="menu-item" @click="goToMembership">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/membership.svg"></image>
+    </view>
+
+    <view class="menu-section">
+      <view class="section-title">个人中心</view>
+      <view class="menu-card">
+        <view class="menu-item" @click="goToMembership">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/membership.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">我的会员卡</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">我的会员卡</view>
-        <view class="menu-arrow"></view>
-      </view>
-      <view class="menu-item" @click="goToCards">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/wallet.svg"></image>
+        <view class="menu-item" @click="goToCards">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/wallet.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">我的卡片</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">我的卡片</view>
-        <view class="menu-arrow"></view>
-      </view>
-      <view class="menu-item" @click="goToInvoice">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/invoice.svg"></image>
+        <view class="menu-item" @click="goToInvoice">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/invoice.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">发票管理</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">发票管理</view>
-        <view class="menu-arrow"></view>
       </view>
-      <view class="menu-item" @click="goToFAQ">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/faq.svg"></image>
+    </view>
+
+    <view class="menu-section">
+      <view class="section-title">帮助与支持</view>
+      <view class="menu-card">
+        <view class="menu-item" @click="goToFAQ">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/faq.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">常见问题</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">常见问题</view>
-        <view class="menu-arrow"></view>
-      </view>
-      <view class="menu-item" @click="callService">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/service.svg"></image>
+        <view class="menu-item" @click="callService">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/service.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">联系客服</view>
+          <view class="service-phone">400-0755-315</view>
         </view>
-        <view class="menu-text">联系客服</view>
-        <view class="service-phone">400-0755-315</view>
-      </view>
-      <view class="menu-item" @click="goToOfficialAccount">
-        <view class="menu-icon">
-          <image class="menu-svg-icon" src="/static/icons/official.svg"></image>
+        <view class="menu-item" @click="goToOfficialAccount">
+          <view class="menu-icon">
+            <image class="menu-svg-icon" src="/static/icons/official.svg" mode="aspectFit"></image>
+          </view>
+          <view class="menu-text">公众号信息</view>
+          <view class="menu-arrow"></view>
         </view>
-        <view class="menu-text">公众号信息</view>
-        <view class="menu-arrow"></view>
       </view>
     </view>
 
-    <!-- 退出登录按钮 -->
+    <!-- 退出登录 -->
     <view class="logout-section">
       <button class="logout-btn" @click="handleLogout">退出登录</button>
     </view>
@@ -120,24 +135,28 @@ const goBack = () => {
 };
 
 const goToOrders = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.navigateTo({
     url: '/pages/orders/orders'
   });
 };
 
 const goToCoupons = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.navigateTo({
     url: '/pages/coupons/coupons'
   });
 };
 
 const goToCouponCenter = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.navigateTo({
     url: '/pages/couponCenter/couponCenter'
   });
 };
 
 const goToMembership = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showToast({
     title: '会员卡功能开发中',
     icon: 'none'
@@ -145,6 +164,7 @@ const goToMembership = () => {
 };
 
 const goToCards = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showToast({
     title: '卡片功能开发中',
     icon: 'none'
@@ -152,6 +172,7 @@ const goToCards = () => {
 };
 
 const goToInvoice = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showToast({
     title: '发票管理功能开发中',
     icon: 'none'
@@ -159,6 +180,7 @@ const goToInvoice = () => {
 };
 
 const goToFAQ = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showToast({
     title: '常见问题功能开发中',
     icon: 'none'
@@ -166,12 +188,14 @@ const goToFAQ = () => {
 };
 
 const callService = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.makePhoneCall({
     phoneNumber: '400-0755-315'
   });
 };
 
 const goToOfficialAccount = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showToast({
     title: '公众号信息功能开发中',
     icon: 'none'
@@ -207,154 +231,182 @@ const handleLogout = () => {
 };
 </script>
 
-<style>
+<style lang="scss">
 .container {
   min-height: 100vh;
-  background-color: #ffffff;
+  background: $color-bg-secondary;
   display: flex;
   flex-direction: column;
+  padding-bottom: $spacing-xl;
 }
 
-/* 用户信息区域 */
-.user-info {
-  display: flex;
-  align-items: center;
-  padding: 20rpx 32rpx 40rpx;
-  background-color: #FFD700;
-  margin-top: 0;
-}
-
-.avatar {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 50%;
-  background-color: #ffffff;
+/* ========== 用户信息卡片 ========== */
+.user-card {
+  background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
+  padding: $spacing-xxl $spacing-lg $spacing-xl;
   display: flex;
   align-items: center;
-  justify-content: center;
-  margin-right: 32rpx;
-}
-
-.avatar-icon {
-  width: 90rpx;
-  height: 90rpx;
-}
-
-.user-details {
-  flex: 1;
-}
-
-.user-name {
-  font-size: 34rpx;
-  font-weight: 600;
-  margin-bottom: 8rpx;
-  color: #000000;
+  animation: slideDown 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  .user-avatar {
+    width: 120rpx;
+    height: 120rpx;
+    border-radius: $radius-circle;
+    background: rgba(255, 255, 255, 0.9);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: $spacing-lg;
+    box-shadow: $shadow-md;
+    
+    .avatar-icon {
+      width: 72rpx;
+      height: 72rpx;
+    }
+  }
+  
+  .user-details {
+    flex: 1;
+  }
+  
+  .user-name {
+    font-size: 36rpx;
+    font-weight: 500;
+    color: $color-text-primary;
+    margin-bottom: $spacing-xs;
+  }
+  
+  .user-phone {
+    font-size: 24rpx;
+    color: rgba(44, 44, 44, 0.7);
+  }
 }
 
-.user-phone {
-  font-size: 24rpx;
-  color: #333333;
+/* ========== 菜单分组 ========== */
+.menu-section {
+  margin-top: $spacing-lg;
+  animation: slideUp 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  .section-title {
+    font-size: 24rpx;
+    color: $color-text-secondary;
+    padding: 0 $spacing-lg $spacing-sm;
+    font-weight: 500;
+  }
 }
 
-/* 菜单列表 */
-.menu-list {
-  background-color: #ffffff;
-  margin-top: 0;
-  border-top: 1rpx solid #f0f0f0;
-  border-bottom: 1rpx solid #f0f0f0;
+.menu-card {
+  background: $color-bg-primary;
+  margin: 0 $spacing-lg;
+  border-radius: $radius-lg;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
 }
 
 .menu-item {
   display: flex;
   align-items: center;
-  padding: 32rpx 32rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.menu-item:last-child {
-  border-bottom: none;
-}
-
-.menu-item:active {
-  background-color: #f9f9f9;
-}
-
-.menu-icon {
-  width: 48rpx;
-  height: 48rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 24rpx;
-}
-
-.menu-svg-icon {
-  width: 32rpx;
-  height: 32rpx;
-}
-
-.menu-text {
-  flex: 1;
-  font-size: 28rpx;
-  color: #333333;
-}
-
-.menu-arrow {
-  width: 16rpx;
-  height: 16rpx;
-  border-top: 2rpx solid #999999;
-  border-right: 2rpx solid #999999;
-  transform: rotate(45deg);
-}
-
-.menu-badge {
-  background-color: #FF4D4F;
-  color: #ffffff;
-  font-size: 20rpx;
-  min-width: 32rpx;
-  height: 32rpx;
-  line-height: 32rpx;
-  text-align: center;
-  border-radius: 16rpx;
-  padding: 0 8rpx;
-  margin-right: 12rpx;
-}
-
-.service-phone {
-  font-size: 24rpx;
-  color: #666666;
+  padding: $spacing-md $spacing-lg;
+  border-bottom: 1rpx solid $color-border;
+  transition: all $duration-fast $ease-out;
+  position: relative;
+  
+  &:last-child {
+    border-bottom: none;
+  }
+  
+  &:active {
+    background: $color-bg-secondary;
+  }
+  
+  .menu-icon {
+    width: 40rpx;
+    height: 40rpx;
+    margin-right: $spacing-md;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    
+    .menu-svg-icon {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  
+  .menu-text {
+    flex: 1;
+    font-size: 28rpx;
+    color: $color-text-primary;
+  }
+  
+  .menu-arrow {
+    width: 16rpx;
+    height: 16rpx;
+    border-top: 2rpx solid $color-text-tertiary;
+    border-right: 2rpx solid $color-text-tertiary;
+    transform: rotate(45deg);
+    margin-left: $spacing-sm;
+  }
+  
+  .menu-badge {
+    background: $color-error;
+    color: #ffffff;
+    font-size: 20rpx;
+    min-width: 32rpx;
+    height: 32rpx;
+    line-height: 32rpx;
+    text-align: center;
+    border-radius: 16rpx;
+    padding: 0 8rpx;
+    margin-right: $spacing-sm;
+    animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+  }
+  
+  .service-phone {
+    font-size: 24rpx;
+    color: $color-text-secondary;
+    margin-left: $spacing-sm;
+  }
 }
 
-/* 退出登录区域 */
+/* ========== 退出登录 ========== */
 .logout-section {
-  padding: 60rpx 32rpx 20rpx;
-  background-color: #ffffff;
+  padding: $spacing-xxl $spacing-lg $spacing-lg;
   display: flex;
   justify-content: center;
+  animation: fadeIn 1s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  .logout-btn {
+    width: 60%;
+    height: 88rpx;
+    background: $color-bg-primary;
+    border: 2rpx solid $color-border;
+    border-radius: $radius-xl;
+    color: $color-text-secondary;
+    font-size: 28rpx;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all $duration-fast $ease-out;
+    
+    &::after {
+      border: none;
+    }
+    
+    &:active {
+      background: $color-bg-secondary;
+      border-color: $color-text-tertiary;
+    }
+  }
 }
 
-.logout-btn {
-  width: 60%;
-  height: 80rpx;
-  line-height: 80rpx;
-  background-color: #FFD700;
-  border: none;
-  border-radius: 40rpx;
-  color: #333333;
-  font-size: 28rpx;
-  font-weight: 500;
-}
-
-.logout-btn:active {
-  background-color: #E6C200;
-}
-
-/* 版本信息 */
+/* ========== 版本信息 ========== */
 .version-info {
-  padding: 40rpx 0;
   text-align: center;
   font-size: 22rpx;
-  color: #999999;
-  background-color: #ffffff;
+  color: $color-text-tertiary;
+  padding: $spacing-lg 0;
+  animation: fadeIn 1.2s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 </style>

+ 122 - 68
haha-mp/src/pages/orderDetail/orderDetail.vue

@@ -293,11 +293,11 @@ const viewVideo = (url: string) => {
 };
 </script>
 
-<style>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background-color: #f8f8f8;
-  padding: 20rpx;
+  background: linear-gradient(180deg, #FFF8E1 0%, #F5F5F5 30%);
+  padding: 24rpx;
   box-sizing: border-box;
 }
 
@@ -308,15 +308,17 @@ const viewVideo = (url: string) => {
   min-height: 400rpx;
   font-size: 28rpx;
   color: #999;
+  animation: fadeIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 /* 通用卡片样式 */
 .card {
   background-color: #ffffff;
-  border-radius: 16rpx;
-  padding: 30rpx;
-  margin-bottom: 20rpx;
-  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+  border-radius: 24rpx;
+  padding: 32rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04), 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
+  animation: slideUp 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) both;
 }
 
 /* 订单状态卡片 */
@@ -324,6 +326,7 @@ const viewVideo = (url: string) => {
   display: flex;
   justify-content: space-between;
   align-items: center;
+  animation-delay: 0.1s;
 }
 
 .status-left {
@@ -332,88 +335,102 @@ const viewVideo = (url: string) => {
 }
 
 .status-icon-wrapper {
-  width: 44rpx;
-  height: 44rpx;
-  background-color: #7ED321;
+  width: 56rpx;
+  height: 56rpx;
+  background: linear-gradient(135deg, #00CC66 0%, #00B359 100%);
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-right: 16rpx;
+  margin-right: 20rpx;
+  box-shadow: 0 4rpx 12rpx rgba(0, 204, 102, 0.3);
+  animation: scaleIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.3s both;
 }
 
 .status-icon-wrapper.status-success {
-  background-color: #7ED321;
+  background: linear-gradient(135deg, #00CC66 0%, #00B359 100%);
+  box-shadow: 0 4rpx 12rpx rgba(0, 204, 102, 0.3);
 }
 
 .status-icon-wrapper.status-pending {
-  background-color: #FF9100;
+  background: linear-gradient(135deg, #FFA000 0%, #FF8C00 100%);
+  box-shadow: 0 4rpx 12rpx rgba(255, 160, 0, 0.3);
 }
 
 .status-icon-wrapper.status-cancelled {
-  background-color: #999999;
+  background: linear-gradient(135deg, #999999 0%, #757575 100%);
+  box-shadow: 0 4rpx 12rpx rgba(153, 153, 153, 0.3);
 }
 
 .status-icon {
   color: #ffffff;
-  font-size: 28rpx;
+  font-size: 32rpx;
   font-weight: bold;
 }
 
 .status-text {
-  font-size: 32rpx;
-  color: #333333;
-  font-weight: 500;
+  font-size: 34rpx;
+  color: #1A1A1A;
+  font-weight: 700;
 }
 
 .invoice-status {
-  font-size: 28rpx;
+  font-size: 24rpx;
   color: #FF6B6B;
+  padding: 6rpx 16rpx;
+  background: #FFF0F0;
+  border-radius: 12rpx;
 }
 
 /* 订单明细卡片 */
 .detail-card {
   padding: 0;
   overflow: hidden;
+  animation-delay: 0.2s;
 }
 
 .card-header {
-  padding: 30rpx;
-  border-bottom: 1rpx solid #f5f5f5;
+  padding: 32rpx;
+  border-bottom: 1rpx solid #F5F5F5;
 }
 
 .card-title {
   font-size: 32rpx;
-  font-weight: bold;
-  color: #333333;
+  font-weight: 700;
+  color: #1A1A1A;
 }
 
 .product-item {
   display: flex;
-  padding: 30rpx;
+  padding: 32rpx;
   align-items: flex-start;
+  transition: background 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    background: #FAFAFA;
+  }
 }
 
 .product-image {
   width: 140rpx;
   height: 140rpx;
-  background-color: #f9f9f9;
-  border-radius: 8rpx;
-  margin-right: 20rpx;
-  border: 1rpx solid #eeeeee;
+  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
+  border-radius: 16rpx;
+  margin-right: 24rpx;
+  overflow: hidden;
 }
 
 .product-image image {
   width: 100%;
   height: 100%;
-  border-radius: 8rpx;
+  border-radius: 16rpx;
 }
 
 .image-placeholder {
   width: 100%;
   height: 100%;
-  background-color: #f5f5f5;
-  border-radius: 8rpx;
+  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
+  border-radius: 16rpx;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -425,9 +442,11 @@ const viewVideo = (url: string) => {
 
 .product-name {
   font-size: 30rpx;
-  color: #333333;
+  color: #1A1A1A;
+  font-weight: 600;
   margin-bottom: 16rpx;
   display: block;
+  line-height: 1.5;
 }
 
 .product-price {
@@ -437,16 +456,18 @@ const viewVideo = (url: string) => {
 
 .product-quantity {
   font-size: 26rpx;
-  color: #999999;
+  color: #BBBBBB;
   margin-top: 60rpx;
+  font-weight: 500;
 }
 
 .amount-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 30rpx;
-  border-top: 1rpx solid #f5f5f5;
+  padding: 32rpx;
+  border-top: 1rpx solid #F5F5F5;
+  background: linear-gradient(180deg, #FFFFFF 0%, #FFFDF5 100%);
 }
 
 .amount-label {
@@ -461,37 +482,44 @@ const viewVideo = (url: string) => {
 
 .amount-prefix {
   font-size: 26rpx;
-  color: #333333;
+  color: #666666;
   margin-right: 10rpx;
 }
 
 .amount-symbol {
-  font-size: 26rpx;
-  color: #333333;
-  font-weight: bold;
+  font-size: 28rpx;
+  color: #FF8C00;
+  font-weight: 700;
 }
 
 .amount-integer {
-  font-size: 36rpx;
-  color: #333333;
-  font-weight: bold;
+  font-size: 40rpx;
+  color: #FF8C00;
+  font-weight: 800;
 }
 
 .card-footer-actions {
   display: flex;
   justify-content: flex-end;
-  padding: 20rpx 30rpx 30rpx;
+  padding: 24rpx 32rpx 32rpx;
+  gap: 16rpx;
 }
 
 .action-btn-outline {
-  background-color: #f8f8f8;
-  border: 1rpx solid #e0e0e0;
-  border-radius: 12rpx;
+  background-color: #ffffff;
+  border: 2rpx solid #E0E0E0;
+  border-radius: 32rpx;
   font-size: 26rpx;
   color: #666666;
-  margin: 0 0 0 20rpx;
-  padding: 10rpx 24rpx;
-  line-height: 1.5;
+  margin: 0;
+  padding: 16rpx 32rpx;
+  line-height: 1;
+  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.95);
+    background-color: #F5F5F5;
+  }
 }
 
 .action-btn-outline::after {
@@ -499,14 +527,21 @@ const viewVideo = (url: string) => {
 }
 
 .action-btn-primary {
-  background-color: #FFD200;
-  border-radius: 12rpx;
+  background: linear-gradient(135deg, #FFC107 0%, #FFB300 100%);
+  border-radius: 32rpx;
   font-size: 26rpx;
-  color: #333333;
-  margin: 0 0 0 20rpx;
-  padding: 10rpx 24rpx;
-  line-height: 1.5;
-  font-weight: 500;
+  color: #1A1A1A;
+  margin: 0;
+  padding: 16rpx 32rpx;
+  line-height: 1;
+  font-weight: 600;
+  box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.25);
+  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.95);
+    box-shadow: 0 2rpx 8rpx rgba(255, 193, 7, 0.3);
+  }
 }
 
 .action-btn-primary::after {
@@ -516,18 +551,24 @@ const viewVideo = (url: string) => {
 /* 订单信息卡片 */
 .info-card {
   padding: 0;
+  animation-delay: 0.3s;
 }
 
 .info-list {
-  padding: 10rpx 30rpx 30rpx;
+  padding: 10rpx 32rpx 32rpx;
 }
 
 .info-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f9f9f9;
+  padding: 28rpx 0;
+  border-bottom: 1rpx solid #F5F5F5;
+  transition: background 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    background: #FAFAFA;
+  }
 }
 
 .info-row:last-child {
@@ -536,7 +577,7 @@ const viewVideo = (url: string) => {
 
 .info-label {
   font-size: 28rpx;
-  color: #666666;
+  color: #999999;
 }
 
 .info-value-group {
@@ -546,12 +587,18 @@ const viewVideo = (url: string) => {
 
 .info-value {
   font-size: 28rpx;
-  color: #333333;
+  color: #1A1A1A;
+  font-weight: 500;
 }
 
 .copy-btn {
-  margin-left: 10rpx;
-  padding: 4rpx;
+  margin-left: 12rpx;
+  padding: 8rpx;
+  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.9);
+  }
 }
 
 .copy-icon-box {
@@ -577,27 +624,34 @@ const viewVideo = (url: string) => {
 .info-link {
   display: flex;
   align-items: center;
+  transition: opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    opacity: 0.6;
+  }
 }
 
 .link-text {
   font-size: 28rpx;
-  color: #999999;
+  color: #FF8C00;
+  font-weight: 600;
   margin-right: 6rpx;
 }
 
 .link-arrow {
   font-size: 28rpx;
-  color: #cccccc;
+  color: #FFB300;
 }
 
 /* 底部提示 */
 .footer-note {
   text-align: center;
-  padding: 40rpx 0;
+  padding: 60rpx 0 40rpx;
+  animation: fadeIn 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.5s both;
 }
 
 .footer-note text {
-  font-size: 26rpx;
-  color: #cccccc;
+  font-size: 24rpx;
+  color: #CCCCCC;
 }
 </style>

+ 77 - 50
haha-mp/src/pages/orders/orders.vue

@@ -160,10 +160,10 @@ const viewOrderDetail = (order: OrderInfo) => {
 };
 </script>
 
-<style>
+<style scoped lang="scss">
 .container {
   min-height: 100vh;
-  background-color: #f5f5f5;
+  background: linear-gradient(180deg, #FFF8E1 0%, #F5F5F5 30%);
   position: relative;
   padding-top: 0;
 }
@@ -176,12 +176,14 @@ const viewOrderDetail = (order: OrderInfo) => {
   justify-content: center;
   min-height: 80vh;
   padding: 40rpx;
+  animation: fadeIn 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .empty-icon {
   width: 300rpx;
   height: 300rpx;
   margin-bottom: 40rpx;
+  animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.2s both;
 }
 
 .empty-icon svg {
@@ -194,25 +196,34 @@ const viewOrderDetail = (order: OrderInfo) => {
   color: #666666;
   margin-bottom: 16rpx;
   font-weight: 500;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.3s both;
 }
 
 .empty-tip {
   font-size: 28rpx;
   color: #999999;
+  animation: slideUp 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) 0.4s both;
 }
 
 /* 订单列表 */
 .order-list {
-  padding: 10rpx 0 30rpx;
+  padding: 10rpx 24rpx 30rpx;
 }
 
 /* 订单项 */
 .order-item {
   background-color: #ffffff;
-  border-radius: 0;
-  margin-bottom: 10rpx;
+  border-radius: 24rpx;
+  margin-bottom: 24rpx;
   overflow: hidden;
-  box-shadow: none;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04), 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
+  animation: slideUp 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) both;
+  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), box-shadow 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.98);
+    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+  }
 }
 
 /* 订单头部 */
@@ -220,13 +231,13 @@ const viewOrderDetail = (order: OrderInfo) => {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 20rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  padding: 28rpx 32rpx;
+  border-bottom: 1rpx solid #F5F5F5;
 }
 
 .order-time {
   font-size: 24rpx;
-  color: #666666;
+  color: #999999;
 }
 
 .time-invoice-container {
@@ -236,18 +247,22 @@ const viewOrderDetail = (order: OrderInfo) => {
 }
 
 .invoice-status {
-  font-size: 24rpx;
-  color: #ff6666;
-  margin-left: 20rpx;
+  font-size: 22rpx;
+  color: #FF6B6B;
+  margin-left: 16rpx;
+  padding: 4rpx 12rpx;
+  background: #FFF0F0;
+  border-radius: 8rpx;
 }
 
 .order-status {
-  font-size: 24rpx;
+  font-size: 26rpx;
+  font-weight: 600;
 }
 
 .order-status.paid,
 .order-status.completed {
-  color: #00cc66;
+  color: #00CC66;
 }
 
 .order-status.refunded {
@@ -255,23 +270,23 @@ const viewOrderDetail = (order: OrderInfo) => {
 }
 
 .order-status.pending {
-  color: #FF9100;
+  color: #FFA000;
 }
 
 /* 订单内容 */
 .order-content {
   display: flex;
-  padding: 20rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  padding: 32rpx;
+  border-bottom: 1rpx solid #F5F5F5;
 }
 
 .product-image {
   width: 160rpx;
   height: 160rpx;
-  margin-right: 20rpx;
-  flex-shrink: 0; /* 防止被压缩 */
-  background-color: #f5f5f5;
-  border-radius: 8rpx;
+  margin-right: 24rpx;
+  flex-shrink: 0;
+  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
+  border-radius: 16rpx;
   overflow: hidden;
 }
 
@@ -283,7 +298,7 @@ const viewOrderDetail = (order: OrderInfo) => {
 .image-placeholder {
   width: 100%;
   height: 100%;
-  background-color: #f5f5f5;
+  background: linear-gradient(135deg, #F5F5F5 0%, #EEEEEE 100%);
 }
 
 .order-info {
@@ -291,55 +306,58 @@ const viewOrderDetail = (order: OrderInfo) => {
   display: flex;
   flex-direction: column;
   justify-content: space-between;
-  min-width: 0; /* 允许内容换行 */
+  min-width: 0;
 }
 
 .product-name {
-  font-size: 28rpx;
-  color: #333333;
-  font-weight: 500;
+  font-size: 30rpx;
+  color: #1A1A1A;
+  font-weight: 600;
   width: 100%;
   text-align: left;
-  margin-bottom: 10rpx;
-  line-height: 1.4;
-  word-break: break-all; /* 允许换行 */
+  margin-bottom: 12rpx;
+  line-height: 1.5;
+  word-break: break-all;
 }
 
 .price-info {
   font-size: 28rpx;
+  margin-bottom: 8rpx;
 }
 
 .price-label {
-  color: #666666;
+  color: #999999;
 }
 
 .price-value {
-  color: #333333;
-  font-weight: 500;
+  color: #FF8C00;
+  font-weight: 700;
+  font-size: 32rpx;
 }
 
 .quantity-info {
   font-size: 24rpx;
-  color: #999999;
+  color: #BBBBBB;
 }
 
 /* 订单底部 */
 .order-footer {
   display: flex;
   justify-content: flex-end;
-  padding: 20rpx;
+  padding: 24rpx 32rpx;
   width: 100%;
   box-sizing: border-box;
+  gap: 16rpx;
 }
 
 /* 重置按钮默认样式 */
 .action-btn {
   background-color: #ffffff;
-  color: #333333;
-  border: 1rpx solid #dddddd;
-  padding: 12rpx 24rpx;
-  border-radius: 30rpx;
-  font-size: 24rpx;
+  color: #666666;
+  border: 2rpx solid #E0E0E0;
+  padding: 16rpx 32rpx;
+  border-radius: 32rpx;
+  font-size: 26rpx;
   flex-shrink: 0;
   text-align: center;
   white-space: nowrap;
@@ -348,13 +366,13 @@ const viewOrderDetail = (order: OrderInfo) => {
   display: flex;
   align-items: center;
   justify-content: center;
-  margin: 0; /* 彻底移除默认 margin */
+  margin: 0;
   width: auto;
-}
-
-/* 按钮之间的间距 */
-.action-btn + .action-btn {
-  margin-left: 20rpx;
+  transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
+  
+  &:active {
+    transform: scale(0.95);
+  }
 }
 
 .action-btn::after {
@@ -363,13 +381,22 @@ const viewOrderDetail = (order: OrderInfo) => {
 
 /* 详情按钮高亮 */
 .detail-btn {
-  background-color: #FFD700;
-  border-color: #FFD700;
-  color: #000000;
-  font-weight: 500;
+  background: linear-gradient(135deg, #FFC107 0%, #FFB300 100%);
+  border-color: #FFC107;
+  color: #1A1A1A;
+  font-weight: 600;
+  box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.25);
+  
+  &:active {
+    box-shadow: 0 2rpx 8rpx rgba(255, 193, 7, 0.3);
+  }
 }
 
 .refund-btn {
-  color: #666666;
+  color: #999999;
+  
+  &:active {
+    background-color: #F5F5F5;
+  }
 }
 </style>

+ 254 - 69
haha-mp/src/pages/shopping/shopping.vue

@@ -1,27 +1,64 @@
 <template>
   <view class="container">
     <!-- 阶段一:选购中 (门已开) -->
-    <view v-if="doorStatus === 'opened'" class="shopping-section">
-      <view class="status-icon">🚪</view>
+    <view v-if="doorStatus === 'opened'" class="status-section animate-fade-in">
+      <view class="status-icon-wrapper">
+        <view class="status-icon door-open">
+          <svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+            <rect x="20" y="10" width="80" height="100" rx="8" fill="#FFF8E1" stroke="#FFC107" stroke-width="3"/>
+            <path d="M 35 10 L 35 110" stroke="#FFC107" stroke-width="2" stroke-dasharray="4,4"/>
+            <circle cx="60" cy="60" r="15" fill="#FFC107" opacity="0.3"/>
+            <path d="M 55 60 L 65 60 M 60 55 L 60 65" stroke="#FFC107" stroke-width="3" stroke-linecap="round"/>
+          </svg>
+        </view>
+        <view class="status-icon-pulse"></view>
+      </view>
       <view class="status-title">门已开,请选购商品</view>
       <view class="status-tip">请在60秒内完成选购并关门</view>
-      <view class="countdown" v-if="countdown > 0">{{ countdown }}秒</view>
-      <view class="countdown warning" v-else>请尽快关门</view>
+      <view class="countdown-wrapper">
+        <view class="countdown" v-if="countdown > 0">
+          <text class="countdown-number">{{ countdown }}</text>
+          <text class="countdown-label">秒</text>
+        </view>
+        <view class="countdown warning" v-else>
+          <text class="countdown-number">请尽快关门</text>
+        </view>
+      </view>
       <view class="problem-link" @click="showProblem">遇到问题?(如门没开)</view>
     </view>
     
     <!-- 阶段二:购物完成 (门已关) -->
-    <view v-else-if="doorStatus === 'closing'" class="shopping-section">
-      <view class="success-icon">✅</view>
+    <view v-else-if="doorStatus === 'closing'" class="status-section animate-scale-in">
+      <view class="status-icon-wrapper">
+        <view class="status-icon success">
+          <svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="60" cy="60" r="50" fill="#E8F5E9" stroke="#4CAF50" stroke-width="3"/>
+            <path d="M 35 60 L 50 75 L 85 40" stroke="#4CAF50" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
+          </svg>
+        </view>
+      </view>
       <view class="status-title">购物已完成</view>
       <view class="status-tip">订单稍后自动扣款,如有疑问请联系客服</view>
-      <view class="countdown" v-if="returnCountdown > 0">{{ returnCountdown }}秒后返回首页</view>
-      <button class="home-button" @click="goHome">回到首页</button>
+      <view class="countdown-wrapper">
+        <view class="countdown-return" v-if="returnCountdown > 0">
+          <text class="countdown-return-text">{{ returnCountdown }}秒后返回首页</text>
+        </view>
+      </view>
+      <button class="action-button" @click="goHome">
+        <text class="button-text">回到首页</text>
+      </button>
     </view>
     
     <!-- 阶段三:结算完成 -->
-    <view v-else-if="doorStatus === 'closed'" class="shopping-section">
-      <view class="success-icon">✅</view>
+    <view v-else-if="doorStatus === 'closed'" class="status-section animate-slide-up">
+      <view class="status-icon-wrapper">
+        <view class="status-icon success">
+          <svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="60" cy="60" r="50" fill="#E8F5E9" stroke="#4CAF50" stroke-width="3"/>
+            <path d="M 35 60 L 50 75 L 85 40" stroke="#4CAF50" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
+          </svg>
+        </view>
+      </view>
       <view class="status-title">结算完成</view>
       <view class="status-tip">您已成功购买以下商品</view>
       <view class="purchase-info">
@@ -35,15 +72,26 @@
           <view class="total-price">¥{{ totalPrice }}</view>
         </view>
       </view>
-      <button class="view-order-button" @click="goToOrderDetail">查看订单详情</button>
+      <button class="action-button primary" @click="goToOrderDetail">
+        <text class="button-text">查看订单详情</text>
+      </button>
     </view>
     
     <!-- 错误状态 -->
-    <view v-else-if="doorStatus === 'error'" class="shopping-section">
-      <view class="error-icon">❌</view>
+    <view v-else-if="doorStatus === 'error'" class="status-section animate-shake">
+      <view class="status-icon-wrapper">
+        <view class="status-icon error">
+          <svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="60" cy="60" r="50" fill="#FFEBEE" stroke="#F44336" stroke-width="3"/>
+            <path d="M 40 40 L 80 80 M 80 40 L 40 80" stroke="#F44336" stroke-width="5" fill="none" stroke-linecap="round"/>
+          </svg>
+        </view>
+      </view>
       <view class="status-title">出错了</view>
       <view class="status-tip">{{ errorMessage }}</view>
-      <button class="retry-button" @click="goHome">返回首页</button>
+      <button class="action-button" @click="goHome">
+        <text class="button-text">返回首页</text>
+      </button>
     </view>
   </view>
 </template>
@@ -217,6 +265,7 @@ const startStatusPolling = async () => {
 };
 
 const showProblem = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.showActionSheet({
     itemList: ['辅助远程开门', '报修'],
     success: function (res) {
@@ -226,6 +275,7 @@ const showProblem = () => {
           icon: 'loading'
         });
       } else if (res.tapIndex === 1) {
+        uni.vibrateShort({ type: 'medium' });
         uni.showToast({
           title: '已提交报修申请',
           icon: 'success'
@@ -236,6 +286,7 @@ const showProblem = () => {
 };
 
 const goToOrderDetail = () => {
+  uni.vibrateShort({ type: 'light' });
   // 跳转到订单详情页
   uni.navigateTo({
     url: `/pages/orderDetail/orderDetail?orderNo=${currentOrderNo.value}`
@@ -243,60 +294,149 @@ const goToOrderDetail = () => {
 };
 
 const goHome = () => {
+  uni.vibrateShort({ type: 'light' });
   uni.reLaunch({
     url: '/pages/index/index'
   });
 };
 </script>
 
-<style>
+<style lang="scss">
 .container {
   min-height: 100vh;
-  background-color: #fff;
+  background: linear-gradient(180deg, $color-bg-secondary 0%, $color-bg-primary 100%);
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  padding: 50rpx;
+  padding: $spacing-xl;
 }
 
-.shopping-section {
+.status-section {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
   text-align: center;
   width: 100%;
+  max-width: 600rpx;
 }
 
-.status-icon, .loading-icon, .success-icon, .error-icon {
-  font-size: 120rpx;
-  margin-bottom: 40rpx;
+/* ========== 状态图标 ========== */
+.status-icon-wrapper {
+  position: relative;
+  width: 200rpx;
+  height: 200rpx;
+  margin-bottom: $spacing-xl;
+  animation: scaleIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 }
 
+.status-icon {
+  width: 100%;
+  height: 100%;
+  
+  svg {
+    width: 100%;
+    height: 100%;
+  }
+  
+  &.door-open {
+    animation: doorOpen 1s $ease-out;
+  }
+  
+  &.success {
+    animation: successPop 0.6s $bounce;
+  }
+  
+  &.error {
+    animation: shake 0.5s $ease-in-out;
+  }
+}
+
+.status-icon-pulse {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 100%;
+  height: 100%;
+  border-radius: $radius-circle;
+  background: $color-primary;
+  transform: translate(-50%, -50%);
+  animation: pulse 2s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+  opacity: 0.2;
+  z-index: -1;
+}
+
+@keyframes doorOpen {
+  0% {
+    transform: scale(0) rotate(-180deg);
+    opacity: 0;
+  }
+  100% {
+    transform: scale(1) rotate(0);
+    opacity: 1;
+  }
+}
+
+@keyframes successPop {
+  0% {
+    transform: scale(0);
+    opacity: 0;
+  }
+  50% {
+    transform: scale(1.1);
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+/* ========== 状态文本 ========== */
 .status-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  margin-bottom: 20rpx;
-  color: #333;
+  font-size: 40rpx;
+  font-weight: 500;
+  margin-bottom: $spacing-sm;
+  color: $color-text-primary;
+  animation: slideUp 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .status-tip {
-  font-size: 28rpx;
-  color: #666;
-  margin-bottom: 40rpx;
+  font-size: 26rpx;
+  color: $color-text-secondary;
+  margin-bottom: $spacing-xl;
+  animation: slideUp 0.9s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
-.countdown {
-  font-size: 48rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 40rpx;
+/* ========== 倒计时 ========== */
+.countdown-wrapper {
+  margin-bottom: $spacing-xl;
+  animation: scaleIn 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
 }
 
-.countdown.warning {
-  color: #FF6B6B;
-  animation: blink 1s infinite;
+.countdown {
+  display: flex;
+  align-items: baseline;
+  gap: 8rpx;
+  
+  &-number {
+    font-size: 72rpx;
+    font-weight: 600;
+    color: $color-text-primary;
+    font-variant-numeric: tabular-nums;
+  }
+  
+  &-label {
+    font-size: 28rpx;
+    color: $color-text-secondary;
+  }
+  
+  &.warning {
+    .countdown-number {
+      color: $color-error;
+      animation: blink 1s infinite;
+    }
+  }
 }
 
 @keyframes blink {
@@ -304,79 +444,124 @@ const goHome = () => {
   50% { opacity: 0.5; }
 }
 
+.countdown-return {
+  padding: $spacing-md $spacing-lg;
+  background: $color-bg-tertiary;
+  border-radius: $radius-lg;
+  
+  &-text {
+    font-size: 26rpx;
+    color: $color-text-secondary;
+  }
+}
+
+/* ========== 问题链接 ========== */
 .problem-link {
   font-size: 24rpx;
-  color: #FFD700;
+  color: $color-primary-dark;
   text-decoration: underline;
-  margin-top: 20rpx;
+  margin-top: $spacing-lg;
+  animation: fadeIn 1.2s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
+/* ========== 商品列表 ========== */
 .purchase-info {
   width: 100%;
-  background-color: #f9f9f9;
-  border-radius: 15rpx;
-  padding: 30rpx;
-  margin: 40rpx 0;
+  background: $color-bg-primary;
+  border-radius: $radius-lg;
+  padding: $spacing-lg;
+  margin: $spacing-xl 0;
+  box-shadow: $shadow-sm;
+  animation: slideUp 1s cubic-bezier(0.25, 0.1, 0.25, 1);
 }
 
 .product-item {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 15rpx 0;
-  border-bottom: 1rpx solid #eee;
-}
-
-.product-item:last-child {
-  border-bottom: none;
+  padding: $spacing-sm 0;
+  border-bottom: 1rpx solid $color-border;
+  
+  &:last-child {
+    border-bottom: none;
+  }
 }
 
 .product-name {
   font-size: 28rpx;
-  color: #333;
+  color: $color-text-primary;
   flex: 1;
+  text-align: left;
 }
 
 .product-quantity {
-  font-size: 28rpx;
-  color: #666;
-  margin: 0 20rpx;
+  font-size: 26rpx;
+  color: $color-text-secondary;
+  margin: 0 $spacing-md;
 }
 
 .product-price {
   font-size: 28rpx;
-  color: #333;
+  color: $color-text-primary;
+  font-weight: 500;
 }
 
 .total-info {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding-top: 20rpx;
-  margin-top: 20rpx;
-  border-top: 1rpx solid #eee;
+  padding-top: $spacing-md;
+  margin-top: $spacing-md;
+  border-top: 2rpx solid $color-border;
 }
 
 .total-label {
   font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
+  font-weight: 500;
+  color: $color-text-primary;
 }
 
 .total-price {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #FF6B6B;
+  font-size: 36rpx;
+  font-weight: 600;
+  color: $color-error;
 }
 
-.view-order-button, .retry-button, .home-button {
-  background-color: #FFD700;
-  color: #333;
-  border: none;
-  padding: 20rpx 60rpx;
-  border-radius: 30rpx;
+/* ========== 操作按钮 ========== */
+.action-button {
+  background: $color-bg-primary;
+  color: $color-text-primary;
+  border: 2rpx solid $color-border;
+  padding: $spacing-md $spacing-xl;
+  border-radius: $radius-xl;
   font-size: 28rpx;
-  font-weight: bold;
-  margin-top: 40rpx;
+  font-weight: 500;
+  margin-top: $spacing-lg;
+  transition: all $duration-fast $ease-out;
+  
+  &::after {
+    border: none;
+  }
+  
+  &:active {
+    transform: scale(0.98);
+    background: $color-bg-secondary;
+  }
+  
+  &.primary {
+    background: linear-gradient(135deg, $color-primary-light 0%, $color-primary 100%);
+    border: none;
+    box-shadow: $shadow-primary;
+    color: $color-text-primary;
+    
+    &:active {
+      box-shadow: 0 4rpx 16rpx rgba(255, 193, 7, 0.3);
+    }
+  }
+  
+  .button-text {
+    font-size: 28rpx;
+    font-weight: 500;
+  }
 }
 </style>

+ 214 - 0
haha-mp/src/static/animations.css

@@ -0,0 +1,214 @@
+/**
+ * haha-mp 全局动画样式
+ * 使用纯CSS动画类,避免SCSS mixins的导入问题
+ */
+
+/* ========== 淡入淡出 ========== */
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
+@keyframes fadeOut {
+  from { opacity: 1; }
+  to { opacity: 0; }
+}
+
+/* ========== 滑入效果 ========== */
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes slideLeft {
+  from {
+    opacity: 0;
+    transform: translateX(40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+@keyframes slideRight {
+  from {
+    opacity: 0;
+    transform: translateX(-40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+/* ========== 缩放效果 ========== */
+@keyframes scaleIn {
+  from {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+@keyframes scaleOut {
+  from {
+    opacity: 1;
+    transform: scale(1);
+  }
+  to {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+}
+
+/* ========== 脉冲动画 ========== */
+@keyframes pulse {
+  0%, 100% { 
+    transform: scale(1);
+    opacity: 0.3;
+  }
+  50% { 
+    transform: scale(1.05);
+    opacity: 0.5;
+  }
+}
+
+/* ========== 呼吸动画 ========== */
+@keyframes breathe {
+  0%, 100% { 
+    transform: scale(1);
+  }
+  50% { 
+    transform: scale(1.02);
+  }
+}
+
+/* ========== 骨架屏闪烁 ========== */
+@keyframes skeleton {
+  0%, 100% { 
+    background-color: #F5F5F5; 
+  }
+  50% { 
+    background-color: #EEEEEE; 
+  }
+}
+
+/* ========== 旋转加载 ========== */
+@keyframes spin {
+  to { 
+    transform: rotate(360deg); 
+  }
+}
+
+/* ========== 抖动效果 ========== */
+@keyframes shake {
+  0%, 100% { transform: translateX(0); }
+  10%, 30%, 50%, 70%, 90% { transform: translateX(-8rpx); }
+  20%, 40%, 60%, 80% { transform: translateX(8rpx); }
+}
+
+/* ========== 弹跳效果 ========== */
+@keyframes bounce {
+  0% {
+    transform: scale(0);
+    opacity: 0;
+  }
+  50% {
+    transform: scale(1.1);
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+/* ========== 通用动画类 ========== */
+.animate-fade-in {
+  animation: fadeIn 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-fade-out {
+  animation: fadeOut 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-slide-up {
+  animation: slideUp 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-slide-down {
+  animation: slideDown 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-slide-left {
+  animation: slideLeft 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-slide-right {
+  animation: slideRight 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-scale-in {
+  animation: scaleIn 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
+}
+
+.animate-scale-out {
+  animation: scaleOut 300ms cubic-bezier(0.25, 0.1, 0.25, 1);
+}
+
+.animate-pulse {
+  animation: pulse 2s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+}
+
+.animate-breathe {
+  animation: breathe 3s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+}
+
+.animate-skeleton {
+  animation: skeleton 1.5s cubic-bezier(0.42, 0, 0.58, 1) infinite;
+}
+
+.animate-spin {
+  animation: spin 0.8s linear infinite;
+}
+
+.animate-shake {
+  animation: shake 0.5s cubic-bezier(0.42, 0, 0.58, 1);
+}
+
+.animate-bounce {
+  animation: bounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+}
+
+/* ========== 延迟动画类 ========== */
+.delay-100 { animation-delay: 100ms; }
+.delay-200 { animation-delay: 200ms; }
+.delay-300 { animation-delay: 300ms; }
+.delay-400 { animation-delay: 400ms; }
+.delay-500 { animation-delay: 500ms; }
+
+/* ========== GPU加速 ========== */
+.gpu-accelerated {
+  transform: translateZ(0);
+  will-change: transform, opacity;
+}

+ 2 - 2
haha-mp/src/static/icons/my.svg

@@ -1,4 +1,4 @@
 <svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <circle cx="25" cy="15" r="8" fill="#666666"/>
-  <path d="M10 35C10 28 18 22 25 22C32 22 40 28 40 35C40 42 32 48 25 48C18 48 10 42 10 35Z" fill="#666666"/>
+  <circle cx="25" cy="15" r="8" fill="#FFD700"/>
+  <path d="M10 35C10 28 18 22 25 22C32 22 40 28 40 35C40 42 32 48 25 48C18 48 10 42 10 35Z" fill="#FFD700"/>
 </svg>

+ 332 - 0
haha-mp/src/styles/animations.scss

@@ -0,0 +1,332 @@
+/**
+ * haha-mp 全局动画库
+ * 极简留白风格 · 优雅动效 · 微交互体验
+ */
+
+/* ========================================
+ * 动画 Mixins
+ * ======================================== */
+
+// 淡入效果
+@mixin fade-in($duration: $duration-normal) {
+  animation: fadeIn $duration $ease-out;
+}
+
+@keyframes fadeIn {
+  from { 
+    opacity: 0; 
+  }
+  to { 
+    opacity: 1; 
+  }
+}
+
+// 淡出效果
+@mixin fade-out($duration: $duration-normal) {
+  animation: fadeOut $duration $ease-out;
+}
+
+@keyframes fadeOut {
+  from { 
+    opacity: 1; 
+  }
+  to { 
+    opacity: 0; 
+  }
+}
+
+// 向上滑入
+@mixin slide-up($duration: $duration-normal, $distance: 40rpx) {
+  animation: slideUp $duration $ease-out;
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 向下滑入
+@mixin slide-down($duration: $duration-normal) {
+  animation: slideDown $duration $ease-out;
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 向左滑入
+@mixin slide-left($duration: $duration-normal) {
+  animation: slideLeft $duration $ease-out;
+}
+
+@keyframes slideLeft {
+  from {
+    opacity: 0;
+    transform: translateX(40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+// 向右滑入
+@mixin slide-right($duration: $duration-normal) {
+  animation: slideRight $duration $ease-out;
+}
+
+@keyframes slideRight {
+  from {
+    opacity: 0;
+    transform: translateX(-40rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+// 缩放进入
+@mixin scale-in($duration: $duration-normal) {
+  animation: scaleIn $duration $bounce;
+}
+
+@keyframes scaleIn {
+  from {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+// 缩放退出
+@mixin scale-out($duration: $duration-normal) {
+  animation: scaleOut $duration $ease-out;
+}
+
+@keyframes scaleOut {
+  from {
+    opacity: 1;
+    transform: scale(1);
+  }
+  to {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+}
+
+// 脉冲动画
+@mixin pulse($duration: 2s) {
+  animation: pulse $duration $ease-in-out infinite;
+}
+
+@keyframes pulse {
+  0%, 100% { 
+    transform: scale(1);
+    opacity: 0.3;
+  }
+  50% { 
+    transform: scale(1.05);
+    opacity: 0.5;
+  }
+}
+
+// 呼吸动画
+@mixin breathe($duration: 3s) {
+  animation: breathe $duration $ease-in-out infinite;
+}
+
+@keyframes breathe {
+  0%, 100% { 
+    transform: scale(1);
+  }
+  50% { 
+    transform: scale(1.02);
+  }
+}
+
+// 骨架屏闪烁
+@mixin skeleton-loading {
+  animation: skeleton 1.5s $ease-in-out infinite;
+}
+
+@keyframes skeleton {
+  0%, 100% { 
+    background-color: #F5F5F5; 
+  }
+  50% { 
+    background-color: #EEEEEE; 
+  }
+}
+
+// 旋转加载
+@mixin spin($duration: 0.8s) {
+  animation: spin $duration linear infinite;
+}
+
+@keyframes spin {
+  to { 
+    transform: rotate(360deg); 
+  }
+}
+
+// 抖动效果
+@mixin shake($duration: 0.5s) {
+  animation: shake $duration $ease-in-out;
+}
+
+@keyframes shake {
+  0%, 100% { transform: translateX(0); }
+  10%, 30%, 50%, 70%, 90% { transform: translateX(-8rpx); }
+  20%, 40%, 60%, 80% { transform: translateX(8rpx); }
+}
+
+// 弹跳效果
+@mixin bounce($duration: 0.6s) {
+  animation: bounce $duration $bounce;
+}
+
+@keyframes bounce {
+  0% {
+    transform: scale(0);
+    opacity: 0;
+  }
+  50% {
+    transform: scale(1.1);
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+// 翻转效果
+@mixin flip($duration: 0.6s) {
+  animation: flip $duration $ease-in-out;
+}
+
+@keyframes flip {
+  0% {
+    transform: perspective(400px) rotateY(0);
+  }
+  100% {
+    transform: perspective(400px) rotateY(360deg);
+  }
+}
+
+/* ========================================
+ * 通用动画类
+ * ======================================== */
+
+// 渐入类
+.animate-fade-in {
+  @include fade-in;
+}
+
+// 滑入类
+.animate-slide-up {
+  @include slide-up;
+}
+
+.animate-slide-down {
+  @include slide-down;
+}
+
+.animate-slide-left {
+  @include slide-left;
+}
+
+.animate-slide-right {
+  @include slide-right;
+}
+
+// 缩放类
+.animate-scale-in {
+  @include scale-in;
+}
+
+// 脉冲类
+.animate-pulse {
+  @include pulse;
+}
+
+// 呼吸类
+.animate-breathe {
+  @include breathe;
+}
+
+// 加载旋转类
+.animate-spin {
+  @include spin;
+}
+
+// 抖动类
+.animate-shake {
+  @include shake;
+}
+
+// 弹跳类
+.animate-bounce {
+  @include bounce;
+}
+
+/* ========================================
+ * 延迟动画类
+ * ======================================== */
+
+.delay-100 {
+  animation-delay: 100ms;
+}
+
+.delay-200 {
+  animation-delay: 200ms;
+}
+
+.delay-300 {
+  animation-delay: 300ms;
+}
+
+.delay-400 {
+  animation-delay: 400ms;
+}
+
+.delay-500 {
+  animation-delay: 500ms;
+}
+
+/* ========================================
+ * 性能优化
+ * ======================================== */
+
+// GPU加速类
+.gpu-accelerated {
+  transform: translateZ(0);
+  will-change: transform, opacity;
+}
+
+// 减少动画类(用户偏好减少动画时)
+@media (prefers-reduced-motion: reduce) {
+  *,
+  *::before,
+  *::after {
+    animation-duration: 0.01ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: 0.01ms !important;
+  }
+}

+ 60 - 1
haha-mp/src/uni.scss

@@ -12,7 +12,66 @@
  * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
  */
 
-/* 颜色变量 */
+/* ========================================
+ * haha-mp 设计系统 - Design Tokens
+ * 极简留白风格 · 优雅动效 · 微交互体验
+ * ======================================== */
+
+/* ---------- 主色系 - 柔和暖黄 ---------- */
+$color-primary: #FFC107;
+$color-primary-light: #FFE082;
+$color-primary-dark: #FFA000;
+$color-primary-bg: #FFF8E1;
+
+/* ---------- 中性色 ---------- */
+$color-text-primary: #2C2C2C;
+$color-text-secondary: #8C8C8C;
+$color-text-tertiary: #BDBDBD;
+$color-bg-primary: #FFFFFF;
+$color-bg-secondary: #FAFAFA;
+$color-bg-tertiary: #F5F5F5;
+$color-border: #EEEEEE;
+
+/* ---------- 功能色 ---------- */
+$color-success: #4CAF50;
+$color-warning: #FF9800;
+$color-error: #F44336;
+$color-info: #2196F3;
+
+/* ---------- 间距系统 ---------- */
+$spacing-xs: 8rpx;
+$spacing-sm: 16rpx;
+$spacing-md: 24rpx;
+$spacing-lg: 32rpx;
+$spacing-xl: 48rpx;
+$spacing-xxl: 64rpx;
+
+/* ---------- 圆角系统 ---------- */
+$radius-sm: 8rpx;
+$radius-md: 16rpx;
+$radius-lg: 24rpx;
+$radius-xl: 32rpx;
+$radius-circle: 50%;
+
+/* ---------- 阴影系统 ---------- */
+$shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
+$shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
+$shadow-primary: 0 8rpx 24rpx rgba(255, 193, 7, 0.25);
+
+/* ---------- 动画时长 ---------- */
+$duration-fast: 150ms;
+$duration-normal: 300ms;
+$duration-slow: 500ms;
+
+/* ---------- 动画曲线 ---------- */
+$ease-out: cubic-bezier(0.25, 0.1, 0.25, 1);
+$ease-in-out: cubic-bezier(0.42, 0, 0.58, 1);
+$bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
+
+/* ========================================
+ * 以下为 uni-app 原始变量(保持兼容)
+ * ======================================== */
 
 /* 行为相关颜色 */
 $uni-color-primary: #007aff;

+ 9 - 0
haha-service/src/main/java/com/haha/service/LoginService.java

@@ -24,6 +24,15 @@ public interface LoginService {
      */
     Result<LoginVO> wechatPhoneLogin(String code, String encryptedData, String iv);
     
+    /**
+     * 微信小程序手机号快速登录(新版)
+     * 使用button open-type="getPhoneNumber"获取手机号
+     * @param code 微信登录凭证code(用于获取openid)
+     * @param phoneCode 手机号获取凭证(用于获取手机号)
+     * @return 登录结果,包含token和用户信息
+     */
+    Result<LoginVO> wechatMiniappPhoneLogin(String code, String phoneCode);
+    
     /**
      * 根据用户ID获取用户信息
      * @param userId 用户ID

+ 199 - 0
haha-service/src/main/java/com/haha/service/impl/LoginServiceImpl.java

@@ -9,7 +9,13 @@ import com.haha.common.vo.LoginVO;
 import com.haha.common.vo.UserVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -21,6 +27,15 @@ public class LoginServiceImpl implements LoginService {
     @Autowired
     private UserService userService;
     
+    @Autowired
+    private RestTemplate restTemplate;
+    
+    @Value("${wechat.miniapp.app-id}")
+    private String miniappAppId;
+    
+    @Value("${wechat.miniapp.secret}")
+    private String miniappSecret;
+    
     @Override
     public Result<LoginVO> loginByPassword(String phone, String password) {
         try {
@@ -116,6 +131,190 @@ public class LoginServiceImpl implements LoginService {
         }
     }
     
+    @Override
+    public Result<LoginVO> wechatMiniappPhoneLogin(String code, String phoneCode) {
+        try {
+            log.info("[登录服务] 微信小程序手机号快速登录 - code: {}, phoneCode: {}",
+                    code != null ? code.substring(0, Math.min(8, code.length())) + "..." : "null",
+                    phoneCode != null ? phoneCode.substring(0, Math.min(8, phoneCode.length())) + "..." : "null");
+            
+            // 1. 通过code获取openid
+            String openid = getOpenidByCode(code);
+            if (openid == null || openid.isEmpty()) {
+                log.warn("[登录服务] 获取openid失败 - code: {}", 
+                        code != null ? code.substring(0, Math.min(8, code.length())) + "..." : "null");
+                return Result.error(400, "获取用户标识失败,请重试");
+            }
+            
+            // 2. 通过phoneCode获取手机号
+            String phoneNumber = getPhoneNumberByCode(phoneCode);
+            if (phoneNumber == null || phoneNumber.isEmpty()) {
+                log.warn("[登录服务] 获取手机号失败 - phoneCode: {}", 
+                        phoneCode != null ? phoneCode.substring(0, Math.min(8, phoneCode.length())) + "..." : "null");
+                return Result.error(400, "获取手机号失败,请重试");
+            }
+            
+            log.info("[登录服务] 获取到用户信息 - openid: {}, phone: {}", openid, maskPhone(phoneNumber));
+            
+            // 3. 查找或创建用户
+            User user = userService.getUserByPhone(phoneNumber);
+            if (user == null) {
+                // 如果手机号不存在,尝试通过openid查找
+                user = userService.getUserByOpenId(openid);
+            }
+            
+            if (user == null) {
+                // 创建新用户
+                user = new User();
+                user.setPhone(phoneNumber);
+                user.setOpenid(openid);
+                user.setNickname("用户" + phoneNumber.substring(7));
+                user.setStatus(1);
+                user.setCreditScore(100);
+                userService.save(user);
+                log.info("[登录服务] 创建新用户 - userId: {}, phone: {}, openid: {}", 
+                        user.getId(), maskPhone(phoneNumber), openid);
+            } else {
+                // 更新用户openid(如果是通过手机号找到的用户)
+                if (user.getOpenid() == null || !user.getOpenid().equals(openid)) {
+                    user.setOpenid(openid);
+                    userService.updateById(user);
+                    log.info("[登录服务] 更新用户openid - userId: {}", user.getId());
+                }
+                // 更新用户手机号(如果是通过openid找到的用户)
+                if (user.getPhone() == null || !user.getPhone().equals(phoneNumber)) {
+                    user.setPhone(phoneNumber);
+                    userService.updateById(user);
+                    log.info("[登录服务] 更新用户手机号 - userId: {}", user.getId());
+                }
+            }
+            
+            // 4. 生成token
+            StpUtil.login(user.getId());
+            String token = StpUtil.getTokenValue();
+            
+            log.info("[登录服务] 微信小程序手机号登录成功 - userId: {}, phone: {}", 
+                    user.getId(), maskPhone(phoneNumber));
+            
+            // 5. 构建返回结果
+            UserVO userVO = UserVO.builder()
+                    .id(user.getId())
+                    .phone(user.getPhone())
+                    .nickname(user.getNickname())
+                    .avatar(user.getAvatar())
+                    .build();
+            
+            LoginVO loginVO = LoginVO.builder()
+                    .token(token)
+                    .userInfo(userVO)
+                    .build();
+            
+            return Result.success("登录成功", loginVO);
+            
+        } catch (Exception e) {
+            log.error("[登录服务] 微信小程序手机号登录异常", e);
+            return Result.error(500, "登录失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 通过登录凭证code获取openid
+     */
+    private String getOpenidByCode(String code) {
+        try {
+            String url = String.format(
+                "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
+                miniappAppId, miniappSecret, code
+            );
+            
+            ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
+            Map<String, Object> result = response.getBody();
+            
+            if (result != null && result.containsKey("openid")) {
+                return (String) result.get("openid");
+            } else {
+                log.error("[微信API] 获取openid失败 - response: {}", result);
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("[微信API] 调用jscode2session异常", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 通过手机号获取凭证code获取手机号
+     */
+    private String getPhoneNumberByCode(String phoneCode) {
+        try {
+            // 获取access_token
+            String accessToken = getAccessToken();
+            if (accessToken == null || accessToken.isEmpty()) {
+                log.error("[微信API] 获取access_token失败");
+                return null;
+            }
+            
+            // 使用手机号获取凭证换取手机号
+            String url = String.format(
+                "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s",
+                accessToken
+            );
+            
+            Map<String, String> requestBody = new HashMap<>();
+            requestBody.put("code", phoneCode);
+            
+            HttpHeaders headers = new HttpHeaders();
+            headers.set("Content-Type", "application/json");
+            HttpEntity<Map<String, String>> entity = new HttpEntity<>(requestBody, headers);
+            
+            ResponseEntity<Map> response = restTemplate.postForEntity(url, entity, Map.class);
+            Map<String, Object> result = response.getBody();
+            
+            if (result != null) {
+                Integer errcode = (Integer) result.get("errcode");
+                if (errcode != null && errcode == 0) {
+                    Map<String, Object> phoneInfo = (Map<String, Object>) result.get("phone_info");
+                    if (phoneInfo != null) {
+                        return (String) phoneInfo.get("phoneNumber");
+                    }
+                } else {
+                    log.error("[微信API] 获取手机号失败 - errcode: {}, errmsg: {}", 
+                            result.get("errcode"), result.get("errmsg"));
+                }
+            }
+            
+            return null;
+        } catch (Exception e) {
+            log.error("[微信API] 调用getuserphonenumber异常", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 获取access_token
+     */
+    private String getAccessToken() {
+        try {
+            String url = String.format(
+                "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
+                miniappAppId, miniappSecret
+            );
+            
+            ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
+            Map<String, Object> result = response.getBody();
+            
+            if (result != null && result.containsKey("access_token")) {
+                return (String) result.get("access_token");
+            } else {
+                log.error("[微信API] 获取access_token失败 - response: {}", result);
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("[微信API] 调用获取access_token异常", e);
+            return null;
+        }
+    }
+    
     @Override
     public Result<Map<String, Object>> getUserInfo(String userId) {
         try {