前端初始化

This commit is contained in:
ni ziyi 2025-12-12 16:49:06 +08:00
commit 71bc2db985
104 changed files with 12636 additions and 0 deletions

132
.gitignore vendored Normal file
View File

@ -0,0 +1,132 @@
# ======================
# 操作系统 & 编辑器
# ======================
.DS_Store
Thumbs.db
ehthumbs.db
Icon?
desktop.ini
*.swp
*.swo
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea/
*.sublime-project
*.sublime-workspace
# ======================
# 日志 & 临时文件
# ======================
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# ======================
# 依赖目录
# ======================
node_modules/
jspm_packages/
bower_components/
.mvn/
target/
build/
dist/
out/
coverage/
# ======================
# 环境变量 & 敏感配置
# ======================
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
config/local.*
secrets.yml
# ======================
# 构建产物(前端)
# ======================
/public/build
/public/hot
/public/dist
/assets/*.min.js
/assets/*.min.css
# ======================
# Python
# ======================
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
venv/
ENV/
.env
# ======================
# Java / Maven / Gradle
# ======================
*.class
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
hs_err_pid*
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
# ======================
# Docker
# ======================
.dockerignore
docker-compose.override.yml
# ======================
# 其他
# ======================
*.tmp
*.bak
*.orig
*.rej
*~
.swp
.lock
.nyc_output/
.grunt/
gulpfile.js
package-lock.json # 可选:有些团队会提交它
yarn.lock # 可选:通常建议提交 yarn.lock
pnpm-lock.yaml # 可选:通常建议提交
/node_modules/

23
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,23 @@
{
"version" : "2.0.0",
"configurations" : [
{
"name" : "运行到H5",
"request" : "launch",
"runtime" : "h5",
"type" : "uni-app",
"url" : "http://localhost:8080"
},
{
"default" : {
"launchtype" : "local"
},
"h5" : {
"launchtype" : "local"
},
"provider" : "alipay",
"type" : "uniCloud"
}
]
}

20
App.vue Normal file
View File

@ -0,0 +1,20 @@
<script>
import { useUserStore } from './store/user'
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
@import './static/css/common.css';
</style>

82
DEBUG.md Normal file
View File

@ -0,0 +1,82 @@
# 调试指南 - 空白页面问题
## 可能的原因和解决方案
### 1. 检查浏览器控制台
打开浏览器开发者工具F12查看Console标签页看是否有JavaScript错误。
### 2. 检查网络请求
在Network标签页查看是否有资源加载失败红色标记
### 3. 检查uni-app配置
确保项目被HBuilderX识别为uni-app项目
- `manifest.json` 存在且配置正确
- `pages.json` 存在且配置正确
- `App.vue` 存在
### 4. 尝试以下操作
#### 方法一使用HBuilderX的标准运行方式
1. 关闭当前运行
2. 在HBuilderX中运行 → 运行到浏览器 → Chrome
3. 不要使用命令行运行
#### 方法二:检查项目类型
确保HBuilderX识别项目类型为"uni-app项目"
- 右键项目根目录
- 查看项目类型
#### 方法三:清除缓存
```bash
cd frontend
rm -rf node_modules/.vite
rm -rf .hbuilderx
```
#### 方法四:检查页面路由
确保 `pages.json` 中第一个页面路径正确:
```json
{
"pages": [
{
"path": "pages/index/index", // 确保这个文件存在
...
}
]
}
```
### 5. 常见错误
#### 错误Cannot find module 'vue'
**解决**:执行 `npm install vue pinia`
#### 错误Failed to parse .vue files
**解决**:已安装 `@vitejs/plugin-vue`
#### 错误:空白页面
**可能原因**
- main.js格式不正确
- App.vue缺少template
- 页面路由配置错误
- JavaScript运行时错误
### 6. 调试步骤
1. **打开浏览器控制台**F12
2. **查看Console错误**
3. **查看Network请求**
4. **检查页面元素**Elements标签
5. **提供错误信息**以便进一步排查
## 如果仍然空白
请提供以下信息:
1. 浏览器控制台的错误信息Console标签
2. 网络请求状态Network标签是否有404或500错误
3. HBuilderX的运行日志
4. 页面源代码(右键页面 → 查看网页源代码)

74
DIAGNOSIS.md Normal file
View File

@ -0,0 +1,74 @@
# 诊断指南 - 页面空白问题
## 当前状态
- ✅ main.js 已加载完成
- ❌ 页面显示空白
- ❌ Console没有页面相关的输出
## 可能的原因
### 1. uni-app路由系统未初始化
uni-app在HBuilderX中运行时需要特殊的路由系统。如果路由系统没有正确初始化页面就不会显示。
### 2. 页面组件未加载
如果控制台没有"登录页面加载成功"的输出,说明页面组件根本没有加载。
### 3. App.vue的template是空的
App.vue只有一个空的view标签uni-app可能无法正确渲染。
## 排查步骤
### 步骤1检查控制台输出
请查看浏览器控制台,告诉我:
- 是否有"登录页面加载成功"的输出?
- 是否有"App Launch"的输出?
- 是否有任何错误信息?
### 步骤2检查Network标签
查看Network标签检查
- `pages/login/login-simple.vue` 是否加载?
- 是否有404错误
- 是否有其他失败的请求?
### 步骤3检查页面元素
在Elements标签中检查
- `#app` 元素是否存在?
- 是否有内容被渲染?
- 是否有uni-app的路由容器
## 解决方案
### 方案1检查HBuilderX项目类型
确保HBuilderX识别为"uni-app项目"
- 右键项目文件夹
- 查看项目类型
- 如果不是uni-app可能需要重新创建项目
### 方案2使用HBuilderX的标准运行方式
- 不要使用命令行运行
- 使用HBuilderX菜单运行 → 运行到浏览器
- 确保选择的是"uni-app"运行方式
### 方案3检查manifest.json
确保manifest.json配置正确
- vueVersion: "3"
- 其他配置正确
## 请提供以下信息
1. **控制台输出**
- 是否有"登录页面加载成功"
- 是否有"App Launch"
- 是否有任何错误?
2. **Network标签**
- login-simple.vue是否加载
- 状态码是什么?
3. **Elements标签**
- #app元素的内容是什么
- 是否有任何内容被渲染?

67
FILES_CHECKLIST.md Normal file
View File

@ -0,0 +1,67 @@
# 前端文件清单
## 已创建的文件
### 核心配置文件
- ✅ `package.json` - 项目依赖配置
- ✅ `pages.json` - 页面路由配置
- ✅ `manifest.json` - uni-app应用配置
- ✅ `main.js` - 应用入口文件
- ✅ `App.vue` - 应用根组件
- ✅ `vite.config.js` - Vite构建配置
- ✅ `tsconfig.json` - TypeScript配置
- ✅ `uni.scss` - uni-app样式变量
### 工具类文件
- ✅ `utils/request.js` - HTTP请求封装包含JWT token管理
- ✅ `utils/date.js` - 日期工具函数
### 状态管理
- ✅ `store/user.js` - 用户状态管理Pinia store
### API接口封装
- ✅ `api/auth.js` - 认证相关API注册、登录
- ✅ `api/bill.js` - 账单相关APICRUD操作
- ✅ `api/category.js` - 分类相关API
- ✅ `api/statistics.js` - 统计相关API
- ✅ `api/ocr.js` - OCR识别API
### 页面文件
- ✅ `pages/login/login.vue` - 登录/注册页面
- ✅ `pages/index/index.vue` - 首页(账单列表)
- ✅ `pages/add/add.vue` - 添加账单页面(手动+OCR
- ✅ `pages/statistics/statistics.vue` - 统计页面
- ✅ `pages/category/category.vue` - 分类管理页面
### 样式文件
- ✅ `static/css/common.css` - 全局公共样式
## 可选文件(需要时添加)
### TabBar图标可选
如果需要显示底部导航栏图标,需要添加以下图片文件:
- `static/tabbar/home.png` - 首页图标
- `static/tabbar/home-active.png` - 首页选中图标
- `static/tabbar/add.png` - 记账图标
- `static/tabbar/add-active.png` - 记账选中图标
- `static/tabbar/statistics.png` - 统计图标
- `static/tabbar/statistics-active.png` - 统计选中图标
如果没有这些图标tabBar会显示文字但不显示图标功能不受影响。
## 文件说明
所有核心功能文件都已创建完成,项目可以直接运行。如果遇到问题,请检查:
1. 是否已安装依赖:`npm install`
2. 后端服务是否已启动
3. API地址配置是否正确`utils/request.js`中的BASE_URL

174
ICONS_GUIDE.md Normal file
View File

@ -0,0 +1,174 @@
# 图标文件使用指南
## 📋 项目中使用的图标文件
### 1. TabBar 底部导航图标(必需)
`pages.json` 中配置的 TabBar 需要以下 6 个图标文件:
| 图标名称 | 用途 | 路径 |
|---------|------|------|
| `home.png` | 首页(未选中) | `static/tabbar/home.png` |
| `home-active.png` | 首页(选中) | `static/tabbar/home-active.png` |
| `add.png` | 记账(未选中) | `static/tabbar/add.png` |
| `add-active.png` | 记账(选中) | `static/tabbar/add-active.png` |
| `statistics.png` | 统计(未选中) | `static/tabbar/statistics.png` |
| `statistics-active.png` | 统计(选中) | `static/tabbar/statistics-active.png` |
**图标规格**
- 尺寸81px × 81px@2x或 40px × 40px@1x
- 格式PNG支持透明背景
- 颜色:
- 未选中:#7A7E83灰色
- 选中:#667eea紫色
### 2. 分类图标(使用 Emoji无需文件
代码中使用 Emoji 作为分类图标,例如:
- 📦 默认分类
- 🍔 餐饮
- 👕 服装
- 🏠 住房
- 🚗 交通
- 等等...
这些图标存储在数据库中,不需要图标文件。
### 3. 功能图标(使用 Emoji无需文件
- 📷 上传图片图标(在 OCR 页面)
## 🚀 快速获取图标的方法
### 方法1使用在线图标库推荐
#### IconFont阿里巴巴图标库
1. 访问https://www.iconfont.cn/
2. 注册/登录账号
3. 搜索关键词:
- "首页" 或 "home"
- "添加" 或 "add" 或 "plus"
- "统计" 或 "statistics" 或 "chart"
4. 选择图标,点击下载
5. 选择 PNG 格式,尺寸选择 81×81
6. 下载后重命名并放入 `static/tabbar/` 目录
#### Flaticon
1. 访问https://www.flaticon.com/
2. 搜索图标
3. 下载 PNG 格式(免费账号需要注明来源)
### 方法2使用 AI 生成图标
使用 ChatGPT、Midjourney 等 AI 工具生成图标:
**提示词示例**
```
生成一个简单的首页图标极简风格紫色主题透明背景81x81像素PNG格式
```
### 方法3临时解决方案使用文字图标
如果暂时没有图标文件,可以修改 `pages.json`,移除图标路径,只显示文字:
```json
{
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
// 移除 iconPath 和 selectedIconPath
}
]
}
}
```
### 方法4使用 uni-app 内置图标
uni-app 支持使用字体图标,可以集成 iconfont
1. 访问 https://www.iconfont.cn/
2. 创建项目,添加图标
3. 下载字体文件
4. 在项目中引入字体文件
5. 使用类名引用图标
## 📥 推荐的免费图标资源
1. **IconFont** - https://www.iconfont.cn/
- 中文界面,搜索方便
- 免费使用,需登录
2. **Flaticon** - https://www.flaticon.com/
- 图标丰富
- 免费使用需注明来源
3. **Icons8** - https://icons8.com/
- 图标质量高
- 部分免费
4. **Material Icons** - https://fonts.google.com/icons
- Google 官方图标库
- 完全免费
5. **Font Awesome** - https://fontawesome.com/
- 图标库丰富
- 部分免费
## 🎨 图标设计建议
### 风格统一
- 所有图标使用相同的设计风格
- 线条粗细一致
- 圆角或直角统一
### 颜色方案
- 未选中:灰色(#7A7E83
- 选中:紫色(#667eea与主题色一致
### 尺寸规范
- TabBar 图标81px × 81px@2x
- 确保图标在选中和未选中状态下清晰可辨
## ⚠️ 注意事项
1. **文件路径**:图标文件必须放在 `static/tabbar/` 目录下
2. **文件命名**:文件名必须与 `pages.json` 中的配置完全一致(区分大小写)
3. **文件格式**:使用 PNG 格式,支持透明背景
4. **文件大小**:建议单个图标文件不超过 50KB
5. **缺失处理**如果图标文件不存在TabBar 可能无法正常显示
## 🔧 快速测试
创建占位图标文件进行测试:
1. 创建 6 个 81×81px 的纯色 PNG 图片
2. 未选中状态使用灰色,选中状态使用紫色
3. 放入 `static/tabbar/` 目录
4. 运行项目查看效果
## 📝 图标文件清单
请确保以下文件存在:
```
frontend/static/tabbar/
├── home.png ✅ 需要创建
├── home-active.png ✅ 需要创建
├── add.png ✅ 需要创建
├── add-active.png ✅ 需要创建
├── statistics.png ✅ 需要创建
└── statistics-active.png ✅ 需要创建
```
## 💡 快速开始
**最快的方式**
1. 访问 https://www.iconfont.cn/
2. 搜索并下载 6 个图标(首页、添加、统计各 2 个)
3. 重命名为对应文件名
4. 放入 `static/tabbar/` 目录
5. 完成!

83
QUICK_FIX.md Normal file
View File

@ -0,0 +1,83 @@
# 快速修复指南 - 登录页不显示
## 问题分析
uni-app项目在HBuilderX中使用Vite时可能存在路由系统不兼容的问题。
## 解决方案
### 方案一使用HBuilderX标准运行方式推荐
1. **删除或重命名 `vite.config.js`**
- HBuilderX有自己的构建系统不需要Vite配置
- 已重命名为 `vite.config.js.bak`
2. **在HBuilderX中运行**
- 运行 → 运行到浏览器 → Chrome
- 不要使用命令行运行
3. **检查项目类型**
- 确保HBuilderX识别为"uni-app项目"
- 右键项目 → 查看项目类型
### 方案二:检查浏览器控制台
1. **打开浏览器开发者工具**F12
2. **查看Console标签**,看是否有错误:
- 路由错误
- 模块加载错误
- JavaScript运行时错误
3. **查看Network标签**,检查:
- `main.js` 是否加载成功
- `pages/login/login.vue` 是否加载成功
- 是否有404错误
### 方案三:简化测试
如果仍然不显示,创建一个最简单的测试页面:
`pages/login/login.vue` 中,先测试最基础的内容:
```vue
<template>
<view style="padding: 100px; text-align: center;">
<text style="font-size: 32px;">登录页面测试</text>
</view>
</template>
<script>
export default {
onLoad() {
console.log('登录页面加载')
}
}
</script>
```
### 方案四:检查文件路径
确保文件路径正确:
- `pages/login/login.vue` 文件存在
- `pages.json` 中路径配置正确:`"path": "pages/login/login"`
## 常见问题
### Q: 页面完全空白?
A: 检查浏览器控制台看是否有JavaScript错误
### Q: 显示"加载中"
A: 检查 `index.html` 是否还有加载提示,已移除
### Q: 路由不工作?
A: 确保使用HBuilderX的标准运行方式不要使用纯Vite
## 下一步
1. 删除 `vite.config.js.bak`(如果方案一不行)
2. 检查浏览器控制台错误
3. 提供具体的错误信息以便进一步排查

158
README.md Normal file
View File

@ -0,0 +1,158 @@
# 强宝爱记账前端
基于 uni-app + Vue 3 开发的记账应用前端项目。
## 功能特性
- ✅ 用户登录/注册
- ✅ 手动记账
- ✅ OCR识别记账支持截图识别金额
- ✅ 账单列表展示(按日期分组)
- ✅ 统计功能(按日/周/月统计)
- ✅ 分类管理
## 技术栈
- **框架**: uni-app + Vue 3
- **状态管理**: Pinia
- **UI组件**: uni-ui
- **HTTP请求**: uni.request 封装
## 项目结构
```
frontend/
├── pages/ # 页面
│ ├── login/ # 登录页
│ ├── index/ # 首页(账单列表)
│ ├── add/ # 添加账单
│ └── statistics/ # 统计页
├── api/ # API接口封装
│ ├── auth.js # 认证接口
│ ├── bill.js # 账单接口
│ ├── category.js # 分类接口
│ ├── statistics.js # 统计接口
│ └── ocr.js # OCR接口
├── store/ # Pinia状态管理
│ └── user.js # 用户状态
├── utils/ # 工具函数
│ ├── request.js # HTTP请求封装
│ └── date.js # 日期工具
├── static/ # 静态资源
├── App.vue # 应用根组件
├── main.js # 入口文件
└── pages.json # 页面配置
```
## 开发说明
### 环境要求
- Node.js >= 14
- HBuilderX推荐或 uni-app CLI
### 安装依赖
```bash
npm install
```
### 运行项目
#### HBuilderX
1. 使用 HBuilderX 打开项目
2. 选择运行 -> 运行到浏览器 -> Chrome
#### CLI
```bash
npm run dev:h5
```
### 配置后端API地址
修改 `utils/request.js` 中的 `BASE_URL`
```javascript
const BASE_URL = process.env.NODE_ENV === 'development'
? 'http://localhost:8080/api' // 开发环境
: 'https://your-api-domain.com/api' // 生产环境
```
## 页面说明
### 登录页 (`pages/login/login.vue`)
- 支持登录和注册
- 自动保存token和用户信息
- 登录成功后跳转到首页
### 首页 (`pages/index/index.vue`)
- 显示本月收支统计
- 账单列表按日期分组展示
- 支持下拉刷新
- 点击账单可查看详情(待完善)
### 添加账单页 (`pages/add/add.vue`)
- **手动记账**: 手动输入分类、金额、日期、备注
- **OCR识别**:
- 选择图片(相机/相册)
- 上传识别
- 自动填充金额、商户、日期
- 可手动修改识别结果
### 统计页 (`pages/statistics/statistics.vue`)
- 按日/周/月切换统计
- 显示总收入、总支出、余额
- 分类统计展示(带进度条)
## API接口
### 认证接口
- `POST /api/auth/register` - 注册
- `POST /api/auth/login` - 登录
### 账单接口
- `GET /api/bills` - 获取账单列表
- `POST /api/bills` - 创建账单
- `GET /api/bills/:id` - 获取账单详情
- `PUT /api/bills/:id` - 更新账单
- `DELETE /api/bills/:id` - 删除账单
### 分类接口
- `GET /api/categories` - 获取分类列表
- `POST /api/categories` - 创建分类
- `PUT /api/categories/:id` - 更新分类
- `DELETE /api/categories/:id` - 删除分类
### 统计接口
- `GET /api/statistics/daily` - 按日统计
- `GET /api/statistics/weekly` - 按周统计
- `GET /api/statistics/monthly` - 按月统计
### OCR接口
- `POST /api/ocr/recognize` - OCR识别
## 注意事项
1. **登录状态**: 所有页面都会检查登录状态,未登录会自动跳转到登录页
2. **Token管理**: Token存储在本地存储中过期后会自动跳转登录
3. **图片上传**: OCR功能需要上传图片注意图片大小限制
4. **平台差异**: 注意H5和App平台的API差异部分功能可能需要平台判断
## 待完善功能
- [ ] 账单编辑功能
- [ ] 账单删除功能
- [ ] 分类管理页面
- [ ] 账单详情页面
- [ ] 图表展示echarts集成

47
TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,47 @@
# 故障排查指南
## 问题Failed to resolve import "vue"
### 解决方案
1. **确保依赖已安装**
```bash
cd frontend
npm install vue pinia
```
2. **清除缓存并重新安装**
```bash
cd frontend
npm cache clean --force
rm -rf node_modules
npm install
```
3. **在HBuilderX中运行**
- 不要使用命令行运行
- 直接在HBuilderX中运行 → 运行到浏览器
- HBuilderX会自动处理依赖
4. **检查node_modules**
- 确保 `frontend/node_modules/vue` 目录存在
- 如果不存在,执行 `npm install vue`
5. **如果仍然报错**
- 尝试删除 `vite.config.js`HBuilderX项目可能不需要
- 或者简化 `vite.config.js` 配置
## 常见问题
### Q: HBuilderX提示缺少index.html
A: 已创建 `index.html` 文件,如果还有问题,检查文件是否在根目录。
### Q: 依赖安装失败?
A: 对于HBuilderX项目通常不需要手动安装uni-app相关依赖只需要安装业务依赖vue、pinia
### Q: API请求失败
A: 确保后端服务已启动,检查 `utils/request.js` 中的API地址配置。

20
api/auth.js Normal file
View File

@ -0,0 +1,20 @@
import { post } from '../utils/request'
// 用户注册
export function register(data) {
return post('/auth/register', data)
}
// 用户登录
export function login(data) {
return post('/auth/login', data)
}

35
api/bill.js Normal file
View File

@ -0,0 +1,35 @@
import { get, post, put, del } from '../utils/request'
// 获取账单列表
export function getBills(params) {
return get('/bills', params)
}
// 获取账单详情
export function getBill(id) {
return get(`/bills/${id}`)
}
// 创建账单
export function createBill(data) {
return post('/bills', data)
}
// 更新账单
export function updateBill(id, data) {
return put(`/bills/${id}`, data)
}
// 删除账单
export function deleteBill(id) {
return del(`/bills/${id}`)
}

30
api/category.js Normal file
View File

@ -0,0 +1,30 @@
import { get, post, put, del } from '../utils/request'
// 获取分类列表
export function getCategories(type) {
return get('/categories', type ? { type } : {})
}
// 创建分类
export function createCategory(data) {
return post('/categories', data)
}
// 更新分类
export function updateCategory(id, data) {
return put(`/categories/${id}`, data)
}
// 删除分类
export function deleteCategory(id) {
return del(`/categories/${id}`)
}

15
api/ocr.js Normal file
View File

@ -0,0 +1,15 @@
import { upload } from '../utils/request'
// OCR识别
export function recognizeImage(filePath) {
return upload('/ocr/recognize', filePath)
}

25
api/statistics.js Normal file
View File

@ -0,0 +1,25 @@
import { get } from '../utils/request'
// 按日期范围统计
export function getDailyStatistics(startDate, endDate) {
return get('/statistics/daily', { startDate, endDate })
}
// 按周统计
export function getWeeklyStatistics(weekStart) {
return get('/statistics/weekly', { weekStart })
}
// 按月统计
export function getMonthlyStatistics(year, month) {
return get('/statistics/monthly', { year, month })
}

120
common/uni-ui.scss Normal file
View File

@ -0,0 +1,120 @@
.uni-flex {
display: flex;
}
.uni-flex-row {
@extend .uni-flex;
flex-direction: row;
box-sizing: border-box;
}
.uni-flex-column {
@extend .uni-flex;
flex-direction: column;
}
.uni-color-gary {
color: #3b4144;
}
/* 标题 */
.uni-title {
display: flex;
margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-lg;
font-weight: bold;
color: #3b4144;
}
.uni-title-sub {
display: flex;
// margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-base;
font-weight: 500;
color: #3b4144;
}
/* 描述 额外文本 */
.uni-note {
margin-top: 10px;
color: #999;
font-size: $uni-font-size-sm;
}
/* 列表内容 */
.uni-list-box {
@extend .uni-flex-row;
flex: 1;
margin-top: 10px;
}
/* 略缩图 */
.uni-thumb {
flex-shrink: 0;
margin-right: $uni-spacing-row-base;
width: 125px;
height: 75px;
border-radius: $uni-border-radius-lg;
overflow: hidden;
border: 1px #f5f5f5 solid;
image {
width: 100%;
height: 100%;
}
}
.uni-media-box {
@extend .uni-flex-row;
// margin-bottom: $uni-spacing-col-base;
border-radius: $uni-border-radius-lg;
overflow: hidden;
.uni-thumb {
margin: 0;
margin-left: 4px;
flex-shrink: 1;
width: 33%;
border-radius:0;
&:first-child {
margin: 0;
}
}
}
/* 内容 */
.uni-content {
@extend .uni-flex-column;
justify-content: space-between;
}
/* 列表footer */
.uni-footer {
@extend .uni-flex-row;
justify-content: space-between;
margin-top: $uni-spacing-col-lg;
}
.uni-footer-text {
font-size: $uni-font-size-sm;
color: $uni-text-color-grey;
margin-left: 5px;
}
/* 标签 */
.uni-tag {
flex-shrink: 0;
padding: 0 5px;
border: 1px $uni-border-color solid;
margin-right: $uni-spacing-row-sm;
border-radius: $uni-border-radius-base;
background: $uni-bg-color-grey;
color: $uni-text-color;
font-size: $uni-font-size-sm;
}
/* 链接 */
.uni-link {
margin-left: 10px;
color: $uni-text-color;
text-decoration: underline;
}

View File

