Compare commits

...

16 Commits

46 changed files with 2076 additions and 1559 deletions
-1
View File
@@ -2,7 +2,6 @@
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="DM" uuid="a3a7e92a-b8da-4229-8b83-5db11f9db9a9">
<driver-ref>9fcb59ee-b3c5-470d-89df-ad01e7cabb15</driver-ref>
<imported>true</imported>
<configured-by-url>true</configured-by-url>
<remarks>$PROJECT_DIR$/backend/roomroot-admin/src/main/resources/application-druid.yml</remarks>
+2 -1
View File
@@ -8,7 +8,8 @@
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview"
"preview": "node build/index.js --preview",
"dict:init-course-type": "node scripts/init-course-type-dict.js"
},
"keywords": [
"vue",
+193
View File
@@ -0,0 +1,193 @@
/**
* 版权所有,内部使用。
*
* @description 初始化教学大纲使用的课类型字典,可重复执行且不会重复插入。
* @author 软件研发部
* @since 2026-08-25
*/
const DICT_TYPE = 'course_type'
const DICT_NAME = '课类型'
const DEFAULT_API_BASE_URL = 'http://localhost/api'
const DEFAULT_ADMIN_USERNAME = 'admin'
const DICT_ITEMS = [
{ label: '必修', value: '必修' },
{ label: '选修', value: '选修' },
{ label: '实践', value: '实践' }
]
let authToken = ''
function readHiddenInput(prompt) {
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
throw new Error('当前终端不支持隐藏输入,请在 PowerShell 中直接运行该脚本')
}
return new Promise((resolve, reject) => {
let input = ''
function cleanup() {
process.stdin.removeListener('data', handleInput)
process.stdin.setRawMode(false)
process.stdin.pause()
process.stdout.write('\n')
}
function handleInput(characters) {
for (const character of characters) {
if (character === '\u0003') {
cleanup()
reject(new Error('用户取消执行'))
return
}
if (character === '\r' || character === '\n') {
cleanup()
resolve(input)
return
}
if (character === '\u0008' || character === '\u007f') {
input = input.slice(0, -1)
continue
}
if (character >= ' ') {
input += character
}
}
}
process.stdout.write(prompt)
process.stdin.setEncoding('utf8')
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on('data', handleInput)
})
}
async function authenticate() {
const environmentToken = process.env.EDUCATION_API_TOKEN
if (environmentToken) {
authToken = environmentToken.replace(/^Bearer\s+/i, '')
return
}
const username = process.env.EDUCATION_ADMIN_USERNAME || DEFAULT_ADMIN_USERNAME
let password = await readHiddenInput(`请输入管理员 ${username} 的密码:`)
const response = await fetch(getApiBaseUrl() + '/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json;charset=utf-8' },
body: JSON.stringify({ username, password, code: '', uuid: '' })
})
password = ''
const result = await response.json()
if (!response.ok || result.code !== 200 || !result.token) {
throw new Error(result.msg || `登录失败:HTTP ${response.status}`)
}
authToken = result.token
}
function getApiBaseUrl() {
const configuredUrl = process.env.EDUCATION_API_BASE_URL || DEFAULT_API_BASE_URL
return configuredUrl.replace(/\/+$/, '')
}
async function request(path, options = {}) {
if (!authToken) {
throw new Error('尚未完成管理员认证')
}
const response = await fetch(getApiBaseUrl() + path, {
...options,
headers: {
Authorization: 'Bearer ' + authToken,
'Content-Type': 'application/json;charset=utf-8',
...(options.headers || {})
}
})
const result = await response.json()
if (!response.ok || result.code !== 200) {
throw new Error(result.msg || `请求失败:HTTP ${response.status}`)
}
return result
}
async function ensureDictType() {
const query = new URLSearchParams({
pageNum: '1',
pageSize: '10',
dictType: DICT_TYPE
})
const result = await request('/system/dict/type/list?' + query.toString())
const hasDictType = (result.rows || []).some(item => item.dictType === DICT_TYPE)
if (hasDictType) {
console.log(`字典类型 ${DICT_TYPE} 已存在,跳过新增`)
return
}
await request('/system/dict/type', {
method: 'POST',
body: JSON.stringify({
dictName: DICT_NAME,
dictType: DICT_TYPE,
status: '0',
remark: '教学大纲课类型列表'
})
})
console.log(`已新增字典类型 ${DICT_TYPE}`)
}
async function ensureDictItems() {
const query = new URLSearchParams({
pageNum: '1',
pageSize: '100',
dictType: DICT_TYPE
})
const result = await request('/system/dict/data/list?' + query.toString())
const existingValues = new Set((result.rows || []).map(item => item.dictValue))
for (const [index, item] of DICT_ITEMS.entries()) {
if (existingValues.has(item.value)) {
console.log(`字典项 ${item.label} 已存在,跳过新增`)
continue
}
await request('/system/dict/data', {
method: 'POST',
body: JSON.stringify({
dictSort: index + 1,
dictLabel: item.label,
dictValue: item.value,
dictType: DICT_TYPE,
cssClass: '',
listClass: 'default',
isDefault: 'N',
status: '0',
remark: '教学大纲课类型'
})
})
console.log(`已新增字典项 ${item.label}`)
}
}
async function refreshDictCache() {
await request('/system/dict/type/refreshCache', { method: 'DELETE' })
console.log('字典缓存已刷新')
}
async function main() {
await authenticate()
await ensureDictType()
await ensureDictItems()
await refreshDictCache()
console.log('课类型字典初始化完成')
}
main().catch(error => {
console.error('课类型字典初始化失败:' + error.message)
process.exitCode = 1
})
@@ -0,0 +1,89 @@
import request from '@/utils/request'
// 课表查询管理(KCBController /kcb,前端 baseURL 为 /api,此处不重复 /api 前缀)
// 接口依据:课表页面接口规格
// 查询实体 TimetableQuery:nd 年度(可选,不传自动取当前学期)、xydbh 学员队编号/班次(可选,按班次筛选)
/**
* 今日课表
* GET /kcb/today/list
* @param params TimetableQuerynd、xydbh(均可选)
* 返回:今日课表记录(实体字段 bh/sksj/kc/zrdw/bc/jy/cd/jxnrxff/bz
*/
export function listToday(data) {
return request({
url: '/kcb/today/list',
method: 'get',
params: data
})
}
/**
* 本周课表
* GET /kcb/week/list
* @param params TimetableQuerynd、xydbh(均可选)
* 返回:本周课表记录
*/
export function listWeek(data) {
return request({
url: '/kcb/week/list',
method: 'get',
params: data
})
}
/**
* 班次课表(分页)
* GET /kcb/class/list
* @param params pageNum、pageSizecond=TimetableQuerynd、xydbh
* 返回:PageResult<班次课程记录>records/total
*/
export function listClass(params) {
return request({
url: '/kcb/class/list',
method: 'get',
params
})
}
/**
* 下载学期教学实施计划 Excel
* GET /kcb/class/export-plan
* @param data TimetableQuerynd、xydbh
*/
export function exportClassPlan(data) {
return request({
url: '/kcb/class/export-plan',
method: 'get',
params: data,
responseType: 'blob'
})
}
/**
* 下载学期成绩表 Excel
* GET /kcb/class/export-grades
* @param data TimetableQuerynd、xydbh
*/
export function exportClassGrades(data) {
return request({
url: '/kcb/class/export-grades',
method: 'get',
params: data,
responseType: 'blob'
})
}
/**
* 班次课程列表另存 Excel
* GET /kcb/class/export-courses
* @param data TimetableQuerynd、xydbh
*/
export function exportClassCourses(data) {
return request({
url: '/kcb/class/export-courses',
method: 'get',
params: data,
responseType: 'blob'
})
}
@@ -0,0 +1,615 @@
/**
* Copyright (c) 2026 软件研发部
* 内部使用,未经授权禁止外传。
* @description 教学管理信息系统全局视觉主题
* @author 软件研发部
* @since 2026-08-24
*/
:root {
--edu-color-primary: #0b6b55;
--edu-color-primary-hover: #085442;
--edu-color-primary-active: #063f34;
--edu-color-primary-soft: #e8f3ef;
--edu-color-primary-faint: #f3f8f6;
--edu-color-accent: #c69b3c;
--edu-color-text: #172b25;
--edu-color-text-secondary: #52645f;
--edu-color-text-muted: #7a8a85;
--edu-color-border: #dce6e2;
--edu-color-border-strong: #c8d8d2;
--edu-color-canvas: #f4f7f6;
--edu-color-surface: #ffffff;
--edu-color-success: #238b68;
--edu-color-warning: #b77a17;
--edu-color-danger: #c94b52;
--edu-shadow-sm: 0 1px 2px rgba(19, 45, 37, 0.05);
--edu-shadow-md: 0 6px 18px rgba(19, 45, 37, 0.08);
--edu-shadow-dialog: 0 18px 48px rgba(14, 36, 30, 0.18);
--edu-radius-sm: 5px;
--edu-radius-md: 8px;
--edu-radius-lg: 12px;
--edu-transition-fast: 160ms ease;
--edu-transition-base: 220ms ease;
}
body {
color: var(--edu-color-text);
background: var(--edu-color-canvas);
}
button,
input,
textarea,
select {
font-family: inherit;
}
button,
[role="button"],
.el-button,
.el-dropdown,
.el-menu-item,
.el-submenu__title {
-webkit-tap-highlight-color: transparent;
}
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
.el-button:focus-visible,
.el-input__inner:focus-visible,
.el-textarea__inner:focus-visible {
outline: 2px solid var(--edu-color-primary);
outline-offset: 2px;
}
#app .main-container,
#app .app-main {
background: var(--edu-color-canvas);
}
#app .app-container {
padding: 20px 22px;
}
/* 按钮采用清晰的主次层级,避免同屏操作全部呈现为高强调。 */
#app .el-button {
min-height: 36px;
padding: 9px 16px;
border-radius: var(--edu-radius-sm);
border-color: var(--edu-color-border-strong);
color: #334740;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.1px;
box-shadow: var(--edu-shadow-sm);
transition:
color var(--edu-transition-fast),
background-color var(--edu-transition-fast),
border-color var(--edu-transition-fast),
box-shadow var(--edu-transition-fast);
}
#app .el-button:hover,
#app .el-button:focus {
color: var(--edu-color-primary);
background: var(--edu-color-primary-faint);
border-color: #9cbdb1;
box-shadow: 0 2px 6px rgba(11, 107, 85, 0.10);
}
#app .el-button:active {
color: var(--edu-color-primary-active);
background: #dfede8;
border-color: var(--edu-color-primary);
box-shadow: inset 0 1px 2px rgba(8, 76, 63, 0.12);
}
#app .el-button--primary {
color: #ffffff;
background: var(--edu-color-primary);
border-color: var(--edu-color-primary);
box-shadow: 0 2px 5px rgba(11, 107, 85, 0.18);
}
#app .el-button--primary:hover,
#app .el-button--primary:focus {
color: #ffffff;
background: var(--edu-color-primary-hover);
border-color: var(--edu-color-primary-hover);
box-shadow: 0 4px 10px rgba(11, 107, 85, 0.22);
}
#app .el-button--primary:active {
color: #ffffff;
background: var(--edu-color-primary-active);
border-color: var(--edu-color-primary-active);
}
#app .el-button--primary.is-plain {
color: var(--edu-color-primary);
background: var(--edu-color-primary-faint);
border-color: #9cbdb1;
box-shadow: none;
}
#app .el-button--primary.is-plain:hover,
#app .el-button--primary.is-plain:focus {
color: #ffffff;
background: var(--edu-color-primary);
border-color: var(--edu-color-primary);
}
#app .el-button--success.is-plain,
#app .el-button--warning.is-plain,
#app .el-button--danger.is-plain,
#app .el-button--info.is-plain {
box-shadow: none;
}
#app .el-button--success.is-plain {
color: var(--edu-color-success);
background: #eef8f4;
border-color: #afd5c7;
}
#app .el-button--warning.is-plain {
color: var(--edu-color-warning);
background: #fbf6ea;
border-color: #dfc58f;
}
#app .el-button--danger.is-plain {
color: var(--edu-color-danger);
background: #fbf0f1;
border-color: #e4b3b7;
}
#app .el-button--info.is-plain {
color: #5f706b;
background: #f3f5f4;
border-color: #c8d1ce;
}
#app .el-button--success.is-plain:hover,
#app .el-button--warning.is-plain:hover,
#app .el-button--danger.is-plain:hover,
#app .el-button--info.is-plain:hover {
color: #ffffff;
}
#app .el-button--success {
background: var(--edu-color-success);
border-color: var(--edu-color-success);
}
#app .el-button--warning {
background: var(--edu-color-warning);
border-color: var(--edu-color-warning);
}
#app .el-button--danger {
background: var(--edu-color-danger);
border-color: var(--edu-color-danger);
}
#app .el-button--info {
background: #5f706b;
border-color: #5f706b;
}
#app .el-button--success,
#app .el-button--warning,
#app .el-button--danger,
#app .el-button--info {
color: #ffffff;
}
#app .el-button--success.is-plain:hover {
background: var(--edu-color-success);
border-color: var(--edu-color-success);
}
#app .el-button--warning.is-plain:hover {
background: var(--edu-color-warning);
border-color: var(--edu-color-warning);
}
#app .el-button--danger.is-plain:hover {
background: var(--edu-color-danger);
border-color: var(--edu-color-danger);
}
#app .el-button--info.is-plain:hover {
background: #5f706b;
border-color: #5f706b;
}
#app .el-button--mini {
min-height: 30px;
padding: 6px 11px;
font-size: 13px;
}
#app .el-button--small {
min-height: 32px;
padding: 7px 13px;
}
#app .el-button--text {
min-height: 30px;
padding: 5px 7px;
color: var(--edu-color-primary);
border-color: transparent;
background: transparent;
box-shadow: none;
}
#app .el-button--text:hover,
#app .el-button--text:focus {
color: var(--edu-color-primary-hover);
background: var(--edu-color-primary-soft);
border-color: transparent;
}
#app .el-button.is-circle {
min-width: 36px;
padding: 0;
}
#app .el-button.is-disabled,
#app .el-button.is-disabled:hover,
#app .el-button.is-disabled:focus {
color: #9aa7a3;
background: #f2f4f3;
border-color: #e1e7e4;
box-shadow: none;
}
/* 表单控件保持紧凑,同时用统一焦点状态提升批量录入效率。 */
#app .el-input__inner,
#app .el-textarea__inner,
#app .el-range-editor.el-input__inner {
border-color: var(--edu-color-border-strong);
border-radius: var(--edu-radius-sm);
color: var(--edu-color-text);
background: var(--edu-color-surface);
transition:
border-color var(--edu-transition-fast),
box-shadow var(--edu-transition-fast);
}
#app .el-input__inner:hover,
#app .el-textarea__inner:hover,
#app .el-range-editor.el-input__inner:hover {
border-color: #91b3a7;
}
#app .el-input.is-active .el-input__inner,
#app .el-input__inner:focus,
#app .el-textarea__inner:focus,
#app .el-range-editor.is-active {
border-color: var(--edu-color-primary);
box-shadow: 0 0 0 3px rgba(11, 107, 85, 0.10);
}
#app .el-form-item__label {
color: #344a42;
font-weight: 500;
}
#app .el-checkbox__input.is-checked .el-checkbox__inner,
#app .el-checkbox__input.is-indeterminate .el-checkbox__inner,
#app .el-radio__input.is-checked .el-radio__inner {
background-color: var(--edu-color-primary);
border-color: var(--edu-color-primary);
}
#app .el-checkbox__input.is-checked + .el-checkbox__label,
#app .el-radio__input.is-checked + .el-radio__label {
color: var(--edu-color-primary-hover);
}
/* 数据表格用浅层表头和克制分隔线,强调扫描效率。 */
#app .el-table {
color: #344740;
border-radius: var(--edu-radius-md);
border: 1px solid var(--edu-color-border);
overflow: hidden;
}
#app .el-table::before,
#app .el-table--border::after {
background-color: var(--edu-color-border);
}
#app .el-table th.el-table__cell {
color: #29443b;
font-weight: 600;
background: #edf4f1;
border-bottom-color: #d7e4df;
}
#app .el-table td.el-table__cell {
border-bottom-color: #e7eeeb;
}
#app .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
background: #f2f8f5;
}
#app .el-table .current-row > td.el-table__cell {
background: #e8f3ef;
}
/* 行级操作保持单行,避免按钮被列宽挤压后增加表格行高。 */
#app .el-table .table-action-column .cell {
white-space: nowrap;
}
/* 卡片与弹窗采用一致的白色工作面,不使用装饰性大渐变。 */
#app .el-card {
border: 1px solid var(--edu-color-border);
border-radius: var(--edu-radius-lg);
background: var(--edu-color-surface);
box-shadow: var(--edu-shadow-sm);
}
#app .el-card__header {
padding: 15px 18px;
color: var(--edu-color-text);
font-weight: 600;
background: #fbfcfc;
border-bottom-color: var(--edu-color-border);
}
#app .el-dialog {
border-radius: var(--edu-radius-lg);
box-shadow: var(--edu-shadow-dialog);
overflow: hidden;
}
#app .el-dialog__header {
padding: 18px 22px 16px;
background: #f8faf9;
border-bottom: 1px solid var(--edu-color-border);
}
#app .el-dialog__title {
color: var(--edu-color-text);
font-size: 17px;
font-weight: 600;
}
#app .el-dialog__body {
padding: 22px;
}
#app .el-dialog__footer {
padding: 14px 22px 18px;
background: #fbfcfc;
border-top: 1px solid #edf1ef;
}
#app .el-pagination.is-background .el-pager li:not(.disabled).active {
background: var(--edu-color-primary);
}
#app .el-pagination button:hover,
#app .el-pager li:hover {
color: var(--edu-color-primary);
}
#app .el-tag {
border-radius: 4px;
font-weight: 500;
}
/* 教学管理列表页保持统一的信息层级,同时允许各业务页面保留原有结构。 */
#app .management-page {
--management-section-gap: 16px;
--management-section-padding: 18px;
}
#app .management-page .section-card {
padding: var(--management-section-padding);
margin-bottom: var(--management-section-gap);
border: 1px solid var(--edu-color-border);
border-radius: var(--edu-radius-lg);
background: var(--edu-color-surface);
box-shadow: var(--edu-shadow-sm);
}
#app .management-page .section-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding-bottom: 16px;
margin-bottom: 18px;
border-bottom: 1px solid #edf1ef;
}
#app .management-page .section-heading--compact {
padding-bottom: 12px;
margin-bottom: 14px;
}
#app .management-page .section-title {
margin: 0;
color: var(--edu-color-text);
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
#app .management-page .section-description {
margin: 4px 0 0;
color: var(--edu-color-text-secondary);
font-size: 12px;
line-height: 1.5;
}
#app .management-page .export-bar,
#app .management-page .action-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
#app .management-page .export-bar .el-button + .el-button,
#app .management-page .action-row .el-button + .el-button {
margin-left: 0;
}
#app .management-page .table-heading {
margin-bottom: 0;
}
/* 选修课操作较多,宽屏时并列展示操作与导出,降低按钮堆叠造成的视觉噪音。 */
#app .elective-page {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(360px, 1fr);
gap: var(--management-section-gap);
}
#app .elective-page .section-card {
margin-bottom: 0;
}
#app .elective-page .query-card,
#app .elective-page .upload-card,
#app .elective-page .table-card {
grid-column: 1 / -1;
}
#app .elective-page .action-card {
grid-column: 1;
grid-row: 2;
}
#app .elective-page .export-card {
grid-column: 2;
grid-row: 2;
}
#app .management-page .query-card .q-control,
#app .management-page .search-card .q-control {
max-width: 520px;
}
/* 首页沿用原有模块,不改变信息结构,仅降低大色块与渐变的视觉重量。 */
#app .home {
background: var(--edu-color-canvas);
}
#app .home .module-card {
border: 1px solid var(--edu-color-border);
background: var(--edu-color-surface);
box-shadow: var(--edu-shadow-sm);
}
#app .home .module-card:hover {
border-color: #c9dcd5;
box-shadow: var(--edu-shadow-md);
}
#app .home .module-card .el-card__header {
color: var(--edu-color-text);
background: #fbfcfc;
border-bottom: 1px solid var(--edu-color-border);
}
#app .home .card-header {
color: var(--edu-color-text);
}
#app .home .card-header .card-icon {
color: var(--edu-color-primary);
}
#app .home .card-header .more-btn {
color: var(--edu-color-primary);
}
#app .home .contact-item .contact-icon {
background: var(--edu-color-primary-soft);
color: var(--edu-color-primary);
border-radius: var(--edu-radius-md);
box-shadow: none;
}
/* 下拉浮层渲染在 body 下,因此使用系统级选择器统一其视觉。 */
.el-select-dropdown,
.el-dropdown-menu,
.el-picker-panel,
.el-popover {
border-color: var(--edu-color-border);
border-radius: var(--edu-radius-md);
box-shadow: var(--edu-shadow-md);
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover,
.el-dropdown-menu__item:not(.is-disabled):hover,
.el-dropdown-menu__item:not(.is-disabled):focus {
color: var(--edu-color-primary);
background: var(--edu-color-primary-soft);
}
.el-select-dropdown__item.selected {
color: var(--edu-color-primary);
font-weight: 600;
}
@media (max-width: 768px) {
#app .app-container {
padding: 14px;
}
#app .el-button {
min-height: 38px;
}
#app .el-dialog {
width: calc(100% - 24px);
margin-top: 4vh;
}
#app .management-page {
--management-section-padding: 14px;
}
#app .management-page .section-heading,
#app .management-page .upload-row {
align-items: stretch;
flex-direction: column;
}
#app .management-page .section-heading .el-button,
#app .management-page .upload-right .el-button {
width: 100%;
}
}
@media (max-width: 1200px) {
#app .elective-page {
display: block;
}
#app .elective-page .section-card {
margin-bottom: var(--management-section-gap);
}
}
@media (prefers-reduced-motion: reduce) {
#app *,
#app *::before,
#app *::after {
scroll-behavior: auto;
transition-duration: 0.01ms;
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}
@@ -4,13 +4,13 @@
**/
/* theme color */
$--color-primary: #00875A;
$--color-success: #13ce66;
$--color-warning: #ffba00;
$--color-danger: #ff4949;
$--color-primary: #0b6b55;
$--color-success: #238b68;
$--color-warning: #c58a22;
$--color-danger: #c94b52;
// $--color-info: #1E1E1E;
$--button-font-weight: 400;
$--button-font-weight: 500;
// $--color-text-regular: #1f2d3d;
+7 -6
View File
@@ -4,20 +4,21 @@
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './education-theme.scss';
/* ================= 系统主题色(教育绿) ================= */
:root {
/* 基础绿 */
--edu-green: #007F4F;
--edu-green-hover: #00663E;
--edu-green-bg: #E3F2E9;
--edu-green: #0b6b55;
--edu-green-hover: #085442;
--edu-green-bg: #e8f3ef;
/* 主色:导航、按钮、主题色 */
--edu-green-primary: #00875A; /* rgb(0,135,90) */
--edu-green-primary: #0b6b55;
/* hover 加深 */
--edu-green-dark: #006B47; /* rgb(0,107,71) */
--edu-green-dark: #084c3f;
/* 浅色背景、标签底色 */
--edu-green-light: #E6F4EA; /* rgb(230,244,234) */
--edu-green-light: #e8f3ef;
}
body {
+20 -2
View File
@@ -109,18 +109,36 @@
}
.el-table__body-wrapper {
// 宽表格统一通过内容区拖拽横向查看,隐藏重复的底部滚动条但保留滚动能力。
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.el-button [class*="el-icon-"] + span {
margin-left: 1px;
}
}
// 表格横向拖拽滚动:拖拽过程中光标切换为“抓手”
// 表格横向拖拽滚动:拖拽过程中光标保持默认箭头,不切换为“抓手”
.el-table__body-wrapper.is-dragging,
.el-table__fixed-body-wrapper.is-dragging {
cursor: grabbing;
cursor: default;
}
}
/* 表格横拖期间锁定整页光标与文本选中:
避免光标划过按钮/链接时闪变成手型、以及拖拽过程误选中文字 */
body.el-table-dragging,
body.el-table-dragging * {
user-select: none !important;
cursor: default !important;
}
/* 原生表格同样禁止选中文本,避免横向滑动/滚动时误触发复制前的文本选中 */
table {
-webkit-user-select: none;
+12 -9
View File
@@ -5,6 +5,8 @@
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
border-right: 1px solid rgba(3, 49, 40, 0.32);
box-shadow: 4px 0 14px rgba(8, 51, 42, 0.08);
height: 100%;
position: relative;
overflow: hidden;
@@ -52,14 +54,15 @@
white-space: nowrap !important;
height: 44px !important;
line-height: 44px !important;
font-weight: 600;
font-weight: 500;
letter-spacing: 0.2px;
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
&:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
background-color: rgba(255, 255, 255, 0.08) !important;
}
}
@@ -111,29 +114,29 @@
content: '';
position: absolute;
inset: 0;
background-color: rgba(255, 255, 255, 0.15);
border-right: 3px solid #FFC53D;
background-color: rgba(255, 255, 255, 0.13);
border-left: 3px solid #d7b45a;
pointer-events: none;
z-index: 1;
}
}
.el-submenu.is-active > .el-submenu__title {
color: #E6F4EA !important;
color: #ffffff !important;
font-weight: 600;
&::before {
background-color: rgba(255, 255, 255, 0.15);
border-right: 3px solid #FFC53D;
background-color: rgba(255, 255, 255, 0.10);
border-left: 3px solid #d7b45a;
}
.el-submenu__icon-arrow {
color: #FFC53D !important;
color: #e2c36f !important;
}
}
.el-submenu__title .el-submenu__icon-arrow {
color: rgba(255, 255, 255, 0.9);
color: rgba(255, 255, 255, 0.76);
font-size: 12px;
font-weight: 700;
transition: transform 0.2s;
+5 -5
View File
@@ -8,16 +8,16 @@ $tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
// 默认菜单主题风格(教育绿)
$base-menu-color: #e8f5ef;
// 默认菜单主题风格(学院绿)
$base-menu-color: #dcebe6;
$base-menu-color-active: #ffffff;
$base-menu-background: #00875A;
$base-menu-background: #0b5f4a;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-sub-menu-background: #006B47;
$base-sub-menu-hover: rgba(255,255,255,.12);
$base-sub-menu-background: #084c3f;
$base-sub-menu-hover: rgba(255,255,255,.10);
$base-sidebar-width: 200px;
+73 -35
View File
@@ -3,15 +3,18 @@
* 隐藏表格的横向滚动条,按住鼠标左键横向拖动即可查看后面的字段。
* 通过 document 事件委托全局生效,所有 el-table 自动支持,无需逐页绑定。
*
* 手感优化:
* - 文本选中已在全局样式中禁用(user-select: none),拖拽过程不会再误选文字
* - 位移以横向为主且超过阈值才触发滚动,轻微手抖、普通点击、纵向拖拽(如行排序)不受影响
* - 光标仅在真正拖动时变为抓手,平时保持默认
* - 拖拽结束(确实发生了滚动)后屏蔽本次 click,避免误触发行点击、复选框等
* 实现要点(全面优化
* - 采用 Pointer Events + setPointerCapture:按下瞬间把指针捕获到滚动容器上,
* 之后无论光标移动到固定列、表格下方还是窗口外,移动事件都不会中断,真正"跟手"。
* - requestAnimationFrame 合并每帧滚动,避免高频事件造成卡顿/闪烁。
* - 横向位移超过阈值即触发,允许轻微斜向/弧线;仅当纵向明显占优时才不触发,
* 保留普通点击与纵向拖拽(如行排序)的区分。
* - 拖拽期间整页锁定光标与文本选中,光标保持默认箭头,不闪变为抓手/手型。
* - 拖拽结束(确实发生了滚动)后屏蔽本次 click,避免误触发行点击、复选框等。
*/
const DRAG_THRESHOLD = 4 // 横向位移超过该像素且以横向为主,才判定为拖动
const DRAG_THRESHOLD = 2 // 横向位移超过该像素即判定为拖动,越小越跟手
// 在这些元素上按下鼠标不触发拖拽,保证按钮、复选框等交互不受影响
// 在这些元素上按下不触发拖拽,保证按钮、复选框等交互不受影响
const INTERACTIVE_SELECTOR = [
'a', 'button', 'input', 'textarea', 'select', 'label',
'.el-button', '.el-checkbox', '.el-radio', '.el-switch', '.el-select',
@@ -20,77 +23,112 @@ const INTERACTIVE_SELECTOR = [
].join(',')
let wrapper = null
let pointerId = null
let startX = 0
let startY = 0
let startScrollLeft = 0
let dragging = false
let rafId = null
let pendingScrollLeft = 0
function isHorizontallyScrollable(el) {
return el.scrollWidth > el.clientWidth + 1
}
function handleMouseDown(e) {
if (e.button !== 0) return
// 每帧只应用一次滚动位置,提升平滑度
function applyScroll() {
if (!wrapper) return
wrapper.scrollLeft = pendingScrollLeft
rafId = null
}
function handlePointerDown(e) {
if (e.button !== 0 && e.pointerType === 'mouse') return
if (e.target.closest(INTERACTIVE_SELECTOR)) return
const wrap = e.target.closest('.el-table__body-wrapper')
// 固定列(如 fixed="right" 的操作列)渲染在 .el-table__fixed-body-wrapper 中,悬浮覆盖在
// 表体右侧,需一并识别;最终统一落到真正负责横向滚动的 .el-table__body-wrapper 上
const anyWrapper = e.target.closest('.el-table__body-wrapper, .el-table__fixed-body-wrapper')
if (!anyWrapper) return
const table = anyWrapper.closest('.el-table')
const wrap = table && table.querySelector('.el-table__body-wrapper')
if (!wrap || !isHorizontallyScrollable(wrap)) return
wrapper = wrap
pointerId = e.pointerId
startX = e.clientX
startY = e.clientY
startScrollLeft = wrap.scrollLeft
pendingScrollLeft = startScrollLeft
dragging = false
// 关键:捕获指针,之后所有 pointermove/pointerup 都持续派发到容器,
// 即使光标移出表体/窗口也不中断
if (wrap.setPointerCapture && e.pointerId != null) {
try { wrap.setPointerCapture(e.pointerId) } catch (err) { /* 忽略捕获失败 */ }
}
// 阻止默认行为,抑制原生图片/文本拖拽与选中
e.preventDefault()
}
function handleMouseMove(e) {
if (!wrapper) return
function handlePointerMove(e) {
if (!wrapper || e.pointerId !== pointerId) return
e.preventDefault()
const dx = e.clientX - startX
const dy = e.clientY - startY
if (!dragging) {
// 轻微位移视为点击,不触发滚动;位移必须以横向为主,避免与纵向拖拽(如行排序)冲突
if (Math.abs(dx) <= DRAG_THRESHOLD || Math.abs(dx) <= Math.abs(dy)) return
// 开始拖动:以当前位置为基准,避免起始跳变
// 仅当纵向明显占优(超过横向 3 倍且超过 12px)才判定为纵向操作、不触发横向拖动,
// 其余情况(含轻微斜向/弧线)都正常触发,避免"拖不动"
if (Math.abs(dx) <= DRAG_THRESHOLD || (Math.abs(dy) > Math.abs(dx) * 3 && Math.abs(dy) > 12)) return
dragging = true
startX = e.clientX
startY = e.clientY
startScrollLeft = wrapper.scrollLeft
wrapper.classList.add('is-dragging')
// 整页锁定光标与文本选中,保证拖拽手感一致(光标不闪变、文字不误选)
document.body.classList.add('el-table-dragging')
}
e.preventDefault()
wrapper.scrollLeft = startScrollLeft - (e.clientX - startX)
// 记录目标位置,交由 rAF 统一应用
pendingScrollLeft = startScrollLeft - (e.clientX - startX)
if (rafId == null) rafId = requestAnimationFrame(applyScroll)
}
function handleMouseUp() {
function endDrag(blockClick) {
if (!wrapper) return
if (rafId != null) {
cancelAnimationFrame(rafId)
rafId = null
}
wrapper.classList.remove('is-dragging')
if (dragging) {
document.body.classList.remove('el-table-dragging')
if (blockClick && dragging) {
// 拖拽确实发生了滚动,屏蔽本次 click,避免误触发行点击、复选框等
const blockClick = (ev) => {
const suppress = (ev) => {
ev.preventDefault()
ev.stopPropagation()
document.removeEventListener('click', blockClick, true)
document.removeEventListener('click', suppress, true)
}
document.addEventListener('click', blockClick, true)
document.addEventListener('click', suppress, true)
}
wrapper = null
pointerId = null
dragging = false
}
// 鼠标拖出窗口外时清理拖拽状态,防止状态残留
function handleMouseLeave() {
if (!wrapper) return
wrapper.classList.remove('is-dragging')
wrapper = null
dragging = false
function handlePointerUp(e) {
if (e.pointerId !== pointerId) return
endDrag(true)
}
function handlePointerCancel() {
endDrag(false)
}
const install = function() {
if (install.installed) return
install.installed = true
document.addEventListener('mousedown', handleMouseDown, false)
document.addEventListener('mousemove', handleMouseMove, false)
document.addEventListener('mouseup', handleMouseUp, false)
document.addEventListener('mouseleave', handleMouseLeave, false)
document.addEventListener('pointerdown', handlePointerDown, true)
document.addEventListener('pointermove', handlePointerMove, true)
document.addEventListener('pointerup', handlePointerUp, true)
document.addEventListener('pointercancel', handlePointerCancel, true)
}
export default install
+19 -19
View File
@@ -159,19 +159,19 @@ export default {
justify-content: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
transition: all 0.25s ease;
border-radius: 7px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.10);
transition: background-color 0.18s ease, border-color 0.18s ease;
cursor: pointer;
&:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.28);
}
&:active {
transform: translateY(0);
background: rgba(255, 255, 255, 0.21);
}
.svg-icon {
@@ -192,19 +192,19 @@ export default {
justify-content: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
transition: all 0.25s ease;
border-radius: 7px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.10);
transition: background-color 0.18s ease, border-color 0.18s ease;
cursor: pointer;
&:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.28);
}
&:active {
transform: translateY(0);
background: rgba(255, 255, 255, 0.21);
}
.svg-icon {
@@ -282,7 +282,7 @@ export default {
margin-right: 0;
padding: 0 6px 0 10px;
height: 100%;
border-radius: 22px;
border-radius: 8px;
cursor: pointer;
&.is-active .user-caret {
@@ -299,10 +299,10 @@ export default {
position: relative;
width: 36px;
height: 36px;
border-radius: 50%;
border-radius: 8px;
padding: 2px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.55));
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
flex-shrink: 0;
}
@@ -310,7 +310,7 @@ export default {
cursor: pointer;
width: 100%;
height: 100%;
border-radius: 50%;
border-radius: 6px;
object-fit: cover;
background: var(--edu-green-light);
display: block;
@@ -147,7 +147,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
gap: 10px;
height: 36px;
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
@@ -159,10 +159,9 @@ export default {
justify-content: center;
height: 36px;
padding: 0 16px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.22);
backdrop-filter: blur(2px);
border-radius: 7px;
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.16);
box-sizing: border-box;
i {
@@ -251,7 +250,7 @@ export default {
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.35);
color: #fff;
border-radius: 18px;
border-radius: 7px;
height: 36px;
padding: 0 12px;
display: flex;
@@ -81,7 +81,15 @@ export default {
align-items: center;
justify-content: center;
cursor: pointer;
color: #bfcbd9;
color: rgba(255, 255, 255, 0.72);
border-top: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(3, 49, 40, 0.18);
transition: background-color 160ms ease, color 160ms ease;
&:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.10);
}
.collapse-hamburger {
line-height: 48px;
@@ -135,8 +135,8 @@ export default {
tagActiveStyle(tag) {
if (!this.isActive(tag)) return {}
return {
"background-color": "#00875A",
"border-color": "#00875A"
"background-color": "#e8f3ef",
"border-color": "#0b6b55"
}
},
isAffix(tag) {
@@ -363,7 +363,8 @@ $tags-bar-height: 34px;
height: $tags-bar-height;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
border-bottom: 1px solid #dce6e2;
box-shadow: 0 1px 2px rgba(19, 45, 37, 0.04);
display: flex;
align-items: center;
overflow: hidden;
@@ -413,29 +414,37 @@ $tags-bar-height: 34px;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
border: 1px solid #dce6e2;
color: #52645f;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
border-radius: 3px;
border-radius: 5px;
transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease;
&:hover {
color: #0b6b55;
background: #f3f8f6;
border-color: #b6cec5;
}
&:first-of-type { margin-left: 6px; }
&:last-of-type { margin-right: 15px; }
}
}
&:not(.tags-view-container--chrome) .tags-view-wrapper .tags-view-item.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
background-color: #e8f3ef;
color: #085442;
border-color: #0b6b55;
font-weight: 600;
&::before {
content: '';
background: #fff;
background: #c69b3c;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
width: 3px;
height: 12px;
border-radius: 2px;
position: relative;
margin-right: 2px;
}
+14 -6
View File
@@ -1,5 +1,13 @@
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': '#00875A', '--current-color-light': '#00875A1a', '--current-color-dark-bg': '#00875A33'}">
<div
:class="classObj"
class="app-wrapper"
:style="{
'--current-color': '#0B6B55',
'--current-color-light': '#0B6B551a',
'--current-color-dark-bg': '#0B6B5533'
}"
>
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<!-- 顶部全宽 Header -->
@@ -103,9 +111,9 @@ export default {
height: 60px;
display: flex;
align-items: center;
background: var(--edu-green-primary);
background: linear-gradient(90deg, var(--edu-green-primary) 0%, var(--edu-green-dark) 100%);
box-shadow: 0 2px 8px rgba(0, 21, 41, 0.16);
background: linear-gradient(104deg, #0b6b55 0%, #095b49 58%, #084c3f 100%);
border-bottom: 1px solid rgba(198, 155, 60, 0.5);
box-shadow: 0 3px 12px rgba(8, 51, 42, 0.16);
padding: 0 20px;
z-index: 10;
@@ -140,7 +148,7 @@ export default {
letter-spacing: 1.5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
cursor: pointer;
border-radius: 6px;
border-radius: 5px;
padding: 4px 10px;
transition: background 0.25s ease;
@@ -184,7 +192,7 @@ export default {
position: relative;
overflow: hidden;
height: 100%;
background-color: #f5f7fa;
background-color: var(--edu-color-canvas);
}
.drawer-bg {
+28
View File
@@ -1,6 +1,32 @@
import cache from '@/plugins/cache'
const PERSIST_KEY = 'tags-view-visited'
const MAX_VISITED_VIEWS = 8
function removeViewCache(state, view) {
const cachedViewIndex = state.cachedViews.indexOf(view.name)
if (cachedViewIndex > -1) {
state.cachedViews.splice(cachedViewIndex, 1)
}
state.iframeViews = state.iframeViews.filter(item => item.path !== view.path)
}
function trimVisitedViews(state, currentView) {
while (state.visitedViews.length > MAX_VISITED_VIEWS) {
const removableViewIndex = state.visitedViews.findIndex(view => {
const isAffix = view.meta && view.meta.affix
return !isAffix && view.path !== currentView.path
})
if (removableViewIndex === -1) {
return
}
const [removedView] = state.visitedViews.splice(removableViewIndex, 1)
removeViewCache(state, removedView)
}
}
// 布局已固定,不启用标签页持久化
function isPersistEnabled() {
@@ -43,6 +69,7 @@ const mutations = {
title: view.meta.title || 'no-name'
})
)
trimVisitedViews(state, view)
saveVisitedViews(state.visitedViews)
},
ADD_VISITED_VIEW_FIRST: (state, view) => {
@@ -52,6 +79,7 @@ const mutations = {
title: view.meta.title || 'no-name'
})
)
trimVisitedViews(state, view)
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
@@ -25,12 +25,12 @@
<div class="upload-row">
<div class="upload-left">
<el-button type="primary" @click="handleAdd">新增联教联训</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
<el-button @click="handleDownloadTemplate">联教联训数据文件模板下载</el-button>
<el-button plain @click="handleChooseFile">选择文件</el-button>
<el-button icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">联教联训数据文件模板下载</el-button>
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button type="success" :loading="uploading" @click="handleUpload">上传数据</el-button>
<el-button type="primary" icon="el-icon-upload2" :loading="uploading" @click="handleUpload">上传数据</el-button>
</div>
</div>
</el-card>
+2 -210
View File
@@ -1,89 +1,8 @@
<template>
<div class="app-container home">
<!-- 第一行信息/日志/公告 -->
<!-- 通知公告 -->
<el-row :gutter="20" class="bottom-row">
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="module-card info-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-phone-outline card-icon"></i> 联系信息</span>
</div>
<div class="card-body">
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-s-promotion"></i></div>
<div class="contact-text">
<div class="label">官网</div>
<el-link href="http://www.roomroot.vip" target="_blank">http://www.roomroot.vip</el-link>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-user-solid"></i></div>
<div class="contact-text">
<div class="label">QQ群</div>
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632" target="_blank">127358632</a>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-chat-dot-round"></i></div>
<div class="contact-text">
<div class="label">微信</div>
<span>风影随行</span>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-money"></i></div>
<div class="contact-text">
<div class="label">支付宝</div>
<span>风影随行</span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="module-card log-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-time card-icon"></i> 登录日志</span>
</div>
<div class="card-body" v-loading="loginLogLoading">
<table v-if="loginLogList.length > 0" class="data-table">
<thead>
<tr>
<th>登录人</th>
<th>登录时间</th>
</tr>
</thead>
<tbody>
<tr v-for="item in loginLogList" :key="item.infoId">
<td>
<i class="el-icon-user table-icon"></i>
{{ item.userName }}
</td>
<td>{{ parseTime(item.loginTime) }}</td>
</tr>
</tbody>
</table>
<div v-else class="empty-state">
<i class="el-icon-folder-opened empty-icon"></i>
<p>暂无记录</p>
</div>
</div>
<div class="card-footer">
<el-pagination
@size-change="handleLoginLogSizeChange"
@current-change="handleLoginLogCurrentChange"
:current-page="loginLogPage.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="loginLogPage.pageSize"
:total="loginLogPage.total"
layout="total, prev, pager, next"
small
/>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<el-card class="module-card notice-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-bell card-icon"></i> 通知公告</span>
@@ -131,7 +50,6 @@
<script>
import { listNotice } from "@/api/system/notice"
import { homeList } from "@/api/monitor/logininfor"
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
export default {
@@ -142,13 +60,6 @@ export default {
version: "3.9.2",
noticeList: [],
noticeLoading: false,
loginLogList: [],
loginLogLoading: false,
loginLogPage: {
currentPage: 1,
pageSize: 10,
total: 0
},
noticePage: {
currentPage: 1,
pageSize: 10,
@@ -158,7 +69,6 @@ export default {
},
mounted() {
this.loadNoticeList()
this.loadLoginLogList()
},
methods: {
goTarget(href) {
@@ -208,28 +118,6 @@ export default {
handleViewNotice(item) {
this.$refs.noticeViewRef.open(item)
},
loadLoginLogList() {
this.loginLogLoading = true
homeList({
pageNum: this.loginLogPage.currentPage,
pageSize: this.loginLogPage.pageSize
}).then(response => {
this.loginLogList = response.rows || []
this.loginLogPage.total = response.total || 0
this.loginLogLoading = false
}).catch(() => {
this.loginLogLoading = false
})
},
handleLoginLogSizeChange(val) {
this.loginLogPage.pageSize = val
this.loginLogPage.currentPage = 1
this.loadLoginLogList()
},
handleLoginLogCurrentChange(val) {
this.loginLogPage.currentPage = val
this.loadLoginLogList()
},
handleNoticeSizeChange(val) {
this.noticePage.pageSize = val
this.noticePage.currentPage = 1
@@ -371,102 +259,6 @@ export default {
}
}
.contact-item {
display: flex;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.contact-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, var(--edu-green-dark) 0%, var(--edu-green-primary) 100%);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
margin-right: 12px;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(15, 25, 86, 0.2);
}
.contact-text {
flex: 1;
min-width: 0;
.label {
font-size: 12px;
color: #909399;
margin-bottom: 2px;
}
a, .el-link {
font-size: 13px;
color: var(--edu-green-primary);
}
a:hover, .el-link:hover {
color: var(--edu-green-dark);
}
span {
font-size: 13px;
color: #606266;
}
}
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
thead {
tr {
background: linear-gradient(135deg, rgba(0, 135, 90, 0.06) 0%, rgba(0, 135, 90, 0.04) 100%);
}
th {
padding: 10px 20px;
text-align: center;
font-weight: 600;
color: var(--edu-green-dark);
border-bottom: 1px solid rgba(0, 135, 90, 0.1);
}
}
tbody {
tr {
transition: background 0.2s;
&:hover {
background: rgba(0, 135, 90, 0.04);
}
td {
padding: 10px 20px;
color: #4a4f6b;
border-bottom: 1px solid #f0f1f7;
.table-icon {
color: var(--edu-green-light);
margin-right: 6px;
}
}
&:last-child td {
border-bottom: none;
}
}
}
}
.notice-list {
.notice-item {
padding: 14px 0;
-2
View File
@@ -91,8 +91,6 @@
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
@@ -34,13 +34,10 @@
<!-- 下面分左右两栏 -->
<el-row :gutter="24">
<el-col :xs="24" :md="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jcztEnabled">检查状态</el-checkbox>
</template>
<el-form-item label="检查状态">
<div class="checkbox-group">
<el-checkbox v-model="searchForm.jcztYtg" :disabled="!searchForm.jcztEnabled">已通过</el-checkbox>
<el-checkbox v-model="searchForm.jcztWtg" :disabled="!searchForm.jcztEnabled">未通过</el-checkbox>
<el-checkbox v-model="searchForm.jcztYtg">已通过</el-checkbox>
<el-checkbox v-model="searchForm.jcztWtg">未通过</el-checkbox>
</div>
</el-form-item>
<el-form-item label="教员姓名">
@@ -51,13 +48,10 @@
</el-form-item>
</el-col>
<el-col :xs="24" :md="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.hlztEnabled">忽略状态</el-checkbox>
</template>
<el-form-item label="忽略状态">
<div class="checkbox-group">
<el-checkbox v-model="searchForm.hlztYc" :disabled="!searchForm.hlztEnabled">有用</el-checkbox>
<el-checkbox v-model="searchForm.hlztYbl" :disabled="!searchForm.hlztEnabled">已被忽略</el-checkbox>
<el-checkbox v-model="searchForm.hlztYc">有用</el-checkbox>
<el-checkbox v-model="searchForm.hlztYbl">已被忽略</el-checkbox>
</div>
</el-form-item>
<el-form-item label="班次名称">
@@ -81,7 +75,7 @@
<el-card shadow="never" class="action-card">
<div class="action-bar">
<div class="bar-left">
<el-button type="primary" @click="handleDownloadTemplate">
<el-button icon="el-icon-download" @click="handleDownloadTemplate">
课程表数据文件模板下载
</el-button>
</div>
@@ -89,16 +83,16 @@
<div class="bar-center">
<div class="file-area">
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
<el-button @click="handleChooseFile">选择文件</el-button>
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ selectedFileName }}</span>
</div>
</div>
<div class="bar-right">
<el-button type="primary" @click="handleImportToSystem" :loading="importing">缓存数据导入到系统</el-button>
<el-button type="primary" @click="handleUploadToCache">上传数据至缓存</el-button>
<el-button type="primary" @click="handleCheckCache">检查缓存数据</el-button>
<el-button type="danger" @click="handleDeleteCache">删除缓存数据</el-button>
<el-button type="primary" icon="el-icon-upload2" @click="handleUploadToCache">上传数据至缓存</el-button>
<el-button @click="handleCheckCache">检查缓存数据</el-button>
<el-button type="danger" plain @click="handleDeleteCache">删除缓存数据</el-button>
</div>
</div>
</el-card>
@@ -169,10 +163,8 @@ export default {
searchForm: {
kssj: '',
jssj: '',
jcztEnabled: true,
jcztYtg: false,
jcztWtg: true,
hlztEnabled: true,
hlztYc: false,
hlztYbl: true,
jyxm: '',
@@ -208,10 +200,8 @@ export default {
if (f.bcmc && f.bcmc.trim()) params.bc = f.bcmc.trim()
if (f.kmmc && f.kmmc.trim()) params.kcmc = f.kmmc.trim()
// 检查状态:仅勾选「已通过」或「未通过」其中之一时才按该条件过滤
if (f.jcztEnabled) {
if (f.jcztYtg && !f.jcztWtg) params.ytgjc = true
else if (!f.jcztYtg && f.jcztWtg) params.ytgjc = false
}
return params
},
@@ -50,13 +50,13 @@
<!-- ==================== 2. 工具栏新增 / 导入 / 导出 ==================== -->
<div class="toolbar">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学员队</el-button>
<el-button type="primary" plain icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
<el-button type="primary" plain icon="el-icon-download" @click="handleDownloadTemplate">模板下载</el-button>
<el-button icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">模板下载</el-button>
<div class="file-input">
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button type="primary" plain icon="el-icon-upload2" @click="handleChooseFile">选择文件</el-button>
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ fileName }}</span>
<el-button type="primary" plain :loading="uploading" @click="handleUpload">上传数据</el-button>
<el-button type="primary" icon="el-icon-upload2" :loading="uploading" @click="handleUpload">上传数据</el-button>
</div>
</div>
@@ -12,12 +12,11 @@
<el-form-item label="姓名">
<el-input v-model="searchForm.xm" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.xbEnabled">性别</el-checkbox></template>
<el-radio-group v-model="searchForm.xb" :disabled="!searchForm.xbEnabled">
<el-radio :label="'男'"></el-radio>
<el-radio :label="'女'"></el-radio>
</el-radio-group>
<el-form-item label="性别">
<el-select v-model="searchForm.xb" placeholder="请选择性别" clearable class="full-width">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
</el-select>
</el-form-item>
<el-form-item label="证件号码">
<el-input v-model="searchForm.zjhm" placeholder="请输入证件号码" clearable />
@@ -63,7 +62,7 @@
</el-row>
<div class="search-actions">
<el-button class="gray-btn" @click="handleSearch">查询</el-button>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
</div>
</el-form>
</el-card>
@@ -224,7 +223,7 @@ export default {
// ==================== 查询条件 ====================
// 仅保留后端 XYXX 实体支持的字段:/student-records/list 会按实体字段动态构建查询条件
searchForm: {
xh: '', xm: '', xbEnabled: false, xb: '', zjhm: '', zzmm: '', mz: '', jg: '', xw: '',
xh: '', xm: '', xb: '', zjhm: '', zzmm: '', mz: '', jg: '', xw: '',
ybzb: '', sfzh: '', whcd: '', bkbyyx: '', bq: '', lxdh: '', txdz: ''
},
@@ -312,7 +311,7 @@ export default {
const v = this.searchForm[key]
if (v !== '' && v !== null && v !== undefined) params[key] = v.trim()
})
if (this.searchForm.xbEnabled) params.xb = this.searchForm.xb
if (this.searchForm.xb) params.xb = this.searchForm.xb
},
// ==================== 查询/分页 ====================
@@ -485,21 +484,5 @@ export default {
width: 100%;
}
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
}
}
}
</style>
-2
View File
@@ -80,8 +80,6 @@
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
@@ -49,8 +49,6 @@
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
@@ -11,7 +11,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="submit">保存</el-button>
<el-button type="danger" size="mini" @click="close">关闭</el-button>
<el-button size="mini" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
@@ -17,7 +17,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="submit">保存</el-button>
<el-button type="danger" size="mini" @click="close">关闭</el-button>
<el-button size="mini" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
@@ -18,7 +18,7 @@
<div class="sce-panel-row">
<span class="sce-label">事件名称:</span>
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" placeholder="请输入校历事件名称" />
<el-checkbox v-model="boldDisplay" class="sce-cb">加粗显示</el-checkbox>
<el-checkbox v-model="toolbar.bold" class="sce-cb">加粗显示</el-checkbox>
<el-checkbox v-model="toolbar.schedulable" class="sce-cb">可排课</el-checkbox>
<el-checkbox v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
<el-checkbox v-model="toolbar.remarkShow" class="sce-cb">备注显示</el-checkbox>
@@ -74,18 +74,8 @@
:data-key="cellKey(wIdx, col.colIndex)"
@dblclick="onCellDblClick(wIdx, col)"
>
<span v-if="getEvent(wIdx, col.colIndex)" class="sce-event-name" :class="{ 'is-bold': boldDisplay }">{{ getEvent(wIdx, col.colIndex).name }}</span>
<span v-if="getEvent(wIdx, col.colIndex)" class="sce-event-name" :class="{ 'is-bold': getEvent(wIdx, col.colIndex).bold }">{{ getEvent(wIdx, col.colIndex).name }}</span>
<span v-else class="sce-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
<span
v-if="getEvent(wIdx, col.colIndex) && !getEvent(wIdx, col.colIndex).schedulable"
class="sce-flag sce-flag-noschedule"
title="全校不允许排课"
/>
<span
v-if="getEvent(wIdx, col.colIndex) && getEvent(wIdx, col.colIndex).mainCourse"
class="sce-flag sce-flag-maincourse"
title="正课"
/>
</td>
</tr>
</tbody>
@@ -147,8 +137,6 @@ export default {
totalWeeks: 0,
dateRangeText: '',
weeks: [],
// 加粗显示:全局开关,勾选后所有有事件的格子事件名加粗(纯前端效果,不持久化)
boldDisplay: false,
// 显示开关(默认不勾选,每天仅显示 1-2/3-4/5-6 三个节次)
show78: false,
showNight: false,
@@ -156,6 +144,7 @@ export default {
// 工具栏表单
toolbar: {
eventName: '',
bold: false,
schedulable: false,
mainCourse: false,
remarkShow: false,
@@ -163,6 +152,8 @@ export default {
},
// 事件存储:key = cellKey -> event
events: {},
// 暂存被隐藏节次列上的事件:key = 周-星期-节次 -> event,列再次显示时恢复
hiddenEvents: {},
// 选择集(用数组以保证 Vue2 响应式)
selectedKeys: [],
// 拖拽状态
@@ -188,6 +179,10 @@ export default {
})
})
return cols
},
// xqxlb 的年度(nd)为年份(如 2026),由 6 位学期代号(如 202601)截取前 4 位得出
xqxlbNd() {
return Number(String(this.nd).slice(0, 4))
}
},
watch: {
@@ -197,7 +192,43 @@ export default {
if (val) this.loadSemester()
}
},
visibleColumns() {
visibleColumns(newCols, oldCols) {
// 节次列显隐会改变列索引,按「周期-星期-节次」重定位事件,避免事件随时间格列偏移
if (oldCols && oldCols.length) {
const oldPosOf = index => (oldCols[index] || null)
const newIndex = (dayIndex, slotKey) => {
const c = newCols.find(x => x.dayIndex === dayIndex && x.slotKey === slotKey)
return c ? c.colIndex : -1
}
const next = {}
Object.keys(this.events).forEach(key => {
const m = key.match(/^w(\d+)-c(\d+)$/)
if (!m) return
const wIdx = +m[1]
const old = oldPosOf(+m[2])
if (!old) return
const newC = newIndex(old.dayIndex, old.slotKey)
if (newC < 0) {
// 该节次列被隐藏:事件暂存,待列再次显示时恢复
this.hiddenEvents[this.cellStableKey(wIdx, old.dayIndex, old.slotKey)] = this.events[key]
return
}
next['w' + wIdx + '-c' + newC] = this.events[key]
})
// 恢复重新显示的隐藏节次事件
Object.keys(this.hiddenEvents).forEach(stable => {
const p = stable.split('-')
const wIdx = +p[0]
const dayIndex = +p[1]
const slotKey = p[2]
const newC = newIndex(dayIndex, slotKey)
if (newC >= 0) {
next['w' + wIdx + '-c' + newC] = this.hiddenEvents[stable]
delete this.hiddenEvents[stable]
}
})
this.events = next
}
// 列变化后清空无效选择(列索引含义会变)
this.selectedKeys = []
}
@@ -225,9 +256,9 @@ export default {
this.$message.warning('学期详情获取失败,请确认学期数据')
})
},
// 从后端拉取本学期校历假期,渲染到对应时间格(nd 为 6 位学期代号,如 202601
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位
loadXqxlbEvents() {
listXqxlb({ nd: this.nd }).then(res => {
listXqxlb({ nd: this.xqxlbNd }).then(res => {
const data = res.data
const list = Array.isArray(data) ? data : (data && data.records) || []
return list
@@ -235,15 +266,22 @@ export default {
const loaded = {}
list.forEach(item => {
const pos = this.locateXqxlb(item)
if (pos) {
loaded[pos.key] = {
if (!pos) return
const ev = {
bh: item.bh,
name: item.jqmc || '',
bold: !!item.jc,
schedulable: !!item.kpk,
mainCourse: !!item.zdpk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
const colIndex = this.colIndexOf(pos.dayIndex, pos.slotKey)
if (colIndex >= 0) {
loaded[this.cellKey(pos.wIdx, colIndex)] = ev
} else {
// 该节次列当前隐藏:暂存(含 bh),待列显示时由 visibleColumns 监听恢复
this.hiddenEvents[this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)] = ev
}
})
// 合并到现有格子(后端数据覆盖已有状态)
@@ -252,7 +290,7 @@ export default {
this.$message.warning('校历事件加载失败')
})
},
// 根据假期记录反推时间格 keyjqsj 日期 + courseClass 节次)
// 根据假期记录反推时间格位置jqsj 日期 + courseClass 节次),与列是否可见无关
locateXqxlb(item) {
if (!this.startDate || !item.jqsj) return null
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
@@ -263,9 +301,7 @@ export default {
const dayIndex = offset % 7
const slotKey = this.courseClassToSlotKey(item.courseClass)
if (slotKey === null) return null
const colIndex = this.colIndexOf(dayIndex, slotKey)
if (colIndex < 0) return null
return { key: this.cellKey(wIdx, colIndex) }
return { wIdx, dayIndex, slotKey }
},
// 后端 courseClass 节次范围 -> 前端节次 key
courseClassToSlotKey(courseClass) {
@@ -307,6 +343,10 @@ export default {
cellKey(wIdx, colIndex) {
return 'w' + wIdx + '-c' + colIndex
},
// 稳定的单元格身份:不随列显隐变化的周-星期-节次
cellStableKey(wIdx, dayIndex, slotKey) {
return wIdx + '-' + dayIndex + '-' + slotKey
},
getEvent(wIdx, colIndex) {
return this.events[this.cellKey(wIdx, colIndex)] || null
},
@@ -437,6 +477,7 @@ export default {
this.$set(this.events, key, {
bh: prev ? prev.bh : undefined,
name: name,
bold: this.toolbar.bold,
schedulable: this.toolbar.schedulable,
mainCourse: this.toolbar.mainCourse,
remarkShow: this.toolbar.remarkShow,
@@ -467,6 +508,7 @@ export default {
return
}
this.toolbar.eventName = target.name
this.toolbar.bold = !!target.bold
this.toolbar.schedulable = !!target.schedulable
this.toolbar.mainCourse = !!target.mainCourse
this.toolbar.remarkShow = !!target.remarkShow
@@ -477,6 +519,7 @@ export default {
const ev = this.getEvent(wIdx, col.colIndex)
if (ev) {
this.toolbar.eventName = ev.name
this.toolbar.bold = !!ev.bold
this.toolbar.schedulable = !!ev.schedulable
this.toolbar.mainCourse = !!ev.mainCourse
this.toolbar.remarkShow = !!ev.remarkShow
@@ -489,6 +532,7 @@ export default {
this.selectedKeys = []
this.absorbPreview = ''
this.events = {}
this.hiddenEvents = {}
this.loadXqxlbEvents()
this.$message.info('已刷新')
},
@@ -526,10 +570,10 @@ export default {
return {
delFlag: 0,
bh: ev.bh || undefined,
nd: this.nd,
nd: this.xqxlbNd,
jqsj: fmtDate(d),
jqmc: ev.name || '',
jc: null,
jc: !!ev.bold,
bz: ev.remark || null,
kpk: !!ev.schedulable,
bzxs: !!ev.remarkShow,
@@ -694,16 +738,6 @@ export default {
background: #d4ecdf !important;
}
}
/* 状态标记小方块 */
.sce-flag {
position: absolute;
width: 7px;
height: 7px;
border-radius: 1px;
}
.sce-flag-noschedule { left: 2px; top: 2px; background: #909399; }
.sce-flag-maincourse { right: 2px; top: 2px; background: #409eff; }
}
.sce-status {
@@ -42,16 +42,6 @@
>
<template v-if="getEvent(wIdx, col.colIndex)">
<span class="cal-event-name">{{ getEvent(wIdx, col.colIndex).name }}</span>
<span
v-if="!getEvent(wIdx, col.colIndex).schedulable"
class="cal-flag cal-flag-noschedule"
title="全校不允许排课"
/>
<span
v-if="getEvent(wIdx, col.colIndex).mainCourse"
class="cal-flag cal-flag-maincourse"
title="正课"
/>
</template>
<span v-else-if="showDate" class="cal-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
</td>
@@ -115,8 +105,8 @@ export default {
totalWeeks: 0,
dateRangeText: '',
weeks: [],
// 显示控制:默认勾选显示日期;节次全部展示
showDate: true,
// 显示控制:默认勾选显示日期;节次全部展示
showDate: false,
// 事件存储:key = cellKey -> event
events: {},
loading: false
@@ -199,9 +189,9 @@ export default {
this.$message.warning('学期详情获取失败,请确认学期数据')
})
},
// 从后端拉取本学期校历假期,渲染到对应时间格(nd 为 6 位学期代号,如 202601
// 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位
loadXqxlbEvents() {
listXqxlb({ nd: this.nd }).then(res => {
listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {
const data = res.data
const list = Array.isArray(data) ? data : (data && data.records) || []
const loaded = {}
@@ -485,16 +475,6 @@ export default {
&.has-event .cal-event-name { color: #00663e; font-weight: 500; }
&.no-schedule { background: #fde2e2; }
}
/* 状态标记小方块 */
.cal-flag {
position: absolute;
width: 7px;
height: 7px;
border-radius: 1px;
}
.cal-flag-noschedule { left: 2px; top: 2px; background: #909399; }
.cal-flag-maincourse { right: 2px; top: 2px; background: #409eff; }
}
/* 底部状态栏 */
@@ -5,109 +5,111 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">编辑</el-button>
<el-col :span="1.8">
<el-button type="success" plain icon="el-icon-date" size="mini" :disabled="!currentSemester"
@click="handleEditCalendar">
编辑校历
</el-button>
</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-col :span="1.5">
<el-button type="warning" plain icon="el-icon-date" size="mini" :disabled="single" @click="handleEditCalendar">编辑校历</el-button>
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">
批量删除
</el-button>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="semesterList"
border
:height="tableHeight"
@selection-change="handleSelectionChange"
>
<el-table v-loading="loading" :data="semesterList" border :height="tableHeight" highlight-current-row
@current-change="handleCurrentChange" @selection-change="handleSelectionChange">
<el-table-column type="selection" align="center" width="50" />
<el-table-column label="学期名称" align="center" min-width="190">
<el-table-column label="学期名称" align="center" width="250">
<template slot-scope="scope">
<span>{{ getSemesterName(scope.row.nd) }}</span>
</template>
</el-table-column>
<el-table-column label="当前" align="center" width="70">
<el-table-column label="当前" align="center" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.dqxq" type="success" size="mini">当前</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="开学日期" align="center" prop="kxrq" min-width="130">
<el-table-column label="开学日期" align="center" prop="kxrq" width="160">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.kxrq) }}</span>
</template>
</el-table-column>
<el-table-column label="结束日期" align="center" prop="jsrq" min-width="130">
<el-table-column label="结束日期" align="center" prop="jsrq" width="160">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.jsrq) }}</span>
</template>
</el-table-column>
<el-table-column label="周数" align="center" prop="sdzs" width="70" />
<el-table-column label="调课不审批" align="center" width="100">
<el-table-column label="周数" align="center" prop="sdzs" width="100" />
<el-table-column label="调课不审批" align="center" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="禁止调课" align="center" width="90">
<el-table-column label="禁止调课" align="center" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="终结成绩定及格" align="center" width="120">
<el-table-column label="终结成绩定及格" align="center" width="160">
<template slot-scope="scope">
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否' }}</el-tag>
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="课时系数方案" align="center" min-width="120">
<el-table-column label="课时系数方案" align="center" width="160">
<template slot-scope="scope">
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
</template>
</el-table-column>
<el-table-column label="锁定时长" align="center" min-width="100">
<el-table-column label="锁定时长" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.sdjldw || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="table-action-column">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" class="text-danger"
@click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 学期信息弹窗 -->
<el-dialog :title="title" :visible.sync="open" width="900px" append-to-body class="semester-dialog">
<el-form ref="form" class="semester-dialog-form" :model="form" :rules="rules" label-width="96px" label-position="right">
<el-alert v-if="isSemesterNameDuplicated()" class="semester-dup-alert" type="warning" show-icon
title="该学期已存在,不能重复添加" :closable="false" />
<el-form ref="form" class="semester-dialog-form" :model="form" :rules="rules" label-width="96px"
label-position="right">
<el-row :gutter="20" class="semester-dialog-body">
<!-- 左侧表单区 -->
<el-col :span="15" class="semester-form-col">
<el-form-item label="学期名称">
<el-input
:value="getSemesterName(buildNd(form.xn, form.xq))"
readonly
placeholder="由年份+学期类型自动拼接"
prefix-icon="el-icon-office-building"
/>
<el-input :value="getSemesterName(buildNd(form.xn, form.xq))" readonly placeholder="请在下方选择年份和学期类型"
prefix-icon="el-icon-office-building" />
</el-form-item>
<el-row :gutter="14">
<el-col :span="12">
<el-form-item label="年份" prop="xn">
<el-select v-model="form.xn" placeholder="请选择年份" :disabled="!isAdd" style="width: 100%">
<el-select v-model="form.xn" placeholder="请选择年份" :disabled="!isAdd" style="width: 100%"
@change="checkSemesterNameUnique">
<el-option v-for="item in yearOptions" :key="item" :label="item + '年'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期类型" prop="xq">
<el-select v-model="form.xq" placeholder="请选择学期类型" :disabled="!isAdd" style="width: 100%">
<el-option v-for="item in semesterOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-select v-model="form.xq" placeholder="请选择学期类型" :disabled="!isAdd" style="width: 100%"
@change="checkSemesterNameUnique">
<el-option v-for="item in semesterOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
@@ -115,44 +117,37 @@
<el-row :gutter="14">
<el-col :span="12">
<el-form-item label="开学日期" prop="kxrq">
<el-date-picker
v-model="form.kxrq"
type="date"
placeholder="开学日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
<el-date-picker v-model="form.kxrq" type="date" placeholder="开学日期" value-format="yyyy-MM-dd"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="jsrq">
<el-date-picker
v-model="form.jsrq"
type="date"
placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
<el-date-picker v-model="form.jsrq" type="date" placeholder="结束日期" value-format="yyyy-MM-dd"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="14">
<el-col :span="12">
<el-form-item label="学期周数" prop="sdzs">
<el-input-number v-model="form.sdzs" :min="1" :max="60" controls-position="right" style="width: 100%" />
<el-input-number v-model="form.sdzs" :min="1" :max="60" controls-position="right"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课时系数方案" prop="ksxsfabh">
<el-select v-model="form.ksxsfabh" placeholder="请选择" clearable style="width: 100%">
<el-option v-for="item in coefficientOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in coefficientOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="锁定时长">
<div class="lock-group">
<el-input-number v-model="form.lockValue" :min="0" :max="99" controls-position="right" style="width: 120px" />
<el-input-number v-model="form.lockValue" :min="0" :max="99" controls-position="right"
style="width: 120px" />
<el-radio-group v-model="form.lockUnit" class="lock-unit" style="margin-left: 28px">
<el-radio label="周"></el-radio>
<el-radio label="月"></el-radio>
@@ -178,7 +173,9 @@
<div class="switch-item">
<el-checkbox v-model="form.tkbsp">调课不审批</el-checkbox>
<div class="switch-tip">设定调课不审批在教学信息系统中调课时如果只是更改教员或更改授课地点调课申请会立即生效而对于改变授课地点调课申请会立即生效而对于改变授课时间增加或减少授课教员的调课申请依然不受该设置影响依然需要逐级审批后才能生效</div>
<div class="switch-tip">
设定调课不审批在教学信息系统中调课时如果只是更改教员或更改授课地点调课申请会立即生效而对于改变授课地点调课申请会立即生效而对于改变授课时间增加或减少授课教员的调课申请依然不受该设置影响依然需要逐级审批后才能生效
</div>
</div>
<div class="switch-item">
@@ -219,6 +216,8 @@ export default {
semesterList: [],
// 选中的学期数组
ids: [],
// 当前点击选中的学期
currentSemester: null,
// 非单个禁用
single: true,
// 非多个禁用
@@ -310,6 +309,17 @@ export default {
const s = String(nd)
return { xn: s.slice(0, 4), xq: s.slice(4) }
},
/** 新增时校验学期名称(年份+学期类型拼接)是否已存在于表格 */
isSemesterNameDuplicated() {
if (!this.isAdd) return false
const nd = this.buildNd(this.form.xn, this.form.xq)
if (!nd) return false
return this.semesterList.some(item => String(item.nd) === String(nd))
},
/** 年份/学期类型切换时实时刷新顶部重复提示 */
checkSemesterNameUnique() {
this.$forceUpdate()
},
/** 课时系数方案编号 -> 名称 */
getCoefficientLabel(value) {
if (!value && value !== 0) return '-'
@@ -341,6 +351,10 @@ export default {
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */
handleCurrentChange(currentRow) {
this.currentSemester = currentRow || null
},
/** 组装提交数据 */
buildPayload(form) {
const lockValue = form.lockValue !== undefined && form.lockValue !== null ? form.lockValue : ''
@@ -370,11 +384,12 @@ export default {
this.title = "学期信息 - 新增"
},
/** 编辑按钮 */
handleUpdate() {
if (this.ids.length !== 1) return
handleUpdate(row) {
const semesterId = row && row.nd ? row.nd : this.ids[0]
if (!semesterId) return
this.reset()
this.isAdd = false
getSemester(this.ids[0]).then(response => {
getSemester(semesterId).then(response => {
const data = response.data || {}
const { xn, xq } = this.parseNd(data.nd)
// 解析锁定时长(如 "2周" / "1月" / "1"
@@ -403,32 +418,37 @@ export default {
}
this.open = true
this.title = "学期信息 - 编辑"
}).catch(() => {})
}).catch(() => { })
},
/** 编辑校历按钮 */
/** 编辑校历按钮:对当前选中的学期编辑校历 */
handleEditCalendar() {
if (this.ids.length !== 1) return
const nd = this.ids[0]
const row = this.currentSemester
if (!row || !row.nd) return
this.$router.push({
path: '/teachBusiness/semester/semesterCalendar',
query: { nd: nd, name: this.getSemesterName(nd) }
query: { nd: row.nd, name: this.getSemesterName(row.nd) }
})
},
/** 删除按钮 */
handleDelete() {
if (!this.ids.length) return
const names = this.ids.map(nd => this.getSemesterName(nd)).join('、')
handleDelete(row) {
const semesterIds = row && row.nd ? [row.nd] : this.ids
if (!semesterIds.length) return
const names = semesterIds.map(nd => this.getSemesterName(nd)).join('、')
this.$modal.confirm('确认删除学期【' + names + '】吗?').then(() => {
// 批量删除:逐个调用
const delList = this.ids.map(nd => delSemester(nd))
const delList = semesterIds.map(nd => delSemester(nd))
return Promise.all(delList)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
}).catch(() => { })
},
/** 提交表单 */
submitForm() {
if (this.isSemesterNameDuplicated()) {
this.$modal.msgWarning("该学期已存在,不能重复添加")
return
}
this.$refs["form"].validate(valid => {
if (valid) {
const payload = this.buildPayload(this.form)
@@ -446,7 +466,7 @@ export default {
this.$modal.msgSuccess(this.isAdd ? "新增成功" : "修改成功")
this.open = false
this.getList()
}).catch(() => {})
}).catch(() => { })
}
})
},
@@ -506,6 +526,11 @@ export default {
display: block;
}
/* 顶部学期重复提示与表单间距 */
.semester-dup-alert {
margin-bottom: 16px;
}
.semester-form-col {
padding-right: 4px;
}
@@ -33,7 +33,13 @@
<div class="list-title">教学大纲列表</div>
<div class="list-actions">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button type="danger" icon="el-icon-delete" :disabled="!selection.length" @click="handleBatchDelete">删除所选</el-button>
<el-button
type="danger"
plain
icon="el-icon-delete"
:disabled="!selection.length"
@click="handleBatchDelete"
>删除所选</el-button>
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
</div>
</div>
@@ -68,7 +74,7 @@
<el-table-column label="停用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center" fixed="right">
<el-table-column label="操作" width="160" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" class="danger-text-btn" @click="handleDelete(scope.row)">删除</el-button>
@@ -94,13 +100,41 @@
<el-form ref="syllabusForm" :model="dialog.form" :rules="rules" label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" />
<el-form-item label="专业" prop="zydh">
<el-select
v-model="dialog.form.zydh"
filterable
clearable
placeholder="请选择专业"
class="w-full"
:loading="referenceDataLoading"
>
<el-option
v-for="item in majorOptions"
:key="item.zydh"
:label="item.zymc"
:value="item.zydh"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课编号" prop="kbh">
<el-input v-model="dialog.form.kbh" placeholder="请输入课编号" />
<el-form-item label="课" prop="kbh">
<el-select
v-model="dialog.form.kbh"
filterable
clearable
placeholder="请选择课程"
class="w-full"
:loading="referenceDataLoading"
>
<el-option
v-for="item in subjectOptions"
:key="item.kbh"
:label="item.kmc"
:value="item.kbh"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -110,7 +144,21 @@
</el-col>
<el-col :span="12">
<el-form-item label="课类型" prop="klx">
<el-input v-model="dialog.form.klx" placeholder="请输入课类型" />
<el-select
v-model="dialog.form.klx"
filterable
clearable
placeholder="请选择课类型"
class="w-full"
:loading="referenceDataLoading"
>
<el-option
v-for="item in courseTypeOptions"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -152,8 +200,22 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教研室代号">
<el-input v-model="dialog.form.jysdh" placeholder="请输入教研室代号" />
<el-form-item label="教研室">
<el-select
v-model="dialog.form.jysdh"
filterable
clearable
placeholder="请选择教研室"
class="w-full"
:loading="referenceDataLoading"
>
<el-option
v-for="item in officeOptions"
:key="item.jysdh"
:label="item.jysmc"
:value="item.jysdh"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -219,6 +281,13 @@ import {
listSyllabus,
listSyllabusByZydhAndTy
} from '@/api/teachBusiness/syllabus'
import { listMajor } from '@/api/subjectMajor/major'
import { listKb } from '@/api/teachOffice/kb'
import { listOffice } from '@/api/teachOffice/office'
import { getDicts } from '@/api/system/dict/data'
const REFERENCE_DATA_PAGE_SIZE = 10000
const COURSE_TYPE_DICT_CODE = 'course_type'
export default {
name: 'SyllabusIndex',
@@ -234,6 +303,11 @@ export default {
pageNum: 1,
pageSize: 20,
selection: [],
referenceDataLoading: false,
majorOptions: [],
subjectOptions: [],
courseTypeOptions: [],
officeOptions: [],
dialog: {
visible: false,
title: '',
@@ -241,10 +315,10 @@ export default {
form: this.createEmptyForm()
},
rules: {
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
kbh: [{ required: true, message: '请输入课编号', trigger: 'blur' }],
zydh: [{ required: true, message: '请选择专业', trigger: 'change' }],
kbh: [{ required: true, message: '请选择课程', trigger: 'change' }],
xqdc: [{ required: true, message: '请输入学期第次', trigger: 'blur' }],
klx: [{ required: true, message: '请输入课类型', trigger: 'blur' }],
klx: [{ required: true, message: '请选择课类型', trigger: 'change' }],
xs: [{ required: true, message: '请输入学时', trigger: 'blur' }],
ty: [{ required: true, message: '请选择停用标识', trigger: 'change' }]
}
@@ -258,8 +332,51 @@ export default {
},
created() {
this.fetchList()
this.loadReferenceData()
},
methods: {
loadReferenceData() {
this.referenceDataLoading = true
const query = { pageNum: 1, pageSize: REFERENCE_DATA_PAGE_SIZE }
const requests = [
listMajor(query),
listKb(query),
listOffice(query),
getDicts(COURSE_TYPE_DICT_CODE)
].map(request => {
return request
.then(response => this.getResponseRecords(response))
.catch(() => [])
})
Promise.all(requests).then(([majors, subjects, offices, courseTypes]) => {
this.majorOptions = this.getUniqueOptions(majors, 'zydh', 'zymc')
this.subjectOptions = this.getUniqueOptions(subjects, 'kbh', 'kmc')
this.officeOptions = this.getUniqueOptions(offices, 'jysdh', 'jysmc')
this.courseTypeOptions = this.getUniqueOptions(courseTypes, 'dictValue', 'dictLabel')
}).finally(() => {
this.referenceDataLoading = false
})
},
getResponseRecords(response) {
const data = response.data || {}
return Array.isArray(data) ? data : data.records || []
},
getUniqueOptions(items, valueKey, labelKey) {
const values = new Set()
return items.filter(item => {
const value = item[valueKey]
const label = item[labelKey]
if (!value || !label || values.has(value)) {
return false
}
values.add(value)
return true
})
},
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
@@ -48,7 +48,7 @@
</el-col>
</el-row>
<div class="search-actions">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</div>
</el-form>
</el-card>
@@ -60,9 +60,9 @@
<span>无数据</span>
</template>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="bh" label="编号" width="200" align="center" show-overflow-tooltip />
<el-table-column prop="rwmc" label="任务名称" min-width="160" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="90" align="center" />
<el-table-column prop="bh" label="编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="rwmc" label="任务名称" width="250" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="150" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
@@ -70,11 +70,11 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="170" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="80" align="center" fixed="right">
<el-table-column prop="fbsj" label="发布时间" width="200" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="200" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="200" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="200" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
</template>
@@ -113,7 +113,7 @@
</el-descriptions>
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
<div slot="footer">
<el-button type="primary" @click="detailVisible = false">关闭</el-button>
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
@@ -293,20 +293,4 @@ export default {
}
}
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
}
}
</style>
@@ -1,236 +1,126 @@
<template>
<div class="app-container timetable-page">
<el-tabs v-model="activeTab" class="timetable-tabs">
<!-- 今日 -->
<el-tab-pane label="今日" name="today">
<!-- ==================== 今日教学实施计划 ==================== -->
<el-tab-pane label="今日教学实施计划" name="today">
<div class="tab-body">
<!-- 日期导航 -->
<div class="nav-header">
<div class="date-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevDay">前一天</el-link>
<div class="nav-title">{{ todayTitle }}</div>
<div class="date-title">{{ dateTitle }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextDay">后一天</el-link>
</div>
<!-- 今日表格 -->
<div class="table-card">
<el-table :data="todayList" v-loading="todayLoading" stripe border>
<el-table :data="todayData" v-loading="loading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
<el-table-column label="上课时间" width="130" align="center">
<template #default="{ row }">{{ formatJc(row.jc) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
<el-table-column prop="kcmc" label="课程" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="zrdw" label="责任单位" width="110" align="center" />
<el-table-column prop="bc" label="班次" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="skjy" label="教员" width="90" align="center" />
<el-table-column label="场地" width="140" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jxcd || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
<el-table-column label="教学内容方法" min-width="160" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jxnrff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jybz || '-' }}</template>
</el-table-column>
</el-table>
<el-empty v-show="!todayLoading && !todayList.length" description="当日暂无教学计划数据" />
<el-empty v-show="!loading && !todayData.length" description="暂无教学计划数据" />
</div>
</div>
</el-tab-pane>
<!-- 本周 -->
<el-tab-pane label="本周" name="week">
<!-- ==================== 本周教学实施计划 ==================== -->
<el-tab-pane label="本周教学实施计划" name="week">
<div class="tab-body">
<!-- 周次导航 -->
<div class="nav-header">
<div class="week-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevWeek">上一周</el-link>
<div class="nav-title">{{ weekTitle }}</div>
<div class="week-title">{{ weekRangeText }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek">下一周</el-link>
</div>
<!-- 本周表格 -->
<div class="table-card">
<el-table :data="weekList" v-loading="weekLoading" stripe border>
<el-table :data="weekData" v-loading="loading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
<el-table-column label="上课时间" width="170" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ row.sjsj || '-' }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="zrdw" label="责任单位" width="100" align="center" />
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="skjy" label="教员" width="90" align="center" />
<el-table-column label="场地" width="140" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jxcd || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
<el-table-column label="教学内容方法" min-width="150" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jxnrff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip header-align="center">
<template #default="{ row }">{{ row.jybz || '-' }}</template>
</el-table-column>
</el-table>
<el-empty v-show="!weekLoading && !weekList.length" description="本周暂无教学计划数据" />
<pagination
v-show="weekTotal > 0"
:total="weekTotal"
:page.sync="weekQuery.pageNum"
:limit.sync="weekQuery.pageSize"
@pagination="getWeekList"
/>
<el-empty v-show="!loading && !weekData.length" description="本周暂无教学计划数据" />
</div>
</div>
</el-tab-pane>
<!-- 三实 -->
<el-tab-pane label="三实" name="practice">
<!-- ==================== 教学班次教学实施计划 ==================== -->
<el-tab-pane label="教学班次教学实施计划" name="shift">
<div class="tab-body">
<!-- 周次导航 -->
<div class="nav-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevWeek3">上一周</el-link>
<div class="nav-title">{{ practiceTitle }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek3">下一周</el-link>
</div>
<el-alert
class="mb8"
type="info"
:closable="false"
show-icon
title="说明:后端教学实施计划课次接口未返回课程类型字段,无法按三实课类型筛选,当前展示本周全部教学实施计划课次。"
/>
<!-- 三实表格 -->
<div class="table-card">
<el-table :data="practiceList" v-loading="practiceLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!practiceLoading && !practiceList.length" description="本周暂无三实教学计划数据" />
<pagination
v-show="practiceTotal > 0"
:total="practiceTotal"
:page.sync="practiceQuery.pageNum"
:limit.sync="practiceQuery.pageSize"
@pagination="getPracticeList"
/>
</div>
</div>
</el-tab-pane>
<!-- 班次 -->
<el-tab-pane label="班次" name="shift">
<div class="tab-body">
<!-- 查询条件 -->
<el-form :inline="true" class="shift-search">
<!-- 选择条件 -->
<div class="search-card">
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="队别班次">
<el-select
v-model="shiftXydbh"
placeholder="请选择队别班次"
clearable
filterable
class="shift-select"
>
<el-option v-for="t in teamOptions" :key="t.xydbh" :label="t.xydmc" :value="t.xydbh" />
</el-select>
</el-form-item>
<el-form-item label="年度">
<el-select v-model="shiftNd" placeholder="请选择年度" class="shift-select">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
<el-select v-model="searchForm.teamClass" placeholder="请选择队别班次" clearable filterable class="shift-select">
<el-option v-for="item in teamClassOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleShiftSearch">查询</el-button>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
</div>
<!-- 标题 -->
<div class="shift-title">{{ shiftTitle }}</div>
<div class="page-title">{{ pageTitle }}</div>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表周次星期</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表班次视图</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表排课周含选修</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划含选修</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表周次星期详细</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期成绩表</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-download" size="mini" @click="handleNotProvided">课程列表另存Excel</el-button>
</el-col>
</el-row>
<!-- 下载操作按钮 -->
<el-card shadow="never" class="action-card">
<div class="button-row">
<el-button type="primary" plain size="mini" @click="handleExportPlan">学期教学实施计划</el-button>
<el-button type="primary" plain size="mini" @click="handleExportGrades">学期成绩表</el-button>
<el-button type="primary" plain size="mini" @click="handleExportCourses">课程列表另存Excel</el-button>
</div>
</el-card>
<!-- 班次课程表 -->
<!-- 数据表格 -->
<div class="table-card">
<el-table v-loading="shiftLoading" :data="shiftList" border stripe>
<el-table :data="shiftData" v-loading="loading" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课程名称/简称" min-width="200" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._kcmc || '-' }}</div>
<div class="sub-text">{{ scope.row.jc || '-' }}</div>
<el-table-column label="课程名称/课时系数" min-width="220" header-align="center">
<template #default="{ row }">
<div>{{ row.kcmc || '-' }}</div>
<div class="sub-text">{{ row.kssk || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="责任教员/课次" width="160" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._zrjy || '-' }}</div>
<div class="sub-text">{{ scope.row.kcxh || '-' }}次课</div>
</template>
</el-table-column>
<el-table-column prop="xydmc" label="班次" width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="xs" label="计划学时" width="90" align="center" />
<el-table-column prop="llxs" label="理论学时" width="90" align="center" />
<el-table-column prop="sjxs" label="实践学时" width="90" align="center" />
<el-table-column prop="cjfz" label="考核类型方式" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column label="实施教员" width="110" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._ssjy || '-' }}</template>
</el-table-column>
<el-table-column label="人数/场地" width="130" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row.rs || '-' }} </div>
<div class="sub-text">{{ scope.row.jsbh || '-' }}</div>
</template>
</el-table-column>
<el-table-column prop="xf" label="学分" width="80" align="center" />
<el-table-column label="实施计划变更" width="150" header-align="center">
<template slot-scope="scope">{{ fmtKbbdsj(scope.row.kbbdsj) }}</template>
</el-table-column>
<el-table-column prop="zrjs" label="责任教员/课次" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="bc" label="班次" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="nj" label="年级" width="80" align="center" />
<el-table-column prop="zy" label="专业" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jhxs" label="计划学时" width="90" align="center" />
<el-table-column prop="yxxs" label="运行学时" width="90" align="center" />
<el-table-column prop="khlxfs" label="考核类型方式" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="ssjs" label="实施教员" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="rscd" label="人数/场地" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcjd" label="课程进度" width="120" align="center" />
<el-table-column prop="ssjhbg" label="实施计划变更" min-width="120" align="center" show-overflow-tooltip />
</el-table>
<el-empty v-show="!shiftLoading && !shiftList.length" description="该班次暂无课程数据" />
<pagination
v-show="shiftTotal > 0"
:total="shiftTotal"
:page.sync="shiftQuery.pageNum"
:limit.sync="shiftQuery.pageSize"
@pagination="getShiftList"
/>
<el-empty v-show="!loading && !shiftData.length" description="请选择队别班次后查询" />
</div>
</div>
</el-tab-pane>
@@ -239,14 +129,9 @@
</template>
<script>
import { listTeachingPlan } from '@/api/teachBusiness/courseRunning'
import { listStudentTeamTask } from '@/api/teachBusiness/studentTeamTask'
import { listToday, listWeek, listClass, exportClassPlan, exportClassGrades, exportClassCourses } from '@/api/teachBusiness/timetable'
import { listTeam } from '@/api/studentRecords/team'
import { listKb } from '@/api/teachOffice/kb'
import { listTeacher } from '@/api/teachOffice/teacher'
import { listAllSemester } from '@/api/teachBusiness/semester'
// 节次 -> 节次区间映射
const JC_SECTION = {
1: '1-2节', 2: '3-4节', 3: '5-6节', 4: '7-8节', 5: '9-10节', 6: '11-12节'
}
@@ -273,124 +158,49 @@ export default {
data() {
return {
activeTab: 'today',
loading: false,
// 今日
todayDate: new Date(),
todayList: [],
todayLoading: false,
todayData: [],
// 本周
weekStart: getMonday(new Date()),
weekList: [],
weekLoading: false,
weekTotal: 0,
weekQuery: { pageNum: 1, pageSize: 20 },
// 三实
practiceStart: getMonday(new Date()),
practiceList: [],
practiceLoading: false,
practiceTotal: 0,
practiceQuery: { pageNum: 1, pageSize: 20 },
weekData: [],
// 班次
teamOptions: [],
shiftXydbh: '',
shiftNd: undefined,
shiftList: [],
shiftLoading: false,
shiftTotal: 0,
shiftQuery: { pageNum: 1, pageSize: 20 },
// 年度下拉数据来自 /semester/all(真实后端数据)
yearOptions: [],
defaultNd: undefined,
// 名称映射(真实接口数据,用于班次 tab 课程/教员编号转名称)
kbMap: {},
teacherMap: {}
searchForm: { teamClass: '' },
teamClassOptions: [],
teamMap: {},
shiftData: []
}
},
computed: {
todayTitle() {
return '当前日期:' + formatDate(this.todayDate) + '时间顺序教学实施计划'
dateTitle() {
return formatDate(this.todayDate) + ' 今日教学实施计划'
},
weekTitle() {
weekRangeText() {
const end = new Date(this.weekStart)
end.setDate(end.getDate() + 6)
return formatDate(this.weekStart) + '(' + this.weekDayName(this.weekStart) + ') 至 ' +
formatDate(end) + '(' + this.weekDayName(end) + ') 教学实施计划'
formatDate(end) + '(' + this.weekDayName(end) + ') 本周教学实施计划'
},
practiceTitle() {
const end = new Date(this.practiceStart)
end.setDate(end.getDate() + 6)
return formatDate(this.practiceStart) + '(' + this.weekDayName(this.practiceStart) + ') 至 ' +
formatDate(end) + '(' + this.weekDayName(end) + ') 三实教学实施计划'
},
shiftTitle() {
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
const name = (team && team.xydmc) || ''
return (this.shiftNd ? this.shiftNd + '年' : '') + name + '课程表'
pageTitle() {
const name = this.searchForm.teamClass || ''
return (name ? name + ' ' : '') + '教学班次教学实施计划'
}
},
created() {
this.loadYearOptions()
this.loadTeamOptions()
this.getTodayList()
this.getWeekList()
this.getPracticeList()
this.loadTeamOptions()
this.loadKbMap()
this.loadTeacherMap()
},
methods: {
/* ---------- 年度下拉(数据来自 /semester/all ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
// 去重并按年度倒序
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
this.defaultNd = (current && current.nd) || arr[0]
if (!this.shiftNd) this.shiftNd = this.defaultNd
return arr
}).catch(() => {
this.yearOptions = []
this.defaultNd = undefined
return []
})
},
weekDayName(d) {
return WEEK_DAY_NAMES[d.getDay()]
},
formatSection(jc) {
formatJc(jc) {
if (jc === null || jc === undefined || jc === '') return '第?节'
return JC_SECTION[jc] || ('第' + jc + '节')
},
/** 后端 LocalDateTime -> 日期+周X+节次区间 */
fmtLessonTime(row) {
const rq = row.rq || ''
const date = String(rq).substring(0, 10)
if (!date) return '-'
const d = new Date(date.replace(/-/g, '/'))
const wn = WEEK_DAY_NAMES[d.getDay()]
return (date + ' 周' + wn + ' ' + this.formatSection(row.jc)).trim()
},
/** 后端 LocalDateTime -> yyyy-MM-dd HH:mm */
fmtKbbdsj(v) {
if (!v) return '-'
return String(v).replace('T', ' ').substring(0, 16)
},
txztText(txzt) {
if (txzt === 1) return '已填写'
if (txzt === 2) return '已提交'
return '未填写'
},
txztType(txzt) {
if (txzt === 1) return 'success'
if (txzt === 2) return 'primary'
return 'info'
},
/* ---------- 今日 ---------- */
/* ---------- 今日:日期导航 ---------- */
handlePrevDay() {
const d = new Date(this.todayDate)
d.setDate(d.getDate() - 1)
@@ -403,179 +213,163 @@ export default {
this.todayDate = d
this.getTodayList()
},
getTodayList() {
const day = formatDate(this.todayDate)
const params = {
rqStart: day + 'T00:00:00',
rqEnd: day + 'T23:59:59',
pageNum: 1,
pageSize: 200
}
this.todayLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.todayList = data.records || []
this.todayLoading = false
}).catch(() => {
this.todayList = []
this.todayLoading = false
})
},
/* ---------- 本周 ---------- */
/* ---------- 本周:周次导航 ---------- */
handlePrevWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() - 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
handleNextWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() + 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
weekRange() {
const start = formatDate(this.weekStart) + 'T00:00:00'
const endDate = new Date(this.weekStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getWeekList() {
const range = this.weekRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.weekQuery.pageNum,
pageSize: this.weekQuery.pageSize
}
this.weekLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.weekList = data.records || []
this.weekTotal = data.total || 0
this.weekLoading = false
}).catch(() => {
this.weekList = []
this.weekTotal = 0
this.weekLoading = false
})
},
/* ---------- 三实 ---------- */
handlePrevWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() - 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
handleNextWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() + 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
practiceRange() {
const start = formatDate(this.practiceStart) + 'T00:00:00'
const endDate = new Date(this.practiceStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getPracticeList() {
// 后端 plan/list 接口未返回课程类型字段,无法按三实课筛选,与本周共用同一数据源
const range = this.practiceRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.practiceQuery.pageNum,
pageSize: this.practiceQuery.pageSize
}
this.practiceLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.practiceList = data.records || []
this.practiceTotal = data.total || 0
this.practiceLoading = false
}).catch(() => {
this.practiceList = []
this.practiceTotal = 0
this.practiceLoading = false
})
},
/* ---------- 班次 ---------- */
/* ---------- 队别班次下拉(真实接口) ---------- */
loadTeamOptions() {
listTeam({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
this.teamOptions = data.records || []
}).catch(() => {
this.teamOptions = []
})
},
loadKbMap() {
listKb({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
const list = data.records || []
const map = {}
;(data.records || []).forEach(item => {
if (item.kbh) map[item.kbh] = item.kmc
;(list || []).forEach(item => {
if (item.xydmc) map[item.xydmc] = item.xydbh
})
this.kbMap = map
this.teamMap = map
this.teamClassOptions = list.map(item => item.xydmc).filter(Boolean)
}).catch(() => {
this.kbMap = {}
this.teamMap = {}
this.teamClassOptions = []
})
},
loadTeacherMap() {
listTeacher({ pageNum: 1, pageSize: 1000 }).then(response => {
/* ---------- 数据字段映射(接口字段 -> 页面字段) ---------- */
parseSection(sksj) {
const m = String(sksj || '').match(/第(\d+)节/)
return m ? Number(m[1]) : undefined
},
mapLessonRow(apiRow) {
// 接口今日/本周实体:bh sksj kc zrdw bc jy cd jxnrxff bz
const row = Object.assign({}, apiRow)
row.kcmc = apiRow.kc
row.skjy = apiRow.jy
row.jxcd = apiRow.cd
row.jxnrff = apiRow.jxnrxff
row.jybz = apiRow.bz
row.sjsj = apiRow.sksj
row.jc = this.parseSection(apiRow.sksj)
return row
},
mapClassRow(apiRow) {
// 接口班次实体:sskcbh kcmcksxs zrjykc jhxs yxxs khlxfs ssjy rscd ssjhbg
const row = Object.assign({}, apiRow)
const parts = String(apiRow.kcmcksxs || '').split(' / ')
row.kcmc = parts[0] || '-'
row.kssk = parts[1] || '-'
row.zrjs = apiRow.zrjykc
row.ssjs = apiRow.ssjy
return row
},
/* ---------- 今日:拉取数据 ---------- */
getTodayList() {
this.loading = true
listToday({}).then(response => {
const data = response.data
this.todayData = Array.isArray(data)
? (data || []).map(item => this.mapLessonRow(item))
: ((data && data.records) || []).map(item => this.mapLessonRow(item))
this.loading = false
}).catch(() => {
this.todayData = []
this.loading = false
})
},
/* ---------- 本周:拉取数据 ---------- */
getWeekList() {
this.loading = true
listWeek({}).then(response => {
const data = response.data
this.weekData = Array.isArray(data)
? (data || []).map(item => this.mapLessonRow(item))
: ((data && data.records) || []).map(item => this.mapLessonRow(item))
this.loading = false
}).catch(() => {
this.weekData = []
this.loading = false
})
},
/* ---------- 班次:拉取数据 ---------- */
getShiftList() {
if (!this.searchForm.teamClass) return
const xydbh = this.teamMap[this.searchForm.teamClass] || this.searchForm.teamClass
const params = { xydbh: xydbh, pageNum: 1, pageSize: 1000 }
this.loading = true
listClass(params).then(response => {
const data = response.data || {}
const map = {}
;(data.records || []).forEach(item => {
if (item.jybh) map[item.jybh] = item.jyxm
})
this.teacherMap = map
this.shiftData = (data.records || []).map(item => this.mapClassRow(item))
this.loading = false
}).catch(() => {
this.teacherMap = {}
this.shiftData = []
this.loading = false
})
},
handleShiftSearch() {
if (!this.shiftXydbh) {
/* ---------- 班次:查询 ---------- */
handleQuery() {
if (!this.searchForm.teamClass) {
this.$message.warning('请先选择队别班次')
return
}
this.shiftQuery.pageNum = 1
this.getShiftList()
},
getShiftList() {
const params = {
xydbh: this.shiftXydbh,
nd: this.shiftNd,
pageNum: this.shiftQuery.pageNum,
pageSize: this.shiftQuery.pageSize
}
this.shiftLoading = true
listStudentTeamTask(params).then(response => {
const data = response.data || {}
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
this.shiftList = (data.records || []).map(item => ({
...item,
xydmc: (team && team.xydmc) || item.xydbh,
_kcmc: this.kbMap[item.kbh] || item.kbh,
_zrjy: this.teacherMap[item.jysjhjybh] || item.jysjhjybh || '-',
_ssjy: this.teacherMap[item.jybh] || item.jybh || '-'
}))
this.shiftTotal = data.total || 0
this.shiftLoading = false
}).catch(() => {
this.shiftList = []
this.shiftTotal = 0
this.shiftLoading = false
})
/* ---------- 下载 ---------- */
downloadBlob(blob, filename) {
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(link.href)
},
/* ---------- 无后端接口的操作 ---------- */
handleNotProvided() {
this.$message.info('后端暂未提供该接口')
shiftQueryParams() {
if (!this.searchForm.teamClass) return null
const xydbh = this.teamMap[this.searchForm.teamClass] || this.searchForm.teamClass
return { xydbh: xydbh }
},
/** 下载学期教学实施计划 ExcelGET /kcb/class/export-plan */
handleExportPlan() {
const params = this.shiftQueryParams()
if (!params) {
this.$message.warning('请先选择队别班次')
return
}
exportClassPlan(params).then(blob => {
this.downloadBlob(blob, '学期教学实施计划.xlsx')
this.$message.success('学期教学实施计划导出成功')
}).catch(() => {})
},
/** 下载学期成绩表 ExcelGET /kcb/class/export-grades */
handleExportGrades() {
const params = this.shiftQueryParams()
if (!params) {
this.$message.warning('请先选择队别班次')
return
}
exportClassGrades(params).then(blob => {
this.downloadBlob(blob, '学期成绩表.xlsx')
this.$message.success('学期成绩表导出成功')
}).catch(() => {})
},
/** 班次课程列表另存 ExcelGET /kcb/class/export-courses */
handleExportCourses() {
const params = this.shiftQueryParams()
if (!params) {
this.$message.warning('请先选择队别班次')
return
}
exportClassCourses(params).then(blob => {
this.downloadBlob(blob, '班次课程列表.xlsx')
this.$message.success('班次课程列表导出成功')
}).catch(() => {})
}
}
}
@@ -594,12 +388,15 @@ export default {
padding-bottom: 16px;
}
.mb8 {
margin-bottom: 8px;
.sub-text {
font-size: 12px;
color: #909399;
margin-top: 2px;
}
/* 日期/周次导航 */
.nav-header {
.date-header,
.week-header {
display: flex;
align-items: center;
justify-content: center;
@@ -614,30 +411,28 @@ export default {
font-size: 14px;
}
.nav-title {
font-size: 18px;
.date-title,
.week-title {
font-size: 16px;
font-weight: 700;
color: #303133;
}
}
/* 表格区 */
.table-card {
.danger-text {
color: #f56c6c;
}
}
/* 班次 */
.shift-search {
margin-bottom: 8px;
.search-card {
margin-bottom: 12px;
.search-form {
margin-bottom: 0;
}
.shift-select {
width: 220px;
width: 240px;
}
}
.shift-title {
.page-title {
font-size: 17px;
font-weight: 700;
color: #303133;
@@ -645,10 +440,14 @@ export default {
margin-bottom: 12px;
}
.sub-text {
font-size: 12px;
color: #909399;
margin-top: 2px;
.action-card {
margin-bottom: 12px;
.button-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
}
}
</style>
@@ -2,7 +2,6 @@
<div class="page-container">
<!-- 统计卡片 -->
<div class="section-card">
<div class="section-title">调课申请统计当前页</div>
<div class="stat-grid">
<div
v-for="card in statCards"
@@ -54,21 +53,21 @@
<el-table :data="tableData" v-loading="loading" size="small" border stripe class="compact-table" max-height="520">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="kcmc" label="课程名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="申请教员" min-width="110" align="center" show-overflow-tooltip>
<el-table-column prop="kcmc" label="课程名称" width="200" show-overflow-tooltip header-align="center" />
<el-table-column label="申请教员" width="150" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.sqjyxm || scope.row.sqjybh || '-' }}</template>
</el-table-column>
<el-table-column prop="sy" label="调课事由" min-width="180" show-overflow-tooltip header-align="center" />
<el-table-column label="拟调整时间" width="170" align="center" show-overflow-tooltip>
<el-table-column prop="sy" label="调课事由" width="230" show-overflow-tooltip header-align="center" />
<el-table-column label="拟调整时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtRqJc(scope.row.rq, scope.row.jc) }}</template>
</el-table-column>
<el-table-column prop="nd" label="年度" width="80" align="center" />
<el-table-column label="教研室审批状态" width="110" align="center">
<el-table-column prop="nd" label="年度" width="150" align="center" />
<el-table-column label="教研室审批状态" width="150" align="center">
<template slot-scope="scope">
<el-tag :type="auditStatusTag(scope.row.jyspzzt).type" size="mini">{{ auditStatusTag(scope.row.jyspzzt).label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="教研室查收状态" width="100" align="center">
<el-table-column label="教研室查收状态" width="150" align="center">
<template slot-scope="scope">
<el-tag :type="receiveStatusTag(scope.row.jyscszt).type" size="mini">{{ receiveStatusTag(scope.row.jyscszt).label }}</el-tag>
</template>
@@ -76,7 +75,7 @@
<el-table-column label="创建时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.cjsj) }}</template>
</el-table-column>
<el-table-column label="操作" width="210" align="center" fixed="right">
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<div class="ops-cell">
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
@@ -6,22 +6,22 @@
<el-row :gutter="24">
<el-col :xs="24" :md="8">
<el-form-item label="专业名称">
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称(模糊)" clearable />
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="专业代码">
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码(模糊)" clearable />
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训类型">
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型(精确)" clearable />
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训层次">
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次(精确)" clearable />
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
@@ -65,17 +65,17 @@
</div>
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="zydh" label="专业代号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zymc" label="专业名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="zyfx" label="专业方向" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zydm" label="专业代码" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx" label="培训类型" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxcc" label="培训层次" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx2" label="培训类型2" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="xylb" label="学员类别" min-width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="xnz" label="学年制" min-width="80" show-overflow-tooltip header-align="center" />
<el-table-column prop="zydh" label="专业代号" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zymc" label="专业名称" width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="zyfx" label="专业方向" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zydm" label="专业代码" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx" label="培训类型" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxcc" label="培训层次" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx2" label="培训类型2" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="xylb" label="学员类别" width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="xnz" label="学年制" width="80" show-overflow-tooltip header-align="center" />
<el-table-column prop="xqs" label="学期数" width="80" align="center" />
<el-table-column prop="zybb" label="专业版本" min-width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="zybb" label="专业版本" width="100" show-overflow-tooltip header-align="center" />
<el-table-column label="主干专业" width="90" align="center">
<template slot-scope="scope">{{ fmtYesNo(scope.row.zgzy) }}</template>
</el-table-column>
@@ -92,7 +92,7 @@
<el-table-column label="停用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<el-table-column label="操作" width="240" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
@@ -122,7 +122,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号(主键)" :disabled="dialog.isEdit" />
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" :disabled="dialog.isEdit" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -495,7 +495,8 @@ export default {
return this.isTrue(val) ? '是' : '否'
},
fmtDateTime(val) {
return (val || '').substring(0, 19) || '-'
const s = (val || '').substring(0, 10)
return s || '-'
},
fmtVal(val) {
return val === '' || val === null || val === undefined ? '-' : val
@@ -55,11 +55,11 @@
</div>
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="jsbh" label="教室编号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jsmc" label="教室名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxldh" label="教学楼代号" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxcdlx" label="教学场地类型" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jc" label="简称" min-width="90" show-overflow-tooltip header-align="center" />
<el-table-column prop="jsbh" label="教室编号" width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="jsmc" label="教室名称" width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxldh" label="教学楼代号" width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxcdlx" label="教学场地类型" width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="jc" label="简称" width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="rnrs" label="容纳人数" width="90" align="center" />
<el-table-column prop="mj" label="面积" width="90" align="center" />
<el-table-column prop="ksrnrs" label="考试容纳人数" width="110" align="center" />
@@ -74,8 +74,8 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sssb" label="设施设备" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="操作" width="140" align="center" fixed="right">
<el-table-column prop="sssb" label="设施设备" width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="操作" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" class="danger-text-btn" @click="handleDelete(scope.row)">删除</el-button>
@@ -100,11 +100,6 @@
:close-on-click-modal="false">
<el-form ref="classroomForm" :model="dialog.form" :rules="rules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="主键编号" prop="id">
<el-input v-model="dialog.form.id" placeholder="请输入主键编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教室编号" prop="jsbh">
<el-input v-model="dialog.form.jsbh" placeholder="请输入教室编号" />
@@ -230,7 +225,6 @@ export default {
form: this.createEmptyForm()
},
rules: {
id: [{ required: true, message: '请输入主键编号', trigger: 'blur' }],
jsbh: [{ required: true, message: '请输入教室编号', trigger: 'blur' }],
jsmc: [{ required: true, message: '请输入教室名称', trigger: 'blur' }]
}
+40 -61
View File
@@ -1,17 +1,5 @@
<template>
<div class="app-container teach-notice">
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
</el-row>
<!-- 查询条件区域 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="标题" prop="title">
@@ -22,21 +10,6 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="优先级">
<el-input
v-model="queryParams.priorityFrom"
placeholder="从"
type="number"
style="width: 100px"
/>
<span class="range-sep">-</span>
<el-input
v-model="queryParams.priorityTo"
placeholder="到"
type="number"
style="width: 100px"
/>
</el-form-item>
<el-form-item label="公告类别">
<el-select
v-model="queryParams.category"
@@ -49,13 +22,7 @@
:key="item"
:label="item"
:value="item"
>
<el-checkbox
:value="queryParams.category === item"
@click.stop.prevent="onCategoryCheck(item)"
/>
<span style="margin-left: 6px">{{ item }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="发布日期">
@@ -81,34 +48,40 @@
</el-form-item>
</el-form>
<el-alert
class="query-tip"
title="注意:勾选'选择框'表示启用该项对应的查询条件"
type="info"
:closable="false"
show-icon
/>
<!-- 数据列表区域 -->
<div class="list-heading">
<div class="list-title">公告列表</div>
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</div>
<el-table v-loading="loading" :data="noticeList" border :height="tableHeight">
<el-table-column label="序号" type="index" align="center" width="70" />
<el-table-column label="发布时间" align="center" min-width="160">
<el-table-column label="发布时间" align="center" width="165">
<template slot-scope="scope">{{ formatTime(scope.row.publishTime) }}</template>
</el-table-column>
<el-table-column label="标题" align="center" prop="title" min-width="280" :show-overflow-tooltip="true" />
<el-table-column label="标题" align="left" prop="title" min-width="300" :show-overflow-tooltip="true" />
<el-table-column label="公告状态" align="center" width="100">
<template slot-scope="scope">
<el-tag :type="statusTagType(scope.row.status)">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="公告类别" align="center" prop="category" width="100" />
<el-table-column label="优先级" align="center" prop="priority" width="90" />
<el-table-column label="阅读次数" align="center" prop="readCount" width="100" />
<el-table-column label="公告类别" align="center" prop="category" width="110" />
<el-table-column label="优先级" align="center" prop="priority" width="80" />
<el-table-column label="阅读次数" align="center" prop="readCount" width="90" />
<el-table-column label="接收/已读" align="center" width="110">
<template slot-scope="scope">{{ scope.row.recipientCount }}/{{ scope.row.readerCount }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="240" class-name="small-padding fixed-width">
<el-table-column
label="操作"
align="center"
width="300"
fixed="right"
class-name="small-padding fixed-width action-column"
>
<template slot-scope="scope">
<el-button
type="text"
@@ -248,8 +221,6 @@ export default {
pageNum: 1,
pageSize: 10,
title: undefined,
priorityFrom: undefined,
priorityTo: undefined,
category: undefined,
publishDateFrom: undefined,
publishDateTo: undefined
@@ -300,10 +271,6 @@ export default {
if (status === '已下架') return 'info'
return 'warning'
},
/** 公告类别复选框:点击即选中该项作为查询条件 */
onCategoryCheck(item) {
this.queryParams.category = this.queryParams.category === item ? undefined : item
},
/** 查询按钮 */
handleQuery() {
this.queryParams.pageNum = 1
@@ -315,8 +282,6 @@ export default {
pageNum: 1,
pageSize: 10,
title: undefined,
priorityFrom: undefined,
priorityTo: undefined,
category: undefined,
publishDateFrom: undefined,
publishDateTo: undefined
@@ -412,24 +377,27 @@ export default {
<style scoped lang="scss">
.teach-notice {
.mb8 {
margin-bottom: 8px;
}
.range-sep {
margin: 0 8px;
color: #909399;
}
.query-tip {
margin: 4px 0 16px;
.list-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
::v-deep .action-column .cell {
white-space: nowrap;
}
.detail-body {
padding: 0 8px;
.detail-row {
@@ -454,5 +422,16 @@ export default {
}
}
}
@media (max-width: 768px) {
.list-heading {
align-items: stretch;
flex-direction: column;
}
.list-heading .el-button {
width: 100%;
}
}
}
</style>
@@ -1,7 +1,14 @@
<template>
<div class="app-container">
<div class="app-container management-page elective-page">
<!-- ==================== 2. 查询条件区域 ==================== -->
<div class="section-card query-card">
<div class="section-heading">
<div>
<h2 class="section-title">筛选条件</h2>
<p class="section-description">按课程班次教员及状态筛选选修课</p>
</div>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</div>
<el-form>
<el-row :gutter="32">
<!-- 左栏 -->
@@ -15,9 +22,13 @@
<el-input v-model="searchForm.kbcnj" placeholder="请输入可选班次年级" clearable class="q-control" />
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.xxkztEnabled" />
<span class="q-label">选修课状态</span>
<el-select v-model="searchForm.xxkzt" class="q-control" :disabled="!searchForm.xxkztEnabled">
<span class="q-label no-check">选修课状态</span>
<el-select
v-model="searchForm.xxkzt"
placeholder="请选择选修课状态"
clearable
class="q-control"
>
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
@@ -34,9 +45,8 @@
<el-input v-model="searchForm.xxbmc" placeholder="请输入选修班名称" clearable class="q-control" />
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pxccEnabled" />
<span class="q-label">培训层次</span>
<el-select v-model="searchForm.pxcc" class="q-control" :disabled="!searchForm.pxccEnabled">
<span class="q-label no-check">培训层次</span>
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="q-control">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
@@ -44,50 +54,79 @@
</el-row>
</el-form>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
</div>
<!-- ==================== 3. 导出按钮区域 ==================== -->
<div class="section-card export-card">
<div class="section-heading section-heading--compact">
<div>
<h2 class="section-title">数据导出</h2>
<p class="section-description">下载选修班及学员名单</p>
</div>
</div>
<div class="export-bar">
<el-button type="primary" @click="handleDownloadList">下载选修班列表</el-button>
<el-button type="primary" @click="handleDownloadWord">下载选修班学员名单Word</el-button>
<el-button type="primary" @click="handleDownloadExcel">下载选修班学员名单Excel</el-button>
<el-button icon="el-icon-download" @click="handleDownloadList">下载选修班列表</el-button>
<el-button icon="el-icon-download" @click="handleDownloadWord">下载选修班学员名单Word</el-button>
<el-button icon="el-icon-download" @click="handleDownloadExcel">下载选修班学员名单Excel</el-button>
</div>
</div>
<!-- ==================== 4. 操作按钮区域 ==================== -->
<div class="section-card action-card">
<div class="action-row">
<el-button type="primary" @click="handleOpenNew">新建选修课</el-button>
<el-button type="primary" @click="handleOpenEarly">所选批量开放报名</el-button>
<el-button type="primary" @click="handleCancelEarly">所选批量取消开放报名</el-button>
<el-button type="primary" @click="handleStopSignup">批量停止报名</el-button>
<el-button type="primary" @click="handleReverseRange">所选批量根据学员反向确定班次范围</el-button>
<div class="section-heading section-heading--compact">
<div>
<h2 class="section-title">选修课操作</h2>
<p class="section-description">新建选修课或对已选数据执行批量操作</p>
</div>
</div>
<div class="action-row">
<el-button type="primary" @click="handleDownloadTemplate">选修课数据文件模板下载</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleOpenNew">新建选修课</el-button>
<el-button type="primary" plain icon="el-icon-circle-check" @click="handleOpenEarly">
所选批量开放报名
</el-button>
<el-button type="warning" plain icon="el-icon-remove-outline" @click="handleCancelEarly">
所选批量取消开放报名
</el-button>
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
批量停止报名
</el-button>
<el-button icon="el-icon-refresh" @click="handleReverseRange">
所选批量根据学员反向确定班次范围
</el-button>
</div>
<div class="action-row">
<el-button icon="el-icon-download" @click="handleDownloadTemplate">选修课数据文件模板下载</el-button>
</div>
</div>
<!-- ==================== 5. 文件上传区域 ==================== -->
<div class="section-card upload-card">
<div class="section-heading section-heading--compact">
<div>
<h2 class="section-title">数据导入</h2>
<p class="section-description">选择填写完成的模板文件并上传</p>
</div>
</div>
<div class="upload-row">
<div class="upload-left">
<el-button @click="handleSelectFile">选择文件</el-button>
<el-button icon="el-icon-folder-opened" @click="handleSelectFile">选择文件</el-button>
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
</div>
<div class="upload-right">
<el-button type="primary" @click="handleUpload">上传数据</el-button>
<el-button type="primary" icon="el-icon-upload2" @click="handleUpload">上传数据</el-button>
</div>
</div>
</div>
<!-- ==================== 6. 数据表格区域 ==================== -->
<div class="section-card table-card">
<div class="section-heading section-heading--compact table-heading">
<div>
<h2 class="section-title">选修课列表</h2>
<p class="section-description"> {{ total }} 条数据</p>
</div>
</div>
<el-table v-loading="loading" :data="tableData" border stripe class="main-table" @selection-change="handleSelectionChange">
<template slot="empty">
<span>无数据</span>
@@ -204,12 +243,10 @@ export default {
searchForm: {
js: '',
kbcnj: '',
xxkztEnabled: false,
xxkzt: '拟制',
xxkzt: '',
kckmmc: '',
xxbmc: '',
pxccEnabled: false,
pxcc: '无'
pxcc: ''
},
// ==================== 数据表格 ====================
@@ -259,8 +296,8 @@ export default {
if (this.searchForm.kbcnj) q.kxbcnj = this.searchForm.kbcnj
if (this.searchForm.kckmmc) q.kmc = this.searchForm.kckmmc
if (this.searchForm.xxbmc) q.xxbmc = this.searchForm.xxbmc
if (this.searchForm.xxkztEnabled) q.xkzt = this.searchForm.xxkzt
if (this.searchForm.pxccEnabled) q.pxcc = this.searchForm.pxcc
if (this.searchForm.xxkzt) q.xkzt = this.searchForm.xxkzt
if (this.searchForm.pxcc) q.pxcc = this.searchForm.pxcc
return q
},
@@ -423,11 +460,6 @@ export default {
margin-bottom: 14px;
min-height: 32px;
::v-deep .el-checkbox {
margin-right: 6px;
flex-shrink: 0;
}
.q-label {
width: 120px;
flex-shrink: 0;
@@ -436,10 +468,6 @@ export default {
text-align: right;
margin-right: 10px;
white-space: nowrap;
&.no-check {
margin-left: 24px;
}
}
.q-control {
@@ -447,21 +475,10 @@ export default {
min-width: 0;
}
}
}
.notice {
font-size: 12px;
color: #f56c6c;
margin-top: 8px;
text-align: left;
}
.query-footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.export-bar {
align-items: center;
}
// ==================== 4. 操作按钮区域 ====================
@@ -501,6 +518,11 @@ export default {
}
}
}
.upload-right {
display: flex;
align-items: center;
}
}
}
@@ -518,23 +540,11 @@ export default {
flex-direction: column;
line-height: 1.4;
}
}
// ==================== 按钮样式 ====================
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
.pagination-wrap {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
</style>
@@ -24,13 +24,13 @@
<!-- ==================== 2. 操作按钮区域 ==================== -->
<div class="settings-panel">
<div class="action-buttons-row">
<button type="button" class="blue-btn" @click="handleAdd">新增记录</button>
<button type="button" class="gray-btn" @click="handleAutoGenerate">自动生成必修课程</button>
<button type="button" class="gray-btn" @click="handlePresetMerge">预设合班</button>
<button type="button" class="gray-btn" @click="handlePresetSplit">预设拆班</button>
<button type="button" class="gray-btn" @click="handleApplyRegion">选定区域应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleApplyWhole">整个班历应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleRefresh">刷新</button>
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增记录</el-button>
<el-button size="small" @click="handleAutoGenerate">自动生成必修课程</el-button>
<el-button size="small" @click="handlePresetMerge">预设合班</el-button>
<el-button size="small" @click="handlePresetSplit">预设拆班</el-button>
<el-button size="small" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
<el-button size="small" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
<el-button size="small" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
</div>
</div>
@@ -741,44 +741,4 @@ export default {
}
}
// ========== 银灰色 / 蓝色边框按钮 ==========
.gray-btn {
min-width: 90px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #000;
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
border: 1px solid #7a7a7a;
border-radius: 2px;
cursor: pointer;
&:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
}
&:active {
background: #c8c8c8;
}
}
.blue-btn {
min-width: 90px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #1e6fff;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #d9ecff;
}
&:active {
background: #c6e2ff;
}
}
</style>
@@ -1,8 +1,15 @@
<template>
<div class="app-container subject-page">
<div class="app-container management-page subject-page">
<!-- ==================== 2. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<div class="section-heading">
<div>
<h2 class="section-title">筛选条件</h2>
<p class="section-description">按课程名称代码和培训属性筛选课程科目</p>
</div>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</div>
<el-form :model="searchForm">
<el-row :gutter="32">
<!-- 左栏 -->
@@ -43,41 +50,56 @@
</el-row>
</el-form>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
</el-card>
<!-- ==================== 3. 操作按钮区域 ==================== -->
<el-card shadow="never" class="action-card">
<div class="action-row">
<el-button type="primary" @click="handleOpenDialog">新建课程科目</el-button>
<el-button type="primary" @click="handleBatchDelete">删除所选</el-button>
<el-button type="primary" @click="handleDownloadCourse">下载课程科目基本信息</el-button>
<el-button type="primary" @click="handleDownloadTextbook">下载课程教材基本信息</el-button>
<div class="section-heading section-heading--compact">
<div>
<h2 class="section-title">课程科目操作</h2>
<p class="section-description">维护课程科目或下载业务数据与导入模板</p>
</div>
</div>
<div class="action-row">
<el-button type="primary" @click="handleDownloadTemplate">课程科目数据文件模板下载</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 icon="el-icon-download" @click="handleDownloadCourse">下载课程科目基本信息</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTextbook">下载课程教材基本信息</el-button>
</div>
<div class="action-row">
<el-button icon="el-icon-download" @click="handleDownloadTemplate">课程科目数据文件模板下载</el-button>
</div>
</el-card>
<!-- ==================== 4. 文件上传区域 ==================== -->
<el-card shadow="never" class="upload-card">
<div class="section-heading section-heading--compact">
<div>
<h2 class="section-title">数据导入</h2>
<p class="section-description">选择填写完成的课程科目模板并上传</p>
</div>
</div>
<div class="upload-row">
<div class="upload-left">
<el-button @click="handleSelectFile">选择文件</el-button>
<el-button icon="el-icon-folder-opened" @click="handleSelectFile">选择文件</el-button>
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
</div>
<div class="upload-right">
<el-checkbox v-model="coverExist">覆盖已存在课程科目</el-checkbox>
<el-button type="primary" @click="handleUpload">上传数据</el-button>
<el-button type="primary" icon="el-icon-upload2" @click="handleUpload">上传数据</el-button>
</div>
</div>
</el-card>
<!-- ==================== 6. 数据表格区域 ==================== -->
<el-card shadow="never" class="table-card">
<div class="section-heading section-heading--compact table-heading">
<div>
<h2 class="section-title">课程科目列表</h2>
<p class="section-description"> {{ total }} 条数据</p>
</div>
</div>
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="40" align="center" />
@@ -622,13 +644,6 @@ export default {
text-align: left;
}
.query-footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
// ==================== 3. 操作按钮区域 ====================
@@ -726,20 +741,4 @@ export default {
gap: 12px;
}
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
}
}
</style>
@@ -50,8 +50,8 @@
<span>无数据</span>
</template>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="rwmc" label="任务名称" min-width="180" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="90" align="center" />
<el-table-column prop="rwmc" label="任务名称" width="250" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="150" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
@@ -59,11 +59,11 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="170" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column prop="fbsj" label="发布时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="300" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
@@ -177,7 +177,7 @@
</el-descriptions>
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
<div slot="footer">
<el-button type="primary" @click="detailVisible = false">关闭</el-button>
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
@@ -37,11 +37,11 @@
<div class="list-toolbar">
<div class="left-group">
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建</el-button>
<el-button type="primary" icon="el-icon-delete" @click="handleDeleteSelected">删除所选</el-button>
<el-button type="primary" icon="el-icon-upload" @click="handleImport">导入教员信息</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 type="primary" icon="el-icon-download" @click="handleDownloadTemplate">教员数据文件模板下载</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">教员数据文件模板下载</el-button>
</div>
</div>
@@ -50,26 +50,26 @@
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="jybh" label="教员工号" min-width="110" align="center" />
<el-table-column prop="jyxm" label="教员姓名" min-width="90" align="center" />
<el-table-column label="性别" min-width="60" align="center">
<el-table-column prop="jybh" label="教员工号" width="110" align="center" />
<el-table-column prop="jyxm" label="教员姓名" width="90" align="center" />
<el-table-column label="性别" width="60" align="center">
<template slot-scope="{ row }">{{ fmtGender(row.xb) }}</template>
</el-table-column>
<el-table-column prop="jysmc" label="教研室名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="zc" label="职称" min-width="90" align="center" />
<el-table-column prop="xl" label="学历" min-width="90" align="center" />
<el-table-column prop="xw" label="学位" min-width="90" align="center" />
<el-table-column label="出生时间" min-width="110" align="center">
<el-table-column prop="jysmc" label="教研室名称" width="200" show-overflow-tooltip align="center"/>
<el-table-column prop="zc" label="职称" width="120" align="center" />
<el-table-column prop="xl" label="学历" width="120" align="center" />
<el-table-column prop="xw" label="学位" width="120" align="center" />
<el-table-column label="出生时间" width="110" align="center">
<template slot-scope="{ row }">{{ fmtDateTime(row.cssj) || '-' }}</template>
</el-table-column>
<el-table-column label="入伍工作时间" min-width="120" align="center">
<el-table-column label="入伍工作时间" width="120" align="center">
<template slot-scope="{ row }">{{ fmtDateTime(row.rwgzsj) || '-' }}</template>
</el-table-column>
<el-table-column prop="sjh" label="联系方式" min-width="110" align="center" />
<el-table-column label="虚实类型" min-width="80" align="center">
<el-table-column prop="sjh" label="联系方式" width="110" align="center" />
<el-table-column label="虚实类型" width="80" align="center">
<template slot-scope="{ row }">{{ fmtXslx(row.xslx) }}</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<el-table-column label="操作" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" icon="el-icon-view" @click="handleViewAttribute(row)">查看属性</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
@@ -5,40 +5,28 @@
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.ksrqEnabled">开始日期</el-checkbox>
</template>
<el-form-item label="开始日期">
<el-date-picker v-model="searchForm.ksrq" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
class="w-full" :disabled="!searchForm.ksrqEnabled" />
class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jsrqEnabled">结束日期</el-checkbox>
</template>
<el-form-item label="结束日期">
<el-date-picker v-model="searchForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
class="w-full" :disabled="!searchForm.jsrqEnabled" />
class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.kcmcEnabled">课程名称</el-checkbox>
</template>
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable :disabled="!searchForm.kcmcEnabled" />
<el-form-item label="课程名称">
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jysEnabled">教研室</el-checkbox>
</template>
<el-select v-model="searchForm.jys" placeholder="请选择教研室" class="w-full" :disabled="!searchForm.jysEnabled">
<el-form-item label="教研室">
<el-select v-model="searchForm.jys" placeholder="请选择教研室" clearable class="w-full">
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -47,21 +35,15 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jxffEnabled">教学方法</el-checkbox>
</template>
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" class="w-full" :disabled="!searchForm.jxffEnabled">
<el-form-item label="教学方法">
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" clearable class="w-full">
<el-option v-for="item in jxffOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.skjyEnabled">授课教员</el-checkbox>
</template>
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" class="w-full" :disabled="!searchForm.skjyEnabled">
<el-form-item label="授课教员">
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" clearable class="w-full">
<el-option v-for="item in skjyOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -70,15 +52,12 @@
<el-row :gutter="0">
<el-col :span="24">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.gdqkEnabled">改动情况</el-checkbox>
</template>
<div class="change-options" :class="{ disabled: !searchForm.gdqkEnabled }">
<el-radio-group v-model="searchForm.createSource" :disabled="!searchForm.gdqkEnabled">
<el-form-item label="改动情况">
<div class="change-options">
<el-radio-group v-model="searchForm.createSource">
<el-radio v-for="item in createSourceOptions" :key="item" :label="item">{{ item }}</el-radio>
</el-radio-group>
<el-checkbox-group v-model="searchForm.changeTypes" :disabled="!searchForm.gdqkEnabled">
<el-checkbox-group v-model="searchForm.changeTypes">
<el-checkbox v-for="item in changeTypeOptions" :key="item" :label="item">{{ item }}</el-checkbox>
</el-checkbox-group>
</div>
@@ -88,21 +67,15 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.rwlbEnabled">任务类别</el-checkbox>
</template>
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" class="w-full" :disabled="!searchForm.rwlbEnabled">
<el-form-item label="任务类别">
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" clearable class="w-full">
<el-option v-for="item in rwlbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxqbEnabled">培训期班</el-checkbox>
</template>
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable :disabled="!searchForm.pxqbEnabled" />
<el-form-item label="培训期班">
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable />
</el-form-item>
</el-col>
</el-row>
@@ -141,7 +114,7 @@
<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="jxrzcjfs" label="创建方式" width="100" align="center" />
<el-table-column label="详细" width="80" align="center" fixed="right">
<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>
@@ -203,24 +176,15 @@ export default {
// ==================== 查询表单 ====================
searchForm: {
ksrq: '',
ksrqEnabled: false,
jsrq: '',
jsrqEnabled: false,
kcmc: '',
kcmcEnabled: false,
jys: '',
jysEnabled: false,
jxff: '理论讲授',
jxffEnabled: false,
skjy: '教务处',
skjyEnabled: false,
gdqkEnabled: false,
jxff: '',
skjy: '',
createSource: '',
changeTypes: [],
rwlb: '全军院校训练规划内任务',
rwlbEnabled: false,
pxqb: '',
pxqbEnabled: false
rwlb: '',
pxqb: ''
},
jysOptions: ['教研室', '防空兵系', '通信工程系', '装甲兵系'],
@@ -261,20 +225,20 @@ export default {
this.selectedRows = rows
},
/** 根据"勾选启用"模式构建实际查询参数 */
/** 根据已填写的查询值构建实际查询参数 */
buildSearchParams() {
const form = this.searchForm
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (form.ksrqEnabled && form.ksrq) params.ksrq = form.ksrq
if (form.jsrqEnabled && form.jsrq) params.jsrq = form.jsrq
if (form.kcmcEnabled && form.kcmc) params.kcmc = form.kcmc
if (form.jysEnabled && form.jys) params.jys = form.jys
if (form.jxffEnabled && form.jxff) params.jxff = form.jxff
if (form.skjyEnabled && form.skjy) params.skjy = form.skjy
if (form.rwlbEnabled && form.rwlb) params.rwlb = form.rwlb
if (form.pxqbEnabled && form.pxqb) params.pxqb = form.pxqb
if (form.gdqkEnabled && form.createSource) params.gdqk = form.createSource
if (form.gdqkEnabled && form.changeTypes.length > 0) {
if (form.ksrq) params.ksrq = form.ksrq
if (form.jsrq) params.jsrq = form.jsrq
if (form.kcmc) params.kcmc = form.kcmc
if (form.jys) params.jys = form.jys
if (form.jxff) params.jxff = form.jxff
if (form.skjy) params.skjy = form.skjy
if (form.rwlb) params.rwlb = form.rwlb
if (form.pxqb) params.pxqb = form.pxqb
if (form.createSource) params.gdqk = form.createSource
if (form.changeTypes.length > 0) {
params.gdxxqk = form.changeTypes.join(',')
}
return params
@@ -393,9 +357,6 @@ export default {
gap: 12px;
}
&.disabled {
opacity: 0.6;
}
}
}
@@ -5,40 +5,28 @@
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.ksrqEnabled">开始日期</el-checkbox>
</template>
<el-form-item label="开始日期">
<el-date-picker v-model="searchForm.ksrq" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
class="w-full" :disabled="!searchForm.ksrqEnabled" />
class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jsrqEnabled">结束日期</el-checkbox>
</template>
<el-form-item label="结束日期">
<el-date-picker v-model="searchForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
class="w-full" :disabled="!searchForm.jsrqEnabled" />
class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.kcmcEnabled">课程名称</el-checkbox>
</template>
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable :disabled="!searchForm.kcmcEnabled" />
<el-form-item label="课程名称">
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jysEnabled">教研室</el-checkbox>
</template>
<el-select v-model="searchForm.jys" placeholder="请选择教研室" class="w-full" :disabled="!searchForm.jysEnabled">
<el-form-item label="教研室">
<el-select v-model="searchForm.jys" placeholder="请选择教研室" clearable class="w-full">
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -47,21 +35,15 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jxffEnabled">教学方法</el-checkbox>
</template>
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" class="w-full" :disabled="!searchForm.jxffEnabled">
<el-form-item label="教学方法">
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" clearable class="w-full">
<el-option v-for="item in jxffOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.rzztEnabled">日志状态</el-checkbox>
</template>
<el-select v-model="searchForm.rzzt" placeholder="请选择日志状态" class="w-full" :disabled="!searchForm.rzztEnabled">
<el-form-item label="日志状态">
<el-select v-model="searchForm.rzzt" placeholder="请选择日志状态" clearable class="w-full">
<el-option v-for="item in rzztOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -70,37 +52,28 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.skjyEnabled">授课教员</el-checkbox>
</template>
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" class="w-full" :disabled="!searchForm.skjyEnabled">
<el-form-item label="授课教员">
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" clearable class="w-full">
<el-option v-for="item in skjyOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxqbEnabled">培训期班</el-checkbox>
</template>
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable :disabled="!searchForm.pxqbEnabled" />
<el-form-item label="培训期班">
<el-input v-model="searchForm.pxqb" placeholder="请输入培训期班" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.changeTypeEnabled">改动情况</el-checkbox>
</template>
<div class="change-options" :class="{ disabled: !searchForm.changeTypeEnabled }">
<el-radio-group v-model="searchForm.changeType" :disabled="!searchForm.changeTypeEnabled">
<el-form-item label="改动情况">
<div class="change-options">
<el-radio-group v-model="searchForm.changeType">
<el-radio v-for="item in changeTypeOptions" :key="item.value" :label="item.value">{{ item.label
}}</el-radio>
</el-radio-group>
<el-checkbox-group v-model="searchForm.changeTypes" :disabled="!searchForm.changeTypeEnabled">
<el-checkbox-group v-model="searchForm.changeTypes">
<el-checkbox v-for="item in changeTypeList" :key="item.value" :label="item.value">{{ item.label
}}</el-checkbox>
</el-checkbox-group>
@@ -108,11 +81,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.rwlbEnabled">任务类别</el-checkbox>
</template>
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" class="w-full" :disabled="!searchForm.rwlbEnabled">
<el-form-item label="任务类别">
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" clearable class="w-full">
<el-option v-for="item in rwlbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -122,13 +92,13 @@
<div class="search-actions">
<div class="left-group">
<el-button type="primary" @click="handleDeleteSelected">删除所选</el-button>
<el-button type="danger" plain @click="handleDeleteSelected">删除所选</el-button>
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
<el-button type="primary" @click="handleRecheck">重新检查通报变更情况</el-button>
<el-button type="primary" @click="handleExport">导出到 Word</el-button>
<el-button type="primary" @click="handleRefresh">刷新</el-button>
<el-button type="success" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
<el-button type="primary" @click="handleDownloadTemplate">下载模板</el-button>
<el-button @click="handleRecheck">重新检查通报变更情况</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-upload2" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
</div>
<el-button type="primary" @click="handleSearch">查询</el-button>
</div>
@@ -165,7 +135,7 @@
<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="jxrzcjfs" label="创建方式" width="100" align="center" />
<el-table-column label="详细" width="80" align="center" fixed="right">
<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>
@@ -234,26 +204,16 @@ export default {
// ==================== 查询表单 ====================
searchForm: {
ksrq: '',
ksrqEnabled: false,
jsrq: '',
jsrqEnabled: false,
kcmc: '',
kcmcEnabled: false,
jys: '教研室',
jysEnabled: false,
jxff: '理论讲授',
jxffEnabled: false,
rzzt: '教务部门导入教员类',
rzztEnabled: false,
skjy: '教务处',
skjyEnabled: false,
jys: '',
jxff: '',
rzzt: '',
skjy: '',
pxqb: '',
pxqbEnabled: false,
changeType: '与教学计划一致',
changeTypeEnabled: false,
changeType: '',
changeTypes: [],
rwlb: '全军院校训练规划内任务',
rwlbEnabled: false
rwlb: ''
},
jysOptions: ['教研室', '防空兵系', '通信工程系', '装甲兵系'],
@@ -301,21 +261,21 @@ export default {
this.selectedRows = rows
},
/** 根据"勾选启用"模式构建实际查询参数 */
/** 根据已填写的查询值构建实际查询参数 */
buildSearchParams() {
const form = this.searchForm
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (form.ksrqEnabled && form.ksrq) params.ksrq = form.ksrq
if (form.jsrqEnabled && form.jsrq) params.jsrq = form.jsrq
if (form.kcmcEnabled && form.kcmc) params.kcmc = form.kcmc
if (form.jysEnabled && form.jys) params.jys = form.jys
if (form.jxffEnabled && form.jxff) params.jxff = form.jxff
if (form.rzztEnabled && form.rzzt) params.rzzt = form.rzzt
if (form.skjyEnabled && form.skjy) params.skjy = form.skjy
if (form.pxqbEnabled && form.pxqb) params.pxqb = form.pxqb
if (form.rwlbEnabled && form.rwlb) params.rwlb = form.rwlb
if (form.changeTypeEnabled && form.changeType) params.gdqk = form.changeType
if (form.changeTypeEnabled && form.changeTypes.length > 0) {
if (form.ksrq) params.ksrq = form.ksrq
if (form.jsrq) params.jsrq = form.jsrq
if (form.kcmc) params.kcmc = form.kcmc
if (form.jys) params.jys = form.jys
if (form.jxff) params.jxff = form.jxff
if (form.rzzt) params.rzzt = form.rzzt
if (form.skjy) params.skjy = form.skjy
if (form.pxqb) params.pxqb = form.pxqb
if (form.rwlb) params.rwlb = form.rwlb
if (form.changeType) params.gdqk = form.changeType
if (form.changeTypes.length > 0) {
params.gdxxqk = form.changeTypes.join(',')
}
return params
@@ -544,9 +504,6 @@ export default {
gap: 12px;
}
&.disabled {
opacity: 0.6;
}
}
}
@@ -5,42 +5,28 @@
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.startDateEnabled">开始日期</el-checkbox>
</template>
<el-form-item label="开始日期">
<el-date-picker v-model="searchForm.startDate" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
class="w-full" :disabled="!searchForm.startDateEnabled" />
class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.endDateEnabled">结束日期</el-checkbox>
</template>
<el-form-item label="结束日期">
<el-date-picker v-model="searchForm.endDate" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
class="w-full" :disabled="!searchForm.endDateEnabled" />
class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.courseNameEnabled">课程名称</el-checkbox>
</template>
<el-input v-model="searchForm.courseName" placeholder="请输入课程名称" clearable
:disabled="!searchForm.courseNameEnabled" />
<el-form-item label="课程名称">
<el-input v-model="searchForm.courseName" placeholder="请输入课程名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.teachMethodEnabled">教学方法</el-checkbox>
</template>
<el-select v-model="searchForm.teachMethod" placeholder="请选择教学方法" class="w-full"
:disabled="!searchForm.teachMethodEnabled">
<el-form-item label="教学方法">
<el-select v-model="searchForm.teachMethod" placeholder="请选择教学方法" clearable class="w-full">
<el-option v-for="item in teachMethodOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -49,22 +35,15 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.changeEnabled">改动情况</el-checkbox>
</template>
<el-select v-model="searchForm.change" placeholder="请选择改动情况" class="w-full" :disabled="!searchForm.changeEnabled">
<el-form-item label="改动情况">
<el-select v-model="searchForm.change" placeholder="请选择改动情况" clearable class="w-full">
<el-option v-for="item in changeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.taskCategoryEnabled">任务类别</el-checkbox>
</template>
<el-select v-model="searchForm.taskCategory" placeholder="请选择任务类别" class="w-full"
:disabled="!searchForm.taskCategoryEnabled">
<el-form-item label="任务类别">
<el-select v-model="searchForm.taskCategory" placeholder="请选择任务类别" clearable class="w-full">
<el-option v-for="item in taskCategoryOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -73,22 +52,15 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.deptEnabled">教研室</el-checkbox>
</template>
<el-select v-model="searchForm.dept" placeholder="请选择教研室" class="w-full" :disabled="!searchForm.deptEnabled">
<el-form-item label="教研室">
<el-select v-model="searchForm.dept" placeholder="请选择教研室" clearable class="w-full">
<el-option v-for="item in deptOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.teacherEnabled">授课教员</el-checkbox>
</template>
<el-select v-model="searchForm.teacher" placeholder="请选择授课教员" class="w-full"
:disabled="!searchForm.teacherEnabled">
<el-form-item label="授课教员">
<el-select v-model="searchForm.teacher" placeholder="请选择授课教员" clearable class="w-full">
<el-option v-for="item in teacherOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -97,12 +69,8 @@
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.classNameEnabled">培训班</el-checkbox>
</template>
<el-input v-model="searchForm.className" placeholder="请输入培训班" clearable
:disabled="!searchForm.classNameEnabled" />
<el-form-item label="培训班">
<el-input v-model="searchForm.className" placeholder="请输入培训班" clearable />
</el-form-item>
</el-col>
</el-row>
@@ -110,12 +78,12 @@
<div class="search-actions">
<div class="left-group">
<el-button type="primary" @click="handleDelete">删除所选</el-button>
<el-button type="danger" plain @click="handleDelete">删除所选</el-button>
<el-button type="primary" @click="handleAudit">审核通过所选</el-button>
<el-input v-model="remark" placeholder="退回原因/意见" clearable class="action-input" />
<el-button type="primary" @click="handleWithdraw">撤回重填所选</el-button>
<el-button type="primary" @click="handleRecheck">重新检查所选变更情况</el-button>
<el-button type="primary" @click="handleExport">导出到 Word</el-button>
<el-button type="warning" plain @click="handleWithdraw">撤回重填所选</el-button>
<el-button @click="handleRecheck">重新检查所选变更情况</el-button>
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
</div>
<div class="right-group">
<el-button @click="handleReset">重置</el-button>
@@ -152,7 +120,7 @@
<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="jxrzcjfs" label="创建方式" width="100" align="center" />
<el-table-column label="详细" width="80" align="center" fixed="right">
<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>
@@ -220,23 +188,14 @@ export default {
// ==================== 查询表单 ====================
searchForm: {
startDate: '',
startDateEnabled: false,
endDate: '',
endDateEnabled: false,
courseName: '',
courseNameEnabled: false,
teachMethod: '理论讲授',
teachMethodEnabled: false,
change: '与教学计划一致',
changeEnabled: false,
taskCategory: '全军院校训练规划内任务',
taskCategoryEnabled: false,
dept: '教务处',
deptEnabled: false,
teacher: '教务处',
teacherEnabled: false,
className: '',
classNameEnabled: false
teachMethod: '',
change: '',
taskCategory: '',
dept: '',
teacher: '',
className: ''
},
teachMethodOptions: ['理论讲授', '实践教学', '研讨教学', '案例教学', '实操演练'],
@@ -282,19 +241,19 @@ export default {
this.selectedRows = rows
},
/** 根据"勾选启用"模式构建实际查询参数 */
/** 根据已填写的查询值构建实际查询参数 */
buildSearchParams() {
const form = this.searchForm
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (form.startDateEnabled && form.startDate) params.ksrq = form.startDate
if (form.endDateEnabled && form.endDate) params.jsrq = form.endDate
if (form.courseNameEnabled && form.courseName) params.kcmc = form.courseName
if (form.teachMethodEnabled && form.teachMethod) params.jxff = form.teachMethod
if (form.changeEnabled && form.change) params.gdqk = form.change
if (form.taskCategoryEnabled && form.taskCategory) params.rwlb = form.taskCategory
if (form.deptEnabled && form.dept) params.jys = form.dept
if (form.teacherEnabled && form.teacher) params.skjy = form.teacher
if (form.classNameEnabled && form.className) params.pxqb = form.className
if (form.startDate) params.ksrq = form.startDate
if (form.endDate) params.jsrq = form.endDate
if (form.courseName) params.kcmc = form.courseName
if (form.teachMethod) params.jxff = form.teachMethod
if (form.change) params.gdqk = form.change
if (form.taskCategory) params.rwlb = form.taskCategory
if (form.dept) params.jys = form.dept
if (form.teacher) params.skjy = form.teacher
if (form.className) params.pxqb = form.className
return params
},
@@ -329,23 +288,14 @@ export default {
handleReset() {
this.searchForm = {
startDate: '',
startDateEnabled: false,
endDate: '',
endDateEnabled: false,
courseName: '',
courseNameEnabled: false,
teachMethod: '理论讲授',
teachMethodEnabled: false,
change: '与教学计划一致',
changeEnabled: false,
taskCategory: '全军院校训练规划内任务',
taskCategoryEnabled: false,
dept: '教务处',
deptEnabled: false,
teacher: '教务处',
teacherEnabled: false,
className: '',
classNameEnabled: false
teachMethod: '',
change: '',
taskCategory: '',
dept: '',
teacher: '',
className: ''
}
this.remark = ''
this.handleSearch()
+5 -5
View File
@@ -68,16 +68,16 @@
<div class="list-toolbar">
<div class="left-group">
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建教材信息</el-button>
<el-button type="primary" icon="el-icon-download" @click="handleDownloadList">下载教材列表</el-button>
<el-button type="primary" icon="el-icon-folder-opened" @click="handleDownloadZip">所选文件资料打包ZIP下载</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 class="right-group">
<el-button type="primary" icon="el-icon-download" @click="handleDownloadTemplate">教材数据文件模板下载</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">教材数据文件模板下载</el-button>
<div class="file-input">
<input ref="fileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button type="primary" plain icon="el-icon-upload" @click="handleChooseFile">选择文件</el-button>
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ fileName }}</span>
<el-button type="success" icon="el-icon-upload2" @click="handleImport">上传数据</el-button>
<el-button type="primary" icon="el-icon-upload2" @click="handleImport">上传数据</el-button>
</div>
</div>
</div>