master
yujialong 2 years ago
commit 196a34b1e9
  1. 3
      .browserslistrc
  2. 22
      .gitignore
  3. 6
      .prettierrc
  4. 21
      LICENSE
  5. 1
      README.md
  6. 5
      babel.config.js
  7. 13428
      package-lock.json
  8. 39
      package.json
  9. 5
      postcss.config.js
  10. 18
      public/index.html
  11. 26
      src/App.vue
  12. 263
      src/api/index.js
  13. BIN
      src/assets/img/arrow-down.png
  14. BIN
      src/assets/img/avg.png
  15. BIN
      src/assets/img/bind.png
  16. BIN
      src/assets/img/bug.png
  17. BIN
      src/assets/img/close.png
  18. BIN
      src/assets/img/cup.png
  19. BIN
      src/assets/img/date.png
  20. BIN
      src/assets/img/edit.png
  21. BIN
      src/assets/img/func.png
  22. BIN
      src/assets/img/icon-xiangyou.png
  23. BIN
      src/assets/img/icon-yigouxuan.png
  24. BIN
      src/assets/img/icon_.png
  25. BIN
      src/assets/img/icon_1.png
  26. BIN
      src/assets/img/idcard.png
  27. BIN
      src/assets/img/images/back_02.png
  28. BIN
      src/assets/img/img.jpg
  29. BIN
      src/assets/img/info1.png
  30. BIN
      src/assets/img/info2.png
  31. BIN
      src/assets/img/info3.png
  32. BIN
      src/assets/img/label.png
  33. BIN
      src/assets/img/log-bg.png
  34. BIN
      src/assets/img/log-bg1.png
  35. BIN
      src/assets/img/login-bg.png
  36. BIN
      src/assets/img/login-input.png
  37. BIN
      src/assets/img/logo-hh.png
  38. BIN
      src/assets/img/logo.png
  39. BIN
      src/assets/img/none.png
  40. BIN
      src/assets/img/open.png
  41. BIN
      src/assets/img/optimize.png
  42. BIN
      src/assets/img/person/bg.png
  43. BIN
      src/assets/img/person/manag.png
  44. BIN
      src/assets/img/person/user.png
  45. BIN
      src/assets/img/point.png
  46. BIN
      src/assets/img/report2.png
  47. BIN
      src/assets/img/report3.png
  48. BIN
      src/assets/img/report4.png
  49. BIN
      src/assets/img/report5.png
  50. BIN
      src/assets/img/rocket.png
  51. BIN
      src/assets/img/school.png
  52. BIN
      src/assets/img/search.png
  53. BIN
      src/assets/img/select.png
  54. BIN
      src/assets/img/station-bg.png
  55. BIN
      src/assets/img/station1.png
  56. BIN
      src/assets/img/station2.png
  57. BIN
      src/assets/img/student1.png
  58. BIN
      src/assets/img/student2.png
  59. BIN
      src/assets/img/student3.png
  60. BIN
      src/assets/img/student4.png
  61. BIN
      src/assets/img/total.png
  62. BIN
      src/assets/img/trash.png
  63. BIN
      src/assets/img/upload.png
  64. 64
      src/components/breadcrumb/index.vue
  65. 8
      src/components/org-tree/index.js
  66. 486
      src/components/org-tree/src/model/node.js
  67. 340
      src/components/org-tree/src/model/tree-store.js
  68. 27
      src/components/org-tree/src/model/util.js
  69. 279
      src/components/org-tree/src/tree-node.vue
  70. 496
      src/components/org-tree/src/tree.vue
  71. 131
      src/components/pdf/index.vue
  72. 255
      src/components/quill/index.vue
  73. 16
      src/components/quill/options.js
  74. 8
      src/components/student-tree/index.js
  75. 486
      src/components/student-tree/src/model/node.js
  76. 340
      src/components/student-tree/src/model/tree-store.js
  77. 27
      src/components/student-tree/src/model/util.js
  78. 279
      src/components/student-tree/src/tree-node.vue
  79. 496
      src/components/student-tree/src/tree.vue
  80. 30
      src/i18n/index.js
  81. 42
      src/layouts/footer/index.vue
  82. 178
      src/layouts/header/index.vue
  83. 237
      src/layouts/home/index.vue
  84. 131
      src/layouts/navbar/index.vue
  85. 20
      src/libs/auth/generateBtnPermission.js
  86. 6
      src/libs/bus.js
  87. 10
      src/libs/random_str.js
  88. 18
      src/libs/resize/index.js
  89. 28
      src/libs/route/addRoutes.js
  90. 6
      src/libs/route/resetRouter.js
  91. 43
      src/libs/util.cookies.js
  92. 83
      src/libs/util.db.js
  93. 227
      src/libs/util.js
  94. 45
      src/main.js
  95. 11
      src/mixins/app.js
  96. 8
      src/mixins/setBackground/index.js
  97. 343
      src/pages/account/login/index.vue
  98. 54
      src/pages/account/redirect/index.vue
  99. 635
      src/pages/assessment/add/index.vue
  100. 445
      src/pages/assessment/list/index.vue
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,3 @@
> 1%
last 2 versions
not ie <= 8

22
.gitignore vendored

@ -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 @@
# 粒子研究院后台前端

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
}

13428
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -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)));
}
//vuexlocalStorage
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`
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

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) { // updown
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) { // leftright
ev.preventDefault();
currentItem.click(); //
}
const hasInput = currentItem.querySelector('[type="checkbox"]');
if ([13, 32].indexOf(keyCode) > -1 && hasInput) { // space entercheckbox
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) { // updown
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) { // leftright
ev.preventDefault();
currentItem.click(); //
}
const hasInput = currentItem.querySelector('[type="checkbox"]');
if ([13, 32].indexOf(keyCode) > -1 && hasInput) { // space entercheckbox
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() // logViewfalse
},
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">&nbsp;</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>

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

Loading…
Cancel
Save