完善
This commit is contained in:
@@ -1,594 +0,0 @@
|
||||
# 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)
|
||||
- ✅ 完成基础管理功能实现
|
||||
- ✅ 用户管理模块
|
||||
- ✅ 商品管理模块
|
||||
- ✅ 订单管理模块
|
||||
- ✅ 财务管理模块
|
||||
- ✅ 系统设置模块
|
||||
|
||||
### 计划功能
|
||||
- 🔄 营销管理模块
|
||||
- 🔄 数据统计图表
|
||||
- 🔄 批量操作优化
|
||||
- 🔄 移动端适配完善
|
||||
- 🔄 性能优化
|
||||
|
||||
---
|
||||
|
||||
本文档持续更新中,如有问题请及时反馈。
|
||||
@@ -1,475 +0,0 @@
|
||||
# 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,807 +0,0 @@
|
||||
# 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提供完善的安全机制
|
||||
|
||||
实际迁移时需要根据具体业务需求进行调整,并充分测试各项功能。
|
||||
@@ -1,427 +0,0 @@
|
||||
# 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. **样式模块化**: 将样式按组件和页面分离组织
|
||||
|
||||
---
|
||||
|
||||
**分析说明**: 本文档基于代码静态分析生成,重点分析了页面结构、组件依赖和文件职责关系。由于项目规模较大,部分细节需要进一步深入分析。
|
||||
@@ -1,292 +0,0 @@
|
||||
# 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. **响应式适配**: 支持多终端访问
|
||||
|
||||
这种架构设计使得系统具有良好的可扩展性和维护性,同时提供了丰富的定制化选项。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,15 +24,12 @@ import UserLevel from '@/pages/mall/admin/user/level.uvue'
|
||||
import UserGroup from '@/pages/mall/admin/user/group.uvue'
|
||||
import UserLabel from '@/pages/mall/admin/user/label.uvue'
|
||||
import MemberConfig from '@/pages/mall/admin/user/MemberConfig.uvue'
|
||||
// 其他用户模块组件暂时使用 PlaceholderPage
|
||||
// import UserGradeType from '@/pages/mall/admin/user/grade/type.uvue'
|
||||
// import UserGradeCard from '@/pages/mall/admin/user/grade/card.uvue'
|
||||
// import UserGradeRecord from '@/pages/mall/admin/user/grade/record.uvue'
|
||||
// import UserGradeRight from '@/pages/mall/admin/user/grade/right.uvue'
|
||||
// import UserMemberConfig from '@/pages/mall/admin/user/MemberConfig.uvue'
|
||||
|
||||
// 导入商品模块(纯组件,不包含 AdminLayout)
|
||||
import ProductList from '@/pages/mall/admin/product/list.uvue'
|
||||
import ProductStatistic from '@/pages/mall/admin/product/product-statistics/index.uvue'
|
||||
import ProductList from '@/pages/mall/admin/product/product-management/index.uvue'
|
||||
import ProductEdit from '@/pages/mall/admin/product/product-management/edit.uvue'
|
||||
import ProductMemberPrice from '@/pages/mall/admin/product/product-management/member-price.uvue'
|
||||
import ProductClassify from '@/pages/mall/admin/product/classify.uvue'
|
||||
import ProductReply from '@/pages/mall/admin/product/reply.uvue'
|
||||
import ProductAttr from '@/pages/mall/admin/product/attr.uvue'
|
||||
@@ -48,16 +45,39 @@ import OrderCashier from '@/pages/mall/admin/order/cashier-order/index.uvue'
|
||||
import OrderVerify from '@/pages/mall/admin/order/write-off-records/index.uvue'
|
||||
import OrderConfig from '@/pages/mall/admin/order/order-configuration/index.uvue'
|
||||
|
||||
// 营销、内容、财务、数据、设置模块暂时使用 PlaceholderPage
|
||||
// 营销设置模块暂时使用 PlaceholderPage
|
||||
// 避免循环依赖问题
|
||||
// import MarketingCoupon from '@/pages/mall/admin/marketing/coupon/list.uvue'
|
||||
// import MarketingIntegral from '@/pages/mall/admin/marketing/integral/list.uvue'
|
||||
// import MarketingBargain from '@/pages/mall/admin/marketing/bargain/list.uvue'
|
||||
// import MarketingCombination from '@/pages/mall/admin/marketing/combination/list.uvue'
|
||||
// import MarketingSeckill from '@/pages/mall/admin/marketing/seckill/list.uvue'
|
||||
// import CmsArticle from '@/pages/mall/admin/cms/article/list.uvue'
|
||||
// import CmsCategory from '@/pages/mall/admin/cms/category/list.uvue'
|
||||
// import FinanceRecord from '@/pages/mall/admin/finance/record.uvue'
|
||||
import CmsArticle from '@/pages/mall/admin/cms/article/list.uvue'
|
||||
import CmsCategory from '@/pages/mall/admin/cms/category/list.uvue'
|
||||
import MarketingCouponList from '@/pages/mall/admin/marketing/coupon/list.uvue'
|
||||
import MarketingCouponUser from '@/pages/mall/admin/marketing/coupon/user.uvue'
|
||||
import MarketingIntegralStatistic from '@/pages/mall/admin/marketing/integral/statistic.uvue'
|
||||
|
||||
// 导入财务模块(纯组件)
|
||||
import FinanceTransactionStats from '@/pages/mall/admin/finance/transaction_stats.uvue'
|
||||
import FinanceWithdrawal from '@/pages/mall/admin/finance/withdrawal.uvue'
|
||||
import FinanceInvoice from '@/pages/mall/admin/finance/invoice.uvue'
|
||||
import FinanceRecharge from '@/pages/mall/admin/finance/recharge.uvue'
|
||||
import FinanceCapitalFlow from '@/pages/mall/admin/finance/capital_flow.uvue'
|
||||
import FinanceBill from '@/pages/mall/admin/finance/bill.uvue'
|
||||
import FinanceCommission from '@/pages/mall/admin/finance/commission.uvue'
|
||||
import FinanceBalanceStats from '@/pages/mall/admin/finance/balance_stats.uvue'
|
||||
import FinanceBalanceRecord from '@/pages/mall/admin/finance/balance_record.uvue'
|
||||
|
||||
// 导入客服模块
|
||||
import KefuList from '@/pages/mall/admin/kefu/list.uvue'
|
||||
import KefuWords from '@/pages/mall/admin/kefu/words.uvue'
|
||||
import KefuFeedback from '@/pages/mall/admin/kefu/feedback.uvue'
|
||||
import KefuConfig from '@/pages/mall/admin/kefu/config.uvue'
|
||||
|
||||
// 导入装修模块
|
||||
import DecorationHome from '@/pages/mall/admin/decoration/home.uvue'
|
||||
import DecorationCategory from '@/pages/mall/admin/decoration/category.uvue'
|
||||
import DecorationUser from '@/pages/mall/admin/decoration/user.uvue'
|
||||
|
||||
// 导入维护模块
|
||||
import MaintainDevConfig from '@/pages/mall/admin/maintain/dev/config.uvue'
|
||||
|
||||
// import StatisticIndex from '@/pages/mall/admin/statistic/index.uvue'
|
||||
// import SettingSystemConfig from '@/pages/mall/admin/setting/system/config.uvue'
|
||||
// import SettingSystemAdmin from '@/pages/mall/admin/setting/system/admin.uvue'
|
||||
@@ -77,13 +97,12 @@ export const componentMap: Map<string, any> = new Map([
|
||||
['UserGroup', UserGroup],
|
||||
['UserLabel', UserLabel],
|
||||
['UserMemberConfig', MemberConfig],
|
||||
['UserGradeType', PlaceholderPage], // 暂时使用占位组件
|
||||
['UserGradeCard', PlaceholderPage],
|
||||
['UserGradeRecord', PlaceholderPage],
|
||||
['UserGradeRight', PlaceholderPage],
|
||||
|
||||
// 商品模块
|
||||
['ProductStatistic', ProductStatistic],
|
||||
['ProductList', ProductList],
|
||||
['ProductEdit', ProductEdit],
|
||||
['ProductMemberPrice', ProductMemberPrice],
|
||||
['ProductClassify', ProductClassify],
|
||||
['ProductReply', ProductReply],
|
||||
['ProductAttr', ProductAttr],
|
||||
@@ -99,19 +118,62 @@ export const componentMap: Map<string, any> = new Map([
|
||||
['OrderVerify', OrderVerify],
|
||||
['OrderConfig', OrderConfig],
|
||||
|
||||
// 营销模块 - 暂时使用占位组件
|
||||
['MarketingCoupon', PlaceholderPage],
|
||||
['MarketingIntegral', PlaceholderPage],
|
||||
['MarketingBargain', PlaceholderPage],
|
||||
['MarketingCombination', PlaceholderPage],
|
||||
['MarketingSeckill', PlaceholderPage],
|
||||
// 营销模块
|
||||
// 1. 优惠券
|
||||
['MarketingCouponList', MarketingCouponList],
|
||||
['MarketingCouponUser', MarketingCouponUser],
|
||||
// 2. 积分管理
|
||||
['MarketingIntegralStatistic', MarketingIntegralStatistic],
|
||||
['MarketingIntegralProduct', PlaceholderPage],
|
||||
['MarketingIntegralOrder', PlaceholderPage],
|
||||
['MarketingIntegralRecord', PlaceholderPage],
|
||||
// 3. 抽奖管理
|
||||
['MarketingLotteryList', PlaceholderPage],
|
||||
['MarketingLotteryConfig', PlaceholderPage],
|
||||
// 4. 砍价管理
|
||||
['MarketingBargainProduct', PlaceholderPage],
|
||||
['MarketingBargainList', PlaceholderPage],
|
||||
// 5. 拼团管理
|
||||
['MarketingCombinationProduct', PlaceholderPage],
|
||||
['MarketingCombinationList', PlaceholderPage],
|
||||
// 6. 秒杀管理
|
||||
['MarketingSeckillList', PlaceholderPage],
|
||||
['MarketingSeckillProduct', PlaceholderPage],
|
||||
['MarketingSeckillConfig', PlaceholderPage],
|
||||
// 7. 付费会员
|
||||
['MarketingMemberType', PlaceholderPage],
|
||||
['MarketingMemberRight', PlaceholderPage],
|
||||
['MarketingMemberCard', PlaceholderPage],
|
||||
['MarketingMemberRecord', PlaceholderPage],
|
||||
['MarketingMemberConfig', PlaceholderPage],
|
||||
// 8. 直播管理
|
||||
['MarketingLiveRoom', PlaceholderPage],
|
||||
['MarketingLiveProduct', PlaceholderPage],
|
||||
['MarketingLiveAnchor', PlaceholderPage],
|
||||
// 9. 用户充值
|
||||
['MarketingRechargeQuota', PlaceholderPage],
|
||||
['MarketingRechargeConfig', PlaceholderPage],
|
||||
// 10. 每日签到
|
||||
['MarketingCheckinConfig', PlaceholderPage],
|
||||
['MarketingCheckinReward', PlaceholderPage],
|
||||
// 11. 渠道码 & 新人礼
|
||||
['MarketingChannelList', PlaceholderPage],
|
||||
['MarketingNewcomerGift', PlaceholderPage],
|
||||
|
||||
// 内容模块 - 暂时使用占位组件
|
||||
['CmsArticle', PlaceholderPage],
|
||||
['CmsCategory', PlaceholderPage],
|
||||
// 内容模块
|
||||
['CmsArticle', CmsArticle],
|
||||
['CmsCategory', CmsCategory],
|
||||
|
||||
// 财务模块 - 暂时使用占位组件
|
||||
['FinanceRecord', PlaceholderPage],
|
||||
// 财务模块
|
||||
['FinanceTransactionStats', FinanceTransactionStats],
|
||||
['FinanceWithdrawal', FinanceWithdrawal],
|
||||
['FinanceInvoice', FinanceInvoice],
|
||||
['FinanceRecharge', FinanceRecharge],
|
||||
['FinanceCapitalFlow', FinanceCapitalFlow],
|
||||
['FinanceBill', FinanceBill],
|
||||
['FinanceCommission', FinanceCommission],
|
||||
['FinanceBalanceStats', FinanceBalanceStats],
|
||||
['FinanceBalanceRecord', FinanceBalanceRecord],
|
||||
|
||||
// 数据模块 - 暂时使用占位组件
|
||||
['StatisticIndex', PlaceholderPage],
|
||||
@@ -119,7 +181,56 @@ export const componentMap: Map<string, any> = new Map([
|
||||
// 设置模块 - 暂时使用占位组件
|
||||
['SettingSystemConfig', PlaceholderPage],
|
||||
['SettingSystemAdmin', PlaceholderPage],
|
||||
['SettingSystemRole', PlaceholderPage]
|
||||
['SettingSystemRole', PlaceholderPage],
|
||||
|
||||
// 分销模块
|
||||
['DistributionStatistic', PlaceholderPage],
|
||||
['DistributionList', PlaceholderPage],
|
||||
['DistributionConfig', PlaceholderPage],
|
||||
|
||||
// 客服模块
|
||||
['KefuList', KefuList],
|
||||
['KefuWords', KefuWords],
|
||||
['KefuFeedback', KefuFeedback],
|
||||
['KefuAutoReply', PlaceholderPage],
|
||||
['KefuConfig', KefuConfig],
|
||||
|
||||
// 装修模块
|
||||
['DecorationHome', DecorationHome],
|
||||
['DecorationCategory', DecorationCategory],
|
||||
['DecorationUser', DecorationUser],
|
||||
['DecorationData', PlaceholderPage],
|
||||
['DecorationStyle', PlaceholderPage],
|
||||
['DecorationMaterial', PlaceholderPage],
|
||||
['DecorationLink', PlaceholderPage],
|
||||
|
||||
// 应用模块
|
||||
['AppStatistic', PlaceholderPage],
|
||||
['AppList', PlaceholderPage],
|
||||
|
||||
// 维护模块
|
||||
['MaintainDevConfig', MaintainDevConfig],
|
||||
['MaintainDevData', PlaceholderPage],
|
||||
['MaintainDevTask', PlaceholderPage],
|
||||
['MaintainDevAuth', PlaceholderPage],
|
||||
['MaintainDevModule', PlaceholderPage],
|
||||
['MaintainDevEvent', PlaceholderPage],
|
||||
['MaintainSecurityCache', PlaceholderPage],
|
||||
['MaintainSecurityLog', PlaceholderPage],
|
||||
['MaintainSecurityUpgrade', PlaceholderPage],
|
||||
['MaintainDataLogistics', PlaceholderPage],
|
||||
['MaintainDataCity', PlaceholderPage],
|
||||
['MaintainDataClear', PlaceholderPage],
|
||||
['MaintainApiAccount', PlaceholderPage],
|
||||
['MaintainLangList', PlaceholderPage],
|
||||
['MaintainLangDetail', PlaceholderPage],
|
||||
['MaintainLangRegion', PlaceholderPage],
|
||||
['MaintainLangConfig', PlaceholderPage],
|
||||
['MaintainToolDb', PlaceholderPage],
|
||||
['MaintainToolFile', PlaceholderPage],
|
||||
['MaintainToolApi', PlaceholderPage],
|
||||
['MaintainToolDic', PlaceholderPage],
|
||||
['MaintainSysInfo', PlaceholderPage]
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,18 +72,7 @@ export const topMenus: TopMenu[] = [
|
||||
path: '/pages/mall/admin/user/list',
|
||||
order: 2,
|
||||
groups: [
|
||||
{ id: 'user-manage', title: '', order: 1 },
|
||||
{ id: 'member-manage', title: '会员管理', order: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
title: '商品',
|
||||
icon: 'product',
|
||||
path: '/pages/mall/admin/product/list',
|
||||
order: 3,
|
||||
groups: [
|
||||
{ id: 'product-manage', title: '商品管理', order: 1 }
|
||||
{ id: 'user-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -91,11 +80,21 @@ export const topMenus: TopMenu[] = [
|
||||
title: '订单',
|
||||
icon: 'order',
|
||||
path: '/pages/mall/admin/order/list',
|
||||
order: 4,
|
||||
order: 3,
|
||||
groups: [
|
||||
{ id: 'order-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
title: '商品',
|
||||
icon: 'product',
|
||||
path: '/pages/mall/admin/product/statistic',
|
||||
order: 4,
|
||||
groups: [
|
||||
{ id: 'product-manage', title: '商品管理', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'marketing',
|
||||
title: '营销',
|
||||
@@ -103,8 +102,51 @@ export const topMenus: TopMenu[] = [
|
||||
path: '/pages/mall/admin/marketing/coupon/list',
|
||||
order: 5,
|
||||
groups: [
|
||||
{ id: 'marketing-tool', title: '营销工具', order: 1 },
|
||||
{ id: 'marketing-activity', title: '营销活动', order: 2 }
|
||||
{ id: 'marketing-coupon', title: '优惠券', order: 1 },
|
||||
{ id: 'marketing-integral', title: '积分管理', order: 2 },
|
||||
{ id: 'marketing-lottery', title: '抽奖管理', order: 3 },
|
||||
{ id: 'marketing-bargain', title: '砍价管理', order: 4 },
|
||||
{ id: 'marketing-combination', title: '拼团管理', order: 5 },
|
||||
{ id: 'marketing-seckill', title: '秒杀管理', order: 6 },
|
||||
{ id: 'marketing-member', title: '付费会员', order: 7 },
|
||||
{ id: 'marketing-live', title: '直播管理', order: 8 },
|
||||
{ id: 'marketing-recharge', title: '用户充值', order: 9 },
|
||||
{ id: 'marketing-checkin', title: '每日签到', order: 10 },
|
||||
{ id: 'marketing-other', title: '其他', order: 11 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'distribution',
|
||||
title: '分销',
|
||||
icon: 'share',
|
||||
path: '/pages/mall/admin/distribution/statistic',
|
||||
order: 6,
|
||||
groups: [
|
||||
{ id: 'distribution-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'kefu',
|
||||
title: '客服',
|
||||
icon: 'customer-service',
|
||||
path: '/pages/mall/admin/kefu/list',
|
||||
order: 7,
|
||||
groups: [
|
||||
{ id: 'kefu-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'finance',
|
||||
title: '财务',
|
||||
icon: 'finance',
|
||||
path: '/pages/mall/admin/finance/transaction_stats',
|
||||
order: 8,
|
||||
groups: [
|
||||
{ id: 'finance-data', title: '', order: 1 },
|
||||
{ id: 'finance-ops', title: '财务操作', order: 2 },
|
||||
{ id: 'finance-record', title: '财务记录', order: 3 },
|
||||
{ id: 'finance-commission', title: '佣金记录', order: 4 },
|
||||
{ id: 'finance-balance', title: '余额记录', order: 5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -112,29 +154,29 @@ export const topMenus: TopMenu[] = [
|
||||
title: '内容',
|
||||
icon: 'content',
|
||||
path: '/pages/mall/admin/cms/article/list',
|
||||
order: 6,
|
||||
order: 9,
|
||||
groups: [
|
||||
{ id: 'cms-manage', title: '内容管理', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'finance',
|
||||
title: '财务',
|
||||
icon: 'finance',
|
||||
path: '/pages/mall/admin/finance/record',
|
||||
order: 7,
|
||||
id: 'decoration',
|
||||
title: '装修',
|
||||
icon: 'decoration',
|
||||
path: '/pages/mall/admin/decoration/home',
|
||||
order: 10,
|
||||
groups: [
|
||||
{ id: 'finance-manage', title: '财务管理', order: 1 }
|
||||
{ id: 'decoration-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'statistic',
|
||||
title: '数据',
|
||||
icon: 'statistic',
|
||||
path: '/pages/mall/admin/statistic/index',
|
||||
order: 8,
|
||||
id: 'app',
|
||||
title: '应用',
|
||||
icon: 'app',
|
||||
path: '/pages/mall/admin/app/statistic',
|
||||
order: 11,
|
||||
groups: [
|
||||
{ id: 'statistic-data', title: '数据统计', order: 1 }
|
||||
{ id: 'app-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -142,11 +184,26 @@ export const topMenus: TopMenu[] = [
|
||||
title: '设置',
|
||||
icon: 'setting',
|
||||
path: '/pages/mall/admin/setting/system/config',
|
||||
order: 9,
|
||||
order: 12,
|
||||
groups: [
|
||||
{ id: 'setting-system', title: '系统设置', order: 1 },
|
||||
{ id: 'setting-application', title: '应用设置', order: 2 },
|
||||
{ id: 'setting-maintain', title: '维护管理', order: 3 }
|
||||
{ id: 'setting-application', title: '应用设置', order: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'maintain',
|
||||
title: '维护',
|
||||
icon: 'tool',
|
||||
path: '/pages/mall/admin/maintain/dev/config',
|
||||
order: 13,
|
||||
groups: [
|
||||
{ id: 'maintain-dev', title: '开发配置', order: 1 },
|
||||
{ id: 'maintain-security', title: '安全维护', order: 2 },
|
||||
{ id: 'maintain-data', title: '数据维护', order: 3 },
|
||||
{ id: 'maintain-api', title: '对外接口', order: 4 },
|
||||
{ id: 'maintain-lang', title: '语言设置', order: 5 },
|
||||
{ id: 'maintain-tool', title: '开发工具', order: 6 },
|
||||
{ id: 'maintain-sys', title: '系统信息', order: 7 }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -218,58 +275,18 @@ export const routes: RouteRecord[] = [
|
||||
auth: ['user-user-level'],
|
||||
order: 5
|
||||
},
|
||||
{
|
||||
id: 'user_member_config',
|
||||
title: '用户配置',
|
||||
path: '/pages/mall/admin/user/MemberConfig',
|
||||
componentKey: 'UserMemberConfig',
|
||||
parentId: 'user',
|
||||
groupId: 'user-manage',
|
||||
auth: ['admin-user-member-config'],
|
||||
order: 6
|
||||
},
|
||||
{
|
||||
id: 'user_type',
|
||||
title: '会员类型',
|
||||
path: '/pages/mall/admin/user/grade/type',
|
||||
componentKey: 'UserGradeType',
|
||||
parentId: 'user',
|
||||
groupId: 'member-manage',
|
||||
auth: ['admin-user-member-type'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'user_card',
|
||||
title: '卡密会员',
|
||||
path: '/pages/mall/admin/user/grade/card',
|
||||
componentKey: 'UserGradeCard',
|
||||
parentId: 'user',
|
||||
groupId: 'member-manage',
|
||||
auth: ['admin-user-grade-card'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'user_record',
|
||||
title: '会员记录',
|
||||
path: '/pages/mall/admin/user/grade/record',
|
||||
componentKey: 'UserGradeRecord',
|
||||
parentId: 'user',
|
||||
groupId: 'member-manage',
|
||||
auth: ['admin-user-grade-record'],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'user_right',
|
||||
title: '会员权益',
|
||||
path: '/pages/mall/admin/user/grade/right',
|
||||
componentKey: 'UserGradeRight',
|
||||
parentId: 'user',
|
||||
groupId: 'member-manage',
|
||||
auth: ['admin-user-grade-right'],
|
||||
order: 4
|
||||
},
|
||||
|
||||
// ========== 商品模块 ==========
|
||||
{
|
||||
id: 'product_statistic',
|
||||
title: '商品统计',
|
||||
path: '/pages/mall/admin/product/statistic',
|
||||
componentKey: 'ProductStatistic',
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
auth: ['admin-store-storeProuduct-statistic'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'product_productList',
|
||||
title: '商品管理',
|
||||
@@ -279,7 +296,7 @@ export const routes: RouteRecord[] = [
|
||||
groupId: 'product-manage',
|
||||
auth: ['admin-store-storeProuduct-index'],
|
||||
keepAlive: true,
|
||||
order: 1
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'product_productClassify',
|
||||
@@ -289,16 +306,6 @@ export const routes: RouteRecord[] = [
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
auth: ['admin-store-storeCategory-index'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'product_productEvaluate',
|
||||
title: '商品评论',
|
||||
path: '/pages/mall/admin/product/reply',
|
||||
componentKey: 'ProductReply',
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
auth: ['admin-store-storeProuduct-index'],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
@@ -341,6 +348,36 @@ export const routes: RouteRecord[] = [
|
||||
auth: ['admin-product-protection-list'],
|
||||
order: 7
|
||||
},
|
||||
{
|
||||
id: 'product_productEvaluate',
|
||||
title: '商品评论',
|
||||
path: '/pages/mall/admin/product/reply',
|
||||
componentKey: 'ProductReply',
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
auth: ['admin-store-storeProuduct-index'],
|
||||
order: 8
|
||||
},
|
||||
{
|
||||
id: 'product_edit',
|
||||
title: '编辑商品',
|
||||
path: '/pages/mall/admin/product/edit',
|
||||
componentKey: 'ProductEdit',
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
hidden: true,
|
||||
auth: ['admin-store-storeProuduct-index']
|
||||
},
|
||||
{
|
||||
id: 'product_member_price',
|
||||
title: '预售会员价',
|
||||
path: '/pages/mall/admin/product/member-price',
|
||||
componentKey: 'ProductMemberPrice',
|
||||
parentId: 'product',
|
||||
groupId: 'product-manage',
|
||||
hidden: true,
|
||||
auth: ['admin-store-storeProuduct-index']
|
||||
},
|
||||
|
||||
// ========== 订单模块 ==========
|
||||
{
|
||||
@@ -406,56 +443,307 @@ export const routes: RouteRecord[] = [
|
||||
},
|
||||
|
||||
// ========== 营销模块 ==========
|
||||
// 1. 优惠券
|
||||
{
|
||||
id: 'marketing_coupon',
|
||||
title: '优惠券',
|
||||
id: 'marketing_coupon_list',
|
||||
title: '优惠券列表',
|
||||
path: '/pages/mall/admin/marketing/coupon/list',
|
||||
componentKey: 'MarketingCoupon',
|
||||
componentKey: 'MarketingCouponList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-tool',
|
||||
groupId: 'marketing-coupon',
|
||||
auth: ['admin-marketing-storeCoupon-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_integral',
|
||||
title: '积分管理',
|
||||
path: '/pages/mall/admin/marketing/integral/list',
|
||||
componentKey: 'MarketingIntegral',
|
||||
id: 'marketing_coupon_user',
|
||||
title: '用户领取记录',
|
||||
path: '/pages/mall/admin/marketing/coupon/user',
|
||||
componentKey: 'MarketingCouponUser',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-tool',
|
||||
auth: ['admin-marketing-storeIntegral-index'],
|
||||
groupId: 'marketing-coupon',
|
||||
auth: ['admin-marketing-storeCouponUser-index'],
|
||||
order: 2
|
||||
},
|
||||
// 2. 积分管理
|
||||
{
|
||||
id: 'marketing_integral_statistic',
|
||||
title: '积分统计',
|
||||
path: '/pages/mall/admin/marketing/integral/statistic',
|
||||
componentKey: 'MarketingIntegralStatistic',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
auth: ['admin-marketing-integral-statistic'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_integral_product',
|
||||
title: '积分商品',
|
||||
path: '/pages/mall/admin/marketing/integral/product',
|
||||
componentKey: 'MarketingIntegralProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
auth: ['admin-marketing-integral-product'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'marketing_bargain',
|
||||
title: '砍价活动',
|
||||
path: '/pages/mall/admin/marketing/bargain/list',
|
||||
componentKey: 'MarketingBargain',
|
||||
id: 'marketing_integral_order',
|
||||
title: '积分订单',
|
||||
path: '/pages/mall/admin/marketing/integral/order',
|
||||
componentKey: 'MarketingIntegralOrder',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-activity',
|
||||
auth: ['admin-marketing-storeBargain-index'],
|
||||
groupId: 'marketing-integral',
|
||||
auth: ['admin-marketing-integral-order'],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'marketing_combination',
|
||||
title: '拼团活动',
|
||||
path: '/pages/mall/admin/marketing/combination/list',
|
||||
componentKey: 'MarketingCombination',
|
||||
id: 'marketing_integral_record',
|
||||
title: '积分记录',
|
||||
path: '/pages/mall/admin/marketing/integral/record',
|
||||
componentKey: 'MarketingIntegralRecord',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-activity',
|
||||
auth: ['admin-marketing-storeCombination-index'],
|
||||
groupId: 'marketing-integral',
|
||||
auth: ['admin-marketing-integral-record'],
|
||||
order: 4
|
||||
},
|
||||
// 3. 抽奖管理
|
||||
{
|
||||
id: 'marketing_lottery_list',
|
||||
title: '抽奖列表',
|
||||
path: '/pages/mall/admin/marketing/lottery/list',
|
||||
componentKey: 'MarketingLotteryList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-lottery',
|
||||
auth: ['admin-marketing-lottery-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_lottery_config',
|
||||
title: '抽奖配置',
|
||||
path: '/pages/mall/admin/marketing/lottery/config',
|
||||
componentKey: 'MarketingLotteryConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-lottery',
|
||||
auth: ['admin-marketing-lottery-config'],
|
||||
order: 2
|
||||
},
|
||||
// 4. 砍价管理
|
||||
{
|
||||
id: 'marketing_bargain_product',
|
||||
title: '砍价商品',
|
||||
path: '/pages/mall/admin/marketing/bargain/product',
|
||||
componentKey: 'MarketingBargainProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-bargain',
|
||||
auth: ['admin-marketing-bargain-product'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_bargain_list',
|
||||
title: '砍价列表',
|
||||
path: '/pages/mall/admin/marketing/bargain/list',
|
||||
componentKey: 'MarketingBargainList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-bargain',
|
||||
auth: ['admin-marketing-bargain-index'],
|
||||
order: 2
|
||||
},
|
||||
// 5. 拼团管理
|
||||
{
|
||||
id: 'marketing_combination_product',
|
||||
title: '拼团商品',
|
||||
path: '/pages/mall/admin/marketing/combination/product',
|
||||
componentKey: 'MarketingCombinationProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-combination',
|
||||
auth: ['admin-marketing-combination-product'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_combination_list',
|
||||
title: '拼团列表',
|
||||
path: '/pages/mall/admin/marketing/combination/list',
|
||||
componentKey: 'MarketingCombinationList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-combination',
|
||||
auth: ['admin-marketing-combination-index'],
|
||||
order: 2
|
||||
},
|
||||
// 6. 秒杀管理
|
||||
{
|
||||
id: 'marketing_seckill_list',
|
||||
title: '秒杀列表',
|
||||
path: '/pages/mall/admin/marketing/seckill/list',
|
||||
componentKey: 'MarketingSeckillList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-seckill',
|
||||
auth: ['admin-marketing-seckill-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_seckill_product',
|
||||
title: '秒杀商品',
|
||||
path: '/pages/mall/admin/marketing/seckill/product',
|
||||
componentKey: 'MarketingSeckillProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-seckill',
|
||||
auth: ['admin-marketing-seckill-product'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'marketing_seckill_config',
|
||||
title: '秒杀配置',
|
||||
path: '/pages/mall/admin/marketing/seckill/config',
|
||||
componentKey: 'MarketingSeckillConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-seckill',
|
||||
auth: ['admin-marketing-seckill-config'],
|
||||
order: 3
|
||||
},
|
||||
// 7. 付费会员 (从用户模块迁入)
|
||||
{
|
||||
id: 'marketing_member_type',
|
||||
title: '会员类型',
|
||||
path: '/pages/mall/admin/marketing/member/type',
|
||||
componentKey: 'MarketingMemberType',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-member',
|
||||
auth: ['admin-user-member-type'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_member_right',
|
||||
title: '会员权益',
|
||||
path: '/pages/mall/admin/marketing/member/right',
|
||||
componentKey: 'MarketingMemberRight',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-member',
|
||||
auth: ['admin-user-member-right'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'marketing_member_card',
|
||||
title: '卡密会员',
|
||||
path: '/pages/mall/admin/marketing/member/card',
|
||||
componentKey: 'MarketingMemberCard',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-member',
|
||||
auth: ['admin-user-member-card'],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'marketing_member_record',
|
||||
title: '会员记录',
|
||||
path: '/pages/mall/admin/marketing/member/record',
|
||||
componentKey: 'MarketingMemberRecord',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-member',
|
||||
auth: ['admin-user-member-record'],
|
||||
order: 4
|
||||
},
|
||||
{
|
||||
id: 'marketing_seckill',
|
||||
title: '秒杀活动',
|
||||
path: '/pages/mall/admin/marketing/seckill/list',
|
||||
componentKey: 'MarketingSeckill',
|
||||
id: 'marketing_member_config',
|
||||
title: '会员配置',
|
||||
path: '/pages/mall/admin/marketing/member/config',
|
||||
componentKey: 'MarketingMemberConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-activity',
|
||||
auth: ['admin-marketing-storeSeckill-index'],
|
||||
groupId: 'marketing-member',
|
||||
auth: ['admin-user-member-config'],
|
||||
order: 5
|
||||
},
|
||||
// 8. 直播管理
|
||||
{
|
||||
id: 'marketing_live_room',
|
||||
title: '直播间管理',
|
||||
path: '/pages/mall/admin/marketing/live/room',
|
||||
componentKey: 'MarketingLiveRoom',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-live',
|
||||
auth: ['admin-marketing-live-room'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_live_product',
|
||||
title: '直播商品管理',
|
||||
path: '/pages/mall/admin/marketing/live/product',
|
||||
componentKey: 'MarketingLiveProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-live',
|
||||
auth: ['admin-marketing-live-product'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'marketing_live_anchor',
|
||||
title: '主播管理',
|
||||
path: '/pages/mall/admin/marketing/live/anchor',
|
||||
componentKey: 'MarketingLiveAnchor',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-live',
|
||||
auth: ['admin-marketing-live-anchor'],
|
||||
order: 3
|
||||
},
|
||||
// 9. 用户充值
|
||||
{
|
||||
id: 'marketing_recharge_quota',
|
||||
title: '金额设置',
|
||||
path: '/pages/mall/admin/marketing/recharge/quota',
|
||||
componentKey: 'MarketingRechargeQuota',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-recharge',
|
||||
auth: ['admin-marketing-recharge-quota'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_recharge_config',
|
||||
title: '充值配置',
|
||||
path: '/pages/mall/admin/marketing/recharge/config',
|
||||
componentKey: 'MarketingRechargeConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-recharge',
|
||||
auth: ['admin-marketing-recharge-config'],
|
||||
order: 2
|
||||
},
|
||||
// 10. 每日签到
|
||||
{
|
||||
id: 'marketing_checkin_config',
|
||||
title: '签到配置',
|
||||
path: '/pages/mall/admin/marketing/checkin/config',
|
||||
componentKey: 'MarketingCheckinConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-checkin',
|
||||
auth: ['admin-marketing-checkin-config'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_checkin_reward',
|
||||
title: '签到奖励',
|
||||
path: '/pages/mall/admin/marketing/checkin/reward',
|
||||
componentKey: 'MarketingCheckinReward',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-checkin',
|
||||
auth: ['admin-marketing-checkin-reward'],
|
||||
order: 2
|
||||
},
|
||||
// 11. 渠道码 & 新人礼
|
||||
{
|
||||
id: 'marketing_channel_code',
|
||||
title: '渠道码',
|
||||
path: '/pages/mall/admin/marketing/channel/list',
|
||||
componentKey: 'MarketingChannelList',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-other',
|
||||
auth: ['admin-marketing-channel-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'marketing_newcomer_gift',
|
||||
title: '新人礼',
|
||||
path: '/pages/mall/admin/marketing/newcomer/gift',
|
||||
componentKey: 'MarketingNewcomerGift',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-other',
|
||||
auth: ['admin-marketing-newcomer-index'],
|
||||
order: 2
|
||||
},
|
||||
|
||||
// ========== 内容模块 ==========
|
||||
{
|
||||
@@ -481,15 +769,95 @@ export const routes: RouteRecord[] = [
|
||||
|
||||
// ========== 财务模块 ==========
|
||||
{
|
||||
id: 'finance_record',
|
||||
title: '财务记录',
|
||||
path: '/pages/mall/admin/finance/record',
|
||||
componentKey: 'FinanceRecord',
|
||||
id: 'finance_transaction_stats',
|
||||
title: '交易统计',
|
||||
path: '/pages/mall/admin/finance/transaction_stats',
|
||||
componentKey: 'FinanceTransactionStats',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-manage',
|
||||
auth: ['admin-finance-record-index'],
|
||||
groupId: 'finance-data',
|
||||
auth: ['admin-finance-transaction-stats'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'finance_withdrawal',
|
||||
title: '提现申请',
|
||||
path: '/pages/mall/admin/finance/withdrawal',
|
||||
componentKey: 'FinanceWithdrawal',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-ops',
|
||||
auth: ['admin-finance-withdrawal-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'finance_invoice',
|
||||
title: '发票管理',
|
||||
path: '/pages/mall/admin/finance/invoice',
|
||||
componentKey: 'FinanceInvoice',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-ops',
|
||||
auth: ['admin-finance-invoice-index'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'finance_recharge',
|
||||
title: '充值记录',
|
||||
path: '/pages/mall/admin/finance/recharge',
|
||||
componentKey: 'FinanceRecharge',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-record',
|
||||
auth: ['admin-finance-recharge-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'finance_capital_flow',
|
||||
title: '资金流水',
|
||||
path: '/pages/mall/admin/finance/capital_flow',
|
||||
componentKey: 'FinanceCapitalFlow',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-record',
|
||||
auth: ['admin-finance-capital-flow-index'],
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'finance_bill',
|
||||
title: '账单记录',
|
||||
path: '/pages/mall/admin/finance/bill',
|
||||
componentKey: 'FinanceBill',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-record',
|
||||
auth: ['admin-finance-bill-index'],
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'finance_commission_record',
|
||||
title: '佣金记录',
|
||||
path: '/pages/mall/admin/finance/commission',
|
||||
componentKey: 'FinanceCommission',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-commission',
|
||||
auth: ['admin-finance-commission-index'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'finance_balance_stats',
|
||||
title: '余额统计',
|
||||
path: '/pages/mall/admin/finance/balance_stats',
|
||||
componentKey: 'FinanceBalanceStats',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-balance',
|
||||
auth: ['admin-finance-balance-stats'],
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'finance_balance_record',
|
||||
title: '余额记录',
|
||||
path: '/pages/mall/admin/finance/balance_record',
|
||||
componentKey: 'FinanceBalanceRecord',
|
||||
parentId: 'finance',
|
||||
groupId: 'finance-balance',
|
||||
auth: ['admin-finance-balance-record-index'],
|
||||
order: 2
|
||||
},
|
||||
|
||||
// ========== 数据统计模块 ==========
|
||||
{
|
||||
@@ -533,7 +901,205 @@ export const routes: RouteRecord[] = [
|
||||
groupId: 'setting-system',
|
||||
auth: ['admin-setting-system-role'],
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 分销模块 ==========
|
||||
{
|
||||
id: 'distribution_statistic',
|
||||
title: '分销统计',
|
||||
path: '/pages/mall/admin/distribution/statistic',
|
||||
componentKey: 'DistributionStatistic',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'distribution_list',
|
||||
title: '分销员列表',
|
||||
path: '/pages/mall/admin/distribution/list',
|
||||
componentKey: 'DistributionList',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'distribution_config',
|
||||
title: '分销设置',
|
||||
path: '/pages/mall/admin/distribution/config',
|
||||
componentKey: 'DistributionConfig',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 3
|
||||
},
|
||||
|
||||
// ========== 客服模块 ==========
|
||||
{
|
||||
id: 'kefu_list',
|
||||
title: '客服列表',
|
||||
path: '/pages/mall/admin/kefu/list',
|
||||
componentKey: 'KefuList',
|
||||
parentId: 'kefu',
|
||||
groupId: 'kefu-manage',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'kefu_words',
|
||||
title: '客服话术',
|
||||
path: '/pages/mall/admin/kefu/words',
|
||||
componentKey: 'KefuWords',
|
||||
parentId: 'kefu',
|
||||
groupId: 'kefu-manage',
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'kefu_feedback',
|
||||
title: '用户留言',
|
||||
path: '/pages/mall/admin/kefu/feedback',
|
||||
componentKey: 'KefuFeedback',
|
||||
parentId: 'kefu',
|
||||
groupId: 'kefu-manage',
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'kefu_auto_reply',
|
||||
title: '自动回复',
|
||||
path: '/pages/mall/admin/kefu/auto_reply',
|
||||
componentKey: 'KefuAutoReply',
|
||||
parentId: 'kefu',
|
||||
groupId: 'kefu-manage',
|
||||
order: 4
|
||||
},
|
||||
{
|
||||
id: 'kefu_config',
|
||||
title: '客服配置',
|
||||
path: '/pages/mall/admin/kefu/config',
|
||||
componentKey: 'KefuConfig',
|
||||
parentId: 'kefu',
|
||||
groupId: 'kefu-manage',
|
||||
order: 5
|
||||
},
|
||||
|
||||
// ========== 装修模块 ==========
|
||||
{
|
||||
id: 'decoration_home',
|
||||
title: '首页装修',
|
||||
path: '/pages/mall/admin/decoration/home',
|
||||
componentKey: 'DecorationHome',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'decoration_category',
|
||||
title: '商品分类',
|
||||
path: '/pages/mall/admin/decoration/category',
|
||||
componentKey: 'DecorationCategory',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'decoration_user',
|
||||
title: '个人中心',
|
||||
path: '/pages/mall/admin/decoration/user',
|
||||
componentKey: 'DecorationUser',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'decoration_data',
|
||||
title: '数据配置',
|
||||
path: '/pages/mall/admin/decoration/data',
|
||||
componentKey: 'DecorationData',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 4
|
||||
},
|
||||
{
|
||||
id: 'decoration_style',
|
||||
title: '主题风格',
|
||||
path: '/pages/mall/admin/decoration/style',
|
||||
componentKey: 'DecorationStyle',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 5
|
||||
},
|
||||
{
|
||||
id: 'decoration_material',
|
||||
title: '素材管理',
|
||||
path: '/pages/mall/admin/decoration/material',
|
||||
componentKey: 'DecorationMaterial',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 6
|
||||
},
|
||||
{
|
||||
id: 'decoration_link',
|
||||
title: '链接管理',
|
||||
path: '/pages/mall/admin/decoration/link',
|
||||
componentKey: 'DecorationLink',
|
||||
parentId: 'decoration',
|
||||
groupId: 'decoration-manage',
|
||||
order: 7
|
||||
},
|
||||
|
||||
// ========== 应用模块 ==========
|
||||
{
|
||||
id: 'app_statistic',
|
||||
title: '应用统计',
|
||||
path: '/pages/mall/admin/app/statistic',
|
||||
componentKey: 'AppStatistic',
|
||||
parentId: 'app',
|
||||
groupId: 'app-manage',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'app_list',
|
||||
title: '应用列表',
|
||||
path: '/pages/mall/admin/app/list',
|
||||
componentKey: 'AppList',
|
||||
parentId: 'app',
|
||||
groupId: 'app-manage',
|
||||
order: 2
|
||||
},
|
||||
|
||||
// ========== 维护模块 ==========
|
||||
// 开发配置
|
||||
{ id: 'maintain_dev_config', title: '配置分类', path: '/pages/mall/admin/maintain/dev/config', componentKey: 'MaintainDevConfig', parentId: 'maintain', groupId: 'maintain-dev', order: 1 },
|
||||
{ id: 'maintain_dev_data', title: '组合数据', path: '/pages/mall/admin/maintain/dev/data', componentKey: 'MaintainDevData', parentId: 'maintain', groupId: 'maintain-dev', order: 2 },
|
||||
{ id: 'maintain_dev_task', title: '定时任务', path: '/pages/mall/admin/maintain/dev/task', componentKey: 'MaintainDevTask', parentId: 'maintain', groupId: 'maintain-dev', order: 3 },
|
||||
{ id: 'maintain_dev_auth', title: '权限维护', path: '/pages/mall/admin/maintain/dev/auth', componentKey: 'MaintainDevAuth', parentId: 'maintain', groupId: 'maintain-dev', order: 4 },
|
||||
{ id: 'maintain_dev_module', title: '模块配置', path: '/pages/mall/admin/maintain/dev/module', componentKey: 'MaintainDevModule', parentId: 'maintain', groupId: 'maintain-dev', order: 5 },
|
||||
{ id: 'maintain_dev_event', title: '自定事件', path: '/pages/mall/admin/maintain/dev/event', componentKey: 'MaintainDevEvent', parentId: 'maintain', groupId: 'maintain-dev', order: 6 },
|
||||
|
||||
// 安全维护
|
||||
{ id: 'maintain_security_cache', title: '刷新缓存', path: '/pages/mall/admin/maintain/security/cache', componentKey: 'MaintainSecurityCache', parentId: 'maintain', groupId: 'maintain-security', order: 1 },
|
||||
{ id: 'maintain_security_log', title: '系统日志', path: '/pages/mall/admin/maintain/security/log', componentKey: 'MaintainSecurityLog', parentId: 'maintain', groupId: 'maintain-security', order: 2 },
|
||||
{ id: 'maintain_security_upgrade', title: '在线升级', path: '/pages/mall/admin/maintain/security/upgrade', componentKey: 'MaintainSecurityUpgrade', parentId: 'maintain', groupId: 'maintain-security', order: 3 },
|
||||
|
||||
// 数据维护
|
||||
{ id: 'maintain_data_logistics', title: '物流公司', path: '/pages/mall/admin/maintain/data/logistics', componentKey: 'MaintainDataLogistics', parentId: 'maintain', groupId: 'maintain-data', order: 1 },
|
||||
{ id: 'maintain_data_city', title: '城市数据', path: '/pages/mall/admin/maintain/data/city', componentKey: 'MaintainDataCity', parentId: 'maintain', groupId: 'maintain-data', order: 2 },
|
||||
{ id: 'maintain_data_clear', title: '清除数据', path: '/pages/mall/admin/maintain/data/clear', componentKey: 'MaintainDataClear', parentId: 'maintain', groupId: 'maintain-data', order: 3 },
|
||||
|
||||
// 对外接口
|
||||
{ id: 'maintain_api_account', title: '账号管理', path: '/pages/mall/admin/maintain/api/account', componentKey: 'MaintainApiAccount', parentId: 'maintain', groupId: 'maintain-api', order: 1 },
|
||||
|
||||
// 语言设置
|
||||
{ id: 'maintain_lang_list', title: '语言列表', path: '/pages/mall/admin/maintain/lang/list', componentKey: 'MaintainLangList', parentId: 'maintain', groupId: 'maintain-lang', order: 1 },
|
||||
{ id: 'maintain_lang_detail', title: '语言详情', path: '/pages/mall/admin/maintain/lang/detail', componentKey: 'MaintainLangDetail', parentId: 'maintain', groupId: 'maintain-lang', order: 2 },
|
||||
{ id: 'maintain_lang_region', title: '地区列表', path: '/pages/mall/admin/maintain/lang/region', componentKey: 'MaintainLangRegion', parentId: 'maintain', groupId: 'maintain-lang', order: 3 },
|
||||
{ id: 'maintain_lang_config', title: '翻译配置', path: '/pages/mall/admin/maintain/lang/config', componentKey: 'MaintainLangConfig', parentId: 'maintain', groupId: 'maintain-lang', order: 4 },
|
||||
|
||||
// 开发工具
|
||||
{ id: 'maintain_tool_db', title: '数据库管理', path: '/pages/mall/admin/maintain/tool/db', componentKey: 'MaintainToolDb', parentId: 'maintain', groupId: 'maintain-tool', order: 1 },
|
||||
{ id: 'maintain_tool_file', title: '文件管理', path: '/pages/mall/admin/maintain/tool/file', componentKey: 'MaintainToolFile', parentId: 'maintain', groupId: 'maintain-tool', order: 2 },
|
||||
{ id: 'maintain_tool_api', title: '接口管理', path: '/pages/mall/admin/maintain/tool/api', componentKey: 'MaintainToolApi', parentId: 'maintain', groupId: 'maintain-tool', order: 3 },
|
||||
{ id: 'maintain_tool_dic', title: '数据字典', path: '/pages/mall/admin/maintain/tool/dic', componentKey: 'MaintainToolDic', parentId: 'maintain', groupId: 'maintain-tool', order: 4 },
|
||||
|
||||
// 系统信息
|
||||
{ id: 'maintain_sys_info', title: '系统信息', path: '/pages/mall/admin/maintain/sys/info', componentKey: 'MaintainSysInfo', parentId: 'maintain', groupId: 'maintain-sys', order: 1 }
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,217 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">文章管理</text>
|
||||
<text class="page-subtitle">Component: CmsArticle</text>
|
||||
<view class="admin-cms-article">
|
||||
<view class="content-body">
|
||||
<!-- 顶部过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">文章分类:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">{{ filterCategory }}</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">文章搜索:</text>
|
||||
<input class="search-input" placeholder="请输入" v-model="filterKeyword" />
|
||||
</view>
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-content">
|
||||
<view class="placeholder-card">
|
||||
<text class="placeholder-title">页面占位</text>
|
||||
<text class="placeholder-desc">该功能模块正在开发中</text>
|
||||
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="card-header">
|
||||
<view class="btn-primary" @click="handleAdd">
|
||||
<text class="btn-txt">添加文章</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-img"><text class="th-txt">文章图片</text></view>
|
||||
<view class="th col-name"><text class="th-txt">文章名称</text></view>
|
||||
<view class="th col-link"><text class="th-txt">关联商品</text></view>
|
||||
<view class="th col-v"><text class="th-txt">浏览量</text></view>
|
||||
<view class="th col-time"><text class="th-txt">时间</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in articleList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-img">
|
||||
<view class="img-box"><text class="img-placeholder">🖼️</text></view>
|
||||
</view>
|
||||
<view class="td col-name"><text class="td-txt">{{ item.name }}</text></view>
|
||||
<view class="td col-link"><text class="td-txt">{{ item.linkedProduct }}</text></view>
|
||||
<view class="td col-v"><text class="td-txt">{{ item.views }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-op">
|
||||
<view class="op-links">
|
||||
<text class="link-txt" @click="handleEdit(item)">编辑</text>
|
||||
<view class="divider"></view>
|
||||
<text class="link-txt">关联</text>
|
||||
<view class="divider"></view>
|
||||
<text class="link-txt danger">删除</text>
|
||||
<view class="divider"></view>
|
||||
<text class="link-txt">复制链接</text>
|
||||
<text class="arrow-small">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pagination-bar">
|
||||
<view class="page-info">
|
||||
<text class="page-total">共 {{ articleList.length }} 条</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 侧边弹窗 (Drawer) -->
|
||||
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
|
||||
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
|
||||
<view class="drawer-header">
|
||||
<view class="tab-item active">
|
||||
<text class="tab-txt">文章信息</text>
|
||||
<view class="tab-line"></view>
|
||||
</view>
|
||||
<text class="close-btn" @click="closeDrawer">×</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="drawer-body" :scroll-y="true">
|
||||
<!-- 文章信息区块 -->
|
||||
<view class="section-title">
|
||||
<view class="title-inner active">
|
||||
<text class="title-txt">文章信息</text>
|
||||
<view class="title-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-grid">
|
||||
<view class="form-row">
|
||||
<view class="form-col">
|
||||
<view class="label-box"><text class="required">*</text><text class="label-txt">标题:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="formTitle" placeholder="请输入" />
|
||||
<text class="input-count">{{ formTitle.length }}/80</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-col">
|
||||
<view class="label-box"><text class="label-txt">作者:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="formAuthor" placeholder="请输入" />
|
||||
<text class="input-count">{{ formAuthor.length }}/10</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-row mt-20">
|
||||
<view class="form-col">
|
||||
<view class="label-box"><text class="required">*</text><text class="label-txt">文章分类:</text></view>
|
||||
<view class="input-box">
|
||||
<view class="select-mock">
|
||||
<text class="select-val">{{ formCategory || '请选择' }}</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-col">
|
||||
<view class="label-box"><text class="label-txt">文章简介:</text></view>
|
||||
<view class="input-box">
|
||||
<textarea class="textarea-base" v-model="formIntro" placeholder="请输入" />
|
||||
<text class="input-count">{{ formIntro.length }}/300</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-row mt-20">
|
||||
<view class="form-col full">
|
||||
<view class="label-box"><text class="required">*</text><text class="label-txt">图文封面:</text></view>
|
||||
<view class="upload-container">
|
||||
<view class="upload-btn">
|
||||
<text class="plus-icon">+</text>
|
||||
</view>
|
||||
<text class="tip-txt mt-10">建议尺寸:500 x 312 px</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 文章内容区块 -->
|
||||
<view class="section-title mt-40">
|
||||
<view class="title-inner active">
|
||||
<text class="title-txt">文章内容</text>
|
||||
<view class="title-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="editor-section">
|
||||
<view class="label-box mb-10"><text class="required">*</text><text class="label-txt">文章内容:</text></view>
|
||||
<view class="rich-editor-mock">
|
||||
<view class="editor-toolbar">
|
||||
<text class="tool-ic">HTML</text>
|
||||
<text class="tool-ic">H</text>
|
||||
<text class="tool-ic">B</text>
|
||||
<text class="tool-ic">T↕</text>
|
||||
<text class="tool-ic">F</text>
|
||||
<text class="tool-ic">I</text>
|
||||
<text class="tool-ic">U</text>
|
||||
<text class="tool-ic">S</text>
|
||||
<text class="tool-ic-img">🖼️</text>
|
||||
<text class="tool-ic-img">🎬</text>
|
||||
</view>
|
||||
<view class="editor-content"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他设置 -->
|
||||
<view class="section-title mt-40">
|
||||
<view class="title-inner active">
|
||||
<text class="title-txt">其他设置</text>
|
||||
<view class="title-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="settings-section">
|
||||
<view class="form-item">
|
||||
<view class="label-box-wide"><text class="label-txt">banner显示:</text></view>
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="formBanner = true">
|
||||
<view :class="['radio-circle', formBanner ? 'checked' : '']"><view v-if="formBanner" class="radio-in"></view></view>
|
||||
<text class="radio-la">显示</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="formBanner = false">
|
||||
<view :class="['radio-circle', !formBanner ? 'checked' : '']"><view v-if="!formBanner" class="radio-in"></view></view>
|
||||
<text class="radio-la">不显示</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item mt-20">
|
||||
<view class="label-box-wide"><text class="label-txt">热门文章:</text></view>
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="formHot = true">
|
||||
<view :class="['radio-circle', formHot ? 'checked' : '']"><view v-if="formHot" class="radio-in"></view></view>
|
||||
<text class="radio-la">显示</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="formHot = false">
|
||||
<view :class="['radio-circle', !formHot ? 'checked' : '']"><view v-if="!formHot" class="radio-in"></view></view>
|
||||
<text class="radio-la">不显示</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-container mt-40">
|
||||
<view class="btn-submit" @click="handleConfirm">
|
||||
<text class="submit-txt">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -18,64 +220,163 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 文章管理 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
const filterCategory = ref('全部')
|
||||
const filterKeyword = ref('')
|
||||
const articleList = ref([
|
||||
{ id: '240', name: '赋能消费 | 卷狗优选迈向文化消费新时代', linkedProduct: '', views: '3349', time: '2025-04-01 16:34' },
|
||||
{ id: '237', name: '把重要的日子放在桌面', linkedProduct: '2024新款吹风机...', views: '260', time: '2025-04-01 16:32' }
|
||||
])
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const isClosing = ref(false)
|
||||
const formTitle = ref('')
|
||||
const formAuthor = ref('')
|
||||
const formCategory = ref('')
|
||||
const formIntro = ref('')
|
||||
const formBanner = ref(false)
|
||||
const formHot = ref(false)
|
||||
|
||||
const handleAdd = () => {
|
||||
formTitle.value = ''
|
||||
formAuthor.value = ''
|
||||
formCategory.value = ''
|
||||
formIntro.value = ''
|
||||
formBanner.value = false
|
||||
formHot.value = false
|
||||
isClosing.value = false
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (item: any) => {
|
||||
formTitle.value = item.name
|
||||
formAuthor.value = '管理员'
|
||||
formCategory.value = '全部'
|
||||
formIntro.value = '这是一段文章简介...'
|
||||
formBanner.value = false
|
||||
formHot.value = true
|
||||
isClosing.value = false
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const closeDrawer = () => {
|
||||
isClosing.value = true
|
||||
setTimeout(() => {
|
||||
showDrawer.value = false
|
||||
isClosing.value = false
|
||||
}, 300)
|
||||
}
|
||||
const handleConfirm = () => { closeDrawer() }
|
||||
const handleQuery = () => { console.log('Querying...') }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.admin-cms-article { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }
|
||||
.border-shadow { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
|
||||
.filter-card { padding: 20px; display: flex; flex-direction: row; align-items: center; gap: 30px; margin-bottom: 20px; }
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; }
|
||||
.label-txt { font-size: 14px; color: #606266; margin-right: 12px; }
|
||||
.select-mock { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 0 15px; }
|
||||
.select-val { font-size: 13px; color: #333; }
|
||||
.arrow-down { font-size: 10px; color: #999; }
|
||||
.search-input { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px; font-size: 13px; }
|
||||
.btn-query { width: 76px; height: 34px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
.table-card { display: flex; flex-direction: column; }
|
||||
.card-header { padding: 20px; }
|
||||
.btn-primary { width: 100px; height: 36px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
.btn-txt { color: #fff; font-size: 13px; }
|
||||
.table-header { height: 50px; background-color: #eaf2ff; display: flex; flex-direction: row; align-items: center; }
|
||||
.th { padding: 0 15px; }
|
||||
.th-txt { font-size: 13px; color: #606266; font-weight: bold; }
|
||||
.table-row { height: 80px; display: flex; flex-direction: row; align-items: center; border-bottom: 1px solid #f0f0f0; }
|
||||
.td { padding: 0 15px; }
|
||||
.td-txt { font-size: 13px; color: #606266; }
|
||||
.img-box { width: 60px; height: 40px; background-color: #f5f7fa; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
.img-placeholder { font-size: 20px; }
|
||||
.col-id { width: 80px; justify-content: center; }
|
||||
.col-img { width: 100px; }
|
||||
.col-name { flex: 2; }
|
||||
.col-link { flex: 2; }
|
||||
.col-v { width: 100px; justify-content: center; }
|
||||
.col-time { width: 180px; justify-content: center; }
|
||||
.col-op { width: 220px; }
|
||||
.op-links { display: flex; flex-direction: row; align-items: center; }
|
||||
.link-txt { font-size: 13px; color: #2d8cf0; cursor: pointer; }
|
||||
.danger { color: #ed4014; }
|
||||
.divider { width: 1px; height: 12px; background-color: #e8eaec; margin: 0 8px; }
|
||||
.arrow-small { font-size: 8px; color: #2d8cf0; margin-left: 2px; }
|
||||
.pagination-bar { padding: 20px; display: flex; flex-direction: row; align-items: center; justify-content: flex-end; }
|
||||
.page-info { display: flex; flex-direction: row; align-items: center; }
|
||||
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
|
||||
.drawer-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2000; transition: opacity 0.3s; }
|
||||
.mask-fade-out { opacity: 0; }
|
||||
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; background-color: #fff; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
|
||||
.slide-out { animation: slideOut 0.3s ease forwards; }
|
||||
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
||||
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
|
||||
.drawer-header { height: 54px; padding: 0 20px; border-bottom: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
|
||||
.tab-item { height: 100%; display: flex; align-items: center; position: relative; }
|
||||
.tab-txt { font-size: 15px; color: #2d8cf0; font-weight: 500; }
|
||||
.tab-line { position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background-color: #2d8cf0; }
|
||||
.close-btn { font-size: 24px; color: #999; cursor: pointer; }
|
||||
.drawer-body { flex: 1; padding: 24px; background-color: #fff; }
|
||||
.section-title { height: 44px; border-bottom: 1px solid #f0f0f0; margin-bottom: 24px; display: flex; flex-direction: row; }
|
||||
.title-inner { height: 100%; display: flex; align-items: center; position: relative; padding: 0 10px; }
|
||||
.title-txt { font-size: 15px; color: #333; font-weight: 500; }
|
||||
.title-inner.active .title-txt { color: #2d8cf0; }
|
||||
.title-line { position: absolute; bottom: -1px; left: 0; right: 0; height: 2px; background-color: #2d8cf0; display: none; }
|
||||
.title-inner.active .title-line { display: block; }
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-grid { display: flex; flex-direction: column; }
|
||||
.form-row { display: flex; flex-direction: row; gap: 30px; }
|
||||
.form-col { flex: 1; display: flex; flex-direction: row; align-items: flex-start; }
|
||||
.form-col.full { flex: none; width: 100%; }
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.label-box { width: 100px; display: flex; flex-direction: row; justify-content: flex-end; margin-right: 15px; padding-top: 8px; flex-shrink: 0; }
|
||||
.label-box-wide { width: 120px; display: flex; flex-direction: row; justify-content: flex-end; margin-right: 15px; flex-shrink: 0; }
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
.required { color: #ed4014; margin-right: 4px; }
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
.input-box { flex: 1; position: relative; }
|
||||
.input-base { width: 100%; height: 38px; border: 1px solid #dcdee2; border-radius: 4px; padding: 0 45px 0 12px; font-size: 14px; }
|
||||
.textarea-base { width: 100%; height: 100px; border: 1px solid #dcdee2; border-radius: 4px; padding: 10px 12px; font-size: 14px; }
|
||||
.input-count { position: absolute; bottom: 8px; right: 12px; font-size: 12px; color: #999; }
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.upload-container { display: flex; flex-direction: column; }
|
||||
.upload-btn { width: 80px; height: 80px; border: 1px dashed #dcdee2; border-radius: 4px; display: flex; align-items: center; justify-content: center; background-color: #f8f8f9; }
|
||||
.plus-icon { font-size: 30px; color: #999; }
|
||||
.tip-txt { font-size: 12px; color: #999; }
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.editor-section { display: flex; flex-direction: column; }
|
||||
.rich-editor-mock { border: 1px solid #dcdee2; border-radius: 4px; min-height: 400px; display: flex; flex-direction: column; }
|
||||
.editor-toolbar { height: 44px; background-color: #fafafa; border-bottom: 1px solid #dcdee2; display: flex; flex-direction: row; align-items: center; padding: 0 15px; gap: 20px; flex-wrap: wrap; }
|
||||
.tool-ic { font-size: 14px; color: #515a6e; cursor: pointer; }
|
||||
.tool-ic-img { font-size: 18px; cursor: pointer; }
|
||||
.editor-content { flex: 1; background-color: #fff; }
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.settings-section { display: flex; flex-direction: column; }
|
||||
.form-item { display: flex; flex-direction: row; align-items: center; }
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.radio-group { display: flex; flex-direction: row; gap: 30px; }
|
||||
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
|
||||
.radio-circle { width: 16px; height: 16px; border: 1px solid #dcdee2; border-radius: 50%; margin-right: 8px; display: flex; align-items: center; justify-content: center; }
|
||||
.radio-circle.checked { border-color: #2d8cf0; }
|
||||
.radio-in { width: 8px; height: 8px; background-color: #2d8cf0; border-radius: 50%; }
|
||||
.radio-la { font-size: 14px; color: #606266; }
|
||||
|
||||
.submit-container { display: flex; justify-content: flex-start; padding-left: 135px; padding-bottom: 50px; }
|
||||
.btn-submit { width: 120px; height: 40px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
|
||||
.submit-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.drawer-footer { height: 60px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; padding: 0 24px; background-color: #fff; }
|
||||
.btn-cancel { padding: 8px 20px; border: 1px solid #dcdee2; border-radius: 4px; margin-right: 15px; }
|
||||
.btn-confirm { padding: 8px 20px; background-color: #2d8cf0; border-radius: 4px; }
|
||||
.cancel-txt { font-size: 14px; color: #606266; }
|
||||
.confirm-txt { font-size: 14px; color: #fff; }
|
||||
|
||||
.mt-20 { margin-top: 20px; }
|
||||
.mt-40 { margin-top: 40px; }
|
||||
.mt-10 { margin-top: 10px; }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,135 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">文章分类</text>
|
||||
<text class="page-subtitle">Component: CmsCategory</text>
|
||||
<view class="admin-cms-category">
|
||||
<view class="content-body">
|
||||
<!-- 顶部过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">是否显示:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">分类名称:</text>
|
||||
<input class="search-input" placeholder="请输入分类名称" v-model="filterKeyword" />
|
||||
</view>
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-content">
|
||||
<view class="placeholder-card">
|
||||
<text class="placeholder-title">页面占位</text>
|
||||
<text class="placeholder-desc">该功能模块正在开发中</text>
|
||||
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="card-header">
|
||||
<view class="btn-primary" @click="handleAdd">
|
||||
<text class="btn-txt">添加文章分类</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-name"><text class="th-txt">分类名称</text></view>
|
||||
<view class="th col-img"><text class="th-txt">分类图片</text></view>
|
||||
<view class="th col-status"><text class="th-txt">状态</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in categoryList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-name"><text class="td-txt">{{ item.name }}</text></view>
|
||||
<view class="td col-img">
|
||||
<view class="img-box-placeholder"></view>
|
||||
</view>
|
||||
<view class="td col-status">
|
||||
<view :class="['switch-mock', item.status ? 'active' : '']" @click="toggleStatus(item)">
|
||||
<view class="switch-handle"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-op">
|
||||
<view class="op-links">
|
||||
<text class="link-txt" @click="handleEdit(item)">编辑</text>
|
||||
<view class="divider"></view>
|
||||
<text class="link-txt danger">删除</text>
|
||||
<view class="divider"></view>
|
||||
<text class="link-txt">查看文章</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 侧边弹窗 (Drawer) -->
|
||||
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
|
||||
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
|
||||
<view class="drawer-header">
|
||||
<text class="drawer-title">添加分类</text>
|
||||
<text class="close-btn" @click="closeDrawer">×</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="drawer-body" :scroll-y="true">
|
||||
<view class="form-item row">
|
||||
<view class="label-box"><text class="label-txt">上级分类:</text></view>
|
||||
<view class="input-box">
|
||||
<view class="select-mock">
|
||||
<text class="select-val">顶级分类</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item row">
|
||||
<view class="label-box"><text class="required">*</text><text class="label-txt">分类名称:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="formName" placeholder="请输入分类名称" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item row align-start">
|
||||
<view class="label-box pt-10"><text class="required">*</text><text class="label-txt">分类简介:</text></view>
|
||||
<view class="input-box">
|
||||
<textarea class="textarea-mini" v-model="formDesc" placeholder="请输入分类简介" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item row">
|
||||
<view class="label-box"><text class="label-txt">分类图片:</text></view>
|
||||
<view class="input-box">
|
||||
<view class="upload-btn">
|
||||
<view class="img-icon">🖼️</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item row">
|
||||
<view class="label-box"><text class="label-txt">排序:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="formSort" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item row">
|
||||
<view class="label-box"><text class="label-txt">状态:</text></view>
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="formStatus = true">
|
||||
<view :class="['radio-circle', formStatus ? 'checked' : '']"><view v-if="formStatus" class="radio-in"></view></view>
|
||||
<text class="radio-la">显示</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="formStatus = false">
|
||||
<view :class="['radio-circle', !formStatus ? 'checked' : '']"><view v-if="!formStatus" class="radio-in"></view></view>
|
||||
<text class="radio-la">隐藏</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="drawer-footer">
|
||||
<view class="btn-cancel" @click="closeDrawer"><text class="cancel-txt">取消</text></view>
|
||||
<view class="btn-confirm" @click="handleConfirm"><text class="confirm-txt">确定</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -18,64 +138,129 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 文章分类 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
const filterKeyword = ref('')
|
||||
const categoryList = ref([
|
||||
{ id: '181', name: '购物心得', status: true },
|
||||
{ id: '180', name: '消费文化', status: true },
|
||||
{ id: '179', name: '品牌资讯', status: true }
|
||||
])
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const isClosing = ref(false)
|
||||
const formName = ref('')
|
||||
const formDesc = ref('')
|
||||
const formSort = ref(0)
|
||||
const formStatus = ref(true)
|
||||
|
||||
const handleAdd = () => {
|
||||
formName.value = ''
|
||||
formDesc.value = ''
|
||||
formSort.value = 0
|
||||
formStatus.value = true
|
||||
isClosing.value = false
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (item: any) => {
|
||||
formName.value = item.name
|
||||
// 模拟填充其他字段
|
||||
formDesc.value = ''
|
||||
formSort.value = 0
|
||||
formStatus.value = item.status
|
||||
isClosing.value = false
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const toggleStatus = (item: any) => {
|
||||
item.status = !item.status
|
||||
}
|
||||
|
||||
const closeDrawer = () => {
|
||||
isClosing.value = true
|
||||
setTimeout(() => {
|
||||
showDrawer.value = false
|
||||
isClosing.value = false
|
||||
}, 300)
|
||||
}
|
||||
const handleConfirm = () => { closeDrawer() }
|
||||
const handleQuery = () => { console.log('Querying...') }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.admin-cms-category { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }
|
||||
.border-shadow { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
|
||||
.filter-card { padding: 20px; display: flex; flex-direction: row; align-items: center; gap: 30px; margin-bottom: 20px; }
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; }
|
||||
.label-txt { font-size: 14px; color: #606266; margin-right: 12px; }
|
||||
.select-mock { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 0 15px; }
|
||||
.select-val { font-size: 13px; color: #c0c4cc; }
|
||||
.arrow-down { font-size: 10px; color: #999; }
|
||||
.search-input { width: 220px; height: 38px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 15px; font-size: 13px; }
|
||||
.btn-query { width: 76px; height: 34px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
.table-card { display: flex; flex-direction: column; }
|
||||
.card-header { padding: 20px; }
|
||||
.btn-primary { width: 140px; height: 36px; background-color: #2d8cf0; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
|
||||
.btn-txt { color: #fff; font-size: 13px; }
|
||||
.table-header { height: 50px; background-color: #eaf2ff; display: flex; flex-direction: row; align-items: center; }
|
||||
.th { padding: 0 15px; }
|
||||
.th-txt { font-size: 13px; color: #606266; font-weight: bold; }
|
||||
.table-row { height: 60px; display: flex; flex-direction: row; align-items: center; border-bottom: 1px solid #f0f0f0; }
|
||||
.td { padding: 0 15px; }
|
||||
.td-txt { font-size: 13px; color: #606266; }
|
||||
.col-id { width: 80px; justify-content: center; }
|
||||
.col-name { flex: 1; justify-content: flex-start; }
|
||||
.col-img { flex: 1; justify-content: center; }
|
||||
.col-status { width: 120px; justify-content: center; }
|
||||
.col-op { width: 220px; justify-content: center; }
|
||||
.img-box-placeholder { width: 40px; height: 40px; background-color: #f5f7fa; border-radius: 4px; }
|
||||
.switch-mock { width: 40px; height: 20px; background-color: #dcdfe6; border-radius: 10px; position: relative; transition: all 0.3s; }
|
||||
.switch-mock.active { background-color: #2d8cf0; }
|
||||
.switch-handle { width: 16px; height: 16px; background-color: #fff; border-radius: 8px; position: absolute; top: 2px; left: 2px; transition: all 0.3s; }
|
||||
.active .switch-handle { left: 22px; }
|
||||
.op-links { display: flex; flex-direction: row; align-items: center; }
|
||||
.link-txt { font-size: 13px; color: #2d8cf0; cursor: pointer; }
|
||||
.danger { color: #ed4014; }
|
||||
.divider { width: 1px; height: 12px; background-color: #e8eaec; margin: 0 10px; }
|
||||
.drawer-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2000; transition: opacity 0.3s; }
|
||||
.mask-fade-out { opacity: 0; }
|
||||
.drawer-content { position: absolute; top: 0; right: 0; width: 50%; height: 100%; background-color: #fff; display: flex; flex-direction: column; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.2); animation: slideIn 0.3s ease; }
|
||||
.slide-out { animation: slideOut 0.3s ease forwards; }
|
||||
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
||||
@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }
|
||||
.drawer-header { height: 60px; padding: 0 24px; border-bottom: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
|
||||
.drawer-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.close-btn { font-size: 24px; color: #999; cursor: pointer; padding: 5px; }
|
||||
.drawer-body { flex: 1; padding: 24px 30px; }
|
||||
.form-item { margin-bottom: 24px; display: flex; flex-direction: row; align-items: center; }
|
||||
.label-box { width: 120px; display: flex; justify-content: flex-end; align-items: center; margin-right: 20px; flex-shrink: 0; }
|
||||
.required { color: #ed4014; margin-right: 4px; }
|
||||
.label-txt { font-size: 14px; color: #606266; text-align: right; }
|
||||
.input-box { flex: 1; }
|
||||
.input-base { width: 100%; height: 38px; border: 1px solid #dcdee2; border-radius: 4px; padding: 0 12px; font-size: 14px; }
|
||||
.textarea-mini { width: 100%; height: 80px; border: 1px solid #dcdee2; border-radius: 4px; padding: 10px 12px; font-size: 14px; }
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
.upload-btn {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.img-icon { font-size: 30px; color: #ccc; }
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.radio-group { display: flex; flex-direction: row; gap: 30px; }
|
||||
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
|
||||
.radio-circle { width: 16px; height: 16px; border: 1px solid #dcdee2; border-radius: 50%; margin-right: 8px; display: flex; align-items: center; justify-content: center; }
|
||||
.radio-circle.checked { border-color: #2d8cf0; }
|
||||
.radio-in { width: 8px; height: 8px; background-color: #2d8cf0; border-radius: 50%; }
|
||||
.radio-la { font-size: 14px; color: #606266; }
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.drawer-footer { height: 60px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; padding: 0 24px; }
|
||||
.btn-cancel { padding: 8px 20px; border: 1px solid #dcdee2; border-radius: 4px; margin-right: 15px; }
|
||||
.btn-confirm { padding: 8px 20px; background-color: #2d8cf0; border-radius: 4px; }
|
||||
.cancel-txt { font-size: 14px; color: #606266; }
|
||||
.confirm-txt { font-size: 14px; color: #fff; }
|
||||
</style>
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="cs-auto-reply">
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-left">
|
||||
<text class="title">自动回复</text>
|
||||
<text class="subtitle">customer-service/auto-reply</text>
|
||||
</view>
|
||||
<view class="topbar-right">
|
||||
<view class="btn" @click="onBack"><text class="btn-text">返回</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h1">自动回复</text>
|
||||
<text class="p">这是页面骨架(可跑)。你可以在这里接入你们项目的 TopBar / Container 组件与业务逻辑。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const onBack = () => {
|
||||
// H5/小程序均可用
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef0f6;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f6;
|
||||
}
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.p {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="cs-config">
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-left">
|
||||
<text class="title">客服设置</text>
|
||||
<text class="subtitle">customer-service/config</text>
|
||||
</view>
|
||||
<view class="topbar-right">
|
||||
<view class="btn" @click="onBack"><text class="btn-text">返回</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h1">客服设置</text>
|
||||
<text class="p">这是页面骨架(可跑)。你可以在这里接入你们项目的 TopBar / Container 组件与业务逻辑。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const onBack = () => {
|
||||
// H5/小程序均可用
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef0f6;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f6;
|
||||
}
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.p {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout current-page='list'>
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-left">
|
||||
<text class="title">客服列表</text>
|
||||
<text class="subtitle">customer-service/list</text>
|
||||
</view>
|
||||
<view class="topbar-right">
|
||||
<view class="btn" @click="onBack"><text class="btn-text">返回</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h1">客服列表</text>
|
||||
<text class="p">这是页面骨架(可跑)。你可以在这里接入你们项目的 TopBar / Container 组件与业务逻辑。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const onBack = () => {
|
||||
// H5/小程序均可用
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef0f6;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f6;
|
||||
}
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.p {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="cs-message">
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-left">
|
||||
<text class="title">客服消息</text>
|
||||
<text class="subtitle">customer-service/messages</text>
|
||||
</view>
|
||||
<view class="topbar-right">
|
||||
<view class="btn" @click="onBack"><text class="btn-text">返回</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h1">客服消息</text>
|
||||
<text class="p">这是页面骨架(可跑)。你可以在这里接入你们项目的 TopBar / Container 组件与业务逻辑。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const onBack = () => {
|
||||
// H5/小程序均可用
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef0f6;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f6;
|
||||
}
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.p {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="cs-script">
|
||||
<view class="page">
|
||||
<view class="topbar">
|
||||
<view class="topbar-left">
|
||||
<text class="title">快捷回复话术</text>
|
||||
<text class="subtitle">customer-service/script</text>
|
||||
</view>
|
||||
<view class="topbar-right">
|
||||
<view class="btn" @click="onBack"><text class="btn-text">返回</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<text class="h1">快捷回复话术</text>
|
||||
<text class="p">这是页面骨架(可跑)。你可以在这里接入你们项目的 TopBar / Container 组件与业务逻辑。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const onBack = () => {
|
||||
// H5/小程序均可用
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #eef0f6;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eef0f6;
|
||||
}
|
||||
.h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.p {
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
385
pages/mall/admin/decoration/category.uvue
Normal file
385
pages/mall/admin/decoration/category.uvue
Normal file
@@ -0,0 +1,385 @@
|
||||
<template>
|
||||
<view class="admin-decoration-category">
|
||||
<!-- 顶部标题与按钮 -->
|
||||
<view class="page-header border-shadow">
|
||||
<view class="header-left">
|
||||
<text class="page-title">商品分类</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="btn-primary" @click="handleSave">
|
||||
<text class="btn-txt">保存</text>
|
||||
</view>
|
||||
<view class="btn-ghost" @click="handleReset">
|
||||
<text class="ghost-txt">重置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类展示区域 -->
|
||||
<view class="content-container">
|
||||
<view class="style-list">
|
||||
|
||||
<!-- 样式1 -->
|
||||
<view class="style-card-wrapper">
|
||||
<view :class="['style-card', selectedStyle === 1 ? 'active' : '']" @click="selectedStyle = 1">
|
||||
<view class="phone-mock">
|
||||
<view class="phone-header">
|
||||
<text class="p-title">产品分类</text>
|
||||
<text class="p-dots">••• Ⓞ</text>
|
||||
</view>
|
||||
<view class="phone-body">
|
||||
<view class="search-bar">
|
||||
<text class="ic-search">🔍</text>
|
||||
<text class="search-ph">点击搜索商品信息</text>
|
||||
</view>
|
||||
<view class="style1-content">
|
||||
<view class="sidebar-mock">
|
||||
<text class="sb-item active">精选水果</text>
|
||||
<text class="sb-item" v-for="name in ['肉制品','水产海鲜','米面粮油','厨房主食','新鲜蛋品','调味品','日配冷藏','豆制品']" :key="name">{{name}}</text>
|
||||
</view>
|
||||
<view class="main-mock">
|
||||
<view class="category-section">
|
||||
<view class="section-title"><text class="st-txt">精选水果</text></view>
|
||||
<view class="grid-container">
|
||||
<view class="grid-item" v-for="i in 6" :key="i">
|
||||
<view class="item-img-box"><text class="item-placeholder">🍐</text></view>
|
||||
<text class="item-txt">{{ ['精品香蕉','坚果优选','猕猴桃','大肉块','五花肉','鸡腿'][i-1] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="category-section">
|
||||
<view class="section-title"><text class="st-txt">肉制品</text></view>
|
||||
<view class="grid-container">
|
||||
<view class="grid-item" v-for="i in 3" :key="i">
|
||||
<view class="item-img-box"><text class="item-placeholder">🥩</text></view>
|
||||
<text class="item-txt">{{ ['大肉块','五花肉','鸡腿'][i-1] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="phone-tabbar">
|
||||
<view class="tb-item"><text class="tb-ic">🏠</text></view>
|
||||
<view class="tb-item active"><text class="tb-ic">📂</text></view>
|
||||
<view class="tb-item"><text class="tb-ic">🛒</text></view>
|
||||
<view class="tb-item"><text class="tb-ic">👤</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="style-name" :style="{color: selectedStyle === 1 ? '#2d8cf0' : '#666'}">样式1</text>
|
||||
</view>
|
||||
|
||||
<!-- 样式2 -->
|
||||
<view class="style-card-wrapper">
|
||||
<view :class="['style-card', selectedStyle === 2 ? 'active' : '']" @click="selectedStyle = 2">
|
||||
<view class="phone-mock">
|
||||
<view class="phone-header-img"></view>
|
||||
<view class="phone-header-v2">
|
||||
<text class="p2-title">分类</text>
|
||||
<view class="home-ic">🏠</view>
|
||||
</view>
|
||||
<view class="phone-body p2-body">
|
||||
<view class="search-bar-v2">
|
||||
<text class="ic-search">🔍</text>
|
||||
<text class="search-ph">点击搜索商品信息</text>
|
||||
</view>
|
||||
<view class="tabs-v2">
|
||||
<text class="t2-item active">水果</text>
|
||||
<text class="t2-item">全部</text>
|
||||
<text class="t2-item">热带水果</text>
|
||||
<text class="t2-item">西瓜葡萄</text>
|
||||
<text class="t2-arrow">▼</text>
|
||||
</view>
|
||||
<view class="style2-content">
|
||||
<view class="sidebar-v2">
|
||||
<text class="s2-item active">乳品</text>
|
||||
<text class="s2-item">午间零食</text>
|
||||
<text class="s2-item">新鲜蔬菜</text>
|
||||
<text class="s2-item">美妆护肤</text>
|
||||
<text class="s2-item">宠物用品</text>
|
||||
<text class="s2-item">户外玩具</text>
|
||||
</view>
|
||||
<view class="main-v2">
|
||||
<view class="banner-mock-v2">
|
||||
<text class="b-txt">深层 V8 高清直屏\n双镜头/VR科技体验</text>
|
||||
</view>
|
||||
<view class="prod-v2" v-for="i in 2" :key="i">
|
||||
<text class="p-name">Haier/海尔 BCD-216STPT 时尚静音冰箱 三门出门租家用小型电冰箱</text>
|
||||
<view class="p-price-row">
|
||||
<text class="p-price">¥999.00</text>
|
||||
<text class="p-sales">已售 92</text>
|
||||
<view class="btn-buy"><text class="buy-txt">立即购买</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cart-badge">🛒<text class="badge-num">7</text></view>
|
||||
<view class="footer-p2">
|
||||
<text class="f2-total">¥999.00</text>
|
||||
<view class="btn-settle"><text class="settle-txt">去结算</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="style-name" :style="{color: selectedStyle === 2 ? '#2d8cf0' : '#666'}">样式2</text>
|
||||
</view>
|
||||
|
||||
<!-- 样式3 -->
|
||||
<view class="style-card-wrapper">
|
||||
<view :class="['style-card', selectedStyle === 3 ? 'active' : '']" @click="selectedStyle = 3">
|
||||
<view class="phone-mock">
|
||||
<view class="phone-header">
|
||||
<text class="p-title">产品分类</text>
|
||||
</view>
|
||||
<view class="phone-body">
|
||||
<view class="search-bar-v3">
|
||||
<view class="home-btn">🏠</view>
|
||||
<view class="search-input-v3">
|
||||
<text class="ic-search">🔍</text>
|
||||
<text class="search-ph">搜索商品</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tabs-v3">
|
||||
<text class="t3-item active">水果</text>
|
||||
<text class="t3-item specialty">时时生鲜</text>
|
||||
<text class="t3-item">休闲零食</text>
|
||||
<text class="t3-item">坚果蜜饯</text>
|
||||
<text class="t3-arrow">∨</text>
|
||||
</view>
|
||||
<view class="style3-content">
|
||||
<view class="sidebar-v3">
|
||||
<text class="s3-item active">乳品</text>
|
||||
<text class="s3-item">午间零食</text>
|
||||
<text class="s3-item">新鲜蔬菜</text>
|
||||
<text class="s3-item">特惠专区</text>
|
||||
<text class="s3-item">大闸蟹</text>
|
||||
<text class="s3-item">精选礼盒</text>
|
||||
</view>
|
||||
<view class="main-v3">
|
||||
<view class="prod-v3" v-for="i in 5" :key="i">
|
||||
<view class="pv-img"></view>
|
||||
<view class="pv-info">
|
||||
<text class="pv-name">【橙中爱马仕】果际新骑士晚季甜橙10个单装</text>
|
||||
<text class="pv-price">¥25.99</text>
|
||||
</view>
|
||||
<view class="pv-add-box">
|
||||
<text class="pv-add">🛒</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cart-v3">
|
||||
<view class="c3-ic-box">🛒<text class="c3-badge">7</text></view>
|
||||
<text class="c3-price">¥999.00</text>
|
||||
<view class="btn-settle-v3"><text class="settle-txt">去结算</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="style-name" :style="{color: selectedStyle === 3 ? '#2d8cf0' : '#666'}">样式3</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedStyle = ref(1)
|
||||
|
||||
const handleSave = () => {
|
||||
console.log('Saving classification style:', selectedStyle.value)
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
selectedStyle.value = 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-decoration-category {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
height: 60px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.page-title { font-size: 16px; font-weight: 600; color: #303133; }
|
||||
|
||||
.header-right { display: flex; flex-direction: row; gap: 12px; }
|
||||
|
||||
.btn-primary, .btn-ghost {
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary { background-color: #2d8cf0; }
|
||||
.btn-ghost { border: 1px solid #dcdfe6; position: relative; }
|
||||
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
.ghost-txt { color: #606266; font-size: 14px; }
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
padding: 30px 40px;
|
||||
}
|
||||
|
||||
.style-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.style-card-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.style-card {
|
||||
width: 320px;
|
||||
height: 640px;
|
||||
background-color: #fff;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.style-card.active {
|
||||
border-color: #2d8cf0;
|
||||
box-shadow: 0 4px 20px rgba(45, 140, 240, 0.2);
|
||||
}
|
||||
|
||||
.style-name { font-size: 14px; font-weight: bold; }
|
||||
|
||||
/* Phone Mockup Common */
|
||||
.phone-mock { width: 100%; height: 100%; display: flex; flex-direction: column; background-color: #fff; }
|
||||
.phone-header { height: 44px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 0 15px; border-bottom: 1px solid #eee; }
|
||||
.p-title { font-size: 15px; font-weight: bold; color: #333; }
|
||||
.p-dots { font-size: 12px; color: #333; }
|
||||
|
||||
.phone-body { flex: 1; background-color: #f8f8f8; display: flex; flex-direction: column; overflow: hidden; }
|
||||
|
||||
/* Style 1 Specific */
|
||||
.search-bar { height: 32px; background-color: #f2f2f2; margin: 10px; border-radius: 16px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; }
|
||||
.ic-search { font-size: 12px; margin-right: 6px; color: #999; }
|
||||
.search-ph { font-size: 11px; color: #999; }
|
||||
|
||||
.style1-content { flex: 1; display: flex; flex-direction: row; }
|
||||
.sidebar-mock { width: 80px; background-color: #f7f7f7; display: flex; flex-direction: column; }
|
||||
.sb-item { height: 44px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #666; }
|
||||
.sb-item.active { background-color: #fff; color: #f2270c; font-weight: bold; position: relative; }
|
||||
.sb-item.active::before { content: ''; position: absolute; left: 0; top: 15px; height: 14px; width: 3px; background-color: #f2270c; }
|
||||
|
||||
.main-mock { flex: 1; background-color: #fff; padding: 10px; }
|
||||
.section-title { margin-bottom: 10px; }
|
||||
.st-txt { font-size: 12px; font-weight: bold; color: #333; }
|
||||
.grid-container { display: flex; flex-direction: row; flex-wrap: wrap; gap: 10px; }
|
||||
.grid-item { width: 68px; display: flex; flex-direction: column; align-items: center; margin-bottom: 10px; }
|
||||
.item-img-box { width: 50px; height: 50px; background-color: #f5f5f5; border-radius: 4px; display: flex; align-items: center; justify-content: center; margin-bottom: 4px; }
|
||||
.item-placeholder { font-size: 24px; }
|
||||
.item-txt { font-size: 10px; color: #666; }
|
||||
|
||||
.phone-tabbar { height: 48px; display: flex; flex-direction: row; border-top: 1px solid #eee; }
|
||||
.tb-item { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.tb-ic { font-size: 20px; color: #999; }
|
||||
.tb-item.active .tb-ic { color: #f2270c; }
|
||||
|
||||
/* Style 2 Specific */
|
||||
.phone-header-img { height: 20px; background-color: #fff; }
|
||||
.phone-header-v2 { height: 34px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 0 12px; }
|
||||
.p2-title { font-size: 14px; font-weight: bold; flex: 1; text-align: center; }
|
||||
.home-ic { font-size: 16px; margin-right: 20px;}
|
||||
|
||||
.search-bar-v2 { height: 30px; background-color: #fff; margin: 8px 12px; border-radius: 15px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; border: 1px solid #eee; }
|
||||
.tabs-v2 { height: 40px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; gap: 15px; }
|
||||
.t2-item { font-size: 12px; color: #333; }
|
||||
.t2-item.active { color: #f2270c; font-weight: bold; border-bottom: 2px solid #f2270c; }
|
||||
.t2-arrow { font-size: 10px; color: #999; }
|
||||
|
||||
.style2-content { flex: 1; display: flex; flex-direction: row; }
|
||||
.sidebar-v2 { width: 70px; background-color: #f7f7f7; }
|
||||
.s2-item { height: 48px; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #333; }
|
||||
.s2-item.active { background-color: #fff; font-weight: bold; }
|
||||
|
||||
.main-v2 { flex: 1; background-color: #fff; padding: 10px; }
|
||||
.banner-mock-v2 { height: 80px; background-color: #0081ff; border-radius: 6px; padding: 10px; display: flex; align-items: center; margin-bottom: 12px; }
|
||||
.b-txt { font-size: 12px; color: #fff; font-weight: bold; line-height: 1.4; }
|
||||
|
||||
.prod-v2 { border-bottom: 1px solid #f5f5f5; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.p-name { font-size: 11px; color: #333; line-height: 1.3; margin-bottom: 8px; max-height: 28px; overflow: hidden; }
|
||||
.p-price-row { display: flex; flex-direction: row; align-items: center; }
|
||||
.p-price { font-size: 12px; color: #f2270c; font-weight: bold; margin-right: 6px; }
|
||||
.p-sales { font-size: 9px; color: #999; flex: 1; }
|
||||
.btn-buy { background-color: #f2270c; padding: 4px 8px; border-radius: 10px; }
|
||||
.buy-txt { font-size: 9px; color: #fff; }
|
||||
|
||||
.cart-badge { position: absolute; bottom: 65px; left: 15px; width: 34px; height: 34px; background-color: #fff; border-radius: 17px; display: flex; align-items: center; justify-content: center; font-size: 18px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
|
||||
.badge-num { position: absolute; top: 0; right: 0; background-color: #f2270c; color: #fff; font-size: 9px; width: 14px; height: 14px; border-radius: 7px; text-align: center; }
|
||||
|
||||
.footer-p2 { height: 50px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 0 15px; border-top: 1px solid #eee; }
|
||||
.f2-total { font-size: 16px; color: #f2270c; font-weight: bold; }
|
||||
.btn-settle { background-color: #f2270c; padding: 6px 16px; border-radius: 18px; }
|
||||
.settle-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* Style 3 Specific */
|
||||
.search-bar-v3 { height: 44px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; gap: 10px; background-color: #fff; }
|
||||
.home-btn { font-size: 16px; }
|
||||
.search-input-v3 { flex: 1; height: 30px; background-color: #f5f5f5; border-radius: 15px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; }
|
||||
|
||||
.tabs-v3 { height: 40px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; gap: 12px; background-color: #fff; }
|
||||
.t3-item { font-size: 12px; color: #666; white-space: nowrap; }
|
||||
.t3-item.active { color: #f2270c; font-weight: bold; font-size: 14px; }
|
||||
.t3-item.specialty { background-color: #f2270c; color: #fff; padding: 2px 8px; border-radius: 10px; }
|
||||
.t3-arrow { font-size: 12px; color: #ccc; flex: 1; text-align: right; }
|
||||
|
||||
.style3-content { flex: 1; display: flex; flex-direction: row; background-color: #fff; }
|
||||
.sidebar-v3 { width: 75px; background-color: #f7f7f7; }
|
||||
.s3-item { height: 50px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #666; }
|
||||
.s3-item.active { background-color: #fff; color: #333; font-weight: bold; position: relative; }
|
||||
.s3-item.active::before { content: ''; position: absolute; left: 0; top: 18px; height: 14px; width: 3px; background-color: #f2270c; }
|
||||
|
||||
.main-v3 { flex: 1; padding: 10px; }
|
||||
.prod-v3 { display: flex; flex-direction: row; margin-bottom: 12px; position: relative; }
|
||||
.pv-img { width: 70px; height: 70px; background-color: #f5f5f5; border-radius: 4px; margin-right: 10px; }
|
||||
.pv-info { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
|
||||
.pv-name { font-size: 11px; color: #333; line-height: 1.3; overflow: hidden; max-height: 30px; }
|
||||
.pv-price { font-size: 13px; color: #f2270c; font-weight: bold; }
|
||||
.pv-add-box { position: absolute; right: 0; bottom: 0; }
|
||||
.pv-add { font-size: 18px; color: #f2270c; }
|
||||
|
||||
.cart-v3 { height: 50px; display: flex; flex-direction: row; align-items: center; padding: 0 12px; border-top: 1px solid #eee; position: relative; }
|
||||
.c3-ic-box { font-size: 24px; color: #f2270c; margin-right: 10px; position: relative; margin-top: -15px;}
|
||||
.c3-badge { position: absolute; top: 0; right: -5px; background-color: #f2270c; color: #fff; font-size: 9px; width: 14px; height: 14px; border-radius: 7px; text-align: center; }
|
||||
.c3-price { font-size: 14px; color: #f2270c; font-weight: bold; flex: 1; }
|
||||
.btn-settle-v3 { background-color: #f2270c; padding: 6px 20px; border-radius: 20px; }
|
||||
</style>
|
||||
725
pages/mall/admin/decoration/home.uvue
Normal file
725
pages/mall/admin/decoration/home.uvue
Normal file
@@ -0,0 +1,725 @@
|
||||
<template>
|
||||
<view class="admin-decoration-home">
|
||||
<view class="content-container">
|
||||
<!-- 左侧:手机预览区 -->
|
||||
<view class="preview-section border-shadow">
|
||||
<view class="phone-mock">
|
||||
<view class="phone-inner">
|
||||
<view class="phone-header-img">
|
||||
<view class="status-bar-mock"></view>
|
||||
<view class="search-bar-mock">
|
||||
<text class="search-ic">🔍</text>
|
||||
<text class="search-ph">请输入搜索词</text>
|
||||
</view>
|
||||
<view class="tabs-mock">
|
||||
<text class="tab-item active">首页</text>
|
||||
<text class="tab-item">生活家居</text>
|
||||
<text class="tab-item">运动专区</text>
|
||||
<text class="tab-item">电子产品</text>
|
||||
<text class="tab-item">家用电器</text>
|
||||
<text class="tab-more">≡</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="phone-scroll" scroll-y="true">
|
||||
<!-- Banner -->
|
||||
<view class="banner-mock">
|
||||
<view class="banner-box">
|
||||
<text class="banner-txt">MUSE FOR ALL MOTHERS</text>
|
||||
</view>
|
||||
<view class="dot-box">
|
||||
<view class="dot active"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Grid Menu -->
|
||||
<view class="grid-menu-mock">
|
||||
<view class="menu-item" v-for="i in 10" :key="i">
|
||||
<view :class="['menu-icon', 'ic-'+i]"></view>
|
||||
<text class="menu-txt">{{ menuNames[i-1] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Announcement -->
|
||||
<view class="notice-mock">
|
||||
<view class="notice-ic">📢</view>
|
||||
<text class="notice-txt">CRMEB 年中618活动开启进行中!</text>
|
||||
<text class="notice-arr">></text>
|
||||
</view>
|
||||
|
||||
<!-- Check-in Section -->
|
||||
<view class="checkin-mock">
|
||||
<view class="checkin-days">
|
||||
<view class="day-dot" v-for="i in 7" :key="i">
|
||||
<view class="dot-circle">⭐</view>
|
||||
<text class="dot-text">周{{ weekDays[i-1] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-checkin"><text class="check-txt">签到</text></view>
|
||||
</view>
|
||||
|
||||
<!-- Bottom Space -->
|
||||
<view style="height: 100px;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- Bottom TabBar -->
|
||||
<view class="tabbar-mock">
|
||||
<view class="tb-item active">
|
||||
<text class="tb-ic">🏠</text>
|
||||
<text class="tb-txt">首页</text>
|
||||
</view>
|
||||
<view class="tb-item">
|
||||
<text class="tb-ic">📂</text>
|
||||
<text class="tb-txt">分类</text>
|
||||
</view>
|
||||
<view class="tb-item">
|
||||
<text class="tb-ic">🛒</text>
|
||||
<text class="tb-txt">购物车</text>
|
||||
</view>
|
||||
<view class="tb-item">
|
||||
<text class="tb-ic">👤</text>
|
||||
<text class="tb-txt">我的</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:列表管理区 -->
|
||||
<view class="list-section">
|
||||
<view class="manage-card border-shadow">
|
||||
<view class="action-bar">
|
||||
<view class="btn-primary-blue mr-10" @click="handleAdd">
|
||||
<text class="btn-txt">添加页面</text>
|
||||
</view>
|
||||
<view class="btn-import-blue" @click="handleImport">
|
||||
<text class="btn-txt">导入模板</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表格 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="width: 80px;">页面ID</view>
|
||||
<view class="th" style="flex: 2;">模板名称</view>
|
||||
<view class="th" style="flex: 1;">模板类型</view>
|
||||
<view class="th" style="flex: 2;">添加时间</view>
|
||||
<view class="th" style="flex: 2;">更新时间</view>
|
||||
<view class="th" style="width: 280px;">操作</view>
|
||||
</view>
|
||||
|
||||
<view v-for="(item, index) in tableData" :key="index" class="table-body-row">
|
||||
<view class="td" style="width: 80px;">{{ item.id }}</view>
|
||||
<view class="td" style="flex: 2;">{{ item.name }}</view>
|
||||
<view class="td" style="flex: 1;">
|
||||
<view :class="['type-tag', item.type === '首页' ? 'type-home' : 'type-topic']">
|
||||
<text class="tag-label">{{ item.type }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td" style="flex: 2;">{{ item.addTime }}</view>
|
||||
<view class="td" style="flex: 2;">{{ item.updateTime }}</view>
|
||||
<view class="td" style="width: 280px;">
|
||||
<view class="op-links">
|
||||
<text class="op-link" @click="handleEdit(item)">编辑</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link text-danger">删除</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link">预览</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link" v-if="item.type !== '首页'">设为首页</text>
|
||||
<text class="op-split" v-if="item.type !== '首页'">|</text>
|
||||
<text class="op-link">导出模板</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<view class="pagination-footer">
|
||||
<view class="page-total">
|
||||
<text class="total-txt">共 {{ total }} 条</text>
|
||||
</view>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" value="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加页面侧边栏 -->
|
||||
<view v-if="showDrawer" :class="['drawer-mask', isClosing ? 'mask-fade-out' : '']" @click="closeDrawer">
|
||||
<view :class="['drawer-content', isClosing ? 'slide-out' : '']" @click.stop="">
|
||||
<view class="drawer-header">
|
||||
<text class="title-txt">添加页面</text>
|
||||
<text class="close-btn" @click="closeDrawer">×</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="drawer-body" :scroll-y="true">
|
||||
<view class="form-item-v">
|
||||
<text class="v-label">页面名称</text>
|
||||
<input class="v-input" v-model="formName" placeholder="请填写页面名称" />
|
||||
</view>
|
||||
|
||||
<view class="form-item-v">
|
||||
<text class="v-label">页面类型</text>
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="formType = '首页'">
|
||||
<view :class="['radio-dot', formType === '首页' ? 'active' : '']"></view>
|
||||
<text class="radio-txt">首页</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="formType = '专题页'">
|
||||
<view :class="['radio-dot', formType === '专题页' ? 'active' : '']"></view>
|
||||
<text class="radio-txt">专题页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="template-select-title">
|
||||
<text class="t-title">选择模板</text>
|
||||
<text class="t-sub">请选择要引用的模板</text>
|
||||
</view>
|
||||
|
||||
<view class="template-grid">
|
||||
<view class="tpl-item" v-for="i in 4" :key="i">
|
||||
<view class="tpl-thumb">
|
||||
<text class="tpl-ic">📄</text>
|
||||
</view>
|
||||
<text class="tpl-name">通用模板 {{ i }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="drawer-footer">
|
||||
<view class="btn-cancel" @click="closeDrawer">
|
||||
<text class="btn-cancel-txt">取消</text>
|
||||
</view>
|
||||
<view class="btn-save" @click="handleSavePage">
|
||||
<text class="btn-save-txt">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const menuNames = ['秒杀活动', '商品分类', '拼团活动', '积分商城', '砍价中心', '行业资讯', '我的地址', '积分抽奖', '我的账户', '订单列表']
|
||||
const weekDays = ['一', '二', '三', '四', '五', '六', '日']
|
||||
|
||||
const total = ref(11)
|
||||
const tableData = ref([
|
||||
{ id: 497, name: 'DIY导入数据', type: '专题页', addTime: '2025-03-20 15:18:01', updateTime: '2025-05-21 10:17:45' },
|
||||
{ id: 496, name: 'DIY导入数据', type: '专题页', addTime: '2025-03-20 15:12:58', updateTime: '2025-03-20 15:12:58' },
|
||||
{ id: 494, name: '图书类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:42:08', updateTime: '2025-03-19 10:40:13' },
|
||||
{ id: 493, name: '健康类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:40:55', updateTime: '2025-03-07 09:46:14' },
|
||||
{ id: 492, name: '演出类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:33:09', updateTime: '2025-03-07 09:49:43' },
|
||||
{ id: 491, name: '潮玩类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:31:28', updateTime: '2025-03-07 09:55:53' },
|
||||
{ id: 490, name: '家居类模板,勿动!!', type: '专题页', addTime: '2025-02-27 15:30:21', updateTime: '2025-03-07 09:57:59' },
|
||||
{ id: 482, name: '文具类模板,勿动!!', type: '专题页', addTime: '2025-02-26 11:32:07', updateTime: '2025-03-07 09:59:25' },
|
||||
{ id: 481, name: '模板', type: '专题页', addTime: '2025-02-26 09:21:04', updateTime: '2025-03-12 14:55:46' },
|
||||
{ id: 480, name: '模板', type: '专题页', addTime: '2025-02-26 09:19:24', updateTime: '2026-02-02 17:11:45' },
|
||||
{ id: 479, name: '首页模板,勿动!!', type: '首页', addTime: '2025-02-25 20:59:59', updateTime: '2026-01-20 11:16:20' }
|
||||
])
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const isClosing = ref(false)
|
||||
const formName = ref('')
|
||||
const formType = ref('首页')
|
||||
|
||||
const viewState = ref('list') // 'list' | 'design'
|
||||
const editingName = ref('')
|
||||
|
||||
const handleAdd = () => {
|
||||
showDrawer.value = true
|
||||
isClosing.value = false
|
||||
}
|
||||
|
||||
const closeDrawer = () => {
|
||||
isClosing.value = true
|
||||
setTimeout(() => {
|
||||
showDrawer.value = false
|
||||
isClosing.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const handleEdit = (item: any) => {
|
||||
editingName.value = item.name as string
|
||||
viewState.value = 'design'
|
||||
}
|
||||
|
||||
const handleImport = () => { console.log('Importing...') }
|
||||
|
||||
const handleSavePage = () => {
|
||||
console.log('Saving new page:', formName.value)
|
||||
closeDrawer()
|
||||
}
|
||||
|
||||
const handleSaveDesign = () => {
|
||||
console.log('Saving design...')
|
||||
viewState.value = 'list'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-decoration-home {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 左侧手机预览区 */
|
||||
.preview-section {
|
||||
width: 380px;
|
||||
height: 800px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.phone-mock {
|
||||
width: 320px;
|
||||
height: 640px;
|
||||
background-color: #fff;
|
||||
border: 10px solid #ececec;
|
||||
border-radius: 36px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.phone-inner { flex: 1; display: flex; flex-direction: column; }
|
||||
|
||||
.phone-header-img {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.status-bar-mock { height: 20px; }
|
||||
|
||||
.search-bar-mock {
|
||||
height: 38px;
|
||||
background-color: #fff;
|
||||
margin: 0 12px;
|
||||
border-radius: 19px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.search-ic { font-size: 14px; margin-right: 8px; }
|
||||
.search-ph { font-size: 12px; color: #999; }
|
||||
|
||||
.tabs-mock {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.tab-item { font-size: 13px; color: #333; margin-right: 15px; }
|
||||
.tab-item.active { color: #f2270c; font-weight: bold; border-bottom: 2px solid #f2270c; }
|
||||
.tab-more { font-size: 16px; color: #666; }
|
||||
|
||||
.phone-scroll { flex: 1; background-color: #f8f8f8; }
|
||||
|
||||
.banner-mock {
|
||||
height: 150px;
|
||||
position: relative;
|
||||
margin: 10px;
|
||||
}
|
||||
.banner-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #eee 0%, #ccc 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.banner-txt { font-size: 18px; font-weight: bold; color: #333; text-align: center; }
|
||||
.dot-box {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.dot { width: 6px; height: 6px; background-color: rgba(255,255,255,0.5); border-radius: 3px; }
|
||||
.dot.active { width: 12px; background-color: #fff; }
|
||||
|
||||
.grid-menu-mock {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
.menu-item {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.menu-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #ddd;
|
||||
border-radius: 20px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.ic-1 { background-color: #ff9d00; }
|
||||
.ic-2 { background-color: #ff5000; }
|
||||
.ic-3 { background-color: #8a2be2; }
|
||||
.ic-4 { background-color: #f4ea2a; }
|
||||
.ic-5 { background-color: #ffb6c1; }
|
||||
.ic-6 { background-color: #c0c0c0; }
|
||||
.ic-7 { background-color: #90ee90; }
|
||||
.ic-8 { background-color: #87cefa; }
|
||||
.ic-9 { background-color: #ffa07a; }
|
||||
.ic-10 { background-color: #20b2aa; }
|
||||
|
||||
.menu-txt { font-size: 10px; color: #666; }
|
||||
|
||||
.notice-mock {
|
||||
height: 36px;
|
||||
background-color: #fff;
|
||||
margin: 0 10px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.notice-ic { font-size: 14px; margin-right: 8px; }
|
||||
.notice-txt { flex: 1; font-size: 12px; color: #333; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.notice-arr { color: #ccc; font-size: 12px; }
|
||||
|
||||
.checkin-mock {
|
||||
margin: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.checkin-days { display: flex; flex-direction: row; gap: 8px; }
|
||||
.day-dot { display: flex; flex-direction: column; align-items: center; }
|
||||
.dot-circle { width: 24px; height: 24px; background-color: #fdf6ec; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 12px; margin-bottom: 4px; }
|
||||
.dot-text { font-size: 9px; color: #999; }
|
||||
.btn-checkin { background-color: #ff5000; padding: 4px 12px; border-radius: 12px; }
|
||||
.check-txt { color: #fff; font-size: 11px; }
|
||||
|
||||
.tabbar-mock {
|
||||
height: 50px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tb-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
||||
.tb-ic { font-size: 18px; margin-bottom: 2px; }
|
||||
.tb-txt { font-size: 11px; color: #999; }
|
||||
.tb-item.active .tb-txt { color: #f2270c; }
|
||||
|
||||
/* 右侧列表管理区 */
|
||||
.list-section { flex: 1; }
|
||||
.manage-card { display: flex; flex-direction: column; min-height: 800px; }
|
||||
|
||||
.action-bar { padding: 20px; display: flex; flex-direction: row; }
|
||||
.btn-primary-blue { background-color: #2d8cf0; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-import-blue { border: 1px solid #1890ff; padding: 7px 16px; border-radius: 4px; cursor: pointer; }
|
||||
.mr-10 { margin-right: 10px; }
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
.btn-import-blue .btn-txt { color: #1890ff; }
|
||||
|
||||
.table-container { flex: 1; padding: 0 20px; }
|
||||
.table-header-row { display: flex; flex-direction: row; background-color: #f8f8f9; border-bottom: 1px solid #e8eaec; }
|
||||
.th { padding: 12px 10px; font-size: 14px; color: #515a6e; font-weight: bold; }
|
||||
.table-body-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; }
|
||||
.td { padding: 15px 10px; font-size: 14px; color: #515a6e; display: flex; align-items: center; }
|
||||
|
||||
.type-tag { padding: 2px 8px; border-radius: 4px; border: 1px solid #dcdfe6; }
|
||||
.type-topic { background-color: #f5f7fa; }
|
||||
.type-home { background-color: #f6ffed; border-color: #b7eb8f; }
|
||||
.tag-label { font-size: 12px; }
|
||||
.type-home .tag-label { color: #52c41a; }
|
||||
|
||||
.op-links { display: flex; flex-direction: row; align-items: center; color: #2d8cf0; }
|
||||
.op-link { cursor: pointer; margin: 0 5px; }
|
||||
.op-split { color: #e8eaec; }
|
||||
.text-danger { color: #ed4014; }
|
||||
|
||||
.pagination-footer {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; }
|
||||
|
||||
/* Design View Styles */
|
||||
.design-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
.design-header {
|
||||
height: 60px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 2px solid #2d8cf0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
|
||||
.back-ic { font-size: 20px; color: #2d8cf0; margin-right: 15px; }
|
||||
.design-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
|
||||
.header-right { display: flex; flex-direction: row; gap: 12px; }
|
||||
|
||||
.btn-ghost { border: 1px solid #dcdfe6; padding: 6px 16px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-primary { background-color: #2d8cf0; padding: 6px 16px; border-radius: 4px; cursor: pointer; }
|
||||
.ghost-txt { color: #666; font-size: 14px; }
|
||||
.primary-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.design-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.design-sidebar { width: 280px; background-color: #fff; padding: 15px; border-right: 1px solid #f0f0f0; }
|
||||
.sidebar-item {
|
||||
width: 110px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
float: left;
|
||||
}
|
||||
.side-ic-box { width: 40px; height: 40px; background-color: #f7f8fa; display: flex; align-items: center; justify-content: center; border-radius: 4px; margin-bottom: 8px; font-size: 20px; }
|
||||
.side-txt { font-size: 12px; color: #666; }
|
||||
|
||||
.design-canvas {
|
||||
flex: 1;
|
||||
background-color: #f0f2f5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 30px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.canvas-phone {
|
||||
width: 375px;
|
||||
min-height: 667px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.phone-top { height: 60px; background-color: #fff; border-bottom: 1px solid #eee; }
|
||||
|
||||
.phone-content-mock {
|
||||
padding: 100px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.mock-tip { color: #999; font-size: 14px; }
|
||||
|
||||
.design-attr { width: 320px; background-color: #fff; border-left: 1px solid #f0f0f0; }
|
||||
.attr-header { padding: 15px; border-bottom: 1px solid #f0f0f0; }
|
||||
.ah-txt { font-size: 15px; font-weight: bold; }
|
||||
.attr-empty { padding: 50px 20px; text-align: center; }
|
||||
.ae-txt { color: #999; font-size: 13px; }
|
||||
|
||||
.anim-fade-in {
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Drawer Styles */
|
||||
.drawer-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
width: 450px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-txt { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.close-btn { font-size: 24px; color: #999; cursor: pointer; }
|
||||
|
||||
.drawer-body { flex: 1; padding: 20px; }
|
||||
|
||||
.form-item-v { margin-bottom: 24px; }
|
||||
.v-label { font-size: 14px; color: #666; margin-bottom: 10px; display: block; }
|
||||
.v-input { border: 1px solid #dcdfe6; height: 40px; padding: 0 12px; border-radius: 4px; font-size: 14px; width: 100%; }
|
||||
|
||||
.radio-group { display: flex; flex-direction: row; gap: 30px; }
|
||||
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
|
||||
.radio-dot { width: 16px; height: 16px; border: 1px solid #dcdfe6; border-radius: 8px; margin-right: 8px; position: relative; }
|
||||
.radio-dot.active { border-color: #2d8cf0; }
|
||||
.radio-dot.active::after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
}
|
||||
.radio-txt { font-size: 14px; color: #333; }
|
||||
|
||||
.template-select-title { margin-top: 20px; margin-bottom: 15px; }
|
||||
.t-title { font-size: 15px; font-weight: bold; color: #333; margin-right: 10px; }
|
||||
.t-sub { font-size: 12px; color: #999; }
|
||||
|
||||
.template-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
.tpl-item {
|
||||
width: 190px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tpl-thumb {
|
||||
height: 220px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.tpl-ic { font-size: 40px; color: #ccc; }
|
||||
.tpl-name { font-size: 12px; color: #666; text-align: center; display: block; }
|
||||
|
||||
.drawer-footer {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn-cancel, .btn-save { padding: 8px 20px; border-radius: 4px; cursor: pointer; }
|
||||
.btn-cancel { border: 1px solid #dcdfe6; }
|
||||
.btn-save { background-color: #2d8cf0; }
|
||||
.btn-cancel-txt { color: #666; font-size: 14px; }
|
||||
.btn-save-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* Animations */
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
.slide-out {
|
||||
animation: slideOut 0.3s ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
.mask-fade-out {
|
||||
animation: fadeOut 0.3s ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from { background-color: rgba(0, 0, 0, 0.4); }
|
||||
to { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
566
pages/mall/admin/decoration/user.uvue
Normal file
566
pages/mall/admin/decoration/user.uvue
Normal file
@@ -0,0 +1,566 @@
|
||||
<template>
|
||||
<view class="admin-decoration-user">
|
||||
<!-- 顶部标题与保存按钮 -->
|
||||
<view class="page-header border-shadow">
|
||||
<view class="header-left">
|
||||
<text class="page-title">个人中心</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="btn-primary" @click="handleSave">
|
||||
<text class="btn-txt">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区 -->
|
||||
<view class="content-container anim-fade-in">
|
||||
<view class="main-card border-shadow">
|
||||
<!-- 左侧:手机预览 -->
|
||||
<view class="preview-panel">
|
||||
<view class="phone-mockup">
|
||||
<scroll-view class="phone-body" :scroll-y="true">
|
||||
|
||||
<!-- 样式1 & 样式2 头部 -->
|
||||
<view v-if="selectedStyle === 1 || selectedStyle === 2" class="user-header-gradient">
|
||||
<view class="header-top">
|
||||
<view class="avatar-box">
|
||||
<image class="avatar-img" src="/static/logo.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">用户名称用户名称</text>
|
||||
<view class="bind-phone">
|
||||
<text class="bind-txt">绑定手机号 ></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-icons">
|
||||
<view class="ic-msg">🔔<text class="msg-dot">6</text></view>
|
||||
<view class="ic-set">⚙️</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-row">
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">0.00</text>
|
||||
<text class="stat-label">我的余额</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">65749</text>
|
||||
<text class="stat-label">当前积分</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val">25</text>
|
||||
<text class="stat-label">优惠券</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式1 会员卡 -->
|
||||
<view v-if="selectedStyle === 1" class="member-card-s1">
|
||||
<view class="mc-content-s1">
|
||||
<view class="mc-left">
|
||||
<text class="mc-ic">👑</text>
|
||||
<text class="mc-txt">会员到期 2022-12-31</text>
|
||||
</view>
|
||||
<view class="mc-right">
|
||||
<text class="mc-btn">立即续费 ></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式2 会员卡 -->
|
||||
<view v-if="selectedStyle === 2" class="member-card-s2">
|
||||
<view class="mc-content-s2">
|
||||
<view class="mc-left">
|
||||
<text class="mc-ic">👑</text>
|
||||
<view class="mc-info-col">
|
||||
<text class="mc-t1">会员可享多项权益</text>
|
||||
<text class="mc-t2">会员剩余360天</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mc-right">
|
||||
<text class="mc-btn-white">立即续费</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式3 头部 -->
|
||||
<view v-if="selectedStyle === 3" class="user-header-s3">
|
||||
<view class="header-top-s3">
|
||||
<view class="header-top-left">
|
||||
<view class="avatar-box-s3">
|
||||
<image class="avatar-img" src="/static/logo.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="user-info-s3">
|
||||
<text class="user-name-s3">用户名称用户名称</text>
|
||||
<view class="bind-phone-s3">
|
||||
<text class="bind-txt-s3">绑定手机号 ></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-icons-s3">
|
||||
<view class="ic-msg-s3">🔔<text class="msg-dot-s3">6</text></view>
|
||||
<view class="ic-set-s3">⚙️</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-row-s3">
|
||||
<view class="stat-item">
|
||||
<text class="stat-val-s3">0.00</text>
|
||||
<text class="stat-label-s3">我的余额</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val-s3">65749</text>
|
||||
<text class="stat-label-s3">当前积分</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-val-s3">25</text>
|
||||
<text class="stat-label-s3">优惠券</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式3 会员卡 -->
|
||||
<view class="member-card-s3">
|
||||
<view class="mc-content-s3">
|
||||
<view class="mct-left-s3">
|
||||
<text class="mct-ic-s3">👑</text>
|
||||
<text class="mct-txt-s3">开通会员VIP</text>
|
||||
</view>
|
||||
<view class="mct-right-s3">
|
||||
<text class="mct-more-s3">会员可享多项权益 ></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 公共部分:订单中心 -->
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<text class="sh-title">订单中心</text>
|
||||
<text class="sh-more">查看全部 ></text>
|
||||
</view>
|
||||
<view class="order-grid">
|
||||
<view class="grid-item" v-for="(item, index) in orderItems" :key="index">
|
||||
<view class="gi-ic-box">
|
||||
<text class="gi-ic">{{ item.icon }}</text>
|
||||
</view>
|
||||
<text class="gi-txt">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 广告位 -->
|
||||
<view class="ad-box">
|
||||
<text class="ad-txt">暂无广告数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 我的服务 -->
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<text class="sh-title">我的服务</text>
|
||||
</view>
|
||||
<view class="service-grid">
|
||||
<view class="grid-item-s" v-for="(item, index) in serviceItems" :key="index">
|
||||
<view class="gi-ic-box-s" :style="{backgroundColor: item.color}">
|
||||
<text class="gi-ic-s">{{ item.icon }}</text>
|
||||
</view>
|
||||
<text class="gi-txt-s">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商家管理 -->
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<text class="sh-title">商家管理</text>
|
||||
</view>
|
||||
<view class="merchant-grid">
|
||||
<view class="grid-item-m" v-for="(item, index) in merchantItems" :key="index">
|
||||
<view class="gi-ic-box-m">
|
||||
<text class="gi-ic-m">{{ item.icon }}</text>
|
||||
</view>
|
||||
<text class="gi-txt-m">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:设置面板 -->
|
||||
<view class="settings-panel">
|
||||
<view class="settings-group">
|
||||
<view class="group-title">
|
||||
<view class="title-line"></view>
|
||||
<text class="title-txt">页面设置</text>
|
||||
</view>
|
||||
<view class="setting-item-row mt-20">
|
||||
<text class="item-label">页面风格:</text>
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="selectedStyle = 1">
|
||||
<view :class="['radio-dot', selectedStyle === 1 ? 'active' : '']"></view>
|
||||
<text class="radio-txt">样式1</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="selectedStyle = 2">
|
||||
<view :class="['radio-dot', selectedStyle === 2 ? 'active' : '']"></view>
|
||||
<text class="radio-txt">样式2</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="selectedStyle = 3">
|
||||
<view :class="['radio-dot', selectedStyle === 3 ? 'active' : '']"></view>
|
||||
<text class="radio-txt">样式3</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedStyle = ref(1)
|
||||
|
||||
const orderItems = [
|
||||
{ name: '待付款', icon: '💳' },
|
||||
{ name: '待发货', icon: '🚚' },
|
||||
{ name: '待收货', icon: '📦' },
|
||||
{ name: '待评价', icon: '📝' },
|
||||
{ name: '售后/退款', icon: '🔄' }
|
||||
]
|
||||
|
||||
const serviceItems = [
|
||||
{ name: '付费会员', icon: '💎', color: '#FFF7E6' },
|
||||
{ name: '发票管理', icon: '🧾', color: '#F6FFED' },
|
||||
{ name: '积分中心', icon: '🪙', color: '#E6FFFB' },
|
||||
{ name: '联系客服', icon: '🎧', color: '#F0F5FF' },
|
||||
{ name: '优惠券', icon: '🎫', color: '#FFF1F0' },
|
||||
{ name: '我的收藏', icon: '⭐', color: '#FFF2E8' },
|
||||
{ name: '地址信息', icon: '📍', color: '#F9F0FF' },
|
||||
{ name: '我的余额', icon: '💰', color: '#FCFFE6' },
|
||||
{ name: '我的推广', icon: '📢', color: '#FFF7E6' },
|
||||
{ name: '砍价记录', icon: '✂️', color: '#F6FFED' },
|
||||
{ name: '浏览记录', icon: '🕒', color: '#E6FFFB' },
|
||||
{ name: '我的等级', icon: '📊', color: '#F0F5FF' }
|
||||
]
|
||||
|
||||
const merchantItems = [
|
||||
{ name: '客服接待', icon: '🎧' },
|
||||
{ name: '订单核销', icon: '✅' },
|
||||
{ name: '统计管理', icon: '📉' }
|
||||
]
|
||||
|
||||
const handleSave = () => {
|
||||
uni.showToast({ title: '保存成功' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-decoration-user {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
height: 60px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.page-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
|
||||
.btn-primary {
|
||||
background-color: #2d8cf0;
|
||||
padding: 6px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 800px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 左侧预览区 */
|
||||
.preview-panel {
|
||||
width: 420px;
|
||||
padding: 40px;
|
||||
background-color: #f7f8fa;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.phone-mockup {
|
||||
width: 320px;
|
||||
height: 640px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.phone-body { height: 100%; }
|
||||
|
||||
/* 样式1&2 头部渐变 */
|
||||
.user-header-gradient {
|
||||
background: linear-gradient(135deg, #eb3c2d 0%, #ff5e5e 100%);
|
||||
padding: 25px 15px 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-top { display: flex; flex-direction: row; align-items: center; margin-bottom: 20px; }
|
||||
.avatar-box { width: 50px; height: 50px; border-radius: 25px; border: 2px solid rgba(255,255,255,0.8); overflow: hidden; margin-right: 12px; }
|
||||
.avatar-img { width: 100%; height: 100%; }
|
||||
|
||||
.user-info { flex: 1; display: flex; flex-direction: column; }
|
||||
.user-name { font-size: 14px; font-weight: bold; color: #fff; margin-bottom: 4px; }
|
||||
.bind-phone { background-color: rgba(0,0,0,0.15); align-self: flex-start; padding: 2px 8px; border-radius: 10px; }
|
||||
.bind-txt { color: #fff; font-size: 10px; }
|
||||
|
||||
.header-icons { display: flex; flex-direction: row; gap: 15px; }
|
||||
.ic-msg, .ic-set { font-size: 16px; color: #fff; position: relative; }
|
||||
.msg-dot { position: absolute; top: -5px; right: -5px; background-color: #fff; color: #f2270c; font-size: 9px; width: 12px; height: 12px; border-radius: 6px; text-align: center; }
|
||||
|
||||
.stats-row { display: flex; flex-direction: row; justify-content: space-around; padding: 10px 0; margin-bottom: 10px; }
|
||||
.stat-item { display: flex; flex-direction: column; align-items: center; }
|
||||
.stat-val { font-size: 16px; font-weight: bold; color: #fff; margin-bottom: 4px; }
|
||||
.stat-label { font-size: 10px; color: rgba(255,255,255,0.8); }
|
||||
|
||||
/* 会员卡 样式1 */
|
||||
.member-card-s1 {
|
||||
background: linear-gradient(90deg, #fdf1d6 0%, #fbd795 100%);
|
||||
margin: 0 -5px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 15px;
|
||||
}
|
||||
.mc-content-s1 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.mc-txt { font-size: 11px; color: #7c581c; margin-left: 6px; }
|
||||
.mc-btn { font-size: 10px; color: #7c581c; font-weight: bold; }
|
||||
|
||||
/* 会员卡 样式2 */
|
||||
.member-card-s2 {
|
||||
background-color: rgba(255,255,255,0.25);
|
||||
margin: 0 -5px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 15px;
|
||||
border-top: 1px solid rgba(255,255,255,0.3);
|
||||
border-left: 1px solid rgba(255,255,255,0.3);
|
||||
border-right: 1px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
.mc-content-s2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.mc-info-col { display: flex; flex-direction: column; margin-left: 8px; }
|
||||
.mc-t1 { font-size: 11px; color: #fff; font-weight: bold; }
|
||||
.mc-t2 { font-size: 9px; color: rgba(255,255,255,0.8); }
|
||||
.mc-btn-white { background-color: #fff; color: #f2270c; font-size: 10px; padding: 4px 12px; border-radius: 12px; font-weight: bold; }
|
||||
|
||||
/* 样式3 头部 */
|
||||
.user-header-s3 {
|
||||
background-color: #fff;
|
||||
padding: 30px 20px 0;
|
||||
}
|
||||
.header-top-s3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.header-top-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.avatar-box-s3 {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 27px;
|
||||
overflow: hidden;
|
||||
margin-right: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.user-info-s3 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.user-name-s3 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.bind-phone-s3 {
|
||||
display: flex;
|
||||
}
|
||||
.bind-txt-s3 {
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.header-icons-s3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.ic-msg-s3, .ic-set-s3 {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
position: relative;
|
||||
}
|
||||
.msg-dot-s3 {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
background-color: #f2270c;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.stats-row-s3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding: 10px 0 20px;
|
||||
}
|
||||
.stat-val-s3 {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.stat-label-s3 {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.member-card-s3 {
|
||||
background: #282828;
|
||||
margin: 0 -5px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 18px 15px;
|
||||
}
|
||||
.mc-content-s3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.mct-left-s3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.mct-ic-s3 {
|
||||
font-size: 18px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.mct-txt-s3 {
|
||||
color: #fbd795;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.mct-right-s3 {
|
||||
display: flex;
|
||||
}
|
||||
.mct-more-s3 {
|
||||
color: #fbd795;
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 通用区块间卡片 */
|
||||
.section-card {
|
||||
background-color: #fff;
|
||||
margin: 10px;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.section-header { display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 15px; }
|
||||
.sh-title { font-size: 13px; font-weight: bold; color: #333; }
|
||||
.sh-more { font-size: 11px; color: #999; }
|
||||
|
||||
.order-grid { display: flex; flex-direction: row; justify-content: space-between; }
|
||||
.grid-item { display: flex; flex-direction: column; align-items: center; }
|
||||
.gi-ic { font-size: 20px; margin-bottom: 6px; }
|
||||
.gi-txt { font-size: 10px; color: #666; }
|
||||
|
||||
.ad-box { background-color: #fff; margin: 10px; border-radius: 8px; height: 50px; display: flex; align-items: center; justify-content: center; border: 1px dashed #eee; }
|
||||
.ad-txt { font-size: 12px; color: #999; }
|
||||
|
||||
.service-grid { display: flex; flex-direction: row; flex-wrap: wrap; }
|
||||
.grid-item-s { width: 25%; display: flex; flex-direction: column; align-items: center; margin-bottom: 15px; }
|
||||
.gi-ic-box-s { width: 34px; height: 34px; border-radius: 17px; display: flex; align-items: center; justify-content: center; margin-bottom: 6px; }
|
||||
.gi-ic-s { font-size: 16px; }
|
||||
.gi-txt-s { font-size: 10px; color: #666; }
|
||||
|
||||
.merchant-grid { display: flex; flex-direction: row; gap: 40px; }
|
||||
.grid-item-m { display: flex; flex-direction: column; align-items: center; }
|
||||
.gi-ic-m { font-size: 20px; margin-bottom: 6px; }
|
||||
.gi-txt-m { font-size: 10px; color: #666; }
|
||||
|
||||
/* 右侧设置区 */
|
||||
.settings-panel { flex: 1; padding: 30px; }
|
||||
.group-title { display: flex; flex-direction: row; align-items: center; margin-bottom: 20px; }
|
||||
.title-line { width: 3px; height: 16px; background-color: #2d8cf0; margin-right: 10px; }
|
||||
.title-txt { font-size: 15px; font-weight: bold; color: #333; }
|
||||
|
||||
.setting-item-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 20px; }
|
||||
.item-label { font-size: 14px; color: #666; margin-right: 20px; }
|
||||
|
||||
.radio-group { display: flex; flex-direction: row; gap: 30px; }
|
||||
.radio-item { display: flex; flex-direction: row; align-items: center; cursor: pointer; }
|
||||
.radio-dot { width: 16px; height: 16px; border: 1px solid #dcdfe6; border-radius: 8px; margin-right: 8px; position: relative; }
|
||||
.radio-dot.active { border-color: #2d8cf0; }
|
||||
.radio-dot.active::after { content: ''; width: 8px; height: 8px; background-color: #2d8cf0; border-radius: 4px; position: absolute; top: 3px; left: 3px; }
|
||||
.radio-txt { font-size: 14px; color: #333; }
|
||||
|
||||
.mt-20 { margin-top: 20px; }
|
||||
|
||||
.anim-fade-in {
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
247
pages/mall/admin/finance/balance_record.uvue
Normal file
247
pages/mall/admin/finance/balance_record.uvue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<view class="finance-balance-record">
|
||||
<!-- 筛选卡片 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">订单时间:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">交易类型:</text>
|
||||
<view class="select-box">
|
||||
<text class="select-txt">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表表格 -->
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-order"><text class="th-txt">关联订单</text></view>
|
||||
<view class="th col-time"><text class="th-txt">交易时间</text></view>
|
||||
<view class="th col-amount"><text class="th-txt">交易金额</text></view>
|
||||
<view class="th col-user"><text class="th-txt">用户</text></view>
|
||||
<view class="th col-type"><text class="th-txt">交易类型</text></view>
|
||||
<view class="th col-remark"><text class="th-txt">备注</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-order text-left"><text class="td-txt">{{ item.order }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-amount">
|
||||
<text :class="['td-txt', item.amount.startsWith('+') ? 'red-txt' : 'green-txt']">{{ item.amount }}</text>
|
||||
</view>
|
||||
<view class="td col-user"><text class="td-txt">{{ item.user }}</text></view>
|
||||
<view class="td col-type"><text class="td-txt">{{ item.type }}</text></view>
|
||||
<view class="td col-remark text-left"><text class="td-txt">{{ item.remark }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-link">备注</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface BalanceRecord {
|
||||
id: string
|
||||
order: string
|
||||
time: string
|
||||
amount: string
|
||||
user: string
|
||||
type: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
const tableData = ref<BalanceRecord[]>([
|
||||
{
|
||||
id: '31216',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 10:30:11',
|
||||
amount: '+ 88888.00',
|
||||
user: '1',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
},
|
||||
{
|
||||
id: '31215',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 10:19:52',
|
||||
amount: '+ 88888.00',
|
||||
user: 'circus',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
},
|
||||
{
|
||||
id: '31214',
|
||||
order: 'cp541560738494283776',
|
||||
time: '2026-02-03 10:09:07',
|
||||
amount: '- 999.00',
|
||||
user: '1岁上班22岁退休',
|
||||
type: '余额支付购买商品',
|
||||
remark: '余额支付999.00元购买商品'
|
||||
},
|
||||
{
|
||||
id: '31213',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 10:07:59',
|
||||
amount: '+ 88888.00',
|
||||
user: '1岁上班22岁退休',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
},
|
||||
{
|
||||
id: '31212',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 02:17:24',
|
||||
amount: '+ 88888.00',
|
||||
user: '136****0434',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
},
|
||||
{
|
||||
id: '31211',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 02:04:17',
|
||||
amount: '+ 88888.00',
|
||||
user: '灵境',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
},
|
||||
{
|
||||
id: '31210',
|
||||
order: '新用户注册赠送余额',
|
||||
time: '2026-02-03 00:58:21',
|
||||
amount: '+ 88888.00',
|
||||
user: 'J.',
|
||||
type: '新用户注册赠送余额',
|
||||
remark: '新用户注册赠送88888余额'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-balance-record {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.filter-card { padding: 24px; margin-bottom: 20px; }
|
||||
.filter-row { display: flex; flex-direction: row; align-items: center; }
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; margin-right: 40px; }
|
||||
.filter-label { font-size: 14px; color: #333; margin-right: 15px; }
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 260px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.select-box {
|
||||
width: 200px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon { font-size: 14px; margin-right: 10px; color: #c0c4cc; }
|
||||
.date-placeholder, .select-txt { font-size: 14px; color: #c0c4cc; }
|
||||
.arrow-down { margin-left: auto; font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #e6f0ff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 16px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽分配 (参考截图比例) */
|
||||
.col-id { width: 70px; }
|
||||
.col-order { flex: 1.5; min-width: 180px; justify-content: flex-start; }
|
||||
.col-time { width: 160px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-user { width: 120px; }
|
||||
.col-type { width: 150px; }
|
||||
.col-remark { flex: 1.8; min-width: 200px; justify-content: flex-start; }
|
||||
.col-op { width: 80px; }
|
||||
|
||||
.text-left { justify-content: flex-start; text-align: left; }
|
||||
|
||||
/* 颜色 */
|
||||
.red-txt { color: #f56c6c; font-weight: 500; }
|
||||
.green-txt { color: #67c23a; font-weight: 500; }
|
||||
.btn-link { color: #1890ff; font-size: 13px; cursor: pointer; }
|
||||
</style>
|
||||
541
pages/mall/admin/finance/balance_stats.uvue
Normal file
541
pages/mall/admin/finance/balance_stats.uvue
Normal file
@@ -0,0 +1,541 @@
|
||||
<template>
|
||||
<view class="finance-balance-stats">
|
||||
<!-- 顶部数据统计卡片 (3列布局) -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="stat-icon-circle bg-blue">
|
||||
<text class="icon-white">💰</text>
|
||||
</view>
|
||||
<view class="stat-content">
|
||||
<text class="stat-value">1447117274.55</text>
|
||||
<text class="stat-label">当前余额</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="stat-icon-circle bg-orange">
|
||||
<text class="icon-white">🏦</text>
|
||||
</view>
|
||||
<view class="stat-content">
|
||||
<text class="stat-value">1602611838.49</text>
|
||||
<text class="stat-label">累计余额</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="stat-icon-circle bg-green">
|
||||
<text class="icon-white">💳</text>
|
||||
</view>
|
||||
<view class="stat-content">
|
||||
<text class="stat-value">155494563.94</text>
|
||||
<text class="stat-label">累计消耗余额</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间筛选区 -->
|
||||
<view class="filter-bar border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">时间选择:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-range">2026/01/05 - 2026/02/03</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 趋势图表区 (CRMEB 1:1) -->
|
||||
<view class="chart-box border-shadow">
|
||||
<view class="chart-header">
|
||||
<text class="chart-title">余额使用趋势</text>
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item">
|
||||
<view class="dot blue-dot"></view>
|
||||
<text class="legend-txt">余额积累</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="dot green-dot"></view>
|
||||
<text class="legend-txt">余额消耗</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-ops">
|
||||
<text class="op-icon">📥</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="main-chart-wrap">
|
||||
<EChartsView v-if="trendOption != null" :option="trendOption" class="main-trend-chart" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部双列分析 -->
|
||||
<view class="bottom-analysis">
|
||||
<view class="analysis-box border-shadow">
|
||||
<view class="box-header">
|
||||
<text class="box-title">余额来源分析</text>
|
||||
<view class="btn-toggle" @click="toggleSourceStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-container">
|
||||
<!-- 样式 0: 图表 -->
|
||||
<EChartsView v-if="sourceStyleMode == 0 && sourceOption != null" :option="sourceOption" class="pie-chart" />
|
||||
|
||||
<!-- 样式 1: 列表 -->
|
||||
<view v-if="sourceStyleMode == 1" class="stats-table">
|
||||
<view class="table-header">
|
||||
<text class="th col-idx">序号</text>
|
||||
<text class="th col-name">来源</text>
|
||||
<text class="th col-amount">金额</text>
|
||||
<text class="th col-percent">占比率</text>
|
||||
</view>
|
||||
<scroll-view class="table-body">
|
||||
<view class="table-row" v-for="(item, index) in sourceData" :key="index">
|
||||
<text class="td col-idx">{{ index + 1 }}</text>
|
||||
<text class="td col-name">{{ item.name }}</text>
|
||||
<text class="td col-amount">{{ item.value.toFixed(2) }}</text>
|
||||
<view class="td col-percent">
|
||||
<view class="progress-container">
|
||||
<view class="progress-bar" :style="{ width: item.percent + '%' }"></view>
|
||||
</view>
|
||||
<text class="percent-txt">{{ item.percent.toFixed(2) }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="analysis-box border-shadow">
|
||||
<view class="box-header">
|
||||
<text class="box-title">余额消耗</text>
|
||||
<view class="btn-toggle" @click="toggleConsumptionStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-container">
|
||||
<!-- 样式 0: 图表 -->
|
||||
<EChartsView v-if="consumptionStyleMode == 0 && consumptionOption != null" :option="consumptionOption" class="pie-chart" />
|
||||
|
||||
<!-- 样式 1: 列表 -->
|
||||
<view v-if="consumptionStyleMode == 1" class="stats-table">
|
||||
<view class="table-header">
|
||||
<text class="th col-idx">序号</text>
|
||||
<text class="th col-name">来源</text>
|
||||
<text class="th col-amount">金额</text>
|
||||
<text class="th col-percent">占比率</text>
|
||||
</view>
|
||||
<scroll-view class="table-body">
|
||||
<view class="table-row" v-for="(item, index) in consumptionDataList" :key="index">
|
||||
<text class="td col-idx">{{ index + 1 }}</text>
|
||||
<text class="td col-name">{{ item.name }}</text>
|
||||
<text class="td col-amount">{{ item.value.toFixed(2) }}</text>
|
||||
<view class="td col-percent">
|
||||
<view class="progress-container">
|
||||
<view class="progress-bar" :style="{ width: item.percent + '%' }"></view>
|
||||
</view>
|
||||
<text class="percent-txt">{{ item.percent.toFixed(2) }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
const trendOption = ref<any>(null)
|
||||
const sourceOption = ref<any>(null)
|
||||
const consumptionOption = ref<any>(null)
|
||||
|
||||
// 样式切换状态: 0=图表, 1=列表
|
||||
const sourceStyleMode = ref(0)
|
||||
const consumptionStyleMode = ref(0)
|
||||
|
||||
// 统计数据 (使用 ref 保证响应式)
|
||||
const sourceData = ref([
|
||||
{ value: 125000.00, name: '系统增加', percent: 40.00 },
|
||||
{ value: 93750.00, name: '用户充值', percent: 30.00 },
|
||||
{ value: 78125.00, name: '佣金提现', percent: 25.00 },
|
||||
{ value: 62500.00, name: '抽奖赠送', percent: 20.00 },
|
||||
{ value: 46875.00, name: '商品退款', percent: 15.00 }
|
||||
])
|
||||
|
||||
const consumptionDataList = ref([
|
||||
{ value: 435692.51, name: '购买商品', percent: 50.00 },
|
||||
{ value: 8060.18, name: '购买会员', percent: 20.00 },
|
||||
{ value: 0.00, name: '充值退款', percent: 15.00 },
|
||||
{ value: 0.00, name: '系统减少', percent: 15.00 }
|
||||
])
|
||||
|
||||
/**
|
||||
* 转换 Plain Object 工具
|
||||
*/
|
||||
function toPlainObject(obj : any) : any {
|
||||
if (obj == null) return null
|
||||
if (typeof obj !== 'object') return obj
|
||||
if (Array.isArray(obj)) {
|
||||
return (obj as Array<any>).map((item : any) : any => toPlainObject(item))
|
||||
}
|
||||
const plain : Record<string, any> = {}
|
||||
const keys = Object.keys(obj as Record<string, any>)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (key.startsWith('_') || key == 'toJSON') continue
|
||||
const value = (obj as Record<string, any>)[key]
|
||||
if (typeof value == 'function') continue
|
||||
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
|
||||
plain[key] = toPlainObject(value)
|
||||
} else {
|
||||
plain[key] = value
|
||||
}
|
||||
}
|
||||
return plain
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initTrendChart()
|
||||
initSourceChart()
|
||||
initConsumptionChart()
|
||||
}, 300)
|
||||
})
|
||||
|
||||
function initTrendChart() {
|
||||
const dates = ['01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13', '01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23', '01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02', '02-03']
|
||||
const accumulationData = [2500000, 2900000, 1500000, 2400000, 1800000, 1300000, 500000, 2100000, 3000000, 2800000, 2300000, 2200000, 1500000, 1100000, 2300000, 2800000, 2600000, 2700000, 1800000, 1950000, 650000, 1600000, 1750000, 2400000, 2600000, 2000000, 1400000, 550000, 2100000, 550000]
|
||||
const consumptionData = [10000, 20000, 15000, 120000, 50000, 20000, 10000, 30000, 40000, 35000, 60000, 25000, 30000, 45000, 55000, 110000, 60000, 50000, 40000, 35000, 85000, 45000, 120000, 50000, 45000, 40000, 35000, 55000, 65000, 45000]
|
||||
|
||||
const option = {
|
||||
grid: { left: '3%', right: '4%', bottom: '5%', top: '5%', containLabel: true },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#999', interval: 4 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
splitLine: { lineStyle: { color: '#f5f5f5' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '余额积累',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: accumulationData,
|
||||
itemStyle: { color: '#1890ff' }
|
||||
},
|
||||
{
|
||||
name: '余额消耗',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: consumptionData,
|
||||
itemStyle: { color: '#52c41a' }
|
||||
}
|
||||
]
|
||||
}
|
||||
trendOption.value = toPlainObject(option)
|
||||
}
|
||||
|
||||
function initSourceChart() {
|
||||
const option = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
|
||||
legend: { orient: 'vertical', right: '5%', top: 'center', itemWidth: 10, itemHeight: 10 },
|
||||
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16', '#e8684a'],
|
||||
series: [
|
||||
{
|
||||
name: '余额来源',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
center: ['40%', '50%'],
|
||||
label: { show: true, fontSize: 11, formatter: '{b}\n{c}%' },
|
||||
// 关键点:将图表数据映射到 percent 字段
|
||||
data: sourceData.value.map(item => ({ value: item.percent, name: item.name }))
|
||||
}
|
||||
]
|
||||
}
|
||||
sourceOption.value = toPlainObject(option)
|
||||
}
|
||||
|
||||
function initConsumptionChart() {
|
||||
const option = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
|
||||
legend: { orient: 'vertical', right: '5%', top: 'center', itemWidth: 10, itemHeight: 10 },
|
||||
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16'],
|
||||
series: [
|
||||
{
|
||||
name: '余额消耗',
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
center: ['40%', '50%'],
|
||||
label: { show: true, fontSize: 11, formatter: '{b}\n{c}%' },
|
||||
// 关键点:将图表数据映射到 percent 字段
|
||||
data: consumptionDataList.value.map(item => ({ value: item.percent, name: item.name }))
|
||||
}
|
||||
]
|
||||
}
|
||||
consumptionOption.value = toPlainObject(option)
|
||||
}
|
||||
|
||||
function toggleSourceStyle() {
|
||||
sourceStyleMode.value = sourceStyleMode.value === 0 ? 1 : 0
|
||||
if (sourceStyleMode.value === 0) {
|
||||
setTimeout(() => initSourceChart(), 50)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleConsumptionStyle() {
|
||||
consumptionStyleMode.value = consumptionStyleMode.value === 0 ? 1 : 0
|
||||
if (consumptionStyleMode.value === 0) {
|
||||
setTimeout(() => initConsumptionChart(), 50)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-balance-stats {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 顶部卡片 */
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
margin: 0 10px;
|
||||
padding: 24px 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-grid .stat-card:first-child { margin-left: 0; }
|
||||
.stats-grid .stat-card:last-child { margin-right: 0; }
|
||||
|
||||
.stat-icon-circle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.bg-blue { background-color: #40a9ff; }
|
||||
.bg-orange { background-color: #ffa940; }
|
||||
.bg-green { background-color: #73d13d; }
|
||||
|
||||
.icon-white { color: #fff; font-size: 24px; }
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 时间筛选区 */
|
||||
.filter-bar {
|
||||
padding: 16px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 320px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon { font-size: 14px; color: #c0c4cc; margin-right: 10px; }
|
||||
.date-range { font-size: 14px; color: #606266; }
|
||||
|
||||
/* 趋势图表区 */
|
||||
.chart-box {
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.chart-title { font-size: 16px; font-weight: 600; color: #303133; margin-right: auto; }
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.legend-item { display: flex; flex-direction: row; align-items: center; margin-left: 20px; }
|
||||
.legend-txt { font-size: 13px; color: #666; }
|
||||
.dot { width: 10px; height: 10px; border-radius: 5px; margin-right: 8px; }
|
||||
.blue-dot { background-color: #1890ff; }
|
||||
.green-dot { background-color: #52c41a; }
|
||||
|
||||
.chart-ops { margin-left: 20px; }
|
||||
.op-icon { font-size: 20px; color: #909399; }
|
||||
|
||||
.main-chart-wrap {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-trend-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 底部分析 */
|
||||
.bottom-analysis {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0 -10px 40px -10px;
|
||||
}
|
||||
|
||||
.analysis-box {
|
||||
flex: 1;
|
||||
margin: 0 10px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.box-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.box-title { font-size: 15px; font-weight: 600; color: #303133; }
|
||||
|
||||
.btn-toggle {
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-txt { font-size: 12px; color: #606266; }
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pie-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.stats-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #e6f0ff;
|
||||
padding: 12px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.th {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-idx { width: 60px; }
|
||||
.col-name { flex: 1; }
|
||||
.col-amount { flex: 1.5; }
|
||||
.col-percent { flex: 2; display: flex; flex-direction: row; align-items: center; justify-content: center; padding: 0 20px; }
|
||||
|
||||
.progress-container {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.percent-txt {
|
||||
width: 60px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
422
pages/mall/admin/finance/bill.uvue
Normal file
422
pages/mall/admin/finance/bill.uvue
Normal file
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<view class="finance-bill">
|
||||
<!-- 头部筛选 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">创建时间:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content-card border-shadow">
|
||||
<!-- 汇总选项卡 -->
|
||||
<view class="tab-header">
|
||||
<view class="tab-item active"><text class="tab-txt active-txt">日账单</text></view>
|
||||
<view class="tab-item"><text class="tab-txt">周账单</text></view>
|
||||
<view class="tab-item"><text class="tab-txt">月账单</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-title"><text class="th-txt">标题</text></view>
|
||||
<view class="th col-date"><text class="th-txt">日期</text></view>
|
||||
<view class="th col-income"><text class="th-txt">收入金额</text></view>
|
||||
<view class="th col-expense"><text class="th-txt">支出金额</text></view>
|
||||
<view class="th col-entry"><text class="th-txt">入账金额</text></view>
|
||||
<view class="th col-ops"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-title text-left"><text class="td-txt">{{ item.title }}</text></view>
|
||||
<view class="td col-date"><text class="td-txt">{{ item.date }}</text></view>
|
||||
<view class="td col-income"><text class="td-txt red-txt">¥{{ item.income }}</text></view>
|
||||
<view class="td col-expense"><text class="td-txt green-txt">¥{{ item.expense }}</text></view>
|
||||
<view class="td col-entry"><text class="td-txt">¥{{ item.entry }}</text></view>
|
||||
<view class="td col-ops">
|
||||
<view class="ops-wrap">
|
||||
<text class="op-link">账单详情</text>
|
||||
<text class="op-divider">|</text>
|
||||
<text class="op-link">下载</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface BillSumRecord {
|
||||
id: number
|
||||
title: string
|
||||
date: string
|
||||
income: string
|
||||
expense: string
|
||||
entry: string
|
||||
}
|
||||
|
||||
const tableData = ref<BillSumRecord[]>([
|
||||
{ id: 1, title: '日账单', date: '2026-02-03', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 2, title: '日账单', date: '2026-02-01', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 3, title: '日账单', date: '2026-01-30', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 4, title: '日账单', date: '2026-01-29', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 5, title: '日账单', date: '2026-01-28', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 6, title: '日账单', date: '2026-01-27', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 7, title: '日账单', date: '2026-01-26', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 8, title: '日账单', date: '2026-01-25', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 9, title: '日账单', date: '2026-01-23', income: '0.00', expense: '0.00', entry: '0.00' },
|
||||
{ id: 10, title: '日账单', date: '2026-01-22', income: '0.00', expense: '0.00', entry: '0.00' }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-bill {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 280px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.date-placeholder {
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 内容卡片 */
|
||||
.content-card {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 16px 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.tab-txt {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.active-txt {
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #e6f0ff; /* 对应截图的淡蓝色背景 */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 14px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 16px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽分配 */
|
||||
.col-id { width: 80px; }
|
||||
.col-title { width: 150px; }
|
||||
.col-date { width: 220px; }
|
||||
.col-income { width: 180px; }
|
||||
.col-expense { width: 180px; }
|
||||
.col-entry { width: 180px; }
|
||||
.col-ops { flex: 1; min-width: 150px; }
|
||||
|
||||
.text-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* 颜色 */
|
||||
.red-txt {
|
||||
color: #f5222d; /* 收入红色 */
|
||||
}
|
||||
|
||||
.green-txt {
|
||||
color: #52c41a; /* 支出绿色 */
|
||||
}
|
||||
|
||||
/* 操作项 */
|
||||
.ops-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.op-link {
|
||||
font-size: 14px;
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.op-divider {
|
||||
margin: 0 8px;
|
||||
color: #e8e8e8;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-bill {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.date-picker-wrap, .select-box {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-placeholder, .select-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
margin-left: auto;
|
||||
font-size: 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.btn-query, .btn-export {
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
background-color: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
.export-txt { color: #606266; font-size: 14px; }
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fb;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.col-id { width: 100px; }
|
||||
.col-title { width: 150px; }
|
||||
.col-type { width: 120px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-time { width: 180px; }
|
||||
.col-remark { flex: 1; min-width: 200px; }
|
||||
|
||||
.text-left { justify-content: flex-start; }
|
||||
|
||||
.type-tag {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.tag-green { background-color: #f0f9eb; color: #67c23a; border: 1px solid #e1f3d8; }
|
||||
.tag-orange { background-color: #fdf6ec; color: #e6a23c; border: 1px solid #faecd8; }
|
||||
|
||||
.green-txt { color: #67c23a; }
|
||||
.red-txt { color: #f56c6c; }
|
||||
</style>
|
||||
317
pages/mall/admin/finance/capital_flow.uvue
Normal file
317
pages/mall/admin/finance/capital_flow.uvue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<view class="finance-capital-flow">
|
||||
<!-- 头部筛选区 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">订单时间:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">交易类型:</text>
|
||||
<view class="select-box">
|
||||
<text class="select-txt">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item search-wrap">
|
||||
<text class="filter-label">流水搜索:</text>
|
||||
<input class="search-input" placeholder="订单号/昵称/电话/用户ID" />
|
||||
</view>
|
||||
<view class="btn-query">
|
||||
<text class="btn-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 (Flex 模拟) -->
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-flow"><text class="th-txt">交易单号</text></view>
|
||||
<view class="th col-order"><text class="th-txt">关联订单</text></view>
|
||||
<view class="th col-time"><text class="th-txt">交易时间</text></view>
|
||||
<view class="th col-amount"><text class="th-txt">交易金额</text></view>
|
||||
<view class="th col-user"><text class="th-txt">交易用户</text></view>
|
||||
<view class="th col-method"><text class="th-txt">支付方式</text></view>
|
||||
<view class="th col-remark"><text class="th-txt">备注</text></view>
|
||||
<view class="th col-ops"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.flowNo">
|
||||
<view class="td col-flow"><text class="td-txt">{{ item.flowNo }}</text></view>
|
||||
<view class="td col-order text-left"><text class="td-txt">{{ item.orderNo }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-amount"><text class="td-txt red-txt">{{ item.amount }}</text></view>
|
||||
<view class="td col-user"><text class="td-txt">{{ item.user }}</text></view>
|
||||
<view class="td col-method"><text class="td-txt">{{ item.method }}</text></view>
|
||||
<view class="td col-remark"><text class="td-txt">{{ item.remark }}</text></view>
|
||||
<view class="td col-ops">
|
||||
<text class="op-link">备注</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface FlowRecord {
|
||||
flowNo: string
|
||||
orderNo: string
|
||||
time: string
|
||||
amount: string
|
||||
user: string
|
||||
method: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
const tableData = ref<FlowRecord[]>([
|
||||
{
|
||||
flowNo: 'ZJ202602031258428744',
|
||||
orderNo: 'hy541422245264752640',
|
||||
time: '2026-02-03 00:58:42',
|
||||
amount: '+0.00',
|
||||
user: 'J.',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202602011056592117',
|
||||
orderNo: 'hy541029224517992448',
|
||||
time: '2026-02-01 22:56:59',
|
||||
amount: '+0.00',
|
||||
user: 'dev 王鑫',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202602010513443817',
|
||||
orderNo: 'hy540942844546777088',
|
||||
time: '2026-02-01 17:13:44',
|
||||
amount: '+0.00',
|
||||
user: 'Sunshine',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202602010127248683',
|
||||
orderNo: 'hy540885887420989440',
|
||||
time: '2026-02-01 13:27:24',
|
||||
amount: '+0.00',
|
||||
user: '132****8769',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202602011215407366',
|
||||
orderNo: 'hy540686639874179072',
|
||||
time: '2026-02-01 00:15:40',
|
||||
amount: '+0.00',
|
||||
user: '177****9187',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202601301026512881',
|
||||
orderNo: 'hy540296867267739648',
|
||||
time: '2026-01-30 22:26:51',
|
||||
amount: '+0.00',
|
||||
user: '暂未成功人士',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
flowNo: 'ZJ202601300534267557',
|
||||
orderNo: 'hy540223279126806528',
|
||||
time: '2026-01-30 17:34:26',
|
||||
amount: '+0.00',
|
||||
user: 'b342865d2077',
|
||||
method: '微信',
|
||||
remark: ''
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-capital-flow {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选卡片 */
|
||||
.filter-card {
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-placeholder {
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.select-box {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.select-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
font-size: 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fb;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.col-flow { width: 200px; }
|
||||
.col-order { width: 220px; }
|
||||
.col-time { width: 180px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-user { width: 150px; }
|
||||
.col-method { width: 120px; }
|
||||
.col-remark { flex: 1; min-width: 100px; }
|
||||
.col-ops { width: 100px; }
|
||||
|
||||
.text-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.red-txt {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
358
pages/mall/admin/finance/commission.uvue
Normal file
358
pages/mall/admin/finance/commission.uvue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<view class="finance-commission">
|
||||
<!-- 筛选卡片 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">昵称/手机号/分销商ID:</text>
|
||||
<input class="search-input" placeholder="请输入昵称搜索" />
|
||||
</view>
|
||||
<view class="btn-query">
|
||||
<text class="btn-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表表格 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="action-bar">
|
||||
<view class="btn-export">
|
||||
<text class="export-txt">导出</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="table-container">
|
||||
<view class="table-header">
|
||||
<view class="th col-user"><text class="th-txt">用户信息</text></view>
|
||||
<view class="th col-total"><text class="th-txt">总佣金金额</text></view>
|
||||
<view class="th col-account"><text class="th-txt">账户佣金</text></view>
|
||||
<view class="th col-withdraw"><text class="th-txt">提现佣金</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.uid">
|
||||
<view class="td col-user">
|
||||
<text class="td-txt">{{ item.userInfo }}</text>
|
||||
</view>
|
||||
<view class="td col-total">
|
||||
<text class="td-txt">{{ item.totalAmount }}</text>
|
||||
</view>
|
||||
<view class="td col-account">
|
||||
<text class="td-txt">{{ item.accountAmount }}</text>
|
||||
</view>
|
||||
<view class="td col-withdraw">
|
||||
<text class="td-txt">{{ item.withdrawAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface CommissionSummary {
|
||||
uid: string
|
||||
userInfo: string
|
||||
totalAmount: string
|
||||
accountAmount: string
|
||||
withdrawAmount: string
|
||||
}
|
||||
|
||||
const tableData = ref<CommissionSummary[]>([
|
||||
{
|
||||
uid: '77418',
|
||||
userInfo: '张迪 | 155****5525 | 77418',
|
||||
totalAmount: '11.00',
|
||||
accountAmount: '11.00',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '69696',
|
||||
userInfo: "I'm yours - | 69696",
|
||||
totalAmount: '0.40',
|
||||
accountAmount: '0.40',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '68582',
|
||||
userInfo: 'guan | 68582',
|
||||
totalAmount: '140.80',
|
||||
accountAmount: '140.80',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '65258',
|
||||
userInfo: '纯爱战神别名王富贵儿 | 158****4881 | 65258',
|
||||
totalAmount: '11.00',
|
||||
accountAmount: '11.00',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '66265',
|
||||
userInfo: '王伟兴 | 66265',
|
||||
totalAmount: '11.99',
|
||||
accountAmount: '11.99',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '65270',
|
||||
userInfo: '199****1781 | 199****1781 | 65270',
|
||||
totalAmount: '31.60',
|
||||
accountAmount: '31.60',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '64572',
|
||||
userInfo: '洒笾菂艸 | 188****2434 | 64572',
|
||||
totalAmount: '0.20',
|
||||
accountAmount: '0.20',
|
||||
withdrawAmount: '0'
|
||||
},
|
||||
{
|
||||
uid: '47952',
|
||||
userInfo: '那小子 | 134****3573 | 47952',
|
||||
totalAmount: '19.80',
|
||||
accountAmount: '19.80',
|
||||
withdrawAmount: '0'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-commission {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 240px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
margin-left: 12px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
width: 60px;
|
||||
height: 32px;
|
||||
background-color: #e6f7ff;
|
||||
border: 1px solid #91d5ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.export-txt {
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #e6f0ff; /* 淡蓝色表头 */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 14px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽分配 */
|
||||
.col-user { flex: 2; min-width: 300px; justify-content: flex-start; }
|
||||
.col-total { flex: 1; justify-content: flex-start; }
|
||||
.col-account { flex: 1; justify-content: flex-start; }
|
||||
.col-withdraw { flex: 1; justify-content: flex-start; }
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-commission {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon { font-size: 12px; margin-right: 8px; }
|
||||
.date-placeholder { font-size: 13px; color: #c0c4cc; }
|
||||
|
||||
.search-wrap { flex: 1; }
|
||||
.search-input {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格 */
|
||||
.table-container { display: flex; flex-direction: column; }
|
||||
.table-header { background-color: #f8f9fb; display: flex; flex-direction: row; border-bottom: 1px solid #ebeef5; }
|
||||
.th { padding: 15px 10px; display: flex; align-items: center; justify-content: center; }
|
||||
.th-txt { font-size: 14px; font-weight: 600; color: #909399; }
|
||||
|
||||
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #ebeef5; }
|
||||
.td { padding: 15px 10px; display: flex; align-items: center; justify-content: center; }
|
||||
.td-txt { font-size: 13px; color: #606266; }
|
||||
|
||||
.col-id { width: 80px; }
|
||||
.col-user { width: 180px; justify-content: flex-start; }
|
||||
.col-order { width: 180px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-type { width: 120px; }
|
||||
.col-time { width: 180px; }
|
||||
.col-status { width: 100px; }
|
||||
|
||||
.avatar { width: 32px; height: 32px; border-radius: 16px; margin-right: 10px; }
|
||||
.user-name { font-size: 13px; color: #606266; }
|
||||
.green-txt { color: #67c23a; font-weight: bold; }
|
||||
|
||||
.status-dot-active {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #67c23a;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
55
pages/mall/admin/finance/finance-placeholder.scss
Normal file
55
pages/mall/admin/finance/finance-placeholder.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
394
pages/mall/admin/finance/invoice.uvue
Normal file
394
pages/mall/admin/finance/invoice.uvue
Normal file
@@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<view class="finance-invoice">
|
||||
<!-- 头部筛选区 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">创建时间:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">搜索:</text>
|
||||
<view class="search-group">
|
||||
<view class="select-trigger">
|
||||
<text class="select-txt">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
<input class="search-input" placeholder="请输入" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-query">
|
||||
<text class="btn-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态切换选项卡 -->
|
||||
<view class="tab-section">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === index }"
|
||||
@click="activeTab = index"
|
||||
>
|
||||
<text class="tab-title">{{ tab.name }} ({{ tab.count }})</text>
|
||||
<view class="active-line" v-if="activeTab === index"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 (Flex 模拟) -->
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-order"><text class="th-txt">订单号</text></view>
|
||||
<view class="th col-amount"><text class="th-txt">订单金额</text></view>
|
||||
<view class="th col-type"><text class="th-txt">发票类型</text></view>
|
||||
<view class="th col-header"><text class="th-txt">发票抬头类型</text></view>
|
||||
<view class="th col-time"><text class="th-txt">下单时间</text></view>
|
||||
<view class="th col-status-inv"><text class="th-txt">开票状态</text></view>
|
||||
<view class="th col-status-order"><text class="th-txt">订单状态</text></view>
|
||||
<view class="th col-ops"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.orderNo">
|
||||
<view class="td col-order text-left"><text class="td-txt">{{ item.orderNo }}</text></view>
|
||||
<view class="td col-amount"><text class="td-txt">¥ {{ item.amount }}</text></view>
|
||||
<view class="td col-type"><text class="td-txt">{{ item.invoiceType }}</text></view>
|
||||
<view class="td col-header"><text class="td-txt">{{ item.headerType }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-status-inv"><text class="td-txt red-cell">{{ item.invStatus }}</text></view>
|
||||
<view class="td col-status-order"><text class="td-txt">{{ item.orderStatus }}</text></view>
|
||||
<view class="td col-ops">
|
||||
<view class="ops-box">
|
||||
<text class="op-link">操作</text>
|
||||
<text class="sep">|</text>
|
||||
<text class="op-link">订单信息</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeTab = ref(0)
|
||||
|
||||
const tabs = ref([
|
||||
{ name: '全部发票', count: 163 },
|
||||
{ name: '待开发票', count: 105 },
|
||||
{ name: '已开发票', count: 34 },
|
||||
{ name: '退款发票', count: 33 }
|
||||
])
|
||||
|
||||
const tableData = ref([
|
||||
{
|
||||
orderNo: 'cp538981381384962048',
|
||||
amount: '999.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2026-01-27 07:19:35',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp523490058603331584',
|
||||
amount: '9.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-12-15 13:22:35',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp524967077409193984',
|
||||
amount: '9.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-12-19 15:11:44',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp521126678106210304',
|
||||
amount: '11890.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-12-09 00:51:22',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp521126166883467264',
|
||||
amount: '11800.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-12-09 00:49:20',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp517015093888679936',
|
||||
amount: '142.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-11-27 16:33:24',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp313601579989073920',
|
||||
amount: '0.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2024-05-15 08:59:56',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '待评价'
|
||||
},
|
||||
{
|
||||
orderNo: 'cp503871643047690240',
|
||||
amount: '999.00',
|
||||
invoiceType: '电子普通发票',
|
||||
headerType: '个人',
|
||||
time: '2025-10-22 10:06:01',
|
||||
invStatus: '未开票',
|
||||
orderStatus: '未发货'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-invoice {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-card {
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.date-placeholder {
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.select-trigger {
|
||||
width: 90px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.select-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
font-size: 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 160px;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 选项卡 */
|
||||
.tab-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12px 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-title {
|
||||
color: #1890ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.active-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fb;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽分配 */
|
||||
.col-order { width: 220px; justify-content: flex-start; padding-left: 20px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-type { width: 150px; }
|
||||
.col-header { width: 120px; }
|
||||
.col-time { width: 180px; }
|
||||
.col-status-inv { width: 120px; }
|
||||
.col-status-order { width: 120px; }
|
||||
.col-ops { flex: 1; min-width: 140px; }
|
||||
|
||||
.text-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.red-cell {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.ops-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sep {
|
||||
font-size: 12px;
|
||||
color: #ebeef5;
|
||||
margin: 0 8px;
|
||||
}
|
||||
</style>
|
||||
425
pages/mall/admin/finance/recharge.uvue
Normal file
425
pages/mall/admin/finance/recharge.uvue
Normal file
@@ -0,0 +1,425 @@
|
||||
<template>
|
||||
<view class="finance-recharge">
|
||||
<!-- 头部筛选区 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">时间选择:</text>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">📅</text>
|
||||
<text class="date-placeholder">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">支付类型:</text>
|
||||
<view class="select-box">
|
||||
<text class="select-txt">全部</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item search-wrap">
|
||||
<text class="filter-label">搜索:</text>
|
||||
<input class="search-input" placeholder="请输入用户昵称、订单号" />
|
||||
</view>
|
||||
<view class="btn-query">
|
||||
<text class="btn-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计网格 -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card border-shadow" v-for="(item, index) in stats" :key="index">
|
||||
<view class="icon-circle" :class="item.colorClass">
|
||||
<text class="stat-icon">{{ item.icon }}</text>
|
||||
</view>
|
||||
<view class="stat-info">
|
||||
<text class="stat-value">{{ item.value }}</text>
|
||||
<text class="stat-label">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作区域 -->
|
||||
<view class="action-section">
|
||||
<view class="btn-export">
|
||||
<text class="btn-export-txt">导出</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 (Flex 模拟) -->
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-avatar"><text class="th-txt">头像</text></view>
|
||||
<view class="th col-nickname"><text class="th-txt">用户昵称</text></view>
|
||||
<view class="th col-orderno"><text class="th-txt">订单号</text></view>
|
||||
<view class="th col-amount"><text class="th-txt">支付金额</text></view>
|
||||
<view class="th col-paid"><text class="th-txt">是否支付</text></view>
|
||||
<view class="th col-type"><text class="th-txt">充值类型</text></view>
|
||||
<view class="th col-time"><text class="th-txt">支付时间</text></view>
|
||||
<view class="th col-ops"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in tableData" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-avatar">
|
||||
<view class="avatar-box">
|
||||
<text class="avatar-placeholder" v-if="!item.hasAvatar">🖼️</text>
|
||||
<view class="avatar-img-mock" v-else></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-nickname"><text class="td-txt">{{ item.nickname }}</text></view>
|
||||
<view class="td col-orderno"><text class="td-txt">{{ item.orderNo }}</text></view>
|
||||
<view class="td col-amount"><text class="td-txt">{{ item.amount }}</text></view>
|
||||
<view class="td col-paid"><text class="td-txt">{{ item.isPaid }}</text></view>
|
||||
<view class="td col-type"><text class="td-txt">{{ item.type }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-ops">
|
||||
<text class="op-link">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const stats = ref([
|
||||
{ label: '充值总金额', value: '446747490.72', icon: '¥', colorClass: 'blue' },
|
||||
{ label: '充值退款金额', value: '1.19', icon: '¥', colorClass: 'orange' },
|
||||
{ label: '支付宝充值金额', value: '78812.24', icon: '支', colorClass: 'green' },
|
||||
{ label: '微信充值金额', value: '8518075.11', icon: '✔', colorClass: 'pink' }
|
||||
])
|
||||
|
||||
interface RechargeRecord {
|
||||
id: number
|
||||
hasAvatar: boolean
|
||||
nickname: string
|
||||
orderNo: string
|
||||
amount: string
|
||||
isPaid: string
|
||||
type: string
|
||||
time: string
|
||||
}
|
||||
|
||||
const tableData = ref<RechargeRecord[]>([
|
||||
{
|
||||
id: 4522,
|
||||
hasAvatar: true,
|
||||
nickname: 'TTA NW',
|
||||
orderNo: 'cz540603403592531968',
|
||||
amount: '10.00',
|
||||
isPaid: '未支付',
|
||||
type: '微信充值',
|
||||
time: '暂无'
|
||||
},
|
||||
{
|
||||
id: 4521,
|
||||
hasAvatar: true,
|
||||
nickname: 'TTA NW',
|
||||
orderNo: 'cz540592008763277312',
|
||||
amount: '3343.00',
|
||||
isPaid: '未支付',
|
||||
type: '微信充值',
|
||||
time: '暂无'
|
||||
},
|
||||
{
|
||||
id: 4520,
|
||||
hasAvatar: true,
|
||||
nickname: '绯',
|
||||
orderNo: 'cz538561368400330752',
|
||||
amount: '500.00',
|
||||
isPaid: '未支付',
|
||||
type: '支付宝充值',
|
||||
time: '暂无'
|
||||
},
|
||||
{
|
||||
id: 4519,
|
||||
hasAvatar: false,
|
||||
nickname: '六六狗',
|
||||
orderNo: 'cz538368229429477376',
|
||||
amount: '50.00',
|
||||
isPaid: '未支付',
|
||||
type: '微信充值',
|
||||
time: '暂无'
|
||||
},
|
||||
{
|
||||
id: 4518,
|
||||
hasAvatar: false,
|
||||
nickname: 'aabbc',
|
||||
orderNo: 'cz538165303901683712',
|
||||
amount: '10.00',
|
||||
isPaid: '未支付',
|
||||
type: '其他充值',
|
||||
time: '暂无'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-recharge {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 筛选卡片 */
|
||||
.filter-card {
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-placeholder {
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.select-box {
|
||||
width: 160px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.select-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
font-size: 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 统计网格 */
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
width: 24%;
|
||||
padding: 24px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.blue { background-color: #e6f7ff; color: #1890ff; }
|
||||
.orange { background-color: #fff7e1; color: #fa8c16; }
|
||||
.green { background-color: #f6ffed; color: #52c41a; }
|
||||
.pink { background-color: #fff0f6; color: #eb2f96; }
|
||||
|
||||
.stat-icon { font-size: 22px; font-weight: bold; }
|
||||
|
||||
.stat-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 操作区 */
|
||||
.action-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-export-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fb;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.col-id { width: 80px; }
|
||||
.col-avatar { width: 80px; }
|
||||
.col-nickname { width: 150px; }
|
||||
.col-orderno { width: 220px; }
|
||||
.col-amount { width: 120px; }
|
||||
.col-paid { width: 120px; }
|
||||
.col-type { width: 150px; }
|
||||
.col-time { width: 150px; }
|
||||
.col-ops { flex: 1; min-width: 100px; }
|
||||
|
||||
.avatar-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar-img-mock {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
836
pages/mall/admin/finance/transaction_stats.uvue
Normal file
836
pages/mall/admin/finance/transaction_stats.uvue
Normal file
@@ -0,0 +1,836 @@
|
||||
<template>
|
||||
<view class="finance-transaction-stats">
|
||||
<!-- 头部筛选 -->
|
||||
<view class="header-filters">
|
||||
<view class="date-tabs">
|
||||
<text
|
||||
v-for="(item, index) in dateOptions"
|
||||
:key="index"
|
||||
class="date-tab-item"
|
||||
:class="{ active: activeDateTab === index }"
|
||||
@click="activeDateTab = index"
|
||||
>{{ item }}</text>
|
||||
</view>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-icon">D</text>
|
||||
<text class="date-range-text">2026-02-03 - 2026-02-03</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 交易概况区块 (CRMEB 1:1 复刻) -->
|
||||
<view class="overview-card">
|
||||
<view class="overview-header">
|
||||
<view class="header-left">
|
||||
<text class="section-title">交易概况</text>
|
||||
<text class="info-tag">?</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="date-picker-inline">
|
||||
<text class="date-text">2026/01/05 - 2026/02/03</text>
|
||||
</view>
|
||||
<button class="btn-query">查询</button>
|
||||
<button class="btn-export">导出</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 指标网格 -->
|
||||
<view class="overview-grid">
|
||||
<!-- 第一行 -->
|
||||
<view class="grid-row">
|
||||
<view class="overview-item">
|
||||
<view class="icon-box blue"><text class="icon">🕒</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">营业额</text>
|
||||
<text class="item-value">442753.70</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">44275370% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box green"><text class="icon">¥</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">商品支付金额</text>
|
||||
<text class="item-value">434693.52</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">43469352% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box orange"><text class="icon">🔒</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">购买会员金额</text>
|
||||
<text class="item-value">8059.18</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">805918% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box purple"><text class="icon">💰</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">充值金额</text>
|
||||
<text class="item-value">0.00</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value">0% -</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box cyan"><text class="icon">🛒</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">线下收银金额</text>
|
||||
<text class="item-value">1</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">100% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第二行 -->
|
||||
<view class="grid-row second">
|
||||
<view class="overview-item">
|
||||
<view class="icon-box light-green"><text class="icon">↘</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">支出金额</text>
|
||||
<text class="item-value">442752.69</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">44275269% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box gold"><text class="icon">💳</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">余额支付金额</text>
|
||||
<text class="item-value">442752.69</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value up">5293.00% ▲</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box red-purple"><text class="icon">%</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">支付佣金金额</text>
|
||||
<text class="item-value">0.00</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value">0% -</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-item">
|
||||
<view class="icon-box blue-gray"><text class="icon">📦</text></view>
|
||||
<view class="item-info">
|
||||
<text class="item-label">商品退款金额</text>
|
||||
<text class="item-value">0.00</text>
|
||||
<view class="trend-row">
|
||||
<text class="trend-label">环比增长:</text>
|
||||
<text class="trend-value">0% -</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 占位使其对齐 -->
|
||||
<view class="overview-item transparent"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 概况图表区 -->
|
||||
<view class="overview-chart-section">
|
||||
<view class="overview-chart-legend">
|
||||
<view class="legend-dot blue"><text class="legend-text">营业额</text></view>
|
||||
<view class="legend-dot green"><text class="legend-text">商品支付金额</text></view>
|
||||
<view class="legend-dot gray-blue"><text class="legend-text">购买会员金额</text></view>
|
||||
<view class="legend-dot red"><text class="legend-text">充值金额</text></view>
|
||||
<view class="legend-dot orange"><text class="legend-text">支出金额</text></view>
|
||||
</view>
|
||||
<view class="overview-chart-box">
|
||||
<EChartsView v-if="overviewTrendOption != null" :option="overviewTrendOption" class="main-trend-chart" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 之前的统计卡片区域 -->
|
||||
<view class="stats-section">
|
||||
<!-- 左侧:今日订单金额 -->
|
||||
<view class="stats-card-main">
|
||||
<view class="card-header">
|
||||
<text class="card-title">今日订单金额</text>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<view class="amount-wrap">
|
||||
<text class="currency">¥</text>
|
||||
<text class="amount-value">0</text>
|
||||
</view>
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item">
|
||||
<view class="dot blue"></view>
|
||||
<text>今天</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="dot gray"></view>
|
||||
<text>昨天</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-box">
|
||||
<EChartsView v-if="orderAmountOption != null" :option="orderAmountOption" class="stats-chart" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 侧边统计网格 -->
|
||||
<view class="stats-side-grid">
|
||||
<view class="side-column">
|
||||
<!-- 今日订单数 -->
|
||||
<view class="side-stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">今日订单数</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="main-val">0</text>
|
||||
<view class="compare-row">
|
||||
<text class="label">昨日:</text>
|
||||
<text class="val">4</text>
|
||||
</view>
|
||||
<view class="compare-row">
|
||||
<text class="label">日环比:</text>
|
||||
<text class="val down">-100% ▼</text>
|
||||
</view>
|
||||
<view class="mini-chart-placeholder">
|
||||
<view class="blue-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 本月订单数 -->
|
||||
<view class="side-stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">本月订单数</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="main-val">12</text>
|
||||
<view class="compare-row">
|
||||
<text class="label">上月:</text>
|
||||
<text class="val">206</text>
|
||||
</view>
|
||||
<view class="compare-row">
|
||||
<text class="label">月环比:</text>
|
||||
<text class="val down">-94% ▼</text>
|
||||
</view>
|
||||
<view class="mini-chart-placeholder">
|
||||
<view class="blue-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="side-column">
|
||||
<!-- 今日支付人数 -->
|
||||
<view class="side-stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">今日支付人数</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="main-val">0</text>
|
||||
<view class="compare-row">
|
||||
<text class="label">昨日:</text>
|
||||
<text class="val">4</text>
|
||||
</view>
|
||||
<view class="compare-row">
|
||||
<text class="label">日环比:</text>
|
||||
<text class="val down">-100% ▼</text>
|
||||
</view>
|
||||
<view class="mini-chart-placeholder">
|
||||
<view class="blue-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 本月支付人数 -->
|
||||
<view class="side-stat-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">本月支付人数</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="main-val">7</text>
|
||||
<view class="compare-row">
|
||||
<text class="label">上月:</text>
|
||||
<text class="val">134</text>
|
||||
</view>
|
||||
<view class="compare-row">
|
||||
<text class="label">月环比:</text>
|
||||
<text class="val down">-94% ▼</text>
|
||||
</view>
|
||||
<view class="mini-chart-placeholder">
|
||||
<view class="blue-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
const dateOptions = ['今天', '昨天', '最近7天', '最近30天', '本月', '本年']
|
||||
const activeDateTab = ref(0)
|
||||
|
||||
const orderAmountOption = ref<any>(null)
|
||||
const overviewTrendOption = ref<any>(null)
|
||||
|
||||
/**
|
||||
* 工具函数:将 UTS 对象转换为纯 JavaScript 对象
|
||||
* 确保 ECharts 在 renderjs 中能正确接收数据
|
||||
*/
|
||||
function toPlainObject(obj : any) : any {
|
||||
if (obj == null) return null
|
||||
if (typeof obj !== 'object') return obj
|
||||
if (Array.isArray(obj)) {
|
||||
return (obj as Array<any>).map((item : any) : any => toPlainObject(item))
|
||||
}
|
||||
const plain : Record<string, any> = {}
|
||||
const keys = Object.keys(obj as Record<string, any>)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (key.startsWith('_') || key == 'toJSON') continue
|
||||
const value = (obj as Record<string, any>)[key]
|
||||
if (typeof value == 'function') continue
|
||||
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
|
||||
plain[key] = toPlainObject(value)
|
||||
} else {
|
||||
plain[key] = value
|
||||
}
|
||||
}
|
||||
return plain
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 延迟初始化图表确保容器就位
|
||||
setTimeout(() => {
|
||||
initCharts()
|
||||
}, 300)
|
||||
})
|
||||
|
||||
function initCharts() {
|
||||
// 模拟趋势数据
|
||||
const todayData = [120, 132, 101, 134, 90, 230, 210]
|
||||
const yesterdayData = [220, 182, 191, 234, 290, 330, 310]
|
||||
|
||||
const option = {
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', top: '5%', containLabel: true },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: { color: '#1890ff', type: 'dashed' }
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '23:59'],
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: { show: false },
|
||||
axisLine: { show: false },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '今天',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: todayData,
|
||||
lineStyle: { color: '#1890ff', width: 2 },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0, y: 0, x2: 0, y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(24, 144, 255, 0.2)' },
|
||||
{ offset: 1, color: 'rgba(24, 144, 255, 0)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
itemStyle: { color: '#1890ff' }
|
||||
},
|
||||
{
|
||||
name: '昨天',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: yesterdayData,
|
||||
lineStyle: { color: '#d9d9d9', width: 2, type: 'dashed' },
|
||||
itemStyle: { color: '#d9d9d9' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 核心修复:传递给 ECharts 的 Option 必须是 Plain Object
|
||||
orderAmountOption.value = toPlainObject(option)
|
||||
|
||||
// 初始化交易概况趋势图 (多曲线)
|
||||
const overviewOption = {
|
||||
grid: { left: '3%', right: '4%', bottom: '10%', top: '5%', containLabel: true },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['2026-01-05', '01-10', '01-15', '01-20', '01-25', '01-31', '02-03'],
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#999', fontSize: 10 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
splitLine: { lineStyle: { color: '#f5f5f5' } },
|
||||
axisLabel: { color: '#999' }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '营业额',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [1000, 5000, 20000, 15000, 80000, 20000, 5000],
|
||||
itemStyle: { color: '#1890ff' }
|
||||
},
|
||||
{
|
||||
name: '商品支付金额',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [800, 4000, 18000, 12000, 75000, 18000, 4000],
|
||||
itemStyle: { color: '#52c41a' }
|
||||
},
|
||||
{
|
||||
name: '支出金额',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [500, 3000, 15000, 10000, 90000, 15000, 3000],
|
||||
itemStyle: { color: '#fa8c16' }
|
||||
}
|
||||
]
|
||||
}
|
||||
overviewTrendOption.value = toPlainObject(overviewOption)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-transaction-stats {
|
||||
padding: 16px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部筛选 */
|
||||
.header-filters {
|
||||
background: #fff;
|
||||
padding: 12px 20px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.date-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.date-tab-item {
|
||||
padding: 4px 15px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
border-right: 1px solid #d9d9d9;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.date-range-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 统计区域布局 */
|
||||
.stats-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stats-card-main {
|
||||
flex: 3;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
min-height: 380px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.amount-wrap {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.currency {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 40px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
|
||||
&.blue { background-color: #1890ff; }
|
||||
&.gray { background-color: #d9d9d9; }
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stats-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 侧边网格 */
|
||||
.stats-side-grid {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.side-column {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.side-stat-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-val {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.compare-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.compare-row .label {
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.compare-row .val {
|
||||
color: #333;
|
||||
|
||||
&.down { color: #52c41a; }
|
||||
&.up { color: #f5222d; }
|
||||
}
|
||||
|
||||
.mini-chart-placeholder {
|
||||
margin-top: 20px;
|
||||
height: 2px;
|
||||
background-color: #f0f0f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blue-line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 交易概况复刻样式 */
|
||||
.overview-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.overview-header .header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-tag {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #eee;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.overview-header .header-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.date-picker-inline {
|
||||
border: 1px solid #dcdfe6;
|
||||
padding: 5px 15px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.btn-query, .btn-export {
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 13px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
background: #fff;
|
||||
color: #1890ff;
|
||||
border: 1px solid #1890ff;
|
||||
}
|
||||
|
||||
/* 指标网格 */
|
||||
.overview-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px dashed #f0f0f0;
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.overview-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overview-item.transparent {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.icon-box .icon {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon-box.blue { background-color: #2f54eb; }
|
||||
.icon-box.green { background-color: #52c41a; }
|
||||
.icon-box.orange { background-color: #fa8c16; }
|
||||
.icon-box.purple { background-color: #722ed1; }
|
||||
.icon-box.cyan { background-color: #13c2c2; }
|
||||
.icon-box.light-green { background-color: #a0d911; }
|
||||
.icon-box.gold { background-color: #faad14; }
|
||||
.icon-box.red-purple { background-color: #eb2f96; }
|
||||
.icon-box.blue-gray { background-color: #4096ff; }
|
||||
|
||||
.item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.trend-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.trend-value.up {
|
||||
color: #f5222d;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* 图表区 */
|
||||
.overview-chart-section {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.overview-chart-legend {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.legend-dot.blue { background: #1890ff; }
|
||||
.legend-dot.green { background: #52c41a; }
|
||||
.legend-dot.gray-blue { background: #607d8b; }
|
||||
.legend-dot.red { background: #f44336; }
|
||||
.legend-dot.orange { background: #fa8c16; }
|
||||
|
||||
.legend-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.overview-chart-box {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.main-trend-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
469
pages/mall/admin/finance/withdrawal.uvue
Normal file
469
pages/mall/admin/finance/withdrawal.uvue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<view class="finance-withdrawal">
|
||||
<!-- 头部筛选区 -->
|
||||
<view class="filter-card">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">时间选择:</text>
|
||||
<uni-datetime-picker v-model="timeRange" type="daterange" class="dt-picker" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">提现状态:</text>
|
||||
<uni-data-select v-model="statusValue" :localdata="statusOptions" class="data-select" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">提现方式:</text>
|
||||
<uni-data-select v-model="methodValue" :localdata="methodOptions" class="data-select" />
|
||||
</view>
|
||||
<view class="filter-item search-wrap">
|
||||
<text class="filter-label">搜索:</text>
|
||||
<uni-easyinput v-model="searchKeyword" placeholder="微信昵称/姓名/支付宝账号/银行卡号" class="search-input" />
|
||||
</view>
|
||||
<button class="btn-query" @click="handleQuery">查询</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计指标网格 -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card" v-for="(item, index) in stats" :key="index">
|
||||
<view class="icon-circle" :class="item.colorClass">
|
||||
<text class="stat-icon">{{ item.icon }}</text>
|
||||
</view>
|
||||
<view class="stat-info">
|
||||
<text class="stat-value">{{ item.value }}</text>
|
||||
<text class="stat-label">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<view class="action-section">
|
||||
<view class="btn-record">
|
||||
<text class="btn-record-txt">佣金记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 (使用 Flex 模拟以确保兼容性和精度) -->
|
||||
<view class="table-container">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-user"><text class="th-txt">用户信息</text></view>
|
||||
<view class="th col-amount"><text class="th-txt">提现金额</text></view>
|
||||
<view class="th col-fee"><text class="th-txt">提现手续费</text></view>
|
||||
<view class="th col-net"><text class="th-txt">到账金额</text></view>
|
||||
<view class="th col-method"><text class="th-txt">提现方式</text></view>
|
||||
<view class="th col-qr"><text class="th-txt">收款码</text></view>
|
||||
<view class="th col-time"><text class="th-txt">申请时间</text></view>
|
||||
<view class="th col-remark"><text class="th-txt">备注</text></view>
|
||||
<view class="th col-status"><text class="th-txt">审核状态</text></view>
|
||||
<view class="th col-ops"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="(item, index) in tableData" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-user">
|
||||
<view class="user-info-box">
|
||||
<view class="avatar-box">
|
||||
<text class="avatar-placeholder">U</text>
|
||||
</view>
|
||||
<view class="user-detail">
|
||||
<text class="user-nickname">{{ item.nickname }}</text>
|
||||
<text class="user-id">用户id:{{ item.userId }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-amount"><text class="td-txt">{{ item.amount }}</text></view>
|
||||
<view class="td col-fee"><text class="td-txt">{{ item.fee }}</text></view>
|
||||
<view class="td col-net"><text class="td-txt green-txt">{{ item.netAmount }}</text></view>
|
||||
<view class="td col-method">
|
||||
<view class="method-box">
|
||||
<text class="m-line">姓名:{{ item.name }}</text>
|
||||
<text class="m-line">{{ item.type }}:{{ item.account }}</text>
|
||||
<text v-if="item.bank" class="m-line">银行开户地址:{{ item.bank }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-qr">
|
||||
<view class="qr-box" v-if="item.id === 57">
|
||||
<text class="qr-icon-txt">■</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-remark"><text class="td-txt">{{ item.remark || '' }}</text></view>
|
||||
<view class="td col-status"><text class="td-txt">申请中</text></view>
|
||||
<view class="td col-ops">
|
||||
<view class="ops-box">
|
||||
<text class="op-btn blue">编辑</text>
|
||||
<text class="op-sep">|</text>
|
||||
<text class="op-btn blue">通过</text>
|
||||
<text class="op-sep">|</text>
|
||||
<text class="op-btn blue">驳回</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const timeRange = ref([])
|
||||
const statusValue = ref('all')
|
||||
const methodValue = ref('all')
|
||||
const searchKeyword = ref('')
|
||||
|
||||
const statusOptions = ref([
|
||||
{ value: 'all', text: '全部' },
|
||||
{ value: '0', text: '待审核' },
|
||||
{ value: '1', text: '已通过' },
|
||||
{ value: '-1', text: '已驳回' }
|
||||
])
|
||||
|
||||
const methodOptions = ref([
|
||||
{ value: 'all', text: '全部' },
|
||||
{ value: 'alipay', text: '支付宝' },
|
||||
{ value: 'bank', text: '银行卡' },
|
||||
{ value: 'weixin', text: '微信' }
|
||||
])
|
||||
|
||||
const stats = ref([
|
||||
{ label: '佣金总金额', value: '676809.25', icon: '$', colorClass: 'blue' },
|
||||
{ label: '待提现金额', value: '71', icon: '¥', colorClass: 'orange' },
|
||||
{ label: '已提现金额', value: '68879.25', icon: '$', colorClass: 'green' },
|
||||
{ label: '未提现金额', value: '607930.00', icon: '¥', colorClass: 'pink' }
|
||||
])
|
||||
|
||||
const tableData = ref([
|
||||
{
|
||||
id: 57,
|
||||
nickname: '用户昵称: 177****766',
|
||||
userId: '58837',
|
||||
amount: '20.00',
|
||||
fee: '0.00',
|
||||
netAmount: '20.00',
|
||||
name: '接口',
|
||||
type: '支付宝',
|
||||
account: '1195953899',
|
||||
time: '2025-10-24 16:04',
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
id: 56,
|
||||
nickname: '用户昵称: 测试员的',
|
||||
userId: '20695',
|
||||
amount: '1.00',
|
||||
fee: '0.00',
|
||||
netAmount: '1.00',
|
||||
name: '123',
|
||||
type: '银行卡',
|
||||
account: '4001231221',
|
||||
bank: '中国银行',
|
||||
time: '2025-07-04 15:11',
|
||||
remark: ''
|
||||
}
|
||||
])
|
||||
|
||||
const handleQuery = () => {
|
||||
console.log('Query with:', statusValue.value, methodValue.value, searchKeyword.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finance-withdrawal {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 筛选样式更新 */
|
||||
.filter-card {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dt-picker {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.data-select {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.search-wrap {
|
||||
flex: 1;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 24px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
width: 24%;
|
||||
background-color: #fff;
|
||||
padding: 24px 20px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.blue { background-color: #e6f7ff; color: #1890ff; }
|
||||
.orange { background-color: #fff7e1; color: #fa8c16; }
|
||||
.green { background-color: #f6ffed; color: #52c41a; }
|
||||
.pink { background-color: #fff0f6; color: #eb2f96; }
|
||||
|
||||
.stat-icon { font-size: 24px; font-weight: bold; }
|
||||
|
||||
.stat-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 操作区域 */
|
||||
.action-section {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.btn-record {
|
||||
background-color: #1890ff;
|
||||
border-radius: 4px;
|
||||
padding: 6px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-record-txt {
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 表格容器样式 (Flex 模拟) */
|
||||
.table-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fb;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽定义 (与截图匹配) */
|
||||
.col-id { width: 60px; }
|
||||
.col-user { width: 180px; justify-content: flex-start; }
|
||||
.col-amount { width: 100px; }
|
||||
.col-fee { width: 100px; }
|
||||
.col-net { width: 100px; }
|
||||
.col-method { flex: 1; min-width: 220px; justify-content: flex-start; }
|
||||
.col-qr { width: 80px; }
|
||||
.col-time { width: 150px; }
|
||||
.col-remark { width: 80px; }
|
||||
.col-status { width: 100px; }
|
||||
.col-ops { width: 160px; }
|
||||
|
||||
/* 用户信息单元格 */
|
||||
.user-info-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-box {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background-color: #f0f2f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-nickname {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 提现方式单元格 */
|
||||
.method-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m-line {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.green-txt {
|
||||
color: #52c41a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 收款码 */
|
||||
.qr-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.qr-icon-txt {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 操作项 */
|
||||
.ops-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.op-btn {
|
||||
font-size: 13px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.blue { color: #1890ff; }
|
||||
|
||||
.op-sep {
|
||||
color: #ebeef5;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,483 +0,0 @@
|
||||
<template>
|
||||
<!-- 直接加载 AdminLayout,使用 CRMEB 内部路由系统 -->
|
||||
<AdminLayout />
|
||||
|
||||
|
||||
<!-- 第二行:订单统计图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<view class="header-left">
|
||||
<view class="title-icon">
|
||||
<!-- 不用 emoji,纯样式画一个“图表感”的小方块 -->
|
||||
<view class="title-icon-mark"></view>
|
||||
</view>
|
||||
<text class="admin-card-title">订单</text>
|
||||
</view>
|
||||
|
||||
<view class="chart-controls">
|
||||
<view
|
||||
v-for="p in chartPeriods"
|
||||
:key="p.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: selectedPeriod === p.value }"
|
||||
@click="changePeriod(p.value)"
|
||||
>
|
||||
<text class="seg-btn-text">{{ p.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="admin-card-body">
|
||||
<!-- 图表容器:你后面接 ECharts / uCharts 都挂这里 -->
|
||||
<view class="echarts-container">
|
||||
<!-- 先空着也行;不要放 emoji 占位符 -->
|
||||
111
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第三行:用户统计图表 -->
|
||||
<view class="charts-row">
|
||||
<!-- 用户趋势折线图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户趋势</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">📈 ECharts 折线图:用户增长趋势</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户构成饼图 -->
|
||||
<view class="chart-col">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户构成</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">🥧 ECharts 饼图:用户来源分布</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import KpiMiniCard from './components/KpiMiniCard.uvue'
|
||||
|
||||
// KPI 数据
|
||||
const kpiData = ref({
|
||||
sales: {
|
||||
today: 125680.50,
|
||||
yesterday: 118920.30,
|
||||
monthTotal: 2857808.90,
|
||||
change: 5.7
|
||||
},
|
||||
visits: {
|
||||
today: 15420,
|
||||
yesterday: 14890,
|
||||
monthTotal: 342680,
|
||||
change: 3.4
|
||||
},
|
||||
orders: {
|
||||
today: 342,
|
||||
yesterday: 318,
|
||||
monthTotal: 8956,
|
||||
change: 7.5
|
||||
},
|
||||
users: {
|
||||
today: 156,
|
||||
yesterday: 142,
|
||||
monthTotal: 3245,
|
||||
change: 9.9
|
||||
}
|
||||
})
|
||||
|
||||
// 图表配置
|
||||
const selectedPeriod = ref('30days')
|
||||
const selectedPeriodLabel = computed((): string => {
|
||||
const hit = chartPeriods.value.find((x) => x.value === selectedPeriod.value)
|
||||
return hit ? hit.label : ""
|
||||
})
|
||||
|
||||
const chartPeriods = [
|
||||
{ label: '30天', value: '30days' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '年', value: 'year' }
|
||||
]
|
||||
|
||||
type PeriodItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
|
||||
// 方法
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
const changePeriod = (period: string) => {
|
||||
selectedPeriod.value = period
|
||||
const periodMap: Record<string, string> = {
|
||||
'30days': '30天',
|
||||
'week': '周',
|
||||
'month': '月',
|
||||
'year': '年'
|
||||
}
|
||||
selectedPeriodLabel.value = periodMap[period] || '30天'
|
||||
|
||||
// TODO: 重新加载图表数据
|
||||
console.log('切换时间粒度:', period)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ===== Dashboard 页面样式 ===== */
|
||||
.dashboard-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== KPI 卡片行 ===== */
|
||||
/* 第一行:4 个 KPI 卡片一行 */
|
||||
.kpi-cards-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)); /* 一行 4 列等分 */
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* 卡片本体:不要写死宽高 */
|
||||
.kpi-card{
|
||||
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 200px; /* 你可以改成 140/160,别写死 200px */
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
/* 响应式:宽度不够时变 2 列 / 1 列(可选) */
|
||||
@media (max-width: 1200px){
|
||||
.kpi-cards-row{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
.kpi-cards-row{ grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
|
||||
.kpi-card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-title {
|
||||
position: absolute;
|
||||
|
||||
top: 10rpx;
|
||||
left: 5rpx;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.kpi-card-tag {
|
||||
background-color: #1890ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.kpi-tag-text {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.kpi-card-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-number {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.kpi-value-trend.up {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.kpi-value-trend.down {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.kpi-trend-text {
|
||||
margin-left: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kpi-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.kpi-footer-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.kpi-card-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 图表区域 ===== */
|
||||
|
||||
|
||||
/* 卡片外观 */
|
||||
.admin-card {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头部:左标题 + 右分段按钮(不换行) */
|
||||
.admin-card-header {
|
||||
padding: 16px 24px 12px 24px;
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap; /* 防止被挤下去 */
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 14px;
|
||||
background: #e6f4ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-icon-mark {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.admin-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 分段控件:一整条外框 + 内部分段(完全贴近你第二张图右上角) */
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
flex-direction: row;;
|
||||
align-items: center;
|
||||
justify-content: center;;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
flex-shrink: 0; /* 防止被压缩换行 */
|
||||
}
|
||||
|
||||
.seg-btn {
|
||||
height: 32px;
|
||||
min-width: 44px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.seg-btn:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.seg-btn-text {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.seg-btn.active {
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.seg-btn.active .seg-btn-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ✅ 注意:body 是 header 的兄弟,不要写进 header 嵌套里 */
|
||||
.admin-card-body {
|
||||
padding: 0 24px 16px 24px;
|
||||
}
|
||||
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 300px; /* 贴近截图比例 */
|
||||
}
|
||||
|
||||
.charts-row{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
/* 每个图表列容器 */
|
||||
.chart-col{
|
||||
min-width: 0; /* 防止 ECharts/SVG 内容把列撑爆 */
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
|
||||
.kpi-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.charts-row{
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.kpi-cards-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.dashboard-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.kpi-cards-row,
|
||||
.chart-section,
|
||||
.charts-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.admin-card-header,
|
||||
.admin-card-body {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 图标字体 ===== */
|
||||
.iconfont {
|
||||
font-family: 'iconfont';
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-up:before {
|
||||
content: '↑';
|
||||
}
|
||||
|
||||
.icon-down:before {
|
||||
content: '↓';
|
||||
}
|
||||
|
||||
.icon-sales:before {
|
||||
content: '💰';
|
||||
}
|
||||
|
||||
.icon-visits:before {
|
||||
content: '👁️';
|
||||
}
|
||||
|
||||
.icon-orders:before {
|
||||
content: '📦';
|
||||
}
|
||||
|
||||
.icon-users:before {
|
||||
content: '👥';
|
||||
}
|
||||
</style>
|
||||
317
pages/mall/admin/kefu/config.uvue
Normal file
317
pages/mall/admin/kefu/config.uvue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<view class="admin-kefu-config">
|
||||
<view class="config-container border-shadow">
|
||||
<!-- 页头选项卡 -->
|
||||
<view class="config-tabs">
|
||||
<view class="tab-item active">
|
||||
<text class="tab-txt">客服配置</text>
|
||||
<view class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="config-content">
|
||||
<!-- 客服类型 -->
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="label-txt">客服类型:</text>
|
||||
</view>
|
||||
<view class="input-box flex-row">
|
||||
<view class="radio-group">
|
||||
<view class="radio-item" @click="configType = 'system'">
|
||||
<view :class="['radio-circle', configType === 'system' ? 'checked' : '']">
|
||||
<view v-if="configType === 'system'" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="radio-label">系统客服</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="configType = 'phone'">
|
||||
<view :class="['radio-circle', configType === 'phone' ? 'checked' : '']">
|
||||
<view v-if="configType === 'phone'" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="radio-label">拨打电话</text>
|
||||
</view>
|
||||
<view class="radio-item" @click="configType = 'link'">
|
||||
<view :class="['radio-circle', configType === 'link' ? 'checked' : '']">
|
||||
<view v-if="configType === 'link'" class="radio-inner"></view>
|
||||
</view>
|
||||
<text class="radio-label">跳转链接</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="form-item no-label">
|
||||
<view class="tip-box">
|
||||
<text class="tip-txt">系统客服:点击联系客服使用系统的自带客服;拨打电话:点击联系客服拨打客服电话;跳转链接:跳转外部链接联系客服</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 动态显示内容 -->
|
||||
<template v-if="configType === 'system'">
|
||||
<view class="form-item align-start">
|
||||
<view class="label-box pt-10">
|
||||
<text class="label-txt">客服反馈:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<textarea
|
||||
class="textarea-base"
|
||||
v-model="feedbackText"
|
||||
placeholder="请输入客服反馈内容"
|
||||
/>
|
||||
<text class="input-tip">暂无客服在线是,联系客服跳转的客服反馈页面的显示文字</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else-if="configType === 'phone'">
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="label-txt">客服电话:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="phoneNumber" placeholder="请输入客服电话" />
|
||||
<text class="input-tip">客服类型选择不打电话是,用户点击联系客服的联系电话</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else-if="configType === 'link'">
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="label-txt">跳转链接:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="linkUrl" placeholder="请输入外部链接地址" />
|
||||
<text class="input-tip">客服类型选择跳转链接时,用户点击联系客服跳转的外部链接</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="form-item no-label">
|
||||
<view class="btn-submit" @click="handleSubmit">
|
||||
<text class="submit-txt">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const configType = ref('system') // system, phone, link
|
||||
const feedbackText = ref('尊敬的用户,客服当前不在线,有问题请留言,我们会第一时间进行处理!!!')
|
||||
const phoneNumber = ref('4008888794')
|
||||
const linkUrl = ref('')
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log('提交配置', {
|
||||
type: configType.value,
|
||||
feedback: feedbackText.value,
|
||||
phone: phoneNumber.value,
|
||||
link: linkUrl.value
|
||||
})
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-kefu-config {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.config-container {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.config-tabs {
|
||||
height: 54px;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
margin-right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-txt {
|
||||
font-size: 14px;
|
||||
color: #2d8cf0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #2d8cf0;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.config-content {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.no-label {
|
||||
padding-left: 140px;
|
||||
}
|
||||
|
||||
.label-box {
|
||||
width: 140px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.pt-10 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.label-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
flex: 1;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* Radio Group */
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-circle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.radio-circle.checked {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
|
||||
.radio-inner {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* Tips */
|
||||
.tip-box {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tip-txt {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.input-tip {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.input-base {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.textarea-base {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn-submit {
|
||||
width: 64px;
|
||||
height: 34px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submit-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
347
pages/mall/admin/kefu/feedback.uvue
Normal file
347
pages/mall/admin/kefu/feedback.uvue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<view class="admin-kefu-feedback">
|
||||
<view class="content-body">
|
||||
<!-- 顶部搜索栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">留言时间:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="placeholder">开始日期</text>
|
||||
<text class="sep">-</text>
|
||||
<text class="placeholder">结束日期</text>
|
||||
<text class="calendar-icon">📅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">留言信息:</text>
|
||||
<input class="search-input" placeholder="请输入用户昵称/电话/留言内容搜索" v-model="searchQuery" />
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-nick"><text class="th-txt">昵称</text></view>
|
||||
<view class="th col-phone"><text class="th-txt">电话</text></view>
|
||||
<view class="th col-content"><text class="th-txt">内容</text></view>
|
||||
<view class="th col-status"><text class="th-txt">状态</text></view>
|
||||
<view class="th col-time"><text class="th-txt">时间</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in feedbackList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-nick"><text class="td-txt">{{ item.nickname }}</text></view>
|
||||
<view class="td col-phone"><text class="td-txt">{{ item.phone }}</text></view>
|
||||
<view class="td col-content"><text class="td-txt ellipsis">{{ item.content }}</text></view>
|
||||
<view class="td col-status">
|
||||
<text class="status-tag">{{ item.status }}</text>
|
||||
</view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-link" @click="handleProcess(item)">处理</text>
|
||||
<view class="divider"></view>
|
||||
<text class="btn-link danger" @click="handleDelete(item)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-bar">
|
||||
<text class="page-total">共 {{ feedbackList.length }} 条</text>
|
||||
<view class="page-nav">
|
||||
<view class="nav-btn"><text class="nav-icon"> < </text></view>
|
||||
<view class="nav-item active"><text class="nav-num">1</text></view>
|
||||
<view class="nav-btn"><text class="nav-icon"> > </text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface FeedbackItem {
|
||||
id: string
|
||||
nickname: string
|
||||
phone: string
|
||||
content: string
|
||||
status: string
|
||||
time: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const feedbackList = ref<FeedbackItem[]>([
|
||||
{ id: '37', nickname: 'hhh', phone: '14779580008', content: 'hhh', status: '未处理', time: '2025-12-20 03:22:10' },
|
||||
{ id: '36', nickname: 'hhh', phone: '14779580008', content: 'hhh', status: '未处理', time: '2025-12-20 03:22:05' },
|
||||
{ id: '35', nickname: '发给大哥他', phone: '13212324567', content: '回复挂号费', status: '未处理', time: '2025-12-17 22:11:35' },
|
||||
{ id: '34', nickname: '郑立民', phone: '15604580931', content: '我提现为啥不到账', status: '未处理', time: '2025-11-24 12:29:02' },
|
||||
{ id: '33', nickname: '11', phone: '15012760793', content: '22222', status: '未处理', time: '2025-10-16 17:55:31' },
|
||||
{ id: '32', nickname: '玉兔', phone: '13133164548', content: '我去会吐', status: '未处理', time: '2025-09-09 15:31:45' },
|
||||
{ id: '31', nickname: '哟', phone: '15151424728', content: '玩意', status: '未处理', time: '2025-09-04 15:40:28' },
|
||||
{ id: '30', nickname: '哟', phone: '15151424728', content: '玩意', status: '未处理', time: '2025-09-04 15:40:27' },
|
||||
{ id: '29', nickname: 'pww', phone: '15274289992', content: '我们的', status: '未处理', time: '2025-08-13 18:21:35' },
|
||||
{ id: '28', nickname: '1', phone: '18888888888', content: '1', status: '未处理', time: '2024-12-12 11:56:48' }
|
||||
])
|
||||
|
||||
const handleQuery = () => {
|
||||
console.log('查询:', searchQuery.value)
|
||||
}
|
||||
|
||||
const handleProcess = (item: FeedbackItem) => {
|
||||
console.log('处理留言:', item.id)
|
||||
}
|
||||
|
||||
const handleDelete = (item: FeedbackItem) => {
|
||||
console.log('删除留言:', item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-kefu-feedback {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.date-picker-mock {
|
||||
width: 320px;
|
||||
height: 38px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.sep {
|
||||
margin: 0 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 320px;
|
||||
height: 38px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
width: 76px;
|
||||
height: 34px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.query-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表格卡片 */
|
||||
.table-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
height: 50px;
|
||||
background-color: #eaf2ff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.th {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 列宽定义 */
|
||||
.col-id { width: 80px; justify-content: center; }
|
||||
.col-nick { width: 140px; }
|
||||
.col-phone { width: 180px; }
|
||||
.col-content { flex: 1; justify-content: flex-start; }
|
||||
.col-status { width: 120px; justify-content: center; }
|
||||
.col-time { width: 220px; justify-content: center; }
|
||||
.col-op {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-link.danger {
|
||||
color: #2d8cf0; /* 根据截图,删除也是蓝色的链接感,但通常后台会区分,这里按截图感肉眼识别为蓝色 */
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background-color: #e8eaec;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
/* 分页栏 */
|
||||
.pagination-bar {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.page-total {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.page-nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdee2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdee2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: #2d8cf0;
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-num {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-num, .nav-icon {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
324
pages/mall/admin/kefu/list.uvue
Normal file
324
pages/mall/admin/kefu/list.uvue
Normal file
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<view class="admin-kefu-list">
|
||||
<!-- 顶部操作栏 -->
|
||||
<view class="page-top-bar">
|
||||
<view class="btn-primary" @click="handleAdd">
|
||||
<text class="btn-txt">添加客服</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-avatar"><text class="th-txt">客服头像</text></view>
|
||||
<view class="th col-name"><text class="th-txt">客服名称</text></view>
|
||||
<view class="th col-status"><text class="th-txt">客服状态</text></view>
|
||||
<view class="th col-time"><text class="th-txt">添加时间</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in kefuList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-avatar">
|
||||
<image class="kefu-avatar" :src="item.avatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="td col-name"><text class="td-txt">{{ item.name }}</text></view>
|
||||
<view class="td col-status">
|
||||
<view :class="['switch-btn', item.status ? 'switch-on' : 'switch-off']" @click="toggleStatus(item)">
|
||||
<view class="switch-inner">
|
||||
<text class="switch-txt">{{ item.status ? '开启' : '关闭' }}</text>
|
||||
</view>
|
||||
<view class="switch-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-action">编辑</text>
|
||||
<view class="divider"></view>
|
||||
<text class="btn-action">删除</text>
|
||||
<view class="divider"></view>
|
||||
<text class="btn-action">进入工作台</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页栏 -->
|
||||
<view class="pagination-bar">
|
||||
<text class="page-total">共 {{ kefuList.length }} 条</text>
|
||||
<view class="page-size-select">
|
||||
<text class="size-txt">15条/页</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
<view class="page-nav">
|
||||
<view class="nav-prev"><text class="nav-icon"> < </text></view>
|
||||
<view class="nav-item active"><text class="nav-num">1</text></view>
|
||||
<view class="nav-next"><text class="nav-icon"> > </text></view>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" value="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface KefuItem {
|
||||
id: string
|
||||
avatar: string
|
||||
name: string
|
||||
status: boolean
|
||||
time: string
|
||||
}
|
||||
|
||||
const kefuList = ref<KefuItem[]>([
|
||||
{ id: '167', avatar: '/static/logo.png', name: '客服', status: true, time: '2025-12-31 11:24:35' },
|
||||
{ id: '166', avatar: '/static/logo.png', name: '测试', status: true, time: '2025-11-18 17:33:26' },
|
||||
{ id: '165', avatar: '/static/logo.png', name: 'zhicheng', status: true, time: '2025-09-10 10:13:00' },
|
||||
{ id: '164', avatar: '/static/logo.png', name: 'kefu', status: true, time: '2025-07-29 11:50:20' },
|
||||
{ id: '162', avatar: '/static/logo.png', name: '呐呐', status: true, time: '2024-12-09 10:51:11' },
|
||||
{ id: '153', avatar: '/static/logo.png', name: '小天', status: true, time: '2024-09-19 16:04:59' },
|
||||
{ id: '152', avatar: '/static/logo.png', name: 'kk', status: true, time: '2024-09-16 00:05:22' }
|
||||
])
|
||||
|
||||
const toggleStatus = (item: KefuItem) => {
|
||||
item.status = !item.status
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
console.log('Add Kefu')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-kefu-list {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-top-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100px;
|
||||
height: 36px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
height: 50px;
|
||||
background-color: #e6f0ff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.th {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #f9fbff;
|
||||
}
|
||||
|
||||
.td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 列宽百分比对齐截图 */
|
||||
.col-id { width: 80px; }
|
||||
.col-avatar { width: 120px; }
|
||||
.col-name { flex: 1; justify-content: flex-start; }
|
||||
.col-status { width: 150px; }
|
||||
.col-time { width: 220px; }
|
||||
.col-op {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.kefu-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 开关组件 1:1 复刻 */
|
||||
.switch-btn {
|
||||
width: 60px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.switch-on { background-color: #2d8cf0; }
|
||||
.switch-off { background-color: #ccc; }
|
||||
|
||||
.switch-inner {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.switch-txt {
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.switch-dot {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #fff;
|
||||
border-radius: 9px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.switch-on .switch-dot { right: 3px; }
|
||||
.switch-off .switch-dot { left: 3px; }
|
||||
|
||||
/* 操作按钮 */
|
||||
.btn-action {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background-color: #e8eaec;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
/* 分页栏 */
|
||||
.pagination-bar {
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
|
||||
|
||||
.page-size-select {
|
||||
border: 1px solid #dcdee2;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.size-txt { font-size: 13px; color: #606266; margin-right: 8px; }
|
||||
.arrow-down { font-size: 10px; color: #808695; }
|
||||
|
||||
.page-nav { display: flex; flex-direction: row; align-items: center; margin-right: 15px; }
|
||||
.nav-prev, .nav-next {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdee2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdee2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #2d8cf0;
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
|
||||
.active .nav-num { color: #fff; }
|
||||
.nav-num, .nav-icon { font-size: 13px; color: #606266; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; }
|
||||
.jump-txt { font-size: 13px; color: #606266; }
|
||||
.jump-input {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
margin: 0 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
626
pages/mall/admin/kefu/words.uvue
Normal file
626
pages/mall/admin/kefu/words.uvue
Normal file
@@ -0,0 +1,626 @@
|
||||
<template>
|
||||
<view class="admin-kefu-words">
|
||||
<view class="words-container">
|
||||
<!-- 左侧分类 -->
|
||||
<view class="category-sidebar border-shadow">
|
||||
<view class="sidebar-header" @click="handleAddCategory">
|
||||
<text class="plus-icon">+</text>
|
||||
<text class="header-txt">添加分类</text>
|
||||
</view>
|
||||
<scroll-view class="category-list" scroll-y="true">
|
||||
<view
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
:class="['category-item', activeCategoryId == cat.id ? 'active' : '']"
|
||||
@click="selectCategory(cat.id)"
|
||||
>
|
||||
<view class="folder-icon">📂</view>
|
||||
<text class="cat-name">{{ cat.name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧话术列表 -->
|
||||
<view class="content-main">
|
||||
<view class="top-bar">
|
||||
<view class="btn-primary" @click="handleAddWord">
|
||||
<text class="btn-txt">添加话术</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="table-card border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-cat"><text class="th-txt">分类</text></view>
|
||||
<view class="th col-title"><text class="th-txt">标题</text></view>
|
||||
<view class="th col-detail"><text class="th-txt">详情</text></view>
|
||||
<view class="th col-sort"><text class="th-txt">排序</text></view>
|
||||
<view class="th col-time"><text class="th-txt">添加时间</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in wordList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-cat"><text class="td-txt">{{ item.category }}</text></view>
|
||||
<view class="td col-title"><text class="td-txt">{{ item.title }}</text></view>
|
||||
<view class="td col-detail">
|
||||
<text class="td-txt ellipsis-2">{{ item.content }}</text>
|
||||
</view>
|
||||
<view class="td col-sort"><text class="td-txt">{{ item.sort }}</text></view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-action" @click="handleEdit(item)">编辑</text>
|
||||
<view class="divider"></view>
|
||||
<text class="btn-action danger">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-bar">
|
||||
<text class="page-total">共 {{ wordList.length }} 条</text>
|
||||
<view class="page-size-select">
|
||||
<text class="size-txt">15条/页</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
<view class="page-nav">
|
||||
<view class="nav-prev"><text class="nav-icon"> < </text></view>
|
||||
<view class="nav-item active"><text class="nav-num">1</text></view>
|
||||
<view class="nav-next"><text class="nav-icon"> > </text></view>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" value="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 侧边弹窗 (Drawer) -->
|
||||
<view v-if="showDrawer" class="drawer-mask" @click="closeDrawer">
|
||||
<view class="drawer-content" @click.stop>
|
||||
<view class="drawer-header">
|
||||
<text class="drawer-title">{{ isEdit ? '编辑话术' : '添加话术' }}</text>
|
||||
<text class="close-btn" @click="closeDrawer">×</text>
|
||||
</view>
|
||||
|
||||
<view class="drawer-body">
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="label-txt">话术分类:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<view class="select-mock">
|
||||
<text class="select-val">{{ formData.category || '请选择分类' }}</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="required">*</text>
|
||||
<text class="label-txt">话术标题:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="formData.title" placeholder="请输入话术标题" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item align-start">
|
||||
<view class="label-box">
|
||||
<text class="required">*</text>
|
||||
<text class="label-txt">话术内容:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<textarea class="textarea-base" v-model="formData.content" placeholder="请输入话术内容" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label-box">
|
||||
<text class="label-txt">排序:</text>
|
||||
</view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="formData.sort" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="drawer-footer">
|
||||
<view class="btn-cancel" @click="closeDrawer">
|
||||
<text class="cancel-txt">取消</text>
|
||||
</view>
|
||||
<view class="btn-confirm" @click="submitForm">
|
||||
<text class="confirm-txt">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
interface WordItem {
|
||||
id: string
|
||||
category: string
|
||||
title: string
|
||||
content: string
|
||||
sort: number
|
||||
time: string
|
||||
}
|
||||
|
||||
interface Category {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
const categories = ref<Category[]>([
|
||||
{ id: 1, name: '全部' },
|
||||
{ id: 2, name: '系统话术' }
|
||||
])
|
||||
|
||||
const activeCategoryId = ref(1)
|
||||
|
||||
const wordList = ref<WordItem[]>([
|
||||
{
|
||||
id: '67',
|
||||
category: '系统默认',
|
||||
title: '旗舰版介绍',
|
||||
content: '【旗舰版】可以授权给公司或者个人,企业自用搭建,不限制授权域名,提供专属技术总监、产品总监等。',
|
||||
sort: 0,
|
||||
time: '2024-09-27 10:15:07'
|
||||
}
|
||||
])
|
||||
|
||||
// 表单逻辑
|
||||
const showDrawer = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
category: '系统话术',
|
||||
title: '',
|
||||
content: '',
|
||||
sort: 0
|
||||
})
|
||||
|
||||
const selectCategory = (id: number) => {
|
||||
activeCategoryId.value = id
|
||||
}
|
||||
|
||||
const handleAddCategory = () => {
|
||||
console.log('添加分类')
|
||||
}
|
||||
|
||||
const handleAddWord = () => {
|
||||
isEdit.value = false
|
||||
formData.id = ''
|
||||
formData.title = ''
|
||||
formData.content = ''
|
||||
formData.sort = 0
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (item: WordItem) => {
|
||||
isEdit.value = true
|
||||
formData.id = item.id
|
||||
formData.category = item.category
|
||||
formData.title = item.title
|
||||
formData.content = item.content
|
||||
formData.sort = item.sort
|
||||
showDrawer.value = true
|
||||
}
|
||||
|
||||
const closeDrawer = () => {
|
||||
showDrawer.value = false
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
console.log('提交表单', formData)
|
||||
showDrawer.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-kefu-words {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.words-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(100vh - 40px);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 左侧分类 */
|
||||
.category-sidebar {
|
||||
width: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plus-icon {
|
||||
font-size: 18px;
|
||||
color: #c0c4cc;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.header-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.category-item.active {
|
||||
background-color: #e6f7ff;
|
||||
border-right: 2px solid #2d8cf0;
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cat-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 右侧内容 */
|
||||
.content-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 90px;
|
||||
height: 34px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-txt {
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
height: 50px;
|
||||
background-color: #e6f0ff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.th {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.th-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.td-txt {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 列宽分配 */
|
||||
.col-id { width: 60px; }
|
||||
.col-cat { width: 100px; }
|
||||
.col-title { width: 150px; }
|
||||
.col-detail { flex: 1; justify-content: flex-start; }
|
||||
.col-sort { width: 80px; }
|
||||
.col-time { width: 180px; }
|
||||
.col-op { width: 150px; }
|
||||
|
||||
.btn-action {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: #ed4014;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background-color: #e8eaec;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination-bar {
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.page-total { font-size: 13px; color: #606266; margin-right: 15px; }
|
||||
|
||||
.page-size-select {
|
||||
border: 1px solid #dcdee2;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.size-txt { font-size: 13px; color: #606266; margin-right: 8px; }
|
||||
.nav-icon, .arrow-down { font-size: 10px; color: #808695; }
|
||||
|
||||
.page-nav { display: flex; flex-direction: row; align-items: center; margin-right: 15px; }
|
||||
.nav-prev, .nav-next, .nav-item {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdee2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: #2d8cf0;
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
|
||||
.active .nav-num { color: #fff; }
|
||||
.nav-num { font-size: 13px; color: #606266; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; }
|
||||
.jump-txt { font-size: 13px; color: #606266; }
|
||||
.jump-input {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
margin: 0 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 抽屉 Drawer 1:1 复刻 - 修正位置到右侧 */
|
||||
.drawer-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0; /* 强制靠右占据右边屏幕 */
|
||||
width: 50%; /* 占屏幕一半宽度 */
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
height: 55px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drawer-body {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.label-box {
|
||||
width: 100px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #ed4014;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.label-txt {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.select-mock {
|
||||
height: 36px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.select-val { font-size: 14px; color: #333; }
|
||||
|
||||
.input-base {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.textarea-base {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
height: 60px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
padding: 8px 18px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
padding: 8px 18px;
|
||||
background-color: #2d8cf0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cancel-txt { font-size: 14px; color: #606266; }
|
||||
.confirm-txt { font-size: 14px; color: #fff; }
|
||||
</style>
|
||||
57
pages/mall/admin/maintain/dev/config.uvue
Normal file
57
pages/mall/admin/maintain/dev/config.uvue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<view class="admin-maintain-config">
|
||||
<view class="page-header border-shadow">
|
||||
<text class="page-title">开发配置 - 配置分类</text>
|
||||
<view class="btn-primary"><text class="btn-txt">+ 添加配置</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-container border-shadow">
|
||||
<view class="table-header">
|
||||
<text class="th col-id">ID</text>
|
||||
<text class="th col-name">标题</text>
|
||||
<text class="th col-key">分类Key</text>
|
||||
<text class="th col-type">类型</text>
|
||||
<text class="th col-status">状态</text>
|
||||
<text class="th col-op">操作</text>
|
||||
</view>
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in 5" :key="item">
|
||||
<text class="td col-id">{{ item }}</text>
|
||||
<text class="td col-name">基础配置 {{ item }}</text>
|
||||
<text class="td col-key">basic_conf_{{ item }}</text>
|
||||
<text class="td col-type">系统配置</text>
|
||||
<view class="td col-status"><text class="status-tag success">显</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-link">查看</text>
|
||||
<text class="btn-link">编辑</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-maintain-config { padding: 20px; }
|
||||
.border-shadow { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
|
||||
.page-header { padding: 20px 24px; margin-bottom: 20px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
|
||||
.page-title { font-size: 16px; font-weight: 600; color: #303133; }
|
||||
.btn-primary { padding: 8px 16px; background-color: #1890ff; border-radius: 4px; }
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.table-header { display: flex; flex-direction: row; background-color: #f8f9fb; border-bottom: 1px solid #ebeef5; }
|
||||
.th { padding: 12px 10px; font-size: 14px; color: #909399; text-align: center; font-weight: 500; }
|
||||
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #ebeef5; align-items: center; }
|
||||
.td { padding: 15px 10px; font-size: 13px; color: #606266; text-align: center; }
|
||||
|
||||
.col-id { width: 80px; }
|
||||
.col-name { flex: 1; text-align: left; justify-content: flex-start; }
|
||||
.col-key { width: 150px; }
|
||||
.col-type { width: 120px; }
|
||||
.col-status { width: 80px; }
|
||||
.col-op { width: 150px; }
|
||||
|
||||
.status-tag { padding: 2px 6px; border-radius: 3px; font-size: 11px; }
|
||||
.success { background-color: #f0f9eb; color: #67c23a; border: 1px solid #e1f3d8; }
|
||||
.btn-link { color: #1890ff; margin: 0 8px; font-size: 13px; cursor: pointer; }
|
||||
</style>
|
||||
@@ -1,65 +1,324 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="coupon-list">
|
||||
<view class="page">
|
||||
<view class="Header">
|
||||
<text class="Title">优惠券列表</text>
|
||||
<text class="SubTitle">marketing/coupon/list</text>
|
||||
<view class="admin-marketing-coupon">
|
||||
<view class="content-body">
|
||||
<!-- 搜索过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item item-w">
|
||||
<text class="label-txt">优惠券名称:</text>
|
||||
<view class="input-wrap">
|
||||
<input class="search-input" placeholder="请输入优惠券名称" v-model="filter.name" />
|
||||
<text class="count-txt">0/18</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="Card">
|
||||
<text class="Label">页面参数(query)</text>
|
||||
<text class="Mono">{{ params }}</text>
|
||||
<view class="filter-item item-w">
|
||||
<text class="label-txt">优惠券类型:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item item-w">
|
||||
<text class="label-txt">是否有效:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-row mt-20">
|
||||
<view class="filter-item item-w">
|
||||
<text class="label-txt">发放方式:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据展示区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="card-header">
|
||||
<view class="btn-primary-blue" @click="handleAdd">
|
||||
<text class="btn-txt">添加优惠券</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表格主体 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="width: 80px;">ID</view>
|
||||
<view class="th" style="width: 180px;">优惠券名称</view>
|
||||
<view class="th" style="width: 120px;">优惠券类型</view>
|
||||
<view class="th" style="width: 100px;">面值</view>
|
||||
<view class="th" style="width: 120px;">领取方式</view>
|
||||
<view class="th" style="width: 150px;">领取日期</view>
|
||||
<view class="th" style="width: 120px;">使用时间</view>
|
||||
<view class="th" style="width: 120px;">发布数量</view>
|
||||
<view class="th" style="width: 100px;">是否开启</view>
|
||||
<view class="th" style="flex: 1; min-width: 220px;">操作</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in dataList" :key="item.id" class="table-row">
|
||||
<view class="td" style="width: 80px;"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td" style="width: 180px;"><text class="td-txt name-bold">{{ item.name }}</text></view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.type }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt price-txt">{{ item.value.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.receiveType }}</text></view>
|
||||
<view class="td" style="width: 150px;">
|
||||
<text v-if="item.id === 1628" class="td-txt date-small">2023-10-18 00:00 - 2025-11-05 00:00</text>
|
||||
<text v-else class="td-txt">{{ item.receiveDate }}</text>
|
||||
</view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.useTime }}</text></view>
|
||||
<view class="td" style="width: 120px;">
|
||||
<view v-if="item.publishTotal > 0" class="pub-info">
|
||||
<text class="pub-txt">发布: {{ item.publishTotal }}</text>
|
||||
<text class="pub-txt danger">剩余: {{ item.publishRemain }}</text>
|
||||
</view>
|
||||
<text v-else class="td-txt">不限量</text>
|
||||
</view>
|
||||
<view class="td" style="width: 100px;">
|
||||
<view :class="['switch-box', item.isOpen ? 'active' : '']" @click="toggleStatus(index)">
|
||||
<view class="switch-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td" style="flex: 1; min-width: 220px;">
|
||||
<view class="op-links">
|
||||
<text class="op-link">领取记录</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link">编辑</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link">复制</text>
|
||||
<text class="op-split">|</text>
|
||||
<text class="op-link text-danger">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<text class="total-txt">共 16 条</text>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">2</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
interface CouponItem {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
value: number
|
||||
receiveType: string
|
||||
receiveDate: string
|
||||
useTime: string
|
||||
publishTotal: number
|
||||
publishRemain: number
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
const params = ref('')
|
||||
|
||||
onLoad((options) => {
|
||||
// options: Record<string, any>
|
||||
params.value = JSON.stringify(options ?? {})
|
||||
const filter = reactive({
|
||||
name: ''
|
||||
})
|
||||
|
||||
const dataList = ref<CouponItem[]>([
|
||||
{ id: 1643, name: '满100减30', type: '通用券', value: 30.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '10天', publishTotal: 0, publishRemain: 0, isOpen: false },
|
||||
{ id: 1642, name: '满10减7', type: '通用券', value: 7.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '10天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1641, name: '会员优惠券', type: '通用券', value: 200.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '200天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1640, name: '会员优惠券', type: '通用券', value: 29.90, receiveType: '用户领取', receiveDate: '不限时', useTime: '200天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1639, name: '会员优惠券', type: '通用券', value: 1.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '200天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1638, name: '商品券', type: '商品券', value: 1.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '200天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1636, name: '测试多个商品消耗一个券', type: '商品券', value: 500.00, receiveType: '系统赠送', receiveDate: '不限时', useTime: '3天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1635, name: '优惠券', type: '通用券', value: 10.00, receiveType: '系统赠送', receiveDate: '不限时', useTime: '10天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1634, name: '限时优惠', type: '通用券', value: 20.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '5天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1633, name: '店庆券', type: '品类券', value: 100.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '10天', publishTotal: 0, publishRemain: 0, isOpen: true },
|
||||
{ id: 1632, name: '优惠券', type: '品类券', value: 99.00, receiveType: '用户领取', receiveDate: '不限时', useTime: '10天', publishTotal: 8999, publishRemain: 8604, isOpen: true },
|
||||
{ id: 1628, name: '全场通用券', type: '通用券', value: 9.90, receiveType: '用户领取', receiveDate: 'RANGE', useTime: '不限时', publishTotal: 59999, publishRemain: 59331, isOpen: true }
|
||||
])
|
||||
|
||||
const handleQuery = () => { console.log('Querying...') }
|
||||
const handleAdd = () => { console.log('Adding coupon...') }
|
||||
const toggleStatus = (index: number) => {
|
||||
dataList.value[index].isOpen = !dataList.value[index].isOpen
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Page {
|
||||
padding: 24rpx;
|
||||
<style scoped lang="scss">
|
||||
.admin-marketing-coupon {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
.Header {
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.Title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.SubTitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.7;
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card { padding: 24px; }
|
||||
.filter-row { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 24px; }
|
||||
.mt-20 { margin-top: 20px; }
|
||||
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.item-w { width: 320px; }
|
||||
|
||||
.label-txt { font-size: 14px; color: #606266; min-width: 80px; text-align: right; }
|
||||
|
||||
.input-wrap {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.Card {
|
||||
margin-top: 24rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
.search-input { flex: 1; height: 100%; font-size: 14px; }
|
||||
.count-txt { font-size: 12px; color: #c0c4cc; }
|
||||
|
||||
.select-mock {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.Label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
.select-val { font-size: 14px; color: #c0c4cc; }
|
||||
.arrow-down { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.Mono {
|
||||
font-size: 24rpx;
|
||||
font-family: monospace;
|
||||
line-height: 36rpx;
|
||||
word-break: break-all;
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card { background-color: #fff; display: flex; flex-direction: column; }
|
||||
.card-header { padding: 20px; }
|
||||
|
||||
.btn-primary-blue {
|
||||
background-color: #2d8cf0;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.table-container { padding: 0 20px 20px; }
|
||||
.table-header-row { display: flex; flex-direction: row; background-color: #f8f8f9; border-bottom: 1px solid #e8eaec; }
|
||||
.th { padding: 12px 10px; font-size: 14px; color: #515a6e; font-weight: bold; }
|
||||
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; border-left: 1px solid transparent; }
|
||||
.table-row:hover { background-color: #ebf7ff; }
|
||||
|
||||
.td { padding: 12px 10px; display: flex; align-items: center; }
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
.name-bold { font-weight: 500; color: #333; }
|
||||
.price-txt { color: #515a6e; }
|
||||
.date-small { font-size: 12px; line-height: 1.4; color: #999; }
|
||||
|
||||
.pub-info { display: flex; flex-direction: column; }
|
||||
.pub-txt { font-size: 12px; color: #2d8cf0; }
|
||||
.pub-txt.danger { color: #ed4014; }
|
||||
|
||||
/* Switch 开关 */
|
||||
.switch-box {
|
||||
width: 44px;
|
||||
height: 22px;
|
||||
background-color: #dcdfe6;
|
||||
border-radius: 11px;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.switch-box.active { background-color: #2d8cf0; }
|
||||
.switch-dot {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #fff;
|
||||
border-radius: 9px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.switch-box.active .switch-dot { transform: translateX(22px); }
|
||||
|
||||
.op-links { display: flex; flex-direction: row; align-items: center; }
|
||||
.op-link { color: #2d8cf0; font-size: 14px; cursor: pointer; margin: 0 5px; }
|
||||
.op-split { color: #e8eaec; margin: 0 5px; }
|
||||
.text-danger { color: #ed4014; }
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||
</style>
|
||||
|
||||
|
||||
230
pages/mall/admin/marketing/coupon/user.uvue
Normal file
230
pages/mall/admin/marketing/coupon/user.uvue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<view class="admin-marketing-coupon-user">
|
||||
<view class="content-body">
|
||||
<!-- 搜索过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">是否有效:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">领取人:</text>
|
||||
<input class="search-input" placeholder="请输入领取人" v-model="filter.username" />
|
||||
</view>
|
||||
|
||||
<view class="filter-item filter-long">
|
||||
<text class="label-txt">优惠券搜索:</text>
|
||||
<input class="search-input input-wide" placeholder="请输入优惠券名称" v-model="filter.couponName" />
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要表格区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="width: 100px;">ID</view>
|
||||
<view class="th" style="width: 150px;">优惠券名称</view>
|
||||
<view class="th" style="width: 180px;">领取人</view>
|
||||
<view class="th" style="width: 100px;">面值</view>
|
||||
<view class="th" style="width: 120px;">最低消费额</view>
|
||||
<view class="th" style="width: 180px;">开始使用时间</view>
|
||||
<view class="th" style="width: 180px;">结束使用时间</view>
|
||||
<view class="th" style="width: 120px;">获取方式</view>
|
||||
<view class="th" style="width: 100px;">是否可用</view>
|
||||
<view class="th" style="flex: 1; min-width: 100px;">状态</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="item in recordList" :key="item.id" class="table-row">
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.couponName }}</text></view>
|
||||
<view class="td" style="width: 180px;"><text class="td-txt">{{ item.username }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt price-txt">{{ item.value.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt price-txt">{{ item.minSpend.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 180px;"><text class="td-txt time-txt">{{ item.startTime }}</text></view>
|
||||
<view class="td" style="width: 180px;"><text class="td-txt time-txt">{{ item.endTime }}</text></view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.getType }}</text></view>
|
||||
<view class="td" style="width: 100px;">
|
||||
<text v-if="item.isValid" class="status-ic-success">✓</text>
|
||||
<text v-else class="status-ic-fail">×</text>
|
||||
</view>
|
||||
<view class="td" style="flex: 1; min-width: 100px;"><text :class="['td-txt', item.status === '已使用' ? 'status-used' : '']">{{ item.status }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<text class="total-txt">共 16 条</text>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">2</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
interface CouponRecord {
|
||||
id: number
|
||||
couponName: string
|
||||
username: string
|
||||
value: number
|
||||
minSpend: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
getType: string
|
||||
isValid: boolean
|
||||
status: string
|
||||
}
|
||||
|
||||
const filter = reactive({
|
||||
username: '',
|
||||
couponName: ''
|
||||
})
|
||||
|
||||
const recordList = ref<CouponRecord[]>([
|
||||
{ id: 217732, couponName: '满10减7', username: '嘻嘻', value: 7.00, minSpend: 0.00, startTime: '2026-02-03 17:23', endTime: '2026-02-13 17:23', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217731, couponName: '满10减7', username: '1岁上班22岁退休', value: 7.00, minSpend: 0.00, startTime: '2026-02-03 17:20', endTime: '2026-02-13 17:20', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217730, couponName: '店庆券', username: '136****0434', value: 100.00, minSpend: 1000.00, startTime: '2026-02-03 17:13', endTime: '2026-02-13 17:13', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217729, couponName: '优惠券', username: '187****2801', value: 100.00, minSpend: 599.00, startTime: '2026-02-03 16:36', endTime: '2026-03-05 16:36', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217728, couponName: '会员优惠券', username: '彭祖Dean', value: 29.90, minSpend: 0.00, startTime: '2026-02-03 16:06', endTime: '2026-08-22 16:06', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217727, couponName: '会员优惠券', username: '彭祖Dean', value: 200.00, minSpend: 0.00, startTime: '2026-02-03 16:06', endTime: '2026-08-22 16:06', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217726, couponName: '满10减7', username: '181****6929', value: 7.00, minSpend: 0.00, startTime: '2026-02-03 15:56', endTime: '2026-02-13 15:56', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217725, couponName: '优惠券', username: '181****3601', value: 100.00, minSpend: 599.00, startTime: '2026-02-03 15:51', endTime: '2026-03-05 15:51', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217724, couponName: '商品券', username: '陌年微凉TL', value: 1.00, minSpend: 0.00, startTime: '2026-02-03 15:17', endTime: '2026-08-22 15:17', getType: '手动领取', isValid: true, status: '未使用' },
|
||||
{ id: 217723, couponName: '限时优惠', username: '陌年微凉TL', value: 20.00, minSpend: 199.00, startTime: '2026-02-03 15:17', endTime: '2026-02-08 15:17', getType: '手动领取', isValid: false, status: '已使用' }
|
||||
])
|
||||
|
||||
const handleQuery = () => { console.log('Querying redemption records...') }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-marketing-coupon-user {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card { padding: 24px; }
|
||||
.filter-row { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 32px; }
|
||||
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 12px; }
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
|
||||
.select-mock {
|
||||
width: 180px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-val { font-size: 14px; color: #c0c4cc; }
|
||||
.arrow-down { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
.search-input {
|
||||
width: 180px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.input-wide { width: 240px; }
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card { padding-top: 20px; }
|
||||
.table-container { padding: 0 20px 20px; }
|
||||
.table-header-row { display: flex; flex-direction: row; background-color: #f8f8f9; border-bottom: 1px solid #e8eaec; }
|
||||
.th { padding: 12px 10px; font-size: 14px; color: #515a6e; font-weight: bold; }
|
||||
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; }
|
||||
.table-row:hover { background-color: #ebf7ff; }
|
||||
|
||||
.td { padding: 12px 10px; display: flex; align-items: center; }
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
.price-txt { color: #515a6e; }
|
||||
.time-txt { color: #515a6e; }
|
||||
|
||||
.status-ic-success { color: #2d8cf0; font-weight: bold; font-size: 16px; margin-left: 10px; }
|
||||
.status-ic-fail { color: #ed4014; font-weight: bold; font-size: 16px; margin-left: 10px; }
|
||||
|
||||
.status-used { color: #999; }
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||
</style>
|
||||
480
pages/mall/admin/marketing/integral/statistic.uvue
Normal file
480
pages/mall/admin/marketing/integral/statistic.uvue
Normal file
@@ -0,0 +1,480 @@
|
||||
<template>
|
||||
<view class="admin-marketing-integral-statistic">
|
||||
<view class="content-body">
|
||||
<!-- 顶部时间选择 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">时间选择:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="calendar-ic">📅</text>
|
||||
<text class="date-range">2026/01/05 - 2026/02/03</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标卡片 -->
|
||||
<view class="stats-row">
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-blue">
|
||||
<text class="sc-icon">💠</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">744904340.25</text>
|
||||
<text class="sc-label">当前积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-orange">
|
||||
<text class="sc-icon">🪙</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">59026484</text>
|
||||
<text class="sc-label">累计总积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card border-shadow">
|
||||
<view class="sc-left bg-green">
|
||||
<text class="sc-icon">💎</text>
|
||||
</view>
|
||||
<view class="sc-right">
|
||||
<text class="sc-val">3189</text>
|
||||
<text class="sc-label">累计消耗积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 积分使用趋势 -->
|
||||
<view class="chart-card border-shadow">
|
||||
<view class="chart-header">
|
||||
<text class="chart-title">积分使用趋势</text>
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item">
|
||||
<view class="l-dot bg-blue-line"></view>
|
||||
<text class="l-txt">积分积累</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="l-dot bg-green-line"></view>
|
||||
<text class="l-txt">积分消耗</text>
|
||||
</view>
|
||||
<text class="down-ic">📥</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Mock 线图 -->
|
||||
<view class="line-chart-box">
|
||||
<view class="y-labels">
|
||||
<text class="y-txt">3,500,000</text>
|
||||
<text class="y-txt">3,000,000</text>
|
||||
<text class="y-txt">2,500,000</text>
|
||||
<text class="y-txt">2,000,000</text>
|
||||
<text class="y-txt">1,500,000</text>
|
||||
<text class="y-txt">1,000,000</text>
|
||||
<text class="y-txt">500,000</text>
|
||||
<text class="y-txt">0</text>
|
||||
</view>
|
||||
<view class="chart-area">
|
||||
<view class="chart-grid">
|
||||
<view v-for="i in 7" :key="i" class="grid-line"></view>
|
||||
</view>
|
||||
<!-- 线条绘制 (简单 Mock) -->
|
||||
<view class="line-svg-mock">
|
||||
<view class="trend-path"></view>
|
||||
</view>
|
||||
<view class="x-labels">
|
||||
<text class="x-txt" v-for="date in dates" :key="date">{{ date }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部两个分析卡片 -->
|
||||
<view class="bottom-analysis">
|
||||
<!-- 积分来源分析 -->
|
||||
<view class="analysis-card border-shadow">
|
||||
<view class="analysis-header">
|
||||
<text class="ah-title">积分来源分析</text>
|
||||
<view class="btn-toggle" @click="toggleSourceStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-content">
|
||||
<!-- 饼图样式 -->
|
||||
<view v-if="sourceStyle === 'pie'" class="pie-layout anim-fade">
|
||||
<view class="pie-chart-wrap">
|
||||
<view class="pie-circle"></view>
|
||||
<view class="pie-label-line">
|
||||
<text class="pl-txt">后台赠送</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pie-legend-list">
|
||||
<view v-for="item in sourceData" :key="item.label" class="p-leg-item">
|
||||
<view class="p-dot" :style="{backgroundColor: item.color}"></view>
|
||||
<text class="p-txt">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表样式 -->
|
||||
<view v-else class="list-layout anim-fade">
|
||||
<view class="list-head">
|
||||
<text class="lh-col" style="width: 50px;">来源</text>
|
||||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||||
</view>
|
||||
<view class="list-body">
|
||||
<view v-for="(item, index) in sourceData" :key="item.label" class="list-row">
|
||||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||||
<text class="lr-label">{{ item.label }}</text>
|
||||
<text class="lr-val">{{ item.value }}</text>
|
||||
<view class="lr-progress-box">
|
||||
<view class="prog-bg">
|
||||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||||
</view>
|
||||
<text class="prog-txt">{{ item.percent }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 积分消耗分析 -->
|
||||
<view class="analysis-card border-shadow">
|
||||
<view class="analysis-header">
|
||||
<text class="ah-title">积分消耗</text>
|
||||
<view class="btn-toggle" @click="toggleConsumeStyle">
|
||||
<text class="toggle-txt">切换样式</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-content">
|
||||
<!-- 饼图样式 -->
|
||||
<view v-if="consumeStyle === 'pie'" class="pie-layout anim-fade">
|
||||
<view class="pie-chart-wrap consume-pie">
|
||||
<view class="pie-circle-c"></view>
|
||||
<view class="pie-label-line-c">
|
||||
<text class="pl-txt">订单抵扣</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pie-legend-list">
|
||||
<view v-for="item in consumeData" :key="item.label" class="p-leg-item">
|
||||
<view class="p-dot" :style="{backgroundColor: item.color}"></view>
|
||||
<text class="p-txt">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表样式 -->
|
||||
<view v-else class="list-layout anim-fade">
|
||||
<view class="list-head">
|
||||
<text class="lh-col" style="width: 50px;">来源</text>
|
||||
<text class="lh-col" style="flex: 1; text-align: center;">金额</text>
|
||||
<text class="lh-col" style="width: 200px; text-align: right;">占比率</text>
|
||||
</view>
|
||||
<view class="list-body">
|
||||
<view v-for="(item, index) in consumeData" :key="item.label" class="list-row">
|
||||
<view class="lr-rank"><text class="rank-txt">{{ index + 1 }}</text></view>
|
||||
<text class="lr-label">{{ item.label }}</text>
|
||||
<text class="lr-val">{{ item.value }}</text>
|
||||
<view class="lr-progress-box">
|
||||
<view class="prog-bg">
|
||||
<view class="prog-inner" :style="{width: item.percent + '%'}"></view>
|
||||
</view>
|
||||
<text class="prog-txt">{{ item.percent }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const dates = ['01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13', '01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23', '01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02', '02-03']
|
||||
|
||||
const sourceStyle = ref('pie')
|
||||
const consumeStyle = ref('pie')
|
||||
|
||||
const sourceData = [
|
||||
{ label: '后台赠送', value: 59021632, percent: 100, color: '#778899' },
|
||||
{ label: '签到获得', value: 3620, percent: 0, color: '#FFB980' },
|
||||
{ label: '九宫格抽奖', value: 0, percent: 0, color: '#FF7F50' },
|
||||
{ label: '商品赠送', value: 0, percent: 0, color: '#5AB1EF' },
|
||||
{ label: '订单赠送', value: 0, percent: 0, color: '#2EC7C9' }
|
||||
]
|
||||
|
||||
const consumeData = [
|
||||
{ label: '订单抵扣', value: 3051, percent: 95.7, color: '#5AB1EF' },
|
||||
{ label: '九宫格抽奖', value: 138, percent: 4.3, color: '#2EC7C9' },
|
||||
{ label: '兑换商品', value: 0, percent: 0, color: '#FF7F50' },
|
||||
{ label: '后台减少', value: 0, percent: 0, color: '#FFB980' },
|
||||
{ label: '退款退回', value: 0, percent: 0, color: '#D87A80' }
|
||||
]
|
||||
|
||||
const toggleSourceStyle = () => {
|
||||
sourceStyle.value = sourceStyle.value === 'pie' ? 'list' : 'pie'
|
||||
}
|
||||
|
||||
const toggleConsumeStyle = () => {
|
||||
consumeStyle.value = consumeStyle.value === 'pie' ? 'list' : 'pie'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-marketing-integral-statistic {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 时间选择 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
}
|
||||
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 12px; }
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
.date-picker-mock {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 5px 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.calendar-ic { font-size: 16px; color: #999; }
|
||||
.date-range { font-size: 14px; color: #333; }
|
||||
|
||||
/* 核心卡片 */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
.sc-left {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.sc-icon { font-size: 28px; color: #fff; }
|
||||
.bg-blue { background-color: #409eff; }
|
||||
.bg-orange { background-color: #ff9900; }
|
||||
.bg-green { background-color: #19be6b; }
|
||||
|
||||
.sc-right { display: flex; flex-direction: column; }
|
||||
.sc-val { font-size: 28px; font-weight: bold; color: #333; margin-bottom: 5px; }
|
||||
.sc-label { font-size: 14px; color: #999; }
|
||||
|
||||
/* 趋势图 */
|
||||
.chart-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.chart-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.chart-legend { display: flex; flex-direction: row; align-items: center; gap: 20px; }
|
||||
.legend-item { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.l-dot { width: 12px; height: 12px; border-radius: 6px; }
|
||||
.bg-blue-line { background-color: #409eff; }
|
||||
.bg-green-line { background-color: #19be6b; }
|
||||
.l-txt { font-size: 12px; color: #666; }
|
||||
.down-ic { font-size: 18px; color: #999; cursor: pointer; }
|
||||
|
||||
.line-chart-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 400px;
|
||||
}
|
||||
.y-labels {
|
||||
width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.y-txt { font-size: 12px; color: #999; text-align: right; padding-right: 10px; }
|
||||
|
||||
.chart-area {
|
||||
flex: 1;
|
||||
border-left: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: relative;
|
||||
}
|
||||
.chart-grid {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.grid-line { height: 1px; background-color: #f5f5f5; width: 100%; }
|
||||
|
||||
.line-svg-mock {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 趋势线条 Mock:用一个带有波浪背景的视图模拟,实际项目中应使用 Chart 库 */
|
||||
.trend-path {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at 20% 40%, transparent 10%, #409eff 10.5%, #409eff 11%, transparent 11.5%);
|
||||
background-size: 40px 100%;
|
||||
opacity: 0.1; /* 仅作为占位示意 */
|
||||
}
|
||||
|
||||
.x-labels {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
left: 0; right: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.x-txt { font-size: 10px; color: #999; transform: rotate(-45deg); white-space: nowrap; }
|
||||
|
||||
/* 底部两个分析 */
|
||||
.bottom-analysis {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
.analysis-card {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
}
|
||||
.analysis-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.ah-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.btn-toggle {
|
||||
border: 1px solid #dcdfe6;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-txt { font-size: 12px; color: #666; }
|
||||
|
||||
.analysis-content {
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
/* 饼图样式布局 */
|
||||
.pie-layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.pie-chart-wrap {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.pie-circle {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 90px;
|
||||
background-color: #778899; /* 主体颜色 */
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.pie-circle-c {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 90px;
|
||||
background: conic-gradient(#5AB1EF 0 95%, #2EC7C9 95% 100%);
|
||||
}
|
||||
.pie-label-line {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
border-top: 1px solid #999;
|
||||
width: 40px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.pl-txt { font-size: 12px; color: #666; white-space: nowrap; }
|
||||
|
||||
.pie-legend-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.p-leg-item { display: flex; flex-direction: row; align-items: center; gap: 10px; }
|
||||
.p-dot { width: 10px; height: 10px; border-radius: 2px; }
|
||||
.p-txt { font-size: 13px; color: #666; }
|
||||
|
||||
/* 列表样式布局 */
|
||||
.list-layout { display: flex; flex-direction: column; }
|
||||
.list-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.lh-col { font-size: 14px; font-weight: bold; color: #515a6e; }
|
||||
|
||||
.list-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 15px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.lr-rank { width: 30px; height: 30px; display: flex; align-items: center; }
|
||||
.rank-txt { font-size: 14px; color: #999; }
|
||||
.lr-label { width: 100px; font-size: 14px; color: #333; }
|
||||
.lr-val { flex: 1; font-size: 14px; color: #333; text-align: center; }
|
||||
.lr-progress-box { width: 200px; display: flex; flex-direction: row; align-items: center; gap: 10px; justify-content: flex-end; }
|
||||
.prog-bg { flex: 1; height: 10px; background-color: #f5f5f5; border-radius: 5px; overflow: hidden; }
|
||||
.prog-inner { height: 100%; background-color: #2d8cf0; border-radius: 5px; }
|
||||
.prog-txt { font-size: 13px; color: #666; width: 40px; text-align: right; }
|
||||
|
||||
.anim-fade { animation: fadeIn 0.3s ease-in-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
|
||||
</style>
|
||||
@@ -1,8 +1,100 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">售后订单管理与申请处理</text>
|
||||
<view class="admin-aftersale-order">
|
||||
<view class="content-body">
|
||||
<!-- 顶部过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">退款状态:</text>
|
||||
<view class="select-mock">
|
||||
<text class="select-val">全部</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">退款时间:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="date-txt">开始日期</text>
|
||||
<text class="date-split">-</text>
|
||||
<text class="date-txt">结束日期</text>
|
||||
<text class="calendar-ic">📅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">订单搜索:</text>
|
||||
<input class="search-input" placeholder="请输入订单号" v-model="searchQuery" />
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="width: 180px;">退款订单号</view>
|
||||
<view class="th" style="width: 180px;">原订单号</view>
|
||||
<view class="th" style="flex: 1.5;">商品信息</view>
|
||||
<view class="th" style="width: 120px;">用户信息</view>
|
||||
<view class="th" style="width: 100px;">实际支付</view>
|
||||
<view class="th" style="width: 160px;">发起退款时间</view>
|
||||
<view class="th" style="width: 100px;">退款状态</view>
|
||||
<view class="th" style="width: 100px;">订单状态</view>
|
||||
<view class="th" style="width: 150px;">退款信息</view>
|
||||
<view class="th" style="width: 100px;">操作</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in orderList" :key="item.id" class="table-row">
|
||||
<view class="td" style="width: 180px;"><text class="td-txt">{{ item.refundId }}</text></view>
|
||||
<view class="td" style="width: 180px;"><text class="td-txt">{{ item.orderId }}</text></view>
|
||||
<view class="td" style="flex: 1.5;">
|
||||
<view class="product-info">
|
||||
<image class="product-img" :src="item.productImg" mode="aspectFill"></image>
|
||||
<view class="product-detail">
|
||||
<text class="p-name ellipsis-2">{{ item.productName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payPrice.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 160px;"><text class="td-txt">{{ item.refundTime }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.refundStatus }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.orderStatus }}</text></view>
|
||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.refundReason }}</text></view>
|
||||
<view class="td" style="width: 100px;">
|
||||
<view class="op-more">
|
||||
<text class="op-txt">更多</text>
|
||||
<text class="op-arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<view class="page-total">
|
||||
<text class="total-txt">共 {{ total }} 条</text>
|
||||
</view>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -10,14 +102,307 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref<string>('售后订单')
|
||||
interface RefundOrder {
|
||||
id: number
|
||||
refundId: string
|
||||
orderId: string
|
||||
productImg: string
|
||||
productName: string
|
||||
userInfo: string
|
||||
payPrice: number
|
||||
refundTime: string
|
||||
refundStatus: string
|
||||
orderStatus: string
|
||||
refundReason: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const total = ref(11)
|
||||
const orderList = ref<RefundOrder[]>([
|
||||
{
|
||||
id: 1,
|
||||
refundId: '541647750949765120',
|
||||
orderId: 'cp541646979248160768',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: 'FOMIX 蛋壳椅 进口头层牛皮橙色单人沙发椅Egg chair设计师蛋壳椅 Egg chai...',
|
||||
userInfo: '181****3601',
|
||||
payPrice: 7580.00,
|
||||
refundTime: '2026-02-03 15:54:47',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '其它原因'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
refundId: '541638371601022976',
|
||||
orderId: 'cp541638302298537984',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
userInfo: '陌年微凉',
|
||||
payPrice: 89.05,
|
||||
refundTime: '2026-02-03 15:17:30',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '收货地址填错了'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
refundId: '540965628471672832',
|
||||
orderId: 'cp540873996820807680',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '爱奇艺智能 奇遇LT01 投影仪 家用卧室超高清手机便携式投影机 (4K超清 支...',
|
||||
userInfo: '隆胜科技',
|
||||
payPrice: 1.00,
|
||||
refundTime: '2026-02-01 18:44:16',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '收货地址填错了'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
refundId: '5409655559492149248',
|
||||
orderId: 'cp540964778781179904',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '广儿穿了就会霹死',
|
||||
userInfo: '隆胜科技',
|
||||
payPrice: 579.00,
|
||||
refundTime: '2026-02-01 18:44:00',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '收货地址填错了'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
refundId: '540965491796082688',
|
||||
orderId: 'cp540964551848361984',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: 'CHICVEN「摩登工业」科技感反光渐变羽绒服立领面包服外套女冬季',
|
||||
userInfo: '隆胜科技',
|
||||
payPrice: 900.00,
|
||||
refundTime: '2026-02-01 18:43:43',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '收货地址填错了'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
refundId: '540579611755413504',
|
||||
orderId: 'cp537676043612323840',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
userInfo: '杨咩咩Tel',
|
||||
payPrice: 0.00,
|
||||
refundTime: '2026-01-31 17:10:22',
|
||||
refundStatus: '仅退款',
|
||||
orderStatus: '未发货',
|
||||
refundReason: '收货地址填错了'
|
||||
}
|
||||
])
|
||||
|
||||
const handleQuery = () => { console.log('Querying...') }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.admin-aftersale-order {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
|
||||
.select-mock {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-val { font-size: 14px; color: #606266; }
|
||||
.arrow-down { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
.date-picker-mock {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.date-txt { font-size: 14px; color: #c0c4cc; }
|
||||
.date-split { color: #dcdfe6; }
|
||||
.calendar-ic { font-size: 14px; color: #c0c4cc; margin-left: auto; }
|
||||
|
||||
.search-input {
|
||||
width: 260px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 24px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #ebf7ff;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
|
||||
/* 商品信息列 */
|
||||
.product-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.product-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.product-detail {
|
||||
flex: 1;
|
||||
}
|
||||
.p-name {
|
||||
font-size: 12px;
|
||||
color: #515a6e;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 操作列 */
|
||||
.op-more {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.op-txt { font-size: 14px; }
|
||||
.op-arrow { font-size: 10px; }
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,104 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">收银台订单,管理线下收银记录</text>
|
||||
<view class="admin-cashier-order">
|
||||
<view class="content-body">
|
||||
<!-- 顶部过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">创建时间:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="date-txt">开始日期</text>
|
||||
<text class="date-split">-</text>
|
||||
<text class="date-txt">结束日期</text>
|
||||
<text class="calendar-ic">📅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">订单号:</text>
|
||||
<input class="search-input" placeholder="请输入订单号" v-model="orderId" />
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">用户名:</text>
|
||||
<input class="search-input" placeholder="请输入用户名" v-model="username" />
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="card-header">
|
||||
<view class="btn-primary-blue" @click="openQrModal">
|
||||
<text class="btn-txt">查看收款二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="flex: 1.5;">订单号</view>
|
||||
<view class="th" style="flex: 1.2;">用户信息</view>
|
||||
<view class="th" style="width: 150px;">实际支付</view>
|
||||
<view class="th" style="width: 150px;">优惠价格</view>
|
||||
<view class="th" style="width: 200px;">支付时间</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in orderList" :key="index" class="table-row">
|
||||
<view class="td" style="flex: 1.5;"><text class="td-txt">{{ item.orderId }}</text></view>
|
||||
<view class="td" style="flex: 1.2;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.payPrice.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.discountPrice.toFixed(2) }}</text></view>
|
||||
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.payTime }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<view class="page-total">
|
||||
<text class="total-txt">共 {{ total }} 条</text>
|
||||
</view>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收款码弹窗 -->
|
||||
<view v-if="showQrModal" :class="['modal-mask', isClosing ? 'mask-fade-out' : '']" @click="closeQrModal">
|
||||
<view :class="['modal-content', isClosing ? 'scale-out' : 'scale-in']" @click.stop="">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">收款码</text>
|
||||
<text class="close-btn" @click="closeQrModal">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="qr-item">
|
||||
<image class="qr-img" src="https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg" mode="aspectFit"></image>
|
||||
<text class="qr-label">公众号二维码</text>
|
||||
</view>
|
||||
<view class="qr-item">
|
||||
<view class="qr-placeholder-mp">
|
||||
<image class="mp-qr-mock" src="https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg" mode="aspectFit"></image>
|
||||
</view>
|
||||
<text class="qr-label">小程序二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -10,14 +106,289 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref<string>('收银订单')
|
||||
interface CashierOrder {
|
||||
orderId: string
|
||||
userInfo: string
|
||||
payPrice: number
|
||||
discountPrice: number
|
||||
payTime: string
|
||||
}
|
||||
|
||||
const orderId = ref('')
|
||||
const username = ref('')
|
||||
const total = ref(12)
|
||||
const orderList = ref<CashierOrder[]>([
|
||||
{ orderId: 'hy536720518414336000', userInfo: '东流 | 76058', payPrice: 1.00, discountPrice: 0.00, payTime: '2026-01-21 01:35:43' },
|
||||
{ orderId: 'hy529509398574268416', userInfo: '半个栗子 | 81997', payPrice: 10000.00, discountPrice: 0.00, payTime: '2026-01-01 04:01:18' },
|
||||
{ orderId: 'hy511477797936431104', userInfo: '莉莉 | 80736', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-11-12 09:50:09' },
|
||||
{ orderId: 'hy507152261823070208', userInfo: '. . . | 71546', payPrice: 0.10, discountPrice: 0.00, payTime: '2025-10-31 11:22:01' },
|
||||
{ orderId: 'hy506826113427701760', userInfo: 'A梁 | 80363', payPrice: 0.10, discountPrice: 0.00, payTime: '2025-10-30 13:46:01' },
|
||||
{ orderId: 'hy504707908026499072', userInfo: '前前前前 | 80225', payPrice: 2252.00, discountPrice: 0.00, payTime: '2025-10-24 17:29:02' },
|
||||
{ orderId: 'hy494505602832138240', userInfo: '张总说 | 40028', payPrice: 1.00, discountPrice: 0.00, payTime: '2025-09-26 13:48:43' },
|
||||
{ orderId: 'hy490819329198129152', userInfo: '虚伪 | 79027', payPrice: 10.00, discountPrice: 0.00, payTime: '2025-09-16 09:40:47' }
|
||||
])
|
||||
|
||||
const showQrModal = ref(false)
|
||||
const isClosing = ref(false)
|
||||
|
||||
const handleQuery = () => { console.log('Querying...') }
|
||||
|
||||
const openQrModal = () => {
|
||||
showQrModal.value = true
|
||||
isClosing.value = false
|
||||
}
|
||||
|
||||
const closeQrModal = () => {
|
||||
isClosing.value = true
|
||||
setTimeout(() => {
|
||||
showQrModal.value = false
|
||||
isClosing.value = false
|
||||
}, 300)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.admin-cashier-order {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
|
||||
.date-picker-mock {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.date-txt { font-size: 14px; color: #c0c4cc; }
|
||||
.date-split { color: #dcdfe6; }
|
||||
.calendar-ic { font-size: 14px; color: #c0c4cc; margin-left: auto; }
|
||||
|
||||
.search-input {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 24px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-header { padding: 20px; }
|
||||
|
||||
.btn-primary-blue {
|
||||
background-color: #2d8cf0;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||
|
||||
/* Modal 弹窗逻辑 */
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
z-index: 3000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 600px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
.close-btn { font-size: 24px; color: #999; cursor: pointer; line-height: 1; }
|
||||
|
||||
.modal-body {
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
.qr-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.qr-img {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.qr-placeholder-mp {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mp-qr-mock { width: 140px; height: 140px; border-radius: 70px; }
|
||||
|
||||
.qr-label { font-size: 14px; color: #666; }
|
||||
|
||||
/* 动画 */
|
||||
.scale-in { animation: scaleIn 0.3s ease-out forwards; }
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: scale(0.9); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.scale-out { animation: scaleOut 0.3s ease-in forwards; }
|
||||
@keyframes scaleOut {
|
||||
from { opacity: 1; transform: scale(1); }
|
||||
to { opacity: 0; transform: scale(0.9); }
|
||||
}
|
||||
|
||||
.mask-fade-out { animation: fadeOut 0.3s ease-in forwards; }
|
||||
@keyframes fadeOut {
|
||||
from { background-color: rgba(0, 0, 0, 0.4); }
|
||||
to { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +1,463 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">订单配置,设置订单相关参数</text>
|
||||
<view class="admin-order-config">
|
||||
<view class="content-body">
|
||||
<!-- 顶部选项卡 -->
|
||||
<view class="tabs-card border-shadow">
|
||||
<scroll-view class="tabs-scroll" direction="horizontal" :show-scrollbar="false">
|
||||
<view class="tabs-list">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="['tab-item', currentTab == index ? 'tab-active' : '']"
|
||||
@click="currentTab = index"
|
||||
>
|
||||
<text :class="['tab-txt', currentTab == index ? 'tab-txt-active' : '']">{{ tab }}</text>
|
||||
<view v-if="currentTab == index" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 配置内容区 -->
|
||||
<view class="config-card border-shadow">
|
||||
<!-- 1. 包邮设置 -->
|
||||
<view v-if="currentTab == 0" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">满额包邮:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.freeShippingPrice" placeholder="请输入满额包邮金额" />
|
||||
<text class="hint-txt">商城商品满多少金额即可包邮,此项优先于其他的运费设置</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">线下支付是否包邮:</text></view>
|
||||
<view class="input-box">
|
||||
<radio-group class="radio-group" @change="e => config.offlineFreeShipping = e.detail.value == '1'">
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="config.offlineFreeShipping" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">包邮</text>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="!config.offlineFreeShipping" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">不包邮</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="hint-txt">用户选择线下支付时是否包邮</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 发票功能配置 -->
|
||||
<view v-if="currentTab == 1" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">发票功能启用:</text></view>
|
||||
<view class="input-box">
|
||||
<radio-group class="radio-group" @change="e => config.invoiceEnabled = e.detail.value == '1'">
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="config.invoiceEnabled" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">开启</text>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="!config.invoiceEnabled" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">关闭</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="hint-txt">发票功能开启|关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">专用发票启用:</text></view>
|
||||
<view class="input-box">
|
||||
<radio-group class="radio-group" @change="e => config.specialInvoiceEnabled = e.detail.value == '1'">
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="config.specialInvoiceEnabled" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">开启</text>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="!config.specialInvoiceEnabled" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">关闭</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="hint-txt">专用发票功能开启|关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 售后退款配置 -->
|
||||
<view v-if="currentTab == 2" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">退货收货人姓名:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="config.refundContactName" placeholder="请输入退货收货人姓名" />
|
||||
<text class="hint-txt">用户退货退款后台同意之后,显示在退货订单详情显示的接受退货的人员姓名</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">退货收货人电话:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="config.refundContactPhone" placeholder="请输入退货收货人电话" />
|
||||
<text class="hint-txt">用户退货退款后台同意之后,显示在退货订单详情显示的接受退货的人员电话</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">退货收货人地址:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="config.refundAddress" placeholder="请输入退货收货人地址" />
|
||||
<text class="hint-txt">用户退货退款后台同意之后,显示在退货订单详情显示的接受退货的人员地址信息</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">退货理由:</text></view>
|
||||
<view class="input-box">
|
||||
<textarea class="textarea-base" v-model="config.refundReasons" placeholder="请输入退货理由" />
|
||||
<text class="hint-txt">配置退货理由,一行一个理由</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">优惠券退还状态:</text></view>
|
||||
<view class="input-box">
|
||||
<radio-group class="radio-group" @change="e => config.refundCoupon = e.detail.value == '1'">
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="config.refundCoupon" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">退还</text>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="!config.refundCoupon" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">不退还</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="hint-txt">优惠券是否退回开关,商品成功退款后,退回/不退回使用的优惠券</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">售后期限:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.afterSalesDays" placeholder="0" />
|
||||
<text class="hint-txt">订单收货之后,在多少天内可以进行退款,超出天数前端不显示退款按钮,设置0则永远显示</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 4. 订单取消配置 -->
|
||||
<view v-if="currentTab == 3" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">普通未支付订单取消:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.normalOrderCancelHour" />
|
||||
<text class="hint-txt">普通商品未支付订单的自动取消时间,单位(小时)</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">活动未支付订单取消:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.activityOrderCancelHour" />
|
||||
<text class="hint-txt">活动商品未支付订单的自动取消时间,单位(小时)</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">砍价未支付订单取消:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.bargainOrderCancelHour" />
|
||||
<text class="hint-txt">砍价未支付订单自动取消时间,单位(小时),如果为0将使用默认活动取消时间,优先使用单独活动配置</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">秒杀未支付订单取消:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.seckillOrderCancelHour" />
|
||||
<text class="hint-txt">秒杀未支付订单自动取消时间,单位(小时),如果为0将使用默认活动取消时间,优先使用单独活动配置</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">拼团未支付订单取消:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.combinationOrderCancelHour" />
|
||||
<text class="hint-txt">拼团未支付订单自动取消时间,单位(小时),如果为0将使用默认活动取消时间,优先使用单独活动配置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 5. 自动收货配置 -->
|
||||
<view v-if="currentTab == 4" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">自动收货时间:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.autoReceiveDays" />
|
||||
<text class="hint-txt">商城订单发货之后,用户如果不手动点击收货,则在N天后自动收货,设置0为不自动收货,单位(天)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 6. 自动评价配置 -->
|
||||
<view v-if="currentTab == 5" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">自动评价时间:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.autoCommentDays" />
|
||||
<text class="hint-txt">商城订单在收货之后,用户如果不主动评价订单商品,则在N天后自动评价,设置0为永远不自动评价</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">自动评价文字:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" v-model="config.autoCommentText" />
|
||||
<text class="hint-txt">自动评价显示的评价文字</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 7. 到店自提配置 -->
|
||||
<view v-if="currentTab == 6" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">开启到店自提:</text></view>
|
||||
<view class="input-box">
|
||||
<radio-group class="radio-group" @change="e => config.storeSelfPickup = e.detail.value == '1'">
|
||||
<label class="radio-label">
|
||||
<radio value="1" :checked="config.storeSelfPickup" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">开启</text>
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<radio value="0" :checked="!config.storeSelfPickup" color="#2d8cf0" style="transform:scale(0.8)" />
|
||||
<text class="radio-txt">关闭</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="hint-txt">开启后下单页面支持到店自提,需要在设置->发货设置->提货点设置中添加提货点,关闭则隐藏此功能</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 8. 警戒库存配置 -->
|
||||
<view v-if="currentTab == 7" class="form-container">
|
||||
<view class="form-item">
|
||||
<view class="label-box"><text class="label-txt">警戒库存:</text></view>
|
||||
<view class="input-box">
|
||||
<input class="input-base" type="number" v-model="config.stockWarningCount" />
|
||||
<text class="hint-txt">商品库存数量低于多少时,提示库存不足</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="btn-footer">
|
||||
<view class="btn-submit" @click="handleSave">
|
||||
<text class="btn-submit-txt">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const title = ref<string>('订单配置')
|
||||
const tabs = ['包邮设置', '发票功能配置', '售后退款配置', '订单取消配置', '自动收货配置', '自动评价配置', '到店自提配置', '警戒库存配置']
|
||||
const currentTab = ref(0)
|
||||
|
||||
const config = reactive({
|
||||
// 1. 包邮设置
|
||||
freeShippingPrice: 1000000,
|
||||
offlineFreeShipping: false,
|
||||
// 2. 发票功能配置
|
||||
invoiceEnabled: true,
|
||||
specialInvoiceEnabled: true,
|
||||
// 3. 售后退款配置
|
||||
refundContactName: '',
|
||||
refundContactPhone: '',
|
||||
refundAddress: '',
|
||||
refundReasons: '收货地址填错了\n与描述不符\n信息填错了,重新拍\n收到商品损坏了\n未按预定时间发货\n其它原因',
|
||||
refundCoupon: true,
|
||||
afterSalesDays: 0,
|
||||
// 4. 订单取消配置
|
||||
normalOrderCancelHour: 1,
|
||||
activityOrderCancelHour: 1,
|
||||
bargainOrderCancelHour: 1,
|
||||
seckillOrderCancelHour: 1,
|
||||
combinationOrderCancelHour: 1,
|
||||
// 5. 自动收货配置
|
||||
autoReceiveDays: 7,
|
||||
// 6. 自动评价配置
|
||||
autoCommentDays: 0,
|
||||
autoCommentText: '此用户未做评价',
|
||||
// 7. 到店自提配置
|
||||
storeSelfPickup: true,
|
||||
// 8. 警戒库存配置
|
||||
stockWarningCount: 10
|
||||
})
|
||||
|
||||
const handleSave = () => {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.admin-order-config {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 顶部选项卡 */
|
||||
.tabs-card {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabs-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-txt {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tab-txt-active {
|
||||
color: #2d8cf0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background-color: #2d8cf0;
|
||||
}
|
||||
|
||||
/* 配置内容区 */
|
||||
.config-card {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.label-box {
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.label-txt {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.input-base {
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textarea-base {
|
||||
height: 120px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hint-txt {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.radio-txt {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.btn-footer {
|
||||
margin-top: 40px;
|
||||
padding-left: 170px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background-color: #2d8cf0;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-submit-txt {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,98 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">核销记录,查看优惠券和卡券核销情况</text>
|
||||
<view class="admin-write-off">
|
||||
<view class="content-body">
|
||||
<!-- 顶部过滤栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">核销日期:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="date-txt">开始日期</text>
|
||||
<text class="date-split">-</text>
|
||||
<text class="date-txt">结束日期</text>
|
||||
<text class="calendar-ic">📅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">筛选条件:</text>
|
||||
<view class="select-mock" style="width: 100px;">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
<input class="search-input" style="width: 180px;" placeholder="请输入搜索内容" v-model="searchQuery" />
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">选择门店:</text>
|
||||
<view class="select-mock" style="width: 260px;">
|
||||
<text class="select-val">请选择</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="table-card border-shadow">
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-container">
|
||||
<view class="table-header-row">
|
||||
<view class="th" style="width: 200px;">订单号</view>
|
||||
<view class="th" style="width: 150px;">用户信息</view>
|
||||
<view class="th" style="width: 320px;">商品信息</view>
|
||||
<view class="th" style="width: 100px;">实际支付</view>
|
||||
<view class="th" style="width: 100px;">核销员</view>
|
||||
<view class="th" style="width: 120px;">核销门店</view>
|
||||
<view class="th" style="width: 100px;">支付状态</view>
|
||||
<view class="th" style="width: 100px;">订单状态</view>
|
||||
<view class="th" style="width: 160px;">下单时间</view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view v-for="(item, index) in recordList" :key="index" class="table-row">
|
||||
<view class="td" style="width: 200px;"><text class="td-txt">{{ item.orderId }}</text></view>
|
||||
<view class="td" style="width: 150px;"><text class="td-txt">{{ item.userInfo }}</text></view>
|
||||
<view class="td" style="width: 320px;">
|
||||
<view class="product-info">
|
||||
<image class="product-img" :src="item.productImg" mode="aspectFill"></image>
|
||||
<view class="product-detail">
|
||||
<text class="p-name ellipsis-2">{{ item.productName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payPrice }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.verifier }}</text></view>
|
||||
<view class="td" style="width: 120px;"><text class="td-txt">{{ item.storeName }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.payStatus }}</text></view>
|
||||
<view class="td" style="width: 100px;"><text class="td-txt">{{ item.orderStatus }}</text></view>
|
||||
<view class="td" style="width: 160px;"><text class="td-txt">{{ item.createTime }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<view class="page-total">
|
||||
<text class="total-txt">共 {{ total }} 条</text>
|
||||
</view>
|
||||
<view class="page-select">
|
||||
<text class="page-val">15条/页 ▼</text>
|
||||
</view>
|
||||
<view class="page-btns">
|
||||
<text class="p-btn disabled"><</text>
|
||||
<text class="p-btn active">1</text>
|
||||
<text class="p-btn">></text>
|
||||
</view>
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -10,14 +100,270 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const title = ref<string>('核销记录')
|
||||
interface WriteOffRecord {
|
||||
orderId: string
|
||||
userInfo: string
|
||||
productImg: string
|
||||
productName: string
|
||||
payPrice: string
|
||||
verifier: string
|
||||
storeName: string
|
||||
payStatus: string
|
||||
orderStatus: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const total = ref(10)
|
||||
const recordList = ref<WriteOffRecord[]>([
|
||||
{
|
||||
orderId: 'cp470547161164021760',
|
||||
userInfo: '张迪/77418',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '小米家保温杯云米电热水杯杯旅行便携式烧水壶真空304不锈钢热水壶智能恒...',
|
||||
payPrice: '93',
|
||||
verifier: '总平台',
|
||||
storeName: '提货点222',
|
||||
payStatus: '余额支付',
|
||||
orderStatus: '已完成',
|
||||
createTime: '2025-07-22 11:06:25'
|
||||
},
|
||||
{
|
||||
orderId: 'cp470289876680441856',
|
||||
userInfo: '130****0000/22919',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '米妍 (meyarn) 刮舌苔清洁器舌苔刷清新口气成人清洁舌苔口腔2支装 粉+蓝',
|
||||
payPrice: '28.4',
|
||||
verifier: '总平台',
|
||||
storeName: '提货点222',
|
||||
payStatus: '余额支付',
|
||||
orderStatus: '待评价',
|
||||
createTime: '2025-07-21 18:04:04'
|
||||
},
|
||||
{
|
||||
orderId: 'cp462914742369910784',
|
||||
userInfo: '您好亲亲/76738',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '小米家保温杯云米电热水杯杯旅行便携式烧水壶真空304不锈钢热水壶智能恒...',
|
||||
payPrice: '89.1',
|
||||
verifier: '总平台',
|
||||
storeName: '关东科技',
|
||||
payStatus: '线下支付',
|
||||
orderStatus: '已完成',
|
||||
createTime: '2025-07-01 09:37:55'
|
||||
},
|
||||
{
|
||||
orderId: 'cp450327064277417984',
|
||||
userInfo: 'Leo/74412',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '361度运动鞋男鞋【飞羽2】夏季轻透气网面缓震回弹便捷跑步鞋 羽毛白冰河...',
|
||||
payPrice: '369',
|
||||
verifier: '总平台',
|
||||
storeName: '提货点222',
|
||||
payStatus: '线下支付',
|
||||
orderStatus: '待评价',
|
||||
createTime: '2025-05-27 15:58:58'
|
||||
},
|
||||
{
|
||||
orderId: 'cp439425186874261504',
|
||||
userInfo: '白茶/73171',
|
||||
productImg: 'https://p.demo.crmeb.net/uploads/attach/2024/09/20240905/66d87e35b7e9b.jpg',
|
||||
productName: '【明星同款】FILA FUSION裴乐潮牌卫衣情侣老花男女宽松上衣',
|
||||
payPrice: '649',
|
||||
verifier: '总平台',
|
||||
storeName: '关东科技',
|
||||
payStatus: '余额支付',
|
||||
orderStatus: '待评价',
|
||||
createTime: '2025-04-27 13:58:48'
|
||||
}
|
||||
])
|
||||
|
||||
const handleQuery = () => { console.log('Searching...') }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.admin-write-off {
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.border-shadow {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label-txt { font-size: 14px; color: #606266; }
|
||||
|
||||
.date-picker-mock {
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.date-txt { font-size: 14px; color: #c0c4cc; }
|
||||
.date-split { color: #dcdfe6; }
|
||||
.calendar-ic { font-size: 14px; color: #c0c4cc; margin-left: auto; }
|
||||
|
||||
.select-mock {
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-val { font-size: 14px; color: #c0c4cc; }
|
||||
.arrow-down { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
.search-input {
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-query {
|
||||
background-color: #2d8cf0;
|
||||
padding: 0 24px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.query-txt { color: #fff; font-size: 14px; }
|
||||
|
||||
/* 表格区域 */
|
||||
.table-card {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f8f9;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #ebf7ff;
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 15px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-txt { font-size: 14px; color: #515a6e; }
|
||||
|
||||
/* 商品信息列 */
|
||||
.product-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.product-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.p-name {
|
||||
font-size: 12px;
|
||||
color: #515a6e;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 24px 0 0 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-val { font-size: 14px; color: #606266; border: 1px solid #dcdfe6; padding: 4px 10px; border-radius: 4px; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.p-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c0c4cc; background-color: #f5f7fa; }
|
||||
|
||||
.page-jump { display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||||
.jump-txt { font-size: 14px; color: #606266; }
|
||||
.jump-input { width: 40px; height: 32px; border: 1px solid #dcdfe6; text-align: center; border-radius: 4px; font-size: 14px; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<AdminLayout currentPage="product-statistics">
|
||||
<view class="Page">
|
||||
<view class="Header">
|
||||
<text class="Title">商品统计</text>
|
||||
<text class="SubTitle">product-statistics</text>
|
||||
</view>
|
||||
|
||||
<view class="Card">
|
||||
<text class="Label">页面参数(query)</text>
|
||||
<text class="Mono">{{ params }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
|
||||
const params = ref('')
|
||||
|
||||
onLoad((options) => {
|
||||
// options: Record<string, any>
|
||||
params.value = JSON.stringify(options ?? {})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.Page {
|
||||
padding: 24rpx;
|
||||
}
|
||||
.Header {
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
.Title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.SubTitle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.Card {
|
||||
margin-top: 24rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
.Label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.Mono {
|
||||
font-size: 24rpx;
|
||||
font-family: monospace;
|
||||
line-height: 36rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
347
pages/mall/admin/product/product-management/edit.uvue
Normal file
347
pages/mall/admin/product/product-management/edit.uvue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<view class="product-edit-page">
|
||||
<view class="page-header">
|
||||
<view class="back-link" @click="goBack">
|
||||
<text class="arrow">{"<"}</text>
|
||||
<text class="back-txt">返回</text>
|
||||
</view>
|
||||
<text class="header-title">编辑商品</text>
|
||||
</view>
|
||||
|
||||
<!-- 步骤层 -->
|
||||
<view class="steps-card">
|
||||
<view class="step-items">
|
||||
<view v-for="(step, index) in steps" :key="index" class="step-item" :class="{ active: activeStep === index }">
|
||||
<text class="step-txt">{{ step }}</text>
|
||||
<view v-if="index < steps.length - 1" class="step-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-card">
|
||||
<view class="form-item">
|
||||
<view class="label"><text class="required">*</text><text>商品类型:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="radio-group">
|
||||
<view class="radio-item active">
|
||||
<text class="radio-circle on"></text>
|
||||
<view class="radio-txt">
|
||||
<text class="main">普通商品</text>
|
||||
<text class="sub">(物流发货)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text class="required">*</text><text>商品名称:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="input-box">
|
||||
<input class="real-input" value="UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060" />
|
||||
<text class="count">36/80</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text class="required">*</text><text>单位:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="input-box small">
|
||||
<input class="real-input" value="件" />
|
||||
<text class="count">1/5</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text class="required">*</text><text>商品轮播图:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="image-uploader">
|
||||
<view v-for="(img, i) in carouselImages" :key="i" class="img-item">
|
||||
<image :src="img" mode="aspectFill" />
|
||||
<view class="img-close">×</view>
|
||||
</view>
|
||||
<view class="upload-btn">
|
||||
<text class="icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="tip">建议尺寸:800*800,可拖拽改变图片顺序,默认首张图为主图,最多上传10张</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text>添加视频:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="upload-btn v-btn">
|
||||
<text class="v-icon">📹</text>
|
||||
</view>
|
||||
<text class="tip">建议时长:9~30秒,视频宽高比16:9</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text class="required">*</text><text>商品分类:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="tag-selector">
|
||||
<view v-for="tag in categories" :key="tag" class="tag-item">
|
||||
<text>{{ tag }}</text>
|
||||
<text class="close">×</text>
|
||||
</view>
|
||||
<text class="add-link">新增分类</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text>商品标签:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="mock-btn-select">选择标签</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="label"><text>商品状态:</text></view>
|
||||
<view class="input-wrap">
|
||||
<view class="radio-group-simple">
|
||||
<view class="radio-simple on">
|
||||
<text class="dot"></text>
|
||||
<text>上架</text>
|
||||
</view>
|
||||
<view class="radio-simple">
|
||||
<text class="dot"></text>
|
||||
<text>下架</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-btns">
|
||||
<button class="btn-next">下一步</button>
|
||||
<button class="btn-save">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
|
||||
const activeStep = ref(0)
|
||||
const steps = ['基础信息', '规格库存', '商品详情', '物流设置', '会员价/佣金', '营销设置', '其他设置']
|
||||
|
||||
const carouselImages = ref([
|
||||
'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
'https://img2.baidu.com/it/u=3025255470,3051061730&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
'https://img2.baidu.com/it/u=3775079632,546700868&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'
|
||||
])
|
||||
|
||||
const categories = ref(['生活家居', '运动专区 / 361', '运动专区 / 特步', '运动专区 / 匹克'])
|
||||
|
||||
function goBack() {
|
||||
openRoute('product_productList')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.product-edit-page {
|
||||
padding: 20px;
|
||||
background-color: #f5f7f9;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
.back-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
.arrow { font-size: 14px; }
|
||||
.back-txt { font-size: 14px; }
|
||||
}
|
||||
.header-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
}
|
||||
|
||||
.steps-card {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.step-items {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.step-txt {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
padding: 0 16px;
|
||||
}
|
||||
&.active .step-txt {
|
||||
color: #1890ff;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #1890ff;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.step-line {
|
||||
width: 20px;
|
||||
height: 1px;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 30px;
|
||||
.label {
|
||||
width: 120px;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding-top: 8px;
|
||||
margin-right: 20px;
|
||||
.required { color: #f5222d; margin-right: 4px; }
|
||||
}
|
||||
.input-wrap { flex: 1; }
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid #1890ff;
|
||||
border-radius: 4px;
|
||||
padding: 10px 16px;
|
||||
width: 160px;
|
||||
position: relative;
|
||||
.radio-circle {
|
||||
width: 14px; height: 14px; border: 1px solid #d9d9d9; border-radius: 50%;
|
||||
&.on { border-color: #1890ff; background: #1890ff; }
|
||||
}
|
||||
.radio-txt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.main { font-size: 14px; color: #333; }
|
||||
.sub { font-size: 12px; color: #999; }
|
||||
}
|
||||
&::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
right: 0; bottom: 0;
|
||||
background: #1890ff; color: #fff; font-size: 10px; padding: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-box {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
width: 400px;
|
||||
height: 36px;
|
||||
&.small { width: 150px; }
|
||||
.real-input { flex: 1; font-size: 14px; color: #333; }
|
||||
.count { font-size: 12px; color: #bfbfbf; }
|
||||
}
|
||||
|
||||
.image-uploader {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
.img-item {
|
||||
width: 80px; height: 80px; position: relative;
|
||||
image { width: 100%; height: 100%; border-radius: 4px; }
|
||||
.img-close {
|
||||
position: absolute; right: -6px; top: -6px; width: 16px; height: 16px;
|
||||
background: rgba(0,0,0,0.5); color: #fff; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 80px; height: 80px; border: 1px dashed #d9d9d9; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
.icon { font-size: 24px; color: #999; }
|
||||
&.v-btn { width: 64px; height: 64px; margin-bottom: 8px; .v-icon { font-size: 24px; } }
|
||||
}
|
||||
|
||||
.tip { font-size: 12px; color: #999; }
|
||||
|
||||
.tag-selector {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
.tag-item {
|
||||
background: #f5f5f5; border: 1px solid #d9d9d9; padding: 2px 10px; border-radius: 4px;
|
||||
display: flex; flex-direction: row; align-items: center; gap: 6px;
|
||||
font-size: 14px; color: #666;
|
||||
.close { color: #999; cursor: pointer; }
|
||||
}
|
||||
.add-link { font-size: 14px; color: #1890ff; cursor: pointer; }
|
||||
}
|
||||
|
||||
.mock-btn-select {
|
||||
border: 1px solid #d9d9d9; border-radius: 4px; padding: 6px 16px;
|
||||
font-size: 14px; color: #666; display: inline-block;
|
||||
}
|
||||
|
||||
.radio-group-simple {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
.radio-simple {
|
||||
display: flex; flex-direction: row; align-items: center; gap: 6px; font-size: 14px; color: #666;
|
||||
.dot { width: 14px; height: 14px; border: 1px solid #d9d9d9; border-radius: 50%; position: relative; }
|
||||
&.on {
|
||||
color: #1890ff;
|
||||
.dot { border-color: #1890ff; }
|
||||
.dot::after {
|
||||
content: ''; position: absolute; left: 3px; top: 3px; width: 6px; height: 6px;
|
||||
background: #1890ff; border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-btns {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding-bottom: 40px;
|
||||
.btn-next { background: #1890ff; color: #fff; border: none; padding: 0 24px; height: 40px; border-radius: 4px; }
|
||||
.btn-save { background: #fff; color: #1890ff; border: 1px solid #1890ff; padding: 0 24px; height: 40px; border-radius: 4px; }
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +1,616 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">页面已修复 (UTF-8)</text>
|
||||
<view class="product-list-page">
|
||||
<!-- 1. 搜索表单 -->
|
||||
<view class="search-card">
|
||||
<view class="search-row">
|
||||
<view class="search-item">
|
||||
<text class="label">商品搜索:</text>
|
||||
<input class="mock-input" placeholder="请输入商品名称/关键字/ID" />
|
||||
</view>
|
||||
<view class="search-item">
|
||||
<text class="label">商品类型:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部</text>
|
||||
<text class="arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-item">
|
||||
<text class="label">商品分类:</text>
|
||||
<view class="mock-select">
|
||||
<text>请选择</text>
|
||||
<text class="arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-btns">
|
||||
<button class="btn-primary">查询</button>
|
||||
<button class="btn-reset">重置</button>
|
||||
<view class="expand-control">
|
||||
<text class="expand-txt">展开</text>
|
||||
<text class="expand-arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-row mt-12">
|
||||
<view class="search-item">
|
||||
<text class="label">配送方式:</text>
|
||||
<view class="mock-select">
|
||||
<text>全部</text>
|
||||
<text class="arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 商品状态 Tabs -->
|
||||
<view class="status-tabs-wrap">
|
||||
<view class="status-tabs">
|
||||
<view
|
||||
v-for="(tab, index) in statusTabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: activeStatus === tab.key }"
|
||||
@click="activeStatus = tab.key"
|
||||
>
|
||||
<text>{{ tab.label }}({{ tab.count }})</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 操作按钮行 -->
|
||||
<view class="action-bar">
|
||||
<view class="left-actions">
|
||||
<button class="btn-add" @click="goEdit(null)">添加商品</button>
|
||||
<button class="btn-collect">商品采集</button>
|
||||
<view class="btn-dropdown">
|
||||
<text>批量修改</text>
|
||||
<text class="arrow">▼</text>
|
||||
</view>
|
||||
<view class="btn-dropdown">
|
||||
<text>商品迁移</text>
|
||||
<text class="arrow">▼</text>
|
||||
</view>
|
||||
<button class="btn-export">数据导出</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 4. 商品列表表格 -->
|
||||
<view class="list-card">
|
||||
<view class="table-v5">
|
||||
<view class="th-row">
|
||||
<view class="th col-check"><text>□</text></view>
|
||||
<view class="th col-id"><text>商品ID</text></view>
|
||||
<view class="th col-img"><text>商品图</text></view>
|
||||
<view class="th col-name"><text>商品名称</text></view>
|
||||
<view class="th col-activity"><text>参与活动</text></view>
|
||||
<view class="th col-type"><text>商品类型</text></view>
|
||||
<view class="th col-price"><text>商品售价</text></view>
|
||||
<view class="th col-sales"><text>销量</text></view>
|
||||
<view class="th col-stock"><text>库存</text></view>
|
||||
<view class="th col-sort"><text>排序</text></view>
|
||||
<view class="th col-status"><text>状态</text></view>
|
||||
<view class="th col-op"><text>操作</text></view>
|
||||
</view>
|
||||
|
||||
<view v-for="(item, index) in productList" :key="index"
|
||||
class="tr-row"
|
||||
:style="{ zIndex: activeDropdownId === item.id ? 1000 : 1 }"
|
||||
>
|
||||
<view class="td col-check"><text>□</text></view>
|
||||
<view class="td col-id"><text>{{ item.id }}</text></view>
|
||||
<view class="td col-img">
|
||||
<image class="p-img" :src="item.image" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="td col-name">
|
||||
<text class="p-name-txt">{{ item.name }}</text>
|
||||
</view>
|
||||
<view class="td col-activity">
|
||||
<view class="activity-tags">
|
||||
<text v-for="tag in item.activities" :key="tag" class="tag" :class="tag">{{ getActivityName(tag) }}</text>
|
||||
<text v-if="item.activities.length === 0">-</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-type"><text>{{ item.typeName }}</text></view>
|
||||
<view class="td col-price"><text>{{ item.price }}</text></view>
|
||||
<view class="td col-sales"><text>{{ item.sales }}</text></view>
|
||||
<view class="td col-stock"><text>{{ item.stock }}</text></view>
|
||||
<view class="td col-sort"><text>{{ item.sort }}</text></view>
|
||||
<view class="td col-status">
|
||||
<view class="mock-switch" :class="{ on: item.status === 1 }">
|
||||
<text class="switch-txt">{{ item.status === 1 ? '上架' : '下架' }}</text>
|
||||
<view class="switch-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-op op-group">
|
||||
<text class="op-link" @click.stop="goEdit(item.id)">编辑</text>
|
||||
<text class="op-divider">|</text>
|
||||
<view class="more-hover-box"
|
||||
@mouseenter="activeDropdownId = item.id"
|
||||
@mouseleave="activeDropdownId = null"
|
||||
>
|
||||
<view class="more-trigger-txt">
|
||||
<text class="more-link-text">更多</text>
|
||||
<text class="more-arrow-icon">∨</text>
|
||||
</view>
|
||||
<view class="dropdown-list-container" v-show="activeDropdownId === item.id">
|
||||
<view class="dropdown-triangle"></view>
|
||||
<view class="dropdown-menu-body">
|
||||
<text class="menu-item" @click.stop="goReviews(item.id)">查看评论</text>
|
||||
<text class="menu-item" @click.stop="goMemberPrice(item.id)">会员价管理</text>
|
||||
<text class="menu-item" @click.stop="uni.showToast({title:'佣金管理开发中', icon:'none'})">佣金管理</text>
|
||||
<text class="menu-item danger-item" @click.stop="moveToRecycle(item.id)">{{ activeStatus === 'recycle' ? '恢复商品' : '移到回收站' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 5. 分页 -->
|
||||
<view class="pagination-row">
|
||||
<text class="total">共 {{ total }} 条</text>
|
||||
<view class="page-ctrl">
|
||||
<text class="page-btn disabled">{"<"}</text>
|
||||
<text class="page-num active">1</text>
|
||||
<text class="page-num">2</text>
|
||||
<text class="page-btn">{">"}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-list')
|
||||
const title = ref<string>('index')
|
||||
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
|
||||
const total = ref(49)
|
||||
const activeStatus = ref('selling')
|
||||
const activeDropdownId = ref<number | null>(null)
|
||||
|
||||
const statusTabs = ref([
|
||||
{ key: 'selling', label: '出售中的商品', count: 49 },
|
||||
{ key: 'warehouse', label: '仓库中的商品', count: 4 },
|
||||
{ key: 'soldout', label: '已经售罄商品', count: 11 },
|
||||
{ key: 'alarm', label: '警戒库存商品', count: 27 },
|
||||
{ key: 'recycle', label: '回收站的商品', count: 176 },
|
||||
])
|
||||
|
||||
const productList = ref([
|
||||
{
|
||||
id: 963,
|
||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
activities: ['kj', 'pt'],
|
||||
typeName: '普通商品',
|
||||
price: '0.01',
|
||||
sales: 639,
|
||||
stock: 1602,
|
||||
sort: 9999,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 108,
|
||||
image: 'https://img2.baidu.com/it/u=3033501986,2204481084&fm=253&fmt=auto&app=138&f=JPEG?w=569&h=500',
|
||||
name: 'FOMIX 蛋壳椅 进口头层牛皮橙色单人沙发椅Egg chair设计师师单椅单沙头层牛皮/单椅',
|
||||
activities: ['pt', 'ms'],
|
||||
typeName: '普通商品',
|
||||
price: '7580.00',
|
||||
sales: 14,
|
||||
stock: 16638,
|
||||
sort: 9999,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 48,
|
||||
image: 'https://img0.baidu.com/it/u=1762118431,3101886131&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水蓝/传奇墨水蓝/白 XL',
|
||||
activities: ['kj', 'pt', 'ms'],
|
||||
typeName: '普通商品',
|
||||
price: '100.00',
|
||||
sales: 841,
|
||||
stock: 2318,
|
||||
sort: 9998,
|
||||
status: 1
|
||||
}
|
||||
])
|
||||
|
||||
function getActivityName(tag: string): string {
|
||||
if (tag === 'kj') return '砍价'
|
||||
if (tag === 'pt') return '拼团'
|
||||
if (tag === 'ms') return '秒杀'
|
||||
return tag
|
||||
}
|
||||
|
||||
function goEdit(id: number | null) {
|
||||
openRoute('product_edit')
|
||||
}
|
||||
|
||||
function goReviews(id: number) {
|
||||
openRoute('product_productReply')
|
||||
}
|
||||
|
||||
function goMemberPrice(id: number) {
|
||||
openRoute('product_member_price')
|
||||
}
|
||||
|
||||
function moveToRecycle(id: number) {
|
||||
const action = activeStatus.value === 'recycle' ? '恢复' : '移到回收站';
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确认要将该商品${action}吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' });
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.product-list-page {
|
||||
padding: 20px;
|
||||
background-color: #f5f7f9;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
.mt-12 { margin-top: 12px; }
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.label { font-size: 14px; color: #606266; width: 80px; text-align: right; }
|
||||
}
|
||||
|
||||
.mock-input {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mock-select {
|
||||
width: 160px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
.arrow { font-size: 10px; color: #c0c4cc; }
|
||||
}
|
||||
|
||||
.search-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn-primary { background: #1890ff; color: #fff; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 13px; border: none; }
|
||||
.btn-reset { background: #fff; color: #606266; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 13px; border: 1px solid #dcdfe6; }
|
||||
.expand-control { display: flex; flex-direction: row; align-items: center; gap: 4px; cursor: pointer; }
|
||||
.expand-txt { font-size: 13px; color: #1890ff; }
|
||||
.expand-arrow { font-size: 10px; color: #1890ff; }
|
||||
|
||||
.status-tabs-wrap {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 2px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
color: #1890ff;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.left-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-add { background: #1890ff; color: #fff; height: 32px; padding: 0 12px; border-radius: 4px; font-size: 13px; border: none; }
|
||||
.btn-collect { background: #13ce66; color: #fff; height: 32px; padding: 0 12px; border-radius: 4px; font-size: 13px; border: none; }
|
||||
.btn-export { background: #fff; color: #606266; height: 32px; padding: 0 12px; border-radius: 4px; font-size: 13px; border: 1px solid #dcdfe6; }
|
||||
|
||||
.btn-dropdown {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
.arrow { font-size: 10px; color: #c4cc; }
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding-bottom: 20px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.table-v5 {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.th-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tr-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
&:hover { background-color: #fafafa; }
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: 12px 8px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.col-check { width: 50px; }
|
||||
.col-id { width: 80px; }
|
||||
.col-img { width: 80px; }
|
||||
.col-name { flex: 1; justify-content: flex-start; text-align: left; }
|
||||
.col-activity { width: 140px; }
|
||||
.col-type { width: 100px; }
|
||||
.col-price { width: 100px; }
|
||||
.col-sales { width: 80px; }
|
||||
.col-stock { width: 80px; }
|
||||
.col-sort { width: 80px; }
|
||||
.col-status { width: 100px; }
|
||||
.col-op { width: 150px; }
|
||||
|
||||
.op-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.more-hover-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.more-trigger-txt {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
pointer-events: none; /* 让事件透传给 more-hover-box */
|
||||
}
|
||||
|
||||
.more-link-text {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.more-arrow-icon {
|
||||
font-size: 10px;
|
||||
color: #1890ff;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.dropdown-list-container {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
right: -10px;
|
||||
width: 120px;
|
||||
padding-top: 8px; /* 增加感应连续性 */
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.dropdown-triangle {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 25px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
}
|
||||
|
||||
.dropdown-menu-body {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.danger-item {
|
||||
&:hover {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
||||
.p-name-txt { font-size: 13px; line-height: 1.4; color: #333; }
|
||||
|
||||
.activity-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
.tag {
|
||||
padding: 2px 4px;
|
||||
font-size: 11px;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
&.kj { background: #1890ff; }
|
||||
&.pt { background: #52c41a; }
|
||||
&.ms { background: #f5222d; }
|
||||
}
|
||||
}
|
||||
|
||||
.mock-switch {
|
||||
width: 50px;
|
||||
height: 20px;
|
||||
background: #dbdbdb;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
&.on {
|
||||
background: #1890ff;
|
||||
.switch-dot { left: 32px; }
|
||||
.switch-txt { left: 6px; }
|
||||
}
|
||||
&:not(.on) {
|
||||
.switch-txt { right: 6px; }
|
||||
.switch-dot { left: 2px; }
|
||||
}
|
||||
.switch-txt {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
.switch-dot {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: left 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.op-link {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
&.danger { color: #ff4d4f; }
|
||||
}
|
||||
.op-divider { color: #e8e8e8; font-size: 12px; margin: 0 4px; }
|
||||
|
||||
.more-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.arrow { font-size: 10px; color: #1890ff; margin-left: 2px; }
|
||||
}
|
||||
|
||||
.pagination-row {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
.total { font-size: 13px; color: #606266; }
|
||||
}
|
||||
|
||||
.page-ctrl {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
.page-num, .page-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
&.active { background: #1890ff; color: #fff; border-color: #1890ff; }
|
||||
&.disabled { color: #c0c4cc; background: #f5f7fa; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<view class="member-price-page">
|
||||
<view class="page-header">
|
||||
<view class="back-link" @click="goBack">
|
||||
<text class="arrow">{"<"}</text>
|
||||
<text class="back-txt">返回</text>
|
||||
</view>
|
||||
<text class="header-title">会员价管理</text>
|
||||
</view>
|
||||
|
||||
<view class="content-card">
|
||||
<view class="product-info">
|
||||
<image class="p-img" src="https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500" mode="aspectFill" />
|
||||
<text class="p-name">UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫</text>
|
||||
</view>
|
||||
|
||||
<view class="price-table">
|
||||
<view class="th-row">
|
||||
<view class="th flex-2"><text>规格名称</text></view>
|
||||
<view class="th flex-1"><text>售价</text></view>
|
||||
<view class="th flex-1"><text>普通会员价</text></view>
|
||||
<view class="th flex-1"><text>黄金会员价</text></view>
|
||||
<view class="th flex-1"><text>铂金会员价</text></view>
|
||||
</view>
|
||||
<view class="tr-row">
|
||||
<view class="td flex-2"><text>XL,卡其</text></view>
|
||||
<view class="td flex-1"><text>¥99.00</text></view>
|
||||
<view class="td flex-1"><input class="price-input" value="89.00" /></view>
|
||||
<view class="td flex-1"><input class="price-input" value="79.00" /></view>
|
||||
<view class="td flex-1"><input class="price-input" value="69.00" /></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-btns">
|
||||
<button class="btn-save">保存设置</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
|
||||
function goBack() {
|
||||
openRoute('product_productList')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.member-price-page { padding: 20px; background: #f5f7f9; min-height: 100vh; }
|
||||
.page-header { display: flex; flex-direction: row; align-items: center; gap: 16px; margin-bottom: 20px;
|
||||
.back-link { display: flex; flex-direction: row; align-items: center; gap: 4px; color: #666; cursor: pointer; }
|
||||
.header-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
}
|
||||
.content-card { background: #fff; border-radius: 4px; padding: 24px; }
|
||||
.product-info { display: flex; flex-direction: row; align-items: center; gap: 16px; margin-bottom: 30px;
|
||||
.p-img { width: 64px; height: 64px; border-radius: 4px; }
|
||||
.p-name { font-size: 15px; font-weight: bold; color: #333; }
|
||||
}
|
||||
.price-table { border: 1px solid #f0f0f0; border-radius: 4px; }
|
||||
.th-row { display: flex; flex-direction: row; background: #f8f9fa; border-bottom: 1px solid #f0f0f0; }
|
||||
.th { padding: 12px; font-size: 14px; font-weight: 500; color: #333; text-align: center; }
|
||||
.tr-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; &:last-child { border-bottom: none; } }
|
||||
.td { padding: 16px 12px; font-size: 14px; color: #666; text-align: center; display: flex; align-items: center; justify-content: center; }
|
||||
.flex-1 { flex: 1; } .flex-2 { flex: 2; }
|
||||
.price-input { border: 1px solid #dcdfe6; border-radius: 4px; height: 32px; padding: 0 8px; text-align: center; width: 80%; font-size: 13px; }
|
||||
.footer-btns { margin-top: 40px; display: flex; justify-content: center; .btn-save { background: #1890ff; color: #fff; border: none; padding: 0 32px; height: 40px; border-radius: 4px; } }
|
||||
</style>
|
||||
@@ -1,26 +1,568 @@
|
||||
<template>
|
||||
<AdminLayout :currentPage="currentPage">
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="sub-title">分析商品销售数据和趋势</text>
|
||||
<view class="product-statistic-page">
|
||||
<!-- 商品概况头部 -->
|
||||
<view class="page-header-row">
|
||||
<view class="title-wrap">
|
||||
<text class="page-title">商品概况</text>
|
||||
<view class="info-icon">?</view>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-emoji">📅</text>
|
||||
<text class="date-range">2026/01/04 - 2026/02/02</text>
|
||||
</view>
|
||||
<button class="btn-query">查询</button>
|
||||
<button class="btn-export">导出</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计指标网格 -->
|
||||
<view class="stat-grid">
|
||||
<view v-for="(item, index) in statItems" :key="index" class="stat-card">
|
||||
<view class="stat-main">
|
||||
<view class="icon-box" :style="{ backgroundColor: item.bgColor }">
|
||||
<text class="stat-emoji">{{ item.emoji }}</text>
|
||||
</view>
|
||||
<view class="stat-content">
|
||||
<text class="stat-label">{{ item.label }}</text>
|
||||
<text class="stat-value">{{ item.value }}</text>
|
||||
<view class="stat-compare">
|
||||
<text class="compare-label">坏比增长:</text>
|
||||
<text class="compare-val" :class="item.trendClass">
|
||||
{{ item.compare }}
|
||||
<text v-if="item.trend === 'up'" class="arrow">▲</text>
|
||||
<text v-else-if="item.trend === 'down'" class="arrow">▼</text>
|
||||
<text v-else>-</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图表卡片 -->
|
||||
<view class="chart-card">
|
||||
<view class="chart-header">
|
||||
<view class="legend-wrap">
|
||||
<view class="legend-item"><view class="dot purple"></view><text>商品浏览量</text></view>
|
||||
<view class="legend-item"><view class="dot orange"></view><text>商品访客量</text></view>
|
||||
<view class="legend-item"><view class="dot blue"></view><text>支付金额</text></view>
|
||||
<view class="legend-item"><view class="dot green"></view><text>退款金额</text></view>
|
||||
</view>
|
||||
<view class="download-icon">
|
||||
<text class="download-emoji">📥</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-main">
|
||||
<EChartsView v-if="chartOption != null" :option="chartOption" class="main-chart" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品排行 -->
|
||||
<view class="ranking-card">
|
||||
<view class="ranking-header">
|
||||
<text class="ranking-title">商品排行</text>
|
||||
<view class="ranking-filters">
|
||||
<view class="mock-select-wrap">
|
||||
<text class="select-val">浏览量</text>
|
||||
<text class="select-arrow">▼</text>
|
||||
</view>
|
||||
<view class="date-picker-wrap">
|
||||
<text class="calendar-emoji">📅</text>
|
||||
<text class="date-range">2026/01/04 - 2026/02/02</text>
|
||||
</view>
|
||||
<button class="btn-query small">查询</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="ranking-table">
|
||||
<view class="table-header">
|
||||
<text class="th col-id">ID</text>
|
||||
<text class="th col-img">商品图片</text>
|
||||
<text class="th col-name">商品名称</text>
|
||||
<text class="th col-num">浏览量</text>
|
||||
<text class="th col-num">访客数</text>
|
||||
<text class="th col-num">加购件数</text>
|
||||
<text class="th col-num">下单件数</text>
|
||||
<text class="th col-num">支付件数</text>
|
||||
<text class="th col-num">支付金额</text>
|
||||
<text class="th col-num">收藏数</text>
|
||||
<text class="th col-num wide">访客-支付转化率(%)</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in rankingList" :key="index" class="table-row">
|
||||
<text class="td col-id">{{ item.id }}</text>
|
||||
<view class="td col-img">
|
||||
<image class="product-img" :src="item.image" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="td col-name">
|
||||
<text class="product-name-txt">{{ item.name }}</text>
|
||||
</view>
|
||||
<text class="td col-num">{{ item.views }}</text>
|
||||
<text class="td col-num">{{ item.visitors }}</text>
|
||||
<text class="td col-num">{{ item.cartCount }}</text>
|
||||
<text class="td col-num">{{ item.orderCount }}</text>
|
||||
<text class="td col-num">{{ item.payCount }}</text>
|
||||
<text class="td col-num">{{ item.payAmount }}</text>
|
||||
<text class="td col-num">{{ item.favCount }}</text>
|
||||
<text class="td col-num wide">{{ item.conversion }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
|
||||
const currentPage = ref<string>('product-statistics-group')
|
||||
const title = ref<string>('商品统计')
|
||||
import { ref, onMounted } from 'vue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
const statItems = ref([
|
||||
{ label: '商品浏览量', value: '7576', compare: '0.93%', trend: 'up', trendClass: 'up-red', bgColor: '#e6f7ff', emoji: '👁️' },
|
||||
{ label: '商品访客量', value: '765', compare: '0.79%', trend: 'up', trendClass: 'up-red', bgColor: '#f6ffed', emoji: '👤' },
|
||||
{ label: '支付件数', value: '322', compare: '-49.52%', trend: 'down', trendClass: 'down-green', bgColor: '#fff7e6', emoji: '🛍️' },
|
||||
{ label: '支付金额', value: '443254.62', compare: '-63.62%', trend: 'down', trendClass: 'down-green', bgColor: '#f9f0ff', emoji: '💰' },
|
||||
{ label: '退款件数', value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#e6f7ff', emoji: '🔄' },
|
||||
{ label: '退款金额', value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#f6ffed', emoji: '💴' }
|
||||
])
|
||||
|
||||
const rankingList = ref([
|
||||
{
|
||||
id: 963,
|
||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
name: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
views: 1200,
|
||||
visitors: 246,
|
||||
cartCount: 74,
|
||||
orderCount: 214,
|
||||
payCount: 180,
|
||||
payAmount: '11877.49',
|
||||
favCount: 13,
|
||||
conversion: 18
|
||||
},
|
||||
{
|
||||
id: 116,
|
||||
image: 'https://img2.baidu.com/it/u=3775079632,546700868&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
name: '爱奇艺智能 奇遇LT01 投影仪 家用卧室超高清手机便携投影机 (4K超清 支持侧投 手机同屏 华为一碰即投)',
|
||||
views: 959,
|
||||
visitors: 376,
|
||||
cartCount: 1,
|
||||
orderCount: 60,
|
||||
payCount: 29,
|
||||
payAmount: '26.00',
|
||||
favCount: 6,
|
||||
conversion: 7
|
||||
},
|
||||
{
|
||||
id: 48,
|
||||
image: 'https://img0.baidu.com/it/u=1762118431,3101886131&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
name: '阿迪达斯官网 adidas BBALL CAP COT 男女训练运动帽子FQ5270 传奇墨水蓝/传奇墨水蓝/白 XL',
|
||||
views: 758,
|
||||
visitors: 207,
|
||||
cartCount: 63,
|
||||
orderCount: 67,
|
||||
payCount: 17,
|
||||
payAmount: '1409.30',
|
||||
favCount: 4,
|
||||
conversion: 7
|
||||
},
|
||||
{
|
||||
id: 108,
|
||||
image: 'https://img2.baidu.com/it/u=3033501986,2204481084&fm=253&fmt=auto&app=138&f=JPEG?w=569&h=500',
|
||||
name: 'FOMIX 蛋壳椅 进口头层牛皮橙色单人沙发椅Egg chair设计师师单椅单沙头层牛皮/单椅',
|
||||
views: 730,
|
||||
visitors: 216,
|
||||
cartCount: 26999,
|
||||
orderCount: 327,
|
||||
payCount: 14,
|
||||
payAmount: '66197.00',
|
||||
favCount: 4,
|
||||
conversion: 6
|
||||
}
|
||||
])
|
||||
|
||||
const chartOption = ref<any>({})
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initChart()
|
||||
}, 300)
|
||||
})
|
||||
|
||||
function toPlainObject(obj: any): any {
|
||||
if (obj == null) return null
|
||||
if (typeof obj !== 'object') return obj
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item: any) : any => toPlainObject(item))
|
||||
}
|
||||
const plain: any = {}
|
||||
const keys = Object.keys(obj)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const value = obj[key]
|
||||
if (typeof value === 'function' || key.startsWith('_') || key === 'toJSON') {
|
||||
continue
|
||||
}
|
||||
if (value != null && typeof value === 'object') {
|
||||
plain[key] = toPlainObject(value)
|
||||
} else {
|
||||
plain[key] = value
|
||||
}
|
||||
}
|
||||
return plain
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
const dates = [
|
||||
'01-04', '01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13',
|
||||
'01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23',
|
||||
'01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02'
|
||||
]
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(50, 50, 50, 0.7)',
|
||||
padding: [10, 15],
|
||||
textStyle: { color: '#fff' },
|
||||
formatter: (params: any[]) : string => {
|
||||
let res = `<div style="font-size:12px; color:#ccc; margin-bottom:5px;">${params[0].name}</div>`
|
||||
params.forEach(p => {
|
||||
res += `<div style="display:flex; align-items:center;">
|
||||
<div style="width:8px; height:8px; border-radius:50%; background:${p.color}; margin-right:8px;"></div>
|
||||
<span>${p.seriesName}: ${p.value}</span>
|
||||
</div>`
|
||||
})
|
||||
return res
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '5%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#8c8c8c', fontSize: 11 },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额',
|
||||
nameTextStyle: { color: '#8c8c8c', padding: [0, 30, 0, 0] },
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
|
||||
axisLabel: { color: '#8c8c8c' }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '数量',
|
||||
nameTextStyle: { color: '#8c8c8c', padding: [0, 0, 0, 30] },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: '#8c8c8c' }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '商品浏览量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
itemStyle: { color: '#b37feb' },
|
||||
lineStyle: { width: 2 },
|
||||
data: [90, 110, 115, 100, 95, 80, 60, 40, 70, 85, 75, 65, 70, 80, 100, 120, 110, 90, 60, 95, 115, 110, 85, 50, 45, 55, 75]
|
||||
},
|
||||
{
|
||||
name: '商品访客量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
itemStyle: { color: '#ffbb96' },
|
||||
lineStyle: { width: 2 },
|
||||
data: [15, 12, 10, 8, 11, 14, 13, 8, 9, 11, 10, 15, 12, 11, 9, 12, 14, 15, 11, 10, 13, 15, 11, 8, 12, 10, 14]
|
||||
},
|
||||
{
|
||||
name: '支付金额',
|
||||
type: 'bar',
|
||||
barWidth: '25%',
|
||||
itemStyle: { color: '#1890ff' },
|
||||
data: [10, 5, 8, 0, 145, 15, 5, 0, 0, 0, 0, 5, 30, 0, 15, 20, 100, 20, 25, 5, 1, 3, 70, 5, 10, 5, 15, 10]
|
||||
},
|
||||
{
|
||||
name: '退款金额',
|
||||
type: 'bar',
|
||||
barWidth: '25%',
|
||||
itemStyle: { color: '#52c41a' },
|
||||
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
],
|
||||
markLine: {
|
||||
silent: true,
|
||||
symbol: ['none', 'none'],
|
||||
label: { show: false },
|
||||
lineStyle: { color: '#bfbfbf', type: 'dashed' },
|
||||
data: [{ yAxis: 145853.16 }]
|
||||
}
|
||||
}
|
||||
|
||||
chartOption.value = toPlainObject(option)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/uni.scss';
|
||||
.page { padding: $space-lg; }
|
||||
.header { padding: $space-lg; border-radius: $radius; background: $background-primary; box-shadow: $shadow-xs; }
|
||||
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
|
||||
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }
|
||||
.product-statistic-page {
|
||||
padding: 16px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
|
||||
.info-icon {
|
||||
width: 14px; height: 14px;
|
||||
border-radius: 50%; border: 1px solid #999;
|
||||
color: #999; font-size: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.date-picker-wrap {
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.calendar-emoji { font-size: 14px; }
|
||||
.date-range { font-size: 14px; color: #595959; }
|
||||
|
||||
.btn-query { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
|
||||
.btn-export { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
|
||||
|
||||
.stat-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
width: calc(33.33% - 11px);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 48px; height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.stat-emoji { font-size: 24px; }
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-label { font-size: 13px; color: #8c8c8c; margin-bottom: 4px; }
|
||||
.stat-value { font-size: 24px; font-weight: bold; color: #262626; margin-bottom: 4px; }
|
||||
|
||||
.stat-compare {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.compare-label { color: #8c8c8c; }
|
||||
.up-red { color: #ff4d4f; }
|
||||
.down-green { color: #52c41a; }
|
||||
.none-gray { color: #8c8c8c; }
|
||||
.arrow { font-size: 10px; margin-left: 2px; }
|
||||
|
||||
.chart-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.legend-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text { font-size: 12px; color: #8c8c8c; }
|
||||
}
|
||||
|
||||
.dot { width: 10px; height: 10px; border-radius: 2px; }
|
||||
.purple { background-color: #b37feb; }
|
||||
.orange { background-color: #ffbb96; }
|
||||
.blue { background-color: #1890ff; }
|
||||
.green { background-color: #52c41a; }
|
||||
|
||||
.download-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.download-emoji { font-size: 18px; opacity: 0.6; }
|
||||
|
||||
.chart-main {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
.main-chart { width: 100%; height: 100%; }
|
||||
|
||||
/* 商品排行 */
|
||||
.ranking-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.ranking-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ranking-title { font-size: 16px; font-weight: bold; color: #333; }
|
||||
|
||||
.ranking-filters {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mock-select-wrap {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 4px 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 120px;
|
||||
}
|
||||
.select-val { font-size: 14px; color: #595959; flex: 1; }
|
||||
.select-arrow { font-size: 10px; color: #bfbfbf; }
|
||||
|
||||
.btn-query.small { height: 32px; font-size: 13px; margin: 0; }
|
||||
|
||||
.ranking-table {
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #e6f7ff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: 12px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #595959;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
align-items: stretch;
|
||||
}
|
||||
.table-row:last-child { border-bottom: none; }
|
||||
|
||||
.td {
|
||||
padding: 16px 8px;
|
||||
font-size: 13px;
|
||||
color: #262626;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 列宽度配置 */
|
||||
.col-id { width: 60px; }
|
||||
.col-img { width: 100px; }
|
||||
.col-name { flex: 1; text-align: left; justify-content: flex-start; }
|
||||
.col-num { width: 80px; }
|
||||
.col-num.wide { width: 120px; }
|
||||
|
||||
.product-img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.product-name-txt {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #262626;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,15 +1,95 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品评论</text>
|
||||
<text class="page-subtitle">Component: ProductReply</text>
|
||||
<view class="product-reply-page">
|
||||
<!-- 1. 搜索筛选 -->
|
||||
<view class="search-card">
|
||||
<view class="search-row">
|
||||
<view class="search-item">
|
||||
<text class="label">评价时间:</text>
|
||||
<view class="mock-date-range">
|
||||
<text class="emoji">📅</text>
|
||||
<text class="txt">开始日期 - 结束日期</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-item">
|
||||
<text class="label">评价状态:</text>
|
||||
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
|
||||
</view>
|
||||
<view class="search-item">
|
||||
<text class="label">审核状态:</text>
|
||||
<view class="mock-select"><text>请选择</text><text class="arrow">▼</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-row mt-16">
|
||||
<view class="search-item">
|
||||
<text class="label">商品信息:</text>
|
||||
<input class="mock-input" placeholder="请输入商品信息" />
|
||||
</view>
|
||||
<view class="search-item">
|
||||
<text class="label">用户名称:</text>
|
||||
<input class="mock-input" placeholder="请输入" />
|
||||
</view>
|
||||
<button class="btn-primary">查询</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-content">
|
||||
<view class="placeholder-card">
|
||||
<text class="placeholder-title">页面占位</text>
|
||||
<text class="placeholder-desc">该功能模块正在开发中</text>
|
||||
<text class="placeholder-info">当前采用 CRMEB 路由体系 1:1 映射</text>
|
||||
<!-- 2. 操作行 -->
|
||||
<view class="action-bar">
|
||||
<button class="btn-primary">添加自评</button>
|
||||
<button class="btn-white">批量审核</button>
|
||||
</view>
|
||||
|
||||
<!-- 3. 数据表格 -->
|
||||
<view class="list-card">
|
||||
<view class="table-v5">
|
||||
<view class="th-row">
|
||||
<view class="th col-check"><text>□</text></view>
|
||||
<view class="th col-id"><text>评论ID</text></view>
|
||||
<view class="th col-product"><text>商品信息</text></view>
|
||||
<view class="th col-spec"><text>规格</text></view>
|
||||
<view class="th col-user"><text>用户名称</text></view>
|
||||
<view class="th col-score"><text>评分</text></view>
|
||||
<view class="th col-content"><text>评价内容</text></view>
|
||||
<view class="th col-reply"><text>回复内容</text></view>
|
||||
<view class="th col-status"><text>审核状态</text></view>
|
||||
<view class="th col-time"><text>评价时间</text></view>
|
||||
<view class="th col-op"><text>操作</text></view>
|
||||
</view>
|
||||
|
||||
<view v-for="item in replyList" :key="item.id" class="tr-row">
|
||||
<view class="td col-check"><text>□</text></view>
|
||||
<view class="td col-id"><text>{{ item.id }}</text></view>
|
||||
<view class="td col-product">
|
||||
<image class="p-img" :src="item.image" mode="aspectFill" />
|
||||
<text class="p-name-txt">{{ item.productName }}</text>
|
||||
</view>
|
||||
<view class="td col-spec"><text>{{ item.spec }}</text></view>
|
||||
<view class="td col-user"><text>{{ item.username }}</text></view>
|
||||
<view class="td col-score"><text>{{ item.score }}</text></view>
|
||||
<view class="td col-content"><text class="blue-link">{{ item.content }}</text></view>
|
||||
<view class="td col-reply"><text>{{ item.reply || '无' }}</text></view>
|
||||
<view class="td col-status">
|
||||
<text class="status-tag" :class="item.status === 1 ? 'pass' : 'wait'">
|
||||
{{ item.status === 1 ? '通过' : '待审核' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="td col-time"><text>{{ item.time }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="op-link">通过</text>
|
||||
<text class="op-link">驳回</text>
|
||||
<text class="op-link">回复</text>
|
||||
<text class="op-link red">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-row">
|
||||
<text class="total">共 {{ replyList.length }} 条</text>
|
||||
<view class="page-ctrl">
|
||||
<text class="page-btn disabled">{"<"}</text>
|
||||
<text class="page-num active">1</text>
|
||||
<text class="page-btn">{">"}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -18,64 +98,169 @@
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// TODO: 实现 商品评论 的具体功能
|
||||
const loading = ref<boolean>(false)
|
||||
const replyList = ref([
|
||||
{
|
||||
id: 1069,
|
||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
spec: 'XL,卡其',
|
||||
username: 'demo998',
|
||||
score: 3.5,
|
||||
content: '22',
|
||||
reply: '',
|
||||
status: 0,
|
||||
time: '2025-02-19 14:56:43'
|
||||
},
|
||||
{
|
||||
id: 1059,
|
||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
spec: 'XL,卡其',
|
||||
username: '你好呀',
|
||||
score: 3.5,
|
||||
content: '的',
|
||||
reply: '',
|
||||
status: 0,
|
||||
time: '2025-01-07 15:35:36'
|
||||
},
|
||||
{
|
||||
id: 980,
|
||||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
productName: 'UR2024夏季新款女装复古纯欲氛围感一字肩短款T恤衫UWG440060',
|
||||
spec: 'XL,卡其',
|
||||
username: 'wx209638',
|
||||
score: 5,
|
||||
content: '好',
|
||||
reply: '',
|
||||
status: 1,
|
||||
time: '2024-09-12 14:20:12'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
.product-reply-page {
|
||||
padding: 20px;
|
||||
background-color: #f5f7f9;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
.search-card {
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
.search-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.mt-16 { margin-top: 16px; }
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.label { font-size: 14px; color: #606266; width: 80px; text-align: right; }
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
.mock-date-range {
|
||||
width: 280px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; flex-direction: row; align-items: center; padding: 0 12px; gap: 8px;
|
||||
.emoji { font-size: 14px; }
|
||||
.txt { font-size: 13px; color: #c0c4cc; }
|
||||
}
|
||||
|
||||
.page-content {
|
||||
.mock-select {
|
||||
width: 160px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; flex-direction: row; align-items: center; justify-content: space-between;
|
||||
padding: 0 12px; font-size: 13px; color: #606266;
|
||||
.arrow { font-size: 10px; color: #c0c4cc; }
|
||||
}
|
||||
|
||||
.mock-input {
|
||||
width: 200px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 0 12px; font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-primary { background: #1890ff; color: #fff; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: none; }
|
||||
.btn-white { background: #fff; color: #606266; height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; border: 1px solid #dcdfe6; }
|
||||
|
||||
.action-bar {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.placeholder-card {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
.table-v5 { width: 100%; }
|
||||
|
||||
.th-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
.th {
|
||||
padding: 12px 8px; font-size: 13px; font-weight: 500; color: #333;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.placeholder-desc {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
.tr-row {
|
||||
display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0;
|
||||
&:hover { background-color: #fafafa; }
|
||||
}
|
||||
|
||||
.placeholder-info {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
.td {
|
||||
padding: 12px 8px; font-size: 13px; color: #606266;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.col-check { width: 50px; }
|
||||
.col-id { width: 80px; }
|
||||
.col-product { flex: 1.5; justify-content: flex-start; gap: 12px; }
|
||||
.col-spec { width: 100px; }
|
||||
.col-user { width: 120px; }
|
||||
.col-score { width: 80px; }
|
||||
.col-content { flex: 1; }
|
||||
.col-reply { flex: 1; }
|
||||
.col-status { width: 100px; }
|
||||
.col-time { width: 160px; }
|
||||
.col-op { width: 180px; }
|
||||
|
||||
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
||||
.p-name-txt { font-size: 13px; line-height: 1.4; color: #1890ff; }
|
||||
|
||||
.blue-link { color: #1890ff; }
|
||||
|
||||
.status-tag {
|
||||
padding: 2px 8px; border-radius: 2px; font-size: 12px;
|
||||
&.pass { background: rgba(82, 196, 26, 0.1); color: #52c41a; }
|
||||
&.wait { background: rgba(250, 173, 20, 0.1); color: #faad14; }
|
||||
}
|
||||
|
||||
.op-link { font-size: 13px; color: #1890ff; margin: 0 4px; cursor: pointer; &.red { color: #f5222d; } }
|
||||
|
||||
.pagination-row {
|
||||
padding: 24px; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; gap: 16px;
|
||||
.total { font-size: 13px; color: #606266; }
|
||||
}
|
||||
|
||||
.page-ctrl {
|
||||
display: flex; flex-direction: row; gap: 8px;
|
||||
.page-num, .page-btn {
|
||||
width: 32px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 13px;
|
||||
&.active { background: #1890ff; color: #fff; }
|
||||
&.disabled { background: #f5f5f5; color: #ccc; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user