Compare commits
118 Commits
main
...
feature/1.
Author | SHA1 | Date |
---|---|---|
|
bae4538a74 | |
|
e58fe6f13f | |
|
04b8fa5cbc | |
|
9086285b76 | |
|
b181f828dd | |
|
4f3365999b | |
|
0c1c795d68 | |
|
2ab6b34c2c | |
|
267a47535d | |
|
225bd76543 | |
|
8670a0e655 | |
|
c8405d3d11 | |
|
abb880d17c | |
|
571e410c0e | |
|
657aebdc99 | |
|
7b47fa447a | |
|
13d5df6d05 | |
|
56cff6ecdd | |
|
bbaf21be33 | |
|
46d1d319f6 | |
|
ffefebd785 | |
|
688820ccce | |
|
c9219a877f | |
|
c123a87789 | |
|
930fbdf474 | |
|
02024f1e24 | |
|
15a02bae94 | |
|
43356879ee | |
|
eb54c96072 | |
|
d66336ab60 | |
|
608a340a2a | |
|
f0db8e010d | |
|
7aa48aba48 | |
|
8f96b9ba85 | |
|
2b83ad54a3 | |
|
7cc1046d67 | |
|
f0ed560e73 | |
|
87089b2adf | |
|
f0b15ce2da | |
|
c8c15e6182 | |
|
03ad85478f | |
|
87daede414 | |
|
d0a33280d3 | |
|
347fb88477 | |
|
baf4b4246b | |
|
60750d0ca4 | |
|
4aef7d706d | |
|
cf89aae584 | |
|
df1aab15cd | |
|
e60233645a | |
|
dd0090a826 | |
|
5610e30c11 | |
|
aaedf72cf3 | |
|
54d1bcb9aa | |
|
ccd3305924 | |
|
cf6302cc71 | |
|
80a240675d | |
|
4c60ba4ca7 | |
|
9632a7bcce | |
|
3ffbf5d13e | |
|
32fbd95a23 | |
|
7abb43c29a | |
|
905c460bf3 | |
|
04b3da5ab6 | |
|
337e23ac7e | |
|
4f2001c9d4 | |
|
0e736b3217 | |
|
f3b756a7cf | |
|
0708525435 | |
|
b97af54042 | |
|
e793f86ccd | |
|
1f79794e32 | |
|
2193470e4b | |
|
4c9af6fe98 | |
|
82bd51ca66 | |
|
965fe871c8 | |
|
3f98ead83c | |
|
4cdb5352a6 | |
|
7ba725c13f | |
|
d7bdcadfc7 | |
|
1d68fa9a7b | |
|
d81438ddba | |
|
b9d306edc5 | |
|
f96f5bf6ab | |
|
78d271d832 | |
|
7cb5a9e2bb | |
|
bbe7ca9083 | |
|
9941a2d725 | |
|
7fc0f45a6a | |
|
29f5da8c52 | |
|
39fb1184f6 | |
|
b85755ed33 | |
|
59d3f54666 | |
|
4a1c11dd5e | |
|
0ef5138f30 | |
|
b5e4153350 | |
|
ea441d3126 | |
|
06f8f0c7d9 | |
|
9d1b7cc623 | |
|
171dc8f3f6 | |
|
e7786d3429 | |
|
50a62cbae3 | |
|
f8a938049f | |
|
c90db7414d | |
|
7e7c31417f | |
|
daff63566b | |
|
a3a14d4ccc | |
|
0ef20f7d29 | |
|
7a17e9a0ed | |
|
81d2d93618 | |
|
8cf7443af6 | |
|
2bed8d3933 | |
|
e1f887e055 | |
|
c7569fcfa9 | |
|
5f5c2a6b4a | |
|
61c3e9026b | |
|
9f00876040 | |
|
a4012e1b3a |
2
.env
|
@ -2,7 +2,7 @@
|
|||
VITE_PORT = 3100
|
||||
|
||||
# 网站标题
|
||||
VITE_GLOB_APP_TITLE = JeecgBoot 企业级低代码平台
|
||||
VITE_GLOB_APP_TITLE = 哈尔滨师范大学英语四六级综合管理平台
|
||||
|
||||
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
|
||||
VITE_GLOB_APP_SHORT_NAME = JeecgBootAdmin
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
##### 版本号:
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
##### 截图&代码:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示(为了提高issue处理效率):
|
||||
- 未按格式要求发帖,会被直接删掉;
|
||||
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
|
||||
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
|
||||
|
38
LICENSE
|
@ -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
|
||||
黄晖制作
|
|
@ -1,11 +1,11 @@
|
|||
JEECG BOOT 低代码开发平台(Vue3前端)
|
||||
===============
|
||||
当前最新版本: 3.6.3(发布时间:2024-03-11)
|
||||
当前最新版本: 3.6.2(发布时间:2024-01-08)
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
19750
pnpm-lock.yaml
|
@ -35,11 +35,6 @@
|
|||
if (newValue === ThemeEnum.DARK) {
|
||||
appTheme.value.algorithm = theme.darkAlgorithm;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240322---for:【QQYUN-8570】生产环境暗黑模式下主题色不生效
|
||||
if (import.meta.env.PROD) {
|
||||
changeTheme(appStore.getProjectConfig.themeColor);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240322---for:【QQYUN-8570】生产环境暗黑模式下主题色不生效
|
||||
appTheme.value = {
|
||||
...appTheme.value,
|
||||
};
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
};
|
||||
|
|
|
@ -126,9 +126,6 @@ export function getCaptcha(params) {
|
|||
createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
|
||||
reject();
|
||||
}
|
||||
}).catch((res)=>{
|
||||
createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 752 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 42 KiB |
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -59,9 +59,7 @@
|
|||
instance && emit('register', drawerInstance, instance.uid);
|
||||
|
||||
const getMergeProps = computed((): DrawerProps => {
|
||||
// update-begin--author:liaozhiyang---date:20240320---for:【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误(去掉toRow,否者props变化不会触发computed)
|
||||
return { ...deepMerge(props, unref(propsRef)) };
|
||||
// update-end--author:liaozhiyang---date:20240320---for:【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误(去掉toRow,否者props变化不会触发computed)
|
||||
return deepMerge(toRaw(props), unref(propsRef));
|
||||
});
|
||||
|
||||
const getProps = computed((): DrawerProps => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
|
|
|
@ -105,22 +105,6 @@
|
|||
return disabled;
|
||||
});
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20240308---for:【QQYUN-8377】formSchema 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---for:【QQYUN-8377】formSchema 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---for:【QQYUN-8377】formSchema props支持动态修改
|
||||
const dynamicPropskey = props.schema.dynamicPropskey;
|
||||
if (dynamicPropskey) {
|
||||
propsData[dynamicPropskey] = unref(getDynamicPropsValue);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240308---for:【QQYUN-8377】formSchema props支持动态修改
|
||||
|
||||
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
|
||||
// RangePicker place是一个数组
|
||||
|
@ -337,19 +312,21 @@
|
|||
//update-begin-author:taoyan date:2022-9-7 for: VUEN-2061【样式】online表单超出4个 .. 省略显示
|
||||
//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-2061【样式】online表单超出4个 .. 省略显示
|
||||
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
|
||||
|
@ -411,14 +388,14 @@
|
|||
}
|
||||
|
||||
const { baseColProps = {} } = props.formProps;
|
||||
// update-begin--author:liaozhiyang---date:20230803---for:【issues-641】调整表格搜索表单的span配置无效
|
||||
// update-begin--author:liaozhiyang---date:20230803---for:【issues-641】调整表格搜索表单的span配置无效
|
||||
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---for:【issues-641】调整表格搜索表单的span配置无效
|
||||
// update-end--author:liaozhiyang---date:20230803---for:【issues-641】调整表格搜索表单的span配置无效
|
||||
const { isIfShow, isShow } = getShow();
|
||||
const values = unref(getValues);
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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---for:【QQYUN-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---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置
|
||||
</style>
|
||||
|
|
|
@ -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---for:【QQYUN-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---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
//如果排除的用户id的长度不为0,那么需要改变页数
|
||||
if(unref(excludeUserIdList) && unref(excludeUserIdList).length>0){
|
||||
total = total - unref(excludeUserIdList).length;
|
||||
}
|
||||
totalRecord.value = total;
|
||||
initCurrentUserData(records);
|
||||
userDataList.value = records;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -128,7 +128,6 @@ export type ComponentType =
|
|||
| 'JCategorySelect'
|
||||
| 'JSelectMultiple'
|
||||
| 'JPopup'
|
||||
| 'JPopupDict'
|
||||
| 'JSwitch'
|
||||
| 'JEasyCron'
|
||||
| 'JTreeDict'
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -130,11 +130,10 @@
|
|||
if (!props.visible) return;
|
||||
const wrapperRefDom = unref(wrapperRef);
|
||||
if (!wrapperRefDom) return;
|
||||
// update-begin--author:liaozhiyang---date:20240320---for:【QQYUN-8573】BasicModal组件在非全屏的情况下最大高度获取异常,不论内容高度是否超出屏幕高度,都等于内容高度
|
||||
const bodyDom = wrapperRefDom.$el.parentElement?.parentElement?.parentElement;
|
||||
// update-end--author:liaozhiyang---date:20240320---for:BasicModal组件在非全屏的情况下最大高度获取异常,不论内容高度是否超出屏幕高度,都等于内容高度
|
||||
|
||||
const bodyDom = wrapperRefDom.$el.parentElement;
|
||||
if (!bodyDom) return;
|
||||
// bodyDom.style.padding = '0';
|
||||
bodyDom.style.padding = '0';
|
||||
await nextTick();
|
||||
|
||||
try {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -127,14 +127,6 @@
|
|||
// update-begin--author:sunjianlei---date:20220408---for: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
|
||||
return;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240227---for:【QQYUN-6366】内部路由也可以支持采用新浏览器tab打开
|
||||
const findItem = getMatchingMenu(props.items, key);
|
||||
if (findItem?.internalOrExternal == true) {
|
||||
window.open(location.origin + key);
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240227---for:【QQYUN-6366】内部路由也可以支持采用新浏览器tab打开
|
||||
|
||||
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,
|
||||
|
|
|
@ -251,11 +251,6 @@
|
|||
&-popconfirm {
|
||||
.ant-popconfirm-buttons {
|
||||
min-width: 120px;
|
||||
// update-begin--author:liaozhiyang---date:20240124---for:【issues/1019】popConfirm确认框待端后端返回过程中(处理中)样式错乱
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// update-end--author:liaozhiyang---date:20240124---for:【issues/1019】popConfirm确认框待端后端返回过程中(处理中)样式错乱
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,7 @@
|
|||
if (!isFunction(summaryFunc)) {
|
||||
return [];
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20230227---for:【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
|
||||
let dataSource = cloneDeep(unref(table.getDataSource()));
|
||||
// update-end--author:liaozhiyang---date:20230227---for:【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
|
||||
let dataSource = toRaw(unref(table.getDataSource()));
|
||||
dataSource = summaryFunc(dataSource);
|
||||
dataSource.forEach((item, i) => {
|
||||
item[props.rowKey] = `${i}`;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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---for:【QQYUN-8571】发布路径不以/结尾资源会加载失败
|
||||
if (!publicPath.endsWith('/')) {
|
||||
publicPath += '/';
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240320---for:【QQYUN-8571】发布路径不以/结尾资源会加载失败
|
||||
const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
|
||||
return {
|
||||
selector: `#${unref(tinymceId)}`,
|
||||
height,
|
||||
|
|
Before Width: | Height: | Size: 5.0 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||
// 删除的是当前active的,active往前移,前面没了往后移。
|
||||
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>
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -30,17 +30,6 @@
|
|||
target.selectionStart = selectionStart - 1;
|
||||
target.selectionEnd = selectionStart - 1;
|
||||
}
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20240227---for:【QQYUN-8347】小数点后大于两位且最后一位是0,输入框不可输入了
|
||||
// 例如: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---for:【QQYUN-8347】小数点后大于两位且最后一位是0,输入框不可输入了
|
||||
}
|
||||
}
|
||||
// 触发事件,存储输入的值
|
||||
|
|
|
@ -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自定义组件
|
||||
*
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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页面效果
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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---for:【QQYUN-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---for:【QQYUN-7970】国际化
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner();
|
||||
|
||||
|
@ -94,7 +85,6 @@
|
|||
}
|
||||
|
||||
defineExpose({
|
||||
title,
|
||||
show,
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -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---for:【QQYUN-7970】国际化
|
||||
createMessage.success(t('layout.header.refreshCacheComplete'));
|
||||
// update-end--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
|
||||
createMessage.success('刷新缓存完成!');
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
|
||||
createMessage.error(t('layout.header.refreshCacheFailure'));
|
||||
// update-end--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
|
||||
createMessage.error('刷新缓存失败!');
|
||||
}
|
||||
}
|
||||
// 切换部门
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
&-action {
|
||||
display: flex;
|
||||
min-width: 180px;
|
||||
min-width: 150px;
|
||||
// padding-right: 12px;
|
||||
align-items: center;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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: '请输入新密码',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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'`);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
// 开启快速操作
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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});
|
||||
},
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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: 退出登录后清除拖拽模块的接口前缀
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
/**
|
||||
* 渲染图片
|
||||
|
|
|
@ -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 };
|
|
@ -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 },
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
|
@ -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, //每两行合并一次grade列的单元格,rowSpan为跨度
|
||||
}),
|
||||
},
|
||||
{
|
||||
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;">' +
|
||||
' ' +
|
||||
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>
|
|
@ -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">点击柱状图可查看该学院 </span>
|
||||
<span style="font-size: 16px; color: red">各年级四级通过率</span>
|
||||
</div>
|
||||
|
||||
<!-- <span style=" font-size: 18px;">  变化</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;">' +
|
||||
' ' +
|
||||
params[0].value +
|
||||
'%' +
|
||||
'</td></tr>' +
|
||||
'<tr><td>' +
|
||||
params[1].marker +
|
||||
params[1].seriesName +
|
||||
'</td><td style="font-weight: bold;">' +
|
||||
' ' +
|
||||
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.college和this.batch为null则先赋静态值
|
||||
// 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>
|
|
@ -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;">' +
|
||||
' ' +
|
||||
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.collegeMajor为null则先赋个值
|
||||
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>
|
|
@ -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;">' +
|
||||
' ' +
|
||||
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.batch为null则先赋个值
|
||||
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,
|
||||
// animationn:false,
|
||||
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>
|
|
@ -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.collegeMajorOptions比较,如果value中的数据在this.collegeMajorOptions中则获得他的children长度
|
||||
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) {
|
||||
//找到value中不在this.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('最多选择五个');
|
||||
//和lastCollege比较,如果value中的数据不在lastCollege中则删除
|
||||
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('最多选择五个');
|
||||
//和lastCollegeEntrydate比较,如果value中的数据不在lastCollegeEntrydate中则删除
|
||||
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) {
|
||||
//将value中的数据与this.lastCollegeMajor比较,如果value中的有this.lastCollegeMajor中没有的数据则添加,反之寻找在this.majorOptions中并且在this.lastCollegeMajor中但是不在value中的数据删除
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (!this.lastCollegeMajor.map((item) => item[1]).includes(value[i])) {
|
||||
if (this.majorLength == 5) {
|
||||
message.error('最多选择五个');
|
||||
//从value中删除value[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.majorCheckOn中的数据与this.lastCollegeMajor比较,如果this.lastCollegeMajor中有this.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.college和this.batch为null则先赋静态值
|
||||
// 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>
|