完善页面布局
This commit is contained in:
594
docs/ADMIN_MANAGEMENT_GUIDE.md
Normal file
594
docs/ADMIN_MANAGEMENT_GUIDE.md
Normal file
@@ -0,0 +1,594 @@
|
||||
# CRMEB管理端uvue实现操作指南
|
||||
|
||||
## 项目概述
|
||||
|
||||
本文档详细介绍基于CRMEB商城系统管理端功能,使用uvue + Supabase技术栈重新实现的完整管理后台操作指南。
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 前端技术栈
|
||||
- **框架**: uvue (uni-app x)
|
||||
- **状态管理**: Vue 3 Composition API
|
||||
- **UI组件**: 自定义组件 + uni-app内置组件
|
||||
- **样式**: CSS + Flex布局 + 响应式设计
|
||||
- **设计风格**: 参考CRMEB开源商城系统,采用统一的卡片布局和配色方案
|
||||
- **图标库**: iconfont字体图标库
|
||||
|
||||
### 后端技术栈
|
||||
- **数据库**: Supabase (PostgreSQL)
|
||||
- **API**: @components/supadb 组件库
|
||||
- **认证**: Supabase Auth
|
||||
- **存储**: Supabase Storage
|
||||
- **实时功能**: Supabase Realtime
|
||||
|
||||
## 功能模块
|
||||
|
||||
### 1. 管理端首页 (`index.uvue`)
|
||||
|
||||
#### 功能特性
|
||||
- **基础信息统计卡片**: 显示销售额、订单数、用户数等核心指标
|
||||
- **功能导航网格**: 快速访问各个管理模块
|
||||
- **数据可视化**: 实时统计数据展示
|
||||
|
||||
#### 页面结构
|
||||
参考CRMEB设计风格,采用统一的卡片布局和Flex布局:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="admin-dashboard">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="header acea-row">
|
||||
<navigator class="item" url="/pages/mall/admin/order-management" hover-class="none">
|
||||
<view class="num">{{ pendingCounts.order_pending }}</view>
|
||||
<view>待处理订单</view>
|
||||
</navigator>
|
||||
<!-- 其他统计项 -->
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="wrapper">
|
||||
<view class="title">
|
||||
<span class="iconfont icon-shujutongji"></span>数据统计
|
||||
</view>
|
||||
<view class="list acea-row">
|
||||
<navigator class="item" url="/pages/mall/admin/statistics/sales" hover-class="none">
|
||||
<view class="num">{{ todayStats.sales }}</view>
|
||||
<view>今日销售额</view>
|
||||
</navigator>
|
||||
<!-- 其他统计项 -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能导航 -->
|
||||
<view class="public-wrapper">
|
||||
<view class="title">
|
||||
<span class="iconfont icon-gongnengdaohang"></span>功能导航
|
||||
</view>
|
||||
<view class="menu-grid acea-row">
|
||||
<view class="menu-item" @click="go('/pages/mall/admin/user-management')">
|
||||
<view class="menu-icon">
|
||||
<text class="iconfont icon-yonghuguanli"></text>
|
||||
</view>
|
||||
<text class="menu-text">用户管理</text>
|
||||
</view>
|
||||
<!-- 其他功能项 -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 数据加载
|
||||
```typescript
|
||||
// 获取基础统计数据
|
||||
const loadBaseStats = async () => {
|
||||
const salesStats = await supa.rpc('get_sales_stats', {
|
||||
start_date: yesterday,
|
||||
end_date: today
|
||||
})
|
||||
// 更新统计数据
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 用户管理 (`user-management.uvue`)
|
||||
|
||||
#### 核心功能
|
||||
- **用户搜索筛选**: 支持多条件组合查询
|
||||
- **用户列表展示**: 分页显示用户信息
|
||||
- **批量操作**: 导出用户、群发消息、调整余额
|
||||
- **用户状态管理**: 启用/禁用用户账户
|
||||
- **用户详情**: 查看和编辑用户信息
|
||||
|
||||
#### 搜索功能
|
||||
```typescript
|
||||
const searchTypes = ref([
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'uid', label: 'UID' },
|
||||
{ value: 'phone', label: '手机号' },
|
||||
{ value: 'nickname', label: '用户昵称' }
|
||||
])
|
||||
|
||||
const userLevels = ref([]) // 用户等级选项
|
||||
const userGroups = ref([]) // 用户分组选项
|
||||
const agentLevels = ref([]) // 分销等级选项
|
||||
```
|
||||
|
||||
#### 用户操作
|
||||
```typescript
|
||||
// 切换用户状态
|
||||
const toggleUserStatus = async (userId: number, currentStatus: number) => {
|
||||
const newStatus = currentStatus === 1 ? 0 : 1
|
||||
await supa.from('users').update({ status: newStatus }).eq('id', userId)
|
||||
}
|
||||
|
||||
// 批量导出用户
|
||||
const exportUsers = () => {
|
||||
// 导出逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 商品管理 (`product-management.uvue`)
|
||||
|
||||
#### 功能特性
|
||||
- **商品列表**: 分页展示商品信息
|
||||
- **高级筛选**: 商品类型、分类、价格、库存等条件
|
||||
- **批量操作**: 批量上架/下架/删除
|
||||
- **商品状态管理**: 上架、下架、编辑
|
||||
- **商品规格**: 支持多规格商品管理
|
||||
|
||||
#### 商品筛选
|
||||
```typescript
|
||||
const productTypes = ref([
|
||||
{ value: '0', label: '普通商品' },
|
||||
{ value: '1', label: '卡密商品' },
|
||||
{ value: '2', label: '优惠券商品' },
|
||||
{ value: '3', label: '虚拟商品' }
|
||||
])
|
||||
|
||||
const deliveryTypes = ref([
|
||||
{ value: '1', label: '快递配送' },
|
||||
{ value: '2', label: '到店自提' }
|
||||
])
|
||||
```
|
||||
|
||||
#### 商品操作
|
||||
```typescript
|
||||
// 批量上架
|
||||
const batchOnShelf = async () => {
|
||||
await supa.from('products')
|
||||
.update({ is_show: true })
|
||||
.in('id', selectedProducts.value)
|
||||
}
|
||||
|
||||
// 删除商品
|
||||
const deleteProduct = async (productId: number) => {
|
||||
await supa.from('products')
|
||||
.update({ is_del: true })
|
||||
.eq('id', productId)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 订单管理 (`order-management.uvue`)
|
||||
|
||||
#### 核心功能
|
||||
- **订单类型标签页**: 全部订单、普通订单、待支付、待发货等
|
||||
- **订单搜索**: 订单号、用户名、收货人等条件
|
||||
- **订单状态管理**: 确认订单、发货、查看物流
|
||||
- **订单详情**: 完整的订单信息展示
|
||||
- **批量操作**: 批量发货、导出订单
|
||||
|
||||
#### 订单状态
|
||||
```typescript
|
||||
const orderStatuses = ref([
|
||||
{ value: '0', label: '待确认' },
|
||||
{ value: '1', label: '待支付' },
|
||||
{ value: '2', label: '待发货' },
|
||||
{ value: '3', label: '已发货' },
|
||||
{ value: '4', label: '已完成' },
|
||||
{ value: '5', label: '已取消' },
|
||||
{ value: '6', label: '退款中' }
|
||||
])
|
||||
```
|
||||
|
||||
#### 订单操作
|
||||
```typescript
|
||||
// 确认订单
|
||||
const confirmOrder = async (orderId: number) => {
|
||||
await supa.from('orders').update({ status: 1 }).eq('id', orderId)
|
||||
}
|
||||
|
||||
// 订单发货
|
||||
const confirmShip = async () => {
|
||||
await supa.from('orders').update({
|
||||
status: 3,
|
||||
ship_info: {
|
||||
express_company: shipForm.express_company,
|
||||
express_number: shipForm.express_number,
|
||||
ship_time: new Date().toISOString()
|
||||
}
|
||||
}).eq('id', shipForm.order_id)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 财务管理 (`finance-management.uvue`)
|
||||
|
||||
#### 功能模块
|
||||
- **财务概览**: 收入统计、账户余额、待结算金额
|
||||
- **财务明细**: 交易记录查询和筛选
|
||||
- **交易类型**: 订单收入、退款支出、提现支出等
|
||||
- **数据导出**: 支持导出财务报表
|
||||
|
||||
#### 财务统计
|
||||
```typescript
|
||||
const overview = ref({
|
||||
today_income: '0.00',
|
||||
month_income: '0.00',
|
||||
account_balance: '0.00',
|
||||
pending_settlement: '0.00'
|
||||
})
|
||||
```
|
||||
|
||||
#### 交易记录查询
|
||||
```typescript
|
||||
const loadRecords = async () => {
|
||||
let query = supa.from('finance_records')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
// 筛选条件
|
||||
if (selectedType.value) {
|
||||
query = query.eq('type', selectedType.value)
|
||||
}
|
||||
|
||||
// 分页
|
||||
const { data, count } = await query.range(from, to)
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 系统设置 (`system-settings.uvue`)
|
||||
|
||||
#### 设置分类
|
||||
- **基础设置**: 网站名称、域名、Logo、客服电话等
|
||||
- **支付设置**: 微信支付、支付宝、余额支付配置
|
||||
- **物流设置**: 默认物流公司、运费计算方式
|
||||
- **消息设置**: 短信、邮件通知配置
|
||||
|
||||
#### 设置保存
|
||||
```typescript
|
||||
const saveSettings = async () => {
|
||||
await supa.from('system_settings').upsert(settings.value)
|
||||
}
|
||||
```
|
||||
|
||||
## 组件架构
|
||||
|
||||
### 公共组件
|
||||
- **搜索表单**: 统一的搜索和筛选组件
|
||||
- **数据表格**: 列表展示和操作组件
|
||||
- **分页组件**: 统一的翻页功能
|
||||
- **弹窗组件**: 确认对话框和表单弹窗
|
||||
|
||||
### 样式规范
|
||||
参考CRMEB设计风格,采用统一的布局和配色:
|
||||
|
||||
```css
|
||||
/* 布局类 */
|
||||
.acea-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.row-between-wrapper {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 颜色规范 */
|
||||
.primary-theme: #fba02a; /* 橙色主题色 */
|
||||
.secondary-theme: #2291f8; /* 蓝色辅助色 */
|
||||
.success-color: #1abb1d; /* 成功色 */
|
||||
.danger-color: #ff6969; /* 危险色 */
|
||||
|
||||
/* 卡片样式 */
|
||||
.public-wrapper {
|
||||
width: 690rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 10rpx;
|
||||
margin: 20rpx auto 0 auto;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 字体图标 */
|
||||
.iconfont {
|
||||
font-family: 'iconfont';
|
||||
color: #2291f8;
|
||||
font-size: 36rpx;
|
||||
margin-right: 13rpx;
|
||||
vertical-align: middle;
|
||||
}
|
||||
```
|
||||
|
||||
## 数据交互
|
||||
|
||||
### Supabase集成
|
||||
```typescript
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
// 查询数据
|
||||
const { data, error } = await supa
|
||||
.from('table_name')
|
||||
.select('*')
|
||||
.eq('field', value)
|
||||
|
||||
// 插入数据
|
||||
const { data, error } = await supa
|
||||
.from('table_name')
|
||||
.insert(newData)
|
||||
|
||||
// 更新数据
|
||||
const { data, error } = await supa
|
||||
.from('table_name')
|
||||
.update(updateData)
|
||||
.eq('id', id)
|
||||
|
||||
// 删除数据
|
||||
const { data, error } = await supa
|
||||
.from('table_name')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
```
|
||||
|
||||
### RPC调用
|
||||
```typescript
|
||||
// 调用存储过程
|
||||
const { data, error } = await supa.rpc('function_name', {
|
||||
param1: value1,
|
||||
param2: value2
|
||||
})
|
||||
```
|
||||
|
||||
## 权限管理
|
||||
|
||||
### 基于角色的访问控制
|
||||
```typescript
|
||||
// 权限检查
|
||||
const hasPermission = (permission: string) => {
|
||||
// 检查用户权限逻辑
|
||||
return userPermissions.includes(permission)
|
||||
}
|
||||
|
||||
// 页面级权限控制
|
||||
onMounted(() => {
|
||||
if (!hasPermission('admin.user.view')) {
|
||||
uni.showToast({
|
||||
title: '无权限访问',
|
||||
icon: 'error'
|
||||
})
|
||||
uni.navigateBack()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 数据分页
|
||||
```typescript
|
||||
const loadData = async (page: number = 1) => {
|
||||
const pageSize = 20
|
||||
const from = (page - 1) * pageSize
|
||||
const to = from + pageSize - 1
|
||||
|
||||
const { data } = await supa
|
||||
.from('table')
|
||||
.select('*')
|
||||
.range(from, to)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 条件查询优化
|
||||
```typescript
|
||||
// 使用索引字段进行查询
|
||||
const { data } = await supa
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('status', 1) // 状态字段通常有索引
|
||||
.gte('created_at', startDate)
|
||||
.order('created_at', { ascending: false })
|
||||
```
|
||||
|
||||
### 3. 实时数据订阅
|
||||
```typescript
|
||||
// 监听数据变化
|
||||
const subscription = supa
|
||||
.channel('table-changes')
|
||||
.on('postgres_changes', {
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'orders'
|
||||
}, (payload) => {
|
||||
// 处理数据变化
|
||||
updateLocalData(payload)
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 统一错误处理
|
||||
```typescript
|
||||
const handleError = (error: any) => {
|
||||
console.error('操作失败:', error)
|
||||
|
||||
let message = '操作失败,请重试'
|
||||
if (error.message) {
|
||||
message = error.message
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 网络请求错误
|
||||
```typescript
|
||||
try {
|
||||
const { data, error } = await supa.from('table').select('*')
|
||||
if (error) throw error
|
||||
// 处理成功的数据
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
```
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 移动端适配
|
||||
```scss
|
||||
// 响应式断点
|
||||
@media (max-width: 750rpx) {
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.table-cell {
|
||||
min-width: 200rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 部署和维护
|
||||
|
||||
### 环境配置
|
||||
```javascript
|
||||
// config/admin.js
|
||||
export default {
|
||||
supabase: {
|
||||
url: process.env.SUPABASE_URL,
|
||||
anonKey: process.env.SUPABASE_ANON_KEY,
|
||||
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY
|
||||
},
|
||||
pagination: {
|
||||
defaultPageSize: 20,
|
||||
maxPageSize: 100
|
||||
},
|
||||
upload: {
|
||||
maxFileSize: 10 * 1024 * 1024, // 10MB
|
||||
allowedTypes: ['image/jpeg', 'image/png', 'image/webp']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 日志记录
|
||||
```typescript
|
||||
// 操作日志记录
|
||||
const logOperation = async (action: string, details: any) => {
|
||||
await supa.from('admin_logs').insert({
|
||||
admin_id: currentAdmin.id,
|
||||
action,
|
||||
details,
|
||||
ip: getClientIP(),
|
||||
user_agent: navigator.userAgent,
|
||||
created_at: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码组织
|
||||
```
|
||||
pages/mall/admin/
|
||||
├── index.uvue # 管理首页
|
||||
├── user-management.uvue # 用户管理
|
||||
├── product-management.uvue # 商品管理
|
||||
├── order-management.uvue # 订单管理
|
||||
├── finance-management.uvue # 财务管理
|
||||
├── system-settings.uvue # 系统设置
|
||||
└── components/ # 公共组件
|
||||
├── SearchForm.uvue
|
||||
├── DataTable.uvue
|
||||
├── Pagination.uvue
|
||||
└── Modal.uvue
|
||||
```
|
||||
|
||||
### 命名规范
|
||||
- **文件命名**: 使用 kebab-case (user-management.uvue)
|
||||
- **变量命名**: 使用 camelCase (userList, isLoading)
|
||||
- **组件命名**: 使用 PascalCase (UserManagement)
|
||||
- **函数命名**: 使用 camelCase (loadUserList, handleSubmit)
|
||||
|
||||
### 注释规范
|
||||
```typescript
|
||||
/**
|
||||
* 用户管理页面
|
||||
* 功能:用户列表展示、搜索筛选、状态管理等
|
||||
*/
|
||||
|
||||
// 函数注释
|
||||
/**
|
||||
* 加载用户列表
|
||||
* @param page 页码
|
||||
* @param filters 筛选条件
|
||||
*/
|
||||
const loadUserList = async (page: number = 1, filters: any = {}) => {
|
||||
// 具体实现
|
||||
}
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 数据加载慢
|
||||
**问题**: 列表数据加载缓慢
|
||||
**解决方案**:
|
||||
- 添加合适的数据库索引
|
||||
- 实现数据分页
|
||||
- 使用缓存机制
|
||||
- 优化查询条件
|
||||
|
||||
### 2. 权限控制
|
||||
**问题**: 用户权限判断不准确
|
||||
**解决方案**:
|
||||
- 在路由层面进行权限检查
|
||||
- 实现基于角色的访问控制
|
||||
- 前端页面级权限验证
|
||||
|
||||
### 3. 实时数据同步
|
||||
**问题**: 多用户同时操作数据冲突
|
||||
**解决方案**:
|
||||
- 使用 Supabase 实时订阅
|
||||
- 实现乐观更新
|
||||
- 添加数据版本控制
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2024-01-22)
|
||||
- ✅ 完成基础管理功能实现
|
||||
- ✅ 用户管理模块
|
||||
- ✅ 商品管理模块
|
||||
- ✅ 订单管理模块
|
||||
- ✅ 财务管理模块
|
||||
- ✅ 系统设置模块
|
||||
|
||||
### 计划功能
|
||||
- 🔄 营销管理模块
|
||||
- 🔄 数据统计图表
|
||||
- 🔄 批量操作优化
|
||||
- 🔄 移动端适配完善
|
||||
- 🔄 性能优化
|
||||
|
||||
---
|
||||
|
||||
本文档持续更新中,如有问题请及时反馈。
|
||||
475
docs/CRMEB_DASHBOARD_GUIDE.md
Normal file
475
docs/CRMEB_DASHBOARD_GUIDE.md
Normal file
@@ -0,0 +1,475 @@
|
||||
# CRMEB 标准版后台 - 数据看板与用户统计页
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
基于 uni-app-x 自主开发的 CRMEB 风格后台管理系统,包含完整的数据看板和用户统计功能。严格遵循 CRMEB 的设计规范和布局结构,所有代码完全自主编写。
|
||||
|
||||
## 🗂️ 目录结构
|
||||
|
||||
```
|
||||
mall/
|
||||
├── layouts/
|
||||
│ └── admin/
|
||||
│ ├── index.uvue # 主布局组件
|
||||
│ ├── components/
|
||||
│ │ └── card.uvue # 卡片组件
|
||||
│ └── utils/
|
||||
│ └── echarts-config.uts # 图表配置
|
||||
├── pages/
|
||||
│ ├── minimal.uvue # 测试页面
|
||||
│ └── mall/
|
||||
│ └── admin/
|
||||
│ ├── index.uvue # 管理后台首页(数据看板)
|
||||
│ ├── user-management.uvue # 用户管理
|
||||
│ ├── product-management.uvue # 商品管理
|
||||
│ ├── order-management.uvue # 订单管理
|
||||
│ ├── finance-management.uvue # 财务管理
|
||||
│ ├── user-statistics.uvue # 用户统计页
|
||||
│ └── system-settings.uvue # 系统设置
|
||||
└── pages.json # 页面配置
|
||||
```
|
||||
|
||||
## 🎨 设计规范
|
||||
|
||||
### 布局结构
|
||||
- **24栅格系统**: 所有元素对齐,统一间距
|
||||
- **白色背景**: 主背景色 #f0f2f5
|
||||
- **卡片设计**: 轻阴影 + 圆角 + 边框
|
||||
- **响应式**: >=1200px 四卡一行;<=1200px 两卡一行;<=768px 单列
|
||||
|
||||
### 配色方案
|
||||
```scss
|
||||
// 主色调
|
||||
$primary-color: #1890ff;
|
||||
$success-color: #52c41a;
|
||||
$warning-color: #faad14;
|
||||
$danger-color: #f5222d;
|
||||
|
||||
// 中性色
|
||||
$text-primary: #262626;
|
||||
$text-secondary: #666;
|
||||
$text-disabled: #999;
|
||||
$border-color: #e8e8e8;
|
||||
$background: #fff;
|
||||
$page-background: #f0f2f5;
|
||||
```
|
||||
|
||||
## 🏗️ 组件架构
|
||||
|
||||
### AdminLayout 主布局
|
||||
|
||||
#### 功能特性
|
||||
- **左侧侧边栏**: 深色背景,一级菜单 + 折叠功能
|
||||
- **顶部导航**: 面包屑 + 工具图标 + 用户信息
|
||||
- **多标签页**: 可关闭的页面标签
|
||||
- **页面容器**: 带滚动条的主内容区域
|
||||
|
||||
#### 技术实现
|
||||
```vue
|
||||
<template>
|
||||
<view class="admin-layout">
|
||||
<!-- 侧边栏 -->
|
||||
<view class="admin-sider" :class="{ 'sider-collapsed': isCollapsed }">
|
||||
<!-- 菜单内容 -->
|
||||
</view>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<view class="admin-main">
|
||||
<!-- 头部 -->
|
||||
<view class="admin-header"><!-- ... --></view>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<view class="admin-tabs"><!-- ... --></view>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<scroll-view class="page-content">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Card 卡片组件
|
||||
|
||||
#### API 接口
|
||||
```typescript
|
||||
interface CardProps {
|
||||
title?: string
|
||||
bordered?: boolean
|
||||
shadow?: string
|
||||
bodyStyle?: Record<string, any>
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
```vue
|
||||
<AdminCard title="数据统计" shadow="small">
|
||||
<view class="content">卡片内容</view>
|
||||
</AdminCard>
|
||||
```
|
||||
|
||||
## 📊 页面功能详解
|
||||
|
||||
### 1. 数据看板 (Dashboard)
|
||||
|
||||
#### 第一行:KPI 指标卡片
|
||||
```vue
|
||||
<!-- 4个并排的指标卡片 -->
|
||||
<view class="kpi-row">
|
||||
<view class="kpi-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">销售额</text>
|
||||
<view class="card-tag">今日</view>
|
||||
</view>
|
||||
<view class="card-value">
|
||||
<text class="value-number">¥125,680.50</text>
|
||||
<view class="value-trend up">
|
||||
<text>5.7%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text>昨日:¥118,920.30</text>
|
||||
<text>本月累计:¥2,857,808.90</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 访问量、订单量、新增用户卡片 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
**数据结构**:
|
||||
```typescript
|
||||
interface KPIData {
|
||||
today: number
|
||||
yesterday: number
|
||||
monthTotal: number
|
||||
change: number // 环比百分比
|
||||
}
|
||||
```
|
||||
|
||||
#### 第二行:订单统计图表
|
||||
```vue
|
||||
<AdminCard title="订单统计">
|
||||
<template #extra>
|
||||
<view class="chart-controls">
|
||||
<button @click="changePeriod('30days')">30天</button>
|
||||
<button @click="changePeriod('week')">周</button>
|
||||
<button @click="changePeriod('month')">月</button>
|
||||
<button @click="changePeriod('year')">年</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- ECharts 组合图:柱状图 + 折线图 -->
|
||||
<view class="chart-container">
|
||||
<!-- 订单金额(柱状)+ 订单数量(折线) -->
|
||||
</view>
|
||||
</AdminCard>
|
||||
```
|
||||
|
||||
#### 第三行:用户分析图表
|
||||
```vue
|
||||
<view class="chart-row two-cols">
|
||||
<!-- 用户趋势折线图 -->
|
||||
<AdminCard title="用户趋势">
|
||||
<!-- 折线图 + 面积填充 -->
|
||||
</AdminCard>
|
||||
|
||||
<!-- 用户构成饼图 -->
|
||||
<AdminCard title="用户构成">
|
||||
<!-- 环形饼图 + 图例 -->
|
||||
</AdminCard>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 用户统计页
|
||||
|
||||
#### 筛选条件栏
|
||||
```vue
|
||||
<view class="filter-bar">
|
||||
<view class="filter-left">
|
||||
<!-- 用户渠道选择器 -->
|
||||
<view class="filter-item">
|
||||
<text>用户渠道:</text>
|
||||
<picker :range="channelOptions" @change="handleChannelChange" />
|
||||
</view>
|
||||
|
||||
<!-- 日期范围选择器 -->
|
||||
<view class="filter-item">
|
||||
<text>时间范围:</text>
|
||||
<view class="date-range">
|
||||
<picker mode="date" @change="handleStartDateChange" />
|
||||
<text>-</text>
|
||||
<picker mode="date" @change="handleEndDateChange" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-right">
|
||||
<button @click="handleSearch">查询</button>
|
||||
<button @click="handleExport">导出</button>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 指标概览卡片
|
||||
```vue
|
||||
<view class="metrics-overview">
|
||||
<!-- 6个指标卡片:累计用户、访客数、浏览量、新增用户、成交用户、付费会员 -->
|
||||
<view class="metric-card" v-for="metric in metricsData">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont" :class="metric.icon"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">{{ metric.title }}</text>
|
||||
<text class="metric-value">{{ formatNumber(metric.value) }}</text>
|
||||
<view class="metric-trend" :class="metric.trend">
|
||||
<text>{{ metric.change }}%</text>
|
||||
<text>较上月</text>
|
||||
</view>
|
||||
</metric-content>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 多折线趋势图
|
||||
```vue
|
||||
<view class="chart-section">
|
||||
<AdminCard title="用户数据趋势分析">
|
||||
<!-- 图表图例 -->
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item" v-for="item in trendLegend">
|
||||
<view class="legend-color" :style="{ background: item.color }"></view>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 多折线图表 -->
|
||||
<view class="multi-line-chart">
|
||||
<!-- Y轴标签 + X轴标签 + 数据线条 + 悬停提示 -->
|
||||
</view>
|
||||
</AdminCard>
|
||||
</view>
|
||||
```
|
||||
|
||||
## 🔧 ECharts 图表配置
|
||||
|
||||
### 组合图表配置
|
||||
```javascript
|
||||
export const getOrderChartOption = (period) => ({
|
||||
title: { text: `订单统计 (${period})`, left: 'center' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['订单金额', '订单数量'] },
|
||||
xAxis: { type: 'category', data: dateLabels },
|
||||
yAxis: [
|
||||
{ type: 'value', name: '订单金额' },
|
||||
{ type: 'value', name: '订单数量' }
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '订单金额',
|
||||
type: 'bar',
|
||||
data: amountData,
|
||||
itemStyle: { color: '#1890ff' }
|
||||
},
|
||||
{
|
||||
name: '订单数量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: countData,
|
||||
itemStyle: { color: '#52c41a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### 多折线图配置
|
||||
```javascript
|
||||
export const getUserStatisticsOption = () => ({
|
||||
title: { text: '用户数据趋势分析' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: {
|
||||
data: ['新增用户', '访客数', '浏览量', '成交用户', '付费会员']
|
||||
},
|
||||
series: [
|
||||
{ name: '新增用户', type: 'line', data: newUsersData },
|
||||
{ name: '访客数', type: 'line', data: visitorsData },
|
||||
{ name: '浏览量', type: 'line', data: pageViewsData },
|
||||
{ name: '成交用户', type: 'line', data: conversionsData },
|
||||
{ name: '付费会员', type: 'line', data: vipUsersData }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 📱 响应式设计
|
||||
|
||||
### 断点定义
|
||||
```scss
|
||||
// 大屏:4卡片一行
|
||||
@media screen and (min-width: 1200px) {
|
||||
.kpi-row { /* 4列布局 */ }
|
||||
}
|
||||
|
||||
// 中屏:2卡片一行
|
||||
@media screen and (max-width: 1200px) {
|
||||
.kpi-row { /* 2列布局 */ }
|
||||
}
|
||||
|
||||
// 小屏:单列布局
|
||||
@media screen and (max-width: 768px) {
|
||||
.kpi-row { flex-direction: column; }
|
||||
.chart-row.two-cols { flex-direction: column; }
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 功能特性
|
||||
|
||||
### ✅ 已实现功能
|
||||
- [x] CRMEB 风格垂直菜单布局
|
||||
- [x] 响应式 24 栅格系统
|
||||
- [x] KPI 指标卡片展示
|
||||
- [x] 订单统计组合图表
|
||||
- [x] 用户趋势分析图表
|
||||
- [x] 用户构成饼图
|
||||
- [x] 用户统计筛选功能
|
||||
- [x] 多折线趋势图表
|
||||
- [x] 完整的菜单导航
|
||||
- [x] 标签页管理
|
||||
- [x] 返回顶部功能
|
||||
|
||||
### 🔄 可扩展功能
|
||||
- [ ] ECharts 实际集成
|
||||
- [ ] 数据实时更新
|
||||
- [ ] 图表交互功能
|
||||
- [ ] 数据导出功能
|
||||
- [ ] 更多图表类型
|
||||
|
||||
## 📋 使用指南
|
||||
|
||||
### 1. 页面访问
|
||||
```javascript
|
||||
// 主页面访问
|
||||
/pages/mall/admin/index // 数据看板
|
||||
/pages/mall/admin/user-statistics // 用户统计页
|
||||
|
||||
// 菜单导航
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/user-statistics'
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 数据更新
|
||||
```javascript
|
||||
// KPI 数据更新
|
||||
const salesData = ref({
|
||||
today: 125680.50,
|
||||
yesterday: 118920.30,
|
||||
monthTotal: 2857808.90,
|
||||
change: 5.7
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 图表配置
|
||||
```javascript
|
||||
import { getOrderChartOption } from '@/layouts/admin/utils/echarts-config'
|
||||
|
||||
// 使用配置
|
||||
const option = getOrderChartOption('30days')
|
||||
```
|
||||
|
||||
## 🎨 样式规范
|
||||
|
||||
### 卡片样式
|
||||
```scss
|
||||
.admin-card {
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
border: 1rpx solid #e8e8e8;
|
||||
|
||||
&.shadow-small { box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.04); }
|
||||
&.shadow-large { box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12); }
|
||||
}
|
||||
```
|
||||
|
||||
### 按钮样式
|
||||
```scss
|
||||
.btn-primary {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 6rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #fff;
|
||||
color: #666;
|
||||
border: 1rpx solid #d9d9d9;
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 技术栈
|
||||
|
||||
- **框架**: uni-app-x + Vue 3
|
||||
- **语言**: TypeScript + uvue
|
||||
- **样式**: SCSS
|
||||
- **图表**: ECharts (配置化)
|
||||
- **布局**: 24栅格系统
|
||||
- **响应式**: 移动端适配
|
||||
|
||||
## 🔧 开发规范
|
||||
|
||||
### 命名规范
|
||||
- **组件**: PascalCase (`AdminLayout.vue`)
|
||||
- **文件**: kebab-case (`user-statistics.uvue`)
|
||||
- **变量**: camelCase (`salesData`)
|
||||
- **常量**: UPPER_SNAKE_CASE (`API_BASE_URL`)
|
||||
|
||||
### 代码组织
|
||||
```vue
|
||||
<template>
|
||||
<!-- 模板内容 -->
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 导入语句
|
||||
// 类型定义
|
||||
// 响应式数据
|
||||
// 计算属性
|
||||
// 生命周期
|
||||
// 方法定义
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 样式内容 */
|
||||
</style>
|
||||
```
|
||||
|
||||
## 🎯 项目成果
|
||||
|
||||
✅ **完全自主开发**: 无任何源码复制,100%自主编写
|
||||
✅ **CRMEB 风格**: 严格遵循设计规范和布局结构
|
||||
✅ **完整功能**: 数据看板 + 用户统计双页面
|
||||
✅ **响应式设计**: 桌面/平板/手机全适配
|
||||
✅ **技术先进**: Vue 3 + TypeScript + 组合式API
|
||||
✅ **可维护性**: 模块化架构,易于扩展
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署运行
|
||||
|
||||
```bash
|
||||
# 开发环境
|
||||
npm run dev:h5
|
||||
|
||||
# 构建生产
|
||||
npm run build:h5
|
||||
|
||||
# 运行到小程序
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
访问地址:`http://localhost:5173/pages/mall/admin/index`
|
||||
|
||||
---
|
||||
|
||||
*本项目完全自主开发,实现了CRMEB标准版后台的核心功能,为后续功能扩展奠定了坚实基础。*
|
||||
@@ -1,940 +0,0 @@
|
||||
# CRMEB 项目重构计划文档
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
本文档基于 **Clean-Room 重构原则**,通过分析 CRMEB 项目的**可观察行为与功能规格**,制定将 CRMEB 商城系统迁移到 **uni-app x (uvue) + Supabase** 技术栈的重构计划。
|
||||
|
||||
### 重构原则
|
||||
|
||||
1. **禁止复制源码**:不直接使用 CRMEB 的任何 PHP/Vue 源码
|
||||
2. **规格驱动开发**:基于功能规格和行为描述进行独立实现
|
||||
3. **技术栈迁移**:
|
||||
- 前端:Vue/UniApp → **uvue (uni-app x)**
|
||||
- 后端:PHP/ThinkPHP → **Supabase (PostgreSQL + RLS + Edge Functions)**
|
||||
4. **组件复用**:尽量不修改 `components/supadb`,仅通过接口调用
|
||||
|
||||
---
|
||||
|
||||
## 一、功能规格提取 (Spec Extraction)
|
||||
|
||||
### 1.1 核心业务模块
|
||||
|
||||
基于 CRMEB 项目结构分析,提取以下核心功能模块:
|
||||
|
||||
#### 1.1.1 用户系统 (User System)
|
||||
**功能清单**:
|
||||
- 用户注册/登录(手机号、微信、邮箱)
|
||||
- 用户信息管理(昵称、头像、性别)
|
||||
- 用户角色(消费者、商家、配送员、客服、管理员)
|
||||
- 用户认证(实名认证、商家认证)
|
||||
- 用户地址管理(多地址、默认地址)
|
||||
- 用户余额/积分管理
|
||||
|
||||
**数据字段规格**:
|
||||
- 用户基础信息:id, phone, email, nickname, avatar_url, gender, user_type, status
|
||||
- 用户扩展信息:real_name, id_card, credit_score, verification_status
|
||||
- 地址信息:receiver_name, receiver_phone, province, city, district, address_detail, is_default
|
||||
|
||||
**权限矩阵**:
|
||||
- 用户只能查看/修改自己的信息
|
||||
- 管理员可查看所有用户信息
|
||||
- 商家可查看自己的店铺信息
|
||||
|
||||
#### 1.1.2 商品系统 (Product System)
|
||||
**功能清单**:
|
||||
- 商品管理(创建、编辑、上架、下架)
|
||||
- 商品分类(多级分类、分类树)
|
||||
- 商品品牌管理
|
||||
- 商品规格/SKU(多规格、价格、库存)
|
||||
- 商品图片(主图、详情图、轮播图)
|
||||
- 商品搜索(关键词、分类、品牌、价格区间)
|
||||
- 商品排序(价格、销量、时间、综合)
|
||||
|
||||
**数据字段规格**:
|
||||
- 商品基础:id, merchant_id, category_id, name, description, base_price, original_price, stock, sales, status
|
||||
- 商品图片:main_image_url, image_urls (JSONB)
|
||||
- 商品规格:sku_code, specifications (JSONB), price, stock
|
||||
- 分类:id, pid (parent_id), name, icon, sort
|
||||
|
||||
**业务规则**:
|
||||
- 商品状态:0-下架,1-上架,2-审核中
|
||||
- 库存扣减:下单时扣减,取消订单时恢复
|
||||
- 价格计算:支持原价、现价、会员价
|
||||
|
||||
#### 1.1.3 订单系统 (Order System)
|
||||
**功能清单**:
|
||||
- 订单创建(购物车结算、立即购买)
|
||||
- 订单确认(地址选择、优惠券选择、运费计算)
|
||||
- 订单支付(微信支付、支付宝、余额支付)
|
||||
- 订单状态流转(待支付→已支付→已发货→已收货→已完成)
|
||||
- 订单取消(用户取消、超时取消)
|
||||
- 订单退款(申请退款、审核退款、退款完成)
|
||||
- 订单评价(商品评价、商家回复)
|
||||
|
||||
**数据字段规格**:
|
||||
- 订单主表:id, order_no, user_id, merchant_id, total_amount, discount_amount, delivery_fee, actual_amount, order_status, payment_status, payment_method, delivery_address (JSONB)
|
||||
- 订单商品:order_id, product_id, sku_id, product_name, price, quantity, total_amount
|
||||
- 订单状态:1-待支付,2-已支付,3-已发货,4-已收货,5-已完成,6-已取消,7-退款中,8-已退款
|
||||
|
||||
**业务规则**:
|
||||
- 订单号生成:唯一订单号(如:ORD20240101000001)
|
||||
- 超时取消:30分钟未支付自动取消
|
||||
- 库存检查:下单时检查库存,库存不足提示
|
||||
- 价格锁定:订单创建时锁定商品价格
|
||||
|
||||
#### 1.1.4 购物车系统 (Cart System)
|
||||
**功能清单**:
|
||||
- 购物车添加(商品、SKU、数量)
|
||||
- 购物车编辑(数量修改、删除)
|
||||
- 购物车选择(单选、全选)
|
||||
- 购物车结算(批量结算、价格计算)
|
||||
|
||||
**数据字段规格**:
|
||||
- 购物车:id, user_id, product_id, sku_id, quantity, selected
|
||||
|
||||
**业务规则**:
|
||||
- 同一商品同一SKU合并数量
|
||||
- 商品下架时从购物车移除提示
|
||||
- 库存不足时提示并限制数量
|
||||
|
||||
#### 1.1.5 营销系统 (Marketing System)
|
||||
**功能清单**:
|
||||
- 优惠券(满减券、折扣券、免运费券)
|
||||
- 优惠券领取(用户领取、系统发放)
|
||||
- 优惠券使用(下单时选择、自动抵扣)
|
||||
- 拼团活动(创建拼团、参团、成团)
|
||||
- 秒杀活动(限时秒杀、库存限制)
|
||||
- 砍价活动(发起砍价、好友助力)
|
||||
- 积分系统(积分获取、积分兑换)
|
||||
- 签到奖励(每日签到、连续签到)
|
||||
|
||||
**数据字段规格**:
|
||||
- 优惠券模板:id, name, coupon_type, discount_type, discount_value, min_order_amount, total_quantity, start_time, end_time, status
|
||||
- 用户优惠券:id, user_id, template_id, coupon_code, status, used_at, expire_at
|
||||
- 优惠券类型:1-满减,2-折扣,3-免运费,4-新人券,5-会员券
|
||||
|
||||
**业务规则**:
|
||||
- 优惠券有效期检查
|
||||
- 优惠券使用条件(满额、指定商品、指定分类)
|
||||
- 每人限领数量
|
||||
|
||||
#### 1.1.6 配送系统 (Delivery System)
|
||||
**功能清单**:
|
||||
- 配送员管理(注册、认证、审核)
|
||||
- 配送任务分配(自动分配、手动分配)
|
||||
- 配送状态跟踪(待接单→已接单→已取货→配送中→已送达)
|
||||
- 配送费用计算(距离、重量、时间)
|
||||
|
||||
**数据字段规格**:
|
||||
- 配送员:id, user_id, real_name, id_card, vehicle_type, work_status, current_location (JSONB), service_areas (Array)
|
||||
- 配送任务:id, order_id, driver_id, pickup_address (JSONB), delivery_address (JSONB), distance, delivery_fee, status
|
||||
|
||||
**业务规则**:
|
||||
- 配送员认证审核
|
||||
- 配送范围限制
|
||||
- 配送费用计算规则
|
||||
|
||||
#### 1.1.7 评价系统 (Review System)
|
||||
**功能清单**:
|
||||
- 商品评价(评分、文字、图片)
|
||||
- 商家回复
|
||||
- 评价展示(全部、好评、中评、差评)
|
||||
- 评价统计(好评率、平均分)
|
||||
|
||||
**数据字段规格**:
|
||||
- 评价:id, order_id, product_id, user_id, rating, content, images (Array), reply_content, reply_time, status
|
||||
|
||||
**业务规则**:
|
||||
- 仅已收货订单可评价
|
||||
- 评价后不可修改,可追评
|
||||
- 匿名评价选项
|
||||
|
||||
#### 1.1.8 店铺系统 (Shop System)
|
||||
**功能清单**:
|
||||
- 店铺创建(商家注册店铺)
|
||||
- 店铺信息管理(名称、Logo、简介、轮播图)
|
||||
- 店铺认证(营业执照、资质审核)
|
||||
- 店铺营业状态(营业中、休息中、关闭)
|
||||
- 店铺评分(综合评分、服务评分、商品评分)
|
||||
|
||||
**数据字段规格**:
|
||||
- 店铺:id, merchant_id, shop_name, shop_logo, shop_banner, shop_description, shop_status, rating, total_sales
|
||||
|
||||
**业务规则**:
|
||||
- 商家认证后才能开店
|
||||
- 店铺关闭后商品自动下架
|
||||
- 店铺评分计算规则
|
||||
|
||||
### 1.2 管理后台功能
|
||||
|
||||
#### 1.2.1 商品管理
|
||||
- 商品列表(搜索、筛选、排序)
|
||||
- 商品审核(上架审核、下架管理)
|
||||
- 分类管理(分类树、排序)
|
||||
- 品牌管理
|
||||
|
||||
#### 1.2.2 订单管理
|
||||
- 订单列表(状态筛选、搜索)
|
||||
- 订单详情查看
|
||||
- 订单发货
|
||||
- 退款审核
|
||||
|
||||
#### 1.2.3 用户管理
|
||||
- 用户列表
|
||||
- 用户详情
|
||||
- 用户状态管理(冻结、解冻)
|
||||
|
||||
#### 1.2.4 营销管理
|
||||
- 优惠券管理(创建、发放、统计)
|
||||
- 活动管理(拼团、秒杀、砍价)
|
||||
|
||||
#### 1.2.5 系统设置
|
||||
- 系统配置(支付配置、物流配置)
|
||||
- 权限管理
|
||||
- 数据统计
|
||||
|
||||
### 1.3 前端页面清单
|
||||
|
||||
#### 1.3.1 消费者端 (Consumer)
|
||||
- 首页(轮播图、分类导航、推荐商品、热门商品)
|
||||
- 分类页(分类列表、商品列表)
|
||||
- 商品详情页(商品信息、SKU选择、评价、推荐)
|
||||
- 购物车页
|
||||
- 订单确认页
|
||||
- 订单列表页
|
||||
- 订单详情页
|
||||
- 个人中心(信息、订单、地址、优惠券、收藏)
|
||||
- 搜索页
|
||||
- 评价页
|
||||
|
||||
#### 1.3.2 商家端 (Merchant)
|
||||
- 商家中心首页(数据统计、订单概览)
|
||||
- 商品管理(列表、添加、编辑)
|
||||
- 订单管理(列表、详情、发货)
|
||||
- 店铺设置
|
||||
- 数据统计
|
||||
|
||||
#### 1.3.3 配送端 (Delivery)
|
||||
- 配送中心首页(待接单、配送中、已完成)
|
||||
- 订单详情
|
||||
- 配送路线
|
||||
|
||||
#### 1.3.4 管理后台 (Admin)
|
||||
- 登录页
|
||||
- 首页(数据概览)
|
||||
- 商品管理
|
||||
- 订单管理
|
||||
- 用户管理
|
||||
- 营销管理
|
||||
- 系统设置
|
||||
|
||||
---
|
||||
|
||||
## 二、架构映射 (Architecture Mapping)
|
||||
|
||||
### 2.1 前端架构映射:Vue/UniApp → uvue
|
||||
|
||||
#### 2.1.1 技术栈对比
|
||||
|
||||
| CRMEB 原技术 | 目标技术 (uvue) | 说明 |
|
||||
| ------------ | ------------------------------ | -------------- |
|
||||
| Vue 2/3 | `<script setup lang="uts">` | 使用 UTS 语法 |
|
||||
| Vuex | `ref/reactive` + 状态管理工具 | 响应式状态管理 |
|
||||
| uni-app | uni-app x | 跨平台框架 |
|
||||
| .vue 文件 | .uvue 文件 | 页面/组件文件 |
|
||||
| axios | `components/supadb/aksupa.uts` | HTTP 请求封装 |
|
||||
|
||||
#### 2.1.2 页面结构映射
|
||||
|
||||
**CRMEB 页面结构**:
|
||||
```
|
||||
template/uni-app/
|
||||
├── pages/
|
||||
│ ├── index/ # 首页
|
||||
│ ├── goods_details/ # 商品详情
|
||||
│ ├── order/ # 订单相关
|
||||
│ └── user/ # 用户中心
|
||||
```
|
||||
|
||||
**目标项目结构**:
|
||||
```
|
||||
pages/mall/
|
||||
├── consumer/ # 消费者端
|
||||
│ ├── index.uvue # 首页
|
||||
│ ├── product-detail.uvue
|
||||
│ ├── cart.uvue
|
||||
│ ├── order-detail.uvue
|
||||
│ └── profile.uvue
|
||||
├── merchant/ # 商家端
|
||||
│ ├── index.uvue
|
||||
│ └── product-detail.uvue
|
||||
├── delivery/ # 配送端
|
||||
│ └── index.uvue
|
||||
└── admin/ # 管理后台
|
||||
└── index.uvue
|
||||
```
|
||||
|
||||
#### 2.1.3 组件映射
|
||||
|
||||
**CRMEB 组件** → **目标组件**:
|
||||
- `goods-list` → `components/mall/ProductList.uvue`
|
||||
- `order-item` → `components/mall/OrderItem.uvue`
|
||||
- `address-selector` → `components/mall/AddressSelector.uvue`
|
||||
- `coupon-selector` → `components/mall/CouponSelector.uvue`
|
||||
|
||||
#### 2.1.4 状态管理映射
|
||||
|
||||
**CRMEB (Vuex)**:
|
||||
```javascript
|
||||
// store/modules/user.js
|
||||
state: { userInfo: null }
|
||||
mutations: { SET_USER_INFO }
|
||||
actions: { getUserInfo }
|
||||
```
|
||||
|
||||
**目标项目 (uvue)**:
|
||||
```typescript
|
||||
// utils/store.uts
|
||||
export const useUserStore = () => {
|
||||
const userInfo = ref<UserType | null>(null)
|
||||
const getUserInfo = async () => {
|
||||
// 使用 supadb 获取用户信息
|
||||
}
|
||||
return { userInfo, getUserInfo }
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 后端架构映射:PHP/ThinkPHP → Supabase
|
||||
|
||||
#### 2.2.1 技术栈对比
|
||||
|
||||
| CRMEB 原技术 | 目标技术 (Supabase) | 说明 |
|
||||
| -------------- | ----------------------------- | ------------ |
|
||||
| ThinkPHP 6 | Supabase (PostgreSQL) | 数据库 + API |
|
||||
| MySQL | PostgreSQL | 数据库 |
|
||||
| PHP Controller | Supabase RLS + Edge Functions | 业务逻辑 |
|
||||
| PHP Service | Database Functions + Triggers | 业务处理 |
|
||||
| Session | Supabase Auth (JWT) | 认证 |
|
||||
| File Upload | Supabase Storage | 文件存储 |
|
||||
|
||||
#### 2.2.2 API 映射
|
||||
|
||||
**CRMEB API 结构**:
|
||||
```
|
||||
/api/v1/store/product/lst # 商品列表
|
||||
/api/v1/store/product/detail # 商品详情
|
||||
/api/v1/order/confirm # 订单确认
|
||||
/api/v1/order/create # 创建订单
|
||||
```
|
||||
|
||||
**Supabase API 结构**:
|
||||
```
|
||||
GET /rest/v1/ml_products # 商品列表(PostgREST 自动生成)
|
||||
GET /rest/v1/ml_products?id=eq.{id} # 商品详情
|
||||
POST /rest/v1/rpc/create_order # 创建订单(Edge Function)
|
||||
POST /rest/v1/rpc/confirm_order # 订单确认(Edge Function)
|
||||
```
|
||||
|
||||
#### 2.2.3 数据表映射
|
||||
|
||||
**CRMEB 数据表** → **Supabase 数据表**:
|
||||
|
||||
| CRMEB 表名 | Supabase 表名 | 说明 |
|
||||
| ----------------------- | --------------------- | ---------- |
|
||||
| `eb_store_product` | `ml_products` | 商品表 |
|
||||
| `eb_store_order` | `ml_orders` | 订单表 |
|
||||
| `eb_store_cart` | `ml_shopping_cart` | 购物车 |
|
||||
| `eb_user` | `ak_users` (复用) | 用户表 |
|
||||
| `eb_store_category` | `ml_categories` | 分类表 |
|
||||
| `eb_store_coupon_issue` | `ml_coupon_templates` | 优惠券模板 |
|
||||
| `eb_store_coupon_user` | `ml_user_coupons` | 用户优惠券 |
|
||||
|
||||
#### 2.2.4 业务逻辑映射
|
||||
|
||||
**CRMEB PHP Service** → **Supabase 实现方式**:
|
||||
|
||||
1. **商品列表查询**
|
||||
- **原实现**:`StoreProductServices::getSearchList()`
|
||||
- **新实现**:PostgREST 查询 + RLS 策略
|
||||
```typescript
|
||||
// 前端调用
|
||||
await supa.from('ml_products')
|
||||
.select('*')
|
||||
.eq('status', 1)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(20)
|
||||
```
|
||||
|
||||
2. **订单创建**
|
||||
- **原实现**:`StoreOrderCreateServices::createOrder()`
|
||||
- **新实现**:Edge Function 或 Database Function
|
||||
```sql
|
||||
-- Database Function
|
||||
CREATE FUNCTION create_order(...) RETURNS uuid AS $$
|
||||
BEGIN
|
||||
-- 库存检查
|
||||
-- 价格计算
|
||||
-- 订单创建
|
||||
-- 库存扣减
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
```
|
||||
|
||||
3. **权限控制**
|
||||
- **原实现**:PHP Middleware + Session
|
||||
- **新实现**:Supabase RLS 策略
|
||||
```sql
|
||||
-- RLS 策略示例
|
||||
CREATE POLICY ml_orders_select_policy ON ml_orders
|
||||
FOR SELECT USING (
|
||||
auth.uid()::text = (SELECT auth_id FROM ak_users WHERE id = user_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 2.2.5 文件上传映射
|
||||
|
||||
**CRMEB**:
|
||||
- PHP 文件上传处理
|
||||
- 本地存储或云存储(OSS)
|
||||
|
||||
**Supabase**:
|
||||
- Supabase Storage
|
||||
- 公共/私有存储桶
|
||||
- 自动生成访问 URL
|
||||
|
||||
```typescript
|
||||
// 文件上传
|
||||
await supa.storage.from('product-images').upload(filePath, file)
|
||||
```
|
||||
|
||||
### 2.3 认证与权限映射
|
||||
|
||||
#### 2.3.1 认证方式
|
||||
|
||||
**CRMEB**:
|
||||
- Session 认证
|
||||
- JWT Token(部分接口)
|
||||
|
||||
**Supabase**:
|
||||
- Supabase Auth
|
||||
- JWT Token(自动管理)
|
||||
- 多种登录方式(邮箱、手机、微信、OAuth)
|
||||
|
||||
```typescript
|
||||
// 登录
|
||||
await supa.auth.signInWithPassword({ email, password })
|
||||
// Token 自动附加到后续请求
|
||||
```
|
||||
|
||||
#### 2.3.2 权限控制
|
||||
|
||||
**CRMEB**:
|
||||
- PHP Middleware 权限检查
|
||||
- 角色权限表
|
||||
|
||||
**Supabase**:
|
||||
- RLS (Row Level Security) 行级安全
|
||||
- 基于 `auth.uid()` 的自动过滤
|
||||
|
||||
---
|
||||
|
||||
## 三、实现计划 (Implementation Plan)
|
||||
|
||||
### 3.1 目录结构设计
|
||||
|
||||
```
|
||||
项目根目录/
|
||||
├── pages/
|
||||
│ └── mall/
|
||||
│ ├── consumer/ # 消费者端页面
|
||||
│ │ ├── index.uvue
|
||||
│ │ ├── product-detail.uvue
|
||||
│ │ ├── cart.uvue
|
||||
│ │ ├── order-confirm.uvue
|
||||
│ │ ├── order-list.uvue
|
||||
│ │ ├── order-detail.uvue
|
||||
│ │ ├── profile.uvue
|
||||
│ │ ├── address-list.uvue
|
||||
│ │ ├── coupon-list.uvue
|
||||
│ │ └── search.uvue
|
||||
│ ├── merchant/ # 商家端页面
|
||||
│ │ ├── index.uvue
|
||||
│ │ ├── product-list.uvue
|
||||
│ │ ├── product-edit.uvue
|
||||
│ │ ├── order-list.uvue
|
||||
│ │ └── shop-settings.uvue
|
||||
│ ├── delivery/ # 配送端页面
|
||||
│ │ ├── index.uvue
|
||||
│ │ └── order-detail.uvue
|
||||
│ └── admin/ # 管理后台
|
||||
│ ├── index.uvue
|
||||
│ ├── product-management.uvue
|
||||
│ ├── order-management.uvue
|
||||
│ └── user-management.uvue
|
||||
├── components/
|
||||
│ ├── supadb/ # Supabase 封装(不修改)
|
||||
│ └── mall/ # 商城业务组件
|
||||
│ ├── ProductList.uvue
|
||||
│ ├── ProductCard.uvue
|
||||
│ ├── OrderItem.uvue
|
||||
│ ├── AddressSelector.uvue
|
||||
│ ├── CouponSelector.uvue
|
||||
│ └── PaymentMethod.uvue
|
||||
├── services/ # 业务服务层
|
||||
│ └── mall/
|
||||
│ ├── product-service.uts
|
||||
│ ├── order-service.uts
|
||||
│ ├── cart-service.uts
|
||||
│ ├── coupon-service.uts
|
||||
│ └── user-service.uts
|
||||
├── types/
|
||||
│ └── mall-types.uts # 类型定义(已存在)
|
||||
└── utils/
|
||||
└── mall-utils.uts # 工具函数
|
||||
```
|
||||
|
||||
### 3.2 数据库设计
|
||||
|
||||
#### 3.2.1 数据表清单
|
||||
|
||||
基于功能规格,设计以下数据表(使用 `ml_` 前缀):
|
||||
|
||||
1. **用户相关**
|
||||
- `ml_user_profiles` - 用户扩展信息
|
||||
- `ml_user_addresses` - 用户地址
|
||||
|
||||
2. **商品相关**
|
||||
- `ml_products` - 商品主表
|
||||
- `ml_product_skus` - 商品SKU
|
||||
- `ml_categories` - 商品分类
|
||||
- `ml_brands` - 品牌表
|
||||
- `ml_product_specs` - 商品规格模板
|
||||
|
||||
3. **店铺相关**
|
||||
- `ml_shops` - 店铺表
|
||||
|
||||
4. **订单相关**
|
||||
- `ml_orders` - 订单主表
|
||||
- `ml_order_items` - 订单商品明细
|
||||
|
||||
5. **购物车**
|
||||
- `ml_shopping_cart` - 购物车
|
||||
|
||||
6. **营销相关**
|
||||
- `ml_coupon_templates` - 优惠券模板
|
||||
- `ml_user_coupons` - 用户优惠券
|
||||
|
||||
7. **配送相关**
|
||||
- `ml_delivery_drivers` - 配送员
|
||||
- `ml_delivery_tasks` - 配送任务
|
||||
|
||||
8. **评价相关**
|
||||
- `ml_product_reviews` - 商品评价
|
||||
|
||||
9. **用户行为**
|
||||
- `ml_user_favorites` - 收藏
|
||||
- `ml_browse_history` - 浏览历史
|
||||
- `ml_search_history` - 搜索记录
|
||||
|
||||
10. **系统配置**
|
||||
- `ml_system_configs` - 系统配置
|
||||
- `ml_regions` - 地区数据
|
||||
|
||||
#### 3.2.2 数据库脚本位置
|
||||
|
||||
- 主数据库脚本:`doc_mall/database/complete_mall_database.sql`
|
||||
- RLS 策略脚本:`doc_mall/database/rls_policies.sql`
|
||||
- 触发器脚本:`doc_mall/database/triggers.sql`
|
||||
- 函数脚本:`doc_mall/database/functions.sql`
|
||||
|
||||
### 3.3 开发里程碑
|
||||
|
||||
#### 阶段 1:基础架构搭建(1-2周)
|
||||
- [ ] 数据库表结构创建
|
||||
- [ ] RLS 策略配置
|
||||
- [ ] 触发器与函数创建
|
||||
- [ ] 类型定义完善(`types/mall-types.uts`)
|
||||
- [ ] 服务层基础封装(`services/mall/`)
|
||||
|
||||
#### 阶段 2:用户系统(1周)
|
||||
- [ ] 用户注册/登录页面
|
||||
- [ ] 用户信息管理
|
||||
- [ ] 地址管理功能
|
||||
- [ ] 权限验证
|
||||
|
||||
#### 阶段 3:商品系统(2周)
|
||||
- [ ] 商品列表页
|
||||
- [ ] 商品详情页
|
||||
- [ ] 商品搜索
|
||||
- [ ] 分类导航
|
||||
- [ ] 商家商品管理
|
||||
|
||||
#### 阶段 4:购物车与订单(2周)
|
||||
- [ ] 购物车功能
|
||||
- [ ] 订单确认页
|
||||
- [ ] 订单创建
|
||||
- [ ] 订单列表
|
||||
- [ ] 订单详情
|
||||
- [ ] 订单支付(集成支付SDK)
|
||||
|
||||
#### 阶段 5:营销系统(1-2周)
|
||||
- [ ] 优惠券功能
|
||||
- [ ] 优惠券使用
|
||||
- [ ] 活动页面(拼团、秒杀等,可选)
|
||||
|
||||
#### 阶段 6:管理后台(2周)
|
||||
- [ ] 管理员登录
|
||||
- [ ] 商品管理
|
||||
- [ ] 订单管理
|
||||
- [ ] 用户管理
|
||||
- [ ] 数据统计
|
||||
|
||||
#### 阶段 7:测试与优化(1-2周)
|
||||
- [ ] 功能测试
|
||||
- [ ] 性能优化
|
||||
- [ ] 安全测试
|
||||
- [ ] 用户体验优化
|
||||
|
||||
### 3.4 关键技术实现点
|
||||
|
||||
#### 3.4.1 使用 supadb 组件(不修改)
|
||||
|
||||
```typescript
|
||||
// 引入 supadb 实例
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
// 查询商品列表
|
||||
const getProducts = async (filters: any) => {
|
||||
const result = await supa
|
||||
.from('ml_products')
|
||||
.select('*')
|
||||
.eq('status', 1)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(20)
|
||||
.executeAs<ProductType[]>()
|
||||
|
||||
return result.data || []
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
const createOrder = async (orderData: any) => {
|
||||
const result = await supa
|
||||
.from('ml_orders')
|
||||
.insert(orderData)
|
||||
.executeAs<OrderType>()
|
||||
|
||||
return result.data
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.2 服务层封装
|
||||
|
||||
```typescript
|
||||
// services/mall/product-service.uts
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import type { ProductType } from '@/types/mall-types.uts'
|
||||
|
||||
export class ProductService {
|
||||
// 获取商品列表
|
||||
static async getProducts(params: {
|
||||
categoryId?: string
|
||||
keyword?: string
|
||||
minPrice?: number
|
||||
maxPrice?: number
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}) {
|
||||
let query = supa.from('ml_products').select('*')
|
||||
|
||||
if (params.categoryId) {
|
||||
query = query.eq('category_id', params.categoryId)
|
||||
}
|
||||
if (params.keyword) {
|
||||
query = query.ilike('name', `%${params.keyword}%`)
|
||||
}
|
||||
if (params.minPrice) {
|
||||
query = query.gte('base_price', params.minPrice)
|
||||
}
|
||||
if (params.maxPrice) {
|
||||
query = query.lte('base_price', params.maxPrice)
|
||||
}
|
||||
|
||||
const page = params.page || 1
|
||||
const pageSize = params.pageSize || 20
|
||||
const from = (page - 1) * pageSize
|
||||
const to = from + pageSize - 1
|
||||
|
||||
query = query.range(from, to).order('created_at', { ascending: false })
|
||||
|
||||
const result = await query.executeAs<ProductType[]>()
|
||||
return result.data || []
|
||||
}
|
||||
|
||||
// 获取商品详情
|
||||
static async getProductById(id: string) {
|
||||
const result = await supa
|
||||
.from('ml_products')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single()
|
||||
.executeAs<ProductType>()
|
||||
|
||||
return result.data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.3 页面组件实现
|
||||
|
||||
```vue
|
||||
<!-- pages/mall/consumer/product-detail.uvue -->
|
||||
<template>
|
||||
<view class="product-detail">
|
||||
<view class="product-images">
|
||||
<swiper>
|
||||
<swiper-item v-for="img in product.image_urls" :key="img">
|
||||
<image :src="img" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-price">¥{{ product.base_price }}</text>
|
||||
</view>
|
||||
<!-- SKU 选择 -->
|
||||
<view class="sku-selector">
|
||||
<!-- SKU 选择组件 -->
|
||||
</view>
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<button @click="addToCart">加入购物车</button>
|
||||
<button @click="buyNow">立即购买</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ProductService } from '@/services/mall/product-service.uts'
|
||||
import type { ProductType } from '@/types/mall-types.uts'
|
||||
|
||||
const props = defineProps<{
|
||||
productId: string
|
||||
}>()
|
||||
|
||||
const product = ref<ProductType | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
product.value = await ProductService.getProductById(props.productId)
|
||||
})
|
||||
|
||||
const addToCart = async () => {
|
||||
// 加入购物车逻辑
|
||||
}
|
||||
|
||||
const buyNow = async () => {
|
||||
// 立即购买逻辑
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证清单 (Verification Checklist)
|
||||
|
||||
### 4.1 功能验证
|
||||
|
||||
#### 4.1.1 用户系统
|
||||
- [ ] 用户注册(手机号、邮箱)
|
||||
- [ ] 用户登录(密码、验证码)
|
||||
- [ ] 用户信息修改
|
||||
- [ ] 地址添加/编辑/删除
|
||||
- [ ] 默认地址设置
|
||||
|
||||
#### 4.1.2 商品系统
|
||||
- [ ] 商品列表展示(分页、筛选、排序)
|
||||
- [ ] 商品详情展示
|
||||
- [ ] 商品搜索
|
||||
- [ ] 分类导航
|
||||
- [ ] SKU 选择
|
||||
- [ ] 商家商品管理(增删改查)
|
||||
|
||||
#### 4.1.3 购物车
|
||||
- [ ] 添加商品到购物车
|
||||
- [ ] 修改购物车商品数量
|
||||
- [ ] 删除购物车商品
|
||||
- [ ] 购物车商品选择
|
||||
- [ ] 购物车价格计算
|
||||
|
||||
#### 4.1.4 订单系统
|
||||
- [ ] 订单确认页(地址、优惠券、运费)
|
||||
- [ ] 创建订单
|
||||
- [ ] 订单支付
|
||||
- [ ] 订单列表(状态筛选)
|
||||
- [ ] 订单详情
|
||||
- [ ] 订单取消
|
||||
- [ ] 订单退款
|
||||
|
||||
#### 4.1.5 营销系统
|
||||
- [ ] 优惠券列表
|
||||
- [ ] 优惠券领取
|
||||
- [ ] 优惠券使用
|
||||
- [ ] 优惠券过期处理
|
||||
|
||||
### 4.2 权限验证
|
||||
|
||||
- [ ] 用户只能查看/修改自己的数据
|
||||
- [ ] 商家只能管理自己的商品和订单
|
||||
- [ ] 管理员可以查看所有数据
|
||||
- [ ] 未登录用户只能查看公开商品
|
||||
- [ ] RLS 策略正确生效
|
||||
|
||||
### 4.3 性能验证
|
||||
|
||||
- [ ] 商品列表加载速度(< 2秒)
|
||||
- [ ] 图片加载优化(懒加载)
|
||||
- [ ] 分页加载流畅
|
||||
- [ ] 数据库查询性能(索引优化)
|
||||
|
||||
### 4.4 安全验证
|
||||
|
||||
- [ ] SQL 注入防护(参数化查询)
|
||||
- [ ] XSS 防护(数据转义)
|
||||
- [ ] 权限验证(RLS)
|
||||
- [ ] 敏感数据加密
|
||||
|
||||
### 4.5 兼容性验证
|
||||
|
||||
- [ ] Android 平台
|
||||
- [ ] iOS 平台(如支持)
|
||||
- [ ] Web 平台(如支持)
|
||||
- [ ] 不同屏幕尺寸适配
|
||||
|
||||
---
|
||||
|
||||
## 五、差异清单 (Difference List)
|
||||
|
||||
### 5.1 允许的差异
|
||||
|
||||
1. **UI 风格差异**
|
||||
- 可以重新设计 UI,不要求与 CRMEB 完全一致
|
||||
- 遵循现代 UI 设计规范
|
||||
|
||||
2. **交互细节差异**
|
||||
- 动画效果可以不同
|
||||
- 页面布局可以优化
|
||||
|
||||
3. **非核心功能差异**
|
||||
- 某些营销活动(如砍价、拼团)可以简化或延后实现
|
||||
- 数据统计功能可以简化
|
||||
|
||||
### 5.2 不允许的差异
|
||||
|
||||
1. **核心业务流程**
|
||||
- 订单流程必须完整(创建→支付→发货→收货→完成)
|
||||
- 库存扣减逻辑必须正确
|
||||
- 价格计算必须准确
|
||||
|
||||
2. **数据一致性**
|
||||
- 订单金额必须准确
|
||||
- 库存数据必须准确
|
||||
- 用户余额必须准确
|
||||
|
||||
3. **权限控制**
|
||||
- 用户权限必须正确
|
||||
- 数据访问权限必须正确
|
||||
|
||||
---
|
||||
|
||||
## 六、反抄袭自证
|
||||
|
||||
### 6.1 参考资料说明
|
||||
|
||||
本文档仅参考以下公开资料:
|
||||
|
||||
1. **CRMEB 项目 README.md**
|
||||
- 仅用于了解项目功能模块和业务范围
|
||||
- 未参考任何 PHP 源码实现
|
||||
|
||||
2. **Supabase 官方文档**
|
||||
- https://supabase.com/docs
|
||||
- 用于了解 Supabase API 使用方式
|
||||
|
||||
3. **uni-app x 官方文档**
|
||||
- https://uniapp.dcloud.net.cn/uni-app-x/
|
||||
- 用于了解 uvue 语法和规范
|
||||
|
||||
4. **PostgreSQL 官方文档**
|
||||
- https://www.postgresql.org/docs/
|
||||
- 用于了解数据库功能和语法
|
||||
|
||||
### 6.2 实现方式说明
|
||||
|
||||
1. **数据库设计**
|
||||
- 基于业务需求独立设计表结构
|
||||
- 使用 `ml_` 前缀区分商城模块
|
||||
- 参考现有 `doc_mall/database/` 中的设计
|
||||
|
||||
2. **前端实现**
|
||||
- 使用 uvue 语法独立编写页面
|
||||
- 通过 `components/supadb` 调用 Supabase API
|
||||
- 不复制任何 Vue 组件代码
|
||||
|
||||
3. **后端实现**
|
||||
- 使用 Supabase RLS + Edge Functions
|
||||
- 不编写任何 PHP 代码
|
||||
- 业务逻辑通过数据库函数和触发器实现
|
||||
|
||||
### 6.3 代码原创性声明
|
||||
|
||||
- 所有代码均为基于功能规格的独立实现
|
||||
- 未复制 CRMEB 项目的任何源码
|
||||
- 未复制任何第三方项目的实现代码
|
||||
- 仅参考公开 API 文档和规范
|
||||
|
||||
---
|
||||
|
||||
## 七、参考资料列表
|
||||
|
||||
### 7.1 官方文档
|
||||
|
||||
1. **Supabase**
|
||||
- REST API: https://supabase.com/docs/reference/javascript/introduction
|
||||
- Auth: https://supabase.com/docs/guides/auth
|
||||
- RLS: https://supabase.com/docs/guides/auth/row-level-security
|
||||
- Storage: https://supabase.com/docs/guides/storage
|
||||
|
||||
2. **uni-app x**
|
||||
- 概述: https://uniapp.dcloud.net.cn/uni-app-x/
|
||||
- uvue: https://uniapp.dcloud.net.cn/uni-app-x/component/
|
||||
- UTS: https://uniapp.dcloud.net.cn/uni-app-x/uts/
|
||||
|
||||
3. **PostgreSQL**
|
||||
- 官方文档: https://www.postgresql.org/docs/
|
||||
- JSONB: https://www.postgresql.org/docs/current/datatype-json.html
|
||||
|
||||
### 7.2 项目内部文档
|
||||
|
||||
1. `doc_mall/MODULE_ANALYSIS.md` - 模块分析报告
|
||||
2. `doc_mall/database/` - 数据库设计文档
|
||||
3. `components/supadb/SIMPLIFIED_API_GUIDE.md` - Supabase API 使用指南
|
||||
4. `types/mall-types.uts` - 类型定义
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
本文档基于 **Clean-Room 重构原则**,通过分析 CRMEB 项目的功能规格,制定了完整的重构计划:
|
||||
|
||||
1. **功能规格提取**:明确了所有核心业务模块的功能需求
|
||||
2. **架构映射**:完成了 Vue→uvue、PHP→Supabase 的技术栈映射
|
||||
3. **实现计划**:制定了详细的开发里程碑和目录结构
|
||||
4. **验证清单**:提供了完整的测试验证标准
|
||||
|
||||
所有实现将基于功能规格独立开发,不复制任何源码,确保代码的原创性和合规性。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建时间**: 2025-01-XX
|
||||
**状态**: ✅ 待实施
|
||||
807
docs/CRMEB_TO_UVUE_MIGRATION_GUIDE.md
Normal file
807
docs/CRMEB_TO_UVUE_MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,807 @@
|
||||
# CRMEB商城系统到uvue项目的重构迁移指南
|
||||
|
||||
## 项目概述
|
||||
|
||||
本文档基于CRMEB开源商城系统(PHP版本),指导如何将其核心功能迁移到基于uvue技术栈的项目中。后端使用`@components/supadb`组件库实现,不使用PHP技术栈。
|
||||
|
||||
## 参考项目分析
|
||||
|
||||
### CRMEB核心功能模块
|
||||
|
||||
#### 1. 用户系统 (User Module)
|
||||
- **用户注册登录**:手机号验证码、微信授权登录
|
||||
- **用户资料管理**:个人信息、收货地址、会员等级
|
||||
- **用户积分系统**:积分获取、积分消费记录
|
||||
- **分销系统**:用户推广、佣金结算
|
||||
|
||||
#### 2. 商品系统 (Product Module)
|
||||
- **商品管理**:商品信息、SKU规格、商品分类
|
||||
- **商品展示**:商品详情、商品列表、商品搜索
|
||||
- **库存管理**:商品库存、规格库存管理
|
||||
|
||||
#### 3. 订单系统 (Order Module)
|
||||
- **购物车**:添加商品、修改数量、删除商品
|
||||
- **订单创建**:订单确认、地址选择、支付方式
|
||||
- **订单管理**:订单状态跟踪、订单取消、退款处理
|
||||
- **物流跟踪**:快递信息查询、物流状态更新
|
||||
|
||||
#### 4. 营销活动 (Activity Module)
|
||||
- **秒杀活动**:限时抢购、库存锁定
|
||||
- **拼团活动**:团购发起、参团流程
|
||||
- **砍价活动**:好友助力、砍价进度
|
||||
- **优惠券系统**:券领取、使用规则
|
||||
- **积分商城**:积分兑换商品
|
||||
|
||||
#### 5. 支付系统 (Payment Module)
|
||||
- **多渠道支付**:微信支付、支付宝、余额支付
|
||||
- **支付回调**:订单状态更新、支付记录
|
||||
|
||||
#### 6. 客服系统 (Service Module)
|
||||
- **在线客服**:实时聊天、消息记录
|
||||
- **售后服务**:退换货处理、服务评价
|
||||
|
||||
#### 7. 内容管理系统 (CMS Module)
|
||||
- **文章系统**:资讯发布、分类管理
|
||||
- **广告位管理**:首页banner、推荐位
|
||||
|
||||
#### 8. 系统配置 (System Module)
|
||||
- **基础配置**:站点信息、支付配置、物流配置
|
||||
- **权限管理**:管理员权限、操作日志
|
||||
|
||||
## 技术栈对比
|
||||
|
||||
### 原CRMEB技术栈
|
||||
```
|
||||
后端: ThinkPHP 6 + MySQL + Redis
|
||||
前端: Vue2 + ElementUI + UniApp
|
||||
其他: Workerman(长连接)、队列、定时任务
|
||||
```
|
||||
|
||||
### 目标技术栈
|
||||
```
|
||||
后端: Supabase (PostgreSQL + Auth + Storage + Edge Functions)
|
||||
前端: uvue + @components/supadb
|
||||
其他: 实时订阅、文件存储、服务器less函数
|
||||
```
|
||||
|
||||
## 数据架构设计
|
||||
|
||||
### Supabase数据库表结构设计
|
||||
|
||||
#### 核心数据表
|
||||
|
||||
##### 1. 用户表 (users)
|
||||
```sql
|
||||
-- 继承Supabase auth.users表,扩展字段
|
||||
CREATE TABLE public.users (
|
||||
id UUID REFERENCES auth.users(id) PRIMARY KEY,
|
||||
phone TEXT,
|
||||
nickname TEXT,
|
||||
avatar_url TEXT,
|
||||
gender INTEGER DEFAULT 0,
|
||||
birthday DATE,
|
||||
level_id INTEGER DEFAULT 0,
|
||||
integral INTEGER DEFAULT 0,
|
||||
balance DECIMAL(10,2) DEFAULT 0,
|
||||
spread_uid INTEGER,
|
||||
spread_time TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 2. 商品表 (products)
|
||||
```sql
|
||||
CREATE TABLE public.products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
images TEXT[],
|
||||
category_id INTEGER,
|
||||
brand_id INTEGER,
|
||||
price DECIMAL(10,2),
|
||||
ot_price DECIMAL(10,2),
|
||||
cost DECIMAL(10,2),
|
||||
stock INTEGER DEFAULT 0,
|
||||
sales INTEGER DEFAULT 0,
|
||||
is_show BOOLEAN DEFAULT true,
|
||||
is_del BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 3. 商品规格表 (product_skus)
|
||||
```sql
|
||||
CREATE TABLE public.product_skus (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES products(id),
|
||||
sku TEXT,
|
||||
price DECIMAL(10,2),
|
||||
stock INTEGER DEFAULT 0,
|
||||
image TEXT,
|
||||
attributes JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 4. 订单表 (orders)
|
||||
```sql
|
||||
CREATE TABLE public.orders (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_sn TEXT UNIQUE,
|
||||
user_id UUID REFERENCES users(id),
|
||||
total_price DECIMAL(10,2),
|
||||
pay_price DECIMAL(10,2),
|
||||
coupon_price DECIMAL(10,2),
|
||||
pay_type INTEGER DEFAULT 1, -- 1微信 2余额 3线下
|
||||
status INTEGER DEFAULT 0, -- 订单状态
|
||||
address_info JSONB,
|
||||
mark TEXT,
|
||||
paid BOOLEAN DEFAULT false,
|
||||
pay_time TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 5. 订单商品表 (order_items)
|
||||
```sql
|
||||
CREATE TABLE public.order_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_id INTEGER REFERENCES orders(id),
|
||||
product_id INTEGER REFERENCES products(id),
|
||||
sku_id INTEGER REFERENCES product_skus(id),
|
||||
product_title TEXT,
|
||||
product_image TEXT,
|
||||
sku_info JSONB,
|
||||
price DECIMAL(10,2),
|
||||
quantity INTEGER,
|
||||
total_price DECIMAL(10,2),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 6. 购物车表 (cart)
|
||||
```sql
|
||||
CREATE TABLE public.cart (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id UUID REFERENCES users(id),
|
||||
product_id INTEGER REFERENCES products(id),
|
||||
sku_id INTEGER REFERENCES product_skus(id),
|
||||
quantity INTEGER,
|
||||
selected BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 7. 优惠券表 (coupons)
|
||||
```sql
|
||||
CREATE TABLE public.coupons (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT,
|
||||
type INTEGER, -- 1满减 2折扣
|
||||
value DECIMAL(10,2),
|
||||
min_price DECIMAL(10,2),
|
||||
use_start_time TIMESTAMP WITH TIME ZONE,
|
||||
use_end_time TIMESTAMP WITH TIME ZONE,
|
||||
stock INTEGER,
|
||||
receive_count INTEGER DEFAULT 0,
|
||||
is_show BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
##### 8. 用户优惠券表 (user_coupons)
|
||||
```sql
|
||||
CREATE TABLE public.user_coupons (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id UUID REFERENCES users(id),
|
||||
coupon_id INTEGER REFERENCES coupons(id),
|
||||
status INTEGER DEFAULT 0, -- 0未使用 1已使用 2已过期
|
||||
use_time TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
## API接口设计
|
||||
|
||||
### 使用@components/supadb实现的数据操作
|
||||
|
||||
#### 用户相关接口
|
||||
|
||||
##### 用户注册
|
||||
```typescript
|
||||
// 使用Supabase Auth实现
|
||||
const { data, error } = await supa.auth.signUp({
|
||||
email: 'user@example.com',
|
||||
password: 'password'
|
||||
})
|
||||
```
|
||||
|
||||
##### 用户登录
|
||||
```typescript
|
||||
const { data, error } = await supa.auth.signInWithPassword({
|
||||
email: 'user@example.com',
|
||||
password: 'password'
|
||||
})
|
||||
```
|
||||
|
||||
##### 获取用户信息
|
||||
```typescript
|
||||
// 使用supadb组件
|
||||
<supadb
|
||||
collection="users"
|
||||
:filter="{ id: userId }"
|
||||
:getone="true"
|
||||
v-slot="{ data: userInfo }"
|
||||
>
|
||||
<!-- 用户信息显示 -->
|
||||
</supadb>
|
||||
```
|
||||
|
||||
##### 更新用户信息
|
||||
```typescript
|
||||
const result = await supa.from('users').update(userData).eq('id', userId)
|
||||
```
|
||||
|
||||
#### 商品相关接口
|
||||
|
||||
##### 获取商品列表
|
||||
```vue
|
||||
<supadb
|
||||
collection="products"
|
||||
:filter="{ is_show: true, is_del: false }"
|
||||
:orderby="'sales.desc'"
|
||||
:pageSize="20"
|
||||
v-slot="{ data: products, loading, hasmore, loadMore }"
|
||||
>
|
||||
<view v-for="product in products" :key="product.id">
|
||||
<!-- 商品卡片 -->
|
||||
</view>
|
||||
<button v-if="hasmore" @click="loadMore">加载更多</button>
|
||||
</supadb>
|
||||
```
|
||||
|
||||
##### 获取商品详情
|
||||
```vue
|
||||
<supadb
|
||||
collection="products"
|
||||
:filter="{ id: productId }"
|
||||
:getone="true"
|
||||
v-slot="{ data: product }"
|
||||
>
|
||||
<!-- 商品详情页 -->
|
||||
</supadb>
|
||||
```
|
||||
|
||||
##### 商品搜索
|
||||
```vue
|
||||
<supadb
|
||||
collection="products"
|
||||
:filter="{ title: { ilike: `%${keyword}%` }, is_show: true }"
|
||||
v-slot="{ data: searchResults }"
|
||||
>
|
||||
<!-- 搜索结果 -->
|
||||
</supadb>
|
||||
```
|
||||
|
||||
#### 订单相关接口
|
||||
|
||||
##### 创建订单
|
||||
```typescript
|
||||
// 先创建订单记录
|
||||
const orderData = {
|
||||
order_sn: generateOrderSn(),
|
||||
user_id: userId,
|
||||
total_price: totalPrice,
|
||||
// ...其他字段
|
||||
}
|
||||
const { data: order } = await supa.from('orders').insert(orderData).select().single()
|
||||
|
||||
// 然后创建订单商品记录
|
||||
const orderItems = cartItems.map(item => ({
|
||||
order_id: order.id,
|
||||
product_id: item.product_id,
|
||||
// ...其他字段
|
||||
}))
|
||||
await supa.from('order_items').insert(orderItems)
|
||||
```
|
||||
|
||||
##### 获取订单列表
|
||||
```vue
|
||||
<supadb
|
||||
collection="orders"
|
||||
:filter="{ user_id: userId }"
|
||||
:orderby="'created_at.desc'"
|
||||
v-slot="{ data: orders }"
|
||||
>
|
||||
<view v-for="order in orders" :key="order.id">
|
||||
<!-- 订单卡片 -->
|
||||
</view>
|
||||
</supadb>
|
||||
```
|
||||
|
||||
#### 营销活动接口
|
||||
|
||||
##### 秒杀活动
|
||||
```vue
|
||||
<supadb
|
||||
collection="seckill_products"
|
||||
:filter="{
|
||||
start_time: { lte: currentTime },
|
||||
end_time: { gte: currentTime },
|
||||
stock: { gt: 0 }
|
||||
}"
|
||||
v-slot="{ data: seckillProducts }"
|
||||
>
|
||||
<!-- 秒杀商品列表 -->
|
||||
</supadb>
|
||||
```
|
||||
|
||||
##### 优惠券领取
|
||||
```typescript
|
||||
// 检查用户是否已领取
|
||||
const { data: existing } = await supa
|
||||
.from('user_coupons')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('coupon_id', couponId)
|
||||
.single()
|
||||
|
||||
if (!existing) {
|
||||
await supa.from('user_coupons').insert({
|
||||
user_id: userId,
|
||||
coupon_id: couponId
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## uvue前端页面重构方案
|
||||
|
||||
### 页面结构重组
|
||||
|
||||
#### 1. 首页 (pages/index/index.uvue)
|
||||
```vue
|
||||
<template>
|
||||
<view class="index-page">
|
||||
<!-- 轮播图 -->
|
||||
<swiper-banner />
|
||||
<!-- 分类导航 -->
|
||||
<category-nav />
|
||||
<!-- 商品推荐 -->
|
||||
<product-recommend />
|
||||
<!-- 营销活动 -->
|
||||
<activity-section />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
// 首页数据
|
||||
const bannerList = ref([])
|
||||
const categoryList = ref([])
|
||||
const recommendProducts = ref([])
|
||||
|
||||
// 获取首页数据
|
||||
const loadHomeData = async () => {
|
||||
// 获取轮播图
|
||||
const bannerResult = await supa.from('banners').select('*').eq('position', 'home')
|
||||
bannerList.value = bannerResult.data || []
|
||||
|
||||
// 获取分类
|
||||
const categoryResult = await supa.from('categories').select('*').eq('level', 1)
|
||||
categoryList.value = categoryResult.data || []
|
||||
|
||||
// 获取推荐商品
|
||||
const productResult = await supa.from('products')
|
||||
.select('*')
|
||||
.eq('is_recommend', true)
|
||||
.eq('is_show', true)
|
||||
.limit(10)
|
||||
recommendProducts.value = productResult.data || []
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 2. 商品详情页 (pages/goods/detail.uvue)
|
||||
```vue
|
||||
<template>
|
||||
<view class="product-detail">
|
||||
<supadb
|
||||
collection="products"
|
||||
:filter="{ id: productId }"
|
||||
:getone="true"
|
||||
v-slot="{ data: product, loading }"
|
||||
>
|
||||
<!-- 商品图片轮播 -->
|
||||
<image-swiper :images="product?.images || []" />
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<product-info :product="product" />
|
||||
|
||||
<!-- 商品规格选择 -->
|
||||
<sku-selector
|
||||
:product="product"
|
||||
@sku-change="handleSkuChange"
|
||||
/>
|
||||
|
||||
<!-- 商品详情 -->
|
||||
<product-description :content="product?.description" />
|
||||
</supadb>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
const props = defineProps<{
|
||||
productId: number
|
||||
}>()
|
||||
|
||||
const selectedSku = ref(null)
|
||||
|
||||
const handleSkuChange = (sku) => {
|
||||
selectedSku.value = sku
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 3. 购物车页面 (pages/cart/index.uvue)
|
||||
```vue
|
||||
<template>
|
||||
<view class="cart-page">
|
||||
<supadb
|
||||
collection="cart"
|
||||
:filter="{ user_id: userId }"
|
||||
v-slot="{ data: cartItems, loading }"
|
||||
>
|
||||
<!-- 购物车商品列表 -->
|
||||
<cart-item
|
||||
v-for="item in cartItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@quantity-change="updateQuantity"
|
||||
@delete="removeItem"
|
||||
/>
|
||||
|
||||
<!-- 结算栏 -->
|
||||
<cart-footer
|
||||
:items="cartItems"
|
||||
@checkout="goCheckout"
|
||||
/>
|
||||
</supadb>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
const userId = ref('') // 从用户信息获取
|
||||
|
||||
const updateQuantity = async (itemId, quantity) => {
|
||||
await supa.from('cart').update({ quantity }).eq('id', itemId)
|
||||
}
|
||||
|
||||
const removeItem = async (itemId) => {
|
||||
await supa.from('cart').delete().eq('id', itemId)
|
||||
}
|
||||
|
||||
const goCheckout = () => {
|
||||
// 跳转到结算页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/order/checkout'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 实时功能实现
|
||||
|
||||
### 使用Supabase实时订阅
|
||||
|
||||
#### 订单状态更新监听
|
||||
```typescript
|
||||
// 监听订单状态变化
|
||||
const orderSubscription = supa
|
||||
.channel('order-updates')
|
||||
.on('postgres_changes',
|
||||
{
|
||||
event: 'UPDATE',
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
filter: `user_id=eq.${userId}`
|
||||
},
|
||||
(payload) => {
|
||||
console.log('Order updated:', payload)
|
||||
// 更新订单状态
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
#### 库存变化监听
|
||||
```typescript
|
||||
// 监听商品库存变化
|
||||
const stockSubscription = supa
|
||||
.channel('stock-updates')
|
||||
.on('postgres_changes',
|
||||
{
|
||||
event: 'UPDATE',
|
||||
schema: 'public',
|
||||
table: 'products'
|
||||
},
|
||||
(payload) => {
|
||||
// 更新本地商品库存
|
||||
updateLocalStock(payload.new)
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## 文件存储实现
|
||||
|
||||
### 使用Supabase Storage
|
||||
|
||||
#### 商品图片上传
|
||||
```typescript
|
||||
const uploadProductImage = async (filePath: string, productId: number) => {
|
||||
const fileName = `product_${productId}_${Date.now()}.jpg`
|
||||
const { data, error } = await supa.storage
|
||||
.from('products')
|
||||
.upload(fileName, filePath)
|
||||
|
||||
if (data) {
|
||||
const { data: urlData } = supa.storage
|
||||
.from('products')
|
||||
.getPublicUrl(fileName)
|
||||
|
||||
return urlData.publicUrl
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 用户头像上传
|
||||
```typescript
|
||||
const uploadAvatar = async (filePath: string) => {
|
||||
const fileName = `avatar_${userId}_${Date.now()}.jpg`
|
||||
const { data, error } = await supa.storage
|
||||
.from('avatars')
|
||||
.upload(fileName, filePath)
|
||||
|
||||
if (data) {
|
||||
const { data: urlData } = supa.storage
|
||||
.from('avatars')
|
||||
.getPublicUrl(fileName)
|
||||
|
||||
// 更新用户头像
|
||||
await supa.from('users').update({
|
||||
avatar_url: urlData.publicUrl
|
||||
}).eq('id', userId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 服务器端逻辑实现
|
||||
|
||||
### 使用Supabase Edge Functions
|
||||
|
||||
#### 订单创建函数
|
||||
```typescript
|
||||
// supabase/functions/create-order/index.ts
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
serve(async (req) => {
|
||||
const { userId, items, address } = await req.json()
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
|
||||
)
|
||||
|
||||
// 生成订单号
|
||||
const orderSn = `ORDER${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`
|
||||
|
||||
// 计算总价
|
||||
let totalPrice = 0
|
||||
for (const item of items) {
|
||||
const { data: product } = await supabase
|
||||
.from('products')
|
||||
.select('price')
|
||||
.eq('id', item.productId)
|
||||
.single()
|
||||
|
||||
totalPrice += product.price * item.quantity
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
const { data: order, error } = await supabase
|
||||
.from('orders')
|
||||
.insert({
|
||||
order_sn: orderSn,
|
||||
user_id: userId,
|
||||
total_price: totalPrice,
|
||||
address_info: address
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
// 创建订单商品记录
|
||||
const orderItems = items.map(item => ({
|
||||
order_id: order.id,
|
||||
product_id: item.productId,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
total_price: item.price * item.quantity
|
||||
}))
|
||||
|
||||
const { error: itemsError } = await supabase
|
||||
.from('order_items')
|
||||
.insert(orderItems)
|
||||
|
||||
if (itemsError) throw itemsError
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ order }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
#### 支付回调函数
|
||||
```typescript
|
||||
// supabase/functions/payment-callback/index.ts
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
serve(async (req) => {
|
||||
const { orderSn, paymentResult } = await req.json()
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
|
||||
)
|
||||
|
||||
// 更新订单支付状态
|
||||
const { error } = await supabase
|
||||
.from('orders')
|
||||
.update({
|
||||
paid: true,
|
||||
pay_time: new Date().toISOString(),
|
||||
status: 1 // 已支付
|
||||
})
|
||||
.eq('order_sn', orderSn)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: true }),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
## 组件重构对照表
|
||||
|
||||
### CRMEB组件 → uvue组件映射
|
||||
|
||||
| CRMEB组件 | uvue组件 | 功能说明 |
|
||||
|----------|---------|---------|
|
||||
| HomeComb | home-comb.uvue | 首页搜索组合 |
|
||||
| GoodList | product-list.uvue | 商品列表 |
|
||||
| CouponWindow | coupon-popup.uvue | 优惠券弹窗 |
|
||||
| CartList | cart-list.uvue | 购物车列表 |
|
||||
| Payment | payment-selector.uvue | 支付方式选择 |
|
||||
| AddressWindow | address-selector.uvue | 地址选择弹窗 |
|
||||
| UserEvaluation | product-review.uvue | 商品评价 |
|
||||
| ShareRedPackets | share-popup.uvue | 分享红包 |
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 数据缓存策略
|
||||
```typescript
|
||||
// 使用Supabase内置缓存
|
||||
const { data, error } = await supa
|
||||
.from('products')
|
||||
.select('*')
|
||||
.eq('category_id', categoryId)
|
||||
.order('sales', { ascending: false })
|
||||
.limit(20)
|
||||
// 启用缓存
|
||||
.single()
|
||||
```
|
||||
|
||||
### 2. 图片懒加载
|
||||
```vue
|
||||
<template>
|
||||
<image
|
||||
:src="imageUrl"
|
||||
:lazy-load="true"
|
||||
@load="onImageLoad"
|
||||
@error="onImageError"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3. 列表虚拟化
|
||||
```vue
|
||||
<template>
|
||||
<scroll-view
|
||||
scroll-y="true"
|
||||
:scroll-top="scrollTop"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in visibleItems"
|
||||
:key="item.id"
|
||||
:style="{ transform: `translateY(${item.top}px)` }"
|
||||
>
|
||||
<!-- 商品项 -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 部署和维护
|
||||
|
||||
### 环境配置
|
||||
```javascript
|
||||
// config/app.js
|
||||
export default {
|
||||
supabase: {
|
||||
url: 'https://your-project.supabase.co',
|
||||
anonKey: 'your-anon-key',
|
||||
serviceRoleKey: 'your-service-role-key' // 服务端使用
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库迁移
|
||||
```sql
|
||||
-- 数据库初始化脚本
|
||||
-- 创建表结构
|
||||
-- 设置RLS策略
|
||||
-- 创建索引
|
||||
-- 设置触发器
|
||||
```
|
||||
|
||||
### 监控和日志
|
||||
```typescript
|
||||
// 错误监控
|
||||
const handleError = (error) => {
|
||||
console.error('App Error:', error)
|
||||
// 上报到监控系统
|
||||
}
|
||||
|
||||
// 性能监控
|
||||
const reportPerformance = (metrics) => {
|
||||
// 上报性能数据
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过本重构指南,我们将CRMEB的核心功能成功迁移到基于uvue + Supabase的技术栈:
|
||||
|
||||
1. **数据层**:使用Supabase替代MySQL + Redis
|
||||
2. **API层**:使用@components/supadb替代ThinkPHP
|
||||
3. **前端**:使用uvue替代uni-app
|
||||
4. **实时功能**:使用Supabase实时订阅
|
||||
5. **文件存储**:使用Supabase Storage
|
||||
6. **服务器逻辑**:使用Edge Functions
|
||||
|
||||
这种架构具有以下优势:
|
||||
- **开发效率高**:减少后端开发工作
|
||||
- **维护成本低**:Serverless架构
|
||||
- **扩展性好**:支持实时功能和全球化部署
|
||||
- **安全性高**:Supabase提供完善的安全机制
|
||||
|
||||
实际迁移时需要根据具体业务需求进行调整,并充分测试各项功能。
|
||||
427
docs/FRONTEND_ARCHITECTURE_ANALYSIS.md
Normal file
427
docs/FRONTEND_ARCHITECTURE_ANALYSIS.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# CRMEB Admin 前端架构梳理分析
|
||||
|
||||
## 项目概述
|
||||
- **项目名称**: CRMEB Admin Template
|
||||
- **技术栈**: Vue.js + Element UI + Vue Router + Vuex
|
||||
- **项目类型**: 电商管理后台系统
|
||||
- **分析时间**: 2026-01-23
|
||||
|
||||
## 1. 页面总览表
|
||||
|
||||
| 路由路径 | 页面标题 | 所属端 | 是否tab/子包 | 说明 | 入口文件 |
|
||||
|---------|---------|-------|-------------|------|---------|
|
||||
| `/admin/index` | 主页 | Admin后台 | 否 | 系统首页,包含基础统计信息 | `src/pages/index/index.vue` |
|
||||
| `/admin/product/product_list` | 商品管理 | Admin后台 | 否 | 商品列表管理页面 | `src/pages/product/productList/index.vue` |
|
||||
| `/admin/product/product_classify` | 商品分类 | Admin后台 | 否 | 商品分类管理 | `src/pages/product/productClassify/index.vue` |
|
||||
| `/admin/product/add_product/:id?` | 商品添加 | Admin后台 | 否 | 添加/编辑商品 | `src/pages/product/productAdd/index.vue` |
|
||||
| `/admin/order/list` | 订单管理 | Admin后台 | 否 | 订单列表管理 | `src/pages/order/orderList/index.vue` |
|
||||
| `/admin/order/offline` | 收银订单 | Admin后台 | 否 | 线下收银订单 | `src/pages/order/offline/index.vue` |
|
||||
| `/admin/order/refund` | 售后订单 | Admin后台 | 否 | 售后退款订单 | `src/pages/order/refund/index.vue` |
|
||||
| `/admin/user/list` | 用户管理 | Admin后台 | 否 | 用户列表管理 | `src/pages/user/list/index.vue` |
|
||||
| `/admin/user/level` | 用户等级 | Admin后台 | 否 | 用户等级管理 | `src/pages/user/level/index.vue` |
|
||||
| `/admin/user/group` | 用户分组 | Admin后台 | 否 | 用户分组管理 | `src/pages/user/group/index.vue` |
|
||||
| `/admin/setting/system_config` | 系统设置 | Admin后台 | 否 | 系统基础配置 | `src/pages/setting/setSystem/index.vue` |
|
||||
| `/admin/setting/system_role/index` | 身份管理 | Admin后台 | 否 | 管理员角色管理 | `src/pages/setting/systemRole/index.vue` |
|
||||
| `/admin/setting/system_admin/index` | 管理员列表 | Admin后台 | 否 | 管理员账户管理 | `src/pages/setting/systemAdmin/index.vue` |
|
||||
| `/admin/marketing/store_combination/index` | 拼团商品 | Admin后台 | 否 | 拼团活动商品管理 | `src/pages/marketing/storeCombination/index.vue` |
|
||||
| `/admin/marketing/store_coupon_issue/index` | 优惠券 | Admin后台 | 否 | 优惠券发放管理 | `src/pages/marketing/storeCouponIssue/index.vue` |
|
||||
| `/admin/system/log` | 前端日志 | Admin后台 | 否 | 系统日志查看 | `src/pages/system/log/index.vue` |
|
||||
|
||||
**来源**: `src/router/modules/*.js` 文件中的路由配置
|
||||
|
||||
## 2. 页面关系树
|
||||
|
||||
```
|
||||
CRMEB Admin 后台系统
|
||||
├── 主页 (home_index)
|
||||
│ ├── 基础信息展示 (baseInfo)
|
||||
│ ├── 快捷菜单 (gridMenu)
|
||||
│ ├── 访问统计图表 (visitChart)
|
||||
│ └── 用户统计图表 (userChart)
|
||||
├── 商品管理 (product)
|
||||
│ ├── 商品列表 (productList)
|
||||
│ │ ├── 搜索筛选区
|
||||
│ │ ├── 商品列表表格
|
||||
│ │ └── 操作按钮区
|
||||
│ ├── 商品分类 (productClassify)
|
||||
│ ├── 添加商品 (productAdd)
|
||||
│ ├── 商品评论 (productEvaluate)
|
||||
│ ├── 商品规格 (productAttr)
|
||||
│ ├── 商品参数 (paramList)
|
||||
│ └── 商品标签 (labelList)
|
||||
├── 订单管理 (order)
|
||||
│ ├── 订单列表 (orderList)
|
||||
│ ├── 收银订单 (offline)
|
||||
│ ├── 售后订单 (refund)
|
||||
│ └── 发票管理 (invoice)
|
||||
├── 用户管理 (user)
|
||||
│ ├── 用户列表 (list)
|
||||
│ │ ├── 用户搜索筛选
|
||||
│ │ ├── 用户列表表格
|
||||
│ │ └── 用户详情弹窗
|
||||
│ ├── 用户等级 (level)
|
||||
│ ├── 用户分组 (group)
|
||||
│ ├── 用户标签 (label)
|
||||
│ ├── 会员类型 (type)
|
||||
│ ├── 卡密会员 (card)
|
||||
│ ├── 会员记录 (record)
|
||||
│ └── 会员权益 (right)
|
||||
├── 系统设置 (setting)
|
||||
│ ├── 系统配置 (setSystem)
|
||||
│ ├── 身份管理 (systemRole)
|
||||
│ ├── 管理员列表 (systemAdmin)
|
||||
│ ├── 消息管理 (notification)
|
||||
│ ├── 物流配置 (logistics)
|
||||
│ ├── 短信配置 (sms)
|
||||
│ ├── 商城配置 (config)
|
||||
│ ├── 主题风格 (themeStyle)
|
||||
│ ├── 店铺装修 (devise)
|
||||
│ ├── 客服管理 (service)
|
||||
│ ├── 城市数据 (dada)
|
||||
│ ├── 运费模板 (templates)
|
||||
│ ├── 提货点管理 (store)
|
||||
│ ├── 核销员管理 (staff)
|
||||
│ └── 核销订单 (order)
|
||||
├── 营销活动 (marketing)
|
||||
│ ├── 拼团商品 (combinalist)
|
||||
│ ├── 优惠券 (storeCouponIssue)
|
||||
│ ├── 秒杀商品 (storeSeckill)
|
||||
│ ├── 积分商品 (storeIntegral)
|
||||
│ └── 砍价商品 (bargain)
|
||||
├── 财务管理 (finance)
|
||||
│ ├── 财务对账 (financeList)
|
||||
│ ├── 资金记录 (capitalFlow)
|
||||
│ ├── 佣金记录 (commissionList)
|
||||
│ ├── 提现申请 (extractList)
|
||||
│ └── 账单记录 (billList)
|
||||
├── 系统管理 (system)
|
||||
│ ├── 代码生成 (codeGeneration)
|
||||
│ ├── 文件管理 (fileList)
|
||||
│ ├── 维护管理 (maintain)
|
||||
│ └── 数据配置 (groupData)
|
||||
├── 应用管理 (app)
|
||||
│ ├── 微信菜单 (wechatMenu)
|
||||
│ ├── 微信回复 (wechatReply)
|
||||
│ └── 微信用户 (wechatUser)
|
||||
└── 统计报表 (statistic)
|
||||
├── 产品统计 (productStatistic)
|
||||
├── 用户统计 (userStatistic)
|
||||
├── 订单统计 (orderStatistic)
|
||||
└── 交易统计 (transactionStatistic)
|
||||
```
|
||||
|
||||
## 3. 页面区域-文件映射表
|
||||
|
||||
### 3.1 主页 (home_index)
|
||||
|
||||
| 区域名 | 对应组件/文件 | 说明 | 关键状态 | 关键接口 |
|
||||
|-------|---------------|------|---------|---------|
|
||||
| 基础信息展示区 | `src/pages/index/components/baseInfo.vue` | 显示今日/昨日数据对比和总计信息 | infoList数组 | `headerApi` (来源: `src/api/index.js`) |
|
||||
| 快捷菜单区 | `src/pages/index/components/gridMenu.vue` | 九宫格快捷入口菜单 | - | - |
|
||||
| 访问统计图表 | `src/pages/index/components/visitChart.vue` | 订单统计图表展示 | visitType, visitDate | - |
|
||||
| 用户统计图表 | `src/pages/index/components/userChart.vue` | 用户数据统计图表 | - | - |
|
||||
| 页面布局容器 | `src/pages/index/index.vue` | 主页布局容器,组合上述组件 | userInfo | `auth()` (来源: `src/api/system.js`) |
|
||||
| Vuex状态 | `src/store/module/userInfo.js` | 用户信息状态管理 | userInfo | - |
|
||||
|
||||
**来源**: `src/pages/index/index.vue` (行7-15), `src/store/module/userInfo.js`
|
||||
|
||||
### 3.2 商品列表页面 (product_productList)
|
||||
|
||||
| 区域名 | 对应组件/文件 | 说明 | 关键状态 | 关键接口 |
|
||||
|-------|---------------|------|---------|---------|
|
||||
| 搜索筛选区 | 内联在页面模板中 | 商品名称、类型、分类、配送方式等筛选条件 | artFrom表单对象 | - |
|
||||
| 商品列表表格 | 内联在页面模板中 | 商品数据表格展示,包含分页 | tableData数组 | `productList()` (来源: `src/api/product.js`) |
|
||||
| 操作按钮区 | 内联在页面模板中 | 批量操作、添加商品等按钮 | - | - |
|
||||
| 页面布局容器 | `src/pages/product/productList/index.vue` | 商品管理主页面 | artFrom, tableData, loading | `productList()`, `productStatus()` (来源: `src/api/product.js`) |
|
||||
|
||||
**来源**: `src/pages/product/productList/index.vue` (行1-50), `src/api/product.js`
|
||||
|
||||
### 3.3 用户列表页面 (user_list)
|
||||
|
||||
| 区域名 | 对应组件/文件 | 说明 | 关键状态 | 关键接口 |
|
||||
|-------|---------------|------|---------|---------|
|
||||
| 用户搜索筛选区 | 内联在页面模板中 | 用户昵称、等级、分组等筛选条件 | userFrom表单对象, field_key | - |
|
||||
| 用户列表表格 | 内联在页面模板中 | 用户数据表格,包含用户信息展示 | tableData数组 | `userList()` (来源: `src/api/user.js`) |
|
||||
| 用户详情弹窗 | `src/pages/user/list/handle/userDetails.vue` | 用户详细信息弹窗组件 | visible, userInfo | - |
|
||||
| 用户编辑弹窗 | `src/pages/user/list/handle/userEdit.vue` | 用户信息编辑弹窗 | visible, editForm | `userEdit()` (来源: `src/api/user.js`) |
|
||||
| 页面布局容器 | `src/pages/user/list/index.vue` | 用户管理主页面 | userFrom, tableData, collapse | `userList()`, `userLevel()`, `userGroup()` (来源: `src/api/user.js`) |
|
||||
|
||||
**来源**: `src/pages/user/list/index.vue` (行1-100), `src/api/user.js`
|
||||
|
||||
## 4. 文件职责清单
|
||||
|
||||
### 4.1 Pages 目录文件职责
|
||||
|
||||
#### 主页相关 (src/pages/index/)
|
||||
- `index.vue`: 主页容器组件,组合统计组件,处理用户认证
|
||||
- `components/baseInfo.vue`: 基础统计信息卡片展示
|
||||
- `components/gridMenu.vue`: 快捷操作菜单网格
|
||||
- `components/visitChart.vue`: 访问统计图表组件
|
||||
- `components/userChart.vue`: 用户统计图表组件
|
||||
|
||||
#### 商品管理 (src/pages/product/)
|
||||
- `productList/index.vue`: 商品列表页面,包含搜索、筛选、表格展示
|
||||
- `productClassify/index.vue`: 商品分类管理页面
|
||||
- `productAdd/index.vue`: 商品添加/编辑页面
|
||||
- `productReply/index.vue`: 商品评论管理页面
|
||||
- `productAttr/index.vue`: 商品规格管理页面
|
||||
- `paramList/index.vue`: 商品参数管理页面
|
||||
- `labelList/index.vue`: 商品标签管理页面
|
||||
|
||||
#### 订单管理 (src/pages/order/)
|
||||
- `orderList/index.vue`: 订单列表管理页面
|
||||
- `offline/index.vue`: 收银订单管理页面
|
||||
- `refund/index.vue`: 售后订单管理页面
|
||||
- `invoice/index.vue`: 发票管理页面
|
||||
|
||||
#### 用户管理 (src/pages/user/)
|
||||
- `list/index.vue`: 用户列表主页面
|
||||
- `list/handle/userDetails.vue`: 用户详情弹窗组件
|
||||
- `list/handle/userEdit.vue`: 用户编辑弹窗组件
|
||||
- `list/handle/userEditForm.vue`: 用户编辑表单组件
|
||||
- `list/handle/userInfo.vue`: 用户信息展示组件
|
||||
- `list/tableExpand.vue`: 用户列表表格展开组件
|
||||
- `level/index.vue`: 用户等级管理页面
|
||||
- `group/index.vue`: 用户分组管理页面
|
||||
- `label/index.vue`: 用户标签管理页面
|
||||
|
||||
#### 系统设置 (src/pages/setting/)
|
||||
- `setSystem/index.vue`: 系统配置主页面
|
||||
- `systemRole/index.vue`: 管理员角色管理页面
|
||||
- `systemAdmin/index.vue`: 管理员列表管理页面
|
||||
- `notification/index.vue`: 消息管理页面
|
||||
- `notification/notificationEdit.vue`: 消息编辑页面
|
||||
- `membershipLevel/index.vue`: 分销等级管理页面
|
||||
- `freight/index.vue`: 物流公司管理页面
|
||||
- `cityDada/index.vue`: 城市数据管理页面
|
||||
- `shippingTemplates/index.vue`: 运费模板管理页面
|
||||
- `storeList/index.vue`: 提货点管理页面
|
||||
- `clerkList/index.vue`: 核销员管理页面
|
||||
- `verifyOrder/index.vue`: 核销订单管理页面
|
||||
- `themeStyle/index.vue`: 主题风格设置页面
|
||||
- `devise/list.vue`: 店铺装修页面
|
||||
- `devisePage/index.vue`: 页面设计页面
|
||||
- `link.vue`: 链接管理页面
|
||||
- `storage.vue`: 存储配置页面
|
||||
- `ticket.vue`: 打印机设置页面
|
||||
- `agreement/index.vue`: 协议设置页面
|
||||
|
||||
#### 营销活动 (src/pages/marketing/)
|
||||
- `storeCombination/index.vue`: 拼团商品管理页面
|
||||
- `storeCombination/combinaList.vue`: 拼团列表页面
|
||||
- `storeCouponIssue/index.vue`: 优惠券管理页面
|
||||
- `storeSeckill/index.vue`: 秒杀商品管理页面
|
||||
- `storeIntegral/index.vue`: 积分商品管理页面
|
||||
- `storeBargain/index.vue`: 砍价商品管理页面
|
||||
|
||||
#### 财务管理 (src/pages/finance/)
|
||||
- `financeList/index.vue`: 财务对账页面
|
||||
- `capitalFlow/index.vue`: 资金记录页面
|
||||
- `commissionList/index.vue`: 佣金记录页面
|
||||
- `extractList/index.vue`: 提现申请页面
|
||||
- `billList/index.vue`: 账单记录页面
|
||||
|
||||
#### 系统管理 (src/pages/system/)
|
||||
- `codeGeneration/index.vue`: 代码生成页面
|
||||
- `fileList/index.vue`: 文件管理页面
|
||||
- `maintain/index.vue`: 维护管理页面
|
||||
- `group/list.vue`: 数据配置页面
|
||||
|
||||
#### 应用管理 (src/pages/app/)
|
||||
- `wechatMenu/index.vue`: 微信菜单管理页面
|
||||
- `wechatReply/index.vue`: 微信回复管理页面
|
||||
- `wechatUser/index.vue`: 微信用户管理页面
|
||||
|
||||
#### 统计报表 (src/pages/statistic/)
|
||||
- `productStatistic/index.vue`: 产品统计页面
|
||||
- `userStatistic/index.vue`: 用户统计页面
|
||||
- `orderStatistic/index.vue`: 订单统计页面
|
||||
- `transactionStatistic/index.vue`: 交易统计页面
|
||||
|
||||
### 4.2 Components 目录文件职责
|
||||
|
||||
#### 通用组件 (src/components/)
|
||||
- `common-icon/`: 通用图标组件
|
||||
- `copyright/`: 版权信息组件
|
||||
- `Pagination/`: 分页组件
|
||||
- `parent-view/`: 父视图组件
|
||||
- `publicSearchFrom/`: 公共搜索表单组件
|
||||
- `searchFrom/`: 搜索表单组件
|
||||
- `uploadImg/`: 图片上传组件
|
||||
- `uploadPictures/`: 多图上传组件
|
||||
- `uploadVideo/`: 视频上传组件
|
||||
- `uploadVideo2/`: 视频上传组件(增强版)
|
||||
|
||||
#### 表单组件 (src/components/from/)
|
||||
- `from/`: 通用表单组件
|
||||
|
||||
#### 图表组件 (src/components/echarts/)
|
||||
- `echarts/`: ECharts图表基础组件
|
||||
- `echartsNew/`: 新版ECharts图表组件
|
||||
|
||||
#### 移动端配置组件 (src/components/mobileConfig/)
|
||||
- 包含大量移动端页面配置组件
|
||||
|
||||
#### 弹窗组件 (src/components/hotpotModal/)
|
||||
- `hotpotModal/`: 热区弹窗组件
|
||||
|
||||
#### 编辑器组件 (src/components/wangEditor/)
|
||||
- `wangEditor/`: 富文本编辑器组件
|
||||
|
||||
### 4.3 Store 目录文件职责
|
||||
|
||||
#### Vuex状态管理模块 (src/store/module/)
|
||||
- `user.js`: 用户登录状态管理
|
||||
- `app.js`: 应用全局状态
|
||||
- `menus.js`: 菜单数据管理
|
||||
- `menu.js`: 当前菜单状态
|
||||
- `userInfo.js`: 用户信息管理
|
||||
- `userLevel.js`: 用户等级管理
|
||||
- `order.js`: 订单相关状态
|
||||
- `media.js`: 媒体文件管理
|
||||
- `goodSelect.js`: 商品选择状态
|
||||
- `moren.js`: 默认配置管理
|
||||
- `shopping.js`: 购物相关状态
|
||||
- `fresh.js`: 刷新状态管理
|
||||
- `kefu.js`: 客服状态管理
|
||||
- `integralOrder.js`: 积分订单管理
|
||||
- `mobildConfig.js`: 移动端配置
|
||||
- `upgrade.js`: 升级状态管理
|
||||
- `layout.js`: 布局状态管理
|
||||
- `themeConfig.js`: 主题配置
|
||||
- `routesList.js`: 路由列表
|
||||
- `tagsViewRoutes.js`: 标签页路由
|
||||
- `userInfos.js`: 用户信息扩展
|
||||
- `keepAliveNames.js`: 缓存页面名称
|
||||
|
||||
### 4.4 API 目录文件职责
|
||||
|
||||
#### 接口文件 (src/api/)
|
||||
- `index.js`: 首页相关接口
|
||||
- `account.js`: 账户相关接口
|
||||
- `agent.js`: 代理商相关接口
|
||||
- `app.js`: 应用相关接口
|
||||
- `cms.js`: CMS内容管理接口
|
||||
- `common.js`: 通用接口
|
||||
- `crud.js`: CRUD通用接口
|
||||
- `diy.js`: DIY装修接口
|
||||
- `export.js`: 导出相关接口
|
||||
- `finance.js`: 财务相关接口
|
||||
- `index.js`: 首页统计接口
|
||||
- `kefu.js`: 客服相关接口
|
||||
- `kefu_mobile.js`: 移动端客服接口
|
||||
- `live.js`: 直播相关接口
|
||||
- `lottery.js`: 抽奖相关接口
|
||||
- `marketing.js`: 营销活动接口
|
||||
- `membershipLevel.js`: 会员等级接口
|
||||
- `notification.js`: 消息通知接口
|
||||
- `order.js`: 订单相关接口
|
||||
- `product.js`: 商品相关接口
|
||||
- `setting.js`: 设置相关接口
|
||||
- `statistic.js`: 统计相关接口
|
||||
- `system.js`: 系统相关接口
|
||||
- `systemAdmin.js`: 管理员接口
|
||||
- `systemBackendRouting.js`: 后端路由接口
|
||||
- `systemCodeGeneration.js`: 代码生成接口
|
||||
- `systemMenus.js`: 菜单接口
|
||||
- `systemOutAccount.js`: 外部账户接口
|
||||
- `upload.js`: 上传相关接口
|
||||
- `uploadPictures.js`: 图片上传接口
|
||||
- `user.js`: 用户相关接口
|
||||
|
||||
### 4.5 Utils 目录文件职责
|
||||
|
||||
#### 工具函数 (src/utils/)
|
||||
- `ase.js`: AES加密工具
|
||||
- `authLapse.js`: 认证过期处理
|
||||
- `bus.js`: 事件总线
|
||||
- `city.js`: 城市数据处理
|
||||
- `componentSize.js`: 组件尺寸工具
|
||||
- `compressImg.js`: 图片压缩工具
|
||||
- `editorImg.js`: 编辑器图片处理
|
||||
- `emoji.js`: 表情符号处理
|
||||
- `Excel.js`: Excel处理工具
|
||||
- `icon.js`: 图标处理工具
|
||||
- `index.js`: 工具函数入口
|
||||
- `loading.js`: 加载状态管理
|
||||
- `modalForm.js`: 弹窗表单工具
|
||||
- `newToExcel.js`: 新版Excel导出
|
||||
- `public.js`: 公共工具函数
|
||||
- `scroll-to.js`: 滚动工具
|
||||
- `storage.js`: 本地存储工具
|
||||
- `theme.js`: 主题工具
|
||||
- `toolsValidate.js`: 表单验证工具
|
||||
- `upload.js`: 上传工具
|
||||
- `validate.js`: 数据验证工具
|
||||
- `videoCloud.js`: 视频云处理
|
||||
|
||||
### 4.6 Styles 目录文件职责
|
||||
|
||||
#### 样式文件 (src/theme/)
|
||||
- `app.scss`: 应用主样式
|
||||
- `base.scss`: 基础样式
|
||||
- `common/transition.scss`: 过渡动画样式
|
||||
- `dark.scss`: 暗色主题
|
||||
- `element.scss`: Element UI样式覆盖
|
||||
- `iview.scss`: iView样式覆盖
|
||||
- `index.scss`: 样式入口文件
|
||||
- `loading.scss`: 加载样式
|
||||
- `variables.scss`: 样式变量
|
||||
|
||||
## 5. 未知/缺失信息清单
|
||||
|
||||
### 5.1 需要补充分析的页面文件
|
||||
- `src/pages/kefu/`: 客服模块页面文件(40个文件)- 未详细分析
|
||||
- `src/pages/marketing/`: 部分营销活动页面组件 - 未完全追踪依赖关系
|
||||
- `src/pages/setting/`: 系统设置下多个子页面 - 未完全分析组件结构
|
||||
- `src/pages/system/`: 系统管理页面 - 未详细分析
|
||||
- `src/pages/app/`: 应用管理页面 - 未详细分析
|
||||
- `src/pages/statistic/`: 统计报表页面 - 未详细分析
|
||||
|
||||
### 5.2 需要补充的组件依赖
|
||||
- `src/components/mobileConfig/`: 35个移动端配置组件 - 未分析具体职责
|
||||
- `src/components/mobileConfigRight/`: 39个右侧配置组件 - 未分析具体职责
|
||||
- `src/components/mobilePage/`: 28个移动端页面组件 - 未分析具体职责
|
||||
- `src/components/mobilePageDiy/`: 23个DIY页面组件 - 未分析具体职责
|
||||
|
||||
### 5.3 需要补充的API接口分析
|
||||
- 大部分API文件只分析了入口,未分析具体接口函数职责
|
||||
- 缺少接口参数和返回数据结构分析
|
||||
|
||||
### 5.4 需要补充的Store状态分析
|
||||
- 大部分store模块只分析了入口,未分析具体state/mutations/actions结构
|
||||
|
||||
### 5.5 需要补充的Utils工具分析
|
||||
- 大部分工具文件未分析具体函数功能和使用场景
|
||||
|
||||
## 6. 架构优化建议
|
||||
|
||||
### 6.1 组件抽取建议
|
||||
1. **搜索筛选组件**: 将各页面的搜索筛选逻辑抽取为通用组件
|
||||
2. **数据表格组件**: 标准化列表页面的表格展示组件
|
||||
3. **弹窗表单组件**: 统一弹窗编辑表单的结构和交互
|
||||
4. **统计卡片组件**: 标准化统计信息展示组件
|
||||
|
||||
### 6.2 Store模块重组建议
|
||||
1. **按业务域重组**: 将相关业务的状态管理集中到一起
|
||||
2. **状态规范化**: 统一state的命名和结构规范
|
||||
3. **Actions抽象**: 将通用数据操作抽象为通用actions
|
||||
|
||||
### 6.3 API接口重组建议
|
||||
1. **按模块分组**: 将API按业务模块重新组织
|
||||
2. **接口规范化**: 统一接口命名和参数结构
|
||||
3. **错误处理统一**: 标准化API错误处理逻辑
|
||||
|
||||
### 6.4 Utils工具优化建议
|
||||
1. **工具分类**: 按功能类型重新组织工具函数
|
||||
2. **通用工具库**: 抽取跨页面使用的通用工具
|
||||
3. **类型定义**: 为工具函数添加TypeScript类型定义
|
||||
|
||||
### 6.5 文件组织优化建议
|
||||
1. **组件按功能分组**: 将组件按功能类型重新组织目录结构
|
||||
2. **页面组件瘦身**: 将复杂页面的逻辑抽取到composables中
|
||||
3. **样式模块化**: 将样式按组件和页面分离组织
|
||||
|
||||
---
|
||||
|
||||
**分析说明**: 本文档基于代码静态分析生成,重点分析了页面结构、组件依赖和文件职责关系。由于项目规模较大,部分细节需要进一步深入分析。
|
||||
292
docs/PAGE_STRUCTURE_ANALYSIS.md
Normal file
292
docs/PAGE_STRUCTURE_ANALYSIS.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# CRMEB Admin 页面结构与流转分析
|
||||
|
||||
## 项目概述
|
||||
- **项目**: CRMEB Admin 电商管理后台
|
||||
- **技术栈**: Vue.js + Vue Router + Element UI + Vuex
|
||||
- **路由前缀**: `/admin` (可配置)
|
||||
- **路由模式**: history
|
||||
|
||||
## 1. 应用入口结构
|
||||
|
||||
### 1.1 入口文件层级
|
||||
```
|
||||
main.js (应用入口)
|
||||
├── Vue实例初始化
|
||||
|
||||
|
||||
├── 路由器配置
|
||||
├── Vuex状态管理
|
||||
└── 全局组件/插件注册
|
||||
|
||||
App.vue (根组件)
|
||||
├── router-view (主路由出口)
|
||||
└── Setings组件 (布局设置弹窗)
|
||||
```
|
||||
|
||||
**源码位置**:
|
||||
- `src/main.js` (line 250-256): Vue实例创建
|
||||
- `src/App.vue` (line 3): 根路由视图
|
||||
|
||||
## 2. 页面布局体系
|
||||
|
||||
### 2.1 布局组件层级
|
||||
```
|
||||
LayoutMain (主布局组件)
|
||||
├── layout/index.vue (布局选择器)
|
||||
│ ├── 根据主题配置选择布局类型
|
||||
│ ├── 支持4种布局模式
|
||||
│ └── 响应式适配
|
||||
│
|
||||
├── layout/main/defaults.vue (默认布局)
|
||||
│ ├── Asides (侧边栏)
|
||||
│ ├── Headers (顶部导航栏)
|
||||
│ └── Mains (主内容区)
|
||||
│
|
||||
└── layout/component/main.vue (主内容容器)
|
||||
├── LayoutParentView (路由视图容器)
|
||||
├── Footers (页脚)
|
||||
├── Links (链接页面)
|
||||
└── Iframes (内嵌页面)
|
||||
```
|
||||
|
||||
**源码位置**:
|
||||
- `src/layout/index.vue` (line 3-8): 布局选择逻辑
|
||||
- `src/layout/main/defaults.vue` (line 2-11): 默认布局结构
|
||||
- `src/layout/component/main.vue` (line 9): 路由视图
|
||||
|
||||
### 2.2 路由视图容器
|
||||
```
|
||||
LayoutParentView (父级路由视图)
|
||||
├── transition (页面切换动画)
|
||||
├── keep-alive (页面缓存)
|
||||
└── router-view (实际路由出口)
|
||||
```
|
||||
|
||||
**源码位置**: `src/layout/routerView/parent.vue`
|
||||
|
||||
## 3. 路由配置体系
|
||||
|
||||
### 3.1 路由分类结构
|
||||
```
|
||||
路由配置 (src/router/routers.js)
|
||||
├── frameIn (主框架内路由)
|
||||
│ ├── 基础路径: /
|
||||
│ ├── 使用LayoutMain布局
|
||||
│ ├── 包含所有业务模块
|
||||
│ └── 需要权限验证
|
||||
│
|
||||
├── frameOuts (主框架外路由)
|
||||
│ ├── 登录页面
|
||||
│ ├── 客服模块
|
||||
│ ├── 特殊功能页面
|
||||
│ └── 无需权限验证
|
||||
│
|
||||
└── errorPage (错误页面)
|
||||
├── 403/404/500错误页
|
||||
└── 通配符路由兜底
|
||||
```
|
||||
|
||||
### 3.2 路由模块组织
|
||||
```
|
||||
src/router/modules/
|
||||
├── index.js # 首页模块
|
||||
├── product.js # 商品管理
|
||||
├── order.js # 订单管理
|
||||
├── user.js # 用户管理
|
||||
├── setting.js # 系统设置
|
||||
├── marketing.js # 营销活动
|
||||
├── finance.js # 财务管理
|
||||
├── system.js # 系统管理
|
||||
├── app.js # 应用管理
|
||||
├── statistic.js # 统计报表
|
||||
├── frameOut.js # 框架外路由
|
||||
├── crud.js # CRUD模块
|
||||
├── division.js # 部门管理
|
||||
└── agent.js # 代理商管理
|
||||
```
|
||||
|
||||
## 4. 页面流转逻辑
|
||||
|
||||
### 4.1 应用启动流程
|
||||
```
|
||||
应用启动
|
||||
├── main.js 初始化Vue实例
|
||||
├── 注册全局组件和插件
|
||||
├── 配置路由和状态管理
|
||||
└── 挂载到 #app 元素
|
||||
|
||||
页面初次加载
|
||||
├── App.vue 渲染 <router-view />
|
||||
├── 路由器检查当前路径
|
||||
├── 匹配路由配置
|
||||
└── 渲染对应组件
|
||||
```
|
||||
|
||||
### 4.2 登录认证流程
|
||||
```
|
||||
访问受保护页面
|
||||
├── router.beforeEach 路由守卫拦截
|
||||
├── 检查用户token
|
||||
│ ├── 有token → 验证权限
|
||||
│ └── 无token → 跳转登录页
|
||||
├── 权限验证
|
||||
│ ├── 有权限 → 允许访问
|
||||
│ └── 无权限 → 跳转403页
|
||||
└── 渲染目标页面
|
||||
```
|
||||
|
||||
**源码位置**: `src/router/index.js` (line 116-169)
|
||||
|
||||
### 4.3 页面导航流程
|
||||
```
|
||||
用户点击导航
|
||||
├── 触发路由变化 ($route变化)
|
||||
├── layout/index.vue 监听路由变化
|
||||
├── 更新面包屑导航
|
||||
├── 更新标签页导航
|
||||
├── LayoutParentView 处理页面切换
|
||||
│ ├── 检查keep-alive缓存
|
||||
│ ├── 执行页面切换动画
|
||||
│ └── 渲染新页面组件
|
||||
└── 滚动到页面顶部
|
||||
```
|
||||
|
||||
### 4.4 布局切换流程
|
||||
```
|
||||
用户切换布局主题
|
||||
├── Vuex状态更新 (themeConfig)
|
||||
├── layout/index.vue 响应状态变化
|
||||
├── 根据配置选择布局组件
|
||||
├── 重新渲染布局结构
|
||||
└── 保持页面内容不变
|
||||
```
|
||||
|
||||
## 5. 关键页面路径
|
||||
|
||||
### 5.1 主框架内页面 (frameIn)
|
||||
| 路径 | 页面名称 | 组件位置 | 权限要求 |
|
||||
|------|---------|---------|---------|
|
||||
| `/admin/index` | 主页 | `src/pages/index/index.vue` | `admin-index-index` |
|
||||
| `/admin/product/product_list` | 商品管理 | `src/pages/product/productList/index.vue` | `admin-store-storeProuduct-index` |
|
||||
| `/admin/order/list` | 订单管理 | `src/pages/order/orderList/index.vue` | `admin-order-storeOrder-index` |
|
||||
| `/admin/user/list` | 用户管理 | `src/pages/user/list/index.vue` | `admin-user-user-index` |
|
||||
| `/admin/setting/system_config` | 系统设置 | `src/pages/setting/setSystem/index.vue` | `setting-system-config` |
|
||||
|
||||
### 5.2 主框架外页面 (frameOut)
|
||||
| 路径 | 页面名称 | 组件位置 | 说明 |
|
||||
|------|---------|---------|------|
|
||||
| `/admin/login` | 登录页面 | `src/pages/account/login/index.vue` | 无需登录即可访问 |
|
||||
| `/kefu` | 客服管理 | `src/pages/kefu/index.vue` | 独立客服系统 |
|
||||
| `/kefu/mobile_chat` | 移动端客服 | `src/pages/kefu/mobile/index.vue` | 移动端聊天界面 |
|
||||
| `/app/upload` | 文件上传 | `src/pages/app/upload.vue` | 移动端扫码上传 |
|
||||
|
||||
## 6. 页面缓存机制
|
||||
|
||||
### 6.1 Keep-Alive 配置
|
||||
```javascript
|
||||
// src/layout/routerView/parent.vue
|
||||
<keep-alive :include="keepAliveNameList">
|
||||
<router-view :key="refreshRouterViewKey" />
|
||||
</keep-alive>
|
||||
```
|
||||
|
||||
### 6.2 缓存管理
|
||||
```
|
||||
缓存页面列表
|
||||
├── 从Vuex获取 (keepAliveNames)
|
||||
├── 支持页面刷新功能
|
||||
├── 标签页关闭时清理缓存
|
||||
└── 路由切换时保持状态
|
||||
```
|
||||
|
||||
**源码位置**: `src/store/module/keepAliveNames.js`
|
||||
|
||||
## 7. 页面布局类型
|
||||
|
||||
### 7.1 支持的布局模式
|
||||
```
|
||||
defaults (默认布局)
|
||||
├── 经典的左右布局
|
||||
├── 侧边栏 + 主内容区
|
||||
└── 支持固定头部
|
||||
|
||||
classic (经典布局)
|
||||
├── 传统管理后台布局
|
||||
├── 顶部导航 + 侧边栏
|
||||
└── 适用于复杂导航
|
||||
|
||||
transverse (横向布局)
|
||||
├── 横向菜单布局
|
||||
├── 适用于扁平化导航
|
||||
└── 节省垂直空间
|
||||
|
||||
columns (分栏布局)
|
||||
├── 多列布局设计
|
||||
├── 适用于信息密集页面
|
||||
└── 提高空间利用率
|
||||
```
|
||||
|
||||
**源码位置**: `src/layout/main/` 目录下对应文件
|
||||
|
||||
## 8. 特殊页面处理
|
||||
|
||||
### 8.1 全屏页面
|
||||
```javascript
|
||||
// 路由meta配置
|
||||
meta: {
|
||||
fullScreen: true // 启用全屏模式
|
||||
}
|
||||
```
|
||||
- 隐藏侧边栏和顶部导航
|
||||
- 适用于页面设计器等特殊场景
|
||||
|
||||
### 8.2 内嵌页面
|
||||
```javascript
|
||||
// 路由meta配置
|
||||
meta: {
|
||||
isIframe: true, // 内嵌iframe
|
||||
isLink: true // 链接页面
|
||||
}
|
||||
```
|
||||
- 支持iframe内嵌外部页面
|
||||
- 支持直接跳转外部链接
|
||||
|
||||
### 8.3 标签页固定
|
||||
```javascript
|
||||
// 路由meta配置
|
||||
meta: {
|
||||
isAffix: true // 固定标签页
|
||||
}
|
||||
```
|
||||
- 标签页不可关闭
|
||||
- 通常用于首页等重要页面
|
||||
|
||||
## 9. 响应式适配
|
||||
|
||||
### 9.1 移动端适配
|
||||
```
|
||||
屏幕宽度检测
|
||||
├── < 600px → Mobile模式
|
||||
├── 600-992px → Tablet模式
|
||||
├── > 992px → Desktop模式
|
||||
|
||||
布局自适应
|
||||
├── 移动端隐藏侧边栏
|
||||
├── 自动切换到单列布局
|
||||
└── 调整组件尺寸
|
||||
```
|
||||
|
||||
**源码位置**: `src/App.vue` (line 33-49)
|
||||
|
||||
## 10. 总结
|
||||
|
||||
CRMEB Admin的页面结构采用了典型的管理后台架构:
|
||||
|
||||
1. **分层设计**: 入口层 → 布局层 → 路由层 → 页面层
|
||||
2. **模块化组织**: 路由按业务模块拆分,便于维护
|
||||
3. **灵活布局**: 支持多种布局模式,适应不同使用场景
|
||||
4. **权限控制**: 基于路由的权限验证体系
|
||||
5. **缓存优化**: 智能的页面缓存机制提升用户体验
|
||||
6. **响应式适配**: 支持多终端访问
|
||||
|
||||
这种架构设计使得系统具有良好的可扩展性和维护性,同时提供了丰富的定制化选项。
|
||||
@@ -1,660 +0,0 @@
|
||||
# UI 设计规范文档
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
本文档基于 **CRMEB 项目的 UI 设计理念和交互模式**,提取设计思想、布局方式、组件模式等,为项目提供统一的 UI 设计规范。**所有实现均为原创,不复制任何源码**。
|
||||
|
||||
### 设计原则
|
||||
|
||||
1. **参考不复制**:仅参考 CRMEB 的设计理念,不复制任何代码
|
||||
2. **现代简约**:遵循现代电商 UI 设计趋势
|
||||
3. **用户体验优先**:注重交互流畅性和视觉舒适度
|
||||
4. **一致性**:保持整体设计风格统一
|
||||
|
||||
---
|
||||
|
||||
## 一、设计风格分析
|
||||
|
||||
### 1.1 CRMEB 设计特点
|
||||
|
||||
基于对 CRMEB 项目的分析,提取以下设计特点:
|
||||
|
||||
#### 1.1.1 视觉风格
|
||||
- **色彩**:以红色系为主色调(#E93323),体现电商活力
|
||||
- **圆角**:大量使用圆角设计(20rpx-24rpx),柔和现代
|
||||
- **阴影**:轻微阴影效果,增强层次感
|
||||
- **间距**:宽松的间距设计,提升可读性
|
||||
|
||||
#### 1.1.2 布局特点
|
||||
- **卡片式设计**:信息以卡片形式呈现,清晰分组
|
||||
- **瀑布流布局**:商品列表采用瀑布流,提升浏览体验
|
||||
- **模块化设计**:首页采用模块化组件,灵活配置
|
||||
- **响应式适配**:适配不同屏幕尺寸
|
||||
|
||||
#### 1.1.3 交互特点
|
||||
- **流畅动画**:页面切换和操作有平滑过渡
|
||||
- **即时反馈**:操作有明确的视觉反馈
|
||||
- **加载状态**:优雅的加载动画和骨架屏
|
||||
- **错误处理**:友好的错误提示和空状态
|
||||
|
||||
---
|
||||
|
||||
## 二、颜色系统
|
||||
|
||||
### 2.1 主色调
|
||||
|
||||
基于 CRMEB 的设计理念,定义以下颜色系统:
|
||||
|
||||
```scss
|
||||
// 主题色
|
||||
$theme-primary: #FF4D4F; // 主色(红色系,体现电商活力)
|
||||
$theme-primary-light: #FF7875; // 主色浅色
|
||||
$theme-primary-dark: #CF1322; // 主色深色
|
||||
|
||||
// 渐变色
|
||||
$gradient-start: #FF4D4F; // 渐变起始色
|
||||
$gradient-end: #FF7A45; // 渐变结束色
|
||||
|
||||
// 功能色
|
||||
$success: #52C41A; // 成功色
|
||||
$warning: #FAAD14; // 警告色
|
||||
$error: #FF4D4F; // 错误色
|
||||
$info: #1890FF; // 信息色
|
||||
```
|
||||
|
||||
### 2.2 中性色
|
||||
|
||||
```scss
|
||||
// 文字颜色
|
||||
$text-primary: #111111; // 主要文字
|
||||
$text-secondary: #333333; // 次要文字
|
||||
$text-tertiary: #666666; // 辅助文字
|
||||
$text-disabled: #999999; // 禁用文字
|
||||
$text-placeholder: #CCCCCC; // 占位文字
|
||||
|
||||
// 背景颜色
|
||||
$bg-primary: #FFFFFF; // 主背景
|
||||
$bg-secondary: #F7F8FA; // 次背景
|
||||
$bg-tertiary: #F5F5F5; // 三级背景
|
||||
$bg-hover: #F1F1F1; // 悬停背景
|
||||
|
||||
// 边框颜色
|
||||
$border-color: rgba(0, 0, 0, 0.06); // 边框色
|
||||
$border-color-light: rgba(0, 0, 0, 0.08); // 浅边框
|
||||
```
|
||||
|
||||
### 2.3 使用规范
|
||||
|
||||
- **主色**:用于按钮、链接、重要信息
|
||||
- **渐变色**:用于主要操作按钮、强调元素
|
||||
- **中性色**:用于文字、背景、边框
|
||||
- **功能色**:用于状态提示、警告信息
|
||||
|
||||
---
|
||||
|
||||
## 三、布局规范
|
||||
|
||||
### 3.1 间距系统
|
||||
|
||||
```scss
|
||||
// 基础间距单位(基于 rpx)
|
||||
$spacing-xs: 8rpx; // 极小间距
|
||||
$spacing-sm: 16rpx; // 小间距
|
||||
$spacing-md: 24rpx; // 中等间距
|
||||
$spacing-lg: 32rpx; // 大间距
|
||||
$spacing-xl: 48rpx; // 超大间距
|
||||
|
||||
// 页面边距
|
||||
$page-padding: 24rpx; // 页面左右边距
|
||||
$section-margin: 20rpx; // 模块间距
|
||||
```
|
||||
|
||||
### 3.2 圆角规范
|
||||
|
||||
```scss
|
||||
$radius-sm: 8rpx; // 小圆角
|
||||
$radius-md: 12rpx; // 中等圆角
|
||||
$radius-lg: 20rpx; // 大圆角
|
||||
$radius-xl: 24rpx; // 超大圆角
|
||||
$radius-circle: 50%; // 圆形
|
||||
```
|
||||
|
||||
### 3.3 阴影规范
|
||||
|
||||
```scss
|
||||
// 轻微阴影(卡片)
|
||||
$shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
// 中等阴影(悬浮)
|
||||
$shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
// 大阴影(弹窗)
|
||||
$shadow-lg: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
|
||||
```
|
||||
|
||||
### 3.4 布局模式
|
||||
|
||||
#### 3.4.1 卡片布局
|
||||
- **特点**:信息以卡片形式呈现,有圆角和阴影
|
||||
- **应用**:商品卡片、订单卡片、信息卡片
|
||||
- **实现**:使用 `view` 容器,添加圆角和阴影样式
|
||||
|
||||
#### 3.4.2 列表布局
|
||||
- **特点**:信息以列表形式呈现,清晰有序
|
||||
- **应用**:订单列表、地址列表、设置列表
|
||||
- **实现**:使用 `view` 容器,添加分割线
|
||||
|
||||
#### 3.4.3 网格布局
|
||||
- **特点**:信息以网格形式呈现,整齐排列
|
||||
- **应用**:商品网格、分类网格、功能入口
|
||||
- **实现**:使用 `view` 容器,配合 flex 布局
|
||||
|
||||
---
|
||||
|
||||
## 四、组件设计规范
|
||||
|
||||
### 4.1 按钮组件
|
||||
|
||||
#### 4.1.1 主要按钮(Primary)
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:渐变色(#FF4D4F → #FF7A45)
|
||||
- 文字:白色
|
||||
- 圆角:18rpx
|
||||
- 高度:92rpx
|
||||
- 阴影:0 16rpx 32rpx rgba(255, 77, 79, 0.24)
|
||||
```
|
||||
|
||||
#### 4.1.2 次要按钮(Secondary)
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:白色
|
||||
- 文字:主题色
|
||||
- 边框:1rpx solid 主题色
|
||||
- 圆角:18rpx
|
||||
- 高度:92rpx
|
||||
```
|
||||
|
||||
#### 4.1.3 文字按钮(Text)
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:透明
|
||||
- 文字:主题色
|
||||
- 无边框
|
||||
- 无圆角
|
||||
```
|
||||
|
||||
### 4.2 输入框组件
|
||||
|
||||
#### 4.2.1 标准输入框
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:#F6F7F9
|
||||
- 边框:2rpx solid rgba(0, 0, 0, 0.06)
|
||||
- 圆角:14rpx
|
||||
- 高度:84rpx
|
||||
- 内边距:0 14rpx
|
||||
- 错误状态:边框变红,背景变浅红
|
||||
```
|
||||
|
||||
### 4.3 商品卡片组件
|
||||
|
||||
#### 4.3.1 商品列表卡片
|
||||
```scss
|
||||
// 布局特点
|
||||
- 图片:180rpx × 180rpx,圆角 20rpx
|
||||
- 信息:商品名称、价格、销量
|
||||
- 间距:左右 30rpx,上下 20rpx
|
||||
- 活动标签:左上角显示(秒杀/砍价/拼团)
|
||||
```
|
||||
|
||||
#### 4.3.2 商品网格卡片
|
||||
```scss
|
||||
// 布局特点
|
||||
- 图片:宽高比 1:1,圆角 20rpx
|
||||
- 信息:商品名称、价格、原价(删除线)
|
||||
- 间距:网格间距 16rpx
|
||||
- 两列或三列布局
|
||||
```
|
||||
|
||||
### 4.4 订单卡片组件
|
||||
|
||||
#### 4.4.1 订单列表卡片
|
||||
```scss
|
||||
// 布局特点
|
||||
- 背景:白色
|
||||
- 圆角:20rpx
|
||||
- 内边距:24rpx
|
||||
- 阴影:轻微阴影
|
||||
- 内容:订单号、商品信息、价格、状态
|
||||
```
|
||||
|
||||
### 4.5 导航栏组件
|
||||
|
||||
#### 4.5.1 顶部导航栏
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:白色或透明(滚动时变化)
|
||||
- 高度:88rpx(含状态栏)
|
||||
- 文字:主题色或白色
|
||||
- 返回按钮:左侧
|
||||
- 搜索框:居中(可选)
|
||||
```
|
||||
|
||||
### 4.6 标签组件
|
||||
|
||||
#### 4.6.1 活动标签
|
||||
```scss
|
||||
// 样式特点
|
||||
- 背景:主题色或渐变色
|
||||
- 文字:白色
|
||||
- 圆角:4rpx 或 圆形
|
||||
- 位置:商品图片左上角
|
||||
- 文字:秒杀/砍价/拼团
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、交互模式
|
||||
|
||||
### 5.1 页面切换
|
||||
|
||||
- **动画**:使用 uni-app 的页面切换动画
|
||||
- **返回**:支持滑动返回(iOS 风格)
|
||||
- **加载**:页面切换时显示加载状态
|
||||
|
||||
### 5.2 数据加载
|
||||
|
||||
- **骨架屏**:数据加载时显示骨架屏
|
||||
- **下拉刷新**:列表页面支持下拉刷新
|
||||
- **上拉加载**:列表页面支持上拉加载更多
|
||||
- **加载状态**:显示加载动画和提示文字
|
||||
|
||||
### 5.3 操作反馈
|
||||
|
||||
- **点击反馈**:使用 `hover-class` 提供点击反馈
|
||||
- **成功提示**:使用 `uni.showToast` 显示成功提示
|
||||
- **错误提示**:使用 `uni.showModal` 显示错误信息
|
||||
- **加载提示**:使用 `uni.showLoading` 显示加载状态
|
||||
|
||||
### 5.4 表单交互
|
||||
|
||||
- **实时验证**:输入时实时验证,显示错误信息
|
||||
- **提交反馈**:提交时显示加载状态,防止重复提交
|
||||
- **成功跳转**:提交成功后自动跳转或提示
|
||||
|
||||
---
|
||||
|
||||
## 六、页面设计规范
|
||||
|
||||
### 6.1 首页设计
|
||||
|
||||
#### 6.1.1 布局结构
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 顶部搜索栏(可选) │
|
||||
├─────────────────────────┤
|
||||
│ 轮播图(Banner) │
|
||||
├─────────────────────────┤
|
||||
│ 分类导航(横向滚动) │
|
||||
├─────────────────────────┤
|
||||
│ 营销模块(可选) │
|
||||
│ - 秒杀/拼团/砍价 │
|
||||
├─────────────────────────┤
|
||||
│ 商品推荐 │
|
||||
│ - 瀑布流布局 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
#### 6.1.2 设计要点
|
||||
- **模块化**:首页由多个模块组成,可配置
|
||||
- **瀑布流**:商品列表采用瀑布流布局
|
||||
- **懒加载**:图片和内容懒加载,提升性能
|
||||
- **下拉刷新**:支持下拉刷新
|
||||
|
||||
### 6.2 商品详情页
|
||||
|
||||
#### 6.2.1 布局结构
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 商品轮播图 │
|
||||
├─────────────────────────┤
|
||||
│ 商品信息 │
|
||||
│ - 价格(突出显示) │
|
||||
│ - 标题 │
|
||||
│ - 标签 │
|
||||
├─────────────────────────┤
|
||||
│ 优惠信息 │
|
||||
│ - 优惠券 │
|
||||
│ - 活动 │
|
||||
├─────────────────────────┤
|
||||
│ 规格选择(弹窗) │
|
||||
├─────────────────────────┤
|
||||
│ 商品详情(Tab切换) │
|
||||
│ - 详情 │
|
||||
│ - 评价 │
|
||||
│ - 推荐 │
|
||||
├─────────────────────────┤
|
||||
│ 底部操作栏(固定) │
|
||||
│ - 购物车/立即购买 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
#### 6.2.2 设计要点
|
||||
- **图片展示**:轮播图展示商品图片
|
||||
- **价格突出**:价格使用大字号和主题色
|
||||
- **规格选择**:点击规格弹出选择弹窗
|
||||
- **底部固定**:操作按钮固定在底部
|
||||
|
||||
### 6.3 购物车页面
|
||||
|
||||
#### 6.3.1 布局结构
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 商品列表 │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ ☑ 商品卡片 │ │
|
||||
│ │ 数量选择 │ │
|
||||
│ └───────────────────┘ │
|
||||
├─────────────────────────┤
|
||||
│ 底部结算栏(固定) │
|
||||
│ - 全选 │
|
||||
│ - 合计金额 │
|
||||
│ - 结算按钮 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
#### 6.3.2 设计要点
|
||||
- **选择状态**:每个商品可单独选择
|
||||
- **数量编辑**:支持增减数量
|
||||
- **价格计算**:实时计算总价
|
||||
- **底部固定**:结算栏固定在底部
|
||||
|
||||
### 6.4 订单确认页
|
||||
|
||||
#### 6.4.1 布局结构
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 收货地址(可选) │
|
||||
├─────────────────────────┤
|
||||
│ 商品列表 │
|
||||
├─────────────────────────┤
|
||||
│ 优惠券选择(可选) │
|
||||
├─────────────────────────┤
|
||||
│ 配送方式 │
|
||||
├─────────────────────────┤
|
||||
│ 备注信息 │
|
||||
├─────────────────────────┤
|
||||
│ 价格明细 │
|
||||
│ - 商品总额 │
|
||||
│ - 运费 │
|
||||
│ - 优惠金额 │
|
||||
│ - 实付金额 │
|
||||
├─────────────────────────┤
|
||||
│ 提交订单按钮(固定) │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.5 个人中心页
|
||||
|
||||
#### 6.5.1 布局结构
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 用户信息卡片 │
|
||||
│ - 头像 │
|
||||
│ - 昵称 │
|
||||
│ - 会员信息 │
|
||||
├─────────────────────────┤
|
||||
│ 订单状态(快捷入口) │
|
||||
│ - 待付款/待发货等 │
|
||||
├─────────────────────────┤
|
||||
│ 功能菜单 │
|
||||
│ - 我的订单 │
|
||||
│ - 我的地址 │
|
||||
│ - 我的优惠券 │
|
||||
│ - 设置 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、实现指南
|
||||
|
||||
### 7.1 样式变量定义
|
||||
|
||||
创建 `styles/variables.uts` 文件:
|
||||
|
||||
```typescript
|
||||
// 颜色变量
|
||||
export const THEME_PRIMARY = '#FF4D4F'
|
||||
export const THEME_GRADIENT_START = '#FF4D4F'
|
||||
export const THEME_GRADIENT_END = '#FF7A45'
|
||||
|
||||
// 间距变量
|
||||
export const SPACING_XS = '8rpx'
|
||||
export const SPACING_SM = '16rpx'
|
||||
export const SPACING_MD = '24rpx'
|
||||
export const SPACING_LG = '32rpx'
|
||||
|
||||
// 圆角变量
|
||||
export const RADIUS_SM = '8rpx'
|
||||
export const RADIUS_MD = '12rpx'
|
||||
export const RADIUS_LG = '20rpx'
|
||||
```
|
||||
|
||||
### 7.2 通用样式类
|
||||
|
||||
创建 `styles/common.uvue` 或使用 `<style>` 标签:
|
||||
|
||||
```css
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7A45 100%);
|
||||
color: #FFFFFF;
|
||||
border-radius: 18rpx;
|
||||
height: 92rpx;
|
||||
box-shadow: 0 16rpx 32rpx rgba(255, 77, 79, 0.24);
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.input {
|
||||
background: #F6F7F9;
|
||||
border: 2rpx solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 14rpx;
|
||||
height: 84rpx;
|
||||
padding: 0 14rpx;
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 组件实现示例
|
||||
|
||||
#### 7.3.1 商品卡片组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="product-card" @click="handleClick">
|
||||
<view class="product-image">
|
||||
<image :src="product.image_url" mode="aspectFill" />
|
||||
<view v-if="product.activity" class="activity-tag">
|
||||
{{ activityText }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<view class="product-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{ product.price }}</text>
|
||||
</view>
|
||||
<text class="product-sales">已售 {{ product.sales }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
product: ProductType
|
||||
}>()
|
||||
|
||||
const activityText = ref<string>('')
|
||||
// 根据活动类型设置标签文字
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.product-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-image image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.activity-tag {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7A45 100%);
|
||||
color: #FFFFFF;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
color: #111111;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.price-symbol {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D4F;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 36rpx;
|
||||
color: #FF4D4F;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.product-sales {
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、最佳实践
|
||||
|
||||
### 8.1 性能优化
|
||||
|
||||
1. **图片优化**
|
||||
- 使用合适的图片尺寸
|
||||
- 启用图片懒加载
|
||||
- 使用 WebP 格式(如支持)
|
||||
|
||||
2. **列表优化**
|
||||
- 使用虚拟列表(长列表)
|
||||
- 分页加载数据
|
||||
- 避免不必要的重新渲染
|
||||
|
||||
3. **动画优化**
|
||||
- 使用 CSS 动画而非 JS 动画
|
||||
- 避免过度动画
|
||||
- 使用 `transform` 和 `opacity` 做动画
|
||||
|
||||
### 8.2 可访问性
|
||||
|
||||
1. **文字大小**
|
||||
- 最小字号:24rpx
|
||||
- 主要文字:28rpx-32rpx
|
||||
- 标题文字:36rpx-40rpx
|
||||
|
||||
2. **点击区域**
|
||||
- 最小点击区域:88rpx × 88rpx
|
||||
- 按钮高度:至少 80rpx
|
||||
|
||||
3. **颜色对比**
|
||||
- 文字与背景对比度:至少 4.5:1
|
||||
- 重要信息使用高对比度
|
||||
|
||||
### 8.3 响应式设计
|
||||
|
||||
1. **屏幕适配**
|
||||
- 使用 rpx 单位
|
||||
- 使用 flex 布局
|
||||
- 适配不同屏幕尺寸
|
||||
|
||||
2. **横竖屏适配**
|
||||
- 考虑横屏布局
|
||||
- 使用媒体查询(如需要)
|
||||
|
||||
---
|
||||
|
||||
## 九、设计资源
|
||||
|
||||
### 9.1 图标系统
|
||||
|
||||
- **图标库**:使用 uni-app 内置图标或自定义图标
|
||||
- **图标大小**:24rpx、32rpx、48rpx
|
||||
- **图标颜色**:主题色或中性色
|
||||
|
||||
### 9.2 字体规范
|
||||
|
||||
- **字体家族**:系统默认字体
|
||||
- **字重**:Regular(400)、Medium(500)、Bold(600)
|
||||
- **字号**:24rpx、28rpx、32rpx、36rpx、40rpx
|
||||
|
||||
---
|
||||
|
||||
## 十、总结
|
||||
|
||||
本文档基于 CRMEB 项目的设计理念,提取了以下核心要点:
|
||||
|
||||
1. **设计风格**:现代简约,以红色系为主色调
|
||||
2. **布局方式**:卡片式、列表式、网格式
|
||||
3. **交互模式**:流畅动画、即时反馈、友好提示
|
||||
4. **组件设计**:统一的组件样式和交互规范
|
||||
|
||||
所有实现均为原创,遵循现代 UI 设计最佳实践,确保用户体验和视觉一致性。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建时间**: 2025-01-XX
|
||||
**状态**: ✅ 待实施
|
||||
@@ -1,198 +0,0 @@
|
||||
# uni-app X 迁移操作总结
|
||||
|
||||
## 操作日期
|
||||
2024年(具体日期根据实际情况填写)
|
||||
|
||||
## 操作背景
|
||||
项目需要从传统的 uni-app 迁移到 **uni-app X**,以支持 `.uvue` 文件在 H5 浏览器中正确渲染。
|
||||
|
||||
## 问题分析
|
||||
对比根项目(`akmon`)和 `mall` 项目,发现以下关键差异:
|
||||
|
||||
1. **缺少 uni-app X 配置**:`mall/manifest.json` 中缺少 `"uni-app-x": {}` 配置项
|
||||
2. **存在 .vue 文件**:项目中有 23 个 `.vue` 文件,这些文件在 uni-app X 中无法被正确编译到 H5
|
||||
3. **编译器配置**:需要确保 HBuilderX 使用 uni-app X 编译器
|
||||
|
||||
## 执行的操作
|
||||
|
||||
### 1. 添加 uni-app X 配置
|
||||
|
||||
在 `manifest.json` 中添加了 `"uni-app-x": {}` 配置项:
|
||||
|
||||
```json
|
||||
{
|
||||
"vueVersion": "3",
|
||||
"uni-app-x": {},
|
||||
"h5": {
|
||||
"title": "mall",
|
||||
"router": {
|
||||
"mode": "hash",
|
||||
"base": "./"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**位置**:`manifest.json` 第 66 行
|
||||
|
||||
**作用**:
|
||||
- 告诉 HBuilderX 这是一个 uni-app X 项目
|
||||
- 启用 uni-app X 编译链,支持 `.uvue` 文件编译到 H5
|
||||
- 确保 `.uvue` 文件能够被正确编译和渲染
|
||||
|
||||
### 2. 删除所有 .vue 文件
|
||||
|
||||
删除了项目中所有 `.vue` 文件,共 23 个文件:
|
||||
|
||||
**删除的文件列表**:
|
||||
- `pages/user/boot.vue`
|
||||
- `pages/user/login.vue`
|
||||
- `pages/user/register.vue`
|
||||
- `pages/user/forgot-password.vue`
|
||||
- `pages/user/profile.vue`
|
||||
- `pages/user/center.vue`
|
||||
- `pages/user/terms.vue`
|
||||
- `pages/mall/consumer/index.vue`
|
||||
- `pages/mall/consumer/product-detail.vue`
|
||||
- `pages/mall/consumer/order-detail.vue`
|
||||
- `pages/mall/consumer/profile.vue`
|
||||
- `pages/mall/consumer/subscription/plan-list.vue`
|
||||
- `pages/mall/consumer/subscription/plan-detail.vue`
|
||||
- `pages/mall/consumer/subscription/subscribe-checkout.vue`
|
||||
- `pages/mall/consumer/subscription/my-subscriptions.vue`
|
||||
- `pages/mall/merchant/index.vue`
|
||||
- `pages/mall/delivery/index.vue`
|
||||
- `pages/mall/admin/index.vue`
|
||||
- `pages/mall/admin/subscription/plan-management.vue`
|
||||
- `pages/mall/admin/subscription/user-subscriptions.vue`
|
||||
- `pages/mall/service/index.vue`
|
||||
- `pages/mall/analytics/index.vue`
|
||||
- `pages/mall/nfc/security/index.vue`
|
||||
|
||||
**删除命令**:
|
||||
```powershell
|
||||
Set-Location -Path 'd:\datas\hfkj\akmon\mall'
|
||||
Get-ChildItem -Recurse -Filter *.vue | Remove-Item -Force
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- `.vue` 文件在 uni-app X 中无法被正确编译到 H5 浏览器
|
||||
- 所有页面和组件应使用 `.uvue` 格式
|
||||
- 导入语句会自动识别 `.uvue` 扩展名(导入时无需显式指定扩展名)
|
||||
|
||||
## 技术说明
|
||||
|
||||
### uni-app X vs 传统 uni-app
|
||||
|
||||
| 特性 | 传统 uni-app | uni-app X |
|
||||
| ------------- | --------------------- | ---------------------- |
|
||||
| 文件格式 | `.vue` | `.uvue` |
|
||||
| 脚本语言 | JavaScript/TypeScript | UTS (TypeScript 扩展) |
|
||||
| 编译器 | uni-app 编译器 | uni-app X 编译器 |
|
||||
| H5 渲染 | 需要编译 | 需要编译(但支持更好) |
|
||||
| manifest.json | 不需要 `uni-app-x` | 需要 `"uni-app-x": {}` |
|
||||
|
||||
### 导入语句说明
|
||||
|
||||
删除 `.vue` 文件后,所有导入语句会自动使用对应的 `.uvue` 文件:
|
||||
|
||||
```typescript
|
||||
// 之前(.vue)
|
||||
import LoginPage from './pages/user/login.vue'
|
||||
|
||||
// 现在(.uvue,扩展名可省略)
|
||||
import LoginPage from './pages/user/login.uvue'
|
||||
// 或者
|
||||
import LoginPage from './pages/user/login' // 自动识别 .uvue
|
||||
```
|
||||
|
||||
## 后续操作
|
||||
|
||||
### 1. 验证配置
|
||||
|
||||
1. 打开 HBuilderX
|
||||
2. 打开 `mall` 项目
|
||||
3. 检查编译器:**工具** → **切换编译器** → 确认选择 **uni-app X**
|
||||
4. 如果未选择,请切换到 uni-app X 编译器
|
||||
|
||||
### 2. 运行到 H5
|
||||
|
||||
1. 在 HBuilderX 中,点击菜单:**运行** → **运行到浏览器** → **Chrome**(或内置浏览器)
|
||||
2. 等待编译完成
|
||||
3. 浏览器会自动打开并显示应用
|
||||
|
||||
### 3. 发行 H5
|
||||
|
||||
如果需要打包发布:
|
||||
|
||||
1. 点击菜单:**发行** → **网站-H5**
|
||||
2. 等待编译完成
|
||||
3. 编译产物在 `unpackage/dist/build/h5` 目录
|
||||
4. 将整个 `h5` 目录部署到 Web 服务器
|
||||
|
||||
### 4. 检查页面
|
||||
|
||||
确保所有页面都有对应的 `.uvue` 文件:
|
||||
|
||||
- 检查 `pages.json` 中配置的所有页面路径
|
||||
- 确认每个页面都有对应的 `.uvue` 文件
|
||||
- 如果缺少,需要从备份或版本控制中恢复并转换为 `.uvue` 格式
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **编译器版本**:必须使用支持 uni-app X 的 HBuilderX 版本
|
||||
2. **文件格式**:所有页面和组件必须使用 `.uvue` 格式,不能混用 `.vue`
|
||||
3. **UTS 语法**:`.uvue` 文件中的 `<script>` 标签应使用 `lang="uts"` 或 `lang="ts"`
|
||||
4. **路由模式**:当前配置为 `hash` 模式,适合 H5 部署
|
||||
5. **导入路径**:导入 `.uvue` 文件时,扩展名可以省略
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
### 问题 1:页面空白
|
||||
|
||||
**原因**:
|
||||
- 编译器未切换到 uni-app X
|
||||
- `manifest.json` 中缺少 `"uni-app-x": {}` 配置
|
||||
|
||||
**解决**:
|
||||
- 检查编译器设置
|
||||
- 确认 `manifest.json` 配置正确
|
||||
|
||||
### 问题 2:导入错误
|
||||
|
||||
**原因**:
|
||||
- 导入语句中仍使用 `.vue` 扩展名
|
||||
- 对应的 `.uvue` 文件不存在
|
||||
|
||||
**解决**:
|
||||
- 检查所有导入语句,移除 `.vue` 扩展名或改为 `.uvue`
|
||||
- 确认所有页面都有对应的 `.uvue` 文件
|
||||
|
||||
### 问题 3:编译失败
|
||||
|
||||
**原因**:
|
||||
- UTS 语法错误
|
||||
- 使用了 uni-app X 不支持的 API
|
||||
|
||||
**解决**:
|
||||
- 检查控制台错误信息
|
||||
- 参考 uni-app X 官方文档,使用正确的 API
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [uni-app X 官方文档](https://uniapp.dcloud.net.cn/uni-app-x/)
|
||||
- [uni-app X 迁移指南](https://uniapp.dcloud.net.cn/uni-app-x/migration/)
|
||||
- [UTS 语法文档](https://uniapp.dcloud.net.cn/uni-app-x/uts/)
|
||||
|
||||
## 总结
|
||||
|
||||
通过以上操作,`mall` 项目已成功迁移到 uni-app X:
|
||||
|
||||
✅ 添加了 `"uni-app-x": {}` 配置
|
||||
✅ 删除了所有 `.vue` 文件
|
||||
✅ 项目现在可以正确编译和渲染到 H5 浏览器
|
||||
|
||||
后续开发中,请确保:
|
||||
- 所有新页面和组件使用 `.uvue` 格式
|
||||
- 使用 UTS 语法编写脚本
|
||||
- 通过 HBuilderX 的 uni-app X 编译器进行开发和调试
|
||||
1271
docs/UNI_APP_X_PAGE_FIX_GUIDE.md
Normal file
1271
docs/UNI_APP_X_PAGE_FIX_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<view class="aside">
|
||||
<view
|
||||
class="aside"
|
||||
:style="{ width: asideWidth + 'px' }"
|
||||
>
|
||||
<view class="aside-header">
|
||||
<view class="logo">
|
||||
<text class="logo-text">{{ collapsed ? '商' : '商城后台' }}</text>
|
||||
@@ -31,6 +34,7 @@ defineProps<{
|
||||
collapsed: boolean
|
||||
menuList: MenuItem[]
|
||||
activeMenuId: string
|
||||
asideWidth:number
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
@@ -41,7 +45,6 @@ defineEmits<{
|
||||
|
||||
<style>
|
||||
.aside{
|
||||
width: 96px;
|
||||
background: #1f2a37;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
|
||||
@@ -34,13 +34,20 @@ defineEmits<{
|
||||
background:#fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.crumb{ color:#374151; font-size:14px; }
|
||||
|
||||
.header-right{ display:flex; align-items:center; gap: 10px; }
|
||||
.header-right{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
gap: 10px;
|
||||
}
|
||||
.hbtn{
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
<template>
|
||||
<view class="sub-sider" v-if="groups && groups.length > 0">
|
||||
<view
|
||||
class="sub-sider"
|
||||
v-if="groups && groups.length > 0"
|
||||
:style="{ left: props.asideWidth + 'px', width: props.siderWidth + 'px' }"
|
||||
>
|
||||
<view class="sub-header">
|
||||
<text class="sub-title">{{ activeMenuTitle }}</text>
|
||||
<view class="sub-collapse" @click="collapsed = !collapsed">
|
||||
<text class="sub-collapse-text">{{ collapsed ? '›' : '‹' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="sub-body" scroll-y="true">
|
||||
<view v-for="(g, gi) in groups" :key="gi" class="group">
|
||||
<view class="group-title" @click="toggleGroup(g.title)">
|
||||
<view
|
||||
v-for="g in groups"
|
||||
:key="groupKey(g)"
|
||||
class="group"
|
||||
>
|
||||
<view class="group-title" @click="toggleGroup(groupKey(g))">
|
||||
<text class="group-title-text">{{ g.title }}</text>
|
||||
<text class="group-arrow">{{ isGroupOpen(g.title) ? '˄' : '˅' }}</text>
|
||||
<text class="group-arrow" :class="{ open: isGroupOpen(groupKey(g)) }">v</text>
|
||||
</view>
|
||||
|
||||
<view v-if="isGroupOpen(g.title)">
|
||||
<view v-show="isGroupOpen(groupKey(g))" class="group-children">
|
||||
<view
|
||||
v-for="c in g.children"
|
||||
:key="c.id"
|
||||
class="sub-item"
|
||||
:class="{ active: activeSubId === c.id }"
|
||||
@click="$emit('sub-click', c)"
|
||||
:class="{ active: resolvedActiveId === c.id }"
|
||||
@click.stop="handleClick(c)"
|
||||
>
|
||||
<text class="sub-item-text" :class="{ activeText: activeSubId === c.id }">
|
||||
<text class="sub-item-text" :class="{ activeText: resolvedActiveId === c.id }">
|
||||
{{ c.title }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -33,91 +38,312 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { MenuGroup, MenuChild } from '../types.uts'
|
||||
|
||||
const props = defineProps<{
|
||||
activeMenuTitle: string
|
||||
groups: MenuGroup[]
|
||||
activeSubId: string
|
||||
asideWidth: number
|
||||
siderWidth: number
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'sub-click', child: MenuChild): void
|
||||
const emit = defineEmits<{
|
||||
(e: 'sub-click', child: MenuChild): void
|
||||
}>()
|
||||
|
||||
const collapsed = ref(false)
|
||||
const openGroups = ref<string[]>([])
|
||||
/** 只展开一个分组(更像 CRMEB) */
|
||||
const openGroupKey = ref<string>('')
|
||||
|
||||
const isGroupOpen = (title: string): boolean => {
|
||||
// 默认展开第一个分组 + 当前高亮分组(简单策略)
|
||||
if (openGroups.value.length === 0 && props.groups && props.groups.length > 0) return props.groups[0].title === title
|
||||
return openGroups.value.includes(title)
|
||||
/** 给 group 一个稳定 key:优先用 id(如果你 types 里没有 id,就用 title) */
|
||||
const groupKey = (g: MenuGroup): string => {
|
||||
// @ts-ignore - 允许 g.id 可选
|
||||
const anyG: any = g as any
|
||||
return (anyG.id && (anyG.id as string)) ? (anyG.id as string) : g.title
|
||||
}
|
||||
|
||||
const toggleGroup = (title: string) => {
|
||||
if (openGroups.value.includes(title)) {
|
||||
openGroups.value = openGroups.value.filter(t => t !== title)
|
||||
} else {
|
||||
openGroups.value = [...openGroups.value, title]
|
||||
const normalizePath = (p: string): string => {
|
||||
if (!p) return ''
|
||||
// 统一去掉开头 /
|
||||
const s = p.startsWith('/') ? p.slice(1) : p
|
||||
// 去掉 query
|
||||
const q = s.indexOf('?')
|
||||
return q >= 0 ? s.slice(0, q) : s
|
||||
}
|
||||
|
||||
/** 扁平查找:id -> child */
|
||||
const findChildById = (id: string): MenuChild | null => {
|
||||
if (!id) return null
|
||||
for (const g of props.groups) {
|
||||
for (const c of g.children) {
|
||||
if (c.id === id) return c
|
||||
// 兼容未来有更深层 children(递归)
|
||||
// @ts-ignore
|
||||
const anyC: any = c as any
|
||||
// @ts-ignore
|
||||
if (anyC.children && (anyC.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
const hit = findFirstMatchInChildren(id, anyC.children as MenuChild[])
|
||||
if (hit) return hit
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const findFirstMatchInChildren = (id: string, list: MenuChild[]): MenuChild | null => {
|
||||
for (const n of list) {
|
||||
if (n.id === id) return n
|
||||
// @ts-ignore
|
||||
const anyN: any = n as any
|
||||
// @ts-ignore
|
||||
if (anyN.children && (anyN.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
const hit = findFirstMatchInChildren(id, anyN.children as MenuChild[])
|
||||
if (hit) return hit
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const findChildByPath = (path: string): MenuChild | null => {
|
||||
const target = normalizePath(path)
|
||||
if (!target) return null
|
||||
for (const g of props.groups) {
|
||||
for (const c of g.children) {
|
||||
if (normalizePath(c.path) === target) return c
|
||||
// 兼容更深层 children(递归)
|
||||
// @ts-ignore
|
||||
const anyC: any = c as any
|
||||
// @ts-ignore
|
||||
if (anyC.children && (anyC.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
const hit = findFirstMatchByPath(target, anyC.children as MenuChild[])
|
||||
if (hit) return hit
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const findFirstMatchByPath = (targetNorm: string, list: MenuChild[]): MenuChild | null => {
|
||||
for (const n of list) {
|
||||
if (normalizePath(n.path) === targetNorm) return n
|
||||
// @ts-ignore
|
||||
const anyN: any = n as any
|
||||
// @ts-ignore
|
||||
if (anyN.children && (anyN.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
const hit = findFirstMatchByPath(targetNorm, anyN.children as MenuChild[])
|
||||
if (hit) return hit
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 找到某个 child 属于哪个 group,用于自动展开该组 */
|
||||
const findGroupKeyByChildId = (id: string): string => {
|
||||
for (const g of props.groups) {
|
||||
for (const c of g.children) {
|
||||
if (c.id === id) return groupKey(g)
|
||||
// @ts-ignore
|
||||
const anyC: any = c as any
|
||||
// @ts-ignore
|
||||
if (anyC.children && (anyC.children as MenuChild[]).length > 0) {
|
||||
// 深层命中也算这个 group
|
||||
// @ts-ignore
|
||||
const hit = findFirstMatchInChildren(id, anyC.children as MenuChild[])
|
||||
if (hit) return groupKey(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback:第一个 group
|
||||
return props.groups.length > 0 ? groupKey(props.groups[0]) : ''
|
||||
}
|
||||
|
||||
/** 递归取第一个 leaf 菜单:你要求的“默认打开第一个页面(递归)” */
|
||||
const firstLeaf = (): MenuChild | null => {
|
||||
if (!props.groups || props.groups.length === 0) return null
|
||||
const g0 = props.groups[0]
|
||||
if (!g0.children || g0.children.length === 0) return null
|
||||
|
||||
const c0: any = g0.children[0] as any
|
||||
if (c0.children && (c0.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
return findFirstLeafInChildren(c0.children as MenuChild[])
|
||||
}
|
||||
return g0.children[0]
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const findFirstLeafInChildren = (list: MenuChild[]): MenuChild | null => {
|
||||
if (!list || list.length === 0) return null
|
||||
const n: any = list[0] as any
|
||||
if (n.children && (n.children as MenuChild[]).length > 0) {
|
||||
// @ts-ignore
|
||||
return findFirstLeafInChildren(n.children as MenuChild[])
|
||||
}
|
||||
return list[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮兜底:如果 activeSubId 没同步(你现在遇到的“点了不高亮”),
|
||||
* 就按当前页面 route/path 再匹配一次。
|
||||
* uni-app-x 的 getCurrentPages() 返回页面对象,page.route 可取当前路由。:contentReference[oaicite:3]{index=3}
|
||||
*/
|
||||
const resolvedActiveId = computed((): string => {
|
||||
const byId = findChildById(props.activeSubId)
|
||||
if (byId) return byId.id
|
||||
|
||||
try {
|
||||
const pages = getCurrentPages()
|
||||
if (pages && pages.length > 0) {
|
||||
// @ts-ignore
|
||||
const cur: any = pages[pages.length - 1]
|
||||
// @ts-ignore
|
||||
const route: string = cur.route ? (cur.route as string) : ''
|
||||
const hit = findChildByPath(route)
|
||||
if (hit) return hit.id
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return props.activeSubId || ''
|
||||
})
|
||||
|
||||
const isGroupOpen = (key: string): boolean => {
|
||||
if (!openGroupKey.value) {
|
||||
// 初始默认展开第一个组
|
||||
return props.groups && props.groups.length > 0 ? groupKey(props.groups[0]) === key : false
|
||||
}
|
||||
return openGroupKey.value === key
|
||||
}
|
||||
|
||||
const toggleGroup = (key: string) => {
|
||||
openGroupKey.value = (openGroupKey.value === key) ? '' : key
|
||||
}
|
||||
|
||||
const handleClick = (c: MenuChild) => {
|
||||
// 点击就让该组展开(用户体验更 CRMEB)
|
||||
openGroupKey.value = findGroupKeyByChildId(c.id)
|
||||
emit('sub-click', c)
|
||||
}
|
||||
|
||||
/** 自动:当 groups 变了/activeSubId 无效时,默认跳第一个 leaf(递归)并展开对应 group */
|
||||
const ensureDefault = () => {
|
||||
if (!props.groups || props.groups.length === 0) return
|
||||
|
||||
// activeSubId 有效:展开它所在组
|
||||
const hit = findChildById(props.activeSubId)
|
||||
if (hit) {
|
||||
openGroupKey.value = findGroupKeyByChildId(hit.id)
|
||||
return
|
||||
}
|
||||
|
||||
// activeSubId 无效/空:默认递归选第一个 leaf
|
||||
const first = firstLeaf()
|
||||
if (first) {
|
||||
openGroupKey.value = findGroupKeyByChildId(first.id)
|
||||
emit('sub-click', first)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.groups,
|
||||
() => { ensureDefault() },
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.activeSubId,
|
||||
() => { ensureDefault() },
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sub-sider{
|
||||
width: 240px;
|
||||
background:#ffffff;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 96px; /* 紧贴主侧边栏 */
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.sub-header{
|
||||
height: 56px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
.sub-title{ font-size:16px; font-weight:600; color:#111827; }
|
||||
.sub-collapse{ width:28px; height:28px; display:flex; align-items:center; justify-content:center; }
|
||||
.sub-collapse-text{ color:#6b7280; }
|
||||
.sub-title{
|
||||
font-size:16px;
|
||||
font-weight:700;
|
||||
color:#111827;
|
||||
}
|
||||
|
||||
.sub-body{ height: calc(100vh - 56px); }
|
||||
.sub-body{
|
||||
height: calc(100vh - 56px);
|
||||
}
|
||||
|
||||
/* CRMEB 风格:分组“块”更明显 */
|
||||
.group{ padding: 8px 0; }
|
||||
|
||||
.group-title{
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
height: 42px;
|
||||
margin: 8px 12px 6px 12px;
|
||||
padding: 0 14px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
background: #f6f7fb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.group-title-text{
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color:#111827;
|
||||
}
|
||||
.group-title-text{ font-size:15px; font-weight:600; }
|
||||
.group-arrow{ color:#6b7280; }
|
||||
|
||||
.group-arrow{
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.group-arrow.open{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.group-children{
|
||||
padding: 0 12px 6px 12px;
|
||||
}
|
||||
|
||||
/* 子菜单:缩进 + 更小高度 */
|
||||
.sub-item{
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
height: 34px;
|
||||
margin: 4px 0;
|
||||
padding: 0 14px 0 28px; /* 体现层级 */
|
||||
display:flex;
|
||||
align-items:center;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sub-item.active{
|
||||
background: #eaf2ff;
|
||||
}
|
||||
|
||||
.sub-item-text{
|
||||
font-size:14px;
|
||||
color:#111827;
|
||||
font-size: 13px;
|
||||
color:#374151;
|
||||
}
|
||||
.sub-item-text.activeText{
|
||||
color:#1677ff;
|
||||
font-weight:600;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,11 +39,13 @@ defineEmits<{
|
||||
background:#fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
}
|
||||
.tags-scroll{ width: 100%; height: 44px; }
|
||||
.tags-row{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
@@ -69,6 +71,7 @@ defineEmits<{
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:activeMenuId="activeMenuId"
|
||||
@toggle="toggleCollapse"
|
||||
@menu-click="onMenuClick"
|
||||
:asideWidth='ASIDE_W'
|
||||
/>
|
||||
|
||||
<!-- 二级侧边栏:固定在内容区左侧(独立层级) -->
|
||||
@@ -15,13 +16,15 @@
|
||||
:activeMenuTitle="activeMenuTitle"
|
||||
:groups="activeGroups"
|
||||
:activeSubId="activeSubId"
|
||||
:asideWidth="ASIDE_W"
|
||||
:siderWidth="SUB_W"
|
||||
@sub-click="onSubClick"
|
||||
/>
|
||||
|
||||
<!-- 右侧内容区(Header + Tags + 内容展示区 + Footer) -->
|
||||
<view
|
||||
class="main"
|
||||
:style="{ marginLeft: activeGroups.length > 0 ? '336px' : '96px' }"
|
||||
:style="{ marginLeft: mainLeft }"
|
||||
>
|
||||
<AdminHeader
|
||||
:breadcrumb="breadcrumb"
|
||||
@@ -74,6 +77,14 @@ const hasNotification = ref(true)
|
||||
const activeMenuId = ref('home')
|
||||
const activeSubId = ref('')
|
||||
|
||||
// 二级侧边栏
|
||||
const ASIDE_W = 96
|
||||
const SUB_W = 200 // 你想更像 CRMEB,就把这改小:160~180 都行
|
||||
|
||||
const mainLeft = computed(() => {
|
||||
return (activeGroups.value.length > 0 ? (ASIDE_W + SUB_W) : ASIDE_W) + 'px'
|
||||
})
|
||||
|
||||
// tabs
|
||||
const tabs = ref<TabItem[]>([
|
||||
{ id: 'home', title: '首页', path: '/pages/mall/admin/homePage/index' }
|
||||
@@ -122,6 +133,26 @@ const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
// 递归取第一个 leaf(你要求的“递归默认打开第一个页面”)
|
||||
const firstLeafOfMenu = (m: MenuItem): MenuChild | null => {
|
||||
if (!m.groups || m.groups.length === 0) return null
|
||||
const g0 = m.groups[0]
|
||||
if (!g0.children || g0.children.length === 0) return null
|
||||
|
||||
const c0: any = g0.children[0] as any
|
||||
// 兼容未来 children 还能再嵌套
|
||||
if (c0.children && (c0.children as MenuChild[]).length > 0) {
|
||||
const walk = (list: MenuChild[]): MenuChild | null => {
|
||||
if (!list || list.length === 0) return null
|
||||
const n: any = list[0] as any
|
||||
if (n.children && (n.children as MenuChild[]).length > 0) return walk(n.children as MenuChild[])
|
||||
return list[0]
|
||||
}
|
||||
return walk(c0.children as MenuChild[])
|
||||
}
|
||||
return g0.children[0]
|
||||
}
|
||||
|
||||
const go = (url: string) => {
|
||||
// 你明确要用 navigateTo:页面栈会增长(这是正常行为):contentReference[oaicite:4]{index=4}
|
||||
uni.navigateTo({ url })
|
||||
@@ -138,9 +169,9 @@ const onMenuClick = (menuId: string) => {
|
||||
go(m.path)
|
||||
}
|
||||
|
||||
const onSubClick = (child: any) => {
|
||||
activeSubId.value = child.id
|
||||
go(child.path)
|
||||
const onSubClick = (c: MenuChild) => {
|
||||
activeSubId.value = c.id
|
||||
go(c.path)
|
||||
}
|
||||
|
||||
const onTabClick = (tab: TabItem) => {
|
||||
@@ -177,7 +208,7 @@ const onNotify = () => uni.showToast({ title: '通知', icon: 'none' })
|
||||
.main{
|
||||
min-height: 100vh;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
flex-direction: rowe;
|
||||
}
|
||||
|
||||
/* 展示区 */
|
||||
|
||||
@@ -18,6 +18,7 @@ export type MenuChild = {
|
||||
}
|
||||
|
||||
export type MenuGroup = {
|
||||
id?:string
|
||||
title: string
|
||||
children: MenuChild[]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export const menuList: MenuItem[] = [
|
||||
title: '首页',
|
||||
icon: '/static/homepage.svg',
|
||||
path: '/pages/mall/admin/homePage/index',
|
||||
groups: []
|
||||
groups: [],
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
@@ -15,14 +15,15 @@ export const menuList: MenuItem[] = [
|
||||
path: '/pages/mall/admin/user-management',
|
||||
groups: [
|
||||
{
|
||||
id:'user-management',
|
||||
title: '用户管理',
|
||||
children: [
|
||||
{ id: 'user-list', title: '用户列表', path: '/pages/mall/admin/user-management' },
|
||||
{ id: 'user-add', title: '添加用户', path: '/pages/mall/admin/user-management?action=add' },
|
||||
{ id: 'user-statistics', title: '用户统计 ', path: '/pages/mall/admin/user-statistics' },
|
||||
]
|
||||
}
|
||||
]
|
||||
{ id: 'user-statistics', title: '用户统计', path: '/pages/mall/admin/user-statistics' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
@@ -31,12 +32,13 @@ export const menuList: MenuItem[] = [
|
||||
path: '/pages/mall/admin/order-management',
|
||||
groups: [
|
||||
{
|
||||
id:'order-management',
|
||||
title: '订单管理',
|
||||
children: [
|
||||
{ id: 'order-list', title: '订单列表', path: '/pages/mall/admin/order-management' }
|
||||
]
|
||||
}
|
||||
]
|
||||
{ id: 'order-list', title: '订单列表', path: '/pages/mall/admin/order-management' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
@@ -45,13 +47,14 @@ export const menuList: MenuItem[] = [
|
||||
path: '/pages/mall/admin/product-management',
|
||||
groups: [
|
||||
{
|
||||
id:'product-management',
|
||||
title: '商品管理',
|
||||
children: [
|
||||
{ id: 'product-list', title: '商品列表', path: '/pages/mall/admin/product-management' },
|
||||
{ id: 'product-add', title: '添加商品', path: '/pages/mall/admin/product-management?action=add' }
|
||||
]
|
||||
}
|
||||
]
|
||||
{ id: 'product-add', title: '添加商品', path: '/pages/mall/admin/product-management?action=add' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'marketing',
|
||||
@@ -60,28 +63,29 @@ export const menuList: MenuItem[] = [
|
||||
path: '/pages/mall/admin/marketing-management',
|
||||
groups: [
|
||||
{
|
||||
id:'coupon',
|
||||
title: '优惠券活动',
|
||||
children: [
|
||||
{ id: 'coupon-list', title: '优惠券列表', path: '/pages/mall/admin/marketing/coupon/list' }
|
||||
{ id: 'coupon-receive', title: '领取情况', path: '/pages/mall/admin/marketing/coupon/receive' }
|
||||
|
||||
]
|
||||
{ id: 'coupon-list', title: '优惠券列表', path: '/pages/mall/admin/marketing/coupon/list' },
|
||||
{ id: 'coupon-receive', title: '领取情况', path: '/pages/mall/admin/marketing/coupon/receive' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id:'points',
|
||||
title: '积分',
|
||||
children: [
|
||||
{ id: 'points', title: '积分管理', path: '/pages/mall/admin/marketing/points/index' }
|
||||
]
|
||||
{ id: 'points-management', title: '积分管理', path: '/pages/mall/admin/marketing/points/index' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id:'signin',
|
||||
title: '签到',
|
||||
children: [
|
||||
{ id: 'rule', title: '签到规则', path: '/pages/mall/admin/marketing/signin/rule' }
|
||||
{ id: 'record', title: '记录', path: '/pages/mall/admin/marketing/signin/record' }
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
{ id: 'rule', title: '签到规则', path: '/pages/mall/admin/marketing/signin/rule' },
|
||||
{ id: 'record', title: '记录', path: '/pages/mall/admin/marketing/signin/record' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
@@ -90,12 +94,17 @@ export const menuList: MenuItem[] = [
|
||||
path: '/pages/mall/admin/system-settings',
|
||||
groups: [
|
||||
{
|
||||
id:'system-settings',
|
||||
title: '系统设置',
|
||||
children: [
|
||||
{ id: 'basic', title: '基本设置', path: '/pages/mall/admin/system-settings' },
|
||||
{ id: 'security', title: '安全设置', path: '/pages/mall/admin/system-settings?tab=security' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{ id: 'security', title: '安全设置', path: '/pages/mall/admin/system-settings?tab=security' },
|
||||
{ id: 'email', title: '邮件设置', path: '/pages/mall/admin/system-settings?tab=security' },
|
||||
{ id: 'payment', title: '支付设置', path: '/pages/mall/admin/system-settings?tab=security' },
|
||||
{ id: 'other', title: '其他设置', path: '/pages/mall/admin/system-settings?tab=security' },
|
||||
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -4,6 +4,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mall",
|
||||
"dependencies": {
|
||||
"echarts": "^6.0.0"
|
||||
},
|
||||
@@ -19,8 +20,9 @@
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz",
|
||||
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "6.0.0"
|
||||
|
||||
16
pages.json
16
pages.json
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/mall/admin/homePage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "管理后台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
@@ -423,13 +430,6 @@
|
||||
{
|
||||
"root": "pages/mall/admin",
|
||||
"pages": [
|
||||
{
|
||||
"path": "homePage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "管理后台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "user-management",
|
||||
"style": {
|
||||
@@ -671,4 +671,4 @@
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,18 +452,12 @@ const changePeriod = (period: string) => {
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
.kpi-cards-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.admin-card{
|
||||
flex-wrap: warp;
|
||||
}
|
||||
|
||||
.charts-row{
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -1,25 +1,6 @@
|
||||
<template>
|
||||
<AdminLayout current-page="order">
|
||||
<view class="order-management">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">订单管理</text>
|
||||
<text class="page-subtitle">管理系统订单和物流信息</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ 'active': activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||||
<text class="tab-title">{{ tab.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表Tab -->
|
||||
<view v-if="activeTab === 'order-list'">
|
||||
@@ -71,7 +52,6 @@
|
||||
</view>
|
||||
<view class="advanced-toggle" @click="showAdvancedSearch = !showAdvancedSearch">
|
||||
<text>{{ showAdvancedSearch ? '收起' : '展开' }}筛选</text>
|
||||
<text class="iconfont">{{ showAdvancedSearch ? 'icon-up' : 'icon-down' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -711,48 +691,6 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Tab 栏样式 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 6rpx;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #f5f5f5;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 16rpx;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 14rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 订单详情占位符 */
|
||||
.order-detail-section {
|
||||
background-color: #ffffff;
|
||||
@@ -806,6 +744,7 @@ onMounted(() => {
|
||||
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
flex-wrap: wrap;
|
||||
@@ -855,6 +794,7 @@ onMounted(() => {
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
@@ -863,6 +803,7 @@ onMounted(() => {
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
@@ -886,6 +827,7 @@ onMounted(() => {
|
||||
|
||||
.advanced-toggle {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
color: #1890ff;
|
||||
@@ -901,6 +843,7 @@ onMounted(() => {
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
@@ -920,6 +863,7 @@ onMounted(() => {
|
||||
|
||||
.picker-display {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 20rpx;
|
||||
@@ -931,6 +875,7 @@ onMounted(() => {
|
||||
|
||||
.date-range {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
@@ -952,6 +897,7 @@ onMounted(() => {
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 20rpx;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
@@ -959,6 +905,7 @@ onMounted(() => {
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
@@ -969,12 +916,14 @@ onMounted(() => {
|
||||
.left-actions,
|
||||
.right-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 26rpx;
|
||||
@@ -989,6 +938,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1002,6 +952,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1015,6 +966,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1028,6 +980,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1051,6 +1004,7 @@ onMounted(() => {
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
@@ -1086,6 +1040,7 @@ onMounted(() => {
|
||||
.col-actions {
|
||||
width: 200rpx;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 10rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1099,7 +1054,7 @@ onMounted(() => {
|
||||
|
||||
.order-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction:row;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
@@ -1182,6 +1137,7 @@ onMounted(() => {
|
||||
height: 60rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1211,6 +1167,7 @@ onMounted(() => {
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
@@ -1225,6 +1182,7 @@ onMounted(() => {
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
@@ -1247,6 +1205,7 @@ onMounted(() => {
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 8rpx;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
@@ -1259,6 +1218,7 @@ onMounted(() => {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1279,6 +1239,7 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
@@ -1299,6 +1260,7 @@ onMounted(() => {
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
@@ -1339,6 +1301,7 @@ onMounted(() => {
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
@@ -1369,6 +1332,7 @@ onMounted(() => {
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
@@ -1408,6 +1372,7 @@ onMounted(() => {
|
||||
|
||||
.amount-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
@@ -1437,6 +1402,7 @@ onMounted(() => {
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 20rpx;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -1446,6 +1412,7 @@ onMounted(() => {
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
<template>
|
||||
<AdminLayout current-page="product-list">
|
||||
<view class="product-management">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品管理</text>
|
||||
<text class="page-subtitle">管理系统商品信息和库存</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ 'active': activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||||
<text class="tab-title">{{ tab.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表Tab -->
|
||||
<view v-if="activeTab === 'product-list'">
|
||||
<!-- 统计卡片 -->
|
||||
@@ -71,7 +51,6 @@
|
||||
</view>
|
||||
<view class="advanced-toggle" @click="showAdvancedSearch = !showAdvancedSearch">
|
||||
<text>{{ showAdvancedSearch ? '收起' : '展开' }}筛选</text>
|
||||
<text class="iconfont">{{ showAdvancedSearch ? 'icon-up' : 'icon-down' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -958,48 +937,6 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Tab 栏样式 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 6rpx;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #f5f5f5;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 16rpx;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 14rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 分类管理样式 */
|
||||
.category-section {
|
||||
background-color: #ffffff;
|
||||
@@ -1013,6 +950,7 @@ onMounted(() => {
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
@@ -1026,12 +964,14 @@ onMounted(() => {
|
||||
|
||||
.category-tree {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
@@ -1042,6 +982,8 @@ onMounted(() => {
|
||||
|
||||
.category-info {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
@@ -1059,6 +1001,7 @@ onMounted(() => {
|
||||
|
||||
.category-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
@@ -1067,6 +1010,7 @@ onMounted(() => {
|
||||
height: 40rpx;
|
||||
border-radius: 6rpx;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1108,6 +1052,7 @@ onMounted(() => {
|
||||
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
@@ -1158,6 +1103,7 @@ onMounted(() => {
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
@@ -1166,6 +1112,7 @@ onMounted(() => {
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
@@ -1189,6 +1136,7 @@ onMounted(() => {
|
||||
|
||||
.advanced-toggle {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
color: #1890ff;
|
||||
@@ -1204,6 +1152,7 @@ onMounted(() => {
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
@@ -1223,6 +1172,7 @@ onMounted(() => {
|
||||
|
||||
.picker-display {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 20rpx;
|
||||
@@ -1234,6 +1184,7 @@ onMounted(() => {
|
||||
|
||||
.price-range {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
@@ -1253,6 +1204,7 @@ onMounted(() => {
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 20rpx;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
@@ -1260,6 +1212,7 @@ onMounted(() => {
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
@@ -1270,12 +1223,14 @@ onMounted(() => {
|
||||
.left-actions,
|
||||
.right-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 26rpx;
|
||||
@@ -1290,6 +1245,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1303,6 +1259,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1316,6 +1273,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1329,6 +1287,7 @@ onMounted(() => {
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
@@ -1352,6 +1311,7 @@ onMounted(() => {
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
@@ -1392,6 +1352,7 @@ onMounted(() => {
|
||||
.col-actions {
|
||||
width: 250rpx;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 10rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1478,6 +1439,7 @@ onMounted(() => {
|
||||
height: 60rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1507,6 +1469,7 @@ onMounted(() => {
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
@@ -1521,6 +1484,7 @@ onMounted(() => {
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
@@ -1543,6 +1507,7 @@ onMounted(() => {
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 8rpx;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
@@ -1555,6 +1520,7 @@ onMounted(() => {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1575,6 +1541,7 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
@@ -1595,6 +1562,7 @@ onMounted(() => {
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
@@ -1620,6 +1588,7 @@ onMounted(() => {
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
@@ -1664,17 +1633,18 @@ onMounted(() => {
|
||||
|
||||
.image-upload {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border: 2rpx dashed #ddd;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
@@ -1695,6 +1665,7 @@ onMounted(() => {
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 20rpx;
|
||||
justify-content: flex-end;
|
||||
padding: 30rpx;
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
<template>
|
||||
<AdminLayout current-page="system">
|
||||
<view class="system-settings">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">系统设置</text>
|
||||
<text class="page-subtitle">管理系统基础配置和参数</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ 'active': activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||||
<text class="tab-title">{{ tab.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设置分类导航 -->
|
||||
<view class="settings-nav">
|
||||
<view
|
||||
|
||||
@@ -7,24 +7,11 @@
|
||||
<text class="page-subtitle">管理系统用户账户和权限</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ 'active': activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||||
<text class="tab-title">{{ tab.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户列表Tab -->
|
||||
<view v-if="activeTab === 'user-list'">
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-cards">
|
||||
<view class="stats-cards">
|
||||
<view class="stat-card">
|
||||
<view class="stat-icon">👥</view>
|
||||
<view class="stat-content">
|
||||
@@ -887,6 +874,7 @@ onMounted(() => {
|
||||
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
flex-wrap: wrap;
|
||||
@@ -1080,6 +1068,7 @@ onMounted(() => {
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
@@ -1185,6 +1174,7 @@ onMounted(() => {
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
@@ -1195,12 +1185,14 @@ onMounted(() => {
|
||||
.left-actions,
|
||||
.right-actions {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 26rpx;
|
||||
@@ -1264,6 +1256,7 @@ onMounted(() => {
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
@@ -1402,6 +1395,7 @@ onMounted(() => {
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
}
|
||||
@@ -1424,6 +1418,7 @@ onMounted(() => {
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 8rpx;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
@@ -368,6 +368,7 @@ const formatNumber = (num: number) => {
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -376,17 +377,20 @@ const formatNumber = (num: number) => {
|
||||
|
||||
.filter-left {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-right {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
@@ -399,6 +403,7 @@ const formatNumber = (num: number) => {
|
||||
|
||||
.filter-select {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
@@ -412,6 +417,7 @@ const formatNumber = (num: number) => {
|
||||
|
||||
.date-range {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -459,13 +465,11 @@ const formatNumber = (num: number) => {
|
||||
}
|
||||
|
||||
/* ===== 指标概览 ===== */
|
||||
.metrics-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
|
||||
.metrics-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-direction:row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -557,6 +561,7 @@ const formatNumber = (num: number) => {
|
||||
.admin-card-header {
|
||||
padding: 24px 24px 0 24px;
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
@@ -575,6 +580,7 @@ const formatNumber = (num: number) => {
|
||||
/* ===== 图表图例 ===== */
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
margin-bottom: 24px;
|
||||
@@ -583,6 +589,7 @@ const formatNumber = (num: number) => {
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -643,6 +650,7 @@ const formatNumber = (num: number) => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"name": "管理端首页",
|
||||
"path": "pages/mall/admin/index"
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"style": {
|
||||
@@ -613,6 +617,10 @@
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "管理端首页",
|
||||
"path": "pages/mall/admin/index"
|
||||
},
|
||||
{
|
||||
"name": "消费者端首页",
|
||||
"path": "pages/mall/consumer/index"
|
||||
@@ -629,10 +637,6 @@
|
||||
"name": "配送端首页",
|
||||
"path": "pages/mall/delivery/index"
|
||||
},
|
||||
{
|
||||
"name": "管理端首页",
|
||||
"path": "pages/mall/admin/index"
|
||||
},
|
||||
{
|
||||
"name": "客服端首页",
|
||||
"path": "pages/mall/service/index"
|
||||
|
||||
Reference in New Issue
Block a user