feat: init
This commit is contained in:
commit
81737d93a5
|
|
@ -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,93 @@
|
|||
<p align="center">
|
||||
<a href="https://github.com/unibest-tech/unibest">
|
||||
<img width="160" src="./src/static/logo.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<a href="https://github.com/unibest-tech/unibest" target="_blank">unibest - 最好的 uniapp 开发框架</a>
|
||||
</h1>
|
||||
|
||||
<div align="center">
|
||||
旧仓库 codercup 进不去了,star 也拿不回来,这里也展示一下那个地址的 star.
|
||||
|
||||
[](https://github.com/codercup/unibest)
|
||||
[](https://github.com/codercup/unibest)
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/feige996/unibest)
|
||||
[](https://github.com/feige996/unibest)
|
||||
[](https://gitee.com/feige996/unibest/stargazers)
|
||||
[](https://gitee.com/feige996/unibest/members)
|
||||

|
||||

|
||||

|
||||

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

|
||||
|
||||
<p align="center">
|
||||
<a href="https://unibest.tech/" target="_blank">📖 文档地址(new)</a>
|
||||
<span style="margin:0 10px;">|</span>
|
||||
<a href="https://feige996.github.io/hello-unibest/" target="_blank">📱 DEMO 地址</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
注意旧的地址 [codercup](https://github.com/codercup/unibest) 我进不去了,使用新的 [feige996](https://github.com/feige996/unibest)。PR和 issue 也请使用新地址,否则无法合并。
|
||||
|
||||
## 平台兼容性
|
||||
|
||||
| H5 | IOS | 安卓 | 微信小程序 | 字节小程序 | 快手小程序 | 支付宝小程序 | 钉钉小程序 | 百度小程序 |
|
||||
| --- | --- | ---- | ---------- | ---------- | ---------- | ------------ | ---------- | ---------- |
|
||||
| √ | √ | √ | √ | √ | √ | √ | √ | √ |
|
||||
|
||||
注意每种 `UI框架` 支持的平台有所不同,详情请看各 `UI框架` 的官网,也可以看 `unibest` 文档。
|
||||
|
||||
## ⚙️ 环境
|
||||
|
||||
- node>=18
|
||||
- pnpm>=7.30
|
||||
- Vue Official>=2.1.10
|
||||
- TypeScript>=5.0
|
||||
|
||||
## 📂 快速开始
|
||||
|
||||
执行 `pnpm create unibest` 创建项目
|
||||
执行 `pnpm i` 安装依赖
|
||||
执行 `pnpm dev` 运行 `H5`
|
||||
执行 `pnpm dev:mp` 运行 `微信小程序`
|
||||
|
||||
## 📦 运行(支持热更新)
|
||||
|
||||
- web平台: `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。
|
||||
- weixin平台:`pnpm dev:mp` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。
|
||||
- APP平台:`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。(如果是 `安卓` 和 `鸿蒙` 平台,则不用这个方式,可以把整个unibest项目导入到hbx,通过hbx的菜单来运行到对应的平台。)
|
||||
|
||||
## 🔗 发布
|
||||
|
||||
- web平台: `pnpm build:h5`,打包后的文件在 `dist/build/h5`,可以放到web服务器,如nginx运行。如果最终不是放在根目录,可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。
|
||||
- weixin平台:`pnpm build:mp`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
|
||||
- APP平台:`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。(如果是 `安卓` 和 `鸿蒙` 平台,则不用这个方式,可以把整个unibest项目导入到hbx,通过hbx的菜单来发行到对应的平台。)
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT](https://opensource.org/license/mit/)
|
||||
|
||||
Copyright (c) 2025 菲鸽
|
||||
|
||||
## 捐赠
|
||||
|
||||
<p align='center'>
|
||||
<img alt="special sponsor appwrite" src="https://oss.laf.run/ukw0y1-site/pay/wepay.png" height="330" style="display:inline-block; height:330px;">
|
||||
<img alt="special sponsor appwrite" src="https://oss.laf.run/ukw0y1-site/pay/alipay.jpg" height="330" style="display:inline-block; height:330px; margin-left:10px;">
|
||||
</p>
|
||||
|
|
@ -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.
|
|
@ -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',
|
||||
Loading…
Reference in New Issue