TeachingRelationshipComponent.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. <template>
  2. <div class="page_item step_2_content page_search">
  3. <div class="container_left">
  4. <FormSearchGroup :searchList="searchList" @searchChange="HandleSearchChange"></FormSearchGroup>
  5. <Table
  6. :hidePagination="true"
  7. class="teacher_table"
  8. :tableColumns="teacherTableColumns"
  9. :tableData="teacherList"
  10. :tableRowClassName="tableRowClassName"
  11. @rowClick="HandleSelectTeacher"
  12. ></Table>
  13. </div>
  14. <div class="container_right">
  15. <div class="search_content">
  16. <!-- 年级切换 -->
  17. <div class="content_left">
  18. <TabSearch
  19. class="grade_tab"
  20. tabType="box"
  21. :tabList="gradeList"
  22. :defaultTabKey="searchParmams.gradeId"
  23. @tabChange="(val)=>HandleTabChange(val,'grade')">
  24. </TabSearch>
  25. </div>
  26. <div class="content_right">
  27. <el-button class="delete_button_border_no_bg" @click="ClearTeachingRelationship">清空授课</el-button>
  28. <el-button class="button_refresh">批量导出</el-button>
  29. <el-button class="button_refresh">批量导入</el-button>
  30. <el-button class="button_background" @click="FinishTeachingRelationship">完成</el-button>
  31. </div>
  32. </div>
  33. <div class="page_jg_20"></div>
  34. <div class="search_content">
  35. <div class="content_left">
  36. <TabSearch
  37. tabType="line"
  38. :tabList="tabList"
  39. :defaultTabKey="searchParmams.classType"
  40. @tabChange="(val)=>HandleTabChange(val,'classType')"> </TabSearch>
  41. </div>
  42. <div class="content_right">
  43. <span v-if="isConfigMode" class="config_switch_desc">
  44. {{ selectedTeacher?.teacherId ? `当前选中的教师:${selectedTeacher.teacherName} ${selectedTeacher.userAccount}` : '暂无选中的教师' }}
  45. </span>
  46. <el-switch v-model="isConfigMode"
  47. style="--el-switch-on-color: #2E64FA"
  48. active-text="选中教师配置任课关系" />
  49. </div>
  50. </div>
  51. <div class="page_solid_line"></div>
  52. <div class="page_jg_20"></div>
  53. <div class="end_item">
  54. <!-- 行政班渲染表数据 -->
  55. <Table v-if="searchParmams.classType === 'xingzeng'"
  56. :tableColumns="filterAdministrativeClassTableColumns"
  57. :tableData="administrativeClassTableData"
  58. :hidePagination="true">
  59. <template #className="{row}">
  60. {{ row.className.className }}
  61. </template>
  62. <template v-for="column in filterAdministrativeClassTableColumns.slice(1)" :key="column.name" v-slot:[column.name]="{row}">
  63. <div class="cell_container" @click="HandleAddTeacher(row, column.name,'administrative')">
  64. <template v-if="row?.[column.name] && row?.[column.name]?.length > 0">
  65. <div v-for="(teacher, idx) in row?.[column.name]" :key="teacher.teacherId">
  66. <el-tooltip
  67. effect="dark"
  68. :content="`${teacher.teacherName} ${teacher.userAccount}`"
  69. placement="top-start"
  70. >
  71. <div v-if="isConfigMode" class="teacher_tag">
  72. <span :title="teacher.teacherName">{{ teacher.teacherName }}</span>
  73. <el-icon class="delete_icon" @click.stop="HandleRemoveTeacher(row, column.name, idx,'administrative')"><Close /></el-icon>
  74. </div>
  75. <div v-else class="disabled_teacher">
  76. <span :title="teacher.teacherName">{{ teacher.teacherName }}</span>
  77. </div>
  78. </el-tooltip>
  79. </div>
  80. </template>
  81. <template v-else>
  82. <span v-if="isConfigMode" class="placeholder_text" >请选择教师</span>
  83. <span v-else>-</span>
  84. </template>
  85. </div>
  86. </template>
  87. </Table>
  88. <!-- 教学班渲染表数据 -->
  89. <Table v-else-if="searchParmams.classType === 'teach'"
  90. :tableColumns="filterTeachingClassTableColumns"
  91. :tableData="teachingClassTableData"
  92. :hidePagination="true">
  93. <template v-for="column in filterTeachingClassTableColumns" :key="column.name" v-slot:[column.name]="{row}">
  94. <div class="cell_container teaching_cell_container" @click="HandleAddTeacher(row, column.name,'teach')">
  95. <div class="class_name">
  96. {{ row?.[column.name]?.className.className }}
  97. </div>
  98. <div class="teaching_teacher_list" v-if="row?.[column.name]?.className?.className && row?.[column.name]?.teacherList?.length > 0">
  99. <div v-for="(teacher, idx) in row?.[column.name]?.teacherList" :key="teacher.teacherId">
  100. <el-tooltip
  101. effect="dark"
  102. :content="`${teacher.teacherName} ${teacher.userAccount}`"
  103. placement="top-start"
  104. >
  105. <div v-if="isConfigMode" class="teacher_tag">
  106. <span :title="teacher.teacherName">{{ teacher.teacherName }}</span>
  107. <el-icon class="delete_icon" @click.stop="HandleRemoveTeacher(row, column.name, idx,'teach')"><Close /></el-icon>
  108. </div>
  109. <div v-else class="disabled_teacher">
  110. <span :title="teacher.teacherName">{{ teacher.teacherName }}</span>
  111. </div>
  112. </el-tooltip>
  113. </div>
  114. </div>
  115. <template v-else-if="row?.[column.name]?.className?.className">
  116. <span v-if="isConfigMode" class="placeholder_text" >请选择教师</span>
  117. <span v-else>-</span>
  118. </template>
  119. </div>
  120. </template>
  121. </Table>
  122. </div>
  123. </div>
  124. </div>
  125. </template>
  126. <script setup lang="ts">
  127. import { ref, reactive, onMounted } from 'vue';
  128. import { useRoute } from 'vue-router';
  129. import { useStore } from 'vuex';
  130. import { Close } from '@element-plus/icons-vue';
  131. import FormSearchGroup from '@/baseComponents/FormSearchGroup.vue';
  132. import Table from '@/baseComponents/Table.vue';
  133. import TabSearch from '@/baseComponents/TabSearch.vue';
  134. import {
  135. getTeachingRelationshipTableDataApi, getTeacherListDataApi,
  136. saveTeachingRelationshipApi, deleteTeachingRelationshipApi,
  137. finishTeachingRelationshipApi,clearTeachingRelationshipApi
  138. } from '@/api/teachingRelationshipComponent'
  139. import {getEvaluationDataDetailApi} from '@/api/evaluationDataComponent'
  140. import {administrativeClassTableColumns,teacherTableColumns,teachingClassTableColumns} from './table'
  141. import { ElMessage } from 'element-plus';
  142. import router from '@/router';
  143. const route = useRoute()
  144. const store = useStore()
  145. // 配置模式开关
  146. const isConfigMode = ref(true);
  147. // 1. 查询参数 列表 配置
  148. const tabList = ref([
  149. {
  150. label:'行政班',
  151. key:'xingzeng',
  152. },
  153. {
  154. label:'教学班',
  155. key:'teach',
  156. },
  157. ]);
  158. // 搜索列表配置
  159. const searchList = ref([
  160. {
  161. key: 'searchKey',
  162. type: 'input',
  163. placeholder: '教师姓名、账号',
  164. }
  165. ]);
  166. // 查询参数
  167. const searchParmams = ref({
  168. gradeId:'',
  169. classType:'teach',
  170. searchKey:''
  171. })
  172. // 年级列表
  173. interface GradeItm {
  174. label:string,
  175. key:string
  176. }
  177. const gradeList = ref<GradeItm[]>([])
  178. interface GradeClassType {
  179. gradeId:string,
  180. administrativeClassStatus: number|string, // 0关闭 1开启
  181. teachingClassStatus: number|string // 0关闭 1开启
  182. }
  183. const gradeClassTypeStatus = ref<GradeClassType[]>([]) //存储 不同年级的班级类型状态
  184. // 获取年级列表信息
  185. const GetGradeList = async()=>{
  186. let id = route.query.id
  187. let res = await getEvaluationDataDetailApi(id)
  188. if(res?.code == 200){
  189. gradeList.value = res?.data.map((item,index)=>{
  190. gradeClassTypeStatus.value[index] = {
  191. gradeId:item.id,
  192. administrativeClassStatus: item.administrativeClassStatus,
  193. teachingClassStatus: item.teachingClassStatus
  194. }
  195. return {
  196. label:item.gradeName,
  197. key:item.id
  198. }
  199. }) || []
  200. searchParmams.value.gradeId = gradeList.value[0].key
  201. }
  202. }
  203. // 2. 教师列表数据
  204. interface TeacherItm {
  205. id: null | string
  206. teacherId: string
  207. teacherName: string
  208. userAccount: string
  209. }
  210. const teacherList = ref<TeacherItm[]>([]);
  211. // 选中的教师ID
  212. const selectedTeacher = ref()
  213. // 获取教师列表数据
  214. const GetTeacherTableData = async ()=>{
  215. let params = {
  216. evaluationId: route.query.id,
  217. evaluationGradeId: searchParmams.value.gradeId,
  218. teacherName: searchParmams.value.searchKey
  219. }
  220. let res = await getTeacherListDataApi(params)
  221. if(res?.code == 200){
  222. teacherList.value = res.data || []
  223. }
  224. }
  225. // 行选中 选择教师
  226. const HandleSelectTeacher = (teacher) => {
  227. selectedTeacher.value = {
  228. teacherId:teacher.teacherId,
  229. teacherName:teacher.teacherName,
  230. userAccount:teacher.userAccount,
  231. };
  232. };
  233. // 设置表行选中 样式
  234. const tableRowClassName = ({row,rowIndex})=>{
  235. if(row.teacherId == selectedTeacher.value?.teacherId){
  236. return 'active_row'
  237. }
  238. }
  239. // 搜索变化处理
  240. const HandleSearchChange = async (params) => {
  241. console.log('搜索参数', params);
  242. searchParmams.value.searchKey = params.searchKey
  243. // 重查教师账号信息
  244. await GetTeacherTableData()
  245. };
  246. // 3. 授课关系数据
  247. // 行政班 表头
  248. const filterAdministrativeClassTableColumns = ref([...administrativeClassTableColumns.value])
  249. // 教学班 表头
  250. const filterTeachingClassTableColumns = ref([...teachingClassTableColumns.value])
  251. const administrativeClassTableData = ref([]); // 行政班授课关系数据
  252. const teachingClassTableData = ref([]) //教学班授课关系数据
  253. // 获取表格授课关系配置数据
  254. const GetTeachingRelationshipData = async ()=>{
  255. teachingClassTableData.value = []
  256. administrativeClassTableData.value = []
  257. let res = await getTeachingRelationshipTableDataApi(searchParmams.value.gradeId)
  258. if(res?.code == 200){
  259. // 行政班数据 行政班科目 教学班数据 教学班科目
  260. const {administrativeClassData,administrativeSubjectLists,
  261. teachingClassData, teachingSubjectLists,
  262. } = res.data
  263. // 行政班数据渲染
  264. let administrativeHeaderColumn = administrativeSubjectLists.map(item=>{
  265. return {
  266. name: item.subjectId,
  267. label: item.name,
  268. width: '140',
  269. custom: true,
  270. fixed:'',
  271. hidden: false,
  272. align:'center',
  273. }
  274. })
  275. filterAdministrativeClassTableColumns.value = [...administrativeClassTableColumns.value,...administrativeHeaderColumn]
  276. administrativeClassTableData.value = administrativeClassData.map(item=>{
  277. let subjectOpt = {}
  278. item.subjectData.map(subject=>{
  279. subjectOpt[subject.subjectId] = subject.teacherData
  280. })
  281. return {
  282. className:{
  283. className:item.className,
  284. evaluationClassId:item.evaluationClassId
  285. },
  286. ...subjectOpt
  287. }
  288. })
  289. // 教学班数据渲染
  290. let teachingHeaderColumn = teachingSubjectLists.map(item=>{
  291. return {
  292. name: item.subjectId,
  293. label: item.name,
  294. width: '140',
  295. custom: true,
  296. fixed:'',
  297. hidden: false,
  298. align:'center',
  299. }
  300. })
  301. filterTeachingClassTableColumns.value = [...teachingClassTableColumns.value,...teachingHeaderColumn];
  302. teachingClassData.map((subjectItm:any)=>{
  303. let subjectId = subjectItm.subjectId
  304. subjectItm?.classData.map((classItm,idx)=>{
  305. let subjectOpt = {
  306. className:{
  307. className:classItm.className,
  308. evaluationClassId:classItm.evaluationClassId
  309. },
  310. teacherList:classItm.teacherData
  311. }
  312. if(typeof teachingClassTableData.value[Number(idx)] === 'object'){
  313. teachingClassTableData.value[Number(idx)][subjectId] = subjectOpt
  314. }else{
  315. teachingClassTableData.value[Number(idx)] = {}
  316. teachingClassTableData.value[Number(idx)][subjectId] = subjectOpt
  317. }
  318. });
  319. })
  320. }
  321. }
  322. // 添加教师
  323. const HandleAddTeacher = async (row, cell,type) => {
  324. if (!isConfigMode.value) return;
  325. if (!selectedTeacher.value) return;
  326. let teacherMsg = {
  327. teacherId: selectedTeacher.value?.teacherId,
  328. teacherName: selectedTeacher.value?.teacherName,
  329. userAccount:selectedTeacher.value?.userAccount
  330. }
  331. switch(type){
  332. case 'administrative':{
  333. // 行政班
  334. if (!row[cell]) {
  335. row[cell] = [];
  336. }
  337. let res = await saveTeachingRelationshipApi({
  338. ...teacherMsg,
  339. evaluationId:route.query.id,
  340. evaluationGradeId:searchParmams.value.gradeId,
  341. evaluationSubjectId:cell,
  342. evaluationClassId:row.className.evaluationClassId,
  343. classType:searchParmams.value.classType == 'teach' ? 1 : 0 //班级类型,0-行政班,1-教学班
  344. })
  345. if(res?.code == 200){
  346. ElMessage.success(res?.msg)
  347. row[cell].push({...teacherMsg,id:res?.data});
  348. }
  349. break
  350. }
  351. case 'teach':{
  352. if (row?.[cell]?.className?.className && !row?.[cell]?.teacherList) {
  353. row[cell] = {
  354. name:row?.[cell]?.className,
  355. teacherList:[]
  356. }
  357. }
  358. let res = await saveTeachingRelationshipApi({
  359. ...teacherMsg,
  360. evaluationId:route.query.id,
  361. evaluationGradeId:searchParmams.value.gradeId,
  362. evaluationSubjectId:cell,
  363. evaluationClassId:row[cell].className.evaluationClassId,
  364. classType:searchParmams.value.classType == 'teach' ? 1 : 0 //班级类型,0-行政班,1-教学班
  365. })
  366. if(res?.code == 200){
  367. ElMessage.success(res?.msg)
  368. row[cell].teacherList.push({...teacherMsg,id:res?.data});
  369. }
  370. break
  371. }
  372. }
  373. };
  374. // 移除教师
  375. const HandleRemoveTeacher = async (row, cell, index,type) => {
  376. if (!isConfigMode.value) return;
  377. let id = null
  378. let res = undefined
  379. switch(type){
  380. case 'teach':{
  381. id = row[cell].teacherList[index].id
  382. res = await deleteTeachingRelationshipApi(id)
  383. if(res?.code == 200){
  384. ElMessage.success(res?.msg)
  385. row[cell].teacherList.splice(index, 1);
  386. }
  387. break
  388. }
  389. case 'administrative':{
  390. id = row[cell][index].id
  391. res = await deleteTeachingRelationshipApi(id)
  392. if(res?.code == 200){
  393. ElMessage.success(res?.msg)
  394. row[cell].splice(index, 1);
  395. }
  396. break
  397. }
  398. }
  399. };
  400. // 4 综合.
  401. // 标签切换处理 年级/班级类型
  402. const HandleTabChange = async (val,type) => {
  403. console.log('当前选中的tab',val,type)
  404. switch(type){
  405. case 'grade':{
  406. let aimClassTypeStatus = gradeClassTypeStatus.value.find(item=>{
  407. return item.gradeId == val
  408. })
  409. tabList.value = []
  410. if(aimClassTypeStatus.administrativeClassStatus === 1){
  411. searchParmams.value.classType = 'xingzeng'
  412. tabList.value.push(
  413. {
  414. label:'行政班',
  415. key:'xingzeng',
  416. },
  417. )
  418. }
  419. if(aimClassTypeStatus.teachingClassStatus === 1){
  420. if(aimClassTypeStatus.administrativeClassStatus !== 1){
  421. searchParmams.value.classType = 'teach'
  422. }
  423. tabList.value.push(
  424. {
  425. label:'教学班',
  426. key:'teach',
  427. },
  428. )
  429. }
  430. searchParmams.value.gradeId = val
  431. await reloadData()
  432. break
  433. }
  434. case 'classType':{
  435. searchParmams.value.classType = val
  436. break
  437. }
  438. }
  439. };
  440. // 5. 完成授课关系
  441. const FinishTeachingRelationship = async ()=>{
  442. let params = gradeList.value.map(grade=>{
  443. return grade.key
  444. })
  445. let res = await finishTeachingRelationshipApi(params)
  446. if(res?.code == 200){
  447. // 更新全局状态 并 跳转到下一步
  448. await store.dispatch('updateEvaluationManageStepData',route.query.id)
  449. // 线上匿名 线下答卷
  450. // store.state.evaluationManageStepData.evaluationType // 评价方式 0-线上匿名,1-线下答卷,2-线上导入,3-线上实名
  451. if(store.state.evaluationManageStepData.evaluationType === 0){
  452. store.commit('updateEvaluationManageStepData',{currentActiveStepName:'questionnaireTemplate'}) //更新全局活跃路由
  453. router.push(`questionnaireTemplate?id=${route.query.id}`)
  454. }else if(store.state.evaluationManageStepData.evaluationType === 1){
  455. store.commit('updateEvaluationManageStepData',{currentActiveStepName:'numberMapping'}) //更新全局活跃路由
  456. router.push(`numberMapping?id=${route.query.id}`)
  457. }
  458. }
  459. }
  460. // 6. 清空授课关系
  461. const ClearTeachingRelationship = async ()=>{
  462. let id = searchParmams.value.gradeId
  463. let classType = searchParmams.value.classType == 'teach' ? 1 : 0 //班级类型,0-行政班,1-教学班
  464. let res = await clearTeachingRelationshipApi(id,classType)
  465. if(res?.code == 200){
  466. ElMessage.success(res?.msg)
  467. // 重查页面
  468. await GetTeachingRelationshipData()
  469. }
  470. }
  471. // 重新加载页面数据
  472. const reloadData =async ()=>{
  473. await GetTeacherTableData()
  474. await GetTeachingRelationshipData()
  475. }
  476. onMounted( async ()=>{
  477. await GetGradeList()
  478. HandleTabChange(searchParmams.value.gradeId,'grade')
  479. })
  480. </script>
  481. <style lang="scss" scoped>
  482. .step_2_content {
  483. height: calc(100%);
  484. display: flex;
  485. flex-direction: row;
  486. justify-content: space-between;
  487. align-items: stretch;
  488. gap:20px;
  489. overflow: auto;
  490. .container_left {
  491. flex-shrink: 0;
  492. display: flex;
  493. flex-direction: column;
  494. overflow: auto;
  495. width: 200px;
  496. height: 100%;
  497. .teacher_table{
  498. margin-top: 16px;
  499. :deep(.el-table__body){
  500. tr.active_row td
  501. {
  502. background-color: rgba(46, 100, 250,0.1) !important;
  503. border:none;
  504. color: #2e64fa;
  505. }
  506. }
  507. }
  508. }
  509. .container_right {
  510. height: 100%;
  511. display: flex;
  512. flex-direction: column;
  513. overflow: auto;
  514. .config_switch_desc{
  515. font-size: 14px;
  516. }
  517. .end_item{
  518. overflow-y: auto;
  519. }
  520. .grade_tab{
  521. gap:10px;
  522. :deep(> span){
  523. border: 1px solid #dcdfe6 !important;
  524. border-radius: 6px;
  525. }
  526. }
  527. }
  528. }
  529. /* 单元格内部组件样式 */
  530. .cell_container {
  531. text-align: center;
  532. display: flex;
  533. justify-content: flex-start;
  534. flex-wrap: wrap;
  535. align-items: center;
  536. padding: 8px;
  537. gap: 8px;
  538. &.teaching_cell_container{
  539. flex-wrap: nowrap;
  540. .class_name{
  541. word-break: keep-all;
  542. }
  543. }
  544. .teaching_teacher_list{
  545. display: flex;
  546. justify-content: flex-start;
  547. flex-wrap: wrap;
  548. align-items: center;
  549. gap: 8px;
  550. }
  551. .disabled_teacher,
  552. .placeholder_text {
  553. width: 90px;
  554. height: 36px;
  555. font-weight: 400;
  556. font-size: 14px;
  557. color: #CCCCCC;
  558. text-align: center;
  559. line-height: 36px;
  560. background-color: #f3f3f3;
  561. border-radius: 4px;
  562. cursor: pointer;
  563. }
  564. .disabled_teacher{
  565. color:#666666;
  566. }
  567. .teacher_tag {
  568. display: flex;
  569. justify-content: space-between;
  570. align-items: center;
  571. padding: 0 8px;
  572. min-width: 90px;
  573. height: 36px;
  574. font-weight: 400;
  575. font-size: 14px;
  576. color: #2E64FA;
  577. text-align: center;
  578. line-height: 36px;
  579. background: rgba(46, 100, 250, 0.1);
  580. border-radius: 4px;
  581. .delete_icon {
  582. cursor: pointer;
  583. font-size: 12px;
  584. }
  585. }
  586. }
  587. </style>