@ -0,0 +1,3 @@ |
||||
> 1% |
||||
last 2 versions |
||||
not ie <= 8 |
@ -0,0 +1,22 @@ |
||||
.DS_Store |
||||
node_modules |
||||
/dist |
||||
example.html |
||||
favicon.ico |
||||
# local env files |
||||
.env.local |
||||
.env.*.local |
||||
|
||||
# Log files |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
.vscode |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.sw* |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"tabWidth": 4, |
||||
"singleQuote": true, |
||||
"trailingComma": "none", |
||||
"printWidth": 140 |
||||
} |
@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2016-2019 vue-manage-system |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,5 @@ |
||||
module.exports = { |
||||
presets: [ |
||||
'@vue/app' |
||||
] |
||||
} |
@ -0,0 +1,39 @@ |
||||
{ |
||||
"name": "vue-manage-system", |
||||
"version": "4.2.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"dev": "npm run serve", |
||||
"serve": "vue-cli-service serve", |
||||
"build": "vue-cli-service build" |
||||
}, |
||||
"dependencies": { |
||||
"axios": "^0.18.0", |
||||
"babel-polyfill": "^6.26.0", |
||||
"echarts": "^4.8.0", |
||||
"element-theme": "^2.0.1", |
||||
"element-ui": "^2.13.0", |
||||
"js-cookie": "^2.2.1", |
||||
"mavon-editor": "^2.6.17", |
||||
"postcss-px2rem": "^0.3.0", |
||||
"px2rem-loader": "^0.1.9", |
||||
"sortablejs": "^1.14.0", |
||||
"vue": "^2.6.10", |
||||
"vue-cropperjs": "^3.0.0", |
||||
"vue-i18n": "^8.10.0", |
||||
"vue-pdf": "^4.2.0", |
||||
"vue-quill-editor": "^3.0.6", |
||||
"vue-router": "^3.0.3", |
||||
"vue-schart": "^2.0.0", |
||||
"vuedraggable": "^2.17.0", |
||||
"vuex": "^3.4.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@vue/cli-plugin-babel": "^3.9.0", |
||||
"@vue/cli-service": "^3.9.0", |
||||
"element-theme-chalk": "^2.13.0", |
||||
"node-sass": "^4.14.0", |
||||
"sass-loader": "^8.0.0", |
||||
"vue-template-compiler": "^2.6.10" |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
module.exports = { |
||||
plugins: { |
||||
autoprefixer: {} |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"> |
||||
<link rel="stylesheet" href="//at.alicdn.com/t/font_830376_qzecyukz0s.css"> |
||||
<title>粒子研究院</title> |
||||
</head> |
||||
<body> |
||||
<noscript> |
||||
<strong>We're sorry but vms doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
||||
</noscript> |
||||
<div id="app"></div> |
||||
<!-- built files will be auto injected --> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,26 @@ |
||||
<template> |
||||
<div id="app"> |
||||
<router-view></router-view> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Setting from "@/setting"; |
||||
import util from "@/libs/util"; |
||||
|
||||
export default { |
||||
name: "App", |
||||
created() { |
||||
//在页面加载时读取localStorage里的状态信息 |
||||
if (util.local.get(Setting.storeKey)) { |
||||
this.$store.replaceState(Object.assign({}, this.$store.state, util.local.get(Setting.storeKey))); |
||||
} |
||||
|
||||
//在页面刷新时将vuex里的信息保存到localStorage里 |
||||
window.addEventListener("beforeunload", () => { |
||||
sessionStorage.removeItem('handelPermission') |
||||
util.local.get(Setting.tokenKey) && util.local.set(Setting.storeKey, this.$store.state); |
||||
}); |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,263 @@ |
||||
import Setting from "@/setting"; |
||||
const host = Setting.apiBaseURL |
||||
const uploadURL = Setting.upload.apiURL; |
||||
const host1 = "http://121.37.12.51:8080/" |
||||
|
||||
export default { |
||||
queryToken: `liuwanr/userInfo/queryToken`, |
||||
|
||||
logins: `users/users/user/login`, //登录
|
||||
verification: `${host}users/users/user/captcha`,// 验证码图片
|
||||
isClient: `users/users/user/isClient`,// 是否为客户
|
||||
getUserAllRoleByToken : `users/users/role/getUserAllRoleByToken`, |
||||
getSchoolIdByToken : `users/users/data/user/getSchoolIdByToken`, |
||||
|
||||
platformLogList: `nakadai/nakadai/log/platformLogList`, |
||||
logNotification: `nakadai/nakadai/log/logNotification`, |
||||
//实验台
|
||||
curriculumDetail: `nakadai/nakadai/curriculum/curriculumDetail`, // 课程详情
|
||||
curriculumChapter: `nakadai/nakadai/curriculum/chapter/queryChaptersAndSubsections`, // 根据课程id查询章节小节,树状结构
|
||||
curriculumGetSubsection: `nakadai/nakadai/curriculum/subsection/getSubsection`, // 根据小节id获取预览文件地址
|
||||
|
||||
// 权限管理
|
||||
getUserRolesPermissionMenu: `users/users/user-role/getUserRolesPermissionMenu`, |
||||
|
||||
//学生管理
|
||||
resetPassword: `users/users/userAccount/resetPwd`, // 密码重置
|
||||
updateAccountEnable: `occupationlab/occupationlab/architecture/updateAccountEnable`, // 密码重置
|
||||
checkEmailOrPhone: `occupationlab/occupationlab/architecture/checkEmailOrPhone`, // 新增学生前:校验手机号或者邮箱
|
||||
checkWorkNumOrAccount: `occupationlab/occupationlab/architecture/checkWorkNumOrAccount`, // 新增/编辑学生前:校验学号或者学生账号
|
||||
delStudent: `occupationlab/occupationlab/architecture/delStudent`, // 单个、批量删除学生
|
||||
addStudent: `occupationlab/occupationlab/architecture/addStudent`, // 新增学生
|
||||
modifyStudent: `occupationlab/occupationlab/architecture/modifyStudent`, // 编辑学生
|
||||
getStudentInfoByAccountId: `occupationlab/occupationlab/architecture/getStudentInfoByAccountId`, // 查看学生信息
|
||||
getDetailByAccount: `occupationlab/occupationlab/architecture/getDetailByAccount`, // 新增用户前调用:按帐户获取详细信息
|
||||
importStudent: `${host}occupationlab/occupationlab/architecture/importStudent`, // 批量导入学生
|
||||
exportFailure: `${host}occupationlab/occupationlab/architecture/exportFailure`, // 批量导入学生失败数据导出
|
||||
removeStudent: `occupationlab/occupationlab/architecture/removeStudent`, // 移除学生
|
||||
organizationalStudentList: `occupationlab/occupationlab/architecture/organizationalStudentList`, // 根据组织架构筛选学生列表
|
||||
studentList: `occupationlab/occupationlab/architecture/studentList`, // 学生列表
|
||||
stuOrganizationTree: `occupationlab/occupationlab/architecture/stuOrganizationTree`, // 学生组织架构树
|
||||
treeList: `occupationlab/occupationlab/architecture/treeList`, // 学生组织架构树形列表
|
||||
saveOrg: `occupationlab/occupationlab/architecture/save`, // 新增组织架构
|
||||
updateOrg: `occupationlab/occupationlab/architecture/update`, // 编辑组织架构
|
||||
deleteOrg: `occupationlab/occupationlab/architecture/delete`, // 删除组织架构
|
||||
studentTemplate: `http://121.37.12.51/template/学生导入模板.xlsx`, // 学生导入模板
|
||||
|
||||
// 测评管理
|
||||
questionsList: `occupationlab/occupationlab/questions/list`, // 题库列表查询
|
||||
questionsSave: `occupationlab/occupationlab/questions/save`, // 新增题目
|
||||
questionsUpdate: `occupationlab/occupationlab/questions/update`, // 根据试题id修改试题信息
|
||||
questionsDetail: `occupationlab/occupationlab/questions/detail`, // 查看当前题库信息
|
||||
questionsDelete: `occupationlab/occupationlab/questions/delete`, // 根据主键删除
|
||||
questionsIsDisable: `occupationlab/occupationlab/questions/isDisable`, // 是否禁用试题
|
||||
questionsTemplate: `http://121.37.12.51/template/试题导入模板.xlsx`, //excel模板文件下载
|
||||
questionsImport: `${host}occupationlab/occupationlab/questions/importQuestion`, // 批量导入题库
|
||||
questionsExportFailure: `${host}occupationlab/occupationlab/questions/exportFailure`, // 批量导入题库失败数据导出
|
||||
questionsInfo: `occupationlab/occupationlab/questions/info`, // 测评规则信息的展示
|
||||
questionsUpdateRules: `occupationlab/occupationlab/questions/updateEvaluationRules`, // 修改测评规则信息
|
||||
|
||||
// 考核管理
|
||||
pageByCondition: `occupationlab/occupationlab/assessment/pageByCondition`, // 考核管理列表
|
||||
saveAssessment: `occupationlab/occupationlab/assessment/saveAssessment`, // 创建考核
|
||||
modifyAssessment: `occupationlab/occupationlab/assessment/modifyAssessment`, // 编辑考核
|
||||
enableAssessment: `occupationlab/occupationlab/assessment/enableAssessment`, // 修改考核状态
|
||||
getDetailById: `occupationlab/occupationlab/assessment/getDetailById`, // 根据考核Id查询考核详情
|
||||
deleteAssessment: `occupationlab/occupationlab/assessment/deleteAssessment`, // 单个、批量删除
|
||||
collectPaper: `occupationlab/occupationlab/assessment/collectPaper`, // 收卷(提前结束)
|
||||
schoolCourse: `nakadai/nakadai/curriculum/schoolCourse`, // 获取学校购买订单后的课程
|
||||
projectListByCourseId: `occupationlab/occupationlab/projectManage/getSchoolProjectByAssessent`, // 根据课程id获取实训项目列表
|
||||
|
||||
// 成绩管理
|
||||
deleteExperimentalReport: `occupationlab/occupationlab/achievement/deleteExperimentalReport`, // 单个、批量删除实验报告
|
||||
exportAssessmentInfo: `${host}occupationlab/occupationlab/achievement/exportAssessmentInfo`, // 批量导出考核成绩
|
||||
exportPracticeInfo: `${host}occupationlab/occupationlab/achievement/exportPracticeInfo`, // 批量导出练习成绩
|
||||
getAchievementInfo: `occupationlab/occupationlab/achievement/getAchievementInfo`, // 管理端成绩管理
|
||||
getAssessmentDetail: `occupationlab/occupationlab/achievement/getAssessmentDetail`, // 管理端考核成绩详情
|
||||
getPracticeDetail: `occupationlab/occupationlab/achievement/getPracticeDetail`, // 管理端练习成绩详情
|
||||
myClass: `occupationlab/occupationlab/achievement/myClass`, // 教师端:我的班级
|
||||
deleteReportById: `occupationlab/occupationlab/achievement/deleteReportById`, // 批量删除成绩管理中的项目/批量删除成绩管理中的考核
|
||||
reportDetail: `occupationlab/occupationlab/achievement/reportDetail`, // 查看实验报告
|
||||
schoolCourseByAchievement: `nakadai/nakadai/curriculum/schoolCourseByAchievement`, // 获取学校购买订单后的课程
|
||||
spliceClass: `occupationlab/occupationlab/achievement/spliceClass`, |
||||
|
||||
// 项目管理
|
||||
getSystemIdBySchool: `occupationlab/occupationlab/projectManage/getSystemIdBySchool`, // 获取学校下拥有的系统
|
||||
avgValues: `occupationlab/occupationlab/projectManage/avgValues`, // 平均分分配值
|
||||
deleteProjectManage: `occupationlab/occupationlab/projectManage/deleteProjectManage`, // 新增项目管理
|
||||
getProjectBySystemId: `occupationlab/occupationlab/projectManage/getProjectBySystemId`, // 根据系统id获取全部项目(项目中没有用上这个接口)
|
||||
queryNameIsExist: `occupationlab/occupationlab/projectManage/queryNameIsExist`, // 新增/编辑项目管理名称判重
|
||||
queryProjectManage: `occupationlab/occupationlab/projectManage/queryProjectManage`, // 项目管理列表(分页、筛选)
|
||||
updateIsOpen: `occupationlab/occupationlab/projectManage/updateIsOpen`, // 更新开启状态
|
||||
getProjectDetail: `occupationlab/occupationlab/projectManage/getProjectDetail`, // 根据项目id查询详情
|
||||
saveProjectDraft: `occupationlab/occupationlab/projectManage/saveProjectDraft`, // 新增项目管理
|
||||
addProjectManage: `occupationlab/occupationlab/projectManage/addProjectManage`, // 新增项目管理
|
||||
updateProjectManage: `occupationlab/occupationlab/projectManage/updateProjectManage`, // 修改项目管理
|
||||
editProjectDraft: `occupationlab/occupationlab/projectManage/editProjectDraft`, // 修改项目管理
|
||||
copyProjectManage: `occupationlab/occupationlab/projectManage/copyProjectManage`, // 复制项目管理
|
||||
|
||||
// 判分点
|
||||
getBcJudgmentPoint: `judgment/judgment/bcJudgmentPoint/getTeacherJudgmentPoint`, // 获取编程类判分点列表(分页)
|
||||
getLcJudgmentPoint: `judgment/judgment/lcJudgmentPoint/queryAllJudgmentPoint`, // 获取流程类判分点列表(分页)
|
||||
addProjectJudgment: `occupationlab/occupationlab/projectJudgment/addProjectJudgment`, // 添加项目管理、判分点中间表
|
||||
updateProjectJudgment: `occupationlab/occupationlab/projectJudgment/updateProjectJudgment`, // 判分点中间表批量更新
|
||||
deleteProjectJudgment: `occupationlab/occupationlab/projectJudgment/deleteProjectJudgment`, // 判分点中间表批量删除
|
||||
|
||||
|
||||
// 赛事
|
||||
contestPageConditionQueryByOccupationlab: `occupationlab/occupationlab/enterprise/match/contest/contestPageConditionQueryByOccupationlab`, |
||||
addContest: `occupationlab/occupationlab/enterprise/match/contest/addContest`, |
||||
editContest: `occupationlab/occupationlab/enterprise/match/contest/editContest`, |
||||
deleteContest: `occupationlab/occupationlab/enterprise/match/contest/deleteContest`, |
||||
getContest: `occupationlab/occupationlab/enterprise/match/contest/getContest`, |
||||
deleteAnnex: `occupationlab/occupationlab/contest/annex/delete`, |
||||
saveAnnex: `occupationlab/occupationlab/contest/annex/save`, |
||||
getSchoolsByProvince: `nakadai/nakadai/school/getSchoolsByProvince`, |
||||
disabledEvents: `occupationlab/occupationlab/enterprise/match/contest/disabledEvents`, |
||||
batchDeleteContest: `occupationlab/occupationlab/enterprise/match/contest/batchDeleteContest`, |
||||
|
||||
// 竞赛进展
|
||||
addContestProgress: `occupationlab/occupationlab/enterprise/match/contest-progress/addContestProgress`, |
||||
deleteContestProgress: `occupationlab/occupationlab/enterprise/match/contest-progress/deleteContestProgress`, |
||||
getContestProgress: `occupationlab/occupationlab/enterprise/match/contest-progress/getContestProgress`, |
||||
editContestProgress: `occupationlab/occupationlab/enterprise/match/contest-progress/editContestProgress`, |
||||
// 报名人员
|
||||
addApplicant: `occupationlab/occupationlab/enterprise/match/applicant/addApplicant`, |
||||
disableContests: `occupationlab/occupationlab/enterprise/match/applicant/disableContests`, |
||||
excelExport: `${host}occupationlab/occupationlab/enterprise/match/applicant/excelExport`, |
||||
queryApplicantByCondition: `occupationlab/occupationlab/enterprise/match/applicant/queryApplicantByCondition`, |
||||
disableApplicant: `occupationlab/occupationlab/enterprise/match/applicant/disableApplicant`, |
||||
exportDataInBatches: `${host}occupationlab/occupationlab/enterprise/match/applicant/exportDataInBatches`, |
||||
// 赛事公告
|
||||
addAnnouncement: `occupationlab/occupationlab/contest/announcement/addAnnouncement`, |
||||
amendmentAnnouncement: `occupationlab/occupationlab/contest/announcement/amendmentAnnouncement`, |
||||
deleteAnnouncement: `occupationlab/occupationlab/contest/announcement/deleteAnnouncement`, |
||||
disableAnnouncement: `occupationlab/occupationlab/contest/announcement/disableAnnouncement`, |
||||
queryAnnouncementByContestId: `occupationlab/occupationlab/contest/announcement/queryAnnouncementByContestId`, |
||||
queryAnnouncementDetails: `occupationlab/occupationlab/contest/announcement/queryAnnouncementDetails`, |
||||
deleteAnnouncementAnnex: `occupationlab/occupationlab/contestAnnouncementAnnex/delete`, |
||||
saveAnnouncementAnnex: `occupationlab/occupationlab/contestAnnouncementAnnex/save`, |
||||
|
||||
// 栏目管理
|
||||
addColumn: `occupationlab/occupationlab/enterprise/information/column/addColumn`, |
||||
deleteColumn: `occupationlab/occupationlab/enterprise/information/column/deleteColumn`, |
||||
editColumn: `occupationlab/occupationlab/enterprise/information/column/editColumn`, |
||||
queryAllColumns: `occupationlab/occupationlab/enterprise/information/column/queryAllColumns`, |
||||
columnReorder: `occupationlab/occupationlab/enterprise/information/column/reorder`, |
||||
// 内容管理
|
||||
addArticle: `occupationlab/occupationlab/enterprise/information/article/addArticle`, |
||||
deleteArticles: `occupationlab/occupationlab/enterprise/information/article/deleteArticles`, |
||||
editArticle: `occupationlab/occupationlab/enterprise/information/article/editArticle`, |
||||
enableArticle: `occupationlab/occupationlab/enterprise/information/article/enableArticle`, |
||||
getArticle: `occupationlab/occupationlab/enterprise/information/article/getArticle`, |
||||
getArticles: `occupationlab/occupationlab/enterprise/information/article/getArticles`, |
||||
queryArticleByCondition: `occupationlab/occupationlab/enterprise/information/article/queryArticleByCondition`, |
||||
articleSort: `occupationlab/occupationlab/enterprise/information/article/articleSort`, |
||||
|
||||
// 课程管理
|
||||
queryCourseByCondition: `occupationlab/occupationlab/management/edu/course/queryCourseByCondition`, // 课程列表分页条件查询
|
||||
addCourse: `occupationlab/occupationlab/management/edu/course/addCourse`, // 添加课程
|
||||
deleteCourse: `occupationlab/occupationlab/management/edu/course/deleteCourse`, // 根据id删除课程
|
||||
deleteCourses: `occupationlab/occupationlab/management/edu/course/deleteCourses`, // 批量删除课程
|
||||
editCourse: `occupationlab/occupationlab/management/edu/course/editCourse`, // 修改课程
|
||||
enableCourse: `occupationlab/occupationlab/management/schoolCourse/enableGlCourse`, // 是否启用管理端课程
|
||||
getCourse: `occupationlab/occupationlab/management/edu/course/getCourse`, // 根据id查询课程
|
||||
// 分类管理
|
||||
queryGlClassification: `occupationlab/occupationlab/management/edu/courseClassification/queryGlClassification`, |
||||
deleteClassification: `occupationlab/occupationlab/management/edu/courseClassification/deleteClassification`, |
||||
addClassification: `occupationlab/occupationlab/management/edu/courseClassification/addClassification`, |
||||
editClassification: `occupationlab/occupationlab/management/edu/courseClassification/editClassification`, |
||||
// 课程章节管理
|
||||
addChapter: `occupationlab/occupationlab/management/edu/courseChapter/addChapter`, |
||||
chapterReorder: `occupationlab/occupationlab/management/edu/courseChapter/chapterReorder`, |
||||
deleteChapter: `occupationlab/occupationlab/management/edu/courseChapter/deleteChapter`, |
||||
editChapter: `occupationlab/occupationlab/management/edu/courseChapter/editChapter`, |
||||
queryChaptersAndSubsections: `occupationlab/occupationlab/management/edu/courseChapter/queryChaptersAndSubsections`, |
||||
reorder: `occupationlab/occupationlab/management/edu/courseChapter/reorder`, |
||||
// 课程小节管理
|
||||
addSubsection: `occupationlab/occupationlab/management/edu/courseSubsection/addSubsection`, // 添加小节
|
||||
deleteSubsection: `occupationlab/occupationlab/management/edu/courseSubsection/deleteSubsection`, // 根据id删除小节
|
||||
editSubsection: `occupationlab/occupationlab/management/edu/courseSubsection/editSubsection`, // 修改小节
|
||||
getSubsection: `occupationlab/occupationlab/management/edu/courseSubsection/getSubsection`, // 根据小节id获取预览文件地址
|
||||
|
||||
// 阿里云文件/视频管理
|
||||
fileDeletion: `${uploadURL}oss/manage/fileDeletion`, // 删除OSS文件
|
||||
fileupload: `${uploadURL}oss/manage/fileupload`, // 文件上传
|
||||
getPlayAuth: `${uploadURL}oss/manage/getPlayAuth`, // 获取播放凭证
|
||||
removeMoreVideo: `${uploadURL}oss/manage/removeMoreVideo`, // 批量删除视频文件
|
||||
removeVideo: `${uploadURL}oss/manage/removeVideo`, // 删除视频文件
|
||||
|
||||
queryProvince: `nakadai/nakadai/province/queryProvince`, //查询省份
|
||||
queryCity: `nakadai/nakadai/city/queryCity`, //查询城市
|
||||
querySchoolData: `nakadai/nakadai/school/querySchool`, //根据学校名称查询学校信息
|
||||
queryCourseDiscipline: `nakadai/nakadai/subject/courseDiscipline`, //查询课程学科
|
||||
queryCourseProfessionalClass: `nakadai/nakadai/subject/courseProfessionalClass`, //查询专业类
|
||||
queryCourseProfessional: `nakadai/nakadai/subject/courseProfessional`, //查询专业
|
||||
|
||||
// 个人中心
|
||||
uploadUserAvatars: `${host}users/users/userAccount/updateUserAvatars`, //更改头像
|
||||
queryUserInfoDetails: `users/users/userAccount/queryUserInfoDetails`,//个人中心信息展示
|
||||
updatePersonCenter: `users/users/userAccount/updatePersonCenter`,//个人中心信息修改
|
||||
examinePassword: `users/users/userAccount/examinePassword`,//更换密码
|
||||
bindPhoneOrEmail: `users/users/userAccount/bindPhoneOrEmail`,// 绑定手机或邮箱
|
||||
sendPhoneOrEmailCode: `users/users/userAccount/sendPhoneOrEmailCode`,// 更换手机号或邮箱--发送手机验证码
|
||||
unbindMobilePhone: `users/users/userAccount/unbindMobilePhone`, |
||||
|
||||
// 系统设置
|
||||
// 员工组织架构
|
||||
professionalList: `occupationlab/occupationlab/staff/professionalList`, //查询专业列表
|
||||
staffGradeList: `occupationlab/occupationlab/staff/staffGradeList`, //根据专业id查询年级
|
||||
saveProfessional: `occupationlab/occupationlab/staff/saveProfessional`, //新增专业
|
||||
updateProfessional: `occupationlab/occupationlab/staff/updateProfessional`, //编辑专业
|
||||
deleteProfessional: `occupationlab/occupationlab/staff/deleteProfessional`, //删除专业
|
||||
saveGrade: `occupationlab/occupationlab/staff/saveGrade`, //新增年级
|
||||
updateGrade: `occupationlab/occupationlab/staff/updateGrade`, //编辑年级
|
||||
deleteGrade: `occupationlab/occupationlab/staff/deleteGrade`, //删除年级
|
||||
// 员工管理
|
||||
saveStaff: `occupationlab/occupationlab/staff/saveStaff`, //新增员工
|
||||
staffDetail: `occupationlab/occupationlab/staff/staffDetail`, //员工详情
|
||||
modifyStaff: `occupationlab/occupationlab/staff/modifyStaff`, //编辑员工
|
||||
delStaff: `occupationlab/occupationlab/staff/delStaff`, //删除员工
|
||||
staffList: `occupationlab/occupationlab/staff/staffList`, //员工列表
|
||||
staffTemplate: `http://121.37.12.51/template/员工用户导入模板.xlsx`, //员工用户导入模板
|
||||
exportFailureStaff: `${host}occupationlab/occupationlab/staff/exportFailure`, //批量导入员工失败数据导出
|
||||
importStaff: `${host}occupationlab/occupationlab/staff/importStaff`, //批量导入员工
|
||||
// 角色管理
|
||||
batchRemove: `users/users/role/batchRemove`, //批量删除角色
|
||||
checkRoleIsExist: `users/users/role/checkRoleIsExist`, //判断该角色是否存在
|
||||
delRoleByAccountId: `users/users/role/delRoleByAccountId`, //删除某用户下的某个角色
|
||||
roleList: `users/users/role/list`, //角色分页列表查询
|
||||
obtainDetails: `users/users/role/obtainDetails`, //获取角色详情
|
||||
saveOrUpdate: `users/users/role/saveOrUpdate`, //新增或更新角色
|
||||
queryAllMenus: `users/users/permission/queryAllMenus`, //查询所有菜单
|
||||
// 系统logo设置
|
||||
logoDetail: `occupationlab/occupationlab/sys/logo/detail`, //查看系统设置信息
|
||||
logoSave: `occupationlab/occupationlab/sys/logo/save`, //新增系统设置信息
|
||||
logoUpdate: `occupationlab/occupationlab/sys/logo/update`, //编辑系统设置信息
|
||||
|
||||
|
||||
|
||||
|
||||
// 教师评语
|
||||
addComment: `evaluation/cevaluation/comment/addComment`, |
||||
queryComment: `evaluation/evaluation/ccomment/queryComment`, |
||||
updateComment: `evaluation/evaluation/ccomment/updateComment`, |
||||
|
||||
// 老师签名照
|
||||
daleteSignature: `evaluation/evaluation/csignature/daleteSignature`, |
||||
querySignature: `evaluation/evaluation/csignature/querySignature`, |
||||
uploadSignature: `evaluation/evaluation/csignature/uploadSignature`, |
||||
|
||||
// 判分点
|
||||
queryListTrading: `judgment/judgment/tradingJudgmentPoints/query`, |
||||
queryDetailsTrading: `judgment/judgment/tradingJudgmentPoints/queryDetails`, |
||||
// 科大
|
||||
queryPoint: `kdSys/queryPoint`, |
||||
querySubject: `kdSys/querySubject`, |
||||
queryItem: `kdSys/queryItem`, |
||||
// 川大
|
||||
firstLevel: `${host1}sichuan/point/firstLevel`, |
||||
secondaryLevel: `${host1}sichuan/point/secondaryLevel`, |
||||
thirdLevel: `${host1}sichuan/point/thirdLevel` |
||||
|
||||
}; |
After Width: | Height: | Size: 239 B |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 499 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 829 B |
After Width: | Height: | Size: 683 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 334 B |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 398 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 209 B |
@ -0,0 +1,64 @@ |
||||
<template> |
||||
<!-- 面包屑 --> |
||||
<div class="breadcrumb"> |
||||
<el-breadcrumb separator=">"> |
||||
<template v-for="(item, index) in pages"> |
||||
<el-breadcrumb-item |
||||
v-if="index != pages.length - 1" |
||||
:key="index" |
||||
:to="{ path: route, query }"> |
||||
{{item}} |
||||
</el-breadcrumb-item> |
||||
<el-breadcrumb-item |
||||
v-else |
||||
:key="index"> |
||||
{{item}} |
||||
</el-breadcrumb-item> |
||||
</template> |
||||
</el-breadcrumb> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
props: { |
||||
data: { |
||||
type: String, |
||||
required: true |
||||
}, |
||||
route: { |
||||
type: String, |
||||
default: 'list' |
||||
}, |
||||
query: { |
||||
type: Object |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
pages: this.data.split('/') |
||||
}; |
||||
}, |
||||
methods: { |
||||
update(data){ |
||||
this.pages = data.split('/') |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.breadcrumb { |
||||
margin: 4px 0 16px; |
||||
/deep/.el-breadcrumb__item { |
||||
.is-link, .el-breadcrumb__separator { |
||||
font-weight: 400; |
||||
color: #007EFF; |
||||
} |
||||
&:last-child { |
||||
.is-link { |
||||
color: #0B1D30; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,8 @@ |
||||
import Tree from './src/tree.vue'; |
||||
|
||||
/* istanbul ignore next */ |
||||
Tree.install = function(Vue) { |
||||
Vue.component(Tree.name, Tree); |
||||
}; |
||||
|
||||
export default Tree; |
@ -0,0 +1,486 @@ |
||||
import objectAssign from 'element-ui/src/utils/merge'; |
||||
import { markNodeData, NODE_KEY } from './util'; |
||||
import { arrayFindIndex } from 'element-ui/src/utils/util'; |
||||
|
||||
export const getChildState = node => { |
||||
let all = true; |
||||
let none = true; |
||||
let allWithoutDisable = true; |
||||
for (let i = 0, j = node.length; i < j; i++) { |
||||
const n = node[i]; |
||||
if (n.checked !== true || n.indeterminate) { |
||||
all = false; |
||||
if (!n.disabled) { |
||||
allWithoutDisable = false; |
||||
} |
||||
} |
||||
if (n.checked !== false || n.indeterminate) { |
||||
none = false; |
||||
} |
||||
} |
||||
|
||||
return { all, none, allWithoutDisable, half: !all && !none }; |
||||
}; |
||||
|
||||
const reInitChecked = function(node) { |
||||
if (node.childNodes.length === 0) return; |
||||
|
||||
const {all, none, half} = getChildState(node.childNodes); |
||||
if (all) { |
||||
node.checked = true; |
||||
node.indeterminate = false; |
||||
} else if (half) { |
||||
node.checked = false; |
||||
node.indeterminate = true; |
||||
} else if (none) { |
||||
node.checked = false; |
||||
node.indeterminate = false; |
||||
} |
||||
|
||||
const parent = node.parent; |
||||
if (!parent || parent.level === 0) return; |
||||
|
||||
if (!node.store.checkStrictly) { |
||||
reInitChecked(parent); |
||||
} |
||||
}; |
||||
|
||||
const getPropertyFromData = function(node, prop) { |
||||
const props = node.store.props; |
||||
const data = node.data || {}; |
||||
const config = props[prop]; |
||||
|
||||
if (typeof config === 'function') { |
||||
return config(data, node); |
||||
} else if (typeof config === 'string') { |
||||
return data[config]; |
||||
} else if (typeof config === 'undefined') { |
||||
const dataProp = data[prop]; |
||||
return dataProp === undefined ? '' : dataProp; |
||||
} |
||||
}; |
||||
|
||||
let nodeIdSeed = 0; |
||||
|
||||
export default class Node { |
||||
constructor(options) { |
||||
this.id = nodeIdSeed++; |
||||
this.text = null; |
||||
this.checked = false; |
||||
this.indeterminate = false; |
||||
this.data = null; |
||||
this.expanded = false; |
||||
this.parent = null; |
||||
this.visible = true; |
||||
this.isCurrent = false; |
||||
|
||||
for (let name in options) { |
||||
if (options.hasOwnProperty(name)) { |
||||
this[name] = options[name]; |
||||
} |
||||
} |
||||
|
||||
// internal
|
||||
this.level = 0; |
||||
this.loaded = false; |
||||
this.childNodes = []; |
||||
this.loading = false; |
||||
|
||||
if (this.parent) { |
||||
this.level = this.parent.level + 1; |
||||
} |
||||
|
||||
const store = this.store; |
||||
if (!store) { |
||||
throw new Error('[Node]store is required!'); |
||||
} |
||||
store.registerNode(this); |
||||
|
||||
const props = store.props; |
||||
if (props && typeof props.isLeaf !== 'undefined') { |
||||
const isLeaf = getPropertyFromData(this, 'isLeaf'); |
||||
if (typeof isLeaf === 'boolean') { |
||||
this.isLeafByUser = isLeaf; |
||||
} |
||||
} |
||||
|
||||
if (store.lazy !== true && this.data) { |
||||
this.setData(this.data); |
||||
|
||||
if (store.defaultExpandAll) { |
||||
this.expanded = true; |
||||
} |
||||
} else if (this.level > 0 && store.lazy && store.defaultExpandAll) { |
||||
this.expand(); |
||||
} |
||||
if (!Array.isArray(this.data)) { |
||||
markNodeData(this, this.data); |
||||
} |
||||
if (!this.data) return; |
||||
const defaultExpandedKeys = store.defaultExpandedKeys; |
||||
const key = store.key; |
||||
if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) { |
||||
this.expand(null, store.autoExpandParent); |
||||
} |
||||
|
||||
if (key && store.currentNodeKey !== undefined && this.key === store.currentNodeKey) { |
||||
store.currentNode = this; |
||||
store.currentNode.isCurrent = true; |
||||
} |
||||
|
||||
if (store.lazy) { |
||||
store._initDefaultCheckedNode(this); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
setData(data) { |
||||
if (!Array.isArray(data)) { |
||||
markNodeData(this, data); |
||||
} |
||||
|
||||
this.data = data; |
||||
this.childNodes = []; |
||||
|
||||
let children; |
||||
if (this.level === 0 && this.data instanceof Array) { |
||||
children = this.data; |
||||
} else { |
||||
children = getPropertyFromData(this, 'children') || []; |
||||
} |
||||
|
||||
for (let i = 0, j = children.length; i < j; i++) { |
||||
this.insertChild({ data: children[i] }); |
||||
} |
||||
} |
||||
|
||||
get label() { |
||||
return getPropertyFromData(this, 'label'); |
||||
} |
||||
|
||||
get key() { |
||||
const nodeKey = this.store.key; |
||||
if (this.data) return this.data[nodeKey]; |
||||
return null; |
||||
} |
||||
|
||||
get disabled() { |
||||
return getPropertyFromData(this, 'disabled'); |
||||
} |
||||
|
||||
get nextSibling() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
const index = parent.childNodes.indexOf(this); |
||||
if (index > -1) { |
||||
return parent.childNodes[index + 1]; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
get previousSibling() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
const index = parent.childNodes.indexOf(this); |
||||
if (index > -1) { |
||||
return index > 0 ? parent.childNodes[index - 1] : null; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
contains(target, deep = true) { |
||||
const walk = function(parent) { |
||||
const children = parent.childNodes || []; |
||||
let result = false; |
||||
for (let i = 0, j = children.length; i < j; i++) { |
||||
const child = children[i]; |
||||
if (child === target || (deep && walk(child))) { |
||||
result = true; |
||||
break; |
||||
} |
||||
} |
||||
return result; |
||||
}; |
||||
|
||||
return walk(this); |
||||
} |
||||
|
||||
remove() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
parent.removeChild(this); |
||||
} |
||||
} |
||||
|
||||
insertChild(child, index, batch) { |
||||
if (!child) throw new Error('insertChild error: child is required.'); |
||||
|
||||
if (!(child instanceof Node)) { |
||||
if (!batch) { |
||||
const children = this.getChildren(true); |
||||
if (children.indexOf(child.data) === -1) { |
||||
if (typeof index === 'undefined' || index < 0) { |
||||
children.push(child.data); |
||||
} else { |
||||
children.splice(index, 0, child.data); |
||||
} |
||||
} |
||||
} |
||||
objectAssign(child, { |
||||
parent: this, |
||||
store: this.store |
||||
}); |
||||
child = new Node(child); |
||||
} |
||||
|
||||
child.level = this.level + 1; |
||||
|
||||
if (typeof index === 'undefined' || index < 0) { |
||||
this.childNodes.push(child); |
||||
} else { |
||||
this.childNodes.splice(index, 0, child); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
insertBefore(child, ref) { |
||||
let index; |
||||
if (ref) { |
||||
index = this.childNodes.indexOf(ref); |
||||
} |
||||
this.insertChild(child, index); |
||||
} |
||||
|
||||
insertAfter(child, ref) { |
||||
let index; |
||||
if (ref) { |
||||
index = this.childNodes.indexOf(ref); |
||||
if (index !== -1) index += 1; |
||||
} |
||||
this.insertChild(child, index); |
||||
} |
||||
|
||||
removeChild(child) { |
||||
const children = this.getChildren() || []; |
||||
const dataIndex = children.indexOf(child.data); |
||||
if (dataIndex > -1) { |
||||
children.splice(dataIndex, 1); |
||||
} |
||||
|
||||
const index = this.childNodes.indexOf(child); |
||||
|
||||
if (index > -1) { |
||||
this.store && this.store.deregisterNode(child); |
||||
child.parent = null; |
||||
this.childNodes.splice(index, 1); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
removeChildByData(data) { |
||||
let targetNode = null; |
||||
|
||||
for (let i = 0; i < this.childNodes.length; i++) { |
||||
if (this.childNodes[i].data === data) { |
||||
targetNode = this.childNodes[i]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (targetNode) { |
||||
this.removeChild(targetNode); |
||||
} |
||||
} |
||||
|
||||
expand(callback, expandParent) { |
||||
const done = () => { |
||||
if (expandParent) { |
||||
let parent = this.parent; |
||||
while (parent.level > 0) { |
||||
parent.expanded = true; |
||||
parent = parent.parent; |
||||
} |
||||
} |
||||
this.expanded = true; |
||||
if (callback) callback(); |
||||
}; |
||||
|
||||
if (this.shouldLoadData()) { |
||||
this.loadData((data) => { |
||||
if (data instanceof Array) { |
||||
if (this.checked) { |
||||
this.setChecked(true, true); |
||||
} else if (!this.store.checkStrictly) { |
||||
reInitChecked(this); |
||||
} |
||||
done(); |
||||
} |
||||
}); |
||||
} else { |
||||
done(); |
||||
} |
||||
} |
||||
|
||||
doCreateChildren(array, defaultProps = {}) { |
||||
array.forEach((item) => { |
||||
this.insertChild(objectAssign({ data: item }, defaultProps), undefined, true); |
||||
}); |
||||
} |
||||
|
||||
collapse() { |
||||
this.expanded = false; |
||||
} |
||||
|
||||
shouldLoadData() { |
||||
return this.store.lazy === true && this.store.load && !this.loaded; |
||||
} |
||||
|
||||
updateLeafState() { |
||||
if (this.store.lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') { |
||||
this.isLeaf = this.isLeafByUser; |
||||
return; |
||||
} |
||||
const childNodes = this.childNodes; |
||||
if (!this.store.lazy || (this.store.lazy === true && this.loaded === true)) { |
||||
// this.isLeaf = !childNodes || childNodes.length === 0;
|
||||
this.isLeaf = this.isLeafByUser; |
||||
return; |
||||
} |
||||
this.isLeaf = false; |
||||
} |
||||
|
||||
setChecked(value, deep, recursion, passValue) { |
||||
this.indeterminate = value === 'half'; |
||||
this.checked = value === true; |
||||
|
||||
if (this.store.checkStrictly) return; |
||||
|
||||
if (!(this.shouldLoadData() && !this.store.checkDescendants)) { |
||||
let { all, allWithoutDisable } = getChildState(this.childNodes); |
||||
|
||||
if (!this.isLeaf && (!all && allWithoutDisable)) { |
||||
this.checked = false; |
||||
value = false; |
||||
} |
||||
|
||||
const handleDescendants = () => { |
||||
if (deep) { |
||||
const childNodes = this.childNodes; |
||||
for (let i = 0, j = childNodes.length; i < j; i++) { |
||||
const child = childNodes[i]; |
||||
passValue = passValue || value !== false; |
||||
const isCheck = child.disabled ? child.checked : passValue; |
||||
child.setChecked(isCheck, deep, true, passValue); |
||||
} |
||||
const { half, all } = getChildState(childNodes); |
||||
if (!all) { |
||||
this.checked = all; |
||||
this.indeterminate = half; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
if (this.shouldLoadData()) { |
||||
// Only work on lazy load data.
|
||||
this.loadData(() => { |
||||
handleDescendants(); |
||||
reInitChecked(this); |
||||
}, { |
||||
checked: value !== false |
||||
}); |
||||
return; |
||||
} else { |
||||
handleDescendants(); |
||||
} |
||||
} |
||||
|
||||
const parent = this.parent; |
||||
if (!parent || parent.level === 0) return; |
||||
|
||||
if (!recursion) { |
||||
reInitChecked(parent); |
||||
} |
||||
} |
||||
|
||||
getChildren(forceInit = false) { // this is data
|
||||
if (this.level === 0) return this.data; |
||||
const data = this.data; |
||||
if (!data) return null; |
||||
|
||||
const props = this.store.props; |
||||
let children = 'children'; |
||||
if (props) { |
||||
children = props.children || 'children'; |
||||
} |
||||
|
||||
if (data[children] === undefined) { |
||||
data[children] = null; |
||||
} |
||||
|
||||
if (forceInit && !data[children]) { |
||||
data[children] = []; |
||||
} |
||||
|
||||
return data[children]; |
||||
} |
||||
|
||||
updateChildren() { |
||||
const newData = this.getChildren() || []; |
||||
const oldData = this.childNodes.map((node) => node.data); |
||||
|
||||
const newDataMap = {}; |
||||
const newNodes = []; |
||||
|
||||
newData.forEach((item, index) => { |
||||
const key = item[NODE_KEY]; |
||||
const isNodeExists = !!key && arrayFindIndex(oldData, data => data[NODE_KEY] === key) >= 0; |
||||
if (isNodeExists) { |
||||
newDataMap[key] = { index, data: item }; |
||||
} else { |
||||
newNodes.push({ index, data: item }); |
||||
} |
||||
}); |
||||
|
||||
if (!this.store.lazy) { |
||||
oldData.forEach((item) => { |
||||
if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item); |
||||
}); |
||||
} |
||||
|
||||
newNodes.forEach(({ index, data }) => { |
||||
this.insertChild({ data }, index); |
||||
}); |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
loadData(callback, defaultProps = {}) { |
||||
if (this.store.lazy === true && this.store.load && !this.loaded && (!this.loading || Object.keys(defaultProps).length)) { |
||||
this.loading = true; |
||||
|
||||
const resolve = (children) => { |
||||
this.loaded = true; |
||||
this.loading = false; |
||||
this.childNodes = []; |
||||
|
||||
this.doCreateChildren(children, defaultProps); |
||||
|
||||
this.updateLeafState(); |
||||
if (callback) { |
||||
callback.call(this, children); |
||||
} |
||||
}; |
||||
|
||||
this.store.load(this, resolve); |
||||
} else { |
||||
if (callback) { |
||||
callback.call(this); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,340 @@ |
||||
import Node from './node'; |
||||
import { getNodeKey } from './util'; |
||||
|
||||
export default class TreeStore { |
||||
constructor(options) { |
||||
this.currentNode = null; |
||||
this.currentNodeKey = null; |
||||
|
||||
for (let option in options) { |
||||
if (options.hasOwnProperty(option)) { |
||||
this[option] = options[option]; |
||||
} |
||||
} |
||||
|
||||
this.nodesMap = {}; |
||||
|
||||
this.root = new Node({ |
||||
data: this.data, |
||||
store: this |
||||
}); |
||||
|
||||
if (this.lazy && this.load) { |
||||
const loadFn = this.load; |
||||
loadFn(this.root, (data) => { |
||||
this.root.doCreateChildren(data); |
||||
this._initDefaultCheckedNodes(); |
||||
}); |
||||
} else { |
||||
this._initDefaultCheckedNodes(); |
||||
} |
||||
} |
||||
|
||||
filter(value) { |
||||
const filterNodeMethod = this.filterNodeMethod; |
||||
const lazy = this.lazy; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
child.visible = filterNodeMethod.call(child, value, child.data, child); |
||||
|
||||
traverse(child); |
||||
}); |
||||
|
||||
if (!node.visible && childNodes.length) { |
||||
let allHidden = true; |
||||
allHidden = !childNodes.some(child => child.visible); |
||||
|
||||
if (node.root) { |
||||
node.root.visible = allHidden === false; |
||||
} else { |
||||
node.visible = allHidden === false; |
||||
} |
||||
} |
||||
if (!value) return; |
||||
|
||||
if (node.visible && !node.isLeaf && !lazy) node.expand(); |
||||
}; |
||||
|
||||
traverse(this); |
||||
} |
||||
|
||||
setData(newVal) { |
||||
const instanceChanged = newVal !== this.root.data; |
||||
if (instanceChanged) { |
||||
this.root.setData(newVal); |
||||
this._initDefaultCheckedNodes(); |
||||
} else { |
||||
this.root.updateChildren(); |
||||
} |
||||
} |
||||
|
||||
getNode(data) { |
||||
if (data instanceof Node) return data; |
||||
const key = typeof data !== 'object' ? data : getNodeKey(this.key, data); |
||||
return this.nodesMap[key] || null; |
||||
} |
||||
|
||||
insertBefore(data, refData) { |
||||
const refNode = this.getNode(refData); |
||||
refNode.parent.insertBefore({ data }, refNode); |
||||
} |
||||
|
||||
insertAfter(data, refData) { |
||||
const refNode = this.getNode(refData); |
||||
refNode.parent.insertAfter({ data }, refNode); |
||||
} |
||||
|
||||
remove(data) { |
||||
const node = this.getNode(data); |
||||
|
||||
if (node && node.parent) { |
||||
if (node === this.currentNode) { |
||||
this.currentNode = null; |
||||
} |
||||
node.parent.removeChild(node); |
||||
} |
||||
} |
||||
|
||||
append(data, parentData) { |
||||
const parentNode = parentData ? this.getNode(parentData) : this.root; |
||||
|
||||
if (parentNode) { |
||||
parentNode.insertChild({ data }); |
||||
} |
||||
} |
||||
|
||||
_initDefaultCheckedNodes() { |
||||
const defaultCheckedKeys = this.defaultCheckedKeys || []; |
||||
const nodesMap = this.nodesMap; |
||||
|
||||
defaultCheckedKeys.forEach((checkedKey) => { |
||||
const node = nodesMap[checkedKey]; |
||||
|
||||
if (node) { |
||||
node.setChecked(true, !this.checkStrictly); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
_initDefaultCheckedNode(node) { |
||||
const defaultCheckedKeys = this.defaultCheckedKeys || []; |
||||
|
||||
if (defaultCheckedKeys.indexOf(node.key) !== -1) { |
||||
node.setChecked(true, !this.checkStrictly); |
||||
} |
||||
} |
||||
|
||||
setDefaultCheckedKey(newVal) { |
||||
if (newVal !== this.defaultCheckedKeys) { |
||||
this.defaultCheckedKeys = newVal; |
||||
this._initDefaultCheckedNodes(); |
||||
} |
||||
} |
||||
|
||||
registerNode(node) { |
||||
const key = this.key; |
||||
if (!key || !node || !node.data) return; |
||||
|
||||
const nodeKey = node.key; |
||||
if (nodeKey !== undefined) this.nodesMap[node.key] = node; |
||||
} |
||||
|
||||
deregisterNode(node) { |
||||
const key = this.key; |
||||
if (!key || !node || !node.data) return; |
||||
|
||||
node.childNodes.forEach(child => { |
||||
this.deregisterNode(child); |
||||
}); |
||||
|
||||
delete this.nodesMap[node.key]; |
||||
} |
||||
|
||||
getCheckedNodes(leafOnly = false, includeHalfChecked = false) { |
||||
const checkedNodes = []; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
if ((child.checked || (includeHalfChecked && child.indeterminate)) && (!leafOnly || (leafOnly && child.isLeaf))) { |
||||
checkedNodes.push(child.data); |
||||
} |
||||
|
||||
traverse(child); |
||||
}); |
||||
}; |
||||
|
||||
traverse(this); |
||||
|
||||
return checkedNodes; |
||||
} |
||||
|
||||
getCheckedKeys(leafOnly = false) { |
||||
return this.getCheckedNodes(leafOnly).map((data) => (data || {})[this.key]); |
||||
} |
||||
|
||||
getHalfCheckedNodes() { |
||||
const nodes = []; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
if (child.indeterminate) { |
||||
nodes.push(child.data); |
||||
} |
||||
|
||||
traverse(child); |
||||
}); |
||||
}; |
||||
|
||||
traverse(this); |
||||
|
||||
return nodes; |
||||
} |
||||
|
||||
getHalfCheckedKeys() { |
||||
return this.getHalfCheckedNodes().map((data) => (data || {})[this.key]); |
||||
} |
||||
|
||||
_getAllNodes() { |
||||
const allNodes = []; |
||||
const nodesMap = this.nodesMap; |
||||
for (let nodeKey in nodesMap) { |
||||
if (nodesMap.hasOwnProperty(nodeKey)) { |
||||
allNodes.push(nodesMap[nodeKey]); |
||||
} |
||||
} |
||||
|
||||
return allNodes; |
||||
} |
||||
|
||||
updateChildren(key, data) { |
||||
const node = this.nodesMap[key]; |
||||
if (!node) return; |
||||
const childNodes = node.childNodes; |
||||
for (let i = childNodes.length - 1; i >= 0; i--) { |
||||
const child = childNodes[i]; |
||||
this.remove(child.data); |
||||
} |
||||
for (let i = 0, j = data.length; i < j; i++) { |
||||
const child = data[i]; |
||||
this.append(child, node.data); |
||||
} |
||||
} |
||||
|
||||
_setCheckedKeys(key, leafOnly = false, checkedKeys) { |
||||
const allNodes = this._getAllNodes().sort((a, b) => b.level - a.level); |
||||
const cache = Object.create(null); |
||||
const keys = Object.keys(checkedKeys); |
||||
allNodes.forEach(node => node.setChecked(false, false)); |
||||
for (let i = 0, j = allNodes.length; i < j; i++) { |
||||
const node = allNodes[i]; |
||||
const nodeKey = node.data[key].toString(); |
||||
let checked = keys.indexOf(nodeKey) > -1; |
||||
if (!checked) { |
||||
if (node.checked && !cache[nodeKey]) { |
||||
node.setChecked(false, false); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
let parent = node.parent; |
||||
while (parent && parent.level > 0) { |
||||
cache[parent.data[key]] = true; |
||||
parent = parent.parent; |
||||
} |
||||
|
||||
if (node.isLeaf || this.checkStrictly) { |
||||
node.setChecked(true, false); |
||||
continue; |
||||
} |
||||
node.setChecked(true, true); |
||||
|
||||
if (leafOnly) { |
||||
node.setChecked(false, false); |
||||
const traverse = function(node) { |
||||
const childNodes = node.childNodes; |
||||
childNodes.forEach((child) => { |
||||
if (!child.isLeaf) { |
||||
child.setChecked(false, false); |
||||
} |
||||
traverse(child); |
||||
}); |
||||
}; |
||||
traverse(node); |
||||
} |
||||
} |
||||
} |
||||
|
||||
setCheckedNodes(array, leafOnly = false) { |
||||
const key = this.key; |
||||
const checkedKeys = {}; |
||||
array.forEach((item) => { |
||||
checkedKeys[(item || {})[key]] = true; |
||||
}); |
||||
|
||||
this._setCheckedKeys(key, leafOnly, checkedKeys); |
||||
} |
||||
|
||||
setCheckedKeys(keys, leafOnly = false) { |
||||
this.defaultCheckedKeys = keys; |
||||
const key = this.key; |
||||
const checkedKeys = {}; |
||||
keys.forEach((key) => { |
||||
checkedKeys[key] = true; |
||||
}); |
||||
|
||||
this._setCheckedKeys(key, leafOnly, checkedKeys); |
||||
} |
||||
|
||||
setDefaultExpandedKeys(keys) { |
||||
keys = keys || []; |
||||
this.defaultExpandedKeys = keys; |
||||
|
||||
keys.forEach((key) => { |
||||
const node = this.getNode(key); |
||||
if (node) node.expand(null, this.autoExpandParent); |
||||
}); |
||||
} |
||||
|
||||
setChecked(data, checked, deep) { |
||||
const node = this.getNode(data); |
||||
|
||||
if (node) { |
||||
node.setChecked(!!checked, deep); |
||||
} |
||||
} |
||||
|
||||
getCurrentNode() { |
||||
return this.currentNode; |
||||
} |
||||
|
||||
setCurrentNode(currentNode) { |
||||
const prevCurrentNode = this.currentNode; |
||||
if (prevCurrentNode) { |
||||
prevCurrentNode.isCurrent = false; |
||||
} |
||||
this.currentNode = currentNode; |
||||
this.currentNode.isCurrent = true; |
||||
} |
||||
|
||||
setUserCurrentNode(node) { |
||||
const key = node[this.key]; |
||||
const currNode = this.nodesMap[key]; |
||||
this.setCurrentNode(currNode); |
||||
} |
||||
|
||||
setCurrentNodeKey(key) { |
||||
if (key === null || key === undefined) { |
||||
this.currentNode && (this.currentNode.isCurrent = false); |
||||
this.currentNode = null; |
||||
return; |
||||
} |
||||
const node = this.getNode(key); |
||||
if (node) { |
||||
this.setCurrentNode(node); |
||||
} |
||||
} |
||||
}; |
@ -0,0 +1,27 @@ |
||||
export const NODE_KEY = '$treeNodeId'; |
||||
|
||||
export const markNodeData = function(node, data) { |
||||
if (!data || data[NODE_KEY]) return; |
||||
Object.defineProperty(data, NODE_KEY, { |
||||
value: node.id, |
||||
enumerable: false, |
||||
configurable: false, |
||||
writable: false |
||||
}); |
||||
}; |
||||
|
||||
export const getNodeKey = function(key, data) { |
||||
if (!key) return data[NODE_KEY]; |
||||
return data[key]; |
||||
}; |
||||
|
||||
export const findNearestComponent = (element, componentName) => { |
||||
let target = element; |
||||
while (target && target.tagName !== 'BODY') { |
||||
if (target.__vue__ && target.__vue__.$options.name === componentName) { |
||||
return target.__vue__; |
||||
} |
||||
target = target.parentNode; |
||||
} |
||||
return null; |
||||
}; |
@ -0,0 +1,279 @@ |
||||
<template> |
||||
<div |
||||
class="el-tree-node" |
||||
@click.stop="handleClick" |
||||
@contextmenu="($event) => this.handleContextMenu($event)" |
||||
v-show="node.visible" |
||||
:class="{ |
||||
'is-expanded': expanded, |
||||
'is-current': node.isCurrent, |
||||
'is-hidden': !node.visible, |
||||
'is-focusable': !node.disabled, |
||||
'is-checked': !node.disabled && node.checked |
||||
}" |
||||
role="treeitem" |
||||
tabindex="-1" |
||||
:aria-expanded="expanded" |
||||
:aria-disabled="node.disabled" |
||||
:aria-checked="node.checked" |
||||
:draggable="tree.draggable" |
||||
@dragstart.stop="handleDragStart" |
||||
@dragover.stop="handleDragOver" |
||||
@dragend.stop="handleDragEnd" |
||||
@drop.stop="handleDrop" |
||||
ref="node" |
||||
> |
||||
<div class="el-tree-node__content" |
||||
:style="{ 'padding-left': (node.level - 1) * tree.indent + 'px' }"> |
||||
<span |
||||
@click.stop="handleExpandIconClick" |
||||
:class="[ |
||||
{ 'is-leaf': node.isLeaf, expanded: !node.isLeaf && expanded }, |
||||
'el-tree-node__expand-icon', |
||||
tree.iconClass ? tree.iconClass : 'el-icon-caret-right' |
||||
]" |
||||
> |
||||
</span> |
||||
<el-checkbox |
||||
v-if="showCheckbox" |
||||
v-model="node.checked" |
||||
:indeterminate="node.indeterminate" |
||||
:disabled="!!node.disabled" |
||||
@click.native.stop |
||||
@change="handleCheckChange" |
||||
> |
||||
</el-checkbox> |
||||
<span |
||||
v-if="node.loading" |
||||
class="el-tree-node__loading-icon el-icon-loading"> |
||||
</span> |
||||
<node-content :node="node"></node-content> |
||||
</div> |
||||
<el-collapse-transition> |
||||
<div |
||||
class="el-tree-node__children" |
||||
v-if="!renderAfterExpand || childNodeRendered" |
||||
v-show="expanded" |
||||
role="group" |
||||
:aria-expanded="expanded" |
||||
> |
||||
<el-tree-node |
||||
:render-content="renderContent" |
||||
v-for="child in node.childNodes" |
||||
:render-after-expand="renderAfterExpand" |
||||
:show-checkbox="showCheckbox" |
||||
:key="getNodeKey(child)" |
||||
:node="child" |
||||
@node-expand="handleChildNodeExpand"> |
||||
</el-tree-node> |
||||
</div> |
||||
</el-collapse-transition> |
||||
</div> |
||||
</template> |
||||
|
||||
<script type="text/jsx"> |
||||
import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition'; |
||||
import ElCheckbox from 'element-ui/packages/checkbox'; |
||||
import emitter from 'element-ui/src/mixins/emitter'; |
||||
import { getNodeKey } from './model/util'; |
||||
|
||||
export default { |
||||
name: 'ElTreeNode', |
||||
|
||||
componentName: 'ElTreeNode', |
||||
|
||||
mixins: [emitter], |
||||
|
||||
props: { |
||||
node: { |
||||
default() { |
||||
return {}; |
||||
} |
||||
}, |
||||
props: {}, |
||||
renderContent: Function, |
||||
renderAfterExpand: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
showCheckbox: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
}, |
||||
|
||||
components: { |
||||
ElCollapseTransition, |
||||
ElCheckbox, |
||||
NodeContent: { |
||||
props: { |
||||
node: { |
||||
required: true |
||||
} |
||||
}, |
||||
render(h) { |
||||
const parent = this.$parent; |
||||
const tree = parent.tree; |
||||
const node = this.node; |
||||
const { data, store } = node; |
||||
return ( |
||||
parent.renderContent |
||||
? parent.renderContent.call(parent._renderProxy, h, { _self: tree.$vnode.context, node, data, store }) |
||||
: tree.$scopedSlots.default |
||||
? tree.$scopedSlots.default({ node, data }) |
||||
: <span class="el-tree-node__label">{ node.label }</span> |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
tree: null, |
||||
expanded: false, |
||||
childNodeRendered: false, |
||||
oldChecked: null, |
||||
oldIndeterminate: null |
||||
}; |
||||
}, |
||||
|
||||
watch: { |
||||
'node.indeterminate'(val) { |
||||
this.handleSelectChange(this.node.checked, val); |
||||
}, |
||||
|
||||
'node.checked'(val) { |
||||
this.handleSelectChange(val, this.node.indeterminate); |
||||
}, |
||||
|
||||
'node.expanded'(val) { |
||||
this.$nextTick(() => this.expanded = val); |
||||
if (val) { |
||||
this.childNodeRendered = true; |
||||
} |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
getNodeKey(node) { |
||||
return getNodeKey(this.tree.nodeKey, node.data); |
||||
}, |
||||
|
||||
handleSelectChange(checked, indeterminate) { |
||||
if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) { |
||||
this.tree.$emit('check-change', this.node.data, checked, indeterminate); |
||||
} |
||||
this.oldChecked = checked; |
||||
this.indeterminate = indeterminate; |
||||
}, |
||||
|
||||
handleClick() { |
||||
const store = this.tree.store; |
||||
store.setCurrentNode(this.node); |
||||
this.tree.$emit('current-change', store.currentNode ? store.currentNode.data : null, store.currentNode); |
||||
this.tree.currentNode = this; |
||||
if (this.tree.expandOnClickNode) { |
||||
this.handleExpandIconClick(); |
||||
} |
||||
if (this.tree.checkOnClickNode && !this.node.disabled) { |
||||
this.handleCheckChange(null, { |
||||
target: { checked: !this.node.checked } |
||||
}); |
||||
} |
||||
this.tree.$emit('node-click', this.node.data, this.node, this); |
||||
}, |
||||
|
||||
handleContextMenu(event) { |
||||
if (this.tree._events['node-contextmenu'] && this.tree._events['node-contextmenu'].length > 0) { |
||||
event.stopPropagation(); |
||||
event.preventDefault(); |
||||
} |
||||
this.tree.$emit('node-contextmenu', event, this.node.data, this.node, this); |
||||
}, |
||||
|
||||
handleExpandIconClick() { |
||||
if (this.node.isLeaf) return; |
||||
if (this.expanded) { |
||||
this.tree.$emit('node-collapse', this.node.data, this.node, this); |
||||
this.node.collapse(); |
||||
} else { |
||||
this.node.expand(); |
||||
this.$emit('node-expand', this.node.data, this.node, this); |
||||
} |
||||
}, |
||||
|
||||
handleCheckChange(value, ev) { |
||||
this.node.setChecked(ev.target.checked, !this.tree.checkStrictly); |
||||
this.$nextTick(() => { |
||||
const store = this.tree.store; |
||||
this.tree.$emit('check', this.node.data, { |
||||
checkedNodes: store.getCheckedNodes(), |
||||
checkedKeys: store.getCheckedKeys(), |
||||
halfCheckedNodes: store.getHalfCheckedNodes(), |
||||
halfCheckedKeys: store.getHalfCheckedKeys(), |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
handleChildNodeExpand(nodeData, node, instance) { |
||||
this.broadcast('ElTreeNode', 'tree-node-expand', node); |
||||
this.tree.$emit('node-expand', nodeData, node, instance); |
||||
}, |
||||
|
||||
handleDragStart(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-start', event, this); |
||||
}, |
||||
|
||||
handleDragOver(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-over', event, this); |
||||
event.preventDefault(); |
||||
}, |
||||
|
||||
handleDrop(event) { |
||||
event.preventDefault(); |
||||
}, |
||||
|
||||
handleDragEnd(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-end', event, this); |
||||
} |
||||
}, |
||||
|
||||
created() { |
||||
const parent = this.$parent; |
||||
|
||||
if (parent.isTree) { |
||||
this.tree = parent; |
||||
} else { |
||||
this.tree = parent.tree; |
||||
} |
||||
|
||||
const tree = this.tree; |
||||
if (!tree) { |
||||
console.warn('Can not find node\'s tree.'); |
||||
} |
||||
|
||||
const props = tree.props || {}; |
||||
const childrenKey = props['children'] || 'children'; |
||||
|
||||
this.$watch(`node.data.${childrenKey}`, () => { |
||||
this.node.updateChildren(); |
||||
}); |
||||
|
||||
if (this.node.expanded) { |
||||
this.expanded = true; |
||||
this.childNodeRendered = true; |
||||
} |
||||
|
||||
if(this.tree.accordion) { |
||||
this.$on('tree-node-expand', node => { |
||||
if(this.node !== node) { |
||||
this.node.collapse(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,496 @@ |
||||
<template> |
||||
<div |
||||
class="el-tree" |
||||
:class="{ |
||||
'el-tree--highlight-current': highlightCurrent, |
||||
'is-dragging': !!dragState.draggingNode, |
||||
'is-drop-not-allow': !dragState.allowDrop, |
||||
'is-drop-inner': dragState.dropType === 'inner' |
||||
}" |
||||
role="tree" |
||||
> |
||||
<el-tree-node |
||||
v-for="child in root.childNodes" |
||||
:node="child" |
||||
:props="props" |
||||
:render-after-expand="renderAfterExpand" |
||||
:show-checkbox="showCheckbox" |
||||
:key="getNodeKey(child)" |
||||
:render-content="renderContent" |
||||
@node-expand="handleNodeExpand"> |
||||
</el-tree-node> |
||||
<div class="el-tree__empty-block" v-if="isEmpty"> |
||||
<span class="el-tree__empty-text">{{ emptyText }}</span> |
||||
</div> |
||||
<div |
||||
v-show="dragState.showDropIndicator" |
||||
class="el-tree__drop-indicator" |
||||
ref="dropIndicator"> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import TreeStore from './model/tree-store'; |
||||
import { getNodeKey, findNearestComponent } from './model/util'; |
||||
import ElTreeNode from './tree-node.vue'; |
||||
import {t} from 'element-ui/src/locale'; |
||||
import emitter from 'element-ui/src/mixins/emitter'; |
||||
import { addClass, removeClass } from 'element-ui/src/utils/dom'; |
||||
|
||||
export default { |
||||
name: 'ElTree', |
||||
|
||||
mixins: [emitter], |
||||
|
||||
components: { |
||||
ElTreeNode |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
store: null, |
||||
root: null, |
||||
currentNode: null, |
||||
treeItems: null, |
||||
checkboxItems: [], |
||||
dragState: { |
||||
showDropIndicator: false, |
||||
draggingNode: null, |
||||
dropNode: null, |
||||
allowDrop: true |
||||
} |
||||
}; |
||||
}, |
||||
|
||||
props: { |
||||
data: { |
||||
type: Array |
||||
}, |
||||
emptyText: { |
||||
type: String, |
||||
default() { |
||||
return t('el.tree.emptyText'); |
||||
} |
||||
}, |
||||
renderAfterExpand: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
nodeKey: String, |
||||
checkStrictly: Boolean, |
||||
defaultExpandAll: Boolean, |
||||
expandOnClickNode: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
checkOnClickNode: Boolean, |
||||
checkDescendants: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
autoExpandParent: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
defaultCheckedKeys: Array, |
||||
defaultExpandedKeys: Array, |
||||
currentNodeKey: [String, Number], |
||||
renderContent: Function, |
||||
showCheckbox: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
draggable: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
allowDrag: Function, |
||||
allowDrop: Function, |
||||
props: { |
||||
default() { |
||||
return { |
||||
children: 'children', |
||||
label: 'label', |
||||
disabled: 'disabled' |
||||
}; |
||||
} |
||||
}, |
||||
lazy: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
highlightCurrent: Boolean, |
||||
load: Function, |
||||
filterNodeMethod: Function, |
||||
accordion: Boolean, |
||||
indent: { |
||||
type: Number, |
||||
default: 18 |
||||
}, |
||||
iconClass: String |
||||
}, |
||||
|
||||
computed: { |
||||
children: { |
||||
set(value) { |
||||
this.data = value; |
||||
}, |
||||
get() { |
||||
return this.data; |
||||
} |
||||
}, |
||||
|
||||
treeItemArray() { |
||||
return Array.prototype.slice.call(this.treeItems); |
||||
}, |
||||
|
||||
isEmpty() { |
||||
const { childNodes } = this.root; |
||||
return !childNodes || childNodes.length === 0 || childNodes.every(({visible}) => !visible); |
||||
} |
||||
}, |
||||
|
||||
watch: { |
||||
defaultCheckedKeys(newVal) { |
||||
this.store.setDefaultCheckedKey(newVal); |
||||
}, |
||||
|
||||
defaultExpandedKeys(newVal) { |
||||
this.store.defaultExpandedKeys = newVal; |
||||
this.store.setDefaultExpandedKeys(newVal); |
||||
}, |
||||
|
||||
data(newVal) { |
||||
this.store.setData(newVal); |
||||
}, |
||||
|
||||
checkboxItems(val) { |
||||
Array.prototype.forEach.call(val, (checkbox) => { |
||||
checkbox.setAttribute('tabindex', -1); |
||||
}); |
||||
}, |
||||
|
||||
checkStrictly(newVal) { |
||||
this.store.checkStrictly = newVal; |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
filter(value) { |
||||
if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter'); |
||||
this.store.filter(value); |
||||
}, |
||||
|
||||
getNodeKey(node) { |
||||
return getNodeKey(this.nodeKey, node.data); |
||||
}, |
||||
|
||||
getNodePath(data) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getNodePath'); |
||||
const node = this.store.getNode(data); |
||||
if (!node) return []; |
||||
const path = [node.data]; |
||||
let parent = node.parent; |
||||
while (parent && parent !== this.root) { |
||||
path.push(parent.data); |
||||
parent = parent.parent; |
||||
} |
||||
return path.reverse(); |
||||
}, |
||||
|
||||
getCheckedNodes(leafOnly, includeHalfChecked) { |
||||
return this.store.getCheckedNodes(leafOnly, includeHalfChecked); |
||||
}, |
||||
|
||||
getCheckedKeys(leafOnly) { |
||||
return this.store.getCheckedKeys(leafOnly); |
||||
}, |
||||
|
||||
getCurrentNode() { |
||||
const currentNode = this.store.getCurrentNode(); |
||||
return currentNode ? currentNode.data : null; |
||||
}, |
||||
|
||||
getCurrentKey() { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getCurrentKey'); |
||||
const currentNode = this.getCurrentNode(); |
||||
return currentNode ? currentNode[this.nodeKey] : null; |
||||
}, |
||||
|
||||
setCheckedNodes(nodes, leafOnly) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes'); |
||||
this.store.setCheckedNodes(nodes, leafOnly); |
||||
}, |
||||
|
||||
setCheckedKeys(keys, leafOnly) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys'); |
||||
this.store.setCheckedKeys(keys, leafOnly); |
||||
}, |
||||
|
||||
setChecked(data, checked, deep) { |
||||
this.store.setChecked(data, checked, deep); |
||||
}, |
||||
|
||||
getHalfCheckedNodes() { |
||||
return this.store.getHalfCheckedNodes(); |
||||
}, |
||||
|
||||
getHalfCheckedKeys() { |
||||
return this.store.getHalfCheckedKeys(); |
||||
}, |
||||
|
||||
setCurrentNode(node) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode'); |
||||
this.store.setUserCurrentNode(node); |
||||
}, |
||||
|
||||
setCurrentKey(key) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey'); |
||||
this.store.setCurrentNodeKey(key); |
||||
}, |
||||
|
||||
getNode(data) { |
||||
return this.store.getNode(data); |
||||
}, |
||||
|
||||
remove(data) { |
||||
this.store.remove(data); |
||||
}, |
||||
|
||||
append(data, parentNode) { |
||||
this.store.append(data, parentNode); |
||||
}, |
||||
|
||||
insertBefore(data, refNode) { |
||||
this.store.insertBefore(data, refNode); |
||||
}, |
||||
|
||||
insertAfter(data, refNode) { |
||||
this.store.insertAfter(data, refNode); |
||||
}, |
||||
|
||||
handleNodeExpand(nodeData, node, instance) { |
||||
this.broadcast('ElTreeNode', 'tree-node-expand', node); |
||||
this.$emit('node-expand', nodeData, node, instance); |
||||
}, |
||||
|
||||
updateKeyChildren(key, data) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild'); |
||||
this.store.updateChildren(key, data); |
||||
}, |
||||
|
||||
initTabIndex() { |
||||
this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]'); |
||||
this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]'); |
||||
const checkedItem = this.$el.querySelectorAll('.is-checked[role=treeitem]'); |
||||
if (checkedItem.length) { |
||||
checkedItem[0].setAttribute('tabindex', 0); |
||||
return; |
||||
} |
||||
this.treeItems[0] && this.treeItems[0].setAttribute('tabindex', 0); |
||||
}, |
||||
|
||||
handleKeydown(ev) { |
||||
const currentItem = ev.target; |
||||
if (currentItem.className.indexOf('el-tree-node') === -1) return; |
||||
const keyCode = ev.keyCode; |
||||
this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]'); |
||||
const currentIndex = this.treeItemArray.indexOf(currentItem); |
||||
let nextIndex; |
||||
if ([38, 40].indexOf(keyCode) > -1) { // up、down |
||||
ev.preventDefault(); |
||||
if (keyCode === 38) { // up |
||||
nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0; |
||||
} else { |
||||
nextIndex = (currentIndex < this.treeItemArray.length - 1) ? currentIndex + 1 : 0; |
||||
} |
||||
this.treeItemArray[nextIndex].focus(); // 选中 |
||||
} |
||||
if ([37, 39].indexOf(keyCode) > -1) { // left、right 展开 |
||||
ev.preventDefault(); |
||||
currentItem.click(); // 选中 |
||||
} |
||||
const hasInput = currentItem.querySelector('[type="checkbox"]'); |
||||
if ([13, 32].indexOf(keyCode) > -1 && hasInput) { // space enter选中checkbox |
||||
ev.preventDefault(); |
||||
hasInput.click(); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
created() { |
||||
this.isTree = true; |
||||
|
||||
this.store = new TreeStore({ |
||||
key: this.nodeKey, |
||||
data: this.data, |
||||
lazy: this.lazy, |
||||
props: this.props, |
||||
load: this.load, |
||||
currentNodeKey: this.currentNodeKey, |
||||
checkStrictly: this.checkStrictly, |
||||
checkDescendants: this.checkDescendants, |
||||
defaultCheckedKeys: this.defaultCheckedKeys, |
||||
defaultExpandedKeys: this.defaultExpandedKeys, |
||||
autoExpandParent: this.autoExpandParent, |
||||
defaultExpandAll: this.defaultExpandAll, |
||||
filterNodeMethod: this.filterNodeMethod |
||||
}); |
||||
|
||||
this.root = this.store.root; |
||||
|
||||
let dragState = this.dragState; |
||||
this.$on('tree-node-drag-start', (event, treeNode) => { |
||||
if (typeof this.allowDrag === 'function' && !this.allowDrag(treeNode.node)) { |
||||
event.preventDefault(); |
||||
return false; |
||||
} |
||||
event.dataTransfer.effectAllowed = 'move'; |
||||
|
||||
// wrap in try catch to address IE's error when first param is 'text/plain' |
||||
try { |
||||
// setData is required for draggable to work in FireFox |
||||
// the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox |
||||
event.dataTransfer.setData('text/plain', ''); |
||||
} catch (e) {} |
||||
dragState.draggingNode = treeNode; |
||||
this.$emit('node-drag-start', treeNode.node, event); |
||||
}); |
||||
|
||||
this.$on('tree-node-drag-over', (event, treeNode) => { |
||||
const dropNode = findNearestComponent(event.target, 'ElTreeNode'); |
||||
const oldDropNode = dragState.dropNode; |
||||
if (oldDropNode && oldDropNode !== dropNode) { |
||||
removeClass(oldDropNode.$el, 'is-drop-inner'); |
||||
} |
||||
const draggingNode = dragState.draggingNode; |
||||
if (!draggingNode || !dropNode) return; |
||||
|
||||
let dropPrev = true; |
||||
let dropInner = true; |
||||
let dropNext = true; |
||||
let userAllowDropInner = true; |
||||
if (typeof this.allowDrop === 'function') { |
||||
dropPrev = this.allowDrop(draggingNode.node, dropNode.node, 'prev'); |
||||
userAllowDropInner = dropInner = this.allowDrop(draggingNode.node, dropNode.node, 'inner'); |
||||
dropNext = this.allowDrop(draggingNode.node, dropNode.node, 'next'); |
||||
} |
||||
event.dataTransfer.dropEffect = dropInner ? 'move' : 'none'; |
||||
if ((dropPrev || dropInner || dropNext) && oldDropNode !== dropNode) { |
||||
if (oldDropNode) { |
||||
this.$emit('node-drag-leave', draggingNode.node, oldDropNode.node, event); |
||||
} |
||||
this.$emit('node-drag-enter', draggingNode.node, dropNode.node, event); |
||||
} |
||||
|
||||
if (dropPrev || dropInner || dropNext) { |
||||
dragState.dropNode = dropNode; |
||||
} |
||||
|
||||
if (dropNode.node.nextSibling === draggingNode.node) { |
||||
dropNext = false; |
||||
} |
||||
if (dropNode.node.previousSibling === draggingNode.node) { |
||||
dropPrev = false; |
||||
} |
||||
if (dropNode.node.contains(draggingNode.node, false)) { |
||||
dropInner = false; |
||||
} |
||||
if (draggingNode.node === dropNode.node || draggingNode.node.contains(dropNode.node)) { |
||||
dropPrev = false; |
||||
dropInner = false; |
||||
dropNext = false; |
||||
} |
||||
|
||||
const targetPosition = dropNode.$el.getBoundingClientRect(); |
||||
const treePosition = this.$el.getBoundingClientRect(); |
||||
|
||||
let dropType; |
||||
const prevPercent = dropPrev ? (dropInner ? 0.25 : (dropNext ? 0.45 : 1)) : -1; |
||||
const nextPercent = dropNext ? (dropInner ? 0.75 : (dropPrev ? 0.55 : 0)) : 1; |
||||
|
||||
let indicatorTop = -9999; |
||||
const distance = event.clientY - targetPosition.top; |
||||
if (distance < targetPosition.height * prevPercent) { |
||||
dropType = 'before'; |
||||
} else if (distance > targetPosition.height * nextPercent) { |
||||
dropType = 'after'; |
||||
} else if (dropInner) { |
||||
dropType = 'inner'; |
||||
} else { |
||||
dropType = 'none'; |
||||
} |
||||
|
||||
const iconPosition = dropNode.$el.querySelector('.el-tree-node__expand-icon').getBoundingClientRect(); |
||||
const dropIndicator = this.$refs.dropIndicator; |
||||
if (dropType === 'before') { |
||||
indicatorTop = iconPosition.top - treePosition.top; |
||||
} else if (dropType === 'after') { |
||||
indicatorTop = iconPosition.bottom - treePosition.top; |
||||
} |
||||
dropIndicator.style.top = indicatorTop + 'px'; |
||||
dropIndicator.style.left = (iconPosition.right - treePosition.left) + 'px'; |
||||
|
||||
if (dropType === 'inner') { |
||||
addClass(dropNode.$el, 'is-drop-inner'); |
||||
} else { |
||||
removeClass(dropNode.$el, 'is-drop-inner'); |
||||
} |
||||
|
||||
dragState.showDropIndicator = dropType === 'before' || dropType === 'after'; |
||||
dragState.allowDrop = dragState.showDropIndicator || userAllowDropInner; |
||||
dragState.dropType = dropType; |
||||
this.$emit('node-drag-over', draggingNode.node, dropNode.node, event); |
||||
}); |
||||
|
||||
this.$on('tree-node-drag-end', (event) => { |
||||
const { draggingNode, dropType, dropNode } = dragState; |
||||
event.preventDefault(); |
||||
event.dataTransfer.dropEffect = 'move'; |
||||
|
||||
if (draggingNode && dropNode) { |
||||
const draggingNodeCopy = { data: draggingNode.node.data }; |
||||
if (dropType !== 'none') { |
||||
draggingNode.node.remove(); |
||||
} |
||||
if (dropType === 'before') { |
||||
dropNode.node.parent.insertBefore(draggingNodeCopy, dropNode.node); |
||||
} else if (dropType === 'after') { |
||||
dropNode.node.parent.insertAfter(draggingNodeCopy, dropNode.node); |
||||
} else if (dropType === 'inner') { |
||||
dropNode.node.insertChild(draggingNodeCopy); |
||||
} |
||||
if (dropType !== 'none') { |
||||
this.store.registerNode(draggingNodeCopy); |
||||
} |
||||
|
||||
removeClass(dropNode.$el, 'is-drop-inner'); |
||||
|
||||
this.$emit('node-drag-end', draggingNode.node, dropNode.node, dropType, event); |
||||
if (dropType !== 'none') { |
||||
this.$emit('node-drop', draggingNode.node, dropNode.node, dropType, event); |
||||
} |
||||
} |
||||
if (draggingNode && !dropNode) { |
||||
this.$emit('node-drag-end', draggingNode.node, null, dropType, event); |
||||
} |
||||
|
||||
dragState.showDropIndicator = false; |
||||
dragState.draggingNode = null; |
||||
dragState.dropNode = null; |
||||
dragState.allowDrop = true; |
||||
}); |
||||
}, |
||||
|
||||
mounted() { |
||||
this.initTabIndex(); |
||||
this.$el.addEventListener('keydown', this.handleKeydown); |
||||
}, |
||||
|
||||
updated() { |
||||
this.treeItems = this.$el.querySelectorAll('[role=treeitem]'); |
||||
this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]'); |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,131 @@ |
||||
<template> |
||||
<div> |
||||
<el-dialog |
||||
custom-class="pdf-dia" |
||||
:close-on-click-modal="false" |
||||
:visible.sync="visible" |
||||
@close="closePdf" |
||||
:fullscreen="true" |
||||
:modal="false" |
||||
:append-to-body="true"> |
||||
<div> |
||||
<button type="button" aria-label="Close" class="el-dialog__headerbtn" @click="closePdf"><i class="el-dialog__close el-icon el-icon-close"></i></button> |
||||
<div class="pdf"> |
||||
<p class="arrow"> |
||||
<span @click="changePdfPage(0)" class="turn el-icon-arrow-left" :class="{grey: currentPage==1}"></span> |
||||
{{ currentPage }} / {{ pageCount }} |
||||
<span @click="changePdfPage(1)" class="turn el-icon-arrow-right" :class="{grey: currentPage==pageCount}"></span> |
||||
</p> |
||||
<pdf |
||||
class="pdf-wrap" |
||||
:src="src" |
||||
:page="currentPage" |
||||
@num-pages="pageCount=$event" |
||||
@page-loaded="currentPage=$event" |
||||
@loaded="loadPdfHandler" |
||||
> |
||||
</pdf> |
||||
</div> |
||||
</div> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import pdf from "vue-pdf"; |
||||
|
||||
export default { |
||||
props: ["visible", "src"], |
||||
data() { |
||||
return { |
||||
pdfVisible: false, |
||||
pdfSrc: "", |
||||
currentPage: 0, |
||||
pageCount: 0, |
||||
fileType: "pdf" |
||||
}; |
||||
}, |
||||
components: { pdf }, |
||||
mounted() { |
||||
this.addEvent(); |
||||
}, |
||||
methods: { |
||||
closePdf() { |
||||
this.$emit("update:visible", false); |
||||
this.$emit("update:src", ""); |
||||
this.currentPage = 1; |
||||
}, |
||||
changePdfPage(val) { |
||||
if (val === 0 && this.currentPage > 1) { |
||||
this.currentPage--; |
||||
} |
||||
if (val === 1 && this.currentPage < this.pageCount) { |
||||
this.currentPage++; |
||||
} |
||||
}, |
||||
loadPdfHandler(e) { |
||||
this.currentPage = 1; |
||||
}, |
||||
addEvent() { |
||||
document.onkeydown = e => { |
||||
let key = window.event.keyCode; |
||||
if (key == 37) { |
||||
this.changePdfPage(0); |
||||
} else if (key == 39) { |
||||
this.changePdfPage(1); |
||||
} |
||||
}; |
||||
this.$once("hook:beforeDestroy", () => { |
||||
document.onkeydown = null; |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
/deep/ .pdf-dia { |
||||
border-radius: 0 !important; |
||||
|
||||
.el-dialog__header { |
||||
display: none; |
||||
} |
||||
|
||||
.el-dialog__body { |
||||
padding: 0; |
||||
} |
||||
|
||||
.el-dialog__headerbtn { |
||||
top: 10px; |
||||
|
||||
.el-dialog__close { |
||||
color: #fff; |
||||
font-size: 16px; |
||||
} |
||||
} |
||||
|
||||
.pdf { |
||||
.arrow { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: 100%; |
||||
padding: 10px 0; |
||||
font-size: 16px; |
||||
color: #fff; |
||||
background-color: #333; |
||||
|
||||
.turn { |
||||
margin: 0 10px; |
||||
font-size: 18px; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.pdf-wrap { |
||||
height: calc(100vh - 45px); |
||||
margin: 0 auto; |
||||
overflow: auto; |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
@ -0,0 +1,255 @@ |
||||
<template> |
||||
<div class="quill" ref="quill" :class="classes"> |
||||
<div ref="editor" :style="styles" v-loading="loading"></div> |
||||
|
||||
<el-upload |
||||
:headers="headers" |
||||
:action="this.api.fileupload" |
||||
:before-upload="beforeUpload" |
||||
:on-success="editorUploadSuccess" |
||||
style="display: none" |
||||
> |
||||
<el-button class="editorUpload" type="primary">点击上传</el-button> |
||||
</el-upload> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import util from "@/libs/util"; |
||||
import Setting from "@/setting"; |
||||
import Quill from "quill"; |
||||
import "quill/dist/quill.core.css"; |
||||
import "quill/dist/quill.snow.css"; |
||||
import "quill/dist/quill.bubble.css"; |
||||
import toolbarOptions from "./options"; |
||||
|
||||
export default { |
||||
name: "quill", |
||||
props: { |
||||
value: { |
||||
type: String, |
||||
default: "" |
||||
}, |
||||
readonly: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
toTop: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
border: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
height: { |
||||
type: Number |
||||
}, |
||||
minHeight: { |
||||
type: Number |
||||
}, |
||||
/* |
||||
* 原本的readOnly失效,对比其他项目,发现是quill版本不同导致, |
||||
* 使用props传入elseRead = 'true',手动隐藏工具栏 |
||||
*/ |
||||
elseRead: { |
||||
type: String, default: "false" |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
headers: { |
||||
token: util.local.get(Setting.tokenKey) |
||||
}, |
||||
Quill: null, |
||||
currentValue: "", |
||||
options: { |
||||
theme: "snow", |
||||
bounds: document.body, |
||||
debug: "warn", |
||||
modules: { |
||||
toolbar: { |
||||
container: toolbarOptions, |
||||
handlers: { |
||||
"image": function(value) { |
||||
if (value) { |
||||
// 调用iview图片上传 |
||||
document.querySelector(".editorUpload").click(); |
||||
} else { |
||||
this.Quill.format("image", false); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
placeholder: "", |
||||
readOnly: this.readonly |
||||
}, |
||||
loading: false |
||||
}; |
||||
}, |
||||
computed: { |
||||
classes() { |
||||
return [ |
||||
{ |
||||
"quill-no-border": !this.border |
||||
} |
||||
]; |
||||
}, |
||||
styles() { |
||||
let style = {}; |
||||
if (this.minHeight) { |
||||
style.minHeight = `${this.minHeight}px`; |
||||
} |
||||
if (this.height) { |
||||
style.height = `${this.height}px`; |
||||
} |
||||
return style; |
||||
} |
||||
|
||||
}, |
||||
watch: { |
||||
value: { |
||||
handler(val) { |
||||
if (val !== this.currentValue) { |
||||
this.currentValue = val; |
||||
if (this.Quill) { |
||||
this.Quill.pasteHTML(this.value); |
||||
} |
||||
} |
||||
}, |
||||
immediate: true |
||||
} |
||||
}, |
||||
created() { |
||||
}, |
||||
mounted() { |
||||
this.init(); |
||||
// 处理工具栏隐藏样式 |
||||
if (this.elseRead === "true") { |
||||
let children = this.$refs.quill.children[0].style; |
||||
children.padding = "0"; |
||||
children.overflow = "hidden"; |
||||
children.height = "0"; |
||||
children.borderTop = "0"; |
||||
} |
||||
}, |
||||
beforeDestroy() { |
||||
// 在组件销毁后销毁实例 |
||||
this.Quill = null; |
||||
}, |
||||
methods: { |
||||
init () { |
||||
const editor = this.$refs.editor; |
||||
// 初始化编辑器 |
||||
this.Quill = new Quill(editor, this.options); |
||||
const ins = this.Quill |
||||
// 默认值 |
||||
ins.pasteHTML(this.currentValue); |
||||
if(this.toTop){ |
||||
this.$nextTick(() => { |
||||
window.scrollTo(0,0) |
||||
}) |
||||
} |
||||
// 绑定事件 |
||||
ins.on('text-change', (delta, oldDelta, source) => { |
||||
const html = this.$refs.editor.children[0].innerHTML; |
||||
const text = ins.getText(); |
||||
const quill = this.Quill; |
||||
// 更新内部的值 |
||||
this.currentValue = html; |
||||
// 发出事件 v-model |
||||
this.$emit('input', html); |
||||
// 发出事件 |
||||
this.$emit('on-change', { html, text, quill }); |
||||
}); |
||||
// 将一些 quill 自带的事件传递出去 |
||||
ins.on('text-change', (delta, oldDelta, source) => { |
||||
this.$emit('on-text-change', delta, oldDelta, source); |
||||
}); |
||||
ins.on('selection-change', (range, oldRange, source) => { |
||||
this.$emit('on-selection-change', range, oldRange, source); |
||||
}); |
||||
ins.on('editor-change', (eventName, ...args) => { |
||||
this.$emit('on-editor-change', eventName, ...args); |
||||
}); |
||||
// 监听粘贴事件 |
||||
ins.root.addEventListener('paste', evt => { |
||||
if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) { |
||||
evt.preventDefault(); |
||||
// 检测是否粘贴的是图片 |
||||
[].forEach.call(evt.clipboardData.files, file => { |
||||
if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) { |
||||
return |
||||
} |
||||
const param = new FormData() |
||||
param.append('file', file) |
||||
// 把图片上传到服务器,不然会直接把base64存到数据库 |
||||
this.$post(this.api.fileupload, param, { |
||||
headers: { "Content-Type": "multipart/form-data" } |
||||
}).then(res => { |
||||
var range = ins.getSelection() |
||||
if (range) { |
||||
// 在当前光标位置插入图片 |
||||
ins.insertEmbed(range.index, 'image', res.data.filesResult.fileUrl) |
||||
// 将光标移动到图片后面 |
||||
ins.setSelection(range.index + 1) |
||||
} |
||||
}).catch(res => {}) |
||||
}); |
||||
} |
||||
}, false) |
||||
}, |
||||
beforeUpload(file) { |
||||
this.loading = true; |
||||
}, |
||||
editorUploadSuccess(res) { |
||||
// 获取富文本组件实例 |
||||
let quill = this.Quill; |
||||
// 如果上传成功 |
||||
if (res.data.filesResult.fileUrl) { |
||||
// 获取光标所在位置 |
||||
let length = quill.getSelection().index; |
||||
// 插入图片,res为服务器返回的图片链接地址 |
||||
quill.insertEmbed(length, "image", res.data.filesResult.fileUrl); |
||||
// 调整光标到最后 |
||||
quill.setSelection(length + 1); |
||||
} else { |
||||
util.successMsg("图片插入失败"); |
||||
} |
||||
this.loading = false; |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.quill-no-border { |
||||
.ql-toolbar.ql-snow { |
||||
border: none; |
||||
border-bottom: 1px solid #e8eaec; |
||||
} |
||||
|
||||
.ql-container.ql-snow { |
||||
border: none; |
||||
} |
||||
} |
||||
|
||||
.else { |
||||
.ql-toolbar.ql-snow { |
||||
height: 0; |
||||
overflow: hidden; |
||||
padding: 0; |
||||
border-top: 0; |
||||
} |
||||
} |
||||
/deep/.ql-snow { |
||||
position: relative; |
||||
.ql-tooltip { |
||||
position: absolute !important; |
||||
top: 10px !important; |
||||
left: 10px !important; |
||||
transform: translateY(10px); |
||||
} |
||||
} |
||||
|
||||
</style> |
@ -0,0 +1,16 @@ |
||||
export default [ |
||||
["bold", "italic", "underline", "strike"], |
||||
["blockquote", "code-block"], |
||||
[{ "header": 1 }, { "header": 2 }], |
||||
[{ "list": "ordered" }, { "list": "bullet" }], |
||||
[{ "script": "sub" }, { "script": "super" }], |
||||
[{ "indent": "-1" }, { "indent": "+1" }], |
||||
[{ "direction": "rtl" }], |
||||
[{ "size": ["small", false, "large", "huge"] }], |
||||
[{ "header": [1, 2, 3, 4, 5, 6, false] }], |
||||
[{ "color": [] }, { "background": [] }], |
||||
[{ "font": [] }], |
||||
[{ "align": [] }], |
||||
["clean"], |
||||
["link", "image", "video"] |
||||
]; |
@ -0,0 +1,8 @@ |
||||
import Tree from './src/tree.vue'; |
||||
|
||||
/* istanbul ignore next */ |
||||
Tree.install = function(Vue) { |
||||
Vue.component(Tree.name, Tree); |
||||
}; |
||||
|
||||
export default Tree; |
@ -0,0 +1,486 @@ |
||||
import objectAssign from 'element-ui/src/utils/merge'; |
||||
import { markNodeData, NODE_KEY } from './util'; |
||||
import { arrayFindIndex } from 'element-ui/src/utils/util'; |
||||
|
||||
export const getChildState = node => { |
||||
let all = true; |
||||
let none = true; |
||||
let allWithoutDisable = true; |
||||
for (let i = 0, j = node.length; i < j; i++) { |
||||
const n = node[i]; |
||||
if (n.checked !== true || n.indeterminate) { |
||||
all = false; |
||||
if (!n.disabled) { |
||||
allWithoutDisable = false; |
||||
} |
||||
} |
||||
if (n.checked !== false || n.indeterminate) { |
||||
none = false; |
||||
} |
||||
} |
||||
|
||||
return { all, none, allWithoutDisable, half: !all && !none }; |
||||
}; |
||||
|
||||
const reInitChecked = function(node) { |
||||
if (node.childNodes.length === 0) return; |
||||
|
||||
const {all, none, half} = getChildState(node.childNodes); |
||||
if (all) { |
||||
node.checked = true; |
||||
node.indeterminate = false; |
||||
} else if (half) { |
||||
node.checked = false; |
||||
node.indeterminate = true; |
||||
} else if (none) { |
||||
node.checked = false; |
||||
node.indeterminate = false; |
||||
} |
||||
|
||||
const parent = node.parent; |
||||
if (!parent || parent.level === 0) return; |
||||
|
||||
if (!node.store.checkStrictly) { |
||||
reInitChecked(parent); |
||||
} |
||||
}; |
||||
|
||||
const getPropertyFromData = function(node, prop) { |
||||
const props = node.store.props; |
||||
const data = node.data || {}; |
||||
const config = props[prop]; |
||||
|
||||
if (typeof config === 'function') { |
||||
return config(data, node); |
||||
} else if (typeof config === 'string') { |
||||
return data[config]; |
||||
} else if (typeof config === 'undefined') { |
||||
const dataProp = data[prop]; |
||||
return dataProp === undefined ? '' : dataProp; |
||||
} |
||||
}; |
||||
|
||||
let nodeIdSeed = 0; |
||||
|
||||
export default class Node { |
||||
constructor(options) { |
||||
this.id = nodeIdSeed++; |
||||
this.text = null; |
||||
this.checked = false; |
||||
this.indeterminate = false; |
||||
this.data = null; |
||||
this.expanded = false; |
||||
this.parent = null; |
||||
this.visible = true; |
||||
this.isCurrent = false; |
||||
|
||||
for (let name in options) { |
||||
if (options.hasOwnProperty(name)) { |
||||
this[name] = options[name]; |
||||
} |
||||
} |
||||
|
||||
// internal
|
||||
this.level = 0; |
||||
this.loaded = false; |
||||
this.childNodes = []; |
||||
this.loading = false; |
||||
|
||||
if (this.parent) { |
||||
this.level = this.parent.level + 1; |
||||
} |
||||
|
||||
const store = this.store; |
||||
if (!store) { |
||||
throw new Error('[Node]store is required!'); |
||||
} |
||||
store.registerNode(this); |
||||
|
||||
const props = store.props; |
||||
if (props && typeof props.isLeaf !== 'undefined') { |
||||
const isLeaf = getPropertyFromData(this, 'isLeaf'); |
||||
if (typeof isLeaf === 'boolean') { |
||||
this.isLeafByUser = isLeaf; |
||||
} |
||||
} |
||||
|
||||
if (store.lazy !== true && this.data) { |
||||
this.setData(this.data); |
||||
|
||||
if (store.defaultExpandAll) { |
||||
this.expanded = true; |
||||
} |
||||
} else if (this.level > 0 && store.lazy && store.defaultExpandAll) { |
||||
this.expand(); |
||||
} |
||||
if (!Array.isArray(this.data)) { |
||||
markNodeData(this, this.data); |
||||
} |
||||
if (!this.data) return; |
||||
const defaultExpandedKeys = store.defaultExpandedKeys; |
||||
const key = store.key; |
||||
if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) { |
||||
this.expand(null, store.autoExpandParent); |
||||
} |
||||
|
||||
if (key && store.currentNodeKey !== undefined && this.key === store.currentNodeKey) { |
||||
store.currentNode = this; |
||||
store.currentNode.isCurrent = true; |
||||
} |
||||
|
||||
if (store.lazy) { |
||||
store._initDefaultCheckedNode(this); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
setData(data) { |
||||
if (!Array.isArray(data)) { |
||||
markNodeData(this, data); |
||||
} |
||||
|
||||
this.data = data; |
||||
this.childNodes = []; |
||||
|
||||
let children; |
||||
if (this.level === 0 && this.data instanceof Array) { |
||||
children = this.data; |
||||
} else { |
||||
children = getPropertyFromData(this, 'children') || []; |
||||
} |
||||
|
||||
for (let i = 0, j = children.length; i < j; i++) { |
||||
this.insertChild({ data: children[i] }); |
||||
} |
||||
} |
||||
|
||||
get label() { |
||||
return getPropertyFromData(this, 'label'); |
||||
} |
||||
|
||||
get key() { |
||||
const nodeKey = this.store.key; |
||||
if (this.data) return this.data[nodeKey]; |
||||
return null; |
||||
} |
||||
|
||||
get disabled() { |
||||
return getPropertyFromData(this, 'disabled'); |
||||
} |
||||
|
||||
get nextSibling() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
const index = parent.childNodes.indexOf(this); |
||||
if (index > -1) { |
||||
return parent.childNodes[index + 1]; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
get previousSibling() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
const index = parent.childNodes.indexOf(this); |
||||
if (index > -1) { |
||||
return index > 0 ? parent.childNodes[index - 1] : null; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
contains(target, deep = true) { |
||||
const walk = function(parent) { |
||||
const children = parent.childNodes || []; |
||||
let result = false; |
||||
for (let i = 0, j = children.length; i < j; i++) { |
||||
const child = children[i]; |
||||
if (child === target || (deep && walk(child))) { |
||||
result = true; |
||||
break; |
||||
} |
||||
} |
||||
return result; |
||||
}; |
||||
|
||||
return walk(this); |
||||
} |
||||
|
||||
remove() { |
||||
const parent = this.parent; |
||||
if (parent) { |
||||
parent.removeChild(this); |
||||
} |
||||
} |
||||
|
||||
insertChild(child, index, batch) { |
||||
if (!child) throw new Error('insertChild error: child is required.'); |
||||
|
||||
if (!(child instanceof Node)) { |
||||
if (!batch) { |
||||
const children = this.getChildren(true); |
||||
if (children.indexOf(child.data) === -1) { |
||||
if (typeof index === 'undefined' || index < 0) { |
||||
children.push(child.data); |
||||
} else { |
||||
children.splice(index, 0, child.data); |
||||
} |
||||
} |
||||
} |
||||
objectAssign(child, { |
||||
parent: this, |
||||
store: this.store |
||||
}); |
||||
child = new Node(child); |
||||
} |
||||
|
||||
child.level = this.level + 1; |
||||
|
||||
if (typeof index === 'undefined' || index < 0) { |
||||
this.childNodes.push(child); |
||||
} else { |
||||
this.childNodes.splice(index, 0, child); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
insertBefore(child, ref) { |
||||
let index; |
||||
if (ref) { |
||||
index = this.childNodes.indexOf(ref); |
||||
} |
||||
this.insertChild(child, index); |
||||
} |
||||
|
||||
insertAfter(child, ref) { |
||||
let index; |
||||
if (ref) { |
||||
index = this.childNodes.indexOf(ref); |
||||
if (index !== -1) index += 1; |
||||
} |
||||
this.insertChild(child, index); |
||||
} |
||||
|
||||
removeChild(child) { |
||||
const children = this.getChildren() || []; |
||||
const dataIndex = children.indexOf(child.data); |
||||
if (dataIndex > -1) { |
||||
children.splice(dataIndex, 1); |
||||
} |
||||
|
||||
const index = this.childNodes.indexOf(child); |
||||
|
||||
if (index > -1) { |
||||
this.store && this.store.deregisterNode(child); |
||||
child.parent = null; |
||||
this.childNodes.splice(index, 1); |
||||
} |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
removeChildByData(data) { |
||||
let targetNode = null; |
||||
|
||||
for (let i = 0; i < this.childNodes.length; i++) { |
||||
if (this.childNodes[i].data === data) { |
||||
targetNode = this.childNodes[i]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (targetNode) { |
||||
this.removeChild(targetNode); |
||||
} |
||||
} |
||||
|
||||
expand(callback, expandParent) { |
||||
const done = () => { |
||||
if (expandParent) { |
||||
let parent = this.parent; |
||||
while (parent.level > 0) { |
||||
parent.expanded = true; |
||||
parent = parent.parent; |
||||
} |
||||
} |
||||
this.expanded = true; |
||||
if (callback) callback(); |
||||
}; |
||||
|
||||
if (this.shouldLoadData()) { |
||||
this.loadData((data) => { |
||||
if (data instanceof Array) { |
||||
if (this.checked) { |
||||
this.setChecked(true, true); |
||||
} else if (!this.store.checkStrictly) { |
||||
reInitChecked(this); |
||||
} |
||||
done(); |
||||
} |
||||
}); |
||||
} else { |
||||
done(); |
||||
} |
||||
} |
||||
|
||||
doCreateChildren(array, defaultProps = {}) { |
||||
array.forEach((item) => { |
||||
this.insertChild(objectAssign({ data: item }, defaultProps), undefined, true); |
||||
}); |
||||
} |
||||
|
||||
collapse() { |
||||
this.expanded = false; |
||||
} |
||||
|
||||
shouldLoadData() { |
||||
return this.store.lazy === true && this.store.load && !this.loaded; |
||||
} |
||||
|
||||
updateLeafState() { |
||||
if (this.store.lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') { |
||||
this.isLeaf = this.isLeafByUser; |
||||
return; |
||||
} |
||||
const childNodes = this.childNodes; |
||||
if (!this.store.lazy || (this.store.lazy === true && this.loaded === true)) { |
||||
// this.isLeaf = !childNodes || childNodes.length === 0;
|
||||
this.isLeaf = this.isLeafByUser; |
||||
return; |
||||
} |
||||
this.isLeaf = false; |
||||
} |
||||
|
||||
setChecked(value, deep, recursion, passValue) { |
||||
this.indeterminate = value === 'half'; |
||||
this.checked = value === true; |
||||
|
||||
if (this.store.checkStrictly) return; |
||||
|
||||
if (!(this.shouldLoadData() && !this.store.checkDescendants)) { |
||||
let { all, allWithoutDisable } = getChildState(this.childNodes); |
||||
|
||||
if (!this.isLeaf && (!all && allWithoutDisable)) { |
||||
this.checked = false; |
||||
value = false; |
||||
} |
||||
|
||||
const handleDescendants = () => { |
||||
if (deep) { |
||||
const childNodes = this.childNodes; |
||||
for (let i = 0, j = childNodes.length; i < j; i++) { |
||||
const child = childNodes[i]; |
||||
passValue = passValue || value !== false; |
||||
const isCheck = child.disabled ? child.checked : passValue; |
||||
child.setChecked(isCheck, deep, true, passValue); |
||||
} |
||||
const { half, all } = getChildState(childNodes); |
||||
if (!all) { |
||||
this.checked = all; |
||||
this.indeterminate = half; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
if (this.shouldLoadData()) { |
||||
// Only work on lazy load data.
|
||||
this.loadData(() => { |
||||
handleDescendants(); |
||||
reInitChecked(this); |
||||
}, { |
||||
checked: value !== false |
||||
}); |
||||
return; |
||||
} else { |
||||
handleDescendants(); |
||||
} |
||||
} |
||||
|
||||
const parent = this.parent; |
||||
if (!parent || parent.level === 0) return; |
||||
|
||||
if (!recursion) { |
||||
reInitChecked(parent); |
||||
} |
||||
} |
||||
|
||||
getChildren(forceInit = false) { // this is data
|
||||
if (this.level === 0) return this.data; |
||||
const data = this.data; |
||||
if (!data) return null; |
||||
|
||||
const props = this.store.props; |
||||
let children = 'children'; |
||||
if (props) { |
||||
children = props.children || 'children'; |
||||
} |
||||
|
||||
if (data[children] === undefined) { |
||||
data[children] = null; |
||||
} |
||||
|
||||
if (forceInit && !data[children]) { |
||||
data[children] = []; |
||||
} |
||||
|
||||
return data[children]; |
||||
} |
||||
|
||||
updateChildren() { |
||||
const newData = this.getChildren() || []; |
||||
const oldData = this.childNodes.map((node) => node.data); |
||||
|
||||
const newDataMap = {}; |
||||
const newNodes = []; |
||||
|
||||
newData.forEach((item, index) => { |
||||
const key = item[NODE_KEY]; |
||||
const isNodeExists = !!key && arrayFindIndex(oldData, data => data[NODE_KEY] === key) >= 0; |
||||
if (isNodeExists) { |
||||
newDataMap[key] = { index, data: item }; |
||||
} else { |
||||
newNodes.push({ index, data: item }); |
||||
} |
||||
}); |
||||
|
||||
if (!this.store.lazy) { |
||||
oldData.forEach((item) => { |
||||
if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item); |
||||
}); |
||||
} |
||||
|
||||
newNodes.forEach(({ index, data }) => { |
||||
this.insertChild({ data }, index); |
||||
}); |
||||
|
||||
this.updateLeafState(); |
||||
} |
||||
|
||||
loadData(callback, defaultProps = {}) { |
||||
if (this.store.lazy === true && this.store.load && !this.loaded && (!this.loading || Object.keys(defaultProps).length)) { |
||||
this.loading = true; |
||||
|
||||
const resolve = (children) => { |
||||
this.loaded = true; |
||||
this.loading = false; |
||||
this.childNodes = []; |
||||
|
||||
this.doCreateChildren(children, defaultProps); |
||||
|
||||
this.updateLeafState(); |
||||
if (callback) { |
||||
callback.call(this, children); |
||||
} |
||||
}; |
||||
|
||||
this.store.load(this, resolve); |
||||
} else { |
||||
if (callback) { |
||||
callback.call(this); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,340 @@ |
||||
import Node from './node'; |
||||
import { getNodeKey } from './util'; |
||||
|
||||
export default class TreeStore { |
||||
constructor(options) { |
||||
this.currentNode = null; |
||||
this.currentNodeKey = null; |
||||
|
||||
for (let option in options) { |
||||
if (options.hasOwnProperty(option)) { |
||||
this[option] = options[option]; |
||||
} |
||||
} |
||||
|
||||
this.nodesMap = {}; |
||||
|
||||
this.root = new Node({ |
||||
data: this.data, |
||||
store: this |
||||
}); |
||||
|
||||
if (this.lazy && this.load) { |
||||
const loadFn = this.load; |
||||
loadFn(this.root, (data) => { |
||||
this.root.doCreateChildren(data); |
||||
this._initDefaultCheckedNodes(); |
||||
}); |
||||
} else { |
||||
this._initDefaultCheckedNodes(); |
||||
} |
||||
} |
||||
|
||||
filter(value) { |
||||
const filterNodeMethod = this.filterNodeMethod; |
||||
const lazy = this.lazy; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
child.visible = filterNodeMethod.call(child, value, child.data, child); |
||||
|
||||
traverse(child); |
||||
}); |
||||
|
||||
if (!node.visible && childNodes.length) { |
||||
let allHidden = true; |
||||
allHidden = !childNodes.some(child => child.visible); |
||||
|
||||
if (node.root) { |
||||
node.root.visible = allHidden === false; |
||||
} else { |
||||
node.visible = allHidden === false; |
||||
} |
||||
} |
||||
if (!value) return; |
||||
|
||||
if (node.visible && !node.isLeaf && !lazy) node.expand(); |
||||
}; |
||||
|
||||
traverse(this); |
||||
} |
||||
|
||||
setData(newVal) { |
||||
const instanceChanged = newVal !== this.root.data; |
||||
if (instanceChanged) { |
||||
this.root.setData(newVal); |
||||
this._initDefaultCheckedNodes(); |
||||
} else { |
||||
this.root.updateChildren(); |
||||
} |
||||
} |
||||
|
||||
getNode(data) { |
||||
if (data instanceof Node) return data; |
||||
const key = typeof data !== 'object' ? data : getNodeKey(this.key, data); |
||||
return this.nodesMap[key] || null; |
||||
} |
||||
|
||||
insertBefore(data, refData) { |
||||
const refNode = this.getNode(refData); |
||||
refNode.parent.insertBefore({ data }, refNode); |
||||
} |
||||
|
||||
insertAfter(data, refData) { |
||||
const refNode = this.getNode(refData); |
||||
refNode.parent.insertAfter({ data }, refNode); |
||||
} |
||||
|
||||
remove(data) { |
||||
const node = this.getNode(data); |
||||
|
||||
if (node && node.parent) { |
||||
if (node === this.currentNode) { |
||||
this.currentNode = null; |
||||
} |
||||
node.parent.removeChild(node); |
||||
} |
||||
} |
||||
|
||||
append(data, parentData) { |
||||
const parentNode = parentData ? this.getNode(parentData) : this.root; |
||||
|
||||
if (parentNode) { |
||||
parentNode.insertChild({ data }); |
||||
} |
||||
} |
||||
|
||||
_initDefaultCheckedNodes() { |
||||
const defaultCheckedKeys = this.defaultCheckedKeys || []; |
||||
const nodesMap = this.nodesMap; |
||||
|
||||
defaultCheckedKeys.forEach((checkedKey) => { |
||||
const node = nodesMap[checkedKey]; |
||||
|
||||
if (node) { |
||||
node.setChecked(true, !this.checkStrictly); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
_initDefaultCheckedNode(node) { |
||||
const defaultCheckedKeys = this.defaultCheckedKeys || []; |
||||
|
||||
if (defaultCheckedKeys.indexOf(node.key) !== -1) { |
||||
node.setChecked(true, !this.checkStrictly); |
||||
} |
||||
} |
||||
|
||||
setDefaultCheckedKey(newVal) { |
||||
if (newVal !== this.defaultCheckedKeys) { |
||||
this.defaultCheckedKeys = newVal; |
||||
this._initDefaultCheckedNodes(); |
||||
} |
||||
} |
||||
|
||||
registerNode(node) { |
||||
const key = this.key; |
||||
if (!key || !node || !node.data) return; |
||||
|
||||
const nodeKey = node.key; |
||||
if (nodeKey !== undefined) this.nodesMap[node.key] = node; |
||||
} |
||||
|
||||
deregisterNode(node) { |
||||
const key = this.key; |
||||
if (!key || !node || !node.data) return; |
||||
|
||||
node.childNodes.forEach(child => { |
||||
this.deregisterNode(child); |
||||
}); |
||||
|
||||
delete this.nodesMap[node.key]; |
||||
} |
||||
|
||||
getCheckedNodes(leafOnly = false, includeHalfChecked = false) { |
||||
const checkedNodes = []; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
if ((child.checked || (includeHalfChecked && child.indeterminate)) && (!leafOnly || (leafOnly && child.isLeaf))) { |
||||
checkedNodes.push(child.data); |
||||
} |
||||
|
||||
traverse(child); |
||||
}); |
||||
}; |
||||
|
||||
traverse(this); |
||||
|
||||
return checkedNodes; |
||||
} |
||||
|
||||
getCheckedKeys(leafOnly = false) { |
||||
return this.getCheckedNodes(leafOnly).map((data) => (data || {})[this.key]); |
||||
} |
||||
|
||||
getHalfCheckedNodes() { |
||||
const nodes = []; |
||||
const traverse = function(node) { |
||||
const childNodes = node.root ? node.root.childNodes : node.childNodes; |
||||
|
||||
childNodes.forEach((child) => { |
||||
if (child.indeterminate) { |
||||
nodes.push(child.data); |
||||
} |
||||
|
||||
traverse(child); |
||||
}); |
||||
}; |
||||
|
||||
traverse(this); |
||||
|
||||
return nodes; |
||||
} |
||||
|
||||
getHalfCheckedKeys() { |
||||
return this.getHalfCheckedNodes().map((data) => (data || {})[this.key]); |
||||
} |
||||
|
||||
_getAllNodes() { |
||||
const allNodes = []; |
||||
const nodesMap = this.nodesMap; |
||||
for (let nodeKey in nodesMap) { |
||||
if (nodesMap.hasOwnProperty(nodeKey)) { |
||||
allNodes.push(nodesMap[nodeKey]); |
||||
} |
||||
} |
||||
|
||||
return allNodes; |
||||
} |
||||
|
||||
updateChildren(key, data) { |
||||
const node = this.nodesMap[key]; |
||||
if (!node) return; |
||||
const childNodes = node.childNodes; |
||||
for (let i = childNodes.length - 1; i >= 0; i--) { |
||||
const child = childNodes[i]; |
||||
this.remove(child.data); |
||||
} |
||||
for (let i = 0, j = data.length; i < j; i++) { |
||||
const child = data[i]; |
||||
this.append(child, node.data); |
||||
} |
||||
} |
||||
|
||||
_setCheckedKeys(key, leafOnly = false, checkedKeys) { |
||||
const allNodes = this._getAllNodes().sort((a, b) => b.level - a.level); |
||||
const cache = Object.create(null); |
||||
const keys = Object.keys(checkedKeys); |
||||
allNodes.forEach(node => node.setChecked(false, false)); |
||||
for (let i = 0, j = allNodes.length; i < j; i++) { |
||||
const node = allNodes[i]; |
||||
const nodeKey = node.data[key].toString(); |
||||
let checked = keys.indexOf(nodeKey) > -1; |
||||
if (!checked) { |
||||
if (node.checked && !cache[nodeKey]) { |
||||
node.setChecked(false, false); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
let parent = node.parent; |
||||
while (parent && parent.level > 0) { |
||||
cache[parent.data[key]] = true; |
||||
parent = parent.parent; |
||||
} |
||||
|
||||
if (node.isLeaf || this.checkStrictly) { |
||||
node.setChecked(true, false); |
||||
continue; |
||||
} |
||||
node.setChecked(true, true); |
||||
|
||||
if (leafOnly) { |
||||
node.setChecked(false, false); |
||||
const traverse = function(node) { |
||||
const childNodes = node.childNodes; |
||||
childNodes.forEach((child) => { |
||||
if (!child.isLeaf) { |
||||
child.setChecked(false, false); |
||||
} |
||||
traverse(child); |
||||
}); |
||||
}; |
||||
traverse(node); |
||||
} |
||||
} |
||||
} |
||||
|
||||
setCheckedNodes(array, leafOnly = false) { |
||||
const key = this.key; |
||||
const checkedKeys = {}; |
||||
array.forEach((item) => { |
||||
checkedKeys[(item || {})[key]] = true; |
||||
}); |
||||
|
||||
this._setCheckedKeys(key, leafOnly, checkedKeys); |
||||
} |
||||
|
||||
setCheckedKeys(keys, leafOnly = false) { |
||||
this.defaultCheckedKeys = keys; |
||||
const key = this.key; |
||||
const checkedKeys = {}; |
||||
keys.forEach((key) => { |
||||
checkedKeys[key] = true; |
||||
}); |
||||
|
||||
this._setCheckedKeys(key, leafOnly, checkedKeys); |
||||
} |
||||
|
||||
setDefaultExpandedKeys(keys) { |
||||
keys = keys || []; |
||||
this.defaultExpandedKeys = keys; |
||||
|
||||
keys.forEach((key) => { |
||||
const node = this.getNode(key); |
||||
if (node) node.expand(null, this.autoExpandParent); |
||||
}); |
||||
} |
||||
|
||||
setChecked(data, checked, deep) { |
||||
const node = this.getNode(data); |
||||
|
||||
if (node) { |
||||
node.setChecked(!!checked, deep); |
||||
} |
||||
} |
||||
|
||||
getCurrentNode() { |
||||
return this.currentNode; |
||||
} |
||||
|
||||
setCurrentNode(currentNode) { |
||||
const prevCurrentNode = this.currentNode; |
||||
if (prevCurrentNode) { |
||||
prevCurrentNode.isCurrent = false; |
||||
} |
||||
this.currentNode = currentNode; |
||||
this.currentNode.isCurrent = true; |
||||
} |
||||
|
||||
setUserCurrentNode(node) { |
||||
const key = node[this.key]; |
||||
const currNode = this.nodesMap[key]; |
||||
this.setCurrentNode(currNode); |
||||
} |
||||
|
||||
setCurrentNodeKey(key) { |
||||
if (key === null || key === undefined) { |
||||
this.currentNode && (this.currentNode.isCurrent = false); |
||||
this.currentNode = null; |
||||
return; |
||||
} |
||||
const node = this.getNode(key); |
||||
if (node) { |
||||
this.setCurrentNode(node); |
||||
} |
||||
} |
||||
}; |
@ -0,0 +1,27 @@ |
||||
export const NODE_KEY = '$treeNodeId'; |
||||
|
||||
export const markNodeData = function(node, data) { |
||||
if (!data || data[NODE_KEY]) return; |
||||
Object.defineProperty(data, NODE_KEY, { |
||||
value: node.id, |
||||
enumerable: false, |
||||
configurable: false, |
||||
writable: false |
||||
}); |
||||
}; |
||||
|
||||
export const getNodeKey = function(key, data) { |
||||
if (!key) return data[NODE_KEY]; |
||||
return data[key]; |
||||
}; |
||||
|
||||
export const findNearestComponent = (element, componentName) => { |
||||
let target = element; |
||||
while (target && target.tagName !== 'BODY') { |
||||
if (target.__vue__ && target.__vue__.$options.name === componentName) { |
||||
return target.__vue__; |
||||
} |
||||
target = target.parentNode; |
||||
} |
||||
return null; |
||||
}; |
@ -0,0 +1,279 @@ |
||||
<template> |
||||
<div |
||||
class="el-tree-node" |
||||
@click.stop="handleClick" |
||||
@contextmenu="($event) => this.handleContextMenu($event)" |
||||
v-show="node.visible" |
||||
:class="{ |
||||
'is-expanded': expanded, |
||||
'is-current': node.isCurrent, |
||||
'is-hidden': !node.visible, |
||||
'is-focusable': !node.disabled, |
||||
'is-checked': !node.disabled && node.checked |
||||
}" |
||||
role="treeitem" |
||||
tabindex="-1" |
||||
:aria-expanded="expanded" |
||||
:aria-disabled="node.disabled" |
||||
:aria-checked="node.checked" |
||||
:draggable="tree.draggable" |
||||
@dragstart.stop="handleDragStart" |
||||
@dragover.stop="handleDragOver" |
||||
@dragend.stop="handleDragEnd" |
||||
@drop.stop="handleDrop" |
||||
ref="node" |
||||
> |
||||
<div class="el-tree-node__content" |
||||
:style="{ 'padding-left': (node.level - 1) * tree.indent + 'px' }"> |
||||
<span |
||||
@click.stop="handleExpandIconClick" |
||||
:class="[ |
||||
{ 'is-leaf': node.isLeaf, expanded: !node.isLeaf && expanded }, |
||||
'el-tree-node__expand-icon', |
||||
tree.iconClass ? tree.iconClass : 'el-icon-caret-right' |
||||
]" |
||||
> |
||||
</span> |
||||
<el-checkbox |
||||
v-if="showCheckbox" |
||||
v-model="node.checked" |
||||
:indeterminate="node.indeterminate" |
||||
:disabled="!!node.disabled" |
||||
@click.native.stop |
||||
@change="handleCheckChange" |
||||
> |
||||
</el-checkbox> |
||||
<span |
||||
v-if="node.loading" |
||||
class="el-tree-node__loading-icon el-icon-loading"> |
||||
</span> |
||||
<node-content :node="node"></node-content> |
||||
</div> |
||||
<el-collapse-transition> |
||||
<div |
||||
class="el-tree-node__children" |
||||
v-if="!renderAfterExpand || childNodeRendered" |
||||
v-show="expanded" |
||||
role="group" |
||||
:aria-expanded="expanded" |
||||
> |
||||
<el-tree-node |
||||
:render-content="renderContent" |
||||
v-for="child in node.childNodes" |
||||
:render-after-expand="renderAfterExpand" |
||||
:show-checkbox="showCheckbox" |
||||
:key="getNodeKey(child)" |
||||
:node="child" |
||||
@node-expand="handleChildNodeExpand"> |
||||
</el-tree-node> |
||||
</div> |
||||
</el-collapse-transition> |
||||
</div> |
||||
</template> |
||||
|
||||
<script type="text/jsx"> |
||||
import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition'; |
||||
import ElCheckbox from 'element-ui/packages/checkbox'; |
||||
import emitter from 'element-ui/src/mixins/emitter'; |
||||
import { getNodeKey } from './model/util'; |
||||
|
||||
export default { |
||||
name: 'ElTreeNode', |
||||
|
||||
componentName: 'ElTreeNode', |
||||
|
||||
mixins: [emitter], |
||||
|
||||
props: { |
||||
node: { |
||||
default() { |
||||
return {}; |
||||
} |
||||
}, |
||||
props: {}, |
||||
renderContent: Function, |
||||
renderAfterExpand: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
showCheckbox: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
}, |
||||
|
||||
components: { |
||||
ElCollapseTransition, |
||||
ElCheckbox, |
||||
NodeContent: { |
||||
props: { |
||||
node: { |
||||
required: true |
||||
} |
||||
}, |
||||
render(h) { |
||||
const parent = this.$parent; |
||||
const tree = parent.tree; |
||||
const node = this.node; |
||||
const { data, store } = node; |
||||
return ( |
||||
parent.renderContent |
||||
? parent.renderContent.call(parent._renderProxy, h, { _self: tree.$vnode.context, node, data, store }) |
||||
: tree.$scopedSlots.default |
||||
? tree.$scopedSlots.default({ node, data }) |
||||
: <span class="el-tree-node__label">{ node.label }</span> |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
tree: null, |
||||
expanded: false, |
||||
childNodeRendered: false, |
||||
oldChecked: null, |
||||
oldIndeterminate: null |
||||
}; |
||||
}, |
||||
|
||||
watch: { |
||||
'node.indeterminate'(val) { |
||||
this.handleSelectChange(this.node.checked, val); |
||||
}, |
||||
|
||||
'node.checked'(val) { |
||||
this.handleSelectChange(val, this.node.indeterminate); |
||||
}, |
||||
|
||||
'node.expanded'(val) { |
||||
this.$nextTick(() => this.expanded = val); |
||||
if (val) { |
||||
this.childNodeRendered = true; |
||||
} |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
getNodeKey(node) { |
||||
return getNodeKey(this.tree.nodeKey, node.data); |
||||
}, |
||||
|
||||
handleSelectChange(checked, indeterminate) { |
||||
if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) { |
||||
this.tree.$emit('check-change', this.node.data, checked, indeterminate); |
||||
} |
||||
this.oldChecked = checked; |
||||
this.indeterminate = indeterminate; |
||||
}, |
||||
|
||||
handleClick() { |
||||
const store = this.tree.store; |
||||
store.setCurrentNode(this.node); |
||||
this.tree.$emit('current-change', store.currentNode ? store.currentNode.data : null, store.currentNode); |
||||
this.tree.currentNode = this; |
||||
if (this.tree.expandOnClickNode) { |
||||
this.handleExpandIconClick(); |
||||
} |
||||
if (this.tree.checkOnClickNode && !this.node.disabled) { |
||||
this.handleCheckChange(null, { |
||||
target: { checked: !this.node.checked } |
||||
}); |
||||
} |
||||
this.tree.$emit('node-click', this.node.data, this.node, this); |
||||
}, |
||||
|
||||
handleContextMenu(event) { |
||||
if (this.tree._events['node-contextmenu'] && this.tree._events['node-contextmenu'].length > 0) { |
||||
event.stopPropagation(); |
||||
event.preventDefault(); |
||||
} |
||||
this.tree.$emit('node-contextmenu', event, this.node.data, this.node, this); |
||||
}, |
||||
|
||||
handleExpandIconClick() { |
||||
if (this.node.isLeaf) return; |
||||
if (this.expanded) { |
||||
this.tree.$emit('node-collapse', this.node.data, this.node, this); |
||||
this.node.collapse(); |
||||
} else { |
||||
this.node.expand(); |
||||
this.$emit('node-expand', this.node.data, this.node, this); |
||||
} |
||||
}, |
||||
|
||||
handleCheckChange(value, ev) { |
||||
this.node.setChecked(ev.target.checked, !this.tree.checkStrictly); |
||||
this.$nextTick(() => { |
||||
const store = this.tree.store; |
||||
this.tree.$emit('check', this.node.data, { |
||||
checkedNodes: store.getCheckedNodes(), |
||||
checkedKeys: store.getCheckedKeys(), |
||||
halfCheckedNodes: store.getHalfCheckedNodes(), |
||||
halfCheckedKeys: store.getHalfCheckedKeys(), |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
handleChildNodeExpand(nodeData, node, instance) { |
||||
this.broadcast('ElTreeNode', 'tree-node-expand', node); |
||||
this.tree.$emit('node-expand', nodeData, node, instance); |
||||
}, |
||||
|
||||
handleDragStart(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-start', event, this); |
||||
}, |
||||
|
||||
handleDragOver(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-over', event, this); |
||||
event.preventDefault(); |
||||
}, |
||||
|
||||
handleDrop(event) { |
||||
event.preventDefault(); |
||||
}, |
||||
|
||||
handleDragEnd(event) { |
||||
if (!this.tree.draggable) return; |
||||
this.tree.$emit('tree-node-drag-end', event, this); |
||||
} |
||||
}, |
||||
|
||||
created() { |
||||
const parent = this.$parent; |
||||
|
||||
if (parent.isTree) { |
||||
this.tree = parent; |
||||
} else { |
||||
this.tree = parent.tree; |
||||
} |
||||
|
||||
const tree = this.tree; |
||||
if (!tree) { |
||||
console.warn('Can not find node\'s tree.'); |
||||
} |
||||
|
||||
const props = tree.props || {}; |
||||
const childrenKey = props['children'] || 'children'; |
||||
|
||||
this.$watch(`node.data.${childrenKey}`, () => { |
||||
this.node.updateChildren(); |
||||
}); |
||||
|
||||
if (this.node.expanded) { |
||||
this.expanded = true; |
||||
this.childNodeRendered = true; |
||||
} |
||||
|
||||
if(this.tree.accordion) { |
||||
this.$on('tree-node-expand', node => { |
||||
if(this.node !== node) { |
||||
this.node.collapse(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,496 @@ |
||||
<template> |
||||
<div |
||||
class="el-tree" |
||||
:class="{ |
||||
'el-tree--highlight-current': highlightCurrent, |
||||
'is-dragging': !!dragState.draggingNode, |
||||
'is-drop-not-allow': !dragState.allowDrop, |
||||
'is-drop-inner': dragState.dropType === 'inner' |
||||
}" |
||||
role="tree" |
||||
> |
||||
<el-tree-node |
||||
v-for="child in root.childNodes" |
||||
:node="child" |
||||
:props="props" |
||||
:render-after-expand="renderAfterExpand" |
||||
:show-checkbox="showCheckbox" |
||||
:key="getNodeKey(child)" |
||||
:render-content="renderContent" |
||||
@node-expand="handleNodeExpand"> |
||||
</el-tree-node> |
||||
<div class="el-tree__empty-block" v-if="isEmpty"> |
||||
<span class="el-tree__empty-text">{{ emptyText }}</span> |
||||
</div> |
||||
<div |
||||
v-show="dragState.showDropIndicator" |
||||
class="el-tree__drop-indicator" |
||||
ref="dropIndicator"> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import TreeStore from './model/tree-store'; |
||||
import { getNodeKey, findNearestComponent } from './model/util'; |
||||
import ElTreeNode from './tree-node.vue'; |
||||
import {t} from 'element-ui/src/locale'; |
||||
import emitter from 'element-ui/src/mixins/emitter'; |
||||
import { addClass, removeClass } from 'element-ui/src/utils/dom'; |
||||
|
||||
export default { |
||||
name: 'ElTree', |
||||
|
||||
mixins: [emitter], |
||||
|
||||
components: { |
||||
ElTreeNode |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
store: null, |
||||
root: null, |
||||
currentNode: null, |
||||
treeItems: null, |
||||
checkboxItems: [], |
||||
dragState: { |
||||
showDropIndicator: false, |
||||
draggingNode: null, |
||||
dropNode: null, |
||||
allowDrop: true |
||||
} |
||||
}; |
||||
}, |
||||
|
||||
props: { |
||||
data: { |
||||
type: Array |
||||
}, |
||||
emptyText: { |
||||
type: String, |
||||
default() { |
||||
return t('el.tree.emptyText'); |
||||
} |
||||
}, |
||||
renderAfterExpand: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
nodeKey: String, |
||||
checkStrictly: Boolean, |
||||
defaultExpandAll: Boolean, |
||||
expandOnClickNode: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
checkOnClickNode: Boolean, |
||||
checkDescendants: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
autoExpandParent: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
defaultCheckedKeys: Array, |
||||
defaultExpandedKeys: Array, |
||||
currentNodeKey: [String, Number], |
||||
renderContent: Function, |
||||
showCheckbox: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
draggable: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
allowDrag: Function, |
||||
allowDrop: Function, |
||||
props: { |
||||
default() { |
||||
return { |
||||
children: 'children', |
||||
label: 'label', |
||||
disabled: 'disabled' |
||||
}; |
||||
} |
||||
}, |
||||
lazy: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
highlightCurrent: Boolean, |
||||
load: Function, |
||||
filterNodeMethod: Function, |
||||
accordion: Boolean, |
||||
indent: { |
||||
type: Number, |
||||
default: 18 |
||||
}, |
||||
iconClass: String |
||||
}, |
||||
|
||||
computed: { |
||||
children: { |
||||
set(value) { |
||||
this.data = value; |
||||
}, |
||||
get() { |
||||
return this.data; |
||||
} |
||||
}, |
||||
|
||||
treeItemArray() { |
||||
return Array.prototype.slice.call(this.treeItems); |
||||
}, |
||||
|
||||
isEmpty() { |
||||
const { childNodes } = this.root; |
||||
return !childNodes || childNodes.length === 0 || childNodes.every(({visible}) => !visible); |
||||
} |
||||
}, |
||||
|
||||
watch: { |
||||
defaultCheckedKeys(newVal) { |
||||
this.store.setDefaultCheckedKey(newVal); |
||||
}, |
||||
|
||||
defaultExpandedKeys(newVal) { |
||||
this.store.defaultExpandedKeys = newVal; |
||||
this.store.setDefaultExpandedKeys(newVal); |
||||
}, |
||||
|
||||
data(newVal) { |
||||
this.store.setData(newVal); |
||||
}, |
||||
|
||||
checkboxItems(val) { |
||||
Array.prototype.forEach.call(val, (checkbox) => { |
||||
checkbox.setAttribute('tabindex', -1); |
||||
}); |
||||
}, |
||||
|
||||
checkStrictly(newVal) { |
||||
this.store.checkStrictly = newVal; |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
filter(value) { |
||||
if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter'); |
||||
this.store.filter(value); |
||||
}, |
||||
|
||||
getNodeKey(node) { |
||||
return getNodeKey(this.nodeKey, node.data); |
||||
}, |
||||
|
||||
getNodePath(data) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getNodePath'); |
||||
const node = this.store.getNode(data); |
||||
if (!node) return []; |
||||
const path = [node.data]; |
||||
let parent = node.parent; |
||||
while (parent && parent !== this.root) { |
||||
path.push(parent.data); |
||||
parent = parent.parent; |
||||
} |
||||
return path.reverse(); |
||||
}, |
||||
|
||||
getCheckedNodes(leafOnly, includeHalfChecked) { |
||||
return this.store.getCheckedNodes(leafOnly, includeHalfChecked); |
||||
}, |
||||
|
||||
getCheckedKeys(leafOnly) { |
||||
return this.store.getCheckedKeys(leafOnly); |
||||
}, |
||||
|
||||
getCurrentNode() { |
||||
const currentNode = this.store.getCurrentNode(); |
||||
return currentNode ? currentNode.data : null; |
||||
}, |
||||
|
||||
getCurrentKey() { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in getCurrentKey'); |
||||
const currentNode = this.getCurrentNode(); |
||||
return currentNode ? currentNode[this.nodeKey] : null; |
||||
}, |
||||
|
||||
setCheckedNodes(nodes, leafOnly) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedNodes'); |
||||
this.store.setCheckedNodes(nodes, leafOnly); |
||||
}, |
||||
|
||||
setCheckedKeys(keys, leafOnly) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys'); |
||||
this.store.setCheckedKeys(keys, leafOnly); |
||||
}, |
||||
|
||||
setChecked(data, checked, deep) { |
||||
this.store.setChecked(data, checked, deep); |
||||
}, |
||||
|
||||
getHalfCheckedNodes() { |
||||
return this.store.getHalfCheckedNodes(); |
||||
}, |
||||
|
||||
getHalfCheckedKeys() { |
||||
return this.store.getHalfCheckedKeys(); |
||||
}, |
||||
|
||||
setCurrentNode(node) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode'); |
||||
this.store.setUserCurrentNode(node); |
||||
}, |
||||
|
||||
setCurrentKey(key) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey'); |
||||
this.store.setCurrentNodeKey(key); |
||||
}, |
||||
|
||||
getNode(data) { |
||||
return this.store.getNode(data); |
||||
}, |
||||
|
||||
remove(data) { |
||||
this.store.remove(data); |
||||
}, |
||||
|
||||
append(data, parentNode) { |
||||
this.store.append(data, parentNode); |
||||
}, |
||||
|
||||
insertBefore(data, refNode) { |
||||
this.store.insertBefore(data, refNode); |
||||
}, |
||||
|
||||
insertAfter(data, refNode) { |
||||
this.store.insertAfter(data, refNode); |
||||
}, |
||||
|
||||
handleNodeExpand(nodeData, node, instance) { |
||||
this.broadcast('ElTreeNode', 'tree-node-expand', node); |
||||
this.$emit('node-expand', nodeData, node, instance); |
||||
}, |
||||
|
||||
updateKeyChildren(key, data) { |
||||
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild'); |
||||
this.store.updateChildren(key, data); |
||||
}, |
||||
|
||||
initTabIndex() { |
||||
this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]'); |
||||
this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]'); |
||||
const checkedItem = this.$el.querySelectorAll('.is-checked[role=treeitem]'); |
||||
if (checkedItem.length) { |
||||
checkedItem[0].setAttribute('tabindex', 0); |
||||
return; |
||||
} |
||||
this.treeItems[0] && this.treeItems[0].setAttribute('tabindex', 0); |
||||
}, |
||||
|
||||
handleKeydown(ev) { |
||||
const currentItem = ev.target; |
||||
if (currentItem.className.indexOf('el-tree-node') === -1) return; |
||||
const keyCode = ev.keyCode; |
||||
this.treeItems = this.$el.querySelectorAll('.is-focusable[role=treeitem]'); |
||||
const currentIndex = this.treeItemArray.indexOf(currentItem); |
||||
let nextIndex; |
||||
if ([38, 40].indexOf(keyCode) > -1) { // up、down |
||||
ev.preventDefault(); |
||||
if (keyCode === 38) { // up |
||||
nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0; |
||||
} else { |
||||
nextIndex = (currentIndex < this.treeItemArray.length - 1) ? currentIndex + 1 : 0; |
||||
} |
||||
this.treeItemArray[nextIndex].focus(); // 选中 |
||||
} |
||||
if ([37, 39].indexOf(keyCode) > -1) { // left、right 展开 |
||||
ev.preventDefault(); |
||||
currentItem.click(); // 选中 |
||||
} |
||||
const hasInput = currentItem.querySelector('[type="checkbox"]'); |
||||
if ([13, 32].indexOf(keyCode) > -1 && hasInput) { // space enter选中checkbox |
||||
ev.preventDefault(); |
||||
hasInput.click(); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
created() { |
||||
this.isTree = true; |
||||
|
||||
this.store = new TreeStore({ |
||||
key: this.nodeKey, |
||||
data: this.data, |
||||
lazy: this.lazy, |
||||
props: this.props, |
||||
load: this.load, |
||||
currentNodeKey: this.currentNodeKey, |
||||
checkStrictly: this.checkStrictly, |
||||
checkDescendants: this.checkDescendants, |
||||
defaultCheckedKeys: this.defaultCheckedKeys, |
||||
defaultExpandedKeys: this.defaultExpandedKeys, |
||||
autoExpandParent: this.autoExpandParent, |
||||
defaultExpandAll: this.defaultExpandAll, |
||||
filterNodeMethod: this.filterNodeMethod |
||||
}); |
||||
|
||||
this.root = this.store.root; |
||||
|
||||
let dragState = this.dragState; |
||||
this.$on('tree-node-drag-start', (event, treeNode) => { |
||||
if (typeof this.allowDrag === 'function' && !this.allowDrag(treeNode.node)) { |
||||
event.preventDefault(); |
||||
return false; |
||||
} |
||||
event.dataTransfer.effectAllowed = 'move'; |
||||
|
||||
// wrap in try catch to address IE's error when first param is 'text/plain' |
||||
try { |
||||
// setData is required for draggable to work in FireFox |
||||
// the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox |
||||
event.dataTransfer.setData('text/plain', ''); |
||||
} catch (e) {} |
||||
dragState.draggingNode = treeNode; |
||||
this.$emit('node-drag-start', treeNode.node, event); |
||||
}); |
||||
|
||||
this.$on('tree-node-drag-over', (event, treeNode) => { |
||||
const dropNode = findNearestComponent(event.target, 'ElTreeNode'); |
||||
const oldDropNode = dragState.dropNode; |
||||
if (oldDropNode && oldDropNode !== dropNode) { |
||||
removeClass(oldDropNode.$el, 'is-drop-inner'); |
||||
} |
||||
const draggingNode = dragState.draggingNode; |
||||
if (!draggingNode || !dropNode) return; |
||||
|
||||
let dropPrev = true; |
||||
let dropInner = true; |
||||
let dropNext = true; |
||||
let userAllowDropInner = true; |
||||
if (typeof this.allowDrop === 'function') { |
||||
dropPrev = this.allowDrop(draggingNode.node, dropNode.node, 'prev'); |
||||
userAllowDropInner = dropInner = this.allowDrop(draggingNode.node, dropNode.node, 'inner'); |
||||
dropNext = this.allowDrop(draggingNode.node, dropNode.node, 'next'); |
||||
} |
||||
event.dataTransfer.dropEffect = dropInner ? 'move' : 'none'; |
||||
if ((dropPrev || dropInner || dropNext) && oldDropNode !== dropNode) { |
||||
if (oldDropNode) { |
||||
this.$emit('node-drag-leave', draggingNode.node, oldDropNode.node, event); |
||||
} |
||||
this.$emit('node-drag-enter', draggingNode.node, dropNode.node, event); |
||||
} |
||||
|
||||
if (dropPrev || dropInner || dropNext) { |
||||
dragState.dropNode = dropNode; |
||||
} |
||||
|
||||
if (dropNode.node.nextSibling === draggingNode.node) { |
||||
dropNext = false; |
||||
} |
||||
if (dropNode.node.previousSibling === draggingNode.node) { |
||||
dropPrev = false; |
||||
} |
||||
if (dropNode.node.contains(draggingNode.node, false)) { |
||||
dropInner = false; |
||||
} |
||||
if (draggingNode.node === dropNode.node || draggingNode.node.contains(dropNode.node)) { |
||||
dropPrev = false; |
||||
dropInner = false; |
||||
dropNext = false; |
||||
} |
||||
|
||||
const targetPosition = dropNode.$el.getBoundingClientRect(); |
||||
const treePosition = this.$el.getBoundingClientRect(); |
||||
|
||||
let dropType; |
||||
const prevPercent = dropPrev ? (dropInner ? 0.25 : (dropNext ? 0.45 : 1)) : -1; |
||||
const nextPercent = dropNext ? (dropInner ? 0.75 : (dropPrev ? 0.55 : 0)) : 1; |
||||
|
||||
let indicatorTop = -9999; |
||||
const distance = event.clientY - targetPosition.top; |
||||
if (distance < targetPosition.height * prevPercent) { |
||||
dropType = 'before'; |
||||
} else if (distance > targetPosition.height * nextPercent) { |
||||
dropType = 'after'; |
||||
} else if (dropInner) { |
||||
dropType = 'inner'; |
||||
} else { |
||||
dropType = 'none'; |
||||
} |
||||
|
||||
const iconPosition = dropNode.$el.querySelector('.el-tree-node__expand-icon').getBoundingClientRect(); |
||||
const dropIndicator = this.$refs.dropIndicator; |
||||
if (dropType === 'before') { |
||||
indicatorTop = iconPosition.top - treePosition.top; |
||||
} else if (dropType === 'after') { |
||||
indicatorTop = iconPosition.bottom - treePosition.top; |
||||
} |
||||
dropIndicator.style.top = indicatorTop + 'px'; |
||||
dropIndicator.style.left = (iconPosition.right - treePosition.left) + 'px'; |
||||
|
||||
if (dropType === 'inner') { |
||||
addClass(dropNode.$el, 'is-drop-inner'); |
||||
} else { |
||||
removeClass(dropNode.$el, 'is-drop-inner'); |
||||
} |
||||
|
||||
dragState.showDropIndicator = dropType === 'before' || dropType === 'after'; |
||||
dragState.allowDrop = dragState.showDropIndicator || userAllowDropInner; |
||||
dragState.dropType = dropType; |
||||
this.$emit('node-drag-over', draggingNode.node, dropNode.node, event); |
||||
}); |
||||
|
||||
this.$on('tree-node-drag-end', (event) => { |
||||
const { draggingNode, dropType, dropNode } = dragState; |
||||
event.preventDefault(); |
||||
event.dataTransfer.dropEffect = 'move'; |
||||
|
||||
if (draggingNode && dropNode) { |
||||
const draggingNodeCopy = { data: draggingNode.node.data }; |
||||
if (dropType !== 'none') { |
||||
draggingNode.node.remove(); |
||||
} |
||||
if (dropType === 'before') { |
||||
dropNode.node.parent.insertBefore(draggingNodeCopy, dropNode.node); |
||||
} else if (dropType === 'after') { |
||||
dropNode.node.parent.insertAfter(draggingNodeCopy, dropNode.node); |
||||
} else if (dropType === 'inner') { |
||||
dropNode.node.insertChild(draggingNodeCopy); |
||||
} |
||||
if (dropType !== 'none') { |
||||
this.store.registerNode(draggingNodeCopy); |
||||
} |
||||
|
||||
removeClass(dropNode.$el, 'is-drop-inner'); |
||||
|
||||
this.$emit('node-drag-end', draggingNode.node, dropNode.node, dropType, event); |
||||
if (dropType !== 'none') { |
||||
this.$emit('node-drop', draggingNode.node, dropNode.node, dropType, event); |
||||
} |
||||
} |
||||
if (draggingNode && !dropNode) { |
||||
this.$emit('node-drag-end', draggingNode.node, null, dropType, event); |
||||
} |
||||
|
||||
dragState.showDropIndicator = false; |
||||
dragState.draggingNode = null; |
||||
dragState.dropNode = null; |
||||
dragState.allowDrop = true; |
||||
}); |
||||
}, |
||||
|
||||
mounted() { |
||||
this.initTabIndex(); |
||||
this.$el.addEventListener('keydown', this.handleKeydown); |
||||
}, |
||||
|
||||
updated() { |
||||
this.treeItems = this.$el.querySelectorAll('[role=treeitem]'); |
||||
this.checkboxItems = this.$el.querySelectorAll('input[type=checkbox]'); |
||||
} |
||||
}; |
||||
</script> |
@ -0,0 +1,30 @@ |
||||
export const messages = { |
||||
"zh": { |
||||
i18n: { |
||||
breadcrumb: "国际化产品", |
||||
tips: "通过切换语言按钮,来改变当前内容的语言。", |
||||
btn: "切换英文", |
||||
title1: "常用用法", |
||||
p1: "要是你把你的秘密告诉了风,那就别怪风把它带给树。", |
||||
p2: "没有什么比信念更能支撑我们度过艰难的时光了。", |
||||
p3: "只要能把自己的事做好,并让自己快乐,你就领先于大多数人了。", |
||||
title2: "组件插值", |
||||
info: "Element组件需要国际化,请参考 {action}。", |
||||
value: "文档" |
||||
} |
||||
}, |
||||
"en": { |
||||
i18n: { |
||||
breadcrumb: "International Products", |
||||
tips: "Click on the button to change the current language. ", |
||||
btn: "Switch Chinese", |
||||
title1: "Common usage", |
||||
p1: "If you reveal your secrets to the wind you should not blame the wind for revealing them to the trees.", |
||||
p2: "Nothing can help us endure dark times better than our faith. ", |
||||
p3: "If you can do what you do best and be happy, you're further along in life than most people.", |
||||
title2: "Component interpolation", |
||||
info: "The default language of Element is Chinese. If you wish to use another language, please refer to the {action}.", |
||||
value: "documentation" |
||||
} |
||||
} |
||||
}; |
@ -0,0 +1,42 @@ |
||||
<template> |
||||
<div> |
||||
<div class="copyright"> |
||||
<a href="https://beian.miit.gov.cn/#/Integrated/index" target="_blank">粤ICP备20072679号</a> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
data() { |
||||
return {}; |
||||
}, |
||||
mounted() { |
||||
|
||||
}, |
||||
methods: {} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.copyright { |
||||
padding: 20px 0; |
||||
color: rgba(0, 0, 0, 0.45); |
||||
font-size: 12px; |
||||
text-align: center; |
||||
background-color: #333; |
||||
|
||||
p { |
||||
margin-bottom: 10px; |
||||
color: #fff; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
a { |
||||
color: #fff; |
||||
font-size: 12px; |
||||
|
||||
&:hover { |
||||
opacity: .8; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,178 @@ |
||||
<template> |
||||
<div class="header"> |
||||
<div style="line-height: 60px"> |
||||
<img class="logo" :src="logoUrl" /> |
||||
<span class="title">{{ title }}</span> |
||||
</div> |
||||
<div class="action"> |
||||
<el-dropdown class="user-wrap" @command="userCommand"> |
||||
<div class="user"> |
||||
<el-avatar :size="40" :src="avatar"></el-avatar> |
||||
<span class="m-l-10">{{ customerName || userName }}</span> |
||||
</div> |
||||
<el-dropdown-menu slot="dropdown"> |
||||
<el-dropdown-item v-if="!customerName" command="person">个人中心</el-dropdown-item> |
||||
<el-dropdown-item v-else command="resetPw">修改密码</el-dropdown-item> |
||||
</el-dropdown-menu> |
||||
</el-dropdown> |
||||
<el-divider direction="vertical"></el-divider> |
||||
<el-button type="text" class="ml20" @click="logout">退出</el-button> |
||||
</div> |
||||
<el-dialog title="修改密码" :visible.sync="passwordVisible" :close-on-click-modal="false" :append-to-body="true" @close="resetPassword" width="30%"> |
||||
<el-form ref="passwordForm" label-width="82px"> |
||||
<el-form-item label="原密码"> |
||||
<el-input type="password" v-model="passwordForm.password" placeholder="请输入原密码"></el-input> |
||||
</el-form-item> |
||||
<el-form-item label="新密码"> |
||||
<el-input type="password" v-model="passwordForm.newPassword" placeholder="请输入新密码"></el-input> |
||||
</el-form-item> |
||||
<el-form-item label="确认新密码"> |
||||
<el-input type="password" v-model="passwordForm.reNewPassword" placeholder="请确认新密码" @keyup.enter.native="submitPassword"></el-input> |
||||
</el-form-item> |
||||
</el-form> |
||||
<span slot="footer" class="dialog-footer"> |
||||
<el-button @click="passwordVisible = false">取 消</el-button> |
||||
<el-button type="primary" @click="submitPassword">确 定</el-button> |
||||
</span> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import { mapState, mapMutations, mapActions } from "vuex"; |
||||
import util from "@/libs/util"; |
||||
|
||||
export default { |
||||
data() { |
||||
return { |
||||
passwordVisible: false, |
||||
passwordForm: { |
||||
password: "", |
||||
newPassword: "", |
||||
reNewPassword: "" |
||||
} |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapState("user", [ |
||||
"avatar", "userName", "title", "logoUrl", "customer", "customerName", 'fromClient' |
||||
]) |
||||
}, |
||||
mounted() { |
||||
this.getSystemDetail(); |
||||
this.getUserDetail(); |
||||
}, |
||||
methods: { |
||||
...mapMutations("user", [ |
||||
'SET_SCHOOLID' |
||||
]), |
||||
...mapActions("user", [ |
||||
"setTitle", "setLogoUrl", "setAvatar", "setUserName", 'logout' |
||||
]), |
||||
getSystemDetail() { // 获取系统logo |
||||
this.$get(this.api.logoDetail).then(res => { |
||||
const { data } = res |
||||
if (data) { |
||||
this.setTitle(data.title) |
||||
this.setLogoUrl(data.logoUrl) |
||||
this.SET_SCHOOLID(data.schoolId) |
||||
} else { |
||||
// 获取schoolId |
||||
this.$post(this.api.getSchoolIdByToken).then(res => { |
||||
this.SET_SCHOOLID(res.schoolId) |
||||
}).catch(res => {}) |
||||
} |
||||
}).catch(res => {}) |
||||
}, |
||||
getUserDetail() { // 获取用户详情 |
||||
this.$get(this.api.queryUserInfoDetails).then(res => { |
||||
let { hrUserInfo } = res.result |
||||
if (hrUserInfo) { |
||||
const { userAvatars, userName } = hrUserInfo |
||||
userAvatars && this.setAvatar(userAvatars) |
||||
this.setUserName(userName) |
||||
} else { |
||||
this.$get(this.api.isClient).then(res => { |
||||
res.customerName && this.setUserName(res.customerName) |
||||
}).catch(res => {}) |
||||
} |
||||
}).catch(res => {}) |
||||
}, |
||||
userCommand(command) { // 切换下拉菜单 |
||||
if (command == "person") { |
||||
this.$router.push("/setting/person"); |
||||
} else { |
||||
this.passwordVisible = true; |
||||
} |
||||
}, |
||||
resetPassword() { // 关闭修改密码 |
||||
this.passwordForm = { |
||||
password: "", |
||||
newPassword: "", |
||||
reNewPassword: "" |
||||
}; |
||||
}, |
||||
submitPassword() { // 提交修改密码 |
||||
if (!this.passwordForm.password) return util.warningMsg("请输入原密码"); |
||||
if (!this.passwordForm.newPassword) return util.warningMsg("请输入新密码"); |
||||
if (!this.passwordForm.reNewPassword) return util.warningMsg("请确认新密码"); |
||||
if (this.passwordForm.newPassword.length < 6 || this.passwordForm.reNewPassword.length < 6) return util.warningMsg("请输入6位数以上的密码"); |
||||
if (this.passwordForm.newPassword !== this.passwordForm.reNewPassword) return util.warningMsg("输入的新密码不一致,请重新确认"); |
||||
if (this.passwordForm.password === this.passwordForm.newPassword) return util.warningMsg("原密码跟新密码不能一致"); |
||||
this.$post(this.api.examinePassword, this.passwordForm).then(res => { |
||||
util.successMsg("修改成功"); |
||||
this.passwordVisible = false; |
||||
}).catch(err => { |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.header { |
||||
position: relative; |
||||
height: 60px; |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
font-size: 16px; |
||||
color: #333; |
||||
background-color: #fff; |
||||
|
||||
.logo { |
||||
height: 50px; |
||||
margin: 0 20px; |
||||
} |
||||
|
||||
.title { |
||||
font-size: 18px; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.action { |
||||
display: flex; |
||||
padding-right: 50px; |
||||
align-items: center; |
||||
|
||||
.user { |
||||
display: inline-flex; |
||||
align-items: center; |
||||
cursor: pointer; |
||||
span { |
||||
font-size: 12px; |
||||
} |
||||
} |
||||
|
||||
.el-button--text { |
||||
margin-left: 20px; |
||||
color: #333; |
||||
} |
||||
|
||||
.el-divider { |
||||
width: 2px; |
||||
height: 15px; |
||||
margin-left: 20px; |
||||
background-color: #333; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,237 @@ |
||||
<template> |
||||
<div class="main"> |
||||
<v-head></v-head> |
||||
<div class="layout"> |
||||
<navbar></navbar> |
||||
<div class="content"> |
||||
<transition name="move" mode="out-in"> |
||||
<router-view class="view"></router-view> |
||||
</transition> |
||||
<el-backtop target=".content"></el-backtop> |
||||
</div> |
||||
<v-footer ref="footer"></v-footer> |
||||
<div class="log-mask" v-if="logVisible"></div> |
||||
<div class="log-dia" v-if="logVisible"> |
||||
<img class="bg1" src="@/assets/img/log-bg.png" alt=""> |
||||
<img class="bg2" src="@/assets/img/log-bg1.png" alt=""> |
||||
<p class="log-title">更新日志</p> |
||||
<div class="log-wrap"> |
||||
<div class="logs"> |
||||
<div class="item" v-for="(item, i) in list" :key="i" v-show="!i || (i && logAll)"> |
||||
<h6>{{ item.versionName }}</h6> |
||||
<img v-if="item.coverUrl" :src="item.coverUrl" alt="" class="cover"> |
||||
<ul class="detail"> |
||||
<li v-for="(item, i) in item.logContents" :key="i"> |
||||
<p class="name"><img :src="require('@/assets/img/' + funcList.find(e => e.id === item.type).icon + '.png')" alt=""> {{ funcList.find(e => e.id === item.type).name }}</p> |
||||
<div class="val"> |
||||
<p v-for="(item, i) in item.content" :key="i">{{ i + 1 }}、{{ item }}</p> |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<div class="more-wrap"> |
||||
<el-button class="know" type="primary" size="small" @click="logVisible = false">知道了</el-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import vHead from "../header"; |
||||
import navbar from "../navbar"; |
||||
import vFooter from "../footer"; |
||||
import { mapState, mapMutations, mapActions } from "vuex"; |
||||
import util from "@/libs/util"; |
||||
import Setting from "@/setting"; |
||||
|
||||
export default { |
||||
data() { |
||||
return { |
||||
logVisible: false, |
||||
list: [], |
||||
logAll: false, |
||||
funcList: [ |
||||
{ |
||||
id: 0, |
||||
name: '新功能', |
||||
icon: 'func' |
||||
}, |
||||
{ |
||||
id: 1, |
||||
name: '修复', |
||||
icon: 'bug' |
||||
}, |
||||
{ |
||||
id: 2, |
||||
name: '优化', |
||||
icon: 'optimize' |
||||
} |
||||
] |
||||
}; |
||||
}, |
||||
components: { |
||||
vHead, |
||||
navbar, |
||||
vFooter |
||||
}, |
||||
computed: { |
||||
...mapState("user", [ |
||||
'logView' |
||||
]) |
||||
}, |
||||
mounted() { |
||||
this.autoLogout(); |
||||
this.logView || this.getLogStatus() // logView为false才需要查询接口 |
||||
}, |
||||
methods: { |
||||
...mapMutations("user", [ |
||||
'SET_LOG' |
||||
]), |
||||
...mapActions("user", [ |
||||
"logout" |
||||
]), |
||||
// 获取日志列表 |
||||
getLog() { |
||||
this.$get(`${this.api.platformLogList}?platformId=${Setting.platformId}&port=1&versionName=`).then(({ logList }) => { |
||||
logList.map((e, i) => { |
||||
e.logContents.map(n => { |
||||
n.content = n.content.split('\n') |
||||
}) |
||||
}) |
||||
this.list = logList |
||||
}).catch(res => {}) |
||||
}, |
||||
// 获取日志状态 |
||||
getLogStatus() { |
||||
util.local.get(Setting.tokenKey) && this.$get(this.api.logNotification, { |
||||
platformId: Setting.platformId |
||||
}).then(({ notification }) => { |
||||
this.SET_LOG() // 把查看日志状态设置为true |
||||
if (notification) { |
||||
this.logVisible = true // 返回true,则显示日志弹框 |
||||
this.getLog() |
||||
} |
||||
}).catch(res => {}) |
||||
}, |
||||
// 长时间未操作,自动退出登录 |
||||
autoLogout() { |
||||
let lastTime = new Date().getTime(); |
||||
document.onmousedown = () => { |
||||
lastTime = new Date().getTime(); |
||||
}; |
||||
|
||||
setInterval(() => { |
||||
if (util.local.get(Setting.tokenKey) && (new Date().getTime() - lastTime) > Setting.autoLogoutTime) { |
||||
util.errorMsg("用户登录过期,请重新登录"); |
||||
setTimeout(this.logout, 1500); |
||||
} |
||||
}, 1000); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.main { |
||||
min-height: 100%; |
||||
|
||||
.content { |
||||
min-height: calc(100vh - 176px); |
||||
padding: 24px 24px; |
||||
} |
||||
}.log-mask { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
bottom: 0; |
||||
right: 0; |
||||
background-color: rgba(0, 0, 0, .4); |
||||
} |
||||
/deep/.log-dia { |
||||
z-index: 11; |
||||
position: absolute; |
||||
top: 250px; |
||||
left: 50%; |
||||
width: 550px; |
||||
transform: translateX(-50%); |
||||
background-color: #fff; |
||||
border-radius: 6px; |
||||
.bg1 { |
||||
width: 100%; |
||||
height: 140px; |
||||
} |
||||
.bg2 { |
||||
position: absolute; |
||||
top: -130px; |
||||
left: 48px; |
||||
max-width: 100%; |
||||
height: 250px; |
||||
} |
||||
.log-title { |
||||
position: absolute; |
||||
top: 65px; |
||||
left: 0; |
||||
width: 100%; |
||||
text-align: center; |
||||
font-size: 22px; |
||||
color: #fff; |
||||
font-weight: 600; |
||||
} |
||||
.log-wrap { |
||||
padding-bottom: 30px; |
||||
} |
||||
.logs { |
||||
max-height: 250px; |
||||
padding: 0 100px; |
||||
margin: 30px 0; |
||||
overflow: auto; |
||||
} |
||||
h6 { |
||||
margin-bottom: 15px; |
||||
font-size: 16px; |
||||
color: #333; |
||||
line-height: 1; |
||||
} |
||||
.more-wrap { |
||||
padding: 0 60px; |
||||
font-size: 14px; |
||||
color: #333; |
||||
text-align: center; |
||||
} |
||||
.know { |
||||
margin-top: 15px; |
||||
} |
||||
.cover { |
||||
max-width: 250px; |
||||
max-height: 160px; |
||||
margin: 10px 0 20px; |
||||
} |
||||
.detail { |
||||
li { |
||||
margin-bottom: 20px; |
||||
} |
||||
.name { |
||||
display: flex; |
||||
align-items: center; |
||||
margin-bottom: 5px; |
||||
font-size: 14px; |
||||
img { |
||||
width: 20px; |
||||
margin-right: 8px; |
||||
} |
||||
} |
||||
.val { |
||||
font-size: 15px; |
||||
line-height: 1.8; |
||||
white-space: pre-wrap; |
||||
p { |
||||
position: relative; |
||||
font-size: 13px; |
||||
color: #727272; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,131 @@ |
||||
<template> |
||||
<div> |
||||
<el-menu class="sidebar-el-menu" :default-active="active" background-color="#324157" text-color="#bfcbd9" active-text-color="#9278FF" unique-opened mode="horizontal" router> |
||||
<template v-for="item in menus"> |
||||
<template v-if="item.subs"> |
||||
<el-submenu :index="item.index" :key="item.index"> |
||||
<template slot="title"> |
||||
<i :class="item.icon"></i> |
||||
<span slot="title">{{ item.title }}</span> |
||||
</template> |
||||
<template v-for="subItem in item.subs"> |
||||
<el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index"> |
||||
<template slot="title">{{ subItem.title }}</template> |
||||
<el-menu-item v-for="(threeItem,i) in subItem.subs" :key="i" :index="threeItem.index">{{ threeItem.title }}</el-menu-item> |
||||
</el-submenu> |
||||
<el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }}</el-menu-item> |
||||
</template> |
||||
</el-submenu> |
||||
</template> |
||||
<template v-else> |
||||
<el-menu-item :index="item.index" :key="item.index"> |
||||
<i :class="item.icon"></i> |
||||
<span slot="title">{{ item.title }}</span> |
||||
</el-menu-item> |
||||
</template> |
||||
</template> |
||||
</el-menu> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState, mapActions } from "vuex"; |
||||
import Setting from '@/setting' |
||||
import addRoutes from '@/libs/route/addRoutes' |
||||
export default { |
||||
data() { |
||||
return { |
||||
active: this.$route.path, |
||||
defaultMenus: [ |
||||
{ |
||||
icon: "el-icon-user", |
||||
index: "/student/list", |
||||
title: "学生管理" |
||||
}, |
||||
{ |
||||
icon: "el-icon-takeaway-box", |
||||
index: "/assessment/list", |
||||
title: "考核管理" |
||||
}, |
||||
{ |
||||
icon: "el-icon-setting", |
||||
index: "/system/list", |
||||
title: "系统设置" |
||||
} |
||||
], |
||||
menus: [], |
||||
actives: { |
||||
dashboard: ["add"], |
||||
achievement: ["experiment", "experimentVir", "experimentTeach", "addexperiment", "addexperimentoptions", "showExperiment", "showExperimentoption", "showExperimentoptions"], |
||||
project: ["addproject", "program", "programOption", "programOptions"], |
||||
backstage: ["report"] |
||||
} |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapState('auth', [ |
||||
'routes' |
||||
]) |
||||
}, |
||||
watch: { |
||||
"$route"(to, from) { |
||||
let actives = this.actives; |
||||
for (let i in this.actives) { |
||||
if (actives[i].includes(this.$route.name)) this.active = `/${i}/list`; |
||||
} |
||||
let arr=this.$route.path.split("/"); |
||||
let name = `/${arr[1]}/list` |
||||
this.active = name; |
||||
} |
||||
}, |
||||
created() { |
||||
this.getPer() |
||||
}, |
||||
methods: { |
||||
...mapActions('user', [ |
||||
'logout' |
||||
]), |
||||
// 获取权限列表 |
||||
getPer() { |
||||
this.$get(`${this.api.getUserRolesPermissionMenu}?platformId=${Setting.platformId}`).then(res => { |
||||
const routes = res.permissionMenu[0].children |
||||
addRoutes(routes) |
||||
this.initMenu() |
||||
}).catch(({ status }) => { |
||||
status === 500 && this.logout() |
||||
}) |
||||
}, |
||||
initMenu() { |
||||
// 如果开启了动态路由,则取store里的路由匹配 |
||||
if (Setting.dynamicRoute) { |
||||
const { routes } = this |
||||
const menus = [] |
||||
this.defaultMenus.map(e => { |
||||
routes.find(n => n.path === e.index) && menus.push(e) |
||||
}); |
||||
this.menus = menus |
||||
} else { |
||||
this.menus = this.defaultMenus |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.sidebar::-webkit-scrollbar { |
||||
width: 0; |
||||
} |
||||
|
||||
.sidebar-el-menu:not(.el-menu--collapse) { |
||||
width: 100%; |
||||
} |
||||
|
||||
.el-menu.el-menu--horizontal { |
||||
border-bottom: none; |
||||
} |
||||
|
||||
.sidebar > ul { |
||||
height: 100%; |
||||
} |
||||
</style> |
@ -0,0 +1,20 @@ |
||||
/** |
||||
* @description 生成按钮级别权限组 |
||||
* */ |
||||
|
||||
import store from '@/store' |
||||
|
||||
const result = [] |
||||
// 递归组合权限
|
||||
function createAuth(data, auth) { |
||||
data.map(e => { |
||||
const text = (auth ? auth + ':' : '') + (auth ? e.name : e.path) // 第一级是路由,取path,子级取name
|
||||
result.push(text) |
||||
e.children && e.children.length && createAuth(e.children, text) |
||||
}) |
||||
} |
||||
|
||||
export default function(data) { |
||||
createAuth(data) |
||||
store.dispatch('auth/addBtnAuth', result) |
||||
} |
@ -0,0 +1,6 @@ |
||||
import Vue from "vue"; |
||||
|
||||
// 使用 Event Bus
|
||||
const bus = new Vue(); |
||||
|
||||
export default bus; |
@ -0,0 +1,10 @@ |
||||
// 生成随机字符串
|
||||
export default function(len = 32) { |
||||
const $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; |
||||
const maxPos = $chars.length; |
||||
let str = ""; |
||||
for (let i = 0; i < len; i++) { |
||||
str += $chars.charAt(Math.floor(Math.random() * maxPos)); |
||||
} |
||||
return str; |
||||
} |
@ -0,0 +1,18 @@ |
||||
// rem等比适配配置文件
|
||||
// 基准大小
|
||||
const baseSize = 16; |
||||
|
||||
// 设置 rem 函数
|
||||
function setRem() { |
||||
// 当前页面宽度相对于 1920宽的缩放比例,可根据自己需要修改。
|
||||
const scale = document.documentElement.clientWidth / 1920; |
||||
// 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
|
||||
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + "px"; |
||||
} |
||||
|
||||
// 初始化
|
||||
setRem(); |
||||
// 改变窗口大小时重新设置 rem
|
||||
window.onresize = function() { |
||||
setRem(); |
||||
}; |
@ -0,0 +1,28 @@ |
||||
import store from '@/store' |
||||
import generateBtnPermission from '../auth/generateBtnPermission' |
||||
|
||||
const newRoutes = [] |
||||
|
||||
function createMeta(item) { |
||||
let meta = { title: item.name } |
||||
return meta |
||||
} |
||||
|
||||
function createRoute(data) { |
||||
data.map(e => { |
||||
const { path } = e |
||||
// 有path则添加到路由里
|
||||
path && newRoutes.push({ |
||||
path, |
||||
meta: createMeta(e) |
||||
}) |
||||
// 递归生成路由集合
|
||||
e.children && e.children.length && createRoute(e.children) |
||||
}) |
||||
} |
||||
|
||||
export default function(data, path) { |
||||
generateBtnPermission(data); |
||||
createRoute(data); |
||||
store.dispatch("auth/addRoutes", newRoutes); |
||||
} |
@ -0,0 +1,6 @@ |
||||
import router from "@/router"; |
||||
|
||||
export default function() { |
||||
const newRouter = createRouter(); |
||||
router.matcher = newRouter.matcher; |
||||
} |
@ -0,0 +1,43 @@ |
||||
import Cookies from "js-cookie"; |
||||
import Setting from "@/setting"; |
||||
|
||||
const cookies = {}; |
||||
|
||||
/** |
||||
* @description 存储 cookie 值 |
||||
* @param {String} name cookie name |
||||
* @param {String} value cookie value |
||||
* @param {Object} cookieSetting cookie setting |
||||
*/ |
||||
cookies.set = function(name = "default", value = "", cookieSetting = {}) { |
||||
let currentCookieSetting = { |
||||
expires: Setting.cookiesExpires |
||||
}; |
||||
Object.assign(currentCookieSetting, cookieSetting); |
||||
Cookies.set(`admin-${name}`, value, currentCookieSetting); |
||||
}; |
||||
|
||||
/** |
||||
* @description 拿到 cookie 值 |
||||
* @param {String} name cookie name |
||||
*/ |
||||
cookies.get = function(name = "default") { |
||||
return Cookies.get(`admin-${name}`); |
||||
}; |
||||
|
||||
/** |
||||
* @description 拿到 cookie 全部的值 |
||||
*/ |
||||
cookies.getAll = function() { |
||||
return Cookies.get(); |
||||
}; |
||||
|
||||
/** |
||||
* @description 删除 cookie |
||||
* @param {String} name cookie name |
||||
*/ |
||||
cookies.remove = function(name = "default") { |
||||
return Cookies.remove(`admin-${name}`); |
||||
}; |
||||
|
||||
export default cookies; |
@ -0,0 +1,83 @@ |
||||
/** |
||||
* localStorage |
||||
* @调用:_local.set('access_token', '123456', 5000); |
||||
* @调用:_local.get('access_token'); |
||||
*/ |
||||
|
||||
var _local = { |
||||
//存储,可设置过期时间
|
||||
set(key, value, expires) { |
||||
let params = { key, value, expires }; |
||||
if (expires) { |
||||
// 记录何时将值存入缓存,毫秒级
|
||||
var data = Object.assign(params, { startTime: new Date().getTime() }); |
||||
localStorage.setItem(key, JSON.stringify(data)); |
||||
} else { |
||||
if (Object.prototype.toString.call(value) == "[object Object]") { |
||||
value = JSON.stringify(value); |
||||
} |
||||
if (Object.prototype.toString.call(value) == "[object Array]") { |
||||
value = JSON.stringify(value); |
||||
} |
||||
localStorage.setItem(key, value); |
||||
} |
||||
}, |
||||
|
||||
//取出
|
||||
get(key) { |
||||
let item = localStorage.getItem(key); |
||||
// 先将拿到的试着进行json转为对象的形式
|
||||
try { |
||||
item = JSON.parse(item); |
||||
} catch (error) { |
||||
// eslint-disable-next-line no-self-assign
|
||||
item = item; |
||||
} |
||||
// 如果有startTime的值,说明设置了失效时间
|
||||
if (item && item.startTime) { |
||||
let date = new Date().getTime(); |
||||
// 如果大于就是过期了,如果小于或等于就还没过期
|
||||
if (date - item.startTime > item.expires) { |
||||
localStorage.removeItem(key); |
||||
return false; |
||||
} else { |
||||
return item.value; |
||||
} |
||||
} else { |
||||
return item; |
||||
} |
||||
}, |
||||
// 删除
|
||||
remove(key) { |
||||
localStorage.removeItem(key); |
||||
}, |
||||
// 清除全部
|
||||
clear() { |
||||
localStorage.clear(); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* sessionStorage |
||||
*/ |
||||
var _session = { |
||||
get: function(key) { |
||||
var data = sessionStorage[key]; |
||||
if (!data || data === "null") { |
||||
return null; |
||||
} |
||||
return data; |
||||
}, |
||||
set: function(key, value) { |
||||
sessionStorage[key] = value; |
||||
}, |
||||
// 删除
|
||||
remove(key) { |
||||
sessionStorage.removeItem(key); |
||||
}, |
||||
// 清除全部
|
||||
clear() { |
||||
sessionStorage.clear(); |
||||
} |
||||
}; |
||||
export { _local, _session }; |
@ -0,0 +1,227 @@ |
||||
import cookies from "./util.cookies"; |
||||
import { _local, _session } from "./util.db"; |
||||
import { Message } from "element-ui"; |
||||
import store from "@/store"; |
||||
import axios from "axios"; |
||||
import api from "@/api"; |
||||
import Setting from "@/setting"; |
||||
|
||||
let logout = false; |
||||
// 文件后缀集合
|
||||
const exts = { |
||||
video: 'mp4,3gp,mov,m4v,avi,dat,mkv,flv,vob,rmvb,rm,qlv', |
||||
audio: 'mp3,aac,ape,flac,wav,wma,amr,mid', |
||||
img: 'jpg,jpeg,png,gif,svg,psd', |
||||
doc: 'doc,docx,txt,xls,xlsx,csv,xml,ppt,pptx' |
||||
} |
||||
const util = { |
||||
cookies, |
||||
local: _local, |
||||
session: _session, |
||||
// 传入身份证获取生日
|
||||
getBirth(idCard) { |
||||
var birthday = ""; |
||||
if (idCard != null && idCard != "") { |
||||
if (idCard.length == 15) { |
||||
birthday = "19" + idCard.slice(6, 12); |
||||
} else if (idCard.length == 18) { |
||||
birthday = idCard.slice(6, 14); |
||||
} |
||||
birthday = birthday.replace(/(.{4})(.{2})/, "$1-$2-"); |
||||
//通过正则表达式来指定输出格式为:1990-01-01
|
||||
} |
||||
return birthday; |
||||
}, |
||||
// new Date('2020-11-12 00:00:00') 在IE下失效,因此把-替换成/
|
||||
dateCompatible(date) { |
||||
return date.replace(/\-/g, "/"); |
||||
}, |
||||
// 日期时间前面补零
|
||||
formateTime(num) { |
||||
return num < 10 ? `0${num}` : num; |
||||
}, |
||||
//返回格式化时间,传参例如:"yyyy-MM-dd hh:mm:ss"
|
||||
formatDate(fmt, date) { |
||||
var date = date ? date : new Date(); |
||||
var o = { |
||||
"M+": date.getMonth() + 1, //月份
|
||||
"d+": date.getDate(), //日
|
||||
"h+": date.getHours(), //小时
|
||||
"m+": date.getMinutes(), //分
|
||||
"s+": date.getSeconds(), //秒
|
||||
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
|
||||
"S": date.getMilliseconds() //毫秒
|
||||
}; |
||||
if (/(y+)/.test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); |
||||
} |
||||
for (var k in o) { |
||||
if (new RegExp("(" + k + ")").test(fmt)) { |
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); |
||||
} |
||||
} |
||||
return fmt; |
||||
}, |
||||
// 移除数组中指定值
|
||||
removeByValue(arr, val) { |
||||
for (var i = 0; i < arr.length; i++) { |
||||
if (arr[i] == val) { |
||||
arr.splice(i, 1); |
||||
break; |
||||
} |
||||
} |
||||
}, |
||||
// 传入文件后缀判断是否是视频
|
||||
isVideo(ext) { |
||||
if (exts.video.includes(ext)) return true; |
||||
return false; |
||||
}, |
||||
// 传入文件后缀判断是否是音频
|
||||
isAudio(ext) { |
||||
if (exts.audio.includes(ext)) return true; |
||||
return false; |
||||
}, |
||||
// 传入文件后缀判断是否是图片
|
||||
isImg(ext) { |
||||
if (exts.img.includes(ext)) return true; |
||||
return false; |
||||
}, |
||||
// 传入文件后缀判断是否是pdf以外的文档
|
||||
isDoc(ext) { |
||||
if (exts.doc.includes(ext)) return true; |
||||
return false; |
||||
}, |
||||
// 判断是否能够预览
|
||||
canPreview(ext) { |
||||
if (!util.isVideo(ext) && !util.isAudio(ext) && !util.isImg(ext) && !util.isDoc(ext)) return false |
||||
return true |
||||
}, |
||||
// 循环去除html标签
|
||||
removeHtmlTag(list, attr) { |
||||
list.map(n => { |
||||
n[attr] = n[attr].replace(/<\/?.+?>/gi, ""); |
||||
}); |
||||
return list; |
||||
}, |
||||
// 传入文件名获取文件后缀
|
||||
getFileExt(fileName) { |
||||
return fileName.substring(fileName.lastIndexOf(".") + 1); |
||||
}, |
||||
// 传入文件名和路径,下载图片视频,支持跨域,a标签加download不支持跨域
|
||||
downloadFile(fileName, url) { |
||||
var x = new XMLHttpRequest(); |
||||
x.open("GET", url, true); |
||||
x.responseType = "blob"; |
||||
x.onload = function(e) { |
||||
var url = window.URL.createObjectURL(x.response); |
||||
var a = document.createElement("a"); |
||||
a.href = url; |
||||
a.download = fileName; |
||||
a.click(); |
||||
}; |
||||
x.send(); |
||||
}, |
||||
// 传入文件名和数据,下载文件
|
||||
downloadFileDirect(fileName, data) { |
||||
if ("download" in document.createElement("a")) { // 非IE下载
|
||||
const elink = document.createElement("a"); |
||||
elink.download = fileName; |
||||
elink.style.display = "none"; |
||||
elink.href = URL.createObjectURL(data); |
||||
document.body.appendChild(elink); |
||||
elink.click(); |
||||
URL.revokeObjectURL(elink.href); // 释放URL 对象
|
||||
document.body.removeChild(elink); |
||||
} else { // IE10+下载
|
||||
navigator.msSaveBlob(data, fileName); |
||||
} |
||||
}, |
||||
// 传入字符串,返回去除[]后的字符串,因为通过get请求带入方括号的话会有问题
|
||||
encodeStr(str) { |
||||
if (str.includes("[") || str.includes("]")) { |
||||
let newStr = ""; |
||||
for (let i of str) { |
||||
if (i == "[" || i == "]") { |
||||
newStr += encodeURI(i); |
||||
} else { |
||||
newStr += i; |
||||
} |
||||
} |
||||
return newStr; |
||||
} |
||||
return str; |
||||
}, |
||||
// 成功提示
|
||||
successMsg(message, duration = 3000) { |
||||
Message.closeAll(); |
||||
return Message.success({ message, showClose: true, offset: (document.documentElement.clientHeight - 40) / 2, duration }); |
||||
}, |
||||
// 警告提示
|
||||
warningMsg(message, duration = 3000) { |
||||
Message.closeAll(); |
||||
return Message.warning({ message, showClose: true, offset: (document.documentElement.clientHeight - 40) / 2, duration }); |
||||
}, |
||||
// 错误提示
|
||||
errorMsg(message, duration = 3000) { |
||||
Message.closeAll(); |
||||
return Message.error({ message, showClose: true, offset: (document.documentElement.clientHeight - 40) / 2, duration }); |
||||
}, |
||||
// 登录互踢
|
||||
getToken() { |
||||
if (process.env.NODE_ENV != "production") { |
||||
if (store.state.user.dataTime && !logout) { |
||||
axios.get(`${api.queryToken}?token=${_local.get(Setting.tokenKey)}`).then(res => { |
||||
if (store.state.user.dataTime && (res.data.message != store.state.user.dataTime)) { |
||||
logout || Message.error("您已在另一台设备登录,本次登录已下线!"); |
||||
logout = true; |
||||
setTimeout(() => { |
||||
_local.remove(Setting.storeKey); |
||||
_local.remove(Setting.tokenKey); |
||||
location.reload(); |
||||
}, 1500); |
||||
} |
||||
}).catch(err => { |
||||
}); |
||||
} |
||||
} |
||||
}, |
||||
debounce(fn, delay) { // 防抖
|
||||
let timeout = null; |
||||
return function() { |
||||
const context = this; |
||||
const args = arguments; |
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(() => { |
||||
fn.apply(context, args); |
||||
}, delay); |
||||
}; |
||||
}, |
||||
deepCopy(obj) { // 深拷贝
|
||||
if (obj == null) { |
||||
return null; |
||||
} |
||||
if (typeof obj !== "object") return obj; |
||||
let result; |
||||
if (Array.isArray(obj)) { |
||||
result = []; |
||||
obj.forEach(item => { |
||||
result.push( |
||||
typeof item === "object" && !(item instanceof Date) |
||||
? util.deepCopy(item) |
||||
: item |
||||
); |
||||
}); |
||||
} else { |
||||
result = {}; |
||||
Object.keys(obj).forEach(key => { |
||||
result[key] = |
||||
typeof obj[key] === "object" && !(obj[key] instanceof Date) |
||||
? util.deepCopy(obj[key]) |
||||
: obj[key]; |
||||
}); |
||||
} |
||||
return result; |
||||
} |
||||
}; |
||||
|
||||
export default util; |
@ -0,0 +1,45 @@ |
||||
import Vue from "vue"; |
||||
import App from "@/App.vue"; |
||||
import router from "@/router"; |
||||
import ElementUI from "element-ui"; |
||||
import "@/styles/index.scss"; |
||||
import VueI18n from "vue-i18n"; |
||||
import mixinApp from "@/mixins/app"; |
||||
import { messages } from "@/i18n"; |
||||
import "babel-polyfill"; |
||||
import "@/libs/resize"; |
||||
import { post, get, del, put } from "@/plugins/requests/index.js"; |
||||
import api from "@/api"; |
||||
import store from "@/store"; |
||||
import Setting from "@/setting"; |
||||
import permission from "@/router/permission"; |
||||
|
||||
// 插件
|
||||
import plugins from "@/plugins"; |
||||
import filters from "@/plugins/filters"; |
||||
|
||||
Vue.use(plugins); |
||||
|
||||
Object.keys(filters).forEach(item => Vue.filter(item, filters[item])); |
||||
|
||||
Vue.prototype.api = api; |
||||
Vue.prototype.$get = get; |
||||
Vue.prototype.$post = post; |
||||
Vue.prototype.$del = del; |
||||
Vue.prototype.$put = put; |
||||
|
||||
Vue.config.productionTip = false; |
||||
Vue.use(VueI18n); |
||||
Vue.use(ElementUI, { size: "small" }); |
||||
const i18n = new VueI18n({ |
||||
locale: Setting.i18n.default, |
||||
messages |
||||
}); |
||||
|
||||
new Vue({ |
||||
mixins: [mixinApp], |
||||
router, |
||||
i18n, |
||||
store, |
||||
render: h => h(App) |
||||
}).$mount("#app"); |
@ -0,0 +1,11 @@ |
||||
/** |
||||
* 通用混合 |
||||
* */ |
||||
export default { |
||||
methods: { |
||||
// 当 $route 更新时触发
|
||||
appRouteChange(to, from) { |
||||
|
||||
} |
||||
} |
||||
}; |
@ -0,0 +1,8 @@ |
||||
export default { |
||||
beforeCreate() { |
||||
document.querySelector("body").setAttribute("style", "background-color:#fff"); |
||||
}, |
||||
beforeDestroy() { |
||||
document.body.removeAttribute("style"); |
||||
} |
||||
}; |
@ -0,0 +1,343 @@ |
||||
<template> |
||||
<div class="wrap"> |
||||
<div class="main"> |
||||
<div class="box"> |
||||
<el-tabs v-model="activeName" @tab-click="handleClick"> |
||||
<el-tab-pane label="账号登录" name="1"></el-tab-pane> |
||||
<el-tab-pane label="手机号/邮箱登录" name="2"></el-tab-pane> |
||||
<el-form :model="loginForm" :rules="loginRules" ref="loginForm" style="margin-top: 20px"> |
||||
<el-form-item v-if="activeName === '1'" prop="account"> |
||||
<el-input v-model.trim="loginForm.account" placeholder="请输入账号"></el-input> |
||||
</el-form-item> |
||||
<el-form-item v-if="activeName === '2'" prop="account"> |
||||
<el-input v-model.trim="loginForm.account" placeholder="请输入手机号/邮箱"></el-input> |
||||
</el-form-item> |
||||
<el-form-item prop="password"> |
||||
<el-input |
||||
type="password" |
||||
placeholder="请输入密码" |
||||
v-model.trim="loginForm.password" |
||||
> |
||||
</el-input> |
||||
</el-form-item> |
||||
<el-form-item prop="code"> |
||||
<el-input |
||||
placeholder="请输入验证码" |
||||
v-model.trim="loginForm.code" |
||||
@keyup.enter.native="submitFormLogin" |
||||
> |
||||
</el-input> |
||||
<img @click="getVerImg" :src="verificationIMG" class="verification" alt=""> |
||||
</el-form-item> |
||||
<el-button class="submit" type="primary" @click="submitFormLogin">马上登录</el-button> |
||||
</el-form> |
||||
</el-tabs> |
||||
</div> |
||||
</div> |
||||
|
||||
<el-dialog title="绑定手机号" :visible.sync="phoneVisible" :close-on-click-modal="false" width="30%"> |
||||
<div style='padding: 0 13px 20px 13px;'> |
||||
依据国家政策法规,需绑定手机号进行实网络实名才可登录使用本平台 |
||||
</div> |
||||
<el-form ref="form" label-width="60px"> |
||||
<el-form-item label="手机号"> |
||||
<el-input style="width: 100%;" placeholder="请输入手机号" v-model="phone" maxlength="11"></el-input> |
||||
</el-form-item> |
||||
<el-form-item label="验证码"> |
||||
<div style="display: flex;"> |
||||
<el-input v-model="phoneCode" placeholder="请输入验证码" maxlength="6"></el-input> |
||||
<el-button style="margin-left: 10px" type="text" @click="sendPhoneCode" |
||||
:disabled="phoneDisabled">{{ phoneBtnText }} |
||||
</el-button> |
||||
</div> |
||||
</el-form-item> |
||||
</el-form> |
||||
<span slot="footer" class="dialog-footer"> |
||||
<el-button @click="phoneVisible = false">取 消</el-button> |
||||
<el-button type="primary" @click="phoneSubmit">确 定</el-button> |
||||
</span> |
||||
</el-dialog> |
||||
<v-footer class="footer"></v-footer> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapActions, mapMutations } from "vuex"; |
||||
import vFooter from "@/layouts/footer"; |
||||
import util from "@/libs/util"; |
||||
import Setting from "@/setting"; |
||||
|
||||
export default { |
||||
data: function() { |
||||
return { |
||||
activeName: "1", |
||||
loginForm: { |
||||
account: "", |
||||
password: "", |
||||
code: "", // 验证码 |
||||
random: "", // 随机数 |
||||
distinguish: 1, // 区分手机号账号登录,1为账号登录,2为手机号或邮箱登录 |
||||
type: 0, // 平台端区分:0->教师端 1->学生端 2->无端 |
||||
platform: Setting.platformId |
||||
}, |
||||
loginRules: { |
||||
account: [{ required: true, message: "请输入账号", trigger: "blur" }], |
||||
password: [{ required: true, message: "请输入密码", trigger: "blur" }], |
||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }] |
||||
}, |
||||
verificationIMG: "", |
||||
phoneVisible: false, // 绑定手机号对话框 |
||||
phone: "", |
||||
phoneCode: "", |
||||
phoneDisabled: false, |
||||
phoneTimer: null, |
||||
phoneBtnText: "发送验证码" |
||||
}; |
||||
}, |
||||
components: { |
||||
vFooter |
||||
}, |
||||
created() { |
||||
this.getVerImg(); |
||||
}, |
||||
mounted() { |
||||
// 页面离开的时候销毁手机和邮箱验证码定时器 |
||||
this.$once("hook:beforeDestroy", function() { |
||||
clearInterval(this.phoneTimer); |
||||
this.phoneTimer = null; |
||||
}); |
||||
}, |
||||
methods: { |
||||
...mapMutations("user", [ |
||||
"SET_ROLENAME", 'SET_FROM' |
||||
]), |
||||
...mapActions("user", [ |
||||
"setCustomer", "setCustomerName" |
||||
]), |
||||
getVerImg() { // 获取验证码图片 |
||||
this.loginForm.random = Math.floor(Math.random() * 999999999); |
||||
this.verificationIMG = this.api.verification + "?random=" + `${this.loginForm.random}`; |
||||
}, |
||||
handleClick(tab, event) { // 切换标签 |
||||
this.loginForm.account = ""; |
||||
this.$refs.loginForm.clearValidate(); |
||||
this.loginRules.account[0].message = tab.index === "1" ? "请输入账号" : "请输入手机号/邮箱"; |
||||
}, |
||||
submitFormLogin() { // 提交登录 |
||||
this.$refs.loginForm.validate(valid => { |
||||
if (valid) { |
||||
this.loginForm.distinguish = Number(this.activeName); |
||||
this.$post(this.api.logins, this.loginForm).then(res => { |
||||
if (res && res.status == 30001) { |
||||
this.phoneVisible = true; |
||||
} |
||||
this.getVerImg() |
||||
this.loginForm.code = '' |
||||
util.local.set(Setting.tokenKey, res.data.token, Setting.tokenExpires); |
||||
this.SET_FROM(false) |
||||
this.getRole() |
||||
this.queryCustomer() |
||||
}).catch(res => { |
||||
this.getVerImg() |
||||
this.loginForm.code = '' |
||||
}) |
||||
} else { |
||||
return util.errorMsg("请检查表单数据"); |
||||
} |
||||
}); |
||||
}, |
||||
// 获取当前用户角色 |
||||
getRole() { |
||||
this.$post(`${this.api.getUserAllRoleByToken}?platformId=${Setting.platformId}`).then(res => { |
||||
this.SET_ROLENAME(res) |
||||
}).catch(err => {}) |
||||
}, |
||||
queryCustomer() { // 查询是否是客户 |
||||
this.$get(this.api.isClient).then(res => { |
||||
util.successMsg("登录成功"); |
||||
this.setCustomer(res.customer); |
||||
res.customerName && this.setCustomerName(res.customerName); |
||||
|
||||
const path = '/student/list' // 默认路径 |
||||
this.$get(`${this.api.getUserRolesPermissionMenu}?platformId=${Setting.platformId}`).then(res => { |
||||
const list = res.permissionMenu[0].children |
||||
this.$router.push(list.find(e => e.path === path) ? path : list[0].path) |
||||
}).catch(err => {}) |
||||
}).catch(res => {}); |
||||
}, |
||||
phoneCountdown() { |
||||
let count = 60; |
||||
if (!this.phoneTimer) { |
||||
this.phoneDisabled = true; |
||||
this.phoneTimer = setInterval(() => { |
||||
console.log("倒计时中"); |
||||
if (count > 0) { |
||||
count--; |
||||
this.phoneBtnText = `${count}秒后重试`; |
||||
} else { |
||||
this.phoneDisabled = false; |
||||
clearInterval(this.phoneTimer); |
||||
this.phoneTimer = null; |
||||
this.phoneBtnText = `发送验证码`; |
||||
} |
||||
}, 1000); |
||||
} |
||||
}, |
||||
sendPhoneCode() { |
||||
if (!this.phone) return util.warningMsg("请输入手机号"); |
||||
if (!/^1[3456789]\d{9}$/.test(this.phone)) return util.warningMsg("请输入正确的手机号"); |
||||
let data = { |
||||
platform: Setting.platformId, |
||||
phone: this.phone, |
||||
types: 2 |
||||
}; |
||||
this.$post(this.api.sendPhoneOrEmailCode, data).then(res => { |
||||
if (res.message.opener) { |
||||
this.phoneCountdown(); |
||||
this.phoneOpener = res.message.opener; |
||||
} else { |
||||
util.errorMsg(res.message); |
||||
} |
||||
}).catch(res => {}); |
||||
}, |
||||
phoneSubmit() { |
||||
if (!this.phone) return util.warningMsg("请输入手机号"); |
||||
if (!/^1[3456789]\d{9}$/.test(this.phone)) return util.warningMsg("请输入正确的手机号"); |
||||
if (!this.phoneCode) return util.warningMsg("请输入验证码"); |
||||
let data = { |
||||
phone: this.phone, |
||||
types: 2, |
||||
code: this.phoneCode, |
||||
opener: this.phoneOpener, |
||||
platform: Setting.platformId, |
||||
account: this.loginForm.account |
||||
}; |
||||
this.$post(this.api.bindPhoneOrEmail, data).then(res => { |
||||
util.successMsg("绑定成功"); |
||||
this.loginForm.phone = this.phone; |
||||
this.phoneVisible = false; |
||||
|
||||
util.local.set(Setting.tokenKey, res.token, Setting.tokenExpires); |
||||
let redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : "/index"; |
||||
this.$router.replace(redirect); |
||||
util.successMsg("登录成功"); |
||||
this.queryCustomer(); |
||||
}).catch(res => { |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.wrap { |
||||
position: relative; |
||||
width: 100%; |
||||
height: 100%; |
||||
background-image: url(../../../assets/img/login-bg.png); |
||||
background-size: 100%; |
||||
.header { |
||||
width: 100%; |
||||
height: 60px; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
background-color: #fff; |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
font-size: 18px; |
||||
|
||||
.logo { |
||||
width: 150px; |
||||
margin-left: 20px; |
||||
|
||||
&.hh { |
||||
width: 220px; |
||||
} |
||||
} |
||||
} |
||||
/deep/ .main { |
||||
position: absolute; |
||||
left: 50%; |
||||
top: 42px; |
||||
transform: translate(-50%, 0); |
||||
width: 1200px; |
||||
height: 82%; |
||||
margin-top: 60px; |
||||
background-image: url(../../../assets/img/login-input.png); |
||||
box-shadow: 0px 0px 79px 0px rgba(11, 15, 65, 0.36); |
||||
overflow: hidden; |
||||
|
||||
.box { |
||||
width: 548px; |
||||
position: absolute; |
||||
left: 50%; |
||||
top: 50px; |
||||
transform: translate(-50%, 0); |
||||
|
||||
.el-tabs__nav-scroll { |
||||
display: flex; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.el-tabs__nav-wrap::after { |
||||
background-color: #fff; |
||||
opacity: 0; |
||||
|
||||
.el-tabs__active-bar { |
||||
background-color: #000; |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
.el-tabs__item { |
||||
padding: 0 90px; |
||||
color: #999; |
||||
|
||||
&:hover { |
||||
color: #000; |
||||
} |
||||
|
||||
&.is-active { |
||||
color: #333; |
||||
font-weight: bold; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.el-input__inner { |
||||
height: 80px; |
||||
line-height: 80px; |
||||
border: 1px solid rgba(220, 220, 220, 1); |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
.verification { |
||||
position: absolute; |
||||
top: 1px; |
||||
right: 1px; |
||||
width: 160px; |
||||
height: 78px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.submit { |
||||
width: 100%; |
||||
height: 88px; |
||||
margin-bottom: 50px; |
||||
font-weight: bold; |
||||
background: linear-gradient(90deg, rgba(94, 206, 253, 1), rgba(91, 67, 231, 1)); |
||||
box-shadow: 0px 7px 27px 0px rgba(50, 129, 255, 0.51); |
||||
border-radius: 10px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.footer { |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,54 @@ |
||||
<template></template> |
||||
|
||||
<script> |
||||
import { mapActions, mapMutations } from "vuex"; |
||||
import util from "@/libs/util"; |
||||
import Setting from "@/setting"; |
||||
export default { |
||||
data: function() { |
||||
return { |
||||
token: this.$route.query.auth |
||||
}; |
||||
}, |
||||
mounted() { |
||||
this.token ? this.setLogin() : this.$router.replace('/login') |
||||
}, |
||||
methods: { |
||||
...mapMutations("user", [ |
||||
"SET_ROLENAME", 'SET_FROM' |
||||
]), |
||||
...mapActions("user", [ |
||||
"setCustomer", "setCustomerName" |
||||
]), |
||||
setLogin() { |
||||
this.SET_FROM(true) |
||||
util.local.set(Setting.tokenKey, window.atob(decodeURI(this.token)), Setting.tokenExpires); |
||||
this.getRole() |
||||
this.queryCustomer() |
||||
}, |
||||
// 获取当前用户角色 |
||||
getRole() { |
||||
this.$post(`${this.api.getUserAllRoleByToken}?platformId=${Setting.platformId}`).then(res => { |
||||
this.SET_ROLENAME(res) |
||||
}).catch(err => {}) |
||||
}, |
||||
queryCustomer() { // 查询是否是客户 |
||||
this.$get(this.api.isClient).then(res => { |
||||
util.successMsg('登录成功') |
||||
this.setCustomer(res.customer) |
||||
this.setCustomerName(res.customerName) |
||||
|
||||
const path = '/station/list' // 默认路径 |
||||
this.$get(`${this.api.getUserRolesPermissionMenu}?platformId=${Setting.platformId}`).then(res => { |
||||
const list = res.permissionMenu[0].children |
||||
this.$router.push(list.find(e => e.path === path) ? path : list[0].path) |
||||
}).catch(err => {}) |
||||
}).catch(res => {}) |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
</style> |
@ -0,0 +1,635 @@ |
||||
<template> |
||||
<div> |
||||
<el-form :disabled="isDetail"> |
||||
<el-card shadow="hover" class="m-b-20"> |
||||
<div class="flex-between"> |
||||
<el-page-header @back="goBack" :content="isDetail ? '查看' : (form.id ? '更新' : '创建') + '教学实验'"></el-page-header> |
||||
<div> |
||||
<el-button type="primary" @click="submit" v-show="!isDetail">{{ form.id ? "更新" : "创建" }}</el-button> |
||||
</div> |
||||
</div> |
||||
</el-card> |
||||
|
||||
<el-card shadow="hover" class="mgr20 m-b-20"> |
||||
<div> |
||||
<p class="m-b-20">考核名称</p> |
||||
<el-input |
||||
placeholder="请输入考核名称" |
||||
v-model.trim="form.experimentalName" |
||||
clearable |
||||
maxlength="15" |
||||
class="inline-input" |
||||
></el-input> |
||||
</div> |
||||
</el-card> |
||||
|
||||
<el-card shadow="hover" class="m-b-20"> |
||||
<div> |
||||
<p class="m-b-20">发布方式</p> |
||||
<el-radio-group v-model="form.type" :disabled="form.id ? true : false"> |
||||
<el-radio :label="1">手动发布</el-radio> |
||||
<el-radio :label="2">定时发布</el-radio> |
||||
</el-radio-group> |
||||
</div> |
||||
</el-card> |
||||
<!-- 根据发布方式判断时间的显示 --> |
||||
<el-card shadow="hover" class="m-b-20"> |
||||
<div> |
||||
<p class="m-b-20">实验时间</p> |
||||
<!-- 手动发布显示 --> |
||||
<div class="date-inputs" v-if="form.type==1"> |
||||
实验时长: |
||||
<el-input type="number" min="0" v-model.trim="duration.day" placeholder></el-input> |
||||
天 |
||||
<el-input type="number" min="0" v-model.trim="duration.hour" placeholder></el-input> |
||||
小时 |
||||
<el-input type="number" min="0" v-model.trim="duration.minute" placeholder></el-input> |
||||
分 |
||||
</div> |
||||
<!-- 定时发布显示 --> |
||||
<div v-if="form.type==2" class="addAssess"> |
||||
<span class="mgr10">开始时间:</span> |
||||
<el-date-picker |
||||
size="small" |
||||
v-model="date" |
||||
type="datetimerange" |
||||
range-separator="-" |
||||
start-placeholder="开始日期" |
||||
end-placeholder="结束日期" |
||||
:picker-options="pickerOptions" |
||||
></el-date-picker> |
||||
</div> |
||||
</div> |
||||
</el-card> |
||||
<el-card shadow="hover" class="mgr20 m-b-20"> |
||||
<div> |
||||
<p class="m-b-20">课程</p> |
||||
<div class="inline-input"> |
||||
<el-select v-model="form.curriculumId" @change="initData"> |
||||
<el-option |
||||
v-for="item in curriculumList" |
||||
:key="item.cid" |
||||
:label="item.curriculumName" |
||||
:value="item.cid"> |
||||
</el-option> |
||||
</el-select> |
||||
<!-- <el-select v-model="form.curriculumId" @change="initData"> |
||||
<el-option |
||||
v-for="item in systemList" |
||||
:key="item.id" |
||||
:label="item.label" |
||||
:value="item.id"> |
||||
</el-option> |
||||
</el-select> --> |
||||
</div> |
||||
</div> |
||||
</el-card> |
||||
<!-- 实训项目模块 --> |
||||
<el-card shadow="hover" class="m-b-20"> |
||||
<div class="flex-between m-b-20"> |
||||
<span>实训项目</span> |
||||
<div style="display: inline-flex;"> |
||||
<div> |
||||
<el-input placeholder="请输入项目名称" prefix-icon="el-icon-search" v-model.trim="keyword" clearable></el-input> |
||||
</div> |
||||
<el-button style="margin-left: 5px" type="primary" round @click="toProject">自定义实验项目</el-button> |
||||
</div> |
||||
</div> |
||||
<!-- 实训项目表格 --> |
||||
<el-table :data="projectData" class="table" stripe header-align="center"> |
||||
<!-- 单选实训项目ID --> |
||||
<el-table-column width="60" label="选择" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-radio v-model="form.projectId" :label="scope.row.projectId"> </el-radio> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column> |
||||
<el-table-column prop="auth" label="项目权限" align="center"> |
||||
<template slot-scope="scope"> |
||||
{{ permissionsKeys[scope.row.permissions] }} |
||||
</template> |
||||
</el-table-column> |
||||
<!-- <el-table-column prop="createUser" label="创建人" align="center"></el-table-column> --> |
||||
<el-table-column prop="founder" label="创建人" align="center"> |
||||
<template slot-scope="scope"> |
||||
{{ scope.row.createUser }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="createTime" label="创建时间" align="center"></el-table-column> |
||||
<el-table-column label="操作" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-button type="text" @click="showProject(scope.row)">查看</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<div class="pagination"> |
||||
<el-pagination |
||||
background |
||||
:page-size="pageSize" |
||||
@current-change="handleCurrentChange" |
||||
layout="total,prev, pager, next" |
||||
:total="total" |
||||
></el-pagination> |
||||
</div> |
||||
</el-card> |
||||
<el-card shadow="hover" class="mgr20 m-b-20"> |
||||
<div> |
||||
<p class="m-b-20">考核发布</p> |
||||
<el-radio-group v-model="form.isSpecify"> |
||||
<el-radio :label="1">指定范围</el-radio> |
||||
<el-radio :label="0">无指定范围</el-radio> |
||||
</el-radio-group> |
||||
</div> |
||||
<div v-show="form.isSpecify == 1" style="padding-top: 24px;"> |
||||
<p class="m-b-20">班级名称</p> |
||||
<el-input |
||||
placeholder="请输入班级名称" |
||||
v-model.trim="filterClassName" |
||||
class="inline-input m-b-20" |
||||
clearable> |
||||
<el-button slot="append" icon="el-icon-search"></el-button> |
||||
</el-input> |
||||
<div v-show="tagList.length" class="m-b-20"> |
||||
<el-tag |
||||
v-for="(tag, index) in tagList" |
||||
:key="index" |
||||
closable |
||||
@close="handleCloseTag(tag)" |
||||
style="margin-right: 10px"> |
||||
{{ tag.organizationName }} |
||||
</el-tag> |
||||
</div> |
||||
<div class="tree-con"> |
||||
<student-tree |
||||
ref="tree" |
||||
node-key="id" |
||||
show-checkbox |
||||
highlight-current |
||||
default-expand-all |
||||
lazy |
||||
:load="loadTree" |
||||
:default-checked-keys="defaultCheckedKeys" |
||||
:props="{children: 'children', label: 'organizationName', isLeaf: 'leaf'}" |
||||
:filter-node-method="filterNode" |
||||
@check="handleCheck"> |
||||
</student-tree> |
||||
</div> |
||||
</div> |
||||
</el-card> |
||||
<!-- 邀请码 --> |
||||
<el-card v-if="form.isSpecify == 0" shadow="hover" class="m-b-20"> |
||||
<div style="margin-bottom: 10px"> |
||||
<p class="m-b-20">设置邀请码</p> |
||||
<el-radio-group v-model="form.isEnableCode"> |
||||
<el-radio :label="1">是</el-radio> |
||||
<el-radio :label="0">否</el-radio> |
||||
</el-radio-group> |
||||
</div> |
||||
<div v-if="form.isEnableCode == 1"> |
||||
<el-input style="display: inline-block;width: auto;margin-right: 10px" type="number" v-model.trim="form.invitationCode" maxlength="6" placeholder="请设置6个数字"></el-input> |
||||
<el-button type="text" @click="createInv">随机</el-button> |
||||
</div> |
||||
</el-card> |
||||
</el-form> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import util from "@/libs/util"; |
||||
import { mapState, mapActions } from "vuex"; |
||||
import StudentTree from "@/components/student-tree/src/tree"; |
||||
export default { |
||||
components: { StudentTree }, |
||||
data() { |
||||
return { |
||||
founderKeys: { |
||||
0: "系统", |
||||
1: "老师" |
||||
}, |
||||
cidList: [], |
||||
permissionsKeys: { |
||||
0: "练习", |
||||
1: "考核", |
||||
2: "竞赛" |
||||
}, |
||||
isDetail: Boolean(this.$route.query.show), |
||||
form: { |
||||
id: this.$route.query.id ? this.$route.query.id : "", |
||||
experimentalName: "", |
||||
type: 1, // 发布类型(1、手动发布 2、定时发布) |
||||
experimentDuration: "0d0h0m", |
||||
curriculumId: "", |
||||
projectId: "", |
||||
isSpecify: 1, // 考核发布(1、指定范围 0、无指定范围) |
||||
isEnableCode: 0, //是否设置邀请码 |
||||
invitationCode: "", |
||||
status: 0, // 状态(0、待开始 1、进行中 2、已结束) |
||||
classId: "", |
||||
stuInfo: [] |
||||
}, |
||||
date: "", // 实验时间 |
||||
duration: { |
||||
day: "", |
||||
hour: "", |
||||
minute: "" |
||||
}, // 实验时长 |
||||
startTime: "0000-00-00 00:00:00", //开始时间 |
||||
stopTime: "0000-00-00 00:00:00", //结束时间 |
||||
expNameRepeat: false, // 考核名称是否重复 |
||||
curriculumList: [], // 课程列表 |
||||
filterClassName: "", // 班级名称搜索 |
||||
tagList: [], // 班级名称标签 |
||||
defaultCheckedKeys: [], // 默认选中 |
||||
allCheckedNodes: [], // 当前选中和半选节点 |
||||
keyword: "", // 项目名称搜索 |
||||
searchTimer: null, |
||||
surplusTime: "", |
||||
projectDataAll: [], |
||||
projectData: [], |
||||
pickerOptions: { |
||||
disabledDate: time => { |
||||
return time.getTime() < new Date().getTime() - 86400000; |
||||
} |
||||
}, |
||||
page: 1, |
||||
pageSize: 5, |
||||
total: 0, |
||||
isToProject: false, |
||||
systemList: [], |
||||
submiting: false, // 新增编辑防抖标识 |
||||
updateTime: 0 |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapState("project", [ |
||||
"assFields" |
||||
]) |
||||
}, |
||||
mounted() { |
||||
this.date = [util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(new Date().getTime() + 300000)), util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(new Date().getTime() + 300000))]; |
||||
this.form.id && this.getData(); |
||||
this.recoveryData(); |
||||
this.getschoolCourse(); |
||||
}, |
||||
beforeDestroy() { |
||||
if (!this.isToProject) this.setAss({}); |
||||
}, |
||||
watch: { |
||||
// 监听信息是否有更改,有的话页面离开的时候要询问是否要保存 |
||||
form: { |
||||
handler(val){ |
||||
this.updateTime++ |
||||
}, |
||||
deep:true |
||||
}, |
||||
date: function(val) { |
||||
if (val[0] != "0000-00-00 00:00:00") { |
||||
this.startTime = util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(val[0])); |
||||
this.stopTime = util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(val[1])); |
||||
} |
||||
}, |
||||
duration: { |
||||
handler(n, o) { |
||||
this.form.experimentDuration = `${n.day ? n.day : 0}d${n.hour ? n.hour : 0}h${n.minute ? n.minute : 0}m`; |
||||
}, |
||||
deep: true |
||||
}, |
||||
keyword: function(val) { |
||||
clearTimeout(this.searchTimer); |
||||
this.searchTimer = setTimeout(() => { |
||||
this.initData(); |
||||
}, 500); |
||||
}, |
||||
filterClassName(val) { |
||||
this.$refs.tree.filter(val); |
||||
} |
||||
}, |
||||
methods: { |
||||
...mapActions("project", [ |
||||
"setAss" |
||||
]), |
||||
handleCloseTag(tag) { // 关闭班级标签 |
||||
this.allCheckedNodes = this.$refs.tree.getCheckedNodes().concat(this.$refs.tree.getHalfCheckedNodes()); |
||||
let tagIndex = this.tagList.findIndex(i => i.id === tag.id); |
||||
this.tagList.splice(tagIndex, 1); |
||||
// 设置选中 |
||||
let setKeys = []; |
||||
this.allCheckedNodes.forEach(i => { |
||||
if (i.level === 4 && i.parentId !== tag.id) setKeys.push(i.nodeKey); |
||||
}); |
||||
this.$refs.tree.setCheckedKeys(setKeys); |
||||
this.allCheckedNodes = this.allCheckedNodes.filter(i => (i.level === 3 && i.id !== tag.id) || (i.level === 4 && i.parentId !== tag.id)); |
||||
}, |
||||
handleCheck(data, checked) { // 当复选框被点击的时候触发 |
||||
// 全选和半选状态下的班级都显示在标签 |
||||
let checkedClass = checked.checkedNodes.filter(i => i.level === 3); |
||||
let halfCheckedClass = checked.halfCheckedNodes.filter(i => i.level === 3); |
||||
this.tagList = [...checkedClass, ...halfCheckedClass].map(i => { |
||||
return { id: i.id, organizationName: i.organizationName }; |
||||
}); |
||||
// 缓存全选和半选状态下的所有节点 |
||||
this.allCheckedNodes = [...checked.checkedNodes, ...checked.halfCheckedNodes]; |
||||
}, |
||||
filterNode(value, data) { // 对树节点进行过滤 |
||||
if (!value) return true; |
||||
return data.organizationName.indexOf(value) !== -1; |
||||
}, |
||||
loadTree(node, resolve) { // 懒加载所在班级树结构 |
||||
let level = 1; |
||||
let parentId = ""; |
||||
if (node.level === 0) { |
||||
this.treeNode = node; |
||||
this.treeResolve = resolve; |
||||
this.getTreeData(resolve, level, parentId); |
||||
} else if (node.level > 3) { |
||||
return resolve([]); |
||||
} else { |
||||
if (node.data && node.data.level && node.data.id) { |
||||
this.getTreeData(resolve, node.data.level + 1, node.data.id); |
||||
} |
||||
} |
||||
}, |
||||
async getTreeData(resolve, level, parentId) { // 获取组织架构树数据 |
||||
let { status, treeList } = await this.$post(`${this.api.stuOrganizationTree}?level=${level}&parentId=${parentId}`); |
||||
if (status === 200 && treeList.length) { |
||||
let result = []; |
||||
treeList.forEach(i => { |
||||
if (i.level === 4) { |
||||
i.nodeKey = `${i.parentId}-${i.id}`; |
||||
i.leaf = true; |
||||
} else { |
||||
i.nodeKey = `${i.id}-${new Date().getTime()}`; |
||||
i.leaf = false; |
||||
} |
||||
result.push(i); |
||||
}); |
||||
this.$nextTick(() => { |
||||
// 编辑时,设置默认勾选 |
||||
if (this.form.classInfo && this.form.classInfo.length) { |
||||
let keys = this.form.classInfo.map(i => { |
||||
return i.id; |
||||
}); |
||||
this.defaultCheckedKeys = keys; |
||||
} |
||||
// 获取树结构选中和半选中状态下的数据 |
||||
let nodes = this.$refs.tree.getCheckedNodes().concat(this.$refs.tree.getHalfCheckedNodes()); |
||||
this.allCheckedNodes = nodes; |
||||
this.tagList = nodes.filter(i => i.level === 3); |
||||
}); |
||||
return resolve(result); |
||||
} else { |
||||
return resolve([]); |
||||
} |
||||
}, |
||||
getschoolCourse() { // 获取课程 |
||||
this.$get(this.api.schoolCourse).then(res => { |
||||
this.curriculumList = res.data; |
||||
if (this.curriculumList.length) { |
||||
if (this.form.curriculumId){ |
||||
for(let i=0;i<this.curriculumList.length; i++) { |
||||
if(this.curriculumList[i].cid == this.form.curriculumId){ |
||||
this.getProjectData(); |
||||
} |
||||
} |
||||
}else{ |
||||
this.form.curriculumId = this.curriculumList[0].cid; |
||||
for(let i=0;i<this.curriculumList.length; i++) { |
||||
this.cidList.push(this.curriculumList[i].cid) |
||||
} |
||||
this.getProjectData(); |
||||
} |
||||
} |
||||
}).catch(err => { |
||||
console.log(err); |
||||
}); |
||||
}, |
||||
getProjectData() { |
||||
const curItem = this.curriculumList.find(e => e.cid === this.form.curriculumId) |
||||
let data={ |
||||
pageNum:this.page, |
||||
pageSize:this.pageSize, |
||||
cid:this.form.curriculumId, |
||||
projectName:this.keyword, |
||||
systemId: curItem ? curItem.systemId : 1 |
||||
} |
||||
this.$post(this.api.projectListByCourseId,data).then(res => { |
||||
let { status, data } = res; |
||||
if (status === 200 && data.records) { |
||||
this.projectData = data.records |
||||
this.total = data.total |
||||
} |
||||
}).catch(err => { |
||||
}); |
||||
}, |
||||
handlePage() { |
||||
let result = this.projectDataAll.slice((this.page - 1) * this.pageSize, this.page * this.pageSize); |
||||
this.projectData = result; |
||||
}, |
||||
initData() { |
||||
this.page = 1; |
||||
this.getProjectData(); |
||||
}, |
||||
submit() { // 提交 |
||||
if (this.submiting) return false |
||||
if (!this.form.experimentalName) return util.warningMsg("请填写考核名称"); |
||||
if (this.expNameRepeat) return util.warningMsg("考核名称重复,请重新输入"); |
||||
if (this.form.type !== 1) { |
||||
if (new Date().getTime() > new Date(this.startTime).getTime()) return util.warningMsg("开始时间不能早于当前时间"); |
||||
let timestamp = new Date(new Date(this.stopTime).getTime() - new Date(this.startTime).getTime()); |
||||
let minute = 1000 * 60; |
||||
let hour = minute * 60; |
||||
let day = hour * 24; |
||||
this.form.experimentDuration = `${Math.floor(timestamp / day)}d${Math.floor(timestamp % day / hour)}h${Math.floor(timestamp % day % hour / minute)}m`; |
||||
} |
||||
if (this.form.type == 1 && this.form.experimentDuration == "0d0h0m") return util.warningMsg("请填写实验时长"); |
||||
if (this.form.type == 2 && this.startTime == "0000-00-00 00:00:00") return util.warningMsg("请填写实验时间"); |
||||
if (this.form.type == 1) { |
||||
const { day, hour, minute } = this.duration |
||||
if (String(day).includes('.')) return util.warningMsg('实验天数请填写整数') |
||||
if (day < 0) return util.warningMsg('实验天数请勿填写负数') |
||||
if (String(hour).includes('.')) return util.warningMsg('实验小时请填写整数') |
||||
if (hour < 0) return util.warningMsg('实验小时请勿填写负数') |
||||
if (String(minute).includes('.')) return util.warningMsg('实验分钟请填写整数') |
||||
if (minute < 0) return util.warningMsg('实验分钟请勿填写负数') |
||||
} |
||||
if (!this.form.projectId) return util.warningMsg("请选择实训项目"); |
||||
if (this.form.isSpecify == 0 && this.form.isEnableCode == 1) { |
||||
if (!this.form.invitationCode) return util.warningMsg("请设置邀请码"); |
||||
if (!this.form.invitationCode || String(this.form.invitationCode).length < 6 || isNaN(this.form.invitationCode)) return util.warningMsg("请输入6位纯数字邀请码"); |
||||
} |
||||
if (this.form.type == 2) { |
||||
this.form.startTime = this.startTime; |
||||
this.form.stopTime = this.stopTime; |
||||
} |
||||
let classId = []; |
||||
let stuInfo = []; |
||||
this.allCheckedNodes.forEach(i => { |
||||
if (i.level === 3) { |
||||
classId.push(i.id); |
||||
} else if (i.level === 4) { |
||||
stuInfo.push({ classId: i.parentId, stuAccountId: i.id }); |
||||
} |
||||
}); |
||||
if (this.isSpecify == 1 && !stuInfo.length) { |
||||
util.warningMsg("请选择学生"); |
||||
return; |
||||
} else { |
||||
this.form.classId = classId.toString(); |
||||
this.form.stuInfo = stuInfo; |
||||
} |
||||
this.submiting = true |
||||
if (this.form.id) { |
||||
this.$post(this.api.modifyAssessment, this.form).then(res => { |
||||
util.successMsg("修改成功"); |
||||
this.$router.back(); |
||||
}).catch(err => { |
||||
this.submiting = false |
||||
}); |
||||
} else { |
||||
this.$post(this.api.saveAssessment, this.form).then(res => { |
||||
util.successMsg("创建成功"); |
||||
this.$router.back(); |
||||
}).catch(err => { |
||||
this.submiting = false |
||||
}); |
||||
} |
||||
}, |
||||
getData() { // 获取详情 |
||||
this.$get(`${this.api.getDetailById}?id=${this.form.id}`).then(res => { |
||||
this.form = res.data; |
||||
this.formatDuration(); |
||||
this.getschoolCourse(); |
||||
}).catch(err => { |
||||
}); |
||||
}, |
||||
formatDuration() { // 格式化实验时长 |
||||
let duration = this.form.experimentDuration.replace(/\D+/g, ",").split(","); |
||||
this.duration = { |
||||
day: duration[0], |
||||
hour: duration[1], |
||||
minute: duration[2] |
||||
}; |
||||
this.date = [this.startTime, this.stopTime]; |
||||
}, |
||||
recoveryData() { // 恢复数据 |
||||
if (JSON.stringify(this.assFields) != "{}") { |
||||
let info = this.assFields; |
||||
this.form = info.form; |
||||
this.duration = info.duration; // 实验时长 |
||||
this.startTime = info.startTime; //开始时间 |
||||
this.stopTime = info.startTime; //结束时间 |
||||
this.expNameRepeat = info.expNameRepeat; // 考核名称是否重复 |
||||
this.allCheckedNodes = info.allCheckedNodes; // 选中的树节点 |
||||
this.formatDuration(); |
||||
} |
||||
}, |
||||
handleCacheData() { // 缓存数据,用于从项目管理页面返回时,数据回显 |
||||
this.allCheckedNodes.forEach(i => { |
||||
if (i.level === 4) { |
||||
this.form.stuInfo.push({ classId: i.parentId, stuAccountId: i.id }); |
||||
} |
||||
}); |
||||
let data = { |
||||
form: this.form, |
||||
date: this.date, // 实验时间 |
||||
duration: this.duration, // 实验时长 |
||||
startTime: this.startTime, //开始时间 |
||||
stopTime: this.startTime, //结束时间 |
||||
expNameRepeat: this.expNameRepeat, // 考核名称是否重复 |
||||
allCheckedNodes: this.allCheckedNodes // 选中的树节点 |
||||
}; |
||||
this.setAss(data); |
||||
this.isToProject = true; |
||||
}, |
||||
toProject() { |
||||
this.handleCacheData(); |
||||
this.$router.push("/project/list/?show=1"); |
||||
}, |
||||
showProject(row) { |
||||
this.handleCacheData(); |
||||
this.$router.push(`/project/add?projectId=${row.projectId}&show=1`); |
||||
}, |
||||
createInv() { |
||||
let result = ""; |
||||
for (let i = 0; i < 6; i++) { |
||||
result += Math.floor(Math.random() * 10); |
||||
} |
||||
this.form.invitationCode = result; |
||||
}, |
||||
handleCurrentChange(val) { |
||||
this.page = val; |
||||
this.getProjectData(); |
||||
}, |
||||
// 返回上一页 |
||||
backPage() { |
||||
this.$router.back() |
||||
}, |
||||
goBack() { |
||||
const { id } = this.form |
||||
const updateTime = this.updateTime |
||||
// 更改了信息才需要提示 |
||||
if (updateTime > 1) { |
||||
this.$confirm(`编辑的内容未保存,是否保存?`, '提示', { |
||||
type: 'warning' |
||||
}).then(() => { |
||||
this.submit() |
||||
}).catch(() => { |
||||
this.backPage() |
||||
}) |
||||
} else { |
||||
this.backPage() |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.inline-input { |
||||
width: 500px; |
||||
} |
||||
.date-inputs { |
||||
.el-input { |
||||
width: 100px; |
||||
} |
||||
} |
||||
.tree-con { |
||||
height: 400px; |
||||
max-height: 400px; |
||||
width: 100%; |
||||
border: 1px solid #DCDFE6; |
||||
border-radius: 4px; |
||||
padding: 10px 10px 10px 40px; |
||||
overflow: auto; |
||||
/deep/ .el-tree-node__content{ |
||||
height: 30px; |
||||
.el-tree-node__expand-icon{ |
||||
padding: 2px; |
||||
color: #fff; |
||||
background-color: #9278FF; |
||||
border-radius: 30px; |
||||
margin: 0 10px; |
||||
} |
||||
.el-tree-node__expand-icon.is-leaf{ |
||||
background-color: transparent; |
||||
} |
||||
.el-icon-caret-right:before{ |
||||
font-size: 14px; |
||||
} |
||||
.el-checkbox__inner{ |
||||
width: 18px; |
||||
height: 18px; |
||||
border: 1px solid #9278FF; |
||||
border-radius: 20px; |
||||
} |
||||
.el-checkbox__inner::after{ |
||||
position: absolute; |
||||
top: 3px; |
||||
left: 6px; |
||||
} |
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner::before{ |
||||
position: absolute; |
||||
top: 7px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,445 @@ |
||||
<template> |
||||
<div class="page"> |
||||
<h6 class="p-title">筛选</h6> |
||||
<div class="tool mul"> |
||||
<ul class="filter"> |
||||
<li> |
||||
<label>创建时间</label> |
||||
<el-radio-group v-model="form.month" @change="initData"> |
||||
<el-radio v-for="(item,index) in dateList" :key="index" :label="item.id" border>{{ item.name }}</el-radio> |
||||
</el-radio-group> |
||||
<el-date-picker v-model="date" @blur='pickerInput' align="right" unlink-panels type="daterange" style="width: 300px;margin-left: 10px;" start-placeholder="开始日期" end-placeholder="结束日期" format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable></el-date-picker> |
||||
</li> |
||||
<li> |
||||
<label>发布类型</label> |
||||
<el-select v-model="form.type" clearable placeholder="请选择发布类型" @change="initData"> |
||||
<el-option v-for="(item,index) in typeList" :key="index" :label="item.name" :value="item.value"></el-option> |
||||
</el-select> |
||||
</li> |
||||
<li> |
||||
<label>实验状态</label> |
||||
<el-select v-model="form.status" clearable placeholder="请选择实验状态" @change="initData"> |
||||
<el-option v-for="(item,index) in statusList" :key="index" :label="item.name" :value="item.value"></el-option> |
||||
</el-select> |
||||
</li> |
||||
<li> |
||||
<label>课程</label> |
||||
<el-select v-model="curriculumId" placeholder="请选择" @change="initData"> |
||||
<el-option label="不限" value=""></el-option> |
||||
<el-option |
||||
v-for="item in curriculumList" |
||||
:key="item.cid" |
||||
:label="item.curriculumName" |
||||
:value="item.cid" |
||||
></el-option> |
||||
</el-select> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
<div class="tool mul"> |
||||
<ul class="filter"> |
||||
<li> |
||||
<el-input placeholder="请输入实验班级/项目名称/考核名称" prefix-icon="el-icon-search" v-model.trim="keyWord" clearable style="width: 300px"></el-input> |
||||
</li> |
||||
</ul> |
||||
<div> |
||||
<div> |
||||
<el-button v-auth type="info" round @click="add" >创建考核</el-button> |
||||
<el-button v-auth type="primary" round @click="delAllData">批量删除</el-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<el-table v-loading="listLoading" ref="table" :data="listData" class="table" stripe header-align="center" @selection-change="handleSelectionChange" row-key="id"> |
||||
<el-table-column type="selection" :selectable="row => row.status !== 1" width="50" align="center" :reserve-selection="true"></el-table-column> |
||||
<el-table-column type="index" width="60" label="序号" align="center"> |
||||
<template slot-scope="scope"> |
||||
{{ scope.$index + (pageNum - 1) * pageSize + 1 }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="className" label="实验班级" align="center" min-width="120px" show-overflow-tooltip></el-table-column> |
||||
<el-table-column prop="experimentalName" label="考核名称" align="center" min-width="120px" show-overflow-tooltip></el-table-column> |
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip align="center"></el-table-column> |
||||
<el-table-column prop="experimenterNum" label="实验人数" align="center"></el-table-column> |
||||
<el-table-column prop="experimentDuration" label="实验时长" align="center"></el-table-column> |
||||
<el-table-column label="邀请码" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ scope.row.isEnableCode == 1 ? scope.row.invitationCode : "" }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="type" label="发布类型" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ types[scope.row.type] }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="createTime" width="150" label="创建时间" align="center"> |
||||
</el-table-column> |
||||
<el-table-column prop="startTime" width="150" label="起始时间" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ transferTime(scope.row.startTime) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column prop="stopTime" width="150" label="结束时间" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ transferTime(scope.row.stopTime) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="倒计时" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ timeFilter(scope.row) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="实验状态" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span> |
||||
{{ status[scope.row.status] }} |
||||
</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="操作" width="170" align="center"> |
||||
<template slot-scope="scope"> |
||||
<template v-if="scope.row.status == 0 && (roleName.includes('超级') || roleName === scope.row.roleName || (roleName === '管理员' && !scope.row.roleName.includes('超级')))"> |
||||
<el-button v-auth v-if="scope.row.type == 1" type="text" @click="start(scope.row)">启动</el-button> |
||||
<el-button v-auth type="text" @click="edit(scope.row)">修改</el-button> |
||||
</template> |
||||
<template v-else-if="scope.row.status == 1 && (roleName.includes('超级') || roleName === scope.row.roleName || (roleName === '管理员' && !scope.row.roleName.includes('超级')))"> |
||||
<el-button v-auth type="text" @click="finish(scope.row)">提前结束</el-button> |
||||
</template> |
||||
<template v-else-if="scope.row.status == 2"> |
||||
<el-button v-auth type="text" @click="show(scope.row)">查看成绩</el-button> |
||||
</template> |
||||
<el-button v-auth v-if="(scope.row.status == 0 || scope.row.status == 2) && (roleName.includes('超级') || roleName === scope.row.roleName || (roleName === '管理员' && !scope.row.roleName.includes('超级')))" type="text" @click="delData(scope.row)">删除</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<div class="pagination"> |
||||
<el-pagination background layout="total, prev, pager, next" :total="total" @current-change="handleCurrentChange" :current-page="pageNum"></el-pagination> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from "vuex"; |
||||
import util from "@/libs/util"; |
||||
export default { |
||||
data() { |
||||
return { |
||||
typeList: [ |
||||
{ |
||||
value: "", |
||||
name: "不限" |
||||
}, { |
||||
value: 1, |
||||
name: "手动发布" |
||||
}, { |
||||
value: 2, |
||||
name: "定时发布" |
||||
} |
||||
], |
||||
types: ["", "手动发布", "定时发布"], |
||||
statusList: [ |
||||
{ |
||||
value: "", |
||||
name: "不限" |
||||
}, { |
||||
value: 0, |
||||
name: "待开始" |
||||
}, { |
||||
value: 1, |
||||
name: "进行中" |
||||
}, { |
||||
value: 2, |
||||
name: "已结束" |
||||
} |
||||
], |
||||
status: ["待开始", "进行中", "已结束"], |
||||
dateList: [ |
||||
{ |
||||
id: "", |
||||
name: "不限" |
||||
}, { |
||||
id: 1, |
||||
name: "近一个月" |
||||
}, { |
||||
id: 3, |
||||
name: "近三个月" |
||||
}, { |
||||
id: 6, |
||||
name: "近六个月" |
||||
} |
||||
], |
||||
date: [], |
||||
curriculumList: [], |
||||
curriculumId: "", |
||||
keyWord: "", // 搜索框筛选条件 |
||||
searchTimer: null, |
||||
form: { |
||||
type: "", |
||||
status: "", |
||||
startTime: "", |
||||
endTime: "", |
||||
month: "" |
||||
}, |
||||
pageNum: +this.$route.query.page || 1, // 当前页数 |
||||
pageSize: 10, // 每页10条 |
||||
total: 0, // 总数 |
||||
listData: [], // 表格数据 |
||||
multipleSelection: [], // 多选 |
||||
listLoading:false,// 列表加载 |
||||
ticker: null, // 倒计时定时器 |
||||
sss:1, |
||||
datassdata:0 |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapState("user", [ |
||||
'roleName' |
||||
]) |
||||
}, |
||||
watch: { |
||||
"form.month": function(val) { |
||||
if (val) { |
||||
let unit = 24 * 60 * 60 * 1000; |
||||
this.date = [util.formatDate("yyyy-MM-dd", new Date(new Date().getTime() - unit * 30 * val)), util.formatDate("yyyy-MM-dd", new Date(new Date().getTime() + unit))]; |
||||
} else { |
||||
this.date = []; |
||||
} |
||||
}, |
||||
date: function(val) { |
||||
if (val) { |
||||
this.form.startTime = val[0]; |
||||
this.form.endTime = val[1]; |
||||
} else { |
||||
this.form.startTime = ""; |
||||
this.form.endTime = ""; |
||||
this.form.month = ''; |
||||
} |
||||
this.initData(); |
||||
}, |
||||
keyWord: function(val) { |
||||
clearTimeout(this.searchTimer); |
||||
this.searchTimer = setTimeout(() => { |
||||
this.initData(); |
||||
}, 500); |
||||
} |
||||
}, |
||||
mounted() { |
||||
// 页面离开的时候销毁手机和邮箱验证码定时器 |
||||
this.$once("hook:beforeDestroy", function() { |
||||
this.sss = 0 |
||||
// clearInterval(this.ticker); |
||||
// this.ticker = null; |
||||
}); |
||||
this.getData(); |
||||
this.getschoolCourse(); |
||||
}, |
||||
methods: { |
||||
pickerInput(){ |
||||
this.form.month = '6' |
||||
}, |
||||
beginTimer() { |
||||
this.ticker = setInterval(() => { |
||||
if(this.sss == 0){ |
||||
clearInterval(this.ticker); |
||||
this.ticker = null; |
||||
}else{ |
||||
for (let i = 0; i < this.listData.length; i++) { |
||||
const item = this.listData[i]; |
||||
if (item.countDown > 0) { |
||||
item.countDown--; |
||||
} else { |
||||
if (item.status == 0 && item.type == 2) { // 待开始-定时发布 |
||||
item.status = 1; |
||||
item.countDown = (new Date(item.stopTime).getTime() - new Date().getTime()) / 1000; |
||||
// } |
||||
} else if (item.status == 1) { |
||||
// item.status = 2; |
||||
} |
||||
} |
||||
item.show = true; |
||||
this.$set(this.listData, i, item); |
||||
} |
||||
} |
||||
}, 1000); |
||||
}, |
||||
timeFilter(countDown) { |
||||
if (countDown.countDown > 0) { |
||||
let h = Math.floor(countDown.countDown / (60 * 60)); |
||||
let m = Math.floor(countDown.countDown % (60 * 60) / 60); |
||||
let s = Math.floor(countDown.countDown % (60 * 60) % 60); |
||||
return `${h > 9 ? h : `0${h}`}:${m > 9 ? m : `0${m}`}:${s > 9 ? s : `0${s}`}`; |
||||
} else { |
||||
if(countDown.status == 1){ |
||||
countDown.status = 2 |
||||
} |
||||
return "00:00:00"; |
||||
} |
||||
}, |
||||
getData() { |
||||
this.datassdata = this.datassdata+1 |
||||
this.listLoading = true; |
||||
this.listData.splice(0); |
||||
this.sss = 0 |
||||
let data = { |
||||
...this.form, |
||||
keyWord: this.keyWord, |
||||
pageNum: this.pageNum, |
||||
pageSize: this.pageSize, |
||||
curriculumId: this.curriculumId |
||||
}; |
||||
this.$post(this.api.pageByCondition, data).then(res => { |
||||
this.listData = res.list; |
||||
this.total = res.total; |
||||
this.listData.forEach(i => { |
||||
i.show = false; |
||||
if (i.status == 2) { // 已结束 |
||||
i.countDown = 0; |
||||
} else { |
||||
if (i.type == 2) { // 定时发布 |
||||
if (i.status == 0) { |
||||
// 待开始 |
||||
i.countDown = (new Date(i.startTime).getTime() - new Date().getTime()) / 1000; // 获得两个日期时间的秒数差 |
||||
} else if (i.status == 1) { |
||||
// 进行中 |
||||
i.countDown = (new Date(i.stopTime).getTime() - new Date().getTime()) / 1000; |
||||
} |
||||
} else if (i.type == 1) { // 手动发布 |
||||
if (i.status == 0) { |
||||
// 待开始 |
||||
i.countDown = 0; |
||||
} else if (i.status == 1) { |
||||
// 进行中 |
||||
i.countDown = (new Date(i.stopTime).getTime() - new Date().getTime()) / 1000; |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
this.sss = 1; |
||||
if(this.datassdata == 1){ |
||||
this.beginTimer() |
||||
} |
||||
// setTimeout(,3000) |
||||
this.listLoading = false; |
||||
}).catch(err => { |
||||
this.listLoading = false; |
||||
}); |
||||
}, |
||||
initData() { |
||||
this.$refs.table.clearSelection(); |
||||
this.pageNum = 1; |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}, |
||||
getschoolCourse() { // 获取课程下拉框数据 |
||||
this.$get(this.api.schoolCourse).then(res => { |
||||
this.curriculumList = res.data; |
||||
}).catch(err => { |
||||
console.log(err); |
||||
}); |
||||
}, |
||||
add() { |
||||
this.$router.push("add"); |
||||
}, |
||||
edit(row) { |
||||
this.$router.push(`add?id=${row.id}`); |
||||
}, |
||||
show(row) { |
||||
this.$router.push(`/achievement/teach?id=${row.id}&projectName=${row.projectName}&permissions=1`) |
||||
}, |
||||
start(row) { |
||||
this.$post(`${this.api.enableAssessment}?id=${row.id}`).then(res => { |
||||
util.successMsg("启动成功!"); |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}).catch(err => { |
||||
console.log(err); |
||||
}); |
||||
}, |
||||
finish(row) { |
||||
this.$confirm("确定要提前结束吗?", "提示", { |
||||
type: "warning" |
||||
}).then(() => { |
||||
let data = { |
||||
id: row.id, |
||||
endTime: util.formatDate("yyyy-MM-dd hh:mm:ss", new Date()), |
||||
status: 2 |
||||
}; |
||||
this.$post(`${this.api.collectPaper}?id=${row.id}`).then(res => { |
||||
util.successMsg("提前结束成功!"); |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}).catch(err => { |
||||
console.log(err); |
||||
}); |
||||
}).catch(() => { |
||||
}); |
||||
}, |
||||
delData(row) { |
||||
this.$confirm("确定要删除吗?", "提示", { |
||||
type: "warning" |
||||
}).then(() => { |
||||
this.$post(this.api.deleteAssessment, [row.id]).then(res => { |
||||
util.successMsg("删除成功"); |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}).catch(res => { |
||||
}); |
||||
}).catch(() => { |
||||
}); |
||||
}, |
||||
delAllData() { |
||||
if (this.multipleSelection.length) { |
||||
let ids = this.multipleSelection.map(item => { |
||||
return item.id; |
||||
}); |
||||
this.$confirm("确定要删除吗?", "提示", { |
||||
type: "warning" |
||||
}).then(() => { |
||||
|
||||
this.$post(this.api.deleteAssessment, ids).then(res => { |
||||
this.multipleSelection = []; |
||||
this.$refs.table.clearSelection(); |
||||
util.successMsg("删除成功"); |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}).catch(res => { |
||||
}); |
||||
if(this.multipleSelection.length === this.listData.length && this.pageNum>1) { |
||||
this.handleCurrentChange(this.pageNum - 1) |
||||
} |
||||
}).catch(() => { |
||||
}); |
||||
} else { |
||||
util.errorMsg("请先选择数据 !"); |
||||
} |
||||
}, |
||||
handleSelectionChange(val) { |
||||
this.multipleSelection = val; |
||||
}, |
||||
onSearch() { |
||||
this.pageNum = 1; |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}, |
||||
handleCurrentChange(val) { |
||||
this.pageNum = val |
||||
this.$router.push(`list?page=${val}`) |
||||
this.sss = 0 |
||||
this.getData(); |
||||
}, |
||||
transferTime(date) { |
||||
if (date == "0000-00-00 00:00:00" || !date) return "---"; |
||||
return date; |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.el-radio-group { |
||||
white-space: nowrap; |
||||
} |
||||
.el-radio.is-bordered + .el-radio.is-bordered { |
||||
margin-left: 0; |
||||
} |
||||
</style> |