徐福静0235668 4 bulan lalu
induk
melakukan
401f609d0f
28 mengubah file dengan 3220 tambahan dan 1346 penghapusan
  1. 1 1
      angular.json
  2. 1 0
      backend/src/main/java/com/recycle/config/WebConfig.java
  3. 40 2
      backend/src/main/java/com/recycle/controller/business/BusinessAuthController.java
  4. 42 2
      backend/src/main/java/com/recycle/controller/government/GovernmentAuthController.java
  5. 54 11
      backend/src/main/java/com/recycle/controller/government/StatisticsController.java
  6. TEMPAT SAMPAH
      backend/target/classes/com/recycle/config/WebConfig.class
  7. TEMPAT SAMPAH
      backend/target/classes/com/recycle/controller/business/BusinessAuthController.class
  8. TEMPAT SAMPAH
      backend/target/classes/com/recycle/controller/government/GovernmentAuthController.class
  9. TEMPAT SAMPAH
      backend/target/classes/com/recycle/controller/government/StatisticsController.class
  10. 433 0
      doc/设计思路文档.md
  11. 104 77
      src/app/business/dashboard/dashboard.html
  12. 480 254
      src/app/business/dashboard/dashboard.scss
  13. 3 1
      src/app/business/dashboard/dashboard.ts
  14. 12 40
      src/app/consumer/booking-recycle/booking-recycle.scss
  15. 12 11
      src/app/consumer/earnings/earnings.scss
  16. 154 71
      src/app/consumer/home/home.html
  17. 593 669
      src/app/consumer/home/home.scss
  18. 13 1
      src/app/consumer/home/home.ts
  19. 36 26
      src/app/consumer/points-mall/points-mall.scss
  20. 97 52
      src/app/consumer/profile/profile.scss
  21. 55 60
      src/app/government/supervision-overview/supervision-overview.html
  22. 261 39
      src/app/government/supervision-overview/supervision-overview.scss
  23. 25 29
      src/app/shared/bottom-nav/bottom-nav.component.scss
  24. 136 0
      src/app/shared/components/empty-state/empty-state.component.ts
  25. 145 0
      src/app/shared/components/page-header/page-header.component.ts
  26. 186 0
      src/app/shared/components/skeleton/skeleton.component.ts
  27. 239 0
      src/app/shared/components/stat-card/stat-card.component.ts
  28. 98 0
      src/app/shared/components/status-badge/status-badge.component.ts

+ 1 - 1
angular.json

@@ -40,7 +40,7 @@
             "styles": [
               "src/styles.scss"
             ],
