From 0eb8ac918196b075e6a321b79084b27f94dbc18c Mon Sep 17 00:00:00 2001
From: FlowerWater <9579043+flowerwater2@user.noreply.gitee.com>
Date: Sat, 29 Nov 2025 17:20:17 +0800
Subject: [PATCH] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../settlement-ui-optimization/design.md | 344 ++++++++
.../requirements.md | 107 +++
.../specs/settlement-ui-optimization/tasks.md | 111 +++
src/api/address.ts | 94 +++
src/api/auth.ts | 57 ++
src/api/banner.ts | 17 +
src/api/category.ts | 30 +
src/api/finance.ts | 125 +++
src/api/goods.ts | 95 +++
src/api/member.ts | 29 +
src/api/order.ts | 112 +++
src/components/cart/CartItem.vue | 173 ++++
src/components/cart/CartSummary.vue | 133 ++++
src/components/common/Banner.vue | 77 ++
src/components/common/CategoryGrid.vue | 79 ++
src/components/common/CounterInput.vue | 124 +++
src/components/common/PriceTag.vue | 89 +++
src/components/common/SearchBar.vue | 137 ++++
src/components/finance/CreditCard.vue | 148 ++++
src/components/finance/SettlementItem.vue | 169 ++++
src/components/finance/WriteOffForm.vue | 303 +++++++
src/components/goods/GoodsCard.vue | 130 +++
src/components/goods/SpecSelector.vue | 294 +++++++
src/components/member/MemberBenefits.vue | 61 ++
src/components/member/MemberCard.vue | 116 +++
src/mock/address.ts | 37 +
src/mock/banner.ts | 31 +
src/mock/category.ts | 55 ++
src/mock/finance.ts | 168 ++++
src/mock/goods.ts | 238 ++++++
src/mock/index.ts | 10 +
src/mock/member.ts | 40 +
src/pages/finance/credit.vue | 123 +++
src/pages/finance/settlement.vue | 744 ++++++++++++++++++
src/pages/goods/cart.vue | 205 ++++-
src/pages/goods/detail.vue | 386 +++++++++
src/pages/index/index.vue | 175 +++-
src/pages/login/index.vue | 236 ++++++
src/pages/me/me.vue | 556 ++++++++++++-
src/pages/member/index.vue | 147 ++++
src/pages/order/confirm.vue | 693 ++++++++++++++++
src/pages/order/detail.vue | 384 +++++++++
src/pages/order/list.vue | 329 ++++++++
src/pages/sort/index.vue | 209 ++++-
src/store/cart.ts | 126 +++
src/store/finance.ts | 79 ++
src/store/member.ts | 40 +
src/store/order.ts | 74 ++
src/store/user.ts | 89 +--
src/typings/mall.ts | 206 +++++
50 files changed, 8471 insertions(+), 63 deletions(-)
create mode 100644 .kiro/specs/settlement-ui-optimization/design.md
create mode 100644 .kiro/specs/settlement-ui-optimization/requirements.md
create mode 100644 .kiro/specs/settlement-ui-optimization/tasks.md
create mode 100644 src/api/address.ts
create mode 100644 src/api/auth.ts
create mode 100644 src/api/banner.ts
create mode 100644 src/api/category.ts
create mode 100644 src/api/finance.ts
create mode 100644 src/api/goods.ts
create mode 100644 src/api/member.ts
create mode 100644 src/api/order.ts
create mode 100644 src/components/cart/CartItem.vue
create mode 100644 src/components/cart/CartSummary.vue
create mode 100644 src/components/common/Banner.vue
create mode 100644 src/components/common/CategoryGrid.vue
create mode 100644 src/components/common/CounterInput.vue
create mode 100644 src/components/common/PriceTag.vue
create mode 100644 src/components/common/SearchBar.vue
create mode 100644 src/components/finance/CreditCard.vue
create mode 100644 src/components/finance/SettlementItem.vue
create mode 100644 src/components/finance/WriteOffForm.vue
create mode 100644 src/components/goods/GoodsCard.vue
create mode 100644 src/components/goods/SpecSelector.vue
create mode 100644 src/components/member/MemberBenefits.vue
create mode 100644 src/components/member/MemberCard.vue
create mode 100644 src/mock/address.ts
create mode 100644 src/mock/banner.ts
create mode 100644 src/mock/category.ts
create mode 100644 src/mock/finance.ts
create mode 100644 src/mock/goods.ts
create mode 100644 src/mock/index.ts
create mode 100644 src/mock/member.ts
create mode 100644 src/pages/finance/credit.vue
create mode 100644 src/pages/finance/settlement.vue
create mode 100644 src/pages/goods/detail.vue
create mode 100644 src/pages/login/index.vue
create mode 100644 src/pages/member/index.vue
create mode 100644 src/pages/order/confirm.vue
create mode 100644 src/pages/order/detail.vue
create mode 100644 src/pages/order/list.vue
create mode 100644 src/store/cart.ts
create mode 100644 src/store/finance.ts
create mode 100644 src/store/member.ts
create mode 100644 src/store/order.ts
create mode 100644 src/typings/mall.ts
diff --git a/.kiro/specs/settlement-ui-optimization/design.md b/.kiro/specs/settlement-ui-optimization/design.md
new file mode 100644
index 0000000..0c9d692
--- /dev/null
+++ b/.kiro/specs/settlement-ui-optimization/design.md
@@ -0,0 +1,344 @@
+# Design Document
+
+## Overview
+
+本设计文档描述了结算页面(应结账款)UI优化的技术实现方案。优化目标是提升视觉层次、改善信息密度、增强交互反馈,从而提供更好的用户体验。
+
+当前页面基于 Vue 3 + TypeScript + uni-app 框架开发,使用 SCSS 进行样式管理。优化将保持现有架构不变,主要通过调整样式、优化组件结构和改进视觉设计来实现。
+
+## Architecture
+
+### 组件层次结构
+
+```
+settlement.vue (主页面)
+├── DueAlert (到期提醒区域)
+│ └── DueItem[] (到期项列表)
+├── TabNavigation (标签导航)
+│ └── TabItem[] (标签项)
+├── MerchantGroup[] (商户分组)
+│ ├── MerchantHeader (商户头部)
+│ ├── BatchActionButton (批量操作按钮)
+│ └── OrderList (订单列表)
+│ └── OrderItem[] (订单项)
+│ ├── OrderHeader (订单头部)
+│ ├── OrderContent (订单内容)
+│ └── OrderFooter (订单操作)
+└── EmptyState (空状态)
+```
+
+### 样式架构
+
+采用 BEM 命名规范和模块化 SCSS:
+- 使用语义化的颜色变量
+- 统一的间距系统(基于 4rpx 倍数)
+- 响应式字体大小
+- 可复用的混入(mixin)
+
+## Components and Interfaces
+
+### 1. DueAlert Component
+
+**职责**: 显示即将到期的账款提醒
+
+**Props**: 无(从 store 获取数据)
+
+**数据源**: `financeStore.dueOrders`
+
+**视觉特性**:
+- 琥珀色背景 (#fffbe6)
+- 警告图标 + 标题
+- 横向滚动列表
+- 卡片式到期项
+
+### 2. TabNavigation Component
+
+**职责**: 提供未结/已结状态切换
+
+**Props**: 无
+
+**状态**: `currentTab` (0: 未结, 1: 已结)
+
+**视觉特性**:
+- 固定在顶部
+- 活动标签蓝色下划线
+- 平滑过渡动画
+
+### 3. MerchantGroup Component
+
+**职责**: 按商户聚合显示账款信息
+
+**数据结构**:
+```typescript
+interface MerchantGroup {
+ merchantId: string
+ merchantName: string
+ settlements: Settlement[]
+ totalAmount: number
+ hasOverdue: boolean
+}
+```
+
+**视觉特性**:
+- 圆角卡片 (16rpx)
+- 渐变头部背景
+- 逾期徽章
+- 突出显示总金额
+
+### 4. OrderItem Component
+
+**职责**: 显示单个订单的结算信息
+
+**数据结构**:
+```typescript
+interface Settlement {
+ id: string
+ orderNo: string
+ merchantId: string
+ merchantName: string
+ amount: number
+ dueDate: string
+ settlementDate?: string
+ status: SettlementStatus
+}
+```
+
+**视觉特性**:
+- 清晰的信息层次
+- 状态徽章(颜色编码)
+- 金额突出显示
+- 操作按钮(未结状态)
+
+## Data Models
+
+### SettlementStatus Enum
+
+```typescript
+enum SettlementStatus {
+ UNSETTLED = 'unsettled', // 未结
+ SETTLED = 'settled', // 已结
+ OVERDUE = 'overdue' // 已逾期
+}
+```
+
+### Settlement Interface
+
+```typescript
+interface Settlement {
+ id: string
+ orderNo: string
+ merchantId: string
+ merchantName: string
+ amount: number
+ dueDate: string
+ settlementDate?: string
+ status: SettlementStatus
+}
+```
+
+### DueOrder Interface
+
+```typescript
+interface DueOrder {
+ id: string
+ dueDate: string
+ amount: number
+}
+```
+
+## Correctness Properties
+
+*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
+
+### Property 1: Due Alert Visibility Consistency
+
+*For any* state of the due orders list, when the list is empty, the Due Alert component should not be rendered in the DOM
+**Validates: Requirements 1.4**
+
+### Property 2: Merchant Grouping Completeness
+
+*For any* settlement list, all settlements should be grouped by merchantId with no settlements left ungrouped or duplicated across groups
+**Validates: Requirements 2.1**
+
+### Property 3: Status Badge Color Mapping
+
+*For any* settlement status value, the rendered status badge should use the correct color scheme (green for settled, amber for unsettled, red for overdue)
+**Validates: Requirements 3.3**
+
+### Property 4: Amount Display Formatting
+
+*For any* numeric amount value, when displayed in the UI, it should be formatted with exactly 2 decimal places and prefixed with the ¥ symbol
+**Validates: Requirements 3.4**
+
+### Property 5: Tab State Synchronization
+
+*For any* tab selection change, the active tab indicator should update and the corresponding settlement data should be loaded matching the selected status
+**Validates: Requirements 5.4**
+
+### Property 6: Button Interaction Feedback
+
+*For any* button press event, the button should apply 0.8 opacity during the active state to provide visual feedback
+**Validates: Requirements 4.3**
+
+### Property 7: Overdue Badge Display Logic
+
+*For any* merchant group, the overdue badge should be displayed if and only if at least one settlement in that group has status OVERDUE
+**Validates: Requirements 2.3**
+
+### Property 8: Empty State Rendering
+
+*For any* state where the grouped merchant list is empty, the empty state component should be displayed with centered alignment and appropriate messaging
+**Validates: Requirements 6.1, 6.2, 6.3, 6.4**
+
+### Property 9: Spacing Consistency
+
+*For any* rendered card or container element, the padding and margin values should follow the 4rpx-based spacing system (20rpx, 24rpx, etc.)
+**Validates: Requirements 7.1, 7.2, 7.3**
+
+### Property 10: Color Semantic Consistency
+
+*For any* UI element representing a specific semantic meaning (primary action, amount, success, warning, danger), the color used should match the defined color palette
+**Validates: Requirements 8.1, 8.2, 8.3, 8.4, 8.5, 8.6**
+
+## Error Handling
+
+### 数据加载错误
+
+- 使用 uni.showToast 显示错误提示
+- 保持当前状态,允许用户重试
+- 下拉刷新作为恢复机制
+
+### 消账提交错误
+
+- 显示具体错误信息
+- 保持弹窗打开,允许用户修改后重试
+- 使用 loading 状态防止重复提交
+
+### 空数据处理
+
+- 显示友好的空状态页面
+- 提供明确的提示信息
+- 不显示错误,因为空数据是正常状态
+
+## Testing Strategy
+
+### Unit Testing
+
+本项目将使用 **Vitest** 作为单元测试框架,配合 **@vue/test-utils** 进行 Vue 组件测试。
+
+**测试范围**:
+
+1. **计算属性测试**
+ - `groupedByMerchant`: 验证商户分组逻辑正确性
+ - `currentWriteOffAmount`: 验证单个/批量消账金额计算
+
+2. **方法测试**
+ - `getStatusText()`: 验证状态文本映射
+ - `getStatusClass()`: 验证状态样式类映射
+ - `handleTabChange()`: 验证标签切换逻辑
+
+3. **边界情况**
+ - 空数据列表
+ - 单个商户多个订单
+ - 全部逾期订单
+
+### Property-Based Testing
+
+本项目将使用 **fast-check** 作为属性测试库,验证通用正确性属性。
+
+**配置要求**:
+- 每个属性测试至少运行 100 次迭代
+- 使用明确的注释标记关联的设计文档属性
+
+**测试属性**:
+
+每个属性测试必须使用以下格式的注释标记:
+```typescript
+// **Feature: settlement-ui-optimization, Property {number}: {property_text}**
+```
+
+**属性测试列表**:
+
+1. **Property 1**: Due Alert Visibility Consistency
+2. **Property 2**: Merchant Grouping Completeness
+3. **Property 3**: Status Badge Color Mapping
+4. **Property 4**: Amount Display Formatting
+5. **Property 5**: Tab State Synchronization
+6. **Property 6**: Button Interaction Feedback
+7. **Property 7**: Overdue Badge Display Logic
+8. **Property 8**: Empty State Rendering
+9. **Property 9**: Spacing Consistency
+10. **Property 10**: Color Semantic Consistency
+
+### 测试工具和依赖
+
+```json
+{
+ "devDependencies": {
+ "vitest": "^1.0.0",
+ "@vue/test-utils": "^2.4.0",
+ "fast-check": "^3.15.0",
+ "@vitest/ui": "^1.0.0"
+ }
+}
+```
+
+### 测试命令
+
+```bash
+# 运行所有测试
+pnpm test
+
+# 运行单元测试
+pnpm test:unit
+
+# 运行属性测试
+pnpm test:property
+
+# 测试覆盖率
+pnpm test:coverage
+```
+
+## Implementation Notes
+
+### 样式优化重点
+
+1. **颜色系统**
+ - 主色:#1890ff (蓝色)
+ - 金额:#ff4d4f (红色)
+ - 成功:#52c41a (绿色)
+ - 警告:#faad14 (琥珀色)
+ - 文本层次:#333 / #666 / #999
+
+2. **间距系统**
+ - 基础单位:4rpx
+ - 常用值:20rpx, 24rpx, 32rpx
+ - 卡片间距:20rpx
+ - 内容内边距:24rpx
+
+3. **圆角规范**
+ - 卡片:16rpx
+ - 按钮:8rpx
+ - 徽章:4rpx
+
+4. **阴影效果**
+ - 卡片:0 2rpx 8rpx rgba(0, 0, 0, 0.05)
+ - 固定导航:0 2rpx 8rpx rgba(0, 0, 0, 0.05)
+
+### 性能考虑
+
+- 使用 `v-if` 而非 `v-show` 处理大列表的条件渲染
+- 商户分组减少渲染节点数量
+- 横向滚动使用 `scroll-view` 组件优化性能
+
+### 可访问性
+
+- 保持足够的颜色对比度(WCAG AA 标准)
+- 使用语义化的图标
+- 提供清晰的状态反馈
+
+### 响应式设计
+
+- 使用 rpx 单位适配不同屏幕
+- 固定导航栏考虑 H5 环境的顶部偏移
+- 横向滚动适配小屏幕设备
diff --git a/.kiro/specs/settlement-ui-optimization/requirements.md b/.kiro/specs/settlement-ui-optimization/requirements.md
new file mode 100644
index 0000000..ba6d756
--- /dev/null
+++ b/.kiro/specs/settlement-ui-optimization/requirements.md
@@ -0,0 +1,107 @@
+# Requirements Document
+
+## Introduction
+
+本文档定义了结算页面(应结账款)UI优化的需求。当前页面存在视觉层次不清晰、信息密度过高、交互反馈不足等问题。本次优化旨在提升用户体验,使页面更加清晰、易用、美观。
+
+## Glossary
+
+- **Settlement System**: 结算系统,管理商户应收账款的系统
+- **Merchant Group**: 商户分组,按商户ID聚合的账款集合
+- **Settlement Item**: 结算项,单个订单的应结账款记录
+- **Write-off**: 消账,申请结算账款的操作
+- **Due Alert**: 到期提醒,显示即将到期账款的警告组件
+- **Tab Navigation**: 标签导航,用于切换未结/已结状态的导航组件
+
+## Requirements
+
+### Requirement 1
+
+**User Story:** 作为用户,我希望到期提醒区域更加醒目且易于理解,以便快速识别需要关注的账款
+
+#### Acceptance Criteria
+
+1. WHEN the Due Alert component is displayed THEN the Settlement System SHALL use a distinct warning color scheme with amber background and red accent
+2. WHEN the Due Alert contains multiple items THEN the Settlement System SHALL display them in a horizontally scrollable list with clear visual separation
+3. WHEN a due item is rendered THEN the Settlement System SHALL display the due date and amount with appropriate font sizes and color contrast
+4. WHEN the Due Alert has no items THEN the Settlement System SHALL hide the component completely
+
+### Requirement 2
+
+**User Story:** 作为用户,我希望商户分组卡片具有更好的视觉层次,以便快速区分不同商户的账款信息
+
+#### Acceptance Criteria
+
+1. WHEN a Merchant Group card is rendered THEN the Settlement System SHALL apply rounded corners with 16rpx radius and subtle shadow
+2. WHEN the merchant header is displayed THEN the Settlement System SHALL use a gradient background to distinguish it from the content area
+3. WHEN a merchant has overdue settlements THEN the Settlement System SHALL display a prominent red badge next to the merchant name
+4. WHEN the merchant total amount is shown THEN the Settlement System SHALL use large bold red text with minimum 32rpx font size
+
+### Requirement 3
+
+**User Story:** 作为用户,我希望订单列表项具有清晰的视觉分隔和信息层次,以便快速浏览和理解账款详情
+
+#### Acceptance Criteria
+
+1. WHEN Settlement Items are displayed in a list THEN the Settlement System SHALL separate each item with a 1rpx border or 20rpx spacing
+2. WHEN an order number is shown THEN the Settlement System SHALL display it in gray color with 26rpx font size
+3. WHEN status badges are rendered THEN the Settlement System SHALL use color-coded backgrounds (green for settled, amber for unsettled, red for overdue)
+4. WHEN amount values are displayed THEN the Settlement System SHALL use red color and bold weight for emphasis
+5. WHEN date information is shown THEN the Settlement System SHALL use consistent formatting and gray color for labels
+
+### Requirement 4
+
+**User Story:** 作为用户,我希望操作按钮具有明确的视觉反馈和层次,以便清楚地知道可以执行哪些操作
+
+#### Acceptance Criteria
+
+1. WHEN the batch write-off button is displayed THEN the Settlement System SHALL use a full-width blue gradient button with 16rpx vertical padding
+2. WHEN the single write-off button is displayed THEN the Settlement System SHALL use an outlined style with blue border and text
+3. WHEN a button is pressed THEN the Settlement System SHALL apply 0.8 opacity for visual feedback
+4. WHEN buttons are rendered THEN the Settlement System SHALL use rounded corners with minimum 8rpx radius
+
+### Requirement 5
+
+**User Story:** 作为用户,我希望标签导航清晰且响应迅速,以便在未结和已结账款之间快速切换
+
+#### Acceptance Criteria
+
+1. WHEN the Tab Navigation is displayed THEN the Settlement System SHALL fix it at the top of the viewport with white background
+2. WHEN a tab is active THEN the Settlement System SHALL display a blue underline indicator with 4rpx height
+3. WHEN tab text is rendered THEN the Settlement System SHALL use 28rpx font size with gray color for inactive and blue for active
+4. WHEN a tab is clicked THEN the Settlement System SHALL update the active state and load corresponding data
+
+### Requirement 6
+
+**User Story:** 作为用户,我希望空状态页面友好且信息明确,以便了解当前没有数据的原因
+
+#### Acceptance Criteria
+
+1. WHEN no settlement data exists THEN the Settlement System SHALL display a centered empty state component
+2. WHEN the empty state is shown THEN the Settlement System SHALL include an icon with 4xl size and gray color
+3. WHEN the empty message is displayed THEN the Settlement System SHALL use 28rpx font size with gray color
+4. WHEN the empty state is rendered THEN the Settlement System SHALL position it with 200rpx top padding
+
+### Requirement 7
+
+**User Story:** 作为用户,我希望整体页面具有一致的间距和对齐,以便获得专业和舒适的视觉体验
+
+#### Acceptance Criteria
+
+1. WHEN page content is rendered THEN the Settlement System SHALL use consistent 20rpx padding for main containers
+2. WHEN cards are displayed THEN the Settlement System SHALL apply 20rpx bottom margin for vertical spacing
+3. WHEN internal card content is shown THEN the Settlement System SHALL use 24rpx horizontal padding
+4. WHEN text elements are aligned THEN the Settlement System SHALL maintain consistent baseline alignment across rows
+
+### Requirement 8
+
+**User Story:** 作为用户,我希望颜色使用符合语义且具有良好的可读性,以便快速理解信息的重要性和状态
+
+#### Acceptance Criteria
+
+1. WHEN primary actions are displayed THEN the Settlement System SHALL use blue (#1890ff) as the primary color
+2. WHEN amounts are shown THEN the Settlement System SHALL use red (#ff4d4f) to indicate financial values
+3. WHEN success states are indicated THEN the Settlement System SHALL use green (#52c41a) with light green background
+4. WHEN warning states are indicated THEN the Settlement System SHALL use amber (#faad14) with light yellow background
+5. WHEN danger states are indicated THEN the Settlement System SHALL use red (#ff4d4f) with light red background
+6. WHEN text hierarchy is established THEN the Settlement System SHALL use #333 for primary text, #666 for secondary, and #999 for tertiary
diff --git a/.kiro/specs/settlement-ui-optimization/tasks.md b/.kiro/specs/settlement-ui-optimization/tasks.md
new file mode 100644
index 0000000..9daa057
--- /dev/null
+++ b/.kiro/specs/settlement-ui-optimization/tasks.md
@@ -0,0 +1,111 @@
+# Implementation Plan
+
+
+
+
+ - 创建 SCSS 变量文件定义颜色系统、间距系统、圆角规范
+ - 定义可复用的 mixin(卡片样式、按钮样式、徽章样式)
+ - 确保所有设计令牌符合设计文档规范
+ - _Requirements: 7.1, 7.2, 7.3, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6_
+
+- [ ] 2. 优化到期提醒区域(DueAlert)
+
+
+ - 实现琥珀色背景和警告图标的视觉设计
+ - 创建横向滚动的到期项列表布局
+ - 优化到期日期和金额的字体大小和颜色对比
+ - 实现空状态时隐藏组件的逻辑
+ - _Requirements: 1.1, 1.2, 1.3, 1.4_
+
+- [ ]* 2.1 编写 Property 1 的属性测试
+ - **Property 1: Due Alert Visibility Consistency**
+ - **Validates: Requirements 1.4**
+
+- [ ] 3. 优化标签导航(TabNavigation)
+ - 实现固定在顶部的导航栏样式
+ - 添加活动标签的蓝色下划线指示器
+ - 优化标签文字的字体大小和颜色状态
+ - 实现标签切换的平滑过渡动画
+ - _Requirements: 5.1, 5.2, 5.3, 5.4_
+
+- [ ]* 3.1 编写 Property 5 的属性测试
+ - **Property 5: Tab State Synchronization**
+ - **Validates: Requirements 5.4**
+
+- [ ] 4. 优化商户分组卡片(MerchantGroup)
+ - 实现圆角卡片样式(16rpx)和阴影效果
+ - 创建渐变背景的商户头部设计
+ - 实现逾期徽章的显示逻辑和样式
+ - 优化商户总金额的字体大小和颜色(大号粗体红色)
+ - _Requirements: 2.1, 2.2, 2.3, 2.4_
+
+- [ ]* 4.1 编写 Property 2 的属性测试
+ - **Property 2: Merchant Grouping Completeness**
+ - **Validates: Requirements 2.1**
+
+- [ ]* 4.2 编写 Property 7 的属性测试
+ - **Property 7: Overdue Badge Display Logic**
+ - **Validates: Requirements 2.3**
+
+- [ ] 5. 优化订单列表项(OrderItem)
+ - 实现订单项之间的视觉分隔(边框或间距)
+ - 优化订单号的字体大小和颜色
+ - 实现状态徽章的颜色编码系统(绿色/琥珀色/红色)
+ - 优化金额显示的字体粗细和颜色
+ - 统一日期信息的格式和标签颜色
+ - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
+
+- [ ]* 5.1 编写 Property 3 的属性测试
+ - **Property 3: Status Badge Color Mapping**
+ - **Validates: Requirements 3.3**
+
+- [ ]* 5.2 编写 Property 4 的属性测试
+ - **Property 4: Amount Display Formatting**
+ - **Validates: Requirements 3.4**
+
+- [ ] 6. 优化操作按钮样式
+ - 实现批量消账按钮的全宽蓝色渐变样式
+ - 实现单个消账按钮的描边样式
+ - 添加按钮按下时的透明度反馈(0.8)
+ - 统一按钮圆角规范(最小 8rpx)
+ - _Requirements: 4.1, 4.2, 4.3, 4.4_
+
+- [ ]* 6.1 编写 Property 6 的属性测试
+ - **Property 6: Button Interaction Feedback**
+ - **Validates: Requirements 4.3**
+
+- [ ] 7. 优化空状态页面
+ - 实现居中对齐的空状态组件布局
+ - 添加大尺寸灰色图标(4xl)
+ - 优化空状态消息的字体大小和颜色
+ - 设置适当的顶部内边距(200rpx)
+ - _Requirements: 6.1, 6.2, 6.3, 6.4_
+
+- [ ]* 7.1 编写 Property 8 的属性测试
+ - **Property 8: Empty State Rendering**
+ - **Validates: Requirements 6.1, 6.2, 6.3, 6.4**
+
+- [ ] 8. 全局样式调整和一致性检查
+ - 验证所有容器使用一致的 20rpx 内边距
+ - 验证所有卡片使用 20rpx 底部外边距
+ - 验证卡片内容使用 24rpx 水平内边距
+ - 检查文本元素的基线对齐一致性
+ - _Requirements: 7.1, 7.2, 7.3, 7.4_
+
+- [ ]* 8.1 编写 Property 9 的属性测试
+ - **Property 9: Spacing Consistency**
+ - **Validates: Requirements 7.1, 7.2, 7.3**
+
+- [ ]* 8.2 编写 Property 10 的属性测试
+ - **Property 10: Color Semantic Consistency**
+ - **Validates: Requirements 8.1, 8.2, 8.3, 8.4, 8.5, 8.6**
+
+- [ ] 9. 响应式和性能优化
+ - 验证 rpx 单位在不同屏幕尺寸下的适配效果
+ - 优化固定导航栏在 H5 环境的顶部偏移
+ - 确保横向滚动在小屏幕设备上流畅运行
+ - 验证大列表使用 v-if 的条件渲染性能
+ - _Requirements: 所有需求的性能和响应式方面_
+
+- [ ] 10. 最终检查点 - 确保所有测试通过
+ - 确保所有测试通过,如有问题请询问用户
diff --git a/src/api/address.ts b/src/api/address.ts
new file mode 100644
index 0000000..024a2d2
--- /dev/null
+++ b/src/api/address.ts
@@ -0,0 +1,94 @@
+import { mockAddressList } from '@/mock/address'
+import type { Address } from '@/typings/mall'
+
+/**
+ * 地址相关 API
+ */
+
+// 获取地址列表
+export function getAddressList() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: mockAddressList,
+ })
+ }, 300)
+ })
+}
+
+// 添加地址
+export function addAddress(data: Omit
) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const newAddress = {
+ ...data,
+ id: `addr_${Date.now()}`,
+ }
+ mockAddressList.push(newAddress)
+ resolve({
+ code: 0,
+ data: newAddress,
+ message: '添加成功',
+ })
+ }, 300)
+ })
+}
+
+// 编辑地址
+export function updateAddress(data: Address) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const index = mockAddressList.findIndex(item => item.id === data.id)
+ if (index > -1) {
+ mockAddressList[index] = data
+ resolve({
+ code: 0,
+ data,
+ message: '修改成功',
+ })
+ } else {
+ resolve({
+ code: 1,
+ message: '地址不存在',
+ })
+ }
+ }, 300)
+ })
+}
+
+// 删除地址
+export function deleteAddress(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const index = mockAddressList.findIndex(item => item.id === id)
+ if (index > -1) {
+ mockAddressList.splice(index, 1)
+ resolve({
+ code: 0,
+ message: '删除成功',
+ })
+ } else {
+ resolve({
+ code: 1,
+ message: '地址不存在',
+ })
+ }
+ }, 300)
+ })
+}
+
+// 设置默认地址
+export function setDefaultAddress(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ mockAddressList.forEach(item => {
+ item.isDefault = item.id === id
+ })
+ resolve({
+ code: 0,
+ message: '设置成功',
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/auth.ts b/src/api/auth.ts
new file mode 100644
index 0000000..8a047e8
--- /dev/null
+++ b/src/api/auth.ts
@@ -0,0 +1,57 @@
+import { mockMember } from '@/mock/member'
+import type { User } from '@/typings/mall'
+
+/**
+ * 认证相关 API
+ */
+
+// 登录
+export function login(data: { phone: string, code?: string, password?: string }) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ // 模拟登录成功
+ const user: User = {
+ id: 'user_001',
+ username: data.phone,
+ nickname: `用户${data.phone.slice(-4)}`,
+ avatar: 'https://picsum.photos/200/200?random=avatar',
+ phone: data.phone,
+ creditLimits: [],
+ member: mockMember,
+ }
+
+ resolve({
+ code: 0,
+ data: {
+ token: 'mock_token_123456',
+ user,
+ },
+ message: '登录成功',
+ })
+ }, 500)
+ })
+}
+
+// 发送验证码
+export function sendCode(phone: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ message: '验证码发送成功',
+ })
+ }, 300)
+ })
+}
+
+// 退出登录
+export function logout() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ message: '退出成功',
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/banner.ts b/src/api/banner.ts
new file mode 100644
index 0000000..7ceb268
--- /dev/null
+++ b/src/api/banner.ts
@@ -0,0 +1,17 @@
+import { mockBannerList } from '@/mock/banner'
+
+/**
+ * 轮播图相关 API
+ */
+
+// 获取轮播图列表
+export function getBannerList() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: mockBannerList,
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/category.ts b/src/api/category.ts
new file mode 100644
index 0000000..a5a060c
--- /dev/null
+++ b/src/api/category.ts
@@ -0,0 +1,30 @@
+import { mockCategoryList } from '@/mock/category'
+
+/**
+ * 分类相关 API
+ */
+
+// 获取分类列表
+export function getCategoryList() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: mockCategoryList,
+ })
+ }, 300)
+ })
+}
+
+// 获取分类详情
+export function getCategoryDetail(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const category = mockCategoryList.find(item => item.id === id)
+ resolve({
+ code: 0,
+ data: category || null,
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/finance.ts b/src/api/finance.ts
new file mode 100644
index 0000000..423980a
--- /dev/null
+++ b/src/api/finance.ts
@@ -0,0 +1,125 @@
+import { mockCreditLimitList, mockSettlementList, mockWriteOffList } from '@/mock/finance'
+import { WriteOffStatus } from '@/typings/mall'
+import type { SettlementStatus, WriteOff } from '@/typings/mall'
+
+/**
+ * 金融相关 API
+ */
+
+// 获取信用额度
+export function getCreditLimit() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: mockCreditLimitList,
+ })
+ }, 300)
+ })
+}
+
+// 获取应结账款列表
+export function getSettlementList(params?: {
+ status?: SettlementStatus
+ merchantId?: string
+}) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ let list = [...mockSettlementList]
+
+ // 筛选
+ if (params?.status) {
+ // 如果查询未结,则包含逾期状态
+ if (params.status === 'unsettled') {
+ list = list.filter(item => item.status === 'unsettled' || item.status === 'overdue')
+ } else {
+ list = list.filter(item => item.status === params.status)
+ }
+ }
+ if (params?.merchantId) {
+ list = list.filter(item => item.merchantId === params.merchantId)
+ }
+
+ resolve({
+ code: 0,
+ data: list,
+ })
+ }, 300)
+ })
+}
+
+// 获取到期订单
+export function getDueOrders() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const now = new Date()
+ // 7天后
+ const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
+
+ // 筛选7天内到期的未结账款(包括已逾期)
+ const list = mockSettlementList.filter((item) => {
+ // 只关注未结和逾期状态
+ if (item.status !== 'unsettled' && item.status !== 'overdue')
+ return false
+
+ const dueDate = new Date(item.dueDate)
+ // 只要到期时间在7天内(包括过去的时间,即已逾期),都应该提醒
+ return dueDate <= sevenDaysLater
+ })
+
+ resolve({
+ code: 0,
+ data: list,
+ })
+ }, 300)
+ })
+}
+
+// 提交消账申请
+export function submitWriteOff(data: {
+ settlementId: string
+ amount: number
+ proof: string[]
+ remark: string
+}) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const newWriteOff: WriteOff = {
+ id: `writeoff_${Date.now()}`,
+ settlementId: data.settlementId,
+ amount: data.amount,
+ proof: data.proof,
+ remark: data.remark,
+ submitTime: new Date().toISOString(),
+ status: WriteOffStatus.PENDING,
+ }
+
+ // 模拟添加到列表
+ mockWriteOffList.push(newWriteOff)
+
+ resolve({
+ code: 0,
+ data: newWriteOff,
+ message: '提交成功,等待审核',
+ })
+ }, 500)
+ })
+}
+
+// 获取消账记录
+export function getWriteOffList(settlementId?: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ let list = [...mockWriteOffList]
+
+ if (settlementId) {
+ list = list.filter(item => item.settlementId === settlementId)
+ }
+
+ resolve({
+ code: 0,
+ data: list,
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/goods.ts b/src/api/goods.ts
new file mode 100644
index 0000000..be49a37
--- /dev/null
+++ b/src/api/goods.ts
@@ -0,0 +1,95 @@
+import { mockGoodsList } from '@/mock/goods'
+import type { Goods } from '@/typings/mall'
+
+/**
+ * 商品相关 API
+ */
+
+// 获取商品列表(支持分页、筛选)
+export function getGoodsList(params: {
+ page?: number
+ pageSize?: number
+ categoryId?: string
+ keyword?: string
+ sortBy?: 'sales' | 'price' | 'new'
+ sortOrder?: 'asc' | 'desc'
+}) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ let list = [...mockGoodsList]
+
+ // 筛选
+ if (params.categoryId) {
+ list = list.filter(item => item.categoryId === params.categoryId)
+ }
+ if (params.keyword) {
+ list = list.filter(item => item.name.includes(params.keyword))
+ }
+
+ // 排序
+ if (params.sortBy) {
+ list.sort((a, b) => {
+ let compareValue = 0
+ if (params.sortBy === 'sales') {
+ compareValue = a.sales - b.sales
+ }
+ else if (params.sortBy === 'price') {
+ compareValue = a.price - b.price
+ }
+ else if (params.sortBy === 'new') {
+ compareValue = a.id.localeCompare(b.id)
+ }
+ return params.sortOrder === 'desc' ? -compareValue : compareValue
+ })
+ }
+
+ // 分页
+ const page = params.page || 1
+ const pageSize = params.pageSize || 10
+ const start = (page - 1) * pageSize
+ const end = start + pageSize
+
+ resolve({
+ code: 0,
+ data: {
+ list: list.slice(start, end),
+ total: list.length,
+ page,
+ pageSize,
+ },
+ })
+ }, 300)
+ })
+}
+
+// 获取商品详情
+export function getGoodsDetail(id: string) {
+ return new Promise<{ code: number, data: Goods | null }>((resolve) => {
+ setTimeout(() => {
+ const goods = mockGoodsList.find(item => item.id === id)
+ resolve({ code: 0, data: goods || null })
+ }, 300)
+ })
+}
+
+// 搜索商品
+export function searchGoods(keyword: string) {
+ return getGoodsList({ keyword, pageSize: 20 })
+}
+
+// 获取推荐商品
+export function getRecommendGoods(limit = 10) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ // 按销量排序,取前 N 个
+ const list = [...mockGoodsList]
+ .sort((a, b) => b.sales - a.sales)
+ .slice(0, limit)
+
+ resolve({
+ code: 0,
+ data: list,
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/member.ts b/src/api/member.ts
new file mode 100644
index 0000000..778b5c9
--- /dev/null
+++ b/src/api/member.ts
@@ -0,0 +1,29 @@
+import { mockMember, memberLevelConfig } from '@/mock/member'
+
+/**
+ * 会员相关 API
+ */
+
+// 获取会员信息
+export function getMemberInfo() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: mockMember,
+ })
+ }, 300)
+ })
+}
+
+// 获取会员权益
+export function getMemberBenefits() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ code: 0,
+ data: memberLevelConfig[mockMember.level].benefits,
+ })
+ }, 300)
+ })
+}
diff --git a/src/api/order.ts b/src/api/order.ts
new file mode 100644
index 0000000..b5fe4f6
--- /dev/null
+++ b/src/api/order.ts
@@ -0,0 +1,112 @@
+import { OrderStatus } from '@/typings/mall'
+import type { Order } from '@/typings/mall'
+
+// 模拟订单列表
+const mockOrderList: Order[] = []
+
+/**
+ * 订单相关 API
+ */
+
+// 创建订单
+export function createOrder(data: Omit) {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ // 如果是信用支付
+ if (data.paymentMethod === 'credit') {
+ // 模拟检查额度(这里简单模拟,实际应调用金融服务)
+ // 假设额度总是足够的,或者在前端已经校验过了
+ // 创建应结账款记录(模拟)
+ console.log('创建信用支付订单,生成应结账款...')
+ }
+
+ const newOrder: Order = {
+ ...data,
+ id: `order_${Date.now()}`,
+ orderNo: `ORD${Date.now()}`,
+ status: data.paymentMethod === 'credit' ? OrderStatus.PENDING_DELIVERY : OrderStatus.PENDING_PAYMENT, // 信用支付直接待发货
+ createTime: new Date().toISOString(),
+ isSettled: false,
+ payTime: data.paymentMethod === 'credit' ? new Date().toISOString() : undefined,
+ }
+ mockOrderList.unshift(newOrder)
+ resolve({
+ code: 0,
+ data: newOrder,
+ message: '订单创建成功',
+ })
+ }, 500)
+ })
+}
+
+// 获取订单列表
+export function getOrderList(status?: OrderStatus) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ let list = [...mockOrderList]
+ if (status) {
+ list = list.filter(item => item.status === status)
+ }
+ resolve({
+ code: 0,
+ data: list,
+ })
+ }, 300)
+ })
+}
+
+// 获取订单详情
+export function getOrderDetail(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const order = mockOrderList.find(item => item.id === id)
+ resolve({
+ code: 0,
+ data: order || null,
+ })
+ }, 300)
+ })
+}
+
+// 取消订单
+export function cancelOrder(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const order = mockOrderList.find(item => item.id === id)
+ if (order) {
+ order.status = 'cancelled' as OrderStatus
+ resolve({
+ code: 0,
+ message: '订单已取消',
+ })
+ } else {
+ resolve({
+ code: 1,
+ message: '订单不存在',
+ })
+ }
+ }, 300)
+ })
+}
+
+// 支付订单(模拟)
+export function payOrder(id: string) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const order = mockOrderList.find(item => item.id === id)
+ if (order) {
+ order.status = 'pending_delivery' as OrderStatus // 支付后变为待发货
+ order.payTime = new Date().toISOString()
+ resolve({
+ code: 0,
+ message: '支付成功',
+ })
+ } else {
+ resolve({
+ code: 1,
+ message: '订单不存在',
+ })
+ }
+ }, 500)
+ })
+}
diff --git a/src/components/cart/CartItem.vue b/src/components/cart/CartItem.vue
new file mode 100644
index 0000000..024c496
--- /dev/null
+++ b/src/components/cart/CartItem.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.goodsName }}
+
+ {{ specText }}
+
+
+ ¥{{ item.price }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/cart/CartSummary.vue b/src/components/cart/CartSummary.vue
new file mode 100644
index 0000000..5ddfa40
--- /dev/null
+++ b/src/components/cart/CartSummary.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+ 全选
+
+
+
+
+ 合计:
+ ¥{{ totalPrice.toFixed(2) }}
+
+
+ 结算({{ totalCount }})
+
+
+
+
+
+
+
+
diff --git a/src/components/common/Banner.vue b/src/components/common/Banner.vue
new file mode 100644
index 0000000..0fc54ef
--- /dev/null
+++ b/src/components/common/Banner.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/common/CategoryGrid.vue b/src/components/common/CategoryGrid.vue
new file mode 100644
index 0000000..083d04e
--- /dev/null
+++ b/src/components/common/CategoryGrid.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
diff --git a/src/components/common/CounterInput.vue b/src/components/common/CounterInput.vue
new file mode 100644
index 0000000..5bb63c4
--- /dev/null
+++ b/src/components/common/CounterInput.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/common/PriceTag.vue b/src/components/common/PriceTag.vue
new file mode 100644
index 0000000..17b7fa6
--- /dev/null
+++ b/src/components/common/PriceTag.vue
@@ -0,0 +1,89 @@
+
+
+
+ ¥{{ formatPrice(originalPrice) }}
+
+
+ ¥
+ {{ priceInteger }}
+ .{{ priceDecimal }}
+
+
+
+
+
+
+
diff --git a/src/components/common/SearchBar.vue b/src/components/common/SearchBar.vue
new file mode 100644
index 0000000..c138e5e
--- /dev/null
+++ b/src/components/common/SearchBar.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+
+
+
+
diff --git a/src/components/finance/CreditCard.vue b/src/components/finance/CreditCard.vue
new file mode 100644
index 0000000..0ce5880
--- /dev/null
+++ b/src/components/finance/CreditCard.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+ 总额度
+ ¥{{ formatPrice(totalLimit) }}
+
+
+ 可用额度
+ ¥{{ formatPrice(availableLimit) }}
+
+
+
+
+
+
+
+
+
+ 已用 {{ percent }}%
+ ¥{{ formatPrice(usedLimit) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/finance/SettlementItem.vue b/src/components/finance/SettlementItem.vue
new file mode 100644
index 0000000..05b1923
--- /dev/null
+++ b/src/components/finance/SettlementItem.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+ 商户名称
+ {{ item.merchantName }}
+
+
+ 应结金额
+ ¥{{ item.amount.toFixed(2) }}
+
+
+ 到期日期
+ {{ item.dueDate }}
+
+
+ 结算日期
+ {{ item.settlementDate }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/finance/WriteOffForm.vue b/src/components/finance/WriteOffForm.vue
new file mode 100644
index 0000000..003f3ce
--- /dev/null
+++ b/src/components/finance/WriteOffForm.vue
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
diff --git a/src/components/goods/GoodsCard.vue b/src/components/goods/GoodsCard.vue
new file mode 100644
index 0000000..04910a5
--- /dev/null
+++ b/src/components/goods/GoodsCard.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+ {{ goods.shopName }}
+
+ {{ goods.name }}
+
+ {{ tag }}
+
+
+
+ 已售{{ formatSales(goods.sales) }}
+
+
+
+
+
+
+
+
diff --git a/src/components/goods/SpecSelector.vue b/src/components/goods/SpecSelector.vue
new file mode 100644
index 0000000..606a369
--- /dev/null
+++ b/src/components/goods/SpecSelector.vue
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
diff --git a/src/components/member/MemberBenefits.vue b/src/components/member/MemberBenefits.vue
new file mode 100644
index 0000000..21235c0
--- /dev/null
+++ b/src/components/member/MemberBenefits.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ {{ item }}
+
+
+
+
+
+
+
diff --git a/src/components/member/MemberCard.vue b/src/components/member/MemberCard.vue
new file mode 100644
index 0000000..285a025
--- /dev/null
+++ b/src/components/member/MemberCard.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mock/address.ts b/src/mock/address.ts
new file mode 100644
index 0000000..5e8a81e
--- /dev/null
+++ b/src/mock/address.ts
@@ -0,0 +1,37 @@
+import type { Address } from '@/typings/mall'
+
+/**
+ * 地址模拟数据
+ */
+export const mockAddressList: Address[] = [
+ {
+ id: 'addr_001',
+ name: '张三',
+ phone: '13800138000',
+ province: '广东省',
+ city: '深圳市',
+ district: '南山区',
+ detail: '科技园南区深南大道10000号',
+ isDefault: true,
+ },
+ {
+ id: 'addr_002',
+ name: '李四',
+ phone: '13900139000',
+ province: '广东省',
+ city: '广州市',
+ district: '天河区',
+ detail: '珠江新城花城大道88号',
+ isDefault: false,
+ },
+ {
+ id: 'addr_003',
+ name: '王五',
+ phone: '13700137000',
+ province: '北京市',
+ city: '北京市',
+ district: '朝阳区',
+ detail: '建国路99号',
+ isDefault: false,
+ },
+]
diff --git a/src/mock/banner.ts b/src/mock/banner.ts
new file mode 100644
index 0000000..e414ef5
--- /dev/null
+++ b/src/mock/banner.ts
@@ -0,0 +1,31 @@
+import type { Banner } from '@/typings/mall'
+
+/**
+ * 轮播图模拟数据
+ */
+export const mockBannerList: Banner[] = [
+ {
+ id: 'banner_001',
+ image: 'https://picsum.photos/750/400?random=banner1',
+ title: '春季新品上市',
+ goodsId: 'goods_001',
+ },
+ {
+ id: 'banner_002',
+ image: 'https://picsum.photos/750/400?random=banner2',
+ title: '数码产品大促',
+ goodsId: 'goods_004',
+ },
+ {
+ id: 'banner_003',
+ image: 'https://picsum.photos/750/400?random=banner3',
+ title: '美妆护肤专场',
+ goodsId: 'goods_010',
+ },
+ {
+ id: 'banner_004',
+ image: 'https://picsum.photos/750/400?random=banner4',
+ title: '家居好物推荐',
+ goodsId: 'goods_008',
+ },
+]
diff --git a/src/mock/category.ts b/src/mock/category.ts
new file mode 100644
index 0000000..066954b
--- /dev/null
+++ b/src/mock/category.ts
@@ -0,0 +1,55 @@
+import type { Category } from '@/typings/mall'
+
+/**
+ * 分类模拟数据
+ */
+export const mockCategoryList: Category[] = [
+ {
+ id: 'cat_001',
+ name: '服装',
+ icon: 'i-carbon-clothing',
+ cover: 'https://picsum.photos/200/200?random=cat1',
+ },
+ {
+ id: 'cat_002',
+ name: '数码',
+ icon: 'i-carbon-phone',
+ cover: 'https://picsum.photos/200/200?random=cat2',
+ },
+ {
+ id: 'cat_003',
+ name: '食品',
+ icon: 'i-carbon-restaurant',
+ cover: 'https://picsum.photos/200/200?random=cat3',
+ },
+ {
+ id: 'cat_004',
+ name: '家居',
+ icon: 'i-carbon-home',
+ cover: 'https://picsum.photos/200/200?random=cat4',
+ },
+ {
+ id: 'cat_005',
+ name: '美妆',
+ icon: 'i-carbon-face-satisfied',
+ cover: 'https://picsum.photos/200/200?random=cat5',
+ },
+ {
+ id: 'cat_006',
+ name: '运动',
+ icon: 'i-carbon-basketball',
+ cover: 'https://picsum.photos/200/200?random=cat6',
+ },
+ {
+ id: 'cat_007',
+ name: '图书',
+ icon: 'i-carbon-book',
+ cover: 'https://picsum.photos/200/200?random=cat7',
+ },
+ {
+ id: 'cat_008',
+ name: '母婴',
+ icon: 'i-carbon-baby',
+ cover: 'https://picsum.photos/200/200?random=cat8',
+ },
+]
diff --git a/src/mock/finance.ts b/src/mock/finance.ts
new file mode 100644
index 0000000..9a1070e
--- /dev/null
+++ b/src/mock/finance.ts
@@ -0,0 +1,168 @@
+import { SettlementStatus, WriteOffStatus } from '@/typings/mall'
+import type { CreditLimit, Settlement, WriteOff } from '@/typings/mall'
+
+/**
+ * 信用额度模拟数据
+ */
+export const mockCreditLimitList: CreditLimit[] = [
+ {
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ totalLimit: 100000,
+ usedLimit: 99900,
+ availableLimit: 100,
+ updateTime: '2025-11-28 10:00:00',
+ },
+ {
+ merchantId: 'merchant_b',
+ merchantName: '商户B',
+ totalLimit: 50000,
+ usedLimit: 12000,
+ availableLimit: 38000,
+ updateTime: '2025-11-28 10:00:00',
+ },
+]
+
+/**
+ * 应结账款模拟数据
+ */
+export const mockSettlementList: Settlement[] = [
+ // 未结账款
+ {
+ id: 'settlement_001',
+ orderNo: 'ORD20251128001',
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ amount: 5000,
+ status: SettlementStatus.UNSETTLED,
+ dueDate: '2025-12-15',
+ relatedOrders: ['ORD20251128001', 'ORD20251128002'],
+ },
+ {
+ id: 'settlement_002',
+ orderNo: 'ORD20251125001',
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ amount: 8000,
+ status: SettlementStatus.UNSETTLED,
+ dueDate: '2025-12-10',
+ relatedOrders: ['ORD20251125001'],
+ },
+ {
+ id: 'settlement_003',
+ orderNo: 'ORD20251120001',
+ merchantId: 'merchant_b',
+ merchantName: '商户B',
+ amount: 3000,
+ status: SettlementStatus.UNSETTLED,
+ dueDate: '2025-12-05',
+ relatedOrders: ['ORD20251120001', 'ORD20251120002', 'ORD20251120003'],
+ },
+ {
+ id: 'settlement_004',
+ orderNo: 'ORD20251115001',
+ merchantId: 'merchant_b',
+ merchantName: '商户B',
+ amount: 4500,
+ status: SettlementStatus.OVERDUE,
+ dueDate: '2025-11-30',
+ relatedOrders: ['ORD20251115001'],
+ },
+ // 新增:昨天到期(逾期)
+ {
+ id: 'settlement_new_001',
+ orderNo: 'ORD20251128999',
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ amount: 1200,
+ status: SettlementStatus.OVERDUE,
+ dueDate: '2025-11-28',
+ relatedOrders: ['ORD20251128999'],
+ },
+ // 新增:明天到期
+ {
+ id: 'settlement_new_002',
+ orderNo: 'ORD20251129001',
+ merchantId: 'merchant_b',
+ merchantName: '商户B',
+ amount: 2800,
+ status: SettlementStatus.UNSETTLED,
+ dueDate: '2025-11-30',
+ relatedOrders: ['ORD20251129001'],
+ },
+
+ // 已结账款
+ {
+ id: 'settlement_005',
+ orderNo: 'ORD20251110001',
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ amount: 6000,
+ status: SettlementStatus.SETTLED,
+ dueDate: '2025-11-25',
+ settlementDate: '2025-11-22',
+ relatedOrders: ['ORD20251110001'],
+ },
+ {
+ id: 'settlement_006',
+ orderNo: 'ORD20251105001',
+ merchantId: 'merchant_a',
+ merchantName: '商户A',
+ amount: 7500,
+ status: SettlementStatus.SETTLED,
+ dueDate: '2025-11-20',
+ settlementDate: '2025-11-18',
+ relatedOrders: ['ORD20251105001', 'ORD20251105002'],
+ },
+ {
+ id: 'settlement_007',
+ orderNo: 'ORD20251101001',
+ merchantId: 'merchant_b',
+ merchantName: '商户B',
+ amount: 2500,
+ status: SettlementStatus.SETTLED,
+ dueDate: '2025-11-15',
+ settlementDate: '2025-11-12',
+ relatedOrders: ['ORD20251101001'],
+ },
+]
+
+/**
+ * 消账记录模拟数据
+ */
+export const mockWriteOffList: WriteOff[] = [
+ {
+ id: 'writeoff_001',
+ settlementId: 'settlement_005',
+ amount: 6000,
+ proof: [
+ 'https://picsum.photos/400/300?random=proof1',
+ 'https://picsum.photos/400/300?random=proof2',
+ ],
+ remark: '已完成付款,请查收',
+ submitTime: '2025-11-22 14:30:00',
+ status: WriteOffStatus.APPROVED,
+ },
+ {
+ id: 'writeoff_002',
+ settlementId: 'settlement_006',
+ amount: 7500,
+ proof: [
+ 'https://picsum.photos/400/300?random=proof3',
+ ],
+ remark: '转账凭证',
+ submitTime: '2025-11-18 10:15:00',
+ status: WriteOffStatus.APPROVED,
+ },
+ {
+ id: 'writeoff_003',
+ settlementId: 'settlement_001',
+ amount: 5000,
+ proof: [
+ 'https://picsum.photos/400/300?random=proof4',
+ ],
+ remark: '部分付款',
+ submitTime: '2025-11-28 09:00:00',
+ status: WriteOffStatus.PENDING,
+ },
+]
diff --git a/src/mock/goods.ts b/src/mock/goods.ts
new file mode 100644
index 0000000..a0d5096
--- /dev/null
+++ b/src/mock/goods.ts
@@ -0,0 +1,238 @@
+import type { Goods } from '@/typings/mall'
+
+/**
+ * 商品模拟数据
+ */
+export const mockGoodsList: Goods[] = [
+ // 服装类商品
+ {
+ id: 'goods_001',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '2024春季新款连衣裙',
+ cover: 'https://picsum.photos/400/400?random=1',
+ images: [
+ 'https://picsum.photos/800/800?random=1',
+ 'https://picsum.photos/800/800?random=2',
+ 'https://picsum.photos/800/800?random=3',
+ ],
+ price: 299,
+ originalPrice: 599,
+ stock: 100,
+ sales: 1234,
+ description: '优质面料,舒适透气,修身显瘦,适合春夏季节穿着。',
+ specs: [
+ { name: '颜色', values: ['黑色', '白色', '红色'] },
+ { name: '尺码', values: ['S', 'M', 'L', 'XL'] },
+ ],
+ tags: ['新品', '热销'],
+ categoryId: 'cat_001',
+ categoryName: '服装',
+ },
+ {
+ id: 'goods_002',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '男士休闲T恤',
+ cover: 'https://picsum.photos/400/400?random=4',
+ images: [
+ 'https://picsum.photos/800/800?random=4',
+ 'https://picsum.photos/800/800?random=5',
+ ],
+ price: 89,
+ originalPrice: 159,
+ stock: 200,
+ sales: 856,
+ description: '纯棉面料,柔软舒适,经典百搭款式。',
+ specs: [
+ { name: '颜色', values: ['白色', '灰色', '黑色', '蓝色'] },
+ { name: '尺码', values: ['M', 'L', 'XL', 'XXL'] },
+ ],
+ tags: ['热销'],
+ categoryId: 'cat_001',
+ categoryName: '服装',
+ },
+ {
+ id: 'goods_003',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '女士牛仔裤',
+ cover: 'https://picsum.photos/400/400?random=6',
+ images: [
+ 'https://picsum.photos/800/800?random=6',
+ 'https://picsum.photos/800/800?random=7',
+ ],
+ price: 199,
+ originalPrice: 399,
+ stock: 150,
+ sales: 678,
+ description: '高腰设计,显瘦修身,弹力面料,穿着舒适。',
+ specs: [
+ { name: '颜色', values: ['浅蓝', '深蓝', '黑色'] },
+ { name: '尺码', values: ['25', '26', '27', '28', '29'] },
+ ],
+ tags: ['推荐'],
+ categoryId: 'cat_001',
+ categoryName: '服装',
+ },
+
+ // 数码类商品
+ {
+ id: 'goods_004',
+ shopId: 'merchant_b',
+ shopName: '商户B',
+ name: '无线蓝牙耳机',
+ cover: 'https://picsum.photos/400/400?random=8',
+ images: [
+ 'https://picsum.photos/800/800?random=8',
+ 'https://picsum.photos/800/800?random=9',
+ ],
+ price: 299,
+ originalPrice: 499,
+ stock: 80,
+ sales: 2345,
+ description: '主动降噪,长续航,高音质,支持快充。',
+ specs: [
+ { name: '颜色', values: ['白色', '黑色'] },
+ ],
+ tags: ['新品', '热销'],
+ categoryId: 'cat_002',
+ categoryName: '数码',
+ },
+ {
+ id: 'goods_005',
+ shopId: 'merchant_b',
+ shopName: '商户B',
+ name: '智能手表',
+ cover: 'https://picsum.photos/400/400?random=10',
+ images: [
+ 'https://picsum.photos/800/800?random=10',
+ 'https://picsum.photos/800/800?random=11',
+ ],
+ price: 899,
+ originalPrice: 1299,
+ stock: 50,
+ sales: 567,
+ description: '健康监测,运动追踪,消息提醒,长续航。',
+ specs: [
+ { name: '颜色', values: ['黑色', '银色', '金色'] },
+ { name: '表带', values: ['硅胶', '皮革', '金属'] },
+ ],
+ tags: ['新品'],
+ categoryId: 'cat_002',
+ categoryName: '数码',
+ },
+
+ // 食品类商品
+ {
+ id: 'goods_006',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '进口零食大礼包',
+ cover: 'https://picsum.photos/400/400?random=12',
+ images: [
+ 'https://picsum.photos/800/800?random=12',
+ 'https://picsum.photos/800/800?random=13',
+ ],
+ price: 128,
+ originalPrice: 198,
+ stock: 300,
+ sales: 1890,
+ description: '多种口味,营养健康,适合全家分享。',
+ specs: [],
+ tags: ['热销', '推荐'],
+ categoryId: 'cat_003',
+ categoryName: '食品',
+ },
+ {
+ id: 'goods_007',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '有机坚果礼盒',
+ cover: 'https://picsum.photos/400/400?random=14',
+ images: [
+ 'https://picsum.photos/800/800?random=14',
+ 'https://picsum.photos/800/800?random=15',
+ ],
+ price: 168,
+ originalPrice: 268,
+ stock: 120,
+ sales: 456,
+ description: '精选优质坚果,营养丰富,送礼佳品。',
+ specs: [
+ { name: '规格', values: ['500g', '1000g'] },
+ ],
+ tags: ['推荐'],
+ categoryId: 'cat_003',
+ categoryName: '食品',
+ },
+
+ // 家居类商品
+ {
+ id: 'goods_008',
+ shopId: 'merchant_b',
+ shopName: '商户B',
+ name: '北欧风格台灯',
+ cover: 'https://picsum.photos/400/400?random=16',
+ images: [
+ 'https://picsum.photos/800/800?random=16',
+ 'https://picsum.photos/800/800?random=17',
+ ],
+ price: 159,
+ originalPrice: 299,
+ stock: 90,
+ sales: 234,
+ description: '简约设计,护眼光源,适合卧室书房。',
+ specs: [
+ { name: '颜色', values: ['白色', '木色'] },
+ ],
+ tags: ['新品'],
+ categoryId: 'cat_004',
+ categoryName: '家居',
+ },
+ {
+ id: 'goods_009',
+ shopId: 'merchant_b',
+ shopName: '商户B',
+ name: '四件套床上用品',
+ cover: 'https://picsum.photos/400/400?random=18',
+ images: [
+ 'https://picsum.photos/800/800?random=18',
+ 'https://picsum.photos/800/800?random=19',
+ ],
+ price: 299,
+ originalPrice: 599,
+ stock: 150,
+ sales: 789,
+ description: '纯棉面料,柔软亲肤,多种花色可选。',
+ specs: [
+ { name: '尺寸', values: ['1.5m床', '1.8m床', '2.0m床'] },
+ { name: '颜色', values: ['浅灰', '深灰', '米白', '粉色'] },
+ ],
+ tags: ['热销'],
+ categoryId: 'cat_004',
+ categoryName: '家居',
+ },
+
+ // 美妆类商品
+ {
+ id: 'goods_010',
+ shopId: 'merchant_a',
+ shopName: '商户A',
+ name: '保湿面霜套装',
+ cover: 'https://picsum.photos/400/400?random=20',
+ images: [
+ 'https://picsum.photos/800/800?random=20',
+ 'https://picsum.photos/800/800?random=21',
+ ],
+ price: 399,
+ originalPrice: 699,
+ stock: 200,
+ sales: 1567,
+ description: '深层补水,改善肌肤,温和不刺激。',
+ specs: [],
+ tags: ['热销', '推荐'],
+ categoryId: 'cat_005',
+ categoryName: '美妆',
+ },
+]
diff --git a/src/mock/index.ts b/src/mock/index.ts
new file mode 100644
index 0000000..34ff8e0
--- /dev/null
+++ b/src/mock/index.ts
@@ -0,0 +1,10 @@
+/**
+ * Mock 数据统一导出
+ */
+
+export * from './goods'
+export * from './category'
+export * from './finance'
+export * from './member'
+export * from './banner'
+export * from './address'
diff --git a/src/mock/member.ts b/src/mock/member.ts
new file mode 100644
index 0000000..45207c5
--- /dev/null
+++ b/src/mock/member.ts
@@ -0,0 +1,40 @@
+import { MemberLevel } from '@/typings/mall'
+import type { Member } from '@/typings/mall'
+
+/**
+ * 会员等级配置
+ */
+export const memberLevelConfig = {
+ [MemberLevel.NORMAL]: {
+ name: '普通会员',
+ benefits: ['积分累积', '生日优惠'],
+ color: '#999999',
+ },
+ [MemberLevel.SILVER]: {
+ name: '银卡会员',
+ benefits: ['积分累积', '生日优惠', '专属客服', '9.5折优惠'],
+ color: '#C0C0C0',
+ },
+ [MemberLevel.GOLD]: {
+ name: '金卡会员',
+ benefits: ['积分累积', '生日优惠', '专属客服', '9折优惠', '免运费'],
+ color: '#FFD700',
+ },
+ [MemberLevel.PLATINUM]: {
+ name: '白金会员',
+ benefits: ['积分累积', '生日优惠', '专属客服', '8.5折优惠', '免运费', '优先发货'],
+ color: '#E5E4E2',
+ },
+}
+
+/**
+ * 会员模拟数据
+ */
+export const mockMember: Member = {
+ id: 'member_001',
+ userId: 'user_001',
+ level: MemberLevel.GOLD,
+ points: 3580,
+ expireDate: '2026-11-28',
+ benefits: memberLevelConfig[MemberLevel.GOLD].benefits,
+}
diff --git a/src/pages/finance/credit.vue b/src/pages/finance/credit.vue
new file mode 100644
index 0000000..565fa54
--- /dev/null
+++ b/src/pages/finance/credit.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+ 总可用额度 (元)
+ {{ financeStore.totalAvailableLimit.toLocaleString('zh-CN', { minimumFractionDigits: 2 }) }}
+
+ 总额度 ¥{{ financeStore.creditLimits.reduce((sum, item) => sum + item.totalLimit, 0).toLocaleString() }}
+ |
+ 已用 ¥{{ financeStore.totalUsedLimit.toLocaleString() }}
+
+
+
+
+
+ 商户额度详情
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/finance/settlement.vue b/src/pages/finance/settlement.vue
new file mode 100644
index 0000000..eb59e58
--- /dev/null
+++ b/src/pages/finance/settlement.vue
@@ -0,0 +1,744 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.dueDate }}
+
+
+ ¥{{ item.amount.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 到期日期
+ {{ item.dueDate }}
+
+
+ 结算日期
+ {{ item.settlementDate }}
+
+
+ 应结金额
+ ¥{{ item.amount.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/goods/cart.vue b/src/pages/goods/cart.vue
index 28e6fab..aa25f2e 100644
--- a/src/pages/goods/cart.vue
+++ b/src/pages/goods/cart.vue
@@ -1,13 +1,214 @@
-
- 购物车
+
+
+
+
+
+
+
+
+
+
+
+ 购物车是空的
+ 去逛逛
+
+
+
+
+
diff --git a/src/pages/goods/detail.vue b/src/pages/goods/detail.vue
new file mode 100644
index 0000000..b66dbd4
--- /dev/null
+++ b/src/pages/goods/detail.vue
@@ -0,0 +1,386 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentImageIndex + 1 }} / {{ goods.images.length }}
+
+
+
+
+
+
+
+ 销量 {{ goods.sales }}
+
+ {{ goods.name }}
+ {{ goods.description }}
+
+
+
+
+
+ 选择
+ 规格 / 数量
+
+
+
+
+
+
+ 商品详情
+
+
+
+
+
+
+
+
+ {}">
+
+ 店铺
+
+
+ {{ cartStore.totalCount }}
+
+ 购物车
+
+
+
+ 加入购物车
+ 立即购买
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index 763c110..0fed31e 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -1,13 +1,184 @@
-
- 首页
+
+
+
+
+
+
+
+
+
+ 商品分类
+
+
+
+
+
+
+ 为你推荐
+ uni.switchTab({ url: '/pages/sort/index' })">
+ 更多
+
+
+
+
+
+
+
+
+
+
+ {{ cartStore.totalCount > 99 ? '99+' : cartStore.totalCount }}
+
+
+
diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
new file mode 100644
index 0000000..a036afc
--- /dev/null
+++ b/src/pages/login/index.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+ 商城+金融
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ countdown > 0 ? `${countdown}s后重发` : '获取验证码' }}
+
+
+
+
+ 登录
+ 登录中...
+
+
+
+ 未注册手机号验证后自动创建账号
+
+
+
+
+
+
diff --git a/src/pages/me/me.vue b/src/pages/me/me.vue
index 5df9e2e..3384c80 100644
--- a/src/pages/me/me.vue
+++ b/src/pages/me/me.vue
@@ -1,13 +1,565 @@
-
- 我的页面
+
+
+
+
+
+ {{ userStore.userInfo.nickname }}
+ {{ userStore.userInfo.phone }}
+
+
+
+ 金卡会员
+
+
+
+
+
+
+
+
+
+ 点击登录/注册
+
+
+
+
+
+
+
+
+
+ 待付款
+
+
+
+ 待发货
+
+
+
+ 待收货
+
+
+
+ 已完成
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 信用额度
+ 查看可用额度
+
+
+
+
+
+
+
+ 应结账款
+ 消账与结算
+
+
+
+
+
+
+
+
+
+
+
+ 300万
+ 最高
+
+
+ 3年
+ 最长
+
+
+
+
+
+
+
+
+
+
+ 1000万
+ 最高
+
+
+ 10年
+ 最长
+
+
+
+
+
+
+
+
+
+
+
+
+ 会员中心
+
+
+
+
+ 地址管理
+
+
+
+
+ 退出登录
+
+
+
+
+
+
diff --git a/src/pages/member/index.vue b/src/pages/member/index.vue
new file mode 100644
index 0000000..c6055ab
--- /dev/null
+++ b/src/pages/member/index.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 会员权益
+
+
+
+
+
+ 积分记录
+
+
+
+ 购物赠送
+ 2025-11-28 10:00:00
+
+ +100
+
+
+
+ 签到奖励
+ 2025-11-27 09:00:00
+
+ +10
+
+
+
+
+
+
+
+
diff --git a/src/pages/order/confirm.vue b/src/pages/order/confirm.vue
new file mode 100644
index 0000000..19dd6d7
--- /dev/null
+++ b/src/pages/order/confirm.vue
@@ -0,0 +1,693 @@
+
+
+
+
+
+
+
+
+ {{ address.name }}
+ {{ address.phone }}
+ 默认
+
+
+ {{ address.province }}{{ address.city }}{{ address.district }}{{ address.detail }}
+
+
+
+ 请选择收货地址
+
+
+
+
+
+
+
+
+
+
+
+
+ 可用额度¥{{ group.creditAmount.toFixed(2) }}抵扣,剩余¥{{ group.onlineAmount.toFixed(2) }}使用在线支付
+
+
+
+
+
+ {{ group.creditError }},将使用在线支付
+
+
+
+
+
+ {{ item.goodsName }}
+
+ {{ Object.values(item.selectedSpec).join(',') }}
+
+
+ ¥{{ item.price }}
+ x{{ item.quantity }}
+
+
+
+
+
+ 小计:
+ ¥{{ group.total.toFixed(2) }}
+
+
+
+
+
+
+ 支付方式
+
+
+
+
+
+ 信用支付
+ 开启后优先使用信用额度支付
+
+
+
+
+
+
+
+
+
+
+
+ 商品总额
+ ¥{{ cartStore.totalPrice.toFixed(2) }}
+
+
+ 运费
+ ¥0.00
+
+
+ 实付款
+ ¥{{ cartStore.totalPrice.toFixed(2) }}
+
+
+
+
+
+
+
+ 实付款:
+ ¥{{ paymentSummary.onlineTotal.toFixed(2) }}
+
+
+ 信用扣除:¥{{ paymentSummary.creditTotal.toFixed(2) }}
+
+
+
+ 提交订单
+
+
+
+
+
+
diff --git a/src/pages/order/detail.vue b/src/pages/order/detail.vue
new file mode 100644
index 0000000..f193eeb
--- /dev/null
+++ b/src/pages/order/detail.vue
@@ -0,0 +1,384 @@
+
+
+
+
+
+
+
+ {{ order.status === 'pending_payment' ? '等待买家付款' :
+ order.status === 'pending_delivery' ? '等待卖家发货' :
+ order.status === 'pending_receive' ? '等待买家收货' :
+ order.status === 'completed' ? '交易完成' : '交易关闭' }}
+
+
+ 请在 30 分钟内完成支付
+
+
+
+
+
+
+
+
+
+
+ {{ order.address.name }}
+ {{ order.address.phone }}
+
+
+ {{ order.address.province }}{{ order.address.city }}{{ order.address.district }}{{ order.address.detail }}
+
+
+
+
+
+
+
+
+
+ {{ item.goodsName }}
+ {{ Object.values(item.selectedSpec).join(',') }}
+
+ ¥{{ item.price }}
+ x{{ item.quantity }}
+
+
+
+
+
+
+
+
+ 订单编号
+ {{ order.orderNo }}
+
+
+ 创建时间
+ {{ order.createTime }}
+
+
+ 支付时间
+ {{ order.payTime }}
+
+
+
+
+
+
+ 商品总额
+ ¥{{ order.totalAmount }}
+
+
+ 运费
+ ¥0.00
+
+
+ 信用扣除
+ -¥{{ creditPaid.toFixed(2) }}
+
+
+ 实付款
+ ¥{{ order.actualAmount }}
+
+
+
+
+
+ 取消订单
+ 立即支付
+
+
+
+
+ 加载中...
+
+
+
+
diff --git a/src/pages/order/list.vue b/src/pages/order/list.vue
new file mode 100644
index 0000000..b863d16
--- /dev/null
+++ b/src/pages/order/list.vue
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+
+ {{ tab.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.goodsName }}
+ {{ Object.values(item.selectedSpec).join(',') }}
+
+
+ ¥{{ item.price }}
+ x{{ item.quantity }}
+
+
+
+
+
+
+
+
+
+
+ 暂无订单
+
+
+
+
+
+
diff --git a/src/pages/sort/index.vue b/src/pages/sort/index.vue
index 7070b06..f58a6df 100644
--- a/src/pages/sort/index.vue
+++ b/src/pages/sort/index.vue
@@ -1,13 +1,218 @@
-
- 分类页面
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+ 暂无商品
+
+
+
+
+
+
+
+
+
diff --git a/src/store/cart.ts b/src/store/cart.ts
new file mode 100644
index 0000000..327908a
--- /dev/null
+++ b/src/store/cart.ts
@@ -0,0 +1,126 @@
+import { defineStore } from 'pinia'
+import type { CartItem } from '@/typings/mall'
+
+export const useCartStore = defineStore('cart', {
+ state: () => ({
+ items: [] as CartItem[],
+ }),
+
+
+
+ getters: {
+ // 购物车商品数量
+ totalCount(): number {
+ return this.items.reduce((sum, item) => sum + item.quantity, 0)
+ },
+
+ // 选中的商品
+ checkedItems(): CartItem[] {
+ return this.items.filter(item => item.checked)
+ },
+
+ // 选中商品数量
+ checkedCount(): number {
+ return this.checkedItems.reduce((sum, item) => sum + item.quantity, 0)
+ },
+
+ // 总价
+ totalPrice(): number {
+ return this.checkedItems.reduce((sum, item) => {
+ return sum + item.price * item.quantity
+ }, 0)
+ },
+
+ // 是否全选
+ isAllChecked(): boolean {
+ return this.items.length > 0 && this.items.every(item => item.checked)
+ },
+ },
+
+ actions: {
+ // 初始化检查(迁移旧数据)
+ initCheck() {
+ this.items.forEach(item => {
+ if (!item.shopId) {
+ item.shopId = 'merchant_a'
+ item.shopName = '商户A'
+ }
+ })
+ },
+
+ // 添加商品
+ addItem(item: Omit) {
+ const existItem = this.items.find(
+ i =>
+ i.goodsId === item.goodsId
+ && JSON.stringify(i.selectedSpec) === JSON.stringify(item.selectedSpec),
+ )
+
+ if (existItem) {
+ // 已存在,增加数量
+ existItem.quantity += item.quantity
+ }
+ else {
+ // 不存在,添加新商品
+ this.items.push({
+ ...item,
+ id: `cart_${Date.now()}`,
+ checked: true,
+ shopId: item.shopId || 'merchant_a', // 兼容旧数据,默认商户A
+ shopName: item.shopName || '商户A', // 兼容旧数据,默认商户A
+ })
+ }
+ },
+
+ // 删除商品
+ removeItem(id: string) {
+ const index = this.items.findIndex(item => item.id === id)
+ if (index > -1) {
+ this.items.splice(index, 1)
+ }
+ },
+
+ // 更新数量
+ updateQuantity(id: string, quantity: number) {
+ const item = this.items.find(item => item.id === id)
+ if (item) {
+ item.quantity = Math.max(1, Math.min(quantity, item.stock))
+ }
+ },
+
+ // 切换选中状态
+ toggleChecked(id: string) {
+ const item = this.items.find(item => item.id === id)
+ if (item) {
+ item.checked = !item.checked
+ }
+ },
+
+ // 全选/取消全选
+ toggleAllChecked() {
+ const checked = !this.isAllChecked
+ this.items.forEach((item) => {
+ item.checked = checked
+ })
+ },
+
+ // 清空购物车
+ clear() {
+ this.items = []
+ },
+
+ // 清空已选中的商品
+ clearChecked() {
+ this.items = this.items.filter(item => !item.checked)
+ },
+ },
+
+ // 持久化配置
+ persist: {
+ key: 'shop-toy-cart',
+ storage: {
+ getItem: key => uni.getStorageSync(key),
+ setItem: (key, value) => uni.setStorageSync(key, value),
+ },
+ },
+})
diff --git a/src/store/finance.ts b/src/store/finance.ts
new file mode 100644
index 0000000..61adcb2
--- /dev/null
+++ b/src/store/finance.ts
@@ -0,0 +1,79 @@
+import { defineStore } from 'pinia'
+import { getCreditLimit, getSettlementList, getDueOrders, submitWriteOff, getWriteOffList } from '@/api/finance'
+import type { CreditLimit, Settlement, SettlementStatus, WriteOff } from '@/typings/mall'
+
+export const useFinanceStore = defineStore('finance', {
+ state: () => ({
+ creditLimits: [] as CreditLimit[],
+ settlementList: [] as Settlement[],
+ dueOrders: [] as Settlement[],
+ writeOffList: [] as WriteOff[],
+ }),
+
+ getters: {
+ // 总可用额度
+ totalAvailableLimit(state): number {
+ return state.creditLimits.reduce((sum, item) => sum + item.availableLimit, 0)
+ },
+
+ // 总已用额度
+ totalUsedLimit(state): number {
+ return state.creditLimits.reduce((sum, item) => sum + item.usedLimit, 0)
+ },
+ },
+
+ actions: {
+ // 获取信用额度
+ async fetchCreditLimit() {
+ try {
+ const res: any = await getCreditLimit()
+ this.creditLimits = res.data
+ } catch (error) {
+ console.error('获取信用额度失败', error)
+ }
+ },
+
+ // 获取应结账款列表
+ async fetchSettlementList(params?: { status?: SettlementStatus, merchantId?: string }) {
+ try {
+ const res: any = await getSettlementList(params)
+ this.settlementList = res.data
+ } catch (error) {
+ console.error('获取应结账款失败', error)
+ }
+ },
+
+ // 获取到期订单
+ async fetchDueOrders() {
+ try {
+ const res: any = await getDueOrders()
+ this.dueOrders = res.data
+ } catch (error) {
+ console.error('获取到期订单失败', error)
+ }
+ },
+
+ // 提交消账
+ async submitWriteOff(data: { settlementId: string, amount: number, proof: string[], remark: string }) {
+ try {
+ await submitWriteOff(data)
+ // 刷新列表
+ this.fetchSettlementList()
+ this.fetchWriteOffList(data.settlementId)
+ } catch (error) {
+ console.error('提交消账失败', error)
+ throw error
+ }
+ },
+
+ // 获取消账记录
+ async fetchWriteOffList(settlementId?: string) {
+ try {
+ const res: any = await getWriteOffList(settlementId)
+ this.writeOffList = res.data
+ } catch (error) {
+ console.error('获取消账记录失败', error)
+ }
+ },
+ },
+})
diff --git a/src/store/member.ts b/src/store/member.ts
new file mode 100644
index 0000000..80e0cbd
--- /dev/null
+++ b/src/store/member.ts
@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { getMemberInfo, getMemberBenefits } from '@/api/member'
+import type { Member } from '@/typings/mall'
+import { memberLevelConfig } from '@/mock/member'
+
+export const useMemberStore = defineStore('member', {
+ state: () => ({
+ memberInfo: null as Member | null,
+ benefits: [] as string[],
+ }),
+
+ getters: {
+ currentLevelConfig(state) {
+ if (!state.memberInfo) return null
+ return memberLevelConfig[state.memberInfo.level]
+ },
+ },
+
+ actions: {
+ // 获取会员信息
+ async fetchMemberInfo() {
+ try {
+ const res: any = await getMemberInfo()
+ this.memberInfo = res.data
+ } catch (error) {
+ console.error('获取会员信息失败', error)
+ }
+ },
+
+ // 获取会员权益
+ async fetchBenefits() {
+ try {
+ const res: any = await getMemberBenefits()
+ this.benefits = res.data
+ } catch (error) {
+ console.error('获取会员权益失败', error)
+ }
+ },
+ },
+})
diff --git a/src/store/order.ts b/src/store/order.ts
new file mode 100644
index 0000000..919ca92
--- /dev/null
+++ b/src/store/order.ts
@@ -0,0 +1,74 @@
+import { defineStore } from 'pinia'
+import { createOrder, getOrderList, getOrderDetail, cancelOrder, payOrder } from '@/api/order'
+import type { Order, OrderStatus } from '@/typings/mall'
+
+export const useOrderStore = defineStore('order', {
+ state: () => ({
+ orderList: [] as Order[],
+ currentOrder: null as Order | null,
+ }),
+
+ actions: {
+ // 获取订单列表
+ async fetchOrderList(status?: OrderStatus) {
+ try {
+ const res: any = await getOrderList(status)
+ this.orderList = res.data
+ } catch (error) {
+ console.error('获取订单列表失败', error)
+ }
+ },
+
+ // 获取订单详情
+ async fetchOrderDetail(id: string) {
+ try {
+ const res: any = await getOrderDetail(id)
+ this.currentOrder = res.data
+ return res.data
+ } catch (error) {
+ console.error('获取订单详情失败', error)
+ return null
+ }
+ },
+
+ // 创建订单
+ async createOrder(data: any) {
+ try {
+ const res: any = await createOrder(data)
+ return res.data
+ } catch (error) {
+ console.error('创建订单失败', error)
+ throw error
+ }
+ },
+
+ // 支付订单
+ async payOrder(id: string) {
+ try {
+ await payOrder(id)
+ // 更新列表或详情
+ if (this.currentOrder && this.currentOrder.id === id) {
+ this.fetchOrderDetail(id)
+ }
+ this.fetchOrderList()
+ } catch (error) {
+ console.error('支付订单失败', error)
+ throw error
+ }
+ },
+
+ // 取消订单
+ async cancelOrder(id: string) {
+ try {
+ await cancelOrder(id)
+ if (this.currentOrder && this.currentOrder.id === id) {
+ this.fetchOrderDetail(id)
+ }
+ this.fetchOrderList()
+ } catch (error) {
+ console.error('取消订单失败', error)
+ throw error
+ }
+ },
+ },
+})
diff --git a/src/store/user.ts b/src/store/user.ts
index 3f6d693..468b1bd 100644
--- a/src/store/user.ts
+++ b/src/store/user.ts
@@ -1,61 +1,40 @@
-import type { IUserInfoRes } from '@/api/types/login'
import { defineStore } from 'pinia'
-import { ref } from 'vue'
-import {
- getUserInfo,
-} from '@/api/login'
+import type { User } from '@/typings/mall'
+import { mockMember } from '@/mock/member'
-// 初始化状态
-const userInfoState: IUserInfoRes = {
- userId: -1,
- username: '',
- nickname: '',
- avatar: '/static/images/default-avatar.png',
-}
+export const useUserStore = defineStore('user', {
+ state: () => ({
+ userInfo: {
+ id: 'user_001',
+ username: 'admin',
+ nickname: '测试用户',
+ avatar: 'https://picsum.photos/200/200?random=avatar',
+ phone: '13800138000',
+ creditLimits: [], // 实际应从 financeStore 获取或关联
+ member: mockMember,
+ } as User | null,
+ isLogin: true, // 默认已登录
+ }),
-export const useUserStore = defineStore(
- 'user',
- () => {
- // 定义用户信息
- const userInfo = ref({ ...userInfoState })
- // 设置用户信息
- const setUserInfo = (val: IUserInfoRes) => {
- console.log('设置用户信息', val)
- // 若头像为空 则使用默认头像
- if (!val.avatar) {
- val.avatar = userInfoState.avatar
- }
- userInfo.value = val
- }
- const setUserAvatar = (avatar: string) => {
- userInfo.value.avatar = avatar
- console.log('设置用户头像', avatar)
- console.log('userInfo', userInfo.value)
- }
- // 删除用户信息
- const clearUserInfo = () => {
- userInfo.value = { ...userInfoState }
- uni.removeStorageSync('user')
- }
+ actions: {
+ // 登录(模拟)
+ login(data: any) {
+ this.isLogin = true
+ // ...
+ },
- /**
- * 获取用户信息
- */
- const fetchUserInfo = async () => {
- const res = await getUserInfo()
- setUserInfo(res)
- return res
- }
-
- return {
- userInfo,
- clearUserInfo,
- fetchUserInfo,
- setUserInfo,
- setUserAvatar,
- }
+ // 退出登录
+ logout() {
+ this.isLogin = false
+ this.userInfo = null
+ },
},
- {
- persist: true,
+
+ persist: {
+ key: 'shop-toy-user',
+ storage: {
+ getItem: key => uni.getStorageSync(key),
+ setItem: (key, value) => uni.setStorageSync(key, value),
+ },
},
-)
+})
diff --git a/src/typings/mall.ts b/src/typings/mall.ts
new file mode 100644
index 0000000..ae131d0
--- /dev/null
+++ b/src/typings/mall.ts
@@ -0,0 +1,206 @@
+/**
+ * 商品相关类型定义
+ */
+
+// 商品规格
+export interface GoodsSpec {
+ name: string // 规格名(如:颜色、尺寸)
+ values: string[] // 规格值(如:红色、蓝色)
+}
+
+// 商品信息
+export interface Goods {
+ id: string
+ shopId: string // 店铺ID
+ shopName: string // 店铺名称
+ name: string // 商品名称
+ cover: string // 封面图
+ images: string[] // 商品图片
+ price: number // 价格
+ originalPrice: number // 原价
+ stock: number // 库存
+ sales: number // 销量
+ description: string // 商品描述
+ specs: GoodsSpec[] // 规格
+ tags: string[] // 标签
+ categoryId: string // 分类ID
+ categoryName: string // 分类名称
+}
+
+/**
+ * 分类相关类型定义
+ */
+export interface Category {
+ id: string
+ name: string // 分类名称
+ icon: string // 分类图标
+ cover: string // 分类封面
+ parentId?: string // 父分类ID
+ children?: Category[] // 子分类
+}
+
+/**
+ * 购物车相关类型定义
+ */
+export interface CartItem {
+ id: string
+ shopId: string
+ shopName: string
+ goodsId: string
+ goodsName: string
+ cover: string
+ price: number
+ selectedSpec: Record // 选中的规格
+ quantity: number
+ stock: number
+ checked: boolean // 是否选中
+}
+
+/**
+ * 订单相关类型定义
+ */
+export interface OrderItem {
+ goodsId: string
+ shopId: string
+ shopName: string
+ goodsName: string
+ cover: string
+ price: number
+ quantity: number
+ selectedSpec: Record
+}
+
+export enum OrderStatus {
+ PENDING_PAYMENT = 'pending_payment', // 待支付
+ PENDING_DELIVERY = 'pending_delivery', // 待发货
+ PENDING_RECEIVE = 'pending_receive', // 待收货
+ COMPLETED = 'completed', // 已完成
+ CANCELLED = 'cancelled', // 已取消
+}
+
+export interface Order {
+ id: string
+ orderNo: string // 订单号
+ items: OrderItem[]
+ totalAmount: number // 总金额
+ actualAmount: number // 实付金额
+ status: OrderStatus // 订单状态
+ paymentMethod?: 'online' | 'credit' | 'mixed' // 支付方式
+ paymentDetails?: { shopId: string, creditAmount: number, onlineAmount: number }[] // 混合支付详情
+ address: Address // 收货地址
+ createTime: string
+ payTime?: string
+ // 金融相关
+ merchantId?: string // 关联商户ID
+ isSettled: boolean // 是否已结
+ settlementTime?: string // 结算时间
+ dueDate?: string // 到期日期
+}
+
+export interface Address {
+ id: string
+ name: string // 收货人
+ phone: string // 手机号
+ province: string // 省
+ city: string // 市
+ district: string // 区
+ detail: string // 详细地址
+ isDefault: boolean // 是否默认
+}
+
+/**
+ * 金融相关类型定义
+ */
+
+// 信用额度
+export interface CreditLimit {
+ merchantId: string // 商户ID
+ merchantName: string // 商户名称(商户A、商户B)
+ totalLimit: number // 总额度
+ usedLimit: number // 已用额度
+ availableLimit: number // 可用额度
+ updateTime: string // 更新时间
+}
+
+// 应结账款状态
+export enum SettlementStatus {
+ SETTLED = 'settled', // 已结
+ UNSETTLED = 'unsettled', // 未结
+ OVERDUE = 'overdue', // 逾期
+}
+
+// 应结账款
+export interface Settlement {
+ id: string
+ orderNo: string // 订单号
+ merchantId: string // 商户ID
+ merchantName: string // 商户名称
+ amount: number // 金额
+ status: SettlementStatus // 状态(已结/未结)
+ dueDate: string // 到期日期
+ settlementDate?: string // 结算日期
+ relatedOrders: string[] // 关联订单号列表
+}
+
+// 消账状态
+export enum WriteOffStatus {
+ PENDING = 'pending', // 待审核
+ APPROVED = 'approved', // 已通过
+ REJECTED = 'rejected', // 已拒绝
+}
+
+// 提交消账
+export interface WriteOff {
+ id: string
+ settlementId: string // 应结账款ID
+ amount: number // 消账金额
+ proof: string[] // 凭证图片
+ remark: string // 备注
+ submitTime: string // 提交时间
+ status: WriteOffStatus // 状态
+}
+
+/**
+ * 会员相关类型定义
+ */
+export enum MemberLevel {
+ NORMAL = 'normal', // 普通会员
+ SILVER = 'silver', // 银卡会员
+ GOLD = 'gold', // 金卡会员
+ PLATINUM = 'platinum', // 白金会员
+}
+
+export interface Member {
+ id: string
+ userId: string
+ level: MemberLevel // 会员等级
+ points: number // 积分
+ expireDate: string // 到期日期
+ benefits: string[] // 会员权益
+}
+
+/**
+ * 用户相关类型定义
+ */
+export interface User {
+ id: string
+ username: string
+ nickname: string
+ avatar: string
+ phone: string
+ // 金融相关
+ creditLimits: CreditLimit[] // 信用额度列表
+ // 会员相关
+ member?: Member // 会员信息
+}
+
+/**
+ * 轮播图相关类型定义
+ */
+export interface Banner {
+ id: string
+ image: string // 图片地址
+ title: string // 标题
+ link?: string // 跳转链接
+ goodsId?: string // 关联商品ID
+}