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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ +