35
.gitea/workflows/npm-build-on-push.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
## 工作流名称
|
||||||
|
name: huaian-screen
|
||||||
|
|
||||||
|
## 工作流触发时机
|
||||||
|
on: [ push ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
VERSION: $(echo '${{ github.event.head_commit.message }}' | sed -n 's/.*release(\([^)]*\)).*/\1/p')
|
||||||
|
DOMAIN_NAME: ts-official.tmp.tieshengkeji.cn
|
||||||
|
FLODER_NAME: huaian
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
## 任务名称
|
||||||
|
build:
|
||||||
|
## 任务执行的服务器
|
||||||
|
runs-on: tiesheng-local
|
||||||
|
if: ${{ startsWith(github.event.head_commit.message,'release') }}
|
||||||
|
|
||||||
|
## 任务步骤
|
||||||
|
steps:
|
||||||
|
## 检出代码(固定配置)
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: https://git.tieshengkeji.com/actions/checkout@v4
|
||||||
|
|
||||||
|
## 下载前端依赖
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
## 前端项目打包并发送到本地
|
||||||
|
- name: Build Frontend
|
||||||
|
run: |
|
||||||
|
npm run build
|
||||||
|
rm -rf /usr/local/nginx/html/${{env.DOMAIN_NAME}}/static/${{env.FLODER_NAME}}
|
||||||
|
mv dist "${{env.FLODER_NAME}}"
|
||||||
|
mv ${{env.FLODER_NAME}} /usr/local/nginx/html/${{env.DOMAIN_NAME}}/static
|
||||||
83
.gitea/workflows/npm-build-on-tag.yaml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
## 工作流名称
|
||||||
|
name: yxr-manager
|
||||||
|
|
||||||
|
## 工作流触发时机
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*' # 匹配所有标签
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
## 任务名称
|
||||||
|
build:
|
||||||
|
## 任务执行的服务器
|
||||||
|
runs-on: tiesheng-local
|
||||||
|
|
||||||
|
## 任务步骤
|
||||||
|
steps:
|
||||||
|
## 检出代码(固定配置)
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: https://git.tieshengkeji.com/actions/checkout@v4
|
||||||
|
|
||||||
|
## 下载前端依赖
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
## 获取当前仓库名称和标签名称
|
||||||
|
- name: Get repository name and tag name
|
||||||
|
id: repo-info
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=repo_name::$(basename ${GITHUB_REPOSITORY})"
|
||||||
|
echo "::set-output name=tag_name::${GITHUB_REF#refs/tags/}"
|
||||||
|
|
||||||
|
## 检查本地该项目文件夹是否存在
|
||||||
|
- name: Check if local directory exists
|
||||||
|
id: check-local-directory
|
||||||
|
run: |
|
||||||
|
if [ -d "/usr/local/nginx/html/dist.tmp.tieshengkeji.cn/${{ steps.repo-info.outputs.repo_name }}" ]; then
|
||||||
|
echo "Directory exists, skipping creation."
|
||||||
|
echo "::set-output name=directory_exists::true"
|
||||||
|
else
|
||||||
|
echo "Directory does not exist, will create."
|
||||||
|
echo "::set-output name=directory_exists::false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## 不存在则创建项目文件夹
|
||||||
|
- name: Create Project Directory
|
||||||
|
if: ${{ steps.check-local-directory.outputs.directory_exists != 'true' }}
|
||||||
|
run: |
|
||||||
|
mkdir /usr/local/nginx/html/dist.tmp.tieshengkeji.cn/${{ steps.repo-info.outputs.repo_name }}
|
||||||
|
|
||||||
|
## 前端项目打包并发送到本地
|
||||||
|
- name: Build Frontend
|
||||||
|
run: |
|
||||||
|
npm run build
|
||||||
|
mv dist "${{ steps.repo-info.outputs.tag_name }}"
|
||||||
|
zip -r ${{ steps.repo-info.outputs.repo_name }}${{ steps.repo-info.outputs.tag_name }}.zip ${{ steps.repo-info.outputs.tag_name }}
|
||||||
|
mv ${{ steps.repo-info.outputs.repo_name }}${{ steps.repo-info.outputs.tag_name }}.zip /usr/local/nginx/html/dist.tmp.tieshengkeji.cn/${{ steps.repo-info.outputs.repo_name }}
|
||||||
|
|
||||||
|
##获取最后一条提交消息
|
||||||
|
- name: Get commit title
|
||||||
|
id: get_commit_title
|
||||||
|
run: |
|
||||||
|
COMMIT_TITLE=$(echo "${{ github.event.head_commit.message }}" | head -n 1)
|
||||||
|
echo "::set-output name=commit_title::$COMMIT_TITLE"
|
||||||
|
|
||||||
|
## 发送钉钉消息
|
||||||
|
- name: Send DingDing notification using curl
|
||||||
|
run: |
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"msgtype": "text",
|
||||||
|
"at": {
|
||||||
|
"atMobiles":[
|
||||||
|
"18175194630",
|
||||||
|
],
|
||||||
|
"isAtAll": false
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"content": "@18175194630 项目【${{ steps.repo-info.outputs.repo_name }}】打包完成,版本号${{ steps.repo-info.outputs.tag_name }}。\n 操作人:${{ github.actor }} \n 内容:${{ steps.get_commit_title.outputs.commit_title }} \n https://dist.tmp.tieshengkeji.cn/${{ steps.repo-info.outputs.repo_name }}/${{ steps.repo-info.outputs.repo_name }}${{ steps.repo-info.outputs.tag_name }}.zip"
|
||||||
|
},
|
||||||
|
}' \
|
||||||
|
'https://oapi.dingtalk.com/robot/send?access_token=${{vars.DINGTALK_ACCESS_TOKEN}}'
|
||||||
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/node_modules
|
||||||
|
/.env.local
|
||||||
|
/.umirc.local.ts
|
||||||
|
/config/config.local.ts
|
||||||
|
/src/.umi
|
||||||
|
/src/.umi-production
|
||||||
|
/src/.umi-test
|
||||||
|
/dist
|
||||||
|
.swc
|
||||||
|
.DS_Store
|
||||||
56
.umirc.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { defineConfig } from "umi";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
routes: [
|
||||||
|
{ path: "/", redirect:'/login' },
|
||||||
|
{ path: "/login", component: "@/pages/login" },
|
||||||
|
{ path: "/home", component: "@/pages/home",
|
||||||
|
routes: [
|
||||||
|
// { path: '/home', redirect: '/home/statistics' },
|
||||||
|
{ path: "/home/statistics", component: "@/pages/statistics" },
|
||||||
|
{ path: "/home/analysis", component: "@/pages/analysis" },
|
||||||
|
{ path: "/home/report", component: "@/pages/report" },
|
||||||
|
{ path: "/home/report/online", component: "@/pages/report/online" },
|
||||||
|
{ path: "/home/report/student", component: "@/pages/report/student" },
|
||||||
|
{ path: "/home/report/examDistribution", component: "@/pages/report/examDistribution" },
|
||||||
|
{ path: "/home/report/school", component: "@/pages/report/school" },
|
||||||
|
{ path: "/home/report/teacher", component: "@/pages/report/teacher" },
|
||||||
|
{ path: "/home/report/question", component: "@/pages/report/question" },
|
||||||
|
{ path: "/home/report/quality", component: "@/pages/report/subpage/quality" },
|
||||||
|
{ path: "/home/report/enowledgepoint", component: "@/pages/report/enowledgepoint" },
|
||||||
|
{ path: "/home/examination/comparison", component: "@/pages/examination-comparison",redirect:'/home/ranking-comparison'},
|
||||||
|
{ path: "/home/ranking/comparison", component: "@/pages/examination-comparison/subpage/ranking-comparison" },
|
||||||
|
{ path: "/home/percentage/comparison", component: "@/pages/examination-comparison/subpage/percentage-comparison" },
|
||||||
|
{ path: "/home/params/comparison", component: "@/pages/examination-comparison/subpage/params-comparison" },
|
||||||
|
{ path: "/home/knowledge/comparison", component: "@/pages/examination-comparison/subpage/knowledge-comparison" },
|
||||||
|
{ path: "/home/online/comparison", component: "@/pages/examination-comparison/subpage/online-comparison" },
|
||||||
|
{ path: "/home/template", component: "@/pages/template" },
|
||||||
|
{ path: "/home/test", component: "@/pages/test" },
|
||||||
|
// { path: "/home/backup", component: "@/pages/backup" },
|
||||||
|
{ path: "/home/system/data", component: "@/pages/export-data" },
|
||||||
|
{ path: "/home/system/account", component: "@/pages/admin-account" },
|
||||||
|
{ path: "/home/system/account/admin", component: "@/pages/admin-account/subpage/account" },
|
||||||
|
{ path: "/home/system/account/access", component: "@/pages/admin-account/subpage/access" },
|
||||||
|
{ path: "/home/system/setup", component: "@/pages/setup"},
|
||||||
|
{ path: "/home/system/setup/subject", component: "@/pages/setup/subpage/subject" },
|
||||||
|
{ path: "/home/system/setup/district", component: "@/pages/setup/subpage/district" },
|
||||||
|
{ path: "/home/system/setup/school", component: "@/pages/setup/subpage/school" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||||
|
extraBabelPlugins: process.env.NODE_ENV === 'production'
|
||||||
|
? ['babel-plugin-dynamic-import-node']
|
||||||
|
: [],
|
||||||
|
history: { type: 'hash' },
|
||||||
|
hash: true,
|
||||||
|
title: false,
|
||||||
|
npmClient: 'pnpm',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target:"https://studies.hmbigdata.com",
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: { '^/api': '/hly-huaian' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
17669
package-lock.json
generated
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"author": "赵晨 <chenson918@163.com>",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "umi dev",
|
||||||
|
"build": "umi build",
|
||||||
|
"postinstall": "umi setup",
|
||||||
|
"setup": "umi setup",
|
||||||
|
"start": "npm run dev"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.6.1",
|
||||||
|
"@ant-design/pro-components": "^2.8.6",
|
||||||
|
"@umijs/plugins": "^4.4.6",
|
||||||
|
"ahooks": "^3.8.4",
|
||||||
|
"antd": "^5.24.3",
|
||||||
|
"axios": "^1.8.3",
|
||||||
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"jspdf": "^2.5.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"umi": "^4.4.6",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.33",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"typescript": "^5.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
12701
pnpm-lock.yaml
generated
Normal file
20
src/apis/auth.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
export const userLogin = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/auth/login`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const logger = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/auth/savePathLog`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
185
src/apis/common.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
export const getSubjectList = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSubjectList`,
|
||||||
|
params:{
|
||||||
|
type:0,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSchoolAreaList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getDistrictListNew`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getCurrentSelected = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/queryNewExam`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getExam = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/base/queryExam`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getSubjectListAll = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSubjectListALL`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getGradeEnum = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getTermList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getSemesterEnum = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSemesterList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getExamEnum = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getExamNameList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNoSingleSubject = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSubjectListNoSingle`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSort = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSortItemList`,
|
||||||
|
params:{
|
||||||
|
stage:3,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPercent = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getRateItemList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getParamsType = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getParamTypeItemList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getNoSingleCombine = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSubjectListNoSingleCombine`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
export const getSubjectListCombineList =() => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getSubjectListCombine`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getDataExplain =(params) => {
|
||||||
|
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getExamDescription`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学段
|
||||||
|
export const getStageList =(params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/common/getStageWithRole`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学科列表
|
||||||
|
|
||||||
|
export const getSubjectListSelect =(params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/common/getSubjectList`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
128
src/apis/data.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//获取考试列表
|
||||||
|
export const getExamList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/common/getAllExams`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//创建考试
|
||||||
|
export const addExamList = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/upload_v2/createExam`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//获取考试
|
||||||
|
export const getExam = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/common/getExam`,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//修改考试
|
||||||
|
export const updateExam = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/upload_v2/updateExam`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//获取考试计算文件名
|
||||||
|
export const getExamFileName = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/upload_v2/getExamFile`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//上传考试计算文件
|
||||||
|
|
||||||
|
export const uploadFile = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/upload_v2/createSubjectDetail`,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 开始计算考试
|
||||||
|
export const startComputeExam = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/cal_v2/calExam`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算考试
|
||||||
|
export const afreshComputeExam = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/upload_v2/reCalExam`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除考试
|
||||||
|
export const deleteExam = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/cal_v2/delExam`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 获取编辑描述
|
||||||
|
export const getExamDescription = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/base/getExamDescription`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取分数线列表
|
||||||
|
export const getScoreLineList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/score/page`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存分数线
|
||||||
|
export const saveScoreLine = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/score/edit`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 删除分数线
|
||||||
|
export const deleteScoreLine = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/score/delete`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
27
src/apis/district.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
|
||||||
|
export const getDistrictList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getDistrictListNew`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveDistrict = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/system/saveDistrictNew`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDistrictDetail = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getDistrictById`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
17
src/apis/download.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const downloadTableData = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/download/downLoadExcel`,
|
||||||
|
responseType:"blob",
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
93
src/apis/exam.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
|
||||||
|
export const getExamRanking = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsCompare`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getPercentageRanking = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsCompareNumRate`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPercentageRankingView = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsCompareNumRateView`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getParamsRanking = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/compare/params/analysis`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getKnowledgeRanking = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsSubjectKnowledge`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getKnowledgeRankingAnalysis = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectQuestionDifficultByKnowLedgeV2`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOnlineRankingAnalysis = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/compare/score-line/analysis`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOnlineAnalysis = (data) =>{
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/compare/online/analysis`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
356
src/apis/leader.js
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
|
||||||
|
export const getOnlineAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/compare/online/analysis`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getSchoolAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamSortAnalysisDistrict`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getSchoolPercentAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamLimitRateAnalysisDistrict`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTopStudentAnalysis = (data) => {
|
||||||
|
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudent`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getCriticalStudentAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudentLj`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getBadSubjectStudentAnalysis = (data) => {
|
||||||
|
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudentYslk`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRashStudentAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudentMjs`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTeacherAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamTeacherSub`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTeacherZScoreAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamTeacherZscore`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTeacherTrace = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsTeacher`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTeacherGrade = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamsTeacherGrade`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getKnowledgePoint = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectKnowledge`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getQuestionParams = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectQuestion`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAnswerRate = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectQuestionAnswer`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLittleQuestion = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectQuestionDifficult`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//科目分布
|
||||||
|
export const getSubjectDistribution = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectDistribution`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//组合分布
|
||||||
|
export const getCombinationDistribution = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectCombinationDistribution`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//选考分析
|
||||||
|
export const getSelectedDistribution = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectCombinationAvg`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 赋分分析
|
||||||
|
export const getAssignDistribution = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamSubjectElectiveLevel`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//获取学生群体
|
||||||
|
export const getStudentGroup = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudentList`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//获取学生学情追踪
|
||||||
|
export const getStudentHistory = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisStudentHistory`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//获取学生成绩册
|
||||||
|
export const getStudentBook = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamStudentSub`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//获取学生各小题
|
||||||
|
export const getStudentTopic = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamStudentQuestion`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//获取学生知识点
|
||||||
|
export const getStudentKnowledge = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamStudentQuestionGroup`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//教师群体排名分析
|
||||||
|
export const getTeacherSort = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamTeacherSort`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//教师群体均分分析
|
||||||
|
export const getTeacherAvg = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamTeacherAvg`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//当前情况基本参数分析
|
||||||
|
export const getSchoolParams = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamAnalysisDistrict`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//教师个人的Z值分析
|
||||||
|
export const getSingleTeacherZ = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/analysisExamTeacherSingleZscore`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上线分数线
|
||||||
|
export const getOnlineScoreAnalysis = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/leader/nowExamSortAnalysisDistrictWithScore`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
148
src/apis/report.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
|
||||||
|
export const getReportTemplateList = (params)=> {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/getSystemReportList`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const saveReportTemplate = (data)=> {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/report/saveOrUpdateReportSystem`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteReportTemplate = (params)=> {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/delSysReport`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const setupStandardTemplate = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/setDefaultReport`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTemplateComponentList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/getReportItemList`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const saveTemplateComponent = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/report/saveOrUpdateReportItem`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteComponent = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/delReportItem`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getSystemComponentList = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/getReportItemModelList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function 获取个人报告
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getPersonalReport = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/getAllReportList`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function 复制模板
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const copyTemplate = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/copyReport`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function 获取模板详情
|
||||||
|
*/
|
||||||
|
export const getTemplateDetail = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/report/getReport`,
|
||||||
|
params:{
|
||||||
|
...params,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
48
src/apis/role.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
|
||||||
|
export const getRoleList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/role/getRoleList`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getRoleSelect = () => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/role/getAllRoleList`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除角色
|
||||||
|
export const deleteRole = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/role/deleteRole`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存角色
|
||||||
|
export const saveRole = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/role/saveRole`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取权限树
|
||||||
|
|
||||||
|
export const getRoleTree = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/role/getRoleInfo`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
9
src/apis/rqg.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
export const getTeacherRateSort = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/rqg/dfl/sort`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
38
src/apis/school.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
export const getSchoolList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getSchoolList`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const saveSchool = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/system/saveSchool`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSchoolDetail = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getSchoolById`,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getSchoolDistrictList = (params)=> {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getDistrictListOptionNew`,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
29
src/apis/subject.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config';
|
||||||
|
|
||||||
|
export const getSubjectList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getSubjectList`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const saveSubject = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/exam-data/system/saveSubject`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSubjectDetail = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/exam-data/system/getSubjectDetail`,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
87
src/apis/user.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import {BASE_URL} from '@/config'
|
||||||
|
|
||||||
|
export const getUserInfo = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/getUserInfo`,
|
||||||
|
params:{
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePassword = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/user/resetMyPassword`,
|
||||||
|
data:{
|
||||||
|
...data,
|
||||||
|
stage:3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getUserList = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/getUserList`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//重置密码
|
||||||
|
export const resetPassword = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/resetPassword`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除用户
|
||||||
|
export const deleteUser = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/deleteUser`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//保存用户
|
||||||
|
export const saveUser = (data) => {
|
||||||
|
return request({
|
||||||
|
method: 'post',
|
||||||
|
url: `${BASE_URL}/user/saveUser`,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//获取区县列表下拉框
|
||||||
|
export const getDistrictSelect = () => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/getBaseDistrict`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//获取学校列表下拉框
|
||||||
|
export const getSchoolSelect = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/getBaseSchool`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUserDetail = (params) => {
|
||||||
|
return request({
|
||||||
|
method: 'get',
|
||||||
|
url: `${BASE_URL}/user/getUser`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
BIN
src/assets/file.xls
Normal file
BIN
src/assets/icon/analysis-catalogue-1.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/icon/analysis-catalogue-2.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/icon/analysis-catalogue-3.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/icon/analysis-catalogue-4.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/icon/analysis-catalogue-5.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/icon/analysis-catalogue-6.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/icon/analysis-catalogue-7.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/icon/analysis-catalogue-8.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/icon/analysis-navigation-icon.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/assets/icon/download.png
Normal file
|
After Width: | Height: | Size: 286 B |
BIN
src/assets/icon/edit.png
Normal file
|
After Width: | Height: | Size: 309 B |
BIN
src/assets/icon/login-account.png
Normal file
|
After Width: | Height: | Size: 484 B |
BIN
src/assets/icon/login-password.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
src/assets/icon/login-verification-code.png
Normal file
|
After Width: | Height: | Size: 568 B |
BIN
src/assets/image/analysis-explain-background.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
src/assets/image/analysis-home-background.png
Normal file
|
After Width: | Height: | Size: 881 KiB |
BIN
src/assets/image/avatar.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/image/down.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
src/assets/image/login-background-image.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
src/assets/image/login-code.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/assets/image/login-footer-logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/image/memoir1.png
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
src/assets/image/memoir2.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
src/assets/image/memoir3.png
Normal file
|
After Width: | Height: | Size: 507 KiB |
BIN
src/assets/image/statistics-exam.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/image/statistics-grow.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src/assets/image/statistics-message.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
src/assets/image/statistics-online.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/image/statistics-performance.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/assets/image/statistics-subject.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/image/swipe1.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
src/assets/image/swipe2.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/assets/image/swipe3.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
src/assets/image/swipe4.jpg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
src/assets/image/up.png
Normal file
|
After Width: | Height: | Size: 760 B |
493
src/components/Echarts/index.jsx
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
import { useEffect, useRef, useState, useImperativeHandle, forwardRef,memo } from "react";
|
||||||
|
import { Button, Modal, Image, Space, Empty,Tag } from "antd";
|
||||||
|
import Flex from "@/components/Flex";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import "./index.less";
|
||||||
|
|
||||||
|
let chartInstance = null;
|
||||||
|
|
||||||
|
const Echart = ({
|
||||||
|
data = {},
|
||||||
|
type = "line",
|
||||||
|
hasExport = true,
|
||||||
|
hasTable = true,
|
||||||
|
limitNum = 0,
|
||||||
|
lengButton = [],
|
||||||
|
isShow = true,
|
||||||
|
onTableClick = () => { },
|
||||||
|
text='',
|
||||||
|
isNoneTag = false,
|
||||||
|
isShowTag = true,
|
||||||
|
}, ref) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
const chartInstanceRef = useRef(null); // 新增用于存储图表实例的 ref
|
||||||
|
const [selectButton, setSelectButton] = useState(lengButton);
|
||||||
|
const [activeButtons, setActiveButtons] = useState([]); // 添加新的状态来跟踪选中的按钮
|
||||||
|
const [dataCopy, setDataCopy] = useState(); // 添加新的状态来跟踪选中的按钮
|
||||||
|
const [option, setOption] = useState({}); //用于渲染图表的
|
||||||
|
const [echartImg, setEchartImg] = useState(); //用于导出图表 图片的
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
exportChart,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!lengButton?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelectButton(lengButton);
|
||||||
|
if(isNoneTag){
|
||||||
|
setActiveButtons(lengButton);
|
||||||
|
}else{
|
||||||
|
setActiveButtons([lengButton[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [lengButton,isNoneTag]);
|
||||||
|
|
||||||
|
|
||||||
|
//控制筛选按钮
|
||||||
|
const toggleButton = (data) => {
|
||||||
|
setActiveButtons((prev) => {
|
||||||
|
if (prev.includes(data)) {
|
||||||
|
// 如果只剩一个按钮,不允许取消选中
|
||||||
|
if (prev.length === 1) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return prev.filter((i) => i !== data);
|
||||||
|
} else {
|
||||||
|
// 检查是否达到最大限制
|
||||||
|
if (limitNum > 0 && prev.length >= limitNum) {
|
||||||
|
// 如果已达到限制,返回原数组
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
// 未达到限制,添加新按钮
|
||||||
|
return [...prev, data];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理二维折线和柱状图和饼图
|
||||||
|
const createOption = () => {
|
||||||
|
let option = {};
|
||||||
|
const colors = [
|
||||||
|
"#91cc75",
|
||||||
|
"#fac858",
|
||||||
|
"#ee6666",
|
||||||
|
"#73c0de",
|
||||||
|
"#3ba272",
|
||||||
|
"#fc8452",
|
||||||
|
"#9a60b4",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (type === "radar") {
|
||||||
|
|
||||||
|
|
||||||
|
option = {
|
||||||
|
color: colors,
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: dataCopy?.radarText.yChartData?.map((item) => item.name),
|
||||||
|
right: 10,
|
||||||
|
top: 16,
|
||||||
|
selectedMode: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
},
|
||||||
|
radar: {
|
||||||
|
indicator: dataCopy.radarText.indicator,
|
||||||
|
shape: "polygon",
|
||||||
|
splitNumber: 5,
|
||||||
|
axisName: {
|
||||||
|
color: "#666",
|
||||||
|
fontSize: 10,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: "rgba(238, 238, 238, 0.8)",
|
||||||
|
opacity: 0.2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitArea: {
|
||||||
|
show: false, // 关闭区域填充
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: "radar",
|
||||||
|
data: dataCopy?.radarText?.yChartData?.map((item, index) => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.data,
|
||||||
|
itemStyle: {
|
||||||
|
color: colors[index % colors.length],
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: colors[index % colors.length], // 设置线条颜色
|
||||||
|
},
|
||||||
|
// 移除 areaStyle 配置
|
||||||
|
emphasis: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (type == "pie") {
|
||||||
|
// 计算总数
|
||||||
|
const totalSum = dataCopy?.yChartData?.reduce((sum, item) => {
|
||||||
|
return sum + item.data.reduce((a, b) => a + b, 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
option = {
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
orient: "vertical",
|
||||||
|
// 简化图例数据处理
|
||||||
|
data:
|
||||||
|
dataCopy?.yChartData?.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
icon: "circle", // 设置图例图标为圆形
|
||||||
|
})) || [],
|
||||||
|
right: "18%",
|
||||||
|
top: 16,
|
||||||
|
selectedMode: true,
|
||||||
|
textStyle: {
|
||||||
|
color: "#333",
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
formatter: (name) => {
|
||||||
|
const data = dataCopy?.yChartData?.find(
|
||||||
|
(item) => item.name === name
|
||||||
|
);
|
||||||
|
if (!data) return name;
|
||||||
|
return `${name}${data?.data?.[0]}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
formatter: "{b}:{c}%",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
radius: ["40%", "70%"],
|
||||||
|
type: type,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: (name) => {
|
||||||
|
return `${name?.data?.name}${name?.data?.value}%`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data:
|
||||||
|
dataCopy?.yChartData?.map((key) => {
|
||||||
|
return {
|
||||||
|
name: key.name,
|
||||||
|
value: key?.data?.[0],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
option = {
|
||||||
|
//图例 不用 纯展示
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: dataCopy?.yChartData?.map((item) => item.name),
|
||||||
|
right: 10, // 设置右侧边距
|
||||||
|
top: 24, // 设置顶部边距
|
||||||
|
selectedMode: false,
|
||||||
|
textStyle:{
|
||||||
|
fontSize:12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label:{
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 12,
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.value}`
|
||||||
|
},
|
||||||
|
fontSize: 10,
|
||||||
|
rich: {
|
||||||
|
name: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// dataZoom: [
|
||||||
|
// {
|
||||||
|
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||||
|
// show: true,
|
||||||
|
// start: 0, // 初始起点
|
||||||
|
// end: 30, // 初始终点
|
||||||
|
// handleSize: 8,
|
||||||
|
// xAxisIndex: [0],
|
||||||
|
// height: 24
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 35,
|
||||||
|
interval: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
barGap: '16%',
|
||||||
|
barWidth: "8%",
|
||||||
|
// 布局
|
||||||
|
grid: {
|
||||||
|
left: "16px", // 左边距
|
||||||
|
right: "4%", // 右边距
|
||||||
|
bottom: "48px", // 底部边距,留出空间显示 x 轴标签
|
||||||
|
top: "88px", // 顶部边距
|
||||||
|
containLabel: true, // 确保刻度标签在区域内
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
...dataCopy,
|
||||||
|
xAxis: {
|
||||||
|
...dataCopy?.xAxis,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: dataCopy?.xAxis?.data?.length > 6 ? 45 : 0,
|
||||||
|
interval: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis:{
|
||||||
|
...dataCopy?.yAxis,
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize:14,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: dataCopy?.yChartData?.map((item) => ({
|
||||||
|
type,
|
||||||
|
// barGap: 0,
|
||||||
|
barWidth: '8%', // 控制柱子的宽度
|
||||||
|
barGap: '10%' , // 控制柱子之间的间距
|
||||||
|
emphasis: {
|
||||||
|
focus: "series",
|
||||||
|
},
|
||||||
|
name: item.name,
|
||||||
|
data: item.data
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
//下载图片
|
||||||
|
const downloadImg = () => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = `图表_${new Date().getTime()}.png`;
|
||||||
|
link.href = echartImg;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
setEchartImg("");
|
||||||
|
};
|
||||||
|
// 导出图片
|
||||||
|
const exportChart = () => {
|
||||||
|
const chartInstance = echarts.getInstanceByDom(chartRef.current);
|
||||||
|
if (chartInstance) {
|
||||||
|
const base64 = chartInstance.getDataURL({
|
||||||
|
type: "png",
|
||||||
|
pixelRatio: 2, // 导出的图片分辨率比例
|
||||||
|
backgroundColor: "#fff", // 导出图片的背景色
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartImg(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataCopy) {
|
||||||
|
createOption();
|
||||||
|
}
|
||||||
|
}, [dataCopy, activeButtons]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// setSelectButton(lengButton);
|
||||||
|
// }, [lengButton]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activeButtons.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 根据选中的按钮筛选数据
|
||||||
|
let filteredData = [];
|
||||||
|
if (type === "radar") {
|
||||||
|
filteredData = data?.radarText?.yChartData?.filter((item) =>
|
||||||
|
activeButtons.includes(item.name)
|
||||||
|
);
|
||||||
|
setDataCopy({
|
||||||
|
...data,
|
||||||
|
radarText: {
|
||||||
|
...data.radarText,
|
||||||
|
yChartData: filteredData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(type === 'pie'){
|
||||||
|
setDataCopy({ ...data});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filteredData = data?.yChartData?.filter((item) =>
|
||||||
|
activeButtons.includes(item.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
setDataCopy({ ...data, yChartData: filteredData });
|
||||||
|
}
|
||||||
|
}, [activeButtons, data]);
|
||||||
|
|
||||||
|
//图表初始化
|
||||||
|
useEffect(() => {
|
||||||
|
// 如果 DOM 元素存在,初始化图表
|
||||||
|
if (chartRef.current) {
|
||||||
|
// 如果已有实例,先销毁
|
||||||
|
if (chartInstanceRef.current) {
|
||||||
|
chartInstanceRef.current.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
chartInstanceRef.current = echarts.init(chartRef.current);
|
||||||
|
|
||||||
|
if (Object.values(option)?.length) {
|
||||||
|
chartInstanceRef.current?.setOption(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 响应式调整
|
||||||
|
const resizeHandler = () => chartInstanceRef.current?.resize();
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
return () => {
|
||||||
|
if (chartInstanceRef.current) {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
chartInstanceRef.current.dispose();
|
||||||
|
chartInstanceRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [option]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 20 }}>
|
||||||
|
{isShowTag ? (
|
||||||
|
<Space style={{marginBottom: 16}} wrap>
|
||||||
|
{!!Array.isArray(selectButton) &&
|
||||||
|
isShow &&
|
||||||
|
!!selectButton.length > 0 &&
|
||||||
|
selectButton.map((but, index) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
key={index}
|
||||||
|
color={activeButtons.includes(but) ? "green" : "default"}
|
||||||
|
onClick={() => toggleButton(but)}
|
||||||
|
style={{cursor: 'pointer'}}
|
||||||
|
>
|
||||||
|
{but}
|
||||||
|
</Tag>
|
||||||
|
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
) : null}
|
||||||
|
{/* 图表 */}
|
||||||
|
{lengButton?.length > 0 ? (<div ref={chartRef} style={{ width: "100%", height: "446px" }} />) : <Empty description='暂无图表数据' />}
|
||||||
|
{/* 导出和显示为表格 */}
|
||||||
|
<div>
|
||||||
|
{hasExport && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 20 }}
|
||||||
|
disabled={!lengButton?.length}
|
||||||
|
onClick={exportChart}
|
||||||
|
>
|
||||||
|
导出图表
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{hasTable && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
//关闭图表
|
||||||
|
onTableClick(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
显示为表格
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* 导出为图片 */}
|
||||||
|
{echartImg && (
|
||||||
|
<Modal
|
||||||
|
title="导出图表"
|
||||||
|
className="custom-modal"
|
||||||
|
width={"80vw"}
|
||||||
|
open={true}
|
||||||
|
okText="下载"
|
||||||
|
onCancel={() => {
|
||||||
|
setEchartImg("");
|
||||||
|
}}
|
||||||
|
onOk={downloadImg}
|
||||||
|
>
|
||||||
|
<Flex style={{ height: "100%" }} alignItems="center" justify="center">
|
||||||
|
<Image src={echartImg} width={"100%"} />
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(forwardRef(Echart));
|
||||||
6
src/components/Echarts/index.less
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.custom-modal {
|
||||||
|
.ant-modal-body {
|
||||||
|
height: 70vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/components/Flex/index.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type {CSSProperties} from 'react'
|
||||||
|
|
||||||
|
export interface IFlexProps {
|
||||||
|
direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
||||||
|
wrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||||
|
justify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
|
||||||
|
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch';
|
||||||
|
|
||||||
|
itemSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch';
|
||||||
|
itemOrder?: number;
|
||||||
|
itemGrow?: number;
|
||||||
|
|
||||||
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
|
|
||||||
|
onClick?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Flex extends React.Component<IFlexProps, any> {
|
||||||
|
}
|
||||||
60
src/components/Flex/index.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, {memo} from "react";
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const Index = memo((props) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
direction, wrap, justify, alignItems,
|
||||||
|
itemOrder, itemSelf, itemGrow, onClick,
|
||||||
|
className, style, ...other
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let newStyle = {};
|
||||||
|
if (direction) {
|
||||||
|
newStyle['flexDirection'] = direction;
|
||||||
|
newStyle['WebkitFlexDirection'] = direction;
|
||||||
|
}
|
||||||
|
if (wrap) {
|
||||||
|
newStyle['flexWrap'] = wrap;
|
||||||
|
newStyle['WebkitFlexWrap'] = wrap;
|
||||||
|
}
|
||||||
|
if (justify) {
|
||||||
|
newStyle['justifyContent'] = justify;
|
||||||
|
newStyle['WebkitJustifyContent'] = justify;
|
||||||
|
}
|
||||||
|
if (alignItems) {
|
||||||
|
newStyle['alignItems'] = alignItems;
|
||||||
|
newStyle['WebkitAlignItems'] = alignItems;
|
||||||
|
}
|
||||||
|
if (itemSelf) {
|
||||||
|
newStyle['alignSelf'] = itemSelf;
|
||||||
|
newStyle['WebkitAlignSelf'] = itemSelf;
|
||||||
|
}
|
||||||
|
if (itemOrder) {
|
||||||
|
newStyle['order'] = itemOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemGrow) {
|
||||||
|
newStyle['flexGrow'] = itemGrow;
|
||||||
|
newStyle['WebkitFlexGrow'] = itemGrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clazz = styles.flex;
|
||||||
|
if (className) {
|
||||||
|
clazz = clazz + ' ' + className;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
{...other} className={clazz} style={{...newStyle, ...style}} onClick={onClick}
|
||||||
|
>{props.children}</div>
|
||||||
|
});
|
||||||
|
|
||||||
|
const Flex = (props) => {
|
||||||
|
return <Index {...props} />
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
60
src/components/Flex/index.jsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, {memo} from "react";
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const Index = memo((props) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
direction, wrap, justify, alignItems,
|
||||||
|
itemOrder, itemSelf, itemGrow, onClick,
|
||||||
|
className, style, ...other
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let newStyle = {};
|
||||||
|
if (direction) {
|
||||||
|
newStyle['flexDirection'] = direction;
|
||||||
|
newStyle['WebkitFlexDirection'] = direction;
|
||||||
|
}
|
||||||
|
if (wrap) {
|
||||||
|
newStyle['flexWrap'] = wrap;
|
||||||
|
newStyle['WebkitFlexWrap'] = wrap;
|
||||||
|
}
|
||||||
|
if (justify) {
|
||||||
|
newStyle['justifyContent'] = justify;
|
||||||
|
newStyle['WebkitJustifyContent'] = justify;
|
||||||
|
}
|
||||||
|
if (alignItems) {
|
||||||
|
newStyle['alignItems'] = alignItems;
|
||||||
|
newStyle['WebkitAlignItems'] = alignItems;
|
||||||
|
}
|
||||||
|
if (itemSelf) {
|
||||||
|
newStyle['alignSelf'] = itemSelf;
|
||||||
|
newStyle['WebkitAlignSelf'] = itemSelf;
|
||||||
|
}
|
||||||
|
if (itemOrder) {
|
||||||
|
newStyle['order'] = itemOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemGrow) {
|
||||||
|
newStyle['flexGrow'] = itemGrow;
|
||||||
|
newStyle['WebkitFlexGrow'] = itemGrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clazz = styles.flex;
|
||||||
|
if (className) {
|
||||||
|
clazz = clazz + ' ' + className;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
{...other} className={clazz} style={{...newStyle, ...style}} onClick={onClick}
|
||||||
|
>{props.children}</div>
|
||||||
|
});
|
||||||
|
|
||||||
|
const Flex = (props) => {
|
||||||
|
return <Index {...props} />
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
15
src/components/Flex/index.less
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.flex {
|
||||||
|
display: -webkit-flex; /* Safari */
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
-webkit-flex-shrink: 0;
|
||||||
|
|
||||||
|
}
|
||||||
141
src/components/SchoolChoice/index.jsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Select, Space, Button, Modal } from 'antd';
|
||||||
|
import { getSchoolAreaList } from '@/apis/common'
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Function} onChange
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SchoolChoice = (props) => {
|
||||||
|
|
||||||
|
const { onChange = () => { } } = props
|
||||||
|
|
||||||
|
const typeColorEnum = {
|
||||||
|
"Ⅰ类": "#722ED1",
|
||||||
|
"Ⅱ类": "#FADB14",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const [schoolTypeList, setSchoolTypeList] = useState([]);
|
||||||
|
const [schoolList, setSchoolList] = useState([]);
|
||||||
|
|
||||||
|
const [value, setValue] = useState(null);
|
||||||
|
const [label, setLabel] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const [selectedSchoolList, setSelectedSchoolList] = useState([]);
|
||||||
|
const [selectedSchoolTypeList, setSelectedSchoolTypeList] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSchoolAreaList({ type: 1 });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSchoolTypeList(res?.data || []);
|
||||||
|
setLabel(res?.data?.[0]?.districtName);
|
||||||
|
onChange(res?.data?.[0]?.schoolList?.map(item => item?.id));
|
||||||
|
}
|
||||||
|
const result = await getSchoolAreaList({ type: 0 });
|
||||||
|
if (result?.code === 200) {
|
||||||
|
setSchoolList(result?.data || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
setOpen(false);
|
||||||
|
const ids = Array.from(new Set([...selectedSchoolTypeList, ...selectedSchoolList]));
|
||||||
|
onChange(ids);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.school_select}>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
fieldNames={{
|
||||||
|
label: 'districtName',
|
||||||
|
value: 'id'
|
||||||
|
}}
|
||||||
|
value={label}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
allowClear
|
||||||
|
options={schoolTypeList}
|
||||||
|
placeholder='请选择学校'
|
||||||
|
onChange={(value, options) => {
|
||||||
|
if (!value) {
|
||||||
|
setSelectedSchoolTypeList([]);
|
||||||
|
setLabel(null);
|
||||||
|
setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLabel(options?.districtName);
|
||||||
|
const ids = Array.from(new Set([...options?.schoolList?.map(item => item?.id), ...selectedSchoolList]));
|
||||||
|
onChange(ids);
|
||||||
|
setSelectedSchoolTypeList(options?.schoolList?.map(item => item?.id));
|
||||||
|
|
||||||
|
}} />
|
||||||
|
<Button type="default" onClick={() => setOpen(true)}>自选学校范围</Button>
|
||||||
|
</Space>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
maskClosable={false}
|
||||||
|
centered
|
||||||
|
title='选择学校'
|
||||||
|
width={800}
|
||||||
|
styles={{ body: { height: "60vh", overflowY: "scroll" } }}
|
||||||
|
onCancel={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
onOk={handleOk}
|
||||||
|
>
|
||||||
|
{schoolList?.length > 0 ? schoolList?.map((item) => {
|
||||||
|
return (
|
||||||
|
<section className={styles.area} key={item?.id}>
|
||||||
|
<section className={styles.area_title}>
|
||||||
|
{item?.districtName}
|
||||||
|
</section>
|
||||||
|
<section className={styles.area_list}>
|
||||||
|
{item?.schoolList?.length > 0 ? item?.schoolList?.map((element) => {
|
||||||
|
|
||||||
|
const isSelected = selectedSchoolList.includes(element?.id);
|
||||||
|
return (
|
||||||
|
|
||||||
|
<section style={{ backgroundColor: isSelected ? "#4bbb6b" : "#fff", color: isSelected ? "#fff" : "#333" }} className={styles.area_list_item} key={element?.id} onClick={() => {
|
||||||
|
setSelectedSchoolList(prev => {
|
||||||
|
if (isSelected) {
|
||||||
|
return prev.filter(item => item !== element?.id)
|
||||||
|
} else {
|
||||||
|
return [...prev, element?.id]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
<span className={styles.area_list_item_name}>{element?.cname} </span>
|
||||||
|
<span className={styles.area_list_item_type} style={{ backgroundColor: typeColorEnum?.[element?.schoolType] }}>{element?.schoolType}</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
)
|
||||||
|
}) : null}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}) : null}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SchoolChoice;
|
||||||
41
src/components/SchoolChoice/index.less
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.school {
|
||||||
|
&_select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.area {
|
||||||
|
margin-top: 8px;
|
||||||
|
&_title {
|
||||||
|
border-left: 3px solid #4bbb6b;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-left: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_list {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&_item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display:flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&_type {
|
||||||
|
padding: 2px 6px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/components/ScoreLineSelect/index.jsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useState} from 'react';
|
||||||
|
import { Space, Button, InputNumber, Tag, message } from 'antd';
|
||||||
|
import { CloseOutlined } from '@ant-design/icons'
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ScoreLineSelect = (props) => {
|
||||||
|
const { onChange = () => { },styles={padding: 8,} } = props;
|
||||||
|
const [score, setScore] = useState(null);
|
||||||
|
const [scoreList, setScoreList] = useState([]);
|
||||||
|
|
||||||
|
const handleClose = (item) => {
|
||||||
|
const newList = scoreList.filter(i => i.id !== item.id);
|
||||||
|
setScoreList(newList);
|
||||||
|
onChange(newList.map(item => item.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.score_line_select} style={styles}>
|
||||||
|
设置分数线:
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
value={score}
|
||||||
|
onChange={(value) => setScore(value)}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
分以上
|
||||||
|
<Button onClick={() => {
|
||||||
|
if (typeof score !== 'number') {
|
||||||
|
message.error('请输入数字');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (scoreList.findIndex(item => item.id === score) !== -1) {
|
||||||
|
message.error('该分数已存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setScore(null);
|
||||||
|
const newItem = {
|
||||||
|
id: score,
|
||||||
|
label: `${score}分以上`
|
||||||
|
};
|
||||||
|
const newList = [...scoreList, newItem];
|
||||||
|
setScoreList(newList);
|
||||||
|
onChange(newList.map(item => item.id));
|
||||||
|
}}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
<Space wrap style={{ width: '100%', marginTop: 8 }}>
|
||||||
|
{scoreList?.map((item, index) => (
|
||||||
|
<Tag key={index} style={{ marginTop: 8 }}>
|
||||||
|
{item?.label} <CloseOutlined style={{ cursor: 'pointer' }} onClick={() => handleClose(item)} />
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScoreLineSelect;
|
||||||
10
src/components/ScoreLineSelect/index.less
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.score{
|
||||||
|
&_line{
|
||||||
|
&_select{
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
648
src/components/ScreenPage/index.jsx
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
import Flex from "@/components/Flex";
|
||||||
|
import less from "./index.less";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Modal, Select, ConfigProvider } from "antd";
|
||||||
|
import { getSubjectList, getSchoolAreaList, getCurrentSelected, getGradeEnum, getSemesterEnum, getExamEnum, getNoSingleSubject,getSort,getExam } from "@/apis/common";
|
||||||
|
const ScreenPage = ({
|
||||||
|
isShow = true,
|
||||||
|
isSingle = true, //是否单选
|
||||||
|
isTeacher = false,//是否为老师相关的
|
||||||
|
onChange = () => { },
|
||||||
|
saveshow = () => { },
|
||||||
|
isShowSubject = false, //是否显示学科
|
||||||
|
isShowSort = false,//是否显示排名
|
||||||
|
isShowSchool = true, //是否显示学校
|
||||||
|
onSendSortList = () => { }, //是否发送排名
|
||||||
|
}) => {
|
||||||
|
const [viewIsShow, setViewIsShow] = useState(isShow);
|
||||||
|
const [grade, setGrade] = useState([]); //届别
|
||||||
|
const [currentGrade, setCurrentGrade] = useState([]); //当前属别
|
||||||
|
const [semester, setSemester] = useState([]); //学期
|
||||||
|
const [currentSemester, setCurrentSemester] = useState([]); //当前学期
|
||||||
|
const [examination, setExamination] = useState([]); //考试
|
||||||
|
const [currentExamination, setCurrentExamination] = useState([]); //当前考试
|
||||||
|
const [school, setSchool] = useState([]); //学校
|
||||||
|
const [currentSchool, setCurrentSchool] = useState([]); //当前学校
|
||||||
|
const [currentDiscipline, setCurrentDiscipline] = useState([]); //当前学科
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [chooseList, setChooseList] = useState([]); //自选学校列表
|
||||||
|
|
||||||
|
const [sortList, setSortList] = useState([]); //排名列表
|
||||||
|
const [currentSort, setCurrentSort] = useState([]); //当前排名
|
||||||
|
|
||||||
|
const [nonSingleSubject, setNonSingleSubject] = useState([]); //非单一学科
|
||||||
|
|
||||||
|
const [singleSubjectOptions, setSingleSubjectOptions] = useState([]);
|
||||||
|
const [optionSchoolList, setOptionSchoolList] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
// 考试id
|
||||||
|
const [examId, setExamId] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setViewIsShow(isShow);
|
||||||
|
}, [isShow]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isShowSubject) {
|
||||||
|
singleSubjectListInit();
|
||||||
|
}
|
||||||
|
}, [isShowSubject]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if(isShowSort){
|
||||||
|
// sortInit();
|
||||||
|
// }
|
||||||
|
// }, [isShowSort]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
optionalSchoolInit();
|
||||||
|
getCurrentSelectedInit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// //获取排名
|
||||||
|
|
||||||
|
// const sortInit = async () => {
|
||||||
|
// try {
|
||||||
|
// const res = await getSort();
|
||||||
|
// if(res?.code === 200){
|
||||||
|
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log(error,'get-sort-error');
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
//获取单一学科
|
||||||
|
const singleSubjectListInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectList();
|
||||||
|
|
||||||
|
if (res?.code === 200) {
|
||||||
|
|
||||||
|
setSingleSubjectOptions(() => {
|
||||||
|
return res?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
value: item?.id,
|
||||||
|
label: item?.cname,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, "get-subject-list-error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取自选范围学校
|
||||||
|
const optionalSchoolInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSchoolAreaList({ type: 0 });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setOptionSchoolList(res?.data || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, "get-option-school-list-error");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前考试
|
||||||
|
const getCurrentSelectedInit = async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const grade = await getGradeEnum();
|
||||||
|
const semester = await getSemesterEnum();
|
||||||
|
const exam = await getExamEnum();
|
||||||
|
const subject = await getNoSingleSubject();
|
||||||
|
|
||||||
|
const res = await getCurrentSelected();
|
||||||
|
const res2 = await getSchoolAreaList({ type: 1 });
|
||||||
|
|
||||||
|
let sort;
|
||||||
|
if(isShowSort){
|
||||||
|
const res = await getSort();
|
||||||
|
if(res?.code === 200){
|
||||||
|
sort = res?.data || [];
|
||||||
|
setSortList(res?.data || []);
|
||||||
|
setCurrentSort([res?.data?.[0]]);
|
||||||
|
onSendSortList && onSendSortList(res?.data || []);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setNonSingleSubject(() => {
|
||||||
|
let list = subject?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.cname
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(isTeacher){
|
||||||
|
list = list?.filter(item=>item.name !== '总分');
|
||||||
|
}
|
||||||
|
return [...list, {
|
||||||
|
id: "single",
|
||||||
|
name: "单一学科",
|
||||||
|
type: "choose",
|
||||||
|
},]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
setGrade(() => {
|
||||||
|
return grade?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setSemester(() => {
|
||||||
|
return semester?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
setExamination(() => {
|
||||||
|
return exam?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setCurrentGrade([{ name: res?.data?.term }]);
|
||||||
|
setCurrentSemester([{ name: res?.data?.semester }]);
|
||||||
|
setCurrentExamination([{ name: res?.data?.examName }]);
|
||||||
|
setExamId(res?.data?.id);
|
||||||
|
const element = res2?.data?.find(item => item.districtName === '全部学校');
|
||||||
|
setCurrentSchool([element]);
|
||||||
|
let totalPoints = subject?.data?.map(item=>{
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
name: item.cname
|
||||||
|
}
|
||||||
|
})?.find(item=>item.cname === '总分');
|
||||||
|
if(isTeacher){
|
||||||
|
const list = subject?.data?.filter(item=>item.cname !== '总分')?.map(item=>{
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
name: item.cname
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setCurrentDiscipline([list?.[0]]);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
setCurrentDiscipline([totalPoints])
|
||||||
|
}
|
||||||
|
setSchool(() => {
|
||||||
|
return [
|
||||||
|
...res2.data,
|
||||||
|
{ id: "area", districtName: "自选学校范围", type: "choose" },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const noTotal = subject?.data?.filter(item=>item.cname !== '总分')?.map(item=>{
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
name: item.cname
|
||||||
|
}
|
||||||
|
})?.[0];
|
||||||
|
if(isShowSort){
|
||||||
|
onChange &&
|
||||||
|
onChange({
|
||||||
|
currentGrade: [{ name: res?.data?.term }],
|
||||||
|
currentSemester: [{ name: res?.data?.semester }],
|
||||||
|
currentExamination: [{ name: res?.data?.examName }],
|
||||||
|
currentSchool: [element],
|
||||||
|
currentDiscipline:[isTeacher ? noTotal : totalPoints],
|
||||||
|
examId: res?.data?.id,
|
||||||
|
currentSort:sort?.[0],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange &&
|
||||||
|
onChange({
|
||||||
|
currentGrade: [{ name: res?.data?.term }],
|
||||||
|
currentSemester: [{ name: res?.data?.semester }],
|
||||||
|
currentExamination: [{ name: res?.data?.examName }],
|
||||||
|
currentSchool: [element],
|
||||||
|
currentDiscipline:[isTeacher ? noTotal : totalPoints],
|
||||||
|
examId: res?.data?.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'get-current-selected-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
/**
|
||||||
|
* 自选学校
|
||||||
|
* @param record
|
||||||
|
*/
|
||||||
|
const updateModal = (chooseList, schoolList) => {
|
||||||
|
let datalist = schoolList || [];
|
||||||
|
let colorList = {
|
||||||
|
"Ⅰ类": "#722ED1",
|
||||||
|
"Ⅱ类": "#FADB14",
|
||||||
|
};
|
||||||
|
let chooselist = [...chooseList];
|
||||||
|
let modal = null;
|
||||||
|
const showModal = () => {
|
||||||
|
let content = (
|
||||||
|
<Flex direction="column" className={less.schoolsList}>
|
||||||
|
{datalist.map((item) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.id}
|
||||||
|
className={less.schoolsitem}
|
||||||
|
direction="column"
|
||||||
|
>
|
||||||
|
<Flex className={less.address}>{item.districtName}</Flex>
|
||||||
|
<Flex className={less.children} wrap="wrap">
|
||||||
|
{item?.schoolList?.map((child) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={child?.id}
|
||||||
|
className={
|
||||||
|
chooselist?.find((it) => it?.id == child?.id)
|
||||||
|
? less.childchecked
|
||||||
|
: less.child
|
||||||
|
}
|
||||||
|
alignItems="center"
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
chooselist?.find((it) => it?.id == child?.id)
|
||||||
|
) {
|
||||||
|
chooselist = chooselist.filter(
|
||||||
|
(it) => it?.id != child?.id
|
||||||
|
);
|
||||||
|
showModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chooselist = [...chooselist, child];
|
||||||
|
showModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{child?.cname}
|
||||||
|
<Flex
|
||||||
|
className={less.color}
|
||||||
|
style={{ backgroundColor: colorList[child?.schoolType] }}
|
||||||
|
>
|
||||||
|
{child?.schoolType}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modal) {
|
||||||
|
modal.update({ content });
|
||||||
|
} else {
|
||||||
|
modal = Modal.confirm({
|
||||||
|
title: "选择学校",
|
||||||
|
className: "globalModal",
|
||||||
|
okText: "确定",
|
||||||
|
cancelText: "取消",
|
||||||
|
content,
|
||||||
|
width: 900,
|
||||||
|
closable: true,
|
||||||
|
styles: { content: { height: "60vh", overflow: "auto" } },
|
||||||
|
onOk: () => {
|
||||||
|
setChooseList(chooselist);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
showModal();
|
||||||
|
};
|
||||||
|
const titlePage = (name) => {
|
||||||
|
return (
|
||||||
|
<Flex>
|
||||||
|
{name}
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
color: "#c0c0c0",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
className={less.frame}
|
||||||
|
direction="column"
|
||||||
|
style={{ display: viewIsShow ? "flex" : "none" }}
|
||||||
|
>
|
||||||
|
<Flex className={less.chooseCard} direction="column">
|
||||||
|
<Flex className={less.chooseItem}>
|
||||||
|
{titlePage("届别")}
|
||||||
|
<Flex className={less.list}>
|
||||||
|
{grade?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.name}
|
||||||
|
className={
|
||||||
|
currentGrade?.find((it) => it?.name == item?.name)
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSingle) {
|
||||||
|
setCurrentGrade([item]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentGrade?.find((it) => it?.name == item?.name)) {
|
||||||
|
setCurrentGrade(
|
||||||
|
currentGrade.filter((it) => it?.name != item?.name)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentGrade([...currentGrade, item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex className={less.chooseItem}>
|
||||||
|
{titlePage("学期")}
|
||||||
|
<Flex className={less.list}>
|
||||||
|
{semester?.map((item,index) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={index}
|
||||||
|
className={
|
||||||
|
currentSemester?.find((it) => it?.name == item?.name)
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSingle) {
|
||||||
|
setCurrentSemester([item]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentSemester?.find((it) => it?.name == item?.name)) {
|
||||||
|
setCurrentSemester(
|
||||||
|
currentSemester.filter((it) => it?.name != item?.name)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentSemester([...currentSemester, item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex className={less.chooseItem}>
|
||||||
|
{titlePage("考试")}
|
||||||
|
<Flex className={less.list}>
|
||||||
|
{examination?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.name}
|
||||||
|
className={
|
||||||
|
currentExamination?.find((it) => it?.name == item?.name)
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSingle) {
|
||||||
|
setCurrentExamination([item]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
currentExamination?.find((it) => it?.name == item?.name)
|
||||||
|
) {
|
||||||
|
setCurrentExamination(
|
||||||
|
currentExamination.filter(
|
||||||
|
(it) => it?.name != item?.name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentExamination([...currentExamination, item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
{isShowSubject ? (
|
||||||
|
<Flex className={less.chooseItem}>
|
||||||
|
{titlePage("学科")}
|
||||||
|
<Flex className={less.list} alignItems="center" style={{ marginTop: -6 }}>
|
||||||
|
{nonSingleSubject?.map((item) => {
|
||||||
|
if (item.type == "choose") {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.id}
|
||||||
|
className={show ? less.itemchecked1 : less.item1}
|
||||||
|
alignItems="center"
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
|
<ConfigProvider theme={{
|
||||||
|
components: {
|
||||||
|
Select: {
|
||||||
|
selectorBg: 'transparent',
|
||||||
|
colorTextPlaceholder: "#666",
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Select
|
||||||
|
placeholder="单一学科"
|
||||||
|
variant="borderless"
|
||||||
|
onChange={(e,option) => {
|
||||||
|
setShow(true);
|
||||||
|
setCurrentDiscipline([{name:option?.label,id:e}]);
|
||||||
|
}}
|
||||||
|
options={singleSubjectOptions || []}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.id}
|
||||||
|
className={
|
||||||
|
currentDiscipline?.[0]?.name == item?.name
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setShow(false);
|
||||||
|
setCurrentDiscipline([item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isShowSchool ? ( <Flex className={less.chooseItem}>
|
||||||
|
{titlePage("学校")}
|
||||||
|
<Flex className={less.list}>
|
||||||
|
{school?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.id}
|
||||||
|
className={
|
||||||
|
currentSchool?.find((it) => it?.id == item?.id)
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.type == "choose") {
|
||||||
|
updateModal(chooseList, optionSchoolList);
|
||||||
|
setCurrentSchool([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingle) {
|
||||||
|
setCurrentSchool([item]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentSchool?.find((it) => it?.name == item?.name)) {
|
||||||
|
setCurrentSchool(
|
||||||
|
currentSchool.filter((it) => it?.name != item?.name)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentSchool([...currentSchool, item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.districtName}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>) : null}
|
||||||
|
<Flex className={less.otherSchools}>
|
||||||
|
{chooseList?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Flex key={item?.name} className={less.otheritem}>
|
||||||
|
{item?.cname}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{isShowSort ? (
|
||||||
|
<Flex className={less.chooseItem}>
|
||||||
|
{titlePage("排名")}
|
||||||
|
<Flex className={less.list} alignItems="center">
|
||||||
|
{sortList?.map((item) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item?.value}
|
||||||
|
className={
|
||||||
|
currentSort?.[0]?.label == item?.label
|
||||||
|
? less.itemchecked
|
||||||
|
: less.item
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setShow(false);
|
||||||
|
console.log(item,'item')
|
||||||
|
setCurrentSort([item]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
) : null}
|
||||||
|
<Flex className={less.btns}>
|
||||||
|
<Flex
|
||||||
|
className={less.btn}
|
||||||
|
onClick={async() => {
|
||||||
|
try {
|
||||||
|
const res = await getExam({
|
||||||
|
term: [currentGrade?.[0]?.name],
|
||||||
|
semester:[currentSemester?.[0]?.name],
|
||||||
|
examName:[currentExamination?.[0]?.name],
|
||||||
|
});
|
||||||
|
if(res?.code === 200){
|
||||||
|
onChange &&
|
||||||
|
onChange({
|
||||||
|
currentGrade,
|
||||||
|
currentSemester,
|
||||||
|
currentExamination,
|
||||||
|
currentSchool: [...currentSchool, ...chooseList],
|
||||||
|
currentDiscipline,
|
||||||
|
currentSort,
|
||||||
|
examId:res?.data[0]?.id,
|
||||||
|
});
|
||||||
|
setViewIsShow(false);
|
||||||
|
saveshow && saveshow(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error,'get-exam-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
className={less.btn}
|
||||||
|
style={{
|
||||||
|
color: "#c0c0c0",
|
||||||
|
border: "1px solid #c0c0c0",
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setViewIsShow(false);
|
||||||
|
// getCurrentSelectedInit();
|
||||||
|
setCurrentSchool([]);
|
||||||
|
setCurrentDiscipline([]);
|
||||||
|
saveshow && saveshow(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScreenPage;
|
||||||
124
src/components/ScreenPage/index.less
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
.frame {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
.chooseCard {
|
||||||
|
width: 100%;
|
||||||
|
.chooseItem {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.item {
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.item1 {
|
||||||
|
padding: 3px 8px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.itemchecked {
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin: 0 10px;
|
||||||
|
color: #42b562;
|
||||||
|
background-color: #ecf8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.itemchecked1 {
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #42b562;
|
||||||
|
background-color: #ecf8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.otherSchools {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 70px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
.otheritem {
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: inset 0px 0px 3px #42b562;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btns {
|
||||||
|
width: 100%;
|
||||||
|
.btn {
|
||||||
|
padding: 3px 25px;
|
||||||
|
background-color: #4abb6a;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// :global {
|
||||||
|
// .ant-select-selector {
|
||||||
|
// color: #666 !important;
|
||||||
|
// font-size: 16px !important;
|
||||||
|
// height: 20px !important;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
.schoolsList {
|
||||||
|
width: 100%;
|
||||||
|
.schoolsitem {
|
||||||
|
width: 100%;
|
||||||
|
.address {
|
||||||
|
width: 100%;
|
||||||
|
border-left: 3px solid #4bbb6b;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
.children {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 9px;
|
||||||
|
.child {
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 5px;
|
||||||
|
.color {
|
||||||
|
padding: 1px 2px;
|
||||||
|
font-size: 9px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.childchecked {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 8px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background-color: #4abb6a;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
.color {
|
||||||
|
padding: 1px 2px;
|
||||||
|
font-size: 9px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1698
src/components/StudentGroup/index.jsx
Normal file
1542
src/components/StudentModal/index.jsx
Normal file
462
src/components/TeacherGroup/index.jsx
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
import { useState, useEffect, useImperativeHandle, forwardRef, memo } from "react";
|
||||||
|
import { Modal,Table,Button } from "antd";
|
||||||
|
import ScreenPage from "@/components/ScreenPage";
|
||||||
|
import Flex from "@/components/Flex";
|
||||||
|
import { getTeacherAnalysis, getTeacherZScoreAnalysis, getTeacherTrace, getTeacherGrade, getTeacherSort, getTeacherAvg, getSingleTeacherZ } from '@/apis/leader';
|
||||||
|
import { getExam } from "@/apis/common";
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import upSrc from "@/assets/image/up.png";
|
||||||
|
import downSrc from "@/assets/image/down.png";
|
||||||
|
import styles from "./index.less";
|
||||||
|
|
||||||
|
const TeacherGroup = (props,ref) => {
|
||||||
|
|
||||||
|
const { record = {}, params = {} } = props;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [isShow, setIsShow] = useState(false);
|
||||||
|
const [isShow1, setIsShow1] = useState(false);
|
||||||
|
const [responseData1, setResponseData1] = useState({});
|
||||||
|
const [responseData2, setResponseData2] = useState({});
|
||||||
|
const [responseData3, setResponseData3] = useState({});
|
||||||
|
const [examId, setExamId] = useState('');
|
||||||
|
const [subTitle, setSubTitle] = useState('');
|
||||||
|
const [examList, setExamList] = useState([]);
|
||||||
|
|
||||||
|
const [columns1, setColumns1] = useState([]);
|
||||||
|
const [columns2, setColumns2] = useState([]);
|
||||||
|
const [columns3, setColumns3] = useState([]);
|
||||||
|
|
||||||
|
const [loading1, setLoading1] = useState(false);
|
||||||
|
const [loading2, setLoading2] = useState(false);
|
||||||
|
const [loading3, setLoading3] = useState(false);
|
||||||
|
|
||||||
|
const [dataSource1, setDataSource1] = useState([]);
|
||||||
|
const [dataSource2, setDataSource2] = useState([]);
|
||||||
|
const [dataSource3, setDataSource3] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openModal,
|
||||||
|
closeModal
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
trackInit();
|
||||||
|
gradeInit();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(open && examList?.length > 0 || !examId) {
|
||||||
|
singleTeacherZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
},[open,examList,examId])
|
||||||
|
|
||||||
|
|
||||||
|
const trackInit = async () => {
|
||||||
|
try {
|
||||||
|
setLoading1(true);
|
||||||
|
const res = await getTeacherTrace({
|
||||||
|
teacher: record?.teacherId,
|
||||||
|
examId: params?.examId,
|
||||||
|
subjectId: params?.subjectId
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setResponseData1(res);
|
||||||
|
setLoading1(false);
|
||||||
|
setDataSource1(res?.data || []);
|
||||||
|
setColumns1(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading1(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading1(false);
|
||||||
|
console.log(error, 'track-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const gradeInit = async () => {
|
||||||
|
try {
|
||||||
|
setLoading2(true);
|
||||||
|
const res = await getTeacherGrade({
|
||||||
|
teacher: record?.teacherId,
|
||||||
|
examId: params?.examId,
|
||||||
|
subjectId: params?.subjectId
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setResponseData2(res);
|
||||||
|
setLoading2(false);
|
||||||
|
setDataSource2(res?.data || []);
|
||||||
|
setColumns2(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading2(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading2(false);
|
||||||
|
console.log(error, 'track-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const singleTeacherZ = async () => {
|
||||||
|
try {
|
||||||
|
setLoading3(true);
|
||||||
|
const res = await getSingleTeacherZ({
|
||||||
|
teacher: record?.teacherId,
|
||||||
|
examId,
|
||||||
|
subjectId: params?.subjectId,
|
||||||
|
examIds: examList?.map(item => item?.id).filter(item=>item !== examId),
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setResponseData3(res);
|
||||||
|
setLoading3(false);
|
||||||
|
setDataSource3(res?.data || []);
|
||||||
|
setColumns3(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading3(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading3(false);
|
||||||
|
console.log(error, 'track-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload1 = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData1);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${record?.school}*${record?.name}教师情况追踪`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDownload2 = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData2);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${record?.school}*${record?.name}教师成绩分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDownload3 = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData3);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${record?.school}*${record?.name}教师增量评价`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableCommonProps = {
|
||||||
|
style: { maxHeight: 460},
|
||||||
|
rowKey: "teacherId",
|
||||||
|
scroll: { x: 1400,y:340 },
|
||||||
|
sticky: true,
|
||||||
|
pagination: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.teacher}>
|
||||||
|
<Modal
|
||||||
|
title={`教师成长性:${record?.school}*${record?.name}`}
|
||||||
|
width={'80vw'}
|
||||||
|
open={open}
|
||||||
|
centered
|
||||||
|
styles={{ body: { height: "80vh"} }}
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose
|
||||||
|
onCancel={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
onOk={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
style={{ overflowY: "scroll", height: "80vh" }}
|
||||||
|
>
|
||||||
|
<Flex style={{ color: "#666" }}>教师情况跟踪:</Flex>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource1}
|
||||||
|
columns={columns1}
|
||||||
|
loading={loading1}
|
||||||
|
{...tableCommonProps}
|
||||||
|
></Table>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#4abb6a",
|
||||||
|
color: "#fff",
|
||||||
|
marginTop: 8,
|
||||||
|
}}
|
||||||
|
onClick={handleDownload1}
|
||||||
|
>
|
||||||
|
导出表格
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Flex style={{ color: "#666" }}>教师成绩分析:</Flex>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource2}
|
||||||
|
columns={columns2}
|
||||||
|
loading={loading2}
|
||||||
|
{...tableCommonProps}
|
||||||
|
|
||||||
|
></Table>
|
||||||
|
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#4abb6a",
|
||||||
|
color: "#fff",
|
||||||
|
marginTop: 8,
|
||||||
|
}}
|
||||||
|
onClick={handleDownload2}
|
||||||
|
>
|
||||||
|
导出表格
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<ScreenPage
|
||||||
|
isShow={isShow1}
|
||||||
|
isSingle={true}
|
||||||
|
isShowSubject={false}
|
||||||
|
isTeacher={true}
|
||||||
|
isShowSchool={false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSubTitle([e?.currentGrade?.[0]?.name, e?.currentSemester?.[0]?.name, e?.currentExamination?.[0]?.name].filter(item => !!item).join("*"));
|
||||||
|
setExamId(e?.examId || '');
|
||||||
|
}}
|
||||||
|
|
||||||
|
saveshow={(e) => {
|
||||||
|
setIsShow1(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<h3 style={{marginTop:16}}>教师增量评价:{record?.name}</h3>
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
margin: "5px 0",
|
||||||
|
// gap: 12,
|
||||||
|
}}
|
||||||
|
justify="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Flex style={{ flex: 1 }} alignItems="center">
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#666",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex>当前考试 : </Flex>
|
||||||
|
<Flex style={{ width: "92%" }} wrap="wrap">
|
||||||
|
<h3 style={{color:'#000'}}> {subTitle}</h3>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
padding: "4px 8px",
|
||||||
|
border: "1px solid #666",
|
||||||
|
borderRadius: 5,
|
||||||
|
cursor: "pointer",
|
||||||
|
marginLeft: 10,
|
||||||
|
}}
|
||||||
|
alignItems="center"
|
||||||
|
onClick={() => {
|
||||||
|
setIsShow1(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
选择考试
|
||||||
|
<img
|
||||||
|
src={isShow1 ? upSrc : downSrc}
|
||||||
|
width={14}
|
||||||
|
height={12}
|
||||||
|
style={{ marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<ScreenPage
|
||||||
|
isShow={isShow}
|
||||||
|
isSingle={false}
|
||||||
|
isShowSubject={false}
|
||||||
|
isTeacher={true}
|
||||||
|
isShowSchool={false}
|
||||||
|
onChange={(e) => {
|
||||||
|
const term = e?.currentGrade?.map(item => item?.name);
|
||||||
|
const semester = e?.currentSemester?.map(item => item?.name);
|
||||||
|
const examName = e?.currentExamination?.map(item => item?.name);
|
||||||
|
getExam({
|
||||||
|
term,
|
||||||
|
semester,
|
||||||
|
examName,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
setExamList(res?.data || []);
|
||||||
|
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error, 'get-exam-error');
|
||||||
|
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
|
||||||
|
saveshow={(e) => {
|
||||||
|
setIsShow(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
margin: "5px 0",
|
||||||
|
// gap: 12,
|
||||||
|
}}
|
||||||
|
justify="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Flex style={{ flex: 1 }} alignItems="center">
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#666",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex>对比考试 : </Flex>
|
||||||
|
<Flex style={{ width: "92%" }} wrap="wrap">
|
||||||
|
{examList?.filter((item) => item?.id !== params?.examId)?.map((item) => {
|
||||||
|
return (
|
||||||
|
<h3
|
||||||
|
style={{ color: "#000", marginRight: 8 }}
|
||||||
|
key={item?.id}
|
||||||
|
|
||||||
|
>
|
||||||
|
{`${item?.term}*${item?.semester}*${item?.examName};`}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
padding: "4px 8px",
|
||||||
|
border: "1px solid #666",
|
||||||
|
borderRadius: 5,
|
||||||
|
cursor: "pointer",
|
||||||
|
marginLeft: 10,
|
||||||
|
}}
|
||||||
|
alignItems="center"
|
||||||
|
onClick={() => {
|
||||||
|
setIsShow(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
选择考试
|
||||||
|
<img
|
||||||
|
src={isShow ? upSrc : downSrc}
|
||||||
|
width={14}
|
||||||
|
height={12}
|
||||||
|
style={{ marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource3}
|
||||||
|
columns={columns3}
|
||||||
|
loading={loading3}
|
||||||
|
{...tableCommonProps}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#4abb6a",
|
||||||
|
color: "#fff",
|
||||||
|
marginTop: 8,
|
||||||
|
}}
|
||||||
|
onClick={handleDownload3}
|
||||||
|
>
|
||||||
|
导出表格
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Flex style={{ width: '100%', marginTop: 32 }} justify="center" alignItems="center">
|
||||||
|
<Button type="primary" style={{
|
||||||
|
backgroundColor: "#4abb6a",
|
||||||
|
color: "#fff",
|
||||||
|
marginTop: 10,
|
||||||
|
}}>{record?.name}课堂实录</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(forwardRef(TeacherGroup));
|
||||||
0
src/components/TeacherGroup/index.less
Normal file
151
src/components/UploadScore/index.jsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { useState,useEffect } from 'react';
|
||||||
|
import { Button, message, Upload } from 'antd';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import { getExamFileName,uploadFile } from '@/apis/data';
|
||||||
|
import { logger} from '@/apis/auth';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
|
||||||
|
const allowedTypes = [
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
||||||
|
'application/vnd.ms-excel', // .xls
|
||||||
|
'application/zip', // .zip
|
||||||
|
'application/x-zip-compressed' // .zip
|
||||||
|
];
|
||||||
|
const allowedExtensions = ['.xlsx', '.xls', '.zip'];
|
||||||
|
|
||||||
|
|
||||||
|
const UploadScore = (props) => {
|
||||||
|
|
||||||
|
|
||||||
|
const {examId='',onChange=()=>{}} = props;
|
||||||
|
|
||||||
|
|
||||||
|
const [fileList,setFileList] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getExamFileNameInit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getExamFileNameInit = async () => {
|
||||||
|
if(!examId){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await getExamFileName({examId,});
|
||||||
|
|
||||||
|
if(res.code === 200){
|
||||||
|
if(!res.data?.studentSubjectDetailFileName){
|
||||||
|
setFileList([]);
|
||||||
|
onChange(null);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
setFileList([{
|
||||||
|
uid: res?.data?.nsId,
|
||||||
|
name: res.data?.studentSubjectDetailFileName,
|
||||||
|
status: 'done',
|
||||||
|
}]);
|
||||||
|
onChange(res.data?.studentSubjectDetailFileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res.msg);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error,'getExamFileNameInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkFileType = (file) => {
|
||||||
|
const isAllowedType = allowedTypes.includes(file.type);
|
||||||
|
const fileName = file.name.toLowerCase();
|
||||||
|
const isAllowedExtension = allowedExtensions.some(ext => fileName.endsWith(ext));
|
||||||
|
|
||||||
|
return isAllowedType || isAllowedExtension;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleBeforeUpload = (file) => {
|
||||||
|
if (!checkFileType(file)) {
|
||||||
|
message.error('只能上传 Excel (.xlsx, .xls) 或 ZIP (.zip) 格式的文件!');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
const isLt2M = file.size / 1024 / 1024 < 2000;
|
||||||
|
if (!isLt2M) {
|
||||||
|
message.error('文件不能超过 2000MB!');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomRequest = async({ file, onSuccess, onError}) => {
|
||||||
|
try {
|
||||||
|
let percent = 0;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('examId', examId);
|
||||||
|
const res = await uploadFile(formData,{
|
||||||
|
onUploadProgress: ({ loaded, total }) => {
|
||||||
|
percent = Math.floor((loaded / total) * 100);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if(res?.code === 200){
|
||||||
|
onSuccess('上传成功');
|
||||||
|
message.success('文件上传成功');
|
||||||
|
onChange(file?.name);
|
||||||
|
setFileList([
|
||||||
|
{
|
||||||
|
uid: examId,
|
||||||
|
name: file?.name,
|
||||||
|
status: 'done',
|
||||||
|
percent,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await logger({description:"上传原始成绩"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
} catch (error) {
|
||||||
|
onError(error);
|
||||||
|
message.error('文件上传失败');
|
||||||
|
console.log(error,'upload-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleRemove = (file) => {
|
||||||
|
onChange(null);
|
||||||
|
setFileList((prev)=>{
|
||||||
|
return prev.filter(item=>item.uid !== file.uid);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const originProps = {
|
||||||
|
name: "file",
|
||||||
|
multiple: false,
|
||||||
|
accept: '.xlsx,.xls,.zip',
|
||||||
|
maxCount: 1,
|
||||||
|
beforeUpload: handleBeforeUpload,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Upload {...originProps} customRequest={handleCustomRequest} onRemove={handleRemove} fileList={fileList}>
|
||||||
|
<Button type='primary' icon={<UploadOutlined />}>上传原始成绩</Button>
|
||||||
|
</Upload>
|
||||||
|
<p className={styles.upload_tips}>请上传原始成绩文件,请按照固定的excel表格格式进行上传,勿修改表格结构及固定文案,文件大小限制2000MB以内</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UploadScore;
|
||||||
6
src/components/UploadScore/index.less
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.upload{
|
||||||
|
&_tips{
|
||||||
|
margin-top: 16px;
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/config/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const BASE_URL = 'https://studies.hmbigdata.com/hly-huaian';
|
||||||
94
src/global.less
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.globalModal {
|
||||||
|
.ant-modal-body {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-body > .anticon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-body .ant-modal-confirm-content {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
// border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-body
|
||||||
|
> .anticon
|
||||||
|
+ .ant-modal-confirm-title
|
||||||
|
+ .ant-modal-confirm-content {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.ant-modal .ant-modal-content {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
.ant-descriptions-item > span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-item-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:where(.css-dev-only-do-not-override-240cud).ant-modal .ant-modal-content {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
.globalModalNoPadding {
|
||||||
|
.globalModal;
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.globalModalInfo {
|
||||||
|
.ant-modal-body {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-body > .anticon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-btns {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-header {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-confirm-body
|
||||||
|
> .anticon
|
||||||
|
+ .ant-modal-confirm-title
|
||||||
|
+ .ant-modal-confirm-content {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-row > th,
|
||||||
|
.ant-descriptions-row > td {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-item-label {
|
||||||
|
padding: 6px 8px !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-item-content {
|
||||||
|
padding: 6px 8px 6px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
padding: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
padding: 8px 6px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/layouts/index.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {ConfigProvider} from 'antd';
|
||||||
|
import { Outlet } from 'umi';
|
||||||
|
import locale from 'antd/locale/zh_CN';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import 'dayjs/locale/zh-cn';
|
||||||
|
|
||||||
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
|
export default function Layout() {
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={{
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#4ABB6A',
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
}} locale={locale}>
|
||||||
|
<Outlet/>
|
||||||
|
</ConfigProvider>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
11
src/pages/admin-account/index.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const AdminAccountPage = () => {
|
||||||
|
return (
|
||||||
|
<div>管理账号</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminAccountPage;
|
||||||
0
src/pages/admin-account/index.less
Normal file
663
src/pages/admin-account/subpage/access/index.jsx
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { Button,Form, Input, Select, Space, Card, Table, Checkbox, Tree,Modal, message, Flex, Typography, Drawer, Pagination } from 'antd';
|
||||||
|
import { getRoleList, deleteRole, saveRole, getRoleTree } from '@/apis/role';
|
||||||
|
import { getStageList, getSubjectListSelect } from '@/apis/common';
|
||||||
|
import { logger } from '@/apis/auth';
|
||||||
|
import { getAllCheckedListIds } from '@/utils/data';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const AccessPage = () => {
|
||||||
|
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [tableLoading, setTableLoading] = useState(false);
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const [roleId, setRoleId] = useState(false);
|
||||||
|
|
||||||
|
const [stageOptions, setStageOptions] = useState([]);
|
||||||
|
const [stageIds, setStageIds] = useState([]);
|
||||||
|
const [subjectOptions, setSubjectOptions] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
const [treeData, setTreeData] = useState({});
|
||||||
|
|
||||||
|
const [permissions, setPermissions] = useState([]);
|
||||||
|
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
|
const [pageNum, setPageNum] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
|
||||||
|
const computedTitle = useMemo(() => {
|
||||||
|
return roleId ? '编辑角色' : '新增角色';
|
||||||
|
}, [roleId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
getStageListData();
|
||||||
|
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stageIds?.length > 0) {
|
||||||
|
getSubjectData();
|
||||||
|
}
|
||||||
|
}, [stageIds]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stageIds?.length > 0 && !roleId) {
|
||||||
|
getAccessTreeData();
|
||||||
|
}
|
||||||
|
}, [stageIds, roleId])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!permissions?.length) {
|
||||||
|
form.setFieldValue('permissions', [])
|
||||||
|
}
|
||||||
|
form.setFieldValue('permissions', permissions);
|
||||||
|
}, [permissions])
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
setTableLoading(true);
|
||||||
|
const res = await getRoleList({ pageNum, pageSize, });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTableLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTableLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTableLoading(false);
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStageListData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getStageList();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setStageOptions(() => {
|
||||||
|
return res?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item.value,
|
||||||
|
value: item.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setStageIds(res?.data?.map((item) => item.key));
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getStageListData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSubjectData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectListSelect({ stages: stageIds?.join(',') });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
|
||||||
|
setSubjectOptions(() => {
|
||||||
|
return res?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getStageListData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getAccessTreeData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getRoleTree({ stage: stageIds?.join(',') });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTreeData(res?.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getStageListData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handlePrimaryCityCheck = (value) => {
|
||||||
|
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handlePrimaryDistrictCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handlePrimarySchoolCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJuniorCityCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handleJuniorDistrictCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handleJuniorSchoolCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHighCityCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handleHighDistrictCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const handleHighSchoolCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
})
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataImportCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSecondaryAdminCheck = (value) => {
|
||||||
|
setPermissions((prev) => {
|
||||||
|
return Array.from(new Set([...prev, ...value]));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// const onFormValuesChange = async (values) => {
|
||||||
|
// try {
|
||||||
|
// setTableLoading(true);
|
||||||
|
// const res = await getSubjectList(values);
|
||||||
|
// if (res?.code === 200) {
|
||||||
|
// setTableLoading(false);
|
||||||
|
// setDataSource(res?.data || []);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// setTableLoading(false);
|
||||||
|
// } catch (error) {
|
||||||
|
// setTableLoading(false);
|
||||||
|
// console.log(error, 'onFormValuesChange-error');
|
||||||
|
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
const handleCreateRole = () => {
|
||||||
|
setOpen(true);
|
||||||
|
setRoleId('');
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFinish = async () => {
|
||||||
|
setConfirmLoading(true);
|
||||||
|
form.validateFields().then(async () => {
|
||||||
|
const res = await saveRole(roleId ? {
|
||||||
|
id: roleId,
|
||||||
|
...form.getFieldsValue()
|
||||||
|
} : form.getFieldsValue());
|
||||||
|
if (res.code === 200) {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
setOpen(false);
|
||||||
|
init();
|
||||||
|
form.resetFields();
|
||||||
|
setRoleId('');
|
||||||
|
setPermissions([]);
|
||||||
|
message.success('操作成功');
|
||||||
|
if(roleId){
|
||||||
|
await logger({description:"修改角色"});
|
||||||
|
}else{
|
||||||
|
await logger({description:"创建角色"});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setConfirmLoading(false);
|
||||||
|
}).catch(error => {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
console.log(error, 'handleFinish-error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteRole = (record) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '删除角色',
|
||||||
|
content: '确定删除角色吗?',
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
centered: true,
|
||||||
|
|
||||||
|
onOk() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
deleteRole({ roleId: record?.id }).then(async(res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('删除角色成功');
|
||||||
|
resolve();
|
||||||
|
init();
|
||||||
|
await logger({description:"删除角色"});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
reject();
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error, 'handleResetPassword-error');
|
||||||
|
reject();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleEditRole = async (record) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getRoleTree({ roleId: record?.id, stage: record?.stage });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTreeData(res?.data)
|
||||||
|
setOpen(true);
|
||||||
|
setRoleId(record?.id);
|
||||||
|
form.setFieldsValue({
|
||||||
|
roleName: res?.data?.roleName,
|
||||||
|
subjects: res?.data?.subjects,
|
||||||
|
stages: res?.data?.stages
|
||||||
|
});
|
||||||
|
setStageIds(res?.data?.stages);
|
||||||
|
const checkIds = getAllCheckedListIds(res?.data);
|
||||||
|
form.setFieldValue('permissions', checkIds);
|
||||||
|
setPermissions(checkIds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'handleEditRole-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const onPageChange = async (pageNum, pageSize) => {
|
||||||
|
setPageNum(pageNum);
|
||||||
|
setPageSize(pageSize);
|
||||||
|
try {
|
||||||
|
setTableLoading(true);
|
||||||
|
const res = await getRoleList({ pageNum, pageSize });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTableLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTableLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTableLoading(false);
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '角色名称',
|
||||||
|
dataIndex: 'roleName',
|
||||||
|
key: 'roleName',
|
||||||
|
width: 280,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学科',
|
||||||
|
dataIndex: 'subject',
|
||||||
|
key: 'subject',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '适用范围',
|
||||||
|
dataIndex: 'stage',
|
||||||
|
key: 'stage',
|
||||||
|
align: 'center',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center',
|
||||||
|
render(_, record) {
|
||||||
|
return (
|
||||||
|
<Space size="middle">
|
||||||
|
<Button type="link" onClick={() => { handleEditRole(record) }}>编辑</Button>
|
||||||
|
<Button type='link' danger onClick={() => handleDeleteRole(record)}>删除</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.access}>
|
||||||
|
<header className={styles.access_header}>
|
||||||
|
<Card style={{ height: "100%" }}>
|
||||||
|
<Flex justify="space-between">
|
||||||
|
<Typography.Title level={4} >
|
||||||
|
角色
|
||||||
|
</Typography.Title>
|
||||||
|
<Button type="primary" onClick={handleCreateRole}>
|
||||||
|
创建角色
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</header>
|
||||||
|
<main className={styles.access_main}>
|
||||||
|
<Card>
|
||||||
|
<Table bordered columns={columns} loading={tableLoading} dataSource={dataSource} pagination={false} style={{ height: '100%' }} />
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
<footer className={styles.access_footer}>
|
||||||
|
<Pagination showQuickJumper showSizeChanger total={total} onChange={onPageChange} showTotal={(total) => `共${total}条数据`} />
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
title={computedTitle}
|
||||||
|
open={open}
|
||||||
|
destroyOnClose
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setRoleId('');
|
||||||
|
form.resetFields();
|
||||||
|
setPermissions([]);
|
||||||
|
setTreeData({});
|
||||||
|
}}
|
||||||
|
size="large"
|
||||||
|
width={1000}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
stages: stageOptions?.map(item => item.value),
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Form.Item label="角色名称" name="roleName" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请填写角色名称',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Input placeholder="请输入角色名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="学段" name="stages" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请至少选择一个学段',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Checkbox.Group options={stageOptions} onChange={(value) => {
|
||||||
|
if (!value?.length) {
|
||||||
|
// setStageIds([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStageIds(value);
|
||||||
|
|
||||||
|
}} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="学科" name="subjects" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请至少选择一门学科',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={subjectOptions}
|
||||||
|
maxTagCount={6}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="权限" name="permissions" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择权限',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<div className={styles.access_list}>
|
||||||
|
{stageIds?.length > 0 ? stageIds.map((item) => {
|
||||||
|
if (item == 1) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.access_list_title} style={{ marginTop: 8 }}>小学</div>
|
||||||
|
<Flex justify="space-evenly" style={{ marginTop: 8 }}>
|
||||||
|
<Card title='市级报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.primarySchoolRole?.city?.checkedList}
|
||||||
|
onCheck={handlePrimaryCityCheck}
|
||||||
|
defaultExpandAll={true}
|
||||||
|
treeData={treeData?.primarySchoolRole?.city?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='区县报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.primarySchoolRole?.district?.checkedList}
|
||||||
|
onCheck={handlePrimaryDistrictCheck}
|
||||||
|
treeData={treeData?.primarySchoolRole?.district?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='学校报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.primarySchoolRole?.school?.checkedList}
|
||||||
|
onCheck={handlePrimarySchoolCheck}
|
||||||
|
treeData={treeData?.primarySchoolRole?.school?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
if (item == 2) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.access_list_title} style={{ marginTop: 8 }}>初中</div>
|
||||||
|
<Flex style={{ marginTop: 8 }}>
|
||||||
|
<Card title='市级报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.juniorSchoolRole?.city?.checkedList}
|
||||||
|
onCheck={handleJuniorCityCheck}
|
||||||
|
defaultExpandAll={true}
|
||||||
|
treeData={treeData?.juniorSchoolRole?.city?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='区县报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.juniorSchoolRole?.district?.checkedList}
|
||||||
|
onCheck={handleJuniorDistrictCheck}
|
||||||
|
treeData={treeData?.juniorSchoolRole?.district.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='学校报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.juniorSchoolRole?.school?.checkedList}
|
||||||
|
onCheck={handleJuniorSchoolCheck}
|
||||||
|
treeData={treeData?.juniorSchoolRole?.school.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
if (item == 3) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.access_list_title} style={{ marginTop: 8 }}>高中</div>
|
||||||
|
<Flex style={{ marginTop: 8 }}>
|
||||||
|
<Card title='市级报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.highSchoolRole?.city?.checkedList}
|
||||||
|
onCheck={handleHighCityCheck}
|
||||||
|
defaultExpandAll={true}
|
||||||
|
treeData={treeData?.highSchoolRole?.city?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='区县报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.highSchoolRole?.district?.checkedList}
|
||||||
|
onCheck={handleHighDistrictCheck}
|
||||||
|
treeData={treeData?.highSchoolRole?.district.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='学校报告' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.highSchoolRole?.school?.checkedList}
|
||||||
|
onCheck={handleHighSchoolCheck}
|
||||||
|
treeData={treeData?.highSchoolRole?.school?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}) : null}
|
||||||
|
{stageIds?.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<div className={styles.access_list_title} style={{ marginTop: 8 }}>数据导入</div>
|
||||||
|
<Flex style={{ marginTop: 8 }}>
|
||||||
|
<Card title='数据导入' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.importDataRole?.upload?.checkedList}
|
||||||
|
onCheck={handleDataImportCheck}
|
||||||
|
defaultExpandAll={true}
|
||||||
|
treeData={treeData?.importDataRole?.upload?.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card title='次级管理员' hoverable style={{ width: 300, height: 320, overflow: "auto", marginLeft: 8 }}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll={true}
|
||||||
|
fieldNames={{ title: 'label', key: 'id', children: "children" }}
|
||||||
|
defaultCheckedKeys={treeData?.importDataRole?.report?.checkedList}
|
||||||
|
onCheck={handleSecondaryAdminCheck}
|
||||||
|
treeData={treeData?.importDataRole?.report.permissionTree}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Flex style={{ width: '100%' }}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setRoleId("");
|
||||||
|
form.resetFields();
|
||||||
|
setPermissions([]);
|
||||||
|
setTreeData({});
|
||||||
|
}}>取消</Button>
|
||||||
|
<Button type='primary' loading={confirmLoading} onClick={handleFinish}>确定</Button>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessPage;
|
||||||
42
src/pages/admin-account/subpage/access/index.less
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
.access{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
// background-color: salmon;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&_header{
|
||||||
|
height: 137px;
|
||||||
|
// background: sandybrown;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
&_main{
|
||||||
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
// background: yellow;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
&_footer{
|
||||||
|
height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 32px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
// background: sandybrown;
|
||||||
|
}
|
||||||
|
&_list{
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
height: 520px;
|
||||||
|
// background-color: salmon;
|
||||||
|
overflow: scroll;
|
||||||
|
&_title{
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
571
src/pages/admin-account/subpage/account/index.jsx
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { Button,Form, Input,Select, Space, Card, Table, Tag, Badge, Modal, Radio, message, Flex, Typography, Drawer,Pagination } from 'antd';
|
||||||
|
import { getUserList, deleteUser, resetPassword, getDistrictSelect, getSchoolSelect,getUserDetail,saveUser } from '@/apis/user';
|
||||||
|
import { logger } from '@/apis/auth';
|
||||||
|
import { getRoleSelect } from '@/apis/role';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
|
||||||
|
const RoleTypeBadges = {
|
||||||
|
0: <Badge color="lime" text="教师" />,
|
||||||
|
1: <Badge color="geekblue" text="教研员" />,
|
||||||
|
2: <Badge color="purple" text="校长" />,
|
||||||
|
3: <Badge color="yellow" text="院长" />,
|
||||||
|
4: <Badge color="cyan" text="局长" />,
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleTypeOptions = [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
label: '教师',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '教研员',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: '校长',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: '院长',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
label: '局长',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AccountPage = () => {
|
||||||
|
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [tableLoading, setTableLoading] = useState(false);
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const [disabled, setDisabled] = useState(false);
|
||||||
|
|
||||||
|
const [districtOptions, setDistrictOptions] = useState([]);
|
||||||
|
const [schoolOptions, setSchoolOptions] = useState([]);
|
||||||
|
const [roleOptions, setRoleOptions] = useState([]);
|
||||||
|
const [districts, setDistricts] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
const [userId, setUserId] = useState('');
|
||||||
|
|
||||||
|
const [total,setTotal] =useState(0);
|
||||||
|
|
||||||
|
const [pageNum,setPageNum] = useState(1);
|
||||||
|
const [pageSize,setPageSize] = useState(10);
|
||||||
|
|
||||||
|
const computedTitle = useMemo(() => {
|
||||||
|
if (userId) {
|
||||||
|
return '编辑账号';
|
||||||
|
}
|
||||||
|
return '新增账号';
|
||||||
|
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
getDistrictSelectData();
|
||||||
|
getRoleSelectData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId) {
|
||||||
|
getUserDetailData();
|
||||||
|
}
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (districts?.length > 0) {
|
||||||
|
getSchoolSelectData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [districts])
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
setTableLoading(true);
|
||||||
|
const res = await getUserList({pageNum,pageSize});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTableLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTableLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTableLoading(false);
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const getDistrictSelectData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getDistrictSelect();
|
||||||
|
if (res.code === 200) {
|
||||||
|
setDistrictOptions(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item.districtName,
|
||||||
|
value: item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getDistrictSelectData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSchoolSelectData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSchoolSelect({ baseDistrictIds: districts.join(',') });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSchoolOptions(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item.cname,
|
||||||
|
value: item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getSchoolSelectData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoleSelectData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getRoleSelect();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setRoleOptions(()=>{
|
||||||
|
return res?.data?.map(item=>{
|
||||||
|
return {
|
||||||
|
label:item.roleName,
|
||||||
|
value:item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getSchoolSelectData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserDetailData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getUserDetail({ userId, });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setDistricts(res.data.districts);
|
||||||
|
form.setFieldsValue(res.data);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getDistrictDetailData-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateAccount = () => {
|
||||||
|
setOpen(true);
|
||||||
|
setUserId('');
|
||||||
|
form.resetFields();
|
||||||
|
setDisabled(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFinish = async () => {
|
||||||
|
setConfirmLoading(true);
|
||||||
|
|
||||||
|
form.validateFields().then(async () => {
|
||||||
|
const res = await saveUser(userId ? {
|
||||||
|
id: userId,
|
||||||
|
...form.getFieldsValue()
|
||||||
|
} : form.getFieldsValue());
|
||||||
|
if (res.code === 200) {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
setOpen(false);
|
||||||
|
init();
|
||||||
|
message.success('操作成功');
|
||||||
|
if(userId){
|
||||||
|
await logger({description:"创建账号"});
|
||||||
|
}else{
|
||||||
|
await logger({description:"修改账号"});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setConfirmLoading(false);
|
||||||
|
}).catch(error => {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
console.log(error, 'handleFinish-error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteUser = (record) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '删除账号',
|
||||||
|
content: '确定删除账号吗?',
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
centered: true,
|
||||||
|
|
||||||
|
onOk() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
deleteUser({ userId: record?.id }).then(async(res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('删除账号成功');
|
||||||
|
resolve();
|
||||||
|
init();
|
||||||
|
await logger({description:"删除账号"});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
reject();
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error, 'handleResetPassword-error');
|
||||||
|
reject();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleResetPassword = (record) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '重置密码',
|
||||||
|
content: '确定重置密码吗?',
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
centered: true,
|
||||||
|
|
||||||
|
onOk() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resetPassword({ userId: record?.id }).then(async(res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('重置密码成功');
|
||||||
|
init();
|
||||||
|
resolve();
|
||||||
|
await logger({description:"重置密码"});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.error(res?.msg);
|
||||||
|
reject();
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error, 'handleResetPassword-error');
|
||||||
|
reject();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageChange = async (pageNum, pageSize) => {
|
||||||
|
setPageNum(pageNum);
|
||||||
|
setPageSize(pageSize);
|
||||||
|
try {
|
||||||
|
setTableLoading(true);
|
||||||
|
const res = await getUserList({pageNum,pageSize});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setTableLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setTotal(res?.total || 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTableLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTableLoading(false);
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '账号名称',
|
||||||
|
dataIndex: 'account',
|
||||||
|
key: 'account',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '使用者名称',
|
||||||
|
dataIndex: 'userName',
|
||||||
|
key: 'userName',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '区域',
|
||||||
|
dataIndex: 'district',
|
||||||
|
key: 'district',
|
||||||
|
// width: 360,
|
||||||
|
align: 'center',
|
||||||
|
render(value) {
|
||||||
|
if (!value?.length) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
{value?.map(item => {
|
||||||
|
return (
|
||||||
|
<Space style={{ width: '100%' }} wrap>
|
||||||
|
<Tag bordered={false} color="cyan">
|
||||||
|
{item?.name}
|
||||||
|
</Tag>
|
||||||
|
{item?.schools?.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{item?.schools?.map(item => {
|
||||||
|
return (
|
||||||
|
<Tag bordered={false} color="geekblue">
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色',
|
||||||
|
dataIndex: 'roleName',
|
||||||
|
key: 'roleName',
|
||||||
|
width: 220,
|
||||||
|
align: 'center',
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否为管理员',
|
||||||
|
dataIndex: 'isAdmin',
|
||||||
|
key: 'isAdmin',
|
||||||
|
width: 140,
|
||||||
|
align: 'center',
|
||||||
|
render(value) {
|
||||||
|
if (value === 1) {
|
||||||
|
return <Badge status="success" text="是" />;
|
||||||
|
}
|
||||||
|
if (value === 0) {
|
||||||
|
return <Badge status="warning" text="否" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
|
render(value) {
|
||||||
|
if (value === 0) {
|
||||||
|
return <Badge status="success" text="启用" />;
|
||||||
|
}
|
||||||
|
if (value === 1) {
|
||||||
|
return <Badge status="error" text="禁用" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '使用者称谓',
|
||||||
|
dataIndex: 'userRoleType',
|
||||||
|
key: 'userRoleType',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
render: (value) => RoleTypeBadges?.[value],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center',
|
||||||
|
width: 140,
|
||||||
|
render(_, record) {
|
||||||
|
return (
|
||||||
|
<Space size="middle">
|
||||||
|
<Button type="link" onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
setUserId(record?.id);
|
||||||
|
setDisabled(true);
|
||||||
|
}}>编辑</Button>
|
||||||
|
|
||||||
|
<Button type='link' onClick={() => handleResetPassword(record)}>重置密码</Button>
|
||||||
|
<Button type='link' danger onClick={() => handleDeleteUser(record)}>删除</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.account}>
|
||||||
|
<header className={styles.account_header}>
|
||||||
|
<Card style={{ height: "100%" }}>
|
||||||
|
<Flex justify="space-between">
|
||||||
|
<Typography.Title level={4} >
|
||||||
|
创建账号
|
||||||
|
</Typography.Title>
|
||||||
|
<Button type="primary" onClick={handleCreateAccount}>
|
||||||
|
创建账号
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</header>
|
||||||
|
<main className={styles.account_main}>
|
||||||
|
<Card>
|
||||||
|
<Table scroll={{ x: 1200 }} bordered columns={columns} loading={tableLoading} dataSource={dataSource} pagination={false} style={{ height: '100%' }} />
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
<footer className={styles.account_footer}>
|
||||||
|
|
||||||
|
<Pagination showQuickJumper showSizeChanger total={total} onChange={onPageChange} showTotal={(total)=>`共${total}条数据`} />
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
title={computedTitle}
|
||||||
|
open={open}
|
||||||
|
destroyOnClose
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setUserId('');
|
||||||
|
form.resetFields();
|
||||||
|
}}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
|
||||||
|
>
|
||||||
|
<Form.Item label="账号名称" name="account" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请填写账号名称',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Input placeholder="请输入账号名称" disabled={disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="使用者名称" name="userName" rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请填写使用者名称',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Input placeholder="请输入使用者名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="账号权限范围" style={{ marginBottom: 0 }}>
|
||||||
|
<Form.Item name='districts' rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择区县',
|
||||||
|
},
|
||||||
|
]} style={{ display: 'inline-block', width: 'calc(50% - 8px)' }} >
|
||||||
|
<Select maxTagCount={2} options={districtOptions} placeholder="请选择区县" mode="multiple" allowClear onChange={(value) => {
|
||||||
|
if (!value?.length) {
|
||||||
|
setDistricts([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDistricts(value);
|
||||||
|
form.setFieldValue('districts', value);
|
||||||
|
|
||||||
|
}} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name='schools' style={{ display: 'inline-block', width: 'calc(50% - 8px)', margin: '0 8px' }}>
|
||||||
|
<Select maxTagCount={1} options={schoolOptions} placeholder="请选择学校" mode="multiple" allowClear onChange={(value) => {
|
||||||
|
if (!value?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.setFieldValue('schools', value);
|
||||||
|
|
||||||
|
}} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="角色" name='roleId' rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请选择角色"
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select options={roleOptions} placeholder="请选择角色" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="是否启为管理员" name='isAdmin' rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请选择是否为管理员"
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Radio.Group
|
||||||
|
options={[
|
||||||
|
{ value: 0, label: '否' },
|
||||||
|
{ value: 1, label: '是' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="使用者称谓" name='userRoleType' rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请选择使用者称谓"
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select options={roleTypeOptions} placeholder="请选择使用者称谓" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="是否启用账号" name='status' rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请选择是否启用账号"
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Radio.Group
|
||||||
|
options={[
|
||||||
|
{ value: 0, label: '启用' },
|
||||||
|
{ value: 1, label: '禁用' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Flex style={{ width: '100%' }}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setUserId('');
|
||||||
|
form.resetFields();
|
||||||
|
}}>取消</Button>
|
||||||
|
<Button type='primary' loading={confirmLoading} onClick={handleFinish}>确定</Button>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountPage;
|
||||||
29
src/pages/admin-account/subpage/account/index.less
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.account{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
// background-color: salmon;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&_header{
|
||||||
|
height: 137px;
|
||||||
|
// background: sandybrown;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
&_main{
|
||||||
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
// background: yellow;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
&_footer{
|
||||||
|
height: 80px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 32px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
// background: sandybrown;
|
||||||
|
}
|
||||||
|
}
|
||||||
179
src/pages/analysis/components/Catalogue/index.jsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import { useState,useEffect } from 'react';
|
||||||
|
import navIconSrc from '@/assets/icon/analysis-navigation-icon.png';
|
||||||
|
import sectionSrc1 from '@/assets/icon/analysis-catalogue-1.png';
|
||||||
|
import sectionSrc2 from '@/assets/icon/analysis-catalogue-2.png';
|
||||||
|
import sectionSrc3 from '@/assets/icon/analysis-catalogue-3.png';
|
||||||
|
import sectionSrc4 from '@/assets/icon/analysis-catalogue-4.png';
|
||||||
|
import sectionSrc5 from '@/assets/icon/analysis-catalogue-5.png';
|
||||||
|
import sectionSrc6 from '@/assets/icon/analysis-catalogue-6.png';
|
||||||
|
import sectionSrc7 from '@/assets/icon/analysis-catalogue-7.png';
|
||||||
|
import sectionSrc8 from '@/assets/icon/analysis-catalogue-8.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const Catalogue = (props) => {
|
||||||
|
|
||||||
|
const { idx = '',modelList = [] } = props;
|
||||||
|
const [list,setList] = useState([]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
const arr = [];
|
||||||
|
modelList?.forEach(element => {
|
||||||
|
if(element?.reportItemModel?.id === 3){
|
||||||
|
arr.push({
|
||||||
|
title: '上线情况分析',
|
||||||
|
// text1: '考试排名分析',
|
||||||
|
// text2: '考试排名百分比分析',
|
||||||
|
icon: sectionSrc1,
|
||||||
|
left: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if([4,5,6].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push({
|
||||||
|
title: '学校对比分析',
|
||||||
|
// text1: '教师群体分析',
|
||||||
|
// text2: '教师绩效Z值分析',
|
||||||
|
icon: sectionSrc2,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([15,16,20].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push({
|
||||||
|
title: '教师群体分析',
|
||||||
|
// text1: '整体分析',
|
||||||
|
// text2: '后续措施',
|
||||||
|
icon: sectionSrc3,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([7,45].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push( {
|
||||||
|
title: '学生群体分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc4,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([22,23,24,25].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push({
|
||||||
|
title: '选考分布分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc5,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([28].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push({
|
||||||
|
title: '试卷质量分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc8,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([26,27].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push( {
|
||||||
|
title: '学科小题分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc6,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if([47].includes(element?.reportItemModel?.id)){
|
||||||
|
arr.push({
|
||||||
|
title: '学科知识点分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc7,
|
||||||
|
left: 0
|
||||||
|
},)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
const uniqueArray = arr.filter((item, index, self) =>
|
||||||
|
index === self.findIndex((t) => t.title === item.title)
|
||||||
|
);
|
||||||
|
|
||||||
|
setList(uniqueArray);
|
||||||
|
|
||||||
|
},[modelList])
|
||||||
|
const catalogueList = [
|
||||||
|
{
|
||||||
|
title: '上线情况分析',
|
||||||
|
// text1: '考试排名分析',
|
||||||
|
// text2: '考试排名百分比分析',
|
||||||
|
icon: sectionSrc1,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学校对比分析',
|
||||||
|
// text1: '教师群体分析',
|
||||||
|
// text2: '教师绩效Z值分析',
|
||||||
|
icon: sectionSrc2,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '教师群体分析',
|
||||||
|
// text1: '整体分析',
|
||||||
|
// text2: '后续措施',
|
||||||
|
icon: sectionSrc3,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学生群体分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc4,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '选考分布分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc5,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学科小题分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc6,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '学科知识点分析',
|
||||||
|
// text1: '尖子生分析',
|
||||||
|
// text2: '临界生/冒进生分析',
|
||||||
|
icon: sectionSrc7,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.analysis_content_catalogue} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_catalogue_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>目录</span>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_catalogue_content}>
|
||||||
|
|
||||||
|
{list?.map((item, index) => {
|
||||||
|
return (<section className={styles.analysis_content_catalogue_section} key={index} style={{ marginLeft: item?.left }}>
|
||||||
|
<img src={item.icon} className={styles.analysis_content_catalogue_section_img} />
|
||||||
|
<section className={styles.analysis_content_catalogue_section_description}>
|
||||||
|
<span className={styles.analysis_content_catalogue_section_title}>{item?.title}</span>
|
||||||
|
{/* <span className={styles.analysis_content_catalogue_section_text}>{item?.text1} {item?.text2}</span> */}
|
||||||
|
</section>
|
||||||
|
</section>)
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Catalogue;
|
||||||
52
src/pages/analysis/components/Catalogue/index.less
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_catalogue{
|
||||||
|
margin-top: 16px;
|
||||||
|
height: 800px;
|
||||||
|
background: linear-gradient(134deg, #F7FEFF 0%, #FDFFFF 100%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 56px;
|
||||||
|
&_content{
|
||||||
|
margin-top: 64px;
|
||||||
|
|
||||||
|
}
|
||||||
|
&_section{
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 60%;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&_img{
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
&_description{
|
||||||
|
margin-left: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
&_title{
|
||||||
|
font-size: 26px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
margin-left: 22px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
248
src/pages/analysis/components/ChoiceExamAnalysis/index.jsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Space, Button, Spin, Table, Tag } from 'antd';
|
||||||
|
import { getSelectedDistribution } from '@/apis/leader';
|
||||||
|
import { getNoSingleCombine } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import Echarts from "@/components/Echarts";
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import sectionSrc from '@/assets/icon/analysis-catalogue-5.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ChoiceExamAnalysis = (props) => {
|
||||||
|
const { examId = '', subtitle = '', isShowChart = false, isShowTable = false, idx = '' , actionShow = true } = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
const [echartData, setEchartData] = useState({});
|
||||||
|
const [lengButton, setLengButton] = useState([]);
|
||||||
|
const echartRef = useRef();
|
||||||
|
|
||||||
|
const [subjectId, setSubjectId] = useState('');
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [selectedTag, setSelectedTag] = useState('');
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id,dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getNoSingleCombineInit();
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!examId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, schoolIds,subjectId]);
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getSelectedDistribution({
|
||||||
|
examId,
|
||||||
|
subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setLoading(false);
|
||||||
|
setResponseData(res);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id,res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
sorter: item?.sortable ? (a, b) => a[item?.prop] - b[item?.prop] : false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!res?.columnDiagramResult?.avg?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLengButton(res?.columnDiagramResult?.avg?.map(item => item?.lineName));
|
||||||
|
setEchartData(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: res?.columnDiagramResult?.avg?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: res?.columnDiagramResult?.avg?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName,
|
||||||
|
data: item?.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'paramsInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNoSingleCombineInit = async () => {
|
||||||
|
try {
|
||||||
|
const list = await getNoSingleCombine();
|
||||||
|
if (list?.code === 200) {
|
||||||
|
setTags(list?.data || []);
|
||||||
|
setSelectedTag(list.data?.[0]?.cname || '');
|
||||||
|
setSubjectId(list.data?.[0]?.id || '');
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getNoSingleCombineInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${selectedTag}考试选考组合分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleNavTagClick = (tag) => {
|
||||||
|
setSelectedTag(tag?.cname || '');
|
||||||
|
setSubjectId(tag?.id || '');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (
|
||||||
|
<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}考试选考组合分析-图表</span>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
echartRef?.current?.exportChart();
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content}>
|
||||||
|
<Space style={{marginBottom:8}}>
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
tags.map((item, index) => {
|
||||||
|
const isSelected = selectedTag === item?.cname;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.cname}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
<Echarts
|
||||||
|
data={echartData}
|
||||||
|
text={`考试选考组合分析*${subtitle}*${selectedTag}`}
|
||||||
|
type={"bar"}
|
||||||
|
lengButton={lengButton}
|
||||||
|
ref={echartRef}
|
||||||
|
hasTable={false}
|
||||||
|
hasExport={false}
|
||||||
|
/>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
{isShowTable ? (
|
||||||
|
<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}考试选考组合分析-表格</span>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Space style={{marginBottom:8}}>
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
tags.map((item, index) => {
|
||||||
|
const isSelected = selectedTag === item?.cname;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.cname}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChoiceExamAnalysis;
|
||||||
33
src/pages/analysis/components/ChoiceExamAnalysis/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
300
src/pages/analysis/components/ChoiceQuestionRate/index.jsx
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Space, Select, Button, Spin, Table } from 'antd';
|
||||||
|
import { getAnswerRate } from '@/apis/leader';
|
||||||
|
import { getSubjectList } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import Echarts from "@/components/Echarts";
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import sectionSrc from '@/assets/icon/analysis-catalogue-6.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ChoiceQuestionRate = (props) => {
|
||||||
|
|
||||||
|
|
||||||
|
const { examId = '', subtitle = '', isShowChart = false, isShowTable = false, idx = '' ,actionShow = true} = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [subjectId, setSubjectId] = useState("");
|
||||||
|
const [subjectName, setSubjectName] = useState(null);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
const [echartData1, setEchartData1] = useState({});
|
||||||
|
const [echartData2, setEchartData2] = useState({});
|
||||||
|
const [lengButton, setLengButton] = useState([]);
|
||||||
|
const echartRef1 = useRef();
|
||||||
|
const echartRef2 = useRef();
|
||||||
|
|
||||||
|
const [singleSubjectList, setSingleSubjectList] = useState([]);
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
expandIconColumnIndex: 2,
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
singleSubjectInit();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!examId || !subjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, schoolIds, subjectId]);
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getAnswerRate({
|
||||||
|
examId,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setLoading(false);
|
||||||
|
setResponseData(res);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
sorter: item?.sortable ? (a, b) => a[item?.prop] - b[item?.prop] : false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!res?.columnDiagramResult?.rate?.[0]?.data?.length || !res?.columnDiagramResult?.rate?.[0]?.xdata?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLengButton([res?.columnDiagramResult?.rate?.[0]?.lineName]);
|
||||||
|
setEchartData1(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: res?.columnDiagramResult?.rate?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: res?.columnDiagramResult?.rate?.[0]?.lineName,
|
||||||
|
data: res?.columnDiagramResult?.rate?.[0]?.data
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartData2(() => {
|
||||||
|
return {
|
||||||
|
radarText: {
|
||||||
|
indicator: res?.columnDiagramResult?.rate?.[0]?.xdata?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: res?.columnDiagramResult?.rate?.[0]?.lineName,
|
||||||
|
data: res?.columnDiagramResult?.rate?.[0]?.data,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const singleSubjectInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectList();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
const element = res?.data?.find(item => item?.cname === '数学');
|
||||||
|
setSubjectId(element?.id || '');
|
||||||
|
setSubjectName(element?.cname || null);
|
||||||
|
setSingleSubjectList(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'singleSubjectInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${subjectName}考试选择题答题率`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (
|
||||||
|
<>
|
||||||
|
<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}考试选择题答题率-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={singleSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
onChange={(value, options) => {
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(options?.label);
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
{actionShow && ( <Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
echartRef1?.current?.exportChart();
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content}>
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
<Echarts
|
||||||
|
text={`考试选择题答题率*${subtitle}${subjectName}*折线图`}
|
||||||
|
data={echartData1}
|
||||||
|
ref={echartRef1}
|
||||||
|
isShow={false}
|
||||||
|
type={"line"}
|
||||||
|
needTab={true}
|
||||||
|
lengButton={lengButton}
|
||||||
|
hasExport={false}
|
||||||
|
hasTable={false}
|
||||||
|
/>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}考试选择题答题率-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={singleSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
onChange={(value, options) => {
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(options?.label);
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
echartRef2?.current?.exportChart();
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content}>
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
<Echarts
|
||||||
|
text={`考试选择题答题率*${subtitle}${subjectName}*雷达图`}
|
||||||
|
data={echartData2}
|
||||||
|
ref={echartRef2}
|
||||||
|
isShow={false}
|
||||||
|
type={"radar"}
|
||||||
|
needTab={true}
|
||||||
|
lengButton={lengButton}
|
||||||
|
hasExport={false}
|
||||||
|
hasTable={false}
|
||||||
|
/>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{isShowTable ? (<section className={styles.analysis_content_table} id={`operationExport${idx + 2}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}考试选择题答题率-表格</span>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
{actionShow && ( <Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChoiceQuestionRate;
|
||||||
33
src/pages/analysis/components/ChoiceQuestionRate/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,334 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Space, Button, Spin, Table } from 'antd';
|
||||||
|
import { getCombinationDistribution } from '@/apis/leader';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import Echarts from "@/components/Echarts";
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import sectionSrc from '@/assets/icon/analysis-catalogue-5.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const CombineExaminationSubject = (props) => {
|
||||||
|
const { examId = '', subtitle = '', isShowChart = false, isShowTable = false,idx='' ,actionShow = true } = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
const [echartData1, setEchartData1] = useState({});
|
||||||
|
const [echartData2, setEchartData2] = useState({});
|
||||||
|
const [lengButton, setLengButton] = useState([]);
|
||||||
|
const echartRef = useRef();
|
||||||
|
|
||||||
|
const [selectTag, setSelectTag] = useState('');
|
||||||
|
const [navTags, setNavTags] = useState([]);
|
||||||
|
const [allEchartData, setAllEchartData] = useState({});
|
||||||
|
const [selectedNavTag, setSelectedNavTag] = useState('');
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id,dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedNavTag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
num: allEchartData?.num?.filter(item => item?.lineName === selectedNavTag),
|
||||||
|
rate: allEchartData?.rate?.filter(item => item?.lineName === selectedNavTag),
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
setEchartData1(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: obj.num?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: '人数',
|
||||||
|
data: obj.num?.[0]?.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartData2(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: obj.rate?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "人数占比",
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: '人数占比',
|
||||||
|
data: obj.rate?.[0]?.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [selectedNavTag]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!examId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, schoolIds]);
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getCombinationDistribution({
|
||||||
|
examId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setLoading(false);
|
||||||
|
setResponseData(res);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id,res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 150,
|
||||||
|
sorter: item?.sortable ? (a, b) => a[item?.prop] - b[item?.prop] : false,
|
||||||
|
children: item?.children?.map(element => {
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 160,
|
||||||
|
sorter: element?.sortable ? (a, b) => a[element?.prop] - b[element?.prop] : false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res?.columnDiagramResult || !res?.columnDiagramResult?.num?.length > 0 || !res?.columnDiagramResult?.rate?.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedName = res?.columnDiagramResult?.num?.map(item => item?.lineName)[0];
|
||||||
|
setNavTags(res?.columnDiagramResult?.num?.map(item => item?.lineName));
|
||||||
|
setSelectedNavTag(selectedName);
|
||||||
|
setLengButton(['人数', '人数占比']);
|
||||||
|
setSelectTag('人数');
|
||||||
|
|
||||||
|
setAllEchartData(res?.columnDiagramResult);
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
num: res?.columnDiagramResult?.num?.filter(item => item?.lineName === selectedName),
|
||||||
|
rate: res?.columnDiagramResult?.rate?.filter(item => item?.lineName === selectedName),
|
||||||
|
};
|
||||||
|
|
||||||
|
setEchartData1(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: obj.num?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "人数",
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: '人数',
|
||||||
|
data: obj.num?.[0]?.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setEchartData2(() => {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
name: "",
|
||||||
|
type: "category",
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: obj.rate?.[0]?.xdata,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "人数占比",
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
yChartData: [
|
||||||
|
{
|
||||||
|
name: '人数占比',
|
||||||
|
data: obj.rate?.[0]?.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'paramsInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${selectedNavTag}选考组合分布分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (
|
||||||
|
<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}选考组合分布分析-图表</span>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
echartRef?.current?.exportChart();
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content}>
|
||||||
|
<Space style={{ marginBottom: 8 }}>
|
||||||
|
{navTags?.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
type={item === selectedNavTag ? 'primary' : 'default'}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedNavTag(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
|
||||||
|
{lengButton?.length > 0 ? (
|
||||||
|
<Space>
|
||||||
|
{lengButton?.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
type={item === selectTag ? 'primary' : 'default'}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectTag(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
) : null}
|
||||||
|
{selectTag === '人数' ? (<Echarts
|
||||||
|
isShow={false}
|
||||||
|
data={echartData1}
|
||||||
|
hasExport={false}
|
||||||
|
ref={echartRef}
|
||||||
|
type='bar'
|
||||||
|
text={`选考组合分布分析*${subtitle}${selectedNavTag}*人数`}
|
||||||
|
lengButton={['人数']}
|
||||||
|
hasTable={false}
|
||||||
|
/>) : null}
|
||||||
|
{selectTag === '人数占比' ? (<Echarts
|
||||||
|
isShow={false}
|
||||||
|
type="bar"
|
||||||
|
data={echartData2}
|
||||||
|
ref={echartRef}
|
||||||
|
hasTable={false}
|
||||||
|
hasExport={false}
|
||||||
|
text={`选考组合分布分析*${subtitle}${selectedNavTag}*人数占比`}
|
||||||
|
lengButton={['人数占比']}
|
||||||
|
/>) : null}
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
{isShowTable ? (
|
||||||
|
<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={sectionSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}选考组合分布分析-表格</span>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
{actionShow && ( <Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>)}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CombineExaminationSubject;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
807
src/pages/analysis/components/ContrastExamOnline/index.jsx
Normal file
@ -0,0 +1,807 @@
|
|||||||
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import { Space, Select, Button, Spin, Table, Tag,InputNumber,message } from 'antd';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getOnlineRankingAnalysis } from '@/apis/exam';
|
||||||
|
import { getSubjectList } from '@/apis/subject';
|
||||||
|
import { getScoreLineList } from '@/apis/data'
|
||||||
|
import { getSubjectListAll } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import StudentGroup from '@/components/StudentGroup';
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import navIconSrc from '@/assets/icon/analysis-navigation-icon.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ContrastExamOnline = (props) => {
|
||||||
|
const { examId = '', subtitle = '', examList = [], isShowChart = false, isShowTable = false, idx = '' ,actionShow = true } = props;
|
||||||
|
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [subjectId, setSubjectId] = useState("");
|
||||||
|
const [subjectName, setSubjectName] = useState(null);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [allSubjectList, setAllSubjectList] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
|
||||||
|
const [typeList, setTypeList] = useState([]);
|
||||||
|
const [selectType, setSelectType] = useState('');
|
||||||
|
|
||||||
|
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [selectedTags, setSelectedTags] = useState([]);
|
||||||
|
const [echartXData, setEchartXData] = useState([]);
|
||||||
|
|
||||||
|
const [mockChartData, setMockChartData] = useState([]);
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
|
let chartInstance = null;
|
||||||
|
const [echartImg, setEchartImg] = useState();
|
||||||
|
|
||||||
|
|
||||||
|
const studentGroupRef = useRef();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [studentGroupParams, setStudentGroupParams] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const [selectValues, setSelectValues] = useState([]);
|
||||||
|
const [subjectIds, setSubjectIds] = useState([]);
|
||||||
|
|
||||||
|
const [subjectOptions, setSubjectOptions] = useState([]);
|
||||||
|
const [scoreLine, setScoreLine] = useState({});
|
||||||
|
|
||||||
|
const [score, setScore] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
const [scoreLineList, setScoreLineList] = useState([]);
|
||||||
|
|
||||||
|
const [allEchartData, setAllEchartData] = useState({});
|
||||||
|
|
||||||
|
const [rankingList, setRankingList] = useState([]);
|
||||||
|
const [selectRaking, setSelectRaking] = useState();
|
||||||
|
|
||||||
|
const labelOption = {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 24,
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
formatter: (params) => {
|
||||||
|
if (['增幅','上线率','上线率增长'].includes(selectType)) {
|
||||||
|
return `${params.value}%`;
|
||||||
|
}
|
||||||
|
return `${params.value}人`
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
rich: {
|
||||||
|
name: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const commonSeries = {
|
||||||
|
type: 'line',
|
||||||
|
barGap: 0,
|
||||||
|
label: labelOption,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionData = useMemo(() => {
|
||||||
|
return mockChartData?.filter((item) =>
|
||||||
|
selectedTags.includes(item?.name),
|
||||||
|
);
|
||||||
|
}, [selectedTags, mockChartData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// allSubjectInit();
|
||||||
|
getSortInit();
|
||||||
|
getSubjectSelectOptions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!examId || !examList.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, schoolIds, examList, selectRaking, scoreLine]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectType) {
|
||||||
|
setSelectType(selectType);
|
||||||
|
if (selectType === '上线人数') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartData.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '上线人数增长') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartData?.incr?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '上线率') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartData?.rate?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '上线率增长') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartData?.rateRatio?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '增幅') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartData?.zf?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectType]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(!scoreLineList?.length){
|
||||||
|
setScoreLine({});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},[scoreLineList])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!echartXData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chartRef.current) {
|
||||||
|
// 初始化图表
|
||||||
|
chartInstance = echarts.init(chartRef.current);
|
||||||
|
|
||||||
|
// 设置配置项
|
||||||
|
const option = {
|
||||||
|
// dataZoom: [
|
||||||
|
// {
|
||||||
|
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||||
|
// show: true,
|
||||||
|
// start: 0, // 初始起点
|
||||||
|
// end: 30, // 初始终点
|
||||||
|
// handleSize: 8,
|
||||||
|
// xAxisIndex: [0]
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text: `${subtitle}${scoreLineList?.length > 0 ? scoreLine?.name : rankingList?.find(item=>item?.value === selectRaking)?.label}*对比考试上线情况分析-图表`,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 35,
|
||||||
|
interval: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
barGap: '10%',
|
||||||
|
barWidth: "10%",
|
||||||
|
xAxis: {
|
||||||
|
name: '考试',
|
||||||
|
type: 'category',
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: echartXData,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: echartXData?.length > 6 ? 45 : 0,
|
||||||
|
interval: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
containLabel: true,
|
||||||
|
top: "20%"
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "",
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: tags,
|
||||||
|
right: 10, // 设置右侧边距
|
||||||
|
top: 16, // 设置顶部边距
|
||||||
|
selectedMode: false,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: intersectionData.map((item) => ({
|
||||||
|
...commonSeries,
|
||||||
|
name: item?.name || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
const resizeHandler = () => chartInstance?.resize();
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
chartInstance?.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedTags, echartXData, mockChartData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getOnlineRankingAnalysis({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
lineId: selectRaking,
|
||||||
|
schools: schoolIds,
|
||||||
|
// subjectId:subjectIds,
|
||||||
|
scoreLine,
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
setLoading(false);
|
||||||
|
setResponseData(res);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id, res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setTags(res?.columnDiagramResult?.num?.map(item => item?.lineName) || []);
|
||||||
|
setSelectedTags([res?.columnDiagramResult?.num?.[0]?.lineName || '']);
|
||||||
|
|
||||||
|
setTypeList(['上线人数', '上线人数增长', '上线率', '上线率增长', '增幅']);
|
||||||
|
setSelectType('上线人数');
|
||||||
|
setEchartXData(res?.columnDiagramResult?.num?.[0]?.xdata || []);
|
||||||
|
setAllEchartData(res?.columnDiagramResult);
|
||||||
|
|
||||||
|
|
||||||
|
setMockChartData(() => {
|
||||||
|
return res?.columnDiagramResult?.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: ['区县', '考试人数']?.includes(item?.label) ? 200 : 480,
|
||||||
|
children: item?.children?.map(element => {
|
||||||
|
if (element?.label=== '上线人数') {
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 120,
|
||||||
|
render: (text) => (
|
||||||
|
<Button type="link" style={{ color: "#4ABB6A" }} onClick={() => {
|
||||||
|
const examId = text?.examId;
|
||||||
|
setTitle(text?.typeDisc);
|
||||||
|
studentGroupRef.current?.openModal();
|
||||||
|
setStudentGroupParams({
|
||||||
|
examId,
|
||||||
|
students: text?.data
|
||||||
|
});
|
||||||
|
|
||||||
|
}}>{text?.value}</Button>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 120,
|
||||||
|
sorter: (a, b) => a[element?.prop] - b[element?.prop],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'exam-ranking-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const allSubjectInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectListAll();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSubjectId(res?.data?.[0]?.id || '');
|
||||||
|
setSubjectName(res?.data?.[0]?.cname || null);
|
||||||
|
setAllSubjectList(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'singleSubjectInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getScoreLineList({ stage: 3, examId });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSelectRaking(res?.data?.[0]?.id || '');
|
||||||
|
setRankingList(() => {
|
||||||
|
return res?.data?.map(item => ({
|
||||||
|
value: item?.id,
|
||||||
|
label: item?.name
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'get-sort-init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSubjectSelectOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectList({ stages: 3, contain256: false, level: 1, single: true, showFlag: true });
|
||||||
|
if (res?.code === 200) {
|
||||||
|
|
||||||
|
setSelectValues(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'getSubjectSelectOptions-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTagClick = (tag) => {
|
||||||
|
if (selectedTags.includes(tag)) {
|
||||||
|
// 如果 Tag 已经选中,则移除
|
||||||
|
setSelectedTags((prev) => {
|
||||||
|
if (prev.length === 1) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return selectedTags.filter((item) => item !== tag)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 如果 Tag 未选中,则添加
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}${scoreLineList?.length > 0 ? scoreLine?.name : rankingList?.find(item=>item?.value === selectRaking)?.label}*对比考试上线情况分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 导出图片
|
||||||
|
const exportChart = (ref) => {
|
||||||
|
const instance = echarts.getInstanceByDom(ref?.current);
|
||||||
|
if (instance) {
|
||||||
|
const base64 = instance?.getDataURL({
|
||||||
|
type: "png",
|
||||||
|
pixelRatio: 2, // 导出的图片分辨率比例
|
||||||
|
backgroundColor: "#fff", // 导出图片的背景色
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartImg(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//下载图片
|
||||||
|
const downloadImg = () => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = `图表_${new Date().getTime()}.png`;
|
||||||
|
link.href = echartImg;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
setEchartImg("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id, dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleRakingClick = (item) => {
|
||||||
|
setSelectRaking(item?.value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title} >
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试上线情况分析-图表</span>
|
||||||
|
<Space>
|
||||||
|
{/* <Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
exportChart(chartRef)
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<section style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<span style={{ color: " #8a8a8a" }}>设置选科范围:</span>
|
||||||
|
<Select style={{ width: 260 }} maxTagCount={2} placeholder='请选择学科' value={subjectIds} mode='multiple' allowClear options={selectValues} onChange={(value, options) => {
|
||||||
|
if (!value.length) {
|
||||||
|
setSubjectIds([]);
|
||||||
|
setSubjectOptions([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectIds(value);
|
||||||
|
setSubjectOptions(options);
|
||||||
|
|
||||||
|
}} />
|
||||||
|
</section>
|
||||||
|
<section style={{ display: "flex", alignItems: "center", marginTop: 16 }}>
|
||||||
|
<span style={{ color: " #8a8a8a" }}>设置分数线:</span>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
value={score}
|
||||||
|
onChange={(value) => setScore(value)}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
分以上
|
||||||
|
<Button onClick={() => {
|
||||||
|
if (typeof score !== 'number') {
|
||||||
|
message.error('请输入数字');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
score,
|
||||||
|
name: !!subjectOptions?.length ?`总分${score}以上,选${subjectOptions?.map(item => item?.label).join(',')}` : `总分${score}以上`,
|
||||||
|
combIds: subjectIds?.join(',')
|
||||||
|
};
|
||||||
|
if(scoreLineList?.map(item=>item?.name).includes(item.name)){
|
||||||
|
message.error('该分数线已存在,勿重新添加');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setScoreLine(item);
|
||||||
|
setScoreLineList((prev) => {
|
||||||
|
return [...prev, item];
|
||||||
|
})
|
||||||
|
setScore(null);
|
||||||
|
}}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
{scoreLineList?.length ? (
|
||||||
|
<Space>
|
||||||
|
{scoreLineList?.map((item, index) => {
|
||||||
|
const isSelected = scoreLine?.name === item?.name;
|
||||||
|
return (<Tag
|
||||||
|
key={index}
|
||||||
|
color={isSelected ? "green" : "default"}
|
||||||
|
closable
|
||||||
|
style={{cursor:'pointer'}}
|
||||||
|
onClick={()=>{
|
||||||
|
setScoreLine(item);
|
||||||
|
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setScoreLineList(scoreLineList.filter(i => i?.name !== item?.name))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.name}
|
||||||
|
</Tag>);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
{!scoreLineList?.length ? (<Space style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
{rankingList.length > 0 ? (
|
||||||
|
rankingList.map((item, index) => {
|
||||||
|
const isSelected = selectRaking === item?.value;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleRakingClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>) : null}
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
<Space >
|
||||||
|
{typeList.length > 0 ? (
|
||||||
|
typeList.map((item, index) => {
|
||||||
|
const isSelected = selectType === item;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setSelectType(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
<Space >
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
tags.map((item, index) => {
|
||||||
|
const isSelected = selectedTags.includes(item);
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content} >
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
<div ref={chartRef} style={{ width: '100%', height: '460px' }}></div>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
{isShowTable ? (<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试上线情况分析-表格</span>
|
||||||
|
<Space>
|
||||||
|
{/* <Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>)}
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<section >
|
||||||
|
{scoreLineList?.length ? (
|
||||||
|
<Space>
|
||||||
|
{scoreLineList?.map((item, index) => {
|
||||||
|
const isSelected = scoreLine?.name === item?.name;
|
||||||
|
return (<Tag
|
||||||
|
key={index}
|
||||||
|
color={isSelected ? "green" : "default"}
|
||||||
|
closable
|
||||||
|
style={{cursor:'pointer'}}
|
||||||
|
onClick={()=>{
|
||||||
|
setScoreLine(item);
|
||||||
|
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setScoreLineList(scoreLineList.filter(i => i?.name !== item?.name))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.name}
|
||||||
|
</Tag>);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
{!scoreLineList?.length ? (<Space style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
{rankingList.length > 0 ? (
|
||||||
|
rankingList.map((item, index) => {
|
||||||
|
const isSelected = selectRaking === item?.value;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleRakingClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>) : null}
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
<StudentGroup
|
||||||
|
ref={studentGroupRef}
|
||||||
|
params={studentGroupParams}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
{echartImg && (
|
||||||
|
<Modal
|
||||||
|
title="导出图表"
|
||||||
|
width={'80vw'}
|
||||||
|
className="custom-modal"
|
||||||
|
open={true}
|
||||||
|
okText="下载"
|
||||||
|
onCancel={() => {
|
||||||
|
setEchartImg("");
|
||||||
|
}}
|
||||||
|
onOk={downloadImg}
|
||||||
|
>
|
||||||
|
<Flex style={{ height: "100%" }} alignItems="center" justify="center">
|
||||||
|
<Image src={echartImg} width={"100%"} />
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContrastExamOnline;
|
||||||
33
src/pages/analysis/components/ContrastExamOnline/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
747
src/pages/analysis/components/ContrastExamParams/index.jsx
Normal file
@ -0,0 +1,747 @@
|
|||||||
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import { Space, Select, Button, Spin, Table, Tag, Modal } from 'antd';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getParamsRanking } from '@/apis/exam';
|
||||||
|
import { getSubjectListAll, getSort, getParamsType, } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import StudentGroup from '@/components/StudentGroup';
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import navIconSrc from '@/assets/icon/analysis-navigation-icon.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ContrastExamParams = (props) => {
|
||||||
|
|
||||||
|
const { examId = '', subtitle = '', examList = [], isShowChart = false, isShowTable = false,idx='',actionShow = true} = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [subjectId, setSubjectId] = useState("");
|
||||||
|
const [subjectName, setSubjectName] = useState(null);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [allSubjectList, setAllSubjectList] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
const [spinning, setSpinning] = useState(false);
|
||||||
|
const [paramsType, setParamsType] = useState();
|
||||||
|
const [typeList, setTypeList] = useState([]);
|
||||||
|
const [selectType, setSelectType] = useState('');
|
||||||
|
const [allEchartDataParams, setAllEchartDataParams] = useState({});
|
||||||
|
const [sortParamsList, setSortParamsList] = useState([]);
|
||||||
|
const [selectSortParams, setSelectSortParams] = useState();
|
||||||
|
const [paramsLimit, setParamsLimit] = useState();
|
||||||
|
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [selectedTags, setSelectedTags] = useState([]);
|
||||||
|
const [echartXData, setEchartXData] = useState([]);
|
||||||
|
|
||||||
|
const [selectedNavTag, setSelectedNavTag] = useState('');
|
||||||
|
|
||||||
|
const [mockChartData, setMockChartData] = useState([]);
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
|
let chartInstance = null;
|
||||||
|
const [echartImg, setEchartImg] = useState();
|
||||||
|
|
||||||
|
|
||||||
|
const studentGroupRef = useRef();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [studentGroupParams, setStudentGroupParams] = useState({});
|
||||||
|
|
||||||
|
const [paramsTypeList, setParamsTypeList] = useState([]);
|
||||||
|
const [type, setType] = useState();
|
||||||
|
|
||||||
|
const [chartTagList, setChartTagList] = useState([]);
|
||||||
|
const [chartTag, setChartTag] = useState('');
|
||||||
|
|
||||||
|
const labelOption = {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 24,
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.value}`
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
rich: {
|
||||||
|
name: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const commonSeries = {
|
||||||
|
type: 'line',
|
||||||
|
barGap: 0,
|
||||||
|
label: labelOption,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionData = useMemo(() => {
|
||||||
|
return mockChartData?.filter((item) =>
|
||||||
|
selectedTags.includes(item.name),
|
||||||
|
);
|
||||||
|
}, [selectedTags, mockChartData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
allSubjectInit();
|
||||||
|
getSortInit();
|
||||||
|
paramsTypeInit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!examId || !examList.length > 0 || !subjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, subjectId, schoolIds, examList, paramsLimit, paramsType]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type || subjectId) {
|
||||||
|
viewInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [examId, subjectId, schoolIds, examList, paramsLimit, type]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectType) {
|
||||||
|
if (selectType === chartTag) {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataParams.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '增幅数') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataParams?.incr?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '增幅') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataParams?.rate?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!echartXData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chartRef.current) {
|
||||||
|
// 初始化图表
|
||||||
|
chartInstance = echarts.init(chartRef.current);
|
||||||
|
|
||||||
|
// 设置配置项
|
||||||
|
const option = {
|
||||||
|
// dataZoom: [
|
||||||
|
// {
|
||||||
|
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||||
|
// show: true,
|
||||||
|
// start: 0, // 初始起点
|
||||||
|
// end: 30, // 初始终点
|
||||||
|
// handleSize: 8,
|
||||||
|
// xAxisIndex: [0]
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text: `${subtitle}${subjectName}*对比考试参数情况分析-图表`,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 35,
|
||||||
|
interval: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
name: '考试',
|
||||||
|
type: 'category',
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: echartXData,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: echartXData?.length > 6 ? 45 : 0,
|
||||||
|
interval: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "",
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: tags,
|
||||||
|
right: 10, // 设置右侧边距
|
||||||
|
top: 24, // 设置顶部边距
|
||||||
|
selectedMode: false,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
barGap: '10%',
|
||||||
|
barWidth: "10%",
|
||||||
|
series: intersectionData.map((item) => ({
|
||||||
|
...commonSeries,
|
||||||
|
name: item.name,
|
||||||
|
data: item.data,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
const resizeHandler = () => chartInstance?.resize();
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
chartInstance?.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedTags, echartXData, mockChartData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getParamsRanking({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
limit: paramsLimit,
|
||||||
|
type: paramsType,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
setResponseData(res);
|
||||||
|
setLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id,res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: item?.label === '平均分' ? 90 : item?.label === '区县' ? 300 : 180,
|
||||||
|
children: item?.children?.map(element => {
|
||||||
|
if (element?.label === '人数') {
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
render: (text) => (
|
||||||
|
<Button type="link" style={{ color: "#4ABB6A" }} onClick={() => {
|
||||||
|
const examId = element?.prop?.split('-')[1];
|
||||||
|
setTitle(text?.typeDisc);
|
||||||
|
studentGroupRef.current?.openModal();
|
||||||
|
setStudentGroupParams({
|
||||||
|
examId,
|
||||||
|
students: text?.data
|
||||||
|
});
|
||||||
|
|
||||||
|
}}>{text?.value}</Button>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
sorter: (a, b) => a[element?.prop] - b[element?.prop],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'exam-ranking-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const viewInit = async () => {
|
||||||
|
setSpinning(true);
|
||||||
|
try {
|
||||||
|
const res = await getParamsRanking({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
limit: paramsLimit,
|
||||||
|
type: paramsType,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
setSpinning(false);
|
||||||
|
if (Object.values(res?.columnDiagramResult)?.length) {
|
||||||
|
setAllEchartDataParams(res?.columnDiagramResult || {});
|
||||||
|
|
||||||
|
setTypeList([chartTag, '增幅数', '增幅']);
|
||||||
|
setSelectType(chartTag);
|
||||||
|
setTags(res?.columnDiagramResult?.num?.map(item => item?.lineName));
|
||||||
|
setSelectedTags([res?.columnDiagramResult?.num?.[0]?.lineName]);
|
||||||
|
setEchartXData(res?.columnDiagramResult?.num?.[0]?.xdata);
|
||||||
|
|
||||||
|
setMockChartData(() => {
|
||||||
|
return res?.columnDiagramResult?.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName,
|
||||||
|
data: item?.data,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setSpinning(false);
|
||||||
|
console.log(error, 'exam-ranking-error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSubjectInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectListAll();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSubjectId(res?.data?.[0]?.id || '');
|
||||||
|
setSubjectName(res?.data?.[0]?.cname || null);
|
||||||
|
setAllSubjectList(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'singleSubjectInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSort();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSelectSortParams(999999);
|
||||||
|
setParamsLimit(999999);
|
||||||
|
setSortParamsList(() => {
|
||||||
|
return [{ label: "全部", value: 999999 }, ...res?.data];
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'get-sort-init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const paramsTypeInit = async () => {
|
||||||
|
try {
|
||||||
|
const params = await getParamsType();
|
||||||
|
if (params.code === 200) {
|
||||||
|
setParamsTypeList(params?.data || []);
|
||||||
|
setParamsType(params?.data?.[0]?.value || '');
|
||||||
|
setSelectedNavTag(params?.data?.[0]?.label || '');
|
||||||
|
setChartTagList(params?.data || []);
|
||||||
|
setChartTag(params?.data?.[0]?.label || '');
|
||||||
|
setType(params?.data?.[0]?.value || '')
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'params-init-error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTagClick = (tag) => {
|
||||||
|
if (selectedTags.includes(tag)) {
|
||||||
|
// 如果 Tag 已经选中,则移除
|
||||||
|
setSelectedTags((prev) => {
|
||||||
|
if (prev.length === 1) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return selectedTags.filter((item) => item !== tag)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 如果 Tag 未选中,则添加
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${subjectName}对比考试参数分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 导出图片
|
||||||
|
const exportChart = (ref) => {
|
||||||
|
const instance = echarts.getInstanceByDom(ref?.current);
|
||||||
|
if (instance) {
|
||||||
|
const base64 = instance?.getDataURL({
|
||||||
|
type: "png",
|
||||||
|
pixelRatio: 2, // 导出的图片分辨率比例
|
||||||
|
backgroundColor: "#fff", // 导出图片的背景色
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartImg(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//下载图片
|
||||||
|
const downloadImg = () => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = `图表_${new Date().getTime()}.png`;
|
||||||
|
link.href = echartImg;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
setEchartImg("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id,dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavTagClick = (tag) => {
|
||||||
|
setSelectedNavTag(tag?.label || '');
|
||||||
|
setParamsType(tag?.value || '');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavTagClick2 = (tag) => {
|
||||||
|
setChartTag(tag?.label || '');
|
||||||
|
setType(tag?.value || '');
|
||||||
|
|
||||||
|
};
|
||||||
|
const handleRakingClick = (item) => {
|
||||||
|
setSelectSortParams(item?.value);
|
||||||
|
setParamsLimit(item?.value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试参数情况分析-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && <Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
exportChart(chartRef)
|
||||||
|
}}>导出图表</Button>}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Space>
|
||||||
|
{sortParamsList?.length > 0 ? (
|
||||||
|
sortParamsList?.map((item, index) => {
|
||||||
|
const isSelected = selectSortParams === item?.value;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleRakingClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space style={{ marginTop: 8, width: '100%' }}>
|
||||||
|
{chartTagList?.length > 0 ? (
|
||||||
|
chartTagList?.map((item, index) => {
|
||||||
|
const isSelected = chartTag === item?.label;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick2(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<Space style={{ marginTop: 8, width: '100%' }}>
|
||||||
|
{typeList.length > 0 ? (
|
||||||
|
typeList.map((item, index) => {
|
||||||
|
const isSelected = selectType === item;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setSelectType(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
<Space>
|
||||||
|
{tags?.length > 0 ? (
|
||||||
|
tags?.map((item, index) => {
|
||||||
|
const isSelected = selectedTags.includes(item);
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content}>
|
||||||
|
<Spin spinning={spinning} tip='计算中,请稍后'>
|
||||||
|
<div ref={chartRef} style={{ width: '100%', height: '460px' }}></div>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
{isShowTable ? (
|
||||||
|
<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试参数情况分析-表格</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && <Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button> }
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Space style={{ marginTop: 8 }}>
|
||||||
|
{sortParamsList.length > 0 ? (
|
||||||
|
sortParamsList.map((item, index) => {
|
||||||
|
const isSelected = selectSortParams === item?.value;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleRakingClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<Space style={{ marginTop: 8 }}>
|
||||||
|
{paramsTypeList.length > 0 ? (
|
||||||
|
paramsTypeList.map((item, index) => {
|
||||||
|
const isSelected = selectedNavTag === item?.label;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
<StudentGroup
|
||||||
|
ref={studentGroupRef}
|
||||||
|
params={studentGroupParams}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
{echartImg && (
|
||||||
|
<Modal
|
||||||
|
title="导出图表"
|
||||||
|
width={'80vw'}
|
||||||
|
className="custom-modal"
|
||||||
|
open={true}
|
||||||
|
okText="下载"
|
||||||
|
onCancel={() => {
|
||||||
|
setEchartImg("");
|
||||||
|
}}
|
||||||
|
onOk={downloadImg}
|
||||||
|
>
|
||||||
|
<Flex style={{ height: "100%" }} alignItems="center" justify="center">
|
||||||
|
<Image src={echartImg} width={"100%"} />
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContrastExamParams;
|
||||||
33
src/pages/analysis/components/ContrastExamParams/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
643
src/pages/analysis/components/ContrastExamPercentage/index.jsx
Normal file
@ -0,0 +1,643 @@
|
|||||||
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import { Space, Select, Button, Spin, Table, Tag } from 'antd';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getExamRanking, getPercentageRanking, getPercentageRankingView } from '@/apis/exam';
|
||||||
|
import { getSubjectListAll, getPercent } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import StudentGroup from '@/components/StudentGroup';
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import navIconSrc from '@/assets/icon/analysis-navigation-icon.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ContrastExamPercentage = (props) => {
|
||||||
|
const { examId = '', subtitle = '', examList = [], isShowChart = false, isShowTable = false,idx='' , actionShow = true } = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [subjectId, setSubjectId] = useState("");
|
||||||
|
const [subjectName, setSubjectName] = useState(null);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [allSubjectList, setAllSubjectList] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
const [spinning, setSpinning] = useState(false);
|
||||||
|
const [typeList, setTypeList] = useState([]);
|
||||||
|
const [selectType, setSelectType] = useState('');
|
||||||
|
const [allEchartDataPercent, setAllEchartDataPercent] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [selectedTags, setSelectedTags] = useState([]);
|
||||||
|
const [echartXData, setEchartXData] = useState([]);
|
||||||
|
|
||||||
|
const [selectedNavTag, setSelectedNavTag] = useState('');
|
||||||
|
|
||||||
|
const [mockChartData, setMockChartData] = useState([]);
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
|
let chartInstance = null;
|
||||||
|
const [echartImg, setEchartImg] = useState();
|
||||||
|
|
||||||
|
|
||||||
|
const studentGroupRef = useRef();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [studentGroupParams, setStudentGroupParams] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const [rateList, setRateList] = useState([]);
|
||||||
|
const [rate, setRate] = useState();
|
||||||
|
|
||||||
|
const labelOption = {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 24,
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.value}`
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
rich: {
|
||||||
|
name: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const commonSeries = {
|
||||||
|
type: 'line',
|
||||||
|
barGap: 0,
|
||||||
|
label: labelOption,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionData = useMemo(() => {
|
||||||
|
return mockChartData?.filter((item) =>
|
||||||
|
selectedTags.includes(item.name),
|
||||||
|
);
|
||||||
|
}, [selectedTags, mockChartData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
allSubjectInit();
|
||||||
|
percentListInit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!examId || !examList.length > 0 || !subjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
viewInit();
|
||||||
|
|
||||||
|
|
||||||
|
}, [examId, schoolIds, examList, rate, subjectId]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectType) {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataPercent?.[selectType]?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!echartXData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chartRef.current) {
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
chartInstance = echarts.init(chartRef.current);
|
||||||
|
|
||||||
|
// 设置配置项
|
||||||
|
const option = {
|
||||||
|
// dataZoom: [
|
||||||
|
// {
|
||||||
|
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||||
|
// show: true,
|
||||||
|
// start: 0, // 初始起点
|
||||||
|
// end: 30, // 初始终点
|
||||||
|
// handleSize: 8,
|
||||||
|
// xAxisIndex: [0]
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text: `${subtitle}${subjectName}*对比考试百分比情况分析-图表`,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 35,
|
||||||
|
interval: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
barGap: '10%',
|
||||||
|
barWidth: "10%",
|
||||||
|
xAxis: {
|
||||||
|
name: '考试',
|
||||||
|
type: 'category',
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: echartXData,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: echartXData?.length > 6 ? 45 : 0,
|
||||||
|
interval: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "人",
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: tags,
|
||||||
|
right: 10, // 设置右侧边距
|
||||||
|
top: 24, // 设置顶部边距
|
||||||
|
selectedMode: false,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: intersectionData?.map((item) => ({
|
||||||
|
...commonSeries,
|
||||||
|
name: item.name,
|
||||||
|
data: item.data,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
const resizeHandler = () => chartInstance?.resize();
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
chartInstance?.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedTags, echartXData, mockChartData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getPercentageRanking({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
limit: rate,
|
||||||
|
rate,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
setResponseData(res);
|
||||||
|
setLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id,res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: item?.label === '人数' ? 80 : item?.label === '区县' ? 300 : 200,
|
||||||
|
children: item?.children?.map(element => {
|
||||||
|
if (element?.label === '人数') {
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
render: (text) => (
|
||||||
|
<Button type="link" style={{ color: "#4ABB6A" }} onClick={() => {
|
||||||
|
const examId = element?.prop?.split('-')[1];
|
||||||
|
setTitle(text?.typeDisc);
|
||||||
|
studentGroupRef.current?.openModal();
|
||||||
|
setStudentGroupParams({
|
||||||
|
examId,
|
||||||
|
students: text?.data
|
||||||
|
});
|
||||||
|
|
||||||
|
}}>{text?.value}</Button>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
sorter: (a, b) => a[element?.prop] - b[element?.prop],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'exam-ranking-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const viewInit = async () => {
|
||||||
|
setSpinning(true);
|
||||||
|
try {
|
||||||
|
|
||||||
|
const res = await getPercentageRankingView({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
limit: rate,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
setSpinning(false);
|
||||||
|
let list = Object.keys(res?.columnDiagramResult || {});
|
||||||
|
console.log(list, '西湖list');
|
||||||
|
|
||||||
|
setTypeList(list);
|
||||||
|
setSelectType(list?.[0]);
|
||||||
|
setAllEchartDataPercent(res?.columnDiagramResult || {});
|
||||||
|
const element = res?.columnDiagramResult?.[list?.[0]];
|
||||||
|
const label = element?.map(item => item?.lineName);
|
||||||
|
setTags(label);
|
||||||
|
setSelectedTags([label?.[0]]);
|
||||||
|
setEchartXData(element?.[0]?.xdata || []);
|
||||||
|
|
||||||
|
|
||||||
|
setMockChartData(() => {
|
||||||
|
return element?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName,
|
||||||
|
data: item?.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setSpinning(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setSpinning(false);
|
||||||
|
console.log(error, 'ranking-view-init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSubjectInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectListAll();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSubjectId(res?.data?.[0]?.id || '');
|
||||||
|
setSubjectName(res?.data?.[0]?.cname || null);
|
||||||
|
setAllSubjectList(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'singleSubjectInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const percentListInit = async () => {
|
||||||
|
try {
|
||||||
|
const percent = await getPercent();
|
||||||
|
if (percent.code === 200) {
|
||||||
|
setRateList(percent?.data || []);
|
||||||
|
setRate(percent?.data?.[0]?.value || '');
|
||||||
|
setSelectedNavTag(percent?.data?.[0]?.label || '');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'percent-init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleTagClick = (tag) => {
|
||||||
|
if (selectedTags.includes(tag)) {
|
||||||
|
// 如果 Tag 已经选中,则移除
|
||||||
|
setSelectedTags((prev) => {
|
||||||
|
if (prev.length === 1) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return selectedTags.filter((item) => item !== tag)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 如果 Tag 未选中,则添加
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${subjectName}对比考试百分比情况分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 导出图片
|
||||||
|
const exportChart = (ref) => {
|
||||||
|
const instance = echarts.getInstanceByDom(ref?.current);
|
||||||
|
if (instance) {
|
||||||
|
const base64 = instance?.getDataURL({
|
||||||
|
type: "png",
|
||||||
|
pixelRatio: 2, // 导出的图片分辨率比例
|
||||||
|
backgroundColor: "#fff", // 导出图片的背景色
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartImg(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//下载图片
|
||||||
|
const downloadImg = () => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = `图表_${new Date().getTime()}.png`;
|
||||||
|
link.href = echartImg;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
setEchartImg("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id,dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavTagClick = (tag) => {
|
||||||
|
setSelectedNavTag(tag?.label || '');
|
||||||
|
setRate(tag?.value || '');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (<section className={styles.analysis_content_chart} id={`operationExport${idx}`} >
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title} >
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试百分比情况分析-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && <Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
exportChart(chartRef)
|
||||||
|
}}>导出图表</Button>}
|
||||||
|
</section>
|
||||||
|
{/* <Space>
|
||||||
|
{rateList?.length > 0 ? (
|
||||||
|
rateList?.map((item, index) => {
|
||||||
|
const isSelected = selectedNavTag === item?.label;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space> */}
|
||||||
|
<section>
|
||||||
|
<Space style={{ marginTop: 8, width: '100%' }}>
|
||||||
|
{typeList?.length > 0 ? (
|
||||||
|
typeList.map((item, index) => {
|
||||||
|
const isSelected = selectType === item;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setSelectType(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
<Space style={{ marginTop: 8, width: '100%' }}>
|
||||||
|
{tags?.length > 0 ? (
|
||||||
|
tags?.map((item, index) => {
|
||||||
|
const isSelected = selectedTags.includes(item);
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content} >
|
||||||
|
<Spin spinning={spinning} tip='计算中,请稍后'>
|
||||||
|
<div ref={chartRef} style={{ width: '100%', height: '460px' }}></div>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
{isShowTable ? (<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试百分比情况分析-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && (<Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>)}
|
||||||
|
</section>
|
||||||
|
<Space>
|
||||||
|
{rateList?.length > 0 ? (
|
||||||
|
rateList?.map((item, index) => {
|
||||||
|
const isSelected = selectedNavTag === item?.label;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleNavTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
|
||||||
|
<StudentGroup
|
||||||
|
ref={studentGroupRef}
|
||||||
|
params={studentGroupParams}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
{echartImg && (
|
||||||
|
<Modal
|
||||||
|
title="导出图表"
|
||||||
|
width={'80vw'}
|
||||||
|
className="custom-modal"
|
||||||
|
open={true}
|
||||||
|
okText="下载"
|
||||||
|
onCancel={() => {
|
||||||
|
setEchartImg("");
|
||||||
|
}}
|
||||||
|
onOk={downloadImg}
|
||||||
|
>
|
||||||
|
<Flex style={{ height: "100%" }} alignItems="center" justify="center">
|
||||||
|
<Image src={echartImg} width={"100%"} />
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContrastExamPercentage;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
628
src/pages/analysis/components/ContrastExamRanking/index.jsx
Normal file
@ -0,0 +1,628 @@
|
|||||||
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import { Space, Select, Button, Spin, Table, Tag } from 'antd';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { getExamRanking } from '@/apis/exam';
|
||||||
|
import { getSubjectListAll, getSort } from '@/apis/common';
|
||||||
|
import SchoolChoice from '@/components/SchoolChoice';
|
||||||
|
import StudentGroup from '@/components/StudentGroup';
|
||||||
|
import { tableCommonProps } from '@/utils/config.js';
|
||||||
|
import { downloadTableData } from '@/apis/download';
|
||||||
|
import { downloadExcelFile } from '@/utils/file';
|
||||||
|
import navIconSrc from '@/assets/icon/analysis-navigation-icon.png';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ContrastExamRanking = (props) => {
|
||||||
|
const { examId = '', subtitle = '', examList = [], isShowChart = false, isShowTable = false, idx = '' ,actionShow = true } = props;
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const [columns, setColumns] = useState([]);
|
||||||
|
const [responseData, setResponseData] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [subjectId, setSubjectId] = useState("");
|
||||||
|
const [subjectName, setSubjectName] = useState(null);
|
||||||
|
const [schoolIds, setSchoolIds] = useState([]);
|
||||||
|
const [allSubjectList, setAllSubjectList] = useState([]);
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
|
||||||
|
|
||||||
|
const [examSortList, setExamSortList] = useState([]);
|
||||||
|
const [examSelectSort, setExamSelectSort] = useState();
|
||||||
|
const [typeList, setTypeList] = useState([]);
|
||||||
|
const [selectType, setSelectType] = useState('');
|
||||||
|
const [allEchartDataRanking, setAllEchartDataRanking] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [selectedTags, setSelectedTags] = useState([]);
|
||||||
|
const [echartXData, setEchartXData] = useState([]);
|
||||||
|
|
||||||
|
const [mockChartData, setMockChartData] = useState([]);
|
||||||
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
|
let chartInstance = null;
|
||||||
|
const [echartImg, setEchartImg] = useState();
|
||||||
|
|
||||||
|
|
||||||
|
const studentGroupRef = useRef();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [studentGroupParams, setStudentGroupParams] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const labelOption = {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
distance: 24,
|
||||||
|
align: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
formatter: (params) => {
|
||||||
|
return `${params.value}`
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
rich: {
|
||||||
|
name: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const commonSeries = {
|
||||||
|
type: 'line',
|
||||||
|
barGap: 0,
|
||||||
|
label: labelOption,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionData = useMemo(() => {
|
||||||
|
return mockChartData?.filter((item) =>
|
||||||
|
selectedTags.includes(item.name),
|
||||||
|
);
|
||||||
|
}, [selectedTags, mockChartData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
allSubjectInit();
|
||||||
|
getSortInit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!examId || !examList.length > 0 || !subjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
}, [examId, schoolIds, subjectId, examList, examSelectSort]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectType) {
|
||||||
|
if (selectType === '人数') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataRanking.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '增长人数') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataRanking?.incr?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectType === '增幅') {
|
||||||
|
setMockChartData(() => {
|
||||||
|
return allEchartDataRanking?.rate?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!echartXData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chartRef.current) {
|
||||||
|
// 初始化图表
|
||||||
|
chartInstance = echarts.init(chartRef.current);
|
||||||
|
|
||||||
|
// 设置配置项
|
||||||
|
const option = {
|
||||||
|
// dataZoom: [
|
||||||
|
// {
|
||||||
|
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||||
|
// show: true,
|
||||||
|
// start: 0, // 初始起点
|
||||||
|
// end: 30, // 初始终点
|
||||||
|
// handleSize: 8,
|
||||||
|
// xAxisIndex: [0]
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
title: {
|
||||||
|
// 添加标题配置
|
||||||
|
text: `${subtitle}${subjectName}*对比考试排名情况分析-图表`,
|
||||||
|
left: 'center',
|
||||||
|
top: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 35,
|
||||||
|
interval: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
barGap: '10%',
|
||||||
|
barWidth: "10%",
|
||||||
|
xAxis: {
|
||||||
|
name: '考试',
|
||||||
|
type: 'category',
|
||||||
|
axisTick: { show: false },
|
||||||
|
data: echartXData,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: echartXData?.length > 6 ? 45 : 0,
|
||||||
|
interval: 0,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
containLabel: true,
|
||||||
|
top: "20%"
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: "",
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: "scroll",
|
||||||
|
data: tags,
|
||||||
|
right: 10, // 设置右侧边距
|
||||||
|
top: 16, // 设置顶部边距
|
||||||
|
selectedMode: false,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: intersectionData.map((item) => ({
|
||||||
|
...commonSeries,
|
||||||
|
name: item?.name || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
const resizeHandler = () => chartInstance?.resize();
|
||||||
|
window.addEventListener('resize', resizeHandler);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeHandler);
|
||||||
|
chartInstance?.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedTags, echartXData, mockChartData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getExamRanking({
|
||||||
|
examId,
|
||||||
|
examIds: examList.map(item => item.id),
|
||||||
|
limit: examSelectSort,
|
||||||
|
subjectId: subjectId,
|
||||||
|
schools: schoolIds
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
setResponseData(res);
|
||||||
|
setLoading(false);
|
||||||
|
setDataSource(res?.data || []);
|
||||||
|
setExpandedRowKeys([res?.data?.[0]?.id, res?.data?.[0]?.children?.[0]?.id]);
|
||||||
|
setTags(res?.columnDiagramResult?.num?.map(item => item?.lineName) || []);
|
||||||
|
setSelectedTags([res?.columnDiagramResult?.num?.[0]?.lineName || '']);
|
||||||
|
setTypeList(['人数', '增长人数', '增幅']);
|
||||||
|
setSelectType('人数');
|
||||||
|
setEchartXData(res?.columnDiagramResult?.num?.[0]?.xdata || []);
|
||||||
|
setAllEchartDataRanking(res?.columnDiagramResult);
|
||||||
|
setMockChartData(() => {
|
||||||
|
|
||||||
|
return res?.columnDiagramResult?.num?.map(item => {
|
||||||
|
return {
|
||||||
|
name: item?.lineName || '',
|
||||||
|
data: item?.data || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setColumns(() => {
|
||||||
|
return res?.columns?.map(item => {
|
||||||
|
return {
|
||||||
|
title: item?.label,
|
||||||
|
dataIndex: item?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: item.label === "考试人数" ? 80 : item?.label === "区县" ? 300 : 240,
|
||||||
|
children: item?.children?.map(element => {
|
||||||
|
if (element?.label === '人数') {
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 120,
|
||||||
|
render: (text) => (
|
||||||
|
<Button type="link" style={{ color: "#4ABB6A" }} onClick={() => {
|
||||||
|
const examId = element?.prop?.split('-')[1];
|
||||||
|
setTitle(text?.typeDisc);
|
||||||
|
studentGroupRef.current?.openModal();
|
||||||
|
setStudentGroupParams({
|
||||||
|
examId,
|
||||||
|
students: text?.data
|
||||||
|
});
|
||||||
|
|
||||||
|
}}>{text?.value}</Button>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: element?.label,
|
||||||
|
dataIndex: element?.prop,
|
||||||
|
align: "center",
|
||||||
|
width: 120,
|
||||||
|
sorter: (a, b) => a[element?.prop] - b[element?.prop],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(error, 'exam-ranking-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const allSubjectInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSubjectListAll();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setSubjectId(res?.data?.[0]?.id || '');
|
||||||
|
setSubjectName(res?.data?.[0]?.cname || null);
|
||||||
|
setAllSubjectList(() => {
|
||||||
|
return res?.data?.map(item => {
|
||||||
|
return {
|
||||||
|
label: item?.cname,
|
||||||
|
value: item?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'singleSubjectInit-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortInit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSort();
|
||||||
|
if (res?.code === 200) {
|
||||||
|
setExamSortList(res?.data || []);
|
||||||
|
setExamSelectSort(res?.data?.[0]?.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'get-sort-init-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleTagClick = (tag) => {
|
||||||
|
if (selectedTags.includes(tag)) {
|
||||||
|
// 如果 Tag 已经选中,则移除
|
||||||
|
setSelectedTags((prev) => {
|
||||||
|
if (prev.length === 1) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return selectedTags.filter((item) => item !== tag)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 如果 Tag 未选中,则添加
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await downloadTableData(responseData);
|
||||||
|
if (res?.status === 200) {
|
||||||
|
downloadExcelFile(res, `${subtitle}*${subjectName}对比考试排名情况分析`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error, 'download-error');
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 导出图片
|
||||||
|
const exportChart = (ref) => {
|
||||||
|
const instance = echarts.getInstanceByDom(ref?.current);
|
||||||
|
if (instance) {
|
||||||
|
const base64 = instance?.getDataURL({
|
||||||
|
type: "png",
|
||||||
|
pixelRatio: 2, // 导出的图片分辨率比例
|
||||||
|
backgroundColor: "#fff", // 导出图片的背景色
|
||||||
|
});
|
||||||
|
|
||||||
|
setEchartImg(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//下载图片
|
||||||
|
const downloadImg = () => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = `图表_${new Date().getTime()}.png`;
|
||||||
|
link.href = echartImg;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
setEchartImg("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandableConfig = {
|
||||||
|
expandedRowKeys: expandedRowKeys,
|
||||||
|
defaultExpandedRowKeys: [dataSource?.[0]?.id, dataSource?.[0]?.children?.[0]?.id],
|
||||||
|
onExpand: (expanded, record) => {
|
||||||
|
if (expanded) {
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys?.filter((k) => k !== record.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isShowChart ? (<section className={styles.analysis_content_chart} id={`operationExport${idx}`}>
|
||||||
|
<section className={styles.analysis_content_chart_title}>
|
||||||
|
<section className={styles.page_title} >
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试排名情况分析-图表</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && ( <Button type='primary' style={{ fontSize: 16 }} onClick={() => {
|
||||||
|
exportChart(chartRef)
|
||||||
|
}}>导出图表</Button>)}
|
||||||
|
</section>
|
||||||
|
<Space style={{ marginBottom: 8 }}>
|
||||||
|
{examSortList.length > 0 ? (
|
||||||
|
examSortList.map((item, index) => {
|
||||||
|
const isSelected = item?.value === examSelectSort;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setExamSelectSort(item?.value)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<section>
|
||||||
|
<Space >
|
||||||
|
{typeList.length > 0 ? (
|
||||||
|
typeList.map((item, index) => {
|
||||||
|
const isSelected = selectType === item;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setSelectType(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
<Space >
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
tags.map((item, index) => {
|
||||||
|
const isSelected = selectedTags.includes(item);
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => handleTagClick(item)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_chart_content} >
|
||||||
|
<Spin spinning={loading} tip='计算中,请稍后'>
|
||||||
|
<div ref={chartRef} style={{ width: '100%', height: '460px' }}></div>
|
||||||
|
</Spin>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
{isShowTable ? (<section className={styles.analysis_content_table} id={`operationExport${idx + 1}`}>
|
||||||
|
<section className={styles.analysis_content_table_title}>
|
||||||
|
<section className={styles.page_title}>
|
||||||
|
<img src={navIconSrc} className={styles.page_title_img} />
|
||||||
|
<span className={styles.page_title_text}>{subtitle}对比考试排名情况分析-表格</span>
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择学科"
|
||||||
|
options={allSubjectList}
|
||||||
|
value={subjectName}
|
||||||
|
style={{ marginLeft: 20, width: "120px" }}
|
||||||
|
allowClear
|
||||||
|
onChange={(value, option) => {
|
||||||
|
if (!value) {
|
||||||
|
setSubjectName(null);
|
||||||
|
setSubjectId("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubjectName(option?.label);
|
||||||
|
|
||||||
|
setSubjectId(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchoolChoice onChange={(value) => {
|
||||||
|
setSchoolIds(value);
|
||||||
|
}} />
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
{actionShow && <Button type='primary' style={{ fontSize: 16 }} onClick={handleDownload}>导出表格</Button>}
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<Space >
|
||||||
|
{examSortList.length > 0 ? (
|
||||||
|
examSortList.map((item, index) => {
|
||||||
|
const isSelected = item?.value === examSelectSort;
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onClick={() => setExamSelectSort(item?.value)}
|
||||||
|
color={isSelected ? 'green' : 'default'}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item?.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
<section className={styles.analysis_content_table_content}>
|
||||||
|
<Table
|
||||||
|
{...tableCommonProps}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
locale={{ emptyText: loading ? "计算中,请稍后" : "暂无数据" }}
|
||||||
|
loading={{ spinning: loading, tip: "计算中,请稍后" }}
|
||||||
|
expandable={expandableConfig}
|
||||||
|
>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>) : null}
|
||||||
|
<StudentGroup
|
||||||
|
ref={studentGroupRef}
|
||||||
|
params={studentGroupParams}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
{echartImg && (
|
||||||
|
<Modal
|
||||||
|
title="导出图表"
|
||||||
|
width={'80vw'}
|
||||||
|
className="custom-modal"
|
||||||
|
open={true}
|
||||||
|
okText="下载"
|
||||||
|
onCancel={() => {
|
||||||
|
setEchartImg("");
|
||||||
|
}}
|
||||||
|
onOk={downloadImg}
|
||||||
|
>
|
||||||
|
<Flex style={{ height: "100%" }} alignItems="center" justify="center">
|
||||||
|
<Image src={echartImg} width={"100%"} />
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContrastExamRanking;
|
||||||
33
src/pages/analysis/components/ContrastExamRanking/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import '@/pages/analysis/styles/common.less';
|
||||||
|
.analysis_content{
|
||||||
|
&_chart{
|
||||||
|
&:extend(.analysis_content_chart);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_chart_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_chart_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_table{
|
||||||
|
&:extend(.analysis_content_table);
|
||||||
|
&_title{
|
||||||
|
&:extend(.analysis_content_table_title);
|
||||||
|
}
|
||||||
|
&_content{
|
||||||
|
&:extend(.analysis_content_table_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
&_title{
|
||||||
|
&:extend(.title);
|
||||||
|
&_img{
|
||||||
|
&:extend(.title_img);
|
||||||
|
}
|
||||||
|
&_text{
|
||||||
|
&:extend(.title_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||