Compare commits

...

118 Commits

Author SHA1 Message Date
Xubx bae4538a74 人工智能分析优化 2025-07-02 16:43:13 +08:00
Qi e58fe6f13f 批量分配教室 2025-06-17 11:48:02 +08:00
Qi 04b8fa5cbc 批量分组、表格数据优化 2025-06-17 09:46:43 +08:00
Qi 9086285b76 优化数据管理字段 2025-06-15 16:39:45 +08:00
Qi b181f828dd 分组、教室初始化 2025-06-12 14:48:50 +08:00
Qi 4f3365999b 数据管理初始化 2025-06-11 13:31:35 +08:00
Qi 0c1c795d68 初始化四六级监考教师、研究生、非教师分组,分配教室 2025-06-11 13:07:02 +08:00
Xubx 2ab6b34c2c 数据分析优化 2025-04-27 15:34:34 +08:00
Qi 267a47535d 修改analyze、校领导页面默认信息 2025-04-25 19:09:47 +08:00
Xubx 225bd76543 配置修改 2025-04-24 14:22:05 +08:00
Qi 8670a0e655 修改四级成绩分析页面 饼图改为柱状图 2025-04-10 20:27:41 +08:00
Qi c8405d3d11 Merge remote-tracking branch 'origin/feature/1.0版本页面优化' into feature/1.0版本页面优化 2025-03-28 15:51:00 +08:00
Qi abb880d17c 添加引导界面 2025-03-28 15:50:04 +08:00
Cool 571e410c0e 新增成绩优化 2025-03-28 13:48:26 +08:00
Qi 657aebdc99 修改用户退出后记录退出路径重定向不是欢迎界面问题 2025-03-24 20:56:46 +08:00
Qi 7b47fa447a Merge remote-tracking branch 'refs/remotes/origin/feature/1.0版本校领导页面开发' into feature/1.0版本页面优化
# Conflicts:
#	src/views/cet/universityDashboard.vue
2025-03-24 19:56:17 +08:00
Qi 13d5df6d05 优化首页相关视图 2025-03-23 20:42:12 +08:00
Qi 56cff6ecdd 修改按单批次饼图鼠标聚焦提示 2025-03-22 19:07:09 +08:00
YuNan bbaf21be33 添加欢迎界面修改用户登入初始路由 2025-03-19 19:06:30 +08:00
YuNan 46d1d319f6 优化首页表格 2025-03-19 17:49:52 +08:00
Cool ffefebd785 修复学院专业选择框问题以及添加引导界面并删除多余无用代码 2025-03-19 17:12:04 +08:00
YuNan 688820ccce 后端改为本地 2025-03-16 21:00:29 +08:00
YuNan c9219a877f Merge remote-tracking branch 'refs/remotes/origin/feature/1.0版本校领导页面开发' into feature/1.0版本页面优化
# Conflicts:
#	src/views/cet/universityDashboard.vue
2025-03-16 20:14:41 +08:00
YuNan c123a87789 修改按单批次分析批次选择框lable 2025-03-16 15:45:09 +08:00
YuNan 930fbdf474 优化首页效果 2025-03-16 15:14:46 +08:00
YuNan 02024f1e24 首页的顶部优化1 2025-03-15 18:28:54 +08:00
Xubx 15a02bae94 Merge pull request 'feature/1.0版本院领导页面开发' (#2) from feature/1.0版本院领导页面开发 into feature/1.0版本页面优化
Reviewed-on: #2
2025-02-03 15:41:06 +08:00
Xubx 43356879ee 首页优化 2025-02-03 13:09:58 +08:00
YuNan eb54c96072 院领导首页标签更改 2025-01-21 16:15:13 +08:00
YuNan d66336ab60 院首页动态查询功能实现 2025-01-20 17:36:28 +08:00
Su 608a340a2a 布局调整,更改加载方式 2025-01-20 14:51:17 +08:00
Su f0db8e010d 更改了数据的加载方式 2025-01-17 22:32:12 +08:00
Su 7aa48aba48 校领导\外研处首页优化,权限分配 2025-01-16 15:26:35 +08:00
YuNan 8f96b9ba85 添加院领导首页数据的加载效果 2025-01-14 16:00:00 +08:00
YuNan 2b83ad54a3 基本实现院领导首页 2024-12-16 18:40:24 +08:00
YuNan 7cc1046d67 登录页面优化 2024-12-10 14:27:04 +08:00
Xubx f0ed560e73 院领导页面开发 2024-12-10 14:16:33 +08:00
Xubx 87089b2adf 校领导页面开发 2024-12-10 13:49:30 +08:00
YuNan f0b15ce2da 添加首页下拉框全校(不含艺体美外语) 2024-12-04 19:46:05 +08:00
YuNan c8c15e6182 数据懒加载 2024-10-25 15:00:00 +08:00
YuNan 03ad85478f 数据库字段id更改为code 2024-10-24 18:56:43 +08:00
Cool 87daede414 四六级数据同步 2024-10-24 15:04:53 +08:00
Cool d0a33280d3 学生导入 2024-10-23 22:19:31 +08:00
YuNan 347fb88477 学生信息页面 2024-10-23 19:19:55 +08:00
Cool baf4b4246b 导入界面修改 2024-10-23 18:11:28 +08:00
YuNan 60750d0ca4 页面优化 和 数据导入功能 2024-10-23 14:00:52 +08:00
YuNan 4aef7d706d 页面优化 和 数据导入 2024-10-23 14:00:27 +08:00
Xubx cf89aae584 Merge branch 'feature/1.0版本页面优化' of http://62.234.217.137:3000/Big-Data-Lab/CET-vue-3.0 into feature/1.0版本页面优化 2024-10-22 18:57:18 +08:00
Xubx df1aab15cd 学院维度页面优化 2024-10-22 18:57:00 +08:00
YuNan e60233645a 按批次页面优化 2024-10-17 19:19:51 +08:00
Xubx dd0090a826 合并 2024-10-17 14:20:31 +08:00
Xubx 5610e30c11 Merge remote-tracking branch 'origin/feature/1.0版本页面优化' into feature/1.0版本页面优化 2024-10-17 14:19:19 +08:00
Xubx aaedf72cf3 合并 2024-10-17 14:19:04 +08:00
Xubx 54d1bcb9aa Merge branch 'feature/1.0版本页面优化' of http://62.234.217.137:3000/Big-Data-Lab/CET-vue-3.0 into feature/1.0版本页面优化 2024-10-17 14:12:31 +08:00
Xubx ccd3305924 首页与按专业维度分析 2024-10-17 14:08:39 +08:00
YuNan cf6302cc71 页面优化 2024-10-17 13:48:28 +08:00
Xubx 80a240675d 页面布局改进 2024-10-11 15:29:09 +08:00
linlihong 4c60ba4ca7 删除a-table自带分页 2024-09-10 10:57:30 +08:00
Cool 9632a7bcce 修改查询逻辑 2024-09-09 23:12:47 +08:00
Cool 3ffbf5d13e Merge remote-tracking branch 'origin/DEV' into DEV
# Conflicts:
#	src/views/cet/cet-student-query.vue
2024-09-09 23:07:28 +08:00
Cool 32fbd95a23 学生查询修改 2024-09-09 23:05:52 +08:00
linlihong 7abb43c29a 修改接口路径 2024-09-09 09:44:56 +08:00
Cool 905c460bf3 根据学生学号姓名查询成绩 2024-09-08 23:49:52 +08:00
Cool 04b3da5ab6 修改后端路径 2024-09-08 16:03:07 +08:00
xbx 337e23ac7e 返回按钮优化 2024-06-02 19:29:58 +08:00
Cool 4f2001c9d4 添加柱状图点击事件 2024-05-31 20:38:23 +08:00
xbx 0e736b3217 ui修改 2024-05-31 20:37:47 +08:00
xbx f3b756a7cf ui修改 2024-05-31 16:28:13 +08:00
xbx 0708525435 ui优化 2024-05-31 16:12:34 +08:00
Cool b97af54042 修改单批次折线图为柱状图 2024-05-31 14:49:19 +08:00
Cool e793f86ccd Merge branch 'DEV' of http://82.157.76.162:3000/Big-Data-Lab/CET-vue-3.0 into DEV 2024-05-31 14:40:44 +08:00
Cool 1f79794e32 ui修改 2024-05-31 14:40:41 +08:00
xbx 2193470e4b ui优化 2024-05-31 12:56:43 +08:00
xbx 4c9af6fe98 Merge branch 'DEV' of http://82.157.76.162:3000/Big-Data-Lab/CET-vue-3.0 into DEV 2024-05-30 20:49:34 +08:00
xbx 82bd51ca66 ui优化 2024-05-30 20:49:02 +08:00
Cool 965fe871c8 添加标题 2024-05-30 20:48:49 +08:00
Cool 3f98ead83c 学院功能拆分,添加考场管理、四六级排考 2024-05-30 20:08:37 +08:00
Cool 4cdb5352a6 ui修改 2024-05-30 19:52:36 +08:00
xbx 7ba725c13f ui优化 2024-05-30 19:25:28 +08:00
Cool d7bdcadfc7 Merge branch 'DEV' of http://82.157.76.162:3000/Big-Data-Lab/CET-vue-3.0 into DEV 2024-05-27 13:25:14 +08:00
Cool 1d68fa9a7b 修改部分bug以及饼图颜色 2024-05-27 13:23:32 +08:00
xbx d81438ddba 部分bug修改 2024-05-20 12:22:00 +08:00
Cool b9d306edc5 修改部分bug 2024-05-06 14:28:58 +08:00
Cool f96f5bf6ab 对部分数据校验进行修改 2024-04-29 16:52:54 +08:00
Cool 78d271d832 修改学院对比样式 2024-04-29 16:46:41 +08:00
xbx 7cb5a9e2bb 优化查询 2024-04-28 13:35:54 +08:00
Cool bbe7ca9083 修改专业对比 2024-04-26 21:31:34 +08:00
Cool 9941a2d725 添加柱状图legend属性 2024-04-24 17:12:42 +08:00
Cool 7fc0f45a6a 完善学院对比 2024-04-23 23:55:23 +08:00
Cool 29f5da8c52 完善学院对比功能 2024-04-23 16:43:44 +08:00
Cool 39fb1184f6 完善学院对比功能 2024-04-23 15:52:49 +08:00
xbx b85755ed33 首页优化 2024-04-20 20:57:10 +08:00
xbx 59d3f54666 Merge branch 'DEV' of http://82.157.76.162:3000/Big-Data-Lab/CET-vue-3.0 into DEV 2024-04-17 21:44:39 +08:00
xbx 4a1c11dd5e 11 2024-04-17 21:44:36 +08:00
Cool 0ef5138f30 增加全校排序 2024-04-17 13:22:19 +08:00
Cool b5e4153350 添加学院/专业对比年级多选功能 2024-04-16 21:56:54 +08:00
xbx ea441d3126 部分需求 2024-04-15 22:00:30 +08:00
xbx 06f8f0c7d9 部分需求 2024-04-15 21:55:27 +08:00
Cool 9d1b7cc623 四级可视化分析1.1 2024-04-10 21:34:49 +08:00
xbx 171dc8f3f6 基本功能完成 2024-04-08 16:41:42 +08:00
xbx e7786d3429 增加专业 2024-04-03 14:36:10 +08:00
xbx 50a62cbae3 加入专业 2024-04-02 18:00:44 +08:00
xbx f8a938049f 合并 2024-03-27 19:53:47 +08:00
hh c90db7414d 修改UI 2024-03-27 19:12:05 +08:00
hh 7e7c31417f UI修改 2024-03-27 18:46:10 +08:00
hh daff63566b 前端修改 2024-03-27 18:26:50 +08:00
hh a3a14d4ccc 修改 2024-03-27 17:08:37 +08:00
xbx 0ef20f7d29 合并 2024-03-27 16:22:27 +08:00
xbx 7a17e9a0ed 合并 2024-03-27 16:10:08 +08:00
xbx 81d2d93618 Merge remote-tracking branch 'origin/DEV' into DEV
# Conflicts:
#	src/views/dashboard/Analysis/index.vue
2024-03-27 15:58:02 +08:00
xbx 8cf7443af6 首页四六级查询 2024-03-27 15:49:00 +08:00
hh 2bed8d3933 按钮 2024-03-26 19:45:33 +08:00
hh e1f887e055 首页部分 2024-03-26 17:33:19 +08:00
xbx c7569fcfa9 sb 2024-03-26 17:32:55 +08:00
xbx 5f5c2a6b4a Merge branch 'DEV' of http://82.157.76.162:3000/Big-Data-Lab/CET-vue-3.0 into DEV 2024-03-26 13:30:58 +08:00
hh 61c3e9026b 修改样式 2024-03-26 13:30:29 +08:00
xbx 9f00876040 文件路由 2024-03-26 13:30:08 +08:00
hh a4012e1b3a 初始化仓库 2024-03-26 11:19:01 +08:00
198 changed files with 22014 additions and 26633 deletions

2
.env
View File

@ -2,7 +2,7 @@
VITE_PORT = 3100
# 网站标题
VITE_GLOB_APP_TITLE = JeecgBoot 企业级低代码平台
VITE_GLOB_APP_TITLE = 哈尔滨师范大学英语四六级综合管理平台
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
VITE_GLOB_APP_SHORT_NAME = JeecgBootAdmin

View File

@ -5,11 +5,16 @@ VITE_USE_MOCK = true
VITE_PUBLIC_PATH = /
## 跨域代理,您可以配置多个 ,请注意,没有换行符
#VITE_PROXY = [["/jeecgboot","http://62.234.217.137:10000/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
##后台接口全路径地址(必填)
#VITE_GLOB_DOMAIN_URL=http://62.234.217.137:10000/jeecg-boot
# 跨域代理,您可以配置多个 ,请注意,没有换行符
VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
VITE_PROXY = [["/jeecgboot","http://127.0.0.1:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
VITE_GLOB_DOMAIN_URL=http://127.0.0.1:8080/jeecg-boot
#后台接口父地址(必填)
VITE_GLOB_API_URL=/jeecgboot

View File

@ -16,7 +16,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_GLOB_API_URL=/jeecgboot
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
VITE_GLOB_DOMAIN_URL=http://62.234.217.137:10000/jeecg-boot
# 接口父路径前缀
VITE_GLOB_API_URL_PREFIX=

View File

@ -1,16 +0,0 @@
##### 版本号:
##### 问题描述:
##### 截图&代码:
#### 友情提示为了提高issue处理效率
- 未按格式要求发帖,会被直接删掉;
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;

38
LICENSE
View File

@ -1,37 +1 @@
MIT License
Copyright (c) 2020-present, Jeecg
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.
开源协议补充
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
1.允许基于本平台软件开展业务系统开发。
2.JeecgBoot底层依赖的非开源功能online lib依赖、仪表盘lib依赖等统一采用LGPL开源协议不二次改造、不拆分出jeecgboot之外使用就不产生侵权
3.不得基于该平台软件的基础修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售或与JeecgBoot参与同类软件产品市场的竞争。
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
总结在遵循Apache开源协议和开源协议补充条款下允许商用使用不会造成侵权行为
解释权归:
http://www.jeecg.com
http://guojusoft.com
黄晖制作

View File

@ -1,11 +1,11 @@
JEECG BOOT 低代码开发平台Vue3前端
===============
当前最新版本: 3.6.3发布时间2024-03-11
当前最新版本: 3.6.2发布时间2024-01-08
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://jeecg.com/aboutusIndex)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.6.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/version-3.6.2-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)

View File

@ -5,7 +5,6 @@ import purgeIcons from 'vite-plugin-purge-icons';
import UnoCSS from 'unocss/vite';
import { presetTypography, presetUno } from 'unocss';
// 本地调试https配置方法
import VitePluginCertificate from 'vite-plugin-mkcert';
//[issues/555]开发环境vscode断点调试文件或行数对不上
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';

View File

@ -157,11 +157,10 @@
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div class="app-loading-title"><%= title %></div>
<div class="app-loading-title">正在加载 <%= title %></div>
</div>
</div>
</div>

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -6,19 +6,10 @@ const demoList = (keyword, count = 20) => {
list: [] as any[],
};
for (let index = 0; index < count; index++) {
//根据搜索关键词做一下匹配
let name = `选项${index}`;
if(keyword && name.indexOf(keyword)!=-1){
result.list.push({
name: `选项${index}`,
id: `${index}`,
});
}else if(!keyword){
result.list.push({
name: `选项${index}`,
id: `${index}`,
});
}
result.list.push({
name: `${keyword ?? ''}选项${index}`,
id: `${index}`,
});
}
return result;
};

View File

@ -1,6 +1,6 @@
{
"name": "jeecgboot-vue3",
"version": "3.6.3",
"version": "3.6.2",
"author": {
"name": "北京国炬信息技术有限公司",
"email": "jeecgos@163.com",
@ -45,11 +45,7 @@
"intro.js": "^7.2.0",
"lodash-es": "^4.17.21",
"lodash.get": "^4.4.2",
"markdown-it": "^14.0.0",
"markdown-it-link-attributes": "^4.0.1",
"@traptitech/markdown-it-katex": "^3.6.0",
"event-source-polyfill": "^1.0.31",
"highlight.js": "^11.9.0",
"marked": "^12.0.0",
"md5": "^2.3.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",

File diff suppressed because it is too large Load Diff

View File

@ -35,11 +35,6 @@
if (newValue === ThemeEnum.DARK) {
appTheme.value.algorithm = theme.darkAlgorithm;
}
// update-begin--author:liaozhiyang---date:20240322---forQQYUN-8570
if (import.meta.env.PROD) {
changeTheme(appStore.getProjectConfig.themeColor);
}
// update-end--author:liaozhiyang---date:20240322---forQQYUN-8570
appTheme.value = {
...appTheme.value,
};

View File

@ -3,8 +3,7 @@ import { getMenuListResultModel } from './model/menuModel';
enum Api {
GetMenuList = '/sys/permission/getUserPermissionByToken',
// 【QQYUN-8487】
// SwitchVue3Menu = '/sys/switchVue3Menu',
SwitchVue3Menu = '/sys/switchVue3Menu',
}
/**
@ -24,20 +23,11 @@ export const getMenuList = () => {
});
};
/**
* @description:
*/
export function getBackMenuAndPerms() {
return defHttp.get({ url: Api.GetMenuList });
}
/**
* vue3菜单
*/
// update-begin--author:liaozhiyang---date:20240313---for【QQYUN-8487】注释掉判断菜单是否vue2版本逻辑代码
// export const switchVue3Menu = () => {
// return new Promise((resolve) => {
// defHttp.get({ url: Api.SwitchVue3Menu });
// });
// };
// update-end--author:liaozhiyang---date:20240313---for【QQYUN-8487】注释掉判断菜单是否vue2版本逻辑代码
export const switchVue3Menu = () => {
return new Promise((resolve) => {
defHttp.get({ url: Api.SwitchVue3Menu });
});
};

View File

@ -126,9 +126,6 @@ export function getCaptcha(params) {
createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
reject();
}
}).catch((res)=>{
createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
reject();
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -34,7 +34,8 @@
flex-basis: 60%;
-webkit-flex-basis: 60%;
background-color: #0198cd;
background-image: url(../icon/jeecg_ad.png);
background-image: url(../icon/jeecg_ad.jpg);
opacity: 0.91;
background-size: cover;
}

View File

@ -4,9 +4,10 @@
-->
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome">
<img src="../../../assets/images/logo.png" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
{{ title }}
<img style="width: 100%;" src="../../../assets/images/logo-red.png" />
<div style="display: flex;" class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
<!-- {{ title }} -->
<!-- <span>英语四六级综合管理平台</span> -->
</div>
</div>
</template>

View File

@ -12,7 +12,6 @@ export interface SearchResult {
name: string;
path: string;
icon?: string;
internalOrExternal: boolean;
}
// Translate special characters
@ -24,7 +23,7 @@ function transform(c: string) {
function createSearchReg(key: string) {
const keys = [...key].map((item) => transform(item));
const str = ['', ...keys, ''].join('.*');
return new RegExp(str, 'i');
return new RegExp(str);
}
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
@ -69,13 +68,12 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
const ret: SearchResult[] = [];
filterMenu.forEach((item) => {
const { name, path, icon, children, hideMenu, meta, internalOrExternal } = item;
const { name, path, icon, children, hideMenu, meta } = item;
if (!hideMenu && reg.test(name) && (!children?.length || meta?.hideChildrenInMenu)) {
ret.push({
name: parent?.name ? `${parent.name} > ${name}` : name,
path,
icon,
internalOrExternal
});
}
if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) {
@ -151,14 +149,7 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
const to = result[index];
handleClose();
await nextTick();
// update-begin--author:liaozhiyang---date:20230803---for【QQYUN-8369】搜索区分大小写外部链接新页打开
if (to.internalOrExternal) {
window.open(to.path, '_blank');
} else {
go(to.path);
}
// update-end--author:liaozhiyang---date:20230803---for【QQYUN-8369】搜索区分大小写外部链接新页打开
go(to.path);
}
// close search modal

View File

@ -59,9 +59,7 @@
instance && emit('register', drawerInstance, instance.uid);
const getMergeProps = computed((): DrawerProps => {
// update-begin--author:liaozhiyang---date:20240320---forQQYUN-8389vue3.4footertoRow,propscomputed
return { ...deepMerge(props, unref(propsRef)) };
// update-end--author:liaozhiyang---date:20240320---forQQYUN-8389vue3.4footertoRow,propscomputed
return deepMerge(toRaw(props), unref(propsRef));
});
const getProps = computed((): DrawerProps => {

View File

@ -1,14 +1,6 @@
/**
*
* JAreaLinkage
* JEditor
* JMarkdownEditor
* JCodeEditor
* JEasyCron
*/
import type { Component } from 'vue';
import type { ComponentType } from './types/index';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
/**
* Component list, register here to setting it in the form
*/
@ -37,7 +29,7 @@ import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon';
import { CountdownInput } from '/@/components/CountDown';
//自定义组件
// import JAreaLinkage from './jeecg/components/JAreaLinkage.vue';
import JAreaLinkage from './jeecg/components/JAreaLinkage.vue';
import JSelectUser from './jeecg/components/JSelectUser.vue';
import JSelectPosition from './jeecg/components/JSelectPosition.vue';
import JSelectRole from './jeecg/components/JSelectRole.vue';
@ -45,20 +37,17 @@ import JImageUpload from './jeecg/components/JImageUpload.vue';
import JDictSelectTag from './jeecg/components/JDictSelectTag.vue';
import JSelectDept from './jeecg/components/JSelectDept.vue';
import JAreaSelect from './jeecg/components/JAreaSelect.vue';
// import JEditor from './jeecg/components/JEditor.vue';
// import JMarkdownEditor from './jeecg/components/JMarkdownEditor.vue';
import JEditor from './jeecg/components/JEditor.vue';
import JMarkdownEditor from './jeecg/components/JMarkdownEditor.vue';
import JSelectInput from './jeecg/components/JSelectInput.vue';
// import JCodeEditor from './jeecg/components/JCodeEditor.vue';
import JCodeEditor from './jeecg/components/JCodeEditor.vue';
import JCategorySelect from './jeecg/components/JCategorySelect.vue';
import JSelectMultiple from './jeecg/components/JSelectMultiple.vue';
import JPopup from './jeecg/components/JPopup.vue';
// update-begin--author:liaozhiyang---date:20240130---for【QQYUN-7961】popupDict字典
import JPopupDict from './jeecg/components/JPopupDict.vue';
// update-end--author:liaozhiyang---date:20240130---for【QQYUN-7961】popupDict字典
import JSwitch from './jeecg/components/JSwitch.vue';
import JTreeDict from './jeecg/components/JTreeDict.vue';
import JInputPop from './jeecg/components/JInputPop.vue';
// import { JEasyCron } from './jeecg/components/JEasyCron';
import { JEasyCron } from './jeecg/components/JEasyCron';
import JCheckbox from './jeecg/components/JCheckbox.vue';
import JInput from './jeecg/components/JInput.vue';
import JTreeSelect from './jeecg/components/JTreeSelect.vue';
@ -112,10 +101,7 @@ componentMap.set('Upload', BasicUpload);
componentMap.set('Divider', Divider);
//注册自定义组件
componentMap.set(
'JAreaLinkage',
createAsyncComponent(() => import('./jeecg/components/JAreaLinkage.vue'))
);
componentMap.set('JAreaLinkage', JAreaLinkage);
componentMap.set('JSelectPosition', JSelectPosition);
componentMap.set('JSelectUser', JSelectUser);
componentMap.set('JSelectRole', JSelectRole);
@ -123,32 +109,17 @@ componentMap.set('JImageUpload', JImageUpload);
componentMap.set('JDictSelectTag', JDictSelectTag);
componentMap.set('JSelectDept', JSelectDept);
componentMap.set('JAreaSelect', JAreaSelect);
componentMap.set(
'JEditor',
createAsyncComponent(() => import('./jeecg/components/JEditor.vue'))
);
componentMap.set(
'JMarkdownEditor',
createAsyncComponent(() => import('./jeecg/components/JMarkdownEditor.vue'))
);
componentMap.set('JEditor', JEditor);
componentMap.set('JMarkdownEditor', JMarkdownEditor);
componentMap.set('JSelectInput', JSelectInput);
componentMap.set(
'JCodeEditor',
createAsyncComponent(() => import('./jeecg/components/JCodeEditor.vue'))
);
componentMap.set('JCodeEditor', JCodeEditor);
componentMap.set('JCategorySelect', JCategorySelect);
componentMap.set('JSelectMultiple', JSelectMultiple);
componentMap.set('JPopup', JPopup);
// update-begin--author:liaozhiyang---date:20240130---for【QQYUN-7961】popupDict字典
componentMap.set('JPopupDict', JPopupDict);
// update-end--author:liaozhiyang---date:20240130---for【QQYUN-7961】popupDict字典
componentMap.set('JSwitch', JSwitch);
componentMap.set('JTreeDict', JTreeDict);
componentMap.set('JInputPop', JInputPop);
componentMap.set(
'JEasyCron',
createAsyncComponent(() => import('./jeecg/components/JEasyCron/EasyCronInput.vue'))
);
componentMap.set('JEasyCron', JEasyCron);
componentMap.set('JCheckbox', JCheckbox);
componentMap.set('JInput', JInput);
componentMap.set('JTreeSelect', JTreeSelect);

View File

@ -44,9 +44,7 @@
watch(
() => props.params,
() => {
//update-begin---author:wangshuai---date:2024-02-28---for:QQYUN-8346 ApiTreeSelect #1054---
unref(isFirstLoaded) && fetch();
//update-end---author:wangshuai---date:2024-02-28---for:QQYUN-8346 ApiTreeSelect #1054---
!unref(isFirstLoaded) && fetch();
},
{ deep: true }
);

View File

@ -105,22 +105,6 @@
return disabled;
});
// update-begin--author:liaozhiyang---date:20240308---forQQYUN-8377formSchema props
const getDynamicPropsValue = computed(() => {
const { dynamicPropsVal, dynamicPropskey } = props.schema;
if (dynamicPropskey == null) {
return null;
} else {
const { [dynamicPropskey]: itemValue } = unref(getComponentsProps);
let value = itemValue;
if (isFunction(dynamicPropsVal)) {
value = dynamicPropsVal(unref(getValues));
return value;
}
}
});
// update-end--author:liaozhiyang---date:20240308---forQQYUN-8377formSchema props
function getShow(): { isShow: boolean; isIfShow: boolean } {
const { show, ifShow } = props.schema;
const { showAdvancedButton } = props.formProps;
@ -199,12 +183,9 @@
rule.required = false;
}
if (component) {
//update-begin---author:wangshuai---date:2024-02-01---for:QQYUN-8176,,ApiSelect,,使,---
//https://github.com/vbenjs/vue-vben-admin/pull/3082 github
/*if (!Reflect.has(rule, 'type')) {
if (!Reflect.has(rule, 'type')) {
rule.type = component === 'InputNumber' ? 'number' : 'string';
}*/
//update-end---author:wangshuai---date:2024-02-01---for:QQYUN-8176,,ApiSelect,,使,---
}
rule.message = rule.message || defaultMsg;
@ -292,12 +273,6 @@
...unref(getComponentsProps),
disabled: unref(getDisable),
};
// update-begin--author:liaozhiyang---date:20240308---forQQYUN-8377formSchema props
const dynamicPropskey = props.schema.dynamicPropskey;
if (dynamicPropskey) {
propsData[dynamicPropskey] = unref(getDynamicPropsValue);
}
// update-end--author:liaozhiyang---date:20240308---forQQYUN-8377formSchema props
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
// RangePicker place
@ -337,19 +312,21 @@
//update-begin-author:taoyan date:2022-9-7 for: VUEN-2061online4 ..
//label
const { label, helpMessage, helpComponentProps, subLabel, labelLength } = props.schema;
let showLabel: string = label + '';
if (labelLength && showLabel.length > 4) {
let showLabel:string = (label+'')
if(labelLength && showLabel.length>4){
showLabel = showLabel.substr(0, labelLength);
}
const titleObj = { title: label };
const titleObj = {title: label}
const renderLabel = subLabel ? (
<span>
{label} <span class="text-secondary">{subLabel}</span>
</span>
) : labelLength ? (
<label {...titleObj}>{showLabel}</label>
) : (
label
labelLength ? (
<label {...titleObj}>{showLabel}</label>
) : (
label
)
);
//update-end-author:taoyan date:2022-9-7 for: VUEN-2061online4 ..
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
@ -411,14 +388,14 @@
}
const { baseColProps = {} } = props.formProps;
// update-begin--author:liaozhiyang---date:20230803---forissues-641span
// update-begin--author:liaozhiyang---date:20230803---forissues-641span
const { getIsMobile } = useAppInject();
let realColProps;
realColProps = { ...baseColProps, ...colProps };
if (colProps['span'] && !unref(getIsMobile)) {
['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].forEach((name) => delete realColProps[name]);
}
// update-end--author:liaozhiyang---date:20230803---forissues-641span
// update-end--author:liaozhiyang---date:20230803---forissues-641span
const { isIfShow, isShow } = getShow();
const values = unref(getValues);

View File

@ -36,12 +36,6 @@ function genType() {
}
export function setComponentRuleType(rule: ValidationRule, component: ComponentType, valueFormat: string) {
//update-begin---author:wangshuai---date:2024-02-01---for:【QQYUN-8176】编辑表单中,校验必填时,如果组件是ApiSelect,打开编辑页面时,即使该字段有值,也会提示请选择---
//https://github.com/vbenjs/vue-vben-admin/pull/3082 github修复原文
if (Reflect.has(rule, 'type')) {
return;
}
//update-end---author:wangshuai---date:2024-02-01---for:【QQYUN-8176】编辑表单中,校验必填时,如果组件是ApiSelect,打开编辑页面时,即使该字段有值,也会提示请选择---
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
rule.type = valueFormat ? 'string' : 'object';
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {

View File

@ -1,9 +1,5 @@
<template>
<a-checkbox-group v-bind="attrs" v-model:value="checkboxArray" :options="checkOptions" @change="handleChange">
<template #label="{label, value}">
<span :class="[useDicColor && getDicColor(value) ? 'colorText' : '']" :style="{ backgroundColor: `${getDicColor(value)}` }">{{ label }}</span>
</template>
</a-checkbox-group>
<a-checkbox-group v-bind="attrs" v-model:value="checkboxArray" :options="checkOptions" @change="handleChange"></a-checkbox-group>
</template>
<script lang="ts">
@ -17,7 +13,6 @@
props: {
value:propTypes.oneOfType([propTypes.string, propTypes.number]),
dictCode: propTypes.string,
useDicColor: propTypes.bool.def(false),
options: {
type: Array,
default: () => [],
@ -74,7 +69,6 @@
prev.push({
label: next['text'],
value: value,
color: next['color'],
});
}
return prev;
@ -90,30 +84,8 @@
emit('update:value', $event.join(','));
emit('change', $event.join(','));
}
const getDicColor = (value) => {
if (props.useDicColor) {
const findItem = checkOptions.value.find((item) => item.value == value);
if (findItem) {
return findItem.color;
}
}
return null;
};
return { checkboxArray, checkOptions, attrs, handleChange, getDicColor };
return { checkboxArray, checkOptions, attrs, handleChange };
},
});
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
.colorText {
display: inline-block;
height: 20px;
line-height: 20px;
padding: 0 6px;
border-radius: 8px;
background-color: red;
color: #fff;
font-size: 12px;
}
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
</style>

View File

@ -2,9 +2,7 @@
<a-radio-group v-if="compType === CompTypeEnum.Radio" v-bind="attrs" v-model:value="state" @change="handleChangeRadio">
<template v-for="item in dictOptions" :key="`${item.value}`">
<a-radio :value="item.value">
<span :class="[useDicColor && item.color ? 'colorText' : '']" :style="{ backgroundColor: `${useDicColor && item.color}` }">
{{ item.label }}
</span>
{{ item.label }}
</a-radio>
</template>
</a-radio-group>
@ -43,11 +41,7 @@
<a-select-option v-if="showChooseOption" :value="null">请选择</a-select-option>
<template v-for="item in dictOptions" :key="`${item.value}`">
<a-select-option :value="item.value">
<span
:class="[useDicColor && item.color ? 'colorText' : '']"
:style="{ backgroundColor: `${useDicColor && item.color}` }"
:title="item.label"
>
<span style="display: inline-block; width: 100%" :title="item.label">
{{ item.label }}
</span>
</a-select-option>
@ -75,7 +69,6 @@
type: propTypes.string,
placeholder: propTypes.string,
stringToNumber: propTypes.bool,
useDicColor: propTypes.bool.def(false),
getPopupContainer: {
type: Function,
default: (node) => node?.parentNode,
@ -146,8 +139,7 @@
prev.push({
label: next['text'] || next['label'],
value: stringToNumber ? +value : value,
color: next['color'],
...omit(next, ['text', 'value', 'color']),
...omit(next, ['text', 'value']),
});
}
return prev;
@ -221,17 +213,3 @@
},
});
</script>
<style scoped lang="less">
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
.colorText {
display: inline-block;
height: 20px;
line-height: 20px;
padding: 0 6px;
border-radius: 8px;
background-color: red;
color: #fff;
font-size: 12px;
}
// update-begin--author:liaozhiyang---date:20230110---forQQYUN-7799
</style>

View File

@ -1,217 +0,0 @@
<!--popup组件-->
<template>
<div class="components-input-demo-presuffix">
<!--输入框-->
<a-select v-model:value="showText" v-bind="attrs" :mode="multi ? 'multiple' : ''" @click="handleOpen" readOnly :loading="loading">
<a-select-option v-for="item in options" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
<!--popup弹窗-->
<JPopupOnlReportModal
@register="regModal"
:code="code"
:multi="multi"
:sorter="sorter"
:groupId="''"
:param="param"
@ok="callBack"
:getContainer="getContainer"
/>
</div>
</template>
<script lang="ts">
import JPopupOnlReportModal from './modal/JPopupOnlReportModal.vue';
import { defineComponent, ref, nextTick, watch, reactive, unref } from 'vue';
import { useModal } from '/@/components/Modal';
import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
//url
const configUrl = reactive({
getColumns: '/online/cgreport/api/getRpColumns/',
getData: '/online/cgreport/api/getData/',
});
export default defineComponent({
name: 'JPopupDict',
components: {
JPopupOnlReportModal,
},
inheritAttrs: false,
props: {
/**
* 示例demo,name,id
* demo: online报表编码
* name: online报表的字段用户显示的label
* id: online报表的字段用于存储key
*/
dictCode: propTypes.string.def(''),
value: propTypes.string.def(''),
sorter: propTypes.string.def(''),
multi: propTypes.bool.def(false),
param: propTypes.object.def({}),
spliter: propTypes.string.def(','),
getContainer: propTypes.func,
},
emits: ['update:value', 'register', 'change'],
setup(props, { emit }) {
const { createMessage } = useMessage();
const attrs = useAttrs();
const showText = ref<any>(props.multi ? [] : '');
const options = ref<any>([]);
const cgRpConfigId = ref('');
const loading = ref(false);
const code = props.dictCode.split(',')[0];
const labelFiled = props.dictCode.split(',')[1];
const valueFiled = props.dictCode.split(',')[2];
if (!code || !valueFiled || !labelFiled) {
createMessage.error('popupDict参数未正确配置!');
}
//model
const [regModal, { openModal }] = useModal();
/**
* 打开pop弹出框
*/
function handleOpen() {
!props.disabled && openModal(true);
}
/**
* 监听value数值
*/
watch(
() => props.value,
(val) => {
const callBack = () => {
if (props.multi) {
showText.value = val && val.length > 0 ? val.split(props.spliter) : [];
} else {
showText.value = val ?? '';
}
};
if (props.value || props.defaultValue) {
if (cgRpConfigId.value) {
loadData({ callBack });
} else {
loadColumnsInfo({ callBack });
}
} else {
callBack();
}
},
{ immediate: true }
);
watch(
() => showText.value,
(val) => {
let result;
if (props.multi) {
result = val.join(',');
} else {
result = val;
}
nextTick(() => {
emit('change', result);
emit('update:value', result);
});
}
);
/**
* 加载列信息
*/
function loadColumnsInfo({ callBack }) {
loading.value = true;
let url = `${configUrl.getColumns}${code}`;
defHttp
.get({ url }, { isTransformResponse: false, successMessageMode: 'none' })
.then((res) => {
if (res.success) {
cgRpConfigId.value = res.result.cgRpConfigId;
loadData({ callBack });
}
})
.catch((err) => {
loading.value = false;
callBack?.();
});
}
function loadData({ callBack }) {
loading.value = true;
let url = `${configUrl.getData}${unref(cgRpConfigId)}`;
defHttp
.get(
{ url, params: { ['force_' + valueFiled]: props.value || props.defaultValue } },
{ isTransformResponse: false, successMessageMode: 'none' }
)
.then((res) => {
let data = res.result;
if (data.records?.length) {
options.value = data.records.map((item) => {
return { value: item[valueFiled], text: item[labelFiled] };
});
}
})
.finally(() => {
loading.value = false;
callBack?.();
});
}
/**
* 传值回调
*/
function callBack(rows) {
const dataOptions: any = [];
const dataValue: any = [];
let result;
rows.forEach((item) => {
dataOptions.push({ value: item[valueFiled], text: item[labelFiled] });
dataValue.push(item[valueFiled]);
});
options.value = dataOptions;
if (props.multi) {
showText.value = dataValue;
result = dataValue.join(props.spliter);
} else {
showText.value = dataValue[0];
result = dataValue[0];
}
nextTick(() => {
emit('change', result);
emit('update:value', result);
});
}
return {
showText,
attrs,
regModal,
handleOpen,
callBack,
code,
options,
loading,
};
},
});
</script>
<style lang="less" scoped>
.components-input-demo-presuffix {
:deep(.ant-select-dropdown) {
display: none !important;
}
}
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>

View File

@ -11,7 +11,7 @@
:getPopupContainer="getParentContainer"
>
<a-select-option v-for="(item, index) in dictOptions" :key="index" :getPopupContainer="getParentContainer" :value="item.value">
<span :class="[useDicColor && item.color ? 'colorText' : '']" :style="{ backgroundColor: `${useDicColor && item.color}` }">{{ item.text || item.label }}</span>
{{ item.text || item.label }}
</a-select-option>
</a-select>
</template>
@ -68,10 +68,6 @@
type: Boolean,
default: false,
},
useDicColor: {
type: Boolean,
default: false,
},
},
emits: ['options-change', 'change', 'input', 'update:value'],
setup(props, { emit, refs }) {
@ -139,7 +135,7 @@
//update-end-author:taoyan date:2022-6-21 for:
getDictItems(temp).then((res) => {
if (res) {
dictOptions.value = res.map((item) => ({ value: item.value, label: item.text, color:item.color }));
dictOptions.value = res.map((item) => ({ value: item.value, label: item.text }));
//console.info('res', dictOptions.value);
} else {
console.error('getDictItems error: : ', res);
@ -166,15 +162,3 @@
},
});
</script>
<style scoped lang='less'>
.colorText{
display: inline-block;
height: 20px;
line-height: 20px;
padding: 0 6px;
border-radius: 8px;
background-color: red;
color: #fff;
font-size: 12px;
}
</style>

View File

@ -137,8 +137,8 @@
e.preventDefault();
e.stopPropagation();
}
//update-begin---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
/* function records2DataList() {
function records2DataList() {
let arr:any[] = [];
let excludeList = props.excludeUserIdList;
let records = props.dataList;
@ -150,14 +150,13 @@
}
}
return arr;
}*/
}
const showDataList = computed(()=>{
/* let excludeList = props.excludeUserIdList;
let excludeList = props.excludeUserIdList;
if(excludeList && excludeList.length>0){
return records2DataList();
}*/
//update-end---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
}
return props.dataList;
});

View File

@ -141,7 +141,7 @@
const url = '/sys/user/selectUserList';
let params = {
pageNo: 1,
pageSize: 99,
pageSize: 10,
};
if (props.searchText) {
params['keyword'] = props.searchText;
@ -149,11 +149,6 @@
if (selectedDepartId.value) {
params['departId'] = selectedDepartId.value;
}
//update-begin---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
if(props.excludeUserIdList && props.excludeUserIdList.length>0){
params['excludeUserIdList'] = props.excludeUserIdList.join(",");
}
//update-end---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;

View File

@ -90,7 +90,7 @@
const url = '/sys/user/selectUserList';
let params = {
pageNo: 1,
pageSize: 99,
pageSize: 10,
};
if (props.searchText) {
params['keyword'] = props.searchText;
@ -98,11 +98,6 @@
if (selectedRoleId.value) {
params['roleId'] = selectedRoleId.value;
}
//update-begin---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
if(props.excludeUserIdList && props.excludeUserIdList.length>0){
params['excludeUserIdList'] = props.excludeUserIdList.join(",");
}
//update-end---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
const { records } = data.result;

View File

@ -222,16 +222,13 @@
if (selectedDepart.value) {
params['departId'] = selectedDepart.value;
}
//update-begin---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
if(unref(excludeUserIdList) && unref(excludeUserIdList).length>0){
params['excludeUserIdList'] = excludeUserIdList.value.join(",");
}
//update-end---author:wangshuai---date:2024-02-02---for:QQYUN-8239 2---
const data = await defHttp.get({ url, params }, { isTransformResponse: false });
if (data.success) {
let { records, total } = data.result;
//id0
if(unref(excludeUserIdList) && unref(excludeUserIdList).length>0){
total = total - unref(excludeUserIdList).length;
}
totalRecord.value = total;
initCurrentUserData(records);
userDataList.value = records;

View File

@ -199,7 +199,7 @@ export function useTreeBiz(treeRef, getList, props, realProps) {
const options = <any[]>[];
optionData.forEach((item) => {
//update-begin-author:taoyan date:2022-7-4 for: issues/I5F3P4 online配置部门选择后编辑查看数据应该显示部门名称不是部门代码
options.push({ label: item[props.labelKey], value: item[props.rowKey] });
options.push({ label: item[props.titleKey], value: item[props.rowKey] });
//update-end-author:taoyan date:2022-7-4 for: issues/I5F3P4 online配置部门选择后编辑查看数据应该显示部门名称不是部门代码
});
selectOptions.value = options;

View File

@ -192,11 +192,6 @@ export interface FormSchema {
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
// update-begin--author:liaozhiyang---date:20240308---for【QQYUN-8377】formSchema props支持动态修改
// 设置组件props的key
dynamicPropskey?: string;
dynamicPropsVal?: ((renderCallbackParams: RenderCallbackParams) => any);
// update-end--author:liaozhiyang---date:20240308---for【QQYUN-8377】formSchema props支持动态修改
// 这个属性自定义的 用于自定义的业务 比如在表单打开的时候修改表单的禁用状态但是又不能重写componentProps因为他的内容太多了所以使用dynamicDisabled和buss实现
buss?: any;

View File

@ -128,7 +128,6 @@ export type ComponentType =
| 'JCategorySelect'
| 'JSelectMultiple'
| 'JPopup'
| 'JPopupDict'
| 'JSwitch'
| 'JEasyCron'
| 'JTreeDict'

View File

@ -1,7 +1,6 @@
import { registerComponent, registerAsyncComponent, registerASyncComponentReal } from '/@/components/jeecg/JVxeTable';
import { registerComponent, registerAsyncComponent } from '/@/components/jeecg/JVxeTable';
import { JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
import { DictSearchSpanCell, DictSearchInputCell } from './src/components/JVxeSelectDictSearchCell';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export async function registerJVxeCustom() {
// ----------------- ⚠ 注意事项 ⚠ -----------------
@ -24,8 +23,5 @@ export async function registerJVxeCustom() {
// 注册【部门选择】组件
await registerAsyncComponent(JVxeTypes.departSelect, import('./src/components/JVxeDepartSelectCell.vue'));
// 注册【省市区选择】组件
registerASyncComponentReal(
JVxeTypes.pca,
createAsyncComponent(() => import('./src/components/JVxePcaCell.vue'))
);
await registerAsyncComponent(JVxeTypes.pca, import('./src/components/JVxePcaCell.vue'));
}

View File

@ -130,11 +130,10 @@
if (!props.visible) return;
const wrapperRefDom = unref(wrapperRef);
if (!wrapperRefDom) return;
// update-begin--author:liaozhiyang---date:20240320---forQQYUN-8573BasicModal
const bodyDom = wrapperRefDom.$el.parentElement?.parentElement?.parentElement;
// update-end--author:liaozhiyang---date:20240320---forBasicModal
const bodyDom = wrapperRefDom.$el.parentElement;
if (!bodyDom) return;
// bodyDom.style.padding = '0';
bodyDom.style.padding = '0';
await nextTick();
try {

View File

@ -1,6 +1,20 @@
<template>
<div class="scrollbar">
<div ref="wrap" :class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']" :style="style" @scroll="handleScroll">
<div ref="wrap" :class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
:style="style" @scroll="handleScroll">
<div style="display: flex; justify-content: center; align-items: center">
<span style="
font-size: 18px;
font-weight: 700;
transition: all 0.5s;
line-height: normal;
padding: 5px;
">
英语四六级综合管理平台
</span>
</div>
<component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
<slot></slot>
</component>
@ -12,182 +26,182 @@
</div>
</template>
<script lang="ts">
import { addResizeListener, removeResizeListener } from '/@/utils/event';
import componentSetting from '/@/settings/componentSetting';
const { scrollbar } = componentSetting;
import { toObject } from './util';
import { defineComponent, ref, onMounted, onBeforeUnmount, nextTick, provide, computed, unref } from 'vue';
import Bar from './bar';
import { addResizeListener, removeResizeListener } from '/@/utils/event';
import componentSetting from '/@/settings/componentSetting';
const { scrollbar } = componentSetting;
import { toObject } from './util';
import { defineComponent, ref, onMounted, onBeforeUnmount, nextTick, provide, computed, unref } from 'vue';
import Bar from './bar';
export default defineComponent({
name: 'Scrollbar',
// inheritAttrs: false,
components: { Bar },
props: {
native: {
type: Boolean,
default: scrollbar?.native ?? false,
},
wrapStyle: {
type: [String, Array],
default: '',
},
wrapClass: {
type: [String, Array],
default: '',
},
viewClass: {
type: [String, Array],
default: '',
},
viewStyle: {
type: [String, Array],
default: '',
},
noresize: Boolean, // container
tag: {
type: String,
default: 'div',
},
export default defineComponent({
name: 'Scrollbar',
// inheritAttrs: false,
components: { Bar },
props: {
native: {
type: Boolean,
default: scrollbar?.native ?? false,
},
setup(props) {
const sizeWidth = ref('0');
const sizeHeight = ref('0');
const moveX = ref(0);
const moveY = ref(0);
const wrap = ref();
const resize = ref();
provide('scroll-bar-wrap', wrap);
const style = computed(() => {
if (Array.isArray(props.wrapStyle)) {
return toObject(props.wrapStyle);
}
return props.wrapStyle;
});
const handleScroll = () => {
if (!props.native) {
moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight;
moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth;
}
};
const update = () => {
if (!unref(wrap)) return;
const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight;
const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth;
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
};
onMounted(() => {
if (props.native) return;
nextTick(update);
if (!props.noresize) {
addResizeListener(unref(resize), update);
addResizeListener(unref(wrap), update);
addEventListener('resize', update);
}
});
onBeforeUnmount(() => {
if (props.native) return;
if (!props.noresize) {
removeResizeListener(unref(resize), update);
removeResizeListener(unref(wrap), update);
removeEventListener('resize', update);
}
});
return {
moveX,
moveY,
sizeWidth,
sizeHeight,
style,
wrap,
resize,
update,
handleScroll,
};
wrapStyle: {
type: [String, Array],
default: '',
},
});
wrapClass: {
type: [String, Array],
default: '',
},
viewClass: {
type: [String, Array],
default: '',
},
viewStyle: {
type: [String, Array],
default: '',
},
noresize: Boolean, // container
tag: {
type: String,
default: 'div',
},
},
setup(props) {
const sizeWidth = ref('0');
const sizeHeight = ref('0');
const moveX = ref(0);
const moveY = ref(0);
const wrap = ref();
const resize = ref();
provide('scroll-bar-wrap', wrap);
const style = computed(() => {
if (Array.isArray(props.wrapStyle)) {
return toObject(props.wrapStyle);
}
return props.wrapStyle;
});
const handleScroll = () => {
if (!props.native) {
moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight;
moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth;
}
};
const update = () => {
if (!unref(wrap)) return;
const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight;
const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth;
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
};
onMounted(() => {
if (props.native) return;
nextTick(update);
if (!props.noresize) {
addResizeListener(unref(resize), update);
addResizeListener(unref(wrap), update);
addEventListener('resize', update);
}
});
onBeforeUnmount(() => {
if (props.native) return;
if (!props.noresize) {
removeResizeListener(unref(resize), update);
removeResizeListener(unref(wrap), update);
removeEventListener('resize', update);
}
});
return {
moveX,
moveY,
sizeWidth,
sizeHeight,
style,
wrap,
resize,
update,
handleScroll,
};
},
});
</script>
<style lang="less">
.scrollbar {
position: relative;
.scrollbar {
position: relative;
height: 100%;
overflow: hidden;
&__wrap {
height: 100%;
overflow: hidden;
overflow: auto;
&__wrap {
height: 100%;
overflow: auto;
&--hidden-default {
scrollbar-width: none;
&--hidden-default {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
opacity: 0;
}
}
}
&__thumb {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
&__bar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 80ms ease;
transition: opacity 80ms ease;
&.is-vertical {
top: 2px;
width: 6px;
& > div {
width: 100%;
}
}
&.is-horizontal {
left: 2px;
height: 6px;
& > div {
height: 100%;
}
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
opacity: 0;
}
}
}
.scrollbar:active > .scrollbar__bar,
.scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar {
opacity: 1;
transition: opacity 340ms ease-out;
&__thumb {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
&__bar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 80ms ease;
transition: opacity 80ms ease;
&.is-vertical {
top: 2px;
width: 6px;
&>div {
width: 100%;
}
}
&.is-horizontal {
left: 2px;
height: 6px;
&>div {
height: 100%;
}
}
}
}
.scrollbar:active>.scrollbar__bar,
.scrollbar:focus>.scrollbar__bar,
.scrollbar:hover>.scrollbar__bar {
opacity: 1;
transition: opacity 340ms ease-out;
}
</style>

View File

@ -127,14 +127,6 @@
// update-begin--author:sunjianlei---date:20220408---for: VUEN-656#
return;
}
// update-begin--author:liaozhiyang---date:20240227---forQQYUN-6366tab
const findItem = getMatchingMenu(props.items, key);
if (findItem?.internalOrExternal == true) {
window.open(location.origin + key);
return;
}
// update-end--author:liaozhiyang---date:20240227---forQQYUN-6366tab
const { beforeClickFn } = props;
if (beforeClickFn && isFunction(beforeClickFn)) {
const flag = await beforeClickFn(key);
@ -148,26 +140,6 @@
menuState.activeName = key;
}
/**
* 2024-02-27
* liaozhiyang
* 获取菜单中匹配的path所在的项
*/
const getMatchingMenu = (menus, path) => {
for (let i = 0, len = menus.length; i < len; i++) {
const item = menus[i];
if (item.path === path && !item.redirect && !item.paramPath) {
return item;
} else if (item.children?.length) {
const result = getMatchingMenu(item.children, path);
if (result) {
return result;
}
}
}
return '';
}
return {
prefixCls,
getBindValues,

View File

@ -251,11 +251,6 @@
&-popconfirm {
.ant-popconfirm-buttons {
min-width: 120px;
// update-begin--author:liaozhiyang---date:20240124---forissues/1019popConfirm
display: flex;
align-items: center;
justify-content: center;
// update-end--author:liaozhiyang---date:20240124---forissues/1019popConfirm
}
}
}

View File

@ -57,9 +57,7 @@
if (!isFunction(summaryFunc)) {
return [];
}
// update-begin--author:liaozhiyang---date:20230227---forQQYUN-8172
let dataSource = cloneDeep(unref(table.getDataSource()));
// update-end--author:liaozhiyang---date:20230227---forQQYUN-8172
let dataSource = toRaw(unref(table.getDataSource()));
dataSource = summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[props.rowKey] = `${i}`;

View File

@ -1,5 +1,5 @@
<template>
<Tooltip placement="top" v-bind="getBindProps" >
<Tooltip placement="top" v-bind="getBindProps">
<template #title>
<span>{{ t('component.table.settingColumn') }}</span>
</template>

View File

@ -1,6 +1,5 @@
import { computed, nextTick, unref, watchEffect } from 'vue';
import { router } from '/@/router';
import { useRoute } from 'vue-router';
import { createLocalStorage } from '/@/utils/cache';
import { useTableContext } from './useTableContext';
import { useMessage } from '/@/hooks/web/useMessage';
@ -13,13 +12,10 @@ export function useColumnsCache(opt, setColumns, handleColumnFixed) {
const table = useTableContext();
const $ls = createLocalStorage();
const { createMessage: $message } = useMessage();
const route = useRoute();
// 列表配置缓存key
const cacheKey = computed(() => {
// update-begin--author:liaozhiyang---date:20240226---for【QQYUN-8367】online报表配置列展示保存影响到其他页面的table字段的显示隐藏开发环境热更新会有此问题生产环境无问题
const path = route.path;
let key = path.replace(/[\/\\]/g, '_');
// update-end--author:liaozhiyang---date:20240226---for【QQYUN-8367】online报表配置列展示保存影响到其他页面的table字段的显示隐藏开发环境热更新会有此问题生产环境无问题
let { fullPath } = router.currentRoute.value;
let key = fullPath.replace(/[\/\\]/g, '_');
let cacheKey = table.getBindValues.value.tableSetting?.cacheKey;
if (cacheKey) {
key += ':' + cacheKey;

View File

@ -248,14 +248,7 @@ export function useDataSource(
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
// update-begin--author:liaozhiyang---date:20240227---for【QQYUN-8316】table查询条件,请求剔除空字符串字段
for (let item of Object.entries(params)) {
const [key, val] = item;
if (val === '') {
delete params[key];
};
};
// update-end--author:liaozhiyang---date:20240227---for【QQYUN-8316】table查询条件,请求剔除空字符串字段
const res = await api(params);
rawDataSourceRef.value = res;

View File

@ -118,12 +118,7 @@
const initOptions = computed(() => {
const { height, options, toolbar, plugins, menubar } = props;
let publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
// update-begin--author:liaozhiyang---date:20240320---forQQYUN-8571/
if (!publicPath.endsWith('/')) {
publicPath += '/';
}
// update-end--author:liaozhiyang---date:20240320---forQQYUN-8571/
const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
return {
selector: `#${unref(tinymceId)}`,
height,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,445 +0,0 @@
<template>
<div class="chatWrap">
<div class="content">
<div class="main">
<div id="scrollRef" ref="scrollRef" class="scrollArea">
<template v-if="chatData.length">
<div class="chatContentArea">
<chatMessage
v-for="(item, index) of chatData"
:key="index"
:date-time="item.dateTime"
:text="item.text"
:inversion="item.inversion"
:error="item.error"
:loading="item.loading"
></chatMessage>
</div>
<div v-if="loading" class="stopArea">
<a-button type="primary" danger @click="handleStop" class="stopBtn">
<svg
t="1706148514627"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5214"
width="18"
height="18"
>
<path
d="M512 967.111111c-250.311111 0-455.111111-204.8-455.111111-455.111111s204.8-455.111111 455.111111-455.111111 455.111111 204.8 455.111111 455.111111-204.8 455.111111-455.111111 455.111111z m0-56.888889c221.866667 0 398.222222-176.355556 398.222222-398.222222s-176.355556-398.222222-398.222222-398.222222-398.222222 176.355556-398.222222 398.222222 176.355556 398.222222 398.222222 398.222222z"
fill="currentColor"
p-id="5215"
></path>
<path d="M341.333333 341.333333h341.333334v341.333334H341.333333z" fill="currentColor" p-id="5216"></path>
</svg>
<span>停止响应</span>
</a-button>
</div>
</template>
<template v-else>
<div class="emptyArea">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="mr-2 text-3xl iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M16 16a3 3 0 1 1 0 6a3 3 0 0 1 0-6M6 12a4 4 0 1 1 0 8a4 4 0 0 1 0-8m8.5-10a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11"
></path>
</svg>
<span>新建聊天</span>
</div>
</template>
</div>
</div>
<div class="footer">
<a-button type="text" class="delBtn" @click="handleDelSession">
<svg
t="1706504908534"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1584"
width="18"
height="18"
>
<path
d="M816.872727 158.254545h-181.527272V139.636364c0-39.563636-30.254545-69.818182-69.818182-69.818182h-107.054546c-39.563636 0-69.818182 30.254545-69.818182 69.818182v18.618181H207.127273c-48.872727 0-90.763636 41.890909-90.763637 93.09091s41.890909 90.763636 90.763637 90.763636h609.745454c51.2 0 90.763636-41.890909 90.763637-90.763636 0-51.2-41.890909-93.090909-90.763637-93.09091zM435.2 139.636364c0-13.963636 9.309091-23.272727 23.272727-23.272728h107.054546c13.963636 0 23.272727 9.309091 23.272727 23.272728v18.618181h-153.6V139.636364z m381.672727 155.927272H207.127273c-25.6 0-44.218182-20.945455-44.218182-44.218181 0-25.6 20.945455-44.218182 44.218182-44.218182h609.745454c25.6 0 44.218182 20.945455 44.218182 44.218182 0 23.272727-20.945455 44.218182-44.218182 44.218181zM835.490909 407.272727h-121.018182c-13.963636 0-23.272727 9.309091-23.272727 23.272728s9.309091 23.272727 23.272727 23.272727h97.745455V837.818182c0 39.563636-30.254545 69.818182-69.818182 69.818182h-37.236364V602.763636c0-13.963636-9.309091-23.272727-23.272727-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364h-118.690909V602.763636c0-13.963636-9.309091-23.272727-23.272728-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364H372.363636V602.763636c0-13.963636-9.309091-23.272727-23.272727-23.272727s-23.272727 9.309091-23.272727 23.272727V907.636364h-34.909091c-39.563636 0-69.818182-30.254545-69.818182-69.818182V453.818182H558.545455c13.963636 0 23.272727-9.309091 23.272727-23.272727s-9.309091-23.272727-23.272727-23.272728H197.818182c-13.963636 0-23.272727 9.309091-23.272727 23.272728V837.818182c0 65.163636 51.2 116.363636 116.363636 116.363636h451.490909c65.163636 0 116.363636-51.2 116.363636-116.363636V430.545455c0-13.963636-11.636364-23.272727-23.272727-23.272728z"
fill="currentColor"
p-id="1585"
></path>
</svg>
</a-button>
<a-button type="text" class="contextBtn" :class="[usingContext && 'enabled']" @click="handleUsingContext">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.956 9.956 0 0 1-4.708-1.175L2 22l1.176-5.29A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2m0 2a8 8 0 0 0-8 8c0 1.335.326 2.618.94 3.766l.35.654l-.656 2.946l2.948-.654l.653.349A7.955 7.955 0 0 0 12 20a8 8 0 1 0 0-16m1 3v5h4v2h-6V7z"
></path>
</svg>
</a-button>
<a-textarea
ref="inputRef"
v-model:value="prompt"
:autoSize="{ minRows: 1, maxRows: 6 }"
:placeholder="placeholder"
@pressEnter="handleEnter"
autofocus
></a-textarea>
<a-button
@click="
() => {
handleSubmit();
}
"
:disabled="loading"
type="primary"
class="sendBtn"
>
<svg
t="1706147858151"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4237"
width="1em"
height="1em"
>
<path
d="M865.28 202.5472c-17.1008-15.2576-41.0624-19.6608-62.5664-11.5712L177.7664 427.1104c-23.2448 8.8064-38.5024 29.696-39.6288 54.5792-1.1264 24.8832 11.9808 47.104 34.4064 58.0608l97.5872 47.7184c4.5056 2.2528 8.0896 6.0416 9.9328 10.6496l65.4336 161.1776c7.7824 19.1488 24.4736 32.9728 44.7488 37.0688 20.2752 4.096 41.0624-2.1504 55.6032-16.7936l36.352-36.352c6.4512-6.4512 16.5888-7.8848 24.576-3.3792l156.5696 88.8832c9.4208 5.3248 19.8656 8.0896 30.3104 8.0896 8.192 0 16.4864-1.6384 24.2688-5.0176 17.8176-7.68 30.72-22.8352 35.4304-41.6768l130.7648-527.1552c5.5296-22.016-1.7408-45.2608-18.8416-60.416z m-20.8896 50.7904L713.5232 780.4928c-1.536 6.2464-5.8368 11.3664-11.776 13.9264s-12.5952 2.1504-18.2272-1.024L526.9504 704.512c-9.4208-5.3248-19.8656-7.9872-30.208-7.9872-15.9744 0-31.744 6.144-43.52 17.92l-36.352 36.352c-3.8912 3.8912-8.9088 5.9392-14.2336 6.0416l55.6032-152.1664c0.512-1.3312 1.2288-2.56 2.2528-3.6864l240.3328-246.1696c8.2944-8.4992-2.048-21.9136-12.3904-16.0768L301.6704 559.8208c-4.096-3.584-8.704-6.656-13.6192-9.1136L190.464 502.9888c-11.264-5.5296-11.5712-16.1792-11.4688-19.3536 0.1024-3.1744 1.536-13.824 13.2096-18.2272L817.152 229.2736c10.4448-3.9936 18.0224 1.3312 20.8896 3.8912 2.8672 2.4576 9.0112 9.3184 6.3488 20.1728z"
p-id="4238"
fill="currentColor"
></path>
</svg>
</a-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Ref } from 'vue';
import { computed, ref, createVNode, onUnmounted, onMounted } from 'vue';
import { useScroll } from '../hooks/useScroll';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ConfigEnum } from '/@/enums/httpEnum';
import { getToken } from '/@/utils/auth';
import { getAppEnvConfig } from '/@/utils/env';
import chatMessage from './chatMessage.vue';
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { message, Modal } from 'ant-design-vue';
import '../style/github-markdown.less';
import '../style/highlight.less';
import '../style/style.less';
const props = defineProps(['chatData', 'uuid', 'dataSource']);
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
const prompt = ref<string>('');
const loading = ref<boolean>(false);
const inputRef = ref<Ref | null>(null);
// const chatData = computed(() => {
// return props.chatData;
// });
// ,
const usingContext = ref<any>(true);
const uuid = computed(() => {
return props.uuid;
});
let evtSource: any = null;
const { VITE_GLOB_API_URL } = getAppEnvConfig();
const conversationList = computed(() => props.chatData.filter((item) => !item.inversion && !!item.conversationOptions));
const placeholder = computed(() => {
return '来说点什么吧...Shift + Enter = 换行)';
});
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit();
}
}
function handleSubmit() {
onConversation();
}
async function onConversation() {
let message = prompt.value;
if (loading.value) return;
if (!message || message.trim() === '') return;
loading.value = true;
prompt.value = '';
if (props.chatData.length == 0) {
const findItem = props.dataSource.history.find((item) => item.uuid === uuid.value);
if (findItem && findItem.title == '新建聊天') {
findItem.title = message;
}
}
addChat(uuid.value, {
dateTime: new Date().toLocaleString(),
text: message,
inversion: true,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: null },
});
scrollToBottom();
let options: any = {};
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions;
if (lastContext && usingContext.value) options = { ...lastContext };
addChat(uuid.value, {
dateTime: new Date().toLocaleString(),
text: '思考中...',
loading: true,
inversion: false,
error: false,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
const initEventSource = () => {
let lastText = '';
if (typeof EventSource !== 'undefined') {
const token = getToken();
evtSource = new EventSourcePolyfill(
`${VITE_GLOB_API_URL}/ai/chat/send?message=${message}${options.parentMessageId ? '&topicId=' + options.parentMessageId : ''}`,
{
withCredentials: true,
headers: {
[ConfigEnum.TOKEN]: token,
},
}
); //
//
evtSource.onopen = function (e) {
console.log(e);
};
//
evtSource.onmessage = function (e) {
const data = e.data;
// console.log(e);
if (data === '[DONE]') {
updateChatSome(uuid, props.chatData.length - 1, { loading: false });
scrollToBottom();
handleStop();
evtSource.close(); //
} else {
try {
const _data = JSON.parse(data);
const content = _data.content;
if (content != undefined) {
lastText += content;
updateChat(uuid.value, props.chatData.length - 1, {
dateTime: new Date().toLocaleString(),
text: lastText,
inversion: false,
error: false,
loading: true,
conversationOptions: e.lastEventId == '[ERR]' ? null : { conversationId: data.conversationId, parentMessageId: e.lastEventId },
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
} else {
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
scrollToBottom();
handleStop();
}
} catch (error) {
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
scrollToBottom();
handleStop();
evtSource.close(); //
}
}
};
//
evtSource.onerror = function (e) {
// console.log(e);
if (e.error?.message || e.statusText) {
updateChat(uuid.value, props.chatData.length - 1, {
dateTime: new Date().toLocaleString(),
text: e.error?.message ?? e.statusText,
inversion: false,
error: false,
loading: true,
conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } },
});
scrollToBottom();
}
evtSource.close(); //
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
handleStop();
};
} else {
console.log('当前浏览器不支持使用EventSource接收服务器推送事件!');
}
};
initEventSource();
}
onUnmounted(() => {
evtSource?.close();
updateChatSome(uuid.value, props.chatData.length - 1, { loading: false });
});
const addChat = (uuid, data) => {
props.chatData.push({ ...data });
};
const updateChat = (uuid, index, data) => {
props.chatData.splice(index, 1, data);
};
const updateChatSome = (uuid, index, data) => {
props.chatData[index] = { ...props.chatData[index], ...data };
};
//
const handleDelSession = () => {
Modal.confirm({
title: '清空会话',
icon: createVNode(ExclamationCircleOutlined),
content: '是否清空会话?',
closable: true,
okText: '确定',
cancelText: '取消',
async onOk() {
try {
return await new Promise<void>((resolve) => {
props.chatData.length = 0;
resolve();
});
} catch {
return console.log('Oops errors!');
}
},
});
};
//
const handleStop = () => {
if (loading.value) {
loading.value = false;
}
if (evtSource) {
evtSource?.close();
updateChatSome(uuid, props.chatData.length - 1, { loading: false });
}
};
// 使
const handleUsingContext = () => {
usingContext.value = !usingContext.value;
if (usingContext.value) {
message.success('当前模式下, 发送消息会携带之前的聊天记录');
} else {
message.warning('当前模式下, 发送消息不会携带之前的聊天记录');
}
};
onMounted(() => {
scrollToBottom();
});
</script>
<style lang="less" scoped>
.chatWrap {
width: 100%;
height: 100%;
padding: 20px;
.content {
height: 100%;
width: 100%;
background: #fff;
display: flex;
flex-direction: column;
}
}
.main {
flex: 1;
min-height: 0;
.scrollArea {
overflow-y: auto;
height: 100%;
}
.chatContentArea {
padding: 10px;
}
}
.emptyArea {
display: flex;
justify-content: center;
align-items: center;
color: #d4d4d4;
}
.stopArea {
display: flex;
justify-content: center;
padding: 10px 0;
.stopBtn {
display: flex;
justify-content: center;
align-items: center;
svg {
margin-right: 5px;
}
}
}
.footer {
display: flex;
align-items: center;
padding: 6px 16px;
.ant-input {
margin: 0 16px;
}
.ant-input,
.ant-btn {
height: 36px;
}
textarea.ant-input {
padding-top: 6px;
padding-bottom: 6px;
}
.contextBtn,
.delBtn {
padding: 0;
width: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delBtn {
margin-right: 8px;
}
.contextBtn {
color: #a8071a;
&.enabled {
color: @primary-color;
}
font-size: 18px;
}
.sendBtn {
padding: 0 10px;
font-size: 22px;
display: flex;
align-items: center;
}
}
</style>

View File

@ -1,73 +0,0 @@
<template>
<div class="chat" :class="[inversion ? 'self' : 'chatgpt']">
<div class="avatar">
<img v-if="inversion" :src="avatar()" />
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" width="1em" height="1em">
<path
d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z"
fill="currentColor"
/>
</svg>
</div>
<div class="content">
<p class="date">{{ dateTime }}</p>
<div class="msgArea">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading"></chatText>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import chatText from './chatText.vue';
import defaultAvatar from '../assets/avatar.jpg';
import { useUserStore } from '/@/store/modules/user';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
const { userInfo } = useUserStore();
const avatar = () => {
return userInfo?.avatar || defaultAvatar;
};
</script>
<style lang="less" scoped>
.chat {
display: flex;
margin-bottom: 1.5rem;
&.self {
flex-direction: row-reverse;
.avatar {
margin-right: 0;
margin-left: 10px;
}
.msgArea {
flex-direction: row-reverse;
}
.date {
text-align: right;
}
}
}
.avatar {
flex: none;
margin-right: 10px;
img {
width: 34px;
height: 34px;
border-radius: 50%;
overflow: hidden;
}
svg {
font-size: 28px;
}
}
.content {
.date {
color: #b4bbc4;
font-size: 0.75rem;
margin-bottom: 10px;
}
.msgArea {
display: flex;
}
}
</style>

View File

@ -1,125 +0,0 @@
<template>
<div class="textWrap" :class="[inversion ? 'self' : 'chatgpt']" ref="textRef">
<div v-if="!inversion">
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
</div>
<div v-else class="msg" v-text="text" />
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue';
import MarkdownIt from 'markdown-it';
import mdKatex from '@traptitech/markdown-it-katex';
import mila from 'markdown-it-link-attributes';
import hljs from 'highlight.js';
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
const textRef = ref();
const mdi = new MarkdownIt({
html: false,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language));
if (validLang) {
const lang = language ?? '';
return highlightBlock(hljs.highlight(code, { language: lang }).value, lang);
}
return highlightBlock(hljs.highlightAuto(code).value, '');
},
});
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
const text = computed(() => {
const value = props.text ?? '';
if (!props.inversion) return mdi.render(value);
return value;
});
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
}
function addCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy');
copyBtn.forEach((btn) => {
btn.addEventListener('click', () => {
const code = btn.parentElement?.nextElementSibling?.textContent;
if (code) {
copyToClip(code).then(() => {
btn.textContent = '复制成功';
setTimeout(() => {
btn.textContent = '复制代码';
}, 1e3);
});
}
});
});
}
}
function removeCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy');
copyBtn.forEach((btn) => {
btn.removeEventListener('click', () => {});
});
}
}
onMounted(() => {
addCopyEvents();
});
onUpdated(() => {
addCopyEvents();
});
onUnmounted(() => {
removeCopyEvents();
});
function copyToClip(text: string) {
return new Promise((resolve, reject) => {
try {
const input: HTMLTextAreaElement = document.createElement('textarea');
input.setAttribute('readonly', 'readonly');
input.value = text;
document.body.appendChild(input);
input.select();
if (document.execCommand('copy')) document.execCommand('copy');
document.body.removeChild(input);
resolve(text);
} catch (error) {
reject(error);
}
});
}
</script>
<style lang="less" scoped>
.textWrap {
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.self {
// background-color: #d2f9d1;
background-color: @primary-color;
color: #fff;
overflow-wrap: break-word;
line-height: 1.625;
min-width: 20px;
}
.chatgpt {
background-color: #f4f6f8;
font-size: 0.875rem;
line-height: 1.25rem;
}
</style>

View File

@ -1,282 +0,0 @@
<template>
<div class="slide-wrap">
<div class="createArea">
<a-button type="dashed" @click="handleCreate">新建聊天</a-button>
</div>
<div class="historyArea">
<ul>
<li
v-for="item in dataSource.history"
:key="item.uuid"
class="list"
:class="[item.uuid == dataSource.active ? 'active' : 'normal', dataSource.history.length == 1 ? 'last' : '']"
@click="handleToggleChat(item)"
>
<i class="icon message">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M2 8.994A5.99 5.99 0 0 1 8 3h8c3.313 0 6 2.695 6 5.994V21H8c-3.313 0-6-2.695-6-5.994zM20 19V8.994A4.004 4.004 0 0 0 16 5H8a3.99 3.99 0 0 0-4 3.994v6.012A4.004 4.004 0 0 0 8 19zm-6-8h2v2h-2zm-6 0h2v2H8z"
></path>
</svg>
</i>
<a-input
class="title"
ref="inputRef"
v-if="item.isEdit"
:defaultValue="item.title"
placeholder="请输入标题"
@change="handleInputChange"
@blur="inputBlur(item)"
/>
<span class="title" v-else>{{ item.title }}</span>
<span class="icon edit" @click="handleEdit(item)" v-if="!item.isEdit">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M6.414 15.89L16.556 5.748l-1.414-1.414L5 14.476v1.414zm.829 2H3v-4.243L14.435 2.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414zM3 19.89h18v2H3z"
></path>
</svg>
</span>
<span class="icon del" v-if="!item.isEdit">
<a-popconfirm title="确定删除此记录?" placement="bottom" ok-text="确定" cancel-text="取消" @confirm="handleDel(item)">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm1 2H6v12h12zm-9 3h2v6H9zm4 0h2v6h-2zM9 4v2h6V4z"
></path>
</svg>
</a-popconfirm>
</span>
<span class="icon save" v-if="item.isEdit" @click="handleSave(item)">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--ri"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7 19v-6h10v6h2V7.828L16.172 5H5v14zM4 3h13l4 4v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m5 12v4h6v-4z"
></path>
</svg>
</span>
</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const props = defineProps(['dataSource']);
const inputRef = ref(null);
let inputValue = '';
//
const handleCreate = () => {
const uuid = getUuid();
props.dataSource.history.unshift({ title: '新建聊天', uuid, isEdit: false });
props.dataSource.chat.unshift({ uuid, data: [] });
// ()
if (props.dataSource.history.length == 1) {
props.dataSource.active = uuid;
}
};
//
const handleToggleChat = (item) => {
if (item.uuid != props.dataSource.active) {
props.dataSource.active = item.uuid;
const findItem = props.dataSource.history.find((item) => item.isEdit);
if (findItem) {
handleSave(findItem);
}
}
};
const handleInputChange = (e) => {
inputValue = e.target.value.trim();
};
//
const inputBlur = (item) => {
item.isEdit = false;
item.title = inputValue;
};
//
const handleEdit = (item) => {
item.isEdit = true;
inputValue = item.title;
};
//
const handleSave = (item) => {
item.isEdit = false;
item.title = inputValue;
};
//
const handleDel = (data) => {
const findIndex = props.dataSource.history.findIndex((item) => item.uuid == data.uuid);
if (findIndex != -1) {
props.dataSource.history.splice(findIndex, 1);
props.dataSource.chat.splice(findIndex, 1);
// activeactive
if (props.dataSource.history.length) {
if (props.dataSource.active == data.uuid) {
if (findIndex > 0) {
props.dataSource.active = props.dataSource.history[findIndex - 1].uuid;
} else {
props.dataSource.active = props.dataSource.history[0].uuid;
}
}
} else {
//
props.dataSource.active = null;
}
}
};
watch(
() => inputRef.value,
(newVal: any) => {
if (newVal?.length) {
newVal[0].focus();
}
},
{ deep: true }
);
//
const getUuid = (len = 10, radix = 16) => {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid: any = [],
i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
} else {
var r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
};
</script>
<style scoped lang="less">
.slide-wrap {
border-right: 1px solid #e5e7eb;
height: 100%;
display: flex;
flex-direction: column;
.historyArea {
padding: 20px;
padding-top: 0;
flex: 1;
min-height: 0;
overflow: auto;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
}
.createArea {
padding: 20px;
padding-bottom: 0;
}
.ant-btn {
width: 100%;
margin-bottom: 10px;
}
}
ul {
margin-bottom: 0;
}
.list {
width: 100%;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 0.375rem;
border-width: 1px;
cursor: pointer;
margin-bottom: 10px;
color: #333;
display: flex;
justify-content: flex-start;
align-items: center;
&:hover,
&.active {
border-color: @primary-color;
color: @primary-color;
}
.edit,
.save,
.del {
display: none;
}
&.active {
.edit,
.save,
.del {
display: block;
}
&.last {
.del {
display: none;
}
}
}
.message {
margin-right: 8px;
}
.edit {
margin-right: 8px;
}
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.ant-input {
margin-right: 20px;
}
}
svg {
vertical-align: middle;
}
}
</style>

View File

@ -1,186 +0,0 @@
export const localData = {
active: 1002,
usingContext: true,
history: [
{
title: '标题02',
uuid: 1706083575869,
isEdit: false,
},
{
uuid: 1002,
title: '标题01',
isEdit: false,
},
],
chat: [
{
uuid: 1706083575869,
data: [
{
dateTime: '2024/1/24 16:06:27',
text: '',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '',
options: null,
},
},
{
dateTime: '2024/1/24 16:06:29',
text: 'Hello! How can I assist you today?',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kSZA0wju7X8sOdJIyxtpDj0RQVu1',
},
requestOptions: {
prompt: '',
options: {},
},
},
],
},
{
uuid: 1002,
data: [
{
dateTime: '2024/1/24 14:01:52',
text: '1',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '1',
options: null,
},
},
{
dateTime: '2024/1/24 14:01:54',
text: 'Yes, how can I assist you?',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQcb6mbF04o5hpule4SdHk2jFvNQ',
},
requestOptions: {
prompt: '1',
options: {},
},
},
{
dateTime: '2024/1/24 14:03:45',
text: '',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '',
options: null,
},
},
{
dateTime: '2024/1/24 14:03:47',
text: "I'm sorry if my previous response was not clear. Please let me know how I can help you or what you would like to discuss.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQeQ2t8YCXmLeF0ECGkkuOJlk4Pi',
},
requestOptions: {
prompt: '',
options: {
parentMessageId: 'chatcmpl-8kQcb6mbF04o5hpule4SdHk2jFvNQ',
},
},
},
{
dateTime: '2024/1/24 14:10:19',
text: 'js 递归',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: 'js 递归',
options: null,
},
},
{
dateTime: '2024/1/24 14:10:33',
text: 'JavaScript supports recursion, which is the process of a function calling itself. Recursion can be useful for solving problems that can be broken down into smaller, similar sub-problems.\n\nHere\'s an example of a simple recursive function in JavaScript:\n\n```javascript\nfunction countdown(n) {\n if (n <= 0) {\n console.log("Done!");\n } else {\n console.log(n);\n countdown(n - 1); // recursive call\n }\n}\n\ncountdown(5);\n```\n\nIn this example, the `countdown` function takes an argument `n` and logs the value of `n` to the console. If `n` is greater than zero, it then calls itself with `n - 1`. This process continues until `n` becomes less than or equal to zero, at which point it logs "Done!".\n\nRecursion can be helpful in solving problems that involve tree structures, factorial calculations, searching algorithms, and more. However, it\'s important to use recursion properly to avoid infinite loops or excessive stack usage.',
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQkmCbnRe4fG1FhWTlY0EyHTpqau',
},
requestOptions: {
prompt: 'js 递归',
options: {
parentMessageId: 'chatcmpl-8kQeQ2t8YCXmLeF0ECGkkuOJlk4Pi',
},
},
},
{
dateTime: '2024/1/24 14:17:15',
text: 'js 递归',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: 'js 递归',
options: null,
},
},
{
dateTime: '2024/1/24 14:23:50',
text: "Certainly! Here's an example of how you can use recursion in JavaScript:\n\n```javascript\nfunction factorial(n) {\n if (n === 0) {\n return 1;\n } else {\n return n * factorial(n - 1);\n }\n}\n\nconsole.log(factorial(5)); // Output: 120\n```\n\nIn this example, the `factorial` function calculates the factorial of a given number `n` using recursion. If `n` is equal to 0, it returns 1, which is the base case. Otherwise, it recursively calls itself with `n - 1`, multiplying the current value of `n` with the result of the recursive call.\n\nWhen calling `factorial(5)`, the function will execute as follows:\n\n- `factorial(5)` calls `factorial(4)`\n- `factorial(4)` calls `factorial(3)`\n- `factorial(3)` calls `factorial(2)`\n- `factorial(2)` calls `factorial(1)`\n- `factorial(1)` calls `factorial(0)`\n- `factorial(0)` returns 1\n- `factorial(1)` returns 1 * 1 = 1\n- `factorial(2)` returns 2 * 1 = 2\n- `factorial(3)` returns 3 * 2 = 6\n- `factorial(4)` returns 4 * 6 = 24\n- `factorial(5)` returns 5 * 24 = 120\n\nThe final result is then printed to the console using `console.log`.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kQwWVoZoWyqjbWuwMJmu6w3hBvXj',
},
requestOptions: {
prompt: 'js 递归',
options: {
parentMessageId: 'chatcmpl-8kQkmCbnRe4fG1FhWTlY0EyHTpqau',
},
},
},
{
dateTime: '2024/1/24 15:05:30',
text: '///',
inversion: true,
error: false,
conversationOptions: null,
requestOptions: {
prompt: '///',
options: null,
},
},
{
dateTime: '2024/1/24 15:05:33',
text: "I apologize if my previous response was not what you were expecting. If you have any specific questions or need further assistance, please let me know and I'll be happy to help.",
inversion: false,
error: false,
loading: false,
conversationOptions: {
parentMessageId: 'chatcmpl-8kRcAggkC4u47d34UcQW3cI0htw0w',
},
requestOptions: {
prompt: '///',
options: {
parentMessageId: 'chatcmpl-8kQwWVoZoWyqjbWuwMJmu6w3hBvXj',
},
},
},
],
},
],
};

View File

@ -1,28 +0,0 @@
import { useChatStore } from '@/store'
export function useChat() {
const chatStore = useChatStore()
const getChatByUuidAndIndex = (uuid: number, index: number) => {
return chatStore.getChatByUuidAndIndex(uuid, index)
}
const addChat = (uuid: number, chat: Chat.Chat) => {
chatStore.addChatByUuid(uuid, chat)
}
const updateChat = (uuid: number, index: number, chat: Chat.Chat) => {
chatStore.updateChatByUuid(uuid, index, chat)
}
const updateChatSome = (uuid: number, index: number, chat: Partial<Chat.Chat>) => {
chatStore.updateChatSomeByUuid(uuid, index, chat)
}
return {
addChat,
updateChat,
updateChatSome,
getChatByUuidAndIndex,
}
}

View File

@ -1,44 +0,0 @@
import type { Ref } from 'vue'
import { nextTick, ref } from 'vue'
type ScrollElement = HTMLDivElement | null
interface ScrollReturn {
scrollRef: Ref<ScrollElement>
scrollToBottom: () => Promise<void>
scrollToTop: () => Promise<void>
scrollToBottomIfAtBottom: () => Promise<void>
}
export function useScroll(): ScrollReturn {
const scrollRef = ref<ScrollElement>(null)
const scrollToBottom = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
const scrollToTop = async () => {
await nextTick()
if (scrollRef.value)
scrollRef.value.scrollTop = 0
}
const scrollToBottomIfAtBottom = async () => {
await nextTick()
if (scrollRef.value) {
const threshold = 100 // Threshold, indicating the distance threshold to the bottom of the scroll bar.
const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight
if (distanceToBottom <= threshold)
scrollRef.value.scrollTop = scrollRef.value.scrollHeight
}
}
return {
scrollRef,
scrollToBottom,
scrollToTop,
scrollToBottomIfAtBottom,
}
}

View File

@ -1,203 +0,0 @@
<template>
<div ref="chatContainerRef" class="chat-container" :style="chatContainerStyle">
<template v-if="dataSource">
<div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
<div class="content">
<slide v-if="uuid" :dataSource="dataSource"></slide>
</div>
<div class="toggle-btn" @click="handleToggle">
<span class="icon">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
fill="currentColor"
></path>
</svg>
</span>
</div>
</div>
<div class="rightArea" :class="[expand ? 'expand' : 'shrink']">
<chat v-if="uuid && chatVisible" :uuid="uuid" :chatData="chatData" :dataSource="dataSource"></chat>
</div>
</template>
<Spin v-else :spinning="true"></Spin>
</div>
</template>
<script setup lang="ts">
import slide from './components/slide.vue';
import chat from './components/chat.vue';
import { Spin } from 'ant-design-vue';
import { ref, watch, nextTick, onUnmounted } from 'vue';
import { useUserStore } from '/@/store/modules/user';
import { JEECG_CHAT_KEY } from '/@/enums/cacheEnum';
import { defHttp } from '/@/utils/http/axios';
const configUrl = {
get: '/ai/chat/history/get',
save: '/ai/chat/history/save',
};
const userId = useUserStore().getUserInfo?.id;
const localKey = JEECG_CHAT_KEY + userId;
let timer: any = null;
let unwatch01: any = null;
let unwatch02: any = null;
const dataSource = ref<any>(null);
const uuid = ref(null);
const chatData = ref([]);
const expand = ref<any>(true);
const chatVisible = ref(true);
const chatContainerRef = ref<any>(null);
const chatContainerStyle = ref({});
const handleToggle = () => {
expand.value = !expand.value;
};
//
const init = () => {
defHttp
.get({ url: configUrl.get })
.then((res) => {
const { content } = res;
if (content) {
dataSource.value = JSON.parse(content);
} else {
dataSource.value = {
active: 1002,
usingContext: true,
history: [{ uuid: 1002, title: '新建聊天', isEdit: false }],
chat: [{ uuid: 1002, data: [] }],
};
}
!unwatch01 && execute();
})
.catch(() => {});
};
const save = (content) => {
defHttp.post({ url: configUrl.save, params: { content: JSON.stringify(content) } }, { isTransformResponse: false });
};
// dataSource
const execute = () => {
unwatch01 = watch(
() => dataSource.value.active,
(value) => {
if (value) {
const findItem = dataSource.value.chat.find((item) => item.uuid === value);
if (findItem) {
uuid.value = findItem.uuid;
chatData.value = findItem.data;
}
chatVisible.value = false;
nextTick(() => {
chatVisible.value = true;
});
}
},
{ immediate: true }
);
unwatch02 = watch(dataSource.value, () => {
clearInterval(timer);
timer = setTimeout(() => {
save(dataSource.value);
}, 2e3);
});
};
onUnmounted(() => {
unwatch01 && unwatch01();
unwatch02 && unwatch02();
});
watch(
() => chatContainerRef.value,
() => {
chatContainerStyle.value = { height: `${chatContainerRef.value.offsetHeight}px` };
}
);
init();
</script>
<style scoped lang="less">
@width: 260px;
.chat-container {
position: relative;
height: 100%;
box-shadow:
0 0 #0000,
0 0 #0000,
0 0 #0000,
0 0 #0000,
0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
border-width: 1px;
border-radius: 0.375rem;
display: flex;
overflow: hidden;
:deep(.ant-spin) {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.leftArea {
width: @width;
transition: 0.3s left;
position: absolute;
left: 0;
height: 100%;
.content {
width: 100%;
height: 100%;
overflow: hidden;
}
&.shrink {
left: -@width;
.toggle-btn {
.icon {
transform: rotate(0deg);
}
}
}
.toggle-btn {
transition:
color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
right 0.3s cubic-bezier(0.4, 0, 0.2, 1),
left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
width: 24px;
height: 24px;
position: absolute;
top: 50%;
right: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: rgb(51, 54, 57);
border: 1px solid rgb(239, 239, 245);
background-color: #fff;
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.06);
transform: translateX(50%) translateY(-50%);
z-index: 1;
}
.icon {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: rotate(180deg);
font-size: 18px;
height: 18px;
svg {
height: 1em;
width: 1em;
vertical-align: top;
}
}
}
.rightArea {
margin-left: @width;
transition: 0.3s margin-left;
&.shrink {
margin-left: 0;
}
flex: 1;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,206 +0,0 @@
html.dark {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px
}
.hljs {
color: #abb2bf;
background: #282c34
}
.hljs-keyword,
.hljs-operator,
.hljs-pattern-match {
color: #f92672
}
.hljs-function,
.hljs-pattern-match .hljs-constructor {
color: #61aeee
}
.hljs-function .hljs-params {
color: #a6e22e
}
.hljs-function .hljs-params .hljs-typing {
color: #fd971f
}
.hljs-module-access .hljs-module {
color: #7e57c2
}
.hljs-constructor {
color: #e2b93d
}
.hljs-constructor .hljs-string {
color: #9ccc65
}
.hljs-comment,
.hljs-quote {
color: #b18eb1;
font-style: italic
}
.hljs-doctag,
.hljs-formula {
color: #c678dd
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e06c75
}
.hljs-literal {
color: #56b6c2
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #98c379
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #e6c07b
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #d19a66
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #61aeee
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}
html {
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px;
&::-webkit-scrollbar {
height: 4px;
}
}
.hljs {
color: #383a42;
background: #fafafa
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic
}
.hljs-doctag,
.hljs-formula,
.hljs-keyword {
color: #a626a4
}
.hljs-deletion,
.hljs-name,
.hljs-section,
.hljs-selector-tag,
.hljs-subst {
color: #e45649
}
.hljs-literal {
color: #0184bb
}
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #50a14f
}
.hljs-attr,
.hljs-number,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-pseudo,
.hljs-template-variable,
.hljs-type,
.hljs-variable {
color: #986801
}
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-symbol,
.hljs-title {
color: #4078f2
}
.hljs-built_in,
.hljs-class .hljs-title,
.hljs-title.class_ {
color: #c18401
}
.hljs-emphasis {
font-style: italic
}
.hljs-strong {
font-weight: 700
}
.hljs-link {
text-decoration: underline
}
}

View File

@ -1,135 +0,0 @@
.markdown-body {
background-color: transparent;
font-size: 14px;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
@keyframes blink {
to {
visibility: hidden;
}
}
}
html.dark {
.markdown-body {
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
color: #65a665;
}
}
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;
}
}
@media screen and (max-width: 533px) {
.markdown-body .code-block-wrapper {
padding: unset;
code {
padding: 24px 16px 16px 16px;
}
}
}

View File

@ -1,4 +1,4 @@
export { default as JVxeTable } from './src/JVxeTable';
export { registerJVxeTable } from './src/install';
export { deleteComponent } from './src/componentMap';
export { registerComponent, registerAsyncComponent, registerASyncComponentReal } from './src/utils/registerUtils';
export { registerComponent, registerAsyncComponent } from './src/utils/registerUtils';

View File

@ -30,17 +30,6 @@
target.selectionStart = selectionStart - 1;
target.selectionEnd = selectionStart - 1;
}
} else {
// update-begin--author:liaozhiyang---date:20240227---forQQYUN-83470
// 41.1 -> 41.10, 100.1 -> 100.10 handleChangeCommon
if (value.indexOf('.') != -1) {
const result = value.split('.').pop();
if (result && result.length >= 2 && result.substr(-1) === '0') {
change = false;
innerValue.value = value;
}
}
// update-end--author:liaozhiyang---date:20240227---forQQYUN-83470
}
}
//

View File

@ -17,19 +17,6 @@ export function isRegistered(type: JVxeTypes | string) {
return componentMap.has(type);
}
/**
* 2024-03-08
* liaozhiyang
* vxe自定义组件
* QQYUN-8241
* @param type
* @param promise
*/
export function registerASyncComponentReal(type: JVxeTypes, component) {
addComponent(type, component);
registerOneComponent(type);
}
/**
* vxe自定义组件
*

View File

@ -24,7 +24,7 @@
</div>
<upload-chunk ref="uploadRef" :visible="uploadVisible" @select="selectFirstFile"></upload-chunk>
</div>
<UserSelectModal rowKey="username" @register="registerModal" @selected="setValue" :multi="false"></UserSelectModal>
<UserSelectModal labelKey="realname" rowKey="username" @register="registerModal" @getSelectResult="setValue" isRadioSelection></UserSelectModal>
<a-modal v-model:open="visibleEmoji" :footer="null" wrapClassName="emoji-modal" :closable="false" :width="490">
<template #title>
<span></span>
@ -49,7 +49,7 @@
import { propTypes } from '/@/utils/propTypes';
import { UserAddOutlined, PaperClipOutlined, SmileOutlined } from '@ant-design/icons-vue';
import { Tooltip } from 'ant-design-vue';
import UserSelectModal from '/@/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue';
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
import { useModal } from '/@/components/Modal';
import UploadChunk from './UploadChunk.vue';
import 'emoji-mart-vue-fast/css/emoji-mart.css';
@ -91,7 +91,7 @@
const uploadVisible = ref(false);
const uploadRef = ref();
//model
const [registerModal, { openModal, closeModal }] = useModal();
const [registerModal, { openModal }] = useModal();
const buttonLoading = ref(false);
const myComment = ref<string>('');
function sendComment() {
@ -149,27 +149,21 @@
function setValue(options) {
console.log('setValue', options);
if (options && options.length > 0) {
const { realname, username } = options[0];
if (realname && username) {
let str = `${realname}[${username}]`;
const { label, value } = options[0];
if (label && value) {
let str = `${label}[${value}]`;
let temp = myComment.value;
if (!temp) {
myComment.value = '@' + str;
} else {
if (temp.endsWith('@')) {
myComment.value = temp + str +' ';
myComment.value = temp + str;
} else {
myComment.value = '@' + str + ' ' + temp + ' ';
myComment.value = '@' + str + ' ' + temp;
}
}
//update-begin---author:wangshuai---date:2024-01-22---for:QQYUN-8002---
showHtml.value = false;
commentRef.value.focus();
commentActive.value = true;
//update-end---author:wangshuai---date:2024-01-22---for:QQYUN-8002---
}
}
closeModal();
}
function handleCommentChange() {

View File

@ -1,8 +1,8 @@
import type { App } from 'vue';
import { Icon } from './Icon';
import AIcon from '/@/components/jeecg/AIcon.vue';
// //Tinymce富文本
// import Editor from '/@/components/Tinymce/src/Editor.vue'
//Tinymce富文本
import Editor from '/@/components/Tinymce/src/Editor.vue'
import { Button, JUploadButton } from './Button';
@ -57,11 +57,9 @@ import {
Skeleton,
Cascader,
Rate,
Progress
} from 'ant-design-vue';
const compList = [AntButton.Group, Icon, AIcon, JUploadButton];
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export function registerGlobComp(app: App) {
compList.forEach((comp) => {
@ -69,13 +67,7 @@ export function registerGlobComp(app: App) {
});
//仪表盘依赖Tinymce需要提前加载没办法按需加载了
//app.component(Editor.name, Editor);
app.component(
'Tinymce',
createAsyncComponent(() => import('./Tinymce/src/Editor.vue'), {
loading: true,
})
);
app.component(Editor.name, Editor);
app.use(Select)
.use(Alert)
@ -124,7 +116,5 @@ export function registerGlobComp(app: App) {
.use(Popconfirm)
.use(Skeleton)
.use(Cascader)
.use(Rate)
.use(Progress);
console.log("---初始化---全局注册Antd等组件--------------")
.use(Rate);
}

View File

@ -21,17 +21,13 @@
}
&-primary {
// update-begin--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
// color: @white;
// background-color: @button-primary-color;
// update-end--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
color: @white;
background-color: @button-primary-color;
&:hover,
&:focus {
// update-begin--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
// color: @white;
// background-color: @button-primary-hover-color;
// update-end--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
color: @white;
background-color: @button-primary-hover-color;
}
//
//&[disabled],
@ -51,18 +47,16 @@
//}
&-default {
// update-begin--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
// color: @button-cancel-color;
// background-color: @button-cancel-bg-color;
// border-color: @button-cancel-border-color;
color: @button-cancel-color;
background-color: @button-cancel-bg-color;
border-color: @button-cancel-border-color;
// &:hover,
// &:focus {
// color: @button-cancel-hover-color;
// background-color: @button-cancel-hover-bg-color;
// border-color: @button-cancel-hover-border-color;
// }
// update-end--author:liaozhiyang---date:20240223---for【QQYUN-8327】btn样式显示不正确
&:hover,
&:focus {
color: @button-cancel-hover-color;
background-color: @button-cancel-hover-bg-color;
border-color: @button-cancel-hover-border-color;
}
//
//&[disabled],
//&[disabled]:hover {

View File

@ -1,8 +1,6 @@
@import './pagination.less';
@import './input.less';
// update-begin--author:liaozhiyang---date:20240130---for【issues/5857】Button color类型颜色失效
@import './btn.less';
// update-end--author:liaozhiyang---date:20240130---for【issues/5857】Button color类型颜色失效
//@import './btn.less';
// @import './table.less';
// TODO beta.11 fix
@ -161,4 +159,4 @@ html[data-theme='dark'] .ant-input-affix-wrapper-textarea-with-clear-btn {
margin: 0 4px;
}
}
// update-end--author:liaozhiyang---date:20230108---for【QQYUN-7855】table页码同步3.x页面效果
// update-end--author:liaozhiyang---date:20230108---for【QQYUN-7855】table页码同步3.x页面效果

View File

@ -38,12 +38,6 @@ export const JEECG_CHAT_UID = 'JEECG_CHAT_UID';
// 免登录租户id与系统分开避免重复
export const OAUTH2_THIRD_LOGIN_TENANT_ID = 'THIRD_LOGIN_TENANT_ID';
// ai助手标识退出需要记录一下
export const AIDE_FLAG = 'AIDE_FLAG';
// ai助手标识退出需要记录一下
export const JEECG_CHAT_KEY = 'JEECG-CHAT-KEY';
export enum CacheTypeEnum {
SESSION,
LOCAL,

View File

@ -2,7 +2,7 @@ export enum PageEnum {
// basic login path
BASE_LOGIN = '/login',
// basic home path
BASE_HOME = '/dashboard/analysis',
BASE_HOME = '/cet/welcomeInterface',
// error page path
ERROR_PAGE = '/exception',
// error log page path

View File

@ -281,6 +281,7 @@ export function useListTable(tableProps: TableProps): [
// 发送请求之前调用的方法
function beforeFetch(params) {
// 默认以 createTime 降序排序
console.log('tableProps', tableProps);
return Object.assign({ column: 'createTime', order: 'desc' }, params);
}

View File

@ -21,9 +21,7 @@ export function useMethods() {
* @param url
*/
async function exportXls(name, url, params, isXlsx = false) {
//update-begin---author:wangshuai---date:2024-01-25---for:【QQYUN-8118】导出超时时间设置长点---
const data = await defHttp.get({ url: url, params: params, responseType: 'blob', timeout: 60000 }, { isTransformResponse: false });
//update-end---author:wangshuai---date:2024-01-25---for:【QQYUN-8118】导出超时时间设置长点---
const data = await defHttp.get({ url: url, params: params, responseType: 'blob' }, { isTransformResponse: false });
if (!data) {
createMessage.warning('文件下载失败');
return;

View File

@ -1,5 +1,4 @@
import type {Menu} from "@/router/types";
import { ref, watch, unref } from 'vue';
import { watch, unref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTitle as usePageTitle } from '@vueuse/core';
import { useGlobSetting } from '/@/hooks/setting';
@ -19,11 +18,9 @@ export function useTitle() {
const pageTitle = usePageTitle();
const menus = ref<Menu[] | null>(null)
watch(
[() => currentRoute.value.path, () => localeStore.getLocale],
async () => {
() => {
const route = unref(currentRoute);
if (route.name === REDIRECT_NAME) {
@ -31,17 +28,16 @@ export function useTitle() {
}
// update-begin--author:liaozhiyang---date:20231110---for【QQYUN-6938】online菜单名字和页面title不一致
if (route.params && Object.keys(route.params).length) {
if (!menus.value) {
menus.value = await getMenus();
}
const getTitle = getMatchingRouterName(menus.value, route.fullPath);
let tTitle = '';
if (getTitle) {
tTitle = t(getTitle);
} else {
tTitle = t(route?.meta?.title as string);
}
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
getMenus().then((menus) => {
const getTitle = getMatchingRouterName(menus, route.fullPath);
let tTitle = '';
if (getTitle) {
tTitle = t(getTitle);
} else {
tTitle = t(route?.meta?.title as string);
};
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;
});
} else {
const tTitle = t(route?.meta?.title as string);
pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`;

View File

@ -1,5 +1,5 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="600px">
<BasicModal v-bind="$attrs" @register="registerModal" title="修改密码" @ok="handleSubmit" width="600px">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
@ -11,52 +11,43 @@
import BasicForm from '/@/components/Form/src/BasicForm.vue';
import { useForm } from '/@/components/Form/src/hooks/useForm';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocaleStore } from '/@/store/modules/locale';
import { useI18n } from '/@/hooks/web/useI18n';
const localeStore = useLocaleStore();
const { t } = useI18n();
// Emits
const emit = defineEmits(['register']);
const $message = useMessage();
const formRef = ref();
const username = ref('');
// update-begin--author:liaozhiyang---date:20240124---forQQYUN-7970
const title = ref(t('layout.changePassword.changePassword'));
//
const [registerForm, { resetFields, validate, clearValidate }] = useForm({
schemas: [
{
label: t('layout.changePassword.oldPassword'),
label: '旧密码',
field: 'oldpassword',
component: 'InputPassword',
required: true,
},
{
label: t('layout.changePassword.newPassword'),
label: '新密码',
field: 'password',
component: 'StrengthMeter',
componentProps: {
placeholder: t('layout.changePassword.pleaseEnterNewPassword'),
placeholder: '请输入新密码',
},
rules: [
{
required: true,
message: t('layout.changePassword.pleaseEnterNewPassword'),
message: '请输入新密码',
},
],
},
{
label: t('layout.changePassword.confirmNewPassword'),
label: '确认新密码',
field: 'confirmpassword',
component: 'InputPassword',
dynamicRules: ({ values }) => rules.confirmPassword(values, true),
},
],
showActionButtonGroup: false,
wrapperCol: null,
labelWidth: localeStore.getLocale == 'zh_CN' ? 100 : 160,
});
// update-end--author:liaozhiyang---date:20240124---forQQYUN-7970
//
const [registerModal, { setModalProps, closeModal }] = useModalInner();
@ -94,7 +85,6 @@
}
defineExpose({
title,
show,
});
</script>

View File

@ -11,12 +11,7 @@
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="ion:document-text-outline" v-if="getShowDoc" />
<MenuDivider v-if="getShowDoc" />
<MenuItem key="account" :text="t('layout.header.dropdownItemSwitchAccount')" icon="ant-design:setting-outlined" />
<MenuItem key="password" :text="t('layout.header.dropdownItemSwitchPassword')" icon="ant-design:edit-outlined" />
<MenuItem key="depart" :text="t('layout.header.dropdownItemSwitchDepart')" icon="ant-design:cluster-outlined" />
<MenuItem key="cache" :text="t('layout.header.dropdownItemRefreshCache')" icon="ion:sync-outline" />
<!-- <MenuItem
v-if="getUseLockPage"
key="lock"
@ -126,13 +121,9 @@
const res = await queryAllDictItems();
removeAuthCache(DB_DICT_DATA_KEY);
setAuthCache(DB_DICT_DATA_KEY, res.result);
// update-begin--author:liaozhiyang---date:20240124---forQQYUN-7970
createMessage.success(t('layout.header.refreshCacheComplete'));
// update-end--author:liaozhiyang---date:20240124---forQQYUN-7970
createMessage.success('刷新缓存完成!');
} else {
// update-begin--author:liaozhiyang---date:20240124---forQQYUN-7970
createMessage.error(t('layout.header.refreshCacheFailure'));
// update-end--author:liaozhiyang---date:20240124---forQQYUN-7970
createMessage.error('刷新缓存失败!');
}
}
//

View File

@ -105,7 +105,7 @@
&-action {
display: flex;
min-width: 180px;
min-width: 150px;
// padding-right: 12px;
align-items: center;

View File

@ -3,15 +3,11 @@
<!-- left start -->
<div :class="`${prefixCls}-left`">
<!-- logo -->
<AppLogo v-if="getShowHeaderLogo || getIsMobile" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" :style="getLogoWidth" />
<LayoutTrigger
v-if="(getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile"
:theme="getHeaderTheme"
:sider="false"
/>
<AppLogo v-if="getShowHeaderLogo || getIsMobile" :class="`${prefixCls}-logo`" :theme="getHeaderTheme"
:style="getLogoWidth" />
<LayoutTrigger v-if="(getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile"
:theme="getHeaderTheme" :sider="false" />
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
<!-- 欢迎语 -->
<span v-if="getShowContent && getShowBreadTitle && !getIsMobile" :class="[prefixCls, `${prefixCls}--${getHeaderTheme}`,'headerIntroductionClass']"> {{t('layout.header.welcomeIn')}} {{ title }} </span>
</div>
<!-- left end -->
@ -23,236 +19,226 @@
<!-- action -->
<div :class="`${prefixCls}-action`">
<AppSearch :class="`${prefixCls}-action__item `" v-if="getShowSearch" />
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
<LockScreen v-if="getUseLockPage" />
<AppLocalePicker v-if="getShowLocalePicker" :reload="true" :showText="false" :class="`${prefixCls}-action__item`" />
<UserDropDown :theme="getHeaderTheme" />
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
<!-- ai助手 -->
<Aide></Aide>
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
</div>
</Header>
<LoginSelect ref="loginSelectRef" @success="loginSelectOk"></LoginSelect>
</template>
<script lang="ts">
import { defineComponent, unref, computed, ref, onMounted, toRaw } from 'vue';
import { useGlobSetting } from '/@/hooks/setting';
import { propTypes } from '/@/utils/propTypes';
import { defineComponent, unref, computed, ref, onMounted, toRaw } from 'vue';
import { useGlobSetting } from '/@/hooks/setting';
import { propTypes } from '/@/utils/propTypes';
import { Layout } from 'ant-design-vue';
import { AppLogo } from '/@/components/Application';
import LayoutMenu from '../menu/index.vue';
import LayoutTrigger from '../trigger/index.vue';
import { Layout } from 'ant-design-vue';
import { AppLogo } from '/@/components/Application';
import LayoutMenu from '../menu/index.vue';
import LayoutTrigger from '../trigger/index.vue';
import { AppSearch } from '/@/components/Application';
import { AppSearch } from '/@/components/Application';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
import { SettingButtonPositionEnum } from '/@/enums/appEnum';
import { AppLocalePicker } from '/@/components/Application';
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
import { SettingButtonPositionEnum } from '/@/enums/appEnum';
import { AppLocalePicker } from '/@/components/Application';
import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify, ErrorAction, LockScreen } from './components';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify, ErrorAction, LockScreen } from './components';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useLocale } from '/@/locales/useLocale';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useLocale } from '/@/locales/useLocale';
import LoginSelect from '/@/views/sys/login/LoginSelect.vue';
import { useUserStore } from '/@/store/modules/user';
import { useI18n } from '/@/hooks/web/useI18n';
import Aide from "@/views/dashboard/ai/components/aide/index.vue"
const { t } = useI18n();
import LoginSelect from '/@/views/sys/login/LoginSelect.vue';
import { useUserStore } from '/@/store/modules/user';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export default defineComponent({
name: 'LayoutHeader',
components: {
Header: Layout.Header,
AppLogo,
LayoutTrigger,
LayoutBreadcrumb,
LayoutMenu,
UserDropDown,
AppLocalePicker,
FullScreen,
Notify,
AppSearch,
ErrorAction,
LockScreen,
LoginSelect,
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue'), {
loading: true,
}),
Aide
},
props: {
fixed: propTypes.bool,
},
setup(props) {
const { prefixCls } = useDesign('layout-header');
const userStore = useUserStore();
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
const { title } = useGlobSetting();
export default defineComponent({
name: 'LayoutHeader',
components: {
Header: Layout.Header,
AppLogo,
LayoutTrigger,
LayoutBreadcrumb,
LayoutMenu,
UserDropDown,
AppLocalePicker,
FullScreen,
Notify,
AppSearch,
ErrorAction,
LockScreen,
LoginSelect,
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue'), {
loading: true,
}),
},
props: {
fixed: propTypes.bool,
},
setup(props) {
const { prefixCls } = useDesign('layout-header');
const userStore = useUserStore();
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
const { title } = useGlobSetting();
const {
getHeaderTheme,
getShowFullScreen,
getShowNotice,
getShowContent,
getShowBread,
getShowHeaderLogo,
getShowHeader,
getShowSearch,
getUseLockPage,
getShowBreadTitle,
} = useHeaderSetting();
const {
getHeaderTheme,
getShowFullScreen,
getShowNotice,
getShowContent,
getShowBread,
getShowHeaderLogo,
getShowHeader,
getShowSearch,
getUseLockPage,
getShowBreadTitle,
} = useHeaderSetting();
const { getShowLocalePicker } = useLocale();
const { getShowLocalePicker } = useLocale();
const { getIsMobile } = useAppInject();
const { getIsMobile } = useAppInject();
const getHeaderClass = computed(() => {
const theme = unref(getHeaderTheme);
return [
prefixCls,
{
[`${prefixCls}--fixed`]: props.fixed,
[`${prefixCls}--mobile`]: unref(getIsMobile),
[`${prefixCls}--${theme}`]: theme,
},
];
});
const getShowSetting = computed(() => {
if (!unref(getShowSettingButton)) {
return false;
}
const settingButtonPosition = unref(getSettingButtonPosition);
if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
return unref(getShowHeader);
}
return settingButtonPosition === SettingButtonPositionEnum.HEADER;
});
const getLogoWidth = computed(() => {
if (!unref(getIsMixMode) || unref(getIsMobile)) {
return {};
}
const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth);
return { width: `${width}px` };
});
const getSplitType = computed(() => {
return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
});
const getMenuMode = computed(() => {
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
});
/**
* 首页多租户部门弹窗逻辑
*/
const loginSelectRef = ref();
function showLoginSelect() {
//update-begin---author:liusq Date:20220101 for----
//
const loginInfo = toRaw(userStore.getLoginInfo) || {};
if (!!loginInfo.isLogin) {
loginSelectRef.value.show(loginInfo);
}
//update-end---author:liusq Date:20220101 for----
}
function loginSelectOk() {
console.log('成功。。。。。');
}
onMounted(() => {
showLoginSelect();
});
return {
const getHeaderClass = computed(() => {
const theme = unref(getHeaderTheme);
return [
prefixCls,
getHeaderClass,
getShowHeaderLogo,
getHeaderTheme,
getShowHeaderTrigger,
getIsMobile,
getShowBreadTitle,
getShowBread,
getShowContent,
getSplitType,
getSplit,
getMenuMode,
getShowTopMenu,
getShowLocalePicker,
getShowFullScreen,
getShowNotice,
getUseErrorHandle,
getLogoWidth,
getIsMixSidebar,
getShowSettingButton,
getShowSetting,
getShowSearch,
getUseLockPage,
loginSelectOk,
loginSelectRef,
title,
t
};
},
});
{
[`${prefixCls}--fixed`]: props.fixed,
[`${prefixCls}--mobile`]: unref(getIsMobile),
[`${prefixCls}--${theme}`]: theme,
},
];
});
const getShowSetting = computed(() => {
if (!unref(getShowSettingButton)) {
return false;
}
const settingButtonPosition = unref(getSettingButtonPosition);
if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
return unref(getShowHeader);
}
return settingButtonPosition === SettingButtonPositionEnum.HEADER;
});
const getLogoWidth = computed(() => {
if (!unref(getIsMixMode) || unref(getIsMobile)) {
return {};
}
const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth);
return { width: `${width}px` };
});
const getSplitType = computed(() => {
return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
});
const getMenuMode = computed(() => {
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
});
/**
* 首页多租户部门弹窗逻辑
*/
const loginSelectRef = ref();
function showLoginSelect() {
//update-begin---author:liusq Date:20220101 for----
//
const loginInfo = toRaw(userStore.getLoginInfo) || {};
if (!!loginInfo.isLogin) {
loginSelectRef.value.show(loginInfo);
}
//update-end---author:liusq Date:20220101 for----
}
function loginSelectOk() {
console.log('成功。。。。。');
}
onMounted(() => {
showLoginSelect();
});
return {
prefixCls,
getHeaderClass,
getShowHeaderLogo,
getHeaderTheme,
getShowHeaderTrigger,
getIsMobile,
getShowBreadTitle,
getShowBread,
getShowContent,
getSplitType,
getSplit,
getMenuMode,
getShowTopMenu,
getShowLocalePicker,
getShowFullScreen,
getShowNotice,
getUseErrorHandle,
getLogoWidth,
getIsMixSidebar,
getShowSettingButton,
getShowSetting,
getShowSearch,
getUseLockPage,
loginSelectOk,
loginSelectRef,
title,
t
};
},
});
</script>
<style lang="less">
@import './index.less';
//update-begin---author:scott ---date:2022-09-30 for-----------
//
@prefix-cls: ~'@{namespace}-layout-header';
.ant-layout .@{prefix-cls} {
display: flex;
padding: 0 8px;
height: 48px;
align-items: center;
@import './index.less';
//update-begin---author:scott ---date:2022-09-30 for-----------
//
@prefix-cls: ~'@{namespace}-layout-header';
.ant-layout .@{prefix-cls} {
display: flex;
padding: 0 8px;
height: 48px;
align-items: center;
.headerIntroductionClass {
margin-right: 4px;
margin-bottom: 2px;
border-bottom: 0px;
border-left: 0px;
}
&--light {
.headerIntroductionClass {
margin-right: 4px;
margin-bottom: 2px;
border-bottom: 0px;
border-left: 0px;
color: @breadcrumb-item-normal-color;
}
&--light {
.headerIntroductionClass {
color: @breadcrumb-item-normal-color;
}
}
&--dark {
.headerIntroductionClass {
color: rgba(255, 255, 255, 0.6);
}
&--dark {
.headerIntroductionClass {
color: rgba(255, 255, 255, 0.6);
}
.anticon, .truncate {
color: rgba(255, 255, 255, 0.8);
}
.anticon,
.truncate {
color: rgba(255, 255, 255, 0.8);
}
//update-end---author:scott ---date::2022-09-30 for--------------
}
//update-end---author:scott ---date::2022-09-30 for--------------
}
</style>

View File

@ -49,8 +49,7 @@
},
]"
>
<span class="text"> {{ title }}</span>
<Icon :size="16" :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'" class="pushpin" @click="handleFixedMenu" />
<span class="text" style="margin-left: 15px;"> {{ title }}</span>
</div>
<ScrollContainer :class="`${prefixCls}-menu-list__content`">
<SimpleMenu :items="childrenMenus" :theme="getMenuTheme" mixSider @menuClick="handleMenuClick" />

View File

@ -17,14 +17,11 @@ export default {
tooltipExitFull: 'Exit Full Screen',
// lock
lockScreenPassword: 'Password',
lockScreenPassword: 'Lock screen password',
lockScreen: 'Lock screen',
lockScreenBtn: 'Locking',
home: 'Home',
welcomeIn: 'Welcome in',
refreshCacheComplete: 'Refresh cache complete',
refreshCacheFailure: 'Refresh cache failure',
},
multipleTab: {
reload: 'Refresh current',
@ -122,11 +119,4 @@ export default {
mixSidebarFixed: 'Fixed expanded menu',
},
changePassword: {
changePassword: 'Change password',
oldPassword: 'Old password',
newPassword: 'New password',
confirmNewPassword: 'Confirm new password',
pleaseEnterNewPassword: 'Please enter new password',
},
};

View File

@ -23,9 +23,6 @@ export default {
lockScreenBtn: '锁定',
home: '首页',
welcomeIn:"欢迎进入",
refreshCacheComplete: "刷新缓存完成!",
refreshCacheFailure: "刷新缓存失败!",
},
multipleTab: {
reload: '重新加载',
@ -123,11 +120,4 @@ export default {
mixSidebarFixed: '固定展开菜单',
},
changePassword: {
changePassword: '修改密码',
oldPassword: '旧密码',
newPassword: '新密码',
confirmNewPassword: '确认新密码',
pleaseEnterNewPassword: '请输入新密码',
},
};

View File

@ -1,19 +1,10 @@
import { getThemeColors, generateColors } from '../../../build/config/themeConfig';
import {
replaceStyleVariables,
loadDarkThemeCss,
replaceCssColors,
darkCssIsReady,
linkID,
styleTagId,
appendCssToDom,
getStyleDom,
} from '@rys-fe/vite-plugin-theme/es/client';
import { replaceStyleVariables } from '@rys-fe/vite-plugin-theme/es/client';
import { mixLighten, mixDarken, tinycolor } from '@rys-fe/vite-plugin-theme/es/colorUtils';
import { useAppStore } from '/@/store/modules/app';
import { defHttp } from '/@/utils/http/axios';
let cssText = '';
export async function changeTheme(color: string) {
// update-begin--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
const appStore = useAppStore();
@ -25,32 +16,17 @@ export async function changeTheme(color: string) {
tinycolor,
color,
});
// update-begin--author:liaozhiyang---date:20240322---for【QQYUN-8570】生产环境暗黑模式下主题色不生效
if (import.meta.env.PROD && appStore.getDarkMode === 'dark') {
if (!darkCssIsReady && !cssText) {
await loadDarkThemeCss();
}
const el: HTMLLinkElement = document.getElementById(linkID) as HTMLLinkElement;
if (el?.href) {
// cssText = await fetchCss(el.href) as string;
!cssText && (cssText = await defHttp.get({ url: el.href }, { isTransformResponse: false }));
const colorVariables = [...getThemeColors(color), ...colors];
const processCss = await replaceCssColors(cssText, colorVariables);
appendCssToDom(getStyleDom(styleTagId) as HTMLStyleElement, processCss);
}
} else {
await replaceStyleVariables({
colorVariables: [...getThemeColors(color), ...colors],
});
fixDark();
}
// update-end--author:liaozhiyang---date:20240322---for【QQYUN-8570】生产环境暗黑模式下主题色不生效
let res = await replaceStyleVariables({
colorVariables: [...getThemeColors(color), ...colors],
});
fixDark();
return res;
}
// 【LOWCOD-2262】修复黑暗模式下切换皮肤无效的问题
async function fixDark() {
// update-begin--author:liaozhiyang---date:20240322---for【QQYUN-8570】生产环境暗黑模式下主题色不生效
const el = document.getElementById(styleTagId);
// update-end--author:liaozhiyang---date:20240322---for【QQYUN-8570】生产环境暗黑模式下主题色不生效
let el = document.getElementById('__VITE_PLUGIN_THEME__');
if (el) {
el.innerHTML = el.innerHTML.replace(/\\["']dark\\["']/g, `'dark'`);
}

View File

@ -141,7 +141,6 @@ export function createPermissionGuard(router: Router) {
// update-begin-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
redirect: to.fullPath,
// update-end-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
};
}
next(redirectData);
@ -162,44 +161,40 @@ export function createPermissionGuard(router: Router) {
return;
}
//update-begin---author:scott ---date:2024-02-21 for【QQYUN-8326】刷新首页不需要重新获取用户信息---
// // get userinfo while last fetch time is empty
// if (userStore.getLastUpdateTime === 0) {
// try {
// console.log("--LastUpdateTime---getUserInfoAction-----")
// await userStore.getUserInfoAction();
// } catch (err) {
// console.info(err);
// next();
// }
// }
//update-end---author:scott ---date::2024-02-21 for【QQYUN-8326】刷新首页不需要重新获获取用户信息---
// update-begin--author:liaozhiyang---date:20240321---for【QQYUN-8572】表格行选择卡顿问题customRender中字典引起的
// get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) {
userStore.setAllDictItemsByLocal();
try {
await userStore.getUserInfoAction();
} catch (err) {
console.info(err);
next();
}
}
// update-end--author:liaozhiyang---date:20240321---for【QQYUN-8572】表格行选择卡顿问题customRender中字典引起的
if (permissionStore.getIsDynamicAddedRoute) {
next();
return;
}
// 构建后台菜单路由
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
if (to.name === PAGE_NOT_FOUND_ROUTE.name) {
// 动态添加路由后此处应当重定向到fullPath否则会加载404页面内容
next({ path: to.fullPath, replace: true, query: to.query });
//next({ path: to.fullPath, replace: true, query: to.query });
next({ path: PageEnum.BASE_HOME, replace: true, query: to.query });
} else {
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
//const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
const nextData = PageEnum.BASE_HOME;
next(nextData);
}
});

View File

@ -15,7 +15,7 @@ export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
children: [
{
path: '/:path(.*)*',
name: PAGE_NOT_FOUND_NAME,
name: 'ChildPageNotFound',
component: EXCEPTION_COMPONENT,
meta: {
title: 'ErrorPage',

View File

@ -87,7 +87,7 @@ const setting: ProjectConfig = {
// 菜单配置
menuSetting: {
// 背景色
bgColor: SIDE_BAR_BG_COLOR_LIST[0],
bgColor: SIDE_BAR_BG_COLOR_LIST[3],
// 是否固定住左侧菜单
fixed: true,
// 菜单折叠
@ -106,15 +106,15 @@ const setting: ProjectConfig = {
// 菜单模式
mode: MenuModeEnum.INLINE,
// 菜单类型
type: MenuTypeEnum.SIDEBAR,
type: MenuTypeEnum.MIX,
// 菜单主题
theme: ThemeEnum.DARK,
theme: ThemeEnum.LIGHT,
// 分割菜单
split: false,
// 顶部菜单布局
topMenuAlign: 'center',
// 折叠触发器的位置
trigger: TriggerEnum.HEADER,
trigger: TriggerEnum.NONE,
// 手风琴模式,只展示一个菜单
accordion: true,
// 在路由切换的时候关闭左侧混合菜单展开菜单
@ -130,7 +130,7 @@ const setting: ProjectConfig = {
// 刷新后是否保留已经打开的标签页
cache: false,
// 开启
show: true,
show: false,
// 是否可以拖拽
canDrag: true,
// 开启快速操作

View File

@ -2,13 +2,12 @@ import type { App } from 'vue';
import { registerJVxeTable } from '/@/components/jeecg/JVxeTable';
import { registerJVxeCustom } from '/@/components/JVxeCustom';
// // 注册全局聊天表情包
// import { Picker } from 'emoji-mart-vue-fast/src';
// 注册全局聊天表情包
import { Picker } from 'emoji-mart-vue-fast/src';
// 注册全局dayjs
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export async function registerThirdComp(app: App) {
//---------------------------------------------------------------------
@ -18,22 +17,8 @@ export async function registerThirdComp(app: App) {
await registerJVxeCustom();
//---------------------------------------------------------------------
// 注册全局聊天表情包
//app.component('Picker', Picker);
app.component(
'Picker',
createAsyncComponent(() => {
return new Promise((resolve, rejected) => {
import('emoji-mart-vue-fast/src')
.then((res) => {
const { Picker } = res;
resolve(Picker);
})
.catch((err) => {
rejected(err);
});
});
})
);
app.component('Picker', Picker);
//---------------------------------------------------------------------
// 注册全局dayjs
dayjs.locale('zh-cn');

View File

@ -1,75 +0,0 @@
import {store} from '/@/store';
import {defineStore} from 'pinia';
import {defHttp} from "@/utils/http/axios";
interface DefIndexState {
// 首页url
url: string,
// 首页组件
component: string
}
export const useDefIndexStore = defineStore({
id: 'defIndex',
state: (): DefIndexState => ({
url: '',
component: '',
}),
getters: {},
actions: {
/**
*
*/
async query() {
const config = await defIndexApi.query();
this.url = config.url;
this.component = config.component;
},
/**
*
* @param url url
* @param component
* @param isRoute
*/
async update(url: string, component: string, isRoute: boolean) {
await defIndexApi.update(url, component, isRoute);
await this.query()
},
check(url: string) {
return url === this.url;
}
}
});
// Need to be used outside the setup
export function useDefIndexStoreWithOut() {
return useDefIndexStore(store);
}
/**
* API
*/
export const defIndexApi = {
/**
*
*/
async query() {
const url = '/sys/sysRoleIndex/queryDefIndex'
return await defHttp.get({url});
},
/**
*
* @param url url
* @param component
* @param isRoute
*/
async update(url: string, component: string, isRoute: boolean) {
let apiUrl = '/sys/sysRoleIndex/updateDefIndex'
apiUrl += '?url=' + url
apiUrl += '&component=' + component
apiUrl += '&isRoute=' + isRoute
return await defHttp.put({url: apiUrl});
},
}

View File

@ -18,7 +18,8 @@ import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
import { filter } from '/@/utils/helper/treeHelper';
import { getBackMenuAndPerms } from '/@/api/sys/menu';
import { getMenuList,switchVue3Menu } from '/@/api/sys/menu';
import { getPermCode } from '/@/api/sys/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { PageEnum } from '/@/enums/pageEnum';
@ -123,14 +124,10 @@ export const usePermissionStore = defineStore({
this.lastBuildMenuTime = 0;
},
async changePermissionCode() {
const systemPermission = await getBackMenuAndPerms();
const systemPermission = await getPermCode();
const codeList = systemPermission.codeList;
this.setPermCodeList(codeList);
this.setAuthData(systemPermission);
//菜单路由
const routeList = systemPermission.menu;
return routeList;
},
async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
const { t } = useI18n();
@ -210,7 +207,6 @@ export const usePermissionStore = defineStore({
// 后台菜单构建
case PermissionModeEnum.BACK:
const { createMessage, createWarningModal } = useMessage();
console.log(" --- 构建后台路由菜单 --- ")
// 菜单加载提示
// createMessage.loading({
// content: t('sys.app.menuLoading'),
@ -221,42 +217,42 @@ export const usePermissionStore = defineStore({
// 这个函数可能只需要执行一次,并且实际的项目可以在正确的时间被放置
let routeList: AppRouteRecordRaw[] = [];
try {
routeList = await this.changePermissionCode();
//routeList = (await getMenuList()) as AppRouteRecordRaw[];
this.changePermissionCode();
routeList = (await getMenuList()) as AppRouteRecordRaw[];
// update-begin----author:sunjianlei---date:20220315------for: 判断是否是 vue3 版本的菜单 ---
// let hasIndex: boolean = false;
// let hasIcon: boolean = false;
// for (let menuItem of routeList) {
// // 条件1判断组件是否是 layouts/default/index
// if (!hasIndex) {
// hasIndex = menuItem.component === 'layouts/default/index';
// }
// // 条件2判断图标是否带有 冒号
// if (!hasIcon) {
// hasIcon = !!menuItem.meta?.icon?.includes(':');
// }
// // 满足任何一个条件都直接跳出循环
// if (hasIcon || hasIndex) {
// break;
// }
// }
// // 两个条件都不满足,就弹出提示框
// if (!hasIcon && !hasIndex) {
// // 延迟1.5秒之后再出现提示,否则提示框出不来
// setTimeout(
// () =>
// createWarningModal({
// title: '检测提示',
// content:
// '当前菜单表是 <b>Vue2版本</b>,导致菜单加载异常!<br>点击确认切换到Vue3版菜单',
// onOk:function () {
// switchVue3Menu();
// location.reload();
// }
// }),
// 100
// );
// }
let hasIndex: boolean = false;
let hasIcon: boolean = false;
for (let menuItem of routeList) {
// 条件1判断组件是否是 layouts/default/index
if (!hasIndex) {
hasIndex = menuItem.component === 'layouts/default/index';
}
// 条件2判断图标是否带有 冒号
if (!hasIcon) {
hasIcon = !!menuItem.meta?.icon?.includes(':');
}
// 满足任何一个条件都直接跳出循环
if (hasIcon || hasIndex) {
break;
}
}
// 两个条件都不满足,就弹出提示框
if (!hasIcon && !hasIndex) {
// 延迟1.5秒之后再出现提示,否则提示框出不来
setTimeout(
() =>
createWarningModal({
title: '检测提示',
content:
'当前菜单表是 <b>Vue2版本</b>,导致菜单加载异常!<br>点击确认切换到Vue3版菜单',
onOk:function () {
switchVue3Menu();
location.reload();
}
}),
100
);
}
// update-end----author:sunjianlei---date:20220315------for: 判断是否是 vue3 版本的菜单 ---
} catch (error) {
console.error(error);

View File

@ -19,14 +19,11 @@ import { useGlobSetting } from '/@/hooks/setting';
import { JDragConfigEnum } from '/@/enums/jeecgEnum';
import { useSso } from '/@/hooks/web/useSso';
import { isOAuth2AppEnv } from "/@/views/sys/login/useLogin";
interface dictType {
[key: string]: any;
}
interface UserState {
userInfo: Nullable<UserInfo>;
token?: string;
roleList: RoleEnum[];
dictItems?: dictType | null;
dictItems?: [];
sessionTimeout?: boolean;
lastUpdateTime: number;
tenantid?: string | number;
@ -44,7 +41,7 @@ export const useUserStore = defineStore({
// 角色列表
roleList: [],
// 字典
dictItems: null,
dictItems: [],
// session过期时间
sessionTimeout: false,
// Last fetch time
@ -59,9 +56,6 @@ export const useUserStore = defineStore({
}),
getters: {
getUserInfo(): UserInfo {
if(this.userInfo == null){
this.userInfo = getAuthCache<UserInfo>(USER_INFO_KEY)!=null ? getAuthCache<UserInfo>(USER_INFO_KEY) : null;
}
return this.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY) || {};
},
getLoginInfo(): LoginInfo {
@ -112,16 +106,6 @@ export const useUserStore = defineStore({
this.dictItems = dictItems;
setAuthCache(DB_DICT_DATA_KEY, dictItems);
},
setAllDictItemsByLocal() {
// update-begin--author:liaozhiyang---date:20240321---for【QQYUN-8572】表格行选择卡顿问题customRender中字典引起的
if (!this.dictItems) {
const allDictItems = getAuthCache(DB_DICT_DATA_KEY);
if (allDictItems) {
this.dictItems = allDictItems;
}
}
// update-end--author:liaozhiyang---date:20240321---for【QQYUN-8572】表格行选择卡顿问题customRender中字典引起的
},
setTenant(id) {
this.tenantid = id;
setAuthCache(TENANT_ID, id);
@ -134,7 +118,7 @@ export const useUserStore = defineStore({
},
resetState() {
this.userInfo = null;
this.dictItems = null;
this.dictItems = [];
this.token = '';
this.roleList = [];
this.sessionTimeout = false;
@ -184,19 +168,15 @@ export const useUserStore = defineStore({
if (sessionTimeout) {
this.setSessionTimeout(false);
} else {
//update-begin---author:scott ---date::2024-02-21 for【QQYUN-8326】登录不需要构建路由进入首页有构建---
// // 构建后台菜单路由
// const permissionStore = usePermissionStore();
// if (!permissionStore.isDynamicAddedRoute) {
// const routes = await permissionStore.buildRoutesAction();
// routes.forEach((route) => {
// router.addRoute(route as unknown as RouteRecordRaw);
// });
// router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
// permissionStore.setDynamicAddedRoute(true);
// }
//update-end---author:scott ---date::2024-02-21 for【QQYUN-8326】登录不需要构建路由进入首页有构建---
const permissionStore = usePermissionStore();
if (!permissionStore.isDynamicAddedRoute) {
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
}
await this.setLoginInfo({ ...data, isLogin: true });
//update-begin-author:liusq date:2022-5-5 for:登录成功后缓存拖拽模块的接口前缀
localStorage.setItem(JDragConfigEnum.DRAG_BASE_URL, useGlobSetting().domainUrl);
@ -299,7 +279,6 @@ export const useUserStore = defineStore({
this.setUserInfo(null);
this.setLoginInfo(null);
this.setTenant(null);
this.setAllDictItems(null);
//update-begin-author:liusq date:2022-5-5 for:退出登录后清除拖拽模块的接口前缀
localStorage.removeItem(JDragConfigEnum.DRAG_BASE_URL);
//update-end-author:liusq date:2022-5-5 for: 退出登录后清除拖拽模块的接口前缀

View File

@ -52,8 +52,7 @@ const render = {
if (obj.length > 0) {
text = obj[0].text;
}
//【jeecgboot-vue3/issues/903】render.renderDict使用tag渲染报警告问题 #903
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, () => text);
return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, text);
},
/**
*

70
src/utils/download.js Normal file
View File

@ -0,0 +1,70 @@
function myBrowser() {
var userAgent = navigator.userAgent; // 取得浏览器的userAgent字符串
var isOpera = userAgent.indexOf('Opera') > -1;
if (isOpera) {
return 'Opera';
} // 判断是否Opera浏览器
if (userAgent.indexOf('Firefox') > -1) {
return 'FF';
} // 判断是否Firefox浏览器
if (userAgent.indexOf('Chrome') > -1) {
return 'Chrome';
}
if (userAgent.indexOf('Safari') > -1) {
return 'Safari';
} // 判断是否Safari浏览器
if (userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 && !isOpera) {
return 'IE';
} // 判断是否IE浏览器
if (userAgent.indexOf('Trident') > -1) {
return 'Edge';
} // 判断是否Edge浏览器
}
function SaveAs5(imgURL) {
var oPop = window.open(imgURL, '', 'width=1, height=1, top=5000, left=5000');
for (; oPop.document.readyState !== 'complete'; ) {
if (oPop.document.readyState === 'complete') break;
}
oPop.document.execCommand('SaveAs');
oPop.close();
}
function download(src, fileName) {
// 创建隐藏的可下载链接
var eleLink = document.createElement('a');
if (fileName) {
eleLink.setAttribute('download', fileName);
} else {
eleLink.download = src;
}
eleLink.style.display = 'none';
// // 字符内容转变成blob地址
eleLink.href = src;
// // 触发点击
document.body.appendChild(eleLink);
eleLink.click();
// // 然后移除
document.body.removeChild(eleLink);
}
function downLoadFile(url, fileName = '') {
if (myBrowser() === 'IE' || myBrowser() === 'Edge') {
SaveAs5(url);
} else {
download(url, fileName);
}
}
function conversionFileDownload(res) {
const a = document.createElement('a');
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' });
const temp = res.headers['content-disposition'].split(';')[1].split('filename=')[1];
// 对文件名乱码转义--【Node.js】使用iconv-lite解决中文乱码
const iconv = require('iconv-lite');
iconv.skipDecodeWarning = true; // 忽略警告
const fileName = iconv.decode(temp, 'utf-8');
console.log(fileName);
a.setAttribute('download', fileName);
const objectUrl = URL.createObjectURL(blob);
a.setAttribute('href', objectUrl);
a.click();
}
export { downLoadFile, conversionFileDownload };

View File

@ -91,22 +91,13 @@ const transform: AxiosTransform = {
beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
//update-begin---author:scott ---date:2024-02-20 for以http开头的请求url不拼加前缀--
// http开头的请求url不加前缀
let isStartWithHttp = false;
const requestUrl = config.url;
if(requestUrl!=null && (requestUrl.startsWith("http:") || requestUrl.startsWith("https:"))){
isStartWithHttp = true;
}
if (!isStartWithHttp && joinPrefix) {
if (joinPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
if (!isStartWithHttp && apiUrl && isString(apiUrl)) {
if (apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`;
}
//update-end---author:scott ---date::2024-02-20 for以http开头的请求url不拼加前缀--
const params = config.params || {};
const data = config.data || false;
formatDate && data && !isString(data) && formatRequestDate(data);
@ -255,7 +246,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
// authenticationScheme: 'Bearer',
authenticationScheme: '',
//接口超时设置
timeout: 10 * 1000,
timeout: 15 * 1000,
// 基础接口地址
// baseURL: globSetting.apiUrl,
headers: { 'Content-Type': ContentTypeEnum.JSON },

View File

@ -104,7 +104,7 @@ export function cloneObject(obj) {
}
export const withInstall = <T>(component: T, alias?: string) => {
//console.log("---初始化---", component)
console.log("---初始化---", component)
const comp = component as any;
comp.install = (app: App) => {

View File

@ -0,0 +1,24 @@
<template>
<div>
<div>
<h1>二期开发</h1>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
h1{
color: red;
font-size: 50px;
text-align: center;
margin-top: 100px;
}
</style>

511
src/views/cet/analyze.vue Normal file
View File

@ -0,0 +1,511 @@
<template>
<div style="background: #ececec; padding: 15px">
<a-card title="四级成绩分析" :loading="loading" :bordered="false">
<template #extra>
<a-cascader v-model:value="collegeMajor" :options="collegeMajorOptions" change-on-select />
<a-button style="margin-left: 10px" type="primary" @click="query">查询</a-button>
<a-button style="margin-left: 10px" :loading="aiLoading" v-show="resultData != null" type="primary"
@click="getAI">分析</a-button>
</template>
<a-row :gutter="12">
<a-col :xl="12">
<div style="width: 100%; height: 215px" class="resChart" id="resChart"> </div>
</a-col>
<a-col :xl="12">
<div style="width: 100%; height: 215px" class="piechart" id="pieChart"> </div>
</a-col>
</a-row>
<a-card style="margin-bottom: 10px" class="tip">
<div style="display: flex">
<div v-html="aiMessage"></div>
</div>
</a-card>
</a-card>
</div>
</template>
<script>
import { defHttp } from '/@/utils/http/axios';
import * as echarts from 'echarts';
import axios from 'axios';
import { marked } from 'marked';
export default {
data() {
return {
loading: false,
dataSourceCet4: [],
columns: [
{
title: '学年',
dataIndex: 'grade',
key: 'grade',
align: 'center',
customCell: (_, index) => ({
rowSpan: index % 2 === 0 ? 2 : 0, //graderowSpan
}),
},
{
title: '考试批次',
dataIndex: 'batch',
key: 'batch',
align: 'center',
},
{
title: '参加人数',
dataIndex: 'attendNumber',
key: 'attendNumber',
align: 'center',
},
{
title: '通过人数',
dataIndex: 'passNumber',
key: 'passNumber',
align: 'center',
},
{
title: '本批次通过率',
dataIndex: 'batchpassrate',
key: 'batchpassrate',
align: 'center',
},
{
slots: { title: 'passRateSlot' },
dataIndex: 'gradepassrate',
key: 'gradepassrate',
align: 'center',
customCell: (_, index) => ({
rowSpan: index % 2 === 0 ? 2 : 0,
}),
},
],
Url: {
getEntrydate: '/cet/getEntrydate',
getCollege: '/cet/getCollege',
getData: '/cet/getData',
getCollegeMajor: '/cet/getCollegeMajor',
getAnalyze: '/cet_4/analyze',
},
total: 0,
totalName: '',
passNumberBottom: 0,
passRateBottom: 0,
passRatePie: [],
lineXData: [],
resultData: [],
aiMessage: '',
lineYData: [],
collegeOptions: [],
collegeMajorOptions: [],
entrydateOptions: [],
college: null,
aiLoading: false,
entrydate: 2017, //
//
collegeMajor: ['全校'], //
topCollege: null, //
topMajor: null, //
topEntrydate: null, //
};
},
mounted() {
this.getEntrydateAndCollegeData();
this.getCollegeMajorData();
this.query();
//this.map2Chart();
},
methods: {
//
async getEntrydateAndCollegeData() {
const getEntrydate = await defHttp.get({ url: this.Url.getEntrydate });
const getCollege = await defHttp.get({ url: this.Url.getCollege });
this.collegeOptions = getCollege.colleges;
//
this.collegeOptions.unshift({ value: '全校', label: '全校' });
this.college = this.collegeOptions[0].value;
this.entrydateOptions = getEntrydate.entrydates;
// this.entrydate = this.entrydateOptions[0].value;
},
//
async getCollegeMajorData() {
const res = await defHttp.get({ url: this.Url.getCollegeMajor });
//map
this.collegeMajorOptions = res.collegeMajor.map((item) => {
return {
value: item.college,
label: item.college,
children: item.major.map((major) => {
return {
value: major,
label: major,
};
}),
};
});
//
this.collegeMajorOptions.unshift({ value: '专升本', label: '专升本' });
//
this.collegeMajorOptions.unshift({ value: '全校', label: '全校' });
// this.collegeMajor = [''];
console.log(this.collegeMajorOptions);
},
//
async query() {
try {
//this.collegeMajor[1]null
let major = this.collegeMajor.length > 1 ? this.collegeMajor[1] : '';
this.topCollege = this.collegeMajor[0];
this.topMajor = major;
this.topEntrydate = this.entrydate;
if (this.college) this.loading = true;
let params = {
college: this.collegeMajor[0],
major: major,
};
console.log('params', params);
const result = await defHttp.post({ url: this.Url.getAnalyze, params });
this.resultData = result.data;
//
console.log(result, 'res');
const resdata = [];
//
for (const [range, rate] of Object.entries(result.data[0].rateByBatch)) {
//
resdata.push({
value: parseFloat(rate), //
name: `${range}分人数占比`,
});
}
//
resdata.sort((a, b) => {
const aMin = parseInt(a.name.split('-')[0]); //
const bMin = parseInt(b.name.split('-')[0]); //
return aMin - bMin; //
});
console.log(resdata, 'resdata');
//
resdata[0].name = '400分以下人数占比';
//
resdata[4].name = '475分以上人数占比';
const piedata = [];
piedata.push({ value: result.data[0].scoreByBatch.read, name: '阅读' });
piedata.push({ value: result.data[0].scoreByBatch.listen, name: '听力' });
piedata.push({ value: result.data[0].scoreByBatch.write, name: '写作' });
setTimeout(() => {
this.drawResChart(resdata);
this.drawPieChart(piedata);
this.map2Chart();
}, 100);
// this.drawPieChart();
} finally {
this.loading = false;
}
},
test() {
defHttp.get({ url: '/cet/getTest' });
},
map2Chart() {
var myChart = echarts.init(document.getElementById('map2'));
var option = {
title: {
text: '各批次通过率分析',
left: 'left',
top: '0%',
textStyle: {
fontSize: 16,
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
return (
params[0].name +
'<br/>' +
'<table>' +
'<tr><td>' +
params[0].marker +
'</td><td style="font-weight: bold;">' +
'&nbsp;&nbsp;&nbsp;&nbsp;' +
params[0].value +
'%' +
'</td></tr>' +
'<tr>' +
'</table>'
);
},
},
grid: {
top: '50px',
left: '50px',
right: '50px',
bottom: '30px',
},
xAxis: {
type: 'category',
data: this.lineXData,
},
yAxis: {
type: 'value',
},
series: [
{
data: this.lineYData,
type: 'line',
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
textStyle: {
//
color: 'black',
fontSize: 12,
},
formatter: '{c}%',
},
},
},
},
],
};
option && myChart.setOption(option);
},
async getAI() {
this.aiLoading = true;
this.aiMessage = ''; //
// AI
const SYSTEM_PROMPT = `
#Role:
我是某大学英语教研组的成员致力于通过科学分析和有效建议帮助学生全面提升四级成绩
#Background:
作为英语教学与考试研究的专业团队我们关注学生英语能力的各项提升尤其重视数据驱动的教学改进我们分析学生的四级考试成绩提供针对性的建议和指导
数据来源本校最近三个批次的四级考试数据batch1-3,三个批次之间不存在直接关系
分析维度各分数段人数占比rateByBatch模块平均分scoreByBatch阅读/写作/听力
#Goal:
通过对四级成绩数据的深入分析识别学生在各个模块阅读写作听力中的优势和不足提出切实可行的提升建议帮助学生在未来的考试中取得更好的成绩
#Tone:
以专业客观和建设性的态度进行分析确保建议具有实用性和可操作性
#Format:
总体分析
模块诊断阅读/写作/听力分别说明
教学建议
#Constraints:
1. 分析应基于提供的四级成绩数据确保准确性和相关性
2. 不需要重复说明数据内容直接进行分析
下面是我们学校的四级成绩数据batch为批次rateByBatch为每个分数段的人数占比scoreByBatch为四级每个模块的平均分分别为阅读写作听力
` + JSON.stringify(this.resultData) + `
`;
let validMessages = [
{
role: 'user',
content: SYSTEM_PROMPT
}
];
try {
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer sk-1d3dd761c40045228547efb38ce59754',
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: validMessages,
stream: true, //
}),
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullMessage = '(数据分析由deepseek生成仅供参考数据来源为学校近三个批次的四级考试数据)\n\n';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.choices[0].delta?.content) {
fullMessage += parsed.choices[0].delta.content;
//
this.aiMessage = marked(fullMessage);
}
} catch (e) {
console.error('解析响应数据时出错:', e);
}
}
}
}
} catch (error) {
console.error('获取AI分析失败:', error);
this.aiMessage = marked('抱歉,分析过程中发生了错误,请稍后重试。');
} finally {
this.aiLoading = false;
}
},
drawResChart(piedata) {
console.log(this.passRatePie);
console.log('piedata', piedata);
let xData = piedata.map((item) => item.name);
let Data = piedata.map((item) => item.value);
console.log('xData', xData);
console.log('Data', Data);
let myChart = echarts.init(document.getElementById('resChart'));
let option = {
grid: {
left: '10%', //
right: '10%', //
bottom: '30%', // X
},
tooltip: {
trigger: 'axis',
},
title: {
text: '四级分数占比',
left: 'left',
top: '0%',
textStyle: {
fontSize: 16,
},
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0, //
//rotate: 10, // 45
margin: 15, // 线
formatter: function (value) {
// ''
return value.replace(/分/g, '分\n');
},
},
axisLine: {
show: true,
},
boundaryGap: true, //
},
yAxis: {
type: 'value',
},
series: [
{
data: Data,
type: 'bar',
label: {
show: true, //
position: 'top', //
formatter: '{c}%', // 使 {c}
fontSize: 12, //
color: '#000', //
},
},
],
};
myChart.setOption(option);
},
drawPieChart(piedata) {
console.log(this.passRatePie);
console.log('piedata', piedata);
let xData = piedata.map((item) => item.name);
let data = piedata.map((item) => item.value);
let myChart = echarts.init(document.getElementById('pieChart'));
let option = {
grid: {
left: '10%', //
right: '10%', //
bottom: '30%', // X
},
tooltip: {
trigger: 'axis',
},
title: {
text: '四级分数分析',
left: 'left',
top: '0%',
textStyle: {
fontSize: 16,
},
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0, //
//rotate: 10, // 45
margin: 15, // 线
},
axisLine: {
show: true,
},
boundaryGap: true, //
},
yAxis: {
type: 'value',
},
series: [
{
data: data,
type: 'bar',
label: {
show: true, //
position: 'top', //
fontSize: 12, //
color: '#000', //
},
barWidth: '25%',
},
],
};
myChart.setOption(option);
},
},
};
</script>
<style lang="less" scoped>
.container {
display: flex;
background-color: #fff;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.title {
font-size: 34px;
color: rgb(8, 8, 8);
font-weight: bold;
}
</style>