@ -0,0 +1,12 @@
{
"id": "99999",
"name": "Section",
"desc": "标题栏",
"edition": "0.0.1",
"url": "section",
"type": "布局组件",
"path": "https://ext.dcloud.net.cn/plugin?id=",
"hidden": true,
"test":true,
"update_log": []
}

View File

@ -0,0 +1,30 @@
### Section 标题栏
标题栏,用于显示标题,组件名:``uni-section``,代码块: uSection。
### 使用方式
在 ``script`` 中引用组件
```javascript
import uniSection from "@/components/uni-section/uni-section.vue"
export default {
components: {uniSection}
}
```
在 ``template`` 中使用组件
```html
<uni-section title="只有主标题"></uni-section>
<uni-section title="竖线装饰" sub-title="副标题" type="line"></uni-section>
<uni-section title="圆形装饰" sub-title="副标题" type="circle"></uni-section>
```
### 属性说明
|属性名 |类型 |默认值 |说明 |
|--- |---- |--- |--- |
|type |String |- |标题装饰类型 可选值line竖线、circle圆形|
|title |String |- |主标题 |
|sub-title |String |- |副标题 |

View File

@ -0,0 +1,136 @@
<template>
<view class="uni-section" nvue>
<view v-if="type" class="uni-section__head">
<view :class="type" class="uni-section__head-tag" />
</view>
<view class="uni-section__content">
<text :class="{'distraction':!subTitle}" class="uni-section__content-title">{{ title }}</text>
<text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text>
</view>
<slot />
</view>
</template>
<script>
/**
* Section 标题栏
* @description 标题栏
* @property {String} type = [line|circle] 标题装饰类型
* @value line 竖线
* @value circle 圆形
* @property {String} title 主标题
* @property {String} subTitle 副标题
*/
export default {
name: 'UniSection',
props: {
type: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
}
},
data() {
return {}
},
watch: {
title(newVal) {
if (uni.report && newVal !== '') {
uni.report('title', newVal)
}
}
},
methods: {
onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
.uni-section {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
margin-top: 10px;
flex-direction: row;
align-items: center;
padding: 0 10px;
height: 50px;
background-color: $uni-bg-color-grey;
/* #ifdef APP-NVUE */
// border-bottom-color: $uni-border-color;
// border-bottom-style: solid;
// border-bottom-width: 0.5px;
/* #endif */
font-weight: normal;
}
/* #ifndef APP-NVUE */
// .uni-section:after {
// position: absolute;
// bottom: 0;
// right: 0;
// left: 0;
// height: 1px;
// content: '';
// -webkit-transform: scaleY(.5);
// transform: scaleY(.5);
// background-color: $uni-border-color;
// }
/* #endif */
.uni-section__head {
flex-direction: row;
justify-content: center;
align-items: center;
margin-right: 10px;
}
.line {
height: 15px;
background-color: $uni-text-color-disable;
border-radius: 5px;
width: 3px;
}
.circle {
width: 8px;
height: 8px;
border-top-right-radius: 50px;
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
background-color: $uni-text-color-disable;
}
.uni-section__content {
flex-direction: column;
flex: 1;
color: $uni-text-color;
}
.uni-section__content-title {
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.distraction {
flex-direction: row;
align-items: center;
}
.uni-section__content-sub {
font-size: $uni-font-size-sm;
color: $uni-text-color-grey;
}
</style>

26
index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>强宝爱记账</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
background-color: #f5f5f5;
}
#app {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.js"></script>
</body>
</html>

12
main.js Normal file
View File

@ -0,0 +1,12 @@
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
return {
app
}
}

71
manifest.json Normal file
View File

@ -0,0 +1,71 @@
{
"name": "强宝爱记账",
"appid": "__UNI__ACCOUNTING",
"description": "智能记账应用支持OCR识别账单",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {},
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>"
]
},
"ios": {},
"sdkConfigs": {}
}
},
"quickapp": {},
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3",
"h5": {
"router": {
"mode": "hash"
}
}
}

906
package-lock.json generated Normal file
View File

@ -0,0 +1,906 @@
{
"name": "accounting-app",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "accounting-app",
"version": "1.0.0",
"dependencies": {
"@dcloudio/uni-app": "^2.0.2-4080720251210002",
"@dcloudio/uni-h5": "^2.0.2-4080720251210002",
"pinia": "^2.3.1",
"vue": "^3.5.25"
},
"devDependencies": {
"@types/node": "^24.10.2",
"@vitejs/plugin-vue": "^4.0.0",
"typescript": "^5.2.2",
"vite": "^4.5.14"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@dcloudio/uni-app": {
"version": "2.0.2-4080720251210002",
"resolved": "https://registry.npmmirror.com/@dcloudio/uni-app/-/uni-app-2.0.2-4080720251210002.tgz",
"integrity": "sha512-FUw/bJJwBPl/scKBog21wusICrU9T4TFwyKKVbtNbWIO98IBzjRIin0mmwzhqHNL1kZO0LbwnoRe7LtusW8LQw==",
"peerDependencies": {
"@dcloudio/types": "^3.0.15",
"@vue/composition-api": "^1.7.0"
}
},
"node_modules/@dcloudio/uni-h5": {
"version": "2.0.2-4080720251210002",
"resolved": "https://registry.npmmirror.com/@dcloudio/uni-h5/-/uni-h5-2.0.2-4080720251210002.tgz",
"integrity": "sha512-yBYX2rvX+03eVjkmzNfWetMW5jjhz+7B643Otjs7IOp+KXVSpslAwLO3F5kKhaBQQXGFJ9Tn7f4DEtrQZkAxKg==",
"dependencies": {
"base64-arraybuffer": "^0.2.0",
"intersection-observer": "^0.7.0",
"pako": "^1.0.11",
"postcss-urlrewrite": "^0.3.0",
"safe-area-insets": "^1.4.1"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
},
"node_modules/@types/node": {
"version": "24.10.2",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.2.tgz",
"integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==",
"dev": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz",
"integrity": "sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^4.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"dependencies": {
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz",
"integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.25",
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
"integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/reactivity": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz",
"integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
"dependencies": {
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz",
"integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz",
"integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/runtime-core": "3.5.25",
"@vue/shared": "3.5.25",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz",
"integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
"dependencies": {
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"vue": "3.5.25"
}
},
"node_modules/@vue/shared": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg=="
},
"node_modules/base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/intersection-observer": {
"version": "0.7.0",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.7.0.tgz",
"integrity": "sha512-Id0Fij0HsB/vKWGeBe9PxeY45ttRiBmhFyyt/geBdDHBYNctMRTE3dC1U3ujzz3lap+hVXlEcVaB56kZP/eEUg==",
"deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019."
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/pinia": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
"dependencies": {
"@vue/devtools-api": "^6.6.3",
"vue-demi": "^0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.4.4",
"vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-helpers": {
"version": "0.3.3",
"resolved": "https://registry.npmmirror.com/postcss-helpers/-/postcss-helpers-0.3.3.tgz",
"integrity": "sha512-VumiUcrpbxGlTBNQj6fUOkb/HNRUk/xYz8bNlhgVOdvk3yWEy4B+0nlDUZZM9mTVZ5bJoxUy7WT6z/4E7oMTgw==",
"dependencies": {
"urijs": "^1.18.12"
},
"engines": {
"node": ">=0.12.9"
}
},
"node_modules/postcss-urlrewrite": {
"version": "0.3.0",
"resolved": "https://registry.npmmirror.com/postcss-urlrewrite/-/postcss-urlrewrite-0.3.0.tgz",
"integrity": "sha512-504S/dMa7a0n1yghE2I6fxY/DfMUM+w9qsFaoYnXE8KsCofmKLlA7PKbR+wtdEJ0N00Z1lGYxX/oFz13xArcLQ==",
"dependencies": {
"postcss-helpers": "^0.3.3"
},
"engines": {
"node": ">=16"
}
},
"node_modules/rollup": {
"version": "3.29.5",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.29.5.tgz",
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/safe-area-insets": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/safe-area-insets/-/safe-area-insets-1.4.1.tgz",
"integrity": "sha512-r/nRWTjFGhhm3w1Z6Kd/jY11srN+lHt2mNl1E/emQGW8ic7n3Avu4noibklfSM+Y34peNphHD/BSZecav0sXYQ=="
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true
},
"node_modules/urijs": {
"version": "1.19.11",
"resolved": "https://registry.npmmirror.com/urijs/-/urijs-1.19.11.tgz",
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="
},
"node_modules/vite": {
"version": "4.5.14",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.5.14.tgz",
"integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.25",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-sfc": "3.5.25",
"@vue/runtime-dom": "3.5.25",
"@vue/server-renderer": "3.5.25",
"@vue/shared": "3.5.25"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
}
}
}

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "accounting-app",
"version": "1.0.0",
"description": "强宝爱记账",
"main": "main.js",
"scripts": {
"dev:h5": "npm run serve",
"serve": "uni serve",
"build:h5": "uni build"
},
"dependencies": {
"@dcloudio/uni-app": "^2.0.2-4080720251210002",
"@dcloudio/uni-h5": "^2.0.2-4080720251210002",
"pinia": "^2.3.1",
"vue": "^3.5.25"
},
"devDependencies": {
"@types/node": "^24.10.2",
"typescript": "^5.2.2"
}
}

74
pages.json Normal file
View File

@ -0,0 +1,74 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
"pages": [
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/add/add",
"style": {
"navigationBarTitleText": "添加账单"
}
},
{
"path": "pages/statistics/statistics",
"style": {
"navigationBarTitleText": "统计"
}
},
{
"path": "pages/bill/detail",
"style": {
"navigationBarTitleText": "编辑账单"
}
}
],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#667eea",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/add/add",
"iconPath": "static/tabbar/add.png",
"selectedIconPath": "static/tabbar/add-active.png",
"text": "记账"
},
{
"pagePath": "pages/statistics/statistics",
"iconPath": "static/tabbar/statistics.png",
"selectedIconPath": "static/tabbar/statistic-active.png",
"text": "统计"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "强宝爱记账",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}

895
pages/add/add.vue Normal file
View File

@ -0,0 +1,895 @@
<template>
<scroll-view scroll-y class="page-scroll" :scroll-top="pageScrollTop">
<view class="container">
<view class="tabs">
<view class="tab" :class="{ active: activeTab === 'manual' }" @click="activeTab = 'manual'">
手动记账
</view>
<view class="tab" :class="{ active: activeTab === 'ocr' }" @click="activeTab = 'ocr'">
OCR识别
</view>
</view>
<!-- 手动记账 -->
<view v-if="activeTab === 'manual'" class="form">
<view class="form-item">
<text class="label">账单类型</text>
<view class="type-selector-compact">
<view
class="type-option-compact"
:class="{ active: form.type === 1 }"
@click="form.type = 1"
>
<text class="type-icon-compact">📤</text>
<text class="type-text-compact">支出</text>
</view>
<view
class="type-option-compact"
:class="{ active: form.type === 2 }"
@click="form.type = 2"
>
<text class="type-icon-compact">📥</text>
<text class="type-text-compact">收入</text>
</view>
</view>
</view>
<view class="form-item">
<text class="label">分类</text>
<view v-if="categories.length > 0" class="category-grid">
<view
v-for="category in categories"
:key="category.id"
class="category-icon-item"
:class="{ active: selectedCategory?.id === category.id }"
@click="selectCategory(category)"
>
<view class="category-icon-circle">
<text class="category-icon-emoji">{{ category.icon || '📦' }}</text>
</view>
<text class="category-icon-name">{{ category.name }}</text>
</view>
</view>
<view v-else class="category-empty">
<text>暂无分类请先创建分类</text>
</view>
</view>
<view class="form-item">
<text class="label">金额</text>
<input
v-model="form.amount"
type="number"
inputmode="decimal"
placeholder="请输入金额"
@input="onAmountInput"
@focus="onAmountFocus"
/>
</view>
<view class="form-item">
<text class="label">日期</text>
<picker mode="date" v-model="form.billDate" @change="onDateChange">
<view class="picker">
<text>{{ form.billDate || '选择日期' }}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<text class="label">备注</text>
<textarea v-model="form.description" placeholder="请输入备注(可选)" class="textarea" />
</view>
<button class="submit-btn" @click="submitBill">保存</button>
</view>
<!-- OCR识别 -->
<view v-if="activeTab === 'ocr'" class="ocr-container">
<!-- 图片预览区域可折叠 -->
<view class="image-section">
<view class="section-header" @click="toggleImageSection">
<text class="section-title">图片预览</text>
<text class="expand-icon">{{ imageSectionExpanded ? '▼' : '▶' }}</text>
</view>
<view v-if="imageSectionExpanded" class="section-content">
<view class="upload-area" @click="chooseImage">
<image v-if="imagePath" :src="imagePath" class="preview-image" mode="aspectFit" />
<view v-else class="upload-placeholder">
<text class="upload-icon">📷</text>
<text>点击选择图片</text>
</view>
</view>
</view>
</view>
<button v-if="imagePath && ocrResultList.length === 0" class="recognize-btn" @click="recognizeImage" :loading="recognizing">
识别账单
</button>
<!-- OCR识别结果列表可折叠 -->
<view v-if="ocrResultList.length > 0" class="ocr-result-list-container">
<view class="section-header" @click="toggleResultList">
<text class="list-title">识别到 {{ ocrResultList.length }} 条账单记录</text>
<text class="expand-icon">{{ resultListExpanded ? '▼' : '▶' }}</text>
</view>
<view v-if="resultListExpanded" class="section-content">
<view class="result-list-wrapper">
<view v-for="(item, index) in ocrResultList" :key="index" class="result-card" :class="{ active: selectedOcrResult === item, expanded: expandedCards.has(index) }">
<view class="result-header" @click.stop="toggleCard(index)">
<view class="header-left">
<text class="result-index">#{{ index + 1 }}</text>
<text class="result-value amount summary">¥{{ item.amount }}</text>
</view>
<view class="header-right">
<text v-if="item.confidence" class="result-confidence">置信度: {{ (item.confidence * 100).toFixed(1) }}%</text>
<text class="expand-icon">{{ expandedCards.has(index) ? '▼' : '▶' }}</text>
</view>
</view>
<view v-if="expandedCards.has(index)" class="result-details">
<view class="result-item">
<text class="result-label">金额</text>
<text class="result-value amount">¥{{ item.amount ? Math.abs(item.amount).toFixed(2) : '0.00' }}</text>
</view>
<view v-if="item.merchant" class="result-item">
<text class="result-label">商户</text>
<text class="result-value">{{ item.merchant }}</text>
</view>
<view v-if="item.date" class="result-item">
<text class="result-label">日期</text>
<text class="result-value">{{ formatOcrDate(item.date) }}</text>
</view>
<button class="select-btn" @click.stop="goToManualWithOcrData(item)">选择此条</button>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { createBill } from '../../api/bill'
import { getCategories } from '../../api/category'
import { recognizeImage as ocrRecognize } from '../../api/ocr'
import { formatDate } from '../../utils/date'
const activeTab = ref('manual')
const categories = ref([])
const selectedCategory = ref(null)
const imagePath = ref('')
const recognizing = ref(false)
const ocrResultList = ref([])
const selectedOcrResult = ref(null)
const expandedCards = ref(new Set()) //
const imageSectionExpanded = ref(true) //
const resultListExpanded = ref(true) //
const pageScrollTop = ref(0) //
const form = ref({
type: 1, // 1-2-
categoryId: null,
amount: '',
description: '',
billDate: formatDate(new Date()),
imageUrl: ''
})
const loadCategories = async () => {
try {
// 1-2-
const type = form.value.type || 1
const data = await getCategories(type)
categories.value = data || []
//
if (selectedCategory.value && selectedCategory.value.type !== type) {
selectedCategory.value = null
form.value.categoryId = null
}
} catch (error) {
console.error('加载分类失败', error)
}
}
//
watch(() => form.value.type, (newType) => {
if (newType) {
loadCategories()
}
})
//
watch(() => form.value.type, (newType) => {
if (newType) {
loadCategories()
}
})
const chooseImage = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera', 'album'],
success: (res) => {
imagePath.value = res.tempFilePaths[0]
ocrResultList.value = []
selectedOcrResult.value = null
expandedCards.value.clear()
form.value.amount = ''
}
})
}
const recognizeImage = async () => {
if (!imagePath.value) {
uni.showToast({
title: '请先选择图片',
icon: 'none'
})
return
}
recognizing.value = true
try {
const resultList = await ocrRecognize(imagePath.value)
//
ocrResultList.value = Array.isArray(resultList) ? resultList : [resultList]
if (ocrResultList.value.length === 0) {
uni.showToast({
title: '未识别到账单信息',
icon: 'none'
})
} else {
uni.showToast({
title: `识别到 ${ocrResultList.value.length} 条记录`,
icon: 'success'
})
//
if (ocrResultList.value.length === 1) {
expandedCards.value.add(0)
selectOcrResult(ocrResultList.value[0])
} else if (ocrResultList.value.length > 0) {
//
expandedCards.value.add(0)
}
}
} catch (error) {
console.error('OCR识别失败:', error)
uni.showToast({
title: error.message || '识别失败,请重试',
icon: 'none'
})
} finally {
recognizing.value = false
}
}
const scrollTop = ref(0)
const toggleImageSection = () => {
imageSectionExpanded.value = !imageSectionExpanded.value
}
const toggleResultList = () => {
resultListExpanded.value = !resultListExpanded.value
}
const toggleCard = (index) => {
if (expandedCards.value.has(index)) {
expandedCards.value.delete(index)
} else {
expandedCards.value.add(index)
}
}
const goToManualWithOcrData = (item) => {
//
// -> type: 1 -> type: 2
const amount = parseFloat(item.amount) || 0
const billType = amount < 0 ? 1 : 2 // 1-2-
// OCR
const ocrData = {
amount: Math.abs(amount).toString(), //
billDate: item.date ? formatOcrDate(item.date) : formatDate(new Date()),
description: item.merchant || '',
type: billType, //
fromOcr: true
}
uni.setStorageSync('ocrFormData', ocrData)
//
activeTab.value = 'manual'
//
form.value.amount = ocrData.amount
form.value.billDate = ocrData.billDate
form.value.description = ocrData.description
form.value.type = billType //
//
loadCategories()
uni.showToast({
title: `已填充到手动记账(${billType === 1 ? '支出' : '收入'}`,
icon: 'success'
})
}
const formatOcrDate = (dateStr) => {
if (!dateStr) return formatDate(new Date())
// YYYY-MM-DD
if (typeof dateStr === 'string' && dateStr.match(/^\d{4}-\d{2}-\d{2}/)) {
return dateStr.split('T')[0] // LocalDateTime
}
//
try {
const date = new Date(dateStr)
return formatDate(date)
} catch (e) {
return formatDate(new Date())
}
}
const selectCategory = (category) => {
selectedCategory.value = category
form.value.categoryId = category.id
}
const onDateChange = (e) => {
form.value.billDate = e.detail.value
}
const onAmountInput = (e) => {
//
let value = e.detail.value || ''
//
value = value.replace(/[^\d.]/g, '')
//
const parts = value.split('.')
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('')
}
// 2
if (parts.length === 2 && parts[1].length > 2) {
value = parts[0] + '.' + parts[1].substring(0, 2)
}
form.value.amount = value
}
const onAmountFocus = () => {
//
console.log('金额输入框聚焦')
}
const submitBill = async () => {
if (!form.value.type) {
uni.showToast({
title: '请选择账单类型',
icon: 'none'
})
return
}
if (!form.value.categoryId) {
uni.showToast({
title: '请选择分类',
icon: 'none'
})
return
}
if (!form.value.amount || parseFloat(form.value.amount) <= 0) {
uni.showToast({
title: '请输入有效金额',
icon: 'none'
})
return
}
try {
await createBill({
type: form.value.type,
categoryId: form.value.categoryId,
amount: parseFloat(form.value.amount),
description: form.value.description,
billDate: form.value.billDate,
imageUrl: form.value.imageUrl
})
uni.showToast({
title: '保存成功',
icon: 'success'
})
//
form.value = {
type: 1,
categoryId: null,
amount: '',
description: '',
billDate: formatDate(new Date()),
imageUrl: ''
}
selectedCategory.value = null
ocrResultList.value = []
selectedOcrResult.value = null
expandedCards.value.clear()
imagePath.value = ''
//
loadCategories()
//
uni.$emit('refreshBills')
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
} catch (error) {
console.error('保存失败:', error)
uni.showToast({
title: error.message || '保存失败',
icon: 'none'
})
}
}
onMounted(() => {
loadCategories()
// OCR
const ocrFormData = uni.getStorageSync('ocrFormData')
if (ocrFormData && ocrFormData.fromOcr) {
form.value.amount = ocrFormData.amount || ''
form.value.billDate = ocrFormData.billDate || formatDate(new Date())
form.value.description = ocrFormData.description || ''
//
uni.removeStorageSync('ocrFormData')
//
activeTab.value = 'manual'
}
})
</script>
<style scoped>
.page-scroll {
height: 100vh;
width: 100%;
padding-bottom: calc(50px + env(safe-area-inset-bottom));
}
.container {
min-height: calc(100vh - 100rpx);
background: #f5f5f5;
padding: 20rpx;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
.tabs {
display: flex;
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
padding: 10rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 8rpx;
color: #666;
font-size: 28rpx;
}
.tab.active {
background: #667eea;
color: #fff;
}
.form {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.type-selector-compact {
display: flex;
gap: 15rpx;
justify-content: center;
}
.type-option-compact {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15rpx 30rpx;
background: #f8f8f8;
border-radius: 8rpx;
border: 2rpx solid transparent;
transition: all 0.3s;
min-width: 120rpx;
}
.type-option-compact.active {
background: #f0f7ff;
border-color: #667eea;
}
.type-icon-compact {
font-size: 32rpx;
margin-bottom: 5rpx;
}
.type-text-compact {
font-size: 24rpx;
color: #333;
}
.type-option-compact.active .type-text-compact {
color: #667eea;
font-weight: bold;
}
.category-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 10rpx;
}
.category-icon-item {
display: flex;
flex-direction: column;
align-items: center;
width: calc(25% - 15rpx);
margin-bottom: 10rpx;
}
.category-icon-circle {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
border: 3rpx solid transparent;
transition: all 0.3s;
}
.category-icon-item.active .category-icon-circle {
background: #f0f7ff;
border-color: #667eea;
transform: scale(1.1);
}
.category-icon-emoji {
font-size: 48rpx;
line-height: 1;
}
.category-icon-name {
font-size: 22rpx;
color: #666;
text-align: center;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.category-icon-item.active .category-icon-name {
color: #667eea;
font-weight: bold;
}
.category-empty {
text-align: center;
padding: 40rpx 0;
color: #999;
font-size: 26rpx;
}
.input, .textarea {
width: 100%;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
border: none;
outline: none;
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
pointer-events: auto;
user-select: text;
-webkit-user-select: text;
}
.textarea {
min-height: 200rpx;
}
.category-selector, .picker {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
}
.placeholder {
color: #999;
}
.arrow {
color: #999;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #667eea;
color: #fff;
border-radius: 12rpx;
font-size: 32rpx;
margin-top: 40rpx;
margin-bottom: 20rpx;
border: none;
}
.ocr-container {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
}
.image-section {
margin-bottom: 30rpx;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f8f8f8;
border-radius: 12rpx;
cursor: pointer;
user-select: none;
transition: background 0.3s;
}
.section-header:active {
background: #f0f0f0;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.section-content {
padding: 20rpx 0;
animation: slideDown 0.3s ease-out;
}
.upload-area {
width: 100%;
height: 400rpx;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
}
.preview-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.upload-placeholder {
text-align: center;
color: #999;
display: flex;
flex-direction: column;
align-items: center;
}
.upload-icon {
font-size: 80rpx;
display: block;
margin-bottom: 20rpx;
}
.recognize-btn {
width: 100%;
height: 88rpx;
background: #19be6b;
color: #fff;
border-radius: 12rpx;
font-size: 32rpx;
margin-bottom: 30rpx;
border: none;
}
.ocr-result-list-container {
margin-top: 30rpx;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.list-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.result-list-wrapper {
max-height: 600rpx;
overflow-y: auto;
padding: 0 20rpx 20rpx 20rpx;
-webkit-overflow-scrolling: touch;
}
.result-card {
background: #fff;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
transition: all 0.3s;
overflow: hidden;
}
.result-card.active {
border-color: #667eea;
background: #f0f7ff;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
user-select: none;
}
.header-left {
display: flex;
align-items: center;
flex: 1;
}
.header-right {
display: flex;
align-items: center;
gap: 15rpx;
}
.result-index {
font-size: 24rpx;
color: #667eea;
font-weight: bold;
margin-right: 15rpx;
}
.result-value.amount.summary {
font-size: 30rpx;
font-weight: bold;
color: #fa3534;
}
.result-confidence {
font-size: 22rpx;
color: #999;
}
.expand-icon {
font-size: 24rpx;
color: #667eea;
transition: transform 0.3s;
margin-left: 10rpx;
}
.result-card.expanded .expand-icon {
transform: rotate(90deg);
}
.result-details {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
max-height: 0;
}
to {
opacity: 1;
max-height: 500rpx;
}
}
.result-item {
display: flex;
padding: 10rpx 0;
align-items: center;
}
.result-label {
font-size: 26rpx;
color: #666;
width: 100rpx;
flex-shrink: 0;
}
.result-value {
font-size: 28rpx;
color: #333;
flex: 1;
}
.result-value.amount {
font-weight: bold;
color: #fa3534;
font-size: 32rpx;
}
.select-btn {
width: 100%;
height: 70rpx;
background: #667eea;
color: #fff;
border-radius: 8rpx;
font-size: 26rpx;
margin-top: 20rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.ocr-result {
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 2rpx solid #667eea;
}
</style>

158
pages/add/list/list.vue Normal file
View File

@ -0,0 +1,158 @@
<template>
<!--
本页面模板教程https://ext.dcloud.net.cn/plugin?id=2588
uni-list 文档https://ext.dcloud.net.cn/plugin?id=24
uniCloud 文档https://uniapp.dcloud.io/uniCloud/README
unicloud-db 组件文档https://uniapp.dcloud.io/uniCloud/unicloud-db
DB Schema 规范https://uniapp.dcloud.net.cn/uniCloud/schema
-->
<view>
<!-- 刷新页面后的顶部提示框 -->
<view class="tips" :class="{ 'tips-ani': tipShow }">为您更新了10条最新新闻动态</view>
<unicloud-db ref="udb" v-slot:default="{data, loading, error, options}" :options="formData" :collection="collection"
:field="field" @load="load">
<!-- 基于 uni-list 的页面布局 -->
<uni-list>
<!-- to 属性携带参数跳转详情页面当前只为参考 -->
<uni-list-item direction="column" v-for="item in data" :key="item.id" :to="'/pages/detail/detail?id='+item._id+'&title='+item.title">
<!-- 通过header插槽定义列表的标题 -->
<template v-slot:header>
<view class="uni-title">{{item.title}}</view>
</template>
<!-- 通过body插槽定义列表内容显示 -->
<template v-slot:body>
<view class="uni-list-box">
<view class="uni-content">
<view class="uni-title-sub uni-ellipsis-2">{{item.excerpt}}</view>
<view class="uni-note">{{item.user_name + ' '+item.last_modify_date}}</view>
</view>
</view>
</template>
<!-- 同步footer插槽定义列表底部的显示效果 -->
<template v-slot:footer>
<view class="uni-footer">
<text class="uni-footer-text">评论</text>
<text class="uni-footer-text">点赞</text>
<text class="uni-footer-text">分享</text>
</view>
</template>
</uni-list-item>
</uni-list>
<!-- 通过 loadMore 组件实现上拉加载效果如需自定义显示内容可参考https://ext.dcloud.net.cn/plugin?id=29 -->
<uni-load-more v-if="loading || options.status === 'noMore' " :status="options.status" />
</unicloud-db>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
//
collection: 'opendb-news-articles',
// ,
field: '_id,mode,avatar,title,user_name,excerpt,last_modify_date',
formData: {
status: 'loading' //
},
tipShow: false //
};
},
onLoad() {},
methods: {
load(data, ended) {
if (ended) {
this.formData.status = 'noMore'
}
}
},
/**
* 下拉刷新回调函数
*/
onPullDownRefresh() {
this.formData.status = 'more'
this.$refs.udb.loadData({
clear: true
}, () => {
this.tipShow = true
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.tipShow = false
},1000)
uni.stopPullDownRefresh()
})
},
/**
* 上拉加载回调函数
*/
onReachBottom() {
this.$refs.udb.loadMore()
}
};
</script>
<style lang="scss">
@import '@/common/uni-ui.scss';
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #efeff4;
min-height: 100%;
height: auto;
}
.tips {
color: #67c23a;
font-size: 14px;
line-height: 40px;
text-align: center;
background-color: #f0f9eb;
height: 0;
opacity: 0;
transform: translateY(-100%);
transition: all 0.3s;
}
.tips-ani {
transform: translateY(0);
height: 40px;
opacity: 1;
}
.content {
width: 100%;
display: flex;
}
.list-picture {
width: 100%;
height: 145px;
}
.thumb-image {
width: 100%;
height: 100%;
}
.ellipsis {
display: flex;
overflow: hidden;
}
.uni-ellipsis-1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.uni-ellipsis-2 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>

