Merge huangzhenbao-admin into main and fix config
5
.gitignore
vendored
@@ -42,4 +42,7 @@ ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Project specific ignores
|
||||
# Add any other project specific ignores below this line
|
||||
# Add any other project specific ignores below this line
|
||||
# local supabase
|
||||
supabase/
|
||||
|
||||
|
||||
272
ADMIN_LAYOUT_GUIDE.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Mall Admin 布局系统使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目已基于CRMEB Admin的vertical布局设计,创建了一套统一的admin管理后台布局系统。该系统提供:
|
||||
|
||||
- 🎨 **统一视觉设计** - 参考CRMEB Admin的深色侧边栏风格
|
||||
- 📱 **响应式布局** - 支持桌面端和移动端自适应
|
||||
- 🔧 **灵活配置** - 支持菜单折叠、主题切换等功能
|
||||
- 🧭 **智能导航** - 自动高亮当前页面,支持子菜单展开
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
layouts/
|
||||
├── admin/
|
||||
│ └── index.uvue # 主布局组件
|
||||
|
||||
pages/mall/admin/
|
||||
├── index.uvue # 首页(已集成布局)
|
||||
├── user-management.uvue # 用户管理(已集成布局)
|
||||
└── ... # 其他页面
|
||||
|
||||
pages.json # 页面配置(已更新)
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 在页面中使用AdminLayout
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AdminLayout current-page="your-page-id">
|
||||
<!-- 你的页面内容 -->
|
||||
<view class="your-page-content">
|
||||
<!-- 页面具体内容 -->
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
|
||||
// 你的页面逻辑
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. current-page 参数说明
|
||||
|
||||
`current-page` 属性用于标识当前页面,对应的菜单项会被高亮显示:
|
||||
|
||||
| 页面 | current-page 值 | 说明 |
|
||||
|------|----------------|------|
|
||||
| 首页 | `dashboard` | 主页 |
|
||||
| 用户管理 | `user-list` | 用户列表页 |
|
||||
| 商品管理 | `product-list` | 商品列表页 |
|
||||
| 订单管理 | `order` | 订单管理页 |
|
||||
| 商家管理 | `merchant-list` | 商家列表页 |
|
||||
| 系统设置 | `system` | 系统设置页 |
|
||||
|
||||
### 3. 页面配置
|
||||
|
||||
在 `pages.json` 中,所有admin页面都需要设置:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "admin/your-page",
|
||||
"style": {
|
||||
"navigationBarTitleText": "页面标题",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意**: `navigationStyle: "custom"` 是必需的,用于隐藏uni-app默认导航栏。
|
||||
|
||||
## AdminLayout 组件功能
|
||||
|
||||
### 侧边栏功能
|
||||
|
||||
#### 菜单结构
|
||||
```javascript
|
||||
menuList: [
|
||||
{
|
||||
id: 'dashboard', // 菜单唯一标识
|
||||
title: '首页', // 菜单显示文本
|
||||
icon: 'icon-shouye', // 图标类名
|
||||
path: '/pages/mall/admin/index' // 跳转路径
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户管理',
|
||||
icon: 'icon-yonghuguanli',
|
||||
children: [ // 子菜单
|
||||
{
|
||||
id: 'user-list',
|
||||
title: '用户列表',
|
||||
path: '/pages/mall/admin/user-management'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 菜单图标
|
||||
系统使用iconfont图标库,支持以下图标:
|
||||
|
||||
- `icon-shouye` - 首页
|
||||
- `icon-yonghuguanli` - 用户管理
|
||||
- `icon-shangpinguanli` - 商品管理
|
||||
- `icon-dingdanguanli` - 订单管理
|
||||
- `icon-caiwuguanli` - 财务管理
|
||||
- `icon-yingxiaoguanli` - 营销管理
|
||||
- `icon-xitongshezhi` - 系统设置
|
||||
- `icon-shangjiaguanli` - 商家管理
|
||||
|
||||
### 顶部导航栏
|
||||
|
||||
#### 左侧功能
|
||||
- **菜单切换按钮** - 展开/收起侧边栏
|
||||
- **面包屑导航** - 显示当前页面标题
|
||||
|
||||
#### 右侧功能
|
||||
- **通知中心** - 显示未读消息数量
|
||||
- **用户头像** - 点击进入个人资料
|
||||
|
||||
### 响应式设计
|
||||
|
||||
#### 桌面端 (> 768px)
|
||||
- 侧边栏默认展开,宽度240rpx
|
||||
- 支持折叠到80rpx
|
||||
- 完整显示菜单文本和图标
|
||||
|
||||
#### 平板端 (600px - 768px)
|
||||
- 侧边栏可折叠
|
||||
- 菜单文本正常显示
|
||||
|
||||
#### 移动端 (< 600px)
|
||||
- 侧边栏默认隐藏
|
||||
- 点击菜单按钮显示侧边栏
|
||||
- 菜单文本正常显示
|
||||
- 点击遮罩层关闭侧边栏
|
||||
|
||||
## 样式定制
|
||||
|
||||
### 主题色配置
|
||||
|
||||
系统默认使用以下颜色:
|
||||
|
||||
```scss
|
||||
// 主色调
|
||||
$primary-color: #1890ff;
|
||||
$sidebar-bg: #001529;
|
||||
$navbar-bg: #ffffff;
|
||||
|
||||
// 文字颜色
|
||||
$text-primary: #333333;
|
||||
$text-secondary: rgba(255, 255, 255, 0.75);
|
||||
$text-muted: rgba(255, 255, 255, 0.65);
|
||||
```
|
||||
|
||||
### 自定义样式
|
||||
|
||||
如需修改样式,可以在 `layouts/admin/index.uvue` 的 `<style>` 部分进行调整:
|
||||
|
||||
```scss
|
||||
// 修改侧边栏背景色
|
||||
.admin-sidebar {
|
||||
background-color: #002140; // 更深的蓝色
|
||||
}
|
||||
|
||||
// 修改菜单项激活状态
|
||||
.menu-link-active {
|
||||
background-color: #0050b3; // 不同的激活色
|
||||
}
|
||||
```
|
||||
|
||||
## 数据持久化
|
||||
|
||||
系统自动保存用户偏好设置到本地存储:
|
||||
|
||||
- **侧边栏折叠状态** - `admin_sidebar_collapsed`
|
||||
- **展开的子菜单** - `admin_open_menus`
|
||||
- **用户信息** - 从本地存储读取
|
||||
|
||||
## 扩展指南
|
||||
|
||||
### 添加新菜单项
|
||||
|
||||
1. 在 `menuList` 中添加新菜单配置
|
||||
2. 为菜单项分配唯一的 `id`
|
||||
3. 设置合适的图标和路径
|
||||
4. 如果有子菜单,使用 `children` 数组
|
||||
|
||||
### 添加新页面
|
||||
|
||||
1. 创建页面文件 `pages/mall/admin/new-page.uvue`
|
||||
2. 使用 `AdminLayout` 包装页面内容
|
||||
3. 设置正确的 `current-page` 属性
|
||||
4. 在 `pages.json` 中注册页面,设置 `"navigationStyle": "custom"`
|
||||
|
||||
### 自定义菜单图标
|
||||
|
||||
1. 在iconfont中添加新图标
|
||||
2. 获取图标的class名称(如 `icon-new-feature`)
|
||||
3. 在菜单配置中使用该class名
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 菜单不显示
|
||||
- 检查页面是否正确导入了 `AdminLayout` 组件
|
||||
- 确认 `current-page` 属性值与菜单配置中的 `id` 匹配
|
||||
|
||||
### 样式异常
|
||||
- 检查页面是否设置了 `"navigationStyle": "custom"`
|
||||
- 确认没有与uni-app默认样式冲突
|
||||
|
||||
### 移动端适配问题
|
||||
- 检查响应式断点是否正确设置
|
||||
- 确认遮罩层和侧边栏切换逻辑正常
|
||||
|
||||
## 迁移现有页面
|
||||
|
||||
对于已有的admin页面,按以下步骤迁移:
|
||||
|
||||
1. **导入AdminLayout组件**
|
||||
```javascript
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
```
|
||||
|
||||
2. **包装页面内容**
|
||||
```vue
|
||||
<template>
|
||||
<AdminLayout current-page="page-id">
|
||||
<!-- 原有页面内容 -->
|
||||
</AdminLayout>
|
||||
</template>
|
||||
```
|
||||
|
||||
3. **更新pages.json配置**
|
||||
```json
|
||||
{
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **移除原有导航栏代码**
|
||||
- 删除页面内的自定义导航栏
|
||||
- 移除相关的导航栏样式
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持菜单结构清晰** - 使用合理的分组和层级
|
||||
2. **图标选择一致** - 使用统一的图标风格
|
||||
3. **页面标题准确** - 确保面包屑显示正确的页面标题
|
||||
4. **响应式测试** - 在不同设备上测试布局效果
|
||||
5. **性能优化** - 避免在菜单中放置大量数据
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2025-01-23)
|
||||
- ✅ 基于CRMEB Admin设计创建统一布局系统
|
||||
- ✅ 支持响应式布局和菜单折叠
|
||||
- ✅ 集成用户管理和首页
|
||||
- ✅ 完成pages.json配置更新
|
||||
- ✅ 提供完整的使用文档
|
||||
|
||||
---
|
||||
|
||||
如有问题或需要进一步定制,请参考源代码或联系开发团队。
|
||||
429
App.uvue
@@ -1,54 +1,413 @@
|
||||
<template>
|
||||
<view class="app-root">
|
||||
<view class="dev-probe">APP 已挂载(dev probe)</view>
|
||||
<view class="dev-nav">
|
||||
<a class="dev-nav-btn" href="#/pages/user/boot">去启动页 boot</a>
|
||||
<a class="dev-nav-btn" href="#/pages/user/login">去登录 login</a>
|
||||
<a class="dev-nav-btn" href="#/pages/mall/consumer/index">去商城首页</a>
|
||||
</view>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 简化的App组件
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ===== 全局重置样式 ===== */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #262626;
|
||||
background-color: #f0f2f5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
view, text, input, textarea, button {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ===== 全局主题色 ===== */
|
||||
:root {
|
||||
/* 主色调 */
|
||||
--primary-color: #1890ff;
|
||||
--primary-hover: #40a9ff;
|
||||
--primary-active: #096dd9;
|
||||
|
||||
/* 成功色 */
|
||||
--success-color: #52c41a;
|
||||
--success-hover: #73d13d;
|
||||
--success-active: #389e0d;
|
||||
|
||||
/* 警告色 */
|
||||
--warning-color: #faad14;
|
||||
--warning-hover: #ffc53d;
|
||||
--warning-active: #d48806;
|
||||
|
||||
/* 错误色 */
|
||||
--error-color: #ff4d4f;
|
||||
--error-hover: #ff7875;
|
||||
--error-active: #d4380d;
|
||||
|
||||
/* 中性色 */
|
||||
--text-primary: #262626;
|
||||
--text-secondary: #595959;
|
||||
--text-disabled: #bfbfbf;
|
||||
--text-inverse: #ffffff;
|
||||
|
||||
/* 边框色 */
|
||||
--border-color: #d9d9d9;
|
||||
--border-color-light: #f0f0f0;
|
||||
--border-color-dark: #bfbfbf;
|
||||
|
||||
/* 背景色 */
|
||||
--background-color: #ffffff;
|
||||
--background-color-light: #fafafa;
|
||||
--background-color-dark: #f5f5f5;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--shadow-xl: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
|
||||
/* 圆角 */
|
||||
--border-radius-sm: 2px;
|
||||
--border-radius: 4px;
|
||||
--border-radius-lg: 6px;
|
||||
--border-radius-xl: 8px;
|
||||
|
||||
/* 间距 */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xxl: 48px;
|
||||
|
||||
/* 字体大小 */
|
||||
--font-size-xs: 12px;
|
||||
--font-size-sm: 14px;
|
||||
--font-size: 16px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 20px;
|
||||
--font-size-xxl: 24px;
|
||||
|
||||
/* 行高 */
|
||||
--line-height-tight: 1.25;
|
||||
--line-height-normal: 1.5;
|
||||
--line-height-relaxed: 1.75;
|
||||
}
|
||||
|
||||
/* ===== 全局字体设置 ===== */
|
||||
.text-xs { font-size: var(--font-size-xs); }
|
||||
.text-sm { font-size: var(--font-size-sm); }
|
||||
.text-base { font-size: var(--font-size); }
|
||||
.text-lg { font-size: var(--font-size-lg); }
|
||||
.text-xl { font-size: var(--font-size-xl); }
|
||||
.text-2xl { font-size: var(--font-size-xxl); }
|
||||
|
||||
.font-medium { font-weight: 500; }
|
||||
.font-semibold { font-weight: 600; }
|
||||
.font-bold { font-weight: 700; }
|
||||
|
||||
/* ===== 全局颜色类 ===== */
|
||||
.text-primary { color: var(--primary-color); }
|
||||
.text-success { color: var(--success-color); }
|
||||
.text-warning { color: var(--warning-color); }
|
||||
.text-error { color: var(--error-color); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-disabled { color: var(--text-disabled); }
|
||||
|
||||
/* ===== 全局背景类 ===== */
|
||||
.bg-white { background-color: var(--background-color); }
|
||||
.bg-light { background-color: var(--background-color-light); }
|
||||
.bg-dark { background-color: var(--background-color-dark); }
|
||||
|
||||
/* ===== 全局边框类 ===== */
|
||||
.border { border: 1px solid var(--border-color); }
|
||||
.border-light { border: 1px solid var(--border-color-light); }
|
||||
.border-primary { border: 1px solid var(--primary-color); }
|
||||
|
||||
/* ===== 全局阴影类 ===== */
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow { box-shadow: var(--shadow); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
|
||||
/* ===== 全局圆角类 ===== */
|
||||
.rounded-sm { border-radius: var(--border-radius-sm); }
|
||||
.rounded { border-radius: var(--border-radius); }
|
||||
.rounded-lg { border-radius: var(--border-radius-lg); }
|
||||
.rounded-xl { border-radius: var(--border-radius-xl); }
|
||||
|
||||
/* ===== 全局间距类 ===== */
|
||||
.m-1 { margin: var(--spacing-xs); }
|
||||
.m-2 { margin: var(--spacing-sm); }
|
||||
.m-3 { margin: var(--spacing); }
|
||||
.m-4 { margin: var(--spacing-lg); }
|
||||
.m-5 { margin: var(--spacing-xl); }
|
||||
|
||||
.p-1 { padding: var(--spacing-xs); }
|
||||
.p-2 { padding: var(--spacing-sm); }
|
||||
.p-3 { padding: var(--spacing); }
|
||||
.p-4 { padding: var(--spacing-lg); }
|
||||
.p-5 { padding: var(--spacing-xl); }
|
||||
|
||||
/* ===== 全局布局类 ===== */
|
||||
.flex { display: flex; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
.block { display: block; }
|
||||
.inline-block { display: inline-block; }
|
||||
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-center { align-items: center; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.items-stretch { align-items: stretch; }
|
||||
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-around { justify-content: space-around; }
|
||||
|
||||
.flex-1 { flex: 1; }
|
||||
.flex-auto { flex: auto; }
|
||||
.flex-none { flex: none; }
|
||||
|
||||
/* ===== 全局工具类 ===== */
|
||||
.w-full { width: 100%; }
|
||||
.h-full { height: 100%; }
|
||||
.text-left { text-align: left; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-default { cursor: default; }
|
||||
|
||||
/* ===== 响应式断点 ===== */
|
||||
@media (min-width: 1200px) {
|
||||
:root {
|
||||
--container-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) and (min-width: 768px) {
|
||||
:root {
|
||||
--container-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
:root {
|
||||
--container-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== App根容器 ===== */
|
||||
.app-root {
|
||||
min-height: 100vh;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
.dev-probe {
|
||||
position: fixed;
|
||||
left: 16rpx;
|
||||
top: 16rpx;
|
||||
z-index: 999999;
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 12rpx;
|
||||
background: rgba(17, 24, 39, 0.82);
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.dev-nav {
|
||||
position: fixed;
|
||||
left: 16rpx;
|
||||
top: 64rpx;
|
||||
z-index: 999999;
|
||||
/* ===== 24栅格系统 ===== */
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 calc(-1 * var(--spacing-sm));
|
||||
}
|
||||
|
||||
.dev-nav-btn {
|
||||
display: inline-block;
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: #111827;
|
||||
font-size: 22rpx;
|
||||
border: 2rpx solid rgba(17, 24, 39, 0.12);
|
||||
text-decoration: none;
|
||||
.col {
|
||||
padding: 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
.col-1 { flex: 0 0 4.16666667%; max-width: 4.16666667%; }
|
||||
.col-2 { flex: 0 0 8.33333333%; max-width: 8.33333333%; }
|
||||
.col-3 { flex: 0 0 12.5%; max-width: 12.5%; }
|
||||
.col-4 { flex: 0 0 16.66666667%; max-width: 16.66666667%; }
|
||||
.col-5 { flex: 0 0 20.83333333%; max-width: 20.83333333%; }
|
||||
.col-6 { flex: 0 0 25%; max-width: 25%; }
|
||||
.col-7 { flex: 0 0 29.16666667%; max-width: 29.16666667%; }
|
||||
.col-8 { flex: 0 0 33.33333333%; max-width: 33.33333333%; }
|
||||
.col-9 { flex: 0 0 37.5%; max-width: 37.5%; }
|
||||
.col-10 { flex: 0 0 41.66666667%; max-width: 41.66666667%; }
|
||||
.col-11 { flex: 0 0 45.83333333%; max-width: 45.83333333%; }
|
||||
.col-12 { flex: 0 0 50%; max-width: 50%; }
|
||||
.col-13 { flex: 0 0 54.16666667%; max-width: 54.16666667%; }
|
||||
.col-14 { flex: 0 0 58.33333333%; max-width: 58.33333333%; }
|
||||
.col-15 { flex: 0 0 62.5%; max-width: 62.5%; }
|
||||
.col-16 { flex: 0 0 66.66666667%; max-width: 66.66666667%; }
|
||||
.col-17 { flex: 0 0 70.83333333%; max-width: 70.83333333%; }
|
||||
.col-18 { flex: 0 0 75%; max-width: 75%; }
|
||||
.col-19 { flex: 0 0 79.16666667%; max-width: 79.16666667%; }
|
||||
.col-20 { flex: 0 0 83.33333333%; max-width: 83.33333333%; }
|
||||
.col-21 { flex: 0 0 87.5%; max-width: 87.5%; }
|
||||
.col-22 { flex: 0 0 91.66666667%; max-width: 91.66666667%; }
|
||||
.col-23 { flex: 0 0 95.83333333%; max-width: 95.83333333%; }
|
||||
.col-24 { flex: 0 0 100%; max-width: 100%; }
|
||||
|
||||
/* ===== 响应式栅格 ===== */
|
||||
@media (max-width: 1199px) {
|
||||
.col-lg-1 { flex: 0 0 4.16666667%; max-width: 4.16666667%; }
|
||||
.col-lg-2 { flex: 0 0 8.33333333%; max-width: 8.33333333%; }
|
||||
.col-lg-3 { flex: 0 0 12.5%; max-width: 12.5%; }
|
||||
.col-lg-4 { flex: 0 0 16.66666667%; max-width: 16.66666667%; }
|
||||
.col-lg-5 { flex: 0 0 20.83333333%; max-width: 20.83333333%; }
|
||||
.col-lg-6 { flex: 0 0 25%; max-width: 25%; }
|
||||
.col-lg-7 { flex: 0 0 29.16666667%; max-width: 29.16666667%; }
|
||||
.col-lg-8 { flex: 0 0 33.33333333%; max-width: 33.33333333%; }
|
||||
.col-lg-9 { flex: 0 0 37.5%; max-width: 37.5%; }
|
||||
.col-lg-10 { flex: 0 0 41.66666667%; max-width: 41.66666667%; }
|
||||
.col-lg-11 { flex: 0 0 45.83333333%; max-width: 45.83333333%; }
|
||||
.col-lg-12 { flex: 0 0 50%; max-width: 50%; }
|
||||
.col-lg-13 { flex: 0 0 54.16666667%; max-width: 54.16666667%; }
|
||||
.col-lg-14 { flex: 0 0 58.33333333%; max-width: 58.33333333%; }
|
||||
.col-lg-15 { flex: 0 0 62.5%; max-width: 62.5%; }
|
||||
.col-lg-16 { flex: 0 0 66.66666667%; max-width: 66.66666667%; }
|
||||
.col-lg-17 { flex: 0 0 70.83333333%; max-width: 70.83333333%; }
|
||||
.col-lg-18 { flex: 0 0 75%; max-width: 75%; }
|
||||
.col-lg-19 { flex: 0 0 79.16666667%; max-width: 79.16666667%; }
|
||||
.col-lg-20 { flex: 0 0 83.33333333%; max-width: 83.33333333%; }
|
||||
.col-lg-21 { flex: 0 0 87.5%; max-width: 87.5%; }
|
||||
.col-lg-22 { flex: 0 0 91.66666667%; max-width: 91.66666667%; }
|
||||
.col-lg-23 { flex: 0 0 95.83333333%; max-width: 95.83333333%; }
|
||||
.col-lg-24 { flex: 0 0 100%; max-width: 100%; }
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.col-md-1 { flex: 0 0 4.16666667%; max-width: 4.16666667%; }
|
||||
.col-md-2 { flex: 0 0 8.33333333%; max-width: 8.33333333%; }
|
||||
.col-md-3 { flex: 0 0 12.5%; max-width: 12.5%; }
|
||||
.col-md-4 { flex: 0 0 16.66666667%; max-width: 16.66666667%; }
|
||||
.col-md-5 { flex: 0 0 20.83333333%; max-width: 20.83333333%; }
|
||||
.col-md-6 { flex: 0 0 25%; max-width: 25%; }
|
||||
.col-md-7 { flex: 0 0 29.16666667%; max-width: 29.16666667%; }
|
||||
.col-md-8 { flex: 0 0 33.33333333%; max-width: 33.33333333%; }
|
||||
.col-md-9 { flex: 0 0 37.5%; max-width: 37.5%; }
|
||||
.col-md-10 { flex: 0 0 41.66666667%; max-width: 41.66666667%; }
|
||||
.col-md-11 { flex: 0 0 45.83333333%; max-width: 45.83333333%; }
|
||||
.col-md-12 { flex: 0 0 50%; max-width: 50%; }
|
||||
.col-md-13 { flex: 0 0 54.16666667%; max-width: 54.16666667%; }
|
||||
.col-md-14 { flex: 0 0 58.33333333%; max-width: 58.33333333%; }
|
||||
.col-md-15 { flex: 0 0 62.5%; max-width: 62.5%; }
|
||||
.col-md-16 { flex: 0 0 66.66666667%; max-width: 66.66666667%; }
|
||||
.col-md-17 { flex: 0 0 70.83333333%; max-width: 70.83333333%; }
|
||||
.col-md-18 { flex: 0 0 75%; max-width: 75%; }
|
||||
.col-md-19 { flex: 0 0 79.16666667%; max-width: 79.16666667%; }
|
||||
.col-md-20 { flex: 0 0 83.33333333%; max-width: 83.33333333%; }
|
||||
.col-md-21 { flex: 0 0 87.5%; max-width: 87.5%; }
|
||||
.col-md-22 { flex: 0 0 91.66666667%; max-width: 91.66666667%; }
|
||||
.col-md-23 { flex: 0 0 95.83333333%; max-width: 95.83333333%; }
|
||||
.col-md-24 { flex: 0 0 100%; max-width: 100%; }
|
||||
|
||||
.row {
|
||||
margin: 0 calc(-1 * var(--spacing-xs));
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 0 var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 按钮基础样式 ===== */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-sm) var(--spacing);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
line-height: var(--line-height-tight);
|
||||
white-space: nowrap;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: var(--text-inverse);
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
color: var(--text-secondary);
|
||||
background-color: var(--background-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* ===== 卡片基础样式 ===== */
|
||||
.card {
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--border-color-light);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ===== 输入框基础样式 ===== */
|
||||
.input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-tight);
|
||||
color: var(--text-primary);
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
/* ===== 滚动条样式 ===== */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--background-color-light);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-disabled);
|
||||
}
|
||||
</style>
|
||||
|
||||
570
CRMEB_DASHBOARD_README.md
Normal file
@@ -0,0 +1,570 @@
|
||||
# CRMEB 标准版后台管理系统
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
基于 uni-app-x 完全自主开发的 CRMEB 风格后台管理系统,严格遵循 CRMEB 设计规范,实现完整的数据看板和用户统计功能。
|
||||
|
||||
## 🏗️ 目录结构
|
||||
|
||||
```
|
||||
mall/
|
||||
├── App.uvue # 全局样式配置
|
||||
├── layouts/
|
||||
│ └── admin/
|
||||
│ ├── index.uvue # 主布局组件
|
||||
│ ├── components/
|
||||
│ │ └── card.uvue # 卡片组件
|
||||
│ └── utils/
|
||||
│ └── echarts-config.uts # ECharts配置
|
||||
├── pages/
|
||||
│ ├── minimal.uvue # 测试页面
|
||||
│ └── mall/
|
||||
│ └── admin/
|
||||
│ ├── index.uvue # 数据看板
|
||||
│ ├── user-management.uvue # 用户管理
|
||||
│ ├── product-management.uvue # 商品管理
|
||||
│ ├── order-management.uvue # 订单管理
|
||||
│ ├── finance-management.uvue # 财务管理
|
||||
│ └── user-statistics.uvue # 用户统计页
|
||||
├── pages.json # 页面配置
|
||||
└── CRMEB_DASHBOARD_README.md # 项目文档
|
||||
```
|
||||
|
||||
## 🎨 设计规范
|
||||
|
||||
### 全局样式体系
|
||||
- **24栅格系统**: 响应式布局,支持1-24列
|
||||
- **CSS变量**: 统一的颜色、间距、圆角规范
|
||||
- **全局重置**: 消除浏览器默认样式差异
|
||||
- **主题色**: CRMEB 风格的蓝色系配色
|
||||
|
||||
### 布局架构
|
||||
- **AdminLayout**: 左侧菜单 + 顶部导航 + 标签页 + 内容区
|
||||
- **垂直菜单**: 一级图标菜单 + 二级文字菜单 + 折叠功能
|
||||
- **标签页**: 可关闭的多标签页,支持切换导航
|
||||
- **内容区**: flex:1 + height:0 + scroll-view 确保正确滚动
|
||||
|
||||
## 📊 核心功能
|
||||
|
||||
### 1. 数据看板 (Dashboard)
|
||||
|
||||
#### KPI 指标卡片 (第一行)
|
||||
```vue
|
||||
<!-- 4个KPI卡片:销售额/访问量/订单量/新增用户 -->
|
||||
<view class="kpi-cards-row">
|
||||
<view class="kpi-card">
|
||||
<view class="card-content">
|
||||
<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">+5.7%</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<text>昨日:¥118,920.30</text>
|
||||
<text>本月累计:¥2,857,808.90</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 订单统计图表 (第二行)
|
||||
```vue
|
||||
<!-- 柱状图(订单金额) + 折线图(订单数量) -->
|
||||
<AdminCard title="订单统计">
|
||||
<view class="chart-container">
|
||||
<!-- ECharts 组合图表 -->
|
||||
</view>
|
||||
</AdminCard>
|
||||
```
|
||||
|
||||
#### 用户分析图表 (第三行)
|
||||
```vue
|
||||
<!-- 用户趋势折线图 + 用户构成饼图 -->
|
||||
<view class="charts-row two-cols">
|
||||
<AdminCard title="用户趋势"><!-- 折线图 --></AdminCard>
|
||||
<AdminCard title="用户构成"><!-- 饼图 --></AdminCard>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 2. 用户统计页
|
||||
|
||||
#### 筛选条件栏
|
||||
```vue
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-left">
|
||||
<!-- 用户渠道选择器 + 日期范围选择器 -->
|
||||
</view>
|
||||
<view class="filter-right">
|
||||
<!-- 查询按钮 + 导出按钮 -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 指标概览 (6个KPI卡片)
|
||||
```vue
|
||||
<view class="metrics-row">
|
||||
<!-- 累计用户/访客数/浏览量/新增用户/成交用户/付费会员 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 多折线趋势图
|
||||
```vue
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<!-- 图表图例 + 多折线图表 -->
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### AdminLayout 组件
|
||||
|
||||
#### 核心特性
|
||||
```javascript
|
||||
// 双层侧边栏
|
||||
const menuList = ref([
|
||||
{
|
||||
id: 'dashboard',
|
||||
title: '首页',
|
||||
icon: 'icon-dashboard',
|
||||
path: '/pages/mall/admin/index',
|
||||
subMenus: [] // 二级菜单
|
||||
}
|
||||
// ... 其他菜单项
|
||||
])
|
||||
|
||||
// 标签页管理
|
||||
const tabs = ref([
|
||||
{ id: 'dashboard', title: '首页', closable: false }
|
||||
])
|
||||
|
||||
// 折叠状态
|
||||
const isCollapsed = ref(false)
|
||||
```
|
||||
|
||||
#### 布局结构
|
||||
```vue
|
||||
<view class="admin-layout">
|
||||
<!-- 左侧 Sider -->
|
||||
<view class="admin-sider" :class="{ 'sider-collapsed': isCollapsed }">
|
||||
<view class="sider-header"><!-- Logo + 折叠按钮 --></view>
|
||||
<view class="menu-primary"><!-- 一级菜单 --></view>
|
||||
<view class="menu-secondary"><!-- 二级菜单 --></view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<view class="admin-main">
|
||||
<view class="admin-header"><!-- 顶部导航 --></view>
|
||||
<view class="admin-tabs"><!-- 标签页 --></view>
|
||||
<scroll-view class="page-content" scroll-y="true" :style="{ flex: '1', height: '0' }">
|
||||
<slot /><!-- 页面内容 -->
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
### ECharts 图表配置
|
||||
|
||||
#### 组合图表配置
|
||||
```javascript
|
||||
export const getOrderChartOption = (period) => ({
|
||||
series: [
|
||||
{
|
||||
name: '订单金额',
|
||||
type: 'bar',
|
||||
data: amountData,
|
||||
itemStyle: { color: '#1890ff' }
|
||||
},
|
||||
{
|
||||
name: '订单数量',
|
||||
type: 'line',
|
||||
data: countData,
|
||||
itemStyle: { color: '#52c41a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
#### 多折线图配置
|
||||
```javascript
|
||||
export const getUserStatisticsOption = () => ({
|
||||
series: [
|
||||
{ name: '新增用户', type: 'line', data: newUsersData },
|
||||
{ name: '访客数', type: 'line', data: visitorsData },
|
||||
// ... 更多数据线
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 📱 响应式设计
|
||||
|
||||
### 断点系统
|
||||
```scss
|
||||
/* >=1200px: 4卡片一行 */
|
||||
.kpi-cards-row { display: flex; gap: 24px; }
|
||||
|
||||
/* <=1200px: 2卡片一行 */
|
||||
@media (max-width: 1199px) {
|
||||
.kpi-card { min-width: 45%; }
|
||||
}
|
||||
|
||||
/* <=768px: 单列布局 */
|
||||
@media (max-width: 767px) {
|
||||
.kpi-cards-row { flex-direction: column; }
|
||||
.charts-row.two-cols { flex-direction: column; }
|
||||
}
|
||||
```
|
||||
|
||||
### 栅格系统
|
||||
```scss
|
||||
/* 24列栅格系统 */
|
||||
.col-6 { flex: 0 0 25%; max-width: 25%; }
|
||||
.col-12 { flex: 0 0 50%; max-width: 50%; }
|
||||
.col-24 { flex: 0 0 100%; max-width: 100%; }
|
||||
```
|
||||
|
||||
## 🚀 运行指南
|
||||
|
||||
### 开发环境
|
||||
```bash
|
||||
# HBuilderX 中运行
|
||||
# 选择:运行 -> 运行到浏览器 -> Chrome
|
||||
```
|
||||
|
||||
### 页面访问
|
||||
- **数据看板**: `/pages/mall/admin/index`
|
||||
- **用户统计**: `/pages/mall/admin/user-statistics`
|
||||
- **其他页面**: 通过左侧菜单导航
|
||||
|
||||
### 功能测试
|
||||
1. **菜单导航**: 点击左侧菜单切换页面
|
||||
2. **标签页**: 点击标签切换,点击关闭按钮关闭
|
||||
3. **折叠功能**: 点击折叠按钮收起/展开菜单
|
||||
4. **图表展示**: 查看各种数据图表
|
||||
5. **响应式**: 调整浏览器窗口测试适配
|
||||
|
||||
## 📚 开发规范
|
||||
|
||||
### 文件命名
|
||||
- **组件**: PascalCase (`AdminLayout.vue`)
|
||||
- **页面**: kebab-case (`user-statistics.uvue`)
|
||||
- **工具**: camelCase (`echarts-config.uts`)
|
||||
|
||||
### 代码组织
|
||||
```vue
|
||||
<template>
|
||||
<!-- 模板:清晰的结构层次 -->
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 导入语句
|
||||
// 响应式数据
|
||||
// 计算属性
|
||||
// 生命周期
|
||||
// 方法定义
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 组件内样式:避免scoped污染 */
|
||||
</style>
|
||||
```
|
||||
|
||||
### 样式原则
|
||||
- **组件内样式**: 避免 `scoped`,确保样式隔离
|
||||
- **CSS变量**: 使用统一的主题变量
|
||||
- **BEM命名**: 清晰的样式命名规范
|
||||
- **移动优先**: 响应式设计从移动端开始
|
||||
|
||||
## 🎯 项目特色
|
||||
|
||||
### ✅ 完全自主开发
|
||||
- **0%源码复制**: 100%自主编写
|
||||
- **CRMEB风格**: 严格遵循设计规范
|
||||
- **技术先进**: Vue 3 + TypeScript + uni-app-x
|
||||
- **功能完整**: 数据看板 + 用户统计双页面
|
||||
|
||||
### ✅ 设计还原度高
|
||||
- **布局结构**: 1:1还原CRMEB后台布局
|
||||
- **视觉风格**: 白底轻阴影,Element-UI设计语言
|
||||
- **交互体验**: 流畅的动画和反馈效果
|
||||
- **响应式**: 全设备适配
|
||||
|
||||
### ✅ 架构优秀
|
||||
- **组件化**: 模块化组件设计
|
||||
- **可扩展**: 易于添加新功能
|
||||
- **可维护**: 清晰的代码结构
|
||||
- **性能优化**: 合理的渲染策略
|
||||
|
||||
## 📋 功能清单
|
||||
|
||||
### 已实现功能
|
||||
- ✅ CRMEB风格垂直菜单布局
|
||||
- ✅ 顶部多标签页系统
|
||||
- ✅ 双层侧边栏导航
|
||||
- ✅ KPI指标卡片展示
|
||||
- ✅ 订单统计组合图表
|
||||
- ✅ 用户趋势分析图表
|
||||
- ✅ 用户构成饼图
|
||||
- ✅ 用户统计筛选功能
|
||||
- ✅ 多折线趋势图表
|
||||
- ✅ 响应式24栅格布局
|
||||
- ✅ 完整的样式系统
|
||||
- ✅ ECharts图表配置
|
||||
|
||||
### 扩展功能
|
||||
- 🔄 ECharts实际集成
|
||||
- 🔄 数据实时更新
|
||||
- 🔄 图表交互功能
|
||||
- 🔄 数据导出功能
|
||||
- 🔄 更多管理页面
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
本项目成功实现了CRMEB标准版后台管理系统,具备完整的数据看板和用户统计功能。通过严格遵循CRMEB的设计规范和自主开发,确保了代码质量和技术先进性。
|
||||
|
||||
项目采用了现代化的技术栈,实现了响应式设计和模块化架构,为后续功能扩展奠定了坚实基础。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署运行
|
||||
|
||||
### 开发环境
|
||||
```bash
|
||||
# HBuilderX 中运行
|
||||
# 选择:运行 -> 运行到浏览器
|
||||
```
|
||||
|
||||
### 访问页面
|
||||
- **数据看板**: `/pages/mall/admin/index`
|
||||
- **用户统计**: `/pages/mall/admin/user-statistics`
|
||||
- **其他页面**: 通过左侧菜单导航
|
||||
|
||||
### 功能验证
|
||||
1. **菜单导航**: 左侧双层菜单切换页面
|
||||
2. **标签页**: 顶部标签页切换和关闭
|
||||
3. **折叠功能**: 菜单栏收起/展开
|
||||
4. **图表展示**: 查看各种数据可视化
|
||||
5. **响应式**: 调整窗口测试适配效果
|
||||
|
||||
## 📋 功能清单
|
||||
|
||||
### ✅ 已实现功能
|
||||
- [x] CRMEB风格垂直菜单布局
|
||||
- [x] 顶部多标签页系统
|
||||
- [x] 双层侧边栏导航
|
||||
- [x] 二级菜单Tab切换功能
|
||||
- [x] KPI指标卡片展示
|
||||
- [x] 订单统计组合图表
|
||||
- [x] 用户趋势分析图表
|
||||
- [x] 用户构成饼图
|
||||
- [x] 用户统计筛选功能
|
||||
- [x] 多折线趋势图表
|
||||
- [x] 响应式24栅格布局
|
||||
- [x] 完整的样式系统
|
||||
- [x] ECharts图表配置
|
||||
- [x] 页面参数处理(onLoad)
|
||||
- [x] Tab内部状态管理
|
||||
|
||||
### 🎯 技术亮点
|
||||
- **完全自主开发**: 0%源码复制,100%原创
|
||||
- **CRMEB风格还原**: 严格遵循设计规范
|
||||
- **现代技术栈**: Vue 3 + TypeScript + uni-app-x
|
||||
- **架构设计**: 模块化组件,易于维护
|
||||
- **用户体验**: 流畅交互,响应式适配
|
||||
|
||||
---
|
||||
|
||||
## 🔧 二级菜单Tab切换机制详解
|
||||
|
||||
### 实现原理
|
||||
|
||||
CRMEB后台的二级菜单采用 **页面级Tab切换** 模式:
|
||||
- 点击一级菜单:跳转到对应页面的**默认Tab**
|
||||
- 点击二级菜单:跳转到同一页面的**指定Tab**
|
||||
- 通过URL参数控制Tab状态
|
||||
|
||||
### 技术实现
|
||||
|
||||
#### 1. AdminLayout菜单配置
|
||||
```javascript
|
||||
const menuList = ref([
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户管理',
|
||||
icon: 'icon-user',
|
||||
path: '/pages/mall/admin/user-management',
|
||||
subMenus: [
|
||||
{
|
||||
id: 'user-list',
|
||||
title: '用户列表',
|
||||
path: '/pages/mall/admin/user-management' // 默认Tab
|
||||
},
|
||||
{
|
||||
id: 'user-add',
|
||||
title: '添加用户',
|
||||
path: '/pages/mall/admin/user-management?action=add' // 指定Tab
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
title: '商品管理',
|
||||
icon: 'icon-shopping',
|
||||
path: '/pages/mall/admin/product-management',
|
||||
subMenus: [
|
||||
{
|
||||
id: 'product-list',
|
||||
title: '商品列表',
|
||||
path: '/pages/mall/admin/product-management'
|
||||
},
|
||||
{
|
||||
id: 'product-add',
|
||||
title: '添加商品',
|
||||
path: '/pages/mall/admin/product-management?action=add'
|
||||
},
|
||||
{
|
||||
id: 'category',
|
||||
title: '商品分类',
|
||||
path: '/pages/mall/admin/product-management?tab=category'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
#### 2. 菜单点击处理
|
||||
```javascript
|
||||
const handleMenuClick = (menu: any) => {
|
||||
activeMenu.value = menu.id
|
||||
// 跳转到默认Tab
|
||||
uni.navigateTo({ url: menu.path })
|
||||
}
|
||||
|
||||
const handleSubMenuClick = (subMenu: any) => {
|
||||
activeSubMenu.value = subMenu.id
|
||||
// 跳转到指定Tab(带参数)
|
||||
uni.navigateTo({ url: subMenu.path })
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 页面参数处理
|
||||
```javascript
|
||||
// 页面Tab配置
|
||||
const tabs = ref([
|
||||
{ key: 'user-list', title: '用户列表', icon: 'icon-list' },
|
||||
{ key: 'user-add', title: '添加用户', icon: 'icon-add' },
|
||||
{ key: 'category', title: '商品分类', icon: 'icon-category' }
|
||||
])
|
||||
|
||||
const activeTab = ref('user-list')
|
||||
|
||||
// 页面加载时处理参数
|
||||
onLoad((options: any) => {
|
||||
if (options && options.action) {
|
||||
if (options.action === 'add') {
|
||||
activeTab.value = 'user-add'
|
||||
showAddModal.value = true
|
||||
}
|
||||
} else if (options && options.tab) {
|
||||
if (options.tab === 'category') {
|
||||
activeTab.value = 'category'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 4. Tab内容切换
|
||||
```vue
|
||||
<!-- Tab栏 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ 'active': activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<text class="tab-title">{{ tab.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab内容 -->
|
||||
<view v-if="activeTab === 'user-list'">
|
||||
<!-- 用户列表内容 -->
|
||||
</view>
|
||||
<view v-if="activeTab === 'user-add'">
|
||||
<!-- 添加用户表单 -->
|
||||
</view>
|
||||
<view v-if="activeTab === 'category'">
|
||||
<!-- 商品分类管理 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
### 功能示例
|
||||
|
||||
#### 用户管理页面
|
||||
- **用户列表Tab**: 显示用户表格、搜索、筛选、分页
|
||||
- **添加用户Tab**: 显示新增用户表单
|
||||
|
||||
#### 商品管理页面
|
||||
- **商品列表Tab**: 商品表格管理
|
||||
- **添加商品Tab**: 商品信息表单
|
||||
- **商品分类Tab**: 分类树形管理
|
||||
|
||||
#### 订单管理页面
|
||||
- **订单列表Tab**: 订单表格展示
|
||||
- **订单详情Tab**: 订单详细信息
|
||||
|
||||
### URL参数映射
|
||||
|
||||
| 页面 | 默认Tab | 参数Tab | 功能 |
|
||||
|------|---------|---------|------|
|
||||
| 用户管理 | `user-list` | `?action=add` → `user-add` | 添加用户 |
|
||||
| 商品管理 | `product-list` | `?action=add` → `product-add`<br>`?tab=category` → `category` | 添加商品/分类管理 |
|
||||
| 订单管理 | `order-list` | `?action=detail` → `order-detail` | 订单详情 |
|
||||
| 财务管理 | `finance-overview` | `?tab=withdrawals` → `withdrawals` | 提现管理 |
|
||||
| 系统设置 | `basic` | `?tab=security` → `security`<br>`?tab=email` → `email` | 安全设置/邮件设置 |
|
||||
|
||||
### 样式实现
|
||||
|
||||
#### Tab栏样式
|
||||
```scss
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 6rpx;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: #f5f5f5;
|
||||
color: #666666;
|
||||
|
||||
&.active {
|
||||
background: #1890ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*技术栈:uni-app-x + Vue 3 + TypeScript + SCSS + ECharts*
|
||||
*设计风格:CRMEB标准版后台*
|
||||
*开发时间:完全自主开发* 🎊
|
||||
@@ -19,6 +19,11 @@ export const WS_URL: string = 'ws://192.168.1.63:8000/realtime/v1/websocket'
|
||||
// export const SUPA_KEY: string = 'your-anon-key'
|
||||
// export const WS_URL: string = 'wss://ak3.oulog.com/realtime/v1/websocket'
|
||||
|
||||
// 指向你的 Supabase 服务(开发/私有部署)
|
||||
// export const SUPA_URL: string = 'http://192.168.1.64:3000'
|
||||
// export const SUPA_KEY: string = 'your-anon-key'
|
||||
// export const WS_URL: string = 'ws://192.168.1.64:3000/realtime/v1'
|
||||
|
||||
// 路由配置
|
||||
export const HOME_REDIRECT: string = '/pages/mall/consumer/index'
|
||||
export const TABORPAGE: string = '/pages/mall/consumer/index'
|
||||
|
||||
@@ -646,6 +646,10 @@ export class AkSupa {
|
||||
this.user = null
|
||||
}
|
||||
async signIn(email : string, password : string) : Promise<AkSupaSignInResult> {
|
||||
// 提前检查 apikey 配置是否为占位符,避免发送无效请求导致 401
|
||||
if (this.apikey == null || this.apikey.trim() === '' || this.apikey === 'your-anon-key') {
|
||||
throw new Error('Supabase 配置错误:请在 ak/config.uts 中设置 SUPA_KEY(当前为占位符)');
|
||||
}
|
||||
const res = await AkReq.request({
|
||||
url: this.baseUrl + '/auth/v1/token?grant_type=password',
|
||||
method: 'POST',
|
||||
@@ -656,13 +660,31 @@ export class AkSupa {
|
||||
data: { email, password } as UTSJSONObject,
|
||||
contentType: 'application/json'
|
||||
}, false);
|
||||
//console.log(res)
|
||||
const data = new UTSJSONObject(res.data); // 修正:确保data为UTSJSONObject
|
||||
// 如果响应不是 2xx(例如 401),提取后端错误信息并抛出,便于上层显示具体原因
|
||||
const status = res.status ?? 0;
|
||||
if (!(status >= 200 && status < 400)) {
|
||||
let msg = 'user.login.login_failed';
|
||||
try {
|
||||
if (res.data != null) {
|
||||
const obj = new UTSJSONObject(res.data);
|
||||
msg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? msg;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
// 解析成功的返回体
|
||||
let data: UTSJSONObject;
|
||||
try {
|
||||
data = new UTSJSONObject(res.data);
|
||||
} catch (e) {
|
||||
data = new UTSJSONObject({});
|
||||
}
|
||||
const access_token = data.getString('access_token') ?? '';
|
||||
const refresh_token = data.getString('refresh_token') ?? '';
|
||||
const expires_at = data.getNumber('expires_at') ?? 0;
|
||||
const user = data.getJSON('user');
|
||||
//console.log(user, data)
|
||||
AkReq.setToken(access_token, refresh_token, expires_at);
|
||||
const session : AkSupaSignInResult = {
|
||||
access_token: access_token,
|
||||
@@ -675,7 +697,6 @@ export class AkSupa {
|
||||
};
|
||||
this.session = session;
|
||||
this.user = user;
|
||||
//console.log(this.user)
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,19 @@ import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
|
||||
|
||||
const supa = new AkSupa(SUPA_URL, SUPA_KEY)
|
||||
|
||||
// Do not perform hard-coded auto sign-in during page preload (development mode may preload pages).
|
||||
// Instead, mark supa as ready if an existing session is present; otherwise defer sign-in to explicit user action.
|
||||
const supaReady: Promise<boolean> = (async () => {
|
||||
try {
|
||||
// await supa.signIn('akoo@163.com', 'Hf2152111')
|
||||
await supa.signIn('am@163.com', 'kookoo')
|
||||
return true
|
||||
const sess = supa.getSession();
|
||||
if (sess != null && sess.session != null) {
|
||||
return true;
|
||||
}
|
||||
// No session found — do not auto sign-in with hard-coded credentials.
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Supabase auto sign-in failed', err)
|
||||
return false
|
||||
console.error('Supabase instance init failed', err)
|
||||
return false;
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
window.process = { env: { NODE_ENV: 'development' } };
|
||||
</script>
|
||||
<!-- 入口脚本:通过 main.js 间接引入 main.uts,确保 MIME 为 JS -->
|
||||
<script type="module" src="/main.js"></script>
|
||||
<script type="module" src="/main"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
217
layouts/admin/README.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Mall Admin 布局系统
|
||||
|
||||
基于CRMEB Admin的设计,创建的uni-app版本的管理后台布局系统。
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
layouts/admin/
|
||||
├── index.uvue # 主布局组件(入口)
|
||||
├── defaults.uvue # 默认布局(完整布局)
|
||||
├── aside.uvue # 侧边栏组件
|
||||
├── header.uvue # 顶部栏组件
|
||||
├── breadcrumb.uvue # 面包屑导航组件
|
||||
├── tags-view.uvue # 标签页组件(可选)
|
||||
└── README.md # 使用说明
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 在页面中使用布局
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<AdminLayout current-page="page-id">
|
||||
<!-- 你的页面内容 -->
|
||||
<view class="page-container">
|
||||
<text>页面内容</text>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
|
||||
// 页面逻辑
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 页面配置
|
||||
|
||||
在 `pages.json` 中,所有admin页面都需要设置:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "admin/your-page",
|
||||
"style": {
|
||||
"navigationBarTitleText": "页面标题",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 组件说明
|
||||
|
||||
### AdminLayout (主组件)
|
||||
- **用途**: 统一的admin布局入口
|
||||
- **属性**:
|
||||
- `current-page`: 当前页面ID,用于菜单高亮
|
||||
|
||||
### AdminDefaults (默认布局)
|
||||
- **用途**: 完整的页面布局容器
|
||||
- **功能**: 包含侧边栏、主内容区、响应式适配
|
||||
|
||||
### AdminAside (侧边栏)
|
||||
- **用途**: 垂直菜单栏
|
||||
- **功能**:
|
||||
- 菜单折叠/展开
|
||||
- 子菜单支持
|
||||
- 移动端抽屉模式
|
||||
|
||||
### AdminHeader (顶部栏)
|
||||
- **用途**: 页面头部导航
|
||||
- **功能**: 面包屑导航、用户信息、通知中心
|
||||
|
||||
### AdminBreadcrumb (面包屑)
|
||||
- **用途**: 页面导航指示器
|
||||
- **功能**: 显示当前页面位置、快速导航
|
||||
|
||||
## 🎨 菜单配置
|
||||
|
||||
菜单配置在 `defaults.uvue` 中:
|
||||
|
||||
```javascript
|
||||
menuList: [
|
||||
{
|
||||
id: 'dashboard', // 唯一标识
|
||||
title: '首页', // 显示文本
|
||||
icon: 'icon-shujutongji', // 图标类名
|
||||
path: '/pages/mall/admin/index' // 跳转路径
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户管理',
|
||||
icon: 'icon-yonghuguanli',
|
||||
children: [ // 子菜单
|
||||
{
|
||||
id: 'user-list',
|
||||
title: '用户列表',
|
||||
icon: 'icon-yonghuguanli',
|
||||
path: '/pages/mall/admin/user-management'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 📱 响应式特性
|
||||
|
||||
### 桌面端 (> 768px)
|
||||
- 侧边栏默认展开 (240rpx)
|
||||
- 支持折叠到 80rpx
|
||||
- 完整菜单显示
|
||||
|
||||
### 平板端 (600px - 768px)
|
||||
- 侧边栏可折叠
|
||||
- 菜单文本正常显示
|
||||
|
||||
### 移动端 (< 600px)
|
||||
- 侧边栏隐藏
|
||||
- 点击菜单按钮显示抽屉
|
||||
- 带背景遮罩
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
- ✅ **垂直菜单布局** - 参考CRMEB Admin设计
|
||||
- ✅ **菜单折叠** - 支持展开/收起
|
||||
- ✅ **子菜单支持** - 多级菜单结构
|
||||
- ✅ **路由联动** - 自动高亮当前菜单
|
||||
- ✅ **响应式设计** - 适配各种屏幕尺寸
|
||||
- ✅ **移动端适配** - 抽屉式菜单
|
||||
- ✅ **主题定制** - 支持样式调整
|
||||
|
||||
## 🔧 自定义配置
|
||||
|
||||
### 修改菜单
|
||||
编辑 `defaults.uvue` 中的 `menuList` 数组
|
||||
|
||||
### 调整样式
|
||||
修改各组件的 `<style>` 部分
|
||||
|
||||
### 添加新页面
|
||||
1. 在菜单配置中添加新项
|
||||
2. 创建页面文件,使用 `AdminLayout` 包装
|
||||
3. 在 `pages.json` 中注册
|
||||
|
||||
## 📋 页面ID对照表
|
||||
|
||||
| 页面ID | 对应页面 | 说明 |
|
||||
|--------|----------|------|
|
||||
| dashboard | 首页 | 主页 |
|
||||
| user-list | 用户管理 | 用户列表页 |
|
||||
| merchant-list | 商家管理 | 商家列表页 |
|
||||
| product-list | 商品管理 | 商品列表页 |
|
||||
| order | 订单管理 | 订单管理页 |
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### 菜单不显示
|
||||
- 检查 `current-page` 属性是否正确
|
||||
- 确认菜单ID与页面ID匹配
|
||||
|
||||
### 样式异常
|
||||
- 确保页面设置了 `"navigationStyle": "custom"`
|
||||
- 检查组件导入是否正确
|
||||
|
||||
### 移动端异常
|
||||
- 确认响应式断点设置正确
|
||||
- 检查抽屉菜单逻辑
|
||||
|
||||
## 🔄 迁移指南
|
||||
|
||||
### 从旧布局迁移
|
||||
|
||||
1. **导入新布局**
|
||||
```javascript
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
```
|
||||
|
||||
2. **包装页面内容**
|
||||
```vue
|
||||
<template>
|
||||
<AdminLayout current-page="page-id">
|
||||
<!-- 原有内容 -->
|
||||
</AdminLayout>
|
||||
</template>
|
||||
```
|
||||
|
||||
3. **更新配置**
|
||||
```json
|
||||
{
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 样式变量
|
||||
|
||||
```scss
|
||||
// 主题色
|
||||
$primary-color: #1890ff;
|
||||
$sidebar-bg: #001529;
|
||||
$navbar-bg: #ffffff;
|
||||
|
||||
// 文字颜色
|
||||
$text-primary: #333333;
|
||||
$text-secondary: rgba(255, 255, 255, 0.75);
|
||||
$text-muted: rgba(255, 255, 255, 0.65);
|
||||
```
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请检查:
|
||||
1. 组件导入是否正确
|
||||
2. 页面配置是否完整
|
||||
3. 菜单ID是否匹配
|
||||
4. 样式冲突是否解决
|
||||
81
layouts/admin/components/AdminAside.uvue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="aside">
|
||||
<view class="aside-header">
|
||||
<view class="logo">
|
||||
<text class="logo-text">{{ collapsed ? '商' : '商城后台' }}</text>
|
||||
</view>
|
||||
<view class="collapse-btn" @click="$emit('toggle')">
|
||||
<text class="collapse-text">{{ collapsed ? '›' : '‹' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="aside-menu">
|
||||
<view
|
||||
v-for="m in menuList"
|
||||
:key="m.id"
|
||||
class="aside-item"
|
||||
:class="{ active: activeMenuId === m.id }"
|
||||
@click="$emit('menu-click', m.id)"
|
||||
>
|
||||
<image class="aside-icon" :src="m.icon" mode="aspectFit" />
|
||||
<text class="aside-title" v-if="!collapsed">{{ m.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import type { MenuItem } from '../types.uts'
|
||||
|
||||
defineProps<{
|
||||
collapsed: boolean
|
||||
menuList: MenuItem[]
|
||||
activeMenuId: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'toggle'): void
|
||||
(e:'menu-click', menuId: string): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.aside{
|
||||
width: 96px;
|
||||
background: #1f2a37;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
z-index: 1000;
|
||||
}
|
||||
.aside-header{
|
||||
height: 56px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.logo-text{ color:#fff; font-size:14px; font-weight:600; }
|
||||
.collapse-btn{ width:28px; height:28px; display:flex; align-items:center; justify-content:center; }
|
||||
.collapse-text{ color:rgba(255,255,255,0.7); }
|
||||
|
||||
.aside-menu{ padding: 8px 0; }
|
||||
.aside-item{
|
||||
height: 54px;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
gap: 6px;
|
||||
margin: 6px 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.aside-item.active{ background:#1677ff; }
|
||||
.aside-icon{ width:22px; height:22px; }
|
||||
.aside-title{ color:#fff; font-size:12px; }
|
||||
</style>
|
||||
20
layouts/admin/components/AdminFooter.uvue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<view class="footer">
|
||||
<text class="footer-text">商城后台 © {{ year }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const year = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.footer{
|
||||
height: 44px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
color:#9ca3af;
|
||||
}
|
||||
.footer-text{ font-size:12px; }
|
||||
</style>
|
||||
63
layouts/admin/components/AdminHeader.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="header">
|
||||
<view class="header-left">
|
||||
<text class="crumb">{{ breadcrumb }}</text>
|
||||
</view>
|
||||
|
||||
<view class="header-right">
|
||||
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
|
||||
<view class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
|
||||
<view class="hbtn" @click="$emit('notify')">
|
||||
<text>🔔</text>
|
||||
<view class="dot" v-if="hasNotification"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
defineProps<{
|
||||
breadcrumb: string
|
||||
hasNotification: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'search'): void
|
||||
(e:'refresh'): void
|
||||
(e:'notify'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header{
|
||||
height: 56px;
|
||||
background:#fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.crumb{ color:#374151; font-size:14px; }
|
||||
|
||||
.header-right{ display:flex; align-items:center; gap: 10px; }
|
||||
.hbtn{
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
background:#f6f7fb;
|
||||
position: relative;
|
||||
}
|
||||
.dot{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background:#ff4d4f;
|
||||
position:absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
</style>
|
||||
123
layouts/admin/components/AdminSubsider.uvue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view class="sub-sider" v-if="groups && groups.length > 0">
|
||||
<view class="sub-header">
|
||||
<text class="sub-title">{{ activeMenuTitle }}</text>
|
||||
<view class="sub-collapse" @click="collapsed = !collapsed">
|
||||
<text class="sub-collapse-text">{{ collapsed ? '›' : '‹' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="sub-body" scroll-y="true">
|
||||
<view v-for="(g, gi) in groups" :key="gi" class="group">
|
||||
<view class="group-title" @click="toggleGroup(g.title)">
|
||||
<text class="group-title-text">{{ g.title }}</text>
|
||||
<text class="group-arrow">{{ isGroupOpen(g.title) ? '˄' : '˅' }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="isGroupOpen(g.title)">
|
||||
<view
|
||||
v-for="c in g.children"
|
||||
:key="c.id"
|
||||
class="sub-item"
|
||||
:class="{ active: activeSubId === c.id }"
|
||||
@click="$emit('sub-click', c)"
|
||||
>
|
||||
<text class="sub-item-text" :class="{ activeText: activeSubId === c.id }">
|
||||
{{ c.title }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import type { MenuGroup, MenuChild } from '../types.uts'
|
||||
|
||||
const props = defineProps<{
|
||||
activeMenuTitle: string
|
||||
groups: MenuGroup[]
|
||||
activeSubId: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'sub-click', child: MenuChild): void
|
||||
}>()
|
||||
|
||||
const collapsed = ref(false)
|
||||
const openGroups = ref<string[]>([])
|
||||
|
||||
const isGroupOpen = (title: string): boolean => {
|
||||
// 默认展开第一个分组 + 当前高亮分组(简单策略)
|
||||
if (openGroups.value.length === 0 && props.groups && props.groups.length > 0) return props.groups[0].title === title
|
||||
return openGroups.value.includes(title)
|
||||
}
|
||||
|
||||
const toggleGroup = (title: string) => {
|
||||
if (openGroups.value.includes(title)) {
|
||||
openGroups.value = openGroups.value.filter(t => t !== title)
|
||||
} else {
|
||||
openGroups.value = [...openGroups.value, title]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sub-sider{
|
||||
width: 240px;
|
||||
background:#ffffff;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 96px; /* 紧贴主侧边栏 */
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 900;
|
||||
}
|
||||
.sub-header{
|
||||
height: 56px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
.sub-title{ font-size:16px; font-weight:600; color:#111827; }
|
||||
.sub-collapse{ width:28px; height:28px; display:flex; align-items:center; justify-content:center; }
|
||||
.sub-collapse-text{ color:#6b7280; }
|
||||
|
||||
.sub-body{ height: calc(100vh - 56px); }
|
||||
|
||||
.group{ padding: 8px 0; }
|
||||
.group-title{
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content: space-between;
|
||||
color:#111827;
|
||||
}
|
||||
.group-title-text{ font-size:15px; font-weight:600; }
|
||||
.group-arrow{ color:#6b7280; }
|
||||
|
||||
.sub-item{
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
background: transparent;
|
||||
}
|
||||
.sub-item.active{
|
||||
background: #eaf2ff;
|
||||
}
|
||||
.sub-item-text{
|
||||
font-size:14px;
|
||||
color:#111827;
|
||||
}
|
||||
.sub-item-text.activeText{
|
||||
color:#1677ff;
|
||||
font-weight:600;
|
||||
}
|
||||
</style>
|
||||
76
layouts/admin/components/AdminTagsView.uvue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<view class="tags">
|
||||
<scroll-view class="tags-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<view class="tags-row">
|
||||
<view
|
||||
v-for="t in tabs"
|
||||
:key="t.id"
|
||||
class="tag"
|
||||
:class="{ active: activeTabId === t.id }"
|
||||
@click="$emit('tab-click', t)"
|
||||
>
|
||||
<text class="tag-text">{{ t.title }}</text>
|
||||
<view class="tag-close" @click.stop="$emit('tab-close', t.id)">
|
||||
<text class="tag-close-text">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import type { TabItem } from '../types.uts'
|
||||
|
||||
defineProps<{
|
||||
tabs: TabItem[]
|
||||
activeTabId: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e:'tab-click', tab: TabItem): void
|
||||
(e:'tab-close', tabId: string): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tags{
|
||||
height: 44px;
|
||||
background:#fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
}
|
||||
.tags-scroll{ width: 100%; height: 44px; }
|
||||
.tags-row{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
height: 44px;
|
||||
}
|
||||
.tag{
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background:#fff;
|
||||
border-radius: 6px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap: 8px;
|
||||
}
|
||||
.tag.active{
|
||||
border-color:#1677ff;
|
||||
background:#eaf2ff;
|
||||
}
|
||||
.tag-text{ font-size:12px; color:#374151; }
|
||||
.tag-close{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
.tag-close-text{ font-size:14px; color:#6b7280; line-height:14px; }
|
||||
</style>
|
||||
94
layouts/admin/components/card.uvue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<view class="admin-card" :class="cardClass">
|
||||
<view class="card-header" v-if="title || $slots.header">
|
||||
<view class="card-title-section">
|
||||
<text class="card-title">{{ title }}</text>
|
||||
<view class="card-extra" v-if="$slots.extra">
|
||||
<slot name="extra"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
title?: string
|
||||
bordered?: boolean
|
||||
shadow?: string
|
||||
bodyStyle?: Record<string, any>
|
||||
}>()
|
||||
|
||||
// Computed
|
||||
const cardClass = computed(() => {
|
||||
return {
|
||||
'card-bordered': props.bordered !== false,
|
||||
'card-shadow': props.shadow !== 'none',
|
||||
'card-shadow-small': props.shadow === 'small',
|
||||
'card-shadow-medium': props.shadow === 'medium',
|
||||
'card-shadow-large': props.shadow === 'large'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.admin-card {
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&.card-bordered {
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
&.card-shadow {
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
&.card-shadow-small {
|
||||
box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
&.card-shadow-medium {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.card-shadow-large {
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.card-title-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-extra {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
</style>
|
||||
190
layouts/admin/index.uvue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<view class="layout-root">
|
||||
<!-- 主侧边栏 -->
|
||||
<AdminAside
|
||||
:collapsed="isCollapsed"
|
||||
:menuList="menuList"
|
||||
:activeMenuId="activeMenuId"
|
||||
@toggle="toggleCollapse"
|
||||
@menu-click="onMenuClick"
|
||||
/>
|
||||
|
||||
<!-- 二级侧边栏:固定在内容区左侧(独立层级) -->
|
||||
<AdminSubSider
|
||||
v-if="activeGroups.length > 0"
|
||||
:activeMenuTitle="activeMenuTitle"
|
||||
:groups="activeGroups"
|
||||
:activeSubId="activeSubId"
|
||||
@sub-click="onSubClick"
|
||||
/>
|
||||
|
||||
<!-- 右侧内容区(Header + Tags + 内容展示区 + Footer) -->
|
||||
<view
|
||||
class="main"
|
||||
:style="{ marginLeft: activeGroups.length > 0 ? '336px' : '96px' }"
|
||||
>
|
||||
<AdminHeader
|
||||
:breadcrumb="breadcrumb"
|
||||
:hasNotification="hasNotification"
|
||||
@search="onSearch"
|
||||
@refresh="onRefresh"
|
||||
@notify="onNotify"
|
||||
/>
|
||||
|
||||
<AdminTagsView
|
||||
:tabs="tabs"
|
||||
:activeTabId="activeTabId"
|
||||
@tab-click="onTabClick"
|
||||
@tab-close="onTabClose"
|
||||
/>
|
||||
|
||||
<!-- 展示区:只渲染 slot 内容(你的页面内容都在这里展示) -->
|
||||
<scroll-view class="content" scroll-y="true">
|
||||
<view class="content-inner">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<AdminFooter />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed } from 'vue'
|
||||
import AdminAside from './components/AdminAside.uvue'
|
||||
import AdminSubSider from './components/AdminSubSider.uvue'
|
||||
import AdminHeader from './components/AdminHeader.uvue'
|
||||
import AdminTagsView from './components/AdminTagsView.uvue'
|
||||
import AdminFooter from './components/AdminFooter.uvue'
|
||||
|
||||
import { menuList as menuConst } from './utils/menu.uts'
|
||||
import { findActiveByCurrentPage, getCurrentRoutePath } from './utils/nav.uts'
|
||||
import { makeTabFromPath, upsertTab, removeTab } from './utils/tabs.uts'
|
||||
import type { MenuItem, TabItem } from './types.uts'
|
||||
|
||||
// 你页面传进来的 currentPage:可能是顶级 id,也可能是子页面 id(user-list)
|
||||
const props = defineProps<{ currentPage: string }>()
|
||||
|
||||
const menuList = ref<MenuItem[]>(menuConst)
|
||||
|
||||
const isCollapsed = ref(false)
|
||||
const hasNotification = ref(true)
|
||||
|
||||
// active states
|
||||
const activeMenuId = ref('home')
|
||||
const activeSubId = ref('')
|
||||
|
||||
// tabs
|
||||
const tabs = ref<TabItem[]>([
|
||||
{ id: 'home', title: '首页', path: '/pages/mall/admin/homePage/index' }
|
||||
])
|
||||
const activeTabId = ref('home')
|
||||
|
||||
// 每次 layout 渲染时,同步高亮(靠 currentPage)
|
||||
const syncActiveByCurrentPage = () => {
|
||||
const r = findActiveByCurrentPage(menuList.value, props.currentPage)
|
||||
activeMenuId.value = r.activeMenuId
|
||||
activeSubId.value = r.activeSubId
|
||||
}
|
||||
|
||||
// 同步 tabs(靠当前 route)
|
||||
const syncTabsByRoute = () => {
|
||||
const path = getCurrentRoutePath()
|
||||
if (!path) return
|
||||
|
||||
const tab = makeTabFromPath(menuList.value, path)
|
||||
tabs.value = upsertTab(tabs.value, tab)
|
||||
activeTabId.value = tab.id
|
||||
}
|
||||
|
||||
// 初始化同步(setup 执行一次)
|
||||
syncActiveByCurrentPage()
|
||||
syncTabsByRoute()
|
||||
|
||||
// computed
|
||||
const activeMenu = computed(() => menuList.value.find(m => m.id === activeMenuId.value))
|
||||
const activeMenuTitle = computed(() => activeMenu.value?.title || '商城后台')
|
||||
const activeGroups = computed(() => activeMenu.value?.groups || [])
|
||||
|
||||
const breadcrumb = computed(() => {
|
||||
// “一级 / 二级”
|
||||
let subTitle = ''
|
||||
const groups = activeGroups.value
|
||||
for (const g of groups) {
|
||||
const hit = g.children.find(c => c.id === activeSubId.value)
|
||||
if (hit) { subTitle = hit.title; break }
|
||||
}
|
||||
return subTitle ? `${activeMenuTitle.value} / ${subTitle}` : `${activeMenuTitle.value}`
|
||||
})
|
||||
|
||||
// handlers
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
const go = (url: string) => {
|
||||
// 你明确要用 navigateTo:页面栈会增长(这是正常行为):contentReference[oaicite:4]{index=4}
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
const onMenuClick = (menuId: string) => {
|
||||
const m = menuList.value.find(x => x.id === menuId)
|
||||
if (!m) return
|
||||
activeMenuId.value = m.id
|
||||
// 默认激活该菜单第一个子项
|
||||
const g0 = (m.groups && m.groups.length > 0) ? m.groups[0] : null
|
||||
const c0 = g0 && g0.children.length > 0 ? g0.children[0] : null
|
||||
activeSubId.value = c0 ? c0.id : ''
|
||||
go(m.path)
|
||||
}
|
||||
|
||||
const onSubClick = (child: any) => {
|
||||
activeSubId.value = child.id
|
||||
go(child.path)
|
||||
}
|
||||
|
||||
const onTabClick = (tab: TabItem) => {
|
||||
activeTabId.value = tab.id
|
||||
go(tab.path)
|
||||
}
|
||||
|
||||
const onTabClose = (tabId: string) => {
|
||||
// 关闭当前 tab:删除后回到最后一个 tab
|
||||
const wasActive = activeTabId.value === tabId
|
||||
tabs.value = removeTab(tabs.value, tabId)
|
||||
if (wasActive) {
|
||||
const last = tabs.value[tabs.value.length - 1]
|
||||
if (last) {
|
||||
activeTabId.value = last.id
|
||||
go(last.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onSearch = () => uni.showToast({ title: '搜索', icon: 'none' })
|
||||
const onRefresh = () => uni.showToast({ title: '刷新', icon: 'none' })
|
||||
const onNotify = () => uni.showToast({ title: '通知', icon: 'none' })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.layout-root{
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background:#f3f4f6;
|
||||
}
|
||||
|
||||
/* 右侧主区域:左边距由 template 动态控制(96 或 336) */
|
||||
.main{
|
||||
min-height: 100vh;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 展示区 */
|
||||
.content{
|
||||
height: calc(100vh - 56px - 44px);
|
||||
}
|
||||
.content-inner{
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
37
layouts/admin/types.uts
Normal file
@@ -0,0 +1,37 @@
|
||||
// 统一类型定义文件,避免重复定义冲突
|
||||
|
||||
export type UserInfo = {
|
||||
nickname: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export type TagItem = {
|
||||
path: string
|
||||
title: string
|
||||
isAffix?: boolean
|
||||
}
|
||||
|
||||
export type MenuChild = {
|
||||
id: string
|
||||
title: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type MenuGroup = {
|
||||
title: string
|
||||
children: MenuChild[]
|
||||
}
|
||||
|
||||
export type MenuItem = {
|
||||
id: string
|
||||
title: string
|
||||
icon: string // 你的 svg 路径
|
||||
path?: string
|
||||
groups?: MenuGroup[]
|
||||
}
|
||||
|
||||
export type TabItem = {
|
||||
id: string
|
||||
title: string
|
||||
path: string
|
||||
}
|
||||
691
layouts/admin/utils/echarts-config.uts
Normal file
@@ -0,0 +1,691 @@
|
||||
// ECharts 配置工具 - CRMEB 风格图表配置
|
||||
// 订单统计图表配置(柱状图 + 折线图)
|
||||
export const getOrderChartOption = (period: string = '30days') => {
|
||||
const periods = {
|
||||
'30days': { label: '30天', days: 30 },
|
||||
'week': { label: '本周', days: 7 },
|
||||
'month': { label: '本月', days: 30 },
|
||||
'year': { label: '本年', days: 365 }
|
||||
}
|
||||
|
||||
const periodConfig = periods[period as keyof typeof periods] || periods['30days']
|
||||
|
||||
return {
|
||||
title: {
|
||||
text: `订单统计 (${periodConfig.label})`,
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['订单金额', '订单数量'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(periodConfig.days),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '订单金额',
|
||||
position: 'left',
|
||||
axisLabel: {
|
||||
formatter: '¥{value}',
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '订单数量',
|
||||
position: 'right',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '订单金额',
|
||||
type: 'bar',
|
||||
data: generateAmountData(periodConfig.days),
|
||||
barWidth: '40%',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 1, color: '#36cfc9' }
|
||||
]),
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#40a9ff' },
|
||||
{ offset: 1, color: '#5cdbd3' }
|
||||
])
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '订单数量',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: generateCountData(periodConfig.days),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: '#52c41a',
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#52c41a',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{ offset: 0, color: 'rgba(82, 196, 26, 0.1)' },
|
||||
{ offset: 1, color: 'rgba(82, 196, 26, 0.3)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户趋势图表配置
|
||||
export const getUserTrendOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户增长趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增用户'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(30),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '用户数量',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
data: generateUserTrendData(30),
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
color: '#1890ff',
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890ff',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{ offset: 0, color: 'rgba(24, 144, 255, 0.1)' },
|
||||
{ offset: 1, color: 'rgba(24, 144, 255, 0.3)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户构成饼图配置
|
||||
export const getUserCompositionOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户来源构成',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c}% ({d}%)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
top: 'center',
|
||||
itemGap: 16,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
},
|
||||
data: ['自然流量', '搜索引擎', '社交媒体', '广告投放', '其他']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '用户来源',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['60%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
formatter: '{b}\n{c}%'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 35,
|
||||
name: '自然流量',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1890ff' },
|
||||
{ offset: 1, color: '#36cfc9' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 28,
|
||||
name: '搜索引擎',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#52c41a' },
|
||||
{ offset: 1, color: '#73d13d' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 20,
|
||||
name: '社交媒体',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#faad14' },
|
||||
{ offset: 1, color: '#ffc53d' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 12,
|
||||
name: '广告投放',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 1, color: '#ff7875' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
name: '其他',
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#722ed1' },
|
||||
{ offset: 1, color: '#b37feb' }
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 用户统计多折线图配置
|
||||
export const getUserStatisticsOption = () => {
|
||||
return {
|
||||
title: {
|
||||
text: '用户数据趋势分析',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: '#262626'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 12
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增用户', '访客数', '浏览量', '成交用户', '付费会员'],
|
||||
top: 30,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#666666'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: generateDateLabels(30, 7), // 30天的数据,每7天一个标签
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '数量',
|
||||
axisLabel: {
|
||||
color: '#999999',
|
||||
fontSize: 12
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('newUsers', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#1890ff',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890ff',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '访客数',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('visitors', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#52c41a',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#52c41a',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '浏览量',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('pageViews', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#faad14',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#faad14',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '成交用户',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('conversions', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#f5222d',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#f5222d',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
},
|
||||
{
|
||||
name: '付费会员',
|
||||
type: 'line',
|
||||
data: generateUserStatisticsData('vipUsers', 7),
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#722ed1',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#722ed1',
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 2
|
||||
},
|
||||
smooth: true
|
||||
}
|
||||
],
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:生成日期标签
|
||||
function generateDateLabels(days: number, step: number = 1): string[] {
|
||||
const labels = []
|
||||
const today = new Date()
|
||||
|
||||
for (let i = days - 1; i >= 0; i -= step) {
|
||||
const date = new Date(today)
|
||||
date.setDate(date.getDate() - i)
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
labels.push(`${month}-${day}`)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
// 辅助函数:生成订单金额数据
|
||||
function generateAmountData(days: number): number[] {
|
||||
const data = []
|
||||
for (let i = 0; i < days; i++) {
|
||||
// 生成12000-25000之间的随机金额
|
||||
data.push(Math.floor(Math.random() * 13000) + 12000)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成订单数量数据
|
||||
function generateCountData(days: number): number[] {
|
||||
const data = []
|
||||
for (let i = 0; i < days; i++) {
|
||||
// 生成50-150之间的随机数量
|
||||
data.push(Math.floor(Math.random() * 100) + 50)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成用户趋势数据
|
||||
function generateUserTrendData(days: number): number[] {
|
||||
const data = []
|
||||
let base = 100
|
||||
|
||||
for (let i = 0; i < days; i++) {
|
||||
base += Math.floor(Math.random() * 20) - 5 // -5到+15的随机变化
|
||||
base = Math.max(50, base) // 最低50
|
||||
data.push(base)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// 辅助函数:生成用户统计数据
|
||||
function generateUserStatisticsData(type: string, points: number): number[] {
|
||||
const data = []
|
||||
const baseValues = {
|
||||
newUsers: 120,
|
||||
visitors: 450,
|
||||
pageViews: 680,
|
||||
conversions: 45,
|
||||
vipUsers: 12
|
||||
}
|
||||
|
||||
let base = baseValues[type as keyof typeof baseValues] || 100
|
||||
|
||||
for (let i = 0; i < points; i++) {
|
||||
const variation = type === 'vipUsers' ? 0.3 : 0.2 // 付费会员变化小一些
|
||||
base += Math.floor(Math.random() * (base * variation * 2)) - (base * variation)
|
||||
base = Math.max(0, base)
|
||||
data.push(Math.floor(base))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Mock API 数据接口
|
||||
export const mockApi = {
|
||||
// 获取订单统计数据
|
||||
getOrderStats: (period: string = '30days') => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
period,
|
||||
amountData: generateAmountData(30),
|
||||
countData: generateCountData(30),
|
||||
dateLabels: generateDateLabels(30)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户趋势数据
|
||||
getUserTrend: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
data: generateUserTrendData(30),
|
||||
dateLabels: generateDateLabels(30)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户构成数据
|
||||
getUserComposition: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{ name: '自然流量', value: 35, color: '#1890ff' },
|
||||
{ name: '搜索引擎', value: 28, color: '#52c41a' },
|
||||
{ name: '社交媒体', value: 20, color: '#faad14' },
|
||||
{ name: '广告投放', value: 12, color: '#f5222d' },
|
||||
{ name: '其他', value: 5, color: '#722ed1' }
|
||||
])
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户统计数据
|
||||
getUserStatistics: () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
newUsers: generateUserStatisticsData('newUsers', 7),
|
||||
visitors: generateUserStatisticsData('visitors', 7),
|
||||
pageViews: generateUserStatisticsData('pageViews', 7),
|
||||
conversions: generateUserStatisticsData('conversions', 7),
|
||||
vipUsers: generateUserStatisticsData('vipUsers', 7),
|
||||
dateLabels: generateDateLabels(30, 7)
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出所有配置
|
||||
export const chartConfigs = {
|
||||
orderChart: getOrderChartOption,
|
||||
userTrendChart: getUserTrendOption,
|
||||
userCompositionChart: getUserCompositionOption,
|
||||
userStatisticsChart: getUserStatisticsOption,
|
||||
mockApi
|
||||
}
|
||||
101
layouts/admin/utils/menu.uts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { MenuItem } from '../types.uts'
|
||||
|
||||
export const menuList: MenuItem[] = [
|
||||
{
|
||||
id: 'home',
|
||||
title: '首页',
|
||||
icon: '/static/homepage.svg',
|
||||
path: '/pages/mall/admin/homePage/index',
|
||||
groups: []
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户',
|
||||
icon: '/static/user.svg',
|
||||
path: '/pages/mall/admin/user-management',
|
||||
groups: [
|
||||
{
|
||||
title: '用户管理',
|
||||
children: [
|
||||
{ id: 'user-list', title: '用户列表', path: '/pages/mall/admin/user-management' },
|
||||
{ id: 'user-add', title: '添加用户', path: '/pages/mall/admin/user-management?action=add' },
|
||||
{ id: 'user-statistics', title: '用户统计 ', path: '/pages/mall/admin/user-statistics' },
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
title: '订单',
|
||||
icon: '/static/order.svg',
|
||||
path: '/pages/mall/admin/order-management',
|
||||
groups: [
|
||||
{
|
||||
title: '订单管理',
|
||||
children: [
|
||||
{ id: 'order-list', title: '订单列表', path: '/pages/mall/admin/order-management' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'product',
|
||||
title: '商品',
|
||||
icon: '/static/shopping.svg',
|
||||
path: '/pages/mall/admin/product-management',
|
||||
groups: [
|
||||
{
|
||||
title: '商品管理',
|
||||
children: [
|
||||
{ id: 'product-list', title: '商品列表', path: '/pages/mall/admin/product-management' },
|
||||
{ id: 'product-add', title: '添加商品', path: '/pages/mall/admin/product-management?action=add' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'marketing',
|
||||
title: '营销',
|
||||
icon: '/static/finance.svg',
|
||||
path: '/pages/mall/admin/marketing-management',
|
||||
groups: [
|
||||
{
|
||||
title: '优惠券活动',
|
||||
children: [
|
||||
{ id: 'coupon-list', title: '优惠券列表', path: '/pages/mall/admin/marketing/coupon/list' }
|
||||
{ id: 'coupon-receive', title: '领取情况', path: '/pages/mall/admin/marketing/coupon/receive' }
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
children: [
|
||||
{ id: 'points', title: '积分管理', path: '/pages/mall/admin/marketing/points/index' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '签到',
|
||||
children: [
|
||||
{ id: 'rule', title: '签到规则', path: '/pages/mall/admin/marketing/signin/rule' }
|
||||
{ id: 'record', title: '记录', path: '/pages/mall/admin/marketing/signin/record' }
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
title: '设置',
|
||||
icon: '/static/setting.svg',
|
||||
path: '/pages/mall/admin/system-settings',
|
||||
groups: [
|
||||
{
|
||||
title: '系统设置',
|
||||
children: [
|
||||
{ id: 'basic', title: '基本设置', path: '/pages/mall/admin/system-settings' },
|
||||
{ id: 'security', title: '安全设置', path: '/pages/mall/admin/system-settings?tab=security' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
34
layouts/admin/utils/nav.uts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { MenuItem } from '../types.uts'
|
||||
|
||||
export function findActiveByCurrentPage(menuList: MenuItem[], currentPage: string) {
|
||||
// currentPage 既可能是顶级菜单 id,也可能是子页面 id(如 user-list)
|
||||
// 返回:activeMenuId / activeSubId / activeGroupTitle
|
||||
for (const m of menuList) {
|
||||
if (m.id === currentPage) {
|
||||
return { activeMenuId: m.id, activeSubId: '', activeGroupTitle: '' }
|
||||
}
|
||||
const groups = m.groups || []
|
||||
for (const g of groups) {
|
||||
for (const c of g.children) {
|
||||
if (c.id === currentPage) {
|
||||
return { activeMenuId: m.id, activeSubId: c.id, activeGroupTitle: g.title }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { activeMenuId: menuList[0]?.id || 'home', activeSubId: '', activeGroupTitle: '' }
|
||||
}
|
||||
|
||||
export function getCurrentRoutePath(): string {
|
||||
// 使用页面栈获取当前路由(uni-app标准能力)
|
||||
// getCurrentPages 用于获取当前页面栈实例 :contentReference[oaicite:2]{index=2}
|
||||
const pages = getCurrentPages()
|
||||
const last: any = pages[pages.length - 1]
|
||||
// #ifdef H5
|
||||
return last?.route ? `/${last.route}` : ''
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// 小程序/App 可能是 route / $page?.fullPath 形式,按你项目实际字段微调
|
||||
return last?.route ? `/${last.route}` : (last?.$page?.fullPath || '')
|
||||
// #endif
|
||||
}
|
||||
33
layouts/admin/utils/tabs.uts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { TabItem, MenuItem } from '../types.uts'
|
||||
|
||||
export function makeTabFromPath(menuList: MenuItem[], path: string): TabItem {
|
||||
// path 可能带 query;用于 tab 的 id 也要稳定
|
||||
const pure = path.split('?')[0]
|
||||
|
||||
// 先找子页面
|
||||
for (const m of menuList) {
|
||||
const groups = m.groups || []
|
||||
for (const g of groups) {
|
||||
for (const c of g.children) {
|
||||
if (c.path.split('?')[0] === pure) {
|
||||
return { id: c.id, title: c.title, path: c.path }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m.path.split('?')[0] === pure) {
|
||||
return { id: m.id, title: m.title, path: m.path }
|
||||
}
|
||||
}
|
||||
// 找不到就兜底
|
||||
return { id: pure, title: '页面', path }
|
||||
}
|
||||
|
||||
export function upsertTab(tabs: TabItem[], tab: TabItem): TabItem[] {
|
||||
const idx = tabs.findIndex(t => t.id === tab.id)
|
||||
if (idx >= 0) return tabs
|
||||
return [...tabs, tab]
|
||||
}
|
||||
|
||||
export function removeTab(tabs: TabItem[], tabId: string): TabItem[] {
|
||||
return tabs.filter(t => t.id !== tabId)
|
||||
}
|
||||
12
main.uts
@@ -1,14 +1,14 @@
|
||||
// 简化的main.uts,移除i18n依赖
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.uvue'
|
||||
import i18n from '@/uni_modules/i18n/index.uts'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// 注册 i18n 全局属性,使组件可以使用 $t 方法
|
||||
app.config.globalProperties.$t = (key: string, values?: any, locale?: string): string => {
|
||||
return i18n.global.t(key, values, locale)
|
||||
|
||||
// 简化的$t方法
|
||||
app.config.globalProperties.$t = (key: string): string => {
|
||||
return key // 直接返回key,不进行翻译
|
||||
}
|
||||
|
||||
|
||||
return { app }
|
||||
}
|
||||
|
||||
9
package-lock.json
generated
@@ -6,8 +6,17 @@
|
||||
"": {
|
||||
"dependencies": {
|
||||
"echarts": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.29"
|
||||
}
|
||||
},
|
||||
"node_modules/@dcloudio/types": {
|
||||
"version": "3.4.29",
|
||||
"resolved": "https://registry.npmjs.org/@dcloudio/types/-/types-3.4.29.tgz",
|
||||
"integrity": "sha512-7uBInqqYLoLmQMqlzW4FsYCEHTUgTkrtZVsFGgQnJT7ZCA12U9y0ovrqAM1ZWkLruHYfOS7xIqO77Who6UBLJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"echarts": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.29"
|
||||
}
|
||||
}
|
||||
|
||||
10
pages-simple.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/minimal",
|
||||
"style": {
|
||||
"navigationBarTitleText": "最小测试"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
92
pages.json
@@ -1,70 +1,7 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商城首页",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/boot",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/forgot-password",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/center",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/terms",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议与隐私政策"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/index",
|
||||
"path": "pages/mall/admin/homePage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "管理后台",
|
||||
"navigationStyle": "custom"
|
||||
@@ -96,37 +33,42 @@
|
||||
"root": "pages/mall",
|
||||
"pages": [
|
||||
{
|
||||
"path": "consumer/product-detail",
|
||||
"path": "admin/user-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情"
|
||||
"navigationBarTitleText": "用户管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "consumer/order-detail",
|
||||
"path": "admin/product-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情"
|
||||
"navigationBarTitleText": "商品管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "consumer/profile",
|
||||
"path": "admin/order-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
"navigationBarTitleText": "订单管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "consumer/subscription/plan-list",
|
||||
"path": "admin/finance-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "软件订阅"
|
||||
"navigationBarTitleText": "财务管理",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "consumer/subscription/plan-detail",
|
||||
"path": "admin/user-statistics",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订阅详情"
|
||||
"navigationBarTitleText": "用户统计",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "consumer/subscription/subscribe-checkout",
|
||||
"path": "admin/system-settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "确认订阅"
|
||||
}
|
||||
|
||||
63
pages/mall/admin/activity-log.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="activity-log">
|
||||
<view class="page-header">
|
||||
<text class="page-title">活动日志</text>
|
||||
<text class="page-subtitle">查看系统活动和操作日志</text>
|
||||
</view>
|
||||
|
||||
<view class="log-content">
|
||||
<text class="coming-soon">活动日志功能正在开发中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 统一的导航方法
|
||||
const go = (url: string) => {
|
||||
// 1) 目标页面必须是非 tabBar 页面
|
||||
// 2) 必须在 pages.json / subPackages 注册
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.activity-log {
|
||||
padding: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.log-content {
|
||||
background-color: #fff;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.coming-soon {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
pages/mall/admin/complaints.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="complaints">
|
||||
<view class="page-header">
|
||||
<text class="page-title">投诉处理</text>
|
||||
<text class="page-subtitle">处理用户投诉和反馈</text>
|
||||
</view>
|
||||
|
||||
<view class="complaints-content">
|
||||
<text class="coming-soon">投诉处理功能正在开发中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 统一的导航方法
|
||||
const go = (url: string) => {
|
||||
// 1) 目标页面必须是非 tabBar 页面
|
||||
// 2) 必须在 pages.json / subPackages 注册
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.complaints {
|
||||
padding: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.complaints-content {
|
||||
background-color: #fff;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.coming-soon {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
pages/mall/admin/delivery-management.uvue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<text>配送管理 - 占位页</text>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
export default {}
|
||||
</script>
|
||||
<style>
|
||||
.page { padding: 30rpx; }
|
||||
</style>
|
||||
1583
pages/mall/admin/finance-management.uvue
Normal file
187
pages/mall/admin/homePage/components/KpiMiniCard.uvue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="kpi-card">
|
||||
<!-- Header -->
|
||||
<view class="kpi-header">
|
||||
<text class="kpi-title">{{ title }}</text>
|
||||
|
||||
<view v-if="tagText" class="kpi-tag">
|
||||
<text class="kpi-tag-text">{{ tagText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 可选:你想在右上角塞额外按钮/图标 -->
|
||||
<slot name="headerRight"></slot>
|
||||
</view>
|
||||
|
||||
<!-- Body -->
|
||||
<view class="kpi-body">
|
||||
<text class="kpi-main-value">{{ valuePrefix }}{{ valueText }}</text>
|
||||
|
||||
<!-- 中间“昨日 / 日环比”行(可完全替换) -->
|
||||
<view v-if="metaLeft || metaRight" class="kpi-meta">
|
||||
<text v-if="metaLeft" class="kpi-meta-text">{{ metaLeft }}</text>
|
||||
|
||||
<view v-if="metaRight" class="kpi-meta-right">
|
||||
<text class="kpi-meta-text">{{ metaRight }}</text>
|
||||
|
||||
<text
|
||||
v-if="trend !== 'none'"
|
||||
class="kpi-trend-arrow"
|
||||
:class="trendClass"
|
||||
>
|
||||
{{ trendArrow }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 可选:完全自定义这行 -->
|
||||
<slot name="meta"></slot>
|
||||
</view>
|
||||
|
||||
<view class="kpi-divider"></view>
|
||||
|
||||
<!-- 底部一行:左文案 + 右数值 -->
|
||||
<view class="kpi-footer">
|
||||
<text class="kpi-footer-left">{{ footerLeftText }}</text>
|
||||
<text class="kpi-footer-right">{{ footerRightText }}</text>
|
||||
|
||||
<!-- 可选:完全自定义 footer -->
|
||||
<slot name="footer"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
// Header
|
||||
title: string
|
||||
tagText?: string
|
||||
|
||||
// Body main
|
||||
valueText: string
|
||||
valuePrefix?: string // 例如 "¥"
|
||||
|
||||
// Meta line (可替换)
|
||||
metaLeft?: string // 例如 "昨日 4"
|
||||
metaRight?: string // 例如 "日环比 0%"
|
||||
trend?: 'up' | 'down' | 'flat' | 'none' // none = 不显示箭头
|
||||
|
||||
// Footer
|
||||
footerLeftText: string // 例如 "本月订单量"
|
||||
footerRightText: string // 例如 "181单"
|
||||
}>(), {
|
||||
tagText: '今日',
|
||||
valuePrefix: '',
|
||||
metaLeft: '',
|
||||
metaRight: '',
|
||||
trend: 'none'
|
||||
})
|
||||
|
||||
const trendArrow = computed((): string => {
|
||||
if (props.trend === 'up') return '▲'
|
||||
if (props.trend === 'down') return '▼'
|
||||
return '•'
|
||||
})
|
||||
|
||||
const trendClass = computed((): string => {
|
||||
if (props.trend === 'up') return 'is-up'
|
||||
if (props.trend === 'down') return 'is-down'
|
||||
return 'is-flat'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.kpi-card{
|
||||
background-color:#ffffff;
|
||||
border:1px solid #ebeef5;
|
||||
border-radius:6px;
|
||||
padding:16px;
|
||||
box-shadow:0 2px 12px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.kpi-header{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap:12px;
|
||||
|
||||
.kpi-title{
|
||||
font-size:14px;
|
||||
color:#303133;
|
||||
font-weight:600;
|
||||
}
|
||||
.kpi-tag{
|
||||
padding:2px 8px;
|
||||
border-radius:4px;
|
||||
border:1px solid #e1f3d8;
|
||||
background:#f0f9eb;
|
||||
}
|
||||
.kpi-tag-text{
|
||||
font-size:12px;
|
||||
color:#67c23a;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Body */
|
||||
.kpi-body{
|
||||
margin-top:10px;
|
||||
.kpi-main-value{
|
||||
font-size:32px;
|
||||
font-weight:600;
|
||||
color:#303133;
|
||||
line-height:40px;
|
||||
}
|
||||
|
||||
/* “昨日 / 日环比” */
|
||||
.kpi-meta{
|
||||
margin-top:8px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:flex-start;
|
||||
gap:12px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.kpi-meta-text{
|
||||
font-size:12px;
|
||||
color:#909399;
|
||||
}
|
||||
.kpi-meta-right{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:6px;
|
||||
}
|
||||
.kpi-trend-arrow{
|
||||
font-size:12px;
|
||||
}
|
||||
.kpi-trend-arrow.is-up{ color:#f56c6c; }
|
||||
.kpi-trend-arrow.is-down{ color:#67c23a; }
|
||||
.kpi-trend-arrow.is-flat{ color:#909399; }
|
||||
|
||||
.kpi-divider{
|
||||
height:1px;
|
||||
background:#ebeef5;
|
||||
margin:12px 0;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.kpi-footer{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap:12px;
|
||||
}
|
||||
.kpi-footer-left{
|
||||
font-size:12px;
|
||||
color:#909399;
|
||||
}
|
||||
.kpi-footer-right{
|
||||
font-size:12px;
|
||||
color:#909399;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
494
pages/mall/admin/homePage/index.uvue
Normal file
@@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<AdminLayout current-page="dashboard">
|
||||
<view class="dashboard-page">
|
||||
<!-- 第一行:4 个 KPI 卡片 -->
|
||||
<view class="kpi-cards-row">
|
||||
<KpiMiniCard
|
||||
title="销售额"
|
||||
tagText="今日"
|
||||
valuePrefix="¥"
|
||||
:valueText="String(formatNumber(kpiData.sales.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.sales.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.sales.change)}%`"
|
||||
:trend="kpiData.sales.change > 0 ? 'up' : (kpiData.sales.change < 0 ? 'down' : 'flat')"
|
||||
:footerLeftText="'本月销售额'"
|
||||
:footerRightText="`¥${formatNumber(kpiData.sales.monthTotal)}`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="用户访问量"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.visits.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.visits.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.visits.change)}%`"
|
||||
:trend="kpiData.visits.change > 0 ? 'up' : (kpiData.visits.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月访问量"
|
||||
:footerRightText="`${formatNumber(kpiData.visits.monthTotal)}Pv`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="订单量"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.orders.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.orders.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.orders.change)}%`"
|
||||
:trend="kpiData.orders.change > 0 ? 'up' : (kpiData.orders.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月订单量"
|
||||
:footerRightText="`${formatNumber(kpiData.orders.monthTotal)}单`"
|
||||
/>
|
||||
|
||||
<KpiMiniCard
|
||||
title="新增用户"
|
||||
tagText="今日"
|
||||
:valueText="String(formatNumber(kpiData.users.today))"
|
||||
:metaLeft="`昨日 ${formatNumber(kpiData.users.yesterday)}`"
|
||||
:metaRight="`日环比 ${Math.abs(kpiData.users.change)}%`"
|
||||
:trend="kpiData.users.change > 0 ? 'up' : (kpiData.users.change < 0 ? 'down' : 'flat')"
|
||||
footerLeftText="本月新增用户"
|
||||
:footerRightText="`${formatNumber(kpiData.users.monthTotal)}人`"
|
||||
/>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 第二行:订单统计图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">订单</text>
|
||||
<view class="chart-controls">
|
||||
<button
|
||||
v-for="period in chartPeriods"
|
||||
:key="period.value"
|
||||
class="period-btn"
|
||||
:class="{ 'active': selectedPeriod === period.value }"
|
||||
@click="changePeriod(period.value)"
|
||||
>
|
||||
{{ period.label }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<!-- ECharts 组合图容器 -->
|
||||
<view class="echarts-container">
|
||||
<text class="chart-placeholder">📊 ECharts 组合图:柱状图(订单金额) + 折线图(订单数量)</text>
|
||||
<text class="chart-desc">时间粒度:{{ selectedPeriodLabel }}</text>
|
||||
</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>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
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 = ref('30天')
|
||||
|
||||
const chartPeriods = [
|
||||
{ label: '30天', value: '30days' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '年', value: 'year' }
|
||||
]
|
||||
|
||||
// 方法
|
||||
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;
|
||||
}
|
||||
|
||||
/* ===== 图表区域 ===== */
|
||||
.chart-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.charts-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.chart-col {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ===== Admin Card 组件样式 ===== */
|
||||
.admin-card {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.admin-card-header {
|
||||
padding: 24px 24px 0 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.admin-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.admin-card-body {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== 图表控件 ===== */
|
||||
.chart-controls {
|
||||
display: flex;
|
||||
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.period-btn {
|
||||
padding: 6px 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.period-btn:hover {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.period-btn.active {
|
||||
background-color: #1890ff;
|
||||
color: #ffffff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
/* ===== ECharts 容器 ===== */
|
||||
.echarts-container {
|
||||
height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.chart-desc {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
.kpi-cards-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.kpi-cards-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.charts-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -1,847 +0,0 @@
|
||||
<!-- 后台管理端首页 - UTS Android 兼容 -->
|
||||
<template>
|
||||
<view class="admin-container">
|
||||
<!-- 头部导航 -->
|
||||
<view class="header">
|
||||
<view class="header-left">
|
||||
<text class="app-title">商城管理后台</text>
|
||||
<text class="welcome-text">欢迎回来,{{ adminInfo.nickname }}</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<text class="notification-btn" @click="goToNotifications">🔔</text>
|
||||
<text class="profile-btn" @click="goToProfile">👤</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心指标概览 -->
|
||||
<view class="metrics-section">
|
||||
<text class="section-title">核心指标</text>
|
||||
<view class="metrics-grid">
|
||||
<view class="metric-card">
|
||||
<text class="metric-value">¥{{ platformStats.total_gmv }}</text>
|
||||
<text class="metric-label">总GMV</text>
|
||||
<text class="metric-change positive">+{{ platformStats.gmv_growth }}%</text>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<text class="metric-value">{{ platformStats.total_orders }}</text>
|
||||
<text class="metric-label">总订单数</text>
|
||||
<text class="metric-change positive">+{{ platformStats.order_growth }}%</text>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<text class="metric-value">{{ platformStats.total_users }}</text>
|
||||
<text class="metric-label">注册用户</text>
|
||||
<text class="metric-change positive">+{{ platformStats.user_growth }}%</text>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<text class="metric-value">{{ platformStats.total_merchants }}</text>
|
||||
<text class="metric-label">入驻商家</text>
|
||||
<text class="metric-change positive">+{{ platformStats.merchant_growth }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日数据 -->
|
||||
<view class="today-section">
|
||||
<text class="section-title">今日数据</text>
|
||||
<view class="today-grid">
|
||||
<view class="today-item">
|
||||
<text class="today-value">¥{{ todayStats.sales }}</text>
|
||||
<text class="today-label">销售额</text>
|
||||
</view>
|
||||
<view class="today-item">
|
||||
<text class="today-value">{{ todayStats.orders }}</text>
|
||||
<text class="today-label">订单数</text>
|
||||
</view>
|
||||
<view class="today-item">
|
||||
<text class="today-value">{{ todayStats.new_users }}</text>
|
||||
<text class="today-label">新增用户</text>
|
||||
</view>
|
||||
<view class="today-item">
|
||||
<text class="today-value">{{ todayStats.active_users }}</text>
|
||||
<text class="today-label">活跃用户</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 待处理事项 -->
|
||||
<view class="pending-section">
|
||||
<text class="section-title">待处理事项</text>
|
||||
<view class="pending-list">
|
||||
<view class="pending-item urgent" @click="goToMerchantReview">
|
||||
<text class="pending-icon">🏪</text>
|
||||
<view class="pending-content">
|
||||
<text class="pending-title">商家入驻审核</text>
|
||||
<text class="pending-subtitle">{{ pendingCounts.merchant_review }}个商家待审核</text>
|
||||
</view>
|
||||
<text class="pending-count">{{ pendingCounts.merchant_review }}</text>
|
||||
</view>
|
||||
|
||||
<view class="pending-item" @click="goToProductReview">
|
||||
<text class="pending-icon">📦</text>
|
||||
<view class="pending-content">
|
||||
<text class="pending-title">商品审核</text>
|
||||
<text class="pending-subtitle">{{ pendingCounts.product_review }}个商品待审核</text>
|
||||
</view>
|
||||
<text class="pending-count">{{ pendingCounts.product_review }}</text>
|
||||
</view>
|
||||
|
||||
<view class="pending-item" @click="goToRefundReview">
|
||||
<text class="pending-icon">💰</text>
|
||||
<view class="pending-content">
|
||||
<text class="pending-title">退款处理</text>
|
||||
<text class="pending-subtitle">{{ pendingCounts.refund_review }}个退款申请</text>
|
||||
</view>
|
||||
<text class="pending-count">{{ pendingCounts.refund_review }}</text>
|
||||
</view>
|
||||
|
||||
<view class="pending-item" @click="goToComplaints">
|
||||
<text class="pending-icon">⚠️</text>
|
||||
<view class="pending-content">
|
||||
<text class="pending-title">投诉处理</text>
|
||||
<text class="pending-subtitle">{{ pendingCounts.complaints }}个投诉待处理</text>
|
||||
</view>
|
||||
<text class="pending-count">{{ pendingCounts.complaints }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 实时监控 -->
|
||||
<view class="monitor-section">
|
||||
<text class="section-title">实时监控</text>
|
||||
<view class="monitor-grid">
|
||||
<view class="monitor-card">
|
||||
<text class="monitor-title">在线用户</text>
|
||||
<text class="monitor-value">{{ realTimeStats.online_users }}</text>
|
||||
<text class="monitor-unit">人</text>
|
||||
</view>
|
||||
<view class="monitor-card">
|
||||
<text class="monitor-title">活跃配送员</text>
|
||||
<text class="monitor-value">{{ realTimeStats.active_drivers }}</text>
|
||||
<text class="monitor-unit">人</text>
|
||||
</view>
|
||||
<view class="monitor-card">
|
||||
<text class="monitor-title">配送中订单</text>
|
||||
<text class="monitor-value">{{ realTimeStats.delivering_orders }}</text>
|
||||
<text class="monitor-unit">单</text>
|
||||
</view>
|
||||
<view class="monitor-card">
|
||||
<text class="monitor-title">系统负载</text>
|
||||
<text class="monitor-value">{{ realTimeStats.system_load }}</text>
|
||||
<text class="monitor-unit">%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷管理功能 -->
|
||||
<view class="shortcuts-section">
|
||||
<text class="section-title">快捷管理</text>
|
||||
<view class="shortcuts-grid">
|
||||
<view class="shortcut-item" @click="goToUserManagement">
|
||||
<text class="shortcut-icon">👥</text>
|
||||
<text class="shortcut-text">用户管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToMerchantManagement">
|
||||
<text class="shortcut-icon">🏪</text>
|
||||
<text class="shortcut-text">商家管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToProductManagement">
|
||||
<text class="shortcut-icon">📦</text>
|
||||
<text class="shortcut-text">商品管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToOrderManagement">
|
||||
<text class="shortcut-icon">📋</text>
|
||||
<text class="shortcut-text">订单管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToCouponManagement">
|
||||
<text class="shortcut-icon">🎫</text>
|
||||
<text class="shortcut-text">优惠券管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToDeliveryManagement">
|
||||
<text class="shortcut-icon">🚚</text>
|
||||
<text class="shortcut-text">配送管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToFinanceManagement">
|
||||
<text class="shortcut-icon">💳</text>
|
||||
<text class="shortcut-text">财务管理</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToSystemSettings">
|
||||
<text class="shortcut-icon">⚙️</text>
|
||||
<text class="shortcut-text">系统设置</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToAdminUserSubscriptions">
|
||||
<text class="shortcut-icon">📑</text>
|
||||
<text class="shortcut-text">用户订阅</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="goToSubscriptionPlans">
|
||||
<text class="shortcut-icon">🧾</text>
|
||||
<text class="shortcut-text">订阅方案</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最新动态 -->
|
||||
<view class="activities-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">最新动态</text>
|
||||
<text class="section-more" @click="goToActivityLog">查看全部</text>
|
||||
</view>
|
||||
<view class="activities-list">
|
||||
<view v-for="activity in recentActivities" :key="activity.id" class="activity-item">
|
||||
<text class="activity-icon">{{ getActivityIcon(activity.type) }}</text>
|
||||
<view class="activity-content">
|
||||
<text class="activity-text">{{ activity.description }}</text>
|
||||
<text class="activity-time">{{ formatTime(activity.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
type AdminInfoType = {
|
||||
id: string
|
||||
nickname: string
|
||||
role: string
|
||||
}
|
||||
|
||||
type PlatformStatsType = {
|
||||
total_gmv: string
|
||||
gmv_growth: number
|
||||
total_orders: number
|
||||
order_growth: number
|
||||
total_users: number
|
||||
user_growth: number
|
||||
total_merchants: number
|
||||
merchant_growth: number
|
||||
}
|
||||
|
||||
type TodayStatsType = {
|
||||
sales: string
|
||||
orders: number
|
||||
new_users: number
|
||||
active_users: number
|
||||
}
|
||||
|
||||
type PendingCountsType = {
|
||||
merchant_review: number
|
||||
product_review: number
|
||||
refund_review: number
|
||||
complaints: number
|
||||
}
|
||||
|
||||
type RealTimeStatsType = {
|
||||
online_users: number
|
||||
active_drivers: number
|
||||
delivering_orders: number
|
||||
system_load: number
|
||||
}
|
||||
|
||||
type ActivityType = {
|
||||
id: string
|
||||
type: string
|
||||
description: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
adminInfo: {
|
||||
id: '',
|
||||
nickname: '管理员',
|
||||
role: 'admin'
|
||||
} as AdminInfoType,
|
||||
|
||||
platformStats: {
|
||||
total_gmv: '0.00',
|
||||
gmv_growth: 0,
|
||||
total_orders: 0,
|
||||
order_growth: 0,
|
||||
total_users: 0,
|
||||
user_growth: 0,
|
||||
total_merchants: 0,
|
||||
merchant_growth: 0
|
||||
} as PlatformStatsType,
|
||||
|
||||
todayStats: {
|
||||
sales: '0.00',
|
||||
orders: 0,
|
||||
new_users: 0,
|
||||
active_users: 0
|
||||
} as TodayStatsType,
|
||||
|
||||
pendingCounts: {
|
||||
merchant_review: 0,
|
||||
product_review: 0,
|
||||
refund_review: 0,
|
||||
complaints: 0
|
||||
} as PendingCountsType,
|
||||
|
||||
realTimeStats: {
|
||||
online_users: 0,
|
||||
active_drivers: 0,
|
||||
delivering_orders: 0,
|
||||
system_load: 0
|
||||
} as RealTimeStatsType,
|
||||
|
||||
recentActivities: [] as Array<ActivityType>
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadAdminInfo()
|
||||
this.loadPlatformStats()
|
||||
this.loadTodayStats()
|
||||
this.loadPendingCounts()
|
||||
this.loadRealTimeStats()
|
||||
this.loadRecentActivities()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时刷新实时数据
|
||||
this.refreshRealTimeData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载管理员信息
|
||||
loadAdminInfo() {
|
||||
// TODO: 调用API获取管理员信息
|
||||
this.adminInfo.nickname = '系统管理员'
|
||||
},
|
||||
|
||||
// 加载平台统计
|
||||
loadPlatformStats() {
|
||||
// TODO: 调用API获取平台统计数据
|
||||
this.platformStats = {
|
||||
total_gmv: '12,580,000.00',
|
||||
gmv_growth: 15.6,
|
||||
total_orders: 125800,
|
||||
order_growth: 12.3,
|
||||
total_users: 45600,
|
||||
user_growth: 8.9,
|
||||
total_merchants: 2560,
|
||||
merchant_growth: 5.2
|
||||
}
|
||||
},
|
||||
|
||||
// 加载今日统计
|
||||
loadTodayStats() {
|
||||
// TODO: 调用API获取今日数据
|
||||
this.todayStats = {
|
||||
sales: '156,800.00',
|
||||
orders: 1568,
|
||||
new_users: 89,
|
||||
active_users: 3456
|
||||
}
|
||||
},
|
||||
|
||||
// 加载待处理数量
|
||||
loadPendingCounts() {
|
||||
// TODO: 调用API获取待处理数量
|
||||
this.pendingCounts = {
|
||||
merchant_review: 12,
|
||||
product_review: 45,
|
||||
refund_review: 8,
|
||||
complaints: 3
|
||||
}
|
||||
},
|
||||
|
||||
// 加载实时统计
|
||||
loadRealTimeStats() {
|
||||
// TODO: 调用API获取实时数据
|
||||
this.realTimeStats = {
|
||||
online_users: 2345,
|
||||
active_drivers: 156,
|
||||
delivering_orders: 234,
|
||||
system_load: 68
|
||||
}
|
||||
},
|
||||
|
||||
// 加载最新动态
|
||||
loadRecentActivities() {
|
||||
// TODO: 调用API获取最新动态
|
||||
this.recentActivities = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'user_register',
|
||||
description: '新用户注册:张三',
|
||||
created_at: '2025-01-08T15:30:00Z'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'merchant_apply',
|
||||
description: '商家申请入驻:华强北电子商城',
|
||||
created_at: '2025-01-08T15:25:00Z'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'order_created',
|
||||
description: '新订单创建:订单号 M202501081567',
|
||||
created_at: '2025-01-08T15:20:00Z'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 刷新实时数据
|
||||
refreshRealTimeData() {
|
||||
this.loadRealTimeStats()
|
||||
this.loadPendingCounts()
|
||||
this.loadRecentActivities()
|
||||
},
|
||||
|
||||
// 获取活动图标
|
||||
getActivityIcon(type: string): string {
|
||||
switch (type) {
|
||||
case 'user_register': return '👤'
|
||||
case 'merchant_apply': return '🏪'
|
||||
case 'order_created': return '📋'
|
||||
case 'product_review': return '📦'
|
||||
case 'refund_request': return '💰'
|
||||
case 'complaint': return '⚠️'
|
||||
default: return '📝'
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timeStr: string): string {
|
||||
const date = new Date(timeStr)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const minutes = Math.floor(diff / (1000 * 60))
|
||||
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟前`
|
||||
} else if (minutes < 1440) {
|
||||
return `${Math.floor(minutes / 60)}小时前`
|
||||
} else {
|
||||
return `${Math.floor(minutes / 1440)}天前`
|
||||
}
|
||||
},
|
||||
|
||||
// 导航方法
|
||||
goToNotifications() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/notifications'
|
||||
})
|
||||
},
|
||||
|
||||
goToProfile() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/profile'
|
||||
})
|
||||
},
|
||||
|
||||
goToMerchantReview() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/merchant-review'
|
||||
})
|
||||
},
|
||||
|
||||
goToProductReview() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/product-review'
|
||||
})
|
||||
},
|
||||
|
||||
goToRefundReview() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/refund-review'
|
||||
})
|
||||
},
|
||||
|
||||
goToComplaints() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/complaints'
|
||||
})
|
||||
},
|
||||
|
||||
goToUserManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/user-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToMerchantManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/merchant-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToProductManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/product-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToOrderManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/order-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToCouponManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/coupon-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToDeliveryManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/delivery-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToFinanceManagement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/finance-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToSystemSettings() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/system-settings'
|
||||
})
|
||||
},
|
||||
|
||||
goToActivityLog() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/activity-log'
|
||||
})
|
||||
},
|
||||
|
||||
goToSubscriptionPlans() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/subscription/plan-management'
|
||||
})
|
||||
},
|
||||
|
||||
goToAdminUserSubscriptions() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/admin/subscription/user-subscriptions'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.admin-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notification-btn,
|
||||
.profile-btn {
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
|
||||
.metrics-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
width: 48%;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
padding: 30rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
font-size: 20rpx;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #4CAF50;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.today-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.today-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.today-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.today-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #2196F3;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.today-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pending-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.pending-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pending-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
border: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.pending-item.urgent {
|
||||
border-color: #FF5722;
|
||||
background-color: #FFF3E0;
|
||||
}
|
||||
|
||||
.pending-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
width: 40rpx;
|
||||
}
|
||||
|
||||
.pending-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pending-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.pending-subtitle {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pending-count {
|
||||
font-size: 28rpx;
|
||||
color: #FF5722;
|
||||
font-weight: bold;
|
||||
background-color: #FFEBEE;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.monitor-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.monitor-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monitor-card {
|
||||
width: 48%;
|
||||
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
|
||||
padding: 30rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monitor-title {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.monitor-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.monitor-unit {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.shortcuts-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.shortcuts-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
width: 22%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.shortcut-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.shortcut-text {
|
||||
font-size: 22rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.activities-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-more {
|
||||
font-size: 24rpx;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.activities-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.activity-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 20rpx;
|
||||
width: 40rpx;
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activity-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
11
pages/mall/admin/marketing/coupon/coupon-management.uvue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<text>优惠券管理 - 占位页</text>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
export default {}
|
||||
</script>
|
||||
<style>
|
||||
.page { padding: 30rpx; }
|
||||
</style>
|
||||
28
pages/mall/admin/marketing/coupon/list.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">优惠券列表</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
28
pages/mall/admin/marketing/coupon/receive.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">用户领取记录</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
28
pages/mall/admin/marketing/points/index.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">积分管理</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
28
pages/mall/admin/marketing/signin/record.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">签到记录</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
28
pages/mall/admin/marketing/signin/rule.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">签到规则</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
13
pages/mall/admin/merchant-management.uvue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<text>商家管理 - 占位页</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { padding: 30rpx; }
|
||||
</style>
|
||||
63
pages/mall/admin/merchant-review.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="merchant-review">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商家入驻审核</text>
|
||||
<text class="page-subtitle">审核商家入驻申请</text>
|
||||
</view>
|
||||
|
||||
<view class="review-content">
|
||||
<text class="coming-soon">商家审核功能正在开发中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 统一的导航方法
|
||||
const go = (url: string) => {
|
||||
// 1) 目标页面必须是非 tabBar 页面
|
||||
// 2) 必须在 pages.json / subPackages 注册
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.merchant-review {
|
||||
padding: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.review-content {
|
||||
background-color: #fff;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.coming-soon {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
pages/mall/admin/notifications.uvue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<text>通知中心 - 占位页</text>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
export default {}
|
||||
</script>
|
||||
<style>
|
||||
.page { padding: 30rpx; }
|
||||
</style>
|
||||
1517
pages/mall/admin/order-management.uvue
Normal file
1755
pages/mall/admin/product-management.uvue
Normal file
63
pages/mall/admin/product-review.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="product-review">
|
||||
<view class="page-header">
|
||||
<text class="page-title">商品审核</text>
|
||||
<text class="page-subtitle">审核商品上架申请</text>
|
||||
</view>
|
||||
|
||||
<view class="review-content">
|
||||
<text class="coming-soon">商品审核功能正在开发中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 统一的导航方法
|
||||
const go = (url: string) => {
|
||||
// 1) 目标页面必须是非 tabBar 页面
|
||||
// 2) 必须在 pages.json / subPackages 注册
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-review {
|
||||
padding: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.review-content {
|
||||
background-color: #fff;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.coming-soon {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- 管理端 - 个人中心 -->
|
||||
<template>
|
||||
<view class="admin-profile">
|
||||
<!-- 管理员信息头部 -->
|
||||
|
||||
63
pages/mall/admin/refund-review.uvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="refund-review">
|
||||
<view class="page-header">
|
||||
<text class="page-title">退款审核</text>
|
||||
<text class="page-subtitle">审核用户退款申请</text>
|
||||
</view>
|
||||
|
||||
<view class="review-content">
|
||||
<text class="coming-soon">退款审核功能正在开发中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 统一的导航方法
|
||||
const go = (url: string) => {
|
||||
// 1) 目标页面必须是非 tabBar 页面
|
||||
// 2) 必须在 pages.json / subPackages 注册
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.refund-review {
|
||||
padding: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.review-content {
|
||||
background-color: #fff;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.coming-soon {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1078
pages/mall/admin/system-settings.uvue
Normal file
@@ -1,4 +1,3 @@
|
||||
<!-- 管理端 - 用户详情页 -->
|
||||
<template>
|
||||
<view class="user-detail-page">
|
||||
<!-- 用户基本信息 -->
|
||||
|
||||
1587
pages/mall/admin/user-management.uvue
Normal file
764
pages/mall/admin/user-statistics.uvue
Normal file
@@ -0,0 +1,764 @@
|
||||
<template>
|
||||
<AdminLayout current-page="statistics">
|
||||
<view class="user-statistics-page">
|
||||
<!-- 筛选条件栏 -->
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-left">
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">用户渠道:</text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="channelOptions"
|
||||
:value="selectedChannel"
|
||||
@change="handleChannelChange"
|
||||
>
|
||||
<view class="filter-select">
|
||||
<text>{{ channelOptions[selectedChannel] }}</text>
|
||||
<text class="iconfont icon-down"></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="filter-label">日期范围:</text>
|
||||
<view class="date-range">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="startDate"
|
||||
:start="minDate"
|
||||
:end="maxDate"
|
||||
@change="handleStartDateChange"
|
||||
>
|
||||
<view class="date-input">
|
||||
<text>{{ startDate || '开始日期' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text class="date-separator">-</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="endDate"
|
||||
:start="minDate"
|
||||
:end="maxDate"
|
||||
@change="handleEndDateChange"
|
||||
>
|
||||
<view class="date-input">
|
||||
<text>{{ endDate || '结束日期' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-right">
|
||||
<button class="btn-secondary" @click="handleSearch">
|
||||
<text class="iconfont icon-search"></text>
|
||||
查询
|
||||
</button>
|
||||
<button class="btn-primary" @click="handleExport">
|
||||
<text class="iconfont icon-export"></text>
|
||||
导出
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 指标概览 -->
|
||||
<view class="metrics-section">
|
||||
<view class="metrics-row">
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-users"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">累计用户</text>
|
||||
<text class="metric-value">{{ formatNumber(totalUsers) }}</text>
|
||||
<view class="metric-change up">
|
||||
<text class="iconfont icon-up"></text>
|
||||
<text class="change-text">{{ userGrowth }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-eye"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">访客数</text>
|
||||
<text class="metric-value">{{ formatNumber(totalVisitors) }}</text>
|
||||
<view class="metric-change up">
|
||||
<text class="iconfont icon-up"></text>
|
||||
<text class="change-text">{{ visitorGrowth }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-view"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">浏览量</text>
|
||||
<text class="metric-value">{{ formatNumber(totalPageViews) }}</text>
|
||||
<view class="metric-change down">
|
||||
<text class="iconfont icon-down"></text>
|
||||
<text class="change-text">{{ pageViewDecline }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-user-add"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">新增用户</text>
|
||||
<text class="metric-value">{{ formatNumber(newUsers) }}</text>
|
||||
<view class="metric-change up">
|
||||
<text class="iconfont icon-up"></text>
|
||||
<text class="change-text">{{ newUserGrowth }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-shopping"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">成交用户</text>
|
||||
<text class="metric-value">{{ formatNumber(convertedUsers) }}</text>
|
||||
<view class="metric-change up">
|
||||
<text class="iconfont icon-up"></text>
|
||||
<text class="change-text">{{ conversionGrowth }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="metric-card">
|
||||
<view class="metric-icon">
|
||||
<text class="iconfont icon-vip"></text>
|
||||
</view>
|
||||
<view class="metric-content">
|
||||
<text class="metric-title">付费会员</text>
|
||||
<text class="metric-value">{{ formatNumber(vipUsers) }}</text>
|
||||
<view class="metric-change up">
|
||||
<text class="iconfont icon-up"></text>
|
||||
<text class="change-text">{{ vipGrowth }}%</text>
|
||||
<text class="change-desc">较上月</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户趋势图表 -->
|
||||
<view class="chart-section">
|
||||
<view class="admin-card">
|
||||
<view class="admin-card-header">
|
||||
<text class="admin-card-title">用户数据趋势分析</text>
|
||||
</view>
|
||||
<view class="admin-card-body">
|
||||
<!-- 图表图例 -->
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item" v-for="item in trendLegend" :key="item.key">
|
||||
<view class="legend-color" :style="{ backgroundColor: item.color }"></view>
|
||||
<text class="legend-text">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 多折线图表容器 -->
|
||||
<view class="multi-line-chart">
|
||||
<!-- 图表区域 -->
|
||||
<view class="chart-area">
|
||||
<!-- 模拟多折线图 -->
|
||||
<view class="line-container" v-for="(line, index) in trendLines" :key="line.key">
|
||||
<view class="line-points">
|
||||
<view
|
||||
v-for="(point, pIndex) in line.data"
|
||||
:key="pIndex"
|
||||
class="line-point"
|
||||
:style="{
|
||||
left: (pIndex * 100 / (line.data.length - 1)) + '%',
|
||||
bottom: point.height + '%',
|
||||
backgroundColor: line.color
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- X轴标签 -->
|
||||
<view class="x-axis-labels">
|
||||
<text class="axis-label" v-for="date in chartDates" :key="date">{{ date }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||||
|
||||
// 筛选条件
|
||||
const selectedChannel = ref(0)
|
||||
const channelOptions = ['全部渠道', '自然流量', '搜索引擎', '社交媒体', '广告投放', '其他']
|
||||
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
const minDate = '2020-01-01'
|
||||
const maxDate = new Date().toISOString().split('T')[0]
|
||||
|
||||
// 指标数据
|
||||
const totalUsers = ref(32456)
|
||||
const userGrowth = ref(12.5)
|
||||
const totalVisitors = ref(156789)
|
||||
const visitorGrowth = ref(8.3)
|
||||
const totalPageViews = ref(456123)
|
||||
const pageViewDecline = ref(3.2)
|
||||
const newUsers = ref(1234)
|
||||
const newUserGrowth = ref(15.7)
|
||||
const convertedUsers = ref(5678)
|
||||
const conversionGrowth = ref(9.4)
|
||||
const vipUsers = ref(1234)
|
||||
const vipGrowth = ref(22.1)
|
||||
|
||||
// 图表数据
|
||||
const chartDates = ['01-01', '01-08', '01-15', '01-22', '01-29', '02-05', '02-12']
|
||||
|
||||
const trendLegend = [
|
||||
{ key: 'newUsers', name: '新增用户', color: '#1890ff' },
|
||||
{ key: 'visitors', name: '访客数', color: '#52c41a' },
|
||||
{ key: 'pageViews', name: '浏览量', color: '#faad14' },
|
||||
{ key: 'conversions', name: '成交用户', color: '#f5222d' },
|
||||
{ key: 'vipUsers', name: '付费会员', color: '#722ed1' }
|
||||
]
|
||||
|
||||
// 趋势线数据
|
||||
const trendLines = ref([
|
||||
{
|
||||
key: 'newUsers',
|
||||
color: '#1890ff',
|
||||
data: [
|
||||
{ value: 120, height: 12 },
|
||||
{ value: 180, height: 18 },
|
||||
{ value: 250, height: 25 },
|
||||
{ value: 320, height: 32 },
|
||||
{ value: 280, height: 28 },
|
||||
{ value: 350, height: 35 },
|
||||
{ value: 420, height: 42 }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'visitors',
|
||||
color: '#52c41a',
|
||||
data: [
|
||||
{ value: 450, height: 45 },
|
||||
{ value: 520, height: 52 },
|
||||
{ value: 580, height: 58 },
|
||||
{ value: 620, height: 62 },
|
||||
{ value: 550, height: 55 },
|
||||
{ value: 680, height: 68 },
|
||||
{ value: 750, height: 75 }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'pageViews',
|
||||
color: '#faad14',
|
||||
data: [
|
||||
{ value: 680, height: 68 },
|
||||
{ value: 720, height: 72 },
|
||||
{ value: 850, height: 85 },
|
||||
{ value: 920, height: 92 },
|
||||
{ value: 780, height: 78 },
|
||||
{ value: 950, height: 95 },
|
||||
{ value: 1000, height: 100 }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'conversions',
|
||||
color: '#f5222d',
|
||||
data: [
|
||||
{ value: 45, height: 4.5 },
|
||||
{ value: 52, height: 5.2 },
|
||||
{ value: 68, height: 6.8 },
|
||||
{ value: 75, height: 7.5 },
|
||||
{ value: 62, height: 6.2 },
|
||||
{ value: 85, height: 8.5 },
|
||||
{ value: 95, height: 9.5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'vipUsers',
|
||||
color: '#722ed1',
|
||||
data: [
|
||||
{ value: 12, height: 1.2 },
|
||||
{ value: 15, height: 1.5 },
|
||||
{ value: 22, height: 2.2 },
|
||||
{ value: 28, height: 2.8 },
|
||||
{ value: 25, height: 2.5 },
|
||||
{ value: 35, height: 3.5 },
|
||||
{ value: 42, height: 4.2 }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// 方法
|
||||
const handleChannelChange = (e: any) => {
|
||||
selectedChannel.value = e.detail.value
|
||||
}
|
||||
|
||||
const handleStartDateChange = (e: any) => {
|
||||
startDate.value = e.detail.value
|
||||
}
|
||||
|
||||
const handleEndDateChange = (e: any) => {
|
||||
endDate.value = e.detail.value
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
uni.showToast({
|
||||
title: '数据已更新',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
uni.showToast({
|
||||
title: '导出功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ===== 用户统计页面样式 ===== */
|
||||
.user-statistics-page {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== 筛选条件栏 ===== */
|
||||
.filter-section {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.filter-left {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-right {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background-color: #ffffff;
|
||||
cursor: pointer;
|
||||
min-width: 120px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background-color: #ffffff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #1890ff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #ffffff;
|
||||
color: #666666;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* ===== 指标概览 ===== */
|
||||
.metrics-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metric-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.metric-change.up {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.metric-change.down {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.change-text {
|
||||
margin: 0 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.change-desc {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* ===== 图表区域 ===== */
|
||||
.chart-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.admin-card-header {
|
||||
padding: 24px 24px 0 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.admin-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.admin-card-body {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
/* ===== 图表图例 ===== */
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* ===== 多折线图表 ===== */
|
||||
.multi-line-chart {
|
||||
height: 400px;
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
right: 40px;
|
||||
bottom: 60px;
|
||||
}
|
||||
|
||||
.line-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.line-points {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.line-point {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ffffff;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.x-axis-labels {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.axis-label {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
@media (max-width: 1200px) {
|
||||
.metrics-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
min-width: 45%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-left {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-right {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-statistics-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.filter-section,
|
||||
.chart-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.admin-card-header,
|
||||
.admin-card-body {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 图标字体 ===== */
|
||||
.iconfont {
|
||||
font-family: 'iconfont';
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-up:before {
|
||||
content: '↑';
|
||||
}
|
||||
|
||||
.icon-down:before {
|
||||
content: '↓';
|
||||
}
|
||||
|
||||
.icon-users:before {
|
||||
content: '👥';
|
||||
}
|
||||
|
||||
.icon-eye:before {
|
||||
content: '👁️';
|
||||
}
|
||||
|
||||
.icon-view:before {
|
||||
content: '📊';
|
||||
}
|
||||
|
||||
.icon-user-add:before {
|
||||
content: '👤';
|
||||
}
|
||||
|
||||
.icon-shopping:before {
|
||||
content: '🛒';
|
||||
}
|
||||
|
||||
.icon-vip:before {
|
||||
content: '👑';
|
||||
}
|
||||
|
||||
.icon-search:before {
|
||||
content: '🔍';
|
||||
}
|
||||
|
||||
.icon-export:before {
|
||||
content: '📤';
|
||||
}
|
||||
|
||||
.icon-down:before {
|
||||
content: '▼';
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- 消费者端首页 - 严格UTS Android规范 -->
|
||||
<template>
|
||||
<view class="consumer-home">
|
||||
<!-- 顶部搜索栏 -->
|
||||
|
||||
@@ -1,569 +0,0 @@
|
||||
{
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^mall-(.*)": "@/components/mall/$1.uvue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商城首页",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/boot",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/forgot-password",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/terms",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议与隐私政策"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/center",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "管理后台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服工作台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/shop-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/shop-setting",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺设置",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/route-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送路线详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/user-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/merchant-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/system-monitor",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统监控详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/ticket-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/user-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "在线客服",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/report-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "报表详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/data-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/insight-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据洞察详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/mall/consumer",
|
||||
"pages": [
|
||||
{
|
||||
"path": "product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "category",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品分类"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "checkout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "确认订单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orders",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "coupons",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的优惠券"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "address",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收货地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "软件订阅"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订阅详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/subscribe-checkout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "确认订阅"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/my-subscriptions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订阅"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/merchant",
|
||||
"pages": [
|
||||
{
|
||||
"path": "products",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orders",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "statistics",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据统计"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "promotions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "营销活动"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "finance",
|
||||
"style": {
|
||||
"navigationBarTitleText": "财务结算"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺设置"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/delivery",
|
||||
"pages": [
|
||||
{
|
||||
"path": "order-history",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "earnings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收入明细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/admin",
|
||||
"pages": [
|
||||
{
|
||||
"path": "user-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订阅方案管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/user-subscriptions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户订阅管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "merchant-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "order-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "coupon-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "优惠券管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "delivery-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "finance-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "财务管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "system-settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统设置"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/service",
|
||||
"pages": [
|
||||
{
|
||||
"path": "conversation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服会话"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "order-inquiry",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单查询"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "refund-process",
|
||||
"style": {
|
||||
"navigationBarTitleText": "退款处理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "knowledge-base",
|
||||
"style": {
|
||||
"navigationBarTitleText": "知识库"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "performance-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绩效报表"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/analytics",
|
||||
"pages": [
|
||||
{
|
||||
"path": "index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "sales-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "销售报表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "user-analysis",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户分析"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-insights",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品洞察"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "market-trends",
|
||||
"style": {
|
||||
"navigationBarTitleText": "市场趋势"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "custom-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "自定义报表"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#3cc51f",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/index",
|
||||
"iconPath": "static/tab-home.png",
|
||||
"selectedIconPath": "static/tab-home-current.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/category",
|
||||
"iconPath": "static/tab-category.png",
|
||||
"selectedIconPath": "static/tab-category-current.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/cart",
|
||||
"iconPath": "static/tab-cart.png",
|
||||
"selectedIconPath": "static/tab-cart-current.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/profile",
|
||||
"iconPath": "static/tab-profile.png",
|
||||
"selectedIconPath": "static/tab-profile-current.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "商城系统",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "消费者端首页",
|
||||
"path": "pages/mall/consumer/index"
|
||||
},
|
||||
{
|
||||
"name": "启动页(登录态判断)",
|
||||
"path": "pages/user/boot"
|
||||
},
|
||||
{
|
||||
"name": "商家端首页",
|
||||
"path": "pages/mall/merchant/index"
|
||||
},
|
||||
{
|
||||
"name": "配送端首页",
|
||||
"path": "pages/mall/delivery/index"
|
||||
},
|
||||
{
|
||||
"name": "管理端首页",
|
||||
"path": "pages/mall/admin/index"
|
||||
},
|
||||
{
|
||||
"name": "客服端首页",
|
||||
"path": "pages/mall/service/index"
|
||||
},
|
||||
{
|
||||
"name": "数据分析端首页",
|
||||
"path": "pages/mall/analytics/index"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
646
pages/mall/pages.json
Normal file
@@ -0,0 +1,646 @@
|
||||
{
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^mall-(.*)": "@/components/mall/$1.uvue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商城首页",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/boot",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/forgot-password",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/terms",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议与隐私政策"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/center",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "管理后台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服工作台",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/shop-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/merchant/shop-setting",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺设置",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/delivery/route-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送路线详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/user-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/merchant-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/admin/system-monitor",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统监控详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/ticket-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/user-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/service/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "在线客服",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/report-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "报表详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/data-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/insight-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据洞察详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/mall/consumer",
|
||||
"pages": [
|
||||
{
|
||||
"path": "product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "category",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品分类"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "checkout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "确认订单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orders",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "coupons",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的优惠券"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "address",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收货地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "软件订阅"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订阅详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/subscribe-checkout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "确认订阅"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/my-subscriptions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订阅"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/merchant",
|
||||
"pages": [
|
||||
{
|
||||
"path": "products",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orders",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "statistics",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据统计"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "promotions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "营销活动"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "finance",
|
||||
"style": {
|
||||
"navigationBarTitleText": "财务结算"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "店铺设置"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/delivery",
|
||||
"pages": [
|
||||
{
|
||||
"path": "order-history",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "earnings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收入明细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/admin",
|
||||
"pages": [
|
||||
{
|
||||
"path": "user-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/plan-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订阅方案管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "subscription/user-subscriptions",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户订阅管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "merchant-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "order-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "coupon-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "优惠券管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing/coupon/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "优惠券列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing/coupon/receive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户领取记录",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing/points/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分管理",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing/signin/rule",
|
||||
"style": {
|
||||
"navigationBarTitleText": "签到规则",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing/signin/record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "签到记录",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "delivery-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配送管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "finance-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "财务管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "system-settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "marketing-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "营销管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "activity-log",
|
||||
"style": {
|
||||
"navigationBarTitleText": "活动日志"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "merchant-review",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商家审核"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-review",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品审核"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "refund-review",
|
||||
"style": {
|
||||
"navigationBarTitleText": "退款审核"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "complaints",
|
||||
"style": {
|
||||
"navigationBarTitleText": "投诉处理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "homePage/components/KpiMiniCard",
|
||||
"style": {
|
||||
"navigationBarTitleText": "卡片模板"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/service",
|
||||
"pages": [
|
||||
{
|
||||
"path": "conversation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服会话"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "order-inquiry",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单查询"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "refund-process",
|
||||
"style": {
|
||||
"navigationBarTitleText": "退款处理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "knowledge-base",
|
||||
"style": {
|
||||
"navigationBarTitleText": "知识库"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "performance-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绩效报表"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/mall/analytics",
|
||||
"pages": [
|
||||
{
|
||||
"path": "sales-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "销售报表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "user-analysis",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户分析"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "product-insights",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品洞察"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "market-trends",
|
||||
"style": {
|
||||
"navigationBarTitleText": "市场趋势"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "custom-report",
|
||||
"style": {
|
||||
"navigationBarTitleText": "自定义报表"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#3cc51f",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/index",
|
||||
"iconPath": "static/tab-home.png",
|
||||
"selectedIconPath": "static/tab-home-current.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/category",
|
||||
"iconPath": "static/tab-category.png",
|
||||
"selectedIconPath": "static/tab-category-current.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/cart",
|
||||
"iconPath": "static/tab-cart.png",
|
||||
"selectedIconPath": "static/tab-cart-current.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/profile",
|
||||
"iconPath": "static/tab-profile.png",
|
||||
"selectedIconPath": "static/tab-profile-current.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "商城系统",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "消费者端首页",
|
||||
"path": "pages/mall/consumer/index"
|
||||
},
|
||||
{
|
||||
"name": "启动页(登录态判断)",
|
||||
"path": "pages/user/boot"
|
||||
},
|
||||
{
|
||||
"name": "商家端首页",
|
||||
"path": "pages/mall/merchant/index"
|
||||
},
|
||||
{
|
||||
"name": "配送端首页",
|
||||
"path": "pages/mall/delivery/index"
|
||||
},
|
||||
{
|
||||
"name": "管理端首页",
|
||||
"path": "pages/mall/admin/index"
|
||||
},
|
||||
{
|
||||
"name": "客服端首页",
|
||||
"path": "pages/mall/service/index"
|
||||
},
|
||||
{
|
||||
"name": "数据分析端首页",
|
||||
"path": "pages/mall/analytics/index"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
29
pages/minimal.uvue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">Minimal Test Page</text>
|
||||
<text class="content">This is a minimal test page to check if uni-app-x compilation works.</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal script
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
16
pages/test-minimal.uvue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<view class="test">
|
||||
<text>Hello World</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// Minimal test
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.test {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
1
static/finance.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417164326" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20559" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M192 960H96c-17.67 0-32-14.33-32-32V480c0-17.67 14.33-32 32-32h96c17.67 0 32 14.33 32 32v448c0 17.67-14.33 32-32 32z m-64-64h32V512h-32v384zM437.33 960h-96c-17.67 0-32-14.33-32-32V608c0-17.67 14.33-32 32-32h96c17.67 0 32 14.33 32 32v320c0 17.67-14.32 32-32 32z m-64-64h32V640h-32v256zM682.67 960h-96c-17.67 0-32-14.33-32-32V608c0-17.67 14.33-32 32-32h96c17.67 0 32 14.33 32 32v320c0 17.67-14.33 32-32 32z m-64-64h32V640h-32v256zM928 960h-96c-17.67 0-32-14.33-32-32V480c0-17.67 14.33-32 32-32h96c17.67 0 32 14.33 32 32v448c0 17.67-14.33 32-32 32z m-64-64h32V512h-32v384zM512 512c-59.83 0-116.08-23.3-158.39-65.61C311.3 404.08 288 347.83 288 288s23.3-116.08 65.61-158.39C395.92 87.3 452.17 64 512 64s116.08 23.3 158.39 65.61C712.7 171.92 736 228.17 736 288s-23.3 116.08-65.61 158.39C628.08 488.7 571.83 512 512 512z m0-384c-88.22 0-160 71.78-160 160s71.78 160 160 160 160-71.78 160-160-71.78-160-160-160z" p-id="20560" fill="#ffffff"></path><path d="M489.83 375.89v-36.33h-43.36c-9.38 0-14.06-4.28-14.06-12.89 0.77-7.03 5.46-11.32 14.06-12.89h43.36v-19.92h-43.36c-9.38 0-14.06-4.28-14.06-12.89 0.77-7.03 5.46-11.32 14.06-12.89h24.61l-37.5-52.73c-3.92-3.11-5.47-7.42-4.69-12.89 0.77-13.28 8.2-20.69 22.27-22.27 7.03 0.79 12.89 3.92 17.58 9.38l43.36 62.11 43.36-62.11c3.9-5.46 9.76-8.59 17.58-9.38 13.28 1.57 20.69 8.99 22.27 22.27 0 5.47-1.57 9.78-4.69 12.89l-37.5 52.73h24.61c7.8 1.57 12.49 5.86 14.06 12.89-0.79 8.61-5.47 12.89-14.06 12.89h-43.36v19.92h43.36c7.8 1.57 12.49 5.86 14.06 12.89-0.79 8.61-5.47 12.89-14.06 12.89h-43.36v36.33c-0.79 13.29-8.2 19.92-22.27 19.92-14.85 0-22.27-6.63-22.27-19.92z" p-id="20561" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1
static/homepage.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417272835" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21779" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M969.6 502.4l-118.4-112-323.2-300.8c-9.6-9.6-22.4-9.6-32 0l-313.6 297.6c-3.2 3.2-6.4 6.4-9.6 9.6l-118.4 112c-9.6 9.6-9.6 22.4 0 32s22.4 9.6 32 0l83.2-80 0 393.6c0 48 41.6 89.6 92.8 89.6l83.2 0c38.4 0 70.4-28.8 70.4-67.2l0-217.6 99.2 0 99.2 0 0 217.6c0 35.2 32 67.2 70.4 67.2l83.2 0c51.2 0 92.8-38.4 92.8-89.6l0-396.8 80 73.6c9.6 9.6 22.4 9.6 32 0C979.2 524.8 979.2 512 969.6 502.4zM809.6 857.6c0 25.6-19.2 44.8-44.8 44.8l-83.2 0c-12.8 0-22.4-9.6-22.4-22.4L659.2 640c0-12.8-9.6-22.4-22.4-22.4l-121.6 0-121.6 0c-12.8 0-22.4 9.6-22.4 22.4l0 240c0 12.8-9.6 22.4-22.4 22.4l-83.2 0c-25.6 0-44.8-19.2-44.8-44.8l0-438.4 294.4-281.6 294.4 281.6L809.6 857.6z" p-id="21780" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
static/order.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417734574" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27067" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M800.037628 928.016126 223.962372 928.016126c-52.980346 0-95.983874-43.003528-95.983874-95.983874l0-639.892491c0-52.980346 43.003528-95.983874 95.983874-95.983874l575.903242 0c52.980346 0 95.983874 43.003528 95.983874 95.983874l0 639.892491C896.021502 884.840585 852.84596 928.016126 800.037628 928.016126zM223.962372 159.973123c-17.545439 0-31.994625 14.449185-31.994625 31.994625l0 639.892491c0 17.717453 14.449185 31.994625 31.994625 31.994625l575.903242 0c17.717453 0 31.994625-14.277171 31.994625-31.994625l0-639.892491c0-17.545439-14.277171-31.994625-31.994625-31.994625L223.962372 159.973123z" fill="#ffffff" p-id="27068"></path><path d="M640.924576 544.768688 287.779607 544.768688c-17.717453 0-31.994625-14.277171-31.994625-31.994625 0-17.717453 14.277171-31.994625 31.994625-31.994625l353.144969 0c17.717453 0 31.994625 14.277171 31.994625 31.994625C672.9192 530.491517 658.642029 544.768688 640.924576 544.768688z" fill="#ffffff" p-id="27069"></path><path d="M734.84428 735.532337l-447.236687 0c-17.717453 0-31.994625-14.277171-31.994625-31.994625s14.277171-31.994625 31.994625-31.994625l447.236687 0c17.717453 0 31.994625 14.277171 31.994625 31.994625S752.561734 735.532337 734.84428 735.532337z" fill="#ffffff" p-id="27070"></path><path d="M255.784982 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S255.784982 278.834873 255.784982 305.325046z" fill="#ffffff" p-id="27071"></path><path d="M463.061986 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S463.061986 278.834873 463.061986 305.325046z" fill="#ffffff" p-id="27072"></path><path d="M671.199059 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S671.199059 278.834873 671.199059 305.325046z" fill="#ffffff" p-id="27073"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
static/setting.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417366593" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="26013" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M919.6 405.6l-57.2-8c-12.7-1.8-23-10.4-28-22.1-11.3-26.7-25.7-51.7-42.9-74.5-7.7-10.2-10-23.5-5.2-35.3l21.7-53.5c6.7-16.4 0.2-35.3-15.2-44.1L669.1 96.6c-15.4-8.9-34.9-5.1-45.8 8.9l-35.4 45.3c-7.9 10.2-20.7 14.9-33.5 13.3-14-1.8-28.3-2.8-42.8-2.8-14.5 0-28.8 1-42.8 2.8-12.8 1.6-25.6-3.1-33.5-13.3l-35.4-45.3c-10.9-14-30.4-17.8-45.8-8.9L230.4 168c-15.4 8.9-21.8 27.7-15.2 44.1l21.7 53.5c4.8 11.9 2.5 25.1-5.2 35.3-17.2 22.8-31.7 47.8-42.9 74.5-5 11.8-15.3 20.4-28 22.1l-57.2 8C86 408 72.9 423 72.9 440.8v142.9c0 17.7 13.1 32.7 30.6 35.2l57.2 8c12.7 1.8 23 10.4 28 22.1 11.3 26.7 25.7 51.7 42.9 74.5 7.7 10.2 10 23.5 5.2 35.3l-21.7 53.5c-6.7 16.4-0.2 35.3 15.2 44.1L354 927.8c15.4 8.9 34.9 5.1 45.8-8.9l35.4-45.3c7.9-10.2 20.7-14.9 33.5-13.3 14 1.8 28.3 2.8 42.8 2.8 14.5 0 28.8-1 42.8-2.8 12.8-1.6 25.6 3.1 33.5 13.3l35.4 45.3c10.9 14 30.4 17.8 45.8 8.9l123.7-71.4c15.4-8.9 21.8-27.7 15.2-44.1l-21.7-53.5c-4.8-11.8-2.5-25.1 5.2-35.3 17.2-22.8 31.7-47.8 42.9-74.5 5-11.8 15.3-20.4 28-22.1l57.2-8c17.6-2.5 30.6-17.5 30.6-35.2V440.8c0.2-17.8-12.9-32.8-30.5-35.2z m-408 245.5c-76.7 0-138.9-62.2-138.9-138.9s62.2-138.9 138.9-138.9 138.9 62.2 138.9 138.9-62.2 138.9-138.9 138.9z" fill="#ffffff" p-id="26014"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
static/shopping.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417326653" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23918" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M800.037628 928.016126 223.962372 928.016126c-52.980346 0-95.983874-43.003528-95.983874-95.983874l0-639.892491c0-52.980346 43.003528-95.983874 95.983874-95.983874l575.903242 0c52.980346 0 95.983874 43.003528 95.983874 95.983874l0 639.892491C896.021502 884.840585 852.84596 928.016126 800.037628 928.016126zM223.962372 159.973123c-17.545439 0-31.994625 14.449185-31.994625 31.994625l0 639.892491c0 17.717453 14.449185 31.994625 31.994625 31.994625l575.903242 0c17.717453 0 31.994625-14.277171 31.994625-31.994625l0-639.892491c0-17.545439-14.277171-31.994625-31.994625-31.994625L223.962372 159.973123z" fill="#ffffff" p-id="23919"></path><path d="M640.924576 544.768688 287.779607 544.768688c-17.717453 0-31.994625-14.277171-31.994625-31.994625 0-17.717453 14.277171-31.994625 31.994625-31.994625l353.144969 0c17.717453 0 31.994625 14.277171 31.994625 31.994625C672.9192 530.491517 658.642029 544.768688 640.924576 544.768688z" fill="#ffffff" p-id="23920"></path><path d="M734.84428 735.532337l-447.236687 0c-17.717453 0-31.994625-14.277171-31.994625-31.994625s14.277171-31.994625 31.994625-31.994625l447.236687 0c17.717453 0 31.994625 14.277171 31.994625 31.994625S752.561734 735.532337 734.84428 735.532337z" fill="#ffffff" p-id="23921"></path><path d="M255.784982 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S255.784982 278.834873 255.784982 305.325046z" fill="#ffffff" p-id="23922"></path><path d="M463.061986 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S463.061986 278.834873 463.061986 305.325046z" fill="#ffffff" p-id="23923"></path><path d="M671.199059 305.325046c0 26.490173 21.501764 47.991937 47.991937 47.991937s47.991937-21.501764 47.991937-47.991937-21.501764-47.991937-47.991937-47.991937S671.199059 278.834873 671.199059 305.325046z" fill="#ffffff" p-id="23924"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
1
static/statistics.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417346887" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24969" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M992 672h-24c-4.4 0-8-3.6-8-8V136c0-4.4 3.6-8 8-8h24c17.7 0 32-14.3 32-32s-14.3-32-32-32H580c-2.2 0-4-1.8-4-4 0-16.6-6.7-31.6-17.6-42.4C547.6 6.7 532.6 0 516 0h-8c-33.1 0-60 26.9-60 60 0 1.1-0.4 2.1-1.2 2.8-0.7 0.7-1.7 1.2-2.8 1.2H32C14.3 64 0 78.3 0 96c0 8.8 3.6 16.8 9.4 22.6 5.8 5.8 13.8 9.4 22.6 9.4h24c4.4 0 8 3.6 8 8v528c0 4.4-3.6 8-8 8H32c-17.7 0-32 14.3-32 32 0 8.8 3.6 16.8 9.4 22.6 5.8 5.8 13.8 9.4 22.6 9.4h364.1c10.2 0 17.8 9.5 15.6 19.5L360.4 986c-4.3 19.5 10.5 38 30.5 38 7.3 0 14.2-2.5 19.6-6.9 5.4-4.3 9.4-10.5 10.9-17.6l50.7-228c1-4.7 7.9-3.9 7.9 0.9v220.3c0 17.3 14 31.2 31.2 31.2h1.5c17.3 0 31.2-14 31.2-31.2V772.4c0-4.8 6.9-5.5 7.9-0.9l50.7 228c3.2 14.3 15.8 24.4 30.5 24.5h0.1c20 0 34.9-18.5 30.5-38l-51.3-230.5c-2.2-10 5.4-19.5 15.6-19.5H992c17.7 0 32-14.3 32-32 0-8.8-3.6-16.8-9.4-22.6-5.8-5.8-13.8-9.4-22.6-9.4z m-704-68V420c0-2.2 1.8-4 4-4h56c2.2 0 4 1.8 4 4v184c0 2.2-1.8 4-4 4h-56c-2.2 0-4-1.8-4-4z m128 0V388c0-2.2 1.8-4 4-4h56c2.2 0 4 1.8 4 4v216c0 2.2-1.8 4-4 4h-56c-2.2 0-4-1.8-4-4z m128 0V324c0-2.2 1.8-4 4-4h56c2.2 0 4 1.8 4 4v280c0 2.2-1.8 4-4 4h-56c-2.2 0-4-1.8-4-4z m192 0c0 2.2-1.8 4-4 4h-56c-2.2 0-4-1.8-4-4V228c0-2.2 1.8-4 4-4h56c2.2 0 4 1.8 4 4v376z" p-id="24970" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
static/user.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769417307751" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22862" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.913993 941.605241c-255.612968 0-385.311608-57.452713-385.311608-170.810012 0-80.846632 133.654964-133.998992 266.621871-151.88846L393.224257 602.049387c-79.986561-55.904586-118.86175-153.436587-118.86175-297.240383 0-139.33143 87.211154-222.586259 233.423148-222.586259l7.912649 0c146.211994 0 233.423148 83.254829 233.423148 222.586259 0 54.184445 0 214.67361-117.829666 297.412397l-0.344028 16.685369c132.966907 18.061482 266.105829 71.041828 266.105829 151.716445C897.225601 884.152528 767.526961 941.605241 511.913993 941.605241zM507.957668 141.567613c-79.470519 0-174.250294 28.382328-174.250294 163.241391 0 129.698639 34.230808 213.469511 104.584579 255.784982 8.944734 5.332437 14.277171 14.965228 14.277171 25.286074l0 59.344868c0 15.309256-11.524945 28.0383-26.662187 29.414413-144.319839 14.449185-239.959684 67.429531-239.959684 95.983874 0 92.199563 177.346548 111.637158 325.966739 111.637158 148.792206 0 325.966739-19.26558 325.966739-111.637158 0-28.726356-95.639845-81.534688-239.959684-95.983874-15.48127-1.548127-27.006215-14.621199-26.662187-30.102469l1.376113-59.344868c0.172014-10.148833 5.676466-19.437594 14.277171-24.770032 70.525785-42.487485 103.208466-123.678145 103.208466-255.784982 0-135.031077-94.779775-163.241391-174.250294-163.241391L507.957668 141.567613 507.957668 141.567613z" fill="#ffffff" p-id="22863"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
51
tsconfig.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"@dcloudio/types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"main.uts",
|
||||
"pages/**/*.uvue",
|
||||
"pages/**/*.uts",
|
||||
"components/**/*.uvue",
|
||||
"components/**/*.uts",
|
||||
"layouts/**/*.uvue",
|
||||
"layouts/**/*.uts",
|
||||
"utils/**/*.ts",
|
||||
"utils/**/*.js",
|
||||
"types/**/*.d.ts",
|
||||
"**/*.uvue",
|
||||
"**/*.uts",
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"unpackage"
|
||||
]
|
||||
}
|
||||
@@ -219,7 +219,18 @@ export class AkReq {
|
||||
await new Promise<void>((r) => { setTimeout(() => { r(); }, delay); });
|
||||
attempt++;
|
||||
}
|
||||
return lastRes!!;
|
||||
const finalRes = lastRes!!;
|
||||
// 全局处理 401 未授权:在非 refresh 场景下,清理 token 并跳转登录以避免未捕获错误
|
||||
if ((finalRes.status === 401) && (skipRefresh !== true)) {
|
||||
try {
|
||||
this.clearToken();
|
||||
uni.showToast({ title: '未授权或登录已过期,请重新登录', icon: 'none' });
|
||||
} catch (e) {}
|
||||
try {
|
||||
uni.reLaunch({ url: '/pages/user/login' });
|
||||
} catch (e) {}
|
||||
}
|
||||
return finalRes;
|
||||
}
|
||||
|
||||
// 新增 upload 方法,支持 uni.uploadFile,自动带 token/apikey
|
||||
|
||||