866
src/views/cet/cet-ana-1.vue Normal file
View File

@ -0,0 +1,866 @@
<template>
<div style="background: #ececec; padding: 15px">
<a-card :bordered="false">
<div>
<div class="query">
<span style="font-size: 15px; margin-right: 10px; display: flex; justify-content: center; align-items: center; font-weight: bold"
>年级:
</span>
<a-select v-model:value="oneentrydate" style="width: 200px" :options="entrydateOptions" />
<a-button style="margin-left: 10px" type="primary" @click="allQuery">查询</a-button>
</div>
<a-card :bordered="true">
<a-row :gutter="12">
<a-col :xl="16">
<a-card :loading="map1loading">
<div id="span">
<div style="display: flex; justify-content: center; align-items: center">
<span style="font-size: 16px">点击柱状图可查看该学院&nbsp; </span>
<span style="font-size: 16px; color: red">各年级四级通过率</span>
</div>
<!-- <span style=" font-size: 18px;">&nbsp 变化</span> -->
</div>
<div id="map1" style="width: 100%; height: 500px"></div>
</a-card>
</a-col>
<a-col :xl="8">
<a-card :loading="map2loading" :style="{ marginBottom: '12px' }">
<div id="map2" style="width: 100%; height: 230px"></div>
</a-card>
<a-card :loading="map3loading">
<div id="map3" style="width: 100%; height: 230px"></div>
</a-card>
</a-col>
</a-row>
</a-card>
</div>
</a-card>
</div>
</template>
<script>
import { defHttp } from '/@/utils/http/axios';
import * as echarts from 'echarts';
import { message } from 'ant-design-vue';
export default {
data() {
return {
activeKey: '1',
allCollege: [],
showBox: false,
showGroup: false,
oneentrydate: null,
collegeentrydate: [],
majorentrydate: [],
lastMajorEntrydate: [],
checkedOptions: [],
collegetab2: [],
collegeMajorOptions: [],
collegeMajor: [],
Url: {
getBatch: '/cet/getBatch',
getEntrydate: '/cet/getEntrydate',
getCollege: '/cet/getCollege',
getCollegeRate: '/cet/getRateByCollege',
getAllRate: '/cet/getAllRate',
getCollegeMajor: '/cet/getCollegeMajor',
getMajor: '/cet/getMajorByCollege',
getMajorRate: '/cet/getRateByMajor',
getRateByMajor: '/cet/getRateByMajor',
},
map1loading: false,
map2loading: false,
map3loading: false,
collegeOptions: [],
batchOptions: [],
levelOptions: [
{ value: 'cet4', label: '英语四级' },
{ value: 'cet6', label: '英语六级' },
],
visible: false,
level: null,
college: [],
batch: null,
entrydateOptions: [],
entrydate: [],
majorCheckOn: [],
//
lastCollegeMajor: [],
majorLength: 0,
majorOptions: [],
//
lastCollege: [],
//
lastCollegeEntrydate: [],
};
},
mounted() {
this.getCollegeOptions();
// this.getBatch();
this.getEntrydate();
this.allQuery();
this.getCollegeMajorData();
},
methods: {
allQuery() {
this.map1loading = true;
this.map2loading = true; //
this.map3loading = true; //
let college = ['全校'];
let query = 'tab1';
if (this.oneentrydate == null) {
this.oneentrydate = '2017';
}
this.query(query, college, [this.oneentrydate]);
this.majorPassRate();
this.gradePassRate();
},
async majorQuery() {
if (this.majorentrydate.length == 0) {
message.error('请选择年级');
return;
}
if (this.collegeMajor.length == 0 || this.collegeMajor == null) {
message.error('请选择学院');
return;
}
if (this.lastCollegeMajor.length == 0 || this.lastCollegeMajor == null) {
message.error('请选择专业');
return;
}
this.tab3loading = true;
let res = null;
try {
let params = {
college: this.lastCollegeMajor,
entrydate: this.majorentrydate,
};
res = await defHttp.post({ url: this.Url.getRateByMajor, params });
} finally {
console.log(res, 'res');
this.tab3loading = false;
this.$nextTick(() => {
this.drawChart(res.data, 'tab3');
});
}
},
//
async getCollegeMajorData() {
const res = await defHttp.get({ url: this.Url.getCollegeMajor });
//map
this.collegeMajorOptions = res.collegeMajor.map((item) => {
return {
value: item.college,
label: item.college,
children: item.major.map((major) => {
return {
value: major,
label: major,
};
}),
};
});
// this.collegeMajor = [''];
console.log(this.collegeMajor, 'collegeMajor');
console.log(this.collegeMajorOptions, 'collegeMajorOptions1');
},
getCollegeOptions() {
defHttp.get({ url: this.Url.getCollege }).then((res) => {
this.collegeOptions = res.colleges;
//
// this.collegeOptions.unshift({ value: '', label: '' });
// this.collegeOptions.forEach(option => {
// if (option.value !== '') {
// option.disabled = true;
//}
// });
// this.college = [''];
// this.collegetab2 = ['', ''];
console.log(this.collegeOptions, 'collegeOptions');
});
},
//
async gradePassRate() {
this.map2loading = true;
const getEntrydate = await defHttp.get({ url: this.Url.getEntrydate });
this.entrydateOptions = getEntrydate.entrydates;
let entrydate = this.entrydateOptions.map((item) => item.value);
const name = ['西语学院'];
let queryParams = {
college: name,
entrydate: entrydate,
level: 'cet4',
};
let url = this.Url.getCollegeRate;
let result = await defHttp.post({ url: url, data: queryParams });
if (!result) {
return;
}
result = result.data[name];
let collegeOption = {
title: {
text: name + '四级通过率',
textStyle: {
fontSize: 14,
},
},
xAxis: {
data: result.map((item) => item.entrydate),
},
//
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
//tooltip,
let result = params[0].name + '<br>';
for (let i = 0; i < params.length; i++) {
result += params[i].marker + name + ' : ' + params[i].value + '%' + '<br>';
}
return result;
},
},
series: {
type: 'line',
data: result.map((item) => item.passRate),
label: {
show: true, //
position: 'top', //
formatter: function (params) {
//
return params.value + '%';
},
//
},
universalTransition: {
// enabled: true,
// divideShape: 'clone',
},
},
yAxis: {},
animationDurationUpdate: 500,
};
this.map2loading = false;
this.$nextTick(() => {
var myChart = echarts.init(document.getElementById('map2'));
myChart.setOption(collegeOption);
});
},
//
async majorPassRate() {
this.map3loading = true;
const name = '西语学院';
let queryParams = {
college: [[name]],
entrydate: [this.oneentrydate],
level: 'cet4',
};
let url = this.Url.getMajorRate;
console.log(queryParams, 'queryParams');
let result = await defHttp.post({ url: url, data: queryParams });
if (!result) {
return;
}
//result = result.data[name];
// result.data
const majors = Object.keys(result.data); //
const passRates = majors.map((major) => {
const entries = result.data[major];
return entries.length > 0 ? parseFloat(entries[0].passRate) : 0; //
});
let xData = majors.map((label) => label.split('').join('\n')); //x
var majorOption = {
tooltip: {
trigger: 'item',
conginee: false,
//
itemStytle: {
fontSize: 100,
},
formatter: function (params) {
// %
return `${params.marker}${params.name} ${params.value}%`;
},
},
title: {
text: name + '各专业四级通过率',
textStyle: {
fontSize: 14,
},
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
//rotate: -10, //30
},
//data: result.map((item) => item.major),
//data: ['', '', '', '', '西'],
},
yAxis: {
type: 'value',
},
series: [
{
label: {
show: true,
fontSize: 14,
overflow: 'truncate',
position: 'top',
formatter: function (params) {
//
return params.value + '%';
},
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
//
label: {
show: false,
fontSize: 14,
fontWeight: 'bold',
},
},
data: passRates,
//data: result.map((item) => item.passRate),
//data: [200, 150, 80, 70, 110, 130],
type: 'bar',
},
],
animationDurationUpdate: 500,
};
this.map3loading = false;
this.$nextTick(() => {
var myChart = echarts.init(document.getElementById('map3'));
myChart.setOption(majorOption);
});
},
dataChart(data, tab) {
let seriesData = [];
let xData = [];
let colors = [
'#5370c5',
'#ffffff',
'#fac858',
'#ee6666',
'#73c0de',
'#FF6A6A',
'#FFA500',
'#EE2C2C',
'#90EE90',
'#008B8B',
'#FFC0CB',
'#FFDAB9',
'#FFDEAD',
'#FFE4B5',
'#FFE4C4',
'#FFE4E1',
'#FFEBCD',
'#FFEFD5',
'#FFFAF0',
'#FFFAFA',
'#FFFFE0',
'#FFFFF0',
'#FFFFFF',
'#F0F8FF',
'#FAEBD7',
'#FAF0E6',
'#FAFAD2',
'#F5FFFA',
'#F8F8FF',
'#F0FFF0',
'#F0FFFF',
'#F0E68C',
'#F0F8FF',
'#F0FFF0',
'#F0FFFF',
'#F4A460',
'#F5DEB3',
'#F5F5DC',
'#F5F5F5',
'#F5FFFA',
'#F8F8FF',
'#F9EBEA',
'#FAD7A0',
'#FAF0E6',
'#FAFAD2',
'#FAF0E6 ',
];
let j = 0;
for (let i in data) {
xData = [];
let yData = [];
for (let key in data[i]) {
xData.push(data[i][key].college);
// %
yData.push(data[i][key].passRate);
}
xData = xData.map((label) => label.split('').join('\n')); //x
seriesData.push({
name: i + '级累计总通过率',
type: 'bar',
//
barWidth: 1,
data: yData,
//
barGap: '30%',
//
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
formatter: '{c}%',
textStyle: {
//
color: 'black',
fontSize: 13,
},
},
color: colors[j++],
},
},
});
if (j == colors.length) {
j = 0;
}
let rankData = yData
.slice()
.sort((a, b) => b - a)
.map((value) => yData.indexOf(value) + 1);
//
seriesData.push({
name: i + '级累计总通过率排名',
type: 'line',
yAxisIndex: 1,
data: rankData,
bar: {},
show: false,
itemStyle: {
normal: {
label: {
show: false, //
position: 'top', //
formatter: '{c}',
textStyle: {
//
color: 'black',
fontSize: 8,
},
},
color: colors[j++],
},
},
});
if (j == colors.length) {
j = 0;
}
}
let myChart = null;
myChart = echarts.init(document.getElementById('map1'));
//
// const college1 = this.college;
let option = {
title: {
text: '本年级学院四级通过率排名',
textStyle: {
fontSize: 16,
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
//tooltip,
let result = params[0].name + '<br>';
for (let i = 0; i < params.length; i++) {
result += params[i].marker + params[i].seriesName + ' : ' + params[i].value + '%' + '<br>';
}
if (tab == 'tab1') {
result =
params[0].name +
'<br/>' +
'<table>' +
'<tr><td>' +
params[0].marker +
params[0].seriesName +
'</td><td style="font-weight: bold;">' +
'&nbsp;&nbsp;&nbsp;&nbsp;' +
params[0].value +
'%' +
'</td></tr>' +
'<tr><td>' +
params[1].marker +
params[1].seriesName +
'</td><td style="font-weight: bold;">' +
'&nbsp;&nbsp;&nbsp;&nbsp;' +
params[1].value +
'</td></tr>' +
'</table>';
}
return result;
},
},
toolbox: {
// show: true,
// feature: {
// magicType: { show: true, type: ['line', 'bar'] },
// restore: { show: true },
// saveAsImage: { show: true },
// },
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
rotate: 0, //0
},
},
],
yAxis: [
{
type: 'value',
name: '通过率',
axisLabel: {
formatter: '{value} %',
},
},
{
type: 'value',
name: '排名',
show: false,
},
],
series: seriesData,
};
option.series = [
{
name: '累计总通过率',
type: 'bar',
data: seriesData[0].data,
//
barWidth: 16,
// barWidth: '60%',
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
formatter: '{c}%',
textStyle: {
//
color: 'black',
fontSize: 10,
},
},
color: colors[0],
},
},
},
{
name: '排名',
type: 'bar',
data: seriesData[1].data,
label: {
show: false,
position: 'inside',
formatter: '{c}',
},
yAxisIndex: 1,
color: colors[1],
},
];
myChart.setOption(option, true);
// 使
myChart.on('click', async (params) => {
let name = params.name;
//name
name = name.replace(/\n/g, '');
console.log(
this.collegeOptions.map((item) => item.value),
'collegeOptions'
);
console.log(this.collegeOptions.map((item) => item.value).includes(name), 'bool');
if (params != null && params != undefined && params.name != '' && this.collegeOptions.map((item) => item.value).includes(name)) {
console.log(params, 'params');
let entrydate = this.entrydateOptions.map((item) => item.value);
let queryParams = {
college: [name],
entrydate: entrydate,
level: 'cet4',
};
let url = this.Url.getCollegeRate;
this.map2loading = true;
this.map3loading = true;
let result = await defHttp.post({ url: url, data: queryParams });
if (!result) {
return;
}
result = result.data[name];
let collegeOption = {
title: {
text: name + '四级通过率',
textStyle: {
fontSize: 14,
},
},
xAxis: {
data: result.map((item) => item.entrydate),
},
//
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
//tooltip,
let result = params[0].name + '<br>';
for (let i = 0; i < params.length; i++) {
result += params[i].marker + name + ' : ' + params[i].value + '%' + '<br>';
}
return result;
},
},
series: {
type: 'line',
data: result.map((item) => item.passRate),
label: {
show: true, //
position: 'top', //
formatter: function (params) {
//
return params.value + '%';
},
//
},
universalTransition: {
// enabled: true,
// divideShape: 'clone',
},
},
yAxis: {},
animationDurationUpdate: 500,
};
//
let querMajorParam = {
college: [[name]],
entrydate: [this.oneentrydate],
level: 'cet4',
};
let urlMajor = this.Url.getMajorRate;
let resultMajor = await defHttp.post({ url: urlMajor, data: querMajorParam });
if (!resultMajor) {
return;
}
//result = result.data[name];
// result.data
const majors = Object.keys(resultMajor.data); //
const passRates = majors.map((major) => {
const entries = resultMajor.data[major];
return entries.length > 0 ? parseFloat(entries[0].passRate) : 0; //
});
xData = majors.map((label) => label.split('').join('\n')); //x
var majorOption = {
tooltip: {
trigger: 'item',
conginee: false,
//
itemStytle: {
fontSize: 100,
},
},
title: {
text: name + '各专业四级通过率',
textStyle: {
fontSize: 14,
},
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
//rotate: -10, //30
},
//data: result.map((item) => item.major),
//data: ['', '', '', '', '西'],
},
yAxis: {
type: 'value',
},
series: [
{
//
label: {
show: true,
fontSize: 14,
overflow: 'truncate',
position: 'top',
formatter: function (params) {
//
return params.value + '%';
},
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
//
label: {
show: false,
fontSize: 14,
fontWeight: 'bold',
},
},
data: passRates,
//data: result.map((item) => item.passRate),
//data: [200, 150, 80, 70, 110, 130],
type: 'bar',
},
],
animationDurationUpdate: 500,
};
this.map3loading = false;
this.map2loading = false;
this.$nextTick(() => {
var map2Chart = echarts.init(document.getElementById('map2'));
map2Chart.setOption(collegeOption, true);
var map3Chart = echarts.init(document.getElementById('map3'));
map3Chart.setOption(majorOption, true);
});
}
});
},
//
async getBatch() {
const getBatch = await defHttp.get({ url: this.Url.getBatch });
this.batchOptions = getBatch.batches;
this.batch = this.batchOptions[0].value;
this.level = this.levelOptions[0].value;
},
//
async getEntrydate() {
const getEntrydate = await defHttp.get({ url: this.Url.getEntrydate });
this.entrydateOptions = getEntrydate.entrydates;
this.oneentrydate = this.entrydateOptions[0].value;
// this.collegeentrydate = [this.entrydateOptions[0].value];
// this.majorentrydate = [this.entrydateOptions[0].value];
// this.entrydate.push (this.entrydateOptions[0].value);
},
//
async query(query, college, entrydate) {
this.visible = false;
let result = null;
// console.log(this.college, this.entrydate);
// //this.collegethis.batchnull
// if (this.college === null || this.college.length === 0) {
// this.college = [''];
// }
// if (!this.entrydate || this.entrydate.length === 0) {
// this.entrydate = ['2017'];
// }
try {
// console.log(this.college, this.entrydate)
let params = {
college: college,
entrydate: entrydate,
level: 'cet4',
};
let url = query == 'tab1' ? this.Url.getAllRate : this.Url.getCollegeRate;
console.log(params.college, 'college');
result = await defHttp.post({ url: url, params });
//使passRate
// result.data.sort((a, b) => {
// return b.passRate - a.passRate;
// });
console.log('data', result.data);
} finally {
this.map1loading = false;
this.$nextTick(() => {
this.dataChart(result.data, query);
});
}
},
},
};
</script>
<style lang="less" scoped>
.container {
// display: flex;
background-color: #fff;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.query {
//
display: flex;
justify-content: flex-end;
align-items: center;
margin-left: 24px;
padding-bottom: 10px;
}
.tab3 {
justify-content: flex-start;
align-items: center;
margin-left: 24px;
}
.title {
font-size: 34px;
color: rgb(8, 8, 8);
font-weight: bold;
}
</style>

