feat(dev): 项目初始化。
8
.changeset/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Changesets
|
||||||
|
|
||||||
|
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||||
|
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||||
|
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
|
||||||
|
|
||||||
|
We have a quick list of common questions to get you started engaging with this project in
|
||||||
|
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
|
||||||
11
.changeset/config.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json",
|
||||||
|
"changelog": "@changesets/cli/changelog",
|
||||||
|
"commit": false,
|
||||||
|
"fixed": [],
|
||||||
|
"linked": [],
|
||||||
|
"access": "restricted",
|
||||||
|
"baseBranch": "base",
|
||||||
|
"updateInternalDependencies": "patch",
|
||||||
|
"ignore": []
|
||||||
|
}
|
||||||
3
.commitlintrc.cjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
}
|
||||||
51
.cursor/rules/api-http-patterns.mdc
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# API 和 HTTP 请求规范
|
||||||
|
|
||||||
|
## HTTP 请求封装
|
||||||
|
- 可以使用 `简单http` 或者 `alova` 或者 `@tanstack/vue-query` 进行请求管理
|
||||||
|
- HTTP 配置在 [src/http/](mdc:src/http/) 目录下
|
||||||
|
- `简单http` - [src/http/http.ts](mdc:src/http/http.ts)
|
||||||
|
- `alova` - [src/http/alova.ts](mdc:src/http/alova.ts)
|
||||||
|
- `vue-query` - [src/http/vue-query.ts](mdc:src/http/vue-query.ts)
|
||||||
|
- 请求拦截器在 [src/http/interceptor.ts](mdc:src/http/interceptor.ts)
|
||||||
|
- 支持请求重试、缓存、错误处理
|
||||||
|
|
||||||
|
## API 接口规范
|
||||||
|
- API 接口定义在 [src/api/](mdc:src/api/) 目录下
|
||||||
|
- 按功能模块组织 API 文件
|
||||||
|
- 使用 TypeScript 定义请求和响应类型
|
||||||
|
- 支持 `简单http`、`alova` 和 `vue-query` 三种请求方式
|
||||||
|
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```typescript
|
||||||
|
// API 接口定义
|
||||||
|
export interface LoginParams {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
token: string
|
||||||
|
userInfo: UserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// alova 方式
|
||||||
|
export const login = (params: LoginParams) =>
|
||||||
|
http.Post<LoginResponse>('/api/login', params)
|
||||||
|
|
||||||
|
// vue-query 方式
|
||||||
|
export const useLogin = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: LoginParams) =>
|
||||||
|
http.post<LoginResponse>('/api/login', params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
- 统一错误处理在拦截器中配置
|
||||||
|
- 支持网络错误、业务错误、认证错误等
|
||||||
|
- 自动处理 token 过期和刷新
|
||||||
|
---
|
||||||
|
globs: src/api/*.ts,src/http/*.ts
|
||||||
|
---
|
||||||
43
.cursor/rules/development-workflow.mdc
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# 开发工作流程
|
||||||
|
|
||||||
|
## 项目启动
|
||||||
|
1. 安装依赖:`pnpm install`
|
||||||
|
2. 开发环境:
|
||||||
|
- H5: `pnpm dev` 或 `pnpm dev:h5`
|
||||||
|
- 微信小程序: `pnpm dev:mp`
|
||||||
|
- 支付宝小程序: `pnpm dev:mp-alipay`
|
||||||
|
- APP: `pnpm dev:app`
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
- 使用 ESLint 进行代码检查:`pnpm lint`
|
||||||
|
- 自动修复代码格式:`pnpm lint:fix`
|
||||||
|
- 使用 eslint 格式化代码
|
||||||
|
- 遵循 TypeScript 严格模式
|
||||||
|
|
||||||
|
## 构建和部署
|
||||||
|
- H5 构建:`pnpm build:h5`
|
||||||
|
- 微信小程序构建:`pnpm build:mp`
|
||||||
|
- 支付宝小程序构建:`pnpm build:mp-alipay`
|
||||||
|
- APP 构建:`pnpm build:app`
|
||||||
|
- 类型检查:`pnpm type-check`
|
||||||
|
|
||||||
|
## 开发工具
|
||||||
|
- 推荐使用 VSCode 编辑器
|
||||||
|
- 安装 Vue 和 TypeScript 相关插件
|
||||||
|
- 使用 uni-app 开发者工具调试小程序
|
||||||
|
- 使用 HBuilderX 调试 APP
|
||||||
|
|
||||||
|
## 调试技巧
|
||||||
|
- 使用 console.log 和 uni.showToast 调试
|
||||||
|
- 利用 Vue DevTools 调试组件状态
|
||||||
|
- 使用网络面板调试 API 请求
|
||||||
|
- 平台差异测试和兼容性检查
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- 使用懒加载和代码分割
|
||||||
|
- 优化图片和静态资源
|
||||||
|
- 减少不必要的重渲染
|
||||||
|
- 合理使用缓存策略
|
||||||
|
---
|
||||||
|
description: 开发工作流程和最佳实践指南
|
||||||
|
---
|
||||||
36
.cursor/rules/project-overview.mdc
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# unibest 项目概览
|
||||||
|
|
||||||
|
这是一个基于 uniapp + Vue3 + TypeScript + Vite5 + UnoCSS 的跨平台开发框架。
|
||||||
|
|
||||||
|
## 项目特点
|
||||||
|
- 支持 H5、小程序、APP 多平台开发
|
||||||
|
- 使用最新的前端技术栈
|
||||||
|
- 内置约定式路由、layout布局、请求封装、登录拦截、自定义tabbar等功能
|
||||||
|
- 无需依赖 HBuilderX,支持命令行开发
|
||||||
|
|
||||||
|
## 核心配置文件
|
||||||
|
- [package.json](mdc:package.json) - 项目依赖和脚本配置
|
||||||
|
- [vite.config.ts](mdc:vite.config.ts) - Vite 构建配置
|
||||||
|
- [pages.config.ts](mdc:pages.config.ts) - 页面路由配置
|
||||||
|
- [manifest.config.ts](mdc:manifest.config.ts) - 应用清单配置
|
||||||
|
- [uno.config.ts](mdc:uno.config.ts) - UnoCSS 配置
|
||||||
|
|
||||||
|
## 主要目录结构
|
||||||
|
- `src/pages/` - 页面文件
|
||||||
|
- `src/components/` - 组件文件
|
||||||
|
- `src/layouts/` - 布局文件
|
||||||
|
- `src/api/` - API 接口
|
||||||
|
- `src/http/` - HTTP 请求封装
|
||||||
|
- `src/store/` - 状态管理
|
||||||
|
- `src/tabbar/` - 底部导航栏
|
||||||
|
- `src/App.ku.vue` - 全局根组件(类似 App.vue 里面的 template作用)
|
||||||
|
|
||||||
|
## 开发命令
|
||||||
|
- `pnpm dev` - 开发 H5 版本
|
||||||
|
- `pnpm dev:mp` - 开发微信小程序
|
||||||
|
- `pnpm dev:mp-alipay` - 开发支付宝小程序(含钉钉)
|
||||||
|
- `pnpm dev:app` - 开发 APP 版本
|
||||||
|
- `pnpm build` - 构建生产版本
|
||||||
54
.cursor/rules/styling-css-patterns.mdc
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# 样式和 CSS 开发规范
|
||||||
|
|
||||||
|
## UnoCSS 原子化 CSS
|
||||||
|
- 项目使用 UnoCSS 作为原子化 CSS 框架
|
||||||
|
- 配置在 [uno.config.ts](mdc:uno.config.ts)
|
||||||
|
- 支持预设和自定义规则
|
||||||
|
- 优先使用原子化类名,减少自定义 CSS
|
||||||
|
|
||||||
|
## SCSS 规范
|
||||||
|
- 使用 SCSS 预处理器
|
||||||
|
- 样式文件使用 `lang="scss"` 和 `scoped` 属性
|
||||||
|
- 遵循 BEM 命名规范
|
||||||
|
- 使用变量和混入提高复用性
|
||||||
|
|
||||||
|
## 样式组织
|
||||||
|
- 全局样式在 [src/style/](mdc:src/style/) 目录下
|
||||||
|
- 组件样式使用 scoped 作用域
|
||||||
|
- 图标字体在 [src/style/iconfont.css](mdc:src/style/iconfont.css)
|
||||||
|
- 主题变量在 [src/uni_modules/uni-scss/](mdc:src/uni_modules/uni-scss/) 目录下
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view class="container flex flex-col items-center p-4">
|
||||||
|
<text class="title text-lg font-bold mb-2">标题</text>
|
||||||
|
<view class="content bg-gray-100 rounded-lg p-3">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
## 响应式设计
|
||||||
|
- 使用 rpx 单位适配不同屏幕
|
||||||
|
- 支持横屏和竖屏布局
|
||||||
|
- 使用 flexbox 和 grid 布局
|
||||||
|
- 考虑不同平台的样式差异
|
||||||
|
---
|
||||||
|
globs: *.vue,*.scss,*.css
|
||||||
|
---
|
||||||
63
.cursor/rules/uni-app-patterns.mdc
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# uni-app 开发规范
|
||||||
|
|
||||||
|
## 页面开发
|
||||||
|
- 页面文件放在 [src/pages/](mdc:src/pages/) 目录下
|
||||||
|
- 使用约定式路由,文件名即路由路径
|
||||||
|
- 页面配置在仅需要在 宏`definePage` 中配置标题等内容即可,会自动生成到 `pages.json` 中
|
||||||
|
- definePage的顺序在最上面
|
||||||
|
|
||||||
|
## 组件开发
|
||||||
|
- 组件文件放在 [src/components/](mdc:src/components/) 或者 [src/pages/xx/components/](mdc:src/pages/xx/components/) 目录下
|
||||||
|
- 使用 uni-app 内置组件和第三方组件库
|
||||||
|
- 支持 wot-ui\uview-pro\uv-ui\sard-ui\uview-plus 等多种第三方组件库 和 z-paging 组件
|
||||||
|
- 自定义组件遵循 uni-app 组件规范
|
||||||
|
|
||||||
|
## 平台适配
|
||||||
|
- 使用条件编译处理平台差异
|
||||||
|
- 支持 H5、小程序、APP 多平台
|
||||||
|
- 注意各平台的 API 差异
|
||||||
|
- 使用 uni.xxx API 替代原生 API
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// #ifdef H5
|
||||||
|
import { h5Api } from '@/utils/h5'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
import { mpApi } from '@/utils/mp'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
h5Api.showToast('H5 平台')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
mpApi.showToast('微信小程序')
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- uni-app 组件 -->
|
||||||
|
<button @click="handleClick">点击</button>
|
||||||
|
|
||||||
|
<!-- 条件渲染 -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<view>H5 特有内容</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生命周期
|
||||||
|
- 使用 uni-app 页面生命周期
|
||||||
|
- onLoad、onShow、onReady、onHide、onUnload
|
||||||
|
- 组件生命周期遵循 Vue3 规范
|
||||||
|
- 注意页面栈和导航管理
|
||||||
|
---
|
||||||
|
globs: src/pages/*.vue,src/components/*.vue
|
||||||
|
---
|
||||||
53
.cursor/rules/vue-typescript-patterns.mdc
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Vue3 + TypeScript 开发规范
|
||||||
|
|
||||||
|
## Vue 组件规范
|
||||||
|
- 使用 Composition API 和 `<script setup>` 语法
|
||||||
|
- 组件文件使用 PascalCase 命名
|
||||||
|
- 页面文件放在 `src/pages/` 目录下
|
||||||
|
- 全局组件文件放在 `src/components/` 目录下
|
||||||
|
- 局部组件文件放在页面的 `/components/` 目录下
|
||||||
|
|
||||||
|
## Vue SFC 组件规范
|
||||||
|
- `<script setup lang="ts">` 标签必须是第一个子元素
|
||||||
|
- `<template>` 标签必须是第二个子元素
|
||||||
|
- `<style scoped>` 标签必须是最后一个子元素(因为推荐使用原子化类名,所以很可能没有)
|
||||||
|
|
||||||
|
## TypeScript 规范
|
||||||
|
- 严格使用 TypeScript,避免使用 `any` 类型
|
||||||
|
- 为 API 响应数据定义接口类型
|
||||||
|
- 使用 `interface` 定义对象类型,`type` 定义联合类型
|
||||||
|
- 导入类型时使用 `import type` 语法
|
||||||
|
|
||||||
|
## 状态管理
|
||||||
|
- 使用 Pinia 进行状态管理
|
||||||
|
- Store 文件放在 `src/store/` 目录下
|
||||||
|
- 使用 `defineStore` 定义 store
|
||||||
|
- 支持持久化存储
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import type { UserInfo } from '@/types/user'
|
||||||
|
|
||||||
|
const userInfo = ref<UserInfo | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化逻辑
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 模板内容 -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
// 样式
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
---
|
||||||
|
globs: *.vue,*.ts,*.tsx
|
||||||
|
---
|
||||||
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*] # 表示所有文件适用
|
||||||
|
charset = utf-8 # 设置文件字符集为 utf-8
|
||||||
|
indent_style = space # 缩进风格(tab | space)
|
||||||
|
indent_size = 2 # 缩进大小
|
||||||
|
end_of_line = lf # 控制换行类型(lf | cr | crlf)
|
||||||
|
trim_trailing_whitespace = true # 去除行首的任意空白字符
|
||||||
|
insert_final_newline = true # 始终在文件末尾插入一个新行
|
||||||
|
|
||||||
|
[*.md] # 表示仅 md 文件适用以下规则
|
||||||
|
max_line_length = off # 关闭最大行长度限制
|
||||||
|
trim_trailing_whitespace = false # 关闭末尾空格修剪
|
||||||
48
.gitignore
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
.hbuilderx
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
docs/.vitepress/dist
|
||||||
|
docs/.vitepress/cache
|
||||||
|
|
||||||
|
src/types
|
||||||
|
# 单独把这个文件排除掉,用以解决部分电脑生成的 auto-import.d.ts 的API不完整导致类型提示报错问题
|
||||||
|
!src/types/auto-import.d.ts
|
||||||
|
src/manifest.json
|
||||||
|
src/pages.json
|
||||||
|
|
||||||
|
# 2025-10-15 by 菲鸽: lock 文件还是需要加入版本管理,今天又遇到版本不一致导致无法运行的问题了。
|
||||||
|
# pnpm-lock.yaml
|
||||||
|
# package-lock.json
|
||||||
|
|
||||||
|
# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
|
||||||
|
# `git rm -r --cached .` 然后提交 commit 即可。
|
||||||
|
|
||||||
|
# git rm -r --cached file1 file2 ## 针对某些文件
|
||||||
|
# git rm -r --cached dir1 dir2 ## 针对某些文件夹
|
||||||
|
# git rm -r --cached . ## 针对所有文件
|
||||||
|
|
||||||
|
# 更新 uni-app 官方版本
|
||||||
|
# npx @dcloudio/uvm@latest
|
||||||
1
.husky/commit-msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
npx --no-install commitlint --edit "$1"
|
||||||
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
|||||||
|
npx lint-staged --allow-empty
|
||||||
9
.npmrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# registry = https://registry.npmjs.org
|
||||||
|
registry = https://registry.npmmirror.com
|
||||||
|
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
auto-install-peers=true
|
||||||
|
shamefully-hoist=true
|
||||||
|
ignore-workspace-root-check=true
|
||||||
|
install-workspace-root=true
|
||||||
|
node-options=--max-old-space-size=8192
|
||||||
123
.trae/rules/project_rules.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# unibest 项目概览
|
||||||
|
|
||||||
|
这是一个基于 uniapp + Vue3 + TypeScript + Vite5 + UnoCSS 的跨平台开发框架。
|
||||||
|
|
||||||
|
## 项目特点
|
||||||
|
- 支持 H5、小程序、APP 多平台开发
|
||||||
|
- 使用最新的前端技术栈
|
||||||
|
- 内置约定式路由、layout布局、请求封装等功能
|
||||||
|
- 无需依赖 HBuilderX,支持命令行开发
|
||||||
|
|
||||||
|
## 核心配置文件
|
||||||
|
- [package.json](mdc:package.json) - 项目依赖和脚本配置
|
||||||
|
- [vite.config.ts](mdc:vite.config.ts) - Vite 构建配置
|
||||||
|
- [pages.config.ts](mdc:pages.config.ts) - 页面路由配置
|
||||||
|
- [manifest.config.ts](mdc:manifest.config.ts) - 应用清单配置
|
||||||
|
- [uno.config.ts](mdc:uno.config.ts) - UnoCSS 配置
|
||||||
|
|
||||||
|
## 主要目录结构
|
||||||
|
- `src/pages/` - 页面文件
|
||||||
|
- `src/components/` - 组件文件
|
||||||
|
- `src/layouts/` - 布局文件
|
||||||
|
- `src/api/` - API 接口
|
||||||
|
- `src/http/` - HTTP 请求封装
|
||||||
|
- `src/store/` - 状态管理
|
||||||
|
- `src/tabbar/` - 底部导航栏
|
||||||
|
- `src/App.ku.vue` - 全局根组件(类似 App.vue 里面的 template作用)
|
||||||
|
|
||||||
|
## 开发命令
|
||||||
|
- `pnpm dev` - 开发 H5 版本
|
||||||
|
- `pnpm dev:mp` - 开发微信小程序
|
||||||
|
- `pnpm dev:mp-alipay` - 开发支付宝小程序(含钉钉)
|
||||||
|
- `pnpm dev:app` - 开发 APP 版本
|
||||||
|
- `pnpm build` - 构建生产版本
|
||||||
|
|
||||||
|
## Vue 组件规范
|
||||||
|
- 使用 Composition API 和 `<script setup>` 语法
|
||||||
|
- 组件文件使用 PascalCase 命名
|
||||||
|
- 页面文件放在 `src/pages/` 目录下
|
||||||
|
- 全局组件文件放在 `src/components/` 目录下
|
||||||
|
- 局部组件文件放在页面的 `/components/` 目录下
|
||||||
|
|
||||||
|
## TypeScript 规范
|
||||||
|
- 严格使用 TypeScript,避免使用 `any` 类型
|
||||||
|
- 为 API 响应数据定义接口类型
|
||||||
|
- 使用 `interface` 定义对象类型,`type` 定义联合类型
|
||||||
|
- 导入类型时使用 `import type` 语法
|
||||||
|
|
||||||
|
## 状态管理
|
||||||
|
- 使用 Pinia 进行状态管理
|
||||||
|
- Store 文件放在 `src/store/` 目录下
|
||||||
|
- 使用 `defineStore` 定义 store
|
||||||
|
- 支持持久化存储
|
||||||
|
|
||||||
|
## UnoCSS 原子化 CSS
|
||||||
|
- 项目使用 UnoCSS 作为原子化 CSS 框架
|
||||||
|
- 配置在 [uno.config.ts]
|
||||||
|
- 支持预设和自定义规则
|
||||||
|
- 优先使用原子化类名,减少自定义 CSS
|
||||||
|
|
||||||
|
## Vue SFC 组件规范
|
||||||
|
- `<script setup lang="ts">` 标签必须是第一个子元素
|
||||||
|
- `<template>` 标签必须是第二个子元素
|
||||||
|
- `<style scoped>` 标签必须是最后一个子元素(因为推荐使用原子化类名,所以很可能没有)
|
||||||
|
|
||||||
|
## 页面开发
|
||||||
|
- 页面文件放在 [src/pages/]目录下
|
||||||
|
- 使用约定式路由,文件名即路由路径
|
||||||
|
- 页面配置在仅需要在 宏`definePage` 中配置标题等内容即可,会自动生成到 `pages.json` 中
|
||||||
|
- definePage的顺序在最上面
|
||||||
|
|
||||||
|
## 组件开发
|
||||||
|
- 全局组件文件放在 `src/components/` 目录下
|
||||||
|
- 局部组件文件放在页面的 `/components/` 目录下
|
||||||
|
- 使用 uni-app 内置组件和第三方组件库
|
||||||
|
- 支持 wot-ui\uview-pro\uv-ui\sard-ui\uview-plus 等多种第三方组件库 和 z-paging 组件
|
||||||
|
- 自定义组件遵循 uni-app 组件规范
|
||||||
|
|
||||||
|
## 平台适配
|
||||||
|
- 使用条件编译处理平台差异
|
||||||
|
- 支持 H5、小程序、APP 多平台
|
||||||
|
- 注意各平台的 API 差异
|
||||||
|
- 使用 uni.xxx API 替代原生 API
|
||||||
|
|
||||||
|
## 示例代码结构
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// #ifdef H5
|
||||||
|
import { h5Api } from '@/utils/h5'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
import { mpApi } from '@/utils/mp'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
// #ifdef H5
|
||||||
|
h5Api.showToast('H5 平台')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
mpApi.showToast('微信小程序')
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- uni-app 组件 -->
|
||||||
|
<button @click="handleClick">点击</button>
|
||||||
|
|
||||||
|
<!-- 条件渲染 -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<view>H5 特有内容</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生命周期
|
||||||
|
- 使用 uni-app 页面生命周期
|
||||||
|
- onLoad、onShow、onReady、onHide、onUnload
|
||||||
|
- 组件生命周期遵循 Vue3 规范
|
||||||
|
- 注意页面栈和导航管理
|
||||||
15
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"antfu.unocss",
|
||||||
|
"antfu.iconify",
|
||||||
|
"evils.uniapp-vscode",
|
||||||
|
"uni-helper.uni-helper-vscode",
|
||||||
|
"uni-helper.uni-app-schemas-vscode",
|
||||||
|
"uni-helper.uni-highlight-vscode",
|
||||||
|
"uni-helper.uni-ui-snippets-vscode",
|
||||||
|
"uni-helper.uni-app-snippets-vscode",
|
||||||
|
"streetsidesoftware.code-spell-checker"
|
||||||
|
]
|
||||||
|
}
|
||||||
102
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
// 配置语言的文件关联
|
||||||
|
"files.associations": {
|
||||||
|
"pages.json": "jsonc", // pages.json 可以写注释
|
||||||
|
"manifest.json": "jsonc" // manifest.json 可以写注释
|
||||||
|
},
|
||||||
|
|
||||||
|
"stylelint.enable": false, // 禁用 stylelint
|
||||||
|
"css.validate": false, // 禁用 CSS 内置验证
|
||||||
|
"scss.validate": false, // 禁用 SCSS 内置验证
|
||||||
|
"less.validate": false, // 禁用 LESS 内置验证
|
||||||
|
|
||||||
|
// 新版本 VsCode 中这个配置已失效
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
|
||||||
|
// 配置新版本 VsCode 工作区的 TypeScript 的版本
|
||||||
|
"js/ts.tsdk.path": "node_modules/typescript/lib",
|
||||||
|
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
|
||||||
|
|
||||||
|
"explorer.fileNesting.enabled": true,
|
||||||
|
"explorer.fileNesting.expand": false,
|
||||||
|
"explorer.fileNesting.patterns": {
|
||||||
|
"README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
|
||||||
|
"docker.md": "Dockerfile,docker*.md,nginx*,.dockerignore",
|
||||||
|
"pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
|
||||||
|
"package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
|
||||||
|
"eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disable the default formatter, use eslint instead
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||||
|
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable eslint for all supported languages
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml",
|
||||||
|
"toml",
|
||||||
|
"xml",
|
||||||
|
"gql",
|
||||||
|
"graphql",
|
||||||
|
"astro",
|
||||||
|
"svelte",
|
||||||
|
"css",
|
||||||
|
"less",
|
||||||
|
"scss",
|
||||||
|
"pcss",
|
||||||
|
"postcss"
|
||||||
|
],
|
||||||
|
"cSpell.words": [
|
||||||
|
"alova",
|
||||||
|
"Aplipay",
|
||||||
|
"attributify",
|
||||||
|
"chooseavatar",
|
||||||
|
"climblee",
|
||||||
|
"commitlint",
|
||||||
|
"dcloudio",
|
||||||
|
"iconfont",
|
||||||
|
"oxlint",
|
||||||
|
"qrcode",
|
||||||
|
"refresherrefresh",
|
||||||
|
"scrolltolower",
|
||||||
|
"tabbar",
|
||||||
|
"Toutiao",
|
||||||
|
"uniapp",
|
||||||
|
"unibest",
|
||||||
|
"unocss",
|
||||||
|
"uview",
|
||||||
|
"uvui",
|
||||||
|
"Wechat",
|
||||||
|
"WechatMiniprogram",
|
||||||
|
"Weixin"
|
||||||
|
]
|
||||||
|
}
|
||||||
80
.vscode/vue3.code-snippets
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
// Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||||
|
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||||
|
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||||
|
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||||
|
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||||
|
// Placeholders with the same ids are connected.
|
||||||
|
// Example:
|
||||||
|
// "Print to console": {
|
||||||
|
// "scope": "javascript,typescript",
|
||||||
|
// "prefix": "log",
|
||||||
|
// "body": [
|
||||||
|
// "console.log('$1');",
|
||||||
|
// "$2"
|
||||||
|
// ],
|
||||||
|
// "description": "Log output to console"
|
||||||
|
// }
|
||||||
|
"Print unibest Vue3 SFC": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "v3",
|
||||||
|
"body": [
|
||||||
|
"<script lang=\"ts\" setup>",
|
||||||
|
"definePage({",
|
||||||
|
" style: {",
|
||||||
|
" navigationBarTitleText: '$1',",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"defineOptions({",
|
||||||
|
" name: '$2',",
|
||||||
|
" options: { ",
|
||||||
|
" virtualHost: true,",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"</script>\n",
|
||||||
|
"<template>",
|
||||||
|
" <view class=\"\">$3</view>",
|
||||||
|
"</template>\n",
|
||||||
|
"<style lang=\"scss\" scoped>",
|
||||||
|
"//$4",
|
||||||
|
"</style>\n",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest style": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "st",
|
||||||
|
"body": [
|
||||||
|
"<style lang=\"scss\" scoped>",
|
||||||
|
"//",
|
||||||
|
"</style>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest script with definePage": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "sc",
|
||||||
|
"body": [
|
||||||
|
"<script lang=\"ts\" setup>",
|
||||||
|
"definePage({",
|
||||||
|
" style: {",
|
||||||
|
" navigationBarTitleText: '$1',",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"defineOptions({",
|
||||||
|
" name: '$2',",
|
||||||
|
" options: { ",
|
||||||
|
" virtualHost: true,",
|
||||||
|
" },",
|
||||||
|
"})",
|
||||||
|
"</script>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Print unibest template": {
|
||||||
|
"scope": "vue",
|
||||||
|
"prefix": "te",
|
||||||
|
"body": [
|
||||||
|
"<template>",
|
||||||
|
" <view class=\"\">$1</view>",
|
||||||
|
"</template>\n"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 菲鸽
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
98
README.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/unibest-tech/unibest">
|
||||||
|
<img width="160" src="./src/static/logo.svg">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">
|
||||||
|
<a href="https://github.com/unibest-tech/unibest" target="_blank">unibest - 最好的 uniapp 开发框架</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
旧仓库 codercup 进不去了,star 也拿不回来,这里也展示一下那个地址的 star.
|
||||||
|
|
||||||
|
[](https://github.com/codercup/unibest)
|
||||||
|
[](https://github.com/codercup/unibest)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/feige996/unibest)
|
||||||
|
[](https://github.com/feige996/unibest)
|
||||||
|
[](https://gitee.com/feige996/unibest/stargazers)
|
||||||
|
[](https://gitee.com/feige996/unibest/members)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`unibest` —— 最好的 `uniapp` 开发模板,由 `uniapp` + `Vue3` + `Ts` + `Vite5` + `UnoCss` + `wot-ui` + `z-paging` 构成,使用了最新的前端技术栈,无需依靠 `HBuilderX`,通过命令行方式运行 `web`、`小程序` 和 `App`(编辑器推荐 `VSCode`,可选 `webstorm`)。
|
||||||
|
|
||||||
|
`unibest` 内置了 `约定式路由`、`layout布局`、`请求封装`、`请求拦截`、`登录拦截`、`UnoCSS`、`i18n多语言` 等基础功能,提供了 `代码提示`、`自动格式化`、`统一配置`、`代码片段` 等辅助功能,让你编写 `uniapp` 拥有 `best` 体验 ( `unibest 的由来`)。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://unibest.tech/" target="_blank">📖 文档地址(new)</a>
|
||||||
|
<span style="margin:0 10px;">|</span>
|
||||||
|
<a href="https://unibest-tech.github.io/hello-unibest" target="_blank">📱 DEMO 地址</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
注意旧的地址 [codercup](https://github.com/codercup/unibest) 我进不去了,使用新的 [feige996](https://github.com/feige996/unibest)。PR和 issue 也请使用新地址,否则无法合并。
|
||||||
|
|
||||||
|
## 平台兼容性
|
||||||
|
|
||||||
|
| H5 | IOS | 安卓 | 微信小程序 | 字节小程序 | 快手小程序 | 支付宝小程序 | 钉钉小程序 | 百度小程序 |
|
||||||
|
| --- | --- | ---- | ---------- | ---------- | ---------- | ------------ | ---------- | ---------- |
|
||||||
|
| √ | √ | √ | √ | √ | √ | √ | √ | √ |
|
||||||
|
|
||||||
|
注意每种 `UI框架` 支持的平台有所不同,详情请看各 `UI框架` 的官网,也可以看 `unibest` 文档。
|
||||||
|
|
||||||
|
## ⚙️ 环境
|
||||||
|
|
||||||
|
- node>=18
|
||||||
|
- pnpm>=7.30
|
||||||
|
- Vue Official>=2.1.10
|
||||||
|
- TypeScript>=5.0
|
||||||
|
|
||||||
|
## 新版分支
|
||||||
|
- main == base
|
||||||
|
- base --> base-i18n
|
||||||
|
- base-login --> base-login-i18n
|
||||||
|
|
||||||
|
## 📂 快速开始
|
||||||
|
|
||||||
|
执行 `pnpm create unibest` 创建项目
|
||||||
|
执行 `pnpm i` 安装依赖
|
||||||
|
执行 `pnpm dev` 运行 `H5`
|
||||||
|
执行 `pnpm dev:mp` 运行 `微信小程序`
|
||||||
|
|
||||||
|
## 📦 运行(支持热更新)
|
||||||
|
|
||||||
|
- web平台: `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。
|
||||||
|
- weixin平台:`pnpm dev:mp` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。
|
||||||
|
- APP平台:`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。(如果是 `安卓` 和 `鸿蒙` 平台,则不用这个方式,可以把整个unibest项目导入到hbx,通过hbx的菜单来运行到对应的平台。)
|
||||||
|
|
||||||
|
## 🔗 发布
|
||||||
|
|
||||||
|
- web平台: `pnpm build:h5`,打包后的文件在 `dist/build/h5`,可以放到web服务器,如nginx运行。如果最终不是放在根目录,可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。
|
||||||
|
- weixin平台:`pnpm build:mp`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
|
||||||
|
- APP平台:`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。(如果是 `安卓` 和 `鸿蒙` 平台,则不用这个方式,可以把整个unibest项目导入到hbx,通过hbx的菜单来发行到对应的平台。)
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
[MIT](https://opensource.org/license/mit/)
|
||||||
|
|
||||||
|
Copyright (c) 2025 菲鸽
|
||||||
|
|
||||||
|
## 捐赠
|
||||||
|
|
||||||
|
<p align='center'>
|
||||||
|
<img alt="special sponsor appwrite" src="https://oss.laf.run/ukw0y1-site/pay/wepay.png" height="330" style="display:inline-block; height:330px;">
|
||||||
|
<img alt="special sponsor appwrite" src="https://oss.laf.run/ukw0y1-site/pay/alipay.jpg" height="330" style="display:inline-block; height:330px; margin-left:10px;">
|
||||||
|
</p>
|
||||||
42
env/.env
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
VITE_APP_TITLE = 'unibest'
|
||||||
|
VITE_APP_PORT = 9000
|
||||||
|
|
||||||
|
VITE_UNI_APPID = '__UNI__D1E5001'
|
||||||
|
VITE_WX_APPID = 'wxa2abb91f64032a2b'
|
||||||
|
|
||||||
|
# 微信开发者工具 CLI 路径,仅当默认安装路径不正确时配置(就是当 pnpm dev:mp 无法自动打开微信开发者工具时,才需要配置,通常是你更改了默认的安装位置导致的,一般出现在windows系统)
|
||||||
|
# macOS 示例:
|
||||||
|
# WECHAT_DEVTOOLS_CLI_PATH = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
|
||||||
|
# Windows 示例:
|
||||||
|
# WECHAT_DEVTOOLS_CLI_PATH = 'C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat'
|
||||||
|
|
||||||
|
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
|
||||||
|
# https://uniapp.dcloud.net.cn/collocation/manifest.html#h5-router
|
||||||
|
# 比如你要部署到 https://unibest.tech/doc/ ,则配置为 /doc/
|
||||||
|
VITE_APP_PUBLIC_BASE=/
|
||||||
|
|
||||||
|
# 默认后台请求地址
|
||||||
|
# 不同命令会按 mode 叠加读取 .env.development / .env.test / .env.production。
|
||||||
|
# 微信小程序如果没有配置下面的专用地址,也会回退使用这个值。
|
||||||
|
VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
|
||||||
|
# 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api
|
||||||
|
|
||||||
|
# 微信小程序专用后台请求地址,按微信开发者工具 envVersion 区分。
|
||||||
|
# 不配置时会回退使用 VITE_SERVER_BASEURL。
|
||||||
|
# VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://dev.xxx.com'
|
||||||
|
# VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://trial.xxx.com'
|
||||||
|
# VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://prod.xxx.com'
|
||||||
|
|
||||||
|
# h5是否需要配置代理
|
||||||
|
VITE_APP_PROXY_ENABLE = false
|
||||||
|
# 下面的不用修改,只要不跟你后台的统一前缀冲突就行。如果修改了,记得修改 `nginx` 里面的配置
|
||||||
|
VITE_APP_PROXY_PREFIX = '/fg-api'
|
||||||
|
|
||||||
|
# 第二个请求地址 (目前alova中可以使用)
|
||||||
|
VITE_SERVER_BASEURL_SECONDARY = 'https://ukw0y1.laf.run'
|
||||||
|
|
||||||
|
# 认证模式,'single' | 'double' ==> 单token | 双token
|
||||||
|
VITE_AUTH_MODE = 'single'
|
||||||
|
|
||||||
|
# 原生插件资源复制开关,启用后 App 构建会把根目录 nativeplugins 复制到 dist
|
||||||
|
VITE_COPY_NATIVE_RES_ENABLE = true
|
||||||
9
env/.env.development
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = false
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# development mode 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://dev.xxx.com'
|
||||||
9
env/.env.production
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'production'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = true
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# production mode 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://prod.xxx.com'
|
||||||
9
env/.env.test
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = false
|
||||||
|
# 是否开启sourcemap
|
||||||
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
|
# test mode 后台请求地址
|
||||||
|
# VITE_SERVER_BASEURL = 'https://test.xxx.com'
|
||||||
60
eslint.config.mjs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import uniHelper from '@uni-helper/eslint-config'
|
||||||
|
|
||||||
|
export default uniHelper({
|
||||||
|
unocss: true,
|
||||||
|
vue: true,
|
||||||
|
markdown: false,
|
||||||
|
ignores: [
|
||||||
|
// 忽略uni_modules目录
|
||||||
|
'**/uni_modules/',
|
||||||
|
// 忽略原生插件目录
|
||||||
|
'**/nativeplugins/',
|
||||||
|
'dist',
|
||||||
|
// unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
|
||||||
|
'auto-import.d.ts',
|
||||||
|
// vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
|
||||||
|
'uni-pages.d.ts',
|
||||||
|
// 插件生成的文件
|
||||||
|
'src/pages.json',
|
||||||
|
'src/manifest.json',
|
||||||
|
// 忽略自动生成文件
|
||||||
|
'src/service/**',
|
||||||
|
],
|
||||||
|
// https://eslint-config.antfu.me/rules
|
||||||
|
rules: {
|
||||||
|
'no-useless-return': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'vue/no-unused-refs': 'off',
|
||||||
|
'unused-imports/no-unused-vars': 'off',
|
||||||
|
'eslint-comments/no-unlimited-disable': 'off',
|
||||||
|
'jsdoc/check-param-names': 'off',
|
||||||
|
'jsdoc/require-returns-description': 'off',
|
||||||
|
'ts/no-empty-object-type': 'off',
|
||||||
|
'no-extend-native': 'off',
|
||||||
|
// uni 条件编译注释可能包裹 import,自动排序会破坏平台条件边界
|
||||||
|
'perfectionist/sort-imports': 'off',
|
||||||
|
'vue/singleline-html-element-content-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
externalIgnores: ['text'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// vue SFC 调换顺序改这里
|
||||||
|
'vue/block-order': ['error', {
|
||||||
|
order: [['script', 'template'], 'style'],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
formatters: {
|
||||||
|
/**
|
||||||
|
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
|
||||||
|
* By default uses Prettier
|
||||||
|
*/
|
||||||
|
css: true,
|
||||||
|
/**
|
||||||
|
* Format HTML files
|
||||||
|
* By default uses Prettier
|
||||||
|
*/
|
||||||
|
html: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 14 KiB |
26
index.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html build-time="%BUILD_TIME%">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||||
|
<script>
|
||||||
|
var coverSupport =
|
||||||
|
'CSS' in window &&
|
||||||
|
typeof CSS.supports === 'function' &&
|
||||||
|
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') +
|
||||||
|
'" />',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<title>%VITE_APP_TITLE%</title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
164
manifest.config.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
// manifest.config.ts
|
||||||
|
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
|
||||||
|
import { loadEnv } from 'vite'
|
||||||
|
|
||||||
|
// 手动解析命令行参数获取 mode
|
||||||
|
function getMode() {
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
const modeFlagIndex = args.findIndex(arg => arg === '--mode')
|
||||||
|
return modeFlagIndex !== -1 ? args[modeFlagIndex + 1] : args[0] === 'build' ? 'production' : 'development' // 默认 development
|
||||||
|
}
|
||||||
|
// 获取环境变量的范例
|
||||||
|
const env = loadEnv(getMode(), path.resolve(process.cwd(), 'env'))
|
||||||
|
const {
|
||||||
|
VITE_APP_TITLE,
|
||||||
|
VITE_UNI_APPID,
|
||||||
|
VITE_WX_APPID,
|
||||||
|
VITE_APP_PUBLIC_BASE,
|
||||||
|
VITE_FALLBACK_LOCALE,
|
||||||
|
} = env
|
||||||
|
// console.log('manifest.config.ts env:', env)
|
||||||
|
|
||||||
|
export default defineManifestConfig({
|
||||||
|
'name': VITE_APP_TITLE,
|
||||||
|
'appid': VITE_UNI_APPID,
|
||||||
|
'description': '',
|
||||||
|
'versionName': '1.0.0',
|
||||||
|
'versionCode': '100',
|
||||||
|
'transformPx': false,
|
||||||
|
'locale': VITE_FALLBACK_LOCALE, // 'zh-Hans'
|
||||||
|
'h5': {
|
||||||
|
router: {
|
||||||
|
base: VITE_APP_PUBLIC_BASE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
'app-plus': {
|
||||||
|
usingComponents: true,
|
||||||
|
nvueStyleCompiler: 'uni-app',
|
||||||
|
compilerVersion: 3,
|
||||||
|
compatible: {
|
||||||
|
ignoreVersion: true,
|
||||||
|
},
|
||||||
|
splashscreen: {
|
||||||
|
alwaysShowBeforeRender: true,
|
||||||
|
waiting: true,
|
||||||
|
autoclose: true,
|
||||||
|
delay: 0,
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
modules: {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
distribute: {
|
||||||
|
/* android打包配置 */
|
||||||
|
android: {
|
||||||
|
minSdkVersion: 21,
|
||||||
|
targetSdkVersion: 30,
|
||||||
|
abiFilters: ['armeabi-v7a', 'arm64-v8a'],
|
||||||
|
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"/>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
ios: {},
|
||||||
|
/* SDK配置 */
|
||||||
|
sdkConfigs: {},
|
||||||
|
/* 图标配置 */
|
||||||
|
icons: {
|
||||||
|
android: {
|
||||||
|
hdpi: 'static/app/icons/72x72.png',
|
||||||
|
xhdpi: 'static/app/icons/96x96.png',
|
||||||
|
xxhdpi: 'static/app/icons/144x144.png',
|
||||||
|
xxxhdpi: 'static/app/icons/192x192.png',
|
||||||
|
},
|
||||||
|
ios: {
|
||||||
|
appstore: 'static/app/icons/1024x1024.png',
|
||||||
|
ipad: {
|
||||||
|
'app': 'static/app/icons/76x76.png',
|
||||||
|
'app@2x': 'static/app/icons/152x152.png',
|
||||||
|
'notification': 'static/app/icons/20x20.png',
|
||||||
|
'notification@2x': 'static/app/icons/40x40.png',
|
||||||
|
'proapp@2x': 'static/app/icons/167x167.png',
|
||||||
|
'settings': 'static/app/icons/29x29.png',
|
||||||
|
'settings@2x': 'static/app/icons/58x58.png',
|
||||||
|
'spotlight': 'static/app/icons/40x40.png',
|
||||||
|
'spotlight@2x': 'static/app/icons/80x80.png',
|
||||||
|
},
|
||||||
|
iphone: {
|
||||||
|
'app@2x': 'static/app/icons/120x120.png',
|
||||||
|
'app@3x': 'static/app/icons/180x180.png',
|
||||||
|
'notification@2x': 'static/app/icons/40x40.png',
|
||||||
|
'notification@3x': 'static/app/icons/60x60.png',
|
||||||
|
'settings@2x': 'static/app/icons/58x58.png',
|
||||||
|
'settings@3x': 'static/app/icons/87x87.png',
|
||||||
|
'spotlight@2x': 'static/app/icons/80x80.png',
|
||||||
|
'spotlight@3x': 'static/app/icons/120x120.png',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
'quickapp': {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
'mp-weixin': {
|
||||||
|
appid: VITE_WX_APPID,
|
||||||
|
setting: {
|
||||||
|
urlCheck: false,
|
||||||
|
// 是否启用 ES6 转 ES5
|
||||||
|
es6: true,
|
||||||
|
minified: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
subPackages: true,
|
||||||
|
},
|
||||||
|
// 是否合并组件虚拟节点外层属性,uni-app 3.5.1+ 开始支持。目前仅支持 style、class 属性。
|
||||||
|
// 默认不开启(undefined),这里设置为开启。
|
||||||
|
mergeVirtualHostAttributes: true,
|
||||||
|
// styleIsolation: 'shared',
|
||||||
|
usingComponents: true,
|
||||||
|
// __usePrivacyCheck__: true,
|
||||||
|
},
|
||||||
|
'mp-alipay': {
|
||||||
|
usingComponents: true,
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
optimization: {
|
||||||
|
subPackages: true,
|
||||||
|
},
|
||||||
|
// 解决支付宝小程序开发工具报错 【globalThis is not defined】
|
||||||
|
compileOptions: {
|
||||||
|
globalObjectMode: 'enable',
|
||||||
|
transpile: {
|
||||||
|
script: {
|
||||||
|
ignore: ['node_modules/**'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'mp-baidu': {
|
||||||
|
usingComponents: true,
|
||||||
|
},
|
||||||
|
'mp-toutiao': {
|
||||||
|
usingComponents: true,
|
||||||
|
},
|
||||||
|
'uniStatistics': {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
|
'vueVersion': '3',
|
||||||
|
})
|
||||||
14
openapi-ts-request.config.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'openapi-ts-request'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
describe: 'unibest-openapi-test',
|
||||||
|
schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
|
||||||
|
serversPath: './src/service',
|
||||||
|
requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`,
|
||||||
|
requestOptionsType: 'CustomRequestOptions_',
|
||||||
|
isGenReactQuery: false,
|
||||||
|
reactQueryMode: 'vue',
|
||||||
|
isGenJavaScript: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
228
package.json
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
{
|
||||||
|
"name": "app.xpcool.com",
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"unibest": {
|
||||||
|
"platforms": [
|
||||||
|
"h5",
|
||||||
|
"mp-weixin",
|
||||||
|
"mp-alipay",
|
||||||
|
"app",
|
||||||
|
"mp-toutiao"
|
||||||
|
],
|
||||||
|
"uiLibrary": "wot-ui-v2",
|
||||||
|
"loginStrategy": true,
|
||||||
|
"i18n": false,
|
||||||
|
"chartLibraries": [
|
||||||
|
"ucharts",
|
||||||
|
"lime-echart"
|
||||||
|
],
|
||||||
|
"cliVersion": "4.0.16",
|
||||||
|
"unibestVersion": "4.4.1",
|
||||||
|
"unibestUpdateTime": "2026-05-22",
|
||||||
|
"createdAt": "2026-07-13 00:34:01"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.10.0",
|
||||||
|
"description": "unibest - 最好的 uniapp 开发模板",
|
||||||
|
"generate-time": "用户创建项目时生成",
|
||||||
|
"author": {
|
||||||
|
"name": "feige996",
|
||||||
|
"zhName": "菲鸽",
|
||||||
|
"email": "1020103647@qq.com",
|
||||||
|
"github": "https://github.com/feige996",
|
||||||
|
"gitee": "https://gitee.com/feige996"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://unibest.tech",
|
||||||
|
"repository": "https://github.com/feige996/unibest",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/feige996/unibest/issues",
|
||||||
|
"url-old": "https://github.com/codercup/unibest/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20",
|
||||||
|
"pnpm": ">=9"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"openapi": "openapi-ts",
|
||||||
|
"init-husky": "git init && husky",
|
||||||
|
"init-baseFiles": "node ./scripts/create-base-files.js",
|
||||||
|
"prepare": "pnpm init-husky & pnpm init-baseFiles",
|
||||||
|
"predev": "pnpm init-baseFiles",
|
||||||
|
"predev:app": "pnpm init-baseFiles",
|
||||||
|
"predev:mp": "pnpm init-baseFiles",
|
||||||
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
"uvm": "npx @dcloudio/uvm@latest",
|
||||||
|
"uvm-rm": "node ./scripts/postupgrade.js",
|
||||||
|
"bump-version": "node ./scripts/bump-version.js",
|
||||||
|
"postuvm": "echo upgrade uni-app success!",
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:app:test": "uni -p app --mode test",
|
||||||
|
"dev:app:prod": "uni -p app --mode production",
|
||||||
|
"dev:app-android": "uni -p app-android",
|
||||||
|
"dev:app-ios": "uni -p app-ios",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev": "uni",
|
||||||
|
"dev:test": "uni --mode test",
|
||||||
|
"dev:prod": "uni --mode production",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:test": "uni --mode test",
|
||||||
|
"dev:h5:prod": "uni --mode production",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp": "uni -p mp-weixin",
|
||||||
|
"dev:mp:test": "uni -p mp-weixin --mode test",
|
||||||
|
"dev:mp:prod": "uni -p mp-weixin --mode production",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-jd": "uni -p mp-jd",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:mp-xhs": "uni -p mp-xhs",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:app:test": "uni build -p app --mode test",
|
||||||
|
"build:app:prod": "uni build -p app --mode production",
|
||||||
|
"build:app-android": "uni build -p app-android",
|
||||||
|
"build:app-ios": "uni build -p app-ios",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:test": "uni build --mode test",
|
||||||
|
"build:h5:prod": "uni build --mode production",
|
||||||
|
"build": "uni build",
|
||||||
|
"build:test": "uni build --mode test",
|
||||||
|
"build:prod": "uni build --mode production",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp": "uni build -p mp-weixin",
|
||||||
|
"build:mp:test": "uni build -p mp-weixin --mode test",
|
||||||
|
"build:mp:prod": "uni build -p mp-weixin --mode production",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-jd": "uni build -p mp-jd",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:mp-xhs": "uni build -p mp-xhs",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"upload:changeset": "pnpm changeset && pnpm changeset version",
|
||||||
|
"upload:mp": "node ./scripts/upload-weixin.js",
|
||||||
|
"type-check": "vue-tsc --noEmit",
|
||||||
|
"lint": "eslint",
|
||||||
|
"lint:fix": "eslint --fix",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:run": "vitest run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alova/adapter-uniapp": "^2.0.14",
|
||||||
|
"@alova/shared": "^1.3.1",
|
||||||
|
"@changesets/cli": "^2.30.0",
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-harmony": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
|
||||||
|
"abortcontroller-polyfill": "^1.7.8",
|
||||||
|
"alova": "^3.3.3",
|
||||||
|
"dayjs": "1.11.10",
|
||||||
|
"pinia": "2.0.36",
|
||||||
|
"pinia-plugin-persistedstate": "3.2.1",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "9.1.9",
|
||||||
|
"vue-router": "4.5.1",
|
||||||
|
"z-paging": "2.8.7",
|
||||||
|
"@qiun/ucharts": "2.5.0-20230101",
|
||||||
|
"echarts": "^5.4.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"@wot-ui/ui": "latest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^19.8.1",
|
||||||
|
"@commitlint/config-conventional": "^19.8.1",
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
|
||||||
|
"@iconify-json/carbon": "^1.2.4",
|
||||||
|
"@iconify/utils": "^3.0.2",
|
||||||
|
"@types/node": "^20.17.9",
|
||||||
|
"@uni-helper/eslint-config": "0.5.0",
|
||||||
|
"@uni-helper/plugin-uni": "0.1.0",
|
||||||
|
"@uni-helper/uni-env": "0.1.8",
|
||||||
|
"@uni-helper/uni-types": "1.0.0-alpha.6",
|
||||||
|
"@uni-helper/unocss-preset-uni": "0.2.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-components": "0.2.3",
|
||||||
|
"@uni-helper/vite-plugin-uni-layouts": "0.1.11",
|
||||||
|
"@uni-helper/vite-plugin-uni-manifest": "0.2.12",
|
||||||
|
"@uni-helper/vite-plugin-uni-pages": "0.3.23",
|
||||||
|
"@uni-helper/vite-plugin-uni-platform": "0.0.5",
|
||||||
|
"@uni-ku/bundle-optimizer": "v1.3.15-beta.2",
|
||||||
|
"@uni-ku/root": "1.4.1",
|
||||||
|
"@unocss/eslint-plugin": "^66.2.3",
|
||||||
|
"@unocss/preset-legacy-compat": "66.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.1.0",
|
||||||
|
"@vue/runtime-core": "^3.4.21",
|
||||||
|
"@vue/test-utils": "^2.4.10",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"cross-env": "^10.0.0",
|
||||||
|
"enquirer": "^2.4.1",
|
||||||
|
"eslint": "^9.31.0",
|
||||||
|
"eslint-plugin-format": "^1.0.1",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"jsdom": "^29.1.1",
|
||||||
|
"lint-staged": "^15.2.10",
|
||||||
|
"miniprogram-api-typings": "^4.1.0",
|
||||||
|
"miniprogram-ci": "^2.1.26",
|
||||||
|
"openapi-ts-request": "^1.10.0",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"postcss-html": "^1.8.0",
|
||||||
|
"postcss-scss": "^4.0.9",
|
||||||
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
|
"sass": "1.77.8",
|
||||||
|
"std-env": "^3.9.0",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
|
"unocss": "66.0.0",
|
||||||
|
"unplugin-auto-import": "^20.0.0",
|
||||||
|
"vite": "5.2.8",
|
||||||
|
"vite-plugin-restart": "^1.0.0",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.5",
|
||||||
|
"vitest": "3.2.4",
|
||||||
|
"vue-tsc": "^3.0.6"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"bin-wrapper": "npm:bin-wrapper-china",
|
||||||
|
"unconfig": "7.3.2"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "echo 祝菲鸽身体健康"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
pages.config.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||||
|
import { tabBar } from './src/tabbar/config'
|
||||||
|
|
||||||
|
export default defineUniPages({
|
||||||
|
globalStyle: {
|
||||||
|
navigationStyle: 'default',
|
||||||
|
navigationBarTitleText: 'unibest',
|
||||||
|
navigationBarBackgroundColor: '#f8f8f8',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
easycom: {
|
||||||
|
autoscan: true,
|
||||||
|
custom: {
|
||||||
|
'^fg-(.*)': '@/components/fg-$1/fg-$1.vue',
|
||||||
|
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
|
||||||
|
'z-paging/components/z-paging$1/z-paging$1.vue',
|
||||||
|
'^wd-(.*)': '@wot-ui/ui/components/wd-$1/wd-$1.vue',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中
|
||||||
|
subPackages: [{
|
||||||
|
root: 'pages-demo',
|
||||||
|
pages: [],
|
||||||
|
}],
|
||||||
|
tabBar: tabBar as any,
|
||||||
|
})
|
||||||
22342
pnpm-lock.yaml
generated
Normal file
154
scripts/bump-version.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
import enquirer from 'enquirer'
|
||||||
|
import pc from 'picocolors'
|
||||||
|
|
||||||
|
const manifestPath = path.resolve(process.cwd(), 'manifest.config.ts')
|
||||||
|
// 获取是否只是测试运行
|
||||||
|
const dryRun = process.argv.includes('--dry-run')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将语义化版本号根据传递的类型进行递增
|
||||||
|
* @param {string} version 当前版本号,如 '1.1.0'
|
||||||
|
* @param {string} type 升级类型: 'patch' | 'minor' | 'major' | 'none'
|
||||||
|
* @returns {string} 新的版本号
|
||||||
|
*/
|
||||||
|
function bumpVersionName(version, type) {
|
||||||
|
if (type === 'none') return version
|
||||||
|
|
||||||
|
const parts = version.split('.')
|
||||||
|
let major = Number.parseInt(parts[0] || '0', 10)
|
||||||
|
let minor = Number.parseInt(parts[1] || '0', 10)
|
||||||
|
let patch = Number.parseInt(parts[2] || '0', 10)
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'major':
|
||||||
|
major += 1; minor = 0; patch = 0;
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
minor += 1; patch = 0;
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
patch += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return `${major}.${minor}.${patch}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const source = fs.readFileSync(manifestPath, 'utf8')
|
||||||
|
|
||||||
|
// 匹配 versionCode 和 versionName 的正则表达式,支持键名带有引号的情况,例如 'versionCode': '100'
|
||||||
|
const versionCodeRegex = /((?:['"])?versionCode(?:['"])?\s*:\s*)(['"])(\d+)\2/
|
||||||
|
const versionNameRegex = /((?:['"])?versionName(?:['"])?\s*:\s*)(['"])([\d\.]+)\2/
|
||||||
|
|
||||||
|
const codeMatch = source.match(versionCodeRegex)
|
||||||
|
const nameMatch = source.match(versionNameRegex)
|
||||||
|
|
||||||
|
if (!codeMatch || !nameMatch) {
|
||||||
|
console.error(pc.red('✖ [bump-version] 未在 manifest.config.ts 中找到合法的 versionCode 或 versionName'))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersionCode = Number.parseInt(codeMatch[3], 10)
|
||||||
|
const currentVersionName = nameMatch[3]
|
||||||
|
const nextVersionCode = String(currentVersionCode + 1)
|
||||||
|
|
||||||
|
// 1. 检查命令行参数是否有 --type
|
||||||
|
const typeArgMatch = process.argv.find(arg => arg.startsWith('--type='))
|
||||||
|
let bumpType = typeArgMatch ? typeArgMatch.split('=')[1] : null
|
||||||
|
|
||||||
|
// 2. 环境判定:如果不在交互终端或者是CI环境,但是没有指定类型,则默认只升级 versionCode
|
||||||
|
const isInteractive = process.stdout.isTTY && !process.env.CI
|
||||||
|
|
||||||
|
if (!bumpType) {
|
||||||
|
if (!isInteractive) {
|
||||||
|
console.log(pc.yellow('⚠ [bump-version] 非交互环境且未指定参数,默认不修改 versionName'))
|
||||||
|
bumpType = 'none'
|
||||||
|
} else {
|
||||||
|
// 3. 在终端交互式询问用户怎么处理 versionName
|
||||||
|
console.log('')
|
||||||
|
console.log(pc.cyan('📦 准备发布新版本'))
|
||||||
|
console.log(`${pc.gray('当前版本:')} ${pc.bold(currentVersionName)} ${pc.gray(`(v${currentVersionCode})`)}`)
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
const response = await enquirer.prompt({
|
||||||
|
type: 'select',
|
||||||
|
name: 'selectedType',
|
||||||
|
message: '请选择如何升级版本名称 (versionName)?',
|
||||||
|
pointer: pc.cyan('❯'),
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: `${pc.bold('修复')} (Patch) ${pc.gray(currentVersionName)} → ${pc.green(bumpVersionName(currentVersionName, 'patch'))}`,
|
||||||
|
name: 'patch',
|
||||||
|
hint: pc.gray('修复Bug、极小的代码安全变动')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `${pc.bold('特性')} (Minor) ${pc.gray(currentVersionName)} → ${pc.cyan(bumpVersionName(currentVersionName, 'minor'))}`,
|
||||||
|
name: 'minor',
|
||||||
|
hint: pc.gray('新增功能、向下兼容的API更新')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `${pc.bold('重大')} (Major) ${pc.gray(currentVersionName)} → ${pc.magenta(bumpVersionName(currentVersionName, 'major'))}`,
|
||||||
|
name: 'major',
|
||||||
|
hint: pc.gray('重大重构、不兼容的API修改')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `${pc.bold('仅Code')} (None) ${pc.gray('保持 ' + currentVersionName)}`,
|
||||||
|
name: 'none',
|
||||||
|
hint: pc.gray('保持名称不变,仅升级构建号(versionCode)')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `${pc.bold('取消')} (Cancel) ${pc.gray('完全不升级版本')}`,
|
||||||
|
name: 'cancel',
|
||||||
|
hint: pc.gray('跳过版本修改,直接进入后续打包流程')
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 如果用户按 ctrl+c 退出
|
||||||
|
onCancel: () => {
|
||||||
|
console.log(pc.red('✖ 取消操作并退出编译'))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bumpType = response.selectedType
|
||||||
|
|
||||||
|
// 用户选择了完全不升级
|
||||||
|
if (bumpType === 'cancel') {
|
||||||
|
console.log('')
|
||||||
|
console.log(pc.green('✔ 已跳过版本升级操作'))
|
||||||
|
console.log('')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 根据类型计算下一代版本并进行替换计算
|
||||||
|
const nextVersionName = bumpVersionName(currentVersionName, bumpType)
|
||||||
|
|
||||||
|
let updated = source.replace(versionCodeRegex, `${codeMatch[1]}${codeMatch[2]}${nextVersionCode}${codeMatch[2]}`)
|
||||||
|
updated = updated.replace(versionNameRegex, `${nameMatch[1]}${nameMatch[2]}${nextVersionName}${nameMatch[2]}`)
|
||||||
|
|
||||||
|
// 5. 回填文件内容
|
||||||
|
if (!dryRun) {
|
||||||
|
fs.writeFileSync(manifestPath, updated, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 美化成功提示输出
|
||||||
|
console.log('')
|
||||||
|
console.log(pc.green(`✔ ${dryRun ? '(模拟运行) ' : ''}版本更新成功!`))
|
||||||
|
|
||||||
|
if (bumpType !== 'none') {
|
||||||
|
console.log(` ${pc.gray('versionName:')} ${pc.strikethrough(pc.gray(currentVersionName))} → ${pc.bold(pc.green(nextVersionName))}`)
|
||||||
|
} else {
|
||||||
|
console.log(` ${pc.gray('versionName:')} ${pc.dim(currentVersionName)} (未更改)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` ${pc.gray('versionCode:')} ${pc.strikethrough(pc.gray(currentVersionCode))} → ${pc.bold(pc.green(nextVersionCode))}`)
|
||||||
|
console.log('')
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch(err => {
|
||||||
|
console.error(pc.red('✖ [bump-version] 发生错误:'), err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
55
scripts/create-base-files.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 基础配置文件生成脚本
|
||||||
|
// 此脚本用于生成 src/manifest.json 和 src/pages.json 基础文件
|
||||||
|
// 由于这两个配置文件会被添加到 .gitignore 中,因此需要通过此脚本确保项目能正常运行
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
// 获取当前文件的目录路径(替代 CommonJS 中的 __dirname)
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
// 最简可运行配置
|
||||||
|
const manifest = { }
|
||||||
|
const pages = {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
path: 'pages/index/index',
|
||||||
|
type: 'home',
|
||||||
|
style: {
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
navigationBarTitleText: '首页',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'pages/me/me',
|
||||||
|
type: 'page',
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '我的',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subPackages: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用修复后的 __dirname 来解析文件路径
|
||||||
|
const manifestPath = path.resolve(__dirname, '../src/manifest.json')
|
||||||
|
const pagesPath = path.resolve(__dirname, '../src/pages.json')
|
||||||
|
|
||||||
|
// 确保 src 目录存在
|
||||||
|
const srcDir = path.resolve(__dirname, '../src')
|
||||||
|
if (!fs.existsSync(srcDir)) {
|
||||||
|
fs.mkdirSync(srcDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIN_SIZE = `{ }`.length // 如果只有一个空对象,必定是不对的,需要重新生成
|
||||||
|
|
||||||
|
// 如果 src/manifest.json 不存在,就创建它;或者如果文件大小小于等于 MIN_SIZE,也重新创建
|
||||||
|
if (!fs.existsSync(manifestPath) || fs.statSync(manifestPath).size <= MIN_SIZE) {
|
||||||
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 src/pages.json 不存在,就创建它;或者如果文件大小小于等于 MIN_SIZE,也重新创建
|
||||||
|
if (!fs.existsSync(pagesPath) || fs.statSync(pagesPath).size <= MIN_SIZE) {
|
||||||
|
fs.writeFileSync(pagesPath, JSON.stringify(pages, null, 2))
|
||||||
|
}
|
||||||
107
scripts/open-dev-tools.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { exec } from 'node:child_process'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开开发者工具
|
||||||
|
* @param {string} env - 环境,'dev' 或 'build'
|
||||||
|
* @param {object} options - 配置选项
|
||||||
|
* @param {string} options.wechatDevtoolsCliPath - 微信开发者工具 CLI 路径
|
||||||
|
*/
|
||||||
|
function _openDevTools(env = 'dev', options = {}) {
|
||||||
|
const { wechatDevtoolsCliPath } = options
|
||||||
|
const platform = process.platform // darwin, win32, linux
|
||||||
|
const { UNI_PLATFORM } = process.env // mp-weixin, mp-alipay, mp-lark
|
||||||
|
|
||||||
|
const uniPlatformText = UNI_PLATFORM === 'mp-weixin' ? '微信小程序' : UNI_PLATFORM === 'mp-alipay' ? '支付宝小程序' : UNI_PLATFORM === 'mp-lark' ? '抖音小程序' : '小程序'
|
||||||
|
|
||||||
|
// 项目路径(构建输出目录),根据环境选择不同目录
|
||||||
|
const outputDir = env === 'build' ? `dist/build/${UNI_PLATFORM}` : `dist/dev/${UNI_PLATFORM}`
|
||||||
|
const projectPath = path.resolve(process.cwd(), outputDir)
|
||||||
|
|
||||||
|
// 检查构建输出目录是否存在
|
||||||
|
if (!fs.existsSync(projectPath)) {
|
||||||
|
console.log(`❌ ${uniPlatformText}构建目录不存在:`, projectPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🚀 正在打开${uniPlatformText}开发者工具...`)
|
||||||
|
|
||||||
|
// 根据不同操作系统执行不同命令
|
||||||
|
let command = ''
|
||||||
|
|
||||||
|
if (platform === 'darwin') {
|
||||||
|
// macOS
|
||||||
|
if (UNI_PLATFORM === 'mp-weixin') {
|
||||||
|
const cliPath = wechatDevtoolsCliPath || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
|
||||||
|
command = `"${cliPath}" -o "${projectPath}"`
|
||||||
|
}
|
||||||
|
else if (UNI_PLATFORM === 'mp-alipay') {
|
||||||
|
command = `/Applications/小程序开发者工具.app/Contents/MacOS/小程序开发者工具 --p "${projectPath}"`
|
||||||
|
}
|
||||||
|
else if (UNI_PLATFORM === 'mp-lark') {
|
||||||
|
command = `/Applications/抖音开发者工具.app/Contents/MacOS/抖音开发者工具 --p "${projectPath}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (platform === 'win32' || platform === 'win64') {
|
||||||
|
// Windows
|
||||||
|
if (UNI_PLATFORM === 'mp-weixin') {
|
||||||
|
const cliPath = wechatDevtoolsCliPath || 'C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat'
|
||||||
|
command = `"${cliPath}" -o "${projectPath}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Linux 或其他系统
|
||||||
|
console.log('❌ 当前系统不支持自动打开微信开发者工具')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(command, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(`❌ 打开${uniPlatformText}开发者工具失败:`, error.message)
|
||||||
|
if (UNI_PLATFORM === 'mp-weixin') {
|
||||||
|
console.log('💡 当前使用的微信开发者工具 CLI 命令:', command)
|
||||||
|
console.log('💡 如果安装位置不同,可以在 env/.env 配置 WECHAT_DEVTOOLS_CLI_PATH 为本机实际 CLI 路径')
|
||||||
|
}
|
||||||
|
console.log(`💡 请确保${uniPlatformText}开发者工具服务端口已启用`)
|
||||||
|
console.log(`💡 可以手动打开${uniPlatformText}开发者工具并导入项目:`, projectPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderr) {
|
||||||
|
console.log('⚠️ 警告:', stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ ${uniPlatformText}开发者工具已打开`)
|
||||||
|
|
||||||
|
if (stdout) {
|
||||||
|
console.log(stdout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Vite 插件,用于自动打开开发者工具
|
||||||
|
* @param {object} options - 配置选项
|
||||||
|
* @param {string} options.mode - 构建模式,'development' 或 'production'
|
||||||
|
* @param {string} options.wechatDevtoolsCliPath - 微信开发者工具 CLI 路径
|
||||||
|
*/
|
||||||
|
export default function openDevTools(options = {}) {
|
||||||
|
const { mode = 'development', wechatDevtoolsCliPath } = options
|
||||||
|
// 根据 mode 确定环境:development -> dev, production -> build
|
||||||
|
const env = mode === 'production' ? 'build' : 'dev'
|
||||||
|
|
||||||
|
// 首次构建标记
|
||||||
|
let isFirstBuild = true
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'uni-devtools',
|
||||||
|
writeBundle() {
|
||||||
|
if (isFirstBuild && process.env.UNI_PLATFORM?.includes('mp')) {
|
||||||
|
isFirstBuild = false
|
||||||
|
_openDevTools(env, { wechatDevtoolsCliPath })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
95
scripts/postupgrade.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// # 执行 `pnpm upgrade` 后会升级 `uniapp` 相关依赖
|
||||||
|
// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
|
||||||
|
// # 只需要执行下面的命令即可
|
||||||
|
|
||||||
|
import { exec } from 'node:child_process'
|
||||||
|
import { promisify } from 'node:util'
|
||||||
|
|
||||||
|
// 日志控制开关,设置为 true 可以启用所有日志输出
|
||||||
|
const FG_LOG_ENABLE = true
|
||||||
|
|
||||||
|
// 将 exec 转换为返回 Promise 的函数
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
// 定义要执行的命令
|
||||||
|
const dependencies = [
|
||||||
|
// TODO: 如果不需要某个平台的小程序,请手动删除或注释掉
|
||||||
|
'@dcloudio/uni-mp-baidu',
|
||||||
|
'@dcloudio/uni-mp-jd',
|
||||||
|
'@dcloudio/uni-mp-kuaishou',
|
||||||
|
'@dcloudio/uni-mp-qq',
|
||||||
|
'@dcloudio/uni-mp-xhs',
|
||||||
|
'@dcloudio/uni-quickapp-webview',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带开关的日志输出函数
|
||||||
|
* @param {string} message 日志消息
|
||||||
|
* @param {string} type 日志类型 (log, error)
|
||||||
|
*/
|
||||||
|
function log(message, type = 'log') {
|
||||||
|
if (FG_LOG_ENABLE) {
|
||||||
|
if (type === 'error') {
|
||||||
|
console.error(message)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载单个依赖包
|
||||||
|
* @param {string} dep 依赖包名
|
||||||
|
* @returns {Promise<boolean>} 是否成功卸载
|
||||||
|
*/
|
||||||
|
async function uninstallDependency(dep) {
|
||||||
|
try {
|
||||||
|
log(`开始卸载依赖: ${dep}`)
|
||||||
|
const { stdout, stderr } = await execPromise(`pnpm un ${dep}`)
|
||||||
|
if (stdout) {
|
||||||
|
log(`stdout [${dep}]: ${stdout}`)
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
log(`stderr [${dep}]: ${stderr}`, 'error')
|
||||||
|
}
|
||||||
|
log(`成功卸载依赖: ${dep}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// 单个依赖卸载失败不影响其他依赖
|
||||||
|
log(`卸载依赖 ${dep} 失败: ${error.message}`, 'error')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 串行卸载所有依赖包
|
||||||
|
*/
|
||||||
|
async function uninstallAllDependencies() {
|
||||||
|
log(`开始串行卸载 ${dependencies.length} 个依赖包...`)
|
||||||
|
|
||||||
|
let successCount = 0
|
||||||
|
let failedCount = 0
|
||||||
|
|
||||||
|
// 串行执行所有卸载命令
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
const success = await uninstallDependency(dep)
|
||||||
|
if (success) {
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
failedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了避免命令执行过快导致的问题,添加短暂延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`卸载操作完成: 成功 ${successCount} 个, 失败 ${failedCount} 个`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行串行卸载
|
||||||
|
uninstallAllDependencies().catch((err) => {
|
||||||
|
log(`串行卸载过程中出现未捕获的错误: ${err}`, 'error')
|
||||||
|
})
|
||||||
270
scripts/upload-weixin.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
/**
|
||||||
|
* 微信小程序 CLI 上传脚本
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* pnpm upload:mp # 版本号读取 package.json,描述使用最新 Git commit
|
||||||
|
* pnpm upload:mp --version=1.0.1 # 指定版本号(覆盖 package.json)
|
||||||
|
* pnpm upload:mp --desc="修复bug" # 指定版本描述(覆盖 Git commit)
|
||||||
|
* pnpm upload:mp --robot=2 # 指定机器人编号(1-30)
|
||||||
|
* pnpm upload:mp --version=2.0.0 --desc="重大更新" # 组合使用多个参数
|
||||||
|
*
|
||||||
|
* 版本号策略: 命令行参数 > package.json version
|
||||||
|
* 描述策略: 命令行参数 > Git 最新 commit > 默认时间戳
|
||||||
|
*
|
||||||
|
* 注意事项:
|
||||||
|
* 1. 确保已在微信公众平台开通 "小程序代码上传" 权限
|
||||||
|
* 2. 确保私钥文件存在(private.${appid}.key),并且配置了上传IP白名单
|
||||||
|
* 3. 上传前会自动执行 build:mp:prod 构建 并跳过打开开发者工具
|
||||||
|
* 4. 秘钥文件的appid(VITE_WX_APPID)需要与微信公众平台的小程序appid一致
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import ci from 'miniprogram-ci'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
const ROOT_DIR = path.resolve(__dirname, '..')
|
||||||
|
|
||||||
|
// 从 package.json 读取版本号
|
||||||
|
function getPackageVersion() {
|
||||||
|
try {
|
||||||
|
const pkgPath = path.resolve(ROOT_DIR, 'package.json')
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
||||||
|
return pkg.version || '1.0.0'
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最新的 Git commit 信息
|
||||||
|
function getGitCommitMessage() {
|
||||||
|
try {
|
||||||
|
// 获取最新 commit 的作者和标题
|
||||||
|
const message = execSync('git log -1 --pretty="%an: %s"', {
|
||||||
|
cwd: ROOT_DIR,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}).trim()
|
||||||
|
return message || null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成默认描述
|
||||||
|
function getDefaultDesc() {
|
||||||
|
// 优先使用 Git commit 信息
|
||||||
|
const gitMessage = getGitCommitMessage()
|
||||||
|
if (gitMessage) {
|
||||||
|
return gitMessage
|
||||||
|
}
|
||||||
|
// 回退到时间戳
|
||||||
|
return `上传于 ${new Date().toLocaleString('zh-CN')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析命令行参数
|
||||||
|
function parseArgs() {
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
const params = {
|
||||||
|
version: null, // 稍后设置,优先级:命令行 > package.json
|
||||||
|
desc: null, // 稍后设置,优先级:命令行 > Git commit > 默认
|
||||||
|
robot: 1, // 机器人编号 1-30
|
||||||
|
}
|
||||||
|
|
||||||
|
args.forEach((arg) => {
|
||||||
|
if (arg.startsWith('--version=')) {
|
||||||
|
params.version = arg.split('=')[1]
|
||||||
|
}
|
||||||
|
else if (arg.startsWith('--desc=')) {
|
||||||
|
params.desc = arg.split('=')[1]
|
||||||
|
}
|
||||||
|
else if (arg.startsWith('--robot=')) {
|
||||||
|
params.robot = Number.parseInt(arg.split('=')[1], 10)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果命令行没有指定版本号,则读取 package.json
|
||||||
|
if (!params.version) {
|
||||||
|
params.version = getPackageVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果命令行没有指定描述,则读取 Git commit 或使用默认
|
||||||
|
if (!params.desc) {
|
||||||
|
params.desc = getDefaultDesc()
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取环境变量
|
||||||
|
function loadEnvFile(mode = 'production') {
|
||||||
|
const envPath = path.resolve(ROOT_DIR, 'env', `.env.${mode}`)
|
||||||
|
const defaultEnvPath = path.resolve(ROOT_DIR, 'env', '.env')
|
||||||
|
|
||||||
|
const envContent = {}
|
||||||
|
|
||||||
|
// 先读取默认 .env
|
||||||
|
if (fs.existsSync(defaultEnvPath)) {
|
||||||
|
const content = fs.readFileSync(defaultEnvPath, 'utf-8')
|
||||||
|
content.split('\n').forEach((line) => {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
if (trimmed && !trimmed.startsWith('#')) {
|
||||||
|
const [key, ...valueParts] = trimmed.split('=')
|
||||||
|
if (key) {
|
||||||
|
envContent[key.trim()] = valueParts.join('=').trim().replace(/^['"]|['"]$/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再读取对应模式的 .env 文件(会覆盖默认值)
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
const content = fs.readFileSync(envPath, 'utf-8')
|
||||||
|
content.split('\n').forEach((line) => {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
if (trimmed && !trimmed.startsWith('#')) {
|
||||||
|
const [key, ...valueParts] = trimmed.split('=')
|
||||||
|
if (key) {
|
||||||
|
envContent[key.trim()] = valueParts.join('=').trim().replace(/^['"]|['"]$/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return envContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取私钥路径
|
||||||
|
function getPrivateKeyPath(appid) {
|
||||||
|
// 查找私钥文件
|
||||||
|
const keyPatterns = [
|
||||||
|
`private.${appid}.key`,
|
||||||
|
'private.key',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const pattern of keyPatterns) {
|
||||||
|
const keyPath = path.resolve(ROOT_DIR, pattern)
|
||||||
|
if (fs.existsSync(keyPath)) {
|
||||||
|
return keyPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`未找到私钥文件,请确保项目根目录存在 private.${appid}.key 文件`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function main() {
|
||||||
|
console.log('\n🚀 开始微信小程序上传流程...\n')
|
||||||
|
|
||||||
|
const params = parseArgs()
|
||||||
|
const env = loadEnvFile('production')
|
||||||
|
const appid = env.VITE_WX_APPID
|
||||||
|
|
||||||
|
if (!appid) {
|
||||||
|
throw new Error('未找到 VITE_WX_APPID 环境变量,请检查 env/.env 文件')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📱 AppID: ${appid}`)
|
||||||
|
console.log(`📌 版本号: ${params.version}`)
|
||||||
|
console.log(`📝 版本描述: ${params.desc}`)
|
||||||
|
console.log(`🤖 机器人编号: ${params.robot}`)
|
||||||
|
|
||||||
|
// 获取私钥路径
|
||||||
|
const privateKeyPath = getPrivateKeyPath(appid)
|
||||||
|
console.log(`🔑 私钥路径: ${privateKeyPath}`)
|
||||||
|
|
||||||
|
// 构建小程序(跳过自动打开开发者工具)
|
||||||
|
console.log('\n📦 正在构建小程序...(跳过自动打开开发者工具)\n')
|
||||||
|
try {
|
||||||
|
execSync('pnpm build:mp:prod', {
|
||||||
|
cwd: ROOT_DIR,
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
SKIP_OPEN_DEVTOOLS: 'true', // 上传时跳过打开开发者工具
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('❌ 构建失败:', error.message)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小程序代码目录
|
||||||
|
const projectPath = path.resolve(ROOT_DIR, 'dist', 'build', 'mp-weixin')
|
||||||
|
|
||||||
|
if (!fs.existsSync(projectPath)) {
|
||||||
|
throw new Error(`构建产物不存在: ${projectPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📂 项目路径: ${projectPath}`)
|
||||||
|
console.log('\n⬆️ 正在上传到微信服务器...\n')
|
||||||
|
|
||||||
|
// 创建项目实例
|
||||||
|
const project = new ci.Project({
|
||||||
|
appid,
|
||||||
|
type: 'miniProgram',
|
||||||
|
projectPath,
|
||||||
|
privateKeyPath,
|
||||||
|
ignores: ['node_modules/**/*'],
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 上传代码
|
||||||
|
const uploadResult = await ci.upload({
|
||||||
|
project,
|
||||||
|
version: params.version,
|
||||||
|
desc: params.desc,
|
||||||
|
robot: params.robot,
|
||||||
|
setting: {
|
||||||
|
es6: true,
|
||||||
|
es7: true,
|
||||||
|
minify: true,
|
||||||
|
autoPrefixWXSS: true,
|
||||||
|
minifyWXML: true,
|
||||||
|
minifyWXSS: true,
|
||||||
|
minifyJS: true,
|
||||||
|
},
|
||||||
|
onProgressUpdate: (task) => {
|
||||||
|
if (task._status === 'done') {
|
||||||
|
console.log(` ✅ ${task._msg}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n✅ 上传成功!')
|
||||||
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
||||||
|
console.log(` 📌 版本号: ${params.version}`)
|
||||||
|
console.log(` 📝 描述: ${params.desc}`)
|
||||||
|
console.log(` 🤖 机器人: ${params.robot}`)
|
||||||
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
||||||
|
console.log('\n📋 下一步操作:')
|
||||||
|
console.log(' 1. 登录微信公众平台: https://mp.weixin.qq.com')
|
||||||
|
console.log(' 2. 进入 "管理 -> 版本管理"')
|
||||||
|
console.log(' 3. 在 "开发版本" 中找到刚上传的版本')
|
||||||
|
console.log(' 4. 点击 "选为体验版" 按钮\n')
|
||||||
|
|
||||||
|
return uploadResult
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('\n❌ 上传失败:', error.message)
|
||||||
|
if (error.message.includes('privateKeyPath')) {
|
||||||
|
console.log('\n💡 提示: 请确保已在微信公众平台配置代码上传密钥')
|
||||||
|
console.log(' 1. 登录微信公众平台')
|
||||||
|
console.log(' 2. 进入 "开发 -> 开发设置"')
|
||||||
|
console.log(' 3. 在 "小程序代码上传" 区域生成并下载密钥')
|
||||||
|
console.log(' 4. 在 "小程序代码上传" 区域配置上传IP白名单')
|
||||||
|
}
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('❌ 执行出错:', error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
37
scripts/vite-plugin-eruda.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @description 通过 vite 自定义条件动态导入 eruda
|
||||||
|
* @description Eruda 配置参考 https://eruda.liriliri.io/zh/docs/
|
||||||
|
* @param {object} options
|
||||||
|
* @param {boolean} [options.open] - 是否开启 eruda
|
||||||
|
* @param {object} [options.erudaOptions] - eruda 配置
|
||||||
|
* @param {string} [options.erudaUrl] - eruda 地址
|
||||||
|
*/
|
||||||
|
export default function vitePluginEruda(options = {}) {
|
||||||
|
const { open = true, erudaOptions = {}, erudaUrl = 'https://cdn.jsdelivr.net/npm/eruda' } = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-eruda',
|
||||||
|
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
const tags = [
|
||||||
|
{
|
||||||
|
tag: 'script',
|
||||||
|
attrs: {
|
||||||
|
src: erudaUrl,
|
||||||
|
},
|
||||||
|
injectTo: 'head',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'script',
|
||||||
|
children: `eruda.init(${JSON.stringify(erudaOptions)});`,
|
||||||
|
injectTo: 'head',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
return { html, tags }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/App.ku.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import FgTabbar from '@/tabbar/index.vue'
|
||||||
|
import { isPageTabbar } from './tabbar/store'
|
||||||
|
import { currRoute } from './utils'
|
||||||
|
|
||||||
|
const isCurrentPageTabbar = ref(true)
|
||||||
|
onShow(() => {
|
||||||
|
const { path } = currRoute()
|
||||||
|
// “蜡笔小开心”提到本地是 '/pages/index/index',线上是 '/' 导致线上 tabbar 不见了
|
||||||
|
// 所以这里需要判断一下,如果是 '/' 就当做首页,也要显示 tabbar
|
||||||
|
if (path === '/') {
|
||||||
|
isCurrentPageTabbar.value = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isCurrentPageTabbar.value = isPageTabbar(path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const helloKuRoot = ref('Hello AppKuVue')
|
||||||
|
|
||||||
|
const exposeRef = ref('this is form app.Ku.vue')
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
exposeRef,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 这个先隐藏了,知道这样用就行 -->
|
||||||
|
<view class="hidden text-center">
|
||||||
|
{{ helloKuRoot }},这里可以配置全局的东西
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<KuRootView />
|
||||||
|
|
||||||
|
<FgTabbar v-if="isCurrentPageTabbar" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
52
src/App.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||||
|
import { getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { navigateToInterceptor } from '@/router/interceptor'
|
||||||
|
import { tabbarStore } from '@/tabbar/store'
|
||||||
|
import { permission } from '@/router/permission'
|
||||||
|
|
||||||
|
const { proxy } = (getCurrentInstance() || {}) as any
|
||||||
|
const router = proxy?.$router
|
||||||
|
|
||||||
|
router && permission.install(router)
|
||||||
|
|
||||||
|
onLaunch((options) => {
|
||||||
|
console.log('App.vue onLaunch', options)
|
||||||
|
})
|
||||||
|
onShow((options) => {
|
||||||
|
console.log('App.vue onShow', options)
|
||||||
|
// 处理直接进入页面路由的情况:如h5直接输入路由、微信小程序分享后进入等
|
||||||
|
// https://github.com/unibest-tech/unibest/issues/192
|
||||||
|
if (options?.path) {
|
||||||
|
navigateToInterceptor.invoke({ url: `/${options.path}`, query: options.query })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
navigateToInterceptor.invoke({ url: '/' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onHide(() => {
|
||||||
|
console.log('App Hide')
|
||||||
|
})
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
function syncTabbarWhenPageVisible() {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
tabbarStore.syncCurIdxByCurrentPageAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('visibilitychange', syncTabbarWhenPageVisible)
|
||||||
|
window.addEventListener('pageshow', syncTabbarWhenPageVisible)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('visibilitychange', syncTabbarWhenPageVisible)
|
||||||
|
window.removeEventListener('pageshow', syncTabbarWhenPageVisible)
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
17
src/api/foo-alova.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { API_DOMAINS, http } from '@/http/alova'
|
||||||
|
|
||||||
|
export interface IFoo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function foo() {
|
||||||
|
return http.Get<IFoo>('/foo', {
|
||||||
|
params: {
|
||||||
|
name: '菲鸽',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
meta: { domain: API_DOMAINS.SECONDARY }, // 用于切换请求地址
|
||||||
|
})
|
||||||
|
}
|
||||||
43
src/api/foo.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { http } from '@/http/http'
|
||||||
|
|
||||||
|
export interface IFoo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function foo() {
|
||||||
|
return http.Get<IFoo>('/foo', {
|
||||||
|
params: {
|
||||||
|
name: '菲鸽',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFooItem {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET 请求 */
|
||||||
|
export async function getFooAPI(name: string) {
|
||||||
|
return await http.get<IFooItem>('/foo', { name })
|
||||||
|
}
|
||||||
|
/** GET 请求;支持 传递 header 的范例 */
|
||||||
|
export function getFooAPI2(name: string) {
|
||||||
|
return http.get<IFooItem>('/foo', { name }, { 'Content-Type-100': '100' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST 请求 */
|
||||||
|
export function postFooAPI(name: string) {
|
||||||
|
return http.post<IFooItem>('/foo', { name })
|
||||||
|
}
|
||||||
|
/** POST 请求;需要传递 query 参数的范例;微信小程序经常有同时需要query参数和body参数的场景 */
|
||||||
|
export function postFooAPI2(name: string) {
|
||||||
|
return http.post<IFooItem>('/foo', { name }, { a: 1, b: 2 })
|
||||||
|
}
|
||||||
|
/** POST 请求;支持 传递 header 的范例 */
|
||||||
|
export function postFooAPI3(name: string) {
|
||||||
|
return http.post<IFooItem>('/foo', { name }, { a: 1, b: 2 }, { 'Content-Type-100': '100' })
|
||||||
|
}
|
||||||
85
src/api/login.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import type { IAuthLoginRes, ICaptcha, IDoubleTokenRes, IUpdateInfo, IUpdatePassword, IUserInfoRes } from './types/login'
|
||||||
|
import { http } from '@/http/http'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录表单
|
||||||
|
*/
|
||||||
|
export interface ILoginForm {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
* @returns ICaptcha 验证码
|
||||||
|
*/
|
||||||
|
export function getCode() {
|
||||||
|
return http.get<ICaptcha>('/user/getCode')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param loginForm 登录表单
|
||||||
|
*/
|
||||||
|
export function login(loginForm: ILoginForm) {
|
||||||
|
return http.post<IAuthLoginRes>('/auth/login', loginForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新token
|
||||||
|
* @param refreshToken 刷新token
|
||||||
|
*/
|
||||||
|
export function refreshToken(refreshToken: string) {
|
||||||
|
return http.post<IDoubleTokenRes>('/auth/refreshToken', { refreshToken })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
export function getUserInfo() {
|
||||||
|
return http.get<IUserInfoRes>('/user/info')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
export function logout() {
|
||||||
|
return http.get<void>('/auth/logout')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户信息
|
||||||
|
*/
|
||||||
|
export function updateInfo(data: IUpdateInfo) {
|
||||||
|
return http.post('/user/updateInfo', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户密码
|
||||||
|
*/
|
||||||
|
export function updateUserPassword(data: IUpdatePassword) {
|
||||||
|
return http.post('/user/updatePassword', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信登录凭证
|
||||||
|
* @returns Promise 包含微信登录凭证(code)
|
||||||
|
*/
|
||||||
|
export function getWxCode() {
|
||||||
|
return new Promise<UniApp.LoginRes>((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: res => resolve(res),
|
||||||
|
fail: err => reject(new Error(err)),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信登录
|
||||||
|
* @param params 微信登录参数,包含code
|
||||||
|
* @returns Promise 包含登录结果
|
||||||
|
*/
|
||||||
|
export function wxLogin(data: { code: string }) {
|
||||||
|
return http.post<IAuthLoginRes>('/auth/wxLogin', data)
|
||||||
|
}
|
||||||
102
src/api/types/login.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// 认证模式类型
|
||||||
|
export type AuthMode = 'single' | 'double'
|
||||||
|
|
||||||
|
// 单Token响应类型
|
||||||
|
export interface ISingleTokenRes {
|
||||||
|
token: string
|
||||||
|
expiresIn: number // 有效期(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双Token响应类型
|
||||||
|
export interface IDoubleTokenRes {
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
accessExpiresIn: number // 访问令牌有效期(秒)
|
||||||
|
refreshExpiresIn: number // 刷新令牌有效期(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录返回的信息,其实就是 token 信息
|
||||||
|
*/
|
||||||
|
export type IAuthLoginRes = ISingleTokenRes | IDoubleTokenRes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
export type UserRole = string
|
||||||
|
|
||||||
|
export interface IUserInfoRes {
|
||||||
|
userId: number
|
||||||
|
username: string
|
||||||
|
nickname: string
|
||||||
|
avatar?: string
|
||||||
|
/** 同时支持单角色和多角色,你自行选择一种就行 */
|
||||||
|
role?: UserRole
|
||||||
|
roles?: UserRole[]
|
||||||
|
[key: string]: any // 允许其他扩展字段
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证存储数据结构
|
||||||
|
export interface AuthStorage {
|
||||||
|
mode: AuthMode
|
||||||
|
tokens: ISingleTokenRes | IDoubleTokenRes
|
||||||
|
userInfo?: IUserInfoRes
|
||||||
|
loginTime: number // 登录时间戳
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*/
|
||||||
|
export interface ICaptcha {
|
||||||
|
captchaEnabled: boolean
|
||||||
|
uuid: string
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 上传成功的信息
|
||||||
|
*/
|
||||||
|
export interface IUploadSuccessInfo {
|
||||||
|
fileId: number
|
||||||
|
originalName: string
|
||||||
|
fileName: string
|
||||||
|
storagePath: string
|
||||||
|
fileHash: string
|
||||||
|
fileType: string
|
||||||
|
fileBusinessType: string
|
||||||
|
fileSize: number
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
*/
|
||||||
|
export interface IUpdateInfo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
sex: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
*/
|
||||||
|
export interface IUpdatePassword {
|
||||||
|
id: number
|
||||||
|
oldPassword: string
|
||||||
|
newPassword: string
|
||||||
|
confirmPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为单Token响应
|
||||||
|
* @param tokenRes 登录响应数据
|
||||||
|
* @returns 是否为单Token响应
|
||||||
|
*/
|
||||||
|
export function isSingleTokenRes(tokenRes: IAuthLoginRes): tokenRes is ISingleTokenRes {
|
||||||
|
return 'token' in tokenRes && !('refreshToken' in tokenRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为双Token响应
|
||||||
|
* @param tokenRes 登录响应数据
|
||||||
|
* @returns 是否为双Token响应
|
||||||
|
*/
|
||||||
|
export function isDoubleTokenRes(tokenRes: IAuthLoginRes): tokenRes is IDoubleTokenRes {
|
||||||
|
return 'accessToken' in tokenRes && 'refreshToken' in tokenRes
|
||||||
|
}
|
||||||
0
src/components/.gitkeep
Normal file
444
src/components/qiun-data-charts/config-echarts.js
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
* uCharts®
|
||||||
|
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
|
||||||
|
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
|
||||||
|
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||||
|
* 复制使用请保留本段注释,感谢支持开源!
|
||||||
|
*
|
||||||
|
* uCharts®官方网站
|
||||||
|
* https://www.uCharts.cn
|
||||||
|
*
|
||||||
|
* 开源地址:
|
||||||
|
* https://gitee.com/uCharts/uCharts
|
||||||
|
*
|
||||||
|
* uni-app插件市场地址:
|
||||||
|
* http://ext.dcloud.net.cn/plugin?id=271
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 通用配置项
|
||||||
|
|
||||||
|
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
|
||||||
|
const color = [
|
||||||
|
'#1890FF',
|
||||||
|
'#91CB74',
|
||||||
|
'#FAC858',
|
||||||
|
'#EE6666',
|
||||||
|
'#73C0DE',
|
||||||
|
'#3CA272',
|
||||||
|
'#FC8452',
|
||||||
|
'#9A60B4',
|
||||||
|
'#ea7ccc',
|
||||||
|
]
|
||||||
|
|
||||||
|
const cfe = {
|
||||||
|
// demotype为自定义图表类型
|
||||||
|
type: [
|
||||||
|
'pie',
|
||||||
|
'ring',
|
||||||
|
'rose',
|
||||||
|
'funnel',
|
||||||
|
'line',
|
||||||
|
'column',
|
||||||
|
'area',
|
||||||
|
'radar',
|
||||||
|
'gauge',
|
||||||
|
'candle',
|
||||||
|
'demotype',
|
||||||
|
],
|
||||||
|
// 增加自定义图表类型,如果需要categories,请在这里加入您的图表类型例如最后的"demotype"
|
||||||
|
categories: ['line', 'column', 'area', 'radar', 'gauge', 'candle', 'demotype'],
|
||||||
|
// instance为实例变量承载属性,option为eopts承载属性,不要删除
|
||||||
|
instance: {},
|
||||||
|
option: {},
|
||||||
|
// 下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
||||||
|
formatter: {
|
||||||
|
tooltipDemo1(res) {
|
||||||
|
let result = ''
|
||||||
|
for (const i in res) {
|
||||||
|
if (i == 0) {
|
||||||
|
result += `${res[i].axisValueLabel}年销售额`
|
||||||
|
}
|
||||||
|
let value = '--'
|
||||||
|
if (res[i].data !== null) {
|
||||||
|
value = res[i].data
|
||||||
|
}
|
||||||
|
// #ifdef H5
|
||||||
|
result += `\n${res[i].seriesName}:${value} 万元`
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
result += `<br/>${res[i].marker}${res[i].seriesName}:${value} 万元`
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
legendFormat(name) {
|
||||||
|
return `自定义图例+${name}`
|
||||||
|
},
|
||||||
|
yAxisFormatDemo(value, index) {
|
||||||
|
return `${value}元`
|
||||||
|
},
|
||||||
|
seriesFormatDemo(res) {
|
||||||
|
return `${res.name}年${res.value}元`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在eopts参数,会将demotype与eopts中option合并后渲染图表。
|
||||||
|
demotype: {
|
||||||
|
color,
|
||||||
|
// 在这里填写echarts的option即可
|
||||||
|
},
|
||||||
|
// 下面是自定义配置,请添加项目所需的通用配置
|
||||||
|
column: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 50,
|
||||||
|
right: 15,
|
||||||
|
left: 40,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 'left',
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
boundaryGap: true,
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'bar',
|
||||||
|
data: [],
|
||||||
|
barwidth: 20,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#666666',
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 50,
|
||||||
|
right: 15,
|
||||||
|
left: 40,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 'left',
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
boundaryGap: true,
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'line',
|
||||||
|
data: [],
|
||||||
|
barwidth: 20,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#666666',
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
bottom: 50,
|
||||||
|
right: 15,
|
||||||
|
left: 40,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 'left',
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
boundaryGap: true,
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#CCCCCC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'line',
|
||||||
|
data: [],
|
||||||
|
areaStyle: {},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#666666',
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pie: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 30,
|
||||||
|
right: 15,
|
||||||
|
left: 15,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 'left',
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'pie',
|
||||||
|
data: [],
|
||||||
|
radius: '50%',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#666666',
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
bottom: 30,
|
||||||
|
right: 15,
|
||||||
|
left: 15,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 'left',
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'pie',
|
||||||
|
data: [],
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#666666',
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rose: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 'bottom',
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'pie',
|
||||||
|
data: [],
|
||||||
|
radius: '55%',
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
roseType: 'area',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funnel: {
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c}%',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 'bottom',
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'funnel',
|
||||||
|
left: '10%',
|
||||||
|
top: 60,
|
||||||
|
bottom: 60,
|
||||||
|
width: '80%',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
minSize: '0%',
|
||||||
|
maxSize: '100%',
|
||||||
|
sort: 'descending',
|
||||||
|
gap: 2,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'inside',
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
type: 'solid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
bordercolor: '#fff',
|
||||||
|
borderwidth: 1,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gauge: {
|
||||||
|
color,
|
||||||
|
tooltip: {
|
||||||
|
formatter: '{a} <br/>{b} : {c}%',
|
||||||
|
},
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '业务指标',
|
||||||
|
type: 'gauge',
|
||||||
|
detail: { formatter: '{value}%' },
|
||||||
|
data: [{ value: 50, name: '完成率' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
candle: {
|
||||||
|
xAxis: {
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
xAxisIndex: [0, 1],
|
||||||
|
start: 10,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
xAxisIndex: [0, 1],
|
||||||
|
type: 'slider',
|
||||||
|
bottom: 10,
|
||||||
|
start: 10,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
seriesTemplate: {
|
||||||
|
name: '',
|
||||||
|
type: 'k',
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cfe
|
||||||
675
src/components/qiun-data-charts/config-ucharts.js
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
/*
|
||||||
|
* uCharts®
|
||||||
|
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
|
||||||
|
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
|
||||||
|
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||||
|
* 复制使用请保留本段注释,感谢支持开源!
|
||||||
|
*
|
||||||
|
* uCharts®官方网站
|
||||||
|
* https://www.uCharts.cn
|
||||||
|
*
|
||||||
|
* 开源地址:
|
||||||
|
* https://gitee.com/uCharts/uCharts
|
||||||
|
*
|
||||||
|
* uni-app插件市场地址:
|
||||||
|
* http://ext.dcloud.net.cn/plugin?id=271
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
|
||||||
|
const color = [
|
||||||
|
'#1890FF',
|
||||||
|
'#91CB74',
|
||||||
|
'#FAC858',
|
||||||
|
'#EE6666',
|
||||||
|
'#73C0DE',
|
||||||
|
'#3CA272',
|
||||||
|
'#FC8452',
|
||||||
|
'#9A60B4',
|
||||||
|
'#ea7ccc',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 事件转换函数,主要用作格式化x轴为时间轴,根据需求自行修改
|
||||||
|
const formatDateTime = (timeStamp, returnType) => {
|
||||||
|
const date = new Date()
|
||||||
|
date.setTime(timeStamp * 1000)
|
||||||
|
const y = date.getFullYear()
|
||||||
|
let m = date.getMonth() + 1
|
||||||
|
m = m < 10 ? `0${m}` : m
|
||||||
|
let d = date.getDate()
|
||||||
|
d = d < 10 ? `0${d}` : d
|
||||||
|
let h = date.getHours()
|
||||||
|
h = h < 10 ? `0${h}` : h
|
||||||
|
let minute = date.getMinutes()
|
||||||
|
let second = date.getSeconds()
|
||||||
|
minute = minute < 10 ? `0${minute}` : minute
|
||||||
|
second = second < 10 ? `0${second}` : second
|
||||||
|
if (returnType == 'full') {
|
||||||
|
return `${y}-${m}-${d} ${h}:${minute}:${second}`
|
||||||
|
}
|
||||||
|
if (returnType == 'y-m-d') {
|
||||||
|
return `${y}-${m}-${d}`
|
||||||
|
}
|
||||||
|
if (returnType == 'h:m') {
|
||||||
|
return `${h}:${minute}`
|
||||||
|
}
|
||||||
|
if (returnType == 'h:m:s') {
|
||||||
|
return `${h}:${minute}:${second}`
|
||||||
|
}
|
||||||
|
return [y, m, d, h, minute, second]
|
||||||
|
}
|
||||||
|
|
||||||
|
const cfu = {
|
||||||
|
// demotype为自定义图表类型,一般不需要自定义图表类型,只需要改根节点上对应的类型即可
|
||||||
|
type: [
|
||||||
|
'pie',
|
||||||
|
'ring',
|
||||||
|
'rose',
|
||||||
|
'word',
|
||||||
|
'funnel',
|
||||||
|
'map',
|
||||||
|
'arcbar',
|
||||||
|
'line',
|
||||||
|
'column',
|
||||||
|
'mount',
|
||||||
|
'bar',
|
||||||
|
'area',
|
||||||
|
'radar',
|
||||||
|
'gauge',
|
||||||
|
'candle',
|
||||||
|
'mix',
|
||||||
|
'tline',
|
||||||
|
'tarea',
|
||||||
|
'scatter',
|
||||||
|
'bubble',
|
||||||
|
'demotype',
|
||||||
|
],
|
||||||
|
range: [
|
||||||
|
'饼状图',
|
||||||
|
'圆环图',
|
||||||
|
'玫瑰图',
|
||||||
|
'词云图',
|
||||||
|
'漏斗图',
|
||||||
|
'地图',
|
||||||
|
'圆弧进度条',
|
||||||
|
'折线图',
|
||||||
|
'柱状图',
|
||||||
|
'山峰图',
|
||||||
|
'条状图',
|
||||||
|
'区域图',
|
||||||
|
'雷达图',
|
||||||
|
'仪表盘',
|
||||||
|
'K线图',
|
||||||
|
'混合图',
|
||||||
|
'时间轴折线',
|
||||||
|
'时间轴区域',
|
||||||
|
'散点图',
|
||||||
|
'气泡图',
|
||||||
|
'自定义类型',
|
||||||
|
],
|
||||||
|
// 增加自定义图表类型,如果需要categories,请在这里加入您的图表类型,例如最后的"demotype"
|
||||||
|
// 自定义类型时需要注意"tline","tarea","scatter","bubble"等时间轴(矢量x轴)类图表,没有categories,不需要加入categories
|
||||||
|
categories: [
|
||||||
|
'line',
|
||||||
|
'column',
|
||||||
|
'mount',
|
||||||
|
'bar',
|
||||||
|
'area',
|
||||||
|
'radar',
|
||||||
|
'gauge',
|
||||||
|
'candle',
|
||||||
|
'mix',
|
||||||
|
'demotype',
|
||||||
|
],
|
||||||
|
// instance为实例变量承载属性,不要删除
|
||||||
|
instance: {},
|
||||||
|
// option为opts及eopts承载属性,不要删除
|
||||||
|
option: {},
|
||||||
|
// 下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
|
||||||
|
formatter: {
|
||||||
|
yAxisDemo1(val, index, opts) {
|
||||||
|
return `${val}元`
|
||||||
|
},
|
||||||
|
yAxisDemo2(val, index, opts) {
|
||||||
|
return val.toFixed(2)
|
||||||
|
},
|
||||||
|
xAxisDemo1(val, index, opts) {
|
||||||
|
return `${val}年`
|
||||||
|
},
|
||||||
|
xAxisDemo2(val, index, opts) {
|
||||||
|
return formatDateTime(val, 'h:m')
|
||||||
|
},
|
||||||
|
seriesDemo1(val, index, series, opts) {
|
||||||
|
return `${val}元`
|
||||||
|
},
|
||||||
|
tooltipDemo1(item, category, index, opts) {
|
||||||
|
if (index == 0) {
|
||||||
|
return `随便用${item.data}年`
|
||||||
|
}
|
||||||
|
return `其他我没改${item.data}天`
|
||||||
|
},
|
||||||
|
pieDemo(val, index, series, opts) {
|
||||||
|
if (index !== undefined) {
|
||||||
|
return `${series[index].name}:${series[index].data}元`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在opts参数,会将demotype与opts中option合并后渲染图表。
|
||||||
|
demotype: {
|
||||||
|
// 我这里把曲线图当做了自定义图表类型,您可以根据需要随意指定类型或配置
|
||||||
|
type: 'line',
|
||||||
|
color,
|
||||||
|
padding: [15, 10, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 2,
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
line: {
|
||||||
|
type: 'curve',
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 下面是自定义配置,请添加项目所需的通用配置
|
||||||
|
pie: {
|
||||||
|
type: 'pie',
|
||||||
|
color,
|
||||||
|
padding: [5, 5, 5, 5],
|
||||||
|
extra: {
|
||||||
|
pie: {
|
||||||
|
activeOpacity: 0.5,
|
||||||
|
activeRadius: 10,
|
||||||
|
offsetAngle: 0,
|
||||||
|
labelWidth: 15,
|
||||||
|
border: true,
|
||||||
|
borderWidth: 3,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
type: 'ring',
|
||||||
|
color,
|
||||||
|
padding: [5, 5, 5, 5],
|
||||||
|
rotate: false,
|
||||||
|
dataLabel: true,
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
lineHeight: 25,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
name: '收益率',
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
name: '70%',
|
||||||
|
fontSize: 25,
|
||||||
|
color: '#7cb5ec',
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
ring: {
|
||||||
|
ringWidth: 30,
|
||||||
|
activeOpacity: 0.5,
|
||||||
|
activeRadius: 10,
|
||||||
|
offsetAngle: 0,
|
||||||
|
labelWidth: 15,
|
||||||
|
border: true,
|
||||||
|
borderWidth: 3,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rose: {
|
||||||
|
type: 'rose',
|
||||||
|
color,
|
||||||
|
padding: [5, 5, 5, 5],
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
position: 'left',
|
||||||
|
lineHeight: 25,
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
rose: {
|
||||||
|
type: 'area',
|
||||||
|
minRadius: 50,
|
||||||
|
activeOpacity: 0.5,
|
||||||
|
activeRadius: 10,
|
||||||
|
offsetAngle: 0,
|
||||||
|
labelWidth: 15,
|
||||||
|
border: false,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
word: {
|
||||||
|
type: 'word',
|
||||||
|
color,
|
||||||
|
extra: {
|
||||||
|
word: {
|
||||||
|
type: 'normal',
|
||||||
|
autoColors: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funnel: {
|
||||||
|
type: 'funnel',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
extra: {
|
||||||
|
funnel: {
|
||||||
|
activeOpacity: 0.3,
|
||||||
|
activeWidth: 10,
|
||||||
|
border: true,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
fillOpacity: 1,
|
||||||
|
labelAlign: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
type: 'map',
|
||||||
|
color,
|
||||||
|
padding: [0, 0, 0, 0],
|
||||||
|
dataLabel: true,
|
||||||
|
extra: {
|
||||||
|
map: {
|
||||||
|
border: true,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#666666',
|
||||||
|
fillOpacity: 0.6,
|
||||||
|
activeBorderColor: '#F04864',
|
||||||
|
activeFillColor: '#FACC14',
|
||||||
|
activeFillOpacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arcbar: {
|
||||||
|
type: 'arcbar',
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
name: '百分比',
|
||||||
|
fontSize: 25,
|
||||||
|
color: '#00FF00',
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
name: '默认标题',
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#666666',
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
arcbar: {
|
||||||
|
type: 'default',
|
||||||
|
width: 12,
|
||||||
|
backgroundColor: '#E9E9E9',
|
||||||
|
startAngle: 0.75,
|
||||||
|
endAngle: 0.25,
|
||||||
|
gap: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
color,
|
||||||
|
padding: [15, 10, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 2,
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
line: {
|
||||||
|
type: 'straight',
|
||||||
|
width: 2,
|
||||||
|
activeType: 'hollow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tline: {
|
||||||
|
type: 'line',
|
||||||
|
color,
|
||||||
|
padding: [15, 10, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: false,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 2,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
max: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
line: {
|
||||||
|
type: 'curve',
|
||||||
|
width: 2,
|
||||||
|
activeType: 'hollow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tarea: {
|
||||||
|
type: 'area',
|
||||||
|
color,
|
||||||
|
padding: [15, 10, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 2,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
max: 80,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
area: {
|
||||||
|
type: 'curve',
|
||||||
|
opacity: 0.2,
|
||||||
|
addLine: true,
|
||||||
|
width: 2,
|
||||||
|
gradient: true,
|
||||||
|
activeType: 'hollow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: 'column',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 5],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
data: [{ min: 0 }],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
column: {
|
||||||
|
type: 'group',
|
||||||
|
width: 30,
|
||||||
|
activeBgColor: '#000000',
|
||||||
|
activeBgOpacity: 0.08,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mount: {
|
||||||
|
type: 'mount',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 5],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
data: [{ min: 0 }],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
mount: {
|
||||||
|
type: 'mount',
|
||||||
|
widthRatio: 1.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
type: 'bar',
|
||||||
|
color,
|
||||||
|
padding: [15, 30, 0, 5],
|
||||||
|
xAxis: {
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
disableGrid: false,
|
||||||
|
min: 0,
|
||||||
|
axisLine: false,
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
bar: {
|
||||||
|
type: 'group',
|
||||||
|
width: 30,
|
||||||
|
meterBorde: 1,
|
||||||
|
meterFillColor: '#FFFFFF',
|
||||||
|
activeBgColor: '#000000',
|
||||||
|
activeBgOpacity: 0.08,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
type: 'area',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 2,
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
area: {
|
||||||
|
type: 'straight',
|
||||||
|
opacity: 0.2,
|
||||||
|
addLine: true,
|
||||||
|
width: 2,
|
||||||
|
gradient: false,
|
||||||
|
activeType: 'hollow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
radar: {
|
||||||
|
type: 'radar',
|
||||||
|
color,
|
||||||
|
padding: [5, 5, 5, 5],
|
||||||
|
dataLabel: false,
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
position: 'right',
|
||||||
|
lineHeight: 25,
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
radar: {
|
||||||
|
gridType: 'radar',
|
||||||
|
gridColor: '#CCCCCC',
|
||||||
|
gridCount: 3,
|
||||||
|
opacity: 0.2,
|
||||||
|
max: 200,
|
||||||
|
labelShow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gauge: {
|
||||||
|
type: 'gauge',
|
||||||
|
color,
|
||||||
|
title: {
|
||||||
|
name: '66Km/H',
|
||||||
|
fontSize: 25,
|
||||||
|
color: '#2fc25b',
|
||||||
|
offsetY: 50,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
name: '实时速度',
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#1890ff',
|
||||||
|
offsetY: -50,
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
gauge: {
|
||||||
|
type: 'default',
|
||||||
|
width: 30,
|
||||||
|
labelColor: '#666666',
|
||||||
|
startAngle: 0.75,
|
||||||
|
endAngle: 0.25,
|
||||||
|
startNumber: 0,
|
||||||
|
endNumber: 100,
|
||||||
|
labelFormat: '',
|
||||||
|
splitLine: {
|
||||||
|
fixRadius: 0,
|
||||||
|
splitNumber: 10,
|
||||||
|
width: 30,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
childNumber: 5,
|
||||||
|
childWidth: 12,
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
width: 24,
|
||||||
|
color: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
candle: {
|
||||||
|
type: 'candle',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
enableScroll: true,
|
||||||
|
enableMarkLine: true,
|
||||||
|
dataLabel: false,
|
||||||
|
xAxis: {
|
||||||
|
labelCount: 4,
|
||||||
|
itemCount: 40,
|
||||||
|
disableGrid: true,
|
||||||
|
gridColor: '#CCCCCC',
|
||||||
|
gridType: 'solid',
|
||||||
|
dashLength: 4,
|
||||||
|
scrollShow: true,
|
||||||
|
scrollAlign: 'left',
|
||||||
|
scrollColor: '#A6A6A6',
|
||||||
|
scrollBackgroundColor: '#EFEBEF',
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
candle: {
|
||||||
|
color: {
|
||||||
|
upLine: '#f04864',
|
||||||
|
upFill: '#f04864',
|
||||||
|
downLine: '#2fc25b',
|
||||||
|
downFill: '#2fc25b',
|
||||||
|
},
|
||||||
|
average: {
|
||||||
|
show: true,
|
||||||
|
name: ['MA5', 'MA10', 'MA30'],
|
||||||
|
day: [5, 10, 20],
|
||||||
|
color: ['#1890ff', '#2fc25b', '#facc14'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
type: 'dash',
|
||||||
|
dashLength: 5,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: 2150,
|
||||||
|
lineColor: '#f04864',
|
||||||
|
showLabel: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2350,
|
||||||
|
lineColor: '#f04864',
|
||||||
|
showLabel: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mix: {
|
||||||
|
type: 'mix',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
disabled: false,
|
||||||
|
disableGrid: false,
|
||||||
|
splitNumber: 5,
|
||||||
|
gridType: 'dash',
|
||||||
|
dashLength: 4,
|
||||||
|
gridColor: '#CCCCCC',
|
||||||
|
padding: 10,
|
||||||
|
showTitle: true,
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
mix: {
|
||||||
|
column: {
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scatter: {
|
||||||
|
type: 'scatter',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
dataLabel: false,
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: false,
|
||||||
|
gridType: 'dash',
|
||||||
|
splitNumber: 5,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
disableGrid: false,
|
||||||
|
gridType: 'dash',
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
scatter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bubble: {
|
||||||
|
type: 'bubble',
|
||||||
|
color,
|
||||||
|
padding: [15, 15, 0, 15],
|
||||||
|
xAxis: {
|
||||||
|
disableGrid: false,
|
||||||
|
gridType: 'dash',
|
||||||
|
splitNumber: 5,
|
||||||
|
boundaryGap: 'justify',
|
||||||
|
min: 0,
|
||||||
|
max: 250,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
disableGrid: false,
|
||||||
|
gridType: 'dash',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
max: 150,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
extra: {
|
||||||
|
bubble: {
|
||||||
|
border: 2,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cfu
|
||||||
1826
src/components/qiun-data-charts/qiun-data-charts.vue
Normal file
3
src/components/qiun-data-charts/u-charts.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import uCharts from '@qiun/ucharts'
|
||||||
|
|
||||||
|
export default uCharts
|
||||||
48
src/components/qiun-error/qiun-error.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<view class="chartsview">
|
||||||
|
<view class="charts-error"></view>
|
||||||
|
<view class="charts-font">{{ errorMessage == null ? '请点击重试' : errorMessage }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'qiun-error',
|
||||||
|
props: {
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chartsview {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-font {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-error {
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAUz0lEQVR4Xu1de3Bc1X3+zmp3jYVWfkPAgCWwY8zLEglNQCSW0yT9o2SQaDKdNulUykwfM+k09p+J3ImYIPJXi9yZzDSZZiwyaZuZBCwnNG1DMogEmUAAy7xs/MAyNhCMjWWtsK1deU/n23OvtLu6j3Pv3t29d71nxjOSde455/5+3/m9z7kCjRY4BR7eK7fkcmhrasLT37hbTAY+QYADigDHagwFYGhc7gZwHMAUgG4hMPzNe8RoWInTAECAnHl4r+yREt0DXWIbhx3cJ5fHP8TYjntFR4DTBDqUIwBmMrJDCtyPHDoQw0Q8jkeXinCLtECp43Gwh56R22IxTBbu+KFxOTbQJbo9DlW17rYASGdlNySeKl2JADpbkmKiait0mWhoXHZkmzE52CkocmvavvOsbMvl8MhAl+jlQrg2CQzu6BI9NV2Yw+T2AJiVo+DuL2kSeLQ1KfrC8kLcYbkYBv/pbjEWhjUNjUvSpk9KSAicm2tGXxjAaUcbewBkJAm6xeLBp1PJ2os06ttcDl8H0CEEaGnvGegSg2EAQZTW4B0AEntSS2ov0mhgJc5jmwT6IDEWi2E0zNZ2WEFhC4CZjCRxH7GwAfpbkmIkLC9EFQBg20BXeOySsNBGZx2OXkB6Vg5CgAbMZgD7BTDSkhTDOgM3+kSDAr7iANNz8n4hQRdxojUu9kTjVRurtKKAJwBIKZfPZPOuYWFgY6wlgV4hau+GNVjsnQKeAJDOSIp/Wt6lbSKeQG8jSOSdAbV+wisA7FxDSGAqBmwNS5DIiGIucyNwKiGedutTz3/3BgCb4JBJoGqDIJ2VW4REmxRog0S3lGgT/NlfY3RzCgJjQmJSCkxeDuDwBgCb8HAhvQkCIdCbSgQfmSPDmWGDzHsm1UqwjBEUAMbqERCeAEBGz2RknwR2uW0yAZQdL6DR+WEW90syXLmjtW8So0Jg9MoE9tSD4esZANUAAd1M5NAjGOULaaOkAzCKGEaj7Ar7AoBHEGxrSYqdOnxMZ+W3ZA59ZehxnWkC7yMlJkUMIy1x7IyaVPANAAMEHTlgTACO1rYERlqTot+J8nbp58C5VcEBDftnOEpAKAsAgYPAPgNZQbYFP3QeCAybJ/Bg2CVC2QDwCoJUAtudiJKuExCQLoZbPKirAoOHovuIThVByuXii2jE/C9I2TaXBYsfmThyahMtCWy1A4ERbj7rvvRI9aCa3F7pINm3n5XdXgtjFgHAYCQrW4v8bBo6MYFep5cwmEefuSwQpDNSRoq9+osdrqRaGBqXMhfDVi8gWASAdEbuswuyGCKNSLatBygXBHUqAQohMmHESAKrqzSro4TIS2yOq10dVQQAuyKQUoC7BXnIxHQWwwL4ay/qIM/8DHaFJuijv7M99QzaNmAx6hzQFsvhKSmxvakJo7oHUooA4MUA0wHBTDYfQnVUB6bFnLc1JHqiFgPwxPnSzhKjLUn0B+UpsDoqFkOfLvO5HN8AMN5lOJUU2+2IMD0ne0QOtCcq0k7OANe1VGToag7qaBRXeiFFAJjOyBENsV20Jqcgj2FQHgvyJWYvAQfPAJuvAv7198ADm4DMHJBKAmuag5ypemPpGNiVWk2pDcCDDDQCPTU7EOgmjrxMRgA8dgBYmwJOXwBuWgH87m3gz26OLgDy6q9G9RSLvIAymFZUGsaCjJzE7qB1+vvngXRGQebG5QB/P30eaF2iQBHllk8wxdDfGq/eYVLLQJBfEOQNOpk3/Bg86hbA8iAZwt2/a78asX8zsKRJ/fzYQeDttFIHUbcJqi0JnM4FaOX9g2Sw7lgHTgPTs0DHRxTjT5wDtqzTfTr8/aoJArfTwX055P1519q6apGV4v8/XlU6nzv/vo8CvzwK3L0W2LS6Wquo/DzVAoFrMiivyzVSvpUnycIMVAUK2kb28Qm77nn2wQXGRg5uGvEw1AtjaHkpMx/Ro/fqfBliBsOeUbl3fu3lTKko7X/J8vJJOtFuxV/jXKo3Bsna4AI6kbe4BrlTyrYIqsUMi2R1ti0DIxPpT8QnEF5SxQmW8wo5oM5QF9+fLkP4xlcGp2QCaos8VY77yMEK7KOjZ4U7fuN3ShXcTBSbL0BtoHD2vc+JYrS4Ox8yBfD6J/5cX9nB5+5TvapXmh+jQuU9E2Ez0QdgKgu3AfvuXv7RnAu+Nf1c5a2Z6gkGA3xauyPp1yq16rWR/9P/bE5fvXVe06fJv9yxbqI+ZmW4aCPluDbJ1rK8mYq1wEMTEkF4s5Agqo8qCXG/k9TyWqSwfXyT2gKrHjd8gZ3vC62V1B1l6F76qZ1I7nqO1xqCo2OR7c8w9a3kFhkyo5d9h9al+2wzN2t9tPPS3Mntf7Qb2oZqtdpaaf/67XtPZq95fi1mGZm0mo6OaTg4T2TTpJQWPbNly7T01AH4Kp++qmAm3mYwbQ0v0Y4tFQ6T6tq18p2dml/BzV1F/1qxhLCFTbXGxHzS5y9fTg9ys+fxiqy1M4k7xFrXkHVI+6Y6vyXxfkJr3DLkpd3xATVtYkQh2nK9oX7n7NsA+QaVfdnXknGTjrTy9UQy4bV99fPHad0M9RSoO0VgsBKW5Dcy/py99Yd5B1t5FQJfUwvtV0frZGYvFS5Xgh8D8aXc0twT/PBVI3Td2Jp8y1OFfn1KWS7Pj7RagzphcOORcmyifp3YAjc4KJ/eXoIgkBPR7cQXJs8oPb5tyHoVFH1rmiLbt8tZAGt2xR0sQ7R8S0y5t4IF5m8Jg2dR0bXo1Xk8b7dVyUPrTbg1XG7P72Q5iP2p9o1IN+H1e0ulnI7VdWF3a3rfcJq0ZlD8YxpQ0+YrWDR5pD2+9f1bd9JrVkqW7Kk2Wya4QGZP69pUuA70C6j6ddN3E9u7ZlB6w0kI7J55H1pOJ9oAL9GXh4Xnlw7m6qXb2Q8tH3HpgmQWw2A6iH1H2t3M9GZ5zP/2QAytnCRe4TtUffCzPKP1yxW3f0TCJp8i1eYg0pN4h9Qxg+3Zt66I5kYt8dYFM9l6D6GmCqYdPf1okhfO4J3DH9Z2oP4fdpCH0dH1scQ6y9Q0Y2V7+fC9V8qf4c1g3D7m1r5Md2O3q36k7s4p37m6wR/1nsZrV1pgrRmtV2R2k4zJH4AOGi7FQ9gCq7soCw2vIQsmW2qkr7gWXxOAYo5ti8iM0W9nJ67KTYX4K1lNg45XxKfCr5T3jG2w6FqH7h2t9G0lECK7jYY5V0+Up7M+ZCv9DgZ7g2gZ8BYzRQAd2z7L4qf3vbK5kX8+ZyQgK5nB0x0S9W4E1AAeYsMzgO0aG/EDoBUE4bq+nrQfW91bD1K8oE0nQ3VZo3f8bWlPNkS3jP/2yuj+0VwYwU6g2Fgnb8IkC4jk9nLzX9WzTHDkK+oUGd5G4Z4A13rGxf0pEW8yXx0Jsb3AuhQ/3oC6/2t9aU3Tt2Z6b+5OTQ3PLmF4q9VdrC0UsQxOfwG1GQapdwv0tXh4m3Rm9V94+9NfyvA5tdt+9fK2bD1Jp3HO/2rR2u9UR3D5scv5Otxm4y7Y6gG1yXtw1jYxGDHqg2kA1dvvRLR1yYc1TBx5wppNVpjHnQYF7Qn9YJg3lItRz8aQd6nH8p7L5m3gHBK3d4t1e5g2RCdU9ZEn1WwG+it2nNCgaD2yKRGFeP0f6a3p1bF7v4gy2WzFQpJ5Km4qZ8+Kn3KX1Ih1nJ8jB8V6bF4bFhzQy8e8n8vcvV/TH0+cy+YpC9C8q1I9m2/OVrBkVxtm0C3D2t8qoK3nG7Tiiq7uxA1AH7e6S2hZ3gxp5w3q9Y7ZxY9kzK4X5UQwPzgw5OBN55c1juC3B3iGgKsd0hsyV1e14L01/2WwGsfBz8j/6nA6x1C2e9V5P7v1SgE3bXg0bvlqQq3bMreH6kRCpH2F2MC3m2jMNX/ClqffTQ5uMfru9T0rIY5Vq6mFYvdV0x0dXpn+2KCPqHSM8e7+Zb2d6EoS+q9lI2iAI9z+8p8kqGmghjJPb1A6CtE8Pz0j4+2fE2cQyVQ3pHkV0dYwX/4T1l5a8y6n9ws1B7r9ghg4Y/8rWgKq5b9+4YqDk1vF3/8s23aYx2ox5p5fZ+5pZf6g+rB/Ki3feh4iE1sY5S6fiPS7xE3q+7dZPpxbM0ZRU9dtL1Jt9az3N+fYV9UoTRjQHI5OL0zhXf8yI0xvV7KZkVt2I3m1sYkl/YN8x1o0b6p7v0m0vVw6u3rF7VfG7lB7S7aXjVv7x5O3Z7U4wxg49nFl1bGqYy+G5q8vJklSgM6Aq8t9yPqxHqt3pK+rT79Vyoq6aR1muVv8bN9u1zGqcfL6G3xu4j0M3GzVQn1iFS5k8z1K3mS8C2d3Y6b8t/xqGakgs5cBh6z1Cbm+45aKn8c4uh4Y3g+whpY1dPq9GQnO8GKB2c3X0Z9g2z+X7rZ2p+Il3tO5s+tygU0b8f9H0jNWuWcXRg1WbQbrmK3w5xA1jV3I1pYp1PzB67b64O7J3ftmM9OT6kjhH10jHFTOw+tjf0Gm7xgH4+4Rz2fG3WjpEOOxX9tD4qBqMLLIPIOoSGKXkIu8QeDJL0kS+0b2bdVxFz6j0d+F8Y1k6I5C8seBqP+0AL7xbMjbkq5vG8Qhzf98QvDg6rw8EwIQ0bB7uC9j7b2zV6fF9ttH4r6yG8sM0jqzj8ZC4J/T5iP6+0lLykYz1ejQCdMfAsD2P3FasFz3L3dZ5rD7K0x2T4pH0vQ0u8N1aW8S5M0z/4R8sWfoHnQ7T4M7FJ7k+v2o7YzQj0Y6c8rj1t0BP9Yc0u7X9i/8CKR+KpV6FMuIQAAAABJRU5ErkJggg==');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
153
src/components/qiun-loading/loading1.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading1">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading1',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading1 {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91cb74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fac858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape4 {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading1 .shape1 {
|
||||||
|
animation: animation1shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading1 .shape2 {
|
||||||
|
animation: animation1shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-16px, 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading1 .shape3 {
|
||||||
|
animation: animation1shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading1 .shape4 {
|
||||||
|
animation: animation1shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation1shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-16px, -16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
183
src/components/qiun-loading/loading2.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading2">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading2',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading2 {
|
||||||
|
transform: rotate(10deg);
|
||||||
|
transform: rotate(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading2 .shape {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading2 {
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91cb74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fac858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape4 {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading2 .shape1 {
|
||||||
|
animation: animation2shape1 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
transform: translate(20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading2 .shape2 {
|
||||||
|
animation: animation2shape2 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
transform: translate(-20px, 20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading2 .shape3 {
|
||||||
|
animation: animation2shape3 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
transform: translate(20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading2 .shape4 {
|
||||||
|
animation: animation2shape4 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation2shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation2shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
transform: translate(-20px, -20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
190
src/components/qiun-loading/loading3.vue
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading3">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading3',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 {
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 .shape1 {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 .shape2 {
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 .shape3 {
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading3 .shape4 {
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91cb74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fac858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape4 {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading3 .shape1 {
|
||||||
|
animation: animation3shape1 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape1 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape1 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
transform: translate(5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading3 .shape2 {
|
||||||
|
animation: animation3shape2 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape2 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape2 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
transform: translate(-5px, 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading3 .shape3 {
|
||||||
|
animation: animation3shape3 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape3 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape3 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
transform: translate(5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading3 .shape4 {
|
||||||
|
animation: animation3shape4 0.5s ease 0s infinite alternate;
|
||||||
|
animation: animation3shape4 0.5s ease 0s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation3shape4 {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
transform: translate(-5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
250
src/components/qiun-loading/loading4.vue
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading5">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading5',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading5 .shape {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91cb74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fac858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape4 {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading5 .shape1 {
|
||||||
|
animation: animation5shape1 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape1 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape1 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
transform: translate(15px, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading5 .shape2 {
|
||||||
|
animation: animation5shape2 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
transform: translate(-15px, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
transform: translate(0, 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading5 .shape3 {
|
||||||
|
animation: animation5shape3 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape3 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape3 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
transform: translate(15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
transform: translate(15px, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading5 .shape4 {
|
||||||
|
animation: animation5shape4 2s ease 0s infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape4 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation5shape4 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
transform: translate(0, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
transform: translate(-15px, -15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
transform: translate(-15px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
259
src/components/qiun-loading/loading5.vue
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container loading6">
|
||||||
|
<view class="shape shape1"></view>
|
||||||
|
<view class="shape shape2"></view>
|
||||||
|
<view class="shape shape3"></view>
|
||||||
|
<view class="shape shape4"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'loading6',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped="true">
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading6 {
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
animation: rotation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.loading6 .shape {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape1 {
|
||||||
|
left: 0;
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape2 {
|
||||||
|
right: 0;
|
||||||
|
background-color: #91cb74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape3 {
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #fac858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .shape.shape4 {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading6 .shape1 {
|
||||||
|
animation: animation6shape1 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape1 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape1 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape1 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
transform: translate(18px, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading6 .shape2 {
|
||||||
|
animation: animation6shape2 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape2 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
transform: translate(-18px, 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
transform: translate(0, 18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading6 .shape3 {
|
||||||
|
animation: animation6shape3 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape3 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape3 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape3 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
transform: translate(18px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
transform: translate(18px, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading6 .shape4 {
|
||||||
|
animation: animation6shape4 2s linear 0s infinite normal;
|
||||||
|
animation: animation6shape4 2s linear 0s infinite normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape4 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animation6shape4 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
transform: translate(0, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
transform: translate(-18px, -18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
transform: translate(-18px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/components/qiun-loading/qiun-loading.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<Loading1 v-if="loadingType == 1" />
|
||||||
|
<Loading2 v-if="loadingType == 2" />
|
||||||
|
<Loading3 v-if="loadingType == 3" />
|
||||||
|
<Loading4 v-if="loadingType == 4" />
|
||||||
|
<Loading5 v-if="loadingType == 5" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loading1 from './loading1.vue'
|
||||||
|
import Loading2 from './loading2.vue'
|
||||||
|
import Loading3 from './loading3.vue'
|
||||||
|
import Loading4 from './loading4.vue'
|
||||||
|
import Loading5 from './loading5.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Loading1, Loading2, Loading3, Loading4, Loading5 },
|
||||||
|
name: 'qiun-loading',
|
||||||
|
props: {
|
||||||
|
loadingType: {
|
||||||
|
type: Number,
|
||||||
|
default: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
47
src/components/qiun-title-bar/qiun-title-bar.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<view class="qiun-title-bar">
|
||||||
|
<view class="qiun-title-dot"></view>
|
||||||
|
<view class="qiun-title-text">{{ title }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'title-bar',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.qiun-title-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qiun-title-dot {
|
||||||
|
width: 5px;
|
||||||
|
height: 16px;
|
||||||
|
margin-left: 8px;
|
||||||
|
background-color: #409eff;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qiun-title-text {
|
||||||
|
height: 22px;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
src/env.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
/// <reference types="vite-svg-loader" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
/** 网站标题,应用名称 */
|
||||||
|
readonly VITE_APP_TITLE: string
|
||||||
|
/** 服务端口号 */
|
||||||
|
readonly VITE_SERVER_PORT: string
|
||||||
|
/** 后台接口地址 */
|
||||||
|
readonly VITE_SERVER_BASEURL: string
|
||||||
|
/** 微信小程序开发版后台接口地址,不配置则使用 VITE_SERVER_BASEURL */
|
||||||
|
readonly VITE_SERVER_BASEURL__WEIXIN_DEVELOP?: string
|
||||||
|
/** 微信小程序体验版后台接口地址,不配置则使用 VITE_SERVER_BASEURL */
|
||||||
|
readonly VITE_SERVER_BASEURL__WEIXIN_TRIAL?: string
|
||||||
|
/** 微信小程序正式版后台接口地址,不配置则使用 VITE_SERVER_BASEURL */
|
||||||
|
readonly VITE_SERVER_BASEURL__WEIXIN_RELEASE?: string
|
||||||
|
/** H5是否需要代理 */
|
||||||
|
readonly VITE_APP_PROXY_ENABLE: 'true' | 'false'
|
||||||
|
/** H5是否需要代理,需要的话有个前缀 */
|
||||||
|
readonly VITE_APP_PROXY_PREFIX: string
|
||||||
|
/** 后端是否有统一前缀 /api */
|
||||||
|
readonly VITE_SERVER_HAS_API_PREFIX: 'true' | 'false'
|
||||||
|
/** 认证模式,'single' | 'double' ==> 单token | 双token */
|
||||||
|
readonly VITE_AUTH_MODE: 'single' | 'double'
|
||||||
|
/** 是否清除console */
|
||||||
|
readonly VITE_DELETE_CONSOLE: string
|
||||||
|
// 更多环境变量...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const __VITE_APP_PROXY__: 'true' | 'false'
|
||||||
74
src/hooks/useRequest.test.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { defineComponent, h } from 'vue'
|
||||||
|
import useRequest from './useRequest'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Vue 应用上下文中运行 composable。
|
||||||
|
* composable 的 ref/computed/onMounted 只能在 setup() 内使用,
|
||||||
|
* withSetup 通过挂载一个临时组件来提供这个上下文。
|
||||||
|
*/
|
||||||
|
function withSetup<T>(composableFn: () => T): T {
|
||||||
|
let result!: T
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup() {
|
||||||
|
result = composableFn()
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const wrapper = mount(Comp)
|
||||||
|
wrapper.unmount()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('useRequest', () => {
|
||||||
|
it('初始状态:loading=false, error=false, data=undefined', () => {
|
||||||
|
const asyncFn = vi.fn().mockResolvedValue('data')
|
||||||
|
const { loading, error, data } = withSetup(() => useRequest(asyncFn))
|
||||||
|
|
||||||
|
expect(loading.value).toBe(false)
|
||||||
|
expect(error.value).toBe(false)
|
||||||
|
expect(data.value).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('initialData:初始 data 使用传入的默认值', () => {
|
||||||
|
const asyncFn = vi.fn().mockResolvedValue('new')
|
||||||
|
const { data } = withSetup(() => useRequest(asyncFn, { initialData: 'init' }))
|
||||||
|
|
||||||
|
expect(data.value).toBe('init')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('run 成功:loading 先变 true 后变 false,data 更新为返回值', async () => {
|
||||||
|
const asyncFn = vi.fn().mockResolvedValue('result')
|
||||||
|
const { loading, data, run } = withSetup(() => useRequest(asyncFn))
|
||||||
|
|
||||||
|
const runPromise = run()
|
||||||
|
expect(loading.value).toBe(true)
|
||||||
|
|
||||||
|
await runPromise
|
||||||
|
|
||||||
|
expect(loading.value).toBe(false)
|
||||||
|
expect(data.value).toBe('result')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('run 失败:抛出错误,error 被设置,loading 重置为 false', async () => {
|
||||||
|
const err = new Error('network error')
|
||||||
|
const asyncFn = vi.fn().mockRejectedValue(err)
|
||||||
|
const { loading, error, run } = withSetup(() => useRequest(asyncFn))
|
||||||
|
|
||||||
|
await expect(run()).rejects.toThrow('network error')
|
||||||
|
|
||||||
|
expect(loading.value).toBe(false)
|
||||||
|
expect(error.value).toBe(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('immediate=true:组件挂载时立即调用异步函数并更新 data', async () => {
|
||||||
|
const asyncFn = vi.fn().mockResolvedValue('eager')
|
||||||
|
const { data } = withSetup(() => useRequest(asyncFn, { immediate: true }))
|
||||||
|
|
||||||
|
expect(asyncFn).toHaveBeenCalledTimes(1)
|
||||||
|
// 等待 Promise 完成
|
||||||
|
await asyncFn.mock.results[0].value
|
||||||
|
expect(data.value).toBe('eager')
|
||||||
|
})
|
||||||
|
})
|
||||||
54
src/hooks/useRequest.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface IUseRequestOptions<T> {
|
||||||
|
/** 是否立即执行 */
|
||||||
|
immediate?: boolean
|
||||||
|
/** 初始化数据 */
|
||||||
|
initialData?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IUseRequestReturn<T, P = undefined> {
|
||||||
|
loading: Ref<boolean>
|
||||||
|
error: Ref<boolean | Error>
|
||||||
|
data: Ref<T | undefined>
|
||||||
|
run: (args?: P) => Promise<T | undefined>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useRequest是一个定制化的请求钩子,用于处理异步请求和响应。
|
||||||
|
* @param func 一个执行异步请求的函数,返回一个包含响应数据的Promise。
|
||||||
|
* @param options 包含请求选项的对象 {immediate, initialData}。
|
||||||
|
* @param options.immediate 是否立即执行请求,默认为false。
|
||||||
|
* @param options.initialData 初始化数据,默认为undefined。
|
||||||
|
* @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
|
||||||
|
*/
|
||||||
|
export default function useRequest<T, P = undefined>(
|
||||||
|
func: (args?: P) => Promise<T>,
|
||||||
|
options: IUseRequestOptions<T> = { immediate: false },
|
||||||
|
): IUseRequestReturn<T, P> {
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(false)
|
||||||
|
const data = ref<T | undefined>(options.initialData) as Ref<T | undefined>
|
||||||
|
const run = async (args?: P) => {
|
||||||
|
loading.value = true
|
||||||
|
return func(args)
|
||||||
|
.then((res) => {
|
||||||
|
data.value = res
|
||||||
|
error.value = false
|
||||||
|
return data.value
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error.value = err
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.immediate) {
|
||||||
|
(run as (args: P) => Promise<T | undefined>)({} as P)
|
||||||
|
}
|
||||||
|
return { loading, error, data, run }
|
||||||
|
}
|
||||||
116
src/hooks/useScroll.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# 上拉刷新和下拉加载更多
|
||||||
|
|
||||||
|
在 unibest 框架中,我们通过组合 `useScroll` Hook 可结合 `scroll-view` 组件来轻松实现上拉刷新和下拉加载更多的功能。
|
||||||
|
场景一 页面滚动
|
||||||
|
|
||||||
|
```
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '上拉刷新和下拉加载更多',
|
||||||
|
enablePullDownRefresh: true,
|
||||||
|
onReachBottomDistance: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
场景二 局部滚动 结合 `scroll-view`
|
||||||
|
|
||||||
|
## 关键文件
|
||||||
|
|
||||||
|
- `src/hooks/useScroll.ts`: 提供了核心的滚动逻辑处理 Hook。
|
||||||
|
- `src/pages-sub/demo/scroll.vue`: 一个具体的实现示例页面。
|
||||||
|
|
||||||
|
## `useScroll` Hook
|
||||||
|
|
||||||
|
`useScroll` 是一个 Vue Composition API Hook,它封装了处理下拉刷新和上拉加载的通用逻辑。
|
||||||
|
|
||||||
|
### 主要功能
|
||||||
|
|
||||||
|
- **管理加载状态**: 自动处理 `loading`(加载中)、`finished`(已加载全部)和 `error`(加载失败)等状态。
|
||||||
|
- **分页逻辑**: 内部维护分页参数(页码 `page` 和每页数量 `pageSize`)。
|
||||||
|
- **事件处理**: 提供 `onScrollToLower`(滚动到底部)、`onRefresherRefresh`(下拉刷新)等方法,用于在视图层触发。
|
||||||
|
- **数据合并**: 自动将新加载的数据追加到现有列表 `list` 中。
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useScroll } from '@/hooks/useScroll'
|
||||||
|
import { getList } from '@/service/list' // 你的数据请求API
|
||||||
|
|
||||||
|
const {
|
||||||
|
list, // 响应式的数据列表
|
||||||
|
loading, // 是否加载中
|
||||||
|
finished, // 是否已全部加载
|
||||||
|
error, // 是否加载失败
|
||||||
|
onScrollToLower, // 滚动到底部时触发的事件
|
||||||
|
onRefresherRefresh, // 下拉刷新时触发的事件
|
||||||
|
} = useScroll(getList) // 将获取数据的API函数传入
|
||||||
|
```
|
||||||
|
|
||||||
|
## `scroll-view` 组件
|
||||||
|
|
||||||
|
`scroll-view` 是 uni-app 提供的可滚动视图区域组件,它提供了一系列属性来支持下拉刷新和上拉加载。
|
||||||
|
|
||||||
|
### 关键属性
|
||||||
|
|
||||||
|
- `scroll-y`: 允许纵向滚动。
|
||||||
|
- `refresher-enabled`: 启用下拉刷新。
|
||||||
|
- `refresher-triggered`: 控制下拉刷新动画的显示与隐藏,通过 `loading` 状态绑定。
|
||||||
|
- `@scrolltolower`: 滚动到底部时触发的事件,绑定 `onScrollToLower` 方法。
|
||||||
|
- `@refresherrefresh`: 触发下拉刷新时触发的事件,绑定 `onRefresherRefresh` 方法。
|
||||||
|
|
||||||
|
## 示例代码
|
||||||
|
|
||||||
|
以下是 `src/pages-sub/demo/scroll.vue` 中的核心代码,展示了如何将 `useScroll` 和 `scroll-view` 结合使用。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<view class="scroll-page">
|
||||||
|
<scroll-view
|
||||||
|
class="scroll-view"
|
||||||
|
scroll-y
|
||||||
|
:refresher-enabled="true"
|
||||||
|
:refresher-triggered="loading"
|
||||||
|
@scrolltolower="onScrollToLower"
|
||||||
|
@refresherrefresh="onRefresherRefresh"
|
||||||
|
>
|
||||||
|
<view v-for="item in list" :key="item.id" class="scroll-item">
|
||||||
|
{{ item.name }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载状态提示 -->
|
||||||
|
<view v-if="loading" class="loading-tip">加载中...</view>
|
||||||
|
<view v-if="finished" class="finished-tip">没有更多了</view>
|
||||||
|
<view v-if="error" class="error-tip">加载失败,请重试</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useScroll } from '@/hooks/useScroll'
|
||||||
|
import { getList } from '@/service/list'
|
||||||
|
|
||||||
|
const { list, loading, finished, error, onScrollToLower, onRefresherRefresh } = useScroll(getList)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 样式省略 */
|
||||||
|
.scroll-page, .scroll-view {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实现步骤总结
|
||||||
|
|
||||||
|
1. **创建API**: 确保你有一个返回分页数据的API请求函数(例如 `getList`),它应该接受页码和页面大小作为参数。
|
||||||
|
2. **调用 `useScroll`**: 在你的页面脚本中,导入并调用 `useScroll` Hook,将你的API函数作为参数传入。
|
||||||
|
3. **模板绑定**:
|
||||||
|
- 使用 `scroll-view` 组件作为滚动容器。
|
||||||
|
- 将其 `refresher-triggered` 属性绑定到 `useScroll` 返回的 `loading` 状态。
|
||||||
|
- 将其 `@scrolltolower` 事件绑定到 `onScrollToLower` 方法。
|
||||||
|
- 将其 `@refresherrefresh` 事件绑定到 `onRefresherRefresh` 方法。
|
||||||
|
4. **渲染列表**: 使用 `v-for` 指令渲染 `useScroll` 返回的 `list` 数组。
|
||||||
|
5. **添加加载提示**: 根据 `loading`, `finished`, `error` 状态,在列表底部显示不同的提示信息,提升用户体验。
|
||||||
|
|
||||||
|
通过以上步骤,你就可以在项目中快速集成一个功能完善、体验良好的上拉刷新和下拉加载列表。
|
||||||
74
src/hooks/useScroll.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
interface UseScrollOptions<T> {
|
||||||
|
fetchData: (page: number, pageSize: number) => Promise<T[]>
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseScrollReturn<T> {
|
||||||
|
list: Ref<T[]>
|
||||||
|
loading: Ref<boolean>
|
||||||
|
finished: Ref<boolean>
|
||||||
|
error: Ref<any>
|
||||||
|
refresh: () => Promise<void>
|
||||||
|
loadMore: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useScroll<T>({
|
||||||
|
fetchData,
|
||||||
|
pageSize = 10,
|
||||||
|
}: UseScrollOptions<T>): UseScrollReturn<T> {
|
||||||
|
const list = ref<T[]>([]) as Ref<T[]>
|
||||||
|
const loading = ref(false)
|
||||||
|
const finished = ref(false)
|
||||||
|
const error = ref<any>(null)
|
||||||
|
const page = ref(1)
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
if (loading.value || finished.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchData(page.value, pageSize)
|
||||||
|
if (data.length < pageSize) {
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
list.value.push(...data)
|
||||||
|
page.value++
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error.value = err
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
page.value = 1
|
||||||
|
finished.value = false
|
||||||
|
list.value = []
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadMore = async () => {
|
||||||
|
await loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
loading,
|
||||||
|
finished,
|
||||||
|
error,
|
||||||
|
refresh,
|
||||||
|
loadMore,
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/hooks/useUpload.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { getEnvBaseUrl } from '@/utils/index'
|
||||||
|
|
||||||
|
const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/upload`
|
||||||
|
|
||||||
|
type TfileType = 'image' | 'file'
|
||||||
|
type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
|
||||||
|
type TFile = 'doc' | 'docx' | 'ppt' | 'zip' | 'xls' | 'xlsx' | 'txt' | TImage
|
||||||
|
|
||||||
|
interface TOptions<T extends TfileType> {
|
||||||
|
formData?: Record<string, any>
|
||||||
|
maxSize?: number
|
||||||
|
accept?: T extends 'image' ? TImage[] : TFile[]
|
||||||
|
fileType?: T
|
||||||
|
success?: (params: any) => void
|
||||||
|
error?: (err: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
|
||||||
|
const {
|
||||||
|
formData = {},
|
||||||
|
maxSize = 5 * 1024 * 1024,
|
||||||
|
accept = ['*'],
|
||||||
|
fileType = 'image',
|
||||||
|
success,
|
||||||
|
error: onError,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<Error | null>(null)
|
||||||
|
const data = ref<any>(null)
|
||||||
|
|
||||||
|
const handleFileChoose = ({ tempFilePath, size }: { tempFilePath: string, size: number }) => {
|
||||||
|
if (size > maxSize) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `文件大小不能超过 ${maxSize / 1024 / 1024}MB`,
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// const fileExtension = file?.tempFiles?.name?.split('.').pop()?.toLowerCase()
|
||||||
|
// const isTypeValid = accept.some((type) => type === '*' || type.toLowerCase() === fileExtension)
|
||||||
|
|
||||||
|
// if (!isTypeValid) {
|
||||||
|
// uni.showToast({
|
||||||
|
// title: `仅支持 ${accept.join(', ')} 格式的文件`,
|
||||||
|
// icon: 'none',
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
uploadFile({
|
||||||
|
tempFilePath,
|
||||||
|
formData,
|
||||||
|
onSuccess: (res) => {
|
||||||
|
// 修改这里的解析逻辑,适应不同平台的返回格式
|
||||||
|
let parsedData = res
|
||||||
|
try {
|
||||||
|
// 尝试解析为JSON
|
||||||
|
const jsonData = JSON.parse(res)
|
||||||
|
// 检查是否包含data字段
|
||||||
|
parsedData = jsonData.data || jsonData
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// 如果解析失败,使用原始数据
|
||||||
|
console.log('Response is not JSON, using raw data:', res)
|
||||||
|
}
|
||||||
|
data.value = parsedData
|
||||||
|
// console.log('上传成功', res)
|
||||||
|
success?.(parsedData)
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
error.value = err
|
||||||
|
onError?.(err)
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
loading.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
|
||||||
|
// 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议
|
||||||
|
const chooseFileOptions = {
|
||||||
|
count: 1,
|
||||||
|
success: (res: any) => {
|
||||||
|
console.log('File selected successfully:', res)
|
||||||
|
// 小程序中res:{errMsg: "chooseImage:ok", tempFiles: [{fileType: "image", size: 48976, tempFilePath: "http://tmp/5iG1WpIxTaJf3ece38692a337dc06df7eb69ecb49c6b.jpeg"}]}
|
||||||
|
// h5中res:{errMsg: "chooseImage:ok", tempFilePaths: "blob:http://localhost:9000/f74ab6b8-a14d-4cb6-a10d-fcf4511a0de5", tempFiles: [File]}
|
||||||
|
// h5的File有以下字段:{name: "girl.jpeg", size: 48976, type: "image/jpeg"}
|
||||||
|
// App中res:{errMsg: "chooseImage:ok", tempFilePaths: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", tempFiles: [File]}
|
||||||
|
// App的File有以下字段:{path: "file:///Users/feige/xxx/gallery/1522437259-compressed-IMG_0006.jpg", size: 48976}
|
||||||
|
let tempFilePath = ''
|
||||||
|
let size = 0
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
tempFilePath = res.tempFiles[0].tempFilePath
|
||||||
|
size = res.tempFiles[0].size
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
tempFilePath = res.tempFilePaths[0]
|
||||||
|
size = res.tempFiles[0].size
|
||||||
|
// #endif
|
||||||
|
handleFileChoose({ tempFilePath, size })
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.error('File selection failed:', err)
|
||||||
|
error.value = err
|
||||||
|
onError?.(err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType === 'image') {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
...chooseFileOptions,
|
||||||
|
mediaType: ['image'],
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseImage(chooseFileOptions)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uni.chooseFile({
|
||||||
|
...chooseFileOptions,
|
||||||
|
type: 'all',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { loading, error, data, run }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFile({
|
||||||
|
tempFilePath,
|
||||||
|
formData,
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
onComplete,
|
||||||
|
}: {
|
||||||
|
tempFilePath: string
|
||||||
|
formData: Record<string, any>
|
||||||
|
onSuccess: (data: any) => void
|
||||||
|
onError: (err: any) => void
|
||||||
|
onComplete: () => void
|
||||||
|
}) {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: VITE_UPLOAD_BASEURL,
|
||||||
|
filePath: tempFilePath,
|
||||||
|
name: 'file',
|
||||||
|
formData,
|
||||||
|
success: (uploadFileRes) => {
|
||||||
|
try {
|
||||||
|
const data = uploadFileRes.data
|
||||||
|
onSuccess(data)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
onError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('Upload failed:', err)
|
||||||
|
onError(err)
|
||||||
|
},
|
||||||
|
complete: onComplete,
|
||||||
|
})
|
||||||
|
}
|
||||||
59
src/http/README.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# 请求库
|
||||||
|
|
||||||
|
目前 unibest 支持 3 种请求方式:
|
||||||
|
|
||||||
|
- 简单版 `http`:路径 `src/http/http.ts`,适合大多数简单项目。
|
||||||
|
- `alova`:路径 `src/http/alova.ts`。
|
||||||
|
- `vue-query`:路径 `src/http/vue-query.ts`,主要用于自动生成接口,详情见 https://unibest.tech/base/17-generate 。
|
||||||
|
|
||||||
|
## 如何选择
|
||||||
|
|
||||||
|
如果您以前用过 `alova` 或 `vue-query`,可以优先使用熟悉的方案。
|
||||||
|
|
||||||
|
如果项目接口不复杂,简单版 `http` 就够了,也不会增加额外包体积。
|
||||||
|
|
||||||
|
## 关于 http 使用
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { http } from '@/http/http'
|
||||||
|
|
||||||
|
interface IUserInfoRes {
|
||||||
|
id: number
|
||||||
|
nickname: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserInfo() {
|
||||||
|
return http.get<IUserInfoRes>('/user/info')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUserInfo(data: Partial<IUserInfoRes>) {
|
||||||
|
return http.post('/user/update', data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应成功时会返回业务 `data`;业务错误、登录失效、HTTP 状态码异常和网络异常会统一 reject `HttpError`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { HttpError } from '@/http/types'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userInfo = await getUserInfo()
|
||||||
|
console.log(userInfo.nickname)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const httpError = error as HttpError
|
||||||
|
console.log(httpError.type, httpError.message, httpError.statusCode)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果调用方需要自行处理错误提示,可以传入 `hideErrorToast: true`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
http.get<IUserInfoRes>('/user/info', undefined, undefined, {
|
||||||
|
hideErrorToast: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## roadmap
|
||||||
|
|
||||||
|
菲鸽最近在优化脚手架,后续可以选择是否使用第三方请求库,以及选择具体请求库。
|
||||||
119
src/http/alova.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
|
||||||
|
import type { IResponse } from './types'
|
||||||
|
import AdapterUniapp from '@alova/adapter-uniapp'
|
||||||
|
import { createAlova } from 'alova'
|
||||||
|
import { createServerTokenAuthentication } from 'alova/client'
|
||||||
|
import VueHook from 'alova/vue'
|
||||||
|
import { toLoginPage } from '@/utils/toLoginPage'
|
||||||
|
import { ContentTypeEnum, ResultEnum, ShowMessage } from './tools/enum'
|
||||||
|
|
||||||
|
// 配置动态Tag
|
||||||
|
export const API_DOMAINS = {
|
||||||
|
DEFAULT: import.meta.env.VITE_SERVER_BASEURL,
|
||||||
|
SECONDARY: import.meta.env.VITE_SERVER_BASEURL_SECONDARY,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建请求实例
|
||||||
|
*/
|
||||||
|
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
|
||||||
|
typeof VueHook,
|
||||||
|
typeof uniappRequestAdapter
|
||||||
|
>({
|
||||||
|
// 如果下面拦截不到,请使用 refreshTokenOnSuccess by 群友@琛
|
||||||
|
refreshTokenOnError: {
|
||||||
|
isExpired: (error) => {
|
||||||
|
return error.response?.status === ResultEnum.Unauthorized
|
||||||
|
},
|
||||||
|
handler: async () => {
|
||||||
|
try {
|
||||||
|
// await authLogin();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// 切换到登录页
|
||||||
|
toLoginPage({ mode: 'reLaunch' })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alova 请求实例
|
||||||
|
*/
|
||||||
|
const alovaInstance = createAlova({
|
||||||
|
baseURL: API_DOMAINS.DEFAULT,
|
||||||
|
...AdapterUniapp(),
|
||||||
|
timeout: 5000,
|
||||||
|
statesHook: VueHook,
|
||||||
|
|
||||||
|
beforeRequest: onAuthRequired((method) => {
|
||||||
|
// 设置默认 Content-Type
|
||||||
|
method.config.headers = {
|
||||||
|
ContentType: ContentTypeEnum.JSON,
|
||||||
|
Accept: 'application/json, text/plain, */*',
|
||||||
|
...method.config.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { config } = method
|
||||||
|
const ignoreAuth = !config.meta?.ignoreAuth
|
||||||
|
console.log('ignoreAuth===>', ignoreAuth)
|
||||||
|
// 处理认证信息 自行处理认证问题
|
||||||
|
if (ignoreAuth) {
|
||||||
|
const token = 'getToken()'
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('[请求错误]:未登录')
|
||||||
|
}
|
||||||
|
// method.config.headers.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理动态域名
|
||||||
|
if (config.meta?.domain) {
|
||||||
|
method.baseURL = config.meta.domain
|
||||||
|
console.log('当前域名', method.baseURL)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
responded: onResponseRefreshToken((response, method) => {
|
||||||
|
const { config } = method
|
||||||
|
const { requestType } = config
|
||||||
|
const {
|
||||||
|
statusCode,
|
||||||
|
data: rawData,
|
||||||
|
errMsg,
|
||||||
|
} = response as UniNamespace.RequestSuccessCallbackResult
|
||||||
|
|
||||||
|
// 处理特殊请求类型(上传/下载)
|
||||||
|
if (requestType === 'upload' || requestType === 'download') {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 HTTP 状态码错误
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]`
|
||||||
|
console.error('errorMessage===>', errorMessage)
|
||||||
|
uni.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error',
|
||||||
|
})
|
||||||
|
throw new Error(`${errorMessage}:${errMsg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理业务逻辑错误
|
||||||
|
const { code, message, data } = rawData as IResponse
|
||||||
|
// 0和200当做成功都很普遍,这里直接兼容两者,见 ResultEnum
|
||||||
|
if (code !== ResultEnum.Success0 && code !== ResultEnum.Success200) {
|
||||||
|
if (config.meta?.toast !== false) {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
throw new Error(`请求错误[${code}]:${message}`)
|
||||||
|
}
|
||||||
|
// 处理成功响应,返回业务数据
|
||||||
|
return data
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const http = alovaInstance
|
||||||
243
src/http/http.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import type { IDoubleTokenRes } from '@/api/types/login'
|
||||||
|
import type { CustomRequestOptions, HttpError, IResponse } from '@/http/types'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
import { useTokenStore } from '@/store/token'
|
||||||
|
import { isDoubleTokenMode } from '@/utils'
|
||||||
|
import { toLoginPage } from '@/utils/toLoginPage'
|
||||||
|
import { createHttpError, getResponseMessage, HttpErrorType, isSuccessResultCode, ResultEnum, ShowMessage } from './tools/enum'
|
||||||
|
|
||||||
|
// 刷新 token 状态管理
|
||||||
|
let refreshing = false // 防止重复刷新 token 标识
|
||||||
|
let taskQueue: (() => void)[] = [] // 刷新 token 请求队列
|
||||||
|
|
||||||
|
export function http<T>(options: CustomRequestOptions) {
|
||||||
|
// 1. 返回 Promise 对象
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
uni.request({
|
||||||
|
...options,
|
||||||
|
dataType: 'json',
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
responseType: 'json',
|
||||||
|
// #endif
|
||||||
|
// 响应成功
|
||||||
|
success: async (res) => {
|
||||||
|
const responseData = res.data as Partial<IResponse<T>>
|
||||||
|
const code = responseData?.code
|
||||||
|
|
||||||
|
// 检查是否是401错误(包括HTTP状态码401或业务码401)
|
||||||
|
const isTokenExpired = res.statusCode === 401 || code === ResultEnum.Unauthorized
|
||||||
|
|
||||||
|
if (isTokenExpired) {
|
||||||
|
const tokenStore = useTokenStore()
|
||||||
|
if (!isDoubleTokenMode) {
|
||||||
|
// 未启用双token策略,清理用户信息,跳转到登录页
|
||||||
|
tokenStore.logout()
|
||||||
|
toLoginPage()
|
||||||
|
return reject(createHttpError({
|
||||||
|
type: HttpErrorType.Auth,
|
||||||
|
code,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
message: getResponseMessage(responseData, '登录已过期,请重新登录'),
|
||||||
|
data: responseData?.data,
|
||||||
|
raw: res,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 无感刷新 token ----------- */
|
||||||
|
const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
|
||||||
|
// token 失效的,且有刷新 token 的,才放到请求队列里
|
||||||
|
if (refreshToken) {
|
||||||
|
taskQueue.push(() => {
|
||||||
|
resolve(http<T>(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有 refreshToken 且未在刷新中,发起刷新 token 请求
|
||||||
|
if (refreshToken && !refreshing) {
|
||||||
|
refreshing = true
|
||||||
|
try {
|
||||||
|
// 发起刷新 token 请求(使用 store 的 refreshToken 方法)
|
||||||
|
await tokenStore.refreshToken()
|
||||||
|
// 刷新 token 成功
|
||||||
|
refreshing = false
|
||||||
|
nextTick(() => {
|
||||||
|
// 关闭其他弹窗
|
||||||
|
uni.hideToast()
|
||||||
|
uni.showToast({
|
||||||
|
title: 'token 刷新成功',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 将任务队列的所有任务重新请求
|
||||||
|
taskQueue.forEach(task => task())
|
||||||
|
}
|
||||||
|
catch (refreshErr) {
|
||||||
|
console.error('刷新 token 失败:', refreshErr)
|
||||||
|
refreshing = false
|
||||||
|
// 刷新 token 失败,跳转到登录页
|
||||||
|
nextTick(() => {
|
||||||
|
// 关闭其他弹窗
|
||||||
|
uni.hideToast()
|
||||||
|
uni.showToast({
|
||||||
|
title: '登录已过期,请重新登录',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 清除用户信息
|
||||||
|
await tokenStore.logout()
|
||||||
|
// 跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
toLoginPage()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// 不管刷新 token 成功与否,都清空任务队列
|
||||||
|
taskQueue = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reject(createHttpError({
|
||||||
|
type: HttpErrorType.Auth,
|
||||||
|
code,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
message: getResponseMessage(responseData, '登录已过期,请重新登录'),
|
||||||
|
data: responseData?.data,
|
||||||
|
raw: res,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他成功状态(HTTP状态码200-299)
|
||||||
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
// 处理业务逻辑错误
|
||||||
|
if (!isSuccessResultCode(code as number)) {
|
||||||
|
const httpError = createHttpError({
|
||||||
|
type: HttpErrorType.Business,
|
||||||
|
code,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
message: getResponseMessage(responseData),
|
||||||
|
data: responseData?.data,
|
||||||
|
raw: responseData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!options.hideErrorToast) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: httpError.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return reject(httpError)
|
||||||
|
}
|
||||||
|
return resolve(responseData.data as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他错误
|
||||||
|
const httpError = createHttpError({
|
||||||
|
type: HttpErrorType.Http,
|
||||||
|
code,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
message: getResponseMessage(responseData, ShowMessage(res.statusCode)),
|
||||||
|
data: responseData?.data,
|
||||||
|
raw: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!options.hideErrorToast) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: httpError.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reject(httpError)
|
||||||
|
},
|
||||||
|
// 响应失败
|
||||||
|
fail(err) {
|
||||||
|
const httpError = createHttpError({
|
||||||
|
type: HttpErrorType.Network,
|
||||||
|
message: '网络错误,换个网络试试',
|
||||||
|
raw: err,
|
||||||
|
} satisfies HttpError)
|
||||||
|
|
||||||
|
if (!options.hideErrorToast) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: 'none',
|
||||||
|
title: httpError.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reject(httpError)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET 请求
|
||||||
|
* @param url 后台地址
|
||||||
|
* @param query 请求query参数
|
||||||
|
* @param header 请求头,默认为json格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
method: 'GET',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求
|
||||||
|
* @param url 后台地址
|
||||||
|
* @param data 请求body参数
|
||||||
|
* @param query 请求query参数,post请求也支持query,很多微信接口都需要
|
||||||
|
* @param header 请求头,默认为json格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
data,
|
||||||
|
method: 'POST',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* PUT 请求
|
||||||
|
*/
|
||||||
|
export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
data,
|
||||||
|
query,
|
||||||
|
method: 'PUT',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE 请求(无请求体,仅 query)
|
||||||
|
*/
|
||||||
|
export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
method: 'DELETE',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持与 axios 类似的API调用
|
||||||
|
http.get = httpGet
|
||||||
|
http.post = httpPost
|
||||||
|
http.put = httpPut
|
||||||
|
http.delete = httpDelete
|
||||||
|
|
||||||
|
// 支持与 alovaJS 类似的API调用
|
||||||
|
http.Get = httpGet
|
||||||
|
http.Post = httpPost
|
||||||
|
http.Put = httpPut
|
||||||
|
http.Delete = httpDelete
|
||||||
69
src/http/interceptor.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import type { CustomRequestOptions } from '@/http/types'
|
||||||
|
import { useTokenStore } from '@/store'
|
||||||
|
import { getEnvBaseUrl } from '@/utils'
|
||||||
|
import { stringifyQuery } from './tools/queryString'
|
||||||
|
|
||||||
|
// 请求基准地址
|
||||||
|
const baseUrl = getEnvBaseUrl()
|
||||||
|
|
||||||
|
// 拦截器配置
|
||||||
|
const httpInterceptor = {
|
||||||
|
// 拦截前触发
|
||||||
|
invoke(options: CustomRequestOptions) {
|
||||||
|
// 如果您使用了alova,则请把下面的代码放开注释
|
||||||
|
// alova 执行流程:alova beforeRequest --> 本拦截器 --> alova responded
|
||||||
|
// return options
|
||||||
|
|
||||||
|
// 非 alova 请求,正常执行
|
||||||
|
// 接口请求支持通过 query 参数配置 queryString
|
||||||
|
if (options.query) {
|
||||||
|
const queryStr = stringifyQuery(options.query)
|
||||||
|
if (options.url.includes('?')) {
|
||||||
|
options.url += `&${queryStr}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.url += `?${queryStr}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 非 http 开头需拼接地址
|
||||||
|
if (!options.url.startsWith('http')) {
|
||||||
|
// #ifdef H5
|
||||||
|
if (JSON.parse(import.meta.env.VITE_APP_PROXY_ENABLE)) {
|
||||||
|
// 自动拼接代理前缀
|
||||||
|
options.url = import.meta.env.VITE_APP_PROXY_PREFIX + options.url
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.url = baseUrl + options.url
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// 非H5正常拼接
|
||||||
|
// #ifndef H5
|
||||||
|
options.url = baseUrl + options.url
|
||||||
|
// #endif
|
||||||
|
// TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址
|
||||||
|
}
|
||||||
|
// 1. 请求超时
|
||||||
|
options.timeout = 60000 // 60s
|
||||||
|
// 2. (可选)添加小程序端请求头标识
|
||||||
|
options.header = {
|
||||||
|
...options.header,
|
||||||
|
}
|
||||||
|
// 3. 添加 token 请求头标识
|
||||||
|
const tokenStore = useTokenStore()
|
||||||
|
const token = tokenStore.updateNowTime().validToken
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
options.header.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestInterceptor = {
|
||||||
|
install() {
|
||||||
|
// 拦截 request 请求
|
||||||
|
uni.addInterceptor('request', httpInterceptor)
|
||||||
|
// 拦截 uploadFile 文件上传
|
||||||
|
uni.addInterceptor('uploadFile', httpInterceptor)
|
||||||
|
},
|
||||||
|
}
|
||||||
89
src/http/tools/enum.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import type { HttpError, IResponse } from '@/http/types'
|
||||||
|
|
||||||
|
export enum ResultEnum {
|
||||||
|
// 0和200当做成功都很普遍,这里直接兼容两者(PS:0和200通常都不会当做错误码,但是有的接口会返回0,有的接口会返回200)
|
||||||
|
Success0 = 0, // 成功
|
||||||
|
Success200 = 200, // 成功
|
||||||
|
Error = 400, // 错误
|
||||||
|
Unauthorized = 401, // 未授权
|
||||||
|
Forbidden = 403, // 禁止访问(原为forbidden)
|
||||||
|
NotFound = 404, // 未找到(原为notFound)
|
||||||
|
MethodNotAllowed = 405, // 方法不允许(原为methodNotAllowed)
|
||||||
|
RequestTimeout = 408, // 请求超时(原为requestTimeout)
|
||||||
|
InternalServerError = 500, // 服务器错误(原为internalServerError)
|
||||||
|
NotImplemented = 501, // 未实现(原为notImplemented)
|
||||||
|
BadGateway = 502, // 网关错误(原为badGateway)
|
||||||
|
ServiceUnavailable = 503, // 服务不可用(原为serviceUnavailable)
|
||||||
|
GatewayTimeout = 504, // 网关超时(原为gatewayTimeout)
|
||||||
|
HttpVersionNotSupported = 505, // HTTP版本不支持(原为httpVersionNotSupported)
|
||||||
|
}
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
JSON = 'application/json;charset=UTF-8',
|
||||||
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HttpErrorType {
|
||||||
|
Business = 'business',
|
||||||
|
Auth = 'auth',
|
||||||
|
Http = 'http',
|
||||||
|
Network = 'network',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSuccessResultCode(code: number): boolean {
|
||||||
|
return [ResultEnum.Success0, ResultEnum.Success200].includes(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResponseMessage(responseData: Partial<IResponse<any>> | undefined, fallback = '请求错误'): string {
|
||||||
|
return responseData?.msg || responseData?.message || fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createHttpError<T>(params: HttpError<T>): HttpError<T> {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 根据状态码,生成对应的错误信息
|
||||||
|
* @param {number|string} status 状态码
|
||||||
|
* @returns {string} 错误信息
|
||||||
|
*/
|
||||||
|
export function ShowMessage(status: number | string): string {
|
||||||
|
let message: string
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
message = '请求错误(400)'
|
||||||
|
break
|
||||||
|
case 401:
|
||||||
|
message = '未授权,请重新登录(401)'
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
message = '拒绝访问(403)'
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
message = '请求出错(404)'
|
||||||
|
break
|
||||||
|
case 408:
|
||||||
|
message = '请求超时(408)'
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
message = '服务器错误(500)'
|
||||||
|
break
|
||||||
|
case 501:
|
||||||
|
message = '服务未实现(501)'
|
||||||
|
break
|
||||||
|
case 502:
|
||||||
|
message = '网络错误(502)'
|
||||||
|
break
|
||||||
|
case 503:
|
||||||
|
message = '服务不可用(503)'
|
||||||
|
break
|
||||||
|
case 504:
|
||||||
|
message = '网络超时(504)'
|
||||||
|
break
|
||||||
|
case 505:
|
||||||
|
message = 'HTTP版本不受支持(505)'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
message = `连接出错(${status})!`
|
||||||
|
}
|
||||||
|
return `${message},请检查网络或联系管理员!`
|
||||||
|
}
|
||||||
29
src/http/tools/queryString.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 将对象序列化为URL查询字符串,用于替代第三方的 qs 库,节省宝贵的体积
|
||||||
|
* 支持基本类型值和数组,不支持嵌套对象
|
||||||
|
* @param obj 要序列化的对象
|
||||||
|
* @returns 序列化后的查询字符串
|
||||||
|
*/
|
||||||
|
export function stringifyQuery(obj: Record<string, any>): string {
|
||||||
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return Object.entries(obj)
|
||||||
|
.filter(([_, value]) => value !== undefined && value !== null)
|
||||||
|
.map(([key, value]) => {
|
||||||
|
// 对键进行编码
|
||||||
|
const encodedKey = encodeURIComponent(key)
|
||||||
|
|
||||||
|
// 处理数组类型
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
.filter(item => item !== undefined && item !== null)
|
||||||
|
.map(item => `${encodedKey}=${encodeURIComponent(item)}`)
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理基本类型
|
||||||
|
return `${encodedKey}=${encodeURIComponent(value)}`
|
||||||
|
})
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
53
src/http/types.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 在 uniapp 的 RequestOptions 和 IUniUploadFileOptions 基础上,添加自定义参数
|
||||||
|
*/
|
||||||
|
export type CustomRequestOptions = UniApp.RequestOptions & {
|
||||||
|
query?: Record<string, any>
|
||||||
|
/** 出错时是否隐藏错误提示 */
|
||||||
|
hideErrorToast?: boolean
|
||||||
|
} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
|
||||||
|
|
||||||
|
/** 主要提供给 openapi-ts-request 生成的代码使用 */
|
||||||
|
export type CustomRequestOptions_ = Omit<CustomRequestOptions, 'url'>
|
||||||
|
|
||||||
|
export interface HttpRequestResult<T> {
|
||||||
|
promise: Promise<T>
|
||||||
|
requestTask: UniApp.RequestTask
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpError<T = any> {
|
||||||
|
type: 'business' | 'auth' | 'http' | 'network'
|
||||||
|
code?: number
|
||||||
|
statusCode?: number
|
||||||
|
message: string
|
||||||
|
data?: T
|
||||||
|
raw?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用响应格式(兼容 msg + message 字段)
|
||||||
|
export type IResponse<T = any> = {
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
message: string
|
||||||
|
[key: string]: any // 允许额外属性
|
||||||
|
} | {
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
msg: string
|
||||||
|
[key: string]: any // 允许额外属性
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页请求参数
|
||||||
|
export interface PageParams {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应数据
|
||||||
|
export interface PageResult<T> {
|
||||||
|
list: T[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
}
|
||||||
30
src/http/vue-query.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { CustomRequestOptions } from '@/http/types'
|
||||||
|
import { http } from './http'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* openapi-ts-request 工具的 request 跨客户端适配方法
|
||||||
|
*/
|
||||||
|
export default function request<T extends { data?: any }>(
|
||||||
|
url: string,
|
||||||
|
options: Omit<CustomRequestOptions, 'url'> & {
|
||||||
|
params?: Record<string, unknown>
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const requestOptions = {
|
||||||
|
url,
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.params) {
|
||||||
|
requestOptions.query = requestOptions.params
|
||||||
|
delete requestOptions.params
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.headers) {
|
||||||
|
requestOptions.header = options.headers
|
||||||
|
delete requestOptions.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
return http<T['data']>(requestOptions)
|
||||||
|
}
|
||||||
3
src/layouts/default.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
19
src/main.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { requestInterceptor } from './http/interceptor'
|
||||||
|
import { routeInterceptor } from './router/interceptor'
|
||||||
|
|
||||||
|
import store from './store'
|
||||||
|
import '@/style/index.scss'
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
app.use(store)
|
||||||
|
app.use(routeInterceptor)
|
||||||
|
app.use(requestInterceptor)
|
||||||
|
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/pages-demo/hooks/useEcharts.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// APP 与 H5 引入 echarts
|
||||||
|
// #ifdef APP-PLUS || H5
|
||||||
|
import * as echartsAppOrH5 from "echarts";
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
// 小程序中引入 echarts
|
||||||
|
// #ifndef APP-PLUS || H5
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const echarts = require("../uni_modules/lime-echart/static/echarts.min");
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// echarts 图表 Hooks
|
||||||
|
export function useEcharts(options: any): any {
|
||||||
|
// echarts 图实例
|
||||||
|
const chartRef = ref<any>(null);
|
||||||
|
// echarts 图绘制函数
|
||||||
|
const draw = () => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
// #ifndef APP-PLUS || H5
|
||||||
|
const chart = await chartRef.value.init(echarts);
|
||||||
|
chart.setOption(options.value);
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-PLUS || H5
|
||||||
|
const chartAPP = await chartRef.value.init(echartsAppOrH5);
|
||||||
|
chartAPP.setOption(options.value);
|
||||||
|
// #endif
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
// 这里就封装巧妙之处,如果是对象,那么需要重命名,而是是数组,无需重命名
|
||||||
|
return [chartRef, options, draw];
|
||||||
|
}
|
||||||
87
src/pages-demo/lime-echarts/index.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<view class="chart-page">
|
||||||
|
<view class="chart-box">
|
||||||
|
<l-echart ref="chartRef" @finished="draw" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { EChartsOption } from 'echarts'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useEcharts } from '../hooks/useEcharts'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: { navigationBarTitleText: 'lime-echart 图表' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = ref<EChartsOption>({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '邮件营销',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
data: [120, 132, 101, 134, 90, 230, 210],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '联盟广告',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
data: [220, 182, 191, 234, 290, 330, 310],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '视频广告',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
data: [150, 232, 201, 154, 190, 330, 410],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '直接访问',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
data: [320, 332, 301, 334, 390, 330, 320],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '搜索引擎',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [chartRef, , draw] = useEcharts(options)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chart-page {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 600rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
160
src/pages-demo/lime-echarts/index2.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { useEcharts } from '../hooks/useEcharts'
|
||||||
|
|
||||||
|
// 折线图
|
||||||
|
export function useLineEcharts(): any {
|
||||||
|
// 折线图配置项数据
|
||||||
|
// 每次编写新类型的图表时,只需要复制一份修改这里的配置即可
|
||||||
|
// 应用 这个类型的图表时,只需在 .vue 文件中通过 set 修复数据即可,或者简单的样式修改,如果样式过于复杂,可以重新建立一个 linkHookB 等等
|
||||||
|
const option = ref({
|
||||||
|
xAxis: {
|
||||||
|
data: [12, 13, 10, 13, 9, 23, 21, 32, 12, 15, 13, 10],
|
||||||
|
boundaryGap: false,
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#0C4787',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 5,
|
||||||
|
right: 16,
|
||||||
|
bottom: 5,
|
||||||
|
top: 20,
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
},
|
||||||
|
padding: [5, 10],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#0C4787',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
smooth: true,
|
||||||
|
type: 'line',
|
||||||
|
data: [12, 13, 10, 13, 9, 23, 21, 32, 12, 15, 13, 10],
|
||||||
|
animationDuration: 2800,
|
||||||
|
animationEasing: 'cubicInOut',
|
||||||
|
symbol: 'circle',
|
||||||
|
color: '#fed42b',
|
||||||
|
symbolSize: 8,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#fed42b', // 改变折线颜色
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: '#fed42b', // 0% 处的颜色
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(254, 212, 43, 0.1)', // 100% 处的颜色
|
||||||
|
},
|
||||||
|
],
|
||||||
|
global: false, // 缺省为 false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return useEcharts(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 柱状图
|
||||||
|
export function useBarEcharts(): any {
|
||||||
|
const option = ref({
|
||||||
|
xAxis: {
|
||||||
|
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#0C4787',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 5,
|
||||||
|
right: 16,
|
||||||
|
bottom: 5,
|
||||||
|
top: 20,
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
},
|
||||||
|
padding: [5, 10],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#0C4787',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: true,
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
type: 'bar',
|
||||||
|
data: [120, 200, 150, 80, 70, 110, 130],
|
||||||
|
animationDuration: 2800,
|
||||||
|
animationEasing: 'cubicInOut',
|
||||||
|
color: '#fed42b',
|
||||||
|
barWidth: 10,
|
||||||
|
itemStyle: {
|
||||||
|
barBorderRadius: [3, 3, 0, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return useEcharts(option)
|
||||||
|
}
|
||||||
91
src/pages-demo/lime-echarts/index2.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<view>折线图1</view>
|
||||||
|
<view style="width: 100%; height: 200px">
|
||||||
|
<l-echart ref="lineChartRef" />
|
||||||
|
</view>
|
||||||
|
<view>折线图2</view>
|
||||||
|
<view style="width: 100%; height: 200px">
|
||||||
|
<l-echart ref="lineChartRef2" />
|
||||||
|
</view>
|
||||||
|
<view>柱状图</view>
|
||||||
|
<view style="width: 100%; height: 200px">
|
||||||
|
<l-echart ref="barChartRef" />
|
||||||
|
</view>
|
||||||
|
<button @click="changeLineChartData">
|
||||||
|
切换折线图1数据
|
||||||
|
</button>
|
||||||
|
<button @click="changeBarChartData">
|
||||||
|
改变柱状图样式
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { set } from 'lodash-es'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useBarEcharts, useLineEcharts } from './index2'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: { navigationBarTitleText: 'lime-echart 图表2' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const [lineChartRef, lineOption, lineDraw] = useLineEcharts()
|
||||||
|
const [lineChartRef2, lineOption2, lineDraw2] = useLineEcharts()
|
||||||
|
const [barChartRef, barOption, barDraw] = useBarEcharts()
|
||||||
|
|
||||||
|
// 初始化 折线图表1
|
||||||
|
function initLineChart() {
|
||||||
|
set(
|
||||||
|
lineOption.value,
|
||||||
|
'series.data',
|
||||||
|
[120, 132, 101, 134, 90, 230, 210, 220, 182, 191, 234, 290],
|
||||||
|
)
|
||||||
|
lineDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 折线图表2
|
||||||
|
function initLineChart2() {
|
||||||
|
set(
|
||||||
|
lineOption2.value,
|
||||||
|
'series.data',
|
||||||
|
[220, 182, 191, 234, 290, 330, 310, 320, 302, 301, 334, 390],
|
||||||
|
)
|
||||||
|
lineDraw2()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 柱状图表
|
||||||
|
function initBarChart() {
|
||||||
|
set(barOption.value, 'series.data', [120, 200, 150, 80, 70, 110, 130])
|
||||||
|
barDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换折线图数据
|
||||||
|
function changeLineChartData() {
|
||||||
|
set(
|
||||||
|
lineOption.value,
|
||||||
|
'series.data',
|
||||||
|
[324, 332, 301, 334, 390, 330, 320, 302, 301, 334, 390, 330],
|
||||||
|
)
|
||||||
|
lineDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改变柱状图样式
|
||||||
|
function changeBarChartData() {
|
||||||
|
set(barOption.value, 'series.color', '#5677fc')
|
||||||
|
barDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initLineChart()
|
||||||
|
initLineChart2()
|
||||||
|
initBarChart()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 暗黑模式 start */
|
||||||
|
.wot-theme-dark {
|
||||||
|
color: #f5f5f5;
|
||||||
|
background: #222;
|
||||||
|
--theme-bg-color: #222;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
706
src/pages-demo/ucharts/data.json
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
{
|
||||||
|
"localdata": [
|
||||||
|
{ "value": 35, "text": "2016", "group": "目标值" },
|
||||||
|
{ "value": 18, "text": "2016", "group": "完成量" },
|
||||||
|
{ "value": 36, "text": "2017", "group": "目标值" },
|
||||||
|
{ "value": 27, "text": "2017", "group": "完成量" },
|
||||||
|
{ "value": 31, "text": "2018", "group": "目标值" },
|
||||||
|
{ "value": 21, "text": "2018", "group": "完成量" },
|
||||||
|
{ "value": 33, "text": "2019", "group": "目标值" },
|
||||||
|
{ "value": 24, "text": "2019", "group": "完成量" },
|
||||||
|
{ "value": 13, "text": "2020", "group": "目标值" },
|
||||||
|
{ "value": 6, "text": "2020", "group": "完成量" },
|
||||||
|
{ "value": 34, "text": "2021", "group": "目标值" },
|
||||||
|
{ "value": 28, "text": "2021", "group": "完成量" }
|
||||||
|
],
|
||||||
|
"localdataB": [
|
||||||
|
{ "value": 50, "text": "一班" },
|
||||||
|
{ "value": 30, "text": "二班" },
|
||||||
|
{ "value": 20, "text": "三班" },
|
||||||
|
{ "value": 18, "text": "四班" },
|
||||||
|
{ "value": 8, "text": "五班" }
|
||||||
|
],
|
||||||
|
"TLine": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "时间轴1",
|
||||||
|
"data": [
|
||||||
|
[10000, 55],
|
||||||
|
[30000, 25],
|
||||||
|
[50000, 55],
|
||||||
|
[70000, 25],
|
||||||
|
[90000, 55]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "时间轴2",
|
||||||
|
"data": [
|
||||||
|
[0, 25],
|
||||||
|
[20000, 55],
|
||||||
|
[40000, 25],
|
||||||
|
[60000, 55],
|
||||||
|
[80000, 25]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "时间轴3",
|
||||||
|
"data": [
|
||||||
|
[0, 55],
|
||||||
|
[15000, 25],
|
||||||
|
[30000, 55],
|
||||||
|
[45000, 25],
|
||||||
|
[60000, 55]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Scatter": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "散点一",
|
||||||
|
"data": [
|
||||||
|
[10.0, 8.04],
|
||||||
|
[8.07, 6.95],
|
||||||
|
[13.0, 7.58],
|
||||||
|
[9.05, 8.81],
|
||||||
|
[11.0, 8.33],
|
||||||
|
[14.0, 7.66],
|
||||||
|
[13.4, 6.81],
|
||||||
|
[10.0, 6.33],
|
||||||
|
[14.0, 8.96],
|
||||||
|
[12.5, 6.82]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "散点二",
|
||||||
|
"data": [
|
||||||
|
[9.15, 7.2],
|
||||||
|
[11.5, 7.2],
|
||||||
|
[3.03, 4.23],
|
||||||
|
[12.2, 7.83],
|
||||||
|
[2.02, 4.47],
|
||||||
|
[1.05, 3.33],
|
||||||
|
[4.05, 4.96],
|
||||||
|
[6.03, 7.24],
|
||||||
|
[12.0, 6.26],
|
||||||
|
[12.0, 8.84],
|
||||||
|
[7.08, 5.82],
|
||||||
|
[5.02, 5.68]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Bubble": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "气泡一",
|
||||||
|
"data": [
|
||||||
|
[95, 95, 23, "标题1"],
|
||||||
|
[30, 55, 33, "标题2"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "气泡二",
|
||||||
|
"data": [
|
||||||
|
[130, 30, 30, "标题3"],
|
||||||
|
[200, 90, 40, "标题4"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Column": {
|
||||||
|
"categories": ["2016", "2017", "2018", "2019", "2020", "2021"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "目标值",
|
||||||
|
"data": [35, 36, 31, 33, 13, 34]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "完成量",
|
||||||
|
"data": [18, 27, 21, 24, 6, 28]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ColumnA": {
|
||||||
|
"categories": ["2016", "2017", "2018", "2019", "2020", "2021"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "成交量1",
|
||||||
|
"data": [15, { "value": 20, "color": "#f04864" }, 45, 37, 43, 34]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "成交量2",
|
||||||
|
"data": [30, { "value": 40, "color": "#facc14" }, 25, 14, 34, 18]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Mix": {
|
||||||
|
"categories": ["2016", "2017", "2018", "2019", "2020", "2021"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "曲面",
|
||||||
|
"data": [70, 50, 85, 130, 64, 88],
|
||||||
|
"type": "area",
|
||||||
|
"style": "curve"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "柱1",
|
||||||
|
"index": 1,
|
||||||
|
"data": [40, { "value": 30, "color": "#f04864" }, 55, 110, 24, 58],
|
||||||
|
"type": "column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "柱2",
|
||||||
|
"index": 1,
|
||||||
|
"data": [50, 20, 75, 60, 34, 38],
|
||||||
|
"type": "column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "曲线",
|
||||||
|
"data": [70, 50, 85, 130, 64, 88],
|
||||||
|
"type": "line",
|
||||||
|
"style": "curve",
|
||||||
|
"color": "#1890ff",
|
||||||
|
"disableLegend": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "折线",
|
||||||
|
"data": [120, 140, 105, 170, 95, 160],
|
||||||
|
"type": "line",
|
||||||
|
"color": "#2fc25b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "点",
|
||||||
|
"index": 2,
|
||||||
|
"data": [100, 80, 125, 150, 112, 132],
|
||||||
|
"type": "point",
|
||||||
|
"color": "#f04864"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Line": {
|
||||||
|
"categories": ["2016", "2017", "2018", "2019", "2020", "2021"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "成交量A",
|
||||||
|
"data": [35, 8, 25, 37, 4, 20]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "成交量B",
|
||||||
|
"data": [70, 40, 65, 100, 44, 68]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "成交量C",
|
||||||
|
"data": [100, 80, 95, 150, 112, 132]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Pie": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "一班",
|
||||||
|
"data": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二班",
|
||||||
|
"data": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "三班",
|
||||||
|
"data": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "四班",
|
||||||
|
"data": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "五班",
|
||||||
|
"data": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PieA": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "一班",
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二班",
|
||||||
|
"value": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "三班",
|
||||||
|
"value": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "四班",
|
||||||
|
"value": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "五班",
|
||||||
|
"value": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Mount": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "一班",
|
||||||
|
"value": 82
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二班",
|
||||||
|
"value": 63
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "三班",
|
||||||
|
"value": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "四班",
|
||||||
|
"value": 65
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "五班",
|
||||||
|
"value": 79
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Radar": {
|
||||||
|
"categories": ["维度1", "维度2", "维度3", "维度4", "维度5", "维度6"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "成交量1",
|
||||||
|
"data": [90, 110, 165, 195, 187, 172]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "成交量2",
|
||||||
|
"data": [190, 210, 105, 35, 27, 102]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Arcbar1": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "正确率",
|
||||||
|
"data": 0.8,
|
||||||
|
"color": "#2fc25b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Arcbar2": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "一班",
|
||||||
|
"data": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二班",
|
||||||
|
"data": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "三班",
|
||||||
|
"data": 0.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "四班",
|
||||||
|
"data": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "五班",
|
||||||
|
"data": 0.15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Gauge": {
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"value": 0.2,
|
||||||
|
"color": "#1890ff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 0.8,
|
||||||
|
"color": "#2fc25b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 1,
|
||||||
|
"color": "#f04864"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "完成率",
|
||||||
|
"data": 0.66
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Candle": {
|
||||||
|
"categories": [
|
||||||
|
"2020/1/24",
|
||||||
|
"2020/1/25",
|
||||||
|
"2020/1/28",
|
||||||
|
"2020/1/29",
|
||||||
|
"2020/1/30",
|
||||||
|
"2020/1/31",
|
||||||
|
"2020/2/1",
|
||||||
|
"2020/2/4",
|
||||||
|
"2020/2/5",
|
||||||
|
"2020/2/6",
|
||||||
|
"2020/2/7",
|
||||||
|
"2020/2/8",
|
||||||
|
"2020/2/18",
|
||||||
|
"2020/2/19",
|
||||||
|
"2020/2/20",
|
||||||
|
"2020/2/21",
|
||||||
|
"2020/2/22",
|
||||||
|
"2020/2/25",
|
||||||
|
"2020/2/26",
|
||||||
|
"2020/2/27",
|
||||||
|
"2020/2/28",
|
||||||
|
"2020/3/1",
|
||||||
|
"2020/3/4",
|
||||||
|
"2020/3/5",
|
||||||
|
"2020/3/6",
|
||||||
|
"2020/3/7",
|
||||||
|
"2020/3/8",
|
||||||
|
"2020/3/11",
|
||||||
|
"2020/3/12",
|
||||||
|
"2020/3/13",
|
||||||
|
"2020/3/14",
|
||||||
|
"2020/3/15",
|
||||||
|
"2020/3/18",
|
||||||
|
"2020/3/19",
|
||||||
|
"2020/3/20",
|
||||||
|
"2020/3/21",
|
||||||
|
"2020/3/22",
|
||||||
|
"2020/3/25",
|
||||||
|
"2020/3/26",
|
||||||
|
"2020/3/27",
|
||||||
|
"2020/3/28",
|
||||||
|
"2020/3/29",
|
||||||
|
"2020/4/1",
|
||||||
|
"2020/4/2",
|
||||||
|
"2020/4/3",
|
||||||
|
"2020/4/8",
|
||||||
|
"2020/4/9",
|
||||||
|
"2020/4/10",
|
||||||
|
"2020/4/11",
|
||||||
|
"2020/4/12",
|
||||||
|
"2020/4/15",
|
||||||
|
"2020/4/16",
|
||||||
|
"2020/4/17",
|
||||||
|
"2020/4/18",
|
||||||
|
"2020/4/19",
|
||||||
|
"2020/4/22",
|
||||||
|
"2020/4/23",
|
||||||
|
"2020/4/24",
|
||||||
|
"2020/4/25",
|
||||||
|
"2020/4/26",
|
||||||
|
"2020/5/2",
|
||||||
|
"2020/5/3",
|
||||||
|
"2020/5/6",
|
||||||
|
"2020/5/7",
|
||||||
|
"2020/5/8",
|
||||||
|
"2020/5/9",
|
||||||
|
"2020/5/10",
|
||||||
|
"2020/5/13",
|
||||||
|
"2020/5/14",
|
||||||
|
"2020/5/15",
|
||||||
|
"2020/5/16",
|
||||||
|
"2020/5/17",
|
||||||
|
"2020/5/20",
|
||||||
|
"2020/5/21",
|
||||||
|
"2020/5/22",
|
||||||
|
"2020/5/23",
|
||||||
|
"2020/5/24",
|
||||||
|
"2020/5/27",
|
||||||
|
"2020/5/28",
|
||||||
|
"2020/5/29",
|
||||||
|
"2020/5/30",
|
||||||
|
"2020/5/31",
|
||||||
|
"2020/6/3",
|
||||||
|
"2020/6/4",
|
||||||
|
"2020/6/5",
|
||||||
|
"2020/6/6",
|
||||||
|
"2020/6/7",
|
||||||
|
"2020/6/13"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "上证指数",
|
||||||
|
"data": [
|
||||||
|
[2320.26, 2302.6, 2287.3, 2362.94],
|
||||||
|
[2300, 2291.3, 2288.26, 2308.38],
|
||||||
|
[2295.35, 2346.5, 2295.35, 2346.92],
|
||||||
|
[2347.22, 2358.98, 2337.35, 2363.8],
|
||||||
|
[2360.75, 2382.48, 2347.89, 2383.76],
|
||||||
|
[2383.43, 2385.42, 2371.23, 2391.82],
|
||||||
|
[2377.41, 2419.02, 2369.57, 2421.15],
|
||||||
|
[2425.92, 2428.15, 2417.58, 2440.38],
|
||||||
|
[2411, 2433.13, 2403.3, 2437.42],
|
||||||
|
[2432.68, 2434.48, 2427.7, 2441.73],
|
||||||
|
[2430.69, 2418.53, 2394.22, 2433.89],
|
||||||
|
[2416.62, 2432.4, 2414.4, 2443.03],
|
||||||
|
[2441.91, 2421.56, 2415.43, 2444.8],
|
||||||
|
[2420.26, 2382.91, 2373.53, 2427.07],
|
||||||
|
[2383.49, 2397.18, 2370.61, 2397.94],
|
||||||
|
[2378.82, 2325.95, 2309.17, 2378.82],
|
||||||
|
[2322.94, 2314.16, 2308.76, 2330.88],
|
||||||
|
[2320.62, 2325.82, 2315.01, 2338.78],
|
||||||
|
[2313.74, 2293.34, 2289.89, 2340.71],
|
||||||
|
[2297.77, 2313.22, 2292.03, 2324.63],
|
||||||
|
[2322.32, 2365.59, 2308.92, 2366.16],
|
||||||
|
[2364.54, 2359.51, 2330.86, 2369.65],
|
||||||
|
[2332.08, 2273.4, 2259.25, 2333.54],
|
||||||
|
[2274.81, 2326.31, 2270.1, 2328.14],
|
||||||
|
[2333.61, 2347.18, 2321.6, 2351.44],
|
||||||
|
[2340.44, 2324.29, 2304.27, 2352.02],
|
||||||
|
[2326.42, 2318.61, 2314.59, 2333.67],
|
||||||
|
[2314.68, 2310.59, 2296.58, 2320.96],
|
||||||
|
[2309.16, 2286.6, 2264.83, 2333.29],
|
||||||
|
[2282.17, 2263.97, 2253.25, 2286.33],
|
||||||
|
[2255.77, 2270.28, 2253.31, 2276.22],
|
||||||
|
[2269.31, 2278.4, 2250, 2312.08],
|
||||||
|
[2267.29, 2240.02, 2239.21, 2276.05],
|
||||||
|
[2244.26, 2257.43, 2232.02, 2261.31],
|
||||||
|
[2257.74, 2317.37, 2257.42, 2317.86],
|
||||||
|
[2318.21, 2324.24, 2311.6, 2330.81],
|
||||||
|
[2321.4, 2328.28, 2314.97, 2332],
|
||||||
|
[2334.74, 2326.72, 2319.91, 2344.89],
|
||||||
|
[2318.58, 2297.67, 2281.12, 2319.99],
|
||||||
|
[2299.38, 2301.26, 2289, 2323.48],
|
||||||
|
[2273.55, 2236.3, 2232.91, 2273.55],
|
||||||
|
[2238.49, 2236.62, 2228.81, 2246.87],
|
||||||
|
[2229.46, 2234.4, 2227.31, 2243.95],
|
||||||
|
[2234.9, 2227.74, 2220.44, 2253.42],
|
||||||
|
[2232.69, 2225.29, 2217.25, 2241.34],
|
||||||
|
[2196.24, 2211.59, 2180.67, 2212.59],
|
||||||
|
[2215.47, 2225.77, 2215.47, 2234.73],
|
||||||
|
[2224.93, 2226.13, 2212.56, 2233.04],
|
||||||
|
[2236.98, 2219.55, 2217.26, 2242.48],
|
||||||
|
[2218.09, 2206.78, 2204.44, 2226.26],
|
||||||
|
[2199.91, 2181.94, 2177.39, 2204.99],
|
||||||
|
[2169.63, 2194.85, 2165.78, 2196.43],
|
||||||
|
[2195.03, 2193.8, 2178.47, 2197.51],
|
||||||
|
[2181.82, 2197.6, 2175.44, 2206.03],
|
||||||
|
[2201.12, 2244.64, 2200.58, 2250.11],
|
||||||
|
[2236.4, 2242.17, 2232.26, 2245.12],
|
||||||
|
[2242.62, 2184.54, 2182.81, 2242.62],
|
||||||
|
[2187.35, 2218.32, 2184.11, 2226.12],
|
||||||
|
[2213.19, 2199.31, 2191.85, 2224.63],
|
||||||
|
[2203.89, 2177.91, 2173.86, 2210.58],
|
||||||
|
[2170.78, 2174.12, 2161.14, 2179.65],
|
||||||
|
[2179.05, 2205.5, 2179.05, 2222.81],
|
||||||
|
[2212.5, 2231.17, 2212.5, 2236.07],
|
||||||
|
[2227.86, 2235.57, 2219.44, 2240.26],
|
||||||
|
[2242.39, 2246.3, 2235.42, 2255.21],
|
||||||
|
[2246.96, 2232.97, 2221.38, 2247.86],
|
||||||
|
[2228.82, 2246.83, 2225.81, 2247.67],
|
||||||
|
[2247.68, 2241.92, 2231.36, 2250.85],
|
||||||
|
[2238.9, 2217.01, 2205.87, 2239.93],
|
||||||
|
[2217.09, 2224.8, 2213.58, 2225.19],
|
||||||
|
[2221.34, 2251.81, 2210.77, 2252.87],
|
||||||
|
[2249.81, 2282.87, 2248.41, 2288.09],
|
||||||
|
[2286.33, 2299.99, 2281.9, 2309.39],
|
||||||
|
[2297.11, 2305.11, 2290.12, 2305.3],
|
||||||
|
[2303.75, 2302.4, 2292.43, 2314.18],
|
||||||
|
[2293.81, 2275.67, 2274.1, 2304.95],
|
||||||
|
[2281.45, 2288.53, 2270.25, 2292.59],
|
||||||
|
[2286.66, 2293.08, 2283.94, 2301.7],
|
||||||
|
[2293.4, 2321.32, 2281.47, 2322.1],
|
||||||
|
[2323.54, 2324.02, 2321.17, 2334.33],
|
||||||
|
[2316.25, 2317.75, 2310.49, 2325.72],
|
||||||
|
[2320.74, 2300.59, 2299.37, 2325.53],
|
||||||
|
[2300.21, 2299.25, 2294.11, 2313.43],
|
||||||
|
[2297.1, 2272.42, 2264.76, 2297.1],
|
||||||
|
[2270.71, 2270.93, 2260.87, 2276.86],
|
||||||
|
[2264.43, 2242.11, 2240.07, 2266.69],
|
||||||
|
[2242.26, 2210.9, 2205.07, 2250.63],
|
||||||
|
[2190.1, 2148.35, 2126.22, 2190.1]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CandleColumn": {
|
||||||
|
"categories": [
|
||||||
|
"2020/1/24",
|
||||||
|
"2020/1/25",
|
||||||
|
"2020/1/28",
|
||||||
|
"2020/1/29",
|
||||||
|
"2020/1/30",
|
||||||
|
"2020/1/31",
|
||||||
|
"2020/2/1",
|
||||||
|
"2020/2/4",
|
||||||
|
"2020/2/5",
|
||||||
|
"2020/2/6",
|
||||||
|
"2020/2/7",
|
||||||
|
"2020/2/8",
|
||||||
|
"2020/2/18",
|
||||||
|
"2020/2/19",
|
||||||
|
"2020/2/20",
|
||||||
|
"2020/2/21",
|
||||||
|
"2020/2/22",
|
||||||
|
"2020/2/25",
|
||||||
|
"2020/2/26",
|
||||||
|
"2020/2/27",
|
||||||
|
"2020/2/28",
|
||||||
|
"2020/3/1",
|
||||||
|
"2020/3/4",
|
||||||
|
"2020/3/5",
|
||||||
|
"2020/3/6",
|
||||||
|
"2020/3/7",
|
||||||
|
"2020/3/8",
|
||||||
|
"2020/3/11",
|
||||||
|
"2020/3/12",
|
||||||
|
"2020/3/13",
|
||||||
|
"2020/3/14",
|
||||||
|
"2020/3/15",
|
||||||
|
"2020/3/18",
|
||||||
|
"2020/3/19",
|
||||||
|
"2020/3/20",
|
||||||
|
"2020/3/21",
|
||||||
|
"2020/3/22",
|
||||||
|
"2020/3/25",
|
||||||
|
"2020/3/26",
|
||||||
|
"2020/3/27",
|
||||||
|
"2020/3/28",
|
||||||
|
"2020/3/29",
|
||||||
|
"2020/4/1",
|
||||||
|
"2020/4/2",
|
||||||
|
"2020/4/3",
|
||||||
|
"2020/4/8",
|
||||||
|
"2020/4/9",
|
||||||
|
"2020/4/10",
|
||||||
|
"2020/4/11",
|
||||||
|
"2020/4/12",
|
||||||
|
"2020/4/15",
|
||||||
|
"2020/4/16",
|
||||||
|
"2020/4/17",
|
||||||
|
"2020/4/18",
|
||||||
|
"2020/4/19",
|
||||||
|
"2020/4/22",
|
||||||
|
"2020/4/23",
|
||||||
|
"2020/4/24",
|
||||||
|
"2020/4/25",
|
||||||
|
"2020/4/26",
|
||||||
|
"2020/5/2",
|
||||||
|
"2020/5/3",
|
||||||
|
"2020/5/6",
|
||||||
|
"2020/5/7",
|
||||||
|
"2020/5/8",
|
||||||
|
"2020/5/9",
|
||||||
|
"2020/5/10",
|
||||||
|
"2020/5/13",
|
||||||
|
"2020/5/14",
|
||||||
|
"2020/5/15",
|
||||||
|
"2020/5/16",
|
||||||
|
"2020/5/17",
|
||||||
|
"2020/5/20",
|
||||||
|
"2020/5/21",
|
||||||
|
"2020/5/22",
|
||||||
|
"2020/5/23",
|
||||||
|
"2020/5/24",
|
||||||
|
"2020/5/27",
|
||||||
|
"2020/5/28",
|
||||||
|
"2020/5/29",
|
||||||
|
"2020/5/30",
|
||||||
|
"2020/5/31",
|
||||||
|
"2020/6/3",
|
||||||
|
"2020/6/4",
|
||||||
|
"2020/6/5",
|
||||||
|
"2020/6/6",
|
||||||
|
"2020/6/7",
|
||||||
|
"2020/6/13"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "成交量1",
|
||||||
|
"data": [
|
||||||
|
15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20,
|
||||||
|
45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37,
|
||||||
|
43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15,
|
||||||
|
20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45,
|
||||||
|
37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43, 15, 20, 45, 37, 43,
|
||||||
|
15, 20, 45
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Word": {
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "跨全端图表",
|
||||||
|
"textSize": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "微信小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "支付宝小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "百度小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QQ小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "头条小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "抖音小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "360小程序",
|
||||||
|
"textSize": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "跨全端",
|
||||||
|
"textSize": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/pages-demo/ucharts/index.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<view class="content">
|
||||||
|
<qiun-title-bar title="tooltip提示窗format" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="column"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:chart-data="chartsDataLine1"
|
||||||
|
tooltip-format="tooltipDemo1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<qiun-title-bar title="图例的format" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:eopts="{ legend: { format: 'legendFormat' } }"
|
||||||
|
:chart-data="chartsDataColumn2"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<qiun-title-bar title="Y轴format方式1" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:eopts="{ yAxis: { axisLabel: { format: 'yAxisFormatDemo' } } }"
|
||||||
|
:chart-data="chartsDataLine1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<qiun-title-bar title="Y轴format方式2" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:eopts="{ yAxis: { axisLabel: { formatter: '{value} 元' } } }"
|
||||||
|
:chart-data="chartsDataLine1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<qiun-title-bar title="series数据点format方法1" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:eopts="{ seriesTemplate: { label: { format: 'seriesFormatDemo' } } }"
|
||||||
|
:chart-data="chartsDataLine1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<qiun-title-bar title="series数据点format方法2" />
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:echarts-h5="true"
|
||||||
|
:echarts-app="true"
|
||||||
|
:eopts="{ seriesTemplate: { label: { formatter: '{b}年{c}元' } } }"
|
||||||
|
:chart-data="chartsDataLine1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import demodata from './data.json'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: { navigationBarTitleText: 'ucharts 图表' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartsDataLine1 = ref({})
|
||||||
|
const chartsDataColumn2 = ref({})
|
||||||
|
|
||||||
|
function getServerData() {
|
||||||
|
setTimeout(() => {
|
||||||
|
chartsDataLine1.value = JSON.parse(JSON.stringify(demodata.Line))
|
||||||
|
chartsDataColumn2.value = JSON.parse(JSON.stringify(demodata.Column))
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getServerData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
src/pages/about/about.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '关于',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="mt-10 text-center text-green-500">
|
||||||
|
关于页面
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
20
src/pages/auth/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# 登录页
|
||||||
|
需要输入账号、密码/验证码的登录页。
|
||||||
|
|
||||||
|
## 适用性
|
||||||
|
|
||||||
|
本页面主要用于 `h5` 和 `APP`。
|
||||||
|
|
||||||
|
小程序通常有平台的登录方式 `uni.login` 通常用不到登录页,所以不适用于 `小程序`。(即默认情况下,小程序环境是不会走登录拦截逻辑的。)
|
||||||
|
|
||||||
|
但是如果您的小程序也需要现实的 `登录页` 那也是可以使用的。
|
||||||
|
|
||||||
|
在 `src/router/config.ts` 中有一个变量 `LOGIN_PAGE_ENABLE_IN_MP` 来控制是否在小程序中使用 `H5的登录页`。
|
||||||
|
|
||||||
|
更多信息请看 `src/router` 文件夹的内容。
|
||||||
|
|
||||||
|
## 登录跳转
|
||||||
|
|
||||||
|
目前登录的跳转逻辑主要在 `src/router/interceptor.ts` 和 `src/pages/login/login.vue` 里面,默认会在登录后自动重定向到来源/配置的页面。
|
||||||
|
|
||||||
|
如果与您的业务不符,您可以自行修改。
|
||||||
44
src/pages/auth/login.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useTokenStore } from '@/store/token'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '登录',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokenStore = useTokenStore()
|
||||||
|
async function doLogin() {
|
||||||
|
if (tokenStore.hasLogin) {
|
||||||
|
uni.navigateBack()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 调用登录接口
|
||||||
|
await tokenStore.login({
|
||||||
|
username: '菲鸽',
|
||||||
|
password: '123456',
|
||||||
|
})
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log('登录失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="login">
|
||||||
|
<!-- 本页面是非MP的登录页,主要用于 h5 和 APP -->
|
||||||
|
<view class="text-center">
|
||||||
|
登录页
|
||||||
|
</view>
|
||||||
|
<button class="mt-4 w-40 text-center" @click="doLogin">
|
||||||
|
点击模拟登录
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
//
|
||||||
|
</style>
|
||||||
34
src/pages/auth/register.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { LOGIN_PAGE } from '@/router/config'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '注册',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function doRegister() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '注册成功',
|
||||||
|
})
|
||||||
|
// 注册成功后跳转到登录页
|
||||||
|
uni.navigateTo({
|
||||||
|
url: LOGIN_PAGE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="login">
|
||||||
|
<view class="text-center">
|
||||||
|
注册页
|
||||||
|
</view>
|
||||||
|
<button class="mt-4 w-40 text-center" @click="doRegister">
|
||||||
|
点击模拟注册
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
//
|
||||||
|
</style>
|
||||||
53
src/pages/index/index.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'Home',
|
||||||
|
})
|
||||||
|
definePage({
|
||||||
|
// 使用 type: "home" 属性设置首页,其他页面不需要设置,默认为page
|
||||||
|
type: 'home',
|
||||||
|
style: {
|
||||||
|
// 'custom' 表示开启自定义导航栏,默认 'default'
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
navigationBarTitleText: '首页',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const description = ref(
|
||||||
|
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite5 + UnoCss + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。',
|
||||||
|
)
|
||||||
|
console.log('index/index 首页打印了')
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
console.log('测试 uni API 自动引入: onLoad')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="bg-white px-4 pt-safe">
|
||||||
|
<view class="mt-10">
|
||||||
|
<image src="/static/logo.svg" alt="" class="mx-auto block h-28 w-28" />
|
||||||
|
</view>
|
||||||
|
<view class="mt-4 text-center text-4xl text-[#d14328]">
|
||||||
|
unibest
|
||||||
|
</view>
|
||||||
|
<view class="mb-8 mt-2 text-center text-2xl">
|
||||||
|
最好用的 uniapp 开发模板
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="m-auto mb-2 max-w-100 text-justify indent text-4">
|
||||||
|
{{ description }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-4 text-center">
|
||||||
|
作者:
|
||||||
|
<text class="text-green-500">
|
||||||
|
菲鸽
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="mt-4 text-center">
|
||||||
|
官网地址:
|
||||||
|
<text class="text-green-500">
|
||||||
|
https://unibest.tech
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
79
src/pages/me/me.vue
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { LOGIN_PAGE } from '@/router/config'
|
||||||
|
import { useUserStore } from '@/store'
|
||||||
|
import { useTokenStore } from '@/store/token'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '我的',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const tokenStore = useTokenStore()
|
||||||
|
// 使用storeToRefs解构userInfo
|
||||||
|
const { userInfo } = storeToRefs(userStore)
|
||||||
|
|
||||||
|
// 微信小程序下登录
|
||||||
|
async function handleLogin() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 微信登录
|
||||||
|
await tokenStore.wxLogin()
|
||||||
|
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `${LOGIN_PAGE}`,
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// 清空用户信息
|
||||||
|
useTokenStore().logout()
|
||||||
|
// 执行退出登录逻辑
|
||||||
|
uni.showToast({
|
||||||
|
title: '退出登录成功',
|
||||||
|
icon: 'success',
|
||||||
|
})
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 微信小程序,去首页
|
||||||
|
// uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
// 非微信小程序,去登录页
|
||||||
|
// uni.navigateTo({ url: LOGIN_PAGE })
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="profile-container">
|
||||||
|
<view class="mt-3 break-all px-3 text-center text-green-500">
|
||||||
|
{{ userInfo.username ? '已登录' : '未登录' }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-3 break-all px-3">
|
||||||
|
{{ JSON.stringify(userInfo, null, 2) }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="mt-[60vh] px-3">
|
||||||
|
<view class="m-auto w-160px text-center">
|
||||||
|
<button v-if="tokenStore.hasLogin" type="warn" class="w-full" @click="handleLogout">
|
||||||
|
退出登录
|
||||||
|
</button>
|
||||||
|
<button v-else type="primary" class="w-full" @click="handleLogin">
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
55
src/router/README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# 登录 说明
|
||||||
|
|
||||||
|
## 登录 2种策略
|
||||||
|
- 默认无需登录策略: DEFAULT_NO_NEED_LOGIN
|
||||||
|
- 默认需要登录策略: DEFAULT_NEED_LOGIN
|
||||||
|
|
||||||
|
### 默认无需登录策略: DEFAULT_NO_NEED_LOGIN
|
||||||
|
进入任何页面都不需要登录,只有进入到黑名单中的页面/或者页面中某些动作需要登录,才需要登录。
|
||||||
|
|
||||||
|
比如大部分2C的应用,美团、今日头条、抖音等,都可以直接浏览,只有点赞、评论、分享等操作或者去特殊页面(比如个人中心),才需要登录。
|
||||||
|
|
||||||
|
### 默认需要登录策略: DEFAULT_NEED_LOGIN
|
||||||
|
|
||||||
|
进入任何页面都需要登录,只有进入到白名单中的页面,才不需要登录。默认进入应用需要先去登录页。
|
||||||
|
|
||||||
|
比如大部分2B和后台管理类的应用,比如企业微信、钉钉、飞书、内部报表系统、CMS系统等,都需要登录,只有登录后,才能使用。
|
||||||
|
|
||||||
|
### EXCLUDE_LOGIN_PATH_LIST
|
||||||
|
`EXCLUDE_LOGIN_PATH_LIST` 表示排除的路由列表。
|
||||||
|
|
||||||
|
在 `默认无需登录策略: DEFAULT_NO_NEED_LOGIN` 中,只有路由在 `EXCLUDE_LOGIN_PATH_LIST` 中,才需要登录,相当于黑名单。
|
||||||
|
|
||||||
|
在 `默认需要登录策略: DEFAULT_NEED_LOGIN` 中,只有路由在 `EXCLUDE_LOGIN_PATH_LIST` 中,才不需要登录,相当于白名单。
|
||||||
|
|
||||||
|
### excludeLoginPath
|
||||||
|
definePage 中可以通过 `excludeLoginPath` 来配置路由是否需要登录。(类似过去的 needLogin 的功能)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '关于',
|
||||||
|
},
|
||||||
|
// 登录授权(可选):跟以前的 needLogin 类似功能,但是同时支持黑白名单,详情请见 src/router 文件夹
|
||||||
|
excludeLoginPath: true,
|
||||||
|
// 角色授权(可选):如果需要根据角色授权,就配置这个
|
||||||
|
roleAuth: {
|
||||||
|
field: 'role',
|
||||||
|
value: 'admin',
|
||||||
|
redirect: '/pages/auth/403',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录注册页路由
|
||||||
|
|
||||||
|
登录页 `login.vue` 对应路由是 `/pages/login/login`.
|
||||||
|
注册页 `register.vue` 对应路由是 `/pages/login/register`.
|
||||||
|
|
||||||
|
## 登录注册页适用性
|
||||||
|
|
||||||
|
登录注册页主要适用于 `h5` 和 `App`,默认不适用于 `小程序`,因为 `小程序` 通常会使用平台提供的快捷登录。
|
||||||
|
|
||||||
|
特殊情况例外,如业务需要跨平台复用登录注册页时,也可以用在 `小程序` 上,所以主要还是看业务需求。
|
||||||
|
|
||||||
|
通过一个参数 `LOGIN_PAGE_ENABLE_IN_MP` 来控制是否在 `小程序` 中使用 `H5登录页` 的登录逻辑。
|
||||||
30
src/router/config.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getAllPages } from '@/utils'
|
||||||
|
|
||||||
|
export const LOGIN_STRATEGY_MAP = {
|
||||||
|
DEFAULT_NO_NEED_LOGIN: 0, // 黑名单策略,默认可以进入APP
|
||||||
|
DEFAULT_NEED_LOGIN: 1, // 白名单策略,默认不可以进入APP,需要强制登录
|
||||||
|
}
|
||||||
|
// TODO: 1/3 登录策略,默认使用`无需登录策略`,即默认不需要登录就可以访问
|
||||||
|
export const LOGIN_STRATEGY = LOGIN_STRATEGY_MAP.DEFAULT_NO_NEED_LOGIN
|
||||||
|
export const isNeedLoginMode = LOGIN_STRATEGY === LOGIN_STRATEGY_MAP.DEFAULT_NEED_LOGIN
|
||||||
|
|
||||||
|
export const LOGIN_PAGE = '/pages/auth/login'
|
||||||
|
export const REGISTER_PAGE = '/pages/auth/register'
|
||||||
|
|
||||||
|
export const LOGIN_PAGE_LIST = [LOGIN_PAGE, REGISTER_PAGE]
|
||||||
|
|
||||||
|
// 在 definePage 里面配置了 excludeLoginPath 的页面,功能与 EXCLUDE_LOGIN_PATH_LIST 相同
|
||||||
|
export const excludeLoginPathList = getAllPages('excludeLoginPath').map(page => page.path)
|
||||||
|
|
||||||
|
// 排除在外的列表,白名单策略指白名单列表,黑名单策略指黑名单列表
|
||||||
|
// TODO: 2/3 在 definePage 配置 excludeLoginPath,或者在下面配置 EXCLUDE_LOGIN_PATH_LIST
|
||||||
|
export const EXCLUDE_LOGIN_PATH_LIST = [
|
||||||
|
'/pages/xxx/index', // 示例值
|
||||||
|
'/pages-sub/xxx/index', // 示例值
|
||||||
|
...excludeLoginPathList, // 都是以 / 开头的 path
|
||||||
|
]
|
||||||
|
|
||||||
|
// 在小程序里面是否使用H5的登录页,默认为 false
|
||||||
|
// 如果为 true 则复用 h5 的登录逻辑
|
||||||
|
// TODO: 3/3 确定自己的登录页是否需要在小程序里面使用
|
||||||
|
export const LOGIN_PAGE_ENABLE_IN_MP = false
|
||||||
145
src/router/interceptor.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { isMp } from '@uni-helper/uni-env'
|
||||||
|
/**
|
||||||
|
* by 菲鸽 on 2025-08-19
|
||||||
|
* 路由拦截,通常也是登录拦截
|
||||||
|
* 黑、白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
|
||||||
|
*/
|
||||||
|
import { useTokenStore } from '@/store/token'
|
||||||
|
import { isPageTabbar, tabbarStore } from '@/tabbar/store'
|
||||||
|
import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index'
|
||||||
|
import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP } from './config'
|
||||||
|
|
||||||
|
export const FG_LOG_ENABLE = false
|
||||||
|
|
||||||
|
export function judgeIsExcludePath(path: string) {
|
||||||
|
const isDev = import.meta.env.DEV
|
||||||
|
if (!isDev) {
|
||||||
|
return EXCLUDE_LOGIN_PATH_LIST.includes(path)
|
||||||
|
}
|
||||||
|
const allExcludeLoginPages = getAllPages('excludeLoginPath') // dev 环境下,需要每次都重新获取,否则新配置就不会生效
|
||||||
|
return EXCLUDE_LOGIN_PATH_LIST.includes(path) || (isDev && allExcludeLoginPages.some(page => page.path === path))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigateToInterceptor = {
|
||||||
|
// 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
|
||||||
|
// 增加对相对路径的处理,BY 网友 @ideal
|
||||||
|
invoke({ url, query }: { url: string, query?: Record<string, string> }) {
|
||||||
|
if (url === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let { path, query: _query } = parseUrlToObj(url)
|
||||||
|
|
||||||
|
FG_LOG_ENABLE && console.log('\n\n路由拦截器:-------------------------------------')
|
||||||
|
FG_LOG_ENABLE && console.log('路由拦截器 1: url->', url, ', query ->', query)
|
||||||
|
const myQuery = { ..._query, ...query }
|
||||||
|
// /pages/route-interceptor/index?name=feige&age=30
|
||||||
|
FG_LOG_ENABLE && console.log('路由拦截器 2: path->', path, ', _query ->', _query)
|
||||||
|
FG_LOG_ENABLE && console.log('路由拦截器 3: myQuery ->', myQuery)
|
||||||
|
|
||||||
|
// 处理相对路径
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
const currentPath = getLastPage()?.route || ''
|
||||||
|
const normalizedCurrentPath = currentPath.startsWith('/') ? currentPath : `/${currentPath}`
|
||||||
|
const baseDir = normalizedCurrentPath.substring(0, normalizedCurrentPath.lastIndexOf('/'))
|
||||||
|
path = `${baseDir}/${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 处理路由不存在的情况
|
||||||
|
// if (path !== '/' && !getAllPages().some(page => page.path === path)) {
|
||||||
|
// console.warn('路由不存在:', path)
|
||||||
|
// return false // 明确表示阻止原路由继续执行
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 插件页面
|
||||||
|
// if (url.startsWith('plugin://')) {
|
||||||
|
// FG_LOG_ENABLE && console.log('路由拦截器 4: plugin:// 路径 ==>', url)
|
||||||
|
// path = url
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 处理直接进入路由非首页时,tabbarIndex 不正确的问题
|
||||||
|
tabbarStore.setAutoCurIdx(path)
|
||||||
|
|
||||||
|
// 小程序里面使用平台自带的登录,则不走下面的逻辑
|
||||||
|
if (isMp && !LOGIN_PAGE_ENABLE_IN_MP) {
|
||||||
|
return true // 明确表示允许路由继续执行
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenStore = useTokenStore()
|
||||||
|
FG_LOG_ENABLE && console.log('tokenStore.hasLogin:', tokenStore.hasLogin)
|
||||||
|
|
||||||
|
// 不管黑白名单,登录了就直接去吧(但是当前不能是登录页)
|
||||||
|
if (tokenStore.hasLogin) {
|
||||||
|
if (path !== LOGIN_PAGE) {
|
||||||
|
return true // 明确表示允许路由继续执行
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('已经登录,但是还在登录页', myQuery.redirect)
|
||||||
|
const url = myQuery.redirect || HOME_PAGE
|
||||||
|
if (isPageTabbar(url)) {
|
||||||
|
uni.switchTab({ url })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uni.navigateTo({ url })
|
||||||
|
}
|
||||||
|
return false // 明确表示阻止原路由继续执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fullPath = path
|
||||||
|
|
||||||
|
if (Object.keys(myQuery).length) {
|
||||||
|
fullPath += `?${Object.keys(myQuery).map(key => `${key}=${myQuery[key]}`).join('&')}`
|
||||||
|
}
|
||||||
|
const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(fullPath)}`
|
||||||
|
|
||||||
|
// #region 1/2 默认需要登录的情况(白名单策略) ---------------------------
|
||||||
|
if (isNeedLoginMode) {
|
||||||
|
// 需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示白名单,可以直接通过
|
||||||
|
if (judgeIsExcludePath(path)) {
|
||||||
|
return true // 明确表示允许路由继续执行
|
||||||
|
}
|
||||||
|
// 否则需要重定向到登录页
|
||||||
|
else {
|
||||||
|
if (path === LOGIN_PAGE) {
|
||||||
|
return true // 明确表示允许路由继续执行
|
||||||
|
}
|
||||||
|
FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) redirectUrl:', redirectUrl)
|
||||||
|
uni.navigateTo({ url: redirectUrl })
|
||||||
|
return false // 明确表示阻止原路由继续执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion 1/2 默认需要登录的情况(白名单策略) ---------------------------
|
||||||
|
|
||||||
|
// #region 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
|
||||||
|
else {
|
||||||
|
// 不需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示黑名单,需要重定向到登录页
|
||||||
|
if (judgeIsExcludePath(path)) {
|
||||||
|
FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) redirectUrl:', redirectUrl)
|
||||||
|
uni.navigateTo({ url: redirectUrl })
|
||||||
|
return false // 修改为false,阻止原路由继续执行
|
||||||
|
}
|
||||||
|
return true // 明确表示允许路由继续执行
|
||||||
|
}
|
||||||
|
// #endregion 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 针对 chooseLocation 的特殊处理
|
||||||
|
export const chooseLocationInterceptor = {
|
||||||
|
invoke(options: any) {
|
||||||
|
// 直接放行 chooseLocation 调用
|
||||||
|
FG_LOG_ENABLE && console.log('chooseLocation 调用,直接放行:', options)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routeInterceptor = {
|
||||||
|
install() {
|
||||||
|
uni.addInterceptor('navigateTo', navigateToInterceptor)
|
||||||
|
uni.addInterceptor('reLaunch', navigateToInterceptor)
|
||||||
|
uni.addInterceptor('redirectTo', navigateToInterceptor)
|
||||||
|
uni.addInterceptor('switchTab', navigateToInterceptor)
|
||||||
|
|
||||||
|
// 添加 chooseLocation 的拦截器,确保直接放行
|
||||||
|
uni.addInterceptor('chooseLocation', chooseLocationInterceptor)
|
||||||
|
},
|
||||||
|
}
|
||||||
11
src/router/permission.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { tabbarStore } from '@/tabbar/store'
|
||||||
|
|
||||||
|
export const permission = {
|
||||||
|
install(router) {
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const path = to.path
|
||||||
|
tabbarStore.setAutoCurIdx(path)
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
6
src/service/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
export * from './listAll';
|
||||||
|
export * from './info';
|
||||||
14
src/service/info.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
import request from '@/http/vue-query';
|
||||||
|
import { CustomRequestOptions_ } from '@/http/types';
|
||||||
|
|
||||||
|
import * as API from './types';
|
||||||
|
|
||||||
|
/** 用户信息 GET /user/info */
|
||||||
|
export function infoUsingGet({ options }: { options?: CustomRequestOptions_ }) {
|
||||||
|
return request<API.InfoUsingGetResponse>('/user/info', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
18
src/service/listAll.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
import request from '@/http/vue-query';
|
||||||
|
import { CustomRequestOptions_ } from '@/http/types';
|
||||||
|
|
||||||
|
import * as API from './types';
|
||||||
|
|
||||||
|
/** 用户列表 GET /user/listAll */
|
||||||
|
export function listAllUsingGet({
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
options?: CustomRequestOptions_;
|
||||||
|
}) {
|
||||||
|
return request<API.ListAllUsingGetResponse>('/user/listAll', {
|
||||||
|
method: 'GET',
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
29
src/service/types.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
|
||||||
|
export type InfoUsingGetResponse = {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: UserItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InfoUsingGetResponses = {
|
||||||
|
200: InfoUsingGetResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListAllUsingGetResponse = {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: UserItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListAllUsingGetResponses = {
|
||||||
|
200: ListAllUsingGetResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserItem = {
|
||||||
|
userId: number;
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
BIN
src/static/app/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
src/static/app/icons/120x120.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/static/app/icons/144x144.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/static/app/icons/152x152.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/static/app/icons/167x167.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/static/app/icons/180x180.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/static/app/icons/192x192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |