Files
education/frontend/src/layout/components/SemesterBar.vue
T
2026-08-27 16:21:42 +08:00

330 lines
8.6 KiB
Vue
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.
<template>
<div class="semester-bar">
<!-- 左:当前学期信息 -->
<div class="semester-bar__item semester-bar__current">
<i class="el-icon-date" />
<span class="label">当前学期</span>
<span class="value">{{ currentSemesterName }}</span>
<span v-if="currentSemesterDate" class="date">{{ currentSemesterDate }}</span>
</div>
<span class="semester-bar__divider" />
<!-- 中:今日日期 -->
<div class="semester-bar__item semester-bar__date">
<i class="el-icon-time" />
<span class="value">{{ todayText }}</span>
<span class="week">{{ weekText }}</span>
</div>
<span class="semester-bar__divider" />
<!-- 右:学期切换下拉框 -->
<div class="semester-bar__item semester-bar__switch">
<span class="label">切换学期</span>
<div class="semester-select-wrap">
<el-select
:value="activeSemester ? activeSemester.nd : ''"
placeholder="请选择学期"
size="small"
filterable
class="semester-select"
popper-class="semester-select-popper"
@change="handleSwitch"
>
<el-option
v-for="item in semesterList"
:key="item.nd"
:label="getSemesterName(item.nd)"
:value="item.nd"
/>
</el-select>
</div>
</div>
</div>
</template>
<script>
import { listSemester, updateSemester } from '@/api/teachBusiness/semester'
export default {
name: 'SemesterBar',
data() {
return {
// 学期列表(接口数据)
semesterList: [],
// 当前选中的学期对象
activeSemester: null,
today: new Date(),
loading: false
}
},
computed: {
currentSemesterName() {
return this.getSemesterName(this.activeSemester ? this.activeSemester.nd : '')
},
currentSemesterDate() {
const item = this.activeSemester
if (!item) return ''
const start = this.formatDate(item.kxrq)
const end = this.formatDate(item.jsrq)
if (!start && !end) return ''
return `${start || '?'} ~ ${end || '?'}`
},
todayText() {
const d = this.today
const pad = n => (n < 10 ? '0' + n : '' + n)
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
},
weekText() {
const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
return weeks[this.today.getDay()]
}
},
created() {
this.getSemesterList()
},
methods: {
/** 加载学期列表,默认选中当前学期(dqxq=true),无当前学期则选第一条 */
getSemesterList() {
this.loading = true
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
const data = response.data || {}
const list = data.records || []
this.semesterList = list
const current = list.find(i => i.dqxq)
this.activeSemester = current || (list[0] ? list[0] : null)
this.loading = false
}).catch(() => {
this.semesterList = []
this.loading = false
})
},
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
formatDate(value) {
if (!value) return ''
return String(value).slice(0, 10)
},
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
getSemesterName(nd) {
if (!nd) return ''
const s = String(nd)
const xn = s.slice(0, 4)
const xq = s.slice(4)
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
return xn + '年' + (map[xq] || '第' + xq + '学期')
},
/** 切换学期:将目标学期设为当前学期,原当前学期取消 */
handleSwitch(nd) {
const current = this.activeSemester
if (!nd || (current && nd === current.nd)) return
const target = this.semesterList.find(i => i.nd === nd)
if (!target) return
// 当前已是"当前学期"的其他记录(需取消)
const others = this.semesterList.filter(i => i.dqxq && i.nd !== nd)
updateSemester({ ...target, dqxq: true }).then(() => {
// 若存在原当前学期,一并取消
if (others.length) {
return Promise.all(others.map(o => updateSemester({ ...o, dqxq: false })))
}
return Promise.resolve()
}).then(() => {
this.activeSemester = target
this.$message.success(`已切换至:${this.getSemesterName(nd)}`)
// 重新拉取,同步最新 dqxq 状态
this.getSemesterList()
// 通知学期管理页面自动刷新
this.$root.$emit('semester-current-changed')
}).catch(() => {
this.$message.error('切换学期失败,请稍后重试')
})
}
}
}
</script>
<style scoped lang="scss">
.semester-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
height: 36px;
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
user-select: none;
&__item {
display: flex;
align-items: center;
justify-content: center;
height: 36px;
padding: 0 16px;
border-radius: 7px;
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.16);
box-sizing: border-box;
i {
color: #fff;
margin-right: 7px;
font-size: 15px;
display: flex;
align-items: center;
height: 100%;
}
.label,
.value,
.week,
.date {
display: flex;
align-items: center;
height: 100%;
white-space: nowrap;
}
.label {
color: rgba(255, 255, 255, 0.75);
margin-right: 6px;
}
.value {
font-weight: 600;
color: #fff;
}
.date {
margin-left: 10px;
font-size: 12px;
color: rgba(255, 255, 255, 0.75);
letter-spacing: 0.5px;
padding-left: 10px;
border-left: 1px solid rgba(255, 255, 255, 0.25);
}
.week {
margin-left: 8px;
color: rgba(255, 255, 255, 0.75);
}
}
&__current {
margin-right: 0;
}
&__date {
margin-right: 0;
}
/* 胶囊式布局后不再需要分隔线 */
&__divider {
display: none;
}
&__switch {
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.16);
padding: 0 16px;
border-radius: 7px;
height: 36px;
/* 用一层定高 flex 容器包裹 select,彻底隔离其默认行高外溢 */
.semester-select-wrap {
display: flex;
align-items: center;
height: 36px;
}
.semester-select {
width: 200px;
}
/* 关闭 select/input 外层默认行高,交给 flex 居中 */
::v-deep .el-select,
::v-deep .el-input {
height: 36px;
line-height: normal;
}
::v-deep .el-input__inner {
background: transparent !important;
border: none !important;
color: #fff !important;
border-radius: 7px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
&::placeholder {
color: rgba(255, 255, 255, 0.7) !important;
}
}
::v-deep .el-input__inner:hover {
background: transparent !important;
}
/* 去掉 focus 时的蓝色外光晕(Element 默认 box-shadow) */
::v-deep .el-input__inner:focus,
::v-deep .el-select .el-input__inner:focus,
::v-deep .el-select .el-input.is-focus .el-input__inner {
border-color: transparent !important;
box-shadow: none !important;
outline: none !important;
}
::v-deep .el-input__icon {
color: rgba(255, 255, 255, 0.85);
}
}
}
</style>
<style lang="scss">
/* 学期下拉浮层渲染在 body 下,scoped 无法命中,故用非 scoped 样式通过 popper-class 单独定制,
使其与顶部深色栏视觉统一(仅作用于 SemesterBar 的下拉,不影响页面其他 select)。 */
.semester-select-popper {
background: #084c3f !important;
border: 1px solid rgba(255, 255, 255, 0.14) !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35) !important;
border-radius: 7px;
.el-select-dropdown__list {
padding: 4px;
}
.el-select-dropdown__item {
color: rgba(255, 255, 255, 0.85);
border-radius: 5px;
height: 32px;
line-height: 32px;
&.hover,
&:hover {
background: rgba(255, 255, 255, 0.12);
color: #fff;
}
&.selected {
color: #fff;
font-weight: 600;
background: rgba(255, 255, 255, 0.16);
}
/* 关键选项的选中图标保持可见 */
.el-select-dropdown__item span {
color: #fff;
}
}
.el-scrollbar__bar.is-vertical {
> div {
background-color: rgba(255, 255, 255, 0.25);
}
}
}
</style>