425
src/views/cet/cet-ana-2.vue Normal file
View File

@ -0,0 +1,425 @@
<template>
<div style="background: #ececec; padding: 15px">
<a-card title="按批次对比分析" :loading="loading" :bordered="false">
<template #extra>
<!-- <a-select v-model:value="college" style="width: 300px" :options="collegeOptions"></a-select> -->
<a-cascader v-model:value="collegeMajor" :options="collegeMajorOptions" change-on-select />
<a-button style="margin-left: 10px" type="primary" @click="query">查询</a-button>
</template>
<a-row :gutter="12">
<a-col :xl="16" :style="{ marginBottom: '24px' }">
<!-- 根据需求 删除 更改-->
<a-card class="tip">
<div style="display: flex">
<div v-if="topCollege != null">
<span style="font-size: 15px; padding-left: 100px">该数据为 </span>
<span style="color: red; font-size: 18px">{{ topCollege }}</span>
<span v-if="topMajor != null && topMajor != ''" style="color: red; font-size: 18px"> / {{ topMajor }}</span>
<span style="font-size: 15px"> 的四级通过率变化趋势 </span>
</div>
</div>
</a-card>
<a-card>
<div>
<div id="map1" style="width: 100%; height: 400px"></div>
</div>
</a-card>
</a-col>
<a-col :xl="8">
<a-card :loading="participate_Piechartloading" style="margin-bottom: 10px">
<div style="width: 100%; height: 210px; padding: 10px" class="participate_Piechart" id="participate_Piechart"></div>
</a-card>
<a-card :loading="pass_Piechartloading">
<div style="width: 100%; height: 210px; padding: 10px" class="pass_Piechart" id="pass_Piechart"></div>
</a-card>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script>
import { defHttp } from '/@/utils/http/axios';
import * as echarts from 'echarts';
export default {
data() {
return {
Url: {
getBatch: '/cet/getBatch',
getCollege: '/cet/getCollege',
getRate: '/cet/getRateByAllBatch',
getCollegeMajor: '/cet/getCollegeMajor',
getRateByMajor: '/cet/getgetRateByMajor',
getRateByMajorAndLastestBatch: '/cet/getRateByMajorAndLastestBatch',
getRateByEntryDate: '/cet/getRateByEntryDate',
},
loading: false,
participate_Piechartloading: false,
pass_Piechartloading: false,
collegeOptions: [],
collegeMajorOptions: [],
batchOptions: [],
levelOptions: [
{ value: 'cet4', label: '英语四级' },
{ value: 'cet6', label: '英语六级' },
],
level: null,
college: null,
batch: null,
collegeMajor: null,
topCollege: null, //
topMajor: null, //
};
},
mounted() {
this.getCollegeMajorData();
this.getBatch();
this.query();
},
methods: {
dataChart(data) {
let xData = [];
let yData = [];
for (let key in data) {
xData.push(key);
// %
yData.push(data[key].toFixed(1));
}
let myChart = echarts.init(document.getElementById('map1'));
//
let option = {
title: {
// text: ' / ',
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 1, //x
rotate: -10, //30
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
return (
params[0].name +
'<br/>' +
'<table>' +
'<tr><td>' +
params[0].marker +
'</td><td style="font-weight: bold;">' +
'&nbsp;&nbsp;&nbsp;&nbsp;' +
params[0].value +
'%' +
'</td></tr>' +
'<tr>' +
'</table>'
);
},
},
yAxis: {
type: 'value',
name: '通过率',
axisLabel: {
formatter: '{value} %',
},
},
series: [
{
data: yData,
type: 'line',
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
textStyle: {
//
color: 'black',
fontSize: 12,
},
formatter: '{c}%',
},
},
},
},
],
};
// 使
myChart.setOption(option);
},
//--
async draPieChart_Participate() {
console.log(this.collegeMajor, 'collegeMajor');
let college = this.collegeMajor[0];
let major = this.collegeMajor[1];
console.log(college, major, 'college major');
let queryParams = {
college: college,
major: major,
level: 'cet4',
};
let url = this.Url.getRateByMajorAndLastestBatch;
this.participate_Piechartloading = true;
console.log(queryParams, 'queryParams');
let result = await defHttp.post({ url: url, data: queryParams });
if (!result) {
return;
}
console.log(result.data, 'result');
let titleText = major ? `${college} / ${major}专业 最新批次通过人数` : `${college} 最新批次通过人数`;
// 使 API
let chartData = [];
for (let key in result.data) {
chartData.push({
name: key + '级',
value: result.data[key].passRate,
});
}
console.log(chartData, '123');
let xData = chartData.map((item) => item.name); //
let yData = chartData.map((item) => item.value); //
let option = {
grid: {
left: '20%', //
right: '5%', //
top: '20%', //
bottom: '10%', //
},
title: {
text: '最新批次每一年级的通过率',
left: 'top',
top: '0%',
textStyle: {
fontSize: 14,
},
},
tooltip: {
trigger: 'item',
},
xAxis: {
type: 'category',
data: xData,
//data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}%',
},
},
series: [
{
name: titleText,
data: yData,
//data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
textStyle: {
//
color: 'black',
fontSize: 12,
},
formatter: '{c}%',
},
},
},
},
],
animationDurationUpdate: 500,
};
this.participate_Piechartloading = false;
this.$nextTick(() => {
var myChart = echarts.init(document.getElementById('participate_Piechart'));
myChart.setOption(option);
});
},
//--/
async draPieChart_Pass() {
//console.log(this.passRatePie);
//console.log('piedata', piedata);
let college = this.collegeMajor[0];
let major = this.collegeMajor[1];
let queryParams = {
college: college,
major: major,
level: 'cet4',
};
let url = this.Url.getRateByEntryDate;
this.pass_Piechartloading = true;
console.log(queryParams, 'queryParams');
let result = await defHttp.post({ url: url, data: queryParams });
if (!result) {
return;
}
console.log(result, 'result');
// result
let xData = result.data.map((item) => item.entryDate); // X
let values = result.data.map((item) => item.passRate); //
console.log(xData, values, 'xData values');
let titleText = major ? `${college} / ${major}专业 每个年级的通过率柱状图` : `${college} 每个年级的通过率柱状图`;
let option = {
grid: {
left: '15%', //
top: '20%', //
right: '10%', //
bottom: '10%', //
},
tooltip: {
trigger: 'item',
formatter: function (params) {
// %
return `${params.marker}${params.name} ${params.value}%`;
},
},
title: {
text: titleText,
left: 'left',
top: '0%',
textStyle: {
fontSize: 14,
},
},
xAxis: {
type: 'category',
data: xData,
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}%', // Y
},
},
series: [
{
data: values,
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)',
},
label: {
show: true, //
position: 'top', //
formatter: '{c}%', //
fontSize: '10px',
},
},
],
animationDurationUpdate: 500,
};
this.pass_Piechartloading = false;
this.$nextTick(() => {
var myChart = echarts.init(document.getElementById('pass_Piechart'));
myChart.setOption(option);
});
},
//
async getBatch() {
const getBatch = await defHttp.get({ url: this.Url.getBatch });
// const getCollege = await defHttp.get({ url: this.Url.getCollege });
// this.collegeOptions = getCollege.colleges;
// this.college = this.collegeOptions[0].value;
this.level = this.levelOptions[0].value;
// this.query();
},
//
async getCollegeMajorData() {
const res = await defHttp.get({ url: this.Url.getCollegeMajor });
//map
this.collegeMajorOptions = res.collegeMajor.map((item) => {
return {
value: item.college,
label: item.college,
children: item.major.map((major) => {
return {
value: major,
label: major,
};
}),
};
});
this.collegeMajor = ['东语学院', '日语'];
},
//
async query() {
let data = null;
console.log(this.collegeMajor, 'collegeMajor');
try {
//this.collegeMajornull
if (!this.collegeMajor) {
this.collegeMajor = ['东语学院', '日语'];
}
let major = null;
let college = null;
//this.collegeMajor[1]null
college = this.collegeMajor[0];
major = this.collegeMajor.length > 1 ? this.collegeMajor[1] : '';
this.topCollege = college;
this.topMajor = major;
this.loading = true;
console.log(college, major, 'college,major');
let params = {
college: college,
major: major,
level: 'cet4',
};
data = await defHttp.get({ url: this.Url.getRate, params });
console.log(data, 'data');
} finally {
this.loading = false;
this.$nextTick(() => {
this.dataChart(data);
this.draPieChart_Pass();
this.draPieChart_Participate();
});
}
},
},
};
</script>
<style lang="less" scoped>
.container {
// display: flex;
background-color: #fff;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.title {
font-size: 20px;
color: rgb(8, 8, 8);
font-weight: bold;
}
.tip {
margin-bottom: 10px;
background-color: #d0e5fe;
opacity: 0.9;
}
</style>

477
src/views/cet/cet-ana-3.vue Normal file
View File

