classSchedule.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <template>
  2. <div class="courseArrangeClassSchedule">
  3. <van-cell-group title="学生">
  4. <van-cell
  5. v-for="stu in students"
  6. :key="stu.userId"
  7. :title="stu.username"
  8. :value="stu.phone"
  9. ></van-cell>
  10. </van-cell-group>
  11. <van-cell-group title="课程信息">
  12. <!-- <van-cell
  13. title="分部"
  14. :value="formData.fenbu ? formData.fenbu.name : '请选择'"
  15. is-link
  16. @click="show.fenbu = true"
  17. ></van-cell> -->
  18. <van-cell
  19. title="课程类型"
  20. :value="activeCourse.name"
  21. ></van-cell>
  22. <!-- <van-cell
  23. title="课程形式"
  24. :value="activeCourse.name"
  25. ></van-cell> -->
  26. <van-cell title="排课声部" :value="activeCourse.subjectName"></van-cell>
  27. <van-cell
  28. title="乐团主管"
  29. :value="educational.userId ? educational.realName : '请选择'"
  30. is-link
  31. @click="onShowEducational"
  32. ></van-cell>
  33. </van-cell-group>
  34. <van-cell-group title="课程安排">
  35. <van-cell
  36. title="单课时时长"
  37. :value="activeCourse.courseTime + '分钟'"
  38. ></van-cell>
  39. <van-cell title="可排课时" :value="courseNum"></van-cell>
  40. <!-- <van-cell
  41. v-if="activeCourse.coursesStartTime"
  42. title="最早排课时间"
  43. :value="activeCourse.coursesStartTime"
  44. ></van-cell>
  45. <van-cell
  46. v-if="activeCourse.coursesEndTime"
  47. title="最晚排课时间"
  48. :value="activeCourse.coursesEndTime"
  49. ></van-cell> -->
  50. <van-field
  51. label="线上课次数"
  52. input-align="right"
  53. placeholder="请输入排课次数"
  54. type="digit"
  55. v-model="frequency.online"
  56. :formatter="formatterOnline"
  57. ></van-field>
  58. <van-field
  59. v-if="this.activityId != 0"
  60. label="线下课次数"
  61. input-align="right"
  62. placeholder="请输入排课次数"
  63. type="digit"
  64. v-model="frequency.offline"
  65. :formatter="formatterOffline"
  66. ></van-field>
  67. <van-cell
  68. v-if="frequency.offline > 0"
  69. title="线下课地址"
  70. :value="adddress.id ? adddress.name : '请选择'"
  71. is-link
  72. @click="onShowAddress"
  73. ></van-cell>
  74. <van-cell
  75. title="排课开始时间"
  76. :value="formatterDate(formData.time)"
  77. is-link
  78. @click="show.date = true"
  79. ></van-cell>
  80. </van-cell-group>
  81. <van-cell-group style="margin: 12px 16px;">
  82. <van-cell v-for="(item, index) in scheduleList" :key="index">
  83. <template #title>
  84. <div class="course-item">
  85. <span>{{ item.type }}</span>
  86. <span style="margin: 0 16px;">{{ item.weekStr }}</span>
  87. <span>{{ item.startTime }} ~ {{ item.endTime }}</span>
  88. <van-icon
  89. class="clear-icon"
  90. name="cross"
  91. size="18"
  92. @click="onDeleteSchedule(index)"
  93. />
  94. </div>
  95. </template>
  96. </van-cell>
  97. <van-cell>
  98. <template #title>
  99. <div class="course-item" style="justify-content: center;">
  100. <van-button size="small" icon="plus" @click="show.course = true"
  101. >添加课时安排</van-button
  102. >
  103. </div>
  104. </template>
  105. </van-cell>
  106. </van-cell-group>
  107. <van-cell
  108. title="排课列表"
  109. value="查看"
  110. is-link
  111. @click="onShowCourseList"
  112. ></van-cell>
  113. <div style="padding: 10px 30px">
  114. <van-button round block type="info" @click="onSubmit">确认</van-button>
  115. </div>
  116. <van-popup v-model="show.teacher" position="bottom">
  117. <!-- <TeacherModal
  118. @close="show.teacher = false"
  119. @onSelectTeacher="onSelectTeacher"
  120. /> -->
  121. </van-popup>
  122. <!-- 排课开始时间 -->
  123. <van-popup v-model="show.date" position="bottom">
  124. <van-datetime-picker
  125. v-model="formData.time"
  126. type="date"
  127. @cancel="show.date = false"
  128. @confirm="show.date = false"
  129. />
  130. </van-popup>
  131. <!-- 课时安排 -->
  132. <van-popup v-model="show.course" position="bottom">
  133. <CourseModal
  134. :scheduleList="scheduleList"
  135. :singleClassMinutes="activeCourse.courseTime"
  136. @close="show.course = false"
  137. />
  138. </van-popup>
  139. <!-- 课表展示 -->
  140. <van-popup v-model="show.timeTable" position="bottom">
  141. <van-row>
  142. <van-col span="12">上课类型</van-col>
  143. <van-col span="12">上课时间</van-col>
  144. </van-row>
  145. <div class="tableContainer">
  146. <van-row v-for="(item, index) in timeTable" :key="index">
  147. <van-col span="12">
  148. {{ item.teachMode == "ONLINE" ? "线上" : "线下" }}
  149. </van-col>
  150. <van-col span="12">
  151. {{ item.classDate }} {{ item.startClassTimeStr }}
  152. </van-col>
  153. </van-row>
  154. </div>
  155. </van-popup>
  156. <!-- 乐团主管 -->
  157. <van-popup v-model="show.fenbu" position="bottom">
  158. <van-picker
  159. title="乐团主管"
  160. :loading="educationalLoading"
  161. :columns="educationalList"
  162. value-key="userName"
  163. show-toolbar
  164. @cancel="show.fenbu = false"
  165. @confirm="onSetEducational"
  166. />
  167. </van-popup>
  168. <!-- 选择线下课地址 -->
  169. <van-popup
  170. v-model="show.address"
  171. :lock-scroll="true"
  172. position="bottom"
  173. :style="{ height: '5.16rem', overflow: 'hidden' }"
  174. class="studentChose"
  175. >
  176. <AddressList
  177. @close="show.address = false"
  178. :teacherId="teacher.id"
  179. @submit="onSelectAddress"
  180. />
  181. </van-popup>
  182. </div>
  183. </template>
  184. <script>
  185. import AddressList from "./modal/addressList.vue";
  186. // import TeacherModal from "../applyActive/modal/teacher-modal";
  187. import CourseModal from "./modal/course";
  188. import MHeader from "../../components/MHeader.vue";
  189. import { findSubSubjects, findEducationUsers } from "@/api/teacher";
  190. import {
  191. createPracticeGroup,
  192. createVipCourse,
  193. getActiveCourseInfo,
  194. } from "./api.js";
  195. import dayjs from "dayjs";
  196. export default {
  197. name: "classSchedule",
  198. components: { CourseModal, AddressList, MHeader},
  199. data() {
  200. return {
  201. activityId: this.$route.query.activityId,
  202. show: {
  203. fenbu: false,
  204. teacher: false,
  205. date: false,
  206. course: false,
  207. timeTable: false,
  208. address: false,
  209. },
  210. students: [],
  211. activeCourse: {},
  212. frequency: {
  213. // 线上,线下排课
  214. online: "",
  215. offline: "",
  216. },
  217. formData: {
  218. time: new Date(),
  219. fenbu: "",
  220. fullOrganId: "",
  221. },
  222. teacher: {},
  223. adddress: {},
  224. educational: {},
  225. scheduleList: [], // 课时安排
  226. timeTable: [], // 生成的课表
  227. allBranch: [], // 分部
  228. educationalList: [], //乐团主管
  229. educationalLoading: false
  230. };
  231. },
  232. computed: {
  233. courseNum() {
  234. const mins = this.students.map((n) => parseInt(n.subCourseNum));
  235. return Math.min(...mins);
  236. },
  237. },
  238. async mounted() {
  239. this.students = this.$store.state.activeStudents;
  240. this.activeCourse = this.$store.state.activeCourse;
  241. // await this.$store.dispatch("setAllBranch");
  242. // this.allBranch = this.$store.state.allBranch;
  243. // if (this.allBranch.length) {
  244. // this.formData.fenbu = this.allBranch[0];
  245. // }
  246. },
  247. methods: {
  248. onSelectTeacher(val) {
  249. this.teacher = {
  250. id: val.id,
  251. realName: val.realName,
  252. };
  253. },
  254. onSetBranch(val) {
  255. // 设置分部
  256. console.log(val);
  257. this.formData.fenbu = val;
  258. this.show.fenbu = false;
  259. },
  260. formatterOnline(val) {
  261. // 线上排课次数
  262. const n = this.courseNum - this.frequency.offline || 0;
  263. if (val > n) {
  264. return n;
  265. }
  266. return val;
  267. },
  268. formatterOffline(val) {
  269. // 线下排课次数
  270. const n = this.courseNum - this.frequency.online || 0;
  271. if (val > n) {
  272. return n;
  273. }
  274. return val;
  275. },
  276. formatterDate(date) {
  277. return dayjs(date).format("YYYY-MM-DD");
  278. },
  279. onDeleteSchedule(index) {
  280. // 删除课时安排
  281. this.scheduleList.splice(index, 1);
  282. },
  283. async onShowEducational(){ // 显示乐团主管
  284. this.show.fenbu = true
  285. if (this.educationalList.length) return
  286. this.educationalLoading = true
  287. try {
  288. const {data} = await findEducationUsers()
  289. // console.log(data)
  290. if (data.code == 200 && data.data && data.data.EDUCATION) {
  291. const EDUCATION = data.data.EDUCATION || [];
  292. this.educationalList = EDUCATION;
  293. } else {
  294. this.$toast("暂无乐团主管");
  295. }
  296. } catch (error) {
  297. }
  298. this.educationalLoading = false
  299. },
  300. onSetEducational(val){
  301. this.educational = val
  302. this.show.fenbu = false;
  303. },
  304. onShowAddress() {
  305. this.show.address = true;
  306. },
  307. onSelectAddress(val) {
  308. // 设置线下课地址
  309. this.adddress = val;
  310. this.show.address = false;
  311. },
  312. onShowCourseList(isShowPopup = true) {
  313. // 展示排课列表
  314. if (!this.scheduleList.length) {
  315. return this.$toast("缺少课时安排");
  316. }
  317. const online = parseInt(this.frequency.online || 0);
  318. const offline = parseInt(this.frequency.offline || 0);
  319. if (!online && !offline) {
  320. return this.$toast("缺少线上或线下上课次数");
  321. }
  322. if (online + offline > this.courseNum) {
  323. return this.$toast("线上 + 线下 总课时不能大于可排课次数");
  324. }
  325. if (online && !this.scheduleList.filter((n) => n.type == "线上").length) {
  326. return this.$toast("课时安排缺少线上课类型");
  327. }
  328. if (
  329. offline &&
  330. !this.scheduleList.filter((n) => n.type == "线下").length
  331. ) {
  332. return this.$toast("课时安排缺少线下课类型");
  333. }
  334. if (offline && !this.adddress.id) {
  335. this.$toast("请选择线下教学地址");
  336. return;
  337. }
  338. if (!this.formData.time) {
  339. return this.$toast("缺少排课开始时间");
  340. }
  341. this.setTimeTable();
  342. isShowPopup && (this.show.timeTable = true);
  343. },
  344. setTimeTable() {
  345. // 重置排课列表
  346. let timeTable = [];
  347. let online = parseInt(this.frequency.online);
  348. let offline = parseInt(this.frequency.offline);
  349. let scheduleList = this.scheduleList;
  350. let totalCount = parseInt(online || 0) + parseInt(offline || 0);
  351. let dateOperation = new Date(this.formData.time);
  352. // console.log(dateOperation.toLocaleDateString());
  353. let forMark = 0;
  354. // console.log('totalCount',totalCount)
  355. while (totalCount && totalCount > 0) {
  356. for (let i = 0; i < scheduleList.length; i++) {
  357. if (online == 0 && offline == 0) break;
  358. let num = scheduleList[i].weekIndex - dateOperation.getDay();
  359. // 如果是同一天一个周期会出现排课都排到一天
  360. if (forMark > 0 && num == 0 && i == 0) {
  361. num = num + 7;
  362. }
  363. if (num < 0) {
  364. // 如果为负数则为下周
  365. num = num + 7;
  366. }
  367. let dataStr = this.getThinkDate(dateOperation, num);
  368. // 判断是否大于当前时间
  369. let nowGetTime = new Date().getTime();
  370. let courseTime = new Date(
  371. dataStr.replace(/-/gi, "/") +
  372. " " +
  373. scheduleList[i].startTime +
  374. ":00"
  375. ).getTime();
  376. // console.log(nowGetTime < courseTime)
  377. if (nowGetTime < courseTime) {
  378. let tempArr = {
  379. classDate: dataStr,
  380. startClassTimeStr: scheduleList[i].startTime,
  381. endClassTimeStr: scheduleList[i].endTime,
  382. };
  383. if (scheduleList[i].type == "线上" && online > 0) {
  384. tempArr.teachMode = "ONLINE";
  385. timeTable.push(tempArr);
  386. online--;
  387. totalCount--;
  388. } else if (scheduleList[i].type == "线下" && offline > 0) {
  389. tempArr.teachMode = "OFFLINE";
  390. timeTable.push(tempArr);
  391. offline--;
  392. totalCount--;
  393. }
  394. }
  395. }
  396. // 加一周
  397. if (scheduleList.length == 1) {
  398. dateOperation.setDate(dateOperation.getDate() + 7);
  399. } else if (
  400. scheduleList.every((item) => item.weekStr === scheduleList[0].weekStr)
  401. ) {
  402. // 标记循环次数(标记判断课程安排是不是同一天)
  403. forMark++;
  404. }
  405. }
  406. timeTable.sort((a, b) => {
  407. let aStr = dayjs(
  408. dayjs(a.classDate).format("YYYY-MM-DD") +
  409. " " +
  410. a.startClassTimeStr +
  411. ":00"
  412. ).valueOf();
  413. let bStr = dayjs(
  414. dayjs(b.classDate).format("YYYY-MM-DD") +
  415. " " +
  416. b.startClassTimeStr +
  417. ":00"
  418. ).valueOf();
  419. return aStr - bStr;
  420. });
  421. this.timeTable = timeTable;
  422. },
  423. getThinkDate(date, num) {
  424. let Stamp = date;
  425. Stamp.setDate(date.getDate() + num); // 获取当前月数的第几天
  426. return dayjs(Stamp).format("YYYY-MM-DD");
  427. },
  428. async onSubmit() {
  429. if (!this.educational.userId) {
  430. this.$toast("选择乐团主管");
  431. return;
  432. }
  433. // 排课
  434. this.onShowCourseList(false);
  435. if (!this.timeTable.length) return;
  436. const online = parseInt(this.frequency.online || 0)
  437. const offline = parseInt(this.frequency.offline || 0)
  438. const total = online + offline
  439. let params = {
  440. courseSchedules: this.timeTable,
  441. vipGroupApplyBaseInfo: {
  442. vipGroupCategoryId: this.activityId,
  443. subjectId: this.activeCourse.subjectId,
  444. subjectIdList: this.activeCourse.subjectId,
  445. singleClassMinutes: this.activeCourse.courseTime,
  446. totalClassTime: total,
  447. allCourseNum: total,
  448. studentNum: this.students.length,
  449. coursesStart: dayjs(this.formData.time).format("YYYY-MM-DD"),
  450. organId: this.formData.fenbu.id,
  451. studentIdList: this.students.map((n) => n.userId).join(","),
  452. studentId:this.students.map((n) => n.userId).join(","),
  453. onlineClassesNum: online,
  454. offlineClassesNum: offline,
  455. userId: this.teacher.id,
  456. },
  457. };
  458. if (params.vipGroupApplyBaseInfo.offlineClassesNum) {
  459. // 教学点
  460. params.vipGroupApplyBaseInfo.teacherSchoolId = this.adddress.id;
  461. }
  462. // console.log(params)
  463. if (this.activityId == 0) {
  464. params.practiceGroupApplyBaseInfoDto = params.vipGroupApplyBaseInfo
  465. delete params.vipGroupApplyBaseInfo
  466. let res = await createPracticeGroup(params);
  467. if (res.code == 200) {
  468. this.$toast("排课成功");
  469. setTimeout(() => {
  470. history.go(-2);
  471. }, 1000);
  472. }
  473. } else {
  474. let res = await createVipCourse(params);
  475. if (res.code == 200) {
  476. this.$toast("排课成功");
  477. setTimeout(() => {
  478. history.go(-2);
  479. }, 1000);
  480. }
  481. }
  482. },
  483. },
  484. };
  485. </script>
  486. <style>
  487. body{
  488. background: #f3f4f8;
  489. }
  490. </style>
  491. <style lang="less" scoped>
  492. .course-item {
  493. display: flex;
  494. align-content: center;
  495. .clear-icon {
  496. display: flex;
  497. align-items: center;
  498. margin-left: auto;
  499. }
  500. }
  501. </style>
  502. <style lang="less">
  503. .courseArrangeClassSchedule {
  504. .van-cell__value,
  505. .van-field__label {
  506. color: #323233;
  507. }
  508. }
  509. .van-row {
  510. line-height: 0.4rem;
  511. border-top: 1px solid #edeef0;
  512. text-align: center;
  513. font-size: 0.14rem;
  514. &:first-child {
  515. border-top: 0;
  516. background: #edeef0;
  517. color: #444;
  518. font-size: 0.15rem;
  519. }
  520. }
  521. .tableContainer {
  522. max-height: 2.44rem;
  523. overflow: auto;
  524. .van-row {
  525. color: #444;
  526. &:first-child {
  527. border-top: 0;
  528. background: #fff;
  529. font-size: 0.14rem;
  530. }
  531. }
  532. }
  533. </style>