index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <template>
  2. <div class="h-full bg-white">
  3. <div v-if="pageState" style="margin-top: 1rem">
  4. <n-form :label-width="200" inline>
  5. <n-form-item class="min-w-34" label="请选择查询学员" path="user.name">
  6. <n-input placeholder="请选择查询学员" />
  7. </n-form-item>
  8. <n-form-item class="min-w-34" label="请选择要查询的班级" path="classId">
  9. <n-select
  10. v-model:value="scheduleParamsOptions.classId"
  11. :options="groupList"
  12. clearable
  13. placeholder="请选择班级"
  14. />
  15. </n-form-item>
  16. <n-form-item class="min-w-34" label="请选择要查询的时间范围" path="user.name">
  17. <n-date-picker
  18. v-model:value="dateRange"
  19. type="daterange"
  20. clearable
  21. time-zone="Asia/Shanghai"
  22. placeholder="请选择时间范围"
  23. :on-confirm="differenceInSevenDays"
  24. :on-clear="
  25. () => {
  26. scheduleParamsOptions.startTime = '';
  27. scheduleParamsOptions.endTime = '';
  28. }
  29. "
  30. />
  31. </n-form-item>
  32. <n-form-item class="min-w-34" label="要查询的授课教师" path="user.name">
  33. <n-select
  34. v-model:value="scheduleParamsOptions.teacherId"
  35. :options="teacherOptions"
  36. placeholder="请选择授课教师"
  37. clearable
  38. />
  39. </n-form-item>
  40. </n-form>
  41. <n-button-group>
  42. <n-button type="primary" ghost @click="previousDateSchedule"> 上一周 </n-button>
  43. <n-button type="primary" ghost @click="nextDateSchedule"> 下一周 </n-button>
  44. <n-button type="primary" @click="loadScheduleQuery"> 搜索 </n-button>
  45. <n-button type="primary" @click="pageState = !pageState"> 新增排课 </n-button>
  46. </n-button-group>
  47. <div style="height: 1rem"></div>
  48. <n-spin :show="loading">
  49. <table border="1" style="width: 100%">
  50. <tbody width="100">
  51. <tr>
  52. <th class="w-20 color-blue-800">时间</th>
  53. <th v-for="weekDate in [0, 1, 2, 3, 4, 5, 6]" :key="weekDate" class="font-500 color-blue-800">
  54. {{ new Date(weekStartTime + weekDate * 86400000).getMonth() + 1 }}月{{
  55. new Date(weekStartTime + weekDate * 86400000).getDate()
  56. }}号 - 周{{ chineseNumbers[getDayOfTheWeekNow(weekStartTime, weekDate)] }}
  57. </th>
  58. </tr>
  59. <tr v-for="key in timeKey" :key="key">
  60. <td>{{ key }}</td>
  61. <template v-for="index in [0, 1, 2, 3, 4, 5, 6]" :key="index">
  62. <td
  63. v-if="
  64. scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key] &&
  65. scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['empty'] === false &&
  66. scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['break'] === false
  67. "
  68. :rowspan="scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['colspan']"
  69. >
  70. 时间:
  71. {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['startTime'] }} -
  72. {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['endTime'].split(' ').pop() }}<br />
  73. 讲师: {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['teacherName'] }}<br />
  74. 授课: {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['category'] }}
  75. {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['subjects'] }}<br />
  76. 教室: {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['classroom'] }}<br />
  77. 班级: {{ scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['className'] }}
  78. </td>
  79. <td
  80. v-else-if="
  81. scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key] &&
  82. scheduleList[getDayOfTheWeekNow(weekStartTime, index)][key]['empty'] === true
  83. "
  84. >
  85. 暂无
  86. </td>
  87. </template>
  88. </tr>
  89. </tbody>
  90. </table>
  91. </n-spin>
  92. </div>
  93. <div v-if="!pageState" class="wh-full flex-col-center">
  94. <n-spin :show="formLoading">
  95. <n-form ref="formRef" size="large" label-placement="left" style="margin-right: 5rem" :model="model">
  96. <n-form-item label="排课班级" path="groupList">
  97. <n-select v-model:value="model.classId" :options="groupList" />
  98. </n-form-item>
  99. <n-form-item label="所在教室" path="classRoomList">
  100. <n-select v-model:value="model.roomId" :options="classRoomList" />
  101. </n-form-item>
  102. <n-form-item label="任课老师" path="teacherId">
  103. <n-select
  104. v-model:value="model.teacherId"
  105. placeholder="请选择任课老师"
  106. :options="teacherOptions"
  107. clearable
  108. />
  109. </n-form-item>
  110. <n-form-item label="助教老师" path="multipleSelectValue">
  111. <n-select
  112. v-model:value="model.assistantId"
  113. placeholder="请选择助教老师"
  114. :options="teacherOptions"
  115. clearable
  116. />
  117. </n-form-item>
  118. <n-form-item label="教授科目" path="multipleSelectValue">
  119. <n-cascader
  120. v-model:value="model.subjectsId"
  121. placeholder="请选择课程"
  122. :options="categoryAndSubjectOptions"
  123. show-path
  124. check-strategy="child"
  125. remote
  126. :on-load="categoryAndSubjectLoad"
  127. />
  128. </n-form-item>
  129. <n-form-item label="上课时间" path="datetimeValue">
  130. <n-space>
  131. <n-date-picker v-model:value="dateValue" clearable time-zone="Asia/Shanghai" type="date" />
  132. <n-time-picker v-model:value="startTime" clearable time-zone="Asia/Shanghai" />
  133. <n-time-picker v-model:value="endTime" clearable time-zone="Asia/Shanghai" />
  134. </n-space>
  135. </n-form-item>
  136. <n-form-item label="是否连选" path="switchValue">
  137. <n-switch v-model:value="isRepeatTime" />
  138. </n-form-item>
  139. <n-form-item label="连选天数" path="sliderValue">
  140. <n-slider v-model:value="repeatTime" :step="1" :max="7" />
  141. </n-form-item>
  142. <n-form-item label="单节课时长" path="inputNumberValue">
  143. <n-input-number v-model:value="model.lessonTime" />
  144. </n-form-item>
  145. <div style="display: flex; justify-content: flex-end">
  146. <n-button-group>
  147. <n-button type="primary" @click="handleValidateButtonClick"> 确认提交 </n-button>
  148. <n-button type="primary" @click="pageState = !pageState"> 返回前一页 </n-button>
  149. </n-button-group>
  150. </div>
  151. </n-form>
  152. </n-spin>
  153. </div>
  154. <div style="width: 100%; height: 1rem"></div>
  155. </div>
  156. </template>
  157. <script setup lang="ts">
  158. import { ref, reactive } from 'vue';
  159. import { useRoute } from 'vue-router';
  160. import type { CascaderOption } from 'naive-ui';
  161. import { formatTimestamp } from '~/src/utils';
  162. import {
  163. queryUserAll,
  164. queryCategoryParams,
  165. querySubject,
  166. queryClassAll,
  167. addSchedule,
  168. queryClassRoomList,
  169. querySchedule
  170. } from './api';
  171. import type { ScheduleParams, QueryScheduleParams } from './api';
  172. const route = useRoute();
  173. const model = reactive<ScheduleParams>({
  174. week: 0,
  175. startTime: 0,
  176. endTime: 0,
  177. roomId: null,
  178. classId: null,
  179. assistantId: null,
  180. teacherId: null,
  181. categoryId: null,
  182. subjectsId: null,
  183. createTime: 0,
  184. modifyTime: 0,
  185. createUid: 0,
  186. disabled: 'N'
  187. });
  188. const classId = route.query.classId;
  189. const chineseNumbers: { [index: number]: string } = {
  190. 0: '零',
  191. 1: '一',
  192. 2: '二',
  193. 3: '三',
  194. 4: '四',
  195. 5: '五',
  196. 6: '六',
  197. 7: '日',
  198. 8: '八',
  199. 9: '九'
  200. };
  201. const dateValue = ref<number>(0);
  202. const startTime = ref<number>(0);
  203. const endTime = ref<number>(0);
  204. const isRepeatTime = ref<boolean>(true);
  205. const repeatTime = ref<number>(1);
  206. const pageState = ref<boolean>(true);
  207. const categoryAndSubjectOptions = ref<any[]>([]);
  208. const groupList = ref<any[]>([]);
  209. const classRoomList = ref<any[]>([]);
  210. const teacherOptions = ref<any[]>();
  211. const loading = ref<boolean>(false);
  212. const formLoading = ref<boolean>(false);
  213. const nowDate = new Date();
  214. nowDate.setHours(0, 0, 0, 0);
  215. const sevenDaysLater = new Date(nowDate);
  216. sevenDaysLater.setDate(nowDate.getDate() + 6);
  217. sevenDaysLater.setHours(0, 0, 0, 0);
  218. const dateRange = ref<[number, number]>([nowDate.getTime(), sevenDaysLater.getTime()]);
  219. const weekStartTime = ref<number>(nowDate.getTime());
  220. const scheduleParamsOptions = reactive<QueryScheduleParams>({
  221. studentId: null,
  222. classId: null,
  223. subjectId: null,
  224. teacherId: null,
  225. startTime: null,
  226. endTime: null,
  227. week: null,
  228. roomId: null,
  229. assistantId: null,
  230. categoryId: null
  231. });
  232. const scheduleList = ref<{ [index: string]: any }>({
  233. '1': {},
  234. '2': {},
  235. '3': {},
  236. '4': {},
  237. '5': {},
  238. '6': {},
  239. '7': {}
  240. });
  241. const timeKey = ref<string[]>([
  242. '08:00',
  243. '09:00',
  244. '10:00',
  245. '11:00',
  246. '12:00',
  247. '13:00',
  248. '14:00',
  249. '15:00',
  250. '16:00',
  251. '17:00',
  252. '18:00',
  253. '19:00',
  254. '20:00',
  255. '21:00',
  256. '22:00',
  257. '23:00'
  258. ]);
  259. function categoryAndSubjectLoad(option: CascaderOption) {
  260. model.categoryId = option.value as number;
  261. return new Promise<void>(resolve => {
  262. option.children = [];
  263. querySubject(1, 100, {
  264. categoryId: option.value as number
  265. }).then(respone => {
  266. respone.data?.map(r => {
  267. option.children?.push({
  268. label: r.name,
  269. value: r.id,
  270. depth: 2,
  271. isLeaf: true
  272. });
  273. return r;
  274. });
  275. resolve();
  276. });
  277. });
  278. }
  279. async function handleValidateButtonClick() {
  280. formLoading.value = true;
  281. const modelStartTime = new Date(startTime.value);
  282. const modelEndTime = new Date(endTime.value);
  283. if (modelStartTime.getTime() >= modelEndTime.getTime()) {
  284. window.$message?.error('开始时间不能大于或等于结束时间');
  285. formLoading.value = false;
  286. return;
  287. }
  288. const modelDate = new Date(dateValue.value);
  289. modelStartTime.setFullYear(modelDate.getFullYear(), modelDate.getMonth(), modelDate.getDate());
  290. modelEndTime.setFullYear(modelDate.getFullYear(), modelDate.getMonth(), modelDate.getDate());
  291. model.startTime = formatTimestamp(modelStartTime.getTime());
  292. model.endTime = formatTimestamp(modelEndTime.getTime());
  293. model.week = modelStartTime.getDay();
  294. const res = await addSchedule(repeatTime.value, model);
  295. formLoading.value = false;
  296. if (!res.status && res.msg !== null) {
  297. window.$message?.error(res.msg);
  298. return;
  299. }
  300. pageState.value = !pageState.value;
  301. loadScheduleQuery();
  302. window.$message?.success('操作成功!');
  303. }
  304. function validateDate(nowtime: [number, number]): boolean {
  305. if (!nowtime) {
  306. return false;
  307. }
  308. if (nowtime instanceof Array) {
  309. const differenceInMilliseconds: number = nowtime[1] + 86400000 - nowtime[0];
  310. const days = Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24));
  311. if (days > 7) {
  312. return false;
  313. }
  314. return true;
  315. }
  316. return false;
  317. }
  318. function previousDateSchedule() {
  319. const startDateTime = new Date(dateRange.value[0] - 1);
  320. startDateTime.setHours(0, 0, 0, 0);
  321. const sevenDays = new Date(startDateTime);
  322. sevenDays.setDate(startDateTime.getDate() - 6);
  323. sevenDays.setHours(0, 0, 0, 0);
  324. dateRange.value = [sevenDays.getTime(), startDateTime.getTime()];
  325. loadScheduleQuery();
  326. }
  327. function nextDateSchedule() {
  328. const endDateTime = new Date(dateRange.value[1]);
  329. endDateTime.setHours(24, 0, 0, 0);
  330. const sevenDays = new Date(endDateTime);
  331. sevenDays.setDate(endDateTime.getDate() + 6);
  332. sevenDays.setHours(0, 0, 0, 0);
  333. dateRange.value = [endDateTime.getTime(), sevenDays.getTime()];
  334. loadScheduleQuery();
  335. }
  336. function differenceInSevenDays(nowtime: [number, number]): boolean {
  337. if (!validateDate(nowtime)) {
  338. scheduleParamsOptions.startTime = '';
  339. scheduleParamsOptions.endTime = '';
  340. window.$message?.error('查询时间范围不能超过7天');
  341. return false;
  342. }
  343. scheduleParamsOptions.startTime = formatTimestamp(Number(nowtime[0]));
  344. scheduleParamsOptions.endTime = formatTimestamp(Number(nowtime[1] + 86399999));
  345. return true;
  346. }
  347. function loadScheduleQuery() {
  348. if (differenceInSevenDays(dateRange.value)) {
  349. loadSchedule();
  350. }
  351. }
  352. function loadSchedule() {
  353. loading.value = true;
  354. querySchedule(scheduleParamsOptions).then(response => {
  355. scheduleList.value = [];
  356. for (let i = 1; i <= 7; i += 1) {
  357. scheduleList.value[i] = {};
  358. for (let j = 0; j < timeKey.value.length; j += 1) {
  359. scheduleList.value[i][timeKey.value[j]] = { empty: true, break: false };
  360. }
  361. }
  362. response.data?.forEach((r: any) => {
  363. if (!r.startTime) return;
  364. const time = r.startTime
  365. ?.split(' ')
  366. .pop()
  367. .replace(/(\d{2}):\d{2}/, '$1:00');
  368. const week = new Date(r.startTime).getDay();
  369. if (time && timeKey.value.indexOf(time) === -1) return;
  370. if (week && time && r.endTime && r.startTime && scheduleList.value[week][time].empty) {
  371. r.colspan = new Date(r.endTime).getHours() - new Date(r.startTime).getHours() + 1;
  372. r.empty = false;
  373. r.break = false;
  374. scheduleList.value[week][time] = r;
  375. let n = 1;
  376. while (n < r.colspan) {
  377. const index = timeKey.value.indexOf(time) + n;
  378. scheduleList.value[week][timeKey.value[index]] = { empty: false, break: true };
  379. n += 1;
  380. }
  381. }
  382. });
  383. weekStartTime.value = dateRange.value[0];
  384. loading.value = false;
  385. });
  386. }
  387. function getDayOfTheWeekNow(theWeekStartTime: number, weekDate: number): number {
  388. const week = new Date(theWeekStartTime + weekDate * 86400000).getDay();
  389. return week === 0 ? 7 : week;
  390. }
  391. function loadData() {
  392. queryUserAll().then(response => {
  393. teacherOptions.value = response.data?.map(r => {
  394. return {
  395. label: r.relname,
  396. value: r.id
  397. };
  398. });
  399. });
  400. queryCategoryParams(1, 100, {}).then(response => {
  401. response.data?.map(r => {
  402. categoryAndSubjectOptions.value.push({
  403. label: r.name,
  404. value: r.id,
  405. depth: 1,
  406. isLeaf: false
  407. });
  408. return r;
  409. });
  410. });
  411. queryClassAll().then(response => {
  412. response.data?.map(r => {
  413. groupList.value.push({
  414. label: r.name,
  415. value: r.id
  416. });
  417. return r;
  418. });
  419. });
  420. queryClassRoomList(1, 100, {}).then(response => {
  421. response.data?.map(r => {
  422. classRoomList.value.push({
  423. label: r.name,
  424. value: r.id
  425. });
  426. return r;
  427. });
  428. if (classId) {
  429. model.classId = Number(classId);
  430. }
  431. });
  432. loadScheduleQuery();
  433. }
  434. loadData();
  435. </script>
  436. <style scoped>
  437. table {
  438. table-layout: fixed;
  439. }
  440. th {
  441. font-size: calc(var(--baseSize, 14px) * 2);
  442. font-weight: bold;
  443. }
  444. th,
  445. td {
  446. padding: 16px 12px;
  447. font-size: var(--baseSize, 14px);
  448. outline: 1px solid rgb(226, 226, 226);
  449. text-align: center;
  450. }
  451. td {
  452. color: rgb(99, 99, 99);
  453. }
  454. </style>