feat: init
commit
ee873e7404
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
}
|
||||
|
|
@ -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
|
||||
---
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# 开发工作流程
|
||||
|
||||
## 项目启动
|
||||
1. 安装依赖:`pnpm install`
|
||||
2. 开发环境:
|
||||
- H5: `pnpm dev` 或 `pnpm dev:h5`
|
||||
- 微信小程序: `pnpm dev:mp`
|
||||
- APP: `pnpm dev:app`
|
||||
|
||||
## 代码规范
|
||||
- 使用 ESLint 进行代码检查:`pnpm lint`
|
||||
- 自动修复代码格式:`pnpm lint:fix`
|
||||
- 使用 eslint 格式化代码
|
||||
- 遵循 TypeScript 严格模式
|
||||
|
||||
## 构建和部署
|
||||
- H5 构建:`pnpm build:h5`
|
||||
- 小程序构建:`pnpm build:mp`
|
||||
- APP 构建:`pnpm build:app`
|
||||
- 类型检查:`pnpm type-check`
|
||||
|
||||
## 开发工具
|
||||
- 推荐使用 VSCode 编辑器
|
||||
- 安装 Vue 和 TypeScript 相关插件
|
||||
- 使用 uni-app 开发者工具调试小程序
|
||||
- 使用 HBuilderX 调试 APP
|
||||
|
||||
## 调试技巧
|
||||
- 使用 console.log 和 uni.showToast 调试
|
||||
- 利用 Vue DevTools 调试组件状态
|
||||
- 使用网络面板调试 API 请求
|
||||
- 平台差异测试和兼容性检查
|
||||
|
||||
## 性能优化
|
||||
- 使用懒加载和代码分割
|
||||
- 优化图片和静态资源
|
||||
- 减少不必要的重渲染
|
||||
- 合理使用缓存策略
|
||||
---
|
||||
description: 开发工作流程和最佳实践指南
|
||||
---
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
# 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/` - 底部导航栏
|
||||
|
||||
## 开发命令
|
||||
- `pnpm dev` - 开发 H5 版本
|
||||
- `pnpm dev:mp` - 开发微信小程序
|
||||
- `pnpm dev:app` - 开发 APP 版本
|
||||
- `pnpm build` - 构建生产版本
|
||||
|
|
@ -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
|
||||
---
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# uni-app 开发规范
|
||||
|
||||
## 页面开发
|
||||
- 页面文件放在 [src/pages/](mdc:src/pages/) 目录下
|
||||
- 使用约定式路由,文件名即路由路径
|
||||
- 页面配置在仅需要在 `route-block` 中配置标题等内容即可,会自动生成到 `pages.json` 中
|
||||
|
||||
## 组件开发
|
||||
- 组件文件放在 [src/components/](mdc:src/components/) 目录下
|
||||
- 使用 uni-app 内置组件和第三方组件库
|
||||
- 支持 wot-design-uni\uv-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
|
||||
---
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Vue3 + TypeScript 开发规范
|
||||
|
||||
## Vue 组件规范
|
||||
- 使用 Composition API 和 `<script setup>` 语法
|
||||
- 组件文件使用 PascalCase 命名
|
||||
- 页面文件放在 `src/pages/` 目录下
|
||||
- 组件文件放在 `src/components/` 目录下
|
||||
|
||||
## Vue SFC 组件规范
|
||||
- `<script setup>` 标签必须是第一个子元素
|
||||
- `<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
|
||||
---
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# 依赖目录
|
||||
node_modules
|
||||
|
||||
# 版本控制
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# 构建产物
|
||||
/dist
|
||||
|
||||
# 开发工具配置
|
||||
.vscode/
|
||||
.idea/
|
||||
.trae/
|
||||
.cursor/
|
||||
|
||||
# 其他配置文件
|
||||
.github/
|
||||
.husky/
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
|
||||
# 缓存文件
|
||||
.cache/
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
|
|
@ -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 # 关闭末尾空格修剪
|
||||
|
|
@ -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
|
||||
|
||||
# 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
|
||||
|
|
@ -0,0 +1 @@
|
|||
npx --no-install commitlint --edit "$1"
|
||||
|
|
@ -0,0 +1 @@
|
|||
npx lint-staged --allow-empty
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# 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/` - 底部导航栏
|
||||
|
||||
## 开发命令
|
||||
- `pnpm dev` - 开发 H5 版本
|
||||
- `pnpm dev:mp` - 开发微信小程序
|
||||
- `pnpm dev:app` - 开发 APP 版本
|
||||
- `pnpm build` - 构建生产版本
|
||||
|
||||
## Vue 组件规范
|
||||
- 使用 Composition API 和 `<script setup>` 语法
|
||||
- 组件文件使用 PascalCase 命名
|
||||
- 页面文件放在 `src/pages/` 目录下
|
||||
- 组件文件放在 `src/components/` 目录下
|
||||
|
||||
## TypeScript 规范
|
||||
- 严格使用 TypeScript,避免使用 `any` 类型
|
||||
- 为 API 响应数据定义接口类型
|
||||
- 使用 `interface` 定义对象类型,`type` 定义联合类型
|
||||
- 导入类型时使用 `import type` 语法
|
||||
|
||||
## 状态管理
|
||||
- 使用 Pinia 进行状态管理
|
||||
- Store 文件放在 `src/store/` 目录下
|
||||
- 使用 `defineStore` 定义 store
|
||||
- 支持持久化存储
|
||||
|
||||
## UnoCSS 原子化 CSS
|
||||
- 项目使用 UnoCSS 作为原子化 CSS 框架
|
||||
- 配置在 [uno.config.ts](mdc:uno.config.ts)
|
||||
- 支持预设和自定义规则
|
||||
- 优先使用原子化类名,减少自定义 CSS
|
||||
|
||||
## Vue SFC 组件规范
|
||||
- `<script setup>` 标签必须是第一个子元素
|
||||
- `<template>` 标签必须是第二个子元素
|
||||
- `<style scoped>` 标签必须是最后一个子元素(因为推荐使用原子化类名,所以很可能没有)
|
||||
|
||||
## 页面开发
|
||||
- 页面文件放在 [src/pages/](mdc:src/pages/) 目录下
|
||||
- 使用约定式路由,文件名即路由路径
|
||||
- 页面配置在仅需要在 `route-block` 中配置标题等内容即可,会自动生成到 `pages.json` 中
|
||||
|
||||
## 组件开发
|
||||
- 组件文件放在 [src/components/](mdc:src/components/) 目录下
|
||||
- 使用 uni-app 内置组件和第三方组件库
|
||||
- 支持 wot-design-uni\uv-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 规范
|
||||
- 注意页面栈和导航管理
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"esbenp.prettier-vscode",
|
||||
"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",
|
||||
"christian-kohler.path-intellisense"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
// 配置语言的文件关联
|
||||
"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 内置验证
|
||||
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
// 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',",
|
||||
" },",
|
||||
"})",
|
||||
"</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": {
|
||||
"scope": "vue",
|
||||
"prefix": "sc",
|
||||
"body": [
|
||||
"<script lang=\"ts\" setup>",
|
||||
"//$1",
|
||||
"</script>\n"
|
||||
],
|
||||
},
|
||||
"Print unibest script with definePage": {
|
||||
"scope": "vue",
|
||||
"prefix": "scdp",
|
||||
"body": [
|
||||
"<script lang=\"ts\" setup>",
|
||||
"definePage({",
|
||||
" style: {",
|
||||
" navigationBarTitleText: '$1',",
|
||||
" },",
|
||||
"})",
|
||||
"</script>\n"
|
||||
],
|
||||
},
|
||||
"Print unibest template": {
|
||||
"scope": "vue",
|
||||
"prefix": "te",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <view class=\"\">$1</view>",
|
||||
"</template>\n"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# 使用 node:24-alpine 作为基础镜像,固定版本+减少体积
|
||||
FROM node:24-alpine AS builder
|
||||
|
||||
# 在容器中创建目录
|
||||
WORKDIR /app
|
||||
|
||||
# 安装pnpm(使用 npm 的 --global-style 可以减少依赖安装体积)
|
||||
RUN npm install -g pnpm@10.10.0 --global-style
|
||||
# 设置pnpm镜像源
|
||||
RUN pnpm config set registry https://registry.npmmirror.com
|
||||
# 复制依赖文件
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
# 先复制scripts目录,因为prepare脚本需要用到其中的文件
|
||||
COPY scripts ./scripts
|
||||
# 安装依赖,但跳过prepare脚本(这一步会缓存,只有 package.json 或 pnpm-lock.yaml 变化时才会重新运行)
|
||||
RUN pnpm install --ignore-scripts --frozen-lockfile
|
||||
# 手动执行我们需要的docker:prepare脚本
|
||||
RUN pnpm run docker:prepare
|
||||
# 复制其余源代码
|
||||
COPY . .
|
||||
# 构建项目
|
||||
RUN pnpm run build
|
||||
|
||||
|
||||
# 使用nginx作为服务
|
||||
FROM nginx:1.29.1-alpine3.22 AS production-stage
|
||||
|
||||
# 将构建好的项目复制到nginx下
|
||||
COPY --from=builder /app/dist/build/h5 /usr/share/nginx/html
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
# 启动nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# 参考代码
|
||||
|
||||
部分代码片段,供参考。
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
## Docker
|
||||
|
||||
根据提供的 `Dockerfile`,可以通过以下步骤构建并运行镜像:
|
||||
|
||||
### 1. 构建Docker镜像
|
||||
|
||||
在项目根目录执行以下命令:
|
||||
|
||||
- `-t unibest:v1-2025091701`:为镜像指定名称和标签,YYYYMMDD+编号
|
||||
- `.`:表示使用当前目录的Dockerfile
|
||||
|
||||
```bash
|
||||
docker build -t unibest:v1-2025091701 .
|
||||
docker build -t unibest:v1-2025091702 .
|
||||
```
|
||||
### 2. 运行Docker容器
|
||||
使用以下命令运行容器:
|
||||
|
||||
```bash
|
||||
docker run -d --name unibest-v1-2025091701 -p 80:80 unibest:v1-2025091701
|
||||
docker run -d --name unibest-v1-2025091702 -p 80:80 unibest:v1-2025091702
|
||||
```
|
||||
|
||||
- `-d`:表示在后台运行容器
|
||||
- `-p 80:80`:将容器的80端口映射到主机的80端口
|
||||
- `--name unibest-v1-2025091701`:为容器指定一个名称
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
VITE_APP_TITLE = '六玮中考'
|
||||
VITE_APP_PORT = 9000
|
||||
|
||||
VITE_UNI_APPID = 'H57F2ACE4'
|
||||
VITE_WX_APPID = 'wxc2399d3aa57174db'
|
||||
|
||||
# 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=/
|
||||
|
||||
# 后台请求地址
|
||||
# http://192.168.100.129:5066
|
||||
# https://api.v3.ycymedu.com
|
||||
VITE_SERVER_BASEURL = 'http://192.168.100.129:5066'
|
||||
# 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api
|
||||
|
||||
# 注意,如果是微信小程序,还有一套请求地址的配置,根据 develop、trial、release 分别设置上传地址,见 `src/utils/index.ts`。
|
||||
|
||||
# h5是否需要配置代理
|
||||
VITE_APP_PROXY_ENABLE = false
|
||||
# 下面的不用修改,只要不跟你后台的统一前缀冲突就行。如果修改了,记得修改 `nginx` 里面的配置
|
||||
VITE_APP_PROXY_PREFIX = '/fg-api'
|
||||
|
||||
# 第二个请求地址 (目前alova中可以使用)
|
||||
VITE_SERVER_BASEURL_SECONDARY = 'https://api.v3.ycymedu.com'
|
||||
|
||||
# 认证模式,'single' | 'double' ==> 单token | 双token
|
||||
VITE_AUTH_MODE = 'single'
|
||||
|
||||
# 原生插件资源复制开关,控制是否启用 copy-native-resources 插件
|
||||
VITE_COPY_NATIVE_RES_ENABLE = false
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
# 后台请求地址
|
||||
# VITE_SERVER_BASEURL = 'https://dev.xxx.com'
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'production'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
# 后台请求地址
|
||||
# VITE_SERVER_BASEURL = 'https://prod.xxx.com'
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
# 后台请求地址
|
||||
# VITE_SERVER_BASEURL = 'https://test.xxx.com'
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
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',
|
||||
'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,
|
||||
},
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
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,
|
||||
},
|
||||
},
|
||||
'mp-baidu': {
|
||||
usingComponents: true,
|
||||
},
|
||||
'mp-toutiao': {
|
||||
usingComponents: true,
|
||||
},
|
||||
'uniStatistics': {
|
||||
enable: false,
|
||||
},
|
||||
'vueVersion': '3',
|
||||
})
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
# 配置工作进程数,通常设置为 CPU 核心数
|
||||
worker_processes auto;
|
||||
|
||||
# 错误日志配置
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
# 开启多路复用
|
||||
use epoll;
|
||||
}
|
||||
|
||||
# 文件描述符限制 - 移到这里,在http块之前
|
||||
worker_rlimit_nofile 65535;
|
||||
|
||||
http {
|
||||
# 日志格式定义
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# 访问日志配置
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
# 高效文件传输设置
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
# 连接超时设置
|
||||
keepalive_timeout 65;
|
||||
keepalive_requests 100;
|
||||
|
||||
# gzip 压缩优化
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1000;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
# 增加更多文件类型
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
|
||||
|
||||
# 全局设置
|
||||
# 合理限制请求体大小,根据实际需求调整
|
||||
client_max_body_size 10m;
|
||||
client_body_buffer_size 128k;
|
||||
client_header_timeout 60s;
|
||||
client_body_timeout 60s;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
gunzip on;
|
||||
gzip_static always;
|
||||
include /etc/nginx/mime.types;
|
||||
absolute_redirect off;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
# 安全相关响应头
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
# 根据实际情况调整 CSP
|
||||
# add_header Content-Security-Policy "default-src 'self'";
|
||||
|
||||
# 处理 SPA 应用路由
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# HTML 和 JSON 文件 - 短缓存策略
|
||||
location ~ .*\.(html|json)$ {
|
||||
add_header Cache-Control "public, max-age=300, must-revalidate";
|
||||
}
|
||||
|
||||
# 静态资源 - 长缓存策略
|
||||
location ~ .*\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|ttf|woff|woff2|eot|mp4|mp3|swf)$ {
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
expires 365d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# JS 和 CSS - 带版本号的长缓存
|
||||
location ~ .*\.(js|css)$ {
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
expires 365d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# 接口转发 - 替换为实际后端地址
|
||||
# location ^~ /fg-api {
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header Host $host;
|
||||
|
||||
# # 后端是HTTPS时的必要配置
|
||||
# proxy_ssl_server_name on;
|
||||
# proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# proxy_ssl_session_reuse on;
|
||||
|
||||
# # 对于生产环境,应该尽量使用有效的证书而不是依赖``proxy_ssl_verify off;`` ,因为这会带来安全风险
|
||||
# proxy_ssl_verify off;
|
||||
|
||||
# # TODO:替换为实际后端服务地址
|
||||
# # 注意在URL末尾添加了斜杠,这样Nginx会去掉 /fg-api 前缀
|
||||
# # 前端请求 http://your-domain.com/fg-api/users 转发到 https://ukw0y1.laf.run/users
|
||||
# proxy_pass https://ukw0y1.laf.run/;
|
||||
|
||||
# # 上面一行的效果与下面2行一样的效果,都是为了去掉 /fg-api 前缀
|
||||
# # 显式移除/fg-api前缀
|
||||
# # rewrite ^/fg-api(.*)$ $1 break;
|
||||
# # 域名末尾不需要斜杠了
|
||||
# # proxy_pass https://ukw0y1.laf.run;
|
||||
|
||||
# proxy_connect_timeout 60s;
|
||||
# proxy_send_timeout 60s;
|
||||
# proxy_read_timeout 60s;
|
||||
|
||||
# proxy_buffers 8 32k;
|
||||
# proxy_buffer_size 64k;
|
||||
# proxy_busy_buffers_size 128k;
|
||||
|
||||
# proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
|
||||
# }
|
||||
|
||||
# 错误页面配置
|
||||
error_page 404 /index.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
# 禁止访问隐藏文件
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { GenerateServiceProps } from 'openapi-ts-request'
|
||||
|
||||
export default [
|
||||
{
|
||||
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,
|
||||
},
|
||||
] as GenerateServiceProps[]
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
{
|
||||
"name": "volunteer-secondary",
|
||||
"type": "module",
|
||||
"version": "3.18.6",
|
||||
"unibest-version": "3.18.6",
|
||||
"update-time": "2025-10-10",
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"generate-time": "用户创建项目时生成",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"uvm": "npx @dcloudio/uvm@latest",
|
||||
"uvm-rm": "node ./scripts/postupgrade.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",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"openapi": "openapi-ts",
|
||||
"prepare": "git init && husky && node ./scripts/create-base-files.js",
|
||||
"docker:prepare": "node ./scripts/create-base-files.js",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-uniapp": "^2.0.14",
|
||||
"@alova/shared": "^1.3.1",
|
||||
"@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",
|
||||
"@z-cloud/virtual-uni": "^1.0.5",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"alova": "^3.3.3",
|
||||
"dayjs": "1.11.10",
|
||||
"htmlparser2": "^10.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"pinia": "2.0.36",
|
||||
"pinia-plugin-persistedstate": "3.2.1",
|
||||
"sard-uniapp": "^1.22.1",
|
||||
"vue": "^3.4.21",
|
||||
"z-paging": "^2.8.8"
|
||||
},
|
||||
"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",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@iconify-json/carbon": "^1.2.4",
|
||||
"@rollup/rollup-darwin-x64": "^4.28.0",
|
||||
"@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.8",
|
||||
"@uni-helper/vite-plugin-uni-pages": "0.3.19",
|
||||
"@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",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cross-env": "^10.0.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.2.10",
|
||||
"miniprogram-api-typings": "^4.1.0",
|
||||
"openapi-ts-request": "^1.6.7",
|
||||
"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",
|
||||
"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": {
|
||||
"*": "eslint --fix"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||
import { tabBar } from './src/tabbar/config'
|
||||
|
||||
export default defineUniPages({
|
||||
globalStyle: {
|
||||
navigationStyle: 'default',
|
||||
navigationBarTitleText: '中考志愿',
|
||||
navigationBarBackgroundColor: '#f8f8f8',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
easycom: {
|
||||
autoscan: true,
|
||||
custom: {
|
||||
'^fg-(.*)': '@/components/fg-$1/fg-$1.vue',
|
||||
'^sar-(.*)': 'sard-uniapp/components/$1/$1.vue',
|
||||
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
|
||||
'z-paging/components/z-paging$1/z-paging$1.vue',
|
||||
},
|
||||
},
|
||||
// tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中
|
||||
tabBar: tabBar as any,
|
||||
preloadRule: {
|
||||
'pages/index/index': {
|
||||
network: 'all',
|
||||
packages: ['chart-sub'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 2adcf3437a54b6ac130a3d018bdd5c4eab35bdf6..365844a2c06dc7c80d227978c5a4defee4aabc90 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -454,7 +454,7 @@ class PageContext {
|
||||
const customPageMetaData = overrides || [];
|
||||
const result = customPageMetaData.length ? mergePageMetaDataArray(generatedPageMetaData.concat(customPageMetaData)) : generatedPageMetaData;
|
||||
const parseMeta = result.filter(
|
||||
(page, index, self) => self.slice().reverse().findIndex((item) => page.path === item.path) === (self.length - 1 - index)
|
||||
);
|
||||
return type === "main" ? this.setHomePage(parseMeta) : parseMeta;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// 基础配置文件生成脚本
|
||||
// 此脚本用于生成 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: '我的',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// 使用修复后的 __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 })
|
||||
}
|
||||
|
||||
// 如果 src/manifest.json 不存在,就创建它;存在就不处理,以免覆盖
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
// 如果 src/pages.json 不存在,就创建它;存在就不处理,以免覆盖
|
||||
if (!fs.existsSync(pagesPath)) {
|
||||
fs.writeFileSync(pagesPath, JSON.stringify(pages, null, 2))
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { exec } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
|
||||
/**
|
||||
* 打开开发者工具
|
||||
*/
|
||||
function _openDevTools() {
|
||||
const platform = process.platform // darwin, win32, linux
|
||||
const { UNI_PLATFORM } = process.env // mp-weixin, mp-alipay
|
||||
|
||||
const uniPlatformText = UNI_PLATFORM === 'mp-weixin' ? '微信小程序' : UNI_PLATFORM === 'mp-alipay' ? '支付宝小程序' : '小程序'
|
||||
|
||||
// 项目路径(构建输出目录)
|
||||
const projectPath = path.resolve(process.cwd(), `dist/dev/${UNI_PLATFORM}`)
|
||||
|
||||
// 检查构建输出目录是否存在
|
||||
if (!fs.existsSync(projectPath)) {
|
||||
console.log(`❌ ${uniPlatformText}构建目录不存在:`, projectPath)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🚀 正在打开${uniPlatformText}开发者工具...`)
|
||||
|
||||
// 根据不同操作系统执行不同命令
|
||||
let command = ''
|
||||
|
||||
if (platform === 'darwin') {
|
||||
// macOS
|
||||
if (UNI_PLATFORM === 'mp-weixin') {
|
||||
command = `/Applications/wechatwebdevtools.app/Contents/MacOS/cli -o "${projectPath}"`
|
||||
}
|
||||
else if (UNI_PLATFORM === 'mp-alipay') {
|
||||
command = `/Applications/小程序开发者工具.app/Contents/MacOS/小程序开发者工具 --p "${projectPath}"`
|
||||
}
|
||||
}
|
||||
else if (platform === 'win32' || platform === 'win64') {
|
||||
// Windows
|
||||
if (UNI_PLATFORM === 'mp-weixin') {
|
||||
command = `"C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat" -o "${projectPath}"`
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Linux 或其他系统
|
||||
console.log('❌ 当前系统不支持自动打开微信开发者工具')
|
||||
return
|
||||
}
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.log(`❌ 打开${uniPlatformText}开发者工具失败:`, error.message)
|
||||
console.log(`💡 请确保${uniPlatformText}开发者工具服务端口已启用`)
|
||||
console.log(`💡 可以手动打开${uniPlatformText}开发者工具并导入项目:`, projectPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.log('⚠️ 警告:', stderr)
|
||||
}
|
||||
|
||||
console.log(`✅ ${uniPlatformText}开发者工具已打开`)
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default function openDevTools() {
|
||||
// 首次构建标记
|
||||
let isFirstBuild = true
|
||||
|
||||
return {
|
||||
name: 'uni-devtools',
|
||||
writeBundle() {
|
||||
if (isFirstBuild && process.env.UNI_PLATFORM?.includes('mp')) {
|
||||
isFirstBuild = false
|
||||
_openDevTools()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// # 执行 `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 = [
|
||||
'@dcloudio/uni-app-harmony',
|
||||
// TODO: 如果不需要某个平台的小程序,请手动删除或注释掉
|
||||
'@dcloudio/uni-mp-alipay',
|
||||
'@dcloudio/uni-mp-baidu',
|
||||
'@dcloudio/uni-mp-jd',
|
||||
'@dcloudio/uni-mp-kuaishou',
|
||||
'@dcloudio/uni-mp-lark',
|
||||
'@dcloudio/uni-mp-qq',
|
||||
'@dcloudio/uni-mp-toutiao',
|
||||
'@dcloudio/uni-mp-xhs',
|
||||
'@dcloudio/uni-quickapp-webview',
|
||||
// i18n模板要注释掉下面的
|
||||
'vue-i18n',
|
||||
]
|
||||
|
||||
/**
|
||||
* 带开关的日志输出函数
|
||||
* @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')
|
||||
})
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
onLoad(() => {
|
||||
uni.loadFontFace({family:"DinBold",source:"https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/din-bold.ttf"})
|
||||
uni.loadFontFace({family:'JinBuFont',source:"https://lw-zk.oss-cn-hangzhou.aliyuncs.com/img/DingTalkJinBuTi.ttf"})
|
||||
})
|
||||
|
||||
const exposeRef = ref('this is form app.Ku.vue')
|
||||
|
||||
defineExpose({
|
||||
exposeRef,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 这个先隐藏了,知道这样用就行 -->
|
||||
<view class="h-screen flex flex-col">
|
||||
<KuRootView />
|
||||
<FgTabbar v-if="isCurrentPageTabbar" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||
import { navigateToInterceptor } from '@/router/interceptor'
|
||||
|
||||
onLaunch((options) => {
|
||||
console.log('App Launch', options)
|
||||
})
|
||||
onShow((options) => {
|
||||
console.log('App Show', 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')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'sard-uniapp/index.scss';
|
||||
swiper,
|
||||
scroll-view {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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 }, // 用于切换请求地址
|
||||
})
|
||||
}
|
||||
|
|
@ -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' })
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// 认证模式类型
|
||||
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 interface IUserInfoRes {
|
||||
nickName?: string
|
||||
avatar?: string
|
||||
openId?: string
|
||||
userExtend?: any
|
||||
talentExtend?:any
|
||||
[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,0 +1,135 @@
|
|||
<template>
|
||||
<view class="custom-checkbox">
|
||||
|
||||
<CheckboxGroup v-model="defValue" checked-color="#1580FF" @change="handleChange" v-bind="$attrs" :default-cols="4">
|
||||
|
||||
<Checkbox v-for="item in list" :key="item[valueKey]" :name="item[valueKey]" cell shape="button" :default-style="`
|
||||
${checkboxStyle}
|
||||
--checkbox-bg: #f7f8fa;
|
||||
--checkbox-radius: 8rpx;
|
||||
--checkbox-active-border: 2rpx solid #1580ff;
|
||||
--checkbox-active-color: #1580ff;
|
||||
|
||||
height: var(--checkbox-height);
|
||||
|
||||
background-color: var(--checkbox-bg);
|
||||
border-radius: var(--checkbox-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`">
|
||||
{{ item[labelKey] }}
|
||||
</Checkbox>
|
||||
|
||||
</CheckboxGroup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Checkbox from './Checkbox.vue'
|
||||
import CheckboxGroup from './CheckboxGroup.vue'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'code',
|
||||
},
|
||||
defaultValue: {
|
||||
type: Array<string>,
|
||||
default: () => [],
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '216rpx',
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '60rpx',
|
||||
},
|
||||
defaultCols: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
})
|
||||
defineOptions({
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['change'])
|
||||
const defValue = ref<string[]>([])
|
||||
|
||||
// 只在初始化时设置默认值
|
||||
onMounted(() => {
|
||||
if (props.defaultValue?.length) {
|
||||
defValue.value = [...props.defaultValue]
|
||||
}
|
||||
})
|
||||
|
||||
const handleChange = (val: unknown) => {
|
||||
defValue.value = val as string[]
|
||||
emits('change', val)
|
||||
}
|
||||
watch(
|
||||
() => props.defaultValue,
|
||||
(newVal) => {
|
||||
defValue.value = [...newVal]
|
||||
},
|
||||
)
|
||||
|
||||
// 计算复选框样式
|
||||
const checkboxStyle = computed(() => {
|
||||
const width = typeof props.width === 'number' ? `${props.width}rpx` : props.width
|
||||
const height = typeof props.height === 'number' ? `${props.height}rpx` : props.height
|
||||
return `--checkbox-width: ${width};--checkbox-height: ${height};`
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-checkbox {
|
||||
// 定义默认变量
|
||||
--checkbox-width: 216rpx;
|
||||
--checkbox-height: 60rpx;
|
||||
--checkbox-bg: #f7f8fa;
|
||||
--checkbox-radius: 8rpx;
|
||||
color: #333;
|
||||
|
||||
}
|
||||
|
||||
:deep(.ycym-checkbox) {
|
||||
width: var(--checkbox-width);
|
||||
height: var(--checkbox-height);
|
||||
min-width: var(--checkbox-width);
|
||||
background-color: var(--checkbox-bg);
|
||||
border-radius: var(--checkbox-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid var(--checkbox-bg);
|
||||
}
|
||||
|
||||
:deep(.checkbox__icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
:deep(.checkbox-active) {
|
||||
border-color: #1580ff !important;
|
||||
|
||||
.checkbox__label {
|
||||
color: #1580ff !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<view
|
||||
:style="`${defaultStyle}`"
|
||||
:class="`${isChecked?'checkbox-active':''} ${isDisabled?'checkbox-disabled':''} ${rootClass}`"
|
||||
@click="handleClick"
|
||||
>
|
||||
<view class="checkbox__icon" :class="{ 'checkbox__icon--checked': isChecked }" v-if="showIcon">
|
||||
<text v-if="isChecked" class="i-carbon-checkmark checkbox__icon-check"></text>
|
||||
</view>
|
||||
<view class="checkbox__label">
|
||||
<slot>{{ label }}</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showIcon:{
|
||||
default:false
|
||||
},
|
||||
defaultStyle:{
|
||||
type:String,
|
||||
default: 'display: inline-flex;align-items: center;cursor: pointer;font-size: 28rpx;'
|
||||
},
|
||||
checkboxActive:{
|
||||
default:'background-color: #0083ff;border-color: #0083ff;'
|
||||
},
|
||||
rootClass:{
|
||||
type:String,
|
||||
default: ""
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
interface CheckboxGroupContext {
|
||||
modelValue: ComputedRef<any[]>
|
||||
disabled: ComputedRef<boolean>
|
||||
max: ComputedRef<number>
|
||||
selectedCount: ComputedRef<number>
|
||||
toggleOption: (option: { value: string | number }) => void
|
||||
}
|
||||
|
||||
// 注入checkbox group的上下文
|
||||
const checkboxGroup = inject<CheckboxGroupContext>('checkboxGroup', {
|
||||
modelValue: computed(() => []),
|
||||
disabled: computed(() => false),
|
||||
max: computed(() => 0),
|
||||
selectedCount: computed(() => 0),
|
||||
toggleOption: () => {},
|
||||
})
|
||||
|
||||
// 是否选中
|
||||
const isChecked = computed(() => {
|
||||
const modelValue = checkboxGroup.modelValue.value
|
||||
return modelValue.includes(props.name)
|
||||
})
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
const max = checkboxGroup.max.value
|
||||
const selectedCount = checkboxGroup.selectedCount.value
|
||||
// 如果有原始禁用属性,或者group禁用,或者达到最大选择数且未选中,则禁用
|
||||
return (
|
||||
props.disabled ||
|
||||
checkboxGroup.disabled.value ||
|
||||
(max > 1 && !isChecked.value && selectedCount >= max)
|
||||
)
|
||||
})
|
||||
|
||||
// 处理点击事件
|
||||
const handleClick = () => {
|
||||
if (isDisabled.value) return
|
||||
|
||||
checkboxGroup.toggleOption({
|
||||
value: props.name,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.checkbox--disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.checkbox__icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #dcdfe6;
|
||||
border-radius: 4rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8rpx;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.checkbox__icon--checked {
|
||||
border-color: #0083ff;
|
||||
}
|
||||
|
||||
.checkbox__icon-check {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.checkbox__label {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.checkbox-active {
|
||||
border:var(--checkbox-active-border);
|
||||
color: var(--checkbox-active-color)
|
||||
}
|
||||
|
||||
.checkbox-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<view class="checkbox-group" :style="`${checkgroupStyle}`">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
checkgroupStyle:{
|
||||
type:String,
|
||||
default:'grid-template-columns: repeat(3, 1fr);'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
// 内部维护一个响应式的值
|
||||
const innerValue = computed(() => props.modelValue)
|
||||
|
||||
// 切换选项
|
||||
const toggleOption = (option: { label: string; value: string | number }) => {
|
||||
const currentValue = innerValue.value
|
||||
const index = currentValue.indexOf(option.value)
|
||||
let newValue = [...currentValue]
|
||||
|
||||
if (index === -1) {
|
||||
if (props.max === 1) {
|
||||
newValue = [option.value]
|
||||
} else if (props.max && currentValue.length >= props.max) {
|
||||
uni.showToast({
|
||||
title: `最多只能选择${props.max}项`,
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
} else {
|
||||
newValue.push(option.value)
|
||||
}
|
||||
} else {
|
||||
newValue.splice(index, 1)
|
||||
}
|
||||
|
||||
emit('update:modelValue', newValue)
|
||||
emit('change', newValue)
|
||||
}
|
||||
|
||||
// 提供给checkbox的上下文
|
||||
provide('checkboxGroup', {
|
||||
modelValue: computed(() => props.modelValue),
|
||||
disabled: computed(() => props.disabled),
|
||||
max: computed(() => props.max),
|
||||
selectedCount: computed(() => props.modelValue.length),
|
||||
toggleOption,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.checkbox-group{
|
||||
display: grid;
|
||||
gap: 16rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<Overlay :show="show">
|
||||
<view class="bg-white wrapper flex flex-col rounded-[16rpx]" :style="{ width: defaultWidth }">
|
||||
<view
|
||||
class="relative flex items-center w-full justify-center py-[26rpx] title"
|
||||
v-show="title"
|
||||
>
|
||||
<text class="text-[36rpx] text-[#303030] font-bold text-center">
|
||||
{{ title }}
|
||||
</text>
|
||||
|
||||
<view
|
||||
class="i-carbon-close absolute right-[40rpx] text-[40rpx]"
|
||||
@click="emits('update:show', false)"
|
||||
v-if="showClose"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
class="min-h-[200rpx] h-max-content overflow-y-auto"
|
||||
:class="{ 'px-[32rpx]': defaultPadding }"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</Overlay>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Overlay from '@/chart-sub/components/overlay/Overlay.vue'
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '提示',
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultPadding: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultWidth: {
|
||||
type: String,
|
||||
default: '90%',
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:show'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: max-content;
|
||||
|
||||
.title {
|
||||
border-bottom: 2rpx solid #f7f7f7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
<template>
|
||||
<view class="navbar">
|
||||
<!-- 状态栏占位 -->
|
||||
<view
|
||||
v-if="safeAreaInsetTop"
|
||||
class="status-bar"
|
||||
:style="{ height: statusBarHeight + 'px', backgroundColor: bgColor }"
|
||||
></view>
|
||||
|
||||
<!-- 导航栏主体 -->
|
||||
<view
|
||||
class="navbar-content"
|
||||
:class="[contentClass, fixed ? 'navbar-fixed' : '', bordered ? 'navbar-border' : '']"
|
||||
:style="{
|
||||
backgroundColor: bgColor,
|
||||
height: `${navHeight}px`,
|
||||
top: fixed ? (safeAreaInsetTop ? statusBarHeight : 0) + 'px' : '0',
|
||||
}"
|
||||
>
|
||||
<view
|
||||
:class="`navbar-left ${leftWidthMin ? 'min-w-[48rpx]' : ''} min-w-[48rpx]`"
|
||||
@click="handleClickLeft"
|
||||
>
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<view v-if="leftArrow" class="back-icon">
|
||||
<view class="i-carbon-chevron-left text-[40rpx] text-[#333] font-semibold icon-class" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<slot name="left"></slot>
|
||||
</view>
|
||||
<!-- 中间标题区域 -->
|
||||
<view class="navbar-title">
|
||||
<slot name="title">
|
||||
<text class="title-text">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<view class="navbar-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位元素 -->
|
||||
<view
|
||||
v-if="placeholder && fixed"
|
||||
:style="{
|
||||
height: `${navHeight}px`,
|
||||
backgroundColor: bgColor,
|
||||
}"
|
||||
></view>
|
||||
<slot name="background"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDeviceInfo, getWindowInfo } from '@/utils/tools'
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
leftArrow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
safeAreaInsetTop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff',
|
||||
},
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: 'justify-between',
|
||||
},
|
||||
leftWidthMin: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['clickLeft'])
|
||||
|
||||
// 获取系统信息
|
||||
const deviceInfo = getDeviceInfo()
|
||||
const systemInfo = getWindowInfo()
|
||||
|
||||
const statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 动态计算导航栏高度
|
||||
const navHeight = computed(() => {
|
||||
// 获取屏幕信息
|
||||
const { screenWidth } = systemInfo
|
||||
const { platform } = deviceInfo
|
||||
|
||||
// 将px转换为rpx的比例
|
||||
const ratio = 750 / screenWidth
|
||||
|
||||
// 根据平台设置不同的导航栏高度
|
||||
if (platform === 'ios') {
|
||||
return 88 / ratio // iOS 一般是 44pt,转换为px
|
||||
} else if (platform === 'android') {
|
||||
return 96 / ratio // Android 一般是 48dp,转换为px
|
||||
} else {
|
||||
return 88 / ratio // 默认值
|
||||
}
|
||||
})
|
||||
|
||||
const handleClickLeft = () => {
|
||||
emit('clickLeft')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
width: 100%;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
/* justify-content: space-between; */
|
||||
padding: 0 16rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
/* flex: 1; */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
overflow: hidden;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 52rpx;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.back-text {
|
||||
font-size: 48rpx;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="overlay"
|
||||
:class="{ 'overlay-show': show }"
|
||||
@click="handleClick"
|
||||
:style="{ zIndex }"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click', 'update:show'])
|
||||
|
||||
const handleClick = (event: Event) => {
|
||||
emit('click', event)
|
||||
if (props.closeOnClickOverlay) {
|
||||
emit('update:show', false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.overlay-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<view class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar safeAreaInsetTop :bordered="false" leftArrow @clickLeft="handleBack" bg-color="transparent">
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">能力测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative mt-[40rpx] flex flex-col">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="flex flex-col pt-[32rpx] mx-[24rpx] bg-[#fff] px-[30rpx] pt-[30rpx] border-class">
|
||||
<text class="text-[#333] text-[28rpx] mb-[14rpx] z-2 font-700">你的弱势能力为</text>
|
||||
<text class="text-[#117CFC] text-[36rpx] z-2">
|
||||
{{ studyRecord.title.replace('你的弱势能力:', '') }}
|
||||
</text>
|
||||
</view>
|
||||
<!-- 雷达图占位 -->
|
||||
<LineReport :echart-data="studyRecord.linChart" :description="studyRecord.description" />
|
||||
<AbilityDimension :report-items="studyRecord.reportItems" />
|
||||
</view>
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import LineReport from '../components/interestChart/LineReport.vue'
|
||||
import AbilityDimension from '../components/AbilityDimension.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
import { getAbilityDimension } from '@/service'
|
||||
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
linChart: [],
|
||||
reportItems: [],
|
||||
hTag: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getAbilityDimension({ ScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// linChart: any[]
|
||||
// reportItems: any[]
|
||||
// hTag: string
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
width: calc(100% - 80rpx);
|
||||
height: 244rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
font-size: 24rpx;
|
||||
min-width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.position-tag {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-item image {
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.border-class {
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
padding-bottom: 42rpx;
|
||||
margin-bottom: -14rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">性格测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 flex flex-col overflow-auto">
|
||||
<view class="overflow-auto relative mt-[40rpx] flex-1 pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="flex flex-col pt-[32rpx] px-[84rpx] h-[244rpx] mb-[-116rpx] font-700">
|
||||
<view class="header-bg">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/evaluate/bg.png"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-[#333] text-[28rpx] mb-[14rpx] z-2">您的性格类型为</text>
|
||||
<text class="text-[#117CFC] text-[36rpx] z-2">{{ studyRecord.title }}</text>
|
||||
</view>
|
||||
<!-- 雷达图占位 -->
|
||||
<CharacterChart :linChart="studyRecord.linChart" :description="studyRecord.description" />
|
||||
|
||||
<DependenciesChart
|
||||
:mainDomain="studyRecord.reportItem.mainDomain"
|
||||
:major="studyRecord.reportItem.major"
|
||||
:occupation="studyRecord.reportItem.occupation"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import CharacterChart from '../components/interestChart/CharacterChart.vue'
|
||||
import DependenciesChart from '../components/interestChart/DependenciesChart.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { getMBTIDimension } from '@/service'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
linChart: {},
|
||||
reportItem: { mainDomain: '', major: '', occupation: '' },
|
||||
hTag: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getMBTIDimension({ ScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// linChart: any
|
||||
// reportItem: { mainDomain: string; major: string; occupation: string }
|
||||
// hTag: string
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
width: calc(100% - 80rpx);
|
||||
height: 244rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
font-size: 24rpx;
|
||||
min-width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.position-tag {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-item image {
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<view class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar safeAreaInsetTop :bordered="false" leftArrow @clickLeft="handleBack" bg-color="transparent">
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">性格测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
<view class="flex-1 overflow-auto relative mt-[40rpx]">
|
||||
<view class="overflow-auto relative mt-[40rpx] flex-1 pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="flex flex-col pt-[32rpx] px-[84rpx] h-[244rpx] mb-[-116rpx] font-700">
|
||||
<view class="header-bg">
|
||||
<image src="https://api.static.ycymedu.com/src/images/evaluate/bg.png" class="w-full h-full" />
|
||||
</view>
|
||||
<text class="text-[#333] text-[28rpx] mb-[14rpx] z-2">您的兴趣类型为</text>
|
||||
<text class="text-[#117CFC] text-[36rpx] z-2">{{ studyRecord.title }}</text>
|
||||
</view>
|
||||
<!-- 雷达图占位 -->
|
||||
<InterestRadar :picData="studyRecord.picCharts" />
|
||||
<!-- 类型说明 -->
|
||||
<TypeDetail :reportItems="studyRecord.reportItems" />
|
||||
|
||||
<!-- 适合职业 -->
|
||||
<IntroMajor :tag="studyRecord.hTag" />
|
||||
<!-- 兴趣分析与代表人物 -->
|
||||
<InterestingThings :tag="studyRecord.hTag" :description="studyRecord.description" />
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TypeDetail from '../components/TypeDetail.vue'
|
||||
import InterestRadar from '../components/interestChart/InterestRadar.vue'
|
||||
import IntroMajor from '../components/IntroMajor.vue'
|
||||
|
||||
import { getHollandDimensionInfo } from '@/service'
|
||||
import InterestingThings from '../components/InterestingThings.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: '',
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({ description: '', title: '', picCharts: {}, reportItems: [], hTag: '' })
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
getHollandDimensionInfo({ query: { ScaleId: pageId.value.toString() } }).then((resp) => {
|
||||
if (resp.code === 200) {
|
||||
studyRecord.value = resp.result as {
|
||||
description: string
|
||||
title: string
|
||||
picCharts: any
|
||||
reportItems: any[]
|
||||
hTag: string
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
width: calc(100% - 80rpx);
|
||||
height: 244rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
font-size: 24rpx;
|
||||
min-width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.position-tag {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-item image {
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">职业锚测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative mt-[40rpx]">
|
||||
<view class="overflow-auto relative mt-[40rpx] flex-1 pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="flex flex-col pt-[32rpx] px-[84rpx] h-[244rpx] mb-[-116rpx] font-700">
|
||||
<view class="header-bg">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/evaluate/bg.png"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-[#333] text-[28rpx] mb-[14rpx] z-2">您的职业价值观</text>
|
||||
<text class="text-[#117CFC] text-[40rpx] z-2">{{ studyRecord.tag }}</text>
|
||||
</view>
|
||||
<OpinionChart :pic-charts="studyRecord.picCharts" />
|
||||
<AbilityDimension :report-items="studyRecord.reportItems" />
|
||||
</view>
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import OpinionChart from '../components/interestChart/OpinionChart.vue'
|
||||
import AbilityDimension from '../components/AbilityDimension.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
import { getOpinionAbout } from '@/service'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
picCharts: { indicator: [], radars: [] },
|
||||
reportItems: [],
|
||||
tag: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getOpinionAbout({ ScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// picCharts: { radars: any[]; indicator: any[] }
|
||||
// reportItems: any[]
|
||||
// tag: string
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
width: calc(100% - 80rpx);
|
||||
height: 244rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
font-size: 24rpx;
|
||||
min-width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.position-tag {
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-item image {
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.border-class {
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
padding-bottom: 42rpx;
|
||||
margin-bottom: -14rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<view class="mx-[30rpx] mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx]">
|
||||
<TitleBar title="能力纬度详细介绍" />
|
||||
|
||||
<view class="flex flex-col gap-[40rpx]">
|
||||
<view v-for="(item, index) in reportItems" :key="index">
|
||||
<view class="text-[32rpx] text-[#000] font-700">{{ item.title }}</view>
|
||||
<view class="text-[26rpx] text-[#666] mt-[6rpx]">
|
||||
{{ item.resolving || item.description }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TitleBar from './TitleBar.vue'
|
||||
|
||||
defineProps({
|
||||
reportItems: {
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<view
|
||||
class="pt-[16rpx] px-[32rpx] flex items-center justify-center bg-[#fff] pb-safe sticky bottom-0 z-999"
|
||||
@click="toAiAssistant"
|
||||
v-if="aiShow"
|
||||
>
|
||||
<view
|
||||
class="rounded-[8rpx] border-[#1580FF] border-[2rpx] border-solid w-full py-[14rpx] flex items-center justify-center"
|
||||
>
|
||||
<view class="w-[52rpx] h-[52rpx] mr-[10rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/images/btn-bottom.png"
|
||||
class="w-[52rpx] h-[52rpx]"
|
||||
></image>
|
||||
</view>
|
||||
<text class="text-[#1580FF] text-[32rpx] font-700">智能AI顾问</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="pb-safe"></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { sysDictType } from '@/service'
|
||||
|
||||
const aiShow = ref(true)
|
||||
// sysDictType({ id: 619330547859525 }).then((res) => {
|
||||
// const { code, result } = res
|
||||
|
||||
// const { status } = result as { status: number }
|
||||
// if (code === 200) {
|
||||
// if (status === 1) {
|
||||
// aiShow.value = false
|
||||
// } else {
|
||||
// aiShow.value = true
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
const props = defineProps({
|
||||
pageId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
pageType: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const toAiAssistant = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages-sub/ai-service/index?id=${props.pageId}&type=${props.pageType}`,
|
||||
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<view
|
||||
class="text-[26rpx] text-[#000] mx-[24rpx] bg-[#fff] flex flex-col px-[20rpx] gap-[10rpx] pb-[20rpx] custom-class"
|
||||
>
|
||||
<view class="flex gap-[10rpx] w-full">
|
||||
<view class="w-[94rpx] py-[12rpx] bg-color text-center">类型</view>
|
||||
<view class="py-[12rpx] text-center bg-color flex-1">详解</view>
|
||||
</view>
|
||||
<view class="flex gap-[10rpx] w-full" v-for="(item, index) in reportItems" :key="index">
|
||||
<view class="w-[94rpx] py-[12rpx] bg-color text-center">
|
||||
<view>{{ item.tag }}</view>
|
||||
<view>{{ item.title }}</view>
|
||||
</view>
|
||||
<view class="py-[22rpx] px-[14rpx] text-start bg-color flex-1">{{ item.resolving }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
reportItems: {
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-color {
|
||||
background-color: #f5faff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.custom-class {
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<view class="interest-analysis mx-[30rpx] mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx]">
|
||||
<TitleBar title="兴趣分析与代表人物" />
|
||||
|
||||
<text class="text-[26rpx] text-[#666] leading-[40rpx] mb-[30rpx] block">
|
||||
{{ description }}
|
||||
</text>
|
||||
|
||||
<view class="flex items-center gap-[10rpx] mb-[16rpx]">
|
||||
<view class="w-[5rpx] h-[30rpx] bg-[#1580FF]"></view>
|
||||
<text class="text-[#1580FF] text-[32rpx]">代表人物</text>
|
||||
<text class="text-[#999] text-[24rpx]">以下代表人物仅作为参考</text>
|
||||
</view>
|
||||
<view class="avatars-list grid grid-cols-4 gap-x-[52rpx] gap-y-[18rpx]">
|
||||
<view
|
||||
class="avatar-item flex flex-col items-center"
|
||||
v-for="(person, index) in personList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="w-[120rpx] h-[120rpx]">
|
||||
<image
|
||||
:src="person.avatarUrl"
|
||||
class="w-[120rpx] h-[120rpx] rounded-full"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<text class="mt-[10rpx] text-[26rpx] text-[28rpx] font-normal text-center">
|
||||
{{ person.nickName }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getTagMapPerson } from '@/service'
|
||||
import TitleBar from './TitleBar.vue'
|
||||
|
||||
const props = defineProps({
|
||||
tag: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const personList = ref([])
|
||||
|
||||
watch(
|
||||
() => props.tag,
|
||||
(newV) => {
|
||||
// getTagMapPerson({ tag: newV }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// personList.value = resp.result as any[]
|
||||
// }
|
||||
// })
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<view class="mx-[30rpx] mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx]">
|
||||
<TitleBar title="推荐专业" />
|
||||
<text class="text-[24rpx] text-[#999]">以下专业仅作为参考</text>
|
||||
|
||||
<view class="w-full">
|
||||
<view class="flex py-[15rpx] px-[36rpx] bg-[#F5FAFF]">
|
||||
<text class="w-[129rpx] text-center font-bold">专业大类</text>
|
||||
<text class="flex-1 text-center font-bold">专业类</text>
|
||||
<text class="flex-1 text-center font-bold">专业名称</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
:class="`flex py-[15rpx] text-[#333] text-[24rpx] px-[36rpx] ${index % 2 === 0 ? 'bg-[#fff]' : 'bg-[#F5FAFF]'}`"
|
||||
v-for="(major, index) in majorList"
|
||||
:key="index"
|
||||
>
|
||||
<text class="w-[129rpx] text-center">{{ major.tradeName }}</text>
|
||||
<text class="flex-1 text-center">{{ major.categoryName }}</text>
|
||||
<text class="flex-1 text-center">{{ major.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getTagMapPro } from '@/service'
|
||||
import TitleBar from './TitleBar.vue'
|
||||
|
||||
const props = defineProps({
|
||||
tag: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const majorList = ref([])
|
||||
|
||||
watch(
|
||||
() => props.tag,
|
||||
(newV) => {
|
||||
// getTagMapPro({ tag: newV }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// majorList.value = resp.result as any[]
|
||||
// }
|
||||
// })
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/chart-sub/styles/parallelogram.scss';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<view class="mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx]">
|
||||
<TitleBar :title="title" />
|
||||
|
||||
<view v-for="(item, index) in items" :key="index" class="suggestion-item">
|
||||
<view class="text-[32rpx] font-700">{{ item.title }}</view>
|
||||
<view class="text-[26rpx] font-400 mt-[10rpx] text-[#666]">
|
||||
{{ item.description instanceof Array ? item.description.join(',') : item.description }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TitleBar from './TitleBar.vue'
|
||||
defineProps({
|
||||
items: {
|
||||
type: Array<{ title: string; description: string | [] }>,
|
||||
default: () => [],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.suggestion-item {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<view class="mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx]">
|
||||
<TitleBar :title="title" />
|
||||
<view class="text-[26rpx] text-[#666] mt-[10rpx] text-center">
|
||||
<text
|
||||
class="text-[22rpx] px-[12rpx] py-[4rpx] rounded-[20rpx] bg-[rgba(250,142,35,0.15)] text-[#FA8E23]"
|
||||
>
|
||||
{{ item.notes }}
|
||||
</text>
|
||||
<view class="mt-[20rpx] text-center">
|
||||
<view class="text-[#000] text-[26rpx] font-700">学习风格表现</view>
|
||||
<view class="mt-[10rpx]" v-for="(item, index) in item.learning_performance" :key="index">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-[20rpx] text-center">
|
||||
<view class="text-[#000] text-[26rpx] font-700">学习风格特点</view>
|
||||
<view class="mt-[10rpx]" v-for="(item, index) in item.features" :key="index">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TitleBar from './TitleBar.vue'
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<view class="relative pt-[40rpx]">
|
||||
<Dashboard class="absolute right-[38rpx] top-0" :score="score" :color="rules[level].color" />
|
||||
<view class="bg-[#fff] rounded-[24rpx] p-[22rpx]">
|
||||
<view class="mt-[30rpx]">
|
||||
<text class="text-[48rpx] text-[#000] font-600">{{ tagName }}</text>
|
||||
<!-- <view class="flex items-center text-[#999] text-[24rpx] mt-[20rpx]">
|
||||
<view class="i-carbon-warning w-[24rpx] h-[24rpx]"></view>
|
||||
<text class="text-[#999] ml-[10rpx]">状态很好哦,继续保持,轻松迎接高考吧!</text>
|
||||
</view> -->
|
||||
<view class="mt-[58rpx]">
|
||||
<ScoreCard :current-position="level" :rules="rules" />
|
||||
</view>
|
||||
|
||||
<view class="relative mt-[68rpx] bg-[#F5FAFF]">
|
||||
<view class="absolute top-[-9rpx] left-[20rpx] w-[180rpx] h-[52rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/home/test-icon.png"
|
||||
mode="scaleToFill"
|
||||
class="w-[180rpx] h-[52rpx]"
|
||||
/>
|
||||
</view>
|
||||
<view class="px-[20rpx] pb-[20rpx] pt-[58rpx] text-[#333] text-[26rpx]">
|
||||
{{ description }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-[#999] text-[24rpx] mt-[10rpx]">
|
||||
{{ tip }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Dashboard from '@/chart-sub/evaluate/components/psychologicalReportItem/Dashboard.vue'
|
||||
import ScoreCard from './psychologicalReportItem/ScoreCard.vue'
|
||||
|
||||
defineProps({
|
||||
score: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
rules: {
|
||||
type: Array<{ label: string; range: string; color: string }>,
|
||||
default: () => [],
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
default: '结果只做参考,不能准确判断是否有焦虑症。',
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tagName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<view class="flex flex-col bg-white rounded-[24rpx] p-[30rpx] gap-[40rpx]">
|
||||
<view class="flex flex-col gap-[12rpx]">
|
||||
<view class="flex items-center gap-[10rpx]">
|
||||
<view class="w-[38rpx] h-[38rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/home/life-icon.png"
|
||||
mode="scaleToFill"
|
||||
class="w-[38rpx] h-[38rpx]"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-[32rpx] text-[#000] font-700">生活建议</text>
|
||||
</view>
|
||||
<view class="text-[26rpx] text-[#666] font-400">
|
||||
保持规律作息,早睡早起,避免熬夜。 每天适当运动(如散步、跑步),保持精力充沛。
|
||||
避免过度放松,保持适度的学习节奏。
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex flex-col gap-[12rpx]">
|
||||
<view class="flex items-center gap-[10rpx]">
|
||||
<view class="w-[38rpx] h-[38rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/home/diet-icon.png"
|
||||
mode="scaleToFill"
|
||||
class="w-[38rpx] h-[38rpx]"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-[32rpx] text-[#000] font-700">饮食建议</text>
|
||||
</view>
|
||||
<view class="text-[26rpx] text-[#666] font-400">
|
||||
保持规律作息,早睡早起,避免熬夜。 每天适当运动(如散步、跑步),保持精力充沛。
|
||||
避免过度放松,保持适度的学习节奏。
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex flex-col gap-[12rpx]">
|
||||
<view class="flex items-center gap-[10rpx]">
|
||||
<view class="w-[38rpx] h-[38rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/home/learn-icon.png"
|
||||
mode="scaleToFill"
|
||||
class="w-[38rpx] h-[38rpx]"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-[32rpx] text-[#000] font-700">学习建议</text>
|
||||
</view>
|
||||
<view class="text-[26rpx] text-[#666] font-400">
|
||||
保持规律作息,早睡早起,避免熬夜。 每天适当运动(如散步、跑步),保持精力充沛。
|
||||
避免过度放松,保持适度的学习节奏。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<view class="text-center mb-[24rpx]">
|
||||
<view class="text-[32rpx] font-bold relative inline-flex pb-[10rpx] items-center">
|
||||
<view class="i-carbon-flash-filled h-[23rpx] w-[17rpx] text-[#1880FC]"></view>
|
||||
<text class="text-[#1880FC] text-[36rpx] font-bold">{{ title }}</text>
|
||||
<view class="i-carbon-flash-filled h-[23rpx] w-[17rpx] text-[#1880FC]"></view>
|
||||
<view
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-full h-[10rpx] bg-[#cce3fc] rounded-full title-bar"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/chart-sub/styles/parallelogram.scss';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<view
|
||||
class="text-[26rpx] text-[#000] mx-[24rpx] bg-[#fff] flex flex-col px-[20rpx] gap-[10rpx] pb-[20rpx] custom-class"
|
||||
>
|
||||
<view class="flex gap-[10rpx] w-full">
|
||||
<view class="w-[94rpx] py-[12rpx] bg-color text-center">类型</view>
|
||||
<view class="py-[12rpx] text-center bg-color flex-1">性格特点</view>
|
||||
</view>
|
||||
<view class="flex gap-[10rpx] w-full" v-for="(item, index) in reportItems" :key="index">
|
||||
<view
|
||||
class="w-[94rpx] py-[12rpx] bg-color text-center flex flex-col justify-center items-center text-[#333] font-700"
|
||||
>
|
||||
<view>{{ item.tag }}</view>
|
||||
<view>{{ item.title }}</view>
|
||||
</view>
|
||||
<view class="py-[22rpx] px-[14rpx] text-start bg-color flex-1">{{ item.resolving }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
reportItems: {
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-color {
|
||||
background-color: #f5faff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.custom-class {
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<view
|
||||
class="radar-chart-placeholder mx-[24rpx] bg-white chart-class h-[630rpx] flex flex-col justify-center px-[24rpx] pt-[62rpx] pb-[20rpx]"
|
||||
>
|
||||
<view class="w-full z-1 flex flex-col gap-[40rpx]">
|
||||
<view
|
||||
class="w-full flex items-center gap-[16rpx] text-[30rpx] text-[#A8A8A8]"
|
||||
v-for="(item, index) in characterData"
|
||||
:style="`--start-color:${colors[index].startColor};--end-color:${colors[index].endColor};--active-color:${colors[index].activeColor}`"
|
||||
:key="index"
|
||||
>
|
||||
<view
|
||||
:class="`w-110rpx ${item.leftData.value > item.rightData.value ? 'left-active' : ''}`"
|
||||
>
|
||||
{{ item.leftData.name }}
|
||||
</view>
|
||||
<view class="flex-1 h-[28rpx] bg-[#F1F2F4] rounded-[16rpx]">
|
||||
<view
|
||||
class="w-[60%] rounded-[16rpx] bar-color h-[28rpx]"
|
||||
:style="`transform:translateX(${(item.rightData.value / (item.rightData.value + item.leftData.value)) * 100}%);`"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
:class="`w-110rpx ${item.leftData.value > item.rightData.value ? '' : 'right-active'}`"
|
||||
>
|
||||
{{ item.rightData.name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-[16rpx] py-[20rpx] flex flex-col bg-[#F5FAFF] mt-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#000] mb-[10rpx] font-700">性格介绍</text>
|
||||
<text class="text-[26rpx] text-[#333]">{{ description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
linChart: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
name: [],
|
||||
value: [],
|
||||
}),
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const colors = [
|
||||
{ startColor: '#97FBCD', endColor: '#00BAAD', activeColor: '#00BAAD' },
|
||||
{ startColor: '#FF5940', endColor: '#FAA896', activeColor: '#FF4117' },
|
||||
{ startColor: '#FFBF5E', endColor: '#F59300', activeColor: '#EB8D00' },
|
||||
{ startColor: '#96E4FA', endColor: '#117CFC', activeColor: '#117CFC' },
|
||||
]
|
||||
const transformData = (names: string[], values: number[]) => {
|
||||
const result = []
|
||||
const pairMap = new Map()
|
||||
const pairData = { E: 'I', S: 'N', T: 'F', J: 'P' }
|
||||
|
||||
// 先建立名称和值的映射
|
||||
names.forEach((name, index) => {
|
||||
const match = name.match(/\((.*?)\)(\w+)/)
|
||||
if (match) {
|
||||
const letter = match[2]
|
||||
pairMap.set(letter, { name, value: values[index] })
|
||||
}
|
||||
})
|
||||
|
||||
// 根据 pairData 构建配对数据
|
||||
Object.entries(pairData).forEach(([left, right]) => {
|
||||
const leftData = pairMap.get(left)
|
||||
const rightData = pairMap.get(right)
|
||||
if (leftData && rightData) {
|
||||
result.push({
|
||||
leftData,
|
||||
rightData,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const characterData = ref([])
|
||||
watch(
|
||||
() => props.linChart,
|
||||
(newV) => {
|
||||
let data = transformData(newV.name, newV.value)
|
||||
characterData.value = data
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-class {
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.bar-color {
|
||||
background: linear-gradient(90deg, var(--start-color) 0%, var(--end-color) 100%);
|
||||
}
|
||||
|
||||
.left-active {
|
||||
color: var(--active-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
.right-active {
|
||||
color: var(--active-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<view
|
||||
class="suitable-positions mx-[30rpx] mt-[30rpx] bg-white rounded-[20rpx] p-[30rpx] flex flex-col"
|
||||
>
|
||||
<TitleBar title="适合的岗位领域" />
|
||||
|
||||
<view class="flex justify-center items-center">
|
||||
<view class="h-[146px] w-[265px] z-1">
|
||||
<LEchart ref="echart" :is-disable-scroll="true"></LEchart>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center gap-[10rpx] mb-[10rpx]">
|
||||
<view class="w-[5rpx] h-[30rpx] bg-[#1580FF]"></view>
|
||||
<text class="text-[#1580FF] text-[32rpx] font-bold">适合职业</text>
|
||||
<text class="text-[#999] text-[24rpx]">以下数据仅作为参考</text>
|
||||
</view>
|
||||
<text class="text-[#666] text-[28rpx]">{{ major }}</text>
|
||||
|
||||
<view class="flex items-center gap-[10rpx] mb-[10rpx] mt-[40rpx]">
|
||||
<view class="w-[5rpx] h-[30rpx] bg-[#1580FF]"></view>
|
||||
<text class="text-[#1580FF] text-[32rpx] font-bold">适合专业</text>
|
||||
<text class="text-[#999] text-[24rpx]">以下数据仅作为参考</text>
|
||||
</view>
|
||||
<text class="text-[#666] text-[28rpx]">{{ occupation }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
import TitleBar from '../TitleBar.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
mainDomain: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
major: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
occupation: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 定义固定的样式配置
|
||||
const styleConfig = [
|
||||
{ bg: '#FDF0F0', text: '#F58C8C', size: 45, x: 2, y: 38 },
|
||||
{ bg: '#ECF5FF', text: '#3B9DFF', size: 77, x: 52, y: 64 },
|
||||
{ bg: '#F6F4FD', text: '#7E5EFF', size: 63, x: 136, y: 48 },
|
||||
{ bg: '#FFF8E5', text: '#FFC832', size: 62, x: 116, y: 104 },
|
||||
{ bg: '#E6FBF0', text: '#37D480', size: 45, x: 0, y: 110 },
|
||||
{ bg: '#FDF0F0', text: '#F58C8C', size: 45, x: 166, y: 118 },
|
||||
]
|
||||
|
||||
const chartData = ref([])
|
||||
|
||||
// 根据文字长度选择合适的样式
|
||||
const selectStylesByLength = (items: string[]) => {
|
||||
const usedIndices = new Set<number>()
|
||||
const result: Array<{ style: any; text: string }> = []
|
||||
|
||||
// 优先处理大尺寸的元素
|
||||
const processItems = (items: string[], targetSize: number) => {
|
||||
items.forEach((item) => {
|
||||
// 查找匹配大小且未使用的样式
|
||||
const availableIndices = styleConfig
|
||||
.map((style, index) => ({ index, style }))
|
||||
.filter(({ style, index }) => style.size === targetSize && !usedIndices.has(index))
|
||||
|
||||
if (availableIndices.length > 0) {
|
||||
const { index, style } = availableIndices[0]
|
||||
usedIndices.add(index)
|
||||
result.push({ style, text: item })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 按不同尺寸进行分类
|
||||
const bigItems = items.filter((item) => item.length >= 4)
|
||||
const mediumItems = items.filter((item) => item.length === 3)
|
||||
const smallItems = items.filter((item) => item.length <= 2)
|
||||
|
||||
// 依次处理不同尺寸的元素
|
||||
processItems(bigItems, 77)
|
||||
processItems(mediumItems, 63)
|
||||
processItems(smallItems, 45)
|
||||
|
||||
// 处理剩余的未分配元素(使用任何可用样式)
|
||||
const remainingItems = items.filter((item) => !result.some((r) => r.text === item))
|
||||
|
||||
remainingItems.forEach((item) => {
|
||||
// 尝试找到任何未使用的样式
|
||||
for (let i = 0; i < styleConfig.length; i++) {
|
||||
if (!usedIndices.has(i)) {
|
||||
usedIndices.add(i)
|
||||
result.push({ style: styleConfig[i], text: item })
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.mainDomain,
|
||||
(newV) => {
|
||||
if (!newV) return
|
||||
|
||||
chartData.value = newV.split('、')
|
||||
echart.value.init(echarts, (chart) => {
|
||||
// 根据文字长度选择合适的样式
|
||||
const selectedStyles = selectStylesByLength(chartData.value)
|
||||
|
||||
// 生成图表数据
|
||||
const data = selectedStyles.map(({ style, text }) => {
|
||||
return {
|
||||
name: text,
|
||||
x: style.x,
|
||||
y: style.y,
|
||||
symbolSize: style.size,
|
||||
itemStyle: {
|
||||
color: style.bg,
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
color: style.text,
|
||||
fontSize: 14,
|
||||
formatter: (params: any) => {
|
||||
const text = params.name
|
||||
if (text.length >= 5) {
|
||||
const lines = []
|
||||
for (let i = 0; i < text.length; i += 3) {
|
||||
lines.push(text.slice(i, i + 3))
|
||||
}
|
||||
return lines.join('\n\n')
|
||||
}
|
||||
return text
|
||||
},
|
||||
rich: {
|
||||
lineHeight: 20,
|
||||
},
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
position: 'inside',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
let option = {
|
||||
tooltip: {},
|
||||
animationDurationUpdate: 1500,
|
||||
animationEasingUpdate: 'quinticInOut',
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'none',
|
||||
roam: true,
|
||||
data,
|
||||
links: [],
|
||||
lineStyle: {
|
||||
opacity: 0.9,
|
||||
width: 2,
|
||||
curveness: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
})
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<view class="mx-[24rpx] bg-white chart-class h-[500rpx] flex items-center justify-center">
|
||||
<view class="h-[415rpx] w-full z-1">
|
||||
<LEchart ref="echart"></LEchart>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
const props = defineProps({
|
||||
picData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
indicator: [],
|
||||
radars: [],
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
// 处理标签名称的映射
|
||||
const nameMap: Record<string, string> = {
|
||||
A: '艺术型(A)',
|
||||
R: '现实型(R)',
|
||||
I: '研究型(I)',
|
||||
S: '社会型(S)',
|
||||
E: '企业型(E)',
|
||||
C: '常规型(C)',
|
||||
}
|
||||
|
||||
// 转换 indicator 数据
|
||||
const formatIndicator = computed(() => {
|
||||
return props.picData.indicator.map((item) => ({
|
||||
name: nameMap[item.name] || item.name,
|
||||
max: item.max,
|
||||
}))
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.picData,
|
||||
(newV) => {
|
||||
if (newV.radars.length > 0) {
|
||||
echart.value.init(echarts, (chart) => {
|
||||
let option = {
|
||||
radar: {
|
||||
center: ['50%', '50%'],
|
||||
radius: '70%',
|
||||
indicator: formatIndicator.value,
|
||||
splitArea: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e5e6eb',
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#e5e6eb',
|
||||
},
|
||||
},
|
||||
axisName: {
|
||||
color: '#333',
|
||||
},
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
name: '职业兴趣评测',
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: props.picData.radars,
|
||||
name: '测评结果',
|
||||
itemStyle: {
|
||||
color: '#1580FF',
|
||||
},
|
||||
areaStyle: {
|
||||
color: 'rgba(21,128,255,0.2)',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
chart.setOption(option)
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (echart.value) {
|
||||
echart.value.dispose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-class {
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<view class="bg-white mx-[24rpx] rounded-[20rpx] pb-[20rpx]">
|
||||
<view class="px-[24rpx] h-[368rpx] z-1">
|
||||
<LEchart ref="echart" :customStyle="`z-index:1;`"></LEchart>
|
||||
</view>
|
||||
<view class="bg-[#F5FAFF] px-[16rpx] pt-[20rpx] mx-[34rpx] pb-[26rpx]">
|
||||
<view class="text-[#000] text-[30rpx] mb-[10rpx] font-700">能力评测建议</view>
|
||||
<view class="text-[26rpx] text-[#333]">{{ description }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
echartData: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{
|
||||
name: [],
|
||||
value: [],
|
||||
},
|
||||
{
|
||||
name: [],
|
||||
value: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateChart()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.echartData,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
updateChart()
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const updateChart = () => {
|
||||
echart.value.init(echarts, (chart) => {
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
},
|
||||
formatter: function (params) {
|
||||
let result = params[0].name.split('\n').join('') + '\n'
|
||||
params.forEach((param) => {
|
||||
result += `${param.marker} ${param.seriesName}: ${param.value}\n`
|
||||
})
|
||||
return result
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
},
|
||||
position: function (point, params, dom, rect, size) {
|
||||
const tooltipWidth = dom._rect.width
|
||||
const canvasWidth = size.viewSize[0]
|
||||
|
||||
if (point[0] + tooltipWidth > canvasWidth) {
|
||||
return [point[0] - tooltipWidth, '10%']
|
||||
}
|
||||
|
||||
return [point[0], '10%']
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: 40,
|
||||
left: 10,
|
||||
right: 20,
|
||||
bottom: 10,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
data: ['我的数据', '平均水平'],
|
||||
right: 'auto',
|
||||
left: 'center',
|
||||
top: 0,
|
||||
itemWidth: 15,
|
||||
itemHeight: 10,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: (props.echartData[1] as { name: string[] })?.name.map((item) =>
|
||||
item.replace('智能', '').replace(/(.{2})/g, '$1\n'),
|
||||
),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E0E0E0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '分数',
|
||||
min: 0,
|
||||
max: 50,
|
||||
interval: 10,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#E0E0E0',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#E8E8E8',
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '我的数据',
|
||||
type: 'bar',
|
||||
barWidth: '20',
|
||||
itemStyle: {
|
||||
color: '#1580FF',
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
},
|
||||
data: (props.echartData[0] as { value: number[] })?.value,
|
||||
},
|
||||
{
|
||||
name: '平均水平',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#F9AA5B',
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#F9AA5B',
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff',
|
||||
},
|
||||
data: (props.echartData[1] as { value: number[] })?.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<view class="bg-white mx-[24rpx] rounded-[20rpx] pb-[20rpx]">
|
||||
<view class="px-[24rpx] h-[368rpx] z-1">
|
||||
<LEchart ref="echart" :customStyle="`z-index:1;`"></LEchart>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
|
||||
interface IndicatorItem {
|
||||
name: string
|
||||
max: number
|
||||
}
|
||||
|
||||
interface PicChartsData {
|
||||
indicator: IndicatorItem[]
|
||||
radars: number[]
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
picCharts: {
|
||||
type: Object as () => PicChartsData,
|
||||
default: () => ({
|
||||
indicator: [],
|
||||
radars: [],
|
||||
}),
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateChart()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.picCharts,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.indicator && newVal.indicator.length > 0) {
|
||||
updateChart()
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const updateChart = () => {
|
||||
echart.value.init(echarts, (chart) => {
|
||||
// 提取职业锚名称,去除括号内的缩写
|
||||
const categories = props.picCharts.indicator.map((item) => {
|
||||
const match = item.name.match(/(.*?)\([^)]*\)/)
|
||||
return match ? match[1].trim() : item.name
|
||||
})
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: 60,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: categories,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E0E0E0',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
rotate: 45,
|
||||
formatter: function (value) {
|
||||
// 处理长文本
|
||||
if (value.length > 4) {
|
||||
return value.substring(0, 4) + '...'
|
||||
}
|
||||
return value
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '分数',
|
||||
min: 0,
|
||||
max: 30,
|
||||
interval: 5,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#E0E0E0',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#E8E8E8',
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '得分',
|
||||
type: 'bar',
|
||||
barWidth: '20',
|
||||
itemStyle: {
|
||||
color: function (params) {
|
||||
// 为不同柱子设置不同颜色
|
||||
const colorList = [
|
||||
'#1580FF',
|
||||
'#F9AA5B',
|
||||
'#5470c6',
|
||||
'#91cc75',
|
||||
'#fac858',
|
||||
'#ee6666',
|
||||
'#73c0de',
|
||||
'#3ba272',
|
||||
]
|
||||
return colorList[params.dataIndex % colorList.length]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#666',
|
||||
fontSize: 12,
|
||||
},
|
||||
data: props.picCharts.radars,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<view
|
||||
class="rounded-full w-[162rpx] h-[162rpx] bg-[#fff] dashboard-wrapper flex items-center justify-center"
|
||||
:style="{ '--current-color': color }"
|
||||
>
|
||||
<view class="w-[139rpx] h-[139rpx] custom-style flex items-center justify-center">
|
||||
<view
|
||||
v-for="i in 39"
|
||||
:key="i"
|
||||
class="tick"
|
||||
:style="{
|
||||
transform: `rotate(${i * 8 - 90}deg)`,
|
||||
opacity: `${1 - Math.abs(i * 8 - 90) / 90}`,
|
||||
}"
|
||||
></view>
|
||||
<view class="font-500 text-color w-full h-full flex items-center justify-center">
|
||||
<view :class="`${score > 99 ? 'text-[50rpx]' : 'text-[62rpx]'}`">{{ score }}</view>
|
||||
<view class="text-[28rpx] mt-[8rpx]">分</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
color: {
|
||||
type: String,
|
||||
default: '#00b281',
|
||||
},
|
||||
score: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-style {
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: var(--current-color);
|
||||
container-type: inline-size;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tick {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 4rpx;
|
||||
height: 10rpx;
|
||||
background-color: var(--current-color);
|
||||
transform-origin: 0 67rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<view class="bg-white mx-[24rpx] rounded-[20rpx] pb-[20rpx]">
|
||||
<view class="px-[24rpx] h-[368rpx] z-1">
|
||||
<LEchart ref="echart" :customStyle="`z-index:1;`"></LEchart>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
</script>
|
||||
|
||||
<script lang="scss" scoped></script>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<view
|
||||
class="grid grid-cols-4 items-end gap-[6rpx] relative"
|
||||
:style="`grid-template-columns: repeat(${rules.length},minmax(0,1fr))`"
|
||||
>
|
||||
<view v-for="(item, index) in rules" :key="index" class="">
|
||||
<view
|
||||
:class="['common-rectangle']"
|
||||
:style="`height: ${24 + index * 10}rpx;background-color:${item.color}`"
|
||||
></view>
|
||||
<view
|
||||
class="flex flex-col text-[24rpx] text-center"
|
||||
:style="{ color: currentPosition === index ? item.color : '#999' }"
|
||||
>
|
||||
<text :class="currentPosition === index ? 'font-500' : ''">{{ item.label }}</text>
|
||||
<view v-html="item.range" class="whitespace-nowrap"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
:style="{
|
||||
left: `${(currentPosition + 1 / 2) * (100 / rules.length)}%`,
|
||||
top: `${currentPosition * -8 - 10}rpx`,
|
||||
}"
|
||||
class="current-flag-wrapper"
|
||||
>
|
||||
<view class="current-flag">
|
||||
<view class="flag-text">当前</view>
|
||||
<view class="flag-triangle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
currentPosition: number // 0-3 分别对应四个矩形的位置
|
||||
rules: {
|
||||
label: string
|
||||
range: string
|
||||
color: string
|
||||
}[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-rectangle {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-radius: 8rpx;
|
||||
clip-path: polygon(100% 0, 100% 0, 100% 100%, 0 100%, 0 10rpx);
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.current-flag-wrapper {
|
||||
position: absolute;
|
||||
margin-bottom: 8rpx;
|
||||
transition: left 0.3s ease;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.current-flag {
|
||||
position: relative;
|
||||
width: 82rpx;
|
||||
height: 37rpx;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flag-text {
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.flag-triangle {
|
||||
position: absolute;
|
||||
bottom: -8rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8rpx solid transparent;
|
||||
border-right: 8rpx solid transparent;
|
||||
border-top: 8rpx solid rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<view class="bg-white rounded-[20rpx] pb-[20rpx] custom-background">
|
||||
<view class="h-[586rpx] z-1">
|
||||
<LEchart ref="echart" :customStyle="`z-index:1;`"></LEchart>
|
||||
</view>
|
||||
<view class="relative mt-[68rpx] bg-[#F5FAFF] mx-[20rpx] px-[24rpx] pt-[58rpx] pb-[20rpx]">
|
||||
<view class="w-[180rpx] h-[52rpx] absolute top-[-9rpx] left-[20rpx]">
|
||||
<image
|
||||
src="https://api.static.ycymedu.com/src/images/home/test-icon.png"
|
||||
mode="scaleToFill"
|
||||
class="w-[180rpx] h-[52rpx]"
|
||||
/>
|
||||
</view>
|
||||
<view v-for="(item, index) in innerParsing" class="text-[26rpx] mb-[20rpx]" :key="index">
|
||||
<text class="text-[#000] font-700">{{ item.title }}: </text>
|
||||
<text class="text-[#3d3d3d] font-400">{{ item.desc }}</text>
|
||||
</view>
|
||||
<view class="text-[26rpx]">
|
||||
<text class="text-[#000] font-700">策略偏好:</text>
|
||||
<view class="flex gap-x-[20rpx] gap-y-[16rpx] text-[22rpx] flex-wrap mt-[16rpx]">
|
||||
<view
|
||||
v-for="(item, index) in policy.items"
|
||||
:key="index"
|
||||
class="text-[22rpx] px-[12rpx] py-[4rpx] rounded-[20rpx] bg-[rgba(250,142,35,0.15)] text-[#FA8E23]"
|
||||
>
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LEchart from '@/chart-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
|
||||
const echarts = require('../../../uni_modules/lime-echart/static/echarts.min')
|
||||
const echart = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
picData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
parsing: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const innerParsing = ref([])
|
||||
const policy = ref({ items: [] })
|
||||
|
||||
watch(
|
||||
() => props.parsing,
|
||||
(newV) => {
|
||||
const _val = JSON.parse(newV) as {
|
||||
tags: { title: string; items: { title: string; desc: string }[] }[]
|
||||
}
|
||||
_val.tags.forEach((item) => {
|
||||
if (item.title === '策略偏好') {
|
||||
policy.value = item
|
||||
} else {
|
||||
innerParsing.value.push(...item.items)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.picData,
|
||||
(newData) => {
|
||||
if (!newData || newData.length === 0) return
|
||||
|
||||
if (echart.value) {
|
||||
echart.value.init(echarts, (chart) => {
|
||||
const option = {
|
||||
radar: {
|
||||
center: ['50%', '50%'],
|
||||
radius: '60%',
|
||||
indicator: newData.map((item: any) => ({
|
||||
name: `${item.type},${item.desc}`,
|
||||
})),
|
||||
shape: 'polygon',
|
||||
splitNumber: 4,
|
||||
axisName: {
|
||||
color: '#333',
|
||||
fontSize: 12,
|
||||
formatter: (value: string) => {
|
||||
// 处理文字换行
|
||||
const maxLength = 4
|
||||
const result = []
|
||||
const _val = value.split(',')
|
||||
for (let i = 0; i < _val[0].length; i += maxLength) {
|
||||
result.push(value.slice(i, i + maxLength))
|
||||
}
|
||||
result.push(`(${_val[1]})`)
|
||||
return result.join('\n')
|
||||
},
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: ['rgba(255,255,255,0.3)'],
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E5E6EB',
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#E5E6EB',
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: newData.map((item: any) => item.value),
|
||||
name: '学习风格',
|
||||
areaStyle: {
|
||||
color: 'rgba(64, 158, 255, 0.3)',
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#409EFF',
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#409EFF',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: (params: any) => {
|
||||
return params.value
|
||||
},
|
||||
color: '#1580FF',
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (echart.value) {
|
||||
echart.value.dispose()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-background {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, #ffffff 6%);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
|
||||
<template>
|
||||
<view class="flex flex-col h-screen relative">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">{{ pageName }}</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
<view class="h-full w-full custom-bg absolute top-0 left-0 -z-1"></view>
|
||||
|
||||
<view class="question-container flex-1 overflow-hidden mt-[30rpx] flex">
|
||||
<view class="flex-1 h-0 relative">
|
||||
<view
|
||||
v-for="(question, index) in questions"
|
||||
:key="index"
|
||||
:class="`h-full overflow-y-auto card-container ${currentIndex === index ? 'current-card' : ''}`"
|
||||
>
|
||||
<view class="px-[30rpx] py-[40rpx] flex flex-col card-content">
|
||||
<text class="mb-[30rpx] text-[34rpx] font-semibold">
|
||||
{{ index + 1 }}、{{ question.title }}
|
||||
</text>
|
||||
<CheckboxGroup
|
||||
v-model="checkedList"
|
||||
checked-color="#1580FF"
|
||||
@change="handleCheckChange"
|
||||
:max="questionType === 0 ? 1 : 0"
|
||||
checkgroupStyle="grid-template-columns: repeat(1, 1fr);"
|
||||
>
|
||||
<Checkbox
|
||||
v-for="item in question.answer"
|
||||
:key="item.key"
|
||||
:name="item.key"
|
||||
cell
|
||||
shape="button"
|
||||
root-class="custom-checkbox"
|
||||
default-style="width: 100%;height: 80rpx;background-color: #f6f7f8;border-radius: 8rpx;display: flex;align-items: center;justify-content: center;font-size: 28rpx;font-weight: 400;color: #303030;border: 2rpx solid #f6f7f8;"
|
||||
>
|
||||
{{ item.name }}
|
||||
</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-[86rpx] px-[30rpx]">
|
||||
<button class="next-question" :disabled="disableBtn" @click="handleNextQuestion">
|
||||
{{ currentIndex === questions.length - 1 ? '提交' : '下一题' }}
|
||||
({{ currentIndex + 1 }}/{{ questions.length }})
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<MessageBox v-model:show="show" title="" :defaultPadding="false" defaultWidth="85%">
|
||||
<template>
|
||||
<view class="custom-background">
|
||||
<view class="px-[32rpx] pt-[48rpx]">
|
||||
<text class="text-[#000] text-[48rpx] font-semibold">{{ questionName }}</text>
|
||||
<view
|
||||
class="bg-[rgba(21,128,255,0.1)] flex items-center gap-[10rpx] text-[24rpx] text-[#444] px-[24rpx] py-[6rpx] w-max rounded-[10rpx] mt-[10rpx]"
|
||||
>
|
||||
<view class="i-carbon-time-filled text-[#1580FF] w-[24rpx] h-[24rpx]"></view>
|
||||
<view>{{ useTime }}</view>
|
||||
</view>
|
||||
<view class="text-[30rpx] my-[40rpx] text-[#333] text-[28rpx]">
|
||||
{{ quesApplication }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="text-center py-[26rpx] text-[#1580FF] text-[36rpx] font-medium start-border"
|
||||
@click="show = false"
|
||||
>
|
||||
开始答题
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</MessageBox>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import MessageBox from '@/chart-sub/components/messageBox/MessageBox.vue'
|
||||
import {
|
||||
getAssessmentQuestions,
|
||||
getBusScaleDescription,
|
||||
saveBusScaleAnswer,
|
||||
} from '@/service'
|
||||
|
||||
import Checkbox from '@/chart-sub/components/check-group/Checkbox.vue'
|
||||
import CheckboxGroup from '@/chart-sub/components/check-group/CheckboxGroup.vue'
|
||||
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useRouterDetail } from './useRouterDetail'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: '',
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const pageName = ref('')
|
||||
const pageId = ref(-1)
|
||||
|
||||
const show = ref(true)
|
||||
|
||||
const handleBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const checkedList = ref([])
|
||||
const answerMap = new Map()
|
||||
const disableBtn = ref(true)
|
||||
const handleCheckChange = (value: any[]) => {
|
||||
if (value.length === 0) {
|
||||
disableBtn.value = true
|
||||
return
|
||||
} else {
|
||||
disableBtn.value = false
|
||||
}
|
||||
|
||||
if (questionType.value === 0 && value.length > 0) {
|
||||
// 单选题就点完跳下一题
|
||||
const timer = setTimeout(() => {
|
||||
handleNextQuestion()
|
||||
clearTimeout(timer)
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
|
||||
const calcScore = () => {
|
||||
let _type = questions.value[currentIndex.value].type
|
||||
let _name = questions.value[currentIndex.value].answer[0].tag
|
||||
let _options = questions.value[currentIndex.value].answer.filter((answer) => {
|
||||
return checkedList.value.includes(answer.key)
|
||||
})
|
||||
|
||||
if (answerMap.has(_type)) {
|
||||
let val = answerMap.get(_type)
|
||||
val.value += _options.reduce((count, cur) => (count = count + Number(cur.value)), 0)
|
||||
answerMap.set(_type, val)
|
||||
} else {
|
||||
answerMap.set(_type, {
|
||||
name: _name,
|
||||
value: _options.reduce((count, cur) => (count = count + Number(cur.value)), 0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 当前显示的卡片索引
|
||||
const currentIndex = ref(0)
|
||||
const questions = ref([])
|
||||
const questionType = ref(-1)
|
||||
|
||||
const questionName = ref('')
|
||||
const useTime = ref('')
|
||||
const quesApplication = ref('')
|
||||
const isLoading = ref(false)
|
||||
|
||||
onLoad((options) => {
|
||||
pageName.value = options.name
|
||||
pageId.value = options.id
|
||||
getAssessmentQuestions({query:{ ScaleId: pageId.value }}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
let result = res.result as {
|
||||
name: string
|
||||
description: string
|
||||
go: number
|
||||
questionsType: number
|
||||
scaleQuestions: any[]
|
||||
}
|
||||
questions.value = result.scaleQuestions
|
||||
questionType.value = result.questionsType
|
||||
}
|
||||
})
|
||||
|
||||
getBusScaleDescription({query:{ ScaleId: pageId.value }}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
let result = (
|
||||
res.result as {
|
||||
busScaleDescriptions: any[]
|
||||
}
|
||||
).busScaleDescriptions[0]
|
||||
questionName.value = result.title
|
||||
quesApplication.value = result.application
|
||||
useTime.value = result.usesTime
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const handleNextQuestion = () => {
|
||||
if (disableBtn.value) return
|
||||
disableBtn.value = true
|
||||
calcScore()
|
||||
checkedList.value = []
|
||||
if (currentIndex.value === questions.value.length - 1) {
|
||||
handleSubmit()
|
||||
} else {
|
||||
currentIndex.value++
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
let params = {
|
||||
customId: userStore.userInfo.wxId,
|
||||
scaleId: pageId.value,
|
||||
inputs: [],
|
||||
}
|
||||
let _inputs = []
|
||||
answerMap.forEach((value, key) => {
|
||||
_inputs.push({ type: key, name: value.name, value: value.value })
|
||||
})
|
||||
params.inputs = _inputs
|
||||
saveBusScaleAnswer({data:params}).then((res) => {
|
||||
isLoading.value = false
|
||||
let _result = res.result as {
|
||||
reportId: string
|
||||
type: number
|
||||
}
|
||||
if (res.code === 200) {
|
||||
// uni.navigateBack()
|
||||
useRouterDetail({ reportsId: _result.reportId, type: _result.type })
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 56%, #b3ebfc 100%);
|
||||
}
|
||||
|
||||
.custom-background {
|
||||
background: linear-gradient(180deg, #d8e7fc 0%, rgba(255, 255, 255, 0) 20%);
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.start-border {
|
||||
border-top: 1rpx solid #dedede;
|
||||
}
|
||||
|
||||
.next-question {
|
||||
background: #1580ff;
|
||||
border-radius: 16rpx;
|
||||
height: 88rpx;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.custom-checkbox) {
|
||||
// 定义默认变量
|
||||
|
||||
.checkbox {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f6f7f8;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #303030;
|
||||
border: 2rpx solid #f6f7f8;
|
||||
}
|
||||
.checkbox__icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.checkbox-group) {
|
||||
display: grid !important;
|
||||
gap: 16rpx;
|
||||
// padding: 32rpx 16rpx 16rpx;
|
||||
}
|
||||
|
||||
:deep(.checkbox-active) {
|
||||
background: rgba(21, 128, 255, 0.05) !important;
|
||||
|
||||
border: 2rpx solid #1580ff !important;
|
||||
border: 2rpx solid #1580ff;
|
||||
.checkbox__label {
|
||||
color: #2a82e4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.question-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
|
||||
|
||||
// 如果 safe-area-inset-bottom 为 0,则 padding-bottom 为 20rpx
|
||||
@if env(safe-area-inset-bottom) == 0 {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.card-container {
|
||||
background: rgb(140, 199, 245);
|
||||
width: calc(100% - 60rpx);
|
||||
border-radius: 20rpx;
|
||||
position: absolute;
|
||||
transform: translate3d(100vw, 0, 0);
|
||||
margin: 0 30rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.current-card {
|
||||
background: #fff;
|
||||
z-index: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
wx-button[disabled]:not([type]) {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
export const useRouterDetail = (item: { reportsId: string; type: number }) => {
|
||||
// type=0 兴趣测评报告
|
||||
// =1 性格测评报告
|
||||
// =2 能力测评
|
||||
// =3 学生考试考虑
|
||||
// =4 学习风格
|
||||
// =5 学习技能
|
||||
// =6 SAS
|
||||
// =7 SDS
|
||||
// =8 SCL-90
|
||||
// =9 MHT
|
||||
/// =-1 价值观
|
||||
/// =-2 留学咨询
|
||||
let url = ''
|
||||
|
||||
if (item.type === 0) {
|
||||
url = `/chart-sub/evaluate/academicReport/interestReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 1) {
|
||||
url = `/chart-sub/evaluate/academicReport/characterReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 2) {
|
||||
url = `/chart-sub/evaluate/academicReport/capabilityReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === -1) {
|
||||
url = `/chart-sub/evaluate/academicReport/opinionAboutReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 6) {
|
||||
url = `/chart-sub/evaluate/psychologicalReport/sasReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 7) {
|
||||
url = `/chart-sub/evaluate/psychologicalReport/sdsReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 9) {
|
||||
// url = `/chart-sub/evaluate/psychologicalReport/mhtReport?id=${item.reportsId}&type=${item.type}`
|
||||
uni.showToast({
|
||||
title: '开发中....',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
} else if (item.type === 4) {
|
||||
url = `/chart-sub/evaluate/studyReport/learnStudyReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 5) {
|
||||
url = `/chart-sub/evaluate/studyReport/learnSkillReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else if (item.type === 3) {
|
||||
url = `/chart-sub/evaluate/studyReport/anxietyReport?id=${item.reportsId}&type=${item.type}`
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '开发中....',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export const handleBack = () => {
|
||||
const pages = getCurrentPages()
|
||||
console.log(pages[pages.length - 2].route)
|
||||
|
||||
if (
|
||||
pages.length > 1 &&
|
||||
pages[pages.length - 2].route === 'pages-sub/me/evaluation'
|
||||
) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.switchTab({ url: '/pages/evaluation/index' })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar safeAreaInsetTop :bordered="false" leftArrow @clickLeft="handleBack" bg-color="transparent">
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">MHT心理健康自评</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[30rpx] mx-[24rpx]">
|
||||
<StatusCard />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import StatusCard from '../components/StatusCard.vue'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
linChart: [],
|
||||
reportItems: [],
|
||||
hTag: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// linChart: any[]
|
||||
// reportItems: any[]
|
||||
// hTag: string
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">SAS焦虑测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[30rpx] mx-[24rpx]">
|
||||
<StatusCard
|
||||
:score="score"
|
||||
:rules="anxietyRules"
|
||||
tip="结果只做参考,不能准确判断是否有焦虑症。"
|
||||
:level="level"
|
||||
:description="studyRecord.description"
|
||||
:tagName="studyRecord.tagName"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt-[30rpx] mx-[24rpx]">
|
||||
<SuggestionCard />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import StatusCard from '../components/StatusCard.vue'
|
||||
import SuggestionCard from '../components/SuggestionCard.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
const anxietyRules = [
|
||||
{
|
||||
label: '正常范围',
|
||||
range: '<50分',
|
||||
color: '#00B281',
|
||||
},
|
||||
{
|
||||
label: '轻度焦虑',
|
||||
range: '50-59分',
|
||||
color: '#F8B801',
|
||||
},
|
||||
{
|
||||
label: '中度焦虑',
|
||||
range: '60-69分',
|
||||
color: '#F79C33',
|
||||
},
|
||||
{
|
||||
label: '重度焦虑',
|
||||
range: '≥70分',
|
||||
color: '#F5663E',
|
||||
},
|
||||
]
|
||||
|
||||
const score = ref(0)
|
||||
const level = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
result: '',
|
||||
tagName: '',
|
||||
})
|
||||
|
||||
const calcLevel = (val: string) => {
|
||||
let _s = JSON.parse(val)
|
||||
if (_s[0].Total >= 70) {
|
||||
return 3
|
||||
} else if (_s[0].Total >= 60) {
|
||||
return 2
|
||||
} else if (_s[0].Total >= 50) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// result: string
|
||||
// tagName: string
|
||||
// }
|
||||
// level.value = calcLevel(studyRecord.value.result)
|
||||
// score.value = JSON.parse(studyRecord.value.result)[0].Total
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">SDS抑郁测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[30rpx] mx-[24rpx]">
|
||||
<StatusCard
|
||||
:score="score"
|
||||
:rules="depressionRules"
|
||||
tip="结果只做参考,不能准确判断是否有抑郁症。"
|
||||
:level="level"
|
||||
:description="studyRecord.description"
|
||||
:tagName="studyRecord.tagName"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt-[30rpx] mx-[24rpx]">
|
||||
<SuggestionCard />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import StatusCard from '../components/StatusCard.vue'
|
||||
import SuggestionCard from '../components/SuggestionCard.vue'
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
// 示例规则2:抑郁评估
|
||||
const depressionRules = [
|
||||
{
|
||||
label: '正常范围',
|
||||
range: '<52分',
|
||||
color: '#00B281',
|
||||
},
|
||||
{
|
||||
label: '轻度抑郁',
|
||||
range: '53-61分',
|
||||
color: '#F8B801',
|
||||
},
|
||||
{
|
||||
label: '中度抑郁',
|
||||
range: '62-71分',
|
||||
color: '#F79C33',
|
||||
},
|
||||
{
|
||||
label: '重度抑郁',
|
||||
range: '≥72分',
|
||||
color: '#F5663E',
|
||||
},
|
||||
]
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
result: '',
|
||||
tagName: '',
|
||||
total: '',
|
||||
})
|
||||
|
||||
const calcLevel = (val: string) => {
|
||||
let _s = +val
|
||||
if (_s >= 72) {
|
||||
return 3
|
||||
} else if (_s >= 62) {
|
||||
return 2
|
||||
} else if (_s >= 53) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const score = ref(0)
|
||||
const level = ref(0)
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// result: string
|
||||
// tagName: string
|
||||
// total: string
|
||||
// }
|
||||
// level.value = calcLevel(studyRecord.value.total)
|
||||
// score.value = JSON.parse(studyRecord.value.total)
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">学习技能报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[60rpx] mx-[24rpx]">
|
||||
<StatusCard
|
||||
:score="score"
|
||||
:rules="anxietyRules"
|
||||
tip="测评结果只做参考。"
|
||||
:level="level"
|
||||
:description="studyRecord.description"
|
||||
:tagName="studyRecord.tagName"
|
||||
/>
|
||||
</view>
|
||||
<view class="mx-[24rpx]">
|
||||
<LearnSkillSuggestion
|
||||
v-for="(item, index) in suggestions"
|
||||
:key="index"
|
||||
:items="item.items"
|
||||
:title="item.title"
|
||||
></LearnSkillSuggestion>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import StatusCard from '../components/StatusCard.vue'
|
||||
import LearnSkillSuggestion from '../components/LearnSkillSuggestion.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
const anxietyRules = [
|
||||
{
|
||||
label: '很差',
|
||||
range: '≤80分',
|
||||
color: '#F5663E',
|
||||
},
|
||||
{
|
||||
label: '较差',
|
||||
range: '81-104分',
|
||||
color: '#F8B801',
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
range: '105-136分',
|
||||
color: '#F3A953',
|
||||
},
|
||||
{
|
||||
label: '较好',
|
||||
range: '137-160分',
|
||||
color: '#55E5C5',
|
||||
},
|
||||
{
|
||||
label: '优秀',
|
||||
range: '≥161分',
|
||||
color: '#00B281',
|
||||
},
|
||||
]
|
||||
|
||||
const score = ref(0)
|
||||
const level = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
result: '',
|
||||
tagName: '',
|
||||
suggestions: '',
|
||||
})
|
||||
|
||||
const calcLevel = (val: string) => {
|
||||
let _s = JSON.parse(val)
|
||||
if (_s[0].Total >= 161) {
|
||||
return 4
|
||||
} else if (_s[0].Total >= 137) {
|
||||
return 3
|
||||
} else if (_s[0].Total >= 105) {
|
||||
return 2
|
||||
} else if (_s[0].Total >= 81) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const suggestions = ref([])
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// result: string
|
||||
// tagName: string
|
||||
// suggestions: string
|
||||
// }
|
||||
// level.value = calcLevel(studyRecord.value.result)
|
||||
// score.value = JSON.parse(studyRecord.value.result)[0].Total
|
||||
// suggestions.value = JSON.parse(studyRecord.value.suggestions).succestions
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">Solomon学习风格报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto relative">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[60rpx] mx-[24rpx]">
|
||||
<LearnStyleChart :pic-data="chartData" :parsing="parsing" />
|
||||
</view>
|
||||
<view class="mx-[24rpx]">
|
||||
<LearnStudySuggestion
|
||||
:title="item.name"
|
||||
:item="item"
|
||||
v-for="(item, index) in suggestions"
|
||||
:key="index"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部AI智能顾问 -->
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import LearnStyleChart from '../components/studyChart/LearnStyleChart.vue'
|
||||
import LearnStudySuggestion from '../components/LearnStudySuggestion.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
result: '',
|
||||
tagName: '',
|
||||
suggestions: '',
|
||||
})
|
||||
|
||||
const chartData = ref([])
|
||||
const parsing = ref('')
|
||||
const suggestions = ref([])
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// result: string
|
||||
// tagName: string
|
||||
// suggestions: string
|
||||
// }
|
||||
// chartData.value = JSON.parse(studyRecord.value.result)
|
||||
// parsing.value = studyRecord.value.suggestions
|
||||
// suggestions.value = JSON.parse(studyRecord.value.description)
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
|
||||
|
||||
<template>
|
||||
<view :scroll-y="true" class="flex flex-col h-screen relative custom-bg">
|
||||
<Navbar
|
||||
safeAreaInsetTop
|
||||
:bordered="false"
|
||||
leftArrow
|
||||
@clickLeft="handleBack"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template #title>
|
||||
<text class="text-[#1F2329] text-[36rpx] font-medium text-[#fff]">考试焦虑测评报告</text>
|
||||
</template>
|
||||
</Navbar>
|
||||
|
||||
<view class="flex-1 overflow-auto flex flex-col">
|
||||
<view class="flex flex-col flex-1 overflow-auto pb-[20rpx]">
|
||||
<!-- 顶部卡片 -->
|
||||
<view class="mt-[60rpx] mx-[24rpx]">
|
||||
<StatusCard
|
||||
:score="score"
|
||||
:rules="anxietyRules"
|
||||
tip="测评结果只做参考。"
|
||||
:level="level"
|
||||
:description="studyRecord.description"
|
||||
:tagName="studyRecord.tagName"
|
||||
/>
|
||||
</view>
|
||||
<view class="mx-[24rpx]">
|
||||
<LearnSkillSuggestion
|
||||
v-for="(item, index) in suggestions"
|
||||
:key="index"
|
||||
:items="item.items"
|
||||
:title="item.title"
|
||||
></LearnSkillSuggestion>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<AiFooter :pageId="pageId" :pageType="pageType" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/chart-sub/components/navbar/Navbar.vue'
|
||||
import StatusCard from '../components/StatusCard.vue'
|
||||
import LearnSkillSuggestion from '../components/LearnSkillSuggestion.vue'
|
||||
import AiFooter from '../components/AiFooter.vue'
|
||||
import { getCustomScaleExplains } from '@/service'
|
||||
import { handleBack } from '../hooks/useEvaluateBack'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
definePage({
|
||||
style: {
|
||||
navigationStyle: 'custom',
|
||||
transparentTitle: 'always',
|
||||
navigationBarTitleText: ''
|
||||
},
|
||||
excludeLoginPath: false,
|
||||
})
|
||||
// #endif
|
||||
|
||||
const pageType = ref(0)
|
||||
const pageId = ref(0)
|
||||
const anxietyRules = [
|
||||
{
|
||||
label: '较低水平',
|
||||
range: '≤12分',
|
||||
color: '#00B281',
|
||||
},
|
||||
{
|
||||
label: '中等程度',
|
||||
range: '12-20分',
|
||||
color: '#F8B801',
|
||||
},
|
||||
{
|
||||
label: '较高水平',
|
||||
range: '≥21分',
|
||||
color: '#F5663E',
|
||||
},
|
||||
]
|
||||
|
||||
const score = ref(0)
|
||||
const level = ref(0)
|
||||
|
||||
const studyRecord = ref({
|
||||
description: '',
|
||||
title: '',
|
||||
result: '',
|
||||
tagName: '',
|
||||
suggestions: '',
|
||||
})
|
||||
|
||||
const calcLevel = (val: string) => {
|
||||
let _s = JSON.parse(val)
|
||||
if (_s[0].Total >= 21) {
|
||||
return 2
|
||||
} else if (_s[0].Total >= 12) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const suggestions = ref([])
|
||||
|
||||
onLoad((options) => {
|
||||
pageType.value = +options.type
|
||||
pageId.value = options.id
|
||||
|
||||
// getCustomScaleExplains({ CustomScaleId: pageId.value }).then((resp) => {
|
||||
// if (resp.code === 200) {
|
||||
// studyRecord.value = resp.result as {
|
||||
// description: string
|
||||
// title: string
|
||||
// result: string
|
||||
// tagName: string
|
||||
// suggestions: string
|
||||
// }
|
||||
// level.value = calcLevel(studyRecord.value.result)
|
||||
// score.value = JSON.parse(studyRecord.value.result)[0].Total
|
||||
// suggestions.value = JSON.parse(studyRecord.value.suggestions)
|
||||
// }
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-bg {
|
||||
background: linear-gradient(184deg, #0d79fc 0%, #2186fc 100%);
|
||||
}
|
||||
:deep(.icon-class) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
width: 162rpx;
|
||||
height: 162rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx dashed;
|
||||
border-color: #05d69c transparent transparent transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
.custom-check-group {
|
||||
:deep(.checkbox-group) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16rpx;
|
||||
padding: 24rpx 24rpx 36rpx;
|
||||
}
|
||||
|
||||
:deep(.custom-checkbox) {
|
||||
--checkbox-width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.custom-background {
|
||||
background-image: linear-gradient(
|
||||
173deg,
|
||||
rgb(177, 221, 250) 0,
|
||||
rgb(177, 221, 250) 13%,
|
||||
rgba(255, 255, 255, 1) 80%,
|
||||
rgba(255, 255, 255, 1) 100%
|
||||
);
|
||||
background-position: 50% 50%;
|
||||
background-origin: padding-box;
|
||||
background-clip: border-box;
|
||||
background-size: auto auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
:deep(.z-tabs-bottom) {
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.title-bar {
|
||||
transform: translateX(-50%) skewX(-20deg);
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
## 1.0.0(2025-02-27)
|
||||
- fix: 修复uniappx微信小程序不显示问题
|
||||
## 0.9.9(2025-02-24)
|
||||
- feat: 更新v4
|
||||
## 0.9.8(2024-12-20)
|
||||
- fix: 修复 APP 无法放大问题
|
||||
## 0.9.7(2024-12-02)
|
||||
- feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达到横屏效果。
|
||||
- feat: 支持uniapp x 微信小程序
|
||||
## 0.9.6(2024-07-23)
|
||||
- fix: 修复 uni is not defined
|
||||
## 0.9.5(2024-07-19)
|
||||
- chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。
|
||||
## 0.9.4(2024-07-18)
|
||||
- chore: 更新文档
|
||||
## 0.9.3(2024-07-16)
|
||||
- feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3`
|
||||
## 0.9.2(2024-07-12)
|
||||
- chore: 删除多余文件
|
||||
## 0.9.1(2024-07-12)
|
||||
- fix: 修复 安卓5不显示图表问题
|
||||
## 0.9.0(2024-06-13)
|
||||
- chore: 合并nvue和uvue
|
||||
## 0.8.9(2024-05-19)
|
||||
- chore: 更新文档
|
||||
## 0.8.8(2024-05-13)
|
||||
- chore: 更新文档和uvue示例
|
||||
## 0.8.7(2024-04-26)
|
||||
- fix: uniapp x需要HBX 4.13以上
|
||||
## 0.8.6(2024-04-10)
|
||||
- feat: 支持 uniapp x ios
|
||||
## 0.8.5(2024-04-03)
|
||||
- fix: 修复 nvue `reset`传值不生效问题
|
||||
- feat: 支持 uniapp x web
|
||||
## 0.8.4(2024-01-27)
|
||||
- chore: 更新文档
|
||||
## 0.8.3(2024-01-21)
|
||||
- chore: 更新文档
|
||||
## 0.8.2(2024-01-21)
|
||||
- feat: 支持 `uvue`
|
||||
## 0.8.1(2023-08-24)
|
||||
- fix: app 的`touch`事件为`object` 导致无法显示 `tooltip`
|
||||
## 0.8.0(2023-08-22)
|
||||
- fix: 离屏 报错问题
|
||||
- fix: 微信小程序PC无法使用事件
|
||||
- chore: 更新文档
|
||||
## 0.7.9(2023-07-29)
|
||||
- chore: 更新文档
|
||||
## 0.7.8(2023-07-29)
|
||||
- fix: 离屏 报错问题
|
||||
## 0.7.7(2023-07-27)
|
||||
- chore: 更新文档
|
||||
- chore: lime-echart 里的示例使用自定tooltips
|
||||
- feat: 对支持离屏的使用离屏创建(微信、字节、支付宝)
|
||||
## 0.7.6(2023-06-30)
|
||||
- fix: vue3 报`width`的错
|
||||
## 0.7.5(2023-05-25)
|
||||
- chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例
|
||||
## 0.7.4(2023-05-22)
|
||||
- chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议
|
||||
## 0.7.3(2023-05-16)
|
||||
- chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明
|
||||
## 0.7.2(2023-05-16)
|
||||
- chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明
|
||||
## 0.7.1(2023-04-26)
|
||||
- chore: 更新demo,使用`lime-echart`这个标签即可查看示例
|
||||
- chore:微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
|
||||
## 0.7.0(2023-04-24)
|
||||
- fix: 修复`setAttribute is not a function`
|
||||
## 0.6.9(2023-04-15)
|
||||
- chore: 更新文档,vue3请使用echarts esm的包
|
||||
## 0.6.8(2023-03-22)
|
||||
- feat: mac pc无法使用canvas 2d
|
||||
## 0.6.7(2023-03-17)
|
||||
- feat: 更新文档
|
||||
## 0.6.6(2023-03-17)
|
||||
- feat: 微信小程序PC已经支持canvas 2d,故去掉判断PC
|
||||
## 0.6.5(2022-11-03)
|
||||
- fix: 某些手机touches为对象,导致无法交互。
|
||||
## 0.6.4(2022-10-28)
|
||||
- fix: 优化点击事件的触发条件
|
||||
## 0.6.3(2022-10-26)
|
||||
- fix: 修复 dataZoom 拖动问题
|
||||
## 0.6.2(2022-10-23)
|
||||
- fix: 修复 飞书小程序 尺寸问题
|
||||
## 0.6.1(2022-10-19)
|
||||
- fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG,不兼容火狐!
|
||||
- feat: showLoading 增加传参
|
||||
## 0.6.0(2022-09-16)
|
||||
- feat: 增加PC的mousewheel事件
|
||||
## 0.5.4(2022-09-16)
|
||||
- fix: 修复 nvue 动态数据不显示问题
|
||||
## 0.5.3(2022-09-16)
|
||||
- feat: 增加enableHover属性, 在PC端时当鼠标进入显示tooltip,不必按下。
|
||||
- chore: 更新文档
|
||||
## 0.5.2(2022-09-16)
|
||||
- feat: 增加enableHover属性, 在PC端时当鼠标进入显示tooltip,不必按下。
|
||||
## 0.5.1(2022-09-16)
|
||||
- fix: 修复nvue报错
|
||||
## 0.5.0(2022-09-15)
|
||||
- feat: init(echarts, theme?:string, opts?:{}, callback: function(chart))
|
||||
## 0.4.8(2022-09-11)
|
||||
- feat: 增加 @finished
|
||||
## 0.4.7(2022-08-24)
|
||||
- chore: 去掉 stylus
|
||||
## 0.4.6(2022-08-24)
|
||||
- feat: 增加 beforeDelay
|
||||
## 0.4.5(2022-08-12)
|
||||
- chore: 更新文档
|
||||
## 0.4.4(2022-08-12)
|
||||
- fix: 修复 resize 无参数时报错
|
||||
## 0.4.3(2022-08-07)
|
||||
# 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。
|
||||
# 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。
|
||||
# 如果你确认要下载,麻烦看完文档。还有请注意插件是让echarts在uniapp能运行,API 配置请自行去官网查阅!
|
||||
# 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件,uchart的作者人很好,也热情。
|
||||
# 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。
|
||||
# 为了心情愉悦,本人已经使用插件屏蔽差评。
|
||||
- chore: 更新文档
|
||||
## 0.4.2(2022-07-20)
|
||||
- feat: 增加 resize
|
||||
## 0.4.1(2022-06-07)
|
||||
- fix: 修复 canvasToTempFilePath 不生效问题
|
||||
## 0.4.0(2022-06-04)
|
||||
- chore 为了词云 增加一个canvas 标签
|
||||
- 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430)
|
||||
## 0.3.9(2022-06-02)
|
||||
- chore: 更新文档
|
||||
- tips: lines 不支持 `trailLength`
|
||||
## 0.3.8(2022-05-31)
|
||||
- fix: 修复 因mouse事件冲突tooltip跳动问题
|
||||
## 0.3.7(2022-05-26)
|
||||
- chore: 更新文档
|
||||
- chore: 设置默认宽高300px
|
||||
- fix: 修复 vue3 微信小程序 拖影BUG
|
||||
- chore: 支持PC
|
||||
## 0.3.5(2022-04-28)
|
||||
- chore: 更新使用方式
|
||||
- 🔔 必须使用hbuilderx 3.4.8-alpha以上
|
||||
## 0.3.4(2021-08-03)
|
||||
- chore: 增加 setOption的参数值
|
||||
## 0.3.3(2021-07-22)
|
||||
- fix: 修复 径向渐变报错的问题
|
||||
## 0.3.2(2021-07-09)
|
||||
- chore: 统一命名规范,无须主动引入组件
|
||||
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
|
||||
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.3.1(2021-06-21)
|
||||
- fix: 修复 app-nvue ios is-enable 无效的问题
|
||||
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
|
||||
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.3.0(2021-06-14)
|
||||
- fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题
|
||||
- 目前 头条系小程序 2d 无法在开发工具上预览,划动图表页面无法滚动,axisLabel 字体颜色无法更改,建议使用非2d。
|
||||
## 0.2.9(2021-06-06)
|
||||
- fix: 修复 头条系小程序 2d 放大的BUG
|
||||
- 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。
|
||||
## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.2.8(2021-05-19)
|
||||
- fix: 修复 微信小程序 PC 显示过大的问题
|
||||
## 0.2.7(2021-05-19)
|
||||
- fix: 修复 微信小程序 PC 不显示问题
|
||||
## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.2.6(2021-05-14)
|
||||
- feat: 支持 `image`
|
||||
- feat: props 增加 `ec.clear`,更新时是否先删除图表样式
|
||||
- feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动
|
||||
- feat: props 增加 `webviewStyles` ,webview 的样式, 仅nvue有效
|
||||
## 0.2.5(2021-05-13)
|
||||
- docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它
|
||||
## 0.2.4(2021-05-12)
|
||||
- fix: 修复 百度平台 多个图表ctx 和 渐变色 bug
|
||||
- ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.2.3(2021-05-10)
|
||||
- feat: 增加 `canvasToTempFilePath` 方法,用于生成图片
|
||||
```js
|
||||
this.$refs.chart.canvasToTempFilePath({success: (res) => {
|
||||
console.log('tempFilePath:', res.tempFilePath)
|
||||
}})
|
||||
```
|
||||
## 0.2.2(2021-05-10)
|
||||
- feat: 增加 `dispose` 方法,用于销毁实例
|
||||
- feat: 增加 `isClickable` 是否派发点击
|
||||
- feat: 实验性的支持 `nvue` 使用要慎重考虑
|
||||
- ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.2.1(2021-05-06)
|
||||
- fix:修复 微信小程序 json 报错
|
||||
- chore: `reset` 更改为 `setChart`
|
||||
- feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法
|
||||
```html
|
||||
<l-echart ref="chart" is-enable />
|
||||
```
|
||||
```js
|
||||
// 显示加载
|
||||
this.$refs.chart.showLoading()
|
||||
// 使用实例回调
|
||||
this.$refs.chart.setChart(chart => ...code)
|
||||
// 直接设置图表配置
|
||||
this.$refs.chart.setOption(data)
|
||||
```
|
||||
## 0.2.0(2021-05-05)
|
||||
- fix:修复 头条 百度 偏移的问题
|
||||
- docs: 更新文档
|
||||
## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
|
||||
## 0.1.0(2021-05-02)
|
||||
- chore: 第一次上传,基本全端兼容,使用方法与官网一致。
|
||||
- 已知BUG:非2d 无法使用背景色,已反馈官方
|
||||
- 已知BUG:头条 百度 有许些偏移
|
||||
- 后期计划:兼容nvue
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
import {getDeviceInfo} from './utils';
|
||||
|
||||
const cacheChart = {}
|
||||
const fontSizeReg = /([\d\.]+)px/;
|
||||
class EventEmit {
|
||||
constructor() {
|
||||
this.__events = {};
|
||||
}
|
||||
on(type, listener) {
|
||||
if (!type || !listener) {
|
||||
return;
|
||||
}
|
||||
const events = this.__events[type] || [];
|
||||
events.push(listener);
|
||||
this.__events[type] = events;
|
||||
}
|
||||
emit(type, e) {
|
||||
if (type.constructor === Object) {
|
||||
e = type;
|
||||
type = e && e.type;
|
||||
}
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
const events = this.__events[type];
|
||||
if (!events || !events.length) {
|
||||
return;
|
||||
}
|
||||
events.forEach((listener) => {
|
||||
listener.call(this, e);
|
||||
});
|
||||
}
|
||||
off(type, listener) {
|
||||
const __events = this.__events;
|
||||
const events = __events[type];
|
||||
if (!events || !events.length) {
|
||||
return;
|
||||
}
|
||||
if (!listener) {
|
||||
delete __events[type];
|
||||
return;
|
||||
}
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
if (events[i] === listener) {
|
||||
events.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class Image {
|
||||
constructor() {
|
||||
this.currentSrc = null
|
||||
this.naturalHeight = 0
|
||||
this.naturalWidth = 0
|
||||
this.width = 0
|
||||
this.height = 0
|
||||
this.tagName = 'IMG'
|
||||
}
|
||||
set src(src) {
|
||||
this.currentSrc = src
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
this.naturalWidth = this.width = res.width
|
||||
this.naturalHeight = this.height = res.height
|
||||
this.onload()
|
||||
},
|
||||
fail: () => {
|
||||
this.onerror()
|
||||
}
|
||||
})
|
||||
}
|
||||
get src() {
|
||||
return this.currentSrc
|
||||
}
|
||||
}
|
||||
class OffscreenCanvas {
|
||||
constructor(ctx, com, canvasId) {
|
||||
this.tagName = 'canvas'
|
||||
this.com = com
|
||||
this.canvasId = canvasId
|
||||
this.ctx = ctx
|
||||
}
|
||||
set width(w) {
|
||||
this.com.offscreenWidth = w
|
||||
}
|
||||
set height(h) {
|
||||
this.com.offscreenHeight = h
|
||||
}
|
||||
get width() {
|
||||
return this.com.offscreenWidth || 0
|
||||
}
|
||||
get height() {
|
||||
return this.com.offscreenHeight || 0
|
||||
}
|
||||
getContext(type) {
|
||||
return this.ctx
|
||||
}
|
||||
getImageData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.com.$nextTick(() => {
|
||||
uni.canvasGetImageData({
|
||||
x:0,
|
||||
y:0,
|
||||
width: this.com.offscreenWidth,
|
||||
height: this.com.offscreenHeight,
|
||||
canvasId: this.canvasId,
|
||||
success: (res) => {
|
||||
resolve(res)
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
},
|
||||
}, this.com)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
export class Canvas {
|
||||
constructor(ctx, com, isNew, canvasNode={}) {
|
||||
cacheChart[com.canvasId] = {ctx}
|
||||
this.canvasId = com.canvasId;
|
||||
this.chart = null;
|
||||
this.isNew = isNew
|
||||
this.tagName = 'canvas'
|
||||
this.canvasNode = canvasNode;
|
||||
this.com = com;
|
||||
if (!isNew) {
|
||||
this._initStyle(ctx)
|
||||
}
|
||||
this._initEvent();
|
||||
this._ee = new EventEmit()
|
||||
}
|
||||
getContext(type) {
|
||||
if (type === '2d') {
|
||||
return this.ctx;
|
||||
}
|
||||
}
|
||||
setAttribute(key, value) {
|
||||
if(key === 'aria-label') {
|
||||
this.com['ariaLabel'] = value
|
||||
}
|
||||
}
|
||||
setChart(chart) {
|
||||
this.chart = chart;
|
||||
}
|
||||
createOffscreenCanvas(param){
|
||||
if(!this.children) {
|
||||
this.com.isOffscreenCanvas = true
|
||||
this.com.offscreenWidth = param.width||300
|
||||
this.com.offscreenHeight = param.height||300
|
||||
const com = this.com
|
||||
const canvasId = this.com.offscreenCanvasId
|
||||
const context = uni.createCanvasContext(canvasId, this.com)
|
||||
this._initStyle(context)
|
||||
this.children = new OffscreenCanvas(context, com, canvasId)
|
||||
}
|
||||
return this.children
|
||||
}
|
||||
appendChild(child) {
|
||||
console.log('child', child)
|
||||
}
|
||||
dispatchEvent(type, e) {
|
||||
if(typeof type == 'object') {
|
||||
this._ee.emit(type.type, type);
|
||||
} else {
|
||||
this._ee.emit(type, e);
|
||||
}
|
||||
return true
|
||||
}
|
||||
attachEvent() {
|
||||
}
|
||||
detachEvent() {
|
||||
}
|
||||
addEventListener(type, listener) {
|
||||
this._ee.on(type, listener)
|
||||
}
|
||||
removeEventListener(type, listener) {
|
||||
this._ee.off(type, listener)
|
||||
}
|
||||
_initCanvas(zrender, ctx) {
|
||||
// zrender.util.getContext = function() {
|
||||
// return ctx;
|
||||
// };
|
||||
// zrender.util.$override('measureText', function(text, font) {
|
||||
// ctx.font = font || '12px sans-serif';
|
||||
// return ctx.measureText(text, font);
|
||||
// });
|
||||
}
|
||||
_initStyle(ctx, child) {
|
||||
const styles = [
|
||||
'fillStyle',
|
||||
'strokeStyle',
|
||||
'fontSize',
|
||||
'globalAlpha',
|
||||
'opacity',
|
||||
'textAlign',
|
||||
'textBaseline',
|
||||
'shadow',
|
||||
'lineWidth',
|
||||
'lineCap',
|
||||
'lineJoin',
|
||||
'lineDash',
|
||||
'miterLimit',
|
||||
// #ifdef H5
|
||||
'font',
|
||||
// #endif
|
||||
];
|
||||
const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
|
||||
styles.forEach(style => {
|
||||
Object.defineProperty(ctx, style, {
|
||||
set: value => {
|
||||
// #ifdef H5
|
||||
if (style === 'font' && fontSizeReg.test(value)) {
|
||||
const match = fontSizeReg.exec(value);
|
||||
ctx.setFontSize(match[1]);
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
|
||||
if (style === 'opacity') {
|
||||
ctx.setGlobalAlpha(value)
|
||||
return;
|
||||
}
|
||||
if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
|
||||
// #ifdef H5 || APP-PLUS || MP-BAIDU
|
||||
if(typeof value == 'object') {
|
||||
if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
|
||||
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
|
||||
}
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
if(colorReg.test(value)) {
|
||||
value = value.replace(colorReg, '#$1$1$2$2$3$3')
|
||||
}
|
||||
// #endif
|
||||
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if(!this.isNew && !child) {
|
||||
ctx.uniDrawImage = ctx.drawImage
|
||||
ctx.drawImage = (...a) => {
|
||||
a[0] = a[0].src
|
||||
ctx.uniDrawImage(...a)
|
||||
}
|
||||
}
|
||||
if(!ctx.createRadialGradient) {
|
||||
ctx.createRadialGradient = function() {
|
||||
return ctx.createCircularGradient(...[...arguments].slice(-3))
|
||||
};
|
||||
}
|
||||
// 字节不支持
|
||||
if (!ctx.strokeText) {
|
||||
ctx.strokeText = (...a) => {
|
||||
ctx.fillText(...a)
|
||||
}
|
||||
}
|
||||
|
||||
// 钉钉不支持 , 鸿蒙是异步
|
||||
if (!ctx.measureText || getDeviceInfo().osName == 'harmonyos') {
|
||||
ctx._measureText = ctx.measureText
|
||||
const strLen = (str) => {
|
||||
let len = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
|
||||
len++;
|
||||
} else {
|
||||
len += 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
ctx.measureText = (text, font) => {
|
||||
let fontSize = ctx?.state?.fontSize || 12;
|
||||
if (font) {
|
||||
fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
|
||||
}
|
||||
fontSize /= 2;
|
||||
let isBold = fontSize >= 16;
|
||||
const widthFactor = isBold ? 1.3 : 1;
|
||||
// ctx._measureText(text, (res) => {})
|
||||
return {
|
||||
width: strLen(text) * fontSize * widthFactor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initEvent(e) {
|
||||
this.event = {};
|
||||
const eventNames = [{
|
||||
wxName: 'touchStart',
|
||||
ecName: 'mousedown'
|
||||
}, {
|
||||
wxName: 'touchMove',
|
||||
ecName: 'mousemove'
|
||||
}, {
|
||||
wxName: 'touchEnd',
|
||||
ecName: 'mouseup'
|
||||
}, {
|
||||
wxName: 'touchEnd',
|
||||
ecName: 'click'
|
||||
}];
|
||||
|
||||
eventNames.forEach(name => {
|
||||
this.event[name.wxName] = e => {
|
||||
const touch = e.touches[0];
|
||||
this.chart.getZr().handler.dispatch(name.ecName, {
|
||||
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
|
||||
zrY: name.wxName === 'tap' ? touch.clientY : touch.y
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
set width(w) {
|
||||
this.canvasNode.width = w
|
||||
}
|
||||
set height(h) {
|
||||
this.canvasNode.height = h
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this.canvasNode.width || 0
|
||||
}
|
||||
get height() {
|
||||
return this.canvasNode.height || 0
|
||||
}
|
||||
get ctx() {
|
||||
return cacheChart[this.canvasId]['ctx'] || null
|
||||
}
|
||||
set chart(chart) {
|
||||
cacheChart[this.canvasId]['chart'] = chart
|
||||
}
|
||||
get chart() {
|
||||
return cacheChart[this.canvasId]['chart'] || null
|
||||
}
|
||||
}
|
||||
|
||||
export function dispatch(name, {x,y, wheelDelta}) {
|
||||
this.dispatch(name, {
|
||||
zrX: x,
|
||||
zrY: y,
|
||||
zrDelta: wheelDelta,
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () =>{}
|
||||
});
|
||||
}
|
||||
export function setCanvasCreator(echarts, {canvas, node}) {
|
||||
if(echarts && !echarts.registerPreprocessor) {
|
||||
return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式')
|
||||
}
|
||||
echarts.registerPreprocessor(option => {
|
||||
if (option && option.series) {
|
||||
if (option.series.length > 0) {
|
||||
option.series.forEach(series => {
|
||||
series.progressive = 0;
|
||||
});
|
||||
} else if (typeof option.series === 'object') {
|
||||
option.series.progressive = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
function loadImage(src, onload, onerror) {
|
||||
let img = null
|
||||
if(node && node.createImage) {
|
||||
img = node.createImage()
|
||||
img.onload = onload.bind(img);
|
||||
img.onerror = onerror.bind(img);
|
||||
img.src = src;
|
||||
return img
|
||||
} else {
|
||||
img = new Image()
|
||||
img.onload = onload.bind(img)
|
||||
img.onerror = onerror.bind(img);
|
||||
img.src = src
|
||||
return img
|
||||
}
|
||||
}
|
||||
if(echarts.setPlatformAPI) {
|
||||
echarts.setPlatformAPI({
|
||||
loadImage: canvas.setChart ? loadImage : null,
|
||||
createCanvas(){
|
||||
const key = 'createOffscreenCanvas'
|
||||
return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
|
||||
}
|
||||
})
|
||||
} else if(echarts.setCanvasCreator) {
|
||||
echarts.setCanvasCreator(() => {
|
||||
return canvas;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
<template>
|
||||
<!-- #ifdef APP -->
|
||||
<web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[customStyle]" :webview-styles="[webviewStyles]"
|
||||
src="/uni_modules/lime-echart/static/uvue.html?v=10112">
|
||||
</web-view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<div class="lime-echart" ref="chartRef"></div>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || APP-->
|
||||
<view class="lime-echart">
|
||||
<canvas style="width:100%; height:100%" v-if="canvasid" :id="canvasid" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
// @ts-nocheck
|
||||
import { getCurrentInstance, nextTick } from "vue";
|
||||
import { Echarts } from './uvue';
|
||||
// #ifdef WEB
|
||||
import { dispatch } from './canvas';
|
||||
// #endif
|
||||
// #ifndef APP || WEB
|
||||
import { Canvas, setCanvasCreator, dispatch } from './canvas';
|
||||
import { wrapTouch, convertTouchesToArray, devicePixelRatio, sleep, canIUseCanvas2d, getRect } from './utils';
|
||||
// #endif
|
||||
type EchartsResolve = (value : Echarts) => void
|
||||
defineOptions({
|
||||
name: 'l-echart'
|
||||
})
|
||||
const emits = defineEmits(['finished'])
|
||||
const props = defineProps({
|
||||
// #ifdef APP
|
||||
webviewStyles: {
|
||||
type: Object
|
||||
},
|
||||
customStyle: {
|
||||
type: Object
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
webviewStyles: {
|
||||
type: Object
|
||||
},
|
||||
customStyle: {
|
||||
type: [String, Object]
|
||||
},
|
||||
// #endif
|
||||
isDisableScroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isClickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
enableHover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 30
|
||||
}
|
||||
})
|
||||
const instance = getCurrentInstance()!;
|
||||
const canvasid = `lime-echart-${instance.uid}`
|
||||
const finished = ref(false)
|
||||
const map = [] as EchartsResolve[]
|
||||
const callbackMap = [] as EchartsResolve[]
|
||||
// let context = null as UniWebViewElement | null
|
||||
let chart = null as Echarts | null
|
||||
let chartRef = ref<UniWebViewElement | null>(null)
|
||||
|
||||
const trigger = () => {
|
||||
// #ifdef APP
|
||||
if (finished.value) {
|
||||
if (chart == null) {
|
||||
chart = new Echarts(chartRef.value!)
|
||||
}
|
||||
while (map.length > 0) {
|
||||
const resolve = map.pop() as EchartsResolve
|
||||
resolve(chart!)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
while (map.length > 0) {
|
||||
if (chart != null) {
|
||||
const resolve = map.pop() as EchartsResolve
|
||||
resolve(chart!)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
if (chart != null) {
|
||||
while (callbackMap.length > 0) {
|
||||
const callback = callbackMap.pop() as EchartsResolve
|
||||
callback(chart!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef APP
|
||||
const loaded = (event : UniWebViewLoadEvent) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
finished.value = true
|
||||
trigger()
|
||||
emits('finished')
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
const _next = () : boolean => {
|
||||
if (chart == null) {
|
||||
console.warn(`组件还未初始化,请先使用 init`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
const setOption = (option : UTSJSONObject) => {
|
||||
if (_next()) return
|
||||
chart!.setOption(option);
|
||||
}
|
||||
const showLoading = () => {
|
||||
if (_next()) return
|
||||
chart!.showLoading();
|
||||
}
|
||||
const hideLoading = () => {
|
||||
if (_next()) return
|
||||
chart!.hideLoading();
|
||||
}
|
||||
const clear = () => {
|
||||
if (_next()) return
|
||||
chart!.clear();
|
||||
}
|
||||
const dispose = () => {
|
||||
if (_next()) return
|
||||
chart!.dispose();
|
||||
}
|
||||
const resize = (size : UTSJSONObject) => {
|
||||
if (_next()) return
|
||||
chart!.resize(size);
|
||||
}
|
||||
const canvasToTempFilePath = (opt : UTSJSONObject) => {
|
||||
if (_next()) return
|
||||
chart!.canvasToTempFilePath(opt);
|
||||
}
|
||||
|
||||
// #ifdef APP
|
||||
function init(callback : ((chart : Echarts) => void) | null) : Promise<Echarts> {
|
||||
|
||||
if (callback != null) {
|
||||
callbackMap.push(callback)
|
||||
}
|
||||
return new Promise<Echarts>((resolve) => {
|
||||
map.push(resolve)
|
||||
trigger()
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
// #ifndef WEB
|
||||
let use2dCanvas = canIUseCanvas2d()
|
||||
const getContext = async () => {
|
||||
return new Promise((resolve, reject)=>{
|
||||
uni.createCanvasContextAsync({
|
||||
id: canvasid,
|
||||
component: instance.proxy!,
|
||||
success: (context : CanvasContext) => {
|
||||
const canvasContext = context.getContext('2d')!;
|
||||
const canvas = canvasContext.canvas;
|
||||
let uniCanvas;
|
||||
const width = canvas.offsetWidth
|
||||
const height = canvas.offsetHeight
|
||||
// 处理高清屏逻辑
|
||||
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
|
||||
canvas.width = canvas.offsetWidth * dpr;
|
||||
canvas.height = canvas.offsetHeight * dpr;
|
||||
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
|
||||
if(use2dCanvas) {
|
||||
uniCanvas = new Canvas(canvasContext, instance.proxy, true, context);
|
||||
} else {
|
||||
uniCanvas = new Canvas(canvasContext, instance.proxy, false);
|
||||
}
|
||||
resolve({ canvas: uniCanvas, width, height, devicePixelRatio: 1, node: context});
|
||||
},
|
||||
fail(err) {
|
||||
reject(err)
|
||||
console.log('err', err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// return getRect(`#${canvasid}`, {context: instance.proxy!, type: use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
|
||||
// if(res) {
|
||||
// let dpr = uni.getWindowInfo().pixelRatio
|
||||
// let {width, height, node} = res
|
||||
// let canvas;
|
||||
// if(node) {
|
||||
// const ctx = node.getContext('2d');
|
||||
// canvas = new Canvas(ctx, instance.proxy, true, node);
|
||||
// } else {
|
||||
// const ctx = uni.createCanvasContext(canvasid, instance.proxy);
|
||||
// canvas = new Canvas(ctx, instance.proxy, false);
|
||||
// }
|
||||
|
||||
// return { canvas, width, height, devicePixelRatio: dpr, node };
|
||||
// } else {
|
||||
// return {}
|
||||
// }
|
||||
// })
|
||||
}
|
||||
// #endif
|
||||
const getTouch = (e) => {
|
||||
const touches = e.touches[0]
|
||||
// #ifdef WEB
|
||||
const rect = chart!.getZr().dom.getBoundingClientRect();
|
||||
const touch = {
|
||||
x: touches.clientX - rect.left,
|
||||
y: touches.clientY - rect.top
|
||||
}
|
||||
// #endif
|
||||
// #ifndef WEB
|
||||
const touch = {
|
||||
x: touches.x,
|
||||
y: touches.y
|
||||
}
|
||||
// #endif
|
||||
return touch
|
||||
}
|
||||
const touchstart = (e) => {
|
||||
if (chart == null) return
|
||||
const handler = chart.getZr().handler;
|
||||
const touch = getTouch(e)
|
||||
dispatch.call(handler, 'mousedown', touch)
|
||||
dispatch.call(handler, 'click', touch)
|
||||
}
|
||||
const touchmove = (e) => {
|
||||
if (chart == null) return
|
||||
const handler = chart.getZr().handler;
|
||||
const touch = getTouch(e)
|
||||
dispatch.call(handler, 'mousemove', touch)
|
||||
// const rect = chart.getZr().dom.getBoundingClientRect()
|
||||
// handler.dispatch('mousemove', {
|
||||
// zrX: e.touches[0].clientX - rect.left,
|
||||
// zrY: e.touches[0].clientY - rect.top
|
||||
// })
|
||||
}
|
||||
const touchend = (e) => {
|
||||
if (chart == null) return
|
||||
const handler = chart.getZr().handler;
|
||||
|
||||
const touch = {
|
||||
x: 999999999,
|
||||
y: 999999999
|
||||
}
|
||||
|
||||
dispatch.call(handler, 'mousemove', touch)
|
||||
dispatch.call(handler, 'touchend', touch)
|
||||
|
||||
}
|
||||
async function init(echarts : any, ...args : any[]) : Promise<Echarts> {
|
||||
if (echarts == null) {
|
||||
console.error('请确保已经引入了 ECharts 库');
|
||||
return Promise.reject('请确保已经引入了 ECharts 库');
|
||||
}
|
||||
let theme : string | null = null
|
||||
let opts = {}
|
||||
let callback : Function | null = null;
|
||||
|
||||
args.forEach(item => {
|
||||
if (typeof item === 'function') {
|
||||
callback = item
|
||||
} else if (['string'].includes(typeof item)) {
|
||||
theme = item
|
||||
} else if (typeof item === 'object') {
|
||||
opts = item
|
||||
}
|
||||
})
|
||||
|
||||
// #ifdef WEB
|
||||
echarts.env.domSupported = true
|
||||
echarts.env.hasGlobalWindow = true
|
||||
echarts.env.node = false
|
||||
echarts.env.pointerEventsSupported = false
|
||||
echarts.env.svgSupported = true
|
||||
echarts.env.touchEventsSupported = true
|
||||
echarts.env.transform3dSupported = true
|
||||
echarts.env.transformSupported = true
|
||||
echarts.env.worker = false
|
||||
echarts.env.wxa = false
|
||||
chart = echarts.init(chartRef.value, theme, opts)
|
||||
// window.addEventListener('touchstart', touchstart)
|
||||
// window.addEventListener('touchmove', touchmove)
|
||||
// window.addEventListener('touchend', touchend)
|
||||
// #endif
|
||||
|
||||
// #ifndef WEB
|
||||
let config = await getContext();
|
||||
setCanvasCreator(echarts, config)
|
||||
chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
|
||||
// #endif
|
||||
if (callback != null && typeof callback == 'function') {
|
||||
callbackMap.push(callback)
|
||||
}
|
||||
return new Promise<Echarts>((resolve) => {
|
||||
map.push(resolve)
|
||||
trigger()
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
finished.value = true
|
||||
trigger()
|
||||
emits('finished')
|
||||
})
|
||||
})
|
||||
onUnmounted(() => {
|
||||
// #ifdef WEB
|
||||
// window.removeEventListener('touchstart', touchstart)
|
||||
// window.removeEventListener('touchmove', touchmove)
|
||||
// window.removeEventListener('touchend', touchend)
|
||||
// #endif
|
||||
})
|
||||
// #endif
|
||||
|
||||
defineExpose({
|
||||
init,
|
||||
setOption,
|
||||
showLoading,
|
||||
hideLoading,
|
||||
clear,
|
||||
dispose,
|
||||
resize,
|
||||
canvasToTempFilePath
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.lime-echart {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,550 @@
|
|||
<template>
|
||||
<view
|
||||
class="lime-echart"
|
||||
:style="[customStyle]"
|
||||
v-if="canvasId"
|
||||
ref="limeEchart"
|
||||
:aria-label="ariaLabel"
|
||||
>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas
|
||||
class="lime-echart__canvas"
|
||||
v-if="use2dCanvas"
|
||||
type="2d"
|
||||
:id="canvasId"
|
||||
:style="canvasStyle"
|
||||
:disable-scroll="isDisableScroll"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
/>
|
||||
<canvas
|
||||
class="lime-echart__canvas"
|
||||
v-else
|
||||
:width="nodeWidth"
|
||||
:height="nodeHeight"
|
||||
:style="canvasStyle"
|
||||
:canvas-id="canvasId"
|
||||
:id="canvasId"
|
||||
:disable-scroll="isDisableScroll"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
/>
|
||||
<view
|
||||
class="lime-echart__mask"
|
||||
v-if="isPC"
|
||||
@mousedown="touchStart"
|
||||
@mousemove="touchMove"
|
||||
@mouseup="touchEnd"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
></view>
|
||||
<canvas
|
||||
v-if="isOffscreenCanvas"
|
||||
:style="offscreenStyle"
|
||||
:canvas-id="offscreenCanvasId"
|
||||
></canvas>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view
|
||||
class="lime-echart__canvas"
|
||||
:id="canvasId"
|
||||
:style="canvasStyle"
|
||||
:webview-styles="webviewStyles"
|
||||
ref="webview"
|
||||
src="/uni_modules/lime-echart/static/uvue.html?v=1"
|
||||
@pagefinish="finished = true"
|
||||
@onPostMessage="onMessage"
|
||||
></web-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ts-nocheck
|
||||
// #ifndef APP-NVUE
|
||||
import { Canvas, setCanvasCreator, dispatch } from './canvas'
|
||||
import {
|
||||
wrapTouch,
|
||||
convertTouchesToArray,
|
||||
devicePixelRatio,
|
||||
sleep,
|
||||
canIUseCanvas2d,
|
||||
getRect,
|
||||
getDeviceInfo,
|
||||
} from './utils'
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
import { base64ToPath, sleep } from './utils'
|
||||
import { Echarts } from './nvue'
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* LimeChart 图表
|
||||
* @description 全端兼容的eCharts
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=4899
|
||||
|
||||
* @property {String} customStyle 自定义样式
|
||||
* @property {String} type 指定 canvas 类型
|
||||
* @value 2d 使用canvas 2d,部分小程序支持
|
||||
* @value '' 使用原生canvas,会有层级问题
|
||||
* @value bottom right 不缩放图片,只显示图片的右下边区域
|
||||
* @property {Boolean} isDisableScroll
|
||||
* @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
|
||||
* @property {Boolean} enableHover PC端使用鼠标悬浮
|
||||
|
||||
* @event {Function} finished 加载完成触发
|
||||
*/
|
||||
export default {
|
||||
name: 'lime-echart',
|
||||
props: {
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d',
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
webviewStyles: Object,
|
||||
// hybrid: Boolean,
|
||||
// #endif
|
||||
customStyle: String,
|
||||
isDisableScroll: Boolean,
|
||||
isClickable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
enableHover: Boolean,
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
},
|
||||
landscape: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: true,
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: false,
|
||||
// #endif
|
||||
ariaLabel: '图表',
|
||||
width: null,
|
||||
height: null,
|
||||
nodeWidth: null,
|
||||
nodeHeight: null,
|
||||
// canvasNode: null,
|
||||
config: {},
|
||||
inited: false,
|
||||
finished: false,
|
||||
file: '',
|
||||
platform: '',
|
||||
isPC: false,
|
||||
isDown: false,
|
||||
isOffscreenCanvas: false,
|
||||
offscreenWidth: 0,
|
||||
offscreenHeight: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rootStyle() {
|
||||
if (this.landscape) {
|
||||
return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
|
||||
} else {
|
||||
return ``
|
||||
}
|
||||
},
|
||||
canvasId() {
|
||||
return `lime-echart${(this._ && this._.uid) || this._uid}`
|
||||
},
|
||||
offscreenCanvasId() {
|
||||
return `${this.canvasId}_offscreen`
|
||||
},
|
||||
offscreenStyle() {
|
||||
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
|
||||
},
|
||||
canvasStyle() {
|
||||
return (
|
||||
this.rootStyle +
|
||||
(this.width && this.height ? 'width:' + this.width + 'px;height:' + this.height + 'px' : '')
|
||||
)
|
||||
},
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clear()
|
||||
this.dispose()
|
||||
// #ifdef H5
|
||||
if (this.isPC) {
|
||||
document.removeEventListener('mousewheel', this.mousewheel)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clear()
|
||||
this.dispose()
|
||||
// #ifdef H5
|
||||
if (this.isPC) {
|
||||
document.removeEventListener('mousewheel', this.mousewheel)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
// #ifdef H5
|
||||
if (!('ontouchstart' in window)) {
|
||||
this.isPC = true
|
||||
document.addEventListener('mousewheel', this.mousewheel)
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
const { platform } = getDeviceInfo()
|
||||
this.isPC = /windows/i.test(platform)
|
||||
// #endif
|
||||
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('finished')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// #ifdef APP-NVUE
|
||||
onMessage(e) {
|
||||
const detail = e?.detail?.data[0] || null
|
||||
const data = detail?.data
|
||||
const key = detail?.event
|
||||
const options = data?.options
|
||||
const event = data?.event
|
||||
const file = detail?.file
|
||||
if (key == 'log' && data) {
|
||||
console.log(data)
|
||||
}
|
||||
if (event) {
|
||||
this.chart.dispatchAction(event.replace(/"/g, ''), options)
|
||||
}
|
||||
if (file) {
|
||||
thie.file = file
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
setChart(callback) {
|
||||
if (!this.chart) {
|
||||
console.warn(`组件还未初始化,请先使用 init`)
|
||||
return
|
||||
}
|
||||
if (typeof callback === 'function' && this.chart) {
|
||||
callback(this.chart)
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
if (typeof callback === 'function') {
|
||||
this.$refs.webview.evalJs(
|
||||
`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`,
|
||||
)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
setOption() {
|
||||
if (!this.chart || !this.chart.setOption) {
|
||||
console.warn(`组件还未初始化,请先使用 init`)
|
||||
return
|
||||
}
|
||||
this.chart.setOption(...arguments)
|
||||
},
|
||||
showLoading() {
|
||||
if (this.chart) {
|
||||
this.chart.showLoading(...arguments)
|
||||
}
|
||||
},
|
||||
hideLoading() {
|
||||
if (this.chart) {
|
||||
this.chart.hideLoading()
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
if (this.chart && !this.chart.isDisposed()) {
|
||||
this.chart.clear()
|
||||
}
|
||||
},
|
||||
dispose() {
|
||||
if (this.chart && !this.chart.isDisposed()) {
|
||||
this.chart.dispose()
|
||||
}
|
||||
},
|
||||
resize(size) {
|
||||
if (size && size.width && size.height) {
|
||||
this.height = size.height
|
||||
this.width = size.width
|
||||
if (this.chart) {
|
||||
this.chart.resize(size)
|
||||
}
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
getRect('.lime-echart', this).then((res) => {
|
||||
if (res) {
|
||||
let { width, height } = res
|
||||
this.width = width = width || 300
|
||||
this.height = height = height || 300
|
||||
this.chart.resize({ width, height })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
canvasToTempFilePath(args = {}) {
|
||||
// #ifndef APP-NVUE
|
||||
const { use2dCanvas, canvasId } = this
|
||||
return new Promise((resolve, reject) => {
|
||||
const copyArgs = Object.assign(
|
||||
{
|
||||
canvasId,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
},
|
||||
args,
|
||||
)
|
||||
if (use2dCanvas) {
|
||||
delete copyArgs.canvasId
|
||||
copyArgs.canvas = this.canvasNode
|
||||
}
|
||||
uni.canvasToTempFilePath(copyArgs, this)
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.file = ''
|
||||
this.$refs.webview.evalJs(`canvasToTempFilePath()`)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$watch('file', async (file) => {
|
||||
if (file) {
|
||||
const tempFilePath = await base64ToPath(file)
|
||||
resolve(args.success({ tempFilePath }))
|
||||
} else {
|
||||
reject(args.fail({ error: `` }))
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
async init(echarts, ...args) {
|
||||
// #ifndef APP-NVUE
|
||||
if (args && args.length == 0 && !echarts) {
|
||||
console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
let theme = null,
|
||||
opts = {},
|
||||
callback
|
||||
Array.from(arguments).forEach((item) => {
|
||||
if (typeof item === 'function') {
|
||||
callback = item
|
||||
}
|
||||
if (['string'].includes(typeof item)) {
|
||||
theme = item
|
||||
}
|
||||
if (typeof item === 'object') {
|
||||
opts = item
|
||||
}
|
||||
})
|
||||
if (this.beforeDelay) {
|
||||
await sleep(this.beforeDelay)
|
||||
}
|
||||
let config = await this.getContext()
|
||||
// #ifndef APP-NVUE
|
||||
setCanvasCreator(echarts, config)
|
||||
try {
|
||||
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts || {}))
|
||||
|
||||
callback?.(this.chart)
|
||||
return this.chart
|
||||
} catch (e) {
|
||||
console.error('【lime-echarts】:', e)
|
||||
return null
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.chart = new Echarts(this.$refs.webview)
|
||||
this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
|
||||
callback?.(this.chart)
|
||||
return this.chart
|
||||
// #endif
|
||||
},
|
||||
getContext() {
|
||||
// #ifdef APP-NVUE
|
||||
if (this.finished) {
|
||||
return Promise.resolve(this.finished)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
this.$watch('finished', (val) => {
|
||||
if (val) {
|
||||
resolve(this.finished)
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then((res) => {
|
||||
if (res) {
|
||||
let dpr = devicePixelRatio
|
||||
let { width, height, node } = res
|
||||
let canvas
|
||||
this.width = width = width || 300
|
||||
this.height = height = height || 300
|
||||
if (node) {
|
||||
const ctx = node.getContext('2d')
|
||||
canvas = new Canvas(ctx, this, true, node)
|
||||
this.canvasNode = node
|
||||
} else {
|
||||
// #ifdef MP-TOUTIAO
|
||||
dpr = !this.isPC ? devicePixelRatio : 1 // 1.25
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY || MP-TOUTIAO
|
||||
dpr = this.isPC ? devicePixelRatio : 1
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY || MP-LARK
|
||||
dpr = devicePixelRatio
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
dpr = 1
|
||||
// #endif
|
||||
this.rect = res
|
||||
this.nodeWidth = width * dpr
|
||||
this.nodeHeight = height * dpr
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this)
|
||||
canvas = new Canvas(ctx, this, false)
|
||||
}
|
||||
|
||||
return { canvas, width, height, devicePixelRatio: dpr, node }
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// #ifndef APP-NVUE
|
||||
getRelative(e, touches) {
|
||||
let { clientX, clientY } = e
|
||||
if (!(clientX && clientY) && touches && touches[0]) {
|
||||
clientX = touches[0].clientX
|
||||
clientY = touches[0].clientY
|
||||
}
|
||||
return {
|
||||
x: clientX - this.rect.left,
|
||||
y: clientY - this.rect.top,
|
||||
wheelDelta: e.wheelDelta || 0,
|
||||
}
|
||||
},
|
||||
getTouch(e, touches) {
|
||||
const { x } = (touches && touches[0]) || {}
|
||||
const touch = x ? touches[0] : this.getRelative(e, touches)
|
||||
if (this.landscape) {
|
||||
;[touch.x, touch.y] = [touch.y, this.height - touch.x]
|
||||
}
|
||||
return touch
|
||||
},
|
||||
touchStart(e) {
|
||||
this.isDown = true
|
||||
const next = () => {
|
||||
const touches = convertTouchesToArray(e.touches)
|
||||
if (this.chart) {
|
||||
const touch = this.getTouch(e, touches)
|
||||
this.startX = touch.x
|
||||
this.startY = touch.y
|
||||
this.startT = new Date()
|
||||
const handler = this.chart.getZr().handler
|
||||
dispatch.call(handler, 'mousedown', touch)
|
||||
dispatch.call(handler, 'mousemove', touch)
|
||||
handler.processGesture(wrapTouch(e), 'start')
|
||||
clearTimeout(this.endTimer)
|
||||
}
|
||||
}
|
||||
if (this.isPC) {
|
||||
getRect(`#${this.canvasId}`, { context: this }).then((res) => {
|
||||
this.rect = res
|
||||
next()
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
},
|
||||
touchMove(e) {
|
||||
if (this.isPC && this.enableHover && !this.isDown) {
|
||||
this.isDown = true
|
||||
}
|
||||
const touches = convertTouchesToArray(e.touches)
|
||||
if (this.chart && this.isDown) {
|
||||
const handler = this.chart.getZr().handler
|
||||
dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
|
||||
handler.processGesture(wrapTouch(e), 'change')
|
||||
}
|
||||
},
|
||||
touchEnd(e) {
|
||||
this.isDown = false
|
||||
if (this.chart) {
|
||||
const touches = convertTouchesToArray(e.changedTouches)
|
||||
const { x } = (touches && touches[0]) || {}
|
||||
const touch = (x ? touches[0] : this.getRelative(e, touches)) || {}
|
||||
if (this.landscape) {
|
||||
;[touch.x, touch.y] = [touch.y, this.height - touch.x]
|
||||
}
|
||||
const handler = this.chart.getZr().handler
|
||||
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200
|
||||
dispatch.call(handler, 'mouseup', touch)
|
||||
handler.processGesture(wrapTouch(e), 'end')
|
||||
if (isClick) {
|
||||
dispatch.call(handler, 'click', touch)
|
||||
} else {
|
||||
this.endTimer = setTimeout(() => {
|
||||
dispatch.call(handler, 'mousemove', { x: 999999999, y: 999999999 })
|
||||
dispatch.call(handler, 'mouseup', { x: 999999999, y: 999999999 })
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
mousewheel(e) {
|
||||
if (this.chart) {
|
||||
dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.lime-echart {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
}
|
||||
.lime-echart__canvas {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
}
|
||||
/* #ifndef APP-NVUE */
|
||||
.lime-echart__mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
export class Echarts {
|
||||
eventMap = new Map()
|
||||
constructor(webview) {
|
||||
this.webview = webview
|
||||
this.options = null
|
||||
}
|
||||
setOption() {
|
||||
this.options = arguments
|
||||
this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
|
||||
}
|
||||
getOption() {
|
||||
return this.options
|
||||
}
|
||||
showLoading() {
|
||||
this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
|
||||
}
|
||||
hideLoading() {
|
||||
this.webview.evalJs(`hideLoading()`);
|
||||
}
|
||||
clear() {
|
||||
this.webview.evalJs(`clear()`);
|
||||
}
|
||||
dispose() {
|
||||
this.webview.evalJs(`dispose()`);
|
||||
}
|
||||
resize(size) {
|
||||
if(size) {
|
||||
this.webview.evalJs(`resize(${JSON.stringify(size)})`);
|
||||
} else {
|
||||
this.webview.evalJs(`resize()`);
|
||||
}
|
||||
}
|
||||
on(type, ...args) {
|
||||
const query = args[0]
|
||||
const useQuery = query && typeof query != 'function'
|
||||
const param = useQuery ? [type, query] : [type]
|
||||
const key = `${type}${useQuery ? JSON.stringify(query): '' }`
|
||||
const callback = useQuery ? args[1]: args[0]
|
||||
if(typeof callback == 'function'){
|
||||
this.eventMap.set(key, callback)
|
||||
}
|
||||
this.webview.evalJs(`on(${JSON.stringify(param)})`);
|
||||
console.warn('nvue 暂不支持事件')
|
||||
}
|
||||
dispatchAction(type, options){
|
||||
const handler = this.eventMap.get(type)
|
||||
if(handler){
|
||||
handler(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
// @ts-nocheck
|
||||
/**
|
||||
* 获取设备基础信息
|
||||
*
|
||||
* @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.html)
|
||||
*/
|
||||
export function getDeviceInfo() {
|
||||
if (uni.getDeviceInfo || uni.canIUse('getDeviceInfo')) {
|
||||
return uni.getDeviceInfo();
|
||||
} else {
|
||||
return uni.getSystemInfoSync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取窗口信息
|
||||
*
|
||||
* @see [uni.getWindowInfo](https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html)
|
||||
*/
|
||||
export function getWindowInfo() {
|
||||
if (uni.getWindowInfo || uni.canIUse('getWindowInfo')) {
|
||||
return uni.getWindowInfo();
|
||||
} else {
|
||||
return uni.getSystemInfoSync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取APP基础信息
|
||||
*
|
||||
* @see [uni.getAppBaseInfo](https://uniapp.dcloud.net.cn/api/system/getAppBaseInfo.html)
|
||||
*/
|
||||
export function getAppBaseInfo() {
|
||||
if (uni.getAppBaseInfo || uni.canIUse('getAppBaseInfo')) {
|
||||
return uni.getAppBaseInfo();
|
||||
} else {
|
||||
return uni.getSystemInfoSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
// 计算版本
|
||||
export function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i], 10)
|
||||
const num2 = parseInt(v2[i], 10)
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// const systemInfo = uni.getSystemInfoSync();
|
||||
|
||||
function gte(version) {
|
||||
// 截止 2023-03-22 mac pc小程序不支持 canvas 2d
|
||||
// let {
|
||||
// SDKVersion,
|
||||
// platform
|
||||
// } = systemInfo;
|
||||
const { platform } = getDeviceInfo();
|
||||
let { SDKVersion } = getAppBaseInfo();
|
||||
// #ifdef MP-ALIPAY
|
||||
SDKVersion = my.SDKVersion
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0;
|
||||
// #endif
|
||||
return compareVersion(SDKVersion, version) >= 0;
|
||||
}
|
||||
|
||||
|
||||
export function canIUseCanvas2d() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return gte('2.9.0');
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return gte('2.7.0');
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
return gte('1.78.0');
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
export function convertTouchesToArray(touches) {
|
||||
// 如果 touches 是一个数组,则直接返回它
|
||||
if (Array.isArray(touches)) {
|
||||
return touches;
|
||||
}
|
||||
// 如果touches是一个对象,则转换为数组
|
||||
if (typeof touches === 'object' && touches !== null) {
|
||||
return Object.values(touches);
|
||||
}
|
||||
// 对于其他类型,直接返回它
|
||||
return touches;
|
||||
}
|
||||
|
||||
export function wrapTouch(event) {
|
||||
event.touches = convertTouchesToArray(event.touches)
|
||||
for (let i = 0; i < event.touches.length; ++i) {
|
||||
const touch = event.touches[i];
|
||||
touch.offsetX = touch.x;
|
||||
touch.offsetY = touch.y;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
// export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio
|
||||
export const devicePixelRatio = getWindowInfo().pixelRatio;
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
export function base64ToPath(base64) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
export function sleep(time) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true)
|
||||
}, time)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function getRect(selector, context, node) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dom = uni.createSelectorQuery().in(context).select(selector);
|
||||
const result = (rect) => {
|
||||
if (rect) {
|
||||
resolve(rect)
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
if (!node) {
|
||||
dom.boundingClientRect(result).exec()
|
||||
} else {
|
||||
dom.fields({
|
||||
node: true,
|
||||
size: true,
|
||||
rect: true
|
||||
}, result).exec()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// @ts-nocheck
|
||||
// #ifdef APP
|
||||
type EchartsEventHandler = (event: UTSJSONObject)=>void
|
||||
// type EchartsTempResolve = (obj : UTSJSONObject) => void
|
||||
// type EchartsTempOptions = UTSJSONObject
|
||||
export class Echarts {
|
||||
options: UTSJSONObject = {} as UTSJSONObject
|
||||
context: UniWebViewElement
|
||||
eventMap: Map<string, EchartsEventHandler> = new Map()
|
||||
private temp: UTSJSONObject[] = []
|
||||
constructor(context: UniWebViewElement){
|
||||
this.context = context
|
||||
this.init()
|
||||
}
|
||||
init(){
|
||||
this.context.evalJS(`init(null, null, ${JSON.stringify({})})`)
|
||||
|
||||
this.context.addEventListener('message', (e : UniWebViewMessageEvent) => {
|
||||
// event.stopPropagation()
|
||||
// event.preventDefault()
|
||||
|
||||
const detail = e.detail.data[0]
|
||||
const file = detail.getString('file')
|
||||
const data = detail.get('data')
|
||||
const key = detail.getString('event')
|
||||
const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null
|
||||
const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null
|
||||
if (key == 'log' && data != null) {
|
||||
console.log(data)
|
||||
}
|
||||
if (event != null && options != null) {
|
||||
this.dispatchAction(event.replace(/"/g,''), options)
|
||||
}
|
||||
if(file != null){
|
||||
while (this.temp.length > 0) {
|
||||
const opt = this.temp.pop()
|
||||
const success = opt?.get('success')
|
||||
if(typeof success == 'function'){
|
||||
success as (res: UTSJSONObject) => void
|
||||
success({tempFilePath: file})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
setOption(option: UTSJSONObject){
|
||||
this.options = option;
|
||||
this.context.evalJS(`setOption(${JSON.stringify([option])})`)
|
||||
}
|
||||
setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){
|
||||
this.options = option;
|
||||
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`)
|
||||
}
|
||||
setOption(option: UTSJSONObject, notMerge: UTSJSONObject){
|
||||
this.options = option;
|
||||
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`)
|
||||
}
|
||||
getOption(): UTSJSONObject {
|
||||
return this.options
|
||||
}
|
||||
showLoading(){
|
||||
this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`);
|
||||
}
|
||||
showLoading(type: string, opts: UTSJSONObject){
|
||||
this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`);
|
||||
}
|
||||
hideLoading(){
|
||||
this.context.evalJS(`hideLoading()`);
|
||||
}
|
||||
clear(){
|
||||
this.context.evalJS(`clear()`);
|
||||
}
|
||||
dispose(){
|
||||
this.context.evalJS(`dispose()`);
|
||||
}
|
||||
resize(size:UTSJSONObject){
|
||||
setTimeout(()=>{
|
||||
this.context.evalJS(`resize(${JSON.stringify(size)})`);
|
||||
},0)
|
||||
}
|
||||
resize(){
|
||||
setTimeout(()=>{
|
||||
this.context.evalJS(`resize()`);
|
||||
},10)
|
||||
|
||||
}
|
||||
on(type:string, query: any, callback: EchartsEventHandler) {
|
||||
const key = `${type}${JSON.stringify(query)}`
|
||||
if(typeof callback == 'function'){
|
||||
this.eventMap.set(key, callback)
|
||||
}
|
||||
this.context.evalJS(`on(${JSON.stringify([type, query])})`);
|
||||
console.warn('uvue 暂不支持事件')
|
||||
}
|
||||
on(type:string, callback: EchartsEventHandler) {
|
||||
const key = `${type}`
|
||||
if(typeof callback == 'function'){
|
||||
this.eventMap.set(key, callback)
|
||||
}
|
||||
this.context.evalJS(`on(${JSON.stringify([type])})`);
|
||||
console.warn('uvue 暂不支持事件')
|
||||
}
|
||||
dispatchAction(type:string, options: UTSJSONObject){
|
||||
const handler = this.eventMap.get(type)
|
||||
if(handler!=null){
|
||||
handler(options)
|
||||
}
|
||||
}
|
||||
canvasToTempFilePath(opt: UTSJSONObject){
|
||||
// this.context.evalJS(`on(${JSON.stringify(opt)})`);
|
||||
this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`);
|
||||
this.temp.push(opt)
|
||||
}
|
||||
}
|
||||
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
export class Echarts {
|
||||
constructor() {}
|
||||
setOption(option: UTSJSONObject): void
|
||||
isDisposed(): boolean;
|
||||
clear(): void;
|
||||
resize(size:UTSJSONObject): void;
|
||||
resize(): void;
|
||||
canvasToTempFilePath(opt : UTSJSONObject): void;
|
||||
dispose(): void;
|
||||
showLoading(cfg?: UTSJSONObject): void;
|
||||
showLoading(name?: string, cfg?: UTSJSONObject): void;
|
||||
hideLoading(): void;
|
||||
getZr(): any
|
||||
}
|
||||
// #endif
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<view style="width: 100%; height: 408px;">
|
||||
<l-echart ref="chartRef" @finished="init"></l-echart>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showTip: false,
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// shadowBlur: 0,
|
||||
textStyle: {
|
||||
textShadowBlur: 0
|
||||
},
|
||||
renderMode: 'richText',
|
||||
},
|
||||
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]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('lime echarts nvue')
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const chartRef = this.$refs['chartRef']
|
||||
chartRef.init(chart => {
|
||||
chart.setOption(this.option);
|
||||
|
||||
|
||||
setTimeout(()=>{
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// shadowBlur: 0,
|
||||
textStyle: {
|
||||
textShadowBlur: 0
|
||||
},
|
||||
renderMode: 'richText',
|
||||
},
|
||||
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]
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option);
|
||||
},1000)
|
||||
})
|
||||
},
|
||||
save() {
|
||||
// this.$refs.chart.canvasToTempFilePath({
|
||||
// success(res) {
|
||||
// console.log('res::::', res)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<view style="width: 100%; height: 408px;">
|
||||
<l-echart ref="chartRef" @finished="init"></l-echart>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
// @ts-nocheck
|
||||
// #ifndef APP
|
||||
import * as echarts from 'echarts/dist/echarts.esm.js'
|
||||
// #endif
|
||||
const chartRef = ref<LEchartComponentPublicInstance|null>(null)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// shadowBlur: 0,
|
||||
textStyle: {
|
||||
textShadowBlur: 0
|
||||
},
|
||||
renderMode: 'richText',
|
||||
},
|
||||
// formatter: async (params: any) => {
|
||||
// console.log('params', params)
|
||||
// return 1
|
||||
// },
|
||||
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 init = async () =>{
|
||||
if(chartRef.value== null) return
|
||||
// #ifdef APP
|
||||
const chart = await chartRef.value!.init(null)
|
||||
// #endif
|
||||
// #ifndef APP
|
||||
const chart = await chartRef.value!.init(echarts, null)
|
||||
// #endif
|
||||
chart.setOption(option)
|
||||
chart.on('mouseover', function (params) {
|
||||
console.log('params', params);
|
||||
});
|
||||
|
||||
|
||||
// setTimeout(()=> {
|
||||
// const option1 = {
|
||||
// tooltip: {
|
||||
// trigger: 'axis',
|
||||
// // shadowBlur: 0,
|
||||
// textStyle: {
|
||||
// textShadowBlur: 0
|
||||
// },
|
||||
// renderMode: 'richText',
|
||||
// },
|
||||
// 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: [820, 132, 101, 134, 90, 230, 210]
|
||||
// },
|
||||
// {
|
||||
// name: '联盟广告',
|
||||
// type: 'line',
|
||||
// stack: '总量',
|
||||
// data: [220, 182, 191, 234, 290, 330, 310]
|
||||
// },
|
||||
// {
|
||||
// name: '视频广告',
|
||||
// type: 'line',
|
||||
// stack: '总量',
|
||||
// data: [950, 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]
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// chart.setOption(option1)
|
||||
// },1000)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="height: 750rpx; position: relative">
|
||||
<l-echart ref="chart" @finished="init"></l-echart>
|
||||
<view
|
||||
class="customTooltips"
|
||||
:style="{ left: position[0] + 'px', top: position[1] + 'px' }"
|
||||
v-if="params.length && position.length && showTip"
|
||||
>
|
||||
<view>这是个自定的tooltips</view>
|
||||
<view>{{ params[0]['axisValue'] }}</view>
|
||||
<view v-for="item in params">
|
||||
<view>
|
||||
<text>{{ item.seriesName }}</text>
|
||||
<text>{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// nvue 不需要引入
|
||||
// #ifdef VUE2
|
||||
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
// #ifdef MP
|
||||
// 由于vue3 使用vite 不支持umd格式的包,小程序依然可以使用,但需要使用require
|
||||
const echarts = require('../../static/echarts.min')
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// 由于 vue3 使用vite 不支持umd格式的包,故引入npm的包
|
||||
import * as echarts from 'echarts/dist/echarts.esm'
|
||||
// #endif
|
||||
// #endif
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showTip: false,
|
||||
position: [],
|
||||
params: [],
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// shadowBlur: 0,
|
||||
textStyle: {
|
||||
textShadowBlur: 0,
|
||||
},
|
||||
renderMode: 'richText',
|
||||
position: (point, params, dom, rect, size) => {
|
||||
// 假设自定义的tooltips尺寸
|
||||
const box = [170, 170]
|
||||
// 偏移
|
||||
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
|
||||
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
|
||||
const x = point[0] + offsetX
|
||||
const y = point[1] + offsetY
|
||||
|
||||
this.position = [x, y]
|
||||
this.params = params
|
||||
},
|
||||
formatter: (params, ticket, callback) => {},
|
||||
},
|
||||
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],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
// init(echarts, theme?:string, opts?:{}, chart => {})
|
||||
// echarts 必填, 非nvue必填,nvue不用填
|
||||
// theme 可选,应用的主题,目前只支持名称,如:'dark'
|
||||
// opts = { // 可选
|
||||
// locale?: string // 从 `5.0.0` 开始支持
|
||||
// }
|
||||
// chart => {} , callback 返回图表实例
|
||||
// setTimeout(()=>{
|
||||
// this.$refs.chart.init(echarts, chart => {
|
||||
// chart.setOption(this.option);
|
||||
// });
|
||||
// },300)
|
||||
this.$refs.chart.init(echarts, (chart) => {
|
||||
chart.setOption(this.option)
|
||||
|
||||
// 监听tooltip显示事件
|
||||
chart.on('showTip', (params) => {
|
||||
this.showTip = true
|
||||
console.log('showTip::')
|
||||
})
|
||||
chart.on('hideTip', (params) => {
|
||||
setTimeout(() => {
|
||||
this.showTip = false
|
||||
}, 300)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// shadowBlur: 0,
|
||||
textStyle: {
|
||||
textShadowBlur: 0,
|
||||
},
|
||||
renderMode: 'richText',
|
||||
},
|
||||
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: [1120, 132, 101, 134, 90, 230, 210],
|
||||
},
|
||||
{
|
||||
name: '联盟广告',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [220, 182, 191, 234, 290, 330, 310],
|
||||
},
|
||||
{
|
||||
name: '视频广告',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [150, 632, 201, 154, 190, 330, 410],
|
||||
},
|
||||
{
|
||||
name: '直接访问',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [820, 332, 301, 334, 390, 330, 320],
|
||||
},
|
||||
{
|
||||
name: '搜索引擎',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||
},
|
||||
],
|
||||
}
|
||||
chart.setOption(option)
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
save() {
|
||||
this.$refs.chart.canvasToTempFilePath({
|
||||
success(res) {
|
||||
console.log('res::::', res)
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.customTooltips {
|
||||
position: absolute;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"id": "lime-echart",
|
||||
"displayName": "echarts",
|
||||
"version": "1.0.0",
|
||||
"description": "echarts 全端兼容,一款使echarts图表能跑在uniapp各端中的插件, 支持uniapp/uniappx(web,ios,安卓)",
|
||||
"keywords": [
|
||||
"echarts",
|
||||
"canvas",
|
||||
"图表",
|
||||
"可视化"
|
||||
],
|
||||
"repository": "https://gitee.com/liangei/lime-echart",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.6.4"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y",
|
||||
"app-uvue": "y",
|
||||
"app-harmony": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"echarts": "^5.4.1",
|
||||
"zrender": "^5.4.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
# echarts 图表 <span style="font-size:16px;">👑👑👑👑👑 <span style="background:#ff9d00;padding:2px 4px;color:#fff;font-size:10px;border-radius: 3px;">全端</span></span>
|
||||
> 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart) <br>
|
||||
> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html) <br>
|
||||
|
||||
|
||||
## 平台兼容
|
||||
|
||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- |
|
||||
| √ | √ | √ | √ | √ | √ | √ |
|
||||
|
||||
|
||||
## 安装
|
||||
- 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899)
|
||||
- 第二步:选择插件依赖:<br>
|
||||
1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)<br>
|
||||
2、或者使用`npm`安装`echarts`
|
||||
|
||||
**注意**
|
||||
* 🔔 echarts 5.3.0及以上
|
||||
* 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个
|
||||
|
||||
|
||||
## 代码演示
|
||||
|
||||
### Vue2
|
||||
- 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包
|
||||
|
||||
```html
|
||||
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef" @finished="init"></l-echart></view>
|
||||
```
|
||||
|
||||
```js
|
||||
// 插件内的 三选一
|
||||
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
|
||||
// 自定义的 三选一 下载后放入项目的路径
|
||||
import * as echarts from 'xxx/echarts.min'
|
||||
// npm包 三选一 需要在控制台 输入命令:npm install echarts
|
||||
import * as echarts from 'echarts'
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
confine: true
|
||||
},
|
||||
legend: {
|
||||
data: ['热度', '正面', '负面']
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 15,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '热度',
|
||||
type: 'bar',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'inside'
|
||||
}
|
||||
},
|
||||
data: [300, 270, 340, 344, 300, 320, 310],
|
||||
},
|
||||
{
|
||||
name: '正面',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
data: [120, 102, 141, 174, 190, 250, 220]
|
||||
},
|
||||
{
|
||||
name: '负面',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
data: [-20, -32, -21, -34, -90, -130, -110]
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
||||
},
|
||||
// 组件能被调用必须是组件的节点已经被渲染到页面上
|
||||
methods: {
|
||||
async init() {
|
||||
// chart 图表实例不能存在data里
|
||||
const chart = await this.$refs.chartRef.init(echarts);
|
||||
chart.setOption(this.option)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Vue3
|
||||
- 小程序可以使用`require`引入插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html)
|
||||
- `require`仅支持相对路径,不支持路径别名
|
||||
- 非小程序使用 `npm` 包
|
||||
|
||||
|
||||
```html
|
||||
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef"></l-echart></view>
|
||||
```
|
||||
|
||||
```js
|
||||
// 小程序 二选一
|
||||
// 插件内的 二选一
|
||||
const echarts = require('../../uni_modules/lime-echart/static/echarts.min');
|
||||
// 自定义的 二选一 下载后放入项目的路径
|
||||
const echarts = require('xxx/xxx/echarts');
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// 非小程序
|
||||
// 需要在控制台 输入命令:npm install echarts
|
||||
import * as echarts from 'echarts'
|
||||
```
|
||||
|
||||
```js
|
||||
|
||||
const chartRef = ref(null)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
confine: true
|
||||
},
|
||||
legend: {
|
||||
data: ['热度', '正面', '负面']
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 15,
|
||||
top: 40,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
axisTick: { show: false },
|
||||
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#999999'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '热度',
|
||||
type: 'bar',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'inside'
|
||||
}
|
||||
},
|
||||
data: [300, 270, 340, 344, 300, 320, 310],
|
||||
},
|
||||
{
|
||||
name: '正面',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
data: [120, 102, 141, 174, 190, 250, 220]
|
||||
},
|
||||
{
|
||||
name: '负面',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'left'
|
||||
}
|
||||
},
|
||||
data: [-20, -32, -21, -34, -90, -130, -110]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
onMounted( ()=>{
|
||||
// 组件能被调用必须是组件的节点已经被渲染到页面上
|
||||
setTimeout(async()=>{
|
||||
if(!chartRef.value) return
|
||||
const myChart = await chartRef.value.init(echarts)
|
||||
myChart.setOption(option)
|
||||
},300)
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Uvue
|
||||
- Uvue和Nvue不需要引入`echarts`,因为它们的实现方式是`webview`
|
||||
- uniapp x需要HBX 4.13以上
|
||||
|
||||
```html
|
||||
<view style="width: 100%; height: 408px;">
|
||||
<l-echart ref="chartRef" @finished="init"></l-echart>
|
||||
</view>
|
||||
```
|
||||
|
||||
```js
|
||||
// @ts-nocheck
|
||||
// #ifdef H5
|
||||
import * as echarts from 'echarts/dist/echarts.esm.js'
|
||||
// #endif
|
||||
const chartRef = ref<LEchartComponentPublicInstance|null>(null);
|
||||
const init = async () => {
|
||||
if(chartRef.value== null) return
|
||||
// #ifdef APP
|
||||
const chart = await chartRef.value!.init(null)
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
const chart = await chartRef.value!.init(echarts, null)
|
||||
// #endif
|
||||
chart.setOption(option)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 数据更新
|
||||
- 1、使用 `ref` 可获取`setOption`设置更新
|
||||
- 2、也可以拿到图表实例`chart`设置`myChart.setOption(data)`
|
||||
|
||||
```js
|
||||
// ref
|
||||
this.$refs.chart.setOption(data)
|
||||
|
||||
// 图表实例
|
||||
myChart.setOption(data)
|
||||
```
|
||||
|
||||
## 图表大小
|
||||
- 在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。
|
||||
|
||||
```js
|
||||
// 默认获取容器尺寸
|
||||
this.$refs.chart.resize()
|
||||
// 指定尺寸
|
||||
this.$refs.chart.resize({width: 375, height: 375})
|
||||
```
|
||||
|
||||
## 自定义Tooltips
|
||||
- uvue\nvue 不支持
|
||||
由于除H5之外都不存在dom,但又有tooltips个性化的需求,代码就不贴了,看示例吧
|
||||
```
|
||||
代码位于/uni_modules/lime-echart/component/lime-echart
|
||||
```
|
||||
|
||||
|
||||
## 插件标签
|
||||
- 默认 l-echart 为 component
|
||||
- 默认 lime-echart 为 demo
|
||||
```html
|
||||
// 在任意地方使用可查看domo, 代码位于/uni_modules/lime-echart/component/lime-echart
|
||||
<lime-echart></lime-echart>
|
||||
```
|
||||
|
||||
|
||||
## 常见问题
|
||||
- 钉钉小程序 由于没有`measureText`,模拟的`measureText`又无法得到当前字体的`fontWeight`,故可能存在估计不精细的问题
|
||||
- 微信小程序 `2d` 只支持 真机调试2.0
|
||||
- 微信开发工具会出现 `canvas` 不跟随页面的情况,真机不影响
|
||||
- 微信开发工具会出现 `canvas` 层级过高的问题,真机一般不受影响,可以先测只有两个元素的页面看是否会有层级问题。
|
||||
- toolbox 不支持 `saveImage`
|
||||
- echarts 5.3.0 的 lines 不支持 trailLength,故需设置为 `0`
|
||||
- dataZoom H5不要设置 `showDetail`
|
||||
- 如果微信小程序的`tooltip`文字有阴影,可能是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
|
||||
- 如果钉钉小程序上传时报安全问题`Uint8Clamped`,可以向钉钉反馈是安全代码扫描把Uint8Clamped数组错误识别了,也可以在 echarts 文件修改`Uint8Clamped`
|
||||
```js
|
||||
// 找到这段代码把代码中`Uint8Clamped`改成`Uint8_Clamped`,再把下划线去掉,不过直接去掉`Uint8Clamped`也是可行的
|
||||
// ["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"]
|
||||
// 改成如下
|
||||
["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"]
|
||||
```
|
||||
|
||||
### vue3
|
||||
如果您是使用 **vite + vue3** 非微信小程序可能会遇到`echarts`文件缺少`wx`判断导致无法使用或缺少`tooltip`<br>
|
||||
|
||||
方式一:可以在`echarts.min.js`文件开头增加以下内容,参考插件内的echart.min.js的做法
|
||||
|
||||
```js
|
||||
// 某些echarts版本下 uniapp app 需要global,不然会报__ECHARTS__DEFAULT__RENDERER__
|
||||
let global = null
|
||||
let wx = uni
|
||||
```
|
||||
|
||||
方式二:在`vite.config.js`的`define`设置环境
|
||||
|
||||
```js
|
||||
// 或者在`vite.config.js`的`define`设置环境
|
||||
import { defineConfig } from 'vite';
|
||||
import uni from '@dcloudio/vite-plugin-uni';
|
||||
|
||||
const define = {}
|
||||
if(!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) {
|
||||
define['global'] = null
|
||||
define['wx'] = 'uni'
|
||||
}
|
||||
export default defineConfig({
|
||||
plugins: [uni()],
|
||||
define
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --------------- | -------- | ------- | ------------ | ----- |
|
||||
| custom-style | 自定义样式 | `string` | - | - |
|
||||
| type | 指定 canvas 类型 | `string` | `2d` | |
|
||||
| is-disable-scroll | 触摸图表时是否禁止页面滚动 | `boolean` | `false` | |
|
||||
| beforeDelay | 延迟初始化 (毫秒) | `number` | `30` | |
|
||||
| enableHover | PC端使用鼠标悬浮 | `boolean` | `false` | |
|
||||
| landscape | 是否旋转90deg,模拟横屏效果 | `boolean` | `false` | |
|
||||
|
||||
## 事件
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --------------- | --------------- |
|
||||
| init(echarts, chart => {}) | 初始化调用函数,第一个参数是传入`echarts`,第二个参数是回调函数,回调函数的参数是 `chart` 实例 |
|
||||
| setChart(chart => {}) | 已经初始化后,请使用这个方法,是个回调函数,参数是 `chart` 实例 |
|
||||
| setOption(data) | [图表配置项](https://echarts.apache.org/zh/option.html#title),用于更新 ,传递是数据 `option` |
|
||||
| clear() | 清空当前实例,会移除实例中所有的组件和图表。 |
|
||||
| dispose() | 销毁实例 |
|
||||
| showLoading() | 显示加载 |
|
||||
| hideLoading() | 隐藏加载 |
|
||||
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(opt) | 用于生成图片,与官方使用方法一致,但不需要传`canvasId` |
|
||||
|
||||
|
||||
## 打赏
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||

|
||||

|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue