Files
education/frontend/node_modules/.cache/vue-loader/83690c2706c21d4fd4547055ddbac554.json
T

1 line
23 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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"]}]}