forked from liweijie/education
1 line
23 KiB
JSON
1 line
23 KiB
JSON
{"remainingRequest":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js??vue-loader-options!C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\views\\teachBusiness\\calendar\\index.vue?vue&type=style&index=0&id=6d43fc55&scoped=true&lang=scss","dependencies":[{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\views\\teachBusiness\\calendar\\index.vue","mtime":1787626930483},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\css-loader\\dist\\cjs.js","mtime":1787309792478},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\loaders\\stylePostLoader.js","mtime":1787309794547},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\postcss-loader\\src\\index.js","mtime":1787309793084},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\sass-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\cache-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js","mtime":1787309793790}],"contextDependencies":[],"result":[{"type":"Buffer","data":"base64:CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg0KLnRlYWNoLWNhbGVuZGFyIHsNCiAgaGVpZ2h0OiAxMDAlOw0KICBkaXNwbGF5OiBmbGV4Ow0KICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOw0KICBwYWRkaW5nOiAxMnB4Ow0KICBib3gtc2l6aW5nOiBib3JkZXItYm94Ow0KICBiYWNrZ3JvdW5kOiAjZmZmOw0KICBib3JkZXI6IDFweCBzb2xpZCAjZTRlN2VkOw0KICBib3JkZXItcmFkaXVzOiA2cHg7DQogIG92ZXJmbG93OiBoaWRkZW47DQoNCiAgLyog6aG26YOo5qCH6aKY5qCPICovDQogIC5jYWwtdGl0bGViYXIgew0KICAgIGRpc3BsYXk6IGZsZXg7DQogICAgYWxpZ24taXRlbXM6IGNlbnRlcjsNCiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsNCiAgICBoZWlnaHQ6IDQ0cHg7DQogICAgYmFja2dyb3VuZDogdmFyKC0tZWR1LWdyZWVuLXByaW1hcnksICMwMDg3NWEpOw0KICAgIGNvbG9yOiAjZmZmOw0KICAgIGJvcmRlci1yYWRpdXM6IDRweCA0cHggMCAwOw0KDQogICAgLmNhbC10aXRsZSB7DQogICAgICBmb250LXNpemU6IDE1cHg7DQogICAgICBmb250LXdlaWdodDogNjAwOw0KICAgIH0NCiAgfQ0KDQogIC8qIOaYvuekuuaOp+WItuagjyAqLw0KICAuY2FsLXBhbmVsIHsNCiAgICBkaXNwbGF5OiBmbGV4Ow0KICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgZmxleC13cmFwOiB3cmFwOw0KICAgIHBhZGRpbmc6IDhweCAxNHB4Ow0KICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZWJlZWY1Ow0KICAgIGJhY2tncm91bmQ6ICNmYWZhZmE7DQoNCiAgICAuY2FsLWNiIHsgbWFyZ2luLXJpZ2h0OiAxNHB4OyB9DQogICAgLmNhbC1wYW5lbC1yaWdodCB7IG1hcmdpbi1sZWZ0OiBhdXRvOyB9DQogIH0NCg0KICAvKiDooajmoLzljLrln58gKi8NCiAgLmNhbC1ncmlkLXdyYXAgew0KICAgIGZsZXg6IDE7DQogICAgb3ZlcmZsb3c6IGF1dG87DQogICAgcGFkZGluZzogOHB4IDEwcHg7DQogICAgYmFja2dyb3VuZDogI2ZmZjsNCiAgfQ0KDQogIC5jYWwtZ3JpZCB7DQogICAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsNCiAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOw0KICAgIHdpZHRoOiAxMDAlOw0KICAgIGZvbnQtc2l6ZTogMTJweDsNCg0KICAgIHRoLCB0ZCB7DQogICAgICBib3JkZXI6IDFweCBzb2xpZCAjZTRlN2VkOw0KICAgICAgdGV4dC1hbGlnbjogY2VudGVyOw0KICAgICAgcGFkZGluZzogMDsNCiAgICB9DQoNCiAgICAuY2FsLWNvcm5lciB7DQogICAgICBiYWNrZ3JvdW5kOiAjZjVmN2ZhOw0KICAgICAgZm9udC13ZWlnaHQ6IDYwMDsNCiAgICAgIGNvbG9yOiAjMzAzMTMzOw0KICAgICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsNCiAgICB9DQoNCiAgICAuY2FsLXRoLXdlZWtubyB7IHdpZHRoOiA1MnB4OyB9DQogICAgLmNhbC10aC13ZWVrcmFuZ2UgeyB3aWR0aDogMTEwcHg7IH0NCg0KICAgIC5jYWwtdGgtZGF5IHsNCiAgICAgIGJhY2tncm91bmQ6IHZhcigtLWVkdS1ncmVlbi1wcmltYXJ5LCAjMDA4NzVhKTsNCiAgICAgIGNvbG9yOiAjZmZmOw0KICAgICAgZm9udC13ZWlnaHQ6IDYwMDsNCiAgICAgIGhlaWdodDogMjZweDsNCiAgICB9DQoNCiAgICAuY2FsLXRoLXNsb3Qgew0KICAgICAgYmFja2dyb3VuZDogI2VjZjVmMDsNCiAgICAgIGNvbG9yOiAjMDA4NzVhOw0KICAgICAgaGVpZ2h0OiAyMnB4Ow0KICAgICAgZm9udC13ZWlnaHQ6IDUwMDsNCiAgICB9DQoNCiAgICAuY2FsLXdlZWstY2VsbCB7DQogICAgICBiYWNrZ3JvdW5kOiAjZjVmN2ZhOw0KICAgICAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsNCiAgICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7DQogICAgfQ0KDQogICAgLmNhbC13ZWVrbm8tY2VsbCB7DQogICAgICBmb250LXdlaWdodDogNjAwOw0KICAgICAgY29sb3I6ICMzMDMxMzM7DQogICAgICBmb250LXNpemU6IDEzcHg7DQogICAgfQ0KDQogICAgLmNhbC13ZWVrcmFuZ2UtY2VsbCB7DQogICAgICBmb250LXNpemU6IDExcHg7DQogICAgICBjb2xvcjogIzkwOTM5OTsNCiAgICB9DQoNCiAgICAuY2FsLWNlbGwgew0KICAgICAgaGVpZ2h0OiAzMHB4Ow0KICAgICAgcG9zaXRpb246IHJlbGF0aXZlOw0KICAgICAgYmFja2dyb3VuZDogI2ZmZjsNCg0KICAgICAgLmNhbC1kYXRlLW51bSB7IGNvbG9yOiAjYzBjNGNjOyB9DQoNCiAgICAgIC8vIOaYn+acn+S6pOabv+W6leiJsu+8muWRqOS4gC/kuIkv5LqUL+aXpeS4uua1hee7v++8jOWRqOS6jC/lm5sv5YWt5Li655m9DQogICAgICAmLmNhbC1jZWxsLW9kZCB7IGJhY2tncm91bmQ6ICNlZWY2ZjE7IH0NCiAgICAgICYuY2FsLWNlbGwtZXZlbiB7IGJhY2tncm91bmQ6ICNmZmZmZmY7IH0NCiAgICAgICYuaGFzLWV2ZW50IHsgYmFja2dyb3VuZDogI2U2ZjRlYTsgfQ0KICAgICAgJi5oYXMtZXZlbnQgLmNhbC1ldmVudC1uYW1lIHsgY29sb3I6ICMwMDY2M2U7IGZvbnQtd2VpZ2h0OiA1MDA7IH0NCiAgICAgICYubm8tc2NoZWR1bGUgeyBiYWNrZ3JvdW5kOiAjZmRlMmUyOyB9DQogICAgfQ0KICB9DQoNCiAgLyog5bqV6YOo54q25oCB5qCPICovDQogIC5jYWwtc3RhdHVzIHsNCiAgICBwYWRkaW5nOiA2cHggMTRweDsNCiAgICBmb250LXNpemU6IDEycHg7DQogICAgY29sb3I6ICM5MDkzOTk7DQogICAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlYmVlZjU7DQogICAgYmFja2dyb3VuZDogI2ZhZmFmYTsNCiAgfQ0KfQ0K"},{"version":3,"sources":["index.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0WA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.vue","sourceRoot":"src/views/teachBusiness/calendar","sourcesContent":["<template>\r\n <div class=\"teach-calendar app-container\">\r\n <!-- 顶部标题:学期名称-校历 -->\r\n <div class=\"cal-titlebar\">\r\n <span class=\"cal-title\">{{ titleText }}</span>\r\n </div>\r\n\r\n <!-- 显示控制栏 -->\r\n <div class=\"cal-panel\">\r\n <el-checkbox v-model=\"showDate\" class=\"cal-cb\">显示日期</el-checkbox>\r\n <div class=\"cal-panel-right\">\r\n <el-button size=\"mini\" type=\"primary\" icon=\"el-icon-download\" @click=\"exportExcel\">另存为Excel</el-button>\r\n </div>\r\n </div>\r\n\r\n <!-- 学期校历表格 -->\r\n <div class=\"cal-grid-wrap\">\r\n <table class=\"cal-grid\">\r\n <thead>\r\n <tr>\r\n <th class=\"cal-corner cal-th-weekno\" :rowspan=\"2\">周次</th>\r\n <th class=\"cal-corner cal-th-weekrange\" :rowspan=\"2\">日期段</th>\r\n <th v-for=\"day in weekDayHeaders\" :key=\"'h1-' + day.dayIndex\" :colspan=\"day.colCount\" class=\"cal-th-day\">\r\n {{ day.label }}\r\n </th>\r\n </tr>\r\n <tr>\r\n <th v-for=\"col in visibleColumns\" :key=\"'h2-' + col.colIndex\" class=\"cal-th-slot\">\r\n {{ col.slotLabel }}\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr v-for=\"(week, wIdx) in weeks\" :key=\"'w-' + wIdx\">\r\n <td class=\"cal-week-cell cal-weekno-cell\">{{ wIdx + 1 }}</td>\r\n <td class=\"cal-week-cell cal-weekrange-cell\">{{ week.rangeText }}</td>\r\n <td\r\n v-for=\"col in visibleColumns\"\r\n :key=\"'c-' + wIdx + '-' + col.colIndex\"\r\n class=\"cal-cell\"\r\n :class=\"cellClass(wIdx, col)\"\r\n >\r\n <template v-if=\"getEvent(wIdx, col.colIndex)\">\r\n <span class=\"cal-event-name\">{{ getEvent(wIdx, col.colIndex).name }}</span>\r\n </template>\r\n <span v-else-if=\"showDate\" class=\"cal-date-num\">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- 底部状态栏 -->\r\n <div class=\"cal-status\">\r\n <span v-if=\"nd\">从 {{ dateRangeText }} ,共 {{ totalWeeks }} 周</span>\r\n <span v-else>请选择学期</span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport { saveAs } from 'file-saver'\r\nimport { listSemester, getSemester } from '@/api/teachBusiness/semester'\r\nimport { listXqxlb } from '@/api/teachBusiness/xqxlb'\r\n\r\n// 节次定义:全部常显\r\nconst SLOT_DEFS = [\r\n { key: '12', label: '1-2' },\r\n { key: '34', label: '3-4' },\r\n { key: '56', label: '5-6' },\r\n { key: '78', label: '7-8' },\r\n { key: 'night', label: '晚上' },\r\n { key: 'late', label: '夜间' }\r\n]\r\n\r\n// 前端节次 key -> 后端 xqxlb.courseClass 节次范围\r\nconst SLOT_COURSE_MAP = {\r\n '12': '1-2',\r\n '34': '3-4',\r\n '56': '5-6',\r\n '78': '7-8',\r\n night: '9-10',\r\n late: '11-12'\r\n}\r\n\r\nconst WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']\r\n\r\nfunction pad2(n) {\r\n return n < 10 ? '0' + n : '' + n\r\n}\r\n\r\nfunction fmtDate(d) {\r\n return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())\r\n}\r\n\r\nexport default {\r\n name: 'SemesterCalendar',\r\n data() {\r\n return {\r\n // 当前学期代号(跟随顶栏切换学期,自动更新为当前学期)\r\n nd: '',\r\n // 学期日期范围\r\n startDate: null,\r\n endDate: null,\r\n totalWeeks: 0,\r\n dateRangeText: '',\r\n weeks: [],\r\n // 显示控制:默认不勾选显示日期;节次全部展示\r\n showDate: false,\r\n // 事件存储:key = cellKey -> event\r\n events: {},\r\n loading: false\r\n }\r\n },\r\n computed: {\r\n // 页面标题:学期名称-校历\r\n titleText() {\r\n if (!this.nd) return '学期校历'\r\n return this.getSemesterName(this.nd) + '-校历'\r\n },\r\n // 当前可见列(全部节次常显)\r\n visibleColumns() {\r\n const cols = []\r\n let colIndex = 0\r\n WEEK_DAYS.forEach((dayLabel, dayIndex) => {\r\n SLOT_DEFS.forEach(slot => {\r\n cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })\r\n colIndex++\r\n })\r\n })\r\n return cols\r\n },\r\n // 表头第一行星期:每个星期合并其下全部节次列\r\n weekDayHeaders() {\r\n return WEEK_DAYS.map((label, dayIndex) => ({\r\n dayIndex: dayIndex,\r\n label: label,\r\n colCount: SLOT_DEFS.length\r\n }))\r\n }\r\n },\r\n created() {\r\n // 初始加载当前学期\r\n this.loadCurrentSemester()\r\n // 顶栏切换学期后自动刷新为当前学期的校历\r\n this.$root.$on('semester-current-changed', this.loadCurrentSemester)\r\n },\r\n beforeDestroy() {\r\n this.$root.$off('semester-current-changed', this.loadCurrentSemester)\r\n },\r\n methods: {\r\n /* ---------- 加载当前学期 ---------- */\r\n loadCurrentSemester() {\r\n this.loading = true\r\n listSemester({ pageNum: 1, pageSize: 100 }).then(response => {\r\n const data = response.data || {}\r\n const list = data.records || []\r\n // 跟随顶栏当前学期(dqxq=true),无则取第一条\r\n const current = list.find(i => i.dqxq) || list[0]\r\n this.loading = false\r\n if (current) {\r\n if (String(this.nd) !== String(current.nd)) {\r\n this.nd = current.nd\r\n this.loadSemester()\r\n }\r\n }\r\n }).catch(() => {\r\n this.loading = false\r\n })\r\n },\r\n /* ---------- 数据加载 ---------- */\r\n loadSemester() {\r\n if (!this.nd) return\r\n this.startDate = null\r\n this.endDate = null\r\n getSemester(this.nd)\r\n .then(res => {\r\n const data = res.data || res || {}\r\n const kx = (data.kxrq || '').toString().slice(0, 10)\r\n const jx = (data.jsrq || '').toString().slice(0, 10)\r\n if (kx && jx) {\r\n this.startDate = new Date(kx.replace(/-/g, '/'))\r\n this.endDate = new Date(jx.replace(/-/g, '/'))\r\n this.buildWeeks()\r\n }\r\n this.loadXqxlbEvents()\r\n })\r\n .catch(() => {\r\n this.$message.warning('学期详情获取失败,请确认学期数据')\r\n })\r\n },\r\n // 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位)\r\n loadXqxlbEvents() {\r\n listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {\r\n const data = res.data\r\n const list = Array.isArray(data) ? data : (data && data.records) || []\r\n const loaded = {}\r\n list.forEach(item => {\r\n const pos = this.locateXqxlb(item)\r\n if (pos) {\r\n loaded[pos.key] = {\r\n bh: item.bh,\r\n name: item.jqmc || '',\r\n schedulable: !!item.kpk,\r\n mainCourse: !!item.zdpk,\r\n remarkShow: !!item.bzxs,\r\n remark: item.bz || ''\r\n }\r\n }\r\n })\r\n this.events = loaded\r\n }).catch(() => {\r\n this.$message.warning('校历事件加载失败')\r\n })\r\n },\r\n // 根据假期记录反推时间格 key(jqsj 日期 + courseClass 节次)\r\n locateXqxlb(item) {\r\n if (!this.startDate || !item.jqsj) return null\r\n const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))\r\n if (isNaN(d.getTime())) return null\r\n const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))\r\n if (offset < 0) return null\r\n const wIdx = Math.floor(offset / 7)\r\n const dayIndex = offset % 7\r\n const slotKey = this.courseClassToSlotKey(item.courseClass)\r\n if (slotKey === null) return null\r\n const colIndex = this.colIndexOf(dayIndex, slotKey)\r\n if (colIndex < 0) return null\r\n return { key: this.cellKey(wIdx, colIndex) }\r\n },\r\n // 后端 courseClass 节次范围 -> 前端节次 key\r\n courseClassToSlotKey(courseClass) {\r\n if (!courseClass) return null\r\n const s = String(courseClass).trim()\r\n for (const key in SLOT_COURSE_MAP) {\r\n if (s === SLOT_COURSE_MAP[key]) return key\r\n }\r\n // 兼容 \"910\"/\"1112\" 等紧凑写法\r\n if (s === '910') return 'night'\r\n if (s === '1112') return 'late'\r\n return null\r\n },\r\n buildWeeks() {\r\n if (!this.startDate || !this.endDate) return\r\n const start = new Date(this.startDate)\r\n const end = new Date(this.endDate)\r\n const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1\r\n this.totalWeeks = Math.ceil(days / 7)\r\n this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)\r\n const weeks = []\r\n for (let w = 0; w < this.totalWeeks; w++) {\r\n const wkStart = new Date(start)\r\n wkStart.setDate(start.getDate() + w * 7)\r\n const wkEnd = new Date(wkStart)\r\n wkEnd.setDate(wkStart.getDate() + 6)\r\n weeks.push({\r\n rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +\r\n '至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate())\r\n })\r\n }\r\n this.weeks = weeks\r\n },\r\n colIndexOf(dayIndex, slotKey) {\r\n const col = this.visibleColumns.find(c => c.dayIndex === dayIndex && c.slotKey === slotKey)\r\n return col ? col.colIndex : -1\r\n },\r\n /* ---------- 单元格工具 ---------- */\r\n cellKey(wIdx, colIndex) {\r\n return 'w' + wIdx + '-c' + colIndex\r\n },\r\n getEvent(wIdx, colIndex) {\r\n return this.events[this.cellKey(wIdx, colIndex)] || null\r\n },\r\n weekDayLabel(dayIndex) {\r\n return WEEK_DAYS[dayIndex]\r\n },\r\n // 单元格内显示的日期数字(如 0703)\r\n dateNumberOf(wIdx, dayIndex) {\r\n if (!this.startDate) return ''\r\n const d = new Date(this.startDate)\r\n d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)\r\n return pad2(d.getMonth() + 1) + pad2(d.getDate())\r\n },\r\n cellClass(wIdx, col) {\r\n const classes = []\r\n // 星期交替背景(单双日不同底色)\r\n if (col.dayIndex % 2 === 0) classes.push('cal-cell-odd')\r\n else classes.push('cal-cell-even')\r\n const ev = this.getEvent(wIdx, col.colIndex)\r\n if (ev) {\r\n classes.push('has-event')\r\n if (!ev.schedulable) classes.push('no-schedule')\r\n }\r\n return classes\r\n },\r\n /** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */\r\n getSemesterName(nd) {\r\n if (!nd) return ''\r\n const s = String(nd)\r\n const xn = s.slice(0, 4)\r\n const xq = s.slice(4)\r\n const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }\r\n return xn + '年' + (map[xq] || '第' + xq + '学期')\r\n },\r\n /* ---------- 另存为Excel ---------- */\r\n exportExcel() {\r\n if (!this.weeks.length) {\r\n this.$message.warning('暂无校历数据可导出')\r\n return\r\n }\r\n // 按星期分组节次列,用于表头合并\r\n const dayCols = []\r\n this.visibleColumns.forEach(col => {\r\n if (!dayCols[col.dayIndex]) dayCols[col.dayIndex] = []\r\n dayCols[col.dayIndex].push(col)\r\n })\r\n let html = '<html xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:x=\"urn:schemas-microsoft-com:office:excel\">'\r\n html += '<head><meta charset=\"utf-8\"></head><body>'\r\n html += '<table border=\"1\" cellspacing=\"0\" cellpadding=\"4\" style=\"border-collapse:collapse;\">'\r\n // 标题行\r\n html += '<tr><td colspan=\"' + (this.visibleColumns.length + 2) + '\" style=\"font-size:15px;font-weight:bold;text-align:center;\">' + this.titleText + '</td></tr>'\r\n // 表头第一行:星期(合并同类天)\r\n html += '<tr><th rowspan=\"2\">周次</th><th rowspan=\"2\">日期段</th>'\r\n WEEK_DAYS.forEach((dayLabel, dayIndex) => {\r\n const cols = dayCols[dayIndex] || []\r\n if (cols.length) html += '<th colspan=\"' + cols.length + '\">' + dayLabel + '</th>'\r\n })\r\n html += '</tr>'\r\n // 表头第二行:节次\r\n html += '<tr>'\r\n this.visibleColumns.forEach(col => {\r\n html += '<th>' + col.slotLabel + '</th>'\r\n })\r\n html += '</tr>'\r\n // 数据行\r\n this.weeks.forEach((week, wIdx) => {\r\n html += '<tr>'\r\n html += '<td>' + (wIdx + 1) + '</td>'\r\n html += '<td>' + week.rangeText + '</td>'\r\n this.visibleColumns.forEach(col => {\r\n const ev = this.getEvent(wIdx, col.colIndex)\r\n if (ev) {\r\n const remark = ev.remark && ev.remarkShow ? '(' + ev.remark + ')' : ''\r\n html += '<td>' + ev.name + remark + '</td>'\r\n } else if (this.showDate) {\r\n html += '<td>' + this.dateNumberOf(wIdx, col.dayIndex) + '</td>'\r\n } else {\r\n html += '<td></td>'\r\n }\r\n })\r\n html += '</tr>'\r\n })\r\n html += '</table></body></html>'\r\n const blob = new Blob(['\\ufeff' + html], { type: 'application/vnd.ms-excel;charset=utf-8' })\r\n saveAs(blob, (this.titleText || '学期校历') + '.xls')\r\n this.$message.success('已导出Excel')\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.teach-calendar {\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n padding: 12px;\r\n box-sizing: border-box;\r\n background: #fff;\r\n border: 1px solid #e4e7ed;\r\n border-radius: 6px;\r\n overflow: hidden;\r\n\r\n /* 顶部标题栏 */\r\n .cal-titlebar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: 44px;\r\n background: var(--edu-green-primary, #00875a);\r\n color: #fff;\r\n border-radius: 4px 4px 0 0;\r\n\r\n .cal-title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n }\r\n }\r\n\r\n /* 显示控制栏 */\r\n .cal-panel {\r\n display: flex;\r\n align-items: center;\r\n flex-wrap: wrap;\r\n padding: 8px 14px;\r\n border-bottom: 1px solid #ebeef5;\r\n background: #fafafa;\r\n\r\n .cal-cb { margin-right: 14px; }\r\n .cal-panel-right { margin-left: auto; }\r\n }\r\n\r\n /* 表格区域 */\r\n .cal-grid-wrap {\r\n flex: 1;\r\n overflow: auto;\r\n padding: 8px 10px;\r\n background: #fff;\r\n }\r\n\r\n .cal-grid {\r\n border-collapse: collapse;\r\n table-layout: fixed;\r\n width: 100%;\r\n font-size: 12px;\r\n\r\n th, td {\r\n border: 1px solid #e4e7ed;\r\n text-align: center;\r\n padding: 0;\r\n }\r\n\r\n .cal-corner {\r\n background: #f5f7fa;\r\n font-weight: 600;\r\n color: #303133;\r\n vertical-align: middle;\r\n }\r\n\r\n .cal-th-weekno { width: 52px; }\r\n .cal-th-weekrange { width: 110px; }\r\n\r\n .cal-th-day {\r\n background: var(--edu-green-primary, #00875a);\r\n color: #fff;\r\n font-weight: 600;\r\n height: 26px;\r\n }\r\n\r\n .cal-th-slot {\r\n background: #ecf5f0;\r\n color: #00875a;\r\n height: 22px;\r\n font-weight: 500;\r\n }\r\n\r\n .cal-week-cell {\r\n background: #f5f7fa;\r\n vertical-align: middle;\r\n white-space: nowrap;\r\n }\r\n\r\n .cal-weekno-cell {\r\n font-weight: 600;\r\n color: #303133;\r\n font-size: 13px;\r\n }\r\n\r\n .cal-weekrange-cell {\r\n font-size: 11px;\r\n color: #909399;\r\n }\r\n\r\n .cal-cell {\r\n height: 30px;\r\n position: relative;\r\n background: #fff;\r\n\r\n .cal-date-num { color: #c0c4cc; }\r\n\r\n // 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白\r\n &.cal-cell-odd { background: #eef6f1; }\r\n &.cal-cell-even { background: #ffffff; }\r\n &.has-event { background: #e6f4ea; }\r\n &.has-event .cal-event-name { color: #00663e; font-weight: 500; }\r\n &.no-schedule { background: #fde2e2; }\r\n }\r\n }\r\n\r\n /* 底部状态栏 */\r\n .cal-status {\r\n padding: 6px 14px;\r\n font-size: 12px;\r\n color: #909399;\r\n border-top: 1px solid #ebeef5;\r\n background: #fafafa;\r\n }\r\n}\r\n</style>\r\n"]}]} |