-            "externalDependencies": []
+            "externalDependencies": ["buffer"]
           },
           "configurations": {
             "production": {

+ 1 - 0
backend/src/main/java/com/recycle/config/WebConfig.java

@@ -9,6 +9,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 @Configuration
 @RequiredArgsConstructor
+@SuppressWarnings("unused")
 public class WebConfig implements WebMvcConfigurer {
 
     private final AuthInterceptor authInterceptor;

+ 40 - 2
backend/src/main/java/com/recycle/controller/business/BusinessAuthController.java

@@ -1,8 +1,15 @@
 package com.recycle.controller.business;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.recycle.common.Result;
+import com.recycle.entity.EnterpriseEmployee;
+import com.recycle.entity.Enterprise;
+import com.recycle.service.EnterpriseEmployeeService;
+import com.recycle.service.EnterpriseService;
+import com.recycle.util.JwtUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.HashMap;
@@ -11,14 +18,45 @@ import java.util.Map;
 @Api(tags = "B端认证接口")
 @RestController
 @RequestMapping("/api/b/auth")
+@RequiredArgsConstructor
 public class BusinessAuthController {
 
+    private final EnterpriseEmployeeService enterpriseEmployeeService;
+    private final EnterpriseService enterpriseService;
+    private final JwtUtil jwtUtil;
+
     @ApiOperation("企业用户登录")
     @PostMapping("/login")
     public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
-        // TODO: 实现企业用户登录逻辑
+        String phone = params.get("phone");
+        String password = params.get("password");
+
+        if (phone == null || password == null) {
+            return Result.error("手机号和密码不能为空");
+        }
+
+        EnterpriseEmployee employee = enterpriseEmployeeService.getOne(
+                new LambdaQueryWrapper<EnterpriseEmployee>().eq(EnterpriseEmployee::getPhone, phone)
+        );
+
+        if (employee == null) {
+            return Result.error("账号不存在");
+        }
+
+        // 生成 JWT Token
+        String token = jwtUtil.generateToken(employee.getId(), phone);
+
+        // 查询关联企业信息
+        Enterprise enterprise = enterpriseService.getById(employee.getEnterpriseId());
+
         Map<String, Object> data = new HashMap<>();
-        data.put("token", "business_token_placeholder");
+        data.put("token", token);
+        data.put("employee", employee);
+        if (enterprise != null) {
+            data.put("enterpriseName", enterprise.getName());
+            data.put("enterpriseId", enterprise.getId());
+        }
+
         return Result.success(data);
     }
 }

+ 42 - 2
backend/src/main/java/com/recycle/controller/government/GovernmentAuthController.java

@@ -1,24 +1,64 @@
 package com.recycle.controller.government;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.recycle.common.Result;
+import com.recycle.entity.GovernmentUser;
+import com.recycle.service.GovernmentUserService;
+import com.recycle.util.JwtUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.Map;
 
 @Api(tags = "G端认证接口")
 @RestController
 @RequestMapping("/api/g/auth")
+@RequiredArgsConstructor
 public class GovernmentAuthController {
 
+    private final GovernmentUserService governmentUserService;
+    private final JwtUtil jwtUtil;
+
     @ApiOperation("政府用户登录")
     @PostMapping("/login")
     public Result<Map<String, Object>> login(@RequestBody Map<String, String> params) {
-        // TODO: 实现政府用户登录逻辑
+        String username = params.get("username");
+        String password = params.get("password");
+
+        if (username == null || password == null) {
+            return Result.error("用户名和密码不能为空");
+        }
+
+        GovernmentUser user = governmentUserService.getOne(
+                new LambdaQueryWrapper<GovernmentUser>().eq(GovernmentUser::getUsername, username)
+        );
+
+        if (user == null) {
+            return Result.error("账号不存在");
+        }
+
+        if (user.getStatus() != null && user.getStatus() != 1) {
+            return Result.error("账号已被禁用");
+        }
+
+        // 生成 JWT Token
+        String token = jwtUtil.generateToken(user.getId(), username);
+
+        // 更新最后登录时间
+        user.setLastLoginAt(LocalDateTime.now());
+        governmentUserService.updateById(user);
+
+        // 隐藏敏感信息
+        user.setPassword(null);
+
         Map<String, Object> data = new HashMap<>();
-        data.put("token", "government_token_placeholder");
+        data.put("token", token);
+        data.put("user", user);
+
         return Result.success(data);
     }
 }

+ 54 - 11
backend/src/main/java/com/recycle/controller/government/StatisticsController.java

@@ -1,14 +1,22 @@
 package com.recycle.controller.government;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.recycle.common.Result;
+import com.recycle.entity.RecycleOrder;
+import com.recycle.entity.RegionStatistics;
 import com.recycle.service.RecycleOrderService;
+import com.recycle.service.RegionStatisticsService;
+import com.recycle.service.UserService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 @Api(tags = "G端统计接口")
 @RestController
@@ -17,29 +25,64 @@ import java.util.Map;
 public class StatisticsController {
 
     private final RecycleOrderService recycleOrderService;
+    private final UserService userService;
+    private final RegionStatisticsService regionStatisticsService;
 
     @ApiOperation("获取总体统计数据")
     @GetMapping("/overview")
     public Result<Map<String, Object>> getOverview() {
         Map<String, Object> data = new HashMap<>();
-        
+
         // 订单总数
         long totalOrders = recycleOrderService.count();
         data.put("totalOrders", totalOrders);
-        
-        // TODO: 添加更多统计数据
-        data.put("totalRecycleWeight", 0);
-        data.put("totalCarbonReduction", 0);
-        data.put("totalUsers", 0);
-        
+
+        // 用户总数
+        long totalUsers = userService.count();
+        data.put("totalUsers", totalUsers);
+
+        // 从已完成订单中汇总回收重量和碳减排
+        List<RecycleOrder> completedOrders = recycleOrderService.list(
+                new LambdaQueryWrapper<RecycleOrder>().eq(RecycleOrder::getStatus, "completed")
+        );
+
+        BigDecimal totalWeight = completedOrders.stream()
+                .map(o -> o.getWeight() != null ? o.getWeight() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        BigDecimal totalCarbon = completedOrders.stream()
+                .map(o -> o.getCarbonReduction() != null ? o.getCarbonReduction() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        data.put("totalRecycleWeight", totalWeight);
+        data.put("totalCarbonReduction", totalCarbon);
+        data.put("completedOrders", completedOrders.size());
+
         return Result.success(data);
     }
 
     @ApiOperation("获取区域统计")
     @GetMapping("/regions")
-    public Result<Map<String, Object>> getRegionStatistics() {
-        Map<String, Object> data = new HashMap<>();
-        // TODO: 实现区域统计
-        return Result.success(data);
+    public Result<List<Map<String, Object>>> getRegionStatistics() {
+        List<RegionStatistics> regionList = regionStatisticsService.list(
+                new LambdaQueryWrapper<RegionStatistics>().orderByDesc(RegionStatistics::getStatDate)
+        );
+
+        List<Map<String, Object>> result = regionList.stream().map(r -> {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", r.getId());
+            item.put("regionId", r.getRegionId());
+            item.put("regionName", r.getRegionName());
+            item.put("statDate", r.getStatDate());
+            item.put("recycleVolume", r.getRecycleVolume());
+            item.put("recycleRate", r.getRecycleRate());
+            item.put("accuracyRate", r.getAccuracyRate());
+            item.put("carbonReduction", r.getCarbonReduction());
+            item.put("companies", r.getCompanies());
+            item.put("trend", r.getTrend());
+            return item;
+        }).collect(Collectors.toList());
+
+        return Result.success(result);
     }
 }

TEMPAT SAMPAH
backend/target/classes/com/recycle/config/WebConfig.class


TEMPAT SAMPAH
backend/target/classes/com/recycle/controller/business/BusinessAuthController.class


TEMPAT SAMPAH
backend/target/classes/com/recycle/controller/government/GovernmentAuthController.class


TEMPAT SAMPAH
backend/target/classes/com/recycle/controller/government/StatisticsController.class


+ 433 - 0
doc/设计思路文档.md

@@ -0,0 +1,433 @@
+# "再生视界"智能回收物联网系统 — 设计思路文档
+
+---
+
+## 一、项目定位与背景
+
+"再生视界"是一款 **端—边—云协同** 的智能回收物联网系统,致力于打通废弃物从投递到处理的全链路感知与决策闭环。系统面向 **再生资源企业(B端)、居民用户(C端)、政府监管部门(G端)** 三类核心角色,以"硬件 + SaaS + 技术授权"模式构建商业闭环。
+
+### 核心痛点与价值
+
+| 痛点 | 解决方案 | 量化成效 |
+|------|---------|---------|
+| 居民分类投递准确率低 | AR多光谱识别 + 语音引导 | 误投率从23.7%降至4.1% |
+| 回收站点满溢与空载并存 | STGNN时空预测 + 动态调度 | 提前3h预判峰值 |
+| 企业运营数据孤岛 | 端—边—云数据贯通 | 设备利用率提升30%+ |
+| 政府监管缺乏实时抓手 | 可视化大屏 + 合规审计 | 实时预警 + 碳减排追踪 |
+
+---
+
+## 二、系统总体架构
+
+```
+┌──────────────────────────────────────────────────────────┐
+│                      用户交互层                          │
+│  ┌──────────┐   ┌──────────┐   ┌──────────┐            │
+│  │  C端 App │   │  B端 App │   │  G端 App │            │
+│  │ 居民用户 │   │ 企业工作台│   │ 政府监管 │            │
+│  └─────┬────┘   └─────┬────┘   └─────┬────┘            │
+├────────┼──────────────┼──────────────┼──────────────────┤
+│        │         API Gateway         │                  │
+│        └──────────────┼──────────────┘                  │
+│                       │                                  │
+├───────────────────────┼──────────────────────────────────┤
+│                  云端服务层                               │
+│  ┌─────────────┐ ┌──────────┐ ┌───────────────┐        │
+│  │Spring Boot  │ │ DeepSeek │ │ STGNN 预测引擎│        │
+│  │  RESTful API│ │ AI 大模型│ │ (时空图神经网络)│        │
+│  └──────┬──────┘ └────┬─────┘ └───────┬───────┘        │
+│         │             │               │                  │
+│  ┌──────┴─────────────┴───────────────┴──────┐          │
+│  │        MySQL 数据持久层 (MyBatis-Plus)     │          │
+│  └───────────────────────────────────────────┘          │
+├──────────────────────────────────────────────────────────┤
+│                  边缘计算层                               │
+│  ┌─────────────────────────────────────┐                │
+│  │  Jetson 边缘计算网关                 │                │
+│  │  · 本地推理 (TFLite / ONNX)         │                │
+│  │  · 满溢预警 & 异常检测               │                │
+│  │  · 数据预处理 & 压缩上行             │                │
+│  └───────────────┬─────────────────────┘                │
+├──────────────────┼───────────────────────────────────────┤
+│                  感知设备层                               │
+│  ┌───────────┐ ┌───────────┐ ┌───────────┐             │
+│  │多光谱传感器│ │红外模组   │ │称重传感器 │             │
+│  │(材质识别) │ │(污染度)   │ │(重量采集) │             │
+│  └───────────┘ └───────────┘ └───────────┘             │
+│  ┌───────────┐ ┌───────────┐                            │
+│  │摄像头模组 │ │含水率探针 │                            │
+│  │(视觉识别) │ │(水分检测) │                            │
+│  └───────────┘ └───────────┘                            │
+└──────────────────────────────────────────────────────────┘
+```
+
+### 2.1 四层架构说明
+
+| 层级 | 职责 | 关键技术 |
+|------|------|---------|
+| **感知设备层** | 采集废弃物材质、污染度、含水率、重量等物理参数 | 多光谱传感器、红外模组、称重模块、含水率探针 |
+| **边缘计算层** | 本地实时推理、满溢预警、数据预处理与压缩上行 | NVIDIA Jetson、TFLite/ONNX 推理框架 |
+| **云端服务层** | 业务逻辑、AI决策、时空预测、数据持久化 | Spring Boot、DeepSeek大模型、STGNN、MySQL |
+| **用户交互层** | 三端差异化界面、实时数据可视化、智能交互 | Angular 20 + Ionic 8 + NG-ZORRO 跨平台应用 |
+
+---
+
+## 三、技术栈详解
+
+### 3.1 前端技术栈
+
+```
+Angular 20           — 前端框架(Standalone Component、Signal、zoneless-ready)
+Ionic 8              — 跨平台 UI 框架(iOS / Android / Web / PWA)
+NG-ZORRO 20          — 企业级 UI 组件库(表格、表单、图表、模态框)
+TensorFlow.js        — 浏览器端 AI 推理(COCO-SSD 目标检测 → AR识别)
+ECharts 6            — 数据可视化(折线图、热力图、雷达图、碳减排曲线)
+高德地图 JS API      — 地理信息服务(网点分布、路线规划、区域热力图)
+BabylonJS            — 3D/AR 渲染引擎(废弃物三维扫描与可视化)
+DeepSeek API         — AI 对话引擎(C端AI分类助手 / B端AI运营助手 / G端AI决策助手)
+Capacitor 7          — 原生能力桥接(相机、文件系统、剪贴板)
+RxJS 7               — 响应式状态管理与异步数据流
+```
+
+### 3.2 后端技术栈
+
+```
+Spring Boot 2.7      — 微服务框架(RESTful API)
+MyBatis-Plus 3.5     — ORM 持久层框架(代码生成、自动分页、逻辑删除)
+MySQL 8.0            — 关系型数据库(60+ 张业务表)
+JWT (jjwt 0.12)      — 用户认证与鉴权
+Knife4j 3.0          — API 文档自动生成(Swagger增强)
+Hutool 5.8           — Java 工具集(加密、HTTP、日期处理)
+Lombok               — 代码简化(注解驱动的 Getter/Setter/Builder)
+```
+
+### 3.3 数据库设计(核心表域)
+
+```
+                    ┌──────────────────┐
+            ┌───────┤  user (用户表)    ├───────┐
+            │       └──────────────────┘       │
+            ▼                                   ▼
+┌───────────────────┐              ┌────────────────────┐
+│recycle_order      │              │user_address         │
+│(回收订单)         │              │(收货地址)           │
+└─────────┬─────────┘              └────────────────────┘
+          │
+          ▼
+┌───────────────────┐    ┌────────────────┐    ┌──────────────────┐
+│earning            │    │waste_category  │    │drop_point        │
+│(收益记录)         │    │(废品分类)      │    │(投放站点)        │
+└───────────────────┘    └────────────────┘    └──────────────────┘
+
+┌───────────────────┐    ┌────────────────┐    ┌──────────────────┐
+│enterprise         │    │device          │    │government_user   │
+│(企业信息)         │    │(设备管理)      │    │(政务用户)        │
+└───────────────────┘    └────────────────┘    └──────────────────┘
+
+┌───────────────────┐    ┌────────────────┐    ┌──────────────────┐
+│government_warning │    │subsidy_        │    │policy_simulation │
+│(监管预警)         │    │application     │    │(政策模拟)        │
+└───────────────────┘    │(补贴申请)      │    └──────────────────┘
+                         └────────────────┘
+```
+
+**总计 60+ 张业务实体表**,按域划分为:
+
+- **C端域**:User、RecycleOrder、Earning、PointsRecord、Product、ExchangeRecord、UserAddress、Favorite、ScanHistory 等
+- **B端域**:Enterprise、Device、BusinessOrder、BusinessAlert、BusinessTodo、Contract、DeviceMaintenance、Collector 等
+- **G端域**:GovernmentUser、GovernmentWarning、SubsidyApplication、Policy、PolicySimulation、RegionStatistics、ComplianceCheck、MonitoringLocation 等
+- **公共域**:Activity、Badge、Notification、AiChatHistory、AiInsight、WasteCategory、LoginLog 等
+
+---
+
+## 四、三端功能架构
+
+### 4.1 C端(Consumer — 居民用户)
+
+```
+C端首页
+├── 📱 AR 智能识别      → TensorFlow.js + BabylonJS(相机扫描 → 废品分类)
+├── 📅 预约回收         → 分类选择 → 时段预约 → 上门回收 → 订单跟踪
+├── 💰 我的收益         → 现金收益 + 积分收益 + 碳减排曲线(ECharts)
+├── 🛍️ 积分商城        → 商品兑换 + 活动专区 + 环保知识学习
+├── 🤖 AI 分类助手      → DeepSeek 大模型驱动(流式输出 + 语音交互)
+├── 📍 附近投放点       → 高德地图 + 容量可视化 + 导航
+├── 🔔 消息通知         → 订单状态 + 系统公告 + 活动推送
+└── 👤 个人中心         → 订单管理 + 地址管理 + 成就徽章 + 邀请好友
+```
+
+### 4.2 B端(Business — 企业工作台)
+
+```
+B端 Dashboard
+├── 📊 数据大屏         → 今日回收量/订单数/收入/碳减排(实时刷新)
+├── 🚨 预警中心         → 设备满溢/异常报警/合规预警
+├── ⚡ 快捷操作         → 扫码收货 / 派单 / 设备巡检 / 数据导出
+├── 🤖 AI 运营助手      → 运营建议 + 效率优化 + 异常分析
+├── 📋 待办事项         → 待处理订单 + 设备告警 + 合同审批
+├── 📦 订单管理         → 全生命周期(创建→分拣→运输→结算)
+├── 🔧 设备管理         → 设备状态监控 + 维护记录 + 远程诊断
+├── 📈 数据报表         → 多维度统计 + 趋势分析 + Excel/PDF 导出
+└── 🏢 企业中心         → 员工管理 + 合同管理 + 订阅套餐
+```
+
+### 4.3 G端(Government — 政府监管平台)
+
+```
+G端监管总览
+├── 🗺️ 区域监管地图     → 高德地图多图层(网点分布/合规状态/政策覆盖)
+├── 📊 全域核心指标      → 回收总量 / 分类准确率 / 碳减排(实时更新)
+├── 🚨 实时预警列表      → 违规事件 + 高风险区域 + 处理进度追踪
+├── 💰 补贴管理         → 申请审批 + 发放记录 + 资金追踪
+├── 📈 行业分析         → 产业链数据 + 区域对比 + 趋势预测
+├── 🤖 AI 决策助手      → 政策模拟 + 数据问答 + 智能分析
+└── 🏛️ 政务中心        → 政策发布 + 通知管理 + 密码/通知设置
+```
+
+---
+
+## 五、端—边—云数据流设计
+
+```
+ [智能回收箱]              [Jetson边缘]               [云端平台]
+      │                        │                         │
+  传感器采集 ──────────► 本地推理               ┌─► Spring Boot API
+  · 多光谱 → 材质                │               │   · 订单管理
+  · 红外 → 污染度      预处理 & 压缩 ──MQTT──► │   · 数据聚合
+  · 称重 → 重量           │                     │   · 报表生成
+  · 含水率探针           满溢预警               │
+      │               (本地决策)                │
+  投递反馈 ◄──────────  语音/灯光 ────────────► └─► STGNN 预测
+  · LED状态灯            引导投递                   · 峰值预判
+  · 语音提示                                        · 调度优化
+                                                     · 碳排核算
+                          ▼
+                   [AR 分拣终端]
+                   · 摄像头视觉识别
+                   · 实时标注 & 引导
+                   · TF.js 推理
+```
+
+### 关键数据链路
+
+1. **感知→边缘**:传感器以 100ms 粒度采集,边缘设备完成 <50ms 本地推理
+2. **边缘→云端**:MQTT 协议轻量上行,仅传输特征向量与决策结果(带宽节省 90%+)
+3. **云端→终端**:RESTful API + WebSocket 推送实时状态到三端应用
+4. **AI 闭环**:STGNN 融合历史数据 + 实时流,提前 3 小时预判峰值并触发调度
+
+---
+
+## 六、AI 能力矩阵
+
+| AI 能力 | 技术方案 | 应用场景 |
+|---------|---------|---------|
+| **AR 废品识别** | TensorFlow.js + COCO-SSD + BabylonJS | C端:相机扫描 → 实时分类标注 |
+| **AI 对话助手** | DeepSeek 大模型 API(流式输出) | C端分类咨询 / B端运营建议 / G端决策分析 |
+| **时空预测** | STGNN 时空图神经网络 | 峰值预判、最优调度、需求预测 |
+| **边缘推理** | TFLite / ONNX on Jetson | 满溢检测、异常预警、材质初筛 |
+| **语音交互** | 实时语音识别 + TTS | C端语音投递引导、B端语音巡检 |
+
+---
+
+## 七、前端工程化设计
+
+### 7.1 模块化架构
+
+```
+src/app/
+├── auth/                    # 认证模块(登录/注册/忘记密码)
+├── consumer/                # C端模块(21个子页面)
+│   ├── home/               # 首页(附近回收员、投放点、活动、通知、AR识别)
+│   ├── booking-recycle/    # 预约回收(分类、时段、地址、拍照)
+│   ├── earnings/           # 收益(现金、积分、碳减排图表)
+│   ├── points-mall/        # 积分商城(活动、分类、商品列表)
+│   ├── ai-assistant/       # AI 分类助手(对话、语音)
+│   ├── eco-knowledge/      # 环保知识
+│   └── profile/            # 个人中心(订单、地址、收藏、设置、客服...)
+├── business/                # B端模块(6个子页面)
+│   ├── dashboard/          # 企业仪表盘
+│   ├── order-management/   # 订单管理
+│   ├── device-management/  # 设备管理
+│   ├── data-reports/       # 数据报表
+│   ├── ai-operations-assistant/ # AI运营助手
+│   └── enterprise-center/  # 企业中心
+├── government/              # G端模块(5个子页面)
+│   ├── supervision-overview/ # 监管总览(地图、指标、预警)
+│   ├── subsidy-management/  # 补贴管理
+│   ├── industry-analysis/   # 行业分析
+│   ├── ai-decision-assistant/ # AI决策助手
+│   └── government-center/   # 政务中心
+├── core/                    # 核心层
+│   ├── services/           # 全局服务(API、认证、地图、AR、AI、语音、上传)
+│   └── guards/             # 路由守卫(JWT鉴权)
+└── shared/                  # 共享层
+    ├── components/         # 通用UI组件
+    │   ├── skeleton/       # 骨架屏加载组件
+    │   ├── empty-state/    # 空状态组件
+    │   ├── stat-card/      # 统计卡片组件
+    │   ├── status-badge/   # 状态徽章组件
+    │   └── page-header/    # 页面头部组件
+    ├── bottom-nav/         # 底部导航栏
+    └── avatar-picker/      # 头像选择器
+```
+
+### 7.2 服务层设计
+
+| 服务 | 文件 | 职责 |
+|------|------|------|
+| `ApiService` | `api.service.ts` | 通用 HTTP 请求封装(拦截器、错误处理) |
+| `ConsumerApiService` | `consumer-api.service.ts` | C端业务 API(订单、收益、商城、用户) |
+| `BusinessApiService` | `business-api.service.ts` | B端业务 API(仪表盘、设备、报表、运营) |
+| `GovernmentApiService` | `government-api.service.ts` | G端业务 API(监管、补贴、分析、政策) |
+| `AuthService` | `auth.ts` | JWT 认证(登录、注册、Token 管理、角色路由) |
+| `ArScannerService` | `ar-scanner.service.ts` | AR 识别(TF.js 加载、相机流、目标检测) |
+| `DeepSeekAiService` | `deepseek-ai.ts` | AI 对话(流式输出、上下文管理) |
+| `AmapService` | `amap.service.ts` | 高德地图(定位、搜索、路线、标注) |
+| `VoiceService` | `voice.service.ts` | 语音识别与合成 |
+| `FileUploadService` | `file-upload.service.ts` | 文件上传(图片压缩、七牛云/OSS) |
+
+### 7.3 UI 设计规范
+
+| 维度 | C端 | B端 | G端 |
+|------|-----|-----|-----|
+| **主色** | 绿色系 `#1b5e20 → #43a047` | 靛蓝系 `#1a237e → #3949ab` | 深蓝系 `#0d47a1 → #1976d2` |
+| **调性** | 清新、活力、环保 | 专业、高效、数据驱动 | 权威、稳重、可信赖 |
+| **背景色** | `#f4f6f8` | `#f0f2f5` | `#eef1f5` |
+| **圆角** | 14px | 14px | 14px |
+| **阴影** | `0 1px 4px rgba(0,0,0,0.04)` | `0 1px 4px rgba(0,0,0,0.05)` | `0 1px 4px rgba(0,0,0,0.04)` |
+| **字体** | -apple-system, PingFang SC | -apple-system, PingFang SC | -apple-system, PingFang SC |
+| **骨架屏** | ✅ 全覆盖 | ✅ 全覆盖 | 部分覆盖 |
+| **空状态** | ✅ 统一组件 | ✅ 统一组件 | ✅ 统一组件 |
+
+---
+
+## 八、后端 API 架构
+
+### 8.1 API 分层设计
+
+```
+/api/c/      → C端 API(居民用户)
+  ├── /auth      认证(登录/注册/验证码)
+  ├── /user      用户信息(资料/地址/收藏/设置)
+  ├── /order     回收订单(创建/列表/详情/取消)
+  ├── /product   积分商城(商品/兑换/活动)
+  ├── /collector  回收员(附近/评价)
+  └── /category  废品分类(分类列表/参考价格)
+
+/api/b/      → B端 API(企业用户)
+  ├── /auth      企业认证
+  ├── /user      企业用户管理
+  ├── /order     业务订单(全生命周期管理)
+  ├── /dashboard 数据概览(实时统计/趋势)
+  ├── /device    设备管理(状态/维护/告警)
+  └── /report    数据报表(导出/统计/分析)
+
+/api/g/      → G端 API(政府监管)
+  ├── /auth       政务认证
+  ├── /user       管理员管理
+  ├── /statistics 监管统计(总览/区域/趋势)
+  ├── /warning    预警管理(创建/处理/统计)
+  ├── /subsidy    补贴管理(申请/审批/发放)
+  └── /policy     政策管理(发布/模拟/评估)
+```
+
+### 8.2 后端分层架构
+
+```
+Controller 层 (13个控制器)
+    ↓  参数校验、权限检查
+Service 层 (61个业务服务 + 实现)
+    ↓  业务逻辑、事务管理
+Mapper 层 (61个 MyBatis-Plus Mapper)
+    ↓  SQL 映射、自动分页
+MySQL 数据库 (60+ 张表)
+```
+
+---
+
+## 九、安全与认证设计
+
+```
+┌─────────────┐    ┌──────────────┐    ┌─────────────────┐
+│ 客户端请求   │───►│ JWT 拦截器    │───►│ 业务 Controller │
+│ (Bearer Token)│   │ (验签+解密)  │    │ (角色路由分发)  │
+└─────────────┘    └──────┬───────┘    └─────────────────┘
+                          │
+                 ┌────────▼────────┐
+                 │  角色鉴权       │
+                 │  C端: consumer  │
+                 │  B端: business  │
+                 │  G端: government│
+                 └─────────────────┘
+```
+
+- **JWT 签发**:登录成功后颁发 Token,有效期可配置
+- **拦截器校验**:每次请求自动校验 Token 有效性
+- **三端隔离**:API 路径 `/api/c/`、`/api/b/`、`/api/g/` 分端隔离
+- **前端路由守卫**:`authGuard` 保护需认证路由
+
+---
+
+## 十、部署架构
+
+```
+┌──────────────────────────────────────────────────────┐
+│                    生产环境                            │
+│                                                      │
+│  ┌────────────┐     ┌────────────┐    ┌───────────┐ │
+│  │ Nginx      │────►│ Angular SSR│    │ 静态资源   │ │
+│  │ 反向代理   │     │ / CSR 前端 │    │ CDN 分发   │ │
+│  └─────┬──────┘     └────────────┘    └───────────┘ │
+│        │                                             │
+│        ▼                                             │
+│  ┌────────────┐     ┌────────────┐                  │
+│  │ Spring Boot│────►│ MySQL 8.0  │                  │
+│  │  后端服务  │     │ 主从集群   │                  │
+│  └─────┬──────┘     └────────────┘                  │
+│        │                                             │
+│        ▼                                             │
+│  ┌────────────┐     ┌────────────┐                  │
+│  │ Redis 缓存 │     │ 七牛云/OSS │                  │
+│  │ (会话&热数据)│    │ (图片存储) │                  │
+│  └────────────┘     └────────────┘                  │
+│                                                      │
+│  ┌────────────────────────────────────────────┐     │
+│  │  边缘节点 (社区/园区)                       │     │
+│  │  Jetson + MQTT Broker → 云端消息队列       │     │
+│  └────────────────────────────────────────────┘     │
+└──────────────────────────────────────────────────────┘
+```
+
+---
+
+## 十一、创新亮点总结
+
+1. **端—边—云三级架构**:感知层→边缘推理→云端决策,打通全链路数据闭环
+2. **三端一体 SaaS 平台**:C/B/G 三端差异化体验,共享同一后端服务与数据中台
+3. **AI 深度融合**:TF.js 浏览器端识别 + DeepSeek 大模型对话 + STGNN 时空预测
+4. **工业级 UI 设计**:骨架屏、空状态、趋势指示器、动效卡片、响应式布局
+5. **AR 交互创新**:多光谱 + 视觉融合,实时标注引导投递,识别准确率 95%+
+6. **碳减排量化追踪**:从投递→分拣→处理全链路碳足迹核算,支撑碳市场对接
+7. **知识产权储备**:已形成 30+ 项知识产权成果,覆盖硬件、算法、软件著作权
+
+---
+
+## 十二、商业模式
+
+```
+收入来源 = 硬件销售 + SaaS 订阅 + 技术授权
+
+┌─────────────────┬──────────────────┬──────────────────┐
+│   硬件销售       │   SaaS 订阅      │   技术授权        │
+│                 │                  │                  │
+│ · 智能回收箱    │ · B端企业版月费  │ · 算法SDK授权    │
+│ · AR分拣终端    │ · G端政务版年费  │ · 定制化开发     │
+│ · 边缘计算网关  │ · 增值数据服务   │ · 联合实验室     │
+│                 │                  │                  │
+│ 首轮融资200万元 │ 试点社区验证完成  │ 合作企业原型测试  │
+└─────────────────┴──────────────────┴──────────────────┘
+```
+
+---
+
+> **版本**:v1.0 | **最后更新**:2026年4月

+ 104 - 77
src/app/business/dashboard/dashboard.html

@@ -1,99 +1,126 @@
-<!-- 顶部导航 -->
-<header class="header">
-  <div class="container">
-    <div class="header-content">
-      <div class="logo">
-        <span class="logo-icon">♻️</span>
-        <span>再生视界</span>
+<!-- ====== B端 顶部导航 ====== -->
+<header class="b-header">
+  <div class="header-inner">
+    <div class="header-brand">
+      <div class="brand-icon"><i class="fas fa-recycle"></i></div>
+      <div class="brand-text">
+        <span class="brand-name">再生视界</span>
+        <span class="brand-sub">企业工作台</span>
       </div>
-      <div class="user-info">
-        <span>企业管理员</span>
-        <div class="avatar" (click)="onUserAvatarClick()" title="查看企业中心">
-          <span>管</span>
-        </div>
+    </div>
+    <div class="header-right">
+      <button class="hdr-btn" (click)="onViewMoreClick('alerts')" title="预警中心">
+        <i class="fas fa-bell"></i>
+        <span class="badge-dot" *ngIf="alertInfo.content.length > 0"></span>
+      </button>
+      <div class="user-chip" (click)="onUserAvatarClick()">
+        <div class="user-avatar"><i class="fas fa-building"></i></div>
+        <span class="user-name">{{ userInfo.company || '企业' }}</span>
       </div>
     </div>
   </div>
 </header>
 
-<!-- 主要内容 -->
-<main class="content">
-  <div class="container">
-    <!-- 工作台首页 -->
-    <section id="dashboard">
-      <h2 class="section-title">工作台首页 <span class="more-link">今日数据</span></h2>
-      
-      <!-- 核心数据看板 -->
-      <div class="data-dashboard">
-        <div class="data-card" *ngFor="let card of dataCards" (click)="onDataCardClick(card)">
-          <div class="data-label">{{card.label}}</div>
-          <div class="data-value">{{card.value}}</div>
-          <div class="data-trend">{{card.trend}}</div>
-        </div>
-      </div>
-      
-      <!-- 预警信息卡片 -->
-      <div class="card alert-card" *ngIf="alertInfo.content.length > 0">
-        <div class="alert-title">
-          <div>
-            <span class="alert-icon">{{alertInfo.icon}}</span>
-            <span>{{alertInfo.title}}</span>
-          </div>
-          <span class="more-link" (click)="onViewMoreClick('alerts')">查看全部</span>
+<!-- ====== 主内容 ====== -->
+<main class="b-content">
+
+  <!-- 核心数据看板 -->
+  <section class="stats-section">
+    <div class="stats-header">
+      <h2><i class="fas fa-chart-line"></i> 今日数据</h2>
+      <span class="stats-time">实时更新</span>
+    </div>
+    <app-skeleton *ngIf="loading.dashboard" type="stat"></app-skeleton>
+    <div class="stats-grid" *ngIf="!loading.dashboard">
+      <div class="stat-tile" *ngFor="let card of dataCards; let i = index" 
+           [ngClass]="'tile-' + i" (click)="onDataCardClick(card)">
+        <div class="tile-icon">{{ card.icon }}</div>
+        <div class="tile-body">
+          <div class="tile-value">{{ card.value }}</div>
+          <div class="tile-label">{{ card.label }}</div>
         </div>
-        <div class="alert-content">
-          <p *ngFor="let content of alertInfo.content">• {{content}}</p>
+        <div class="tile-trend" [ngClass]="card.trend.startsWith('↑') ? 'up' : card.trend.startsWith('↓') ? 'down' : 'flat'">
+          {{ card.trend }}
         </div>
       </div>
-      
-      <!-- AI运营助手 -->
-      <div class="ai-assistant" (click)="onAIAssistantClick()">
-        <div class="ai-header">
-          <div class="ai-icon">🤖</div>
-          <div class="ai-title">AI运营助手</div>
+    </div>
+  </section>
+
+  <!-- 预警信息 -->
+  <section class="alert-section" *ngIf="alertInfo.content.length > 0">
+    <div class="alert-bar">
+      <div class="alert-icon-wrap"><i class="fas fa-exclamation-triangle"></i></div>
+      <div class="alert-body">
+        <div class="alert-headline">{{ alertInfo.title }}</div>
+        <div class="alert-items">
+          <span *ngFor="let c of alertInfo.content" class="alert-item">{{ c }}</span>
         </div>
-        <div class="ai-suggestions">
-          <div class="ai-suggestion" *ngFor="let suggestion of aiSuggestions" (click)="onAISuggestionClick(suggestion)">
-            {{suggestion.text}}
-          </div>
+      </div>
+      <button class="alert-more" (click)="onViewMoreClick('alerts')">
+        查看 <i class="fas fa-angle-right"></i>
+      </button>
+    </div>
+  </section>
+
+  <!-- 快捷操作 + AI 助手 双栏 -->
+  <div class="dual-row">
+    <!-- 快捷操作 -->
+    <section class="quick-section card-panel">
+      <h3 class="panel-title"><i class="fas fa-bolt"></i> 快捷操作</h3>
+      <div class="quick-grid">
+        <div class="quick-item" *ngFor="let action of quickActions" (click)="onQuickActionClick(action)">
+          <div class="quick-icon">{{ action.icon }}</div>
+          <div class="quick-label">{{ action.label }}</div>
         </div>
       </div>
-      
-      <!-- 快捷操作入口 -->
-      <div class="card">
-        <h3 class="section-title">快捷操作</h3>
-        <div class="quick-actions">
-          <div class="action-item" *ngFor="let action of quickActions" (click)="onQuickActionClick(action)">
-            <div class="action-icon">{{action.icon}}</div>
-            <div class="action-label">{{action.label}}</div>
-          </div>
+    </section>
+
+    <!-- AI运营助手 -->
+    <section class="ai-section card-panel" (click)="onAIAssistantClick()">
+      <div class="ai-banner">
+        <div class="ai-logo"><i class="fas fa-robot"></i></div>
+        <div class="ai-info">
+          <h3>AI运营助手</h3>
+          <span>智能分析 · 运营建议</span>
         </div>
       </div>
-      
-      <!-- 待办事项列表 -->
-      <div class="card">
-        <h3 class="section-title">待办事项 <span class="more-link" (click)="onViewMoreClick('todos')">查看全部</span></h3>
-        <div class="todo-list">
-          <div class="todo-item" *ngFor="let todo of todoItems" (click)="onTodoItemClick(todo)">
-            <div class="todo-info">
-              <div class="todo-icon">{{todo.icon}}</div>
-              <div>
-                <div class="todo-title">{{todo.title}}</div>
-                <div class="todo-desc">{{todo.description}}</div>
-              </div>
-            </div>
-            <div class="todo-count">{{todo.count}}</div>
-          </div>
+      <app-skeleton *ngIf="loading.aiSuggestions" type="lines" [count]="2"></app-skeleton>
+      <div class="ai-chips" *ngIf="!loading.aiSuggestions">
+        <div class="ai-chip" *ngFor="let s of aiSuggestions" (click)="onAISuggestionClick(s); $event.stopPropagation()">
+          <i class="fas fa-lightbulb"></i> {{ s.text }}
         </div>
       </div>
     </section>
   </div>
+
+  <!-- 待办事项 -->
+  <section class="todo-section card-panel">
+    <div class="panel-header">
+      <h3 class="panel-title"><i class="fas fa-tasks"></i> 待办事项</h3>
+      <a class="see-all" (click)="onViewMoreClick('todos')">全部 <i class="fas fa-angle-right"></i></a>
+    </div>
+    <app-skeleton *ngIf="loading.todos" type="list" [count]="3"></app-skeleton>
+    <app-empty-state *ngIf="!loading.todos && todoItems.length === 0"
+      icon="no-order" title="暂无待办事项" description="所有任务已处理完毕" size="small">
+    </app-empty-state>
+    <div class="todo-list" *ngIf="!loading.todos && todoItems.length > 0">
+      <div class="todo-row" *ngFor="let todo of todoItems" (click)="onTodoItemClick(todo)">
+        <div class="todo-icon-box">{{ todo.icon }}</div>
+        <div class="todo-body">
+          <div class="todo-title">{{ todo.title }}</div>
+          <div class="todo-desc">{{ todo.description }}</div>
+        </div>
+        <div class="todo-badge">{{ todo.count }}</div>
+      </div>
+    </div>
+  </section>
+
 </main>
 
-<!-- 底部导航 -->
-<nav class="bottom-nav">
-  <div class="nav-item" *ngFor="let item of navItems" [class.active]="item.active" (click)="onNavItemClick(item)">
-    <div class="nav-icon">{{item.icon}}</div>
-    <div>{{item.label}}</div>
+<!-- ====== 底部导航 ====== -->
+<nav class="b-bottom-nav">
+  <div class="b-nav-item" *ngFor="let item of navItems" [class.active]="item.active" (click)="onNavItemClick(item)">
+    <div class="b-nav-icon">{{ item.icon }}</div>
+    <div class="b-nav-label">{{ item.label }}</div>
   </div>
 </nav>

+ 480 - 254
src/app/business/dashboard/dashboard.scss

@@ -1,406 +1,632 @@
-:root {
-  --primary-color: #2ecc71;
-  --primary-dark: #27ae60;
-  --primary-light: #a9dfbf;
-  --secondary-color: #3498db;
-  --accent-color: #f39c12;
-  --warning-color: #e74c3c;
-  --light-bg: #f8f9fa;
-  --dark-text: #2c3e50;
-  --light-text: #7f8c8d;
-  --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-  --border-radius: 12px;
-}
-
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-}
-
-body {
-  background-color: #f5f7fa;
-  color: var(--dark-text);
-  line-height: 1.6;
-}
-
-.container {
-  max-width: 100%;
-  margin: 0 auto;
-  padding: 0 15px;
-}
-
-/* 顶部导航 */
-.header {
-  background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
-  color: white;
-  padding: 15px 0;
+// ============================================================
+// B端 Dashboard — 工业级企业工作台
+// ============================================================
+
+:host {
+  display: block;
+  width: 100%;
+  min-height: 100vh;
+  background: #f0f2f5;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
+  color: #1a1a2e;
+  -webkit-font-smoothing: antialiased;
+}
+
+// ============ Header ============
+.b-header {
+  background: linear-gradient(135deg, #1a237e 0%, #283593 60%, #3949ab 100%);
   position: sticky;
   top: 0;
   z-index: 100;
-  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 12px rgba(26, 35, 126, 0.3);
 }
 
-.header-content {
+.header-inner {
   display: flex;
+  align-items: center;
   justify-content: space-between;
+  padding: 12px 16px;
+  min-height: 56px;
+}
+
+.header-brand {
+  display: flex;
   align-items: center;
+  gap: 10px;
 }
 
-.logo {
+.brand-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.15);
   display: flex;
   align-items: center;
-  font-weight: bold;
-  font-size: 20px;
+  justify-content: center;
+  color: #fff;
+  font-size: 16px;
+}
+
+.brand-text {
+  display: flex;
+  flex-direction: column;
 }
 
-.logo-icon {
-  margin-right: 10px;
-  font-size: 24px;
+.brand-name {
+  font-size: 17px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 0.5px;
 }
 
-.user-info {
+.brand-sub {
+  font-size: 10px;
+  color: rgba(255,255,255,0.6);
+}
+
+.header-right {
   display: flex;
   align-items: center;
+  gap: 10px;
 }
 
-.avatar {
+.hdr-btn {
+  position: relative;
   width: 36px;
   height: 36px;
-  border-radius: 50%;
-  background-color: rgba(255, 255, 255, 0.3);
+  border: none;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.12);
+  color: #fff;
+  font-size: 15px;
+  cursor: pointer;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-left: 10px;
+  transition: background 0.2s;
+
+  &:hover { background: rgba(255,255,255,0.22); }
+  &:active { transform: scale(0.93); }
 }
 
-/* 底部导航 */
-.bottom-nav {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  background-color: white;
+.badge-dot {
+  position: absolute;
+  top: 7px;
+  right: 7px;
+  width: 8px;
+  height: 8px;
+  background: #ff5252;
+  border-radius: 50%;
+  border: 2px solid #283593;
+  animation: bdPulse 2s infinite;
+}
+
+.user-chip {
   display: flex;
-  justify-content: space-around;
-  padding: 10px 0;
-  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
-  z-index: 100;
+  align-items: center;
+  gap: 6px;
+  background: rgba(255,255,255,0.1);
+  border-radius: 20px;
+  padding: 4px 12px 4px 4px;
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:hover { background: rgba(255,255,255,0.18); }
 }
 
-.nav-item {
+.user-avatar {
+  width: 28px;
+  height: 28px;
+  border-radius: 50%;
+  background: rgba(255,255,255,0.2);
   display: flex;
-  flex-direction: column;
   align-items: center;
+  justify-content: center;
+  color: #fff;
   font-size: 12px;
-  color: var(--light-text);
-  flex: 1;
-  cursor: pointer;
-  transition: all 0.3s ease;
 }
 
-.nav-item.active {
-  color: var(--primary-color);
+.user-name {
+  font-size: 12px;
+  color: rgba(255,255,255,0.9);
+  font-weight: 500;
+  max-width: 80px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
-.nav-icon {
-  font-size: 20px;
-  margin-bottom: 4px;
+// ============ Main Content ============
+.b-content {
+  padding: 16px 16px 90px;
 }
 
-/* 内容区域 */
-.content {
-  padding: 20px 0 80px;
+// ============ Stats Section ============
+.stats-section {
+  margin-bottom: 14px;
 }
 
-.section-title {
-  font-size: 18px;
-  font-weight: 600;
-  margin-bottom: 15px;
+.stats-header {
   display: flex;
-  justify-content: space-between;
   align-items: center;
-}
+  justify-content: space-between;
+  margin-bottom: 12px;
 
-.more-link {
-  font-size: 14px;
-  color: var(--primary-color);
-  font-weight: normal;
-  cursor: pointer;
-  transition: color 0.3s ease;
+  h2 {
+    font-size: 16px;
+    font-weight: 700;
+    color: #1a1a2e;
+    display: flex;
+    align-items: center;
+    gap: 6px;
 
-  &:hover {
-    color: var(--primary-dark);
+    i { color: #3949ab; font-size: 14px; }
   }
 }
 
-/* 卡片样式 */
-.card {
-  background-color: white;
-  border-radius: var(--border-radius);
-  box-shadow: var(--card-shadow);
-  padding: 16px;
-  margin-bottom: 16px;
+.stats-time {
+  font-size: 11px;
+  color: #999;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+
+  &::before {
+    content: '';
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #4caf50;
+    animation: bdPulse 2s infinite;
+  }
 }
 
-/* 核心数据看板 */
-.data-dashboard {
+.stats-grid {
   display: grid;
   grid-template-columns: 1fr 1fr;
-  gap: 12px;
-  margin-bottom: 16px;
+  gap: 10px;
 }
 
-.data-card {
-  padding: 16px;
-  border-radius: var(--border-radius);
-  color: white;
+.stat-tile {
+  position: relative;
+  background: #fff;
+  border-radius: 14px;
+  padding: 14px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.05);
+  cursor: pointer;
+  transition: all 0.25s ease;
+  overflow: hidden;
   display: flex;
   flex-direction: column;
-  cursor: pointer;
-  transition: transform 0.3s ease;
 
-  &:hover {
-    transform: translateY(-2px);
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 3px;
   }
-}
 
-.data-card:nth-child(1) {
-  background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
+  &:hover { transform: translateY(-3px); box-shadow: 0 6px 18px rgba(0,0,0,0.08); }
+  &:active { transform: translateY(-1px); }
+
+  &.tile-0::before { background: linear-gradient(90deg, #4caf50, #81c784); }
+  &.tile-1::before { background: linear-gradient(90deg, #2196f3, #64b5f6); }
+  &.tile-2::before { background: linear-gradient(90deg, #ff9800, #ffb74d); }
+  &.tile-3::before { background: linear-gradient(90deg, #9c27b0, #ce93d8); }
+
+  &.tile-0 .tile-icon { background: rgba(76,175,80,0.1); color: #4caf50; }
+  &.tile-1 .tile-icon { background: rgba(33,150,243,0.1); color: #2196f3; }
+  &.tile-2 .tile-icon { background: rgba(255,152,0,0.1); color: #ff9800; }
+  &.tile-3 .tile-icon { background: rgba(156,39,176,0.1); color: #9c27b0; }
 }
 
-.data-card:nth-child(2) {
-  background: linear-gradient(135deg, var(--secondary-color), #2980b9);
+.tile-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  margin-bottom: 10px;
 }
 
-.data-card:nth-child(3) {
-  background: linear-gradient(135deg, #e67e22, #d35400);
+.tile-body {
+  flex: 1;
 }
 
-.data-card:nth-child(4) {
-  background: linear-gradient(135deg, #9b59b6, #8e44ad);
+.tile-value {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1a1a2e;
+  line-height: 1.2;
+  letter-spacing: -0.5px;
 }
 
-.data-value {
-  font-size: 24px;
-  font-weight: bold;
-  margin: 8px 0 4px;
+.tile-label {
+  font-size: 11px;
+  color: #888;
+  margin-top: 3px;
+  font-weight: 500;
 }
 
-.data-label {
-  font-size: 14px;
-  opacity: 0.9;
+.tile-trend {
+  display: inline-flex;
+  align-items: center;
+  font-size: 11px;
+  font-weight: 600;
+  padding: 2px 8px;
+  border-radius: 10px;
+  margin-top: 8px;
+  align-self: flex-start;
+
+  &.up { color: #4caf50; background: rgba(76,175,80,0.1); }
+  &.down { color: #f44336; background: rgba(244,67,54,0.1); }
+  &.flat { color: #9e9e9e; background: rgba(158,158,158,0.1); }
 }
 
-.data-trend {
-  font-size: 12px;
-  opacity: 0.8;
+// ============ Alert Section ============
+.alert-section {
+  margin-bottom: 14px;
 }
 
-/* 预警卡片 */
-.alert-card {
-  border-left: 4px solid var(--warning-color);
-  background-color: #fff9f9;
+.alert-bar {
+  display: flex;
+  align-items: center;
+  background: linear-gradient(135deg, #fff3e0, #ffe0b2);
+  border: 1px solid #ffcc80;
+  border-radius: 12px;
+  padding: 12px 14px;
+  gap: 10px;
 }
 
-.alert-title {
+.alert-icon-wrap {
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  background: rgba(255,152,0,0.15);
   display: flex;
   align-items: center;
-  color: var(--warning-color);
+  justify-content: center;
+  color: #e65100;
+  font-size: 16px;
+  flex-shrink: 0;
+}
+
+.alert-body {
+  flex: 1;
+  min-width: 0;
+}
+
+.alert-headline {
+  font-size: 13px;
+  font-weight: 700;
+  color: #e65100;
+}
+
+.alert-items {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+  margin-top: 4px;
+}
+
+.alert-item {
+  font-size: 11px;
+  color: #bf360c;
+  background: rgba(255,255,255,0.6);
+  padding: 1px 8px;
+  border-radius: 8px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 180px;
+}
+
+.alert-more {
+  border: none;
+  background: rgba(230,81,0,0.08);
+  color: #e65100;
+  font-size: 12px;
   font-weight: 600;
-  margin-bottom: 8px;
+  padding: 6px 12px;
+  border-radius: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+  display: flex;
+  align-items: center;
+  gap: 3px;
+  transition: background 0.2s;
+
+  &:hover { background: rgba(230,81,0,0.15); }
+  i { font-size: 10px; }
 }
 
-.alert-icon {
-  margin-right: 8px;
+// ============ Card Panel (shared) ============
+.card-panel {
+  background: #fff;
+  border-radius: 14px;
+  padding: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
 }
 
-.alert-content p {
-  margin-bottom: 4px;
+.panel-title {
   font-size: 14px;
-  color: var(--dark-text);
-}
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 12px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
 
-/* 快捷操作 */
-.quick-actions {
-  display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 16px;
-  text-align: center;
+  i { color: #3949ab; font-size: 13px; }
 }
 
-.action-item {
+.panel-header {
   display: flex;
-  flex-direction: column;
   align-items: center;
-  cursor: pointer;
-  transition: transform 0.3s ease;
+  justify-content: space-between;
+  margin-bottom: 12px;
 
-  &:hover {
-    transform: translateY(-2px);
-  }
+  .panel-title { margin-bottom: 0; }
 }
 
-.action-icon {
-  width: 48px;
-  height: 48px;
-  background-color: var(--primary-light);
-  border-radius: 12px;
+.see-all {
+  font-size: 12px;
+  color: #888;
+  cursor: pointer;
   display: flex;
   align-items: center;
-  justify-content: center;
-  margin-bottom: 8px;
-  color: var(--primary-dark);
-  font-size: 20px;
+  gap: 3px;
+  transition: color 0.2s;
+
+  &:hover { color: #3949ab; }
+  i { font-size: 10px; }
 }
 
-.action-label {
-  font-size: 13px;
-  color: var(--dark-text);
+// ============ Dual Row ============
+.dual-row {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 14px;
+}
+
+.quick-section {
+  flex: 1;
+  min-width: 0;
+}
+
+.ai-section {
+  flex: 1;
+  min-width: 0;
+  cursor: pointer;
+  transition: all 0.25s ease;
+
+  &:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0,0,0,0.08); }
+}
+
+// ============ Quick Actions ============
+.quick-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 8px;
 }
 
-/* 待办事项 */
-.todo-item {
+.quick-item {
   display: flex;
-  justify-content: space-between;
+  flex-direction: column;
   align-items: center;
-  padding: 12px 0;
-  border-bottom: 1px solid #eee;
+  padding: 10px 4px;
+  border-radius: 10px;
   cursor: pointer;
-  transition: background-color 0.3s ease;
+  transition: all 0.2s ease;
+  background: #f8f9fc;
 
-  &:hover {
-    background-color: #f8f9fa;
-  }
+  &:hover { background: #eef0f7; transform: translateY(-1px); }
+  &:active { transform: scale(0.97); }
+}
 
-  &:last-child {
-    border-bottom: none;
-  }
+.quick-icon {
+  font-size: 20px;
+  margin-bottom: 4px;
+}
+
+.quick-label {
+  font-size: 11px;
+  color: #555;
+  font-weight: 500;
+  text-align: center;
 }
 
-.todo-info {
+// ============ AI Section ============
+.ai-banner {
   display: flex;
   align-items: center;
+  gap: 10px;
+  margin-bottom: 10px;
 }
 
-.todo-icon {
+.ai-logo {
   width: 36px;
   height: 36px;
-  border-radius: 8px;
-  background-color: #f0f7f4;
+  border-radius: 10px;
+  background: linear-gradient(135deg, #1a237e, #3949ab);
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-right: 12px;
-  color: var(--primary-color);
+  color: #fff;
+  font-size: 16px;
+  flex-shrink: 0;
 }
 
-.todo-title {
-  font-weight: 600;
-  margin-bottom: 2px;
+.ai-info {
+  h3 {
+    font-size: 14px;
+    font-weight: 700;
+    color: #1a1a2e;
+    margin: 0;
+  }
+  span {
+    font-size: 10px;
+    color: #999;
+  }
 }
 
-.todo-desc {
-  font-size: 12px;
-  color: var(--light-text);
+.ai-chips {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
 }
 
-.todo-count {
-  background-color: var(--warning-color);
-  color: white;
-  border-radius: 10px;
-  padding: 2px 8px;
+.ai-chip {
   font-size: 12px;
+  color: #3949ab;
+  background: #eef0f7;
+  padding: 6px 10px;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: background 0.2s;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  &:hover { background: #dee1f0; }
+
+  i { font-size: 10px; color: #ff9800; margin-right: 4px; }
 }
 
-/* AI助手卡片 */
-.ai-assistant {
-  background: linear-gradient(135deg, #2c3e50, #34495e);
-  color: white;
-  border-radius: var(--border-radius);
-  padding: 20px;
-  margin-bottom: 16px;
-  cursor: pointer;
-  transition: transform 0.3s ease;
+// ============ Todo Section ============
+.todo-section {
+  margin-bottom: 14px;
+}
 
-  &:hover {
-    transform: translateY(-2px);
-  }
+.todo-list {
+  display: flex;
+  flex-direction: column;
 }
 
-.ai-header {
+.todo-row {
   display: flex;
   align-items: center;
-  margin-bottom: 16px;
+  padding: 10px 0;
+  border-bottom: 1px solid #f3f3f6;
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:last-child { border-bottom: none; }
+  &:hover { background: #fafbfd; border-radius: 8px; margin: 0 -8px; padding: 10px 8px; }
 }
 
-.ai-icon {
+.todo-icon-box {
   width: 40px;
   height: 40px;
-  background-color: rgba(255, 255, 255, 0.2);
-  border-radius: 50%;
+  border-radius: 10px;
+  background: #f0f2f8;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-right: 12px;
-  font-size: 20px;
+  font-size: 18px;
+  flex-shrink: 0;
 }
 
-.ai-title {
-  font-size: 18px;
+.todo-body {
+  flex: 1;
+  margin-left: 12px;
+  min-width: 0;
+}
+
+.todo-title {
+  font-size: 14px;
   font-weight: 600;
+  color: #1a1a2e;
 }
 
-.ai-suggestions {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 10px;
+.todo-desc {
+  font-size: 11px;
+  color: #999;
+  margin-top: 2px;
 }
 
-.ai-suggestion {
-  background-color: rgba(255, 255, 255, 0.1);
-  border-radius: 8px;
-  padding: 10px;
-  font-size: 14px;
+.todo-badge {
+  min-width: 24px;
+  height: 24px;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #f44336, #e57373);
+  color: #fff;
+  font-size: 11px;
+  font-weight: 700;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  padding: 0 8px;
+}
+
+// ============ Bottom Nav ============
+.b-bottom-nav {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  display: flex;
+  padding: 8px 0 20px;
+  box-shadow: 0 -1px 8px rgba(0,0,0,0.06);
+  z-index: 100;
+}
+
+.b-nav-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
   cursor: pointer;
-  transition: background-color 0.3s ease;
+  transition: all 0.2s ease;
+  color: #999;
 
-  &:hover {
-    background-color: rgba(255, 255, 255, 0.2);
+  &.active {
+    color: #3949ab;
+    .b-nav-icon { transform: scale(1.1); }
   }
+
+  &:hover:not(.active) { color: #666; }
 }
 
-/* 响应式调整 */
-@media (min-width: 768px) {
-  .container {
-    max-width: 750px;
-  }
-  
-  .data-dashboard {
-    grid-template-columns: repeat(4, 1fr);
-  }
+.b-nav-icon {
+  font-size: 20px;
+  margin-bottom: 3px;
+  transition: transform 0.2s;
+}
 
-  .quick-actions {
-    grid-template-columns: repeat(4, 1fr);
-  }
+.b-nav-label {
+  font-size: 10px;
+  font-weight: 500;
+}
 
-  .ai-suggestions {
-    grid-template-columns: repeat(2, 1fr);
-  }
+// ============ Animations ============
+@keyframes bdPulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.4; }
 }
 
-@media (max-width: 480px) {
-  .data-dashboard {
-    grid-template-columns: 1fr;
-  }
+// ============ Responsive ============
+@media (max-width: 380px) {
+  .dual-row { flex-direction: column; }
+  .stats-grid { grid-template-columns: 1fr; }
+  .tile-value { font-size: 18px; }
+  .quick-grid { grid-template-columns: 1fr 1fr; }
+}
 
-  .quick-actions {
-    grid-template-columns: repeat(2, 1fr);
-  }
+@media (min-width: 481px) and (max-width: 768px) {
+  .b-content { padding: 16px 24px 90px; }
+}
 
-  .ai-suggestions {
-    grid-template-columns: 1fr;
-  }
-}
+@media (min-width: 769px) {
+  :host { max-width: 540px; margin: 0 auto; background: #e8eaed; }
+  .stats-grid { grid-template-columns: repeat(4, 1fr); }
+}
+
+// Scrollbar
+::-webkit-scrollbar { width: 0; height: 0; }

+ 3 - 1
src/app/business/dashboard/dashboard.ts

@@ -1,6 +1,8 @@
 import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { RouterModule, Router } from '@angular/router';
+import { SkeletonComponent } from '../../shared/components/skeleton/skeleton.component';
+import { EmptyStateComponent } from '../../shared/components/empty-state/empty-state.component';
 import { BusinessApiService } from '../../core/services/business-api.service';
 import { AuthService } from '../../auth/services/auth.service';
 
@@ -40,7 +42,7 @@ interface AISuggestion {
 @Component({
   selector: 'app-dashboard',
   standalone: true,
-  imports: [CommonModule, RouterModule],
+  imports: [CommonModule, RouterModule, SkeletonComponent, EmptyStateComponent],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 })

+ 12 - 40
src/app/consumer/booking-recycle/booking-recycle.scss

@@ -3,20 +3,15 @@
 // 所有图标采用独立容器包裹,统一渐变背景
 // ====================================
 
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-}
-
-body {
-  background-color: #f8f9fa;
-  color: #333;
-  padding-bottom: 200px;
+:host {
+  display: block;
+  width: 100%;
   min-height: 100vh;
-  overflow-x: hidden;
-  overflow-y: auto;
+  background: #f4f6f8;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
+  color: #1a1a1a;
+  -webkit-font-smoothing: antialiased;
+  padding-bottom: 100px;
 }
 
 // ====================================
@@ -128,40 +123,17 @@ body {
 // 顶部导航栏 - 精美设计
 // ====================================
 .header {
-  background: linear-gradient(135deg, #00c851, #007e33, #00a085);
-  background-size: 200% 200%;
-  animation: gradientShift 8s ease infinite;
+  background: linear-gradient(145deg, #1b5e20 0%, #2e7d32 40%, #43a047 100%);
   color: white;
-  padding: 20px 25px;
+  padding: 14px 16px;
   display: flex;
   align-items: center;
   justify-content: space-between;
   position: sticky;
   top: 0;
   z-index: 100;
-  box-shadow: 0 8px 32px rgba(0, 200, 81, 0.3), 0 2px 16px rgba(0, 0, 0, 0.1);
-  backdrop-filter: blur(10px);
-  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-  
-  &::before {
-    content: '';
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent);
-    pointer-events: none;
-  }
-}
-
-@keyframes gradientShift {
-  0%, 100% {
-    background-position: 0% 50%;
-  }
-  50% {
-    background-position: 100% 50%;
-  }
+  box-shadow: 0 2px 12px rgba(27, 94, 32, 0.3);
+  min-height: 56px;
 }
 
 .header-title {

+ 12 - 11
src/app/consumer/earnings/earnings.scss

@@ -1,8 +1,11 @@
-// 全局样式重置
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
+:host {
+  display: block;
+  width: 100%;
+  min-height: 100vh;
+  background: #f4f6f8;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
+  color: #1a1a1a;
+  -webkit-font-smoothing: antialiased;
 }
 
 // 动画定义
@@ -64,16 +67,14 @@
   top: 0;
   left: 0;
   right: 0;
-  height: 60px;
-  background: linear-gradient(135deg, #2e7d32, #4caf50, #66bb6a);
-  background-size: 200% 200%;
-  animation: gradientShift 8s ease infinite;
+  height: 56px;
+  background: linear-gradient(145deg, #1b5e20 0%, #2e7d32 40%, #43a047 100%);
   color: white;
   display: flex;
   align-items: center;
-  padding: 0 20px;
+  padding: 0 16px;
   z-index: 1000;
-  box-shadow: 0 4px 20px rgba(76, 175, 80, 0.3);
+  box-shadow: 0 2px 12px rgba(27, 94, 32, 0.3);
 
   .back-btn {
     width: 40px;

+ 154 - 71
src/app/consumer/home/home.html

@@ -1,93 +1,176 @@
-<!-- 顶部状态栏 -->
-<div class="status-bar">
-  <div class="user-level">
-    <div class="level-badge">环保达人 LV.{{ userLevel }}</div>
-    <div class="level-progress">
-      <div class="progress-bar" [style.width.%]="levelProgress"></div>
+<!-- ====== 顶部状态栏 ====== -->
+<header class="home-header">
+  <div class="header-row">
+    <div class="user-greeting">
+      <div class="greeting-text">再生视界</div>
+      <div class="greeting-sub">智慧回收 · 绿色生活</div>
+    </div>
+    <div class="header-actions">
+      <button class="icon-btn" (click)="openNotifications()" aria-label="通知">
+        <i class="fas fa-bell"></i>
+        <span class="notification-dot" *ngIf="unreadCount > 0"></span>
+      </button>
     </div>
   </div>
-  <div class="points-cash">
-    <div>积分: {{ userPoints | number }}</div>
-    <div>现金: ¥{{ userCash | number:'1.2-2' }}</div>
-  </div>
-  <div class="notification-icon" (click)="openNotifications()">
-    <i class="far fa-bell"></i>
-  </div>
-</div>
 
-<!-- 核心功能入口区 -->
-<div class="core-functions">
-  <div class="function-title">核心功能</div>
-  <div class="quick-booking" (click)="quickBooking()">
-    <i class="fas fa-recycle"></i> 一键预约回收
+  <!-- 用户资产卡片 -->
+  <div class="asset-card">
+    <app-skeleton *ngIf="loading.user" type="card"></app-skeleton>
+    <ng-container *ngIf="!loading.user">
+      <div class="asset-row">
+        <div class="asset-item">
+          <div class="asset-value">{{ userPoints | number }}</div>
+          <div class="asset-label">环保积分</div>
+        </div>
+        <div class="asset-divider"></div>
+        <div class="asset-item">
+          <div class="asset-value">¥{{ userCash | number:'1.2-2' }}</div>
+          <div class="asset-label">可提现金额</div>
+        </div>
+        <div class="asset-divider"></div>
+        <div class="asset-item">
+          <div class="asset-value">LV.{{ userLevel }}</div>
+          <div class="asset-label">环保等级</div>
+        </div>
+      </div>
+      <div class="level-bar">
+        <div class="level-bar-track">
+          <div class="level-bar-fill" [style.width.%]="levelProgress"></div>
+        </div>
+        <span class="level-bar-text">距下一级 {{ 100 - levelProgress }}%</span>
+      </div>
+    </ng-container>
   </div>
-  <div class="function-buttons">
-    <div class="func-btn" (click)="openARRecognition()">
-      <div class="func-icon"><i class="fas fa-camera"></i></div>
-      <div>AR识废品</div>
+</header>
+
+<!-- ====== 核心功能入口 ====== -->
+<section class="core-actions">
+  <button class="main-cta" (click)="quickBooking()">
+    <div class="cta-icon"><i class="fas fa-recycle"></i></div>
+    <div class="cta-text">
+      <span class="cta-title">一键预约回收</span>
+      <span class="cta-sub">上门取件 · 即时报价 · 安全便捷</span>
+    </div>
+    <i class="fas fa-chevron-right cta-arrow"></i>
+  </button>
+
+  <div class="action-grid">
+    <div class="action-item" (click)="openARRecognition()">
+      <div class="action-icon green"><i class="fas fa-camera"></i></div>
+      <span class="action-text">AR识废品</span>
+      <span class="action-desc">拍照识别</span>
+    </div>
+    <div class="action-item" (click)="findDropPoints()">
+      <div class="action-icon blue"><i class="fas fa-map-marker-alt"></i></div>
+      <span class="action-text">投递点</span>
+      <span class="action-desc">附近站点</span>
     </div>
-    <div class="func-btn" (click)="findDropPoints()">
-      <div class="func-icon"><i class="fas fa-map-marker-alt"></i></div>
-      <div>自助投递点</div>
+    <div class="action-item" (click)="viewMoreCollectors()">
+      <div class="action-icon orange"><i class="fas fa-truck"></i></div>
+      <span class="action-text">回收员</span>
+      <span class="action-desc">在线预约</span>
+    </div>
+    <div class="action-item" (click)="openAIAssistant()">
+      <div class="action-icon purple"><i class="fas fa-robot"></i></div>
+      <span class="action-text">AI助手</span>
+      <span class="action-desc">智能问答</span>
     </div>
   </div>
-</div>
+</section>
 
-<!-- 动态信息区 -->
-<div class="dynamic-info">
-  <!-- 附近回收员动态 -->
-  <div class="info-section">
-    <div class="section-title">
-      <h3>附近回收员</h3>
-      <a (click)="viewMoreCollectors()" class="more-link">查看更多</a>
-    </div>
-    <div class="collector-list">
-      <div class="collector-card" *ngFor="let collector of nearbyCollectors">
-        <div class="collector-avatar"><i class="fas fa-user"></i></div>
-        <div class="collector-name">{{ collector.name }}</div>
-        <div class="collector-status">{{ collector.distance }} · {{ collector.status }}</div>
+<!-- ====== 附近回收员 ====== -->
+<section class="section-card">
+  <div class="section-header">
+    <h3><i class="fas fa-users"></i> 附近回收员</h3>
+    <a class="see-all" (click)="viewMoreCollectors()">全部 <i class="fas fa-angle-right"></i></a>
+  </div>
+  <app-skeleton *ngIf="loading.collectors" type="list" [count]="3"></app-skeleton>
+  <app-empty-state *ngIf="!loading.collectors && nearbyCollectors.length === 0"
+    icon="no-data" title="附近暂无在线回收员" description="尝试扩大搜索范围或稍后再试" size="small">
+  </app-empty-state>
+  <div class="collector-list" *ngIf="!loading.collectors && nearbyCollectors.length > 0">
+    <div class="collector-row" *ngFor="let c of nearbyCollectors; let i = index" [style.animation-delay.ms]="i * 80">
+      <div class="collector-avatar" [class.online]="c.status === '在线'">
+        <i class="fas fa-user-tie"></i>
+        <span class="online-dot" *ngIf="c.status === '在线'"></span>
+      </div>
+      <div class="collector-info">
+        <div class="collector-name">{{ c.name }}</div>
+        <div class="collector-meta">
+          <app-status-badge [text]="c.status" [status]="c.status === '在线' ? 'success' : 'default'" size="small" [pulse]="c.status === '在线'"></app-status-badge>
+          <span class="distance-tag"><i class="fas fa-location-arrow"></i> {{ c.distance }}</span>
+        </div>
       </div>
+      <button class="call-btn" title="联系回收员"><i class="fas fa-phone-alt"></i></button>
     </div>
   </div>
-  
-  <!-- 自助投递点状态 -->
-  <div class="info-section">
-    <div class="section-title">
-      <h3>自助投递点</h3>
-      <a (click)="viewMoreDropPoints()" class="more-link">查看更多</a>
-    </div>
-    <div class="drop-points">
-      <div class="point-card" *ngFor="let point of dropPoints">
-        <div class="point-name">{{ point.name }}</div>
-        <div class="point-distance">{{ point.distance }}</div>
-        <div class="capacity-bar">
-          <div class="capacity-fill" [style.width.%]="point.capacity"></div>
+</section>
+
+<!-- ====== 自助投递点状态 ====== -->
+<section class="section-card">
+  <div class="section-header">
+    <h3><i class="fas fa-recycle"></i> 自助投递点</h3>
+    <a class="see-all" (click)="viewMoreDropPoints()">全部 <i class="fas fa-angle-right"></i></a>
+  </div>
+  <app-skeleton *ngIf="loading.dropPoints" type="list" [count]="3"></app-skeleton>
+  <app-empty-state *ngIf="!loading.dropPoints && dropPoints.length === 0"
+    icon="no-device" title="附近暂无投递点" size="small">
+  </app-empty-state>
+  <div class="point-list" *ngIf="!loading.dropPoints && dropPoints.length > 0">
+    <div class="point-row" *ngFor="let p of dropPoints; let i = index" [style.animation-delay.ms]="i * 80">
+      <div class="point-icon-box" [ngClass]="getCapacityLevel(p.capacity)">
+        <i class="fas fa-trash-alt"></i>
+      </div>
+      <div class="point-info">
+        <div class="point-name">{{ p.name }}</div>
+        <div class="point-meta">
+          <span class="distance-tag"><i class="fas fa-walking"></i> {{ p.distance }}</span>
         </div>
-        <div class="capacity-text">{{ point.capacity }}% 满箱</div>
+      </div>
+      <div class="capacity-indicator">
+        <svg viewBox="0 0 36 36" class="capacity-ring">
+          <path class="ring-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"/>
+          <path class="ring-fill" [ngClass]="getCapacityLevel(p.capacity)"
+            [attr.stroke-dasharray]="p.capacity + ', 100'"
+            d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"/>
+        </svg>
+        <span class="capacity-num">{{ p.capacity }}%</span>
       </div>
     </div>
   </div>
-  
-  <!-- 环保活动/政策公告轮播 -->
-  <div class="info-section">
-    <div class="section-title">
-      <h3>环保活动</h3>
-      <a (click)="viewMoreActivities()" class="more-link">查看更多</a>
-    </div>
-    <div class="activity-carousel">
-      <div class="activity-card" *ngFor="let activity of activities" (click)="openActivity(activity)">
-        <div class="activity-tag">{{ activity.tag }}</div>
-        <div class="activity-title">{{ activity.title }}</div>
-        <div class="activity-desc">{{ activity.description }}</div>
+</section>
+
+<!-- ====== 环保活动 ====== -->
+<section class="section-card section-last">
+  <div class="section-header">
+    <h3><i class="fas fa-leaf"></i> 环保活动</h3>
+    <a class="see-all" (click)="viewMoreActivities()">全部 <i class="fas fa-angle-right"></i></a>
+  </div>
+  <app-skeleton *ngIf="loading.activities" type="card"></app-skeleton>
+  <app-empty-state *ngIf="!loading.activities && activities.length === 0"
+    icon="no-data" title="暂无进行中的活动" size="small">
+  </app-empty-state>
+  <div class="activity-list" *ngIf="!loading.activities && activities.length > 0">
+    <div class="activity-card" *ngFor="let a of activities; let i = index" (click)="openActivity(a)" [style.animation-delay.ms]="i * 100">
+      <div class="activity-header">
+        <span class="activity-tag">{{ a.tag }}</span>
+      </div>
+      <div class="activity-title">{{ a.title }}</div>
+      <div class="activity-desc">{{ a.description }}</div>
+      <div class="activity-footer">
+        <span class="join-hint"><i class="fas fa-hand-point-right"></i> 点击参与</span>
       </div>
     </div>
   </div>
-</div>
+</section>
 
-<!-- AI助手入口 -->
-<div class="ai-assistant" (click)="openAIAssistant()">
-  <i class="fas fa-robot"></i>
+<!-- ====== 悬浮AI助手 ====== -->
+<div class="fab-ai" (click)="openAIAssistant()">
+  <div class="fab-ai-inner">
+    <i class="fas fa-robot"></i>
+  </div>
+  <div class="fab-ai-pulse"></div>
 </div>
 
-<!-- 底部导航栏 -->
+<!-- ====== 底部导航 ====== -->
 <app-bottom-nav [activeTab]="'home'"></app-bottom-nav>

+ 593 - 669
src/app/consumer/home/home.scss

@@ -1,730 +1,654 @@
-// 全局重置和基础样式
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
+// ============================================================
+// C端首页 — 工业级精细化 UI
+// ============================================================
 
 :host {
   display: block;
   width: 100%;
   min-height: 100vh;
-  background: linear-gradient(135deg, #e8f5e8 0%, #f0f8f0 100%);
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-  color: #333;
+  background: #f4f6f8;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', sans-serif;
+  color: #1a1a1a;
   overflow-x: hidden;
+  -webkit-font-smoothing: antialiased;
 }
 
-// 顶部状态栏
-.status-bar {
+// ============ 顶部 Header ============
+.home-header {
+  background: linear-gradient(145deg, #1b5e20 0%, #2e7d32 40%, #43a047 100%);
+  padding: 0 16px 20px;
+  position: relative;
+
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: -20px;
+    left: 0;
+    right: 0;
+    height: 40px;
+    background: #f4f6f8;
+    border-radius: 20px 20px 0 0;
+  }
+}
+
+.header-row {
   display: flex;
-  justify-content: space-between;
   align-items: center;
-  padding: 15px 20px;
-  background: linear-gradient(135deg, #2e7d32 0%, #4caf50 100%);
-  color: white;
-  box-shadow: 0 2px 10px rgba(46, 125, 50, 0.3);
-  
-  .user-level {
-    flex: 1;
-    
-    .level-badge {
-      font-size: 14px;
-      font-weight: 600;
-      margin-bottom: 5px;
-    }
-    
-    .level-progress {
-      width: 120px;
-      height: 6px;
-      background: rgba(255, 255, 255, 0.3);
-      border-radius: 3px;
-      overflow: hidden;
-      
-      .progress-bar {
-        height: 100%;
-        background: linear-gradient(90deg, #81c784, #a5d6a7);
-        border-radius: 3px;
-        transition: width 0.3s ease;
-      }
-    }
+  justify-content: space-between;
+  padding: 14px 0 16px;
+}
+
+.user-greeting {
+  .greeting-text {
+    font-size: 20px;
+    font-weight: 700;
+    color: #fff;
+    letter-spacing: 1px;
   }
-  
-  .points-cash {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
+  .greeting-sub {
     font-size: 12px;
-    gap: 2px;
-    
-    div {
-      background: rgba(255, 255, 255, 0.2);
-      padding: 2px 8px;
-      border-radius: 10px;
-      font-weight: 500;
-    }
-  }
-  
-  .notification-icon {
-    width: 40px;
-    height: 40px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    background: rgba(255, 255, 255, 0.2);
-    border-radius: 50%;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    
-    &:hover {
-      background: rgba(255, 255, 255, 0.3);
-      transform: scale(1.1);
-    }
-    
-    i {
-      font-size: 18px;
-    }
+    color: rgba(255,255,255,0.7);
+    margin-top: 2px;
+    letter-spacing: 0.5px;
   }
 }
 
-// 核心功能入口区
-.core-functions {
-  padding: 20px;
-  background: white;
-  margin: 15px 20px;
-  border-radius: 15px;
-  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
-  
-  .function-title {
-    font-size: 18px;
-    font-weight: 600;
-    color: #2e7d32;
-    margin-bottom: 15px;
-    text-align: center;
-  }
-  
-  .quick-booking {
-    background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
-    color: white;
-    padding: 15px 20px;
-    border-radius: 12px;
-    text-align: center;
-    font-size: 16px;
-    font-weight: 600;
-    cursor: pointer;
-    margin-bottom: 15px;
-    box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
-    transition: all 0.3s ease;
-    
-    &:hover {
-      transform: translateY(-2px);
-      box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
-    }
-    
-    i {
-      margin-right: 8px;
-      font-size: 18px;
-    }
-  }
-  
-  .function-buttons {
-    display: flex;
-    gap: 15px;
-    
-    .func-btn {
-      flex: 1;
-      background: #f8f9fa;
-      border: 2px solid #e9ecef;
-      border-radius: 12px;
-      padding: 15px 10px;
-      text-align: center;
-      cursor: pointer;
-      transition: all 0.3s ease;
-      
-      &:hover {
-        border-color: #4caf50;
-        background: #f1f8e9;
-        transform: translateY(-2px);
-      }
-      
-      .func-icon {
-        width: 40px;
-        height: 40px;
-        background: linear-gradient(135deg, #4caf50, #66bb6a);
-        border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin: 0 auto 8px;
-        color: white;
-        
-        i {
-          font-size: 18px;
-        }
-      }
-      
-      div:last-child {
-        font-size: 12px;
-        color: #666;
-        font-weight: 500;
-      }
-    }
-  }
+.header-actions {
+  display: flex;
+  gap: 8px;
 }
 
-// 动态信息区
-.dynamic-info {
-  padding: 0 20px 100px;
-  
-  .info-section {
-    background: white;
-    border-radius: 15px;
-    padding: 20px;
-    margin-bottom: 15px;
-    box-shadow: 0 2px 15px rgba(0, 0, 0, 0.08);
-    
-    .section-title {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 15px;
-      
-      h3 {
-        font-size: 16px;
-        font-weight: 600;
-        color: #2e7d32;
-      }
-      
-      .more-link {
-        font-size: 12px;
-        color: #4caf50;
-        cursor: pointer;
-        text-decoration: none;
-        
-        &:hover {
-          text-decoration: underline;
-        }
-      }
-    }
-    
-    // 附近回收员样式
-    .collector-list {
-      display: flex;
-      gap: 10px;
-      overflow-x: auto;
-      padding-bottom: 5px;
-      
-      .collector-card {
-        min-width: 80px;
-        text-align: center;
-        
-        .collector-avatar {
-          width: 50px;
-          height: 50px;
-          background: linear-gradient(135deg, #4caf50, #66bb6a);
-          border-radius: 50%;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          margin: 0 auto 8px;
-          color: white;
-          
-          i {
-            font-size: 20px;
-          }
-        }
-        
-        .collector-name {
-          font-size: 12px;
-          font-weight: 500;
-          margin-bottom: 4px;
-        }
-        
-        .collector-status {
-          font-size: 10px;
-          color: #666;
-        }
-      }
-    }
-    
-    // 自助投递点样式
-    .drop-points {
-      .point-card {
-        display: flex;
-        align-items: center;
-        padding: 10px 0;
-        border-bottom: 1px solid #f0f0f0;
-        
-        &:last-child {
-          border-bottom: none;
-        }
-        
-        .point-name {
-          flex: 1;
-          font-size: 14px;
-          font-weight: 500;
-        }
-        
-        .point-distance {
-          font-size: 12px;
-          color: #666;
-          margin-right: 15px;
-        }
-        
-        .capacity-bar {
-          width: 60px;
-          height: 6px;
-          background: #f0f0f0;
-          border-radius: 3px;
-          overflow: hidden;
-          margin-right: 8px;
-          
-          .capacity-fill {
-            height: 100%;
-            background: linear-gradient(90deg, #4caf50, #66bb6a);
-            border-radius: 3px;
-            transition: width 0.3s ease;
-          }
-        }
-        
-        .capacity-text {
-          font-size: 10px;
-          color: #666;
-          min-width: 40px;
-        }
-      }
-    }
-    
-    // 环保活动样式
-    .activity-carousel {
-      .activity-card {
-        border: 1px solid #e9ecef;
-        border-radius: 10px;
-        padding: 15px;
-        margin-bottom: 10px;
-        
-        &:last-child {
-          margin-bottom: 0;
-        }
-        
-        .activity-tag {
-          display: inline-block;
-          background: linear-gradient(135deg, #4caf50, #66bb6a);
-          color: white;
-          padding: 2px 8px;
-          border-radius: 10px;
-          font-size: 10px;
-          margin-bottom: 8px;
-        }
-        
-        .activity-title {
-          font-size: 14px;
-          font-weight: 600;
-          margin-bottom: 5px;
-          color: #333;
-        }
-        
-        .activity-desc {
-          font-size: 12px;
-          color: #666;
-          line-height: 1.4;
-        }
-      }
-    }
-  }
+.icon-btn {
+  position: relative;
+  width: 38px;
+  height: 38px;
+  border: none;
+  border-radius: 12px;
+  background: rgba(255,255,255,0.15);
+  color: #fff;
+  font-size: 16px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: background 0.2s;
+  backdrop-filter: blur(4px);
+
+  &:active { transform: scale(0.93); }
 }
 
-// AI助手入口
-.ai-assistant {
-  position: fixed;
-  right: 20px;
-  bottom: 100px;
-  width: 60px;
-  height: 60px;
-  background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
+.notification-dot {
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  width: 8px;
+  height: 8px;
+  background: #ff5252;
   border-radius: 50%;
+  border: 2px solid #2e7d32;
+  animation: dotPulse 2s infinite;
+}
+
+// ============ 资产卡片 ============
+.asset-card {
+  position: relative;
+  z-index: 1;
+  background: rgba(255,255,255,0.12);
+  backdrop-filter: blur(16px);
+  border: 1px solid rgba(255,255,255,0.2);
+  border-radius: 16px;
+  padding: 16px;
+}
+
+.asset-row {
+  display: flex;
+  align-items: center;
+}
+
+.asset-item {
+  flex: 1;
+  text-align: center;
+}
+
+.asset-value {
+  font-size: 20px;
+  font-weight: 700;
+  color: #fff;
+  line-height: 1.3;
+}
+
+.asset-label {
+  font-size: 11px;
+  color: rgba(255,255,255,0.7);
+  margin-top: 2px;
+}
+
+.asset-divider {
+  width: 1px;
+  height: 28px;
+  background: rgba(255,255,255,0.2);
+}
+
+.level-bar {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 12px;
+}
+
+.level-bar-track {
+  flex: 1;
+  height: 6px;
+  background: rgba(255,255,255,0.15);
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.level-bar-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #a5d6a7, #e8f5e9);
+  border-radius: 3px;
+  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.level-bar-text {
+  font-size: 10px;
+  color: rgba(255,255,255,0.6);
+  white-space: nowrap;
+}
+
+// ============ 核心功能入口 ============
+.core-actions {
+  padding: 0 16px;
+  margin-top: -4px;
+  position: relative;
+  z-index: 2;
+}
+
+.main-cta {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 14px 16px;
+  background: linear-gradient(135deg, #43a047, #66bb6a);
+  border: none;
+  border-radius: 14px;
+  cursor: pointer;
+  color: #fff;
+  box-shadow: 0 6px 20px rgba(67, 160, 71, 0.35);
+  transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
+
+  &:hover { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(67,160,71,0.4); }
+  &:active { transform: translateY(0); }
+}
+
+.cta-icon {
+  width: 44px;
+  height: 44px;
+  background: rgba(255,255,255,0.2);
+  border-radius: 12px;
   display: flex;
   align-items: center;
   justify-content: center;
-  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+.cta-text {
+  flex: 1;
+  text-align: left;
+  margin-left: 12px;
+}
+
+.cta-title {
+  display: block;
+  font-size: 16px;
+  font-weight: 700;
+}
+
+.cta-sub {
+  display: block;
+  font-size: 11px;
+  opacity: 0.8;
+  margin-top: 2px;
+}
+
+.cta-arrow {
+  font-size: 14px;
+  opacity: 0.6;
+}
+
+// 功能网格
+.action-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 10px;
+  margin-top: 14px;
+}
+
+.action-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: #fff;
+  border-radius: 14px;
+  padding: 14px 6px 12px;
   cursor: pointer;
-  box-shadow: 0 4px 20px rgba(76, 175, 80, 0.4);
-  transition: all 0.3s ease;
-  z-index: 1000;
-  
-  &:hover {
-    transform: scale(1.1);
-    box-shadow: 0 6px 25px rgba(76, 175, 80, 0.5);
-  }
-  
-  i {
-    font-size: 24px;
+  transition: all 0.25s ease;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
+
+  &:hover { transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
+  &:active { transform: scale(0.97); }
+}
+
+.action-icon {
+  width: 40px;
+  height: 40px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 18px;
+  margin-bottom: 8px;
+
+  &.green { background: rgba(76,175,80,0.12); color: #2e7d32; }
+  &.blue { background: rgba(33,150,243,0.12); color: #1565c0; }
+  &.orange { background: rgba(255,152,0,0.12); color: #e65100; }
+  &.purple { background: rgba(156,39,176,0.12); color: #7b1fa2; }
+}
+
+.action-text {
+  font-size: 13px;
+  font-weight: 600;
+  color: #333;
+}
+
+.action-desc {
+  font-size: 10px;
+  color: #aaa;
+  margin-top: 2px;
+}
+
+// ============ 通用 Section 卡片 ============
+.section-card {
+  background: #fff;
+  margin: 14px 16px 0;
+  border-radius: 16px;
+  padding: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
+
+  &.section-last {
+    margin-bottom: 90px;
   }
 }
 
-// 底部导航栏
-.bottom-nav {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  background: white;
+.section-header {
   display: flex;
-  padding: 10px 0 20px;
-  box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.1);
-  z-index: 999;
-  
-  .nav-item {
-    flex: 1;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 14px;
+
+  h3 {
+    font-size: 15px;
+    font-weight: 700;
+    color: #1a1a1a;
     display: flex;
-    flex-direction: column;
     align-items: center;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    
-    &.active {
-      color: #4caf50;
-      
-      .nav-icon {
-        background: linear-gradient(135deg, #4caf50, #66bb6a);
-        color: white;
-        transform: scale(1.1);
-      }
-    }
-    
-    .nav-icon {
-      width: 35px;
-      height: 35px;
-      background: #f8f9fa;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      margin-bottom: 5px;
-      transition: all 0.3s ease;
-      
-      i {
-        font-size: 16px;
-      }
-    }
-    
-    div:last-child {
-      font-size: 10px;
-      font-weight: 500;
-    }
-    
-    &:hover:not(.active) {
+    gap: 6px;
+
+    i {
+      font-size: 14px;
       color: #4caf50;
-      
-      .nav-icon {
-        background: #f1f8e9;
-        transform: scale(1.05);
-      }
     }
   }
 }
 
-// 移动端响应式设计
-@media (max-width: 480px) {
-  .header {
-    padding: 10px 15px;
-    
-    .user-info {
-      .user-level {
-        font-size: 12px;
-      }
-      
-      .level-progress {
-        height: 3px;
-      }
-    }
-    
-    .points-cash {
-      gap: 15px;
-      
-      .points, .cash {
-        font-size: 12px;
-        
-        .value {
-          font-size: 16px;
-        }
-      }
-    }
-  }
-  
-  .core-functions {
-    padding: 15px;
-    
-    .quick-booking {
-      padding: 15px;
-      
-      .booking-title {
-        font-size: 16px;
-      }
-      
-      .booking-subtitle {
-        font-size: 12px;
-      }
-    }
-    
-    .function-grid {
-      gap: 10px;
-      
-      .function-item {
-        padding: 12px 8px;
-        
-        .function-icon {
-          width: 35px;
-          height: 35px;
-          
-          i {
-            font-size: 16px;
-          }
-        }
-        
-        .function-text {
-          font-size: 11px;
-        }
-      }
-    }
-  }
-  
-  .dynamic-info {
-    padding: 0 15px 100px;
-    
-    .info-section {
-      padding: 15px;
-      
-      .section-header {
-        .section-title {
-          font-size: 16px;
-        }
-        
-        .view-more {
-          font-size: 12px;
-        }
-      }
-      
-      .collector-list {
-        gap: 8px;
-        
-        .collector-card {
-          min-width: 70px;
-          
-          .collector-avatar {
-            width: 45px;
-            height: 45px;
-            
-            i {
-              font-size: 18px;
-            }
-          }
-          
-          .collector-name {
-            font-size: 11px;
-          }
-          
-          .collector-status {
-            font-size: 9px;
-          }
-        }
-      }
-      
-      .drop-points {
-        .point-card {
-          padding: 8px 0;
-          
-          .point-name {
-            font-size: 13px;
-          }
-          
-          .point-distance {
-            font-size: 11px;
-          }
-          
-          .capacity-bar {
-            width: 50px;
-            height: 5px;
-          }
-          
-          .capacity-text {
-            font-size: 9px;
-            min-width: 35px;
-          }
-        }
-      }
-      
-      .activity-carousel {
-        .activity-card {
-          padding: 12px;
-          
-          .activity-title {
-            font-size: 13px;
-          }
-          
-          .activity-subtitle {
-            font-size: 11px;
-          }
-          
-          .activity-meta {
-            font-size: 10px;
-          }
-        }
-      }
-    }
-  }
-  
-  .ai-assistant {
-    width: 45px;
-    height: 45px;
-    right: 15px;
-    bottom: 85px;
-    
-    i {
-      font-size: 18px;
-    }
-  }
+.see-all {
+  font-size: 12px;
+  color: #888;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 3px;
+  transition: color 0.2s;
+
+  &:hover { color: #4caf50; }
+  i { font-size: 10px; }
 }
 
-// 平板端响应式设计
-@media (max-width: 768px) and (min-width: 481px) {
-  .header {
-    padding: 12px 20px;
-  }
-  
-  .core-functions {
-    padding: 20px;
-    
-    .function-grid {
-      gap: 12px;
-      
-      .function-item {
-        padding: 15px 10px;
-      }
-    }
-  }
-  
-  .dynamic-info {
-    padding: 0 20px 100px;
-    
-    .info-section {
-      padding: 18px;
-    }
-  }
+// ============ 回收员列表 ============
+.collector-list {
+  display: flex;
+  flex-direction: column;
+  gap: 0;
 }
 
-// 动画效果
-@keyframes fadeInUp {
-  from {
-    opacity: 0;
-    transform: translateY(20px);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
+.collector-row {
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+  border-bottom: 1px solid #f5f5f5;
+  animation: fadeSlideIn 0.4s ease both;
+
+  &:last-child { border-bottom: none; }
 }
 
-.info-section {
-  animation: fadeInUp 0.6s ease-out;
+.collector-avatar {
+  position: relative;
+  width: 44px;
+  height: 44px;
+  background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #2e7d32;
+  font-size: 18px;
+  flex-shrink: 0;
+
+  &.online { background: linear-gradient(135deg, #4caf50, #66bb6a); color: #fff; }
 }
 
-// 滚动条样式
-::-webkit-scrollbar {
-  width: 4px;
-  height: 4px;
+.online-dot {
+  position: absolute;
+  bottom: -1px;
+  right: -1px;
+  width: 10px;
+  height: 10px;
+  background: #4caf50;
+  border: 2px solid #fff;
+  border-radius: 50%;
 }
 
-::-webkit-scrollbar-track {
-  background: #f1f1f1;
-  border-radius: 2px;
+.collector-info {
+  flex: 1;
+  margin-left: 12px;
+  min-width: 0;
 }
 
-::-webkit-scrollbar-thumb {
-  background: #4caf50;
-  border-radius: 2px;
+.collector-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #333;
 }
 
-::-webkit-scrollbar-thumb:hover {
-  background: #2e7d32;
+.collector-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 4px;
 }
 
-// 新增:活动详情模态框样式
-.activity-modal-overlay {
-  position: fixed;
+.distance-tag {
+  font-size: 11px;
+  color: #999;
+  display: flex;
+  align-items: center;
+  gap: 3px;
+
+  i { font-size: 9px; }
+}
+
+.call-btn {
+  width: 36px;
+  height: 36px;
+  border: none;
+  border-radius: 10px;
+  background: rgba(76,175,80,0.1);
+  color: #4caf50;
+  font-size: 14px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  transition: all 0.2s;
+
+  &:hover { background: #4caf50; color: #fff; }
+  &:active { transform: scale(0.9); }
+}
+
+// ============ 投递点列表 ============
+.point-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.point-row {
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+  border-bottom: 1px solid #f5f5f5;
+  animation: fadeSlideIn 0.4s ease both;
+
+  &:last-child { border-bottom: none; }
+}
+
+.point-icon-box {
+  width: 40px;
+  height: 40px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  flex-shrink: 0;
+
+  &.cap-low { background: rgba(76,175,80,0.1); color: #4caf50; }
+  &.cap-normal { background: rgba(33,150,243,0.1); color: #2196f3; }
+  &.cap-warning { background: rgba(255,152,0,0.1); color: #ff9800; }
+  &.cap-critical { background: rgba(244,67,54,0.1); color: #f44336; }
+}
+
+.point-info {
+  flex: 1;
+  margin-left: 12px;
+  min-width: 0;
+}
+
+.point-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #333;
+}
+
+.point-meta {
+  margin-top: 3px;
+}
+
+// 圆环容量指示器
+.capacity-indicator {
+  position: relative;
+  width: 40px;
+  height: 40px;
+  flex-shrink: 0;
+}
+
+.capacity-ring {
+  width: 40px;
+  height: 40px;
+  transform: rotate(-90deg);
+}
+
+.ring-bg {
+  fill: none;
+  stroke: #f0f0f0;
+  stroke-width: 3;
+}
+
+.ring-fill {
+  fill: none;
+  stroke-width: 3;
+  stroke-linecap: round;
+  transition: stroke-dasharray 0.6s ease;
+
+  &.cap-low { stroke: #4caf50; }
+  &.cap-normal { stroke: #2196f3; }
+  &.cap-warning { stroke: #ff9800; }
+  &.cap-critical { stroke: #f44336; }
+}
+
+.capacity-num {
+  position: absolute;
   inset: 0;
-  background: rgba(0,0,0,0.35);
-  z-index: 1000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 9px;
+  font-weight: 700;
+  color: #666;
 }
 
-.activity-modal {
-  position: fixed;
-  left: 50%;
-  bottom: 90px; // 底部导航上方
-  transform: translateX(-50%);
-  width: calc(100% - 40px);
-  max-width: 480px;
-  background: #ffffff;
-  border-radius: 16px;
-  box-shadow: 0 12px 30px rgba(0,0,0,0.2);
-  z-index: 1001;
+// ============ 活动列表 ============
+.activity-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.activity-card {
+  border: 1px solid #f0f0f0;
+  border-radius: 12px;
+  padding: 14px;
+  cursor: pointer;
+  transition: all 0.25s ease;
+  animation: fadeSlideIn 0.5s ease both;
+
+  &:hover {
+    border-color: #c8e6c9;
+    background: #fafff9;
+    transform: translateX(4px);
+  }
+}
+
+.activity-header {
+  margin-bottom: 8px;
+}
+
+.activity-tag {
+  display: inline-block;
+  background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+  color: #2e7d32;
+  padding: 3px 10px;
+  border-radius: 10px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.activity-title {
+  font-size: 15px;
+  font-weight: 700;
+  color: #1a1a1a;
+  margin-bottom: 4px;
+}
+
+.activity-desc {
+  font-size: 12px;
+  color: #888;
+  line-height: 1.5;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  -webkit-box-orient: vertical;
   overflow: hidden;
+}
 
-  .modal-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 12px 16px;
-    background: linear-gradient(135deg, #2e7d32, #66bb6a);
-    color: #fff;
+.activity-footer {
+  margin-top: 8px;
+  display: flex;
+  justify-content: flex-end;
+}
 
-    .title {
-      font-size: 16px;
-      font-weight: 600;
-    }
+.join-hint {
+  font-size: 11px;
+  color: #4caf50;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+  gap: 4px;
 
-    .actions {
-      display: flex;
-      gap: 8px;
-
-      button {
-        border: none;
-        border-radius: 12px;
-        padding: 6px 10px;
-        font-size: 12px;
-        cursor: pointer;
-      }
-
-      .view-more {
-        background: #ffffff;
-        color: #2e7d32;
-      }
-
-      .close {
-        background: rgba(255,255,255,0.25);
-        color: #ffffff;
-      }
-    }
-  }
+  i { font-size: 12px; }
+}
 
-  .modal-body {
-    padding: 14px 16px 16px;
-
-    .tag {
-      display: inline-block;
-      font-size: 12px;
-      color: #2e7d32;
-      background: #e8f5e9;
-      border-radius: 10px;
-      padding: 2px 8px;
-      margin-bottom: 8px;
-      font-weight: 500;
-    }
+// ============ FAB AI 助手 ============
+.fab-ai {
+  position: fixed;
+  right: 18px;
+  bottom: 88px;
+  z-index: 100;
+  cursor: pointer;
+}
 
-    .description {
-      font-size: 14px;
-      color: #333;
-      line-height: 1.6;
-    }
+.fab-ai-inner {
+  width: 52px;
+  height: 52px;
+  background: linear-gradient(145deg, #43a047, #2e7d32);
+  border-radius: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 22px;
+  box-shadow: 0 4px 16px rgba(46,125,50,0.4);
+  transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
+  position: relative;
+  z-index: 1;
+
+  &:hover {
+    transform: scale(1.08);
+    box-shadow: 0 8px 24px rgba(46,125,50,0.5);
   }
-}
+
+  &:active { transform: scale(0.96); }
+}
+
+.fab-ai-pulse {
+  position: absolute;
+  inset: -4px;
+  border-radius: 20px;
+  border: 2px solid rgba(76,175,80,0.3);
+  animation: fabPulse 2.5s infinite;
+}
+
+// ============ 动画 ============
+@keyframes fadeSlideIn {
+  from { opacity: 0; transform: translateY(12px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes dotPulse {
+  0%, 100% { opacity: 1; transform: scale(1); }
+  50% { opacity: 0.5; transform: scale(0.8); }
+}
+
+@keyframes fabPulse {
+  0% { opacity: 0.6; transform: scale(1); }
+  50% { opacity: 0; transform: scale(1.3); }
+  100% { opacity: 0; transform: scale(1.3); }
+}
+
+// ============ 响应式 ============
+@media (max-width: 380px) {
+  .asset-value { font-size: 17px; }
+  .action-grid { gap: 8px; }
+  .action-item { padding: 10px 4px 8px; }
+  .action-icon { width: 34px; height: 34px; font-size: 15px; }
+  .action-text { font-size: 11px; }
+  .action-desc { display: none; }
+  .cta-title { font-size: 14px; }
+  .fab-ai-inner { width: 46px; height: 46px; font-size: 18px; }
+}
+
+@media (min-width: 481px) and (max-width: 768px) {
+  .section-card { margin: 14px 24px 0; }
+  .core-actions { padding: 0 24px; }
+  .home-header { padding: 0 24px 20px; }
+}
+
+@media (min-width: 769px) {
+  :host { max-width: 480px; margin: 0 auto; background: #e8ebed; }
+  .section-card, .core-actions { max-width: 480px; }
+}
+
+// 滚动条
+::-webkit-scrollbar { width: 0; height: 0; }

+ 13 - 1
src/app/consumer/home/home.ts

@@ -2,6 +2,9 @@ import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { CommonModule, DecimalPipe } from '@angular/common';
 import { BottomNavComponent } from '../../shared/bottom-nav/bottom-nav.component';
+import { SkeletonComponent } from '../../shared/components/skeleton/skeleton.component';
+import { EmptyStateComponent } from '../../shared/components/empty-state/empty-state.component';
+import { StatusBadgeComponent } from '../../shared/components/status-badge/status-badge.component';
 import { ConsumerApiService } from '../../core/services/consumer-api.service';
 import { AuthService } from '../../auth/services/auth.service';
 
@@ -29,7 +32,7 @@ interface Activity {
 @Component({
   selector: 'app-home',
   standalone: true,
-  imports: [CommonModule, BottomNavComponent],
+  imports: [CommonModule, BottomNavComponent, SkeletonComponent, EmptyStateComponent, StatusBadgeComponent],
   templateUrl: './home.html',
   styleUrls: ['./home.scss']
 })
@@ -39,6 +42,7 @@ export class HomeComponent implements OnInit {
   levelProgress: number = 0;
   userPoints: number = 0;
   userCash: number = 0;
+  unreadCount: number = 0;
   
   // 当前选中的底部导航标签
   currentTab: string = 'home';
@@ -328,4 +332,12 @@ export class HomeComponent implements OnInit {
     this.userCash += amount;
     console.log(`Cash updated: +${amount}, Total: ${this.userCash}`);
   }
+
+  // 获取容量等级样式类
+  getCapacityLevel(capacity: number): string {
+    if (capacity >= 90) return 'cap-critical';
+    if (capacity >= 70) return 'cap-warning';
+    if (capacity >= 40) return 'cap-normal';
+    return 'cap-low';
+  }
 }

+ 36 - 26
src/app/consumer/points-mall/points-mall.scss

@@ -1,55 +1,65 @@
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-}
-
-body {
-  background-color: #f8f9fa;
-  color: #333;
-  padding-bottom: 20px;
+:host {
+  display: block;
+  width: 100%;
+  min-height: 100vh;
+  background: #f4f6f8;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
+  color: #1a1a1a;
+  -webkit-font-smoothing: antialiased;
 }
 
 .header {
-  background: linear-gradient(135deg, #2ecc71, #1abc9c);
+  background: linear-gradient(145deg, #1b5e20 0%, #2e7d32 40%, #43a047 100%);
   color: white;
-  padding: 15px 20px;
+  padding: 14px 16px;
   display: flex;
   align-items: center;
   position: sticky;
   top: 0;
   z-index: 100;
+  box-shadow: 0 2px 12px rgba(27, 94, 32, 0.3);
+  min-height: 56px;
 }
 
 .back-btn {
-  font-size: 20px;
-  margin-right: 15px;
+  width: 36px;
+  height: 36px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  margin-right: 12px;
   cursor: pointer;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.12);
+  transition: background 0.2s;
+
+  &:active { transform: scale(0.93); }
 }
 
 .header-title {
-  font-size: 18px;
-  font-weight: 600;
+  font-size: 17px;
+  font-weight: 700;
+  letter-spacing: 0.5px;
 }
 
 .container {
-  padding: 15px;
+  padding: 16px;
 }
 
 .section {
   background: white;
-  margin-bottom: 15px;
-  padding: 20px;
-  border-radius: 16px;
-  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  margin-bottom: 14px;
+  padding: 16px;
+  border-radius: 14px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
 }
 
 .section-title {
-  font-size: 16px;
-  font-weight: 600;
-  margin-bottom: 15px;
-  color: #2c3e50;
+  font-size: 15px;
+  font-weight: 700;
+  margin-bottom: 14px;
+  color: #1a1a2e;
   display: flex;
   align-items: center;
   justify-content: space-between;

+ 97 - 52
src/app/consumer/profile/profile.scss

@@ -1,26 +1,49 @@
-/* 页面基础布局 */
+:host {
+  display: block;
+  width: 100%;
+  min-height: 100vh;
+  background: #f4f6f8;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
+  color: #1a1a1a;
+  -webkit-font-smoothing: antialiased;
+}
+
 .container {
   padding: 16px;
-  padding-bottom: 88px; /* 为底部导航留空 */
+  padding-bottom: 90px;
 }
 
 /* 顶部标题与头像区域 */
 .header {
   position: relative;
   text-align: center;
-  padding: 20px 0 12px;
+  padding: 24px 16px 16px;
+  background: linear-gradient(145deg, #1b5e20 0%, #2e7d32 40%, #43a047 100%);
+  color: #fff;
+  box-shadow: 0 2px 12px rgba(27, 94, 32, 0.3);
 }
 
 .back-btn,
 .edit-profile {
   position: absolute;
   top: 16px;
-  font-size: 18px;
-  color: #2e7d32;
+  font-size: 16px;
+  color: rgba(255,255,255,0.9);
+  width: 36px;
+  height: 36px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.12);
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:active { transform: scale(0.93); }
 }
 
-.back-btn { left: 12px; }
-.edit-profile { right: 12px; }
+.back-btn { left: 16px; }
+.edit-profile { right: 16px; }
 
 .avatar-container {
   position: relative;
@@ -29,66 +52,77 @@
 }
 
 .avatar {
-  width: 80px;
-  height: 80px;
-  background: linear-gradient(135deg, #2ecc71, #1abc9c);
+  width: 72px;
+  height: 72px;
+  background: rgba(255,255,255,0.15);
+  backdrop-filter: blur(8px);
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   color: #fff;
-  font-size: 32px;
-  border: 3px solid #fff;
-  box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
+  font-size: 28px;
+  border: 3px solid rgba(255,255,255,0.3);
+  box-shadow: 0 4px 16px rgba(0,0,0,0.15);
+
+  img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
 }
 
 .avatar-edit {
   position: absolute;
   bottom: 0;
   right: 0;
-  width: 28px;
-  height: 28px;
-  background: #2e7d32;
+  width: 24px;
+  height: 24px;
+  background: #fff;
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
-  color: #fff;
-  font-size: 12px;
-  border: 2px solid #fff;
+  color: #2e7d32;
+  font-size: 10px;
+  border: 2px solid rgba(255,255,255,0.5);
+  box-shadow: 0 2px 6px rgba(0,0,0,0.15);
 }
 
 .user-name {
-  font-size: 20px;
-  font-weight: 600;
-  margin-bottom: 6px;
+  font-size: 18px;
+  font-weight: 700;
+  margin-bottom: 4px;
+  color: #fff;
 }
 
 .user-level {
   display: inline-block;
-  background: linear-gradient(135deg, #ff9800, #ff5722);
-  color: #fff;
-  padding: 6px 16px;
-  border-radius: 20px;
-  font-size: 14px;
+  background: rgba(255,255,255,0.15);
+  color: rgba(255,255,255,0.9);
+  padding: 4px 14px;
+  border-radius: 16px;
+  font-size: 12px;
   font-weight: 500;
-  margin-bottom: 12px;
-  box-shadow: 0 2px 8px rgba(255, 152, 0, 0.3);
+  margin-bottom: 8px;
+  backdrop-filter: blur(4px);
 }
 
 /* 统计与徽章 */
-.section { margin-top: 10px; }
+.section {
+  margin-top: 14px;
+  background: #fff;
+  border-radius: 14px;
+  padding: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
+}
 
 .stats-container {
   display: flex;
   justify-content: space-around;
-  padding-top: 16px;
-  border-top: 1px solid #f1f3f4;
+  padding-top: 14px;
+  border-top: 1px solid #f5f5f5;
 }
 
 .stat-item { text-align: center; }
-.stat-value { font-size: 18px; font-weight: 600; color: #2e7d32; margin-bottom: 4px; }
-.stat-label { font-size: 12px; color: #6c757d; }
+.stat-value { font-size: 20px; font-weight: 700; color: #2e7d32; margin-bottom: 2px; }
+.stat-label { font-size: 11px; color: #999; font-weight: 500; }
 
 .badges-container { margin-top: 18px; }
 .badges-title { font-size: 14px; color: #6c757d; margin-bottom: 10px; text-align: left; }
@@ -158,44 +192,55 @@
 }
 
 /* 菜单列表 */
-.menu-list { margin-top: 8px; }
+.menu-list { margin-top: 4px; }
 .menu-item {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 12px 6px;
-  border-bottom: 1px solid #eee;
+  padding: 10px 0;
+  border-bottom: 1px solid #f5f5f5;
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:last-child { border-bottom: none; }
+  &:hover { background: #fafbfd; border-radius: 8px; margin: 0 -8px; padding: 10px 8px; }
 }
 
-.menu-info { display: flex; align-items: center; gap: 10px; }
+.menu-info { display: flex; align-items: center; gap: 12px; }
 .menu-icon {
-  width: 36px;
-  height: 36px;
+  width: 38px;
+  height: 38px;
   border-radius: 10px;
-  background: #f8f9fa;
+  background: rgba(76,175,80,0.08);
   display: flex;
   align-items: center;
   justify-content: center;
   color: #2e7d32;
+  font-size: 14px;
+  flex-shrink: 0;
 }
 .menu-text { display: flex; flex-direction: column; }
-.menu-name { font-size: 14px; font-weight: 500; color: #495057; }
-.menu-desc { font-size: 12px; color: #6c757d; }
-.menu-arrow { color: #999; }
-.menu-badge { font-size: 12px; color: #ff5722; margin-right: 8px; }
+.menu-name { font-size: 14px; font-weight: 600; color: #1a1a2e; }
+.menu-desc { font-size: 11px; color: #999; margin-top: 1px; }
+.menu-arrow { color: #ccc; font-size: 12px; }
+.menu-badge { font-size: 11px; color: #ff5252; font-weight: 600; margin-right: 6px; }
 
 /* 退出登录按钮样式优化 */
 .logout-btn {
   width: 100%;
   margin-top: 14px;
-  padding: 12px 16px;
-  background: linear-gradient(135deg, #2ecc71, #1abc9c);
-  color: #fff;
-  border: none;
-  border-radius: 10px;
-  font-size: 16px;
+  padding: 14px 16px;
+  background: #fff;
+  color: #f44336;
+  border: 1px solid #ffcdd2;
+  border-radius: 12px;
+  font-size: 15px;
   font-weight: 600;
-  box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover { background: #fff5f5; }
+  &:active { transform: scale(0.98); }
 }
 
 /* 模态框 */

+ 55 - 60
src/app/government/supervision-overview/supervision-overview.html

@@ -1,18 +1,21 @@
 <div class="supervision-overview-container">
   <!-- 顶部导航 -->
-  <header class="header">
-    <div class="header-content">
-      <div class="logo">
-        <span class="logo-icon">🏛️</span>
-        <span>智回监管</span>
-      </div>
-      <div class="user-info">
-        <span>政府管理员</span>
-        <div class="avatar">
-          <span>政</span>
+  <header class="g-header">
+    <div class="g-header-inner">
+      <div class="g-brand">
+        <div class="g-brand-icon"><i class="fas fa-shield-alt"></i></div>
+        <div class="g-brand-text">
+          <span class="g-brand-name">智回监管</span>
+          <span class="g-brand-sub">政府监管平台</span>
         </div>
-        <div class="settings-icon" (click)="toggleSettingsMenu()" title="系统设置">
+      </div>
+      <div class="g-header-right">
+        <button class="g-hdr-btn" (click)="toggleSettingsMenu()" title="系统设置">
           <i class="fas fa-cog"></i>
+        </button>
+        <div class="g-user-chip">
+          <div class="g-user-avatar"><i class="fas fa-user-shield"></i></div>
+          <span class="g-user-label">管理员</span>
         </div>
       </div>
     </div>
@@ -22,44 +25,36 @@
   <main class="content">
     <div class="container">
       <section id="supervision-overview">
-        <h2 class="section-title">监管总览</h2>
+        <div class="g-overview-title">
+          <h2><i class="fas fa-tachometer-alt"></i> 监管总览</h2>
+          <span class="g-live-dot">实时监控中</span>
+        </div>
 
         <!-- 全域核心指标仪表盘 -->
-        <div class="indicators-dashboard">
-          <div class="indicator-card">
-            <div class="indicator-icon" style="background: linear-gradient(135deg, #2ecc71, #27ae60);">
-              <i class="fas fa-recycle"></i>
-            </div>
-            <div class="indicator-content">
-              <div class="indicator-label">当日回收总量</div>
-              <div class="indicator-value">{{ indicators.todayRecycle.toLocaleString() }}</div>
-              <div class="indicator-unit">kg</div>
-              <div class="indicator-trend up">{{ indicators.trend.recycle }}</div>
-            </div>
+        <div class="g-indicators">
+          <div class="g-ind-card g-ind-green">
+            <div class="g-ind-icon"><i class="fas fa-recycle"></i></div>
+            <div class="g-ind-body">
+              <div class="g-ind-value">{{ indicators.todayRecycle.toLocaleString() }}<span class="g-ind-unit">kg</span></div>
+              <div class="g-ind-label">当日回收总量</div>
+            </div>
+            <div class="g-ind-trend up">{{ indicators.trend.recycle }}</div>
           </div>
-          
-          <div class="indicator-card">
-            <div class="indicator-icon" style="background: linear-gradient(135deg, #3498db, #2980b9);">
-              <i class="fas fa-bullseye"></i>
-            </div>
-            <div class="indicator-content">
-              <div class="indicator-label">分类准确率</div>
-              <div class="indicator-value">{{ indicators.accuracyRate }}</div>
-              <div class="indicator-unit">%</div>
-              <div class="indicator-trend up">{{ indicators.trend.accuracy }}</div>
+          <div class="g-ind-card g-ind-blue">
+            <div class="g-ind-icon"><i class="fas fa-bullseye"></i></div>
+            <div class="g-ind-body">
+              <div class="g-ind-value">{{ indicators.accuracyRate }}<span class="g-ind-unit">%</span></div>
+              <div class="g-ind-label">分类准确率</div>
             </div>
+            <div class="g-ind-trend up">{{ indicators.trend.accuracy }}</div>
           </div>
-          
-          <div class="indicator-card">
-            <div class="indicator-icon" style="background: linear-gradient(135deg, #27ae60, #229954);">
-              <i class="fas fa-leaf"></i>
-            </div>
-            <div class="indicator-content">
-              <div class="indicator-label">碳减排总量</div>
-              <div class="indicator-value">{{ indicators.carbonReduction }}</div>
-              <div class="indicator-unit">kg</div>
-              <div class="indicator-trend up">{{ indicators.trend.carbon }}</div>
+          <div class="g-ind-card g-ind-teal">
+            <div class="g-ind-icon"><i class="fas fa-leaf"></i></div>
+            <div class="g-ind-body">
+              <div class="g-ind-value">{{ indicators.carbonReduction }}<span class="g-ind-unit">kg</span></div>
+              <div class="g-ind-label">碳减排总量</div>
             </div>
+            <div class="g-ind-trend up">{{ indicators.trend.carbon }}</div>
           </div>
         </div>
 
@@ -742,26 +737,26 @@
   </div>
 
   <!-- 底部导航 -->
-  <nav class="bottom-nav">
-    <div class="nav-item active">
-      <div class="nav-icon">📊</div>
-      <div>监管</div>
+  <nav class="g-bottom-nav">
+    <div class="g-nav-item active">
+      <div class="g-nav-icon"><i class="fas fa-chart-bar"></i></div>
+      <div class="g-nav-label">监管</div>
     </div>
-    <div class="nav-item" routerLink="/government/subsidy-management">
-      <div class="nav-icon">💰</div>
-      <div>补贴</div>
+    <div class="g-nav-item" routerLink="/government/subsidy-management">
+      <div class="g-nav-icon"><i class="fas fa-hand-holding-usd"></i></div>
+      <div class="g-nav-label">补贴</div>
     </div>
-    <div class="nav-item" routerLink="/government/industry-analysis">
-      <div class="nav-icon">📈</div>
-      <div>分析</div>
+    <div class="g-nav-item" routerLink="/government/industry-analysis">
+      <div class="g-nav-icon"><i class="fas fa-chart-line"></i></div>
+      <div class="g-nav-label">分析</div>
     </div>
-    <div class="nav-item" routerLink="/government/ai-decision-assistant">
-      <div class="nav-icon">🤖</div>
-      <div>AI助手</div>
+    <div class="g-nav-item" routerLink="/government/ai-decision-assistant">
+      <div class="g-nav-icon"><i class="fas fa-robot"></i></div>
+      <div class="g-nav-label">AI助手</div>
+    </div>
+    <div class="g-nav-item" routerLink="/government/government-center">
+      <div class="g-nav-icon"><i class="fas fa-university"></i></div>
+      <div class="g-nav-label">政务</div>
     </div>
-    <div class="nav-item" routerLink="/government/government-center">
-      <div class="nav-icon">⚙️</div>
-      <div>政务</div>
-  </div>
   </nav>
 </div>

+ 261 - 39
src/app/government/supervision-overview/supervision-overview.scss

@@ -8,8 +8,8 @@
   --light-bg: #f8f9fa;
   --dark-text: #2c3e50;
   --light-text: #7f8c8d;
-  --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-  --border-radius: 12px;
+  --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  --border-radius: 14px;
 }
 
 * {
@@ -20,98 +20,320 @@
 
 .supervision-overview-container {
   min-height: 100vh;
-  background-color: #f5f7fa;
+  background-color: #eef1f5;
   color: var(--dark-text);
-  font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif;
   line-height: 1.6;
+  -webkit-font-smoothing: antialiased;
 }
 
 .container {
   max-width: 100%;
   margin: 0 auto;
-  padding: 0 15px;
+  padding: 0 16px;
 }
 
-/* 顶部导航 */
-.header {
-  background: linear-gradient(135deg, #3498db, #2980b9);
-  color: white;
-  padding: 15px;
+// ============ G端 Header ============
+.g-header {
+  background: linear-gradient(135deg, #0d47a1 0%, #1565c0 50%, #1976d2 100%);
   position: sticky;
   top: 0;
   z-index: 100;
-  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 12px rgba(13, 71, 161, 0.3);
 }
 
-.header-content {
+.g-header-inner {
   display: flex;
+  align-items: center;
   justify-content: space-between;
+  padding: 12px 16px;
+  min-height: 56px;
+}
+
+.g-brand {
+  display: flex;
   align-items: center;
+  gap: 10px;
 }
 
-.logo {
+.g-brand-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.15);
   display: flex;
   align-items: center;
-  font-weight: bold;
-  font-size: 20px;
+  justify-content: center;
+  color: #fff;
+  font-size: 16px;
+}
+
+.g-brand-text {
+  display: flex;
+  flex-direction: column;
+}
+
+.g-brand-name {
+  font-size: 17px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 0.5px;
 }
 
-.logo-icon {
-  margin-right: 10px;
-  font-size: 24px;
+.g-brand-sub {
+  font-size: 10px;
+  color: rgba(255,255,255,0.6);
 }
 
-.user-info {
+.g-header-right {
   display: flex;
   align-items: center;
+  gap: 10px;
 }
 
-.avatar {
+.g-hdr-btn {
+  position: relative;
   width: 36px;
   height: 36px;
+  border: none;
+  border-radius: 10px;
+  background: rgba(255,255,255,0.12);
+  color: #fff;
+  font-size: 15px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: background 0.2s;
+
+  &:hover { background: rgba(255,255,255,0.22); }
+  &:active { transform: scale(0.93); }
+}
+
+.g-user-chip {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  background: rgba(255,255,255,0.1);
+  border-radius: 20px;
+  padding: 4px 12px 4px 4px;
+  cursor: pointer;
+  transition: background 0.2s;
+
+  &:hover { background: rgba(255,255,255,0.18); }
+}
+
+.g-user-avatar {
+  width: 28px;
+  height: 28px;
   border-radius: 50%;
-  background-color: rgba(255, 255, 255, 0.3);
+  background: rgba(255,255,255,0.2);
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-left: 10px;
+  color: #fff;
+  font-size: 12px;
+}
+
+.g-user-label {
+  font-size: 12px;
+  color: rgba(255,255,255,0.9);
+  font-weight: 500;
 }
 
-/* 内容区域 */
+// ============ Content ============
 .content {
-  padding: 20px 0 80px;
+  padding: 16px 0 90px;
 }
 
 .section-title {
-  font-size: 18px;
-  font-weight: 600;
-  margin-bottom: 15px;
+  font-size: 16px;
+  font-weight: 700;
+  margin-bottom: 14px;
+}
+
+// ============ Overview Title ============
+.g-overview-title {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 14px;
+  padding: 0 2px;
+
+  h2 {
+    font-size: 16px;
+    font-weight: 700;
+    color: #1a1a2e;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+
+    i { color: #1565c0; font-size: 14px; }
+  }
+}
+
+.g-live-dot {
+  font-size: 11px;
+  color: #4caf50;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  font-weight: 500;
+
+  &::before {
+    content: '';
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #4caf50;
+    animation: gPulse 2s infinite;
+  }
 }
 
-/* 核心指标仪表盘 */
-.indicators-dashboard {
+// ============ Indicator Cards ============
+.g-indicators {
   display: flex;
   flex-direction: column;
-  gap: 12px;
-  margin-bottom: 24px;
+  gap: 10px;
+  margin-bottom: 16px;
 }
 
-.indicator-card {
-  background: white;
-  border-radius: var(--border-radius);
-  padding: 16px;
+.g-ind-card {
+  background: #fff;
+  border-radius: 14px;
+  padding: 14px 16px;
   display: flex;
   align-items: center;
-  gap: 16px;
-  box-shadow: var(--card-shadow);
-  transition: all 0.3s;
+  gap: 12px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
+  transition: all 0.25s ease;
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 4px;
+  }
 
   &:hover {
     transform: translateY(-2px);
-    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
+    box-shadow: 0 6px 18px rgba(0,0,0,0.08);
+  }
+
+  &.g-ind-green {
+    &::before { background: linear-gradient(180deg, #4caf50, #81c784); }
+    .g-ind-icon { background: rgba(76,175,80,0.1); color: #2e7d32; }
+  }
+  &.g-ind-blue {
+    &::before { background: linear-gradient(180deg, #2196f3, #64b5f6); }
+    .g-ind-icon { background: rgba(33,150,243,0.1); color: #1565c0; }
+  }
+  &.g-ind-teal {
+    &::before { background: linear-gradient(180deg, #009688, #4db6ac); }
+    .g-ind-icon { background: rgba(0,150,136,0.1); color: #00695c; }
   }
 }
 
+.g-ind-icon {
+  width: 44px;
+  height: 44px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 18px;
+  flex-shrink: 0;
+}
+
+.g-ind-body {
+  flex: 1;
+  min-width: 0;
+}
+
+.g-ind-value {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1a1a2e;
+  line-height: 1.2;
+}
+
+.g-ind-unit {
+  font-size: 12px;
+  font-weight: 400;
+  color: #999;
+  margin-left: 2px;
+}
+
+.g-ind-label {
+  font-size: 12px;
+  color: #888;
+  margin-top: 2px;
+  font-weight: 500;
+}
+
+.g-ind-trend {
+  font-size: 11px;
+  font-weight: 600;
+  padding: 3px 8px;
+  border-radius: 10px;
+  flex-shrink: 0;
+
+  &.up { color: #4caf50; background: rgba(76,175,80,0.1); }
+  &.down { color: #f44336; background: rgba(244,67,54,0.1); }
+}
+
+// ============ Bottom Nav ============
+.g-bottom-nav {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  display: flex;
+  padding: 8px 0 20px;
+  box-shadow: 0 -1px 8px rgba(0,0,0,0.06);
+  z-index: 100;
+}
+
+.g-nav-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  color: #999;
+  text-decoration: none;
+
+  &.active {
+    color: #1565c0;
+    .g-nav-icon { transform: scale(1.1); }
+  }
+
+  &:hover:not(.active) { color: #666; }
+}
+
+.g-nav-icon {
+  font-size: 18px;
+  margin-bottom: 3px;
+  transition: transform 0.2s;
+}
+
+.g-nav-label {
+  font-size: 10px;
+  font-weight: 500;
+}
+
+@keyframes gPulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.3; }
+}
+
+// ============ Legacy compat (keep old .indicator-icon used deeper) ============
 .indicator-icon {
   width: 60px;
   height: 60px;

+ 25 - 29
src/app/shared/bottom-nav/bottom-nav.component.scss

@@ -4,53 +4,49 @@
   left: 0;
   right: 0;
   background: #fff;
-  display: grid;
-  grid-template-columns: repeat(5, 1fr);
-  align-items: center;
-  padding: 8px env(safe-area-inset-bottom) 10px;
-  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
+  display: flex;
+  padding: 8px 0 20px;
+  box-shadow: 0 -1px 8px rgba(0,0,0,0.06);
   z-index: 1000;
 }
 
 .nav-item {
-  text-align: center;
-  color: #6c757d;
-  font-size: 12px;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #999;
+  font-size: 10px;
+  font-weight: 500;
   user-select: none;
+  cursor: pointer;
+  transition: color 0.2s ease;
+
+  &:active { .nav-icon { transform: scale(0.9); } }
 }
 
 .nav-icon {
-  width: 34px;
-  height: 34px;
-  background: #f8f9fa;
-  border-radius: 50%;
+  width: 32px;
+  height: 32px;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin: 0 auto 4px auto;
-  transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease;
+  margin-bottom: 3px;
+  transition: transform 0.2s ease, color 0.2s ease;
 
-  i {
-    font-size: 16px;
-  }
+  i { font-size: 18px; }
 }
 
 .nav-item.active {
   color: #2e7d32;
-}
 
-.nav-item.active .nav-icon {
-  background: linear-gradient(135deg, #2e7d32, #43a047);
-  color: #fff;
-  transform: scale(1.06);
+  .nav-icon {
+    color: #2e7d32;
+    transform: scale(1.08);
+  }
 }
 
 @media (max-width: 360px) {
-  .nav-icon {
-    width: 30px;
-    height: 30px;
-  }
-  .nav-item div:last-child {
-    font-size: 10px;
-  }
+  .nav-icon { width: 28px; height: 28px; i { font-size: 16px; } }
+  .nav-item { font-size: 9px; }
 }

+ 136 - 0
src/app/shared/components/empty-state/empty-state.component.ts

@@ -0,0 +1,136 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-empty-state',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="empty-state" [ngClass]="size">
+      <div class="empty-illustration">
+        <div class="empty-icon-wrapper" [ngSwitch]="icon">
+          <svg *ngSwitchCase="'no-data'" viewBox="0 0 120 120" class="empty-svg">
+            <circle cx="60" cy="60" r="50" fill="#f0f7f0" stroke="#c8e6c9" stroke-width="2"/>
+            <rect x="35" y="40" width="50" height="6" rx="3" fill="#c8e6c9"/>
+            <rect x="35" y="52" width="38" height="6" rx="3" fill="#e0e0e0"/>
+            <rect x="35" y="64" width="44" height="6" rx="3" fill="#e0e0e0"/>
+            <rect x="35" y="76" width="30" height="6" rx="3" fill="#e0e0e0"/>
+          </svg>
+          <svg *ngSwitchCase="'no-network'" viewBox="0 0 120 120" class="empty-svg">
+            <circle cx="60" cy="60" r="50" fill="#fff3e0" stroke="#ffe0b2" stroke-width="2"/>
+            <path d="M40 55 L60 35 L80 55" stroke="#ff9800" stroke-width="4" fill="none" stroke-linecap="round"/>
+            <path d="M46 65 L60 50 L74 65" stroke="#ffb74d" stroke-width="3" fill="none" stroke-linecap="round"/>
+            <circle cx="60" cy="78" r="4" fill="#ff9800"/>
+            <line x1="45" y1="40" x2="75" y2="80" stroke="#f44336" stroke-width="3" stroke-linecap="round"/>
+          </svg>
+          <svg *ngSwitchCase="'no-order'" viewBox="0 0 120 120" class="empty-svg">
+            <circle cx="60" cy="60" r="50" fill="#e8f5e9" stroke="#a5d6a7" stroke-width="2"/>
+            <rect x="38" y="30" width="44" height="60" rx="4" fill="white" stroke="#a5d6a7" stroke-width="2"/>
+            <rect x="45" y="40" width="30" height="4" rx="2" fill="#c8e6c9"/>
+            <rect x="45" y="50" width="22" height="4" rx="2" fill="#e0e0e0"/>
+            <rect x="45" y="60" width="26" height="4" rx="2" fill="#e0e0e0"/>
+            <rect x="45" y="70" width="18" height="4" rx="2" fill="#e0e0e0"/>
+          </svg>
+          <svg *ngSwitchCase="'no-device'" viewBox="0 0 120 120" class="empty-svg">
+            <circle cx="60" cy="60" r="50" fill="#e3f2fd" stroke="#90caf9" stroke-width="2"/>
+            <rect x="35" y="35" width="50" height="35" rx="4" fill="white" stroke="#90caf9" stroke-width="2"/>
+            <rect x="50" y="75" width="20" height="4" rx="2" fill="#90caf9"/>
+            <rect x="42" y="82" width="36" height="4" rx="2" fill="#bbdefb"/>
+            <circle cx="60" cy="52" r="8" fill="none" stroke="#64b5f6" stroke-width="2"/>
+            <path d="M56 52 L59 55 L65 49" stroke="#4caf50" stroke-width="2" fill="none" stroke-linecap="round"/>
+          </svg>
+          <svg *ngSwitchDefault viewBox="0 0 120 120" class="empty-svg">
+            <circle cx="60" cy="60" r="50" fill="#f5f5f5" stroke="#e0e0e0" stroke-width="2"/>
+            <circle cx="60" cy="50" r="15" fill="none" stroke="#bdbdbd" stroke-width="2"/>
+            <line x1="71" y1="61" x2="82" y2="72" stroke="#bdbdbd" stroke-width="3" stroke-linecap="round"/>
+            <text x="60" y="90" text-anchor="middle" fill="#bdbdbd" font-size="10">Empty</text>
+          </svg>
+        </div>
+      </div>
+      <div class="empty-text">{{ title }}</div>
+      <div class="empty-sub-text" *ngIf="description">{{ description }}</div>
+      <button class="empty-action" *ngIf="actionText" (click)="action.emit()">
+        <i *ngIf="actionIcon" [class]="actionIcon"></i>
+        {{ actionText }}
+      </button>
+    </div>
+  `,
+  styles: [`
+    .empty-state {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 40px 20px;
+      text-align: center;
+
+      &.small {
+        padding: 24px 16px;
+        .empty-svg { width: 64px; height: 64px; }
+        .empty-text { font-size: 13px; }
+        .empty-sub-text { font-size: 11px; }
+      }
+
+      &.large {
+        padding: 60px 24px;
+        .empty-svg { width: 140px; height: 140px; }
+        .empty-text { font-size: 18px; }
+      }
+    }
+
+    .empty-illustration {
+      margin-bottom: 16px;
+    }
+
+    .empty-svg {
+      width: 100px;
+      height: 100px;
+    }
+
+    .empty-text {
+      font-size: 15px;
+      font-weight: 600;
+      color: #666;
+      margin-bottom: 6px;
+    }
+
+    .empty-sub-text {
+      font-size: 13px;
+      color: #999;
+      max-width: 260px;
+      line-height: 1.5;
+    }
+
+    .empty-action {
+      margin-top: 16px;
+      padding: 10px 24px;
+      background: linear-gradient(135deg, #4caf50, #66bb6a);
+      color: white;
+      border: none;
+      border-radius: 20px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      transition: all 0.3s ease;
+
+      &:hover {
+        transform: translateY(-1px);
+        box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
+      }
+
+      i { font-size: 14px; }
+    }
+  `]
+})
+export class EmptyStateComponent {
+  @Input() icon: 'no-data' | 'no-network' | 'no-order' | 'no-device' | 'search' = 'no-data';
+  @Input() title: string = '暂无数据';
+  @Input() description: string = '';
+  @Input() actionText: string = '';
+  @Input() actionIcon: string = '';
+  @Input() size: 'small' | 'normal' | 'large' = 'normal';
+  @Output() action = new EventEmitter<void>();
+}

+ 145 - 0
src/app/shared/components/page-header/page-header.component.ts

@@ -0,0 +1,145 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'app-page-header',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <header class="page-header" [ngClass]="theme" [class.transparent]="transparent">
+      <div class="header-inner">
+        <div class="header-left">
+          <button class="header-btn" *ngIf="showBack" (click)="goBack()">
+            <i class="fas fa-arrow-left"></i>
+          </button>
+          <div class="header-title-area" *ngIf="!showBack || title">
+            <h1 class="header-title">{{ title }}</h1>
+            <span class="header-subtitle" *ngIf="subtitle">{{ subtitle }}</span>
+          </div>
+        </div>
+        <div class="header-right">
+          <ng-content></ng-content>
+        </div>
+      </div>
+    </header>
+  `,
+  styles: [`
+    .page-header {
+      position: sticky;
+      top: 0;
+      z-index: 100;
+      backdrop-filter: blur(12px);
+      -webkit-backdrop-filter: blur(12px);
+      transition: all 0.3s ease;
+
+      &.transparent {
+        background: transparent;
+        box-shadow: none;
+      }
+    }
+
+    .green {
+      background: linear-gradient(135deg, #2e7d32, #4caf50);
+      color: white;
+      .header-title, .header-subtitle { color: white; }
+      .header-btn { color: white; background: rgba(255,255,255,0.15); }
+      .header-btn:hover { background: rgba(255,255,255,0.25); }
+    }
+
+    .blue {
+      background: linear-gradient(135deg, #1565c0, #1e88e5);
+      color: white;
+      .header-title, .header-subtitle { color: white; }
+      .header-btn { color: white; background: rgba(255,255,255,0.15); }
+    }
+
+    .dark {
+      background: linear-gradient(135deg, #1a1a2e, #16213e);
+      color: white;
+      .header-title, .header-subtitle { color: white; }
+      .header-btn { color: white; background: rgba(255,255,255,0.1); }
+    }
+
+    .light {
+      background: rgba(255, 255, 255, 0.9);
+      border-bottom: 1px solid #f0f0f0;
+      .header-title { color: #1a1a1a; }
+      .header-subtitle { color: #888; }
+      .header-btn { color: #333; background: #f5f5f5; }
+    }
+
+    .header-inner {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 12px 16px;
+      min-height: 56px;
+    }
+
+    .header-left {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      flex: 1;
+    }
+
+    .header-right {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+
+    .header-btn {
+      width: 36px;
+      height: 36px;
+      border: none;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      font-size: 16px;
+      flex-shrink: 0;
+
+      &:active { transform: scale(0.95); }
+    }
+
+    .header-title-area {
+      display: flex;
+      flex-direction: column;
+    }
+
+    .header-title {
+      font-size: 18px;
+      font-weight: 700;
+      line-height: 1.3;
+      margin: 0;
+    }
+
+    .header-subtitle {
+      font-size: 12px;
+      opacity: 0.8;
+      margin-top: 1px;
+    }
+  `]
+})
+export class PageHeaderComponent {
+  @Input() title: string = '';
+  @Input() subtitle: string = '';
+  @Input() theme: 'green' | 'blue' | 'dark' | 'light' = 'green';
+  @Input() showBack: boolean = false;
+  @Input() transparent: boolean = false;
+  @Output() back = new EventEmitter<void>();
+
+  constructor(private router: Router) {}
+
+  goBack(): void {
+    if (this.back.observed) {
+      this.back.emit();
+    } else {
+      history.back();
+    }
+  }
+}

+ 186 - 0
src/app/shared/components/skeleton/skeleton.component.ts

@@ -0,0 +1,186 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-skeleton',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="skeleton-wrapper" [ngSwitch]="type">
+      <!-- 卡片骨架 -->
+      <div *ngSwitchCase="'card'" class="skeleton-card">
+        <div class="skeleton-line w-40 h-12"></div>
+        <div class="skeleton-line w-70 h-24 mt-8"></div>
+        <div class="skeleton-line w-50 h-12 mt-8"></div>
+      </div>
+
+      <!-- 统计卡片骨架 -->
+      <div *ngSwitchCase="'stat'" class="skeleton-stat">
+        <div class="skeleton-circle size-40"></div>
+        <div class="skeleton-line w-60 h-20 mt-8"></div>
+        <div class="skeleton-line w-40 h-12 mt-4"></div>
+      </div>
+
+      <!-- 列表项骨架 -->
+      <div *ngSwitchCase="'list'" class="skeleton-list">
+        <div class="skeleton-list-item" *ngFor="let i of repeatArr">
+          <div class="skeleton-circle size-44"></div>
+          <div class="skeleton-list-content">
+            <div class="skeleton-line w-60 h-14"></div>
+            <div class="skeleton-line w-40 h-12 mt-6"></div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 头像+文字骨架 -->
+      <div *ngSwitchCase="'avatar'" class="skeleton-avatar-row">
+        <div class="skeleton-circle size-48"></div>
+        <div class="skeleton-avatar-text">
+          <div class="skeleton-line w-50 h-16"></div>
+          <div class="skeleton-line w-30 h-12 mt-6"></div>
+        </div>
+      </div>
+
+      <!-- 图表骨架 -->
+      <div *ngSwitchCase="'chart'" class="skeleton-chart">
+        <div class="skeleton-line w-30 h-14 mb-12"></div>
+        <div class="skeleton-chart-bars">
+          <div class="skeleton-bar" *ngFor="let h of chartBarHeights" [style.height.%]="h"></div>
+        </div>
+      </div>
+
+      <!-- 纯行骨架 -->
+      <div *ngSwitchDefault class="skeleton-lines">
+        <div class="skeleton-line" *ngFor="let w of lineWidths" [style.width.%]="w" [style.height.px]="lineHeight" [style.margin-bottom.px]="8"></div>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .skeleton-wrapper {
+      width: 100%;
+    }
+
+    .skeleton-line, .skeleton-circle, .skeleton-bar {
+      background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+      background-size: 200% 100%;
+      animation: shimmer 1.5s infinite ease-in-out;
+      border-radius: 6px;
+    }
+
+    .skeleton-circle {
+      border-radius: 50%;
+      flex-shrink: 0;
+    }
+
+    // Width helpers
+    .w-30 { width: 30%; }
+    .w-40 { width: 40%; }
+    .w-50 { width: 50%; }
+    .w-60 { width: 60%; }
+    .w-70 { width: 70%; }
+    .w-80 { width: 80%; }
+    .w-100 { width: 100%; }
+
+    // Height helpers
+    .h-12 { height: 12px; }
+    .h-14 { height: 14px; }
+    .h-16 { height: 16px; }
+    .h-20 { height: 20px; }
+    .h-24 { height: 24px; }
+
+    // Size helpers for circles
+    .size-32 { width: 32px; height: 32px; }
+    .size-40 { width: 40px; height: 40px; }
+    .size-44 { width: 44px; height: 44px; }
+    .size-48 { width: 48px; height: 48px; }
+
+    // Spacing helpers
+    .mt-4 { margin-top: 4px; }
+    .mt-6 { margin-top: 6px; }
+    .mt-8 { margin-top: 8px; }
+    .mb-12 { margin-bottom: 12px; }
+
+    // Card skeleton
+    .skeleton-card {
+      padding: 16px;
+      background: #fff;
+      border-radius: 12px;
+    }
+
+    // Stat skeleton
+    .skeleton-stat {
+      padding: 16px;
+      text-align: center;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    // List skeleton
+    .skeleton-list-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 0;
+      border-bottom: 1px solid #f5f5f5;
+
+      &:last-child { border-bottom: none; }
+    }
+
+    .skeleton-list-content {
+      flex: 1;
+    }
+
+    // Avatar row
+    .skeleton-avatar-row {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+
+    .skeleton-avatar-text {
+      flex: 1;
+    }
+
+    // Chart skeleton
+    .skeleton-chart {
+      padding: 16px 0;
+    }
+
+    .skeleton-chart-bars {
+      display: flex;
+      align-items: flex-end;
+      gap: 8px;
+      height: 120px;
+    }
+
+    .skeleton-bar {
+      flex: 1;
+      min-height: 20px;
+      border-radius: 4px 4px 0 0;
+    }
+
+    @keyframes shimmer {
+      0% { background-position: -200% 0; }
+      100% { background-position: 200% 0; }
+    }
+  `]
+})
+export class SkeletonComponent {
+  @Input() type: 'card' | 'stat' | 'list' | 'avatar' | 'chart' | 'lines' = 'lines';
+  @Input() count: number = 3;
+  @Input() lineHeight: number = 14;
+
+  get repeatArr(): number[] {
+    return Array(this.count).fill(0);
+  }
+
+  get lineWidths(): number[] {
+    const widths = [90, 70, 50, 80, 60, 40, 75, 55];
+    return widths.slice(0, this.count);
+  }
+
+  get chartBarHeights(): number[] {
+    return [60, 80, 45, 90, 70, 55, 85];
+  }
+}

+ 239 - 0
src/app/shared/components/stat-card/stat-card.component.ts

@@ -0,0 +1,239 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface StatCardData {
+  label: string;
+  value: string | number;
+  unit?: string;
+  trend?: number;       // 正数=上升, 负数=下降, 0=持平
+  icon?: string;         // FontAwesome class or emoji
+  color?: 'green' | 'blue' | 'orange' | 'red' | 'purple' | 'cyan';
+  sparkline?: number[];  // mini trend data
+}
+
+@Component({
+  selector: 'app-stat-card',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="stat-card" [ngClass]="[variant, 'color-' + (data.color || 'green')]" [class.clickable]="clickable">
+      <!-- 图标 -->
+      <div class="stat-icon" *ngIf="data.icon">
+        <span *ngIf="isEmoji(data.icon)">{{ data.icon }}</span>
+        <i *ngIf="!isEmoji(data.icon)" [class]="data.icon"></i>
+      </div>
+
+      <!-- 数值 -->
+      <div class="stat-body">
+        <div class="stat-value-row">
+          <span class="stat-value">{{ data.value }}</span>
+          <span class="stat-unit" *ngIf="data.unit">{{ data.unit }}</span>
+        </div>
+        <div class="stat-label">{{ data.label }}</div>
+      </div>
+
+      <!-- 趋势 -->
+      <div class="stat-trend" *ngIf="data.trend !== undefined" [ngClass]="trendClass">
+        <i class="fas" [ngClass]="trendIcon"></i>
+        <span>{{ trendText }}</span>
+      </div>
+
+      <!-- 迷你趋势线 -->
+      <svg *ngIf="data.sparkline && data.sparkline.length > 1" class="stat-sparkline" viewBox="0 0 80 24" preserveAspectRatio="none">
+        <polyline [attr.points]="sparklinePoints" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+      </svg>
+    </div>
+  `,
+  styles: [`
+    .stat-card {
+      position: relative;
+      border-radius: 14px;
+      padding: 16px;
+      background: white;
+      overflow: hidden;
+      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+      box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+      border: 1px solid rgba(0,0,0,0.04);
+
+      &.clickable {
+        cursor: pointer;
+        &:hover {
+          transform: translateY(-3px);
+          box-shadow: 0 8px 24px rgba(0,0,0,0.1);
+        }
+        &:active {
+          transform: translateY(-1px);
+        }
+      }
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        height: 3px;
+      }
+    }
+
+    // Color variants
+    .color-green {
+      &::before { background: linear-gradient(90deg, #4caf50, #81c784); }
+      .stat-icon { background: rgba(76, 175, 80, 0.1); color: #4caf50; }
+      .stat-sparkline { color: #4caf50; }
+    }
+    .color-blue {
+      &::before { background: linear-gradient(90deg, #2196f3, #64b5f6); }
+      .stat-icon { background: rgba(33, 150, 243, 0.1); color: #2196f3; }
+      .stat-sparkline { color: #2196f3; }
+    }
+    .color-orange {
+      &::before { background: linear-gradient(90deg, #ff9800, #ffb74d); }
+      .stat-icon { background: rgba(255, 152, 0, 0.1); color: #ff9800; }
+      .stat-sparkline { color: #ff9800; }
+    }
+    .color-red {
+      &::before { background: linear-gradient(90deg, #f44336, #e57373); }
+      .stat-icon { background: rgba(244, 67, 54, 0.1); color: #f44336; }
+      .stat-sparkline { color: #f44336; }
+    }
+    .color-purple {
+      &::before { background: linear-gradient(90deg, #9c27b0, #ba68c8); }
+      .stat-icon { background: rgba(156, 39, 176, 0.1); color: #9c27b0; }
+      .stat-sparkline { color: #9c27b0; }
+    }
+    .color-cyan {
+      &::before { background: linear-gradient(90deg, #00bcd4, #4dd0e1); }
+      .stat-icon { background: rgba(0, 188, 212, 0.1); color: #00bcd4; }
+      .stat-sparkline { color: #00bcd4; }
+    }
+
+    // Compact variant
+    .compact {
+      padding: 12px;
+      .stat-icon { width: 32px; height: 32px; font-size: 14px; }
+      .stat-value { font-size: 18px; }
+      .stat-label { font-size: 11px; }
+    }
+
+    // Dashboard variant
+    .dashboard {
+      .stat-body { text-align: left; }
+    }
+
+    .stat-icon {
+      width: 40px;
+      height: 40px;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 18px;
+      margin-bottom: 12px;
+    }
+
+    .stat-body {
+      position: relative;
+      z-index: 1;
+    }
+
+    .stat-value-row {
+      display: flex;
+      align-items: baseline;
+      gap: 2px;
+    }
+
+    .stat-value {
+      font-size: 24px;
+      font-weight: 700;
+      color: #1a1a1a;
+      line-height: 1.2;
+      letter-spacing: -0.5px;
+    }
+
+    .stat-unit {
+      font-size: 12px;
+      color: #999;
+      font-weight: 400;
+    }
+
+    .stat-label {
+      font-size: 12px;
+      color: #888;
+      margin-top: 4px;
+      font-weight: 500;
+    }
+
+    .stat-trend {
+      display: inline-flex;
+      align-items: center;
+      gap: 3px;
+      font-size: 11px;
+      font-weight: 600;
+      padding: 2px 8px;
+      border-radius: 10px;
+      margin-top: 8px;
+
+      &.trend-up {
+        color: #4caf50;
+        background: rgba(76, 175, 80, 0.1);
+      }
+      &.trend-down {
+        color: #f44336;
+        background: rgba(244, 67, 54, 0.1);
+      }
+      &.trend-flat {
+        color: #9e9e9e;
+        background: rgba(158, 158, 158, 0.1);
+      }
+
+      i { font-size: 10px; }
+    }
+
+    .stat-sparkline {
+      position: absolute;
+      bottom: 8px;
+      right: 12px;
+      width: 80px;
+      height: 24px;
+      opacity: 0.5;
+    }
+  `]
+})
+export class StatCardComponent {
+  @Input() data!: StatCardData;
+  @Input() variant: 'default' | 'compact' | 'dashboard' = 'default';
+  @Input() clickable: boolean = false;
+
+  get trendClass(): string {
+    if (!this.data.trend) return 'trend-flat';
+    return this.data.trend > 0 ? 'trend-up' : 'trend-down';
+  }
+
+  get trendIcon(): string {
+    if (!this.data.trend) return 'fa-minus';
+    return this.data.trend > 0 ? 'fa-arrow-up' : 'fa-arrow-down';
+  }
+
+  get trendText(): string {
+    if (!this.data.trend) return '0%';
+    return `${Math.abs(this.data.trend).toFixed(1)}%`;
+  }
+
+  get sparklinePoints(): string {
+    const data = this.data.sparkline || [];
+    if (data.length < 2) return '';
+    const max = Math.max(...data);
+    const min = Math.min(...data);
+    const range = max - min || 1;
+    return data.map((v, i) => {
+      const x = (i / (data.length - 1)) * 80;
+      const y = 24 - ((v - min) / range) * 20 - 2;
+      return `${x},${y}`;
+    }).join(' ');
+  }
+
+  isEmoji(str: string): boolean {
+    return !/^fa[srbl]?\s/.test(str) && !/^icon-/.test(str);
+  }
+}

+ 98 - 0
src/app/shared/components/status-badge/status-badge.component.ts

@@ -0,0 +1,98 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-status-badge',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <span class="status-badge" [ngClass]="[colorClass, sizeClass]" [class.pulse-dot]="pulse">
+      <span class="dot" *ngIf="showDot"></span>
+      <span class="badge-text">{{ text }}</span>
+    </span>
+  `,
+  styles: [`
+    .status-badge {
+      display: inline-flex;
+      align-items: center;
+      gap: 5px;
+      padding: 3px 10px;
+      border-radius: 12px;
+      font-weight: 500;
+      white-space: nowrap;
+
+      &.small {
+        padding: 2px 8px;
+        font-size: 10px;
+        .dot { width: 5px; height: 5px; }
+      }
+      &.medium {
+        padding: 3px 10px;
+        font-size: 12px;
+      }
+      &.large {
+        padding: 4px 14px;
+        font-size: 14px;
+        .dot { width: 8px; height: 8px; }
+      }
+    }
+
+    .dot {
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      flex-shrink: 0;
+    }
+
+    .pulse-dot .dot {
+      animation: badgePulse 2s infinite;
+    }
+
+    // Status colors
+    .status-success {
+      background: rgba(76, 175, 80, 0.12);
+      color: #2e7d32;
+      .dot { background: #4caf50; }
+    }
+    .status-warning {
+      background: rgba(255, 152, 0, 0.12);
+      color: #e65100;
+      .dot { background: #ff9800; }
+    }
+    .status-error {
+      background: rgba(244, 67, 54, 0.12);
+      color: #c62828;
+      .dot { background: #f44336; }
+    }
+    .status-info {
+      background: rgba(33, 150, 243, 0.12);
+      color: #1565c0;
+      .dot { background: #2196f3; }
+    }
+    .status-default {
+      background: rgba(158, 158, 158, 0.12);
+      color: #616161;
+      .dot { background: #9e9e9e; }
+    }
+    .status-purple {
+      background: rgba(156, 39, 176, 0.12);
+      color: #7b1fa2;
+      .dot { background: #9c27b0; }
+    }
+
+    @keyframes badgePulse {
+      0%, 100% { opacity: 1; }
+      50% { opacity: 0.4; }
+    }
+  `]
+})
+export class StatusBadgeComponent {
+  @Input() text: string = '';
+  @Input() status: 'success' | 'warning' | 'error' | 'info' | 'default' | 'purple' = 'default';
+  @Input() size: 'small' | 'medium' | 'large' = 'medium';
+  @Input() showDot: boolean = true;
+  @Input() pulse: boolean = false;
+
+  get colorClass(): string { return `status-${this.status}`; }
+  get sizeClass(): string { return this.size; }
+}