forked from liweijie/education
Compare commits
15 Commits
c2cc535d80
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 81ef173454 | |||
| b7110ccc9a | |||
| 72f0482427 | |||
| 449651dbd4 | |||
| f62b5e394c | |||
| 933af8ca13 | |||
| 4c9cfb8d7f | |||
| f32d19e411 | |||
| fa2832cf3b | |||
| d37c05aaad | |||
| ab3daeed65 | |||
| 7f853afff6 | |||
| 2183b33a7f | |||
| b34efc847d | |||
| be7b91ac20 |
+20
-1
@@ -85,6 +85,24 @@ export function rejectTeachingLog(bh, thyj) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量上报教学日志,接口形态:POST /log/teaching-log/batch-report(body 为裸数组 ["",""])
|
||||||
|
export function batchReportTeachingLog(bhList) {
|
||||||
|
return request({
|
||||||
|
url: '/log/teaching-log/batch-report',
|
||||||
|
method: 'post',
|
||||||
|
data: bhList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量审核教学日志,接口形态:POST /log/teaching-log/batch-audit(body 为裸数组 ["",""])
|
||||||
|
export function batchAuditTeachingLog(bhList) {
|
||||||
|
return request({
|
||||||
|
url: '/log/teaching-log/batch-audit',
|
||||||
|
method: 'post',
|
||||||
|
data: bhList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 根据课表自动创建教学日志,接口形态:POST /log/teaching-log/auto-create?tybh=
|
// 根据课表自动创建教学日志,接口形态:POST /log/teaching-log/auto-create?tybh=
|
||||||
export function autoCreateTeachingLog(tybh) {
|
export function autoCreateTeachingLog(tybh) {
|
||||||
return request({
|
return request({
|
||||||
@@ -101,7 +119,8 @@ export function importTeachingLogExcel(file) {
|
|||||||
return request({
|
return request({
|
||||||
url: '/log/teaching-log/import-excel',
|
url: '/log/teaching-log/import-excel',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: formData
|
data: formData,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,15 +39,6 @@ export function getInfo() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解锁屏幕
|
|
||||||
export function unlockScreen(password) {
|
|
||||||
return request({
|
|
||||||
url: '/unlockscreen',
|
|
||||||
method: 'post',
|
|
||||||
data: { password }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出方法
|
// 退出方法
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ export function getXqxlb(bh) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预生成学期整张校历占位记录(首次进入新学期时调用,接口自带去重)
|
||||||
|
export function addXqxlb(startDate, endDate) {
|
||||||
|
return request({
|
||||||
|
url: '/xqxlb/add',
|
||||||
|
method: 'post',
|
||||||
|
params: { startDate: startDate, endDate: endDate }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 新增 / 修改校历假期(无 bh 时为新增,带 bh 时为修改)
|
// 新增 / 修改校历假期(无 bh 时为新增,带 bh 时为修改)
|
||||||
export function updateXqxlb(data) {
|
export function updateXqxlb(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 444 B |
@@ -98,6 +98,15 @@
|
|||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
// 教学管理系统的列表字段统一居中,避免各业务页面单独声明后出现视觉错位。
|
||||||
|
.el-table__cell,
|
||||||
|
.el-table__cell.is-left,
|
||||||
|
.el-table__cell.is-right,
|
||||||
|
.el-table__cell.is-center,
|
||||||
|
.el-table__cell > .cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
|
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
|
||||||
th {
|
th {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -129,6 +138,17 @@
|
|||||||
.el-table__fixed-body-wrapper.is-dragging {
|
.el-table__fixed-body-wrapper.is-dragging {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 横向滚动条被隐藏后,Element UI 仍会为右侧固定列预留滚动条高度。
|
||||||
|
// 仅在宽表格发生横向溢出时补齐该高度,保证“操作”列与普通数据行底部对齐。
|
||||||
|
&.el-table--scrollable-x .el-table__fixed-right {
|
||||||
|
height: 100% !important; // 组件通过行内样式写入高度,只能在此处覆盖。
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-table--scrollable-x .el-table__fixed-right .el-table__fixed-body-wrapper {
|
||||||
|
bottom: 0;
|
||||||
|
height: auto !important; // 组件通过行内样式写入高度,只能在此处覆盖。
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表格横拖期间锁定整页光标与文本选中:
|
/* 表格横拖期间锁定整页光标与文本选中:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover">
|
<el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover">
|
||||||
<div class="notice-header">
|
<div class="notice-header">
|
||||||
<span class="notice-title">通知公告</span>
|
<span class="notice-title">通知公告</span>
|
||||||
<span class="notice-mark-all" @click="markAllRead">全部已读</span>
|
<span v-show="false" class="notice-mark-all" @click="markAllRead">全部已读</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div>
|
<div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div>
|
||||||
<div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div>
|
<div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div>
|
||||||
|
|||||||
@@ -33,9 +33,6 @@
|
|||||||
<router-link to="/user/profile">
|
<router-link to="/user/profile">
|
||||||
<el-dropdown-item icon="el-icon-user">个人中心</el-dropdown-item>
|
<el-dropdown-item icon="el-icon-user">个人中心</el-dropdown-item>
|
||||||
</router-link>
|
</router-link>
|
||||||
<el-dropdown-item icon="el-icon-lock" @click.native="lockScreen">
|
|
||||||
<span>锁定屏幕</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item divided icon="el-icon-switch-button" @click.native="logout">
|
<el-dropdown-item divided icon="el-icon-switch-button" @click.native="logout">
|
||||||
<span>退出登录</span>
|
<span>退出登录</span>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
@@ -104,12 +101,6 @@ export default {
|
|||||||
this.$refs.searchRef.click()
|
this.$refs.searchRef.click()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lockScreen() {
|
|
||||||
const currentPath = this.$route.fullPath
|
|
||||||
this.$store.dispatch('lock/lockScreen', currentPath).then(() => {
|
|
||||||
this.$router.push('/lock')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
logout() {
|
logout() {
|
||||||
this.$confirm('确定注销并退出系统吗?', '提示', {
|
this.$confirm('确定注销并退出系统吗?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
|
|||||||
@@ -17,46 +17,19 @@
|
|||||||
<span class="week">{{ weekText }}</span>
|
<span class="week">{{ weekText }}</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listSemester, updateSemester } from '@/api/teachBusiness/semester'
|
import { listSemester } from '@/api/teachBusiness/semester'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SemesterBar',
|
name: 'SemesterBar',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 学期列表(接口数据)
|
|
||||||
semesterList: [],
|
|
||||||
// 当前选中的学期对象
|
// 当前选中的学期对象
|
||||||
activeSemester: null,
|
activeSemester: null,
|
||||||
today: new Date(),
|
today: new Date()
|
||||||
loading: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -85,19 +58,15 @@ export default {
|
|||||||
this.getSemesterList()
|
this.getSemesterList()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 加载学期列表,默认选中当前学期(dqxq=true),无当前学期则选第一条 */
|
/** 加载当前学期(dqxq=true),无当前学期则取第一条 */
|
||||||
getSemesterList() {
|
getSemesterList() {
|
||||||
this.loading = true
|
|
||||||
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||||
const data = response.data || {}
|
const data = response.data || {}
|
||||||
const list = data.records || []
|
const list = data.records || []
|
||||||
this.semesterList = list
|
|
||||||
const current = list.find(i => i.dqxq)
|
const current = list.find(i => i.dqxq)
|
||||||
this.activeSemester = current || (list[0] ? list[0] : null)
|
this.activeSemester = current || (list[0] ? list[0] : null)
|
||||||
this.loading = false
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.semesterList = []
|
this.activeSemester = null
|
||||||
this.loading = false
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
|
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
|
||||||
@@ -113,32 +82,6 @@ export default {
|
|||||||
const xq = s.slice(4)
|
const xq = s.slice(4)
|
||||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
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('切换学期失败,请稍后重试')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,109 +165,5 @@ export default {
|
|||||||
&__divider {
|
&__divider {
|
||||||
display: none;
|
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;
|
|
||||||
caret-color: transparent;
|
|
||||||
|
|
||||||
&::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>
|
</style>
|
||||||
|
|||||||
@@ -18,19 +18,12 @@ const isWhiteList = (path) => {
|
|||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
const isLock = store.getters.isLock
|
|
||||||
/* has token*/
|
/* has token*/
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
} else if (isWhiteList(to.path)) {
|
} else if (isWhiteList(to.path)) {
|
||||||
next()
|
next()
|
||||||
} else if (isLock && to.path !== '/lock') {
|
|
||||||
next({ path: '/lock' })
|
|
||||||
NProgress.done()
|
|
||||||
} else if (!isLock && to.path === '/lock') {
|
|
||||||
next({ path: '/' })
|
|
||||||
NProgress.done()
|
|
||||||
} else {
|
} else {
|
||||||
if (store.getters.roles.length === 0) {
|
if (store.getters.roles.length === 0) {
|
||||||
isRelogin.show = true
|
isRelogin.show = true
|
||||||
|
|||||||
@@ -74,12 +74,6 @@ export const constantRoutes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/lock',
|
|
||||||
component: () => import('@/views/lock'),
|
|
||||||
hidden: true,
|
|
||||||
meta: { title: '锁定屏幕' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/user',
|
path: '/user',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
size: state => state.app.size,
|
size: state => state.app.size,
|
||||||
device: state => state.app.device,
|
device: state => state.app.device,
|
||||||
dict: state => state.dict.dict,
|
dict: state => state.dict.dict,
|
||||||
isLock: state => state.lock.isLock,
|
|
||||||
lockPath: state => state.lock.lockPath,
|
|
||||||
visitedViews: state => state.tagsView.visitedViews,
|
visitedViews: state => state.tagsView.visitedViews,
|
||||||
cachedViews: state => state.tagsView.cachedViews,
|
cachedViews: state => state.tagsView.cachedViews,
|
||||||
token: state => state.user.token,
|
token: state => state.user.token,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import app from './modules/app'
|
import app from './modules/app'
|
||||||
import lock from './modules/lock'
|
|
||||||
import dict from './modules/dict'
|
import dict from './modules/dict'
|
||||||
import user from './modules/user'
|
import user from './modules/user'
|
||||||
import tagsView from './modules/tagsView'
|
import tagsView from './modules/tagsView'
|
||||||
@@ -13,7 +12,6 @@ Vue.use(Vuex)
|
|||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
app,
|
app,
|
||||||
lock,
|
|
||||||
dict,
|
dict,
|
||||||
user,
|
user,
|
||||||
tagsView,
|
tagsView,
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
const LOCK_KEY = 'screen-lock'
|
|
||||||
const LOCK_PATH_KEY = 'screen-lock-path'
|
|
||||||
|
|
||||||
const lock = {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
isLock: JSON.parse(localStorage.getItem(LOCK_KEY) || 'false'),
|
|
||||||
lockPath: localStorage.getItem(LOCK_PATH_KEY) || '/index'
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
SET_LOCK(state, status) {
|
|
||||||
state.isLock = status
|
|
||||||
localStorage.setItem(LOCK_KEY, JSON.stringify(status))
|
|
||||||
},
|
|
||||||
SET_LOCK_PATH(state, path) {
|
|
||||||
state.lockPath = path
|
|
||||||
localStorage.setItem(LOCK_PATH_KEY, path)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
// 锁定屏幕,同时记录当前路径
|
|
||||||
lockScreen({ commit }, currentPath) {
|
|
||||||
commit('SET_LOCK_PATH', currentPath || '/index')
|
|
||||||
commit('SET_LOCK', true)
|
|
||||||
},
|
|
||||||
// 解锁屏幕,清除路径
|
|
||||||
unlockScreen({ commit }) {
|
|
||||||
commit('SET_LOCK', false)
|
|
||||||
commit('SET_LOCK_PATH', '/index')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default lock
|
|
||||||
@@ -53,7 +53,6 @@ const user = {
|
|||||||
login(username, password, code, uuid).then(res => {
|
login(username, password, code, uuid).then(res => {
|
||||||
setToken(res.token)
|
setToken(res.token)
|
||||||
commit('SET_TOKEN', res.token)
|
commit('SET_TOKEN', res.token)
|
||||||
store.dispatch('lock/unlockScreen')
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
|
|||||||
@@ -15,108 +15,117 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button type="primary" plain @click="handleAdd">新增{{ currentTitle }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- ==================== 2. 数据表格 ==================== -->
|
<!-- ==================== 2. 数据表格 ==================== -->
|
||||||
<el-card shadow="never" class="table-card">
|
<el-card shadow="never" class="table-card">
|
||||||
<div class="table-title">{{ currentTitle }}</div>
|
<!-- 课时费方案 -->
|
||||||
<div class="list-toolbar">
|
|
||||||
<el-button type="primary" plain size="small" @click="handleAdd">新增{{ currentTitle }}</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
ref="tableRef"
|
v-if="activeTab === 'fee'"
|
||||||
v-loading="currentState.loading"
|
v-loading="feeState.loading"
|
||||||
:data="currentState.list"
|
:data="feeState.list"
|
||||||
border
|
border
|
||||||
stripe
|
stripe
|
||||||
|
class="plan-data-table"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<template v-if="activeTab === 'fee'">
|
|
||||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||||
<el-table-column prop="mrbzksl" label="默认标准课时量" width="120" align="right" header-align="center">
|
<el-table-column prop="mrbzksl" label="默认标准课时量" width="120" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.mrbzksl) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.mrbzksl) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mrksfbz" label="默认课时费标准" width="120" align="right" header-align="center">
|
<el-table-column prop="mrksfbz" label="默认课时费标准" width="120" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.mrksfbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.mrksfbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mrclksfbz" label="默认超量课时费标准" width="150" align="right" header-align="center">
|
<el-table-column prop="mrclksfbz" label="默认超量课时费标准" width="150" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.mrclksfbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.mrclksfbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="xgjxbzksfbz" label="相关教学补助课时费标准" width="180" align="right" header-align="center">
|
<el-table-column prop="xgjxbzksfbz" label="相关教学补助课时费标准" width="180" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.xgjxbzksfbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.xgjxbzksfbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jxyxbz" label="绩效优秀补助" width="110" align="right" header-align="center">
|
<el-table-column prop="jxyxbz" label="绩效优秀补助" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.jxyxbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.jxyxbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jxlhbz" label="绩效良好补助" width="110" align="right" header-align="center">
|
<el-table-column prop="jxlhbz" label="绩效良好补助" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.jxlhbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.jxlhbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jxzdbz" label="绩效中等补助" width="110" align="right" header-align="center">
|
<el-table-column prop="jxzdbz" label="绩效中等补助" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.jxzdbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.jxzdbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jxjgbz" label="绩效及格补助" width="110" align="right" header-align="center">
|
<el-table-column prop="jxjgbz" label="绩效及格补助" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.jxjgbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.jxjgbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jxbjgbz" label="绩效不及格补助" width="120" align="right" header-align="center">
|
<el-table-column prop="jxbjgbz" label="绩效不及格补助" width="120" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.jxbjgbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.jxbjgbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="200" align="center" fixed="right" class-name="op-cell">
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||||
<template slot-scope="{ row }">
|
<template slot-scope="{ row }">
|
||||||
|
<div class="table-actions">
|
||||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
</el-table>
|
||||||
|
|
||||||
<template v-else>
|
<!-- 课时方案 -->
|
||||||
|
<el-table
|
||||||
|
v-else
|
||||||
|
v-loading="coeffState.loading"
|
||||||
|
:data="coeffState.list"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
class="plan-data-table"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||||
<el-table-column prop="zjksxs" label="主讲课时系数" width="110" align="right" header-align="center">
|
<el-table-column prop="zjksxs" label="主讲课时系数" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.zjksxs) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.zjksxs) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="fjksxs" label="辅讲课时系数" width="110" align="right" header-align="center">
|
<el-table-column prop="fjksxs" label="辅讲课时系数" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.fjksxs) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.fjksxs) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mbxyrs" label="满班学员人数" width="110" align="right" header-align="center">
|
<el-table-column prop="mbxyrs" label="满班学员人数" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.mbxyrs) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.mbxyrs) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="dz10xybjxs" label="递增10学员班级系数" width="150" align="right" header-align="center">
|
<el-table-column prop="dz10xybjxs" label="递增10学员班级系数" width="150" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.dz10xybjxs) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.dz10xybjxs) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="bjxssx" label="班级系数上限" width="110" align="right" header-align="center">
|
<el-table-column prop="bjxssx" label="班级系数上限" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.bjxssx) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.bjxssx) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="d78jksl" label="第78节课时量" width="110" align="right" header-align="center">
|
<el-table-column prop="d78jksl" label="第78节课时量" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.d78jksl) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.d78jksl) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="yjksl" label="夜间课时量" width="100" align="right" header-align="center">
|
<el-table-column prop="yjksl" label="夜间课时量" width="100" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.yjksl) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.yjksl) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mrktjxffxs" label="默认课堂教法系数" width="130" align="right" header-align="center">
|
<el-table-column prop="mrktjxffxs" label="默认课堂教法系数" width="130" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.mrktjxffxs) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.mrktjxffxs) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="hbxszl" label="合班系数增量" width="110" align="right" header-align="center">
|
<el-table-column prop="hbxszl" label="合班系数增量" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.hbxszl) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.hbxszl) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="hbxssx" label="合班系数上限" width="110" align="right" header-align="center">
|
<el-table-column prop="hbxssx" label="合班系数上限" width="110" align="right">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.hbxssx) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.hbxssx) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="120" align="center" fixed="right" class-name="op-cell">
|
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||||
<template slot-scope="{ row }">
|
<template slot-scope="{ row }">
|
||||||
|
<div class="table-actions">
|
||||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -475,9 +484,6 @@ export default {
|
|||||||
state.total = 0
|
state.total = 0
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
state.loading = false
|
state.loading = false
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.tableRef && this.$refs.tableRef.doLayout()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -648,6 +654,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.plan-page {
|
.plan-page {
|
||||||
|
.list-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 1. 查询区域 ====================
|
// ==================== 1. 查询区域 ====================
|
||||||
.search-card {
|
.search-card {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -661,37 +674,13 @@ export default {
|
|||||||
|
|
||||||
// ==================== 2. 数据表格 ====================
|
// ==================== 2. 数据表格 ====================
|
||||||
.table-card {
|
.table-card {
|
||||||
.table-title {
|
.table-actions {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-toolbar {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
align-items: center;
|
||||||
margin-bottom: 12px;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-table .cell {
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复操作栏 cell 底部错位:
|
|
||||||
el-button--small 实际高度 30px, 但 .cell 默认 line-height:23px 装不下,
|
|
||||||
按钮溢出 cell 顶部被 overflow:hidden 截掉, 导致按钮底部离行底比文字列小 12px。
|
|
||||||
把 td 上下 padding 去掉、cell line-height 改成 30px, 让 cell 高度 = 按钮高度,
|
|
||||||
并把 cell 顶端贴 td 顶端, 按钮底部就与文字列底部对齐(都离 td 底约 17px)。 */
|
|
||||||
::v-deep .op-cell {
|
|
||||||
padding-top: 0 !important;
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
::v-deep .op-cell > .cell {
|
|
||||||
line-height: 30px !important;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<el-card class="module-card notice-card" shadow="hover">
|
<el-card class="module-card notice-card" shadow="hover">
|
||||||
<div slot="header" class="card-header">
|
<div slot="header" class="card-header">
|
||||||
<span><i class="el-icon-bell card-icon"></i> 通知公告</span>
|
<span><i class="el-icon-bell card-icon"></i> 通知公告</span>
|
||||||
<el-button type="text" class="more-btn" @click="handleMore('notice')">更多 <i class="el-icon-arrow-right"></i></el-button>
|
<el-button v-show="false" type="text" class="more-btn" @click="handleMore('notice')">更多 <i class="el-icon-arrow-right"></i></el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body notice-body" v-loading="noticeLoading">
|
<div class="card-body notice-body" v-loading="noticeLoading">
|
||||||
<div v-if="noticeList.length === 0" class="empty-state mini-empty">
|
<div v-if="noticeList.length === 0" class="empty-state mini-empty">
|
||||||
@@ -28,18 +28,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
|
||||||
<el-pagination
|
|
||||||
@size-change="handleNoticeSizeChange"
|
|
||||||
@current-change="handleNoticeCurrentChange"
|
|
||||||
:current-page="noticePage.currentPage"
|
|
||||||
:page-sizes="[5, 10, 20, 50]"
|
|
||||||
:page-size="noticePage.pageSize"
|
|
||||||
:total="noticePage.total"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
small
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -49,7 +37,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listNotice } from "@/api/system/notice"
|
import { listNoticeTop } from "@/api/system/notice"
|
||||||
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
|
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -59,12 +47,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
version: "3.9.2",
|
version: "3.9.2",
|
||||||
noticeList: [],
|
noticeList: [],
|
||||||
noticeLoading: false,
|
noticeLoading: false
|
||||||
noticePage: {
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -104,12 +87,8 @@ export default {
|
|||||||
},
|
},
|
||||||
loadNoticeList() {
|
loadNoticeList() {
|
||||||
this.noticeLoading = true
|
this.noticeLoading = true
|
||||||
listNotice({
|
listNoticeTop().then(response => {
|
||||||
pageNum: this.noticePage.currentPage,
|
this.noticeList = response.data || []
|
||||||
pageSize: this.noticePage.pageSize
|
|
||||||
}).then(response => {
|
|
||||||
this.noticeList = response.rows || []
|
|
||||||
this.noticePage.total = response.total || 0
|
|
||||||
this.noticeLoading = false
|
this.noticeLoading = false
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.noticeLoading = false
|
this.noticeLoading = false
|
||||||
@@ -117,15 +96,6 @@ export default {
|
|||||||
},
|
},
|
||||||
handleViewNotice(item) {
|
handleViewNotice(item) {
|
||||||
this.$refs.noticeViewRef.open(item)
|
this.$refs.noticeViewRef.open(item)
|
||||||
},
|
|
||||||
handleNoticeSizeChange(val) {
|
|
||||||
this.noticePage.pageSize = val
|
|
||||||
this.noticePage.currentPage = 1
|
|
||||||
this.loadNoticeList()
|
|
||||||
},
|
|
||||||
handleNoticeCurrentChange(val) {
|
|
||||||
this.noticePage.currentPage = val
|
|
||||||
this.loadNoticeList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,375 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="lock-container">
|
|
||||||
<!-- 动态粒子背景 -->
|
|
||||||
<canvas ref="particleCanvas" class="particle-bg"></canvas>
|
|
||||||
|
|
||||||
<!-- 时钟 -->
|
|
||||||
<div class="lock-time">{{ currentTime }}</div>
|
|
||||||
<div class="lock-date">{{ currentDate }}</div>
|
|
||||||
|
|
||||||
<!-- 锁屏卡片 -->
|
|
||||||
<div class="lock-card">
|
|
||||||
<div class="avatar-wrap">
|
|
||||||
<img :src="avatar" class="lock-avatar" @error="onAvatarError" />
|
|
||||||
<div class="lock-icon">🔒</div>
|
|
||||||
</div>
|
|
||||||
<div class="lock-username">{{ nickName }}</div>
|
|
||||||
<div class="lock-hint">系统已锁定,请输入密码解锁</div>
|
|
||||||
|
|
||||||
<div class="input-wrap" :class="{ shake: isShaking }">
|
|
||||||
<input ref="passwordInput" v-model="password" type="password" placeholder="请输入登录密码" class="lock-input" @keydown.enter="handleUnlock" autocomplete="off" />
|
|
||||||
<button class="unlock-btn" @click="handleUnlock" :disabled="loading">
|
|
||||||
<span v-if="!loading">→</span>
|
|
||||||
<span v-else class="loading-dot">···</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="errorMsg" class="error-msg">{{ errorMsg }}</div>
|
|
||||||
|
|
||||||
<div class="lock-footer">
|
|
||||||
<a href="/login" @click.prevent="goLogin">退出重新登录</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import { unlockScreen } from '@/api/login'
|
|
||||||
import defAva from '@/assets/images/profile.jpg'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'LockScreen',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
password: '',
|
|
||||||
loading: false,
|
|
||||||
errorMsg: '',
|
|
||||||
isShaking: false,
|
|
||||||
currentTime: '',
|
|
||||||
currentDate: '',
|
|
||||||
timer: null,
|
|
||||||
animationId: null,
|
|
||||||
particles: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(['avatar', 'nickName'])
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.startClock()
|
|
||||||
this.initParticles()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
clearInterval(this.timer)
|
|
||||||
cancelAnimationFrame(this.animationId)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onAvatarError(e) {
|
|
||||||
e.target.src = defAva
|
|
||||||
},
|
|
||||||
startClock() {
|
|
||||||
const update = () => {
|
|
||||||
const now = new Date()
|
|
||||||
const h = String(now.getHours()).padStart(2, '0')
|
|
||||||
const m = String(now.getMinutes()).padStart(2, '0')
|
|
||||||
const s = String(now.getSeconds()).padStart(2, '0')
|
|
||||||
this.currentTime = `${h}:${m}:${s}`
|
|
||||||
const days = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
|
|
||||||
this.currentDate = `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日 ${days[now.getDay()]}`
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
this.timer = setInterval(update, 1000)
|
|
||||||
},
|
|
||||||
async handleUnlock() {
|
|
||||||
if (!this.password) {
|
|
||||||
this.showError('请输入密码')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.loading = true
|
|
||||||
this.errorMsg = ''
|
|
||||||
try {
|
|
||||||
await unlockScreen(this.password)
|
|
||||||
const lockPath = this.$store.getters.lockPath // 取锁屏前的路径
|
|
||||||
await this.$store.dispatch('lock/unlockScreen')
|
|
||||||
this.$router.replace(lockPath)
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err.message || err.toString()
|
|
||||||
this.showError(msg)
|
|
||||||
this.password = ''
|
|
||||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
|
||||||
} finally {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showError(msg) {
|
|
||||||
this.errorMsg = msg
|
|
||||||
this.isShaking = true
|
|
||||||
setTimeout(() => { this.isShaking = false }, 600)
|
|
||||||
},
|
|
||||||
goLogin() {
|
|
||||||
this.$store.dispatch('lock/unlockScreen')
|
|
||||||
this.$store.dispatch('LogOut').then(() => {
|
|
||||||
this.$router.push('/login')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 粒子背景
|
|
||||||
initParticles() {
|
|
||||||
const canvas = this.$refs.particleCanvas
|
|
||||||
if (!canvas) return
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
const resize = () => {
|
|
||||||
canvas.width = window.innerWidth
|
|
||||||
canvas.height = window.innerHeight
|
|
||||||
}
|
|
||||||
resize()
|
|
||||||
window.addEventListener('resize', resize)
|
|
||||||
const count = 80
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
this.particles.push({
|
|
||||||
x: Math.random() * canvas.width,
|
|
||||||
y: Math.random() * canvas.height,
|
|
||||||
r: Math.random() * 2 + 1,
|
|
||||||
dx: (Math.random() - 0.5) * 0.6,
|
|
||||||
dy: (Math.random() - 0.5) * 0.6,
|
|
||||||
alpha: Math.random() * 0.5 + 0.2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const draw = () => {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
||||||
this.particles.forEach(p => {
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2)
|
|
||||||
ctx.fillStyle = `rgba(255,255,255,${p.alpha})`
|
|
||||||
ctx.fill()
|
|
||||||
p.x += p.dx
|
|
||||||
p.y += p.dy
|
|
||||||
if (p.x < 0 || p.x > canvas.width) p.dx *= -1
|
|
||||||
if (p.y < 0 || p.y > canvas.height) p.dy *= -1
|
|
||||||
})
|
|
||||||
// 连线
|
|
||||||
for (let i = 0; i < this.particles.length; i++) {
|
|
||||||
for (let j = i + 1; j < this.particles.length; j++) {
|
|
||||||
const a = this.particles[i], b = this.particles[j]
|
|
||||||
const dist = Math.hypot(a.x - b.x, a.y - b.y)
|
|
||||||
if (dist < 120) {
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(a.x, a.y)
|
|
||||||
ctx.lineTo(b.x, b.y)
|
|
||||||
ctx.strokeStyle = `rgba(255,255,255,${0.15 * (1 - dist / 120)})`
|
|
||||||
ctx.lineWidth = 0.5
|
|
||||||
ctx.stroke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.animationId = requestAnimationFrame(draw)
|
|
||||||
}
|
|
||||||
draw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.lock-container {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 9999;
|
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.particle-bg {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-time {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
font-size: 72px;
|
|
||||||
font-weight: 200;
|
|
||||||
color: #fff;
|
|
||||||
letter-spacing: 4px;
|
|
||||||
text-shadow: 0 0 40px rgba(255,255,255,0.3);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-date {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
font-size: 15px;
|
|
||||||
color: rgba(255,255,255,0.6);
|
|
||||||
margin-bottom: 48px;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-card {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 40px 48px;
|
|
||||||
width: 360px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 25px 60px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrap {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-avatar {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid rgba(255,255,255,0.3);
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-icon {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -4px;
|
|
||||||
right: -4px;
|
|
||||||
background: rgba(255,255,255,0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 13px;
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-username {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-hint {
|
|
||||||
color: rgba(255,255,255,0.5);
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrap {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
border: 1px solid rgba(255,255,255,0.2);
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 4px 4px 4px 20px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrap:focus-within {
|
|
||||||
border-color: rgba(255,255,255,0.6);
|
|
||||||
background: rgba(255,255,255,0.13);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrap.shake {
|
|
||||||
animation: shake 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shake {
|
|
||||||
0%, 100% { transform: translateX(0); }
|
|
||||||
20% { transform: translateX(-8px); }
|
|
||||||
40% { transform: translateX(8px); }
|
|
||||||
60% { transform: translateX(-6px); }
|
|
||||||
80% { transform: translateX(6px); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-input {
|
|
||||||
flex: 1;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-input::placeholder {
|
|
||||||
color: rgba(255,255,255,0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlock-btn {
|
|
||||||
width: 42px;
|
|
||||||
height: 42px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s, opacity 0.2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlock-btn:hover:not(:disabled) {
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlock-btn:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-dot {
|
|
||||||
font-size: 13px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-msg {
|
|
||||||
margin-top: 14px;
|
|
||||||
color: #ff7675;
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: center;
|
|
||||||
animation: fadeIn 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: translateY(-4px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-footer {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-footer a {
|
|
||||||
color: rgba(255,255,255,0.4);
|
|
||||||
font-size: 13px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-footer a:hover {
|
|
||||||
color: rgba(255,255,255,0.8);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
+326
-57
@@ -1,48 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
<div class="login-card">
|
||||||
<h3 class="title">{{title}}</h3>
|
<aside class="brand-panel">
|
||||||
<el-form-item prop="username">
|
<div class="brand-main">
|
||||||
|
<div class="brand-mark" aria-hidden="true">教</div>
|
||||||
|
<h1 class="brand-title">{{ title }}</h1>
|
||||||
|
<p class="brand-tagline">开课计划 · 课表编排 · 成绩管理 · 教学分析</p>
|
||||||
|
<div class="brand-illustration" aria-hidden="true">
|
||||||
|
<svg-icon icon-class="education" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="brand-footnote">仅限授权单位内部使用</p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="form-panel">
|
||||||
|
<div class="form-heading">
|
||||||
|
<h2>用户登录</h2>
|
||||||
|
<p>请使用内网账号进入系统</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" label-position="top" @submit.native.prevent>
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginForm.username"
|
v-model="loginForm.username"
|
||||||
type="text"
|
type="text"
|
||||||
auto-complete="off"
|
auto-complete="off"
|
||||||
placeholder="账号"
|
placeholder="请输入用户名"
|
||||||
>
|
/>
|
||||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password">
|
<el-form-item label="密码" prop="password">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginForm.password"
|
v-model="loginForm.password"
|
||||||
type="password"
|
type="password"
|
||||||
auto-complete="off"
|
auto-complete="off"
|
||||||
placeholder="密码"
|
placeholder="请输入密码"
|
||||||
|
show-password
|
||||||
@keyup.enter.native="handleLogin"
|
@keyup.enter.native="handleLogin"
|
||||||
>
|
/>
|
||||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
<div class="form-extra">
|
||||||
<el-form-item style="width:100%;">
|
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||||
|
<router-link v-if="register" class="register-link" to="/register">立即注册</router-link>
|
||||||
|
</div>
|
||||||
|
<el-form-item class="login-action">
|
||||||
<el-button
|
<el-button
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
size="medium"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
style="width:100%;"
|
class="login-btn"
|
||||||
|
native-type="submit"
|
||||||
@click.native.prevent="handleLogin"
|
@click.native.prevent="handleLogin"
|
||||||
>
|
>
|
||||||
<span v-if="!loading">登 录</span>
|
<span v-if="!loading">登 录</span>
|
||||||
<span v-else>登 录 中...</span>
|
<span v-else>登 录 中...</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<div style="float: right;" v-if="register">
|
|
||||||
<router-link class="link-type" :to="'/register'">立即注册</router-link>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<!-- 底部 -->
|
|
||||||
<div class="el-login-footer">
|
<p class="access-hint">登录后按账号权限进入对应业务,请妥善保管账号与口令。</p>
|
||||||
<span>{{ footerContent }}</span>
|
<div class="role-tags" aria-label="系统角色">
|
||||||
|
<span v-for="role in roles" :key="role.name" class="role-tag" :class="role.tone">{{ role.name }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="form-footer">{{ footerContent }}</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,7 +75,14 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title: process.env.VUE_APP_TITLE,
|
title: process.env.VUE_APP_TITLE,
|
||||||
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
|
footerContent: "Copyright © 2018-2026 教学管理信息系统 内部使用",
|
||||||
|
roles: [
|
||||||
|
{ name: "系统管理员", tone: "admin" },
|
||||||
|
{ name: "教务人员", tone: "affairs" },
|
||||||
|
{ name: "教研室", tone: "office" },
|
||||||
|
{ name: "教员", tone: "teacher" },
|
||||||
|
{ name: "学员", tone: "student" }
|
||||||
|
],
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: "admin",
|
username: "admin",
|
||||||
password: "admin123",
|
password: "admin123",
|
||||||
@@ -71,7 +97,6 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
// 注册开关
|
|
||||||
register: false,
|
register: false,
|
||||||
redirect: undefined
|
redirect: undefined
|
||||||
}
|
}
|
||||||
@@ -132,49 +157,293 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
min-height: 100%;
|
||||||
background-image: url("../assets/images/login-background.jpg");
|
padding: 28px 20px;
|
||||||
background-size: cover;
|
background:
|
||||||
|
radial-gradient(circle at 18% 16%, rgba(11, 107, 85, 0.28), transparent 42%),
|
||||||
|
radial-gradient(circle at 86% 82%, rgba(198, 155, 60, 0.10), transparent 36%),
|
||||||
|
#10241f;
|
||||||
}
|
}
|
||||||
.title {
|
|
||||||
margin: 0px auto 30px auto;
|
.login-card {
|
||||||
text-align: center;
|
display: flex;
|
||||||
color: #707070;
|
width: min(1080px, 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(160deg, #0c7a62 0%, #0b6b55 46%, #084c3f 100%);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 22px 56px rgba(8, 36, 30, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
width: 42%;
|
||||||
|
padding: 48px 40px 24px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
left: 40px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: #7a5410;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #e4c56a;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 10px rgba(16, 36, 28, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-tagline {
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-illustration {
|
||||||
|
width: 116px;
|
||||||
|
height: 116px;
|
||||||
|
margin-top: 44px;
|
||||||
|
color: rgba(255, 255, 255, 0.12);
|
||||||
|
font-size: 116px;
|
||||||
|
line-height: 1;
|
||||||
|
transform: rotate(-6deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-illustration .svg-icon {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-footnote {
|
||||||
|
margin: 16px 0 0;
|
||||||
|
color: rgba(255, 255, 255, 0.52);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
padding: 56px 56px 28px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-heading {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-heading h2 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: #172b25;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-heading p {
|
||||||
|
margin: 0;
|
||||||
|
color: #7a8a85;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
border-radius: 6px;
|
::v-deep .el-form-item {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-form-item__label {
|
||||||
|
float: none;
|
||||||
|
display: block;
|
||||||
|
padding: 0 0 8px;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #172b25;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-form-item__content {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-input__inner {
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 16px;
|
||||||
|
color: #172b25;
|
||||||
|
background: #f3f6f5;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-input__inner:hover {
|
||||||
|
border-color: #c8d8d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-input__inner:focus {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
width: 400px;
|
border-color: #0b6b55;
|
||||||
padding: 25px 25px 5px 25px;
|
box-shadow: 0 0 0 3px rgba(11, 107, 85, 0.10);
|
||||||
z-index: 1;
|
}
|
||||||
.el-input {
|
|
||||||
height: 38px;
|
::v-deep .el-input__suffix {
|
||||||
input {
|
right: 10px;
|
||||||
height: 38px;
|
}
|
||||||
|
|
||||||
|
::v-deep .el-form-item__error {
|
||||||
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.input-icon {
|
|
||||||
height: 39px;
|
.form-extra {
|
||||||
width: 14px;
|
display: flex;
|
||||||
margin-left: 2px;
|
align-items: center;
|
||||||
}
|
justify-content: space-between;
|
||||||
}
|
margin: -6px 0 18px;
|
||||||
.login-tip {
|
|
||||||
|
::v-deep .el-checkbox__label {
|
||||||
|
color: #52645f;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
text-align: center;
|
font-weight: 400;
|
||||||
color: #bfbfbf;
|
|
||||||
}
|
}
|
||||||
.el-login-footer {
|
}
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
.register-link {
|
||||||
position: fixed;
|
color: #0b6b55;
|
||||||
bottom: 0;
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-action {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
height: 48px;
|
||||||
color: #fff;
|
font-size: 16px;
|
||||||
font-family: Arial;
|
font-weight: 600;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.access-hint {
|
||||||
|
margin: 18px 0 16px;
|
||||||
|
color: #7a8a85;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 1px;
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.6;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag.admin {
|
||||||
|
color: #3d6ea8;
|
||||||
|
background: #e8f1fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag.affairs {
|
||||||
|
color: #0b6b55;
|
||||||
|
background: #e8f3ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag.office {
|
||||||
|
color: #b77a17;
|
||||||
|
background: #f8f1e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag.teacher {
|
||||||
|
color: #6b5b93;
|
||||||
|
background: #f0eaf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag.student {
|
||||||
|
color: #9a7424;
|
||||||
|
background: #f7f0de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 28px;
|
||||||
|
color: #9aa7a3;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.login {
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-panel {
|
||||||
|
width: 100%;
|
||||||
|
padding: 28px 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
position: static;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-title {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-illustration {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-panel {
|
||||||
|
padding: 32px 24px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.form-heading h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -74,12 +74,6 @@
|
|||||||
<!-- ==================== 2. 操作按钮区 ==================== -->
|
<!-- ==================== 2. 操作按钮区 ==================== -->
|
||||||
<el-card shadow="never" class="action-card">
|
<el-card shadow="never" class="action-card">
|
||||||
<div class="action-bar">
|
<div class="action-bar">
|
||||||
<div class="bar-left">
|
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">
|
|
||||||
【课程表数据文件模板】下载
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bar-center">
|
<div class="bar-center">
|
||||||
<div class="file-area">
|
<div class="file-area">
|
||||||
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
|
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
|
||||||
@@ -150,7 +144,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listTeachingPlan, importTeachingPlan, downloadTeachingPlanTemplate } from '@/api/schedule/teachingPlan'
|
import { listTeachingPlan, importTeachingPlan } from '@/api/schedule/teachingPlan'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PlanImportIndex',
|
name: 'PlanImportIndex',
|
||||||
@@ -254,14 +248,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 下载模板 ====================
|
// ==================== 通用 Blob 下载 ====================
|
||||||
handleDownloadTemplate() {
|
|
||||||
downloadTeachingPlanTemplate().then(blob => {
|
|
||||||
this.downloadBlob(blob, '教学实施计划导入模板.xlsx')
|
|
||||||
this.$message.success('模板下载成功')
|
|
||||||
}).catch(() => {})
|
|
||||||
},
|
|
||||||
|
|
||||||
downloadBlob(blob, fileName) {
|
downloadBlob(blob, fileName) {
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||||
<el-card shadow="never" class="search-card">
|
<el-card shadow="never" class="search-card">
|
||||||
<el-form :model="queryForm" label-width="90px" inline class="search-form">
|
<el-form :model="queryForm" label-width="90px" inline class="search-form">
|
||||||
<el-form-item label="学号">
|
<el-form-item v-if="!isStudentRole" label="学号">
|
||||||
<el-input v-model="queryForm.xh" placeholder="请输入学号" clearable style="width: 180px" />
|
<el-input v-model="queryForm.xh" placeholder="请输入学号" clearable style="width: 180px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="年度">
|
<el-form-item label="年度">
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<el-option v-for="item in kbOptions" :key="item.kbh" :label="item.kmc" :value="item.kbh" />
|
<el-option v-for="item in kbOptions" :key="item.kbh" :label="item.kmc" :value="item.kbh" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="班次">
|
<el-form-item v-if="!isStudentRole" label="班次">
|
||||||
<el-select v-model="queryForm.xydbh" placeholder="请选择班次" clearable filterable style="width: 200px">
|
<el-select v-model="queryForm.xydbh" placeholder="请选择班次" clearable filterable style="width: 200px">
|
||||||
<el-option v-for="item in teamOptions" :key="item.xydbh" :label="item.xydmc" :value="item.xydbh" />
|
<el-option v-for="item in teamOptions" :key="item.xydbh" :label="item.xydmc" :value="item.xydbh" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -122,6 +122,13 @@ export default {
|
|||||||
this.handleSearch()
|
this.handleSearch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
/** 学员角色(后端角色键 STUDENT)隐藏学号/班次查询,仅查看本人成绩 */
|
||||||
|
isStudentRole() {
|
||||||
|
const roles = this.$store.getters.roles
|
||||||
|
return Array.isArray(roles) && roles.includes('STUDENT')
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadKbOptions()
|
this.loadKbOptions()
|
||||||
this.loadTeamOptions()
|
this.loadTeamOptions()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container student-info-page">
|
<div class="app-container student-info-page">
|
||||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||||
<el-card shadow="never" class="search-card">
|
<el-card v-if="!isStudentRole" shadow="never" class="search-card">
|
||||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="24">
|
||||||
<!-- 左栏 -->
|
<!-- 左栏 -->
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<el-card shadow="never" class="table-card">
|
<el-card shadow="never" class="table-card">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<div class="list-title">学员信息列表</div>
|
<div class="list-title">学员信息列表</div>
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学员</el-button>
|
<el-button v-if="!isStudentRole" type="primary" icon="el-icon-plus" @click="handleAdd">新增学员</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table v-loading="loading" :data="tableData" class="student-info-table" stripe border>
|
<el-table v-loading="loading" :data="tableData" class="student-info-table" stripe border>
|
||||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||||
@@ -472,6 +472,11 @@ export default {
|
|||||||
|
|
||||||
return [...new Set(years)].sort((firstYear, secondYear) => secondYear.localeCompare(firstYear))
|
return [...new Set(years)].sort((firstYear, secondYear) => secondYear.localeCompare(firstYear))
|
||||||
},
|
},
|
||||||
|
/** 学员角色(后端角色键 STUDENT)隐藏条件查询栏与新增学员按钮 */
|
||||||
|
isStudentRole() {
|
||||||
|
const roles = this.$store.getters.roles
|
||||||
|
return Array.isArray(roles) && roles.includes('STUDENT')
|
||||||
|
},
|
||||||
filteredGradesData() {
|
filteredGradesData() {
|
||||||
return this.filterGradesByYear(this.gradesData)
|
return this.filterGradesByYear(this.gradesData)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getSemester } from '@/api/teachBusiness/semester'
|
import { getSemester } from '@/api/teachBusiness/semester'
|
||||||
import { listXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||||
|
|
||||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||||
const SLOT_DEFS = [
|
const SLOT_DEFS = [
|
||||||
@@ -123,6 +123,16 @@ function fmtDate(d) {
|
|||||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成 32 位小写 UUID(去横线),与后端 UuidUtil.getUUID 一致,用于 bh 缺失时兜底
|
||||||
|
function genUUID() {
|
||||||
|
let s = ''
|
||||||
|
const chars = '0123456789abcdef'
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
s += chars[Math.floor(Math.random() * 16)]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SchoolCalendarEditor',
|
name: 'SchoolCalendarEditor',
|
||||||
props: {
|
props: {
|
||||||
@@ -182,9 +192,10 @@ export default {
|
|||||||
})
|
})
|
||||||
return cols
|
return cols
|
||||||
},
|
},
|
||||||
// xqxlb 的年度(nd)为年份(如 2026),由 6 位学期代号(如 202601)截取前 4 位得出
|
// xqxlb 的 nd 后端已统一为「6 位学期代号」(如 202701),与 this.nd 一致。
|
||||||
|
// 不再按日历起点/季度取年份,避免跨年度学期解析出错。
|
||||||
xqxlbNd() {
|
xqxlbNd() {
|
||||||
return Number(String(this.nd).slice(0, 4))
|
return Number(String(this.nd).slice(0, 6)) || Number(this.nd) || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -260,16 +271,34 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位)
|
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位)
|
||||||
|
// XQXLB 的 nd 存的是「年份」(如 2026),春/夏/秋三学期共用同一批年度记录;
|
||||||
|
// 后端 updateById 只能改已存在的 bh,所以每个格子必须有预生成的记录才能存。
|
||||||
|
// 若本学期日期区间 [kxrq, jsrq] 内还没有任何占位记录(首次进入该学期),
|
||||||
|
// 调用 /xqxlb/add 只按本学期 kxrq/jsrq 预生成这一张网格,再重新拉取,
|
||||||
|
// 这样后续 /xqxlb/update 的 updateById 才能命中每一格。(区间有覆盖则跳过,幂等)
|
||||||
loadXqxlbEvents() {
|
loadXqxlbEvents() {
|
||||||
listXqxlb({ nd: this.xqxlbNd }).then(res => {
|
const fetchList = () => listXqxlb({ nd: this.xqxlbNd }).then(res => {
|
||||||
const data = res.data
|
const data = res.data
|
||||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
return Array.isArray(data) ? data : (data && data.records) || []
|
||||||
return list
|
})
|
||||||
}).then(list => {
|
const render = (list) => {
|
||||||
const loaded = {}
|
// 后端 add() 的按「月日」去重对已存的全量日期不生效,同一年度多次打开/刷新会
|
||||||
|
// 积累同一格的重复占位记录(各带不同 bh)。updateById 只改其中一行,其余空记录
|
||||||
|
// 会在重新拉取时把刚保存的事件名覆盖掉。故这里按「周-星期-节次」先去重:
|
||||||
|
// 同一格保留有事件名(jqmc非空)的一条;都没有则保留最后一条兜底(保证有 bh)。
|
||||||
|
const unique = {}
|
||||||
list.forEach(item => {
|
list.forEach(item => {
|
||||||
const pos = this.locateXqxlb(item)
|
const pos = this.locateXqxlb(item)
|
||||||
if (!pos) return
|
if (!pos) return
|
||||||
|
const stable = this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)
|
||||||
|
const prev = unique[stable]
|
||||||
|
if (!prev || (item.jqmc && !prev.item.jqmc)) {
|
||||||
|
unique[stable] = { item: item, pos: pos }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const loaded = {}
|
||||||
|
Object.keys(unique).forEach(stable => {
|
||||||
|
const { item, pos } = unique[stable]
|
||||||
const ev = {
|
const ev = {
|
||||||
bh: item.bh,
|
bh: item.bh,
|
||||||
name: item.jqmc || '',
|
name: item.jqmc || '',
|
||||||
@@ -289,10 +318,27 @@ export default {
|
|||||||
})
|
})
|
||||||
// 合并到现有格子(后端数据覆盖已有状态)
|
// 合并到现有格子(后端数据覆盖已有状态)
|
||||||
this.events = Object.assign({}, this.events, loaded)
|
this.events = Object.assign({}, this.events, loaded)
|
||||||
}).catch(() => {
|
}
|
||||||
|
fetchList().then(list => {
|
||||||
|
const hasCoverage = list.some(item => this.isInRange(item.jqsj))
|
||||||
|
if (!hasCoverage && this.startDate && this.endDate) {
|
||||||
|
// 本学期区间尚无占位记录:仅用本期 kxrq/jsrq 预生成整张网格
|
||||||
|
const startStr = fmtDate(this.startDate)
|
||||||
|
const endStr = fmtDate(this.endDate)
|
||||||
|
return addXqxlb(startStr, endStr).then(() => fetchList())
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}).then(render).catch(() => {
|
||||||
this.$message.warning('校历事件加载失败')
|
this.$message.warning('校历事件加载失败')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 判断某条记录(jqsj 日期)是否落在本学期 [startDate, endDate] 区间内
|
||||||
|
isInRange(jqsj) {
|
||||||
|
if (!this.startDate || !this.endDate || !jqsj) return false
|
||||||
|
const d = new Date(String(jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||||
|
if (isNaN(d.getTime())) return false
|
||||||
|
return d >= this.startDate && d <= this.endDate
|
||||||
|
},
|
||||||
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
|
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
|
||||||
locateXqxlb(item) {
|
locateXqxlb(item) {
|
||||||
if (!this.calendarStart || !item.jqsj) return null
|
if (!this.calendarStart || !item.jqsj) return null
|
||||||
@@ -476,16 +522,18 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const name = this.toolbar.eventName.trim()
|
const name = this.toolbar.eventName.trim()
|
||||||
// 空名称时按清除处理
|
// 设定时未填写事件名:提示,不触发清除
|
||||||
if (!name) {
|
if (!name) {
|
||||||
this.deleteEvent()
|
this.$message.warning('请输入事件名')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const keys = [...this.selectedKeys]
|
const keys = [...this.selectedKeys]
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const prev = this.events[key]
|
const prev = this.events[key]
|
||||||
this.$set(this.events, key, {
|
this.$set(this.events, key, {
|
||||||
bh: prev ? prev.bh : undefined,
|
// 编辑已有事件:沿用后端返回的 bh;新增事件:bh 由 loadXqxlbEvents
|
||||||
|
// 通过 /xqxlb/add 预生成的占位行提供(首次进入学期时已落库)
|
||||||
|
bh: prev && prev.bh ? prev.bh : undefined,
|
||||||
name: name,
|
name: name,
|
||||||
bold: this.toolbar.bold,
|
bold: this.toolbar.bold,
|
||||||
schedulable: this.toolbar.schedulable,
|
schedulable: this.toolbar.schedulable,
|
||||||
@@ -565,8 +613,14 @@ export default {
|
|||||||
const payload = this.buildXqxlbPayload(key, this.events[key])
|
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||||
if (!payload) return Promise.resolve(false)
|
if (!payload) return Promise.resolve(false)
|
||||||
return updateXqxlb(payload)
|
return updateXqxlb(payload)
|
||||||
.then(() => true)
|
.then(() => {
|
||||||
.catch(() => false)
|
console.warn('[设定] key=', key, 'bh来源=', this.events[key] && this.events[key].bh ? '已有' : '生成', 'bh=', payload.bh, 'nd=', payload.nd, 'name=', payload.jqmc)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('[设定] key=', key, '保存失败', err && (err.message || err))
|
||||||
|
return false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return Promise.all(tasks).then(results => {
|
return Promise.all(tasks).then(results => {
|
||||||
const ok = results.filter(r => r === true).length
|
const ok = results.filter(r => r === true).length
|
||||||
@@ -614,7 +668,9 @@ export default {
|
|||||||
d.setDate(this.calendarStart.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
d.setDate(this.calendarStart.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
||||||
return {
|
return {
|
||||||
delFlag: 0,
|
delFlag: 0,
|
||||||
bh: ev.bh || undefined,
|
// bh 编号必须传:有后端记录则沿用其 bh,否则前端生成 UUID 兜底(避免 payload 缺主键导致 updateById 失效)
|
||||||
|
bh: ev.bh || genUUID(),
|
||||||
|
// nd 后端已统一为 6 位学期代号(如 202701),与查询口径一致,不再取记录日期的年份
|
||||||
nd: this.xqxlbNd,
|
nd: this.xqxlbNd,
|
||||||
jqsj: fmtDate(d),
|
jqsj: fmtDate(d),
|
||||||
jqmc: ev.name || '',
|
jqmc: ev.name || '',
|
||||||
|
|||||||
@@ -192,16 +192,29 @@ export default {
|
|||||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位)
|
// 从后端拉取校历事件,渲染到对应时间格
|
||||||
|
// xqxlb.nd 后端已统一为 6 位学期代号(如 202701),与 this.nd 一致,一次查询即可
|
||||||
loadXqxlbEvents() {
|
loadXqxlbEvents() {
|
||||||
listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {
|
if (!this.calendarStart || !this.nd) return
|
||||||
|
listXqxlb({ nd: Number(String(this.nd).slice(0, 6)) || Number(this.nd) }).then(res => {
|
||||||
const data = res.data
|
const data = res.data
|
||||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||||
const loaded = {}
|
const loaded = {}
|
||||||
|
// 同一格可能有多条(后端 add() 去重失效累积的重复占位)。
|
||||||
|
// 按格子去重:同一格优先保留「有事件名(jqmc非空)」的那条,避免空占位把事件盖成空白。
|
||||||
|
const unique = {}
|
||||||
list.forEach(item => {
|
list.forEach(item => {
|
||||||
const pos = this.locateXqxlb(item)
|
const pos = this.locateXqxlb(item)
|
||||||
if (pos) {
|
if (!pos) return
|
||||||
loaded[pos.key] = {
|
const prev = unique[pos.key]
|
||||||
|
const has = !!item.jqmc
|
||||||
|
if (!prev || (has && !prev.has)) {
|
||||||
|
unique[pos.key] = { has: has, item: item }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.keys(unique).forEach(key => {
|
||||||
|
const { item } = unique[key]
|
||||||
|
loaded[key] = {
|
||||||
bh: item.bh,
|
bh: item.bh,
|
||||||
name: item.jqmc || '',
|
name: item.jqmc || '',
|
||||||
schedulable: !!item.kpk,
|
schedulable: !!item.kpk,
|
||||||
@@ -209,7 +222,6 @@ export default {
|
|||||||
remarkShow: !!item.bzxs,
|
remarkShow: !!item.bzxs,
|
||||||
remark: item.bz || ''
|
remark: item.bz || ''
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
this.events = loaded
|
this.events = loaded
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<div class="teach-calendar app-container">
|
<div class="teach-calendar app-container">
|
||||||
<school-calendar-editor
|
<school-calendar-editor
|
||||||
v-if="nd"
|
v-if="nd"
|
||||||
|
:key="nd"
|
||||||
:nd="nd"
|
:nd="nd"
|
||||||
:semester-name="semesterName"
|
:semester-name="semesterName"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
@@ -26,6 +27,16 @@ export default {
|
|||||||
this.nd = this.$route.query.nd || ''
|
this.nd = this.$route.query.nd || ''
|
||||||
this.semesterName = this.$route.query.name || ''
|
this.semesterName = this.$route.query.name || ''
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
// 编辑校历页被 keep-alive 缓存复用:同路径下 query.nd 变化时不会重新走 created(),
|
||||||
|
// 需在此同步到 nd,并通过 :key="nd" 让编辑器随学期切换全新挂载,读取正确学期数据。
|
||||||
|
'$route.query.nd'(val) {
|
||||||
|
if (val && String(val) !== String(this.nd)) {
|
||||||
|
this.nd = val
|
||||||
|
this.semesterName = this.$route.query.name || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||||
|
|||||||
@@ -6,22 +6,16 @@
|
|||||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.8">
|
<el-col :span="1.8">
|
||||||
<el-button type="success" plain icon="el-icon-date" size="mini" :disabled="!currentSemester"
|
<el-button type="success" plain icon="el-icon-date" size="mini"
|
||||||
@click="handleEditCalendar">
|
@click="handleEditCalendar">
|
||||||
编辑校历
|
编辑校历
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">
|
|
||||||
批量删除
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<el-table v-loading="loading" :data="semesterList" border :height="tableHeight" highlight-current-row
|
<el-table v-loading="loading" :data="semesterList" border :height="tableHeight" highlight-current-row
|
||||||
@current-change="handleCurrentChange" @selection-change="handleSelectionChange">
|
@current-change="handleCurrentChange">
|
||||||
<el-table-column type="selection" align="center" width="50" />
|
|
||||||
<el-table-column label="学期名称" align="center" width="250">
|
<el-table-column label="学期名称" align="center" width="250">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ getSemesterName(scope.row.nd) }}</span>
|
<span>{{ getSemesterName(scope.row.nd) }}</span>
|
||||||
@@ -43,18 +37,18 @@
|
|||||||
<span>{{ formatDate(scope.row.jsrq) }}</span>
|
<span>{{ formatDate(scope.row.jsrq) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="周数" align="center" prop="sdzs" width="100" />
|
<el-table-column label="周数" align="center" prop="sdzs" width="55" />
|
||||||
<el-table-column label="调课不审批" align="center" width="120">
|
<el-table-column label="调课不审批" align="center" width="100">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
|
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="禁止调课" align="center" width="120">
|
<el-table-column label="禁止调课" align="center" width="80">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
|
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="终结成绩定及格" align="center" width="160">
|
<el-table-column label="终结成绩定及格" align="center" width="130">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
|
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
@@ -214,14 +208,8 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
// 学期列表数据
|
// 学期列表数据
|
||||||
semesterList: [],
|
semesterList: [],
|
||||||
// 选中的学期数组
|
|
||||||
ids: [],
|
|
||||||
// 当前点击选中的学期
|
// 当前点击选中的学期
|
||||||
currentSemester: null,
|
currentSemester: null,
|
||||||
// 非单个禁用
|
|
||||||
single: true,
|
|
||||||
// 非多个禁用
|
|
||||||
multiple: true,
|
|
||||||
// 弹出层标题
|
// 弹出层标题
|
||||||
title: "",
|
title: "",
|
||||||
// 是否显示弹出层
|
// 是否显示弹出层
|
||||||
@@ -277,7 +265,14 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'form.kxrq': 'calculateWeeks',
|
'form.kxrq': 'calculateWeeks',
|
||||||
'form.jsrq': 'calculateWeeks'
|
'form.jsrq': 'calculateWeeks',
|
||||||
|
// 新增时选择学期类型后,自动带出开学/结束日期
|
||||||
|
'form.xq'(val) {
|
||||||
|
if (this.isAdd) this.autoFillDatesForXq()
|
||||||
|
},
|
||||||
|
'form.xn'(val) {
|
||||||
|
if (this.isAdd) this.autoFillDatesForXq()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 顶栏切换当前学期后自动刷新 */
|
/** 顶栏切换当前学期后自动刷新 */
|
||||||
@@ -298,6 +293,23 @@ export default {
|
|||||||
if (xn === undefined || xn === null || xn === '' || !xq) return ''
|
if (xn === undefined || xn === null || xn === '' || !xq) return ''
|
||||||
return String(xn) + String(xq)
|
return String(xn) + String(xq)
|
||||||
},
|
},
|
||||||
|
/** 根据学年+学期类型自动带出开学/结束日期(春季01:2-5月,夏季02:6-8月,秋季03:9-12月) */
|
||||||
|
autoFillDatesForXq() {
|
||||||
|
const xn = this.form.xn
|
||||||
|
const xq = this.form.xq
|
||||||
|
if (xn === undefined || xn === null || xn === '') return
|
||||||
|
const year = Number(xn)
|
||||||
|
const pad = m => `${year}-${String(m).padStart(2, '0')}`
|
||||||
|
const rules = {
|
||||||
|
'01': { start: pad(2) + '-01', end: pad(5) + '-31' },
|
||||||
|
'02': { start: pad(6) + '-01', end: pad(8) + '-31' },
|
||||||
|
'03': { start: pad(9) + '-01', end: pad(12) + '-31' }
|
||||||
|
}
|
||||||
|
const rule = rules[xq]
|
||||||
|
if (!rule) return
|
||||||
|
this.form.kxrq = rule.start
|
||||||
|
this.form.jsrq = rule.end
|
||||||
|
},
|
||||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||||
getSemesterName(nd) {
|
getSemesterName(nd) {
|
||||||
if (!nd) return ''
|
if (!nd) return ''
|
||||||
@@ -365,12 +377,6 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 多选变化 */
|
|
||||||
handleSelectionChange(selection) {
|
|
||||||
this.ids = selection.map(item => item.nd)
|
|
||||||
this.single = selection.length !== 1
|
|
||||||
this.multiple = !selection.length
|
|
||||||
},
|
|
||||||
/** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */
|
/** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */
|
||||||
handleCurrentChange(currentRow) {
|
handleCurrentChange(currentRow) {
|
||||||
this.currentSemester = currentRow || null
|
this.currentSemester = currentRow || null
|
||||||
@@ -405,7 +411,7 @@ export default {
|
|||||||
},
|
},
|
||||||
/** 编辑按钮 */
|
/** 编辑按钮 */
|
||||||
handleUpdate(row) {
|
handleUpdate(row) {
|
||||||
const semesterId = row && row.nd ? row.nd : this.ids[0]
|
const semesterId = row && row.nd ? row.nd : ''
|
||||||
if (!semesterId) return
|
if (!semesterId) return
|
||||||
this.reset()
|
this.reset()
|
||||||
this.isAdd = false
|
this.isAdd = false
|
||||||
@@ -443,21 +449,21 @@ export default {
|
|||||||
/** 编辑校历按钮:对当前选中的学期编辑校历 */
|
/** 编辑校历按钮:对当前选中的学期编辑校历 */
|
||||||
handleEditCalendar() {
|
handleEditCalendar() {
|
||||||
const row = this.currentSemester
|
const row = this.currentSemester
|
||||||
if (!row || !row.nd) return
|
if (!row || !row.nd) {
|
||||||
|
this.$modal.msgWarning('请选择学期')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/teachBusiness/semester/semesterCalendar',
|
path: '/teachBusiness/semester/semesterCalendar',
|
||||||
query: { nd: row.nd, name: this.getSemesterName(row.nd) }
|
query: { nd: row.nd, name: this.getSemesterName(row.nd) }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/** 删除按钮 */
|
/** 删除按钮:单行删除 */
|
||||||
handleDelete(row) {
|
handleDelete(row) {
|
||||||
const semesterIds = row && row.nd ? [row.nd] : this.ids
|
if (!row || !row.nd) return
|
||||||
if (!semesterIds.length) return
|
const name = this.getSemesterName(row.nd)
|
||||||
const names = semesterIds.map(nd => this.getSemesterName(nd)).join('、')
|
this.$modal.confirm('确认删除学期【' + name + '】吗?').then(() => {
|
||||||
this.$modal.confirm('确认删除学期【' + names + '】吗?').then(() => {
|
return delSemester(row.nd)
|
||||||
// 批量删除:逐个调用
|
|
||||||
const delList = semesterIds.map(nd => delSemester(nd))
|
|
||||||
return Promise.all(delList)
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.getList()
|
this.getList()
|
||||||
this.$modal.msgSuccess("删除成功")
|
this.$modal.msgSuccess("删除成功")
|
||||||
|
|||||||
@@ -90,12 +90,9 @@
|
|||||||
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
|
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
|
||||||
批量停止报名
|
批量停止报名
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button icon="el-icon-refresh" @click="handleReverseRange">
|
<!-- <el-button icon="el-icon-refresh" @click="handleReverseRange">
|
||||||
所选批量根据学员反向确定班次范围
|
所选批量根据学员反向确定班次范围
|
||||||
</el-button>
|
</el-button> -->
|
||||||
</div>
|
|
||||||
<div class="action-row">
|
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【选修课数据文件模板】下载</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -412,9 +409,6 @@ export default {
|
|||||||
handleReverseRange() {
|
handleReverseRange() {
|
||||||
this.$message.warning('后端暂未提供该接口')
|
this.$message.warning('后端暂未提供该接口')
|
||||||
},
|
},
|
||||||
handleDownloadTemplate() {
|
|
||||||
this.$message.warning('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================== 文件上传 ====================
|
// ==================== 文件上传 ====================
|
||||||
handleSelectFile() {
|
handleSelectFile() {
|
||||||
|
|||||||
@@ -63,8 +63,6 @@
|
|||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="handleOpenDialog">新建课程科目</el-button>
|
<el-button type="primary" icon="el-icon-plus" @click="handleOpenDialog">新建课程科目</el-button>
|
||||||
<el-button type="danger" plain icon="el-icon-delete" @click="handleBatchDelete">删除所选</el-button>
|
<el-button type="danger" plain icon="el-icon-delete" @click="handleBatchDelete">删除所选</el-button>
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadCourse">下载课程科目基本信息</el-button>
|
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTextbook">下载课程教材基本信息</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【课程科目数据文件模板】下载</el-button>
|
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【课程科目数据文件模板】下载</el-button>
|
||||||
@@ -517,12 +515,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 下载 ====================
|
// ==================== 下载 ====================
|
||||||
handleDownloadCourse() {
|
|
||||||
this.$message.warning('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
handleDownloadTextbook() {
|
|
||||||
this.$message.warning('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
handleDownloadTemplate() {
|
handleDownloadTemplate() {
|
||||||
downloadCourseSubjectTemplate().then(blob => {
|
downloadCourseSubjectTemplate().then(blob => {
|
||||||
saveAs(blob, '课程课目数据文件模板.xls')
|
saveAs(blob, '课程课目数据文件模板.xls')
|
||||||
|
|||||||
@@ -38,10 +38,6 @@
|
|||||||
<div class="left-group">
|
<div class="left-group">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建</el-button>
|
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建</el-button>
|
||||||
<el-button type="danger" plain icon="el-icon-delete" @click="handleDeleteSelected">删除所选</el-button>
|
<el-button type="danger" plain icon="el-icon-delete" @click="handleDeleteSelected">删除所选</el-button>
|
||||||
<el-button icon="el-icon-upload" @click="handleImport">导入教员信息</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="right-group">
|
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教员数据文件模板】下载</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -820,14 +816,6 @@ export default {
|
|||||||
this.$message.info('后端暂未提供该接口')
|
this.$message.info('后端暂未提供该接口')
|
||||||
},
|
},
|
||||||
|
|
||||||
handleImport() {
|
|
||||||
this.$message.info('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDownloadTemplate() {
|
|
||||||
this.$message.info('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================== 新增 ====================
|
// ==================== 新增 ====================
|
||||||
handleNew() {
|
handleNew() {
|
||||||
this.addForm = this.createEmptyAddForm()
|
this.addForm = this.createEmptyAddForm()
|
||||||
|
|||||||
@@ -114,11 +114,6 @@
|
|||||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
@@ -225,6 +220,11 @@ export default {
|
|||||||
this.selectedRows = rows
|
this.selectedRows = rows
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 组件方法:包一层全局 formatDate,供模板调用 */
|
||||||
|
formatDate(val) {
|
||||||
|
return formatDate(val)
|
||||||
|
},
|
||||||
|
|
||||||
/** 根据已填写的查询值构建实际查询参数 */
|
/** 根据已填写的查询值构建实际查询参数 */
|
||||||
buildSearchParams() {
|
buildSearchParams() {
|
||||||
const form = this.searchForm
|
const form = this.searchForm
|
||||||
@@ -255,11 +255,10 @@ export default {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
return getAuditedTeachingLogList(this.buildSearchParams())
|
return getAuditedTeachingLogList(this.buildSearchParams())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200 || res.code === 0) {
|
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||||
const { list, total } = this.extractListData(res)
|
const { list, total } = this.extractListData(res)
|
||||||
this.tableData = list
|
this.tableData = list
|
||||||
this.total = total
|
this.total = total
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -94,8 +94,7 @@
|
|||||||
<div class="left-group">
|
<div class="left-group">
|
||||||
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
|
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
|
||||||
<!-- <el-button @click="handleRecheck">重新检查通报变更情况</el-button> -->
|
<!-- <el-button @click="handleRecheck">重新检查通报变更情况</el-button> -->
|
||||||
<el-button type="danger" plain @click="handleDeleteSelected">删除所选</el-button>
|
<el-button icon="el-icon-download" @click="handleExport">导出</el-button>
|
||||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
|
||||||
<el-button icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
<el-button icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
||||||
<el-button icon="el-icon-upload2" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
|
<el-button icon="el-icon-upload2" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
|
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
|
||||||
@@ -137,7 +136,6 @@
|
|||||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
|
||||||
<el-button type="text" @click="handleReport(scope.row)">上报</el-button>
|
<el-button type="text" @click="handleReport(scope.row)">上报</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -191,11 +189,11 @@
|
|||||||
import { formatDate } from '@/utils/index'
|
import { formatDate } from '@/utils/index'
|
||||||
import {
|
import {
|
||||||
getTeachingLogList,
|
getTeachingLogList,
|
||||||
submitTeachingLog,
|
|
||||||
exportTeachingLog,
|
exportTeachingLog,
|
||||||
importTeachingLogExcel,
|
importTeachingLogExcel,
|
||||||
downloadTeachingLogTemplate,
|
downloadTeachingLogTemplate,
|
||||||
getTeachingLogByBh
|
getTeachingLogByBh,
|
||||||
|
batchReportTeachingLog
|
||||||
} from '@/api/log'
|
} from '@/api/log'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -247,6 +245,8 @@ export default {
|
|||||||
|
|
||||||
// ==================== 导入 ====================
|
// ==================== 导入 ====================
|
||||||
importing: false,
|
importing: false,
|
||||||
|
// 导入成功后是否跳到最后一页(定位到刚导入的数据)
|
||||||
|
importJumpLast: false,
|
||||||
|
|
||||||
// ==================== 详情 ====================
|
// ==================== 详情 ====================
|
||||||
detailVisible: false,
|
detailVisible: false,
|
||||||
@@ -258,6 +258,10 @@ export default {
|
|||||||
this.fetchList()
|
this.fetchList()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatDate(val) {
|
||||||
|
return formatDate(val)
|
||||||
|
},
|
||||||
|
|
||||||
handleSelectionChange(rows) {
|
handleSelectionChange(rows) {
|
||||||
this.selectedRows = rows
|
this.selectedRows = rows
|
||||||
},
|
},
|
||||||
@@ -294,11 +298,20 @@ export default {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
return getTeachingLogList(this.buildSearchParams())
|
return getTeachingLogList(this.buildSearchParams())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200 || res.code === 0) {
|
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||||
const { list, total } = this.extractListData(res)
|
const { list, total } = this.extractListData(res)
|
||||||
|
// 导入成功后:跳到最后一页,定位刚导入的数据
|
||||||
|
if (this.importJumpLast) {
|
||||||
|
this.importJumpLast = false
|
||||||
|
const lastPage = total > 0 ? Math.ceil(total / this.pageSize) : 1
|
||||||
|
if (lastPage !== this.pageNum) {
|
||||||
|
this.pageNum = lastPage
|
||||||
|
this.fetchList()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
this.tableData = list
|
this.tableData = list
|
||||||
this.total = total
|
this.total = total
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// 错误已由拦截器统一处理
|
// 错误已由拦截器统一处理
|
||||||
@@ -337,11 +350,6 @@ export default {
|
|||||||
return ids
|
return ids
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDeleteSelected() {
|
|
||||||
// 后端暂未提供删除接口,仅作提示
|
|
||||||
this.$message.info('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
handleBatchReport() {
|
handleBatchReport() {
|
||||||
const ids = this.getSelectedBhs()
|
const ids = this.getSelectedBhs()
|
||||||
if (ids.length === 0) return
|
if (ids.length === 0) return
|
||||||
@@ -351,12 +359,8 @@ export default {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 提交教学日志审核,逐条上报至「已上报」状态
|
// 批量上报教学日志
|
||||||
let settled = Promise.resolve()
|
return batchReportTeachingLog(ids)
|
||||||
ids.forEach((bh) => {
|
|
||||||
settled = settled.then(() => submitTeachingLog(bh, '已上报'))
|
|
||||||
})
|
|
||||||
return settled
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success(`已上报 ${ids.length} 条记录`)
|
this.$message.success(`已上报 ${ids.length} 条记录`)
|
||||||
@@ -376,7 +380,7 @@ export default {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => submitTeachingLog(String(bh), '已上报'))
|
.then(() => batchReportTeachingLog([String(bh)]))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success('上报成功')
|
this.$message.success('上报成功')
|
||||||
this.fetchList()
|
this.fetchList()
|
||||||
@@ -425,6 +429,7 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200 || res.code === 0) {
|
if (res.code === 200 || res.code === 0) {
|
||||||
this.$message.success(res.message || '导入成功')
|
this.$message.success(res.message || '导入成功')
|
||||||
|
this.importJumpLast = true
|
||||||
this.fetchList()
|
this.fetchList()
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.message || '导入失败')
|
this.$message.error(res.message || '导入失败')
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
<div class="search-actions">
|
<div class="search-actions">
|
||||||
<div class="left-group">
|
<div class="left-group">
|
||||||
<el-button type="primary" @click="handleBatchAudit">审核通过所选</el-button>
|
<el-button v-if="!isTeacher" type="primary" @click="handleBatchAudit">审核通过所选</el-button>
|
||||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-group">
|
<div class="right-group">
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
<el-card shadow="never" class="table-card">
|
<el-card shadow="never" class="table-card">
|
||||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||||
@selection-change="handleSelectionChange">
|
@selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column v-if="!isTeacher" type="selection" width="50" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
<el-table-column prop="rwlb" label="任务类别" width="120" align="center" show-overflow-tooltip />
|
<el-table-column prop="rwlb" label="任务类别" width="120" align="center" show-overflow-tooltip />
|
||||||
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
||||||
@@ -104,23 +104,22 @@
|
|||||||
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="kcmc" label="课程名称" width="80" show-overflow-tooltip />
|
||||||
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="pxqbxx" label="培训期班信息" show-overflow-tooltip />
|
||||||
<el-table-column label="人数" width="70" align="center">
|
<el-table-column label="人数" width="80" align="center">
|
||||||
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="分组指导教员" width="120" align="center">
|
<el-table-column label="分组指导教员" width="100" align="center">
|
||||||
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
|
<el-table-column prop="jsfs" label="教学方法" width="80" align="center" />
|
||||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="gdqk" label="变动汇总" width="100" show-overflow-tooltip />
|
||||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
<el-table-column v-if="!isTeacher" label="操作" width="160" align="center" fixed="right" class-name="table-action-column">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" @click="handleAudit(scope.row)">审核</el-button>
|
<el-button type="text" @click="handleAudit(scope.row)">审核</el-button>
|
||||||
<el-button type="text" @click="handleReject(scope.row)">退回</el-button>
|
<el-button type="text" @click="handleReject(scope.row)">退回</el-button>
|
||||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -173,14 +172,21 @@
|
|||||||
import { formatDate } from '@/utils/index'
|
import { formatDate } from '@/utils/index'
|
||||||
import {
|
import {
|
||||||
getReportedTeachingLogList,
|
getReportedTeachingLogList,
|
||||||
approveTeachingLog,
|
|
||||||
rejectTeachingLog,
|
rejectTeachingLog,
|
||||||
exportTeachingLog,
|
exportTeachingLog,
|
||||||
getTeachingLogByBh
|
getTeachingLogByBh,
|
||||||
|
batchAuditTeachingLog
|
||||||
} from '@/api/log'
|
} from '@/api/log'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReportedView',
|
name: 'ReportedView',
|
||||||
|
props: {
|
||||||
|
/** 当前用户是否为教员角色,用于隐藏审核相关操作 */
|
||||||
|
isTeacher: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// ==================== 查询表单 ====================
|
// ==================== 查询表单 ====================
|
||||||
@@ -239,6 +245,11 @@ export default {
|
|||||||
this.selectedRows = rows
|
this.selectedRows = rows
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 组件方法:包一层全局 formatDate,供模板调用 */
|
||||||
|
formatDate(val) {
|
||||||
|
return formatDate(val)
|
||||||
|
},
|
||||||
|
|
||||||
/** 根据已填写的查询值构建实际查询参数 */
|
/** 根据已填写的查询值构建实际查询参数 */
|
||||||
buildSearchParams() {
|
buildSearchParams() {
|
||||||
const form = this.searchForm
|
const form = this.searchForm
|
||||||
@@ -266,11 +277,10 @@ export default {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
return getReportedTeachingLogList(this.buildSearchParams())
|
return getReportedTeachingLogList(this.buildSearchParams())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 200 || res.code === 0) {
|
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||||
const { list, total } = this.extractListData(res)
|
const { list, total } = this.extractListData(res)
|
||||||
this.tableData = list
|
this.tableData = list
|
||||||
this.total = total
|
this.total = total
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -333,12 +343,8 @@ export default {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 审核通过教学日志,逐条审核
|
// 批量审核教学日志
|
||||||
let settled = Promise.resolve()
|
return batchAuditTeachingLog(ids)
|
||||||
ids.forEach((bh) => {
|
|
||||||
settled = settled.then(() => approveTeachingLog(bh, ''))
|
|
||||||
})
|
|
||||||
return settled
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success(`已审核通过 ${ids.length} 条记录`)
|
this.$message.success(`已审核通过 ${ids.length} 条记录`)
|
||||||
@@ -358,7 +364,7 @@ export default {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
.then(() => approveTeachingLog(String(bh), ''))
|
.then(() => batchAuditTeachingLog([String(bh)]))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success('审核通过成功')
|
this.$message.success('审核通过成功')
|
||||||
this.fetchList()
|
this.fetchList()
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-card shadow="never" class="tab-card">
|
<el-card shadow="never" class="tab-card">
|
||||||
<el-tabs v-model="activeTab" class="organ-tabs">
|
<el-tabs v-model="activeTab" class="organ-tabs">
|
||||||
<el-tab-pane label="教学日志管理查询" name="query">
|
<!-- 教学日志管理查询页:仅管理员/教研室等非教员、非机关人员可见 -->
|
||||||
|
<el-tab-pane v-if="hasQueryAccess" label="教学日志管理查询" name="query">
|
||||||
<log-view v-if="activeTab === 'query'" />
|
<log-view v-if="activeTab === 'query'" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="已上报机关教学日志" name="reported">
|
<el-tab-pane label="已上报机关教学日志" name="reported">
|
||||||
<reported-view v-if="activeTab === 'reported'" />
|
<reported-view v-if="activeTab === 'reported'" :is-teacher="isTeacher" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="机关已审核教学日志" name="audited">
|
<el-tab-pane label="机关已审核教学日志" name="audited">
|
||||||
<audited-view v-if="activeTab === 'audited'" />
|
<audited-view v-if="activeTab === 'audited'" />
|
||||||
@@ -28,6 +29,29 @@ export default {
|
|||||||
return {
|
return {
|
||||||
activeTab: 'query'
|
activeTab: 'query'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
roles() {
|
||||||
|
return this.$store.getters.roles || []
|
||||||
|
},
|
||||||
|
/** 教员角色 */
|
||||||
|
isTeacher() {
|
||||||
|
return this.roles.includes('TEACHER')
|
||||||
|
},
|
||||||
|
/** 机关人员角色 */
|
||||||
|
isDepartmentPersonnel() {
|
||||||
|
return this.roles.includes('DEPARTMENT_PERSONNEL')
|
||||||
|
},
|
||||||
|
/** 教学日志管理查询页仅非教员、非机关人员可见 */
|
||||||
|
hasQueryAccess() {
|
||||||
|
return !this.isTeacher && !this.isDepartmentPersonnel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 教员/机关人员无法查看教学日志管理查询页,默认落在已上报页
|
||||||
|
if (!this.hasQueryAccess) {
|
||||||
|
this.activeTab = 'reported'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -62,8 +62,6 @@
|
|||||||
<div class="list-toolbar">
|
<div class="list-toolbar">
|
||||||
<div class="left-group">
|
<div class="left-group">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建教材信息</el-button>
|
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建教材信息</el-button>
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadList">下载教材列表</el-button>
|
|
||||||
<el-button icon="el-icon-folder-opened" @click="handleDownloadZip">所选文件资料打包ZIP下载</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="right-group">
|
<div class="right-group">
|
||||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教材数据文件模板】下载</el-button>
|
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教材数据文件模板】下载</el-button>
|
||||||
@@ -988,15 +986,6 @@ export default {
|
|||||||
this.fetchList()
|
this.fetchList()
|
||||||
},
|
},
|
||||||
|
|
||||||
// ==================== 无后端接口的操作 ====================
|
|
||||||
handleDownloadList() {
|
|
||||||
this.$message.info('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDownloadZip() {
|
|
||||||
this.$message.info('后端暂未提供该接口')
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================== 新建教材 ====================
|
// ==================== 新建教材 ====================
|
||||||
handleNew() {
|
handleNew() {
|
||||||
this.addForm = this.createEmptyForm()
|
this.addForm = this.createEmptyForm()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
|
|||||||
|
|
||||||
const name = process.env.VUE_APP_TITLE || '教学管理信息系统' // 网页标题
|
const name = process.env.VUE_APP_TITLE || '教学管理信息系统' // 网页标题
|
||||||
|
|
||||||
const baseUrl = 'http://10.1.1.17:8080' // 后端接口
|
const baseUrl = 'http://10.1.1.193:8080' // 后端接口
|
||||||
|
|
||||||
const port = 80 // 固定开发服务器端口,避免多实例端口混乱
|
const port = 80 // 固定开发服务器端口,避免多实例端口混乱
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user