@ -0,0 +1,477 @@
<template>
<div style="background: #ececec; padding: 15px">
<a-card title="按单批次对比分析" :loading="loading" :bordered="false">
<template #extra>
<a-select v-model:value="collegeMajor" style="width: 300px" :options="collegeMajorOptions" />
<a-select v-model:value="batch" :options="batchOptions" change-on-select style="margin-left: 10px; width: 120px" />
<a-button style="margin-left: 10px" type="primary" @click="query">查询</a-button>
</template>
<a-row :grutter="24">
<a-col :xl="16" :style="{ marginBottom: '24px' }">
<!-- 根据需求 删除 更改-->
<a-card style="margin-bottom: 10px" class="tip">
<div style="display: flex">
<div v-if="topCollege != null">
<span style="color: red; font-size: 18px; padding-left: 100px"> {{ topCollege }}</span>
<span style="font-size: 15px"> </span>
<span style="color: red; font-size: 18px"> {{ topBath }} </span>
<span style="font-size: 15px"> 批次的四级通过率为</span>
<span style="color: red; font-size: 18px"> {{ sumRate }}%</span>
</div>
</div>
</a-card>
<a-card>
<div>
<div id="map1" style="width: 100%; height: 400px"></div>
</div>
</a-card>
</a-col>
<a-col :xl="8" style="padding-left: 10px">
<a-card :loading="participate_Piechartloading" style="margin-bottom: 10px">
<div style="width: 100%; height: 210px; padding: 10px" class="participate_Piechart" id="participate_Piechart"></div>
</a-card>
<a-card :loading="tableloading" style="height: 268px">
<div>
<a-table
:dataSource="dataSourceCet4"
:columns="columns"
:pagination="false"
bordered
class="custom-table"
:style="{ fontSize: '12px', color: '#333', marginTop: '-11px' }"
/>
</div>
</a-card>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script>
//import { pagination } from 'mock/_util';
import { red } from '@ant-design/colors';
import { defHttp } from '/@/utils/http/axios';
import * as echarts from 'echarts';
export default {
data() {
return {
dataSourceCet4: [],
columns: [
{
title: '年级',
dataIndex: 'grade',
key: 'grade',
align: 'center',
},
{
title: '参加人数',
dataIndex: 'attendNumber',
key: 'attendNumber',
align: 'center',
},
{
title: '通过人数',
dataIndex: 'passNumber',
key: 'passNumber',
align: 'center',
},
{
title: '通过率',
dataIndex: 'passrate',
key: 'passrate',
align: 'center',
},
],
Url: {
getBatch: '/cet/getBatch',
getCollege: '/cet/getCollege',
getRate: '/cet/getRateByBatch',
getCollegeMajor: '/cet/getCollegeMajor',
},
loading: false,
participate_Piechartloading: false,
tableloading: false,
collegeOptions: [],
collegeMajorOptions: [],
batchOptions: [],
levelOptions: [
{ value: 'cet4', label: '英语四级' },
{ value: 'cet6', label: '英语六级' },
],
level: null,
college: null,
batch: null,
collegeMajor: null,
sumRate: null, //
topCollege: null, //
topBath: null, //
};
},
mounted() {
this.getCollegeMajorData();
this.getBatch();
this.query();
},
methods: {
dataChart(data) {
let xData = [];
let yData = [];
for (let key in data) {
xData.push(key);
// %
yData.push(data[key].toFixed(1));
}
xData = xData.map((label) => label.split('').join('\n')); //x
let myChart = echarts.init(document.getElementById('map1'));
//
let option = {
title: {
// text: " / ",
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
rotate: 0, //30
},
},
//
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
return (
params[0].name +
'<br/>' +
'<table>' +
'<tr><td>' +
params[0].marker +
'</td><td style="font-weight: bold;">' +
'&nbsp;&nbsp;&nbsp;&nbsp;' +
params[0].value +
'%' +
'</td></tr>' +
'<tr>' +
'</table>'
);
},
},
toolbox: {
// show: true,
// feature: {
// magicType: { show: true, type: ['line', 'bar'] },
// restore: { show: true },
// saveAsImage: { show: true },
// },
},
//
grid: {
left: '3%',
right: '4%',
bottom: '1%', //3%
containLabel: true,
},
yAxis: {
type: 'value',
name: '通过率',
axisLabel: {
formatter: '{value} %',
},
},
series: [
{
barMaxWidth: 50, //
data: yData,
type: 'bar',
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
textStyle: {
//
color: 'black',
fontSize: 12,
},
formatter: '{c}%',
},
},
},
},
],
};
// 使
myChart.setOption(option);
},
//
async getBatch() {
const getBatch = await defHttp.get({ url: this.Url.getBatch });
console.log('1111', getBatch);
this.batchOptions = getBatch.batches.map((item) => {
return {
value: item.value,
label: item.label,
};
});
console.log('2222', this.batchOptions);
this.batchOptions.forEach((item) => {
const year = item.label.substring(0, 4);
if (item.label.includes('12-01')) {
item.label = `${year}冬季`;
} else if (item.label.includes('06-01')) {
item.label = `${year}夏季`;
} else if (item.label.includes('09-01')) {
item.label = `${year}夏季`;
} else if (item.label.includes('03-01')) {
item.label = `${year}春季`;
}
});
// const getCollege = await defHttp.get({ url: this.Url.getCollege });
// this.collegeOptions = getCollege.colleges;
// this.college = this.collegeOptions[0].value;
this.level = this.levelOptions[0].value;
// this.query();
},
//
async getCollegeMajorData() {
const res = await defHttp.get({ url: this.Url.getCollegeMajor });
//map
this.collegeMajorOptions = res.collegeMajor.map((item) => {
return {
value: item.college,
label: item.college,
children: item.major.map((major) => {
return {
value: major,
label: major,
};
}),
};
});
this.collegeMajorOptions.unshift({ value: '全校', label: '全校' });
this.collegeMajor = this.collegeMajorOptions[0].value;
console.log(this.collegeMajor);
console.log(this.collegeMajorOptions);
},
//
async query() {
this.getBatch();
let resultData = null;
try {
//this.batchnull
if (!this.batch) {
this.batch = '2017-12-01';
}
if (!this.collegeMajor) {
this.collegeMajor = '全校';
}
//this.batch[1]null
this.topCollege = this.collegeMajor;
this.topBath = this.batch;
this.loading = true;
let params = {
batch: this.batch,
college: this.collegeMajor,
level: 'cet4',
};
resultData = await defHttp.get({ url: this.Url.getRate, params });
this.sumRate = resultData.sumRate;
console.log('result', resultData);
//
let tableData = [];
for (let grade in resultData.gradeData) {
tableData.push({
grade: grade + '级',
attendNumber: resultData.gradeData[grade].allStudent,
passNumber: resultData.gradeData[grade].passed,
passrate: resultData.gradeData[grade].passRate,
});
}
this.dataSourceCet4 = tableData; //
console.log(this.dataSourceCet4, 'dataSourceCet4');
} finally {
this.loading = false;
this.$nextTick(() => {
this.dataChart(resultData.data);
this.draPieChart_Participate();
});
}
},
//--
async draPieChart_Participate() {
//console.log(this.passRatePie);
//console.log('piedata', piedata);
let queryParams = {
batch: this.batch,
college: this.collegeMajor,
level: 'cet4',
};
console.log(queryParams, 'queryParams');
let url = this.Url.getRate;
this.participate_Piechartloading = true;
this.tableloading = true;
let result = await defHttp.get({ url: url, params: queryParams });
let totalRate = 0;
for (let key in result.gradeData) {
totalRate += parseFloat(result.gradeData[key].passRate);
}
console.log(totalRate, 'totalRate');
console.log(result, 'result');
let chartsData = [];
for (let key in result.gradeData) {
chartsData.push({
name: key + '级',
value: (parseFloat(result.gradeData[key].passRate) / totalRate) * 100,
});
}
let option = {
tooltip: {
trigger: 'item',
conginee: false,
formatter: function (params) {
return params.name + ': ' + params.value.toFixed(1) + '%';
},
textStyle: {
color: '#ec2e30', //
fontSize: 14, //
fontWeight: 'bold', //
},
//
itemStytle: {
//
shadowBlur: 10, //
shadowColor: 'rgba(0, 0, 0, 0.3)', //
shadowOffsetX: 5, //
shadowOffsetY: 5, //
fontSize: 100,
},
},
title: {
text: '该批次每一年级的通过率环形图',
left: 'top',
top: '0%',
textStyle: {
fontSize: 12,
},
},
legend: {
top: '10%',
left: 'center',
itemGap: 3,
selectedMode: true,
},
color: ['#ec2e30', '#c75af1', '#2b9eef', '#eee03f', '#2aed91', '#c5ed3c'],
series: [
{
name: '学院/专业该批次通过人数环形图',
type: 'pie',
radius: ['50%', '100%'],
center: ['50%', '50%'],
startAngle: 130,
top: 80,
//
itemStyle: {
// borderRadius: 5,
// borderColor: '#fff',
borderWidth: 2,
},
label: {
show: true,
// position: 'inside',
fontSize: 12,
overflow: 'truncate',
},
labelLine: {
show: true,
smooth: true,
length: 10,
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
label: {
show: true,
fontSize: 15,
fontWeight: 'bold',
},
},
data: chartsData,
},
{
type: 'pie',
startAngle: 130,
top: 80,
radius: ['40%', '51%'],
center: ['50%', '50%'],
//
itemStyle: {
// borderRadius: 3,
// borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
},
hoverAnimation: false,
legendHoverLink: false,
// animationnfalse,
tooltip: {
show: false,
},
data: chartsData,
},
],
animationDurationUpate: 500,
};
this.participate_Piechartloading = false;
this.tableloading = false;
this.$nextTick(() => {
var myChart = echarts.init(document.getElementById('participate_Piechart'));
myChart.setOption(option);
});
},
},
};
</script>
<style lang="less" scoped>
.container {
// display: flex;
background-color: #fff;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.title {
font-size: 20px;
color: rgb(8, 8, 8);
font-weight: bold;
}
.tip {
margin-bottom: 10px;
background-color: #d0e5fe;
opacity: 0.9;
}
</style>

View File

@ -0,0 +1,797 @@
<template>
<div style="background: #ececec; padding: 15px">
<a-card :bordered="false">
<div class="query">
<div>
<div style="display: flex">
<span style="font-size: 15px; margin-right: 10px; display: flex; justify-content: center; align-items: center; font-weight: bold"
>年级:
</span>
<a-checkbox-group :options="entrydateOptions" v-model:value="collegeentrydate" @change="onCollegeEntrydateChange" />
</div>
<div style="display: flex; margin-top: 10px">
<span style="font-size: 15px; margin-right: 10px; display: flex; justify-content: center; align-items: center; font-weight: bold"
>学院:
</span>
<a-checkbox-group :options="collegeOptions" v-model:value="collegetab2" @change="onCollegeChange" />
</div>
<div style="margin-top: 10px; margin-right: 20px; display: flex; justify-content: flex-end; align-items: center">
<a-button style="margin-left: 10px" type="primary" @click="clearCollege">清除</a-button>
<a-button style="margin-left: 10px" type="primary" @click="collegeQuery">查询</a-button>
</div>
</div>
</div>
</a-card>
<a-card title="学院四级通过率对比" :loading="tab2loading" :bordered="false">
<a-row :gutter="12">
<a-col :xl="14">
<div class="container">
<div id="map2" style="width: 100%; min-height: 400px"></div>
</div>
</a-col>
<a-col :xl="10">
<div class="container">
<div id="map3" style="width: 100%; min-height: 400px"></div>
</div>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script>
import { defHttp } from '/@/utils/http/axios';
import * as echarts from 'echarts';
import { message } from 'ant-design-vue';
export default {
data() {
return {
activeKey: '1',
allCollege: [],
showBox: false,
showGroup: false,
oneentrydate: null,
collegeentrydate: [],
majorentrydate: [],
lastMajorEntrydate: [],
checkedOptions: [],
collegetab2: [],
collegeMajorOptions: [],
collegeMajor: [],
Url: {
getBatch: '/cet/getBatch',
getEntrydate: '/cet/getEntrydate',
getCollege: '/cet/getCollege',
getCollegeRate: '/cet/getRateByCollege',
getAllRate: '/cet/getAllRate',
getCollegeMajor: '/cet/getCollegeMajor',
getRateByMajor: '/cet/getRateByMajor',
},
tab1loading: false,
tab2loading: false,
tab3loading: false,
collegeOptions: [],
batchOptions: [],
levelOptions: [
{ value: 'cet4', label: '英语四级' },
{ value: 'cet6', label: '英语六级' },
],
visible: false,
level: null,
college: [],
batch: null,
entrydateOptions: [],
entrydate: [],
majorCheckOn: [],
//
lastCollegeMajor: [],
majorLength: 0,
majorOptions: [],
//
lastCollege: [],
//
lastCollegeEntrydate: [],
};
},
mounted() {
this.getCollegeOptions();
// this.getBatch();
this.getEntrydate();
this.allQuery();
this.getCollegeMajorData();
},
methods: {
onMajorChange(value) {
console.log(value, 'value');
let length = value.length;
//value,this.collegeMajorOptionsvaluethis.collegeMajorOptionschildren
for (let i = 0; i < value.length; i++) {
for (let j = 0; j < this.collegeMajorOptions.length; j++) {
if (value[i] == this.collegeMajorOptions[j].value) {
length += this.collegeMajorOptions[j].children.length;
length -= 1;
}
if (length > 5) {
message.error('最多选择五个专业');
//value
value.pop();
return;
}
}
}
console.log(length, 'length');
},
allQuery() {
this.tab1loading = true;
let college = ['全校'];
let query = 'tab1';
if (this.oneentrydate == null) {
this.oneentrydate = '2017';
}
this.query(query, college, [this.oneentrydate]);
},
async majorQuery() {
if (this.majorentrydate.length == 0) {
message.error('请选择年级');
return;
}
if (this.collegeMajor.length == 0 || this.collegeMajor == null) {
message.error('请选择学院');
return;
}
if (this.lastCollegeMajor.length == 0 || this.lastCollegeMajor == null) {
message.error('请选择专业');
return;
}
this.tab3loading = true;
let res = null;
try {
let params = {
college: this.lastCollegeMajor,
entrydate: this.majorentrydate,
};
res = await defHttp.post({ url: this.Url.getRateByMajor, params });
} finally {
console.log(res, 'res');
this.tab3loading = false;
this.$nextTick(() => {
this.drawChart(res.data, 'tab3');
});
}
},
collegeQuery() {
if (this.collegeentrydate.length == 0) {
message.error('请选择年级');
return;
}
if (this.collegetab2.length == 1) {
message.error('请至少选择两个学院');
return;
}
if (this.collegetab2.length == 0) {
message.error('请选择学院');
return;
}
this.tab2loading = true;
let query = 'tab2';
this.query(query, this.collegetab2, this.collegeentrydate);
},
test(value) {
console.log(this.collegeMajor);
console.log(value);
},
//
checkAll(e) {
this.college = ['全校'];
this.showGroup = !this.showGroup;
},
//
checkOne(e) {
console.log(e);
this.college = e;
this.showBox = true;
if (e.length == 0) {
this.showBox = false;
}
},
//
checkoneentrydate(e) {
console.log(e.target.value, 'e');
this.entrydate = [e.target.value];
},
//
async getCollegeMajorData() {
const res = await defHttp.get({ url: this.Url.getCollegeMajor });
//map
this.collegeMajorOptions = res.collegeMajor.map((item) => {
return {
value: item.college,
label: item.college,
children: item.major.map((major) => {
return {
value: major,
label: major,
};
}),
};
});
// this.collegeMajor = [''];
console.log(this.collegeMajor, 'collegeMajor');
console.log(this.collegeMajorOptions, 'collegeMajorOptions1');
},
getCollegeOptions() {
defHttp.get({ url: this.Url.getCollege }).then((res) => {
this.collegeOptions = res.colleges;
//
// this.collegeOptions.unshift({ value: '', label: '' });
// this.collegeOptions.forEach(option => {
// if (option.value !== '') {
// option.disabled = true;
//}
// });
// this.college = [''];
// this.collegetab2 = ['', ''];
console.log(this.collegeOptions, 'collegeOptions');
});
},
onMajorEntrydateChange(value) {
console.log(value, 'value');
if (value.length > 5) {
//valuethis.lastMajorEntrydate
for (let i = 0; i < value.length; i++) {
if (!this.lastMajorEntrydate.includes(value[i])) {
message.error('最多选择五个');
//value[i]
value.splice(i, 1);
return;
}
}
}
this.lastMajorEntrydate = value;
},
//
onCollegeChange(value) {
console.log(value, 'value');
if (value.length > 5) {
message.error('最多选择五个');
//lastCollegevaluelastCollege
for (let i = 0; i < value.length; i++) {
if (!this.lastCollege.includes(value[i])) {
value.splice(i, 1);
return;
}
}
return;
}
this.lastCollege = value;
},
//
onCollegeEntrydateChange(value) {
console.log(value, 'value');
if (value.length > 5) {
message.error('最多选择五个');
//lastCollegeEntrydatevaluelastCollegeEntrydate
for (let i = 0; i < value.length; i++) {
if (!this.lastCollegeEntrydate.includes(value[i])) {
value.splice(i, 1);
return;
}
}
return;
}
this.lastCollegeEntrydate = value;
},
//
onCollegeMajorChange(value) {
//valuethis.lastCollegeMajorvaluethis.lastCollegeMajor,this.majorOptionsthis.lastCollegeMajorvalue
for (let i = 0; i < value.length; i++) {
if (!this.lastCollegeMajor.map((item) => item[1]).includes(value[i])) {
if (this.majorLength == 5) {
message.error('最多选择五个');
//valuevalue[i]
value.splice(i, 1);
return;
}
this.lastCollegeMajor.push([this.collegeMajor, value[i]]);
this.majorLength++;
}
}
for (let i = 0; i < this.lastCollegeMajor.length; i++) {
if (!value.includes(this.lastCollegeMajor[i][1])) {
//this.majorOptions
if (this.majorOptions.find((item) => item.value == this.lastCollegeMajor[i][1])) {
this.lastCollegeMajor.splice(i, 1);
console.log('splice');
this.majorLength--;
}
}
}
console.log(value, 'value');
},
removeTag(index) {
if (this.majorCheckOn.includes(this.lastCollegeMajor[index][1])) {
let index1 = this.majorCheckOn.indexOf(this.lastCollegeMajor[index][1]);
this.majorCheckOn.splice(index1, 1);
}
this.lastCollegeMajor.splice(index, 1);
this.majorLength--;
},
//
onMajorCollegeChange(value) {
let val = value.target.value;
console.log(val, 'value');
console.log(this.collegeMajorOptions.find((item) => item.value == val).children, 'majorOptions');
this.majorOptions = this.collegeMajorOptions.find((item) => item.value == val).children;
//this.majorCheckOnthis.lastCollegeMajorthis.lastCollegeMajorthis.majorCheckOn,
// for (let i = 0; i < this.majorCheckOn.length; i++) {
// if (!this.lastCollegeMajor.includes(this.majorCheckOn[i])) {
// this.lastCollegeMajor.push(this.majorCheckOn[i]);
// }
// }
// for (let i = 0; i < this.lastCollegeMajor.length; i++) {
// if (!this.majorCheckOn.includes(this.lastCollegeMajor[i])) {
// this.lastCollegeMajor.splice(i, 1);
// console.log("splice")
// }
// }
// this.lastCollegeMajor = this.majorCheckOn;
console.log(this.lastCollegeMajor, 'lastCollegeMajor');
this.majorCheckOn = this.lastCollegeMajor.map((item) => item[1]);
console.log(this.majorCheckOn, 'majorCheckOn');
},
clearMajor() {
this.majorCheckOn = [];
this.lastCollegeMajor = [];
this.majorLength = 0;
},
clearCollege() {
this.collegetab2 = [];
this.collegeentrydate = [];
this.lastCollege = [];
this.lastCollegeEntrydate = [];
},
//tab2\3
drawChart(data, tab) {
let seriesData = [];
let xData = this.majorentrydate.sort((a, b) => a - b);
if (tab == 'tab2') {
xData = this.collegeentrydate.sort((a, b) => a - b);
}
let k = 0;
let legendData = [];
let colors = [
'#5370c5',
'#91CC75',
'#fac858',
'#ee6666',
'#73c0de',
'#FF6A6A',
'#FFA500',
'#EE2C2C',
'#90EE90',
'#008B8B',
'#FFC0CB',
'#FFDAB9',
'#FFDEAD',
'#FFE4B5',
'#FFE4C4',
'#FFE4E1',
'#FFEBCD',
'#FFEFD5',
'#FFFAF0',
'#FFFAFA',
'#FFFFE0',
'#FFFFF0',
'#FFFFFF',
'#F0F8FF',
'#FAEBD7',
'#FAF0E6',
'#FAFAD2',
'#F5FFFA',
'#F8F8FF',
'#F0FFF0',
'#F0FFFF',
'#F0E68C',
'#F0F8FF',
'#F0FFF0',
'#F0FFFF',
'#F4A460',
'#F5DEB3',
'#F5F5DC',
'#F5F5F5',
'#F5FFFA',
'#F8F8FF',
'#F9EBEA',
'#FAD7A0',
'#FAF0E6',
'#FAFAD2',
'#FAF0E6 ',
];
let isJK = false;
for (let i in data) {
console.log(i, 'i');
legendData.push(i);
let yData = [];
// legendData=[];
console.log(data[i], 'data[i]');
for (let j in data[i]) {
console.log(data[i][j].college, 'data[i][j].college111');
// if(data[i][j].college == ''){
yData.push(data[i][j].passRate);
// legendData.push(data[i][j].college);
}
console.log(yData, 'yData');
seriesData.push({
name: i,
type: 'bar',
//
barWidth: 16,
data: yData,
//
barGap: '30%',
//
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
formatter: '{c}%',
textStyle: {
//
color: 'black',
fontSize: 10,
},
},
color: colors[k++],
},
},
});
}
console.log(legendData, 'legendData');
console.log(seriesData, 'dasaaseriresdata');
// debugger
let myChart = null;
console.log(tab, 'tab');
myChart = document.getElementById('map2');
if (myChart) {
myChart = echarts.getInstanceByDom(myChart);
if (myChart) {
myChart.dispose();
}
}
myChart = echarts.init(document.getElementById('map2'));
//
let option = {
//title: {
// text: '',
// textStyle: {
// fontSize: 14,
// },
//},
legend: {},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
//tooltip,
let result = params[0].name + '级通过率<br>';
for (let i = 0; i < params.length; i++) {
result += params[i].marker + legendData[i] + ' : ' + params[i].value + '%' + '<br>';
}
return result;
},
},
//toolbox: {
// show: true,
// feature: {
// magicType: { show: true, type: ['line', 'bar'] },
// restore: { show: true },
// saveAsImage: { show: true },
// },
//},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
rotate: 0, //0
},
},
],
yAxis: [
{
type: 'value',
name: '通过率',
axisLabel: {
formatter: '{value} %',
},
},
],
series: seriesData,
};
// 使
console.log(option, 'option');
console.log(myChart, 'myChart');
setTimeout(() => {
myChart.setOption(option);
}, 1);
},
drawChart2(data, tab) {
let seriesData = [];
let xData = this.majorentrydate.sort((a, b) => a - b);
if (tab == 'tab2') {
xData = this.collegeentrydate.sort((a, b) => a - b);
}
let k = 0;
let legendData = [];
let colors = [
'#5370c5',
'#91CC75',
'#fac858',
'#ee6666',
'#73c0de',
'#FF6A6A',
'#FFA500',
'#EE2C2C',
'#90EE90',
'#008B8B',
'#FFC0CB',
'#FFDAB9',
'#FFDEAD',
'#FFE4B5',
'#FFE4C4',
'#FFE4E1',
'#FFEBCD',
'#FFEFD5',
'#FFFAF0',
'#FFFAFA',
'#FFFFE0',
'#FFFFF0',
'#FFFFFF',
'#F0F8FF',
'#FAEBD7',
'#FAF0E6',
'#FAFAD2',
'#F5FFFA',
'#F8F8FF',
'#F0FFF0',
'#F0FFFF',
'#F0E68C',
'#F0F8FF',
'#F0FFF0',
'#F0FFFF',
'#F4A460',
'#F5DEB3',
'#F5F5DC',
'#F5F5F5',
'#F5FFFA',
'#F8F8FF',
'#F9EBEA',
'#FAD7A0',
'#FAF0E6',
'#FAFAD2',
'#FAF0E6 ',
];
let isJK = false;
for (let i in data) {
console.log(i, 'i');
legendData.push(i);
let yData = [];
// legendData=[];
console.log(data[i], 'data[i]');
for (let j in data[i]) {
console.log(data[i][j].college, 'data[i][j].college111');
// if(data[i][j].college == ''){
yData.push(data[i][j].passRate);
// legendData.push(data[i][j].college);
}
console.log(yData, 'yData');
seriesData.push({
name: i,
type: 'line',
//
barWidth: 18,
data: yData,
//
barGap: '30%',
//
itemStyle: {
normal: {
label: {
show: true, //
position: 'top', //
formatter: '{c}%',
textStyle: {
//
color: 'black',
fontSize: 10,
},
},
color: colors[k++],
},
},
});
}
// debugger
let myChart = null;
console.log(tab, 'tab');
myChart = document.getElementById('map3');
if (myChart) {
myChart = echarts.getInstanceByDom(myChart);
if (myChart) {
myChart.dispose();
}
}
myChart = echarts.init(document.getElementById('map3'));
//
let option = {
legend: {},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
formatter: function (params) {
//tooltip,
let result = params[0].name + '级通过率<br>';
for (let i = 0; i < params.length; i++) {
result += params[i].marker + legendData[i] + ' : ' + params[i].value + '%' + '<br>';
}
return result;
},
},
//toolbox: {
// show: true,
// feature: {
// magicType: { show: true, type: ['line', 'bar'] },
// restore: { show: true },
// saveAsImage: { show: true },
// },
//},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: xData,
axisLabel: {
interval: 0, //x
rotate: 0, //0
},
},
],
yAxis: [
{
type: 'value',
name: '通过率',
axisLabel: {
formatter: '{value} %',
},
},
],
series: seriesData,
};
// 使
console.log(option, 'option');
console.log(myChart, 'myChart');
setTimeout(() => {
myChart.setOption(option);
}, 1);
},
//
async getBatch() {
const getBatch = await defHttp.get({ url: this.Url.getBatch });
this.batchOptions = getBatch.batches;
this.batch = this.batchOptions[0].value;
this.level = this.levelOptions[0].value;
},
//
async getEntrydate() {
const getEntrydate = await defHttp.get({ url: this.Url.getEntrydate });
this.entrydateOptions = getEntrydate.entrydates;
this.oneentrydate = this.entrydateOptions[0].value;
// this.collegeentrydate = [this.entrydateOptions[0].value];
// this.majorentrydate = [this.entrydateOptions[0].value];
// this.entrydate.push (this.entrydateOptions[0].value);
},
//
async query(query, college, entrydate) {
this.visible = false;
let result = null;
// console.log(this.college, this.entrydate);
// //this.collegethis.batchnull
// if (this.college === null || this.college.length === 0) {
// this.college = [''];
// }
// if (!this.entrydate || this.entrydate.length === 0) {
// this.entrydate = ['2017'];
// }
try {
// console.log(this.college, this.entrydate)
let params = {
college: college,
entrydate: entrydate,
level: 'cet4',
};
let url = query == 'tab1' ? this.Url.getAllRate : this.Url.getCollegeRate;
console.log(params.college, 'college');
result = await defHttp.post({ url: url, params });
//使passRate
// result.data.sort((a, b) => {
// return b.passRate - a.passRate;
// });
console.log('data', result.data);
} finally {
query == 'tab1' ? (this.tab1loading = false) : (this.tab2loading = false);
this.$nextTick(() => {
if (query == 'tab1') {
this.dataChart(result.data, query);
} else if (query == 'tab2') {
this.drawChart(result.data, query);
this.drawChart2(result.data, query);
}
});
}
},
},
};
</script>
<style lang="less" scoped>
.container {
display: flex;
background-color: #fff;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.query {
display: flex;
justify-content: flex-start;
align-items: center;
margin-left: 24px;
}
.tab3 {
justify-content: flex-start;
align-items: center;
margin-left: 24px;
}
.title {
font-size: 34px;
color: rgb(8, 8, 8);
font-weight: bold;
}
</style>

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