492
pages/bill/detail.vue Normal file
View File

@ -0,0 +1,492 @@
<template>
<scroll-view scroll-y class="page-scroll">
<view class="container">
<view class="form-card">
<view class="form-header">
<text class="form-title">编辑账单</text>
</view>
<view class="form-body">
<view class="form-item">
<text class="label">账单类型</text>
<view class="type-selector-compact">
<view
class="type-option-compact"
:class="{ active: form.type === 1 }"
@click="form.type = 1; loadCategories()"
>
<text class="type-icon-compact">📤</text>
<text class="type-text-compact">支出</text>
</view>
<view
class="type-option-compact"
:class="{ active: form.type === 2 }"
@click="form.type = 2; loadCategories()"
>
<text class="type-icon-compact">📥</text>
<text class="type-text-compact">收入</text>
</view>
</view>
</view>
<view class="form-item">
<text class="label">分类</text>
<view v-if="categories.length > 0" class="category-grid">
<view
v-for="category in categories"
:key="category.id"
class="category-icon-item"
:class="{ active: selectedCategory?.id === category.id }"
@click="selectCategory(category)"
>
<view class="category-icon-circle">
<text class="category-icon-emoji">{{ category.icon || '📦' }}</text>
</view>
<text class="category-icon-name">{{ category.name }}</text>
</view>
</view>
<view v-else class="category-empty">
<text>暂无分类</text>
</view>
</view>
<view class="form-item">
<text class="label">金额</text>
<input
v-model="form.amount"
type="text"
inputmode="decimal"
placeholder="请输入金额"
class="input"
@input="onAmountInput"
/>
</view>
<view class="form-item">
<text class="label">日期</text>
<picker mode="date" v-model="form.billDate" @change="onDateChange">
<view class="picker">
<text>{{ form.billDate || '选择日期' }}</text>
<text class="arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<text class="label">备注</text>
<textarea v-model="form.description" placeholder="请输入备注(可选)" class="textarea" />
</view>
</view>
<view class="form-footer">
<button class="delete-btn" @click="handleDelete">删除账单</button>
<button class="submit-btn" @click="handleUpdate">保存修改</button>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { getBill, updateBill as updateBillApi, deleteBill as deleteBillApi } from '../../api/bill'
import { getCategories } from '../../api/category'
import { formatDate } from '../../utils/date'
import { onLoad } from '@dcloudio/uni-app'
const billId = ref(null)
const categories = ref([])
const selectedCategory = ref(null)
const form = ref({
type: 1,
categoryId: null,
amount: '',
description: '',
billDate: formatDate(new Date())
})
const loadCategories = async () => {
try {
const type = form.value.type || 1
const data = await getCategories(type)
categories.value = data || []
//
if (selectedCategory.value && selectedCategory.value.type !== type) {
selectedCategory.value = null
form.value.categoryId = null
} else if (selectedCategory.value) {
//
const category = categories.value.find(cat => cat.id === selectedCategory.value.id)
if (category) {
selectedCategory.value = category
}
}
} catch (error) {
console.error('加载分类失败', error)
}
}
const loadBill = async (id) => {
try {
const data = await getBill(id)
if (data) {
form.value = {
type: data.type || 1,
categoryId: data.categoryId,
amount: Math.abs(data.amount || 0).toString(),
description: data.description || '',
billDate: formatDate(data.billDate || new Date())
}
//
await loadCategories()
const category = categories.value.find(cat => cat.id === data.categoryId)
if (category) {
selectedCategory.value = category
}
}
} catch (error) {
console.error('加载账单失败', error)
uni.showToast({
title: '加载账单失败',
icon: 'none'
})
}
}
const selectCategory = (category) => {
selectedCategory.value = category
form.value.categoryId = category.id
}
const onDateChange = (e) => {
form.value.billDate = e.detail.value
}
const onAmountInput = (e) => {
let value = e.detail.value || ''
value = value.replace(/[^\d.]/g, '')
const parts = value.split('.')
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('')
}
if (parts.length === 2 && parts[1].length > 2) {
value = parts[0] + '.' + parts[1].substring(0, 2)
}
form.value.amount = value
}
const handleUpdate = async () => {
if (!form.value.type) {
uni.showToast({
title: '请选择账单类型',
icon: 'none'
})
return
}
if (!form.value.categoryId) {
uni.showToast({
title: '请选择分类',
icon: 'none'
})
return
}
if (!form.value.amount || parseFloat(form.value.amount) <= 0) {
uni.showToast({
title: '请输入有效金额',
icon: 'none'
})
return
}
try {
await updateBillApi(billId.value, {
type: form.value.type,
categoryId: form.value.categoryId,
amount: parseFloat(form.value.amount),
description: form.value.description,
billDate: form.value.billDate
})
uni.showToast({
title: '更新成功',
icon: 'success'
})
//
uni.$emit('refreshBills')
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('更新失败', error)
uni.showToast({
title: error.message || '更新失败',
icon: 'none'
})
}
}
const handleDelete = () => {
uni.showModal({
title: '确认删除',
content: '确定要删除这笔账单吗?删除后无法恢复',
confirmColor: '#fa3534',
success: async (res) => {
if (res.confirm) {
try {
await deleteBillApi(billId.value)
uni.showToast({
title: '删除成功',
icon: 'success'
})
//
uni.$emit('refreshBills')
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
console.error('删除失败', error)
uni.showToast({
title: error.message || '删除失败',
icon: 'none'
})
}
}
}
})
}
//
watch(() => form.value.type, (newType) => {
if (newType && billId.value) {
loadCategories()
}
})
onLoad((options) => {
if (options.id) {
billId.value = options.id
loadBill(options.id)
}
})
</script>
<style scoped>
.page-scroll {
height: 100vh;
width: 100%;
padding-bottom: calc(50px + env(safe-area-inset-bottom));
}
.container {
min-height: calc(100vh - 100rpx);
background: #f5f5f5;
padding: 20rpx;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
.form-card {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
}
.form-header {
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.form-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.form-body {
margin-bottom: 30rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.type-selector-compact {
display: flex;
gap: 15rpx;
justify-content: center;
}
.type-option-compact {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 15rpx 30rpx;
background: #f8f8f8;
border-radius: 8rpx;
border: 2rpx solid transparent;
transition: all 0.3s;
min-width: 120rpx;
}
.type-option-compact.active {
background: #f0f7ff;
border-color: #667eea;
}
.type-icon-compact {
font-size: 32rpx;
margin-bottom: 5rpx;
}
.type-text-compact {
font-size: 24rpx;
color: #333;
}
.type-option-compact.active .type-text-compact {
color: #667eea;
font-weight: bold;
}
.category-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 10rpx;
}
.category-icon-item {
display: flex;
flex-direction: column;
align-items: center;
width: calc(25% - 15rpx);
margin-bottom: 10rpx;
}
.category-icon-circle {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
border: 3rpx solid transparent;
transition: all 0.3s;
}
.category-icon-item.active .category-icon-circle {
background: #f0f7ff;
border-color: #667eea;
transform: scale(1.1);
}
.category-icon-emoji {
font-size: 48rpx;
line-height: 1;
}
.category-icon-name {
font-size: 22rpx;
color: #666;
text-align: center;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.category-icon-item.active .category-icon-name {
color: #667eea;
font-weight: bold;
}
.category-empty {
text-align: center;
padding: 40rpx 0;
color: #999;
font-size: 26rpx;
}
.input, .textarea {
width: 100%;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
border: none;
outline: none;
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
pointer-events: auto;
user-select: text;
-webkit-user-select: text;
}
.textarea {
min-height: 200rpx;
}
.picker {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
}
.arrow {
color: #999;
}
.form-footer {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.delete-btn {
flex: 1;
height: 88rpx;
background: #fa3534;
color: #fff;
border-radius: 12rpx;
font-size: 28rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.submit-btn {
flex: 2;
height: 88rpx;
background: #667eea;
color: #fff;
border-radius: 12rpx;
font-size: 32rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
</style>

337
pages/category/category.vue Normal file
View File

@ -0,0 +1,337 @@
<template>
<view class="container">
<view class="header">
<text class="title">分类管理</text>
<button class="add-btn" @click="showAddModal = true">+ 添加</button>
</view>
<view class="tabs">
<view class="tab" :class="{ active: activeType === 1 }" @click="activeType = 1">
支出
</view>
<view class="tab" :class="{ active: activeType === 2 }" @click="activeType = 2">
收入
</view>
</view>
<view class="category-list">
<view v-for="category in filteredCategories" :key="category.id" class="category-item">
<text class="category-icon">{{ category.icon }}</text>
<text class="category-name">{{ category.name }}</text>
<view v-if="!category.userId" class="tag">系统</view>
<view v-else class="actions">
<text class="action-btn" @click="editCategory(category)">编辑</text>
<text class="action-btn delete" @click="deleteCategory(category.id)">删除</text>
</view>
</view>
</view>
<!-- 添加/编辑弹窗 -->
<view v-if="showAddModal || editingCategory" class="modal" @click="closeModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ editingCategory ? '编辑分类' : '添加分类' }}</text>
<text class="close-btn" @click="closeModal">×</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="label">分类名称</text>
<input v-model="form.name" placeholder="请输入分类名称" class="input" />
</view>
<view class="form-item">
<text class="label">图标</text>
<input v-model="form.icon" placeholder="请输入图标(如:🍔)" class="input" />
</view>
<button class="submit-btn" @click="submitCategory">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getCategories, createCategory, updateCategory, deleteCategory as deleteCategoryApi } from '../../api/category'
const activeType = ref(1)
const categories = ref([])
const showAddModal = ref(false)
const editingCategory = ref(null)
const form = ref({
name: '',
icon: '📦',
type: 1
})
const filteredCategories = computed(() => {
return categories.value.filter(cat => cat.type === activeType.value)
})
const loadCategories = async () => {
try {
const data = await getCategories()
categories.value = data || []
} catch (error) {
console.error('加载分类失败', error)
}
}
const editCategory = (category) => {
editingCategory.value = category
form.value = {
name: category.name,
icon: category.icon,
type: category.type
}
}
const deleteCategory = async (id) => {
uni.showModal({
title: '确认删除',
content: '确定要删除这个分类吗?',
success: async (res) => {
if (res.confirm) {
try {
await deleteCategoryApi(id)
uni.showToast({
title: '删除成功',
icon: 'success'
})
loadCategories()
} catch (error) {
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
}
}
})
}
const submitCategory = async () => {
if (!form.value.name) {
uni.showToast({
title: '请输入分类名称',
icon: 'none'
})
return
}
try {
form.value.type = activeType.value
if (editingCategory.value) {
await updateCategory(editingCategory.value.id, form.value)
uni.showToast({
title: '更新成功',
icon: 'success'
})
} else {
await createCategory(form.value)
uni.showToast({
title: '添加成功',
icon: 'success'
})
}
closeModal()
loadCategories()
} catch (error) {
uni.showToast({
title: editingCategory.value ? '更新失败' : '添加失败',
icon: 'none'
})
}
}
const closeModal = () => {
showAddModal.value = false
editingCategory.value = null
form.value = {
name: '',
icon: '📦',
type: 1
}
}
onMounted(() => {
loadCategories()
})
</script>
<style scoped>
.container {
min-height: 100vh;
background: #f5f5f5;
}
.header {
background: #fff;
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.title {
font-size: 36rpx;
font-weight: bold;
}
.add-btn {
background: #667eea;
color: #fff;
padding: 10rpx 20rpx;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
}
.tabs {
display: flex;
background: #fff;
margin-top: 20rpx;
padding: 10rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 8rpx;
color: #666;
}
.tab.active {
background: #667eea;
color: #fff;
}
.category-list {
padding: 20rpx;
}
.category-item {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
}
.category-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.category-name {
flex: 1;
font-size: 30rpx;
color: #333;
}
.tag {
background: #f0f0f0;
color: #999;
padding: 4rpx 12rpx;
border-radius: 4rpx;
font-size: 22rpx;
margin-right: 20rpx;
}
.actions {
display: flex;
gap: 20rpx;
}
.action-btn {
color: #667eea;
font-size: 26rpx;
}
.action-btn.delete {
color: #fa3534;
}
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #fff;
border-radius: 12rpx;
width: 80%;
max-width: 600rpx;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
}
.close-btn {
font-size: 48rpx;
color: #999;
}
.modal-body {
padding: 30rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.input {
width: 100%;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
font-size: 28rpx;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #667eea;
color: #fff;
border-radius: 12rpx;
font-size: 32rpx;
border: none;
}
</style>

297
pages/index/index.vue Normal file
View File

@ -0,0 +1,297 @@
<template>
<view class="container">
<view class="header">
<text class="title">我的账单</text>
<view class="summary">
<view class="summary-item">
<text class="label">本月支出</text>
<text class="amount expense">¥{{ monthlyExpense.toFixed(2) }}</text>
</view>
<view class="summary-item">
<text class="label">本月收入</text>
<text class="amount income">¥{{ monthlyIncome.toFixed(2) }}</text>
</view>
</view>
</view>
<view class="bill-list">
<view v-for="(group, date) in groupedBills" :key="date" class="bill-group">
<view class="group-header">
<text class="date">{{ date }}</text>
<text class="total">¥{{ getGroupTotal(group).toFixed(2) }}</text>
</view>
<view v-for="bill in group" :key="bill.id" class="bill-item" @click="viewBill(bill)">
<view class="bill-icon">{{ bill.categoryIcon || '📦' }}</view>
<view class="bill-info">
<text class="bill-category">{{ bill.categoryName || '未分类' }}</text>
<text class="bill-desc" v-if="bill.description">{{ bill.description }}</text>
</view>
<text class="bill-amount" :class="bill.amount > 0 ? 'income' : 'expense'">
{{ bill.type > 0 ? '-' : '+' }}¥{{ Math.abs(bill.amount).toFixed(2) }}
</text>
</view>
</view>
<view v-if="bills.length === 0 && !loading" class="empty">
<text>暂无账单记录</text>
<text class="empty-tip">点击下方"记账"按钮添加账单</text>
</view>
<view v-if="loading" class="loading">
<text>加载中...</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getBills } from '../../api/bill'
import { getMonthlyStatistics } from '../../api/statistics'
import { formatDate } from '../../utils/date'
import { useUserStore } from '../../store/user'
const userStore = useUserStore()
const bills = ref([])
const monthlyIncome = ref(0)
const monthlyExpense = ref(0)
const loading = ref(false)
const groupedBills = computed(() => {
const groups = {}
bills.value.forEach(bill => {
const date = formatDate(bill.billDate)
if (!groups[date]) {
groups[date] = []
}
groups[date].push(bill)
})
return groups
})
const getGroupTotal = (group) => {
return group.reduce((sum, bill) => sum + parseFloat(bill.amount || 0), 0)
}
const loadBills = async () => {
loading.value = true
try {
const data = await getBills()
bills.value = data || []
} catch (error) {
console.error('加载账单失败', error)
if (error.message && error.message.includes('401')) {
// token
userStore.logout()
uni.reLaunch({
url: '/pages/login/login'
})
}
} finally {
loading.value = false
}
}
const loadMonthlyStatistics = async () => {
try {
const now = new Date()
const data = await getMonthlyStatistics(now.getFullYear(), now.getMonth() + 1)
monthlyIncome.value = data.totalIncome || 0
monthlyExpense.value = data.totalExpense || 0
} catch (error) {
console.error('加载统计失败', error)
}
}
const viewBill = (bill) => {
//
uni.navigateTo({
url: `/pages/bill/detail?id=${bill.id}`
})
}
const onPullDownRefresh = () => {
loadBills()
loadMonthlyStatistics()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
}
onMounted(() => {
//
if (!userStore.isLoggedIn) {
uni.reLaunch({
url: '/pages/login/login'
})
return
}
loadBills()
loadMonthlyStatistics()
})
//
uni.$on('refreshBills', () => {
loadBills()
loadMonthlyStatistics()
})
</script>
<script>
// uni-app
export default {
onShow() {
// -
uni.$emit('refreshBills')
},
onPullDownRefresh() {
// -
uni.$emit('refreshBills')
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
}
}
</script>
<style scoped>
.container {
min-height: calc(100vh - 100rpx);
background: #f5f5f5;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 30rpx;
color: #fff;
}
.title {
font-size: 40rpx;
font-weight: bold;
margin-bottom: 30rpx;
display: block;
}
.summary {
display: flex;
justify-content: space-around;
}
.summary-item {
text-align: center;
}
.label {
display: block;
font-size: 24rpx;
opacity: 0.8;
margin-bottom: 10rpx;
}
.amount {
display: block;
font-size: 36rpx;
font-weight: bold;
}
.bill-list {
padding: 20rpx;
}
.bill-group {
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background: #f8f8f8;
border-bottom: 1rpx solid #eee;
}
.date {
font-size: 28rpx;
color: #666;
}
.total {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.bill-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.bill-item:last-child {
border-bottom: none;
}
.bill-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.bill-info {
flex: 1;
display: flex;
flex-direction: column;
}
.bill-category {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.bill-desc {
font-size: 24rpx;
color: #999;
}
.bill-amount {
font-size: 32rpx;
font-weight: bold;
}
.bill-amount.income {
color: #19be6b;
}
.bill-amount.expense {
color: #fa3534;
}
.empty {
text-align: center;
padding: 100rpx 0;
color: #999;
display: flex;
flex-direction: column;
align-items: center;
}
.empty-tip {
font-size: 24rpx;
margin-top: 20rpx;
color: #ccc;
}
.loading {
text-align: center;
padding: 40rpx 0;
color: #999;
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<view style="padding: 100px; text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<text style="font-size: 48px; color: #fff; margin-bottom: 40px; display: block;">强宝爱记账</text>
<text style="font-size: 32px; color: rgba(255,255,255,0.9); display: block;">登录页面测试</text>
<text style="font-size: 24px; color: rgba(255,255,255,0.7); margin-top: 20px; display: block;">如果能看到这个说明路由正常</text>
</view>
</template>
<script>
export default {
onLoad() {
console.log('登录页面加载成功')
},
onReady() {
console.log('登录页面准备完成')
}
}
</script>

205
pages/login/login.vue Normal file
View File

@ -0,0 +1,205 @@
<template>
<view class="login-container">
<view class="login-header">
<text class="app-title">强宝爱记账</text>
<text class="app-subtitle">智能记账轻松管理</text>
</view>
<view class="login-form">
<view class="form-item">
<input
v-model="form.username"
placeholder="请输入用户名"
class="input"
maxlength="20"
/>
</view>
<view class="form-item">
<input
v-model="form.password"
placeholder="请输入密码"
type="password"
class="input"
maxlength="20"
/>
</view>
<view v-if="isRegister" class="form-item">
<input
v-model="form.nickname"
placeholder="请输入昵称(可选)"
class="input"
maxlength="20"
/>
</view>
<button class="login-btn" @click="handleSubmit" :loading="loading">
{{ isRegister ? '注册' : '登录' }}
</button>
<view class="switch-mode" @click="switchMode">
<text>{{ isRegister ? '已有账号?去登录' : '没有账号?去注册' }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { register, login } from '../../api/auth'
import { useUserStore } from '../../store/user'
const userStore = useUserStore()
const isRegister = ref(false)
const loading = ref(false)
const form = ref({
username: '',
password: '',
nickname: ''
})
const switchMode = () => {
isRegister.value = !isRegister.value
form.value = {
username: '',
password: '',
nickname: ''
}
}
const handleSubmit = async () => {
if (!form.value.username || !form.value.password) {
uni.showToast({
title: '请输入用户名和密码',
icon: 'none'
})
return
}
if (isRegister.value && form.value.password.length < 6) {
uni.showToast({
title: '密码长度至少6位',
icon: 'none'
})
return
}
loading.value = true
try {
let response
if (isRegister.value) {
response = await register({
username: form.value.username,
password: form.value.password,
nickname: form.value.nickname || form.value.username
})
} else {
response = await login({
username: form.value.username,
password: form.value.password
})
}
// token
userStore.login(response.token, {
username: response.username,
nickname: response.nickname
})
uni.showToast({
title: isRegister.value ? '注册成功' : '登录成功',
icon: 'success'
})
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
} catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: error.message || '操作失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.login-header {
text-align: center;
margin-bottom: 80rpx;
}
.app-title {
display: block;
font-size: 56rpx;
font-weight: bold;
color: #fff;
margin-bottom: 20rpx;
}
.app-subtitle {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.login-form {
width: 100%;
max-width: 600rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.input {
width: 100%;
height: 88rpx;
background: #fff;
border-radius: 12rpx;
padding: 0 30rpx;
font-size: 28rpx;
}
.login-btn {
width: 100%;
height: 88rpx;
background: #fff;
color: #667eea;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: bold;
margin-top: 40rpx;
border: none;
}
.switch-mode {
text-align: center;
margin-top: 40rpx;
}
.switch-mode text {
color: rgba(255, 255, 255, 0.9);
font-size: 26rpx;
}
</style>

View File

@ -0,0 +1,246 @@
<template>
<view class="container">
<view class="tabs">
<view class="tab" :class="{ active: activeTab === 'daily' }" @click="switchTab('daily')">
按日
</view>
<view class="tab" :class="{ active: activeTab === 'weekly' }" @click="switchTab('weekly')">
按周
</view>
<view class="tab" :class="{ active: activeTab === 'monthly' }" @click="switchTab('monthly')">
按月
</view>
</view>
<view class="summary-card">
<view class="summary-item">
<text class="label">总收入</text>
<text class="amount income">¥{{ statistics.totalIncome?.toFixed(2) || '0.00' }}</text>
</view>
<view class="summary-item">
<text class="label">总支出</text>
<text class="amount expense">¥{{ statistics.totalExpense?.toFixed(2) || '0.00' }}</text>
</view>
<view class="summary-item">
<text class="label">余额</text>
<text class="amount balance">¥{{ getBalance.toFixed(2) }}</text>
</view>
</view>
<view class="category-stats">
<text class="section-title">分类统计</text>
<view v-if="statistics.categoryStatistics && statistics.categoryStatistics.length > 0">
<view v-for="item in statistics.categoryStatistics" :key="item.categoryId" class="category-item">
<text class="category-icon">{{ item.categoryIcon || '📦' }}</text>
<view class="category-info">
<text class="category-name">{{ item.categoryName }}</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: getProgress(item) + '%' }"></view>
</view>
</view>
<text class="category-amount">¥{{ item.amount?.toFixed(2) || '0.00' }}</text>
</view>
</view>
<view v-else class="empty">
<text>暂无统计数据</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getDailyStatistics, getWeeklyStatistics, getMonthlyStatistics } from '../../api/statistics'
import { formatDate, getWeekStart } from '../../utils/date'
const activeTab = ref('monthly')
const statistics = ref({
totalIncome: 0,
totalExpense: 0,
balance: 0,
categoryStatistics: [],
dailyStatistics: []
})
const switchTab = async (tab) => {
activeTab.value = tab
await loadStatistics()
}
const loadStatistics = async () => {
try {
const now = new Date()
let data
if (activeTab.value === 'daily') {
const startDate = formatDate(now)
const endDate = formatDate(now)
data = await getDailyStatistics(startDate, endDate)
} else if (activeTab.value === 'weekly') {
const weekStart = formatDate(getWeekStart(now))
data = await getWeeklyStatistics(weekStart)
} else {
data = await getMonthlyStatistics(now.getFullYear(), now.getMonth() + 1)
}
const income = data?.totalIncome || 0
const expense = data?.totalExpense || 0
statistics.value = {
totalIncome: income,
totalExpense: expense,
balance: income - expense,
categoryStatistics: data?.categoryStatistics || [],
dailyStatistics: data?.dailyStatistics || []
}
} catch (error) {
console.error('加载统计失败', error)
statistics.value = {
totalIncome: 0,
totalExpense: 0,
balance: 0,
categoryStatistics: [],
dailyStatistics: []
}
}
}
const getBalance = computed(() => {
return statistics.value.totalIncome - statistics.value.totalExpense
})
const getProgress = (item) => {
const total = statistics.value.totalExpense || 1
const amount = parseFloat(item.amount || 0)
return total > 0 ? (amount / total) * 100 : 0
}
onMounted(() => {
loadStatistics()
})
</script>
<style scoped>
.container {
min-height: calc(100vh - 100rpx);
background: #f5f5f5;
padding: 20rpx;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
.tabs {
display: flex;
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
padding: 10rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 8rpx;
color: #666;
font-size: 28rpx;
}
.tab.active {
background: #667eea;
color: #fff;
}
.summary-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12rpx;
padding: 40rpx;
margin-bottom: 20rpx;
display: flex;
justify-content: space-around;
}
.summary-item {
text-align: center;
color: #fff;
}
.label {
display: block;
font-size: 24rpx;
opacity: 0.8;
margin-bottom: 10rpx;
}
.amount {
display: block;
font-size: 36rpx;
font-weight: bold;
}
.category-stats {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 30rpx;
color: #333;
}
.category-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.category-item:last-child {
border-bottom: none;
}
.category-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.category-info {
flex: 1;
}
.category-name {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.progress-bar {
width: 100%;
height: 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #667eea;
transition: width 0.3s;
}
.category-amount {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-left: 20rpx;
}
.empty {
text-align: center;
padding: 60rpx 0;
color: #999;
}
</style>

80
static/css/common.css Normal file
View File

@ -0,0 +1,80 @@
/* 全局样式 */
page {
background-color: #f5f5f5;
font-size: 28rpx;
color: #333;
}
.container {
padding: 20rpx;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.mt-10 {
margin-top: 10rpx;
}
.mt-20 {
margin-top: 20rpx;
}
.mb-10 {
margin-bottom: 10rpx;
}
.mb-20 {
margin-bottom: 20rpx;
}
.p-20 {
padding: 20rpx;
}
.text-center {
text-align: center;
}
.text-primary {
color: #3c9cff;
}
.text-success {
color: #19be6b;
}
.text-warning {
color: #ff9900;
}
.text-error {
color: #fa3534;
}

111
static/tabbar/README.md Normal file
View File

@ -0,0 +1,111 @@
# TabBar 图标文件说明
## 需要的图标文件
本项目需要以下 6 个图标文件用于底部导航栏TabBar
### 1. 首页图标
- `home.png` - 未选中状态的首页图标
- `home-active.png` - 选中状态的首页图标
### 2. 记账图标
- `add.png` - 未选中状态的记账图标
- `add-active.png` - 选中状态的记账图标
### 3. 统计图标
- `statistics.png` - 未选中状态的统计图标
- `statistics-active.png` - 选中状态的统计图标
## 图标规格要求
- **尺寸**: 建议 81px × 81px@2x或 40px × 40px@1x
- **格式**: PNG支持透明背景
- **颜色**:
- 未选中状态:灰色(#7A7E83
- 选中状态:紫色(#667eea
## 如何获取图标
### 方法1使用图标库推荐
1. **IconFont阿里巴巴图标库**
- 访问https://www.iconfont.cn/
- 搜索关键词首页、home、记账、add、统计、statistics
- 下载 PNG 格式,选择合适尺寸
2. **Flaticon**
- 访问https://www.flaticon.com/
- 搜索并下载免费图标
3. **Icons8**
- 访问https://icons8.com/
- 搜索并下载图标
### 方法2使用在线图标生成器
1. **Canva**
- 访问https://www.canva.com/
- 创建 81×81px 的画布
- 设计图标并导出为 PNG
2. **Figma**
- 使用 Figma 设计图标
- 导出为 PNG 格式
### 方法3使用 AI 生成
使用 AI 工具(如 Midjourney、DALL-E生成图标
- 提示词示例:"simple icon home house, minimal style, purple color, transparent background, 81x81px"
### 方法4使用 Emoji临时方案
如果暂时没有图标文件,可以:
1. 使用系统字体图标
2. 或者暂时移除 tabBar 配置,使用其他导航方式
## 快速获取方案
### 使用 uni-app 官方图标
uni-app 提供了内置图标,可以修改 `pages.json` 使用文字图标:
```json
{
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "",
"selectedIconPath": ""
}
]
}
}
```
### 使用字体图标
可以使用 iconfont 字体图标库,将图标转换为字体文件使用。
## 图标命名规范
请确保图标文件命名与 `pages.json` 中的配置一致:
```
static/tabbar/
├── home.png
├── home-active.png
├── add.png
├── add-active.png
├── statistics.png
└── statistics-active.png
```
## 注意事项
1. 图标文件必须放在 `static/tabbar/` 目录下
2. 文件名必须与 `pages.json` 中的配置完全一致
3. 建议使用 @2x 尺寸81px以获得更好的显示效果
4. 如果图标文件不存在tabBar 可能无法正常显示

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
static/tabbar/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
static/tabbar/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

47
store/user.js Normal file
View File

@ -0,0 +1,47 @@
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: uni.getStorageSync('token') || '',
userInfo: uni.getStorageSync('userInfo') || null
}),
getters: {
isLoggedIn: (state) => !!state.token,
username: (state) => state.userInfo?.username || '',
nickname: (state) => state.userInfo?.nickname || ''
},
actions: {
setToken(token) {
this.token = token
uni.setStorageSync('token', token)
},
setUserInfo(userInfo) {
this.userInfo = userInfo
uni.setStorageSync('userInfo', userInfo)
},
login(token, userInfo) {
this.setToken(token)
this.setUserInfo(userInfo)
},
logout() {
this.token = ''
this.userInfo = null
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
}
}
})

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["@dcloudio/types"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"]
}

75
uni.scss Normal file
View File

@ -0,0 +1,75 @@
/**
* 这里是uni-app内置的常用样式变量
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #3c9cff;
$uni-color-success: #19be6b;
$uni-color-warning: #ff9900;
$uni-color-error: #fa3534;
/* 文字基本颜色 */
$uni-text-color: #333; // 基本色
$uni-text-color-inverse: #fff; // 反色
$uni-text-color-grey: #999; // 辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #ffffff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 24rpx;
$uni-font-size-base: 28rpx;
$uni-font-size-lg: 32rpx;
/* 图片尺寸 */
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
/* Border Radius */
$uni-border-radius-sm: 4rpx;
$uni-border-radius-base: 6rpx;
$uni-border-radius-lg: 12rpx;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 10rpx;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
/* 垂直间距 */
$uni-spacing-col-sm: 10rpx;
$uni-spacing-col-base: 20rpx;
$uni-spacing-col-lg: 30rpx;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 40rpx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle: 36rpx;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 30rpx;

View File

@ -0,0 +1,427 @@
// https://uniapp.dcloud.io/uniCloud/cf-database?id=db_init
//
{
"opendb-news-articles": {
"data": [{
"user_id": 121375,
"user_name": "未来汽车日报",
"category_id": "223",
"title": "为什么自动驾驶诉讼不断?",
"content": "",
"excerpt": "没有永远的敌人,只有永远的利益。",
"article_status": 1,
"view_count": 32053,
"like_count": 4603,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 1204,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_9c3331af67e64994aa97a27fffb1a380_img_png?x-oss-process=image/resize,m_mfit,w_520,h_300/crop,w_520,h_300,g_center",
"publish_date": "2020-04-11 17:07:48",
"publish_ip": "",
"last_modify_date": "2020-04-11 17:11:09",
"last_modify_ip": "",
"mode":1
}, {
"user_id": 121374,
"user_name": "36氪深度服务",
"category_id": "0",
"title": "2020数字中国创新大赛-数字政府赛道21强出炉四大赛题紧贴政府数字化发展需求",
"content": "",
"excerpt": "提升数字治理、建设“温暖渔政”——四大赛题助力政府解决治理难题",
"article_status": 1,
"view_count": 53855,
"like_count": 970,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 267,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200411/v2_16417a06088947debe0450950f8fc813_img_png",
"publish_date": "2020-04-11 16:59:15",
"publish_ip": "",
"last_modify_date": "2020-04-11 17:03:18",
"last_modify_ip": "",
"mode":2
}, {
"user_id": 121373,
"user_name": "未来汽车日报",
"category_id": "223",
"title": "地方政府救市哪家强广州补贴上万元广深杭新增指标超5万",
"content": "",
"excerpt": "补贴很难落袋为安。",
"article_status": 1,
"view_count": 51662,
"like_count": 238,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 6217,
"last_comment_user_id": "",
"avatar": ["https://img.36krcdn.com/20200410/v2_6905947498bc4ec0af228afed409f771_img_png?x-oss-process=image/resize,m_mfit,w_520,h_300/crop,w_520,h_300,g_center","https://img.36krcdn.com/20200410/v2_6905947498bc4ec0af228afed409f771_img_png?x-oss-process=image/resize,m_mfit,w_520,h_300/crop,w_520,h_300,g_center","https://img.36krcdn.com/20200410/v2_6905947498bc4ec0af228afed409f771_img_png?x-oss-process=image/resize,m_mfit,w_520,h_300/crop,w_520,h_300,g_center"],
"publish_date": "2020-04-11 16:09:09",
"publish_ip": "",
"last_modify_date": "2020-04-11 16:11:11",
"last_modify_ip": "",
"mode":3
}, {
"user_id": 121372,
"user_name": "智东西",
"category_id": "234",
"title": "救命呼吸机缺口难补!一文扒开供应链真相",
"content": "",
"excerpt": "全球疯抢国产呼吸机背后:技术有短板,产能被卡住。",
"article_status": 1,
"view_count": 77926,
"like_count": 2239,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 9154,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_86bbf8245f754be79f3386a82b385093_img_000",
"publish_date": "2020-04-11 15:40:02",
"publish_ip": "",
"last_modify_date": "2020-04-11 15:53:10",
"last_modify_ip": "",
"mode":4
}, {
"user_id": 121371,
"user_name": "神译局",
"category_id": "223",
"title": "每月节省32%的开支,我是怎么做到的?",
"content": "",
"excerpt": "开源节流",
"article_status": 1,
"view_count": 53577,
"like_count": 862,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 8073,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200408/v2_c67c3edfe4b5446992b32fad93a44a75_img_png",
"publish_date": "2020-04-11 14:48:02",
"publish_ip": "",
"last_modify_date": "2020-04-11 15:02:22",
"last_modify_ip": "",
"mode":5
}, {
"user_id": 121369,
"user_name": "资本侦探",
"category_id": "221",
"title": "瓜子坚果双增长,但洽洽并不能高枕无忧",
"content": "",
"excerpt": "零食市场竞争日益激烈。",
"article_status": 1,
"view_count": 13740,
"like_count": 1354,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 7942,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200411/v2_2204c6132432403184e43df22485545e_img_000?x-oss-process=image/resize,m_mfit,w_432,h_288/crop,w_432,h_288,g_center",
"publish_date": "2020-04-11 14:23:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 14:34:13",
"last_modify_ip": "",
"mode":6
}, {
"user_id": 121367,
"user_name": "神译局",
"category_id": "103",
"title": "克服危机和压力精神力量强大的人都有这5个习惯",
"content": "",
"excerpt": "学会利用好真正艰难的时刻,增强自己的精神力量",
"article_status": 1,
"view_count": 71706,
"like_count": 4978,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 5361,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200406/v2_d2c6a686b4074a1eb43603e67d6ba204_img_png",
"publish_date": "2020-04-11 13:47:01",
"publish_ip": "",
"last_modify_date": "2020-04-11 13:52:12",
"last_modify_ip": "",
"mode":1
}, {
"user_id": 121365,
"user_name": "PingWest品玩",
"category_id": "221",
"title": "神州租车找爹记",
"content": "",
"excerpt": "一爹难求。",
"article_status": 1,
"view_count": 47794,
"like_count": 2397,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 9489,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200411/v2_f497b1c414d5489791569b3ea99df33d_img_000",
"publish_date": "2020-04-11 13:39:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 13:44:11",
"last_modify_ip": "",
"mode":2
}, {
"user_id": 121366,
"user_name": "明星八爪娱",
"category_id": "225",
"title": "​抖音快手明星图鉴:哪个平台更适合明星“再就业”?",
"content": "",
"excerpt": "一个捧红了“十八线”明星,一个靠“偶像”获得新生",
"article_status": 1,
"view_count": 95348,
"like_count": 1356,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 356,
"last_comment_user_id": "",
"avatar": ["https://img.36krcdn.com/20200411/v2_da4c26244cbc494c8e0e5918518e866c_img_png","https://img.36krcdn.com/20200411/v2_da4c26244cbc494c8e0e5918518e866c_img_png","https://img.36krcdn.com/20200411/v2_da4c26244cbc494c8e0e5918518e866c_img_png"],
"publish_date": "2020-04-11 13:38:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 13:44:11",
"last_modify_ip": "",
"mode":3
}, {
"user_id": 121364,
"user_name": "神译局",
"category_id": "223",
"title": "动荡的市场中你需要牢记巴菲特的这3个原则",
"content": "",
"excerpt": "本周股市下跌了不少——沃伦·巴菲特会作何反应?",
"article_status": 1,
"view_count": 86148,
"like_count": 4027,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 6620,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200406/v2_232e9248d5c74ff989db57a0b6713abe_img_png",
"publish_date": "2020-04-11 12:45:02",
"publish_ip": "",
"last_modify_date": "2020-04-11 12:51:10",
"last_modify_ip": "",
"mode":4
}, {
"user_id": 121362,
"user_name": "资本侦探",
"category_id": "0",
"title": "连亏六年、股价跌破1美元曾经的明星公司途牛怎么了",
"content": "",
"excerpt": "携程、京东、红杉共同加持也无济于事。",
"article_status": 1,
"view_count": 83493,
"like_count": 1771,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 2211,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200411/v2_61c1766736df49a0b2d0213624c0ebd2_img_000?x-oss-process=image/resize,m_mfit,w_432,h_288/crop,w_432,h_288,g_center",
"publish_date": "2020-04-11 12:37:11",
"publish_ip": "",
"last_modify_date": "2020-04-11 12:41:10",
"last_modify_ip": "",
"mode":5
}, {
"user_id": 121363,
"user_name": "Tech星球",
"category_id": "0",
"title": "独家 | 快手版「多闪」上线:「一甜面聊」布局视频社交",
"content": "",
"excerpt": "视频社交的时代来了。",
"article_status": 1,
"view_count": 97790,
"like_count": 636,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 4027,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200411/v2_b92317dfc59948cea0f3c69a0357e519_img_000?x-oss-process=image/resize,m_mfit,w_432,h_288/crop,w_432,h_288,g_center",
"publish_date": "2020-04-11 12:36:58",
"publish_ip": "",
"last_modify_date": "2020-04-11 12:41:11",
"last_modify_ip": "",
"mode":6
}, {
"user_id": 121361,
"user_name": "神译局",
"category_id": "103",
"title": "2020年已经过去了四分之一但改变自己为时不晚",
"content": "",
"excerpt": "种一棵树最好的时间是十年前,其次是现在",
"article_status": 1,
"view_count": 67302,
"like_count": 4148,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 286,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200404/v2_d6613223fb15414897a0ba3449d00afd_img_png",
"publish_date": "2020-04-11 11:44:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 11:52:11",
"last_modify_ip": "",
"mode":1
}, {
"user_id": 121360,
"user_name": "沈帅波",
"category_id": "221",
"title": "2020市场部求生指南",
"content": "",
"excerpt": "短期思维的套利空间正在逐渐消失。",
"article_status": 1,
"view_count": 91108,
"like_count": 4029,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 7126,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_c5997a3640f54c2c880cff0906f6896c_img_000",
"publish_date": "2020-04-11 11:31:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 11:33:15",
"last_modify_ip": "",
"mode":2
}, {
"user_id": 121359,
"user_name": "陈淑雅",
"category_id": "223",
"title": "定位新媒体整合营销服务商「通明传媒」用“小V矩阵”打出差异化",
"content": "",
"excerpt": "近期通明传媒还成为了罗永浩抖音直播的官方授权招商服务合作伙伴。",
"article_status": 1,
"view_count": 38875,
"like_count": 4717,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 9477,
"last_comment_user_id": "",
"avatar": ["https://img.36krcdn.com/20200410/v2_224a699a06504292804e4bdf70ca87bb_img_png","https://img.36krcdn.com/20200410/v2_224a699a06504292804e4bdf70ca87bb_img_png","https://img.36krcdn.com/20200410/v2_224a699a06504292804e4bdf70ca87bb_img_png"],
"publish_date": "2020-04-11 11:09:58",
"publish_ip": "",
"last_modify_date": "2020-04-11 11:21:10",
"last_modify_ip": "",
"mode":3
}, {
"user_id": 121358,
"user_name": "来咖智库",
"category_id": "0",
"title": "BAT的直播电商新战事",
"content": "",
"excerpt": "阿里做大、腾讯做深、百度做新。",
"article_status": 1,
"view_count": 41013,
"like_count": 3650,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 682,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_747fc8a18fde4da4b1ba1080d8e6aa04_img_000",
"publish_date": "2020-04-11 11:06:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 11:12:11",
"last_modify_ip": "",
"mode":4
}, {
"user_id": 121357,
"user_name": "创业最前线",
"category_id": "0",
"title": "估值50亿美元“种草”社区头牌小红书如何走出商业化迷途",
"content": "",
"excerpt": "小红书求变。",
"article_status": 1,
"view_count": 36498,
"like_count": 1523,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 8731,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_18c3a78cf8be42ccb45e4daba0c87c13_img_png",
"publish_date": "2020-04-11 10:41:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 10:53:11",
"last_modify_ip": "",
"mode":5
}, {
"user_id": 121356,
"user_name": "神译局",
"category_id": "223",
"title": "在混乱和不确定的时期你最需要的是这5个技能",
"content": "",
"excerpt": "自律就是即使你不想做某件事,但也能强迫自己去做的能力。",
"article_status": 1,
"view_count": 13776,
"like_count": 2214,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 9514,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200401/v2_4afb7581cb5a4c36aac96a51829c13e0_img_png",
"publish_date": "2020-04-11 10:40:01",
"publish_ip": "",
"last_modify_date": "2020-04-11 10:41:11",
"last_modify_ip": "",
"mode":6
}, {
"user_id": 121355,
"user_name": "音乐先声",
"category_id": "0",
"title": "复工按下暂停键,演出报批遥遥无期,线下音乐路在何方?",
"content": "",
"excerpt": "阴霾终究会散去。",
"article_status": 1,
"view_count": 96989,
"like_count": 3607,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 3580,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_41365a0f26a244fdab8e3f5be081ed2b_img_000",
"publish_date": "2020-04-11 10:06:00",
"publish_ip": "",
"last_modify_date": "2020-04-11 10:11:12",
"last_modify_ip": "",
"mode":1
}, {
"user_id": 121354,
"user_name": "36氪出海",
"category_id": "0",
"title": "出海创投周报阿里领投印度BigBasket6000万美元融资Reliance向AI教育平台Embibe追加6600万美元投资",
"content": "",
"excerpt": "出海人的周末精选读物。",
"article_status": 1,
"view_count": 63561,
"like_count": 3817,
"is_sticky": false,
"is_essence": false,
"comment_status": 0,
"comment_count": 1433,
"last_comment_user_id": "",
"avatar": "https://img.36krcdn.com/20200410/v2_fb948f4c18de4b22927f0361d53f6caf_img_png",
"publish_date": "2020-04-11 10:00:01",
"publish_ip": "",
"last_modify_date": "2020-04-11 10:03:18",
"last_modify_ip": "",
"mode":2
}]
}
}

View File

@ -0,0 +1,122 @@
{
"bsonType": "object",
"permission": {
"create": "auth.uid != null",
"delete": "doc.uid == auth.uid",
"read": true,
"update": "doc.uid == auth.uid"
},
"properties": {
"_id": {
"description": "存储文档 ID用户 ID系统自动生成"
},
"article_status": {
"bsonType": "int",
"description": "文章状态0 草稿箱 1 已发布",
"maximum": 1,
"minimum": 0
},
"avatar": {
"bsonType": "string",
"description": "缩略图地址",
"label": "封面大图"
},
"category_id": {
"bsonType": "string",
"description": "分类 id参考`uni-news-categories`表"
},
"comment_count": {
"bsonType": "int",
"description": "评论数量",
"permission": {
"write": false
}
},
"comment_status": {
"bsonType": "int",
"description": "评论状态0 关闭 1 开放",
"maximum": 1,
"minimum": 0
},
"content": {
"bsonType": "string",
"description": "文章内容",
"label": "文章内容"
},
"excerpt": {
"bsonType": "string",
"description": "文章摘录",
"label": "摘要"
},
"is_essence": {
"bsonType": "bool",
"description": "阅读加精",
"permission": {
"write": false
}
},
"is_sticky": {
"bsonType": "bool",
"description": "是否置顶",
"permission": {
"write": false
}
},
"last_comment_user_id": {
"bsonType": "string",
"description": "最后回复用户 id参考`uni-id-users` 表"
},
"last_modify_date": {
"bsonType": "timestamp",
"description": "最后修改时间"
},
"last_modify_ip": {
"bsonType": "string",
"description": "最后修改时 IP 地址"
},
"like_count": {
"bsonType": "int",
"description": "喜欢数、点赞数",
"permission": {
"write": false
}
},
"mode": {
"bsonType": "number",
"description": "排版显示模式"
},
"publish_date": {
"bsonType": "timestamp",
"defaultValue": {
"$env": "now"
},
"description": "发表时间"
},
"publish_ip": {
"bsonType": "string",
"description": "发表时 IP 地址",
"forceDefaultValue": {
"$env": "clientIP"
}
},
"title": {
"bsonType": "string",
"description": "标题",
"label": "标题"
},
"user_id": {
"bsonType": "string",
"description": "文章作者ID 参考`uni-id-users` 表"
},
"view_count": {
"bsonType": "int",
"description": "阅读数量",
"permission": {
"write": false
}
}
},
"required": ["user_id", "title", "content", "article_status", "view_count", "like_count", "is_sticky", "is_essence",
"comment_status", "comment_count", "mode"
]
}

View File

@ -0,0 +1,14 @@
## 1.1.12021-05-12
- 新增 组件示例地址
## 1.1.02021-05-12
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style 支持以对象形式自定义样式
## 1.0.72021-05-07
- 修复 uni-badge 在 App 端数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时宽度缩小的bug
- 新增 uni-badge 属性 custom-style 支持自定义样式
## 1.0.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,252 @@
<template>
<view class="uni-badge--x">
<slot />
<text v-if="text" :class="classNames" :style="[badgeWidth, positionStyle, customStyle, dotStyle]"
class="uni-badge"
@click="onClick()">{{displayValue}}</text>
</view>
</template>
<script>
/**
* Badge 数字角标
* @description 数字角标一般和其它控件列表9宫格等配合使用用于进行数量提示默认为实心灰色背景
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
* @property {String} text 角标内容
* @property {String} type = [default|primary|success|warning|error] 颜色类型
* @value default 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {String} size = [normal|small] Badge 大小
* @value normal 一般尺寸
* @value small 小尺寸
* @property {String} inverted = [true|false] 是否无需背景颜色
* @event {Function} click 点击 Badge 触发事件
* @example <uni-badge text="1"></uni-badge>
*/
export default {
name: 'UniBadge',
props: {
type: {
type: String,
default: 'default'
},
inverted: {
type: Boolean,
default: false
},
isDot: {
type: Boolean,
default: false
},
maxNum: {
type: Number,
default: 99
},
absolute: {
type: String,
default: ''
},
offset: {
type: Array,
default () {
return [0, 0]
}
},
text: {
type: [String, Number],
default: ''
},
size: {
type: String,
default: 'normal'
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {};
},
computed: {
width() {
return String(this.text).length * 8 + 12
},
classNames() {
const {
inverted,
type,
size,
absolute
} = this
return [
inverted ? 'uni-badge--' + type + '-inverted' : '',
'uni-badge--' + type,
'uni-badge--' + size,
absolute ? 'uni-badge--absolute' : ''
]
},
positionStyle() {
if (!this.absolute) return {}
let w = this.width / 2,
h = 10
if (this.isDot) {
w = 5
h = 5
}
const x = `${- w + this.offset[0]}px`
const y = `${- h + this.offset[1]}px`
const whiteList = {
rightTop: {
right: x,
top: y
},
rightBottom: {
right: x,
bottom: y
},
leftBottom: {
left: x,
bottom: y
},
leftTop: {
left: x,
top: y
}
}
const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop']
},
badgeWidth() {
return {
width: `${this.width}px`
}
},
dotStyle() {
if (!this.isDot) return {}
return {
width: '10px',
height: '10px',
borderRadius: '10px'
}
},
displayValue() {
const { isDot, text, maxNum } = this
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
}
},
methods: {
onClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
$bage-size: 12px;
$bage-small: scale(0.8);
$bage-height: 20px;
.uni-badge--x {
/* #ifdef APP-NVUE */
align-self: flex-start;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
position: relative;
}
.uni-badge--absolute {
position: absolute;
}
.uni-badge {
/* #ifndef APP-NVUE */
display: flex;
overflow: hidden;
box-sizing: border-box;
/* #endif */
justify-content: center;
flex-direction: row;
height: $bage-height;
line-height: $bage-height;
color: $uni-text-color;
border-radius: 100px;
background-color: $uni-bg-color-hover;
background-color: transparent;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: $bage-size;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-badge--inverted {
padding: 0 5px 0 0;
color: $uni-bg-color-hover;
}
.uni-badge--default {
color: $uni-text-color;
background-color: $uni-bg-color-hover;
}
.uni-badge--default-inverted {
color: $uni-text-color-grey;
background-color: transparent;
}
.uni-badge--primary {
color: $uni-text-color-inverse;
background-color: $uni-color-primary;
}
.uni-badge--primary-inverted {
color: $uni-color-primary;
background-color: transparent;
}
.uni-badge--success {
color: $uni-text-color-inverse;
background-color: $uni-color-success;
}
.uni-badge--success-inverted {
color: $uni-color-success;
background-color: transparent;
}
.uni-badge--warning {
color: $uni-text-color-inverse;
background-color: $uni-color-warning;
}
.uni-badge--warning-inverted {
color: $uni-color-warning;
background-color: transparent;
}
.uni-badge--error {
color: $uni-text-color-inverse;
background-color: $uni-color-error;
}
.uni-badge--error-inverted {
color: $uni-color-error;
background-color: transparent;
}
.uni-badge--small {
transform: $bage-small;
transform-origin: center center;
}
</style>

View File

@ -0,0 +1,84 @@
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.1.1",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}

View File

@ -0,0 +1,58 @@
## Badge 数字角标
> **组件名uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
<uni-badge size="small" :text="100" absolute="rightBottom" type="primary">
<button type="default">右上</button>
</uni-badge>
<uni-badge text="1"></uni-badge>
<uni-badge text="2" type="purple" @click="bindClick"></uni-badge>
<uni-badge text="3" type="primary" :inverted="true"></uni-badge>
```
## API
### Badge Props
|属性名 |类型 |默认值 |说明 |
|:-: |:-: |:-: |:-: |
|text |String |- |角标内容 |
|type |String |default|颜色类型可选值default灰色、primary蓝色、success绿色、warning(黄色)、error(红色)|
|size |String |normal |Badge 大小可取值normal、small |
|is-dot |Boolean|false |不展示数字,只有一个小点 |
|max-num |String/Numbuer|99 |展示封顶的数字值,超过 99 显示99+ |
|custom-style |Object | {} |自定义 Badge 样式, 样式对象语法 |
|inverted |Boolean|false |是否无需背景颜色,为 true 时,背景颜色将变为文字的字体颜色 |
|absolute |String| rightTop|开启绝对定位, 角标将定位到其包裹的标签的四个角上,可选值: rightTop(右上角)、rightBottom右下角、leftBottom左下角 、leftTop左上角 |
|offset |Array[number]| [0, 0]|距定位角中心点的偏移量,[-10, -10] 表示向 absolute 指定的方向偏移 10px[10, 10] 表示向 absolute 指定的反方向偏移 10px只有存在 absolute 属性时有效与absolute 的值一一对应例如值为rightTop 对应 offset 为 [right, Top]|
### Badge Events
|事件名 |事件说明 |返回参数 |
|:-: |:-: |:-: |
|@click |点击 Badge 触发事件| - |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge](https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge)

View File

@ -0,0 +1,4 @@
## 0.0.22021-04-16
- 修改插件package信息
## 0.0.12021-03-15
- 初始化项目

View File

@ -0,0 +1,80 @@
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.2",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"云函数模板"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,93 @@
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema插件使用者在HBuilderX内编写config.json文件时会有更好的提示后续HBuilderX会提供支持
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖请参考[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置注意uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置返回7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置返回300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置如果不存在则取传入的默认值返回600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件require
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件true: 文件存在false: 文件不存在
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
{
"name": "uni-config-center",
"version": "0.0.2",
"description": "配置中心",
"main": "index.js",
"keywords": [],
"author": "DCloud",
"license": "Apache-2.0",
"origin-plugin-dev-name": "uni-list-news-title",
"origin-plugin-version": "0.0.10",
"plugin-dev-name": "uni-list-news-title",
"plugin-version": "0.0.10"
}

View File

@ -0,0 +1,4 @@
## 1.1.52021-05-12
- 新增 组件示例地址
## 1.1.42021-02-05
- 调整为uni_modules目录规范

View File

@ -0,0 +1,132 @@
export default {
"pulldown": "\ue588",
"refreshempty": "\ue461",
"back": "\ue471",
"forward": "\ue470",
"more": "\ue507",
"more-filled": "\ue537",
"scan": "\ue612",
"qq": "\ue264",
"weibo": "\ue260",
"weixin": "\ue261",
"pengyouquan": "\ue262",
"loop": "\ue565",
"refresh": "\ue407",
"refresh-filled": "\ue437",
"arrowthindown": "\ue585",
"arrowthinleft": "\ue586",
"arrowthinright": "\ue587",
"arrowthinup": "\ue584",
"undo-filled": "\ue7d6",
"undo": "\ue406",
"redo": "\ue405",
"redo-filled": "\ue7d9",
"bars": "\ue563",
"chatboxes": "\ue203",
"camera": "\ue301",
"chatboxes-filled": "\ue233",
"camera-filled": "\ue7ef",
"cart-filled": "\ue7f4",
"cart": "\ue7f5",
"checkbox-filled": "\ue442",
"checkbox": "\ue7fa",
"arrowleft": "\ue582",
"arrowdown": "\ue581",
"arrowright": "\ue583",
"smallcircle-filled": "\ue801",
"arrowup": "\ue580",
"circle": "\ue411",
"eye-filled": "\ue568",
"eye-slash-filled": "\ue822",
"eye-slash": "\ue823",
"eye": "\ue824",
"flag-filled": "\ue825",
"flag": "\ue508",
"gear-filled": "\ue532",
"reload": "\ue462",
"gear": "\ue502",
"hand-thumbsdown-filled": "\ue83b",
"hand-thumbsdown": "\ue83c",
"hand-thumbsup-filled": "\ue83d",
"heart-filled": "\ue83e",
"hand-thumbsup": "\ue83f",
"heart": "\ue840",
"home": "\ue500",
"info": "\ue504",
"home-filled": "\ue530",
"info-filled": "\ue534",
"circle-filled": "\ue441",
"chat-filled": "\ue847",
"chat": "\ue263",
"mail-open-filled": "\ue84d",
"email-filled": "\ue231",
"mail-open": "\ue84e",
"email": "\ue201",
"checkmarkempty": "\ue472",
"list": "\ue562",
"locked-filled": "\ue856",
"locked": "\ue506",
"map-filled": "\ue85c",
"map-pin": "\ue85e",
"map-pin-ellipse": "\ue864",
"map": "\ue364",
"minus-filled": "\ue440",
"mic-filled": "\ue332",
"minus": "\ue410",
"micoff": "\ue360",
"mic": "\ue302",
"clear": "\ue434",
"smallcircle": "\ue868",
"close": "\ue404",
"closeempty": "\ue460",
"paperclip": "\ue567",
"paperplane": "\ue503",
"paperplane-filled": "\ue86e",
"person-filled": "\ue131",
"contact-filled": "\ue130",
"person": "\ue101",
"contact": "\ue100",
"images-filled": "\ue87a",
"phone": "\ue200",
"images": "\ue87b",
"image": "\ue363",
"image-filled": "\ue877",
"location-filled": "\ue333",
"location": "\ue303",
"plus-filled": "\ue439",
"plus": "\ue409",
"plusempty": "\ue468",
"help-filled": "\ue535",
"help": "\ue505",
"navigate-filled": "\ue884",
"navigate": "\ue501",
"mic-slash-filled": "\ue892",
"search": "\ue466",
"settings": "\ue560",
"sound": "\ue590",
"sound-filled": "\ue8a1",
"spinner-cycle": "\ue465",
"download-filled": "\ue8a4",
"personadd-filled": "\ue132",
"videocam-filled": "\ue8af",
"personadd": "\ue102",
"upload": "\ue402",
"upload-filled": "\ue8b1",
"starhalf": "\ue463",
"star-filled": "\ue438",
"star": "\ue408",
"trash": "\ue401",
"phone-filled": "\ue230",
"compose": "\ue400",
"videocam": "\ue300",
"trash-filled": "\ue8dc",
"download": "\ue403",
"chatbubble-filled": "\ue232",
"chatbubble": "\ue202",
"cloud-download": "\ue8e4",
"cloud-upload-filled": "\ue8e5",
"cloud-upload": "\ue8e6",
"cloud-download-filled": "\ue8e9",
"headphones":"\ue8bf",
"shop":"\ue609"
}

View File

@ -0,0 +1,91 @@
<template>
<text class="uni-icons" :style="styleObj">
<slot>{{unicode}}</slot>
</text>
</template>
<script>
import { fontData, IconsDataItem } from './uniicons_file'
/**
* Icons 图标
* @description 用于展示 icon 图标
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
* @property {Number} size 图标大小
* @property {String} type 图标图案,参考示例
* @property {String} color 图标颜色
* @property {String} customPrefix 自定义图标
* @event {Function} click 点击 Icon 触发事件
*/
export default {
name: "uni-icons",
props: {
type: {
type: String,
default: ''
},
color: {
type: String,
default: '#333333'
},
size: {
type: [Number, String],
default: 16
},
fontFamily: {
type: String,
default: ''
}
},
data() {
return {};
},
computed: {
unicode() : string {
let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
if (codes !== null) {
return codes.unicode
}
return ''
},
iconSize() : string {
const size = this.size
if (typeof size == 'string') {
const reg = /^[0-9]*$/g
return reg.test(size as string) ? '' + size + 'px' : '' + size;
// return '' + this.size
}
return this.getFontSize(size as number)
},
styleObj() : UTSJSONObject {
if (this.fontFamily !== '') {
return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
}
return { color: this.color, fontSize: this.iconSize }
}
},
created() { },
methods: {
/**
* 字体大小
*/
getFontSize(size : number) : string {
return size + 'px';
},
},
}
</script>
<style scoped>
@font-face {
font-family: UniIconsFontFamily;
src: url('./uniicons.ttf');
}
.uni-icons {
font-family: UniIconsFontFamily;
font-size: 18px;
font-style: normal;
color: #333;
}
</style>

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,664 @@
.uniui-cart-filled:before {
content: "\e6d0";
}
.uniui-gift-filled:before {
content: "\e6c4";
}
.uniui-color:before {
content: "\e6cf";
}
.uniui-wallet:before {
content: "\e6b1";
}
.uniui-settings-filled:before {
content: "\e6ce";
}
.uniui-auth-filled:before {
content: "\e6cc";
}
.uniui-shop-filled:before {
content: "\e6cd";
}
.uniui-staff-filled:before {
content: "\e6cb";
}
.uniui-vip-filled:before {
content: "\e6c6";
}
.uniui-plus-filled:before {
content: "\e6c7";
}
.uniui-folder-add-filled:before {
content: "\e6c8";
}
.uniui-color-filled:before {
content: "\e6c9";
}
.uniui-tune-filled:before {
content: "\e6ca";
}
.uniui-calendar-filled:before {
content: "\e6c0";
}
.uniui-notification-filled:before {
content: "\e6c1";
}
.uniui-wallet-filled:before {
content: "\e6c2";
}
.uniui-medal-filled:before {
content: "\e6c3";
}
.uniui-fire-filled:before {
content: "\e6c5";
}
.uniui-refreshempty:before {
content: "\e6bf";
}
.uniui-location-filled:before {
content: "\e6af";
}
.uniui-person-filled:before {
content: "\e69d";
}
.uniui-personadd-filled:before {
content: "\e698";
}
.uniui-arrowthinleft:before {
content: "\e6d2";
}
.uniui-arrowthinup:before {
content: "\e6d3";
}
.uniui-arrowthindown:before {
content: "\e6d4";
}
.uniui-back:before {
content: "\e6b9";
}
.uniui-forward:before {
content: "\e6ba";
}
.uniui-arrow-right:before {
content: "\e6bb";
}
.uniui-arrow-left:before {
content: "\e6bc";
}
.uniui-arrow-up:before {
content: "\e6bd";
}
.uniui-arrow-down:before {
content: "\e6be";
}
.uniui-arrowthinright:before {
content: "\e6d1";
}
.uniui-down:before {
content: "\e6b8";
}
.uniui-bottom:before {
content: "\e6b8";
}
.uniui-arrowright:before {
content: "\e6d5";
}
.uniui-right:before {
content: "\e6b5";
}
.uniui-up:before {
content: "\e6b6";
}
.uniui-top:before {
content: "\e6b6";
}
.uniui-left:before {
content: "\e6b7";
}
.uniui-arrowup:before {
content: "\e6d6";
}
.uniui-eye:before {
content: "\e651";
}
.uniui-eye-filled:before {
content: "\e66a";
}
.uniui-eye-slash:before {
content: "\e6b3";
}
.uniui-eye-slash-filled:before {
content: "\e6b4";
}
.uniui-info-filled:before {
content: "\e649";
}
.uniui-reload:before {
content: "\e6b2";
}
.uniui-micoff-filled:before {
content: "\e6b0";
}
.uniui-map-pin-ellipse:before {
content: "\e6ac";
}
.uniui-map-pin:before {
content: "\e6ad";
}
.uniui-location:before {
content: "\e6ae";
}
.uniui-starhalf:before {
content: "\e683";
}
.uniui-star:before {
content: "\e688";
}
.uniui-star-filled:before {
content: "\e68f";
}
.uniui-calendar:before {
content: "\e6a0";
}
.uniui-fire:before {
content: "\e6a1";
}
.uniui-medal:before {
content: "\e6a2";
}
.uniui-font:before {
content: "\e6a3";
}
.uniui-gift:before {
content: "\e6a4";
}
.uniui-link:before {
content: "\e6a5";
}
.uniui-notification:before {
content: "\e6a6";
}
.uniui-staff:before {
content: "\e6a7";
}
.uniui-vip:before {
content: "\e6a8";
}
.uniui-folder-add:before {
content: "\e6a9";
}
.uniui-tune:before {
content: "\e6aa";
}
.uniui-auth:before {
content: "\e6ab";
}
.uniui-person:before {
content: "\e699";
}
.uniui-email-filled:before {
content: "\e69a";
}
.uniui-phone-filled:before {
content: "\e69b";
}
.uniui-phone:before {
content: "\e69c";
}
.uniui-email:before {
content: "\e69e";
}
.uniui-personadd:before {
content: "\e69f";
}
.uniui-chatboxes-filled:before {
content: "\e692";
}
.uniui-contact:before {
content: "\e693";
}
.uniui-chatbubble-filled:before {
content: "\e694";
}
.uniui-contact-filled:before {
content: "\e695";
}
.uniui-chatboxes:before {
content: "\e696";
}
.uniui-chatbubble:before {
content: "\e697";
}
.uniui-upload-filled:before {
content: "\e68e";
}
.uniui-upload:before {
content: "\e690";
}
.uniui-weixin:before {
content: "\e691";
}
.uniui-compose:before {
content: "\e67f";
}
.uniui-qq:before {
content: "\e680";
}
.uniui-download-filled:before {
content: "\e681";
}
.uniui-pyq:before {
content: "\e682";
}
.uniui-sound:before {
content: "\e684";
}
.uniui-trash-filled:before {
content: "\e685";
}
.uniui-sound-filled:before {
content: "\e686";
}
.uniui-trash:before {
content: "\e687";
}
.uniui-videocam-filled:before {
content: "\e689";
}
.uniui-spinner-cycle:before {
content: "\e68a";
}
.uniui-weibo:before {
content: "\e68b";
}
.uniui-videocam:before {
content: "\e68c";
}
.uniui-download:before {
content: "\e68d";
}
.uniui-help:before {
content: "\e679";
}
.uniui-navigate-filled:before {
content: "\e67a";
}
.uniui-plusempty:before {
content: "\e67b";
}
.uniui-smallcircle:before {
content: "\e67c";
}
.uniui-minus-filled:before {
content: "\e67d";
}
.uniui-micoff:before {
content: "\e67e";
}
.uniui-closeempty:before {
content: "\e66c";
}
.uniui-clear:before {
content: "\e66d";
}
.uniui-navigate:before {
content: "\e66e";
}
.uniui-minus:before {
content: "\e66f";
}
.uniui-image:before {
content: "\e670";
}
.uniui-mic:before {
content: "\e671";
}
.uniui-paperplane:before {
content: "\e672";
}
.uniui-close:before {
content: "\e673";
}
.uniui-help-filled:before {
content: "\e674";
}
.uniui-paperplane-filled:before {
content: "\e675";
}
.uniui-plus:before {
content: "\e676";
}
.uniui-mic-filled:before {
content: "\e677";
}
.uniui-image-filled:before {
content: "\e678";
}
.uniui-locked-filled:before {
content: "\e668";
}
.uniui-info:before {
content: "\e669";
}
.uniui-locked:before {
content: "\e66b";
}
.uniui-camera-filled:before {
content: "\e658";
}
.uniui-chat-filled:before {
content: "\e659";
}
.uniui-camera:before {
content: "\e65a";
}
.uniui-circle:before {
content: "\e65b";
}
.uniui-checkmarkempty:before {
content: "\e65c";
}
.uniui-chat:before {
content: "\e65d";
}
.uniui-circle-filled:before {
content: "\e65e";
}
.uniui-flag:before {
content: "\e65f";
}
.uniui-flag-filled:before {
content: "\e660";
}
.uniui-gear-filled:before {
content: "\e661";
}
.uniui-home:before {
content: "\e662";
}
.uniui-home-filled:before {
content: "\e663";
}
.uniui-gear:before {
content: "\e664";
}
.uniui-smallcircle-filled:before {
content: "\e665";
}
.uniui-map-filled:before {
content: "\e666";
}
.uniui-map:before {
content: "\e667";
}
.uniui-refresh-filled:before {
content: "\e656";
}
.uniui-refresh:before {
content: "\e657";
}
.uniui-cloud-upload:before {
content: "\e645";
}
.uniui-cloud-download-filled:before {
content: "\e646";
}
.uniui-cloud-download:before {
content: "\e647";
}
.uniui-cloud-upload-filled:before {
content: "\e648";
}
.uniui-redo:before {
content: "\e64a";
}
.uniui-images-filled:before {
content: "\e64b";
}
.uniui-undo-filled:before {
content: "\e64c";
}
.uniui-more:before {
content: "\e64d";
}
.uniui-more-filled:before {
content: "\e64e";
}
.uniui-undo:before {
content: "\e64f";
}
.uniui-images:before {
content: "\e650";
}
.uniui-paperclip:before {
content: "\e652";
}
.uniui-settings:before {
content: "\e653";
}
.uniui-search:before {
content: "\e654";
}
.uniui-redo-filled:before {
content: "\e655";
}
.uniui-list:before {
content: "\e644";
}
.uniui-mail-open-filled:before {
content: "\e63a";
}
.uniui-hand-down-filled:before {
content: "\e63c";
}
.uniui-hand-down:before {
content: "\e63d";
}
.uniui-hand-up-filled:before {
content: "\e63e";
}
.uniui-hand-up:before {
content: "\e63f";
}
.uniui-heart-filled:before {
content: "\e641";
}
.uniui-mail-open:before {
content: "\e643";
}
.uniui-heart:before {
content: "\e639";
}
.uniui-loop:before {
content: "\e633";
}
.uniui-pulldown:before {
content: "\e632";
}
.uniui-scan:before {
content: "\e62a";
}
.uniui-bars:before {
content: "\e627";
}
.uniui-checkbox:before {
content: "\e62b";
}
.uniui-checkbox-filled:before {
content: "\e62c";
}
.uniui-shop:before {
content: "\e62f";
}
.uniui-headphones:before {
content: "\e630";
}
.uniui-cart:before {
content: "\e631";
}

View File

@ -0,0 +1,664 @@
export type IconsData = {
id : string
name : string
font_family : string
css_prefix_text : string
description : string
glyphs : Array<IconsDataItem>
}
export type IconsDataItem = {
font_class : string
unicode : string
}
export const fontData = [
{
"font_class": "arrow-down",
"unicode": "\ue6be"
},
{
"font_class": "arrow-left",
"unicode": "\ue6bc"
},
{
"font_class": "arrow-right",
"unicode": "\ue6bb"
},
{
"font_class": "arrow-up",
"unicode": "\ue6bd"
},
{
"font_class": "auth",
"unicode": "\ue6ab"
},
{
"font_class": "auth-filled",
"unicode": "\ue6cc"
},
{
"font_class": "back",
"unicode": "\ue6b9"
},
{
"font_class": "bars",
"unicode": "\ue627"
},
{
"font_class": "calendar",
"unicode": "\ue6a0"
},
{
"font_class": "calendar-filled",
"unicode": "\ue6c0"
},
{
"font_class": "camera",
"unicode": "\ue65a"
},
{
"font_class": "camera-filled",
"unicode": "\ue658"
},
{
"font_class": "cart",
"unicode": "\ue631"
},
{
"font_class": "cart-filled",
"unicode": "\ue6d0"
},
{
"font_class": "chat",
"unicode": "\ue65d"
},
{
"font_class": "chat-filled",
"unicode": "\ue659"
},
{
"font_class": "chatboxes",
"unicode": "\ue696"
},
{
"font_class": "chatboxes-filled",
"unicode": "\ue692"
},
{
"font_class": "chatbubble",
"unicode": "\ue697"
},
{
"font_class": "chatbubble-filled",
"unicode": "\ue694"
},
{
"font_class": "checkbox",
"unicode": "\ue62b"
},
{
"font_class": "checkbox-filled",
"unicode": "\ue62c"
},
{
"font_class": "checkmarkempty",
"unicode": "\ue65c"
},
{
"font_class": "circle",
"unicode": "\ue65b"
},
{
"font_class": "circle-filled",
"unicode": "\ue65e"
},
{
"font_class": "clear",
"unicode": "\ue66d"
},
{
"font_class": "close",
"unicode": "\ue673"
},
{
"font_class": "closeempty",
"unicode": "\ue66c"
},
{
"font_class": "cloud-download",
"unicode": "\ue647"
},
{
"font_class": "cloud-download-filled",
"unicode": "\ue646"
},
{
"font_class": "cloud-upload",
"unicode": "\ue645"
},
{
"font_class": "cloud-upload-filled",
"unicode": "\ue648"
},
{
"font_class": "color",
"unicode": "\ue6cf"
},
{
"font_class": "color-filled",
"unicode": "\ue6c9"
},
{
"font_class": "compose",
"unicode": "\ue67f"
},
{
"font_class": "contact",
"unicode": "\ue693"
},
{
"font_class": "contact-filled",
"unicode": "\ue695"
},
{
"font_class": "down",
"unicode": "\ue6b8"
},
{
"font_class": "bottom",
"unicode": "\ue6b8"
},
{
"font_class": "download",
"unicode": "\ue68d"
},
{
"font_class": "download-filled",
"unicode": "\ue681"
},
{
"font_class": "email",
"unicode": "\ue69e"
},
{
"font_class": "email-filled",
"unicode": "\ue69a"
},
{
"font_class": "eye",
"unicode": "\ue651"
},
{
"font_class": "eye-filled",
"unicode": "\ue66a"
},
{
"font_class": "eye-slash",
"unicode": "\ue6b3"
},
{
"font_class": "eye-slash-filled",
"unicode": "\ue6b4"
},
{
"font_class": "fire",
"unicode": "\ue6a1"
},
{
"font_class": "fire-filled",
"unicode": "\ue6c5"
},
{
"font_class": "flag",
"unicode": "\ue65f"
},
{
"font_class": "flag-filled",
"unicode": "\ue660"
},
{
"font_class": "folder-add",
"unicode": "\ue6a9"
},
{
"font_class": "folder-add-filled",
"unicode": "\ue6c8"
},
{
"font_class": "font",
"unicode": "\ue6a3"
},
{
"font_class": "forward",
"unicode": "\ue6ba"
},
{
"font_class": "gear",
"unicode": "\ue664"
},
{
"font_class": "gear-filled",
"unicode": "\ue661"
},
{
"font_class": "gift",
"unicode": "\ue6a4"
},
{
"font_class": "gift-filled",
"unicode": "\ue6c4"
},
{
"font_class": "hand-down",
"unicode": "\ue63d"
},
{
"font_class": "hand-down-filled",
"unicode": "\ue63c"
},
{
"font_class": "hand-up",
"unicode": "\ue63f"
},
{
"font_class": "hand-up-filled",
"unicode": "\ue63e"
},
{
"font_class": "headphones",
"unicode": "\ue630"
},
{
"font_class": "heart",
"unicode": "\ue639"
},
{
"font_class": "heart-filled",
"unicode": "\ue641"
},
{
"font_class": "help",
"unicode": "\ue679"
},
{
"font_class": "help-filled",
"unicode": "\ue674"
},
{
"font_class": "home",
"unicode": "\ue662"
},
{
"font_class": "home-filled",
"unicode": "\ue663"
},
{
"font_class": "image",
"unicode": "\ue670"
},
{
"font_class": "image-filled",
"unicode": "\ue678"
},
{
"font_class": "images",
"unicode": "\ue650"
},
{
"font_class": "images-filled",
"unicode": "\ue64b"
},
{
"font_class": "info",
"unicode": "\ue669"
},
{
"font_class": "info-filled",
"unicode": "\ue649"
},
{
"font_class": "left",
"unicode": "\ue6b7"
},
{
"font_class": "link",
"unicode": "\ue6a5"
},
{
"font_class": "list",
"unicode": "\ue644"
},
{
"font_class": "location",
"unicode": "\ue6ae"
},
{
"font_class": "location-filled",
"unicode": "\ue6af"
},
{
"font_class": "locked",
"unicode": "\ue66b"
},
{
"font_class": "locked-filled",
"unicode": "\ue668"
},
{
"font_class": "loop",
"unicode": "\ue633"
},
{
"font_class": "mail-open",
"unicode": "\ue643"
},
{
"font_class": "mail-open-filled",
"unicode": "\ue63a"
},
{
"font_class": "map",
"unicode": "\ue667"
},
{
"font_class": "map-filled",
"unicode": "\ue666"
},
{
"font_class": "map-pin",
"unicode": "\ue6ad"
},
{
"font_class": "map-pin-ellipse",
"unicode": "\ue6ac"
},
{
"font_class": "medal",
"unicode": "\ue6a2"
},
{
"font_class": "medal-filled",
"unicode": "\ue6c3"
},
{
"font_class": "mic",
"unicode": "\ue671"
},
{
"font_class": "mic-filled",
"unicode": "\ue677"
},
{
"font_class": "micoff",
"unicode": "\ue67e"
},
{
"font_class": "micoff-filled",
"unicode": "\ue6b0"
},
{
"font_class": "minus",
"unicode": "\ue66f"
},
{
"font_class": "minus-filled",
"unicode": "\ue67d"
},
{
"font_class": "more",
"unicode": "\ue64d"
},
{
"font_class": "more-filled",
"unicode": "\ue64e"
},
{
"font_class": "navigate",
"unicode": "\ue66e"
},
{
"font_class": "navigate-filled",
"unicode": "\ue67a"
},
{
"font_class": "notification",
"unicode": "\ue6a6"
},
{
"font_class": "notification-filled",
"unicode": "\ue6c1"
},
{
"font_class": "paperclip",
"unicode": "\ue652"
},
{
"font_class": "paperplane",
"unicode": "\ue672"
},
{
"font_class": "paperplane-filled",
"unicode": "\ue675"
},
{
"font_class": "person",
"unicode": "\ue699"
},
{
"font_class": "person-filled",
"unicode": "\ue69d"
},
{
"font_class": "personadd",
"unicode": "\ue69f"
},
{
"font_class": "personadd-filled",
"unicode": "\ue698"
},
{
"font_class": "personadd-filled-copy",
"unicode": "\ue6d1"
},
{
"font_class": "phone",
"unicode": "\ue69c"
},
{
"font_class": "phone-filled",
"unicode": "\ue69b"
},
{
"font_class": "plus",
"unicode": "\ue676"
},
{
"font_class": "plus-filled",
"unicode": "\ue6c7"
},
{
"font_class": "plusempty",
"unicode": "\ue67b"
},
{
"font_class": "pulldown",
"unicode": "\ue632"
},
{
"font_class": "pyq",
"unicode": "\ue682"
},
{
"font_class": "qq",
"unicode": "\ue680"
},
{
"font_class": "redo",
"unicode": "\ue64a"
},
{
"font_class": "redo-filled",
"unicode": "\ue655"
},
{
"font_class": "refresh",
"unicode": "\ue657"
},
{
"font_class": "refresh-filled",
"unicode": "\ue656"
},
{
"font_class": "refreshempty",
"unicode": "\ue6bf"
},
{
"font_class": "reload",
"unicode": "\ue6b2"
},
{
"font_class": "right",
"unicode": "\ue6b5"
},
{
"font_class": "scan",
"unicode": "\ue62a"
},
{
"font_class": "search",
"unicode": "\ue654"
},
{
"font_class": "settings",
"unicode": "\ue653"
},
{
"font_class": "settings-filled",
"unicode": "\ue6ce"
},
{
"font_class": "shop",
"unicode": "\ue62f"
},
{
"font_class": "shop-filled",
"unicode": "\ue6cd"
},
{
"font_class": "smallcircle",
"unicode": "\ue67c"
},
{
"font_class": "smallcircle-filled",
"unicode": "\ue665"
},
{
"font_class": "sound",
"unicode": "\ue684"
},
{
"font_class": "sound-filled",
"unicode": "\ue686"
},
{
"font_class": "spinner-cycle",
"unicode": "\ue68a"
},
{
"font_class": "staff",
"unicode": "\ue6a7"
},
{
"font_class": "staff-filled",
"unicode": "\ue6cb"
},
{
"font_class": "star",
"unicode": "\ue688"
},
{
"font_class": "star-filled",
"unicode": "\ue68f"
},
{
"font_class": "starhalf",
"unicode": "\ue683"
},
{
"font_class": "trash",
"unicode": "\ue687"
},
{
"font_class": "trash-filled",
"unicode": "\ue685"
},
{
"font_class": "tune",
"unicode": "\ue6aa"
},
{
"font_class": "tune-filled",
"unicode": "\ue6ca"
},
{
"font_class": "undo",
"unicode": "\ue64f"
},
{
"font_class": "undo-filled",
"unicode": "\ue64c"
},
{
"font_class": "up",
"unicode": "\ue6b6"
},
{
"font_class": "top",
"unicode": "\ue6b6"
},
{
"font_class": "upload",
"unicode": "\ue690"
},
{
"font_class": "upload-filled",
"unicode": "\ue68e"
},
{
"font_class": "videocam",
"unicode": "\ue68c"
},
{
"font_class": "videocam-filled",
"unicode": "\ue689"
},
{
"font_class": "vip",
"unicode": "\ue6a8"
},
{
"font_class": "vip-filled",
"unicode": "\ue6c6"
},
{
"font_class": "wallet",
"unicode": "\ue6b1"
},
{
"font_class": "wallet-filled",
"unicode": "\ue6c2"
},
{
"font_class": "weibo",
"unicode": "\ue68b"
},
{
"font_class": "weixin",
"unicode": "\ue691"
}
] as IconsDataItem[]
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

View File

@ -0,0 +1,649 @@
export const fontData = [
{
"font_class": "arrow-down",
"unicode": "\ue6be"
},
{
"font_class": "arrow-left",
"unicode": "\ue6bc"
},
{
"font_class": "arrow-right",
"unicode": "\ue6bb"
},
{
"font_class": "arrow-up",
"unicode": "\ue6bd"
},
{
"font_class": "auth",
"unicode": "\ue6ab"
},
{
"font_class": "auth-filled",
"unicode": "\ue6cc"
},
{
"font_class": "back",
"unicode": "\ue6b9"
},
{
"font_class": "bars",
"unicode": "\ue627"
},
{
"font_class": "calendar",
"unicode": "\ue6a0"
},
{
"font_class": "calendar-filled",
"unicode": "\ue6c0"
},
{
"font_class": "camera",
"unicode": "\ue65a"
},
{
"font_class": "camera-filled",
"unicode": "\ue658"
},
{
"font_class": "cart",
"unicode": "\ue631"
},
{
"font_class": "cart-filled",
"unicode": "\ue6d0"
},
{
"font_class": "chat",
"unicode": "\ue65d"
},
{
"font_class": "chat-filled",
"unicode": "\ue659"
},
{
"font_class": "chatboxes",
"unicode": "\ue696"
},
{
"font_class": "chatboxes-filled",
"unicode": "\ue692"
},
{
"font_class": "chatbubble",
"unicode": "\ue697"
},
{
"font_class": "chatbubble-filled",
"unicode": "\ue694"
},
{
"font_class": "checkbox",
"unicode": "\ue62b"
},
{
"font_class": "checkbox-filled",
"unicode": "\ue62c"
},
{
"font_class": "checkmarkempty",
"unicode": "\ue65c"
},
{
"font_class": "circle",
"unicode": "\ue65b"
},
{
"font_class": "circle-filled",
"unicode": "\ue65e"
},
{
"font_class": "clear",
"unicode": "\ue66d"
},
{
"font_class": "close",
"unicode": "\ue673"
},
{
"font_class": "closeempty",
"unicode": "\ue66c"
},
{
"font_class": "cloud-download",
"unicode": "\ue647"
},
{
"font_class": "cloud-download-filled",
"unicode": "\ue646"
},
{
"font_class": "cloud-upload",
"unicode": "\ue645"
},
{
"font_class": "cloud-upload-filled",
"unicode": "\ue648"
},
{
"font_class": "color",
"unicode": "\ue6cf"
},
{
"font_class": "color-filled",
"unicode": "\ue6c9"
},
{
"font_class": "compose",
"unicode": "\ue67f"
},
{
"font_class": "contact",
"unicode": "\ue693"
},
{
"font_class": "contact-filled",
"unicode": "\ue695"
},
{
"font_class": "down",
"unicode": "\ue6b8"
},
{
"font_class": "bottom",
"unicode": "\ue6b8"
},
{
"font_class": "download",
"unicode": "\ue68d"
},
{
"font_class": "download-filled",
"unicode": "\ue681"
},
{
"font_class": "email",
"unicode": "\ue69e"
},
{
"font_class": "email-filled",
"unicode": "\ue69a"
},
{
"font_class": "eye",
"unicode": "\ue651"
},
{
"font_class": "eye-filled",
"unicode": "\ue66a"
},
{
"font_class": "eye-slash",
"unicode": "\ue6b3"
},
{
"font_class": "eye-slash-filled",
"unicode": "\ue6b4"
},
{
"font_class": "fire",
"unicode": "\ue6a1"
},
{
"font_class": "fire-filled",
"unicode": "\ue6c5"
},
{
"font_class": "flag",
"unicode": "\ue65f"
},
{
"font_class": "flag-filled",
"unicode": "\ue660"
},
{
"font_class": "folder-add",
"unicode": "\ue6a9"
},
{
"font_class": "folder-add-filled",
"unicode": "\ue6c8"
},
{
"font_class": "font",
"unicode": "\ue6a3"
},
{
"font_class": "forward",
"unicode": "\ue6ba"
},
{
"font_class": "gear",
"unicode": "\ue664"
},
{
"font_class": "gear-filled",
"unicode": "\ue661"
},
{
"font_class": "gift",
"unicode": "\ue6a4"
},
{
"font_class": "gift-filled",
"unicode": "\ue6c4"
},
{
"font_class": "hand-down",
"unicode": "\ue63d"
},
{
"font_class": "hand-down-filled",
"unicode": "\ue63c"
},
{
"font_class": "hand-up",
"unicode": "\ue63f"
},
{
"font_class": "hand-up-filled",
"unicode": "\ue63e"
},
{
"font_class": "headphones",
"unicode": "\ue630"
},
{
"font_class": "heart",
"unicode": "\ue639"
},
{
"font_class": "heart-filled",
"unicode": "\ue641"
},
{
"font_class": "help",
"unicode": "\ue679"
},
{
"font_class": "help-filled",
"unicode": "\ue674"
},
{
"font_class": "home",
"unicode": "\ue662"
},
{
"font_class": "home-filled",
"unicode": "\ue663"
},
{
"font_class": "image",
"unicode": "\ue670"
},
{
"font_class": "image-filled",
"unicode": "\ue678"
},
{
"font_class": "images",
"unicode": "\ue650"
},
{
"font_class": "images-filled",
"unicode": "\ue64b"
},
{
"font_class": "info",
"unicode": "\ue669"
},
{
"font_class": "info-filled",
"unicode": "\ue649"
},
{
"font_class": "left",
"unicode": "\ue6b7"
},
{
"font_class": "link",
"unicode": "\ue6a5"
},
{
"font_class": "list",
"unicode": "\ue644"
},
{
"font_class": "location",
"unicode": "\ue6ae"
},
{
"font_class": "location-filled",
"unicode": "\ue6af"
},
{
"font_class": "locked",
"unicode": "\ue66b"
},
{
"font_class": "locked-filled",
"unicode": "\ue668"
},
{
"font_class": "loop",
"unicode": "\ue633"
},
{
"font_class": "mail-open",
"unicode": "\ue643"
},
{
"font_class": "mail-open-filled",
"unicode": "\ue63a"
},
{
"font_class": "map",
"unicode": "\ue667"
},
{
"font_class": "map-filled",
"unicode": "\ue666"
},
{
"font_class": "map-pin",
"unicode": "\ue6ad"
},
{
"font_class": "map-pin-ellipse",
"unicode": "\ue6ac"
},
{
"font_class": "medal",
"unicode": "\ue6a2"
},
{
"font_class": "medal-filled",
"unicode": "\ue6c3"
},
{
"font_class": "mic",
"unicode": "\ue671"
},
{
"font_class": "mic-filled",
"unicode": "\ue677"
},
{
"font_class": "micoff",
"unicode": "\ue67e"
},
{
"font_class": "micoff-filled",
"unicode": "\ue6b0"
},
{
"font_class": "minus",
"unicode": "\ue66f"
},
{
"font_class": "minus-filled",
"unicode": "\ue67d"
},
{
"font_class": "more",
"unicode": "\ue64d"
},
{
"font_class": "more-filled",
"unicode": "\ue64e"
},
{
"font_class": "navigate",
"unicode": "\ue66e"
},
{
"font_class": "navigate-filled",
"unicode": "\ue67a"
},
{
"font_class": "notification",
"unicode": "\ue6a6"
},
{
"font_class": "notification-filled",
"unicode": "\ue6c1"
},
{
"font_class": "paperclip",
"unicode": "\ue652"
},
{
"font_class": "paperplane",
"unicode": "\ue672"
},
{
"font_class": "paperplane-filled",
"unicode": "\ue675"
},
{
"font_class": "person",
"unicode": "\ue699"
},
{
"font_class": "person-filled",
"unicode": "\ue69d"
},
{
"font_class": "personadd",
"unicode": "\ue69f"
},
{
"font_class": "personadd-filled",
"unicode": "\ue698"
},
{
"font_class": "personadd-filled-copy",
"unicode": "\ue6d1"
},
{
"font_class": "phone",
"unicode": "\ue69c"
},
{
"font_class": "phone-filled",
"unicode": "\ue69b"
},
{
"font_class": "plus",
"unicode": "\ue676"
},
{
"font_class": "plus-filled",
"unicode": "\ue6c7"
},
{
"font_class": "plusempty",
"unicode": "\ue67b"
},
{
"font_class": "pulldown",
"unicode": "\ue632"
},
{
"font_class": "pyq",
"unicode": "\ue682"
},
{
"font_class": "qq",
"unicode": "\ue680"
},
{
"font_class": "redo",
"unicode": "\ue64a"
},
{
"font_class": "redo-filled",
"unicode": "\ue655"
},
{
"font_class": "refresh",
"unicode": "\ue657"
},
{
"font_class": "refresh-filled",
"unicode": "\ue656"
},
{
"font_class": "refreshempty",
"unicode": "\ue6bf"
},
{
"font_class": "reload",
"unicode": "\ue6b2"
},
{
"font_class": "right",
"unicode": "\ue6b5"
},
{
"font_class": "scan",
"unicode": "\ue62a"
},
{
"font_class": "search",
"unicode": "\ue654"
},
{
"font_class": "settings",
"unicode": "\ue653"
},
{
"font_class": "settings-filled",
"unicode": "\ue6ce"
},
{
"font_class": "shop",
"unicode": "\ue62f"
},
{
"font_class": "shop-filled",
"unicode": "\ue6cd"
},
{
"font_class": "smallcircle",
"unicode": "\ue67c"
},
{
"font_class": "smallcircle-filled",
"unicode": "\ue665"
},
{
"font_class": "sound",
"unicode": "\ue684"
},
{
"font_class": "sound-filled",
"unicode": "\ue686"
},
{
"font_class": "spinner-cycle",
"unicode": "\ue68a"
},
{
"font_class": "staff",
"unicode": "\ue6a7"
},
{
"font_class": "staff-filled",
"unicode": "\ue6cb"
},
{
"font_class": "star",
"unicode": "\ue688"
},
{
"font_class": "star-filled",
"unicode": "\ue68f"
},
{
"font_class": "starhalf",
"unicode": "\ue683"
},
{
"font_class": "trash",
"unicode": "\ue687"
},
{
"font_class": "trash-filled",
"unicode": "\ue685"
},
{
"font_class": "tune",
"unicode": "\ue6aa"
},
{
"font_class": "tune-filled",
"unicode": "\ue6ca"
},
{
"font_class": "undo",
"unicode": "\ue64f"
},
{
"font_class": "undo-filled",
"unicode": "\ue64c"
},
{
"font_class": "up",
"unicode": "\ue6b6"
},
{
"font_class": "top",
"unicode": "\ue6b6"
},
{
"font_class": "upload",
"unicode": "\ue690"
},
{
"font_class": "upload-filled",
"unicode": "\ue68e"
},
{
"font_class": "videocam",
"unicode": "\ue68c"
},
{
"font_class": "videocam-filled",
"unicode": "\ue689"
},
{
"font_class": "vip",
"unicode": "\ue6a8"
},
{
"font_class": "vip-filled",
"unicode": "\ue6c6"
},
{
"font_class": "wallet",
"unicode": "\ue6b1"
},
{
"font_class": "wallet-filled",
"unicode": "\ue6c2"
},
{
"font_class": "weibo",
"unicode": "\ue68b"
},
{
"font_class": "weixin",
"unicode": "\ue691"
}
]
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

View File

@ -0,0 +1,82 @@
{
"id": "uni-icons",
"displayName": "uni-icons 图标",
"version": "1.1.5",
"description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
"keywords": [
"uni-ui",
"uniui",
"icon",
"图标"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,46 @@
## Icons 图标
> **组件名uni-icons**
> 代码块: `uIcons`
用于展示 icons 图标 。
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
<uni-icons type="contact" size="30"></uni-icons>
```
## API
### Icons Props
|属性名 |类型 |默认值 |说明 |
|:-: |:-: |:-: |:-: |
|size |Number |24 |图标大小 |
|type |String |- |图标图案,参考示例 |
|color |String |- |图标颜色 |
### Icons Events
|事件名 |说明 |返回值|
|:-: |:-: |:-: |
|@click|点击 Icon 触发事件|- |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/icons/icons](https://hellouniapp.dcloud.net.cn/pages/extUI/icons/icons)

View File

@ -0,0 +1,24 @@
## 3.1.02021-04-19
- 增加对用户名、邮箱、密码字段的两端去空格
- 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive)
- 修复 customToken导出async方法报错的Bug
## 3.0.122021-04-13
- 调整bindTokenToDevice默认值为false
## 3.0.112021-04-12
- 修复3.0.7版本引出的多个用户访问时可能出现30201报错的Bug
## 3.0.102021-04-08
- 优化错误提示
## 3.0.92021-04-08
- bindMobile接口支持通过一键登录的方式绑定
- 优化错误提示
## 3.0.82021-03-19
- 修复 3.0.7版本某些情况下生成token报错的Bug
## 3.0.72021-03-19
- 新增 支持uni-config-center更新uni-id无须再担心配置被覆盖 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=uni-config-center)
- 新增 自定义token内容可以缓存角色权限之外的更多信息到客户端 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=custom-token)
- 新增 支持传入context获取uni-id实例防止单实例多并发时全局context混乱 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=create-instance)
## 3.0.62021-03-05
- 新增[uniID.wxBizDataCrypt](https://uniapp.dcloud.io/uniCloud/uni-id?id=%e5%be%ae%e4%bf%a1%e6%95%b0%e6%8d%ae%e8%a7%a3%e5%af%86)方法
- 优化loginByApple方法提高接口响应速度
## 3.0.52021-02-03
- 调整为uni_modules目录规范

View File

@ -0,0 +1,80 @@
{
"id": "uni-id",
"displayName": "uni-id公共模块",
"version": "3.1.0",
"description": "简单、统一、可扩展的用户中心",
"keywords": [
"uniid",
"uni-id",
"用户管理",
"用户中心",
"短信验证码"
],
"repository": "https://gitee.com/dcloud/uni-id.git",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"云函数模板"
],
"sale": {
"regular": {
"price": 0
},
"sourcecode": {
"price": 0
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": ["uni-config-center"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,33 @@
**文档已移至[uni-id文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id)**
> 一般uni-id升级大版本时为不兼容更新从低版本迁移到高版本请参考[uni-id迁移指南](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=migration)
## 重要升级说明
**uni-id 3.x版本搭配的uniCloud admin版本需大于1.2.10。**
### 缓存角色权限
自`uni-id 3.0.0`起支持在token内缓存用户的角色权限默认开启此功能各登录接口的needPermission参数不再生效。如需关闭请在config内配置`"removePermissionAndRoleFromToken": true`。
为什么要缓存角色权限?要知道云数据库是按照读写次数来收取费用的,并且读写数据库会拖慢接口响应速度。未配置`"removePermissionAndRoleFromToken": true`的情况下可以在调用checkToken接口时不查询数据库获取用户角色权限。
详细checkToken流程如下
![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/ed45d350-5a4d-11eb-b997-9918a5dda011.jpg)
可以看出旧版tokenremovePermissionAndRoleFromToken为true时生成的在checkToken时如需返回权限需要进行两次数据库查询。新版token不需要查库即可返回权限信息。
**注意**
- 由于角色权限缓存在token内可能会存在权限已经更新但是用户token未过期之前依然是旧版角色权限的情况。可以调短一些token过期时间来减少这种情况的影响。
- admin角色token内不包含permission如需自行判断用户是否有某个权限要注意admin角色需要额外判断一下写法如下
```js
const {
role,
permission
} = await uniID.checkToken(event.uniIdToken)
if(role.includes('admin') || permission.includes('your permission id')) {
// 当前角色拥有'your permission id'对应的权限
}
```

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
{
"name": "uni-id",
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"uni-config-center": {
"version": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
}

View File

@ -0,0 +1,20 @@
{
"name": "uni-id",
"version": "3.1.0",
"description": "uni-id for uniCloud",
"main": "index.js",
"homepage": "https:\/\/uniapp.dcloud.io\/uniCloud\/uni-id",
"repository": {
"type": "git",
"url": "git+https:\/\/gitee.com\/dcloud\/uni-id.git"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
},
"origin-plugin-dev-name": "uni-list-news-title",
"origin-plugin-version": "0.0.10",
"plugin-dev-name": "uni-list-news-title",
"plugin-version": "0.0.10"
}

View File

@ -0,0 +1,7 @@
## 1.0.172021-05-12
- 新增 组件示例地址
## 1.0.162021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.0.152021-02-05
- 调整为uni_modules目录规范
- 修复 uni-list-chat 角标显示不正常的问题

View File

@ -0,0 +1,107 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<!-- #endif -->
<view class="uni-list-ad">
<view v-if="borderShow" :class="{'uni-list--border':border,'uni-list-item--first':isFirstChild}"></view>
<ad style="width: 200px;height: 300px;border-width: 1px;border-color: red;border-style: solid;" adpid="1111111111"
unit-id="" appid="" apid="" type="feed" @error="aderror" @close="closeAd"></ad>
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom');
// #endif
export default {
name: 'UniListAd',
props: {
title: {
type: String,
default: '',
}
},
// inject: ['list'],
data() {
return {
isFirstChild: false,
border: false,
borderShow: true,
}
},
mounted() {
this.list = this.getForm()
if (this.list) {
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true
this.isFirstChild = true
}
this.border = this.list.border
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
aderror(e) {
console.log("aderror: " + JSON.stringify(e.detail));
},
closeAd(e) {
this.borderShow = false
}
}
}
</script>
<style lang="scss" scoped>
.uni-list-ad {
position: relative;
border: 1px red solid;
}
.uni-list--border {
position: relative;
padding-bottom: 1px;
/* #ifdef APP-PLUS */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
margin-left: $uni-spacing-row-lg;
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
background-color: $uni-border-color;
}
.uni-list-item--first:after {
height: 0px;
}
/* #endif */
</style>

View File

@ -0,0 +1,58 @@
/**
* 这里是 uni-list 组件内置的常用样式变量
* 如果需要覆盖样式这里提供了基本的组件样式变量您可以尝试修改这里的变量去完成样式替换而不用去修改源码
*
*/
// 背景色
$background-color : #fff;
// 分割线颜色
$divide-line-color : #e5e5e5;
// 默认头像大小如需要修改此值注意同步修改 js 中的值 const avatarWidth = xx 目前只支持方形头像
// nvue 页面不支持修改头像大小
$avatar-width : 45px ;
// 头像边框
$avatar-border-radius: 5px;
$avatar-border-color: #eee;
$avatar-border-width: 1px;
// 标题文字样式
$title-size : 16px;
$title-color : #3b4144;
$title-weight : normal;
// 描述文字样式
$note-size : 12px;
$note-color : #999;
$note-weight : normal;
// 右侧额外内容默认样式
$right-text-size : 12px;
$right-text-color : #999;
$right-text-weight : normal;
// 角标样式
// nvue 页面不支持修改圆点位置以及大小
// 角标在左侧时角标的位置默认为 0 负数左/下移动正数右/上移动
$badge-left: 0px;
$badge-top: 0px;
// 显示圆点时圆点大小
$dot-width: 10px;
$dot-height: 10px;
// 显示角标时角标大小和字体大小
$badge-size : 18px;
$badge-font : 12px;
// 显示角标时角标前景色
$badge-color : #fff;
// 显示角标时角标背景色
$badge-background-color : #ff5a5f;
// 显示角标时角标左右间距
$badge-space : 6px;
// 状态样式
// 选中颜色
$hover : #f5f5f5;

View File

@ -0,0 +1,533 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<!-- #endif -->
<view :hover-class="!clickable && !link ? '' : 'uni-list-chat--hover'" class="uni-list-chat" @click.stop="onClick">
<view :class="{ 'uni-list--border': border, 'uni-list-chat--first': isFirstChild }"></view>
<view class="uni-list-chat__container">
<view class="uni-list-chat__header-warp">
<view v-if="avatarCircle || avatarList.length === 0" class="uni-list-chat__header" :class="{ 'header--circle': avatarCircle }">
<image class="uni-list-chat__header-image" :src="avatar" mode="aspectFill"></image>
</view>
<!-- 头像组 -->
<view v-else class="uni-list-chat__header">
<view v-for="(item, index) in avatarList" :key="index" class="uni-list-chat__header-box" :class="computedAvatar"
:style="{ width: imageWidth + 'px', height: imageWidth + 'px' }">
<image class="uni-list-chat__header-image" :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }" :src="item.url"
mode="aspectFill"></image>
</view>
</view>
</view>
<view v-if="badgeText && badgePositon === 'left'" class="uni-list-chat__badge uni-list-chat__badge-pos" :class="[isSingle]">
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
</view>
<view class="uni-list-chat__content">
<view class="uni-list-chat__content-main">
<text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</text>
<text class="uni-list-chat__content-note uni-ellipsis">{{ note }}</text>
</view>
<view class="uni-list-chat__content-extra">
<slot>
<text class="uni-list-chat__content-extra-text">{{ time }}</text>
<view v-if="badgeText && badgePositon === 'right'" class="uni-list-chat__badge" :class="[isSingle, badgePositon === 'right' ? 'uni-list-chat--right' : '']">
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
</view>
</slot>
</view>
</view>
</view>
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
//
const avatarWidth = 45;
/**
* ListChat 聊天列表
* @description 聊天列表,用于创建聊天类列表
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} title 标题
* @property {String} note 描述
* @property {Boolean} clickable = [true|false] 是否开启点击反馈默认为false
* @property {String} badgeText 数字角标内容
* @property {String} badgePositon = [left|right] 角标位置默认为 right
* @property {String} link = [falsenavigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈默认为false
* @value false 不开启
* @value navigateTo uni.navigateTo()
* @value redirectTo uni.redirectTo()
* @value reLaunch uni.reLaunch()
* @value switchTab uni.switchTab()
* @property {String | PageURIString} to 跳转目标页面
* @property {String} time 右侧时间显示
* @property {Boolean} avatarCircle = [true|false] 是否显示圆形头像默认为false
* @property {String} avatar 头像地址avatarCircle 不填时生效
* @property {Array} avatarList 头像组格式为 [{url:''}]
* @event {Function} click 点击 uniListChat 触发事件
*/
export default {
name: 'UniListChat',
props: {
title: {
type: String,
default: ''
},
note: {
type: String,
default: ''
},
clickable: {
type: Boolean,
default: false
},
link: {
type: [Boolean, String],
default: false
},
to: {
type: String,
default: ''
},
badgeText: {
type: [String, Number],
default: ''
},
badgePositon: {
type: String,
default: 'right'
},
time: {
type: String,
default: ''
},
avatarCircle: {
type: Boolean,
default: false
},
avatar: {
type: String,
default: ''
},
avatarList: {
type: Array,
default () {
return [];
}
}
},
// inject: ['list'],
computed: {
isSingle() {
if (this.badgeText === 'dot') {
return 'uni-badge--dot';
} else {
const badgeText = this.badgeText.toString();
if (badgeText.length > 1) {
return 'uni-badge--complex';
} else {
return 'uni-badge--single';
}
}
},
computedAvatar() {
if (this.avatarList.length > 4) {
this.imageWidth = avatarWidth * 0.31;
return 'avatarItem--3';
} else if (this.avatarList.length > 1) {
this.imageWidth = avatarWidth * 0.47;
return 'avatarItem--2';
} else {
this.imageWidth = avatarWidth;
return 'avatarItem--1';
}
}
},
data() {
return {
isFirstChild: false,
border: true,
// avatarList: 3,
imageWidth: 50
};
},
mounted() {
this.list = this.getForm()
if (this.list) {
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true;
this.isFirstChild = true;
}
this.border = this.list.border;
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
onClick() {
if (this.to !== '') {
this.openPage();
return;
}
if (this.clickable || this.link) {
this.$emit('click', {
data: {}
});
}
},
openPage() {
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
this.pageApi(this.link);
} else {
this.pageApi('navigateTo');
}
},
pageApi(api) {
uni[api]({
url: this.to,
success: res => {
this.$emit('click', {
data: res
});
},
fail: err => {
this.$emit('click', {
data: err
});
console.error(err.errMsg);
}
});
}
}
};
</script>
<style lang="scss" scoped>
$background-color: #fff;
$divide-line-color: #e5e5e5;
$avatar-width: 45px;
$avatar-border-radius: 5px;
$avatar-border-color: #eee;
$avatar-border-width: 1px;
$title-size: 16px;
$title-color: #3b4144;
$title-weight: normal;
$note-size: 12px;
$note-color: #999;
$note-weight: normal;
$right-text-size: 12px;
$right-text-color: #999;
$right-text-weight: normal;
$badge-left: 0px;
$badge-top: 0px;
$dot-width: 10px;
$dot-height: 10px;
$badge-size: 18px;
$badge-font: 12px;
$badge-color: #fff;
$badge-background-color: #ff5a5f;
$badge-space: 6px;
$hover: #f5f5f5;
.uni-list-chat {
font-size: $uni-font-size-lg;
position: relative;
flex-direction: column;
justify-content: space-between;
background-color: $background-color;
}
// .uni-list-chat--disabled {
// opacity: 0.3;
// }
.uni-list-chat--hover {
background-color: $hover;
}
.uni-list--border {
position: relative;
margin-left: $uni-spacing-row-lg;
/* #ifdef APP-PLUS */
border-top-color: $divide-line-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $divide-line-color;
}
.uni-list-item--first:after {
height: 0px;
}
/* #endif */
.uni-list-chat--first {
border-top-width: 0px;
}
.uni-ellipsis {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
.uni-ellipsis-2 {
/* #ifndef APP-NVUE */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/* #endif */
/* #ifdef APP-NVUE */
lines: 2;
/* #endif */
}
.uni-list-chat__container {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex: 1;
padding: $uni-spacing-row-base $uni-spacing-row-lg;
position: relative;
overflow: hidden;
}
.uni-list-chat__header-warp {
position: relative;
}
.uni-list-chat__header {
/* #ifndef APP-NVUE */
display: flex;
align-content: center;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
flex-wrap: wrap-reverse;
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: $avatar-width;
height: $avatar-width;
/* #endif */
border-radius: $avatar-border-radius;
border-color: $avatar-border-color;
border-width: $avatar-border-width;
border-style: solid;
overflow: hidden;
}
.uni-list-chat__header-box {
/* #ifndef APP-PLUS */
box-sizing: border-box;
display: flex;
width: $avatar-width;
height: $avatar-width;
/* #endif */
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
overflow: hidden;
border-radius: 2px;
}
.uni-list-chat__header-image {
margin: 1px;
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: $avatar-width;
height: $avatar-width;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list-chat__header-image {
display: block;
width: 100%;
height: 100%;
}
.avatarItem--1 {
width: 100%;
height: 100%;
}
.avatarItem--2 {
width: 47%;
height: 47%;
}
.avatarItem--3 {
width: 32%;
height: 32%;
}
/* #endif */
.header--circle {
border-radius: 50%;
}
.uni-list-chat__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex: 1;
overflow: hidden;
padding: 2px 0;
}
.uni-list-chat__content-main {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
padding-left: $uni-spacing-row-base;
flex: 1;
overflow: hidden;
}
.uni-list-chat__content-title {
font-size: $title-size;
color: $title-color;
font-weight: $title-weight;
overflow: hidden;
}
.uni-list-chat__content-note {
margin-top: 3px;
color: $note-color;
font-size: $note-size;
font-weight: $title-weight;
overflow: hidden;
}
.uni-list-chat__content-extra {
/* #ifndef APP-NVUE */
flex-shrink: 0;
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
margin-left: 5px;
}
.uni-list-chat__content-extra-text {
color: $right-text-color;
font-size: $right-text-size;
font-weight: $right-text-weight;
overflow: hidden;
}
.uni-list-chat__badge-pos {
position: absolute;
/* #ifdef APP-NVUE */
left: 55px;
top: 3px;
/* #endif */
/* #ifndef APP-NVUE */
left: calc(#{$avatar-width} + 10px - #{$badge-space} + #{$badge-left});
top: calc(#{$uni-spacing-row-base}/ 2 + 1px + #{$badge-top});
/* #endif */
}
.uni-list-chat__badge {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
border-radius: 100px;
background-color: $badge-background-color;
}
.uni-list-chat__badge-text {
color: $badge-color;
font-size: $badge-font;
}
.uni-badge--single {
/* #ifndef APP-NVUE */
// left: calc(#{$avatar-width} + 7px + #{$badge-left});
/* #endif */
width: $badge-size;
height: $badge-size;
}
.uni-badge--complex {
/* #ifdef APP-NVUE */
left: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
height: $badge-size;
padding: 0 $badge-space;
}
.uni-badge--dot {
/* #ifdef APP-NVUE */
left: 60px;
top: 6px;
/* #endif */
/* #ifndef APP-NVUE */
left: calc(#{$avatar-width} + 15px - #{$dot-width}/ 2 + 1px + #{$badge-left});
/* #endif */
width: $dot-width;
height: $dot-height;
padding: 0;
}
.uni-list-chat--right {
/* #ifdef APP-NVUE */
left: 0;
/* #endif */
}
</style>

View File

@ -0,0 +1,438 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<!-- #endif -->
<view
:class="{ 'uni-list-item--disabled': disabled }"
:hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'"
class="uni-list-item"
@click.stop="onClick"
>
<view v-if="!isFirstChild" class="border--left" :class="{ 'uni-list--border': border }"></view>
<view class="uni-list-item__container" :class="{ 'container--right': showArrow || link, 'flex--direction': direction === 'column' }">
<slot name="header">
<view class="uni-list-item__header">
<view v-if="thumb" class="uni-list-item__icon"><image :src="thumb" class="uni-list-item__icon-img" :class="['uni-list--' + thumbSize]" /></view>
<view v-else-if="showExtraIcon" class="uni-list-item__icon"><uni-icons :color="extraIcon.color" :size="extraIcon.size" :type="extraIcon.type" /></view>
</view>
</slot>
<slot name="body">
<view class="uni-list-item__content" :class="{ 'uni-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }">
<text v-if="title" class="uni-list-item__content-title" :class="[ellipsis !== 0 && ellipsis <= 2 ? 'uni-ellipsis-' + ellipsis : '']">{{ title }}</text>
<text v-if="note" class="uni-list-item__content-note">{{ note }}</text>
</view>
</slot>
<slot name="footer">
<view v-if="rightText || showBadge || showSwitch" class="uni-list-item__extra" :class="{ 'flex--justify': direction === 'column' }">
<text v-if="rightText" class="uni-list-item__extra-text">{{ rightText }}</text>
<uni-badge v-if="showBadge" :type="badgeType" :text="badgeText" />
<switch v-if="showSwitch" :disabled="disabled" :checked="switchChecked" @change="onSwitchChange" />
</view>
</slot>
</view>
<uni-icons v-if="showArrow || link" :size="16" class="uni-icon-wrapper" color="#bbb" type="arrowright" />
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
/**
* ListItem 列表子组件
* @description 列表子组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} title 标题
* @property {String} note 描述
* @property {String} thumb 左侧缩略图若thumb有值则不会显示扩展图标
* @property {String} thumbSize = [lg|base|sm] 略缩图大小
* @value lg 大图
* @value base 一般
* @value sm 小图
* @property {String} badgeText 数字角标内容
* @property {String} badgeType 数字角标类型参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)
* @property {String} rightText 右侧文字内容
* @property {Boolean} disabled = [true|false] 是否禁用
* @property {Boolean} clickable = [true|false] 是否开启点击反馈
* @property {String} link = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈
* @value navigateTo uni.navigateTo()
* @value redirectTo uni.redirectTo()
* @value reLaunch uni.reLaunch()
* @value switchTab uni.switchTab()
* @property {String | PageURIString} to 跳转目标页面
* @property {Boolean} showBadge = [true|false] 是否显示数字角标
* @property {Boolean} showSwitch = [true|false] 是否显示Switch
* @property {Boolean} switchChecked = [true|false] Switch是否被选中
* @property {Boolean} showExtraIcon = [true|false] 左侧是否显示扩展图标
* @property {Object} extraIcon 扩展图标参数格式为 {color: '#4cd964',size: '22',type: 'spinner'}
* @property {String} direction = [row|column] 排版方向
* @value row 水平排列
* @value column 垂直排列
* @event {Function} click 点击 uniListItem 触发事件
* @event {Function} switchChange 点击切换 Switch 时触发
*/
export default {
name: 'UniListItem',
props: {
direction: {
type: String,
default: 'row'
},
title: {
type: String,
default: ''
},
note: {
type: String,
default: ''
},
ellipsis: {
type: [Number],
default: 0
},
disabled: {
type: [Boolean, String],
default: false
},
clickable: {
type: Boolean,
default: false
},
showArrow: {
type: [Boolean, String],
default: false
},
link: {
type: [Boolean, String],
default: false
},
to: {
type: String,
default: ''
},
showBadge: {
type: [Boolean, String],
default: false
},
showSwitch: {
type: [Boolean, String],
default: false
},
switchChecked: {
type: [Boolean, String],
default: false
},
badgeText: {
type: String,
default: ''
},
badgeType: {
type: String,
default: 'success'
},
rightText: {
type: String,
default: ''
},
thumb: {
type: String,
default: ''
},
thumbSize: {
type: String,
default: 'base'
},
showExtraIcon: {
type: [Boolean, String],
default: false
},
extraIcon: {
type: Object,
default() {
return {
type: 'contact',
color: '#000000',
size: 20
};
}
},
border: {
type: Boolean,
default: true
}
},
// inject: ['list'],
data() {
return {
isFirstChild: false
};
},
mounted() {
this.list = this.getForm()
// uni-list
if(this.list){
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true;
this.isFirstChild = true;
}
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
onClick() {
if (this.to !== '') {
this.openPage();
return;
}
if (this.clickable || this.link) {
this.$emit('click', {
data: {}
});
}
},
onSwitchChange(e) {
this.$emit('switchChange', e.detail);
},
openPage() {
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
this.pageApi(this.link);
} else {
this.pageApi('navigateTo');
}
},
pageApi(api) {
uni[api]({
url: this.to,
success: res => {
this.$emit('click', {
data: res
});
},
fail: err => {
this.$emit('click', {
data: err
});
console.error(err.errMsg);
}
});
}
}
};
</script>
<style lang="scss">
$list-item-pd: $uni-spacing-col-lg $uni-spacing-row-lg;
.uni-list-item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
font-size: $uni-font-size-lg;
position: relative;
justify-content: space-between;
align-items: center;
background-color: #fff;
flex-direction: row;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-list-item--disabled {
opacity: 0.3;
}
.uni-list-item--hover {
background-color: $uni-bg-color-hover;
}
.uni-list-item__container {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: $list-item-pd;
padding-left: $uni-spacing-row-lg;
flex: 1;
overflow: hidden;
// align-items: center;
}
.container--right {
padding-right: 0;
}
// .border--left {
// margin-left: $uni-spacing-row-lg;
// }
.uni-list--border {
position: absolute;
top: 0;
right: 0;
left: 0;
/* #ifdef APP-NVUE */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
}
/* #endif */
.uni-list-item__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding-right: 8px;
flex: 1;
color: #3b4144;
// overflow: hidden;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
.uni-list-item__content--center {
justify-content: center;
}
.uni-list-item__content-title {
font-size: $uni-font-size-base;
color: #3b4144;
overflow: hidden;
}
.uni-list-item__content-note {
margin-top: 6rpx;
color: $uni-text-color-grey;
font-size: $uni-font-size-sm;
overflow: hidden;
}
.uni-list-item__extra {
// width: 25%;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.uni-list-item__header {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
.uni-list-item__icon {
margin-right: 18rpx;
flex-direction: row;
justify-content: center;
align-items: center;
}
.uni-list-item__icon-img {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
height: $uni-img-size-base;
width: $uni-img-size-base;
margin-right: 10px;
}
.uni-icon-wrapper {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 0 10px;
}
.flex--direction {
flex-direction: column;
/* #ifndef APP-NVUE */
align-items: initial;
/* #endif */
}
.flex--justify {
/* #ifndef APP-NVUE */
justify-content: initial;
/* #endif */
}
.uni-list--lg {
height: $uni-img-size-lg;
width: $uni-img-size-lg;
}
.uni-list--base {
height: $uni-img-size-base;
width: $uni-img-size-base;
}
.uni-list--sm {
height: $uni-img-size-sm;
width: $uni-img-size-sm;
}
.uni-list-item__extra-text {
color: $uni-text-color-grey;
font-size: $uni-font-size-sm;
}
.uni-ellipsis-1 {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
.uni-ellipsis-2 {
/* #ifndef APP-NVUE */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/* #endif */
/* #ifdef APP-NVUE */
lines: 2;
/* #endif */
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<!-- #ifndef APP-NVUE -->
<view class="uni-list uni-border-top-bottom">
<view v-if="border" class="uni-list--border-top"></view>
<slot />
<view v-if="border" class="uni-list--border-bottom"></view>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<list class="uni-list" :class="{ 'uni-list--border': border }" :enableBackToTop="enableBackToTop" loadmoreoffset="15"><slot /></list>
<!-- #endif -->
</template>
<script>
/**
* List 列表
* @description 列表组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} border = [true|false] 标题
*/
export default {
name: 'uniList',
'mp-weixin': {
options: {
multipleSlots: false
}
},
props: {
enableBackToTop: {
type: [Boolean, String],
default: false
},
scrollY: {
type: [Boolean, String],
default: false
},
border: {
type: Boolean,
default: true
}
},
// provide() {
// return {
// list: this
// };
// },
created() {
this.firstChildAppend = false;
},
methods: {
loadMore(e) {
this.$emit('scrolltolower');
}
}
};
</script>
<style lang="scss">
.uni-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
background-color: $uni-bg-color;
position: relative;
flex-direction: column;
}
.uni-list--border {
position: relative;
/* #ifdef APP-NVUE */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
border-bottom-color: $uni-border-color;
border-bottom-style: solid;
border-bottom-width: 0.5px;
/* #endif */
z-index: -1;
}
/* #ifndef APP-NVUE */
.uni-list--border-top {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
z-index: 1;
}
.uni-list--border-bottom {
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
}
/* #endif */
</style>

View File

@ -0,0 +1,65 @@
<template>
<!-- #ifdef APP-NVUE -->
<refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown">
<slot />
</refresh>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view ref="uni-refresh" class="uni-refresh" v-show="isShow">
<slot />
</view>
<!-- #endif -->
</template>
<script>
export default {
name: 'UniRefresh',
props: {
display: {
type: [String],
default: "hide"
}
},
data() {
return {
pulling: false
}
},
computed: {
isShow() {
if (this.display === "show" || this.pulling === true) {
return true;
}
return false;
}
},
created() {},
methods: {
onchange(value) {
this.pulling = value;
},
onrefresh(e) {
this.$emit("refresh", e);
},
onpullingdown(e) {
// #ifdef APP-NVUE
this.$emit("pullingdown", e);
// #endif
// #ifndef APP-NVUE
var detail = {
viewHeight: 90,
pullingDistance: e.height
}
this.$emit("pullingdown", detail);
// #endif
}
}
}
</script>
<style>
.uni-refresh {
height: 0;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,87 @@
var pullDown = {
threshold: 95,
maxHeight: 200,
callRefresh: 'onrefresh',
callPullingDown: 'onpullingdown',
refreshSelector: '.uni-refresh'
};
function ready(newValue, oldValue, ownerInstance, instance) {
var state = instance.getState()
state.canPullDown = newValue;
// console.log(newValue);
}
function touchStart(e, instance) {
var state = instance.getState();
state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
if (!state.canPullDown) {
return
}
// console.log("touchStart");
state.height = 0;
state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
state.refreshInstance.setStyle({
'height': 0
});
state.refreshInstance.callMethod("onchange", true);
}
function touchMove(e, ownerInstance) {
var instance = e.instance;
var state = instance.getState();
if (!state.canPullDown) {
return
}
var oldHeight = state.height;
var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
var height = endY - state.touchStartY;
if (height > pullDown.maxHeight) {
return;
}
var refreshInstance = state.refreshInstance;
refreshInstance.setStyle({
'height': height + 'px'
});
height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
state.height = height;
refreshInstance.callMethod(pullDown.callPullingDown, {
height: height
});
}
function touchEnd(e, ownerInstance) {
var state = e.instance.getState();
if (!state.canPullDown) {
return
}
state.refreshInstance.callMethod("onchange", false);
var refreshInstance = state.refreshInstance;
if (state.height > pullDown.threshold) {
refreshInstance.callMethod(pullDown.callRefresh);
return;
}
refreshInstance.setStyle({
'height': 0
});
}
function propObserver(newValue, oldValue, instance) {
pullDown = newValue;
}
module.exports = {
touchmove: touchMove,
touchstart: touchStart,
touchend: touchEnd,
propObserver: propObserver
}

View File

@ -0,0 +1,87 @@
{
"id": "uni-list",
"displayName": "uni-list 列表",
"version": "1.0.17",
"description": "List 组件 ,帮助使用者快速构建列表。",
"keywords": [
"",
"uni-ui",
"uniui",
"列表",
"",
"list"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-badge",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,347 @@
## List 列表
> **组件名uni-list**
> 代码块: `uList`、`uListItem`
> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad`
List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。
在vue页面里它默认使用页面级滚动。在app-nvue页面里它默认使用原生list组件滚动。这样的长列表在滚动出屏幕外后系统会回收不可见区域的渲染内存资源不会造成滚动越长手机越卡的问题。
uni-list组件是父容器里面的核心是uni-list-item子组件它代表列表中的一个可重复行子组件可以无限循环。
uni-list-item有很多风格uni-list-item组件通过内置的属性满足一些常用的场景。当内置属性不满足需求时可以通过扩展插槽来自定义列表内容。
内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。
涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。
下文均有样例给出。
uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29)
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 组件需要依赖 `sass` 插件 ,请自行手动安装
> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件
> - `uni-list``uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item`
> - 只有开启点击反馈后,会有点击选中效果
> - 使用插槽时,可以完全自定义内容
> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展
> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译
> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义
> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli`
> - 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839
### 基本用法
- 设置 `title` 属性,可以显示列表标题
- 设置 `disabled` 属性,可以禁用当前项
```html
<uni-list>
<uni-list-item title="列表文字" ></uni-list-item>
<uni-list-item :disabled="true" title="列表禁用状态" ></uni-list-item>
</uni-list>
```
### 多行内容显示
- 设置 `note` 属性 ,可以在第二行显示描述文本信息
```html
<uni-list>
<uni-list-item title="列表文字" note="列表描述信息"></uni-list-item>
<uni-list-item :disabled="true" title="列表文字" note="列表禁用状态"></uni-list-item>
</uni-list>
```
### 右侧显示角标、switch
- 设置 `show-badge` 属性 ,可以显示角标内容
- 设置 `show-switch` 属性,可以显示 switch 开关
```html
<uni-list>
<uni-list-item title="列表右侧显示角标" :show-badge="true" badge-text="12" ></uni-list-item>
<uni-list-item title="列表右侧显示 switch" :show-switch="true" @switchChange="switchChange" ></uni-list-item>
</uni-list>
```
### 左侧显示略缩图、图标
- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图
- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标
```html
<uni-list>
<uni-list-item title="列表左侧带略缩图" note="列表描述信息" thumb="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png"
thumb-size="lg" rightText="右侧文字"></uni-list-item>
<uni-list-item :show-extra-icon="true" :extra-icon="extraIcon1" title="列表左侧带扩展图标" ></uni-list-item>
</uni-list>
```
### 开启点击反馈和右侧箭头
- 设置 `clickable``true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件
- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头
- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo`
```html
<uni-list>
<uni-list-item title="开启点击反馈" clickable @click="onClick" ></uni-list-item>
<uni-list-item title="默认 navigateTo 方式跳转页面" link to="/pages/vue/index/index" @click="onClick($event,1)" ></uni-list-item>
<uni-list-item title="reLaunch 方式跳转页面" link="reLaunch" to="/pages/vue/index/index" @click="onClick($event,1)" ></uni-list-item>
</uni-list>
```
### 聊天列表示例
- 设置 `clickable``true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件
- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo`
- 设置 `to` 属性,可以跳转页面
- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示
- `avatar``avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 `avatar` 属性将失效
- 可以通过默认插槽自定义列表右侧内容
```html
<uni-list>
<uni-list :border="true">
<!-- 显示圆形头像 -->
<uni-list-chat :avatar-circle="true" title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" ></uni-list-chat>
<!-- 右侧带角标 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-text="12"></uni-list-chat>
<!-- 头像显示圆点 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot"></uni-list-chat>
<!-- 头像显示角标 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="99"></uni-list-chat>
<!-- 显示多头像 -->
<uni-list-chat title="uni-app" :avatar-list="avatarList" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot"></uni-list-chat>
<!-- 自定义右侧内容 -->
<uni-list-chat title="uni-app" :avatar-list="avatarList" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot">
<view class="chat-custom-right">
<text class="chat-custom-text">刚刚</text>
<!-- 需要使用 uni-icons 请自行引入 -->
<uni-icons type="star-filled" color="#999" size="18"></uni-icons>
</view>
</uni-list-chat>
</uni-list>
</uni-list>
```
```javascript
export default {
components: {},
data() {
return {
avatarList: [{
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}, {
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}, {
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}]
}
}
}
```
```css
.chat-custom-right {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}
.chat-custom-text {
font-size: 12px;
color: #999;
}
```
## API
### List Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
border |Boolean |true | 是否显示边框
### ListItem Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
title |String |- | 标题
note |String |- | 描述
ellipsis |Number |0 | title 是否溢出隐藏可选值0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】
thumb |String |- | 左侧缩略图若thumb有值则不会显示扩展图标
thumbSize |String |medium | 略缩图尺寸可选值lg:大图; medium:一般; sm:小图;
showBadge |Boolean |false | 是否显示数字角标
badgeText |String |- | 数字角标内容
badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)
rightText |String |- | 右侧文字内容
disabled |Boolean |false | 是否禁用
showArrow |Boolean |true | 是否显示箭头图标
link |String |navigateTo | 新页面跳转方式,可选值见下表
to |String |- | 新页面跳转地址如填写此属性click 会返回页面是否跳转成功
clickable |Boolean |false | 是否开启点击反馈
showSwitch |Boolean |false | 是否显示Switch
switchChecked |Boolean |false | Switch是否被选中
showExtraIcon |Boolean |false | 左侧是否显示扩展图标
extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28)
direction | String |row | 排版方向可选值row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排也受此属性控制
#### Link Options
属性名 | 说明
:-: | :-:
navigateTo | 同 uni.navigateTo()
redirectTo | 同 uni.reLaunch()
reLaunch | 同 uni.reLaunch()
switchTab | 同 uni.switchTab()
### ListItem Events
事件称名 |说明 |返回参数
:-: |:-: |:-:
click |点击 uniListItem 触发事件,需开启点击反馈 |-
switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked}
### ListItem Slots
名称 | 说明
:-: | :-:
header | 左/上内容插槽,可完全自定义默认显示
body | 中间内容插槽,可完全自定义中间内容
footer | 右/下内容插槽,可完全自定义右侧内容
> **通过插槽扩展**
> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现
> 如果 `uni-list-item` 组件内置属性样式无法满足需求可以使用插槽来自定义uni-list-item里的内容。
> uni-list-item提供了3个可扩展的插槽`header`、`body`、`footer`
> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分
> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分
> 开发者可以只用1个插槽也可以3个一起使用。在插槽中可自主编写view标签实现自己所需的效果。
**示例**
```html
<uni-list>
<uni-list-item title="自定义右侧插槽" note="列表描述信息" link>
<template slot="header">
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
</template>
</uni-list-item>
<uni-list-item>
<!-- 自定义 header -->
<view slot="header" class="slot-box"><image class="slot-image" src="/static/logo.png" mode="widthFix"></image></view>
<!-- 自定义 body -->
<text slot="body" class="slot-box slot-text">自定义插槽</text>
<!-- 自定义 footer-->
<template slot="footer">
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
</template>
</uni-list-item>
</uni-list>
```
### ListItemChat Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
title |String |- | 标题
note |String |- | 描述
clickable |Boolean |false | 是否开启点击反馈
badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点
badgePositon |String |right | 角标位置
link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表
clickable |Boolean |false | 是否开启点击反馈
to |String |- | 跳转页面地址如填写此属性click 会返回页面是否跳转成功
time |String |- | 右侧时间显示
avatarCircle |Boolean |false | 是否显示圆形头像
avatar |String |- | 头像地址avatarCircle 不填时生效
avatarList |Array |- | 头像组,格式为 [{url:''}]
#### Link Options
属性名 | 说明
:-: | :-:
navigateTo | 同 uni.navigateTo()
redirectTo | 同 uni.reLaunch()
reLaunch | 同 uni.reLaunch()
switchTab | 同 uni.switchTab()
### ListItemChat Slots
名称 | 说明
:- | :-
default | 自定义列表右侧内容(包括时间和角标显示)
### ListItemChat Events
事件称名 | 说明 | 返回参数
:-: | :-: | :-:
@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息
## 基于uni-list扩展的页面模板
通过扩展插槽,可实现多种常见样式的列表
**新闻列表类**
1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546)
2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583)
3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584)
4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585)
5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586)
6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587)
7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588)
**商品列表类**
1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651)
2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671)
3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672)
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list)

View File

@ -0,0 +1,6 @@
## 1.1.82021-05-12
- 新增 组件示例地址
## 1.1.72021-03-30
- 修复 uni-load-more 在首页使用时h5 平台报 'uni is not defined' 的 bug
## 1.1.62021-02-05
- 调整为uni_modules目录规范

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,82 @@
{
"id": "uni-load-more",
"displayName": "uni-load-more 加载更多",
"version": "1.1.8",
"description": "LoadMore 组件,常用在列表里面,做滚动加载使用。",
"keywords": [
"uni-ui",
"uniui",
"加载更多",
"load-more"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,70 @@
### LoadMore 加载更多
> **组件名uni-load-more**
> 代码块: `uLoadMore`
用于列表中,做滚动加载使用,展示 loading 的各种状态。
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 使用方式
在 ``template`` 中使用组件
```html
<uni-load-more :status="more"></uni-load-more>
```
## API
### LoadMore Props
|属性名 |类型 | 可选值 |默认值 |说明 |
|:-: |:-: |:-: |:-: |:-: |
|iconSize |Number |- |24 |指定图标大小 |
|status |String |more/loading/noMore |more |loading 的状态 |
|showIcon |Boolean|- |true |是否显示 loading 图标 |
|iconType |String |snow/circle/auto |auto |指定图标样式|
|color |String |- |#777777 |图标和文字颜色 |
|contentText|Object|- |{contentdown: "上拉显示更多",contentrefresh: "正在加载...",contentnomore: "没有更多数据了"}|各状态文字说明 |
#### Status Options
|参数名称 |说明 |
|:-: |:-: |
|more |loading前 |
|loading|loading前中 |
|more |没有更多数据 |
#### IconType Options
|参数名称 |说明 |
|:-: |:-: |
|snow |ios雪花加载样式 |
|circle |安卓环形加载样式 |
|auto |根据平台自动选择加载样式 |
> **说明**
> `iconType`为`snow`时,在`APP-NVUE`平台不可设置大小,在非`APP-NVUE`平台不可设置颜色
### 事件说明
|事件名 |说明 |返回值 |
|:-: |:-: |:-: |
|clickLoadMore |点击加载更多时触发 |e.detail={status:'loading'}|
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/load-more/load-more](https://hellouniapp.dcloud.net.cn/pages/extUI/load-more/load-more)

View File

@ -0,0 +1,8 @@
## 1.0.32022-01-21
- 优化 组件示例
## 1.0.22021-11-22
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
## 1.0.12021-11-22
- 修复 vue3中scss语法兼容问题
## 1.0.02021-11-18
- init

View File

@ -0,0 +1 @@
@import './styles/index.scss';

View File

@ -0,0 +1,82 @@
{
"id": "uni-scss",
"displayName": "uni-scss 辅助样式",
"version": "1.0.3",
"description": "uni-sass是uni-ui提供的一套全局样式 通过一些简单的类名和sass变量实现简单的页面布局操作比如颜色、边距、圆角等。",
"keywords": [
"uni-scss",
"uni-ui",
"辅助样式"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "n",
"联盟": "n"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,4 @@
`uni-sass``uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,7 @@
@import './setting/_variables.scss';
@import './setting/_border.scss';
@import './setting/_color.scss';
@import './setting/_space.scss';
@import './setting/_radius.scss';
@import './setting/_text.scss';
@import './setting/_styles.scss';

View File

@ -0,0 +1,3 @@
.uni-border {
border: 1px $uni-border-1 solid;
}

View File

@ -0,0 +1,66 @@
// TODO 暂时不需要 class 需要用户使用变量实现 如果使用类名其实并不推荐
// @mixin get-styles($k,$c) {
// @if $k == size or $k == weight{
// font-#{$k}:#{$c}
// }@else{
// #{$k}:#{$c}
// }
// }
$uni-ui-color:(
// 主色
primary: $uni-primary,
primary-disable: $uni-primary-disable,
primary-light: $uni-primary-light,
// 辅助色
success: $uni-success,
success-disable: $uni-success-disable,
success-light: $uni-success-light,
warning: $uni-warning,
warning-disable: $uni-warning-disable,
warning-light: $uni-warning-light,
error: $uni-error,
error-disable: $uni-error-disable,
error-light: $uni-error-light,
info: $uni-info,
info-disable: $uni-info-disable,
info-light: $uni-info-light,
// 中性色
main-color: $uni-main-color,
base-color: $uni-base-color,
secondary-color: $uni-secondary-color,
extra-color: $uni-extra-color,
// 背景色
bg-color: $uni-bg-color,
// 边框颜色
border-1: $uni-border-1,
border-2: $uni-border-2,
border-3: $uni-border-3,
border-4: $uni-border-4,
// 黑色
black:$uni-black,
// 白色
white:$uni-white,
// 透明
transparent:$uni-transparent
) !default;
@each $key, $child in $uni-ui-color {
.uni-#{"" + $key} {
color: $child;
}
.uni-#{"" + $key}-bg {
background-color: $child;
}
}
.uni-shadow-sm {
box-shadow: $uni-shadow-sm;
}
.uni-shadow-base {
box-shadow: $uni-shadow-base;
}
.uni-shadow-lg {
box-shadow: $uni-shadow-lg;
}
.uni-mask {
background-color:$uni-mask;
}

View File

@ -0,0 +1,55 @@
@mixin radius($r,$d:null ,$important: false){
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
// Key exists within the $uni-radius variable
@if (map-has-key($uni-radius, $r) and $d){
@if $d == t {
border-top-left-radius:$radius-value;
border-top-right-radius:$radius-value;
}@else if $d == r {
border-top-right-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == b {
border-bottom-left-radius:$radius-value;
border-bottom-right-radius:$radius-value;
}@else if $d == l {
border-top-left-radius:$radius-value;
border-bottom-left-radius:$radius-value;
}@else if $d == tl {
border-top-left-radius:$radius-value;
}@else if $d == tr {
border-top-right-radius:$radius-value;
}@else if $d == br {
border-bottom-right-radius:$radius-value;
}@else if $d == bl {
border-bottom-left-radius:$radius-value;
}
}@else{
border-radius:$radius-value;
}
}
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $key} {
@include radius($key)
}
}@else{
.uni-radius {
@include radius($key)
}
}
}
@each $direction in t, r, b, l,tl, tr, br, bl {
@each $key, $child in $uni-radius {
@if($key){
.uni-radius-#{"" + $direction}-#{"" + $key} {
@include radius($key,$direction,false)
}
}@else{
.uni-radius-#{$direction} {
@include radius($key,$direction,false)
}
}
}
}

View File

@ -0,0 +1,56 @@
@mixin fn($space,$direction,$size,$n) {
@if $n {
#{$space}-#{$direction}: #{$size*$uni-space-root}px
} @else {
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
}
}
@mixin get-styles($direction,$i,$space,$n){
@if $direction == t {
@include fn($space, top,$i,$n);
}
@if $direction == r {
@include fn($space, right,$i,$n);
}
@if $direction == b {
@include fn($space, bottom,$i,$n);
}
@if $direction == l {
@include fn($space, left,$i,$n);
}
@if $direction == x {
@include fn($space, left,$i,$n);
@include fn($space, right,$i,$n);
}
@if $direction == y {
@include fn($space, top,$i,$n);
@include fn($space, bottom,$i,$n);
}
@if $direction == a {
@if $n {
#{$space}:#{$i*$uni-space-root}px;
} @else {
#{$space}:#{-$i*$uni-space-root}px;
}
}
}
@each $orientation in m,p {
$space: margin;
@if $orientation == m {
$space: margin;
} @else {
$space: padding;
}
@for $i from 0 through 16 {
@each $direction in t, r, b, l, x, y, a {
.uni-#{$orientation}#{$direction}-#{$i} {
@include get-styles($direction,$i,$space,true);
}
.uni-#{$orientation}#{$direction}-n#{$i} {
@include get-styles($direction,$i,$space,false);
}
}
}
}

View File

@ -0,0 +1,167 @@
/* #ifndef APP-NVUE */
$-color-white:#fff;
$-color-black:#000;
@mixin base-style($color) {
color: #fff;
background-color: $color;
border-color: mix($-color-black, $color, 8%);
&:not([hover-class]):active {
background: mix($-color-black, $color, 10%);
border-color: mix($-color-black, $color, 20%);
color: $-color-white;
outline: none;
}
}
@mixin is-color($color) {
@include base-style($color);
&[loading] {
@include base-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&[loading],
&:not([hover-class]):active {
color: $-color-white;
border-color: mix(darken($color,10%), $-color-white);
background-color: mix($color, $-color-white);
}
}
}
@mixin base-plain-style($color) {
color:$color;
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 70%);
&:not([hover-class]):active {
background: mix($-color-white, $color, 80%);
color: $color;
outline: none;
border-color: mix($-color-white, $color, 50%);
}
}
@mixin is-plain($color){
&[plain] {
@include base-plain-style($color);
&[loading] {
@include base-plain-style($color);
&::before {
margin-right:5px;
}
}
&[disabled] {
&,
&:active {
color: mix($-color-white, $color, 40%);
background-color: mix($-color-white, $color, 90%);
border-color: mix($-color-white, $color, 80%);
}
}
}
}
.uni-btn {
margin: 5px;
color: #393939;
border:1px solid #ccc;
font-size: 16px;
font-weight: 200;
background-color: #F9F9F9;
// TODO 暂时处理边框隐藏一边的问题
overflow: visible;
&::after{
border: none;
}
&:not([type]),&[type=default] {
color: #999;
&[loading] {
background: none;
&::before {
margin-right:5px;
}
}
&[disabled]{
color: mix($-color-white, #999, 60%);
&,
&[loading],
&:active {
color: mix($-color-white, #999, 60%);
background-color: mix($-color-white,$-color-black , 98%);
border-color: mix($-color-white, #999, 85%);
}
}
&[plain] {
color: #999;
background: none;
border-color: $uni-border-1;
&:not([hover-class]):active {
background: none;
color: mix($-color-white, $-color-black, 80%);
border-color: mix($-color-white, $-color-black, 90%);
outline: none;
}
&[disabled]{
&,
&[loading],
&:active {
background: none;
color: mix($-color-white, #999, 60%);
border-color: mix($-color-white, #999, 85%);
}
}
}
}
&:not([hover-class]):active {
color: mix($-color-white, $-color-black, 50%);
}
&[size=mini] {
font-size: 16px;
font-weight: 200;
border-radius: 8px;
}
&.uni-btn-small {
font-size: 14px;
}
&.uni-btn-mini {
font-size: 12px;
}
&.uni-btn-radius {
border-radius: 999px;
}
&[type=primary] {
@include is-color($uni-primary);
@include is-plain($uni-primary)
}
&[type=success] {
@include is-color($uni-success);
@include is-plain($uni-success)
}
&[type=error] {
@include is-color($uni-error);
@include is-plain($uni-error)
}
&[type=warning] {
@include is-color($uni-warning);
@include is-plain($uni-warning)
}
&[type=info] {
@include is-color($uni-info);
@include is-plain($uni-info)
}
}
/* #endif */

View File

@ -0,0 +1,24 @@
@mixin get-styles($k,$c) {
@if $k == size or $k == weight{
font-#{$k}:#{$c}
}@else{
#{$k}:#{$c}
}
}
@each $key, $child in $uni-headings {
/* #ifndef APP-NVUE */
.uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
/* #ifdef APP-NVUE */
.container .uni-#{$key} {
@each $k, $c in $child {
@include get-styles($k,$c)
}
}
/* #endif */
}

View File

@ -0,0 +1,146 @@
// @use "sass:math";
@import '../tools/functions.scss';
// 间距基础倍数
$uni-space-root: 2 !default;
// 边框半径默认值
$uni-radius-root:5px !default;
$uni-radius: () !default;
// 边框半径断点
$uni-radius: map-deep-merge(
(
0: 0,
// TODO 当前版本暂时不支持 sm 属性
// 'sm': math.div($uni-radius-root, 2),
null: $uni-radius-root,
'lg': $uni-radius-root * 2,
'xl': $uni-radius-root * 6,
'pill': 9999px,
'circle': 50%
),
$uni-radius
);
// 字体家族
$body-font-family: 'Roboto', sans-serif !default;
// 文本
$heading-font-family: $body-font-family !default;
$uni-headings: () !default;
$letterSpacing: -0.01562em;
$uni-headings: map-deep-merge(
(
'h1': (
size: 32px,
weight: 300,
line-height: 50px,
// letter-spacing:-0.01562em
),
'h2': (
size: 28px,
weight: 300,
line-height: 40px,
// letter-spacing: -0.00833em
),
'h3': (
size: 24px,
weight: 400,
line-height: 32px,
// letter-spacing: normal
),
'h4': (
size: 20px,
weight: 400,
line-height: 30px,
// letter-spacing: 0.00735em
),
'h5': (
size: 16px,
weight: 400,
line-height: 24px,
// letter-spacing: normal
),
'h6': (
size: 14px,
weight: 500,
line-height: 18px,
// letter-spacing: 0.0125em
),
'subtitle': (
size: 12px,
weight: 400,
line-height: 20px,
// letter-spacing: 0.00937em
),
'body': (
font-size: 14px,
font-weight: 400,
line-height: 22px,
// letter-spacing: 0.03125em
),
'caption': (
'size': 12px,
'weight': 400,
'line-height': 20px,
// 'letter-spacing': 0.03333em,
// 'text-transform': false
)
),
$uni-headings
);
// 主色
$uni-primary: #2979ff !default;
$uni-primary-disable:lighten($uni-primary,20%) !default;
$uni-primary-light: lighten($uni-primary,25%) !default;
// 辅助色
// 除了主色外的场景色需要在不同的场景中使用例如危险色表示危险的操作
$uni-success: #18bc37 !default;
$uni-success-disable:lighten($uni-success,20%) !default;
$uni-success-light: lighten($uni-success,25%) !default;
$uni-warning: #f3a73f !default;
$uni-warning-disable:lighten($uni-warning,20%) !default;
$uni-warning-light: lighten($uni-warning,25%) !default;
$uni-error: #e43d33 !default;
$uni-error-disable:lighten($uni-error,20%) !default;
$uni-error-light: lighten($uni-error,25%) !default;
$uni-info: #8f939c !default;
$uni-info-disable:lighten($uni-info,20%) !default;
$uni-info-light: lighten($uni-info,25%) !default;
// 中性色
// 中性色用于文本背景和边框颜色通过运用不同的中性色来表现层次结构
$uni-main-color: #3a3a3a !default; // 主要文字
$uni-base-color: #6a6a6a !default; // 常规文字
$uni-secondary-color: #909399 !default; // 次要文字
$uni-extra-color: #c7c7c7 !default; // 辅助说明
// 边框颜色
$uni-border-1: #F0F0F0 !default;
$uni-border-2: #EDEDED !default;
$uni-border-3: #DCDCDC !default;
$uni-border-4: #B9B9B9 !default;
// 常规色
$uni-black: #000000 !default;
$uni-white: #ffffff !default;
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
// 背景色
$uni-bg-color: #f7f7f7 !default;
/* 水平间距 */
$uni-spacing-sm: 8px !default;
$uni-spacing-base: 15px !default;
$uni-spacing-lg: 30px !default;
// 阴影
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
// 蒙版
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;

View File

@ -0,0 +1,19 @@
// 合并 map
@function map-deep-merge($parent-map, $child-map){
$result: $parent-map;
@each $key, $child in $child-map {
$parent-has-key: map-has-key($result, $key);
$parent-value: map-get($result, $key);
$parent-type: type-of($parent-value);
$child-type: type-of($child);
$parent-is-map: $parent-type == map;
$child-is-map: $child-type == map;
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
$result: map-merge($result, ( $key: $child ));
}@else {
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
}
}
@return $result;
};

Some files were not shown because too many files have changed in this diff Show More