forked from liweijie/education
Compare commits
57 Commits
09578b9b58
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 81ef173454 | |||
| b7110ccc9a | |||
| 72f0482427 | |||
| 449651dbd4 | |||
| f62b5e394c | |||
| 933af8ca13 | |||
| 4c9cfb8d7f | |||
| f32d19e411 | |||
| fa2832cf3b | |||
| d37c05aaad | |||
| ab3daeed65 | |||
| 7f853afff6 | |||
| 2183b33a7f | |||
| b34efc847d | |||
| be7b91ac20 | |||
| c2cc535d80 | |||
| 108d848b4b | |||
| 6e472028c8 | |||
| 1e852d03a9 | |||
| b93375ee7a | |||
| f2071455d1 | |||
| b33e6b4a52 | |||
| 5a81ce54b0 | |||
| 743ec5de14 | |||
| 06715c0767 | |||
| 18f538cca3 | |||
| 0de768b459 | |||
| 61d563489c | |||
| 2328a6c938 | |||
| 26072ffe4e | |||
| c8990ab1a4 | |||
| ffdcfaa350 | |||
| 90e03e1faf | |||
| 25f37a1d9a | |||
| 65b96c0732 | |||
| 2778604e99 | |||
| 42e692fc8c | |||
| 87335c4c51 | |||
| 2c684c3dbf | |||
| 0877228043 | |||
| f2aac5aa30 | |||
| 73c00286e7 | |||
| ed6dfd6059 | |||
| 91c2b7c0a7 | |||
| 6f59fa29e1 | |||
| 42fb981c8c | |||
| 2c4cc2aaaf | |||
| ebbbd5fb01 | |||
| e821ac7d97 | |||
| ca7d7b6869 | |||
| d0d871f7c6 | |||
| 4f9b543c74 | |||
| 9ae21acaf8 | |||
| f0b8ba57ff | |||
| 5b8c7b0e7b | |||
| cf04ac37df | |||
| 3443d24d42 |
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* 版权所有,内部使用。
|
||||
*
|
||||
* @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
|
||||
})
|
||||
@@ -67,3 +67,13 @@ export function exportJointTraining() {
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 下载联教联训导入模板(返回 Blob)
|
||||
// GET /download/joint-training
|
||||
export function downloadJointTrainingTemplate() {
|
||||
return request({
|
||||
url: '/download/joint-training',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
+22
-3
@@ -85,6 +85,24 @@ export function rejectTeachingLog(bh, thyj) {
|
||||
})
|
||||
}
|
||||
|
||||
// 批量上报教学日志,接口形态:POST /log/teaching-log/batch-report(body 为裸数组 ["",""])
|
||||
export function batchReportTeachingLog(bhList) {
|
||||
return request({
|
||||
url: '/log/teaching-log/batch-report',
|
||||
method: 'post',
|
||||
data: bhList
|
||||
})
|
||||
}
|
||||
|
||||
// 批量审核教学日志,接口形态:POST /log/teaching-log/batch-audit(body 为裸数组 ["",""])
|
||||
export function batchAuditTeachingLog(bhList) {
|
||||
return request({
|
||||
url: '/log/teaching-log/batch-audit',
|
||||
method: 'post',
|
||||
data: bhList
|
||||
})
|
||||
}
|
||||
|
||||
// 根据课表自动创建教学日志,接口形态:POST /log/teaching-log/auto-create?tybh=
|
||||
export function autoCreateTeachingLog(tybh) {
|
||||
return request({
|
||||
@@ -101,7 +119,8 @@ export function importTeachingLogExcel(file) {
|
||||
return request({
|
||||
url: '/log/teaching-log/import-excel',
|
||||
method: 'post',
|
||||
data: formData
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -115,10 +134,10 @@ export function exportTeachingLog(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 下载导入模板(返回 Blob),接口形态:GET /log/teaching-log/download-template
|
||||
// 下载导入模板(返回 Blob),接口形态:GET /download/teaching-log
|
||||
export function downloadTeachingLogTemplate() {
|
||||
return request({
|
||||
url: '/log/teaching-log/download-template',
|
||||
url: '/download/teaching-log',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
|
||||
@@ -39,15 +39,6 @@ export function getInfo() {
|
||||
})
|
||||
}
|
||||
|
||||
// 解锁屏幕
|
||||
export function unlockScreen(password) {
|
||||
return request({
|
||||
url: '/unlockscreen',
|
||||
method: 'post',
|
||||
data: { password }
|
||||
})
|
||||
}
|
||||
|
||||
// 退出方法
|
||||
export function logout() {
|
||||
return request({
|
||||
|
||||
@@ -21,7 +21,7 @@ export function getApplication(bh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 新增学籍异动申请(请求体 XYXJYDSQB 实体;bh 需前端传入,创建/修改时间后端自动维护)
|
||||
// 新增学籍异动申请(编号、状态和创建/修改时间由后端维护)
|
||||
export function addApplication(data) {
|
||||
return request({
|
||||
url: '/student-records/application/add',
|
||||
|
||||
@@ -45,7 +45,7 @@ export function delStudentRecord(bh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 分页查询学员课程成绩(xybh 学员编号必传;nd 年度可选)
|
||||
// 分页查询学员课程成绩(xybh/nd 查询)
|
||||
export function listStudentGrades(query) {
|
||||
return request({
|
||||
url: '/student-records/grades',
|
||||
@@ -54,7 +54,7 @@ export function listStudentGrades(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 分页查询学员课程过程成绩(xybh 学员编号必传;nd 年度可选)
|
||||
// 分页查询学员课程过程成绩(xybh/nd 查询)
|
||||
export function listStudentProcessGrades(query) {
|
||||
return request({
|
||||
url: '/student-records/process-grades',
|
||||
@@ -63,22 +63,22 @@ export function listStudentProcessGrades(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 导出学员课程成绩 Excel(xybh 学员编号必传)
|
||||
export function exportStudentGrades(xybh) {
|
||||
// 导出指定学员的课程成绩 Excel(xybh 必传)
|
||||
export function exportStudentGrades(query) {
|
||||
return request({
|
||||
url: '/student-records/export-grades',
|
||||
method: 'get',
|
||||
params: { xybh: xybh },
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出学员课程过程成绩 Excel(xybh 学员编号必传)
|
||||
export function exportStudentProcessGrades(xybh) {
|
||||
// 导出指定学员的课程过程成绩 Excel(xybh 必传)
|
||||
export function exportStudentProcessGrades(query) {
|
||||
return request({
|
||||
url: '/student-records/export-process-grades',
|
||||
method: 'get',
|
||||
params: { xybh: xybh },
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -21,6 +21,15 @@ export function getTeam(xydbh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 根据是否为主体培训任务查询可选培训任务
|
||||
export function listTrainingTasks(ztpxrw) {
|
||||
return request({
|
||||
url: '/student-records/training-tasks',
|
||||
method: 'get',
|
||||
params: { ztpxrw }
|
||||
})
|
||||
}
|
||||
|
||||
// 新增学员队(请求体 XYDB)
|
||||
export function addTeam(data) {
|
||||
return request({
|
||||
@@ -68,3 +77,12 @@ export function exportTeam(query) {
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 下载学员队导入模板
|
||||
export function downloadTeamTemplate() {
|
||||
return request({
|
||||
url: '/download/student-team',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,3 +93,15 @@ export function listClassroomByJxldh(jxldh) {
|
||||
params: { jxldh }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载教学场地导入模板(返回 Blob)
|
||||
* GET /download/classroom
|
||||
*/
|
||||
export function downloadClassroomTemplate() {
|
||||
return request({
|
||||
url: '/download/classroom',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,19 +48,11 @@ export function getSyllabus(bh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 查询所有(返回 List,无分页,前端本地分页)
|
||||
export function listSyllabus() {
|
||||
return request({
|
||||
url: '/zyjxjhb/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据专业代号和停用标识查询(zydh、ty 均必填,等值匹配)
|
||||
export function listSyllabusByZydhAndTy(zydh, ty) {
|
||||
// 根据专业代号和停用标识查询
|
||||
export function listByZydhAndTy(query) {
|
||||
return request({
|
||||
url: '/zyjxjhb/listByZydhAndTy',
|
||||
method: 'get',
|
||||
params: { zydh, ty }
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,3 +69,42 @@ export function listTraining(params) {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出培养方案到 Excel(返回 Blob)
|
||||
* GET /training/export
|
||||
*/
|
||||
export function exportTrainingProgram() {
|
||||
return request({
|
||||
url: '/training/export',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载培养方案导入模板(返回 Blob)
|
||||
* GET /download/training-program
|
||||
*/
|
||||
export function downloadTrainingProgramTemplate() {
|
||||
return request({
|
||||
url: '/download/training-program',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入培养方案 Excel(multipart,字段名 file)
|
||||
* POST /training/import
|
||||
*/
|
||||
export function importTrainingProgram(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request({
|
||||
url: '/training/import',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ export function getXqxlb(bh) {
|
||||
})
|
||||
}
|
||||
|
||||
// 预生成学期整张校历占位记录(首次进入新学期时调用,接口自带去重)
|
||||
export function addXqxlb(startDate, endDate) {
|
||||
return request({
|
||||
url: '/xqxlb/add',
|
||||
method: 'post',
|
||||
params: { startDate: startDate, endDate: endDate }
|
||||
})
|
||||
}
|
||||
|
||||
// 新增 / 修改校历假期(无 bh 时为新增,带 bh 时为修改)
|
||||
export function updateXqxlb(data) {
|
||||
return request({
|
||||
|
||||
@@ -10,6 +10,15 @@ export function listElective(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 新建选修课(body: ElectiveCourseSaveDTO)
|
||||
export function addElective(data) {
|
||||
return request({
|
||||
url: '/jys/elective/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 批量开放报名
|
||||
export function batchOpenElective(bhList) {
|
||||
return request({
|
||||
|
||||
@@ -45,3 +45,13 @@ export function updateKb(data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 下载课程科目导入模板(返回 Blob)
|
||||
// GET /download/course-subject
|
||||
export function downloadCourseSubjectTemplate() {
|
||||
return request({
|
||||
url: '/download/course-subject',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -35,3 +35,12 @@ export function disableOffice(jysdh) {
|
||||
params: { jysdh: jysdh }
|
||||
})
|
||||
}
|
||||
|
||||
// 下载教研室导入模板
|
||||
export function downloadOfficeTemplate() {
|
||||
return request({
|
||||
url: '/download/jys',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ export function deleteTeachingMaterial(id) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 教材导入模板下载 GET /teachingMaterial/template */
|
||||
/** 教材导入模板下载 GET /download/teaching-material */
|
||||
export function downloadTeachingMaterialTemplate() {
|
||||
return request({
|
||||
url: '/teachingMaterial/template',
|
||||
url: '/download/teaching-material',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
|
||||
|
Before Width: | Height: | Size: 444 B |
@@ -98,6 +98,15 @@
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
// 教学管理系统的列表字段统一居中,避免各业务页面单独声明后出现视觉错位。
|
||||
.el-table__cell,
|
||||
.el-table__cell.is-left,
|
||||
.el-table__cell.is-right,
|
||||
.el-table__cell.is-center,
|
||||
.el-table__cell > .cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
|
||||
th {
|
||||
word-break: break-word;
|
||||
@@ -129,6 +138,17 @@
|
||||
.el-table__fixed-body-wrapper.is-dragging {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// 横向滚动条被隐藏后,Element UI 仍会为右侧固定列预留滚动条高度。
|
||||
// 仅在宽表格发生横向溢出时补齐该高度,保证“操作”列与普通数据行底部对齐。
|
||||
&.el-table--scrollable-x .el-table__fixed-right {
|
||||
height: 100% !important; // 组件通过行内样式写入高度,只能在此处覆盖。
|
||||
}
|
||||
|
||||
&.el-table--scrollable-x .el-table__fixed-right .el-table__fixed-body-wrapper {
|
||||
bottom: 0;
|
||||
height: auto !important; // 组件通过行内样式写入高度,只能在此处覆盖。
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格横拖期间锁定整页光标与文本选中:
|
||||
|
||||
@@ -19,7 +19,7 @@ $base-menu-light-background:#ffffff;
|
||||
$base-sub-menu-background: #084c3f;
|
||||
$base-sub-menu-hover: rgba(255,255,255,.10);
|
||||
|
||||
$base-sidebar-width: 200px;
|
||||
$base-sidebar-width: 240px;
|
||||
|
||||
// the :export directive is the magic sauce for webpack
|
||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover">
|
||||
<div class="notice-header">
|
||||
<span class="notice-title">通知公告</span>
|
||||
<span class="notice-mark-all" @click="markAllRead">全部已读</span>
|
||||
<span v-show="false" class="notice-mark-all" @click="markAllRead">全部已读</span>
|
||||
</div>
|
||||
<div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div>
|
||||
<div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div>
|
||||
|
||||
@@ -33,9 +33,6 @@
|
||||
<router-link to="/user/profile">
|
||||
<el-dropdown-item icon="el-icon-user">个人中心</el-dropdown-item>
|
||||
</router-link>
|
||||
<el-dropdown-item icon="el-icon-lock" @click.native="lockScreen">
|
||||
<span>锁定屏幕</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided icon="el-icon-switch-button" @click.native="logout">
|
||||
<span>退出登录</span>
|
||||
</el-dropdown-item>
|
||||
@@ -104,12 +101,6 @@ export default {
|
||||
this.$refs.searchRef.click()
|
||||
}
|
||||
},
|
||||
lockScreen() {
|
||||
const currentPath = this.$route.fullPath
|
||||
this.$store.dispatch('lock/lockScreen', currentPath).then(() => {
|
||||
this.$router.push('/lock')
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
this.$confirm('确定注销并退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
||||
@@ -17,44 +17,19 @@
|
||||
<span class="week">{{ weekText }}</span>
|
||||
</div>
|
||||
|
||||
<span class="semester-bar__divider" />
|
||||
|
||||
<!-- 右:学期切换下拉框 -->
|
||||
<div class="semester-bar__item semester-bar__switch">
|
||||
<span class="label">切换学期</span>
|
||||
<div class="semester-select-wrap">
|
||||
<el-select
|
||||
:value="activeSemester ? activeSemester.nd : ''"
|
||||
placeholder="请选择学期"
|
||||
size="small"
|
||||
class="semester-select"
|
||||
@change="handleSwitch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in semesterList"
|
||||
:key="item.nd"
|
||||
:label="getSemesterName(item.nd)"
|
||||
:value="item.nd"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSemester, updateSemester } from '@/api/teachBusiness/semester'
|
||||
import { listSemester } from '@/api/teachBusiness/semester'
|
||||
|
||||
export default {
|
||||
name: 'SemesterBar',
|
||||
data() {
|
||||
return {
|
||||
// 学期列表(接口数据)
|
||||
semesterList: [],
|
||||
// 当前选中的学期对象
|
||||
activeSemester: null,
|
||||
today: new Date(),
|
||||
loading: false
|
||||
today: new Date()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -83,19 +58,15 @@ export default {
|
||||
this.getSemesterList()
|
||||
},
|
||||
methods: {
|
||||
/** 加载学期列表,默认选中当前学期(dqxq=true),无当前学期则选第一条 */
|
||||
/** 加载当前学期(dqxq=true),无当前学期则取第一条 */
|
||||
getSemesterList() {
|
||||
this.loading = true
|
||||
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||
const data = response.data || {}
|
||||
const list = data.records || []
|
||||
this.semesterList = list
|
||||
const current = list.find(i => i.dqxq)
|
||||
this.activeSemester = current || (list[0] ? list[0] : null)
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.semesterList = []
|
||||
this.loading = false
|
||||
this.activeSemester = null
|
||||
})
|
||||
},
|
||||
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
|
||||
@@ -111,32 +82,6 @@ export default {
|
||||
const xq = s.slice(4)
|
||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||
},
|
||||
/** 切换学期:将目标学期设为当前学期,原当前学期取消 */
|
||||
handleSwitch(nd) {
|
||||
const current = this.activeSemester
|
||||
if (!nd || (current && nd === current.nd)) return
|
||||
const target = this.semesterList.find(i => i.nd === nd)
|
||||
if (!target) return
|
||||
// 当前已是"当前学期"的其他记录(需取消)
|
||||
const others = this.semesterList.filter(i => i.dqxq && i.nd !== nd)
|
||||
|
||||
updateSemester({ ...target, dqxq: true }).then(() => {
|
||||
// 若存在原当前学期,一并取消
|
||||
if (others.length) {
|
||||
return Promise.all(others.map(o => updateSemester({ ...o, dqxq: false })))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}).then(() => {
|
||||
this.activeSemester = target
|
||||
this.$message.success(`已切换至:${this.getSemesterName(nd)}`)
|
||||
// 重新拉取,同步最新 dqxq 状态
|
||||
this.getSemesterList()
|
||||
// 通知学期管理页面自动刷新
|
||||
this.$root.$emit('semester-current-changed')
|
||||
}).catch(() => {
|
||||
this.$message.error('切换学期失败,请稍后重试')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,54 +165,5 @@ export default {
|
||||
&__divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__switch {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
height: 36px;
|
||||
|
||||
/* 用一层定高 flex 容器包裹 select,彻底隔离其默认行高外溢 */
|
||||
.semester-select-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.semester-select {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
/* 关闭 select/input 外层默认行高,交给 flex 居中 */
|
||||
::v-deep .el-select,
|
||||
::v-deep .el-input {
|
||||
height: 36px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
color: #fff;
|
||||
border-radius: 7px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner:hover {
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
::v-deep .el-input__icon {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,19 +18,12 @@ const isWhiteList = (path) => {
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
if (getToken()) {
|
||||
const isLock = store.getters.isLock
|
||||
/* has token*/
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else if (isWhiteList(to.path)) {
|
||||
next()
|
||||
} else if (isLock && to.path !== '/lock') {
|
||||
next({ path: '/lock' })
|
||||
NProgress.done()
|
||||
} else if (!isLock && to.path === '/lock') {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else {
|
||||
if (store.getters.roles.length === 0) {
|
||||
isRelogin.show = true
|
||||
|
||||
@@ -74,12 +74,6 @@ export const constantRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/lock',
|
||||
component: () => import('@/views/lock'),
|
||||
hidden: true,
|
||||
meta: { title: '锁定屏幕' }
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: Layout,
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
size: state => state.app.size,
|
||||
device: state => state.app.device,
|
||||
dict: state => state.dict.dict,
|
||||
isLock: state => state.lock.isLock,
|
||||
lockPath: state => state.lock.lockPath,
|
||||
visitedViews: state => state.tagsView.visitedViews,
|
||||
cachedViews: state => state.tagsView.cachedViews,
|
||||
token: state => state.user.token,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import app from './modules/app'
|
||||
import lock from './modules/lock'
|
||||
import dict from './modules/dict'
|
||||
import user from './modules/user'
|
||||
import tagsView from './modules/tagsView'
|
||||
@@ -13,7 +12,6 @@ Vue.use(Vuex)
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
app,
|
||||
lock,
|
||||
dict,
|
||||
user,
|
||||
tagsView,
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
const LOCK_KEY = 'screen-lock'
|
||||
const LOCK_PATH_KEY = 'screen-lock-path'
|
||||
|
||||
const lock = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
isLock: JSON.parse(localStorage.getItem(LOCK_KEY) || 'false'),
|
||||
lockPath: localStorage.getItem(LOCK_PATH_KEY) || '/index'
|
||||
},
|
||||
mutations: {
|
||||
SET_LOCK(state, status) {
|
||||
state.isLock = status
|
||||
localStorage.setItem(LOCK_KEY, JSON.stringify(status))
|
||||
},
|
||||
SET_LOCK_PATH(state, path) {
|
||||
state.lockPath = path
|
||||
localStorage.setItem(LOCK_PATH_KEY, path)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 锁定屏幕,同时记录当前路径
|
||||
lockScreen({ commit }, currentPath) {
|
||||
commit('SET_LOCK_PATH', currentPath || '/index')
|
||||
commit('SET_LOCK', true)
|
||||
},
|
||||
// 解锁屏幕,清除路径
|
||||
unlockScreen({ commit }) {
|
||||
commit('SET_LOCK', false)
|
||||
commit('SET_LOCK_PATH', '/index')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default lock
|
||||
@@ -53,7 +53,6 @@ const user = {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
store.dispatch('lock/unlockScreen')
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<el-table-column prop="xmlx" label="项目类型" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="230" 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>
|
||||
@@ -187,7 +187,8 @@ import {
|
||||
deleteJointTraining,
|
||||
getJointTraining,
|
||||
importJointTraining,
|
||||
exportJointTraining
|
||||
exportJointTraining,
|
||||
downloadJointTrainingTemplate
|
||||
} from '@/api/classHour/jointTraining'
|
||||
|
||||
export default {
|
||||
@@ -433,8 +434,10 @@ export default {
|
||||
},
|
||||
|
||||
handleDownloadTemplate() {
|
||||
// 后端暂未提供模板下载接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
downloadJointTrainingTemplate().then(blob => {
|
||||
saveAs(blob, '联教连训管理.xls')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,15 @@
|
||||
<!-- ==================== 2. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<!-- 课时费方案 -->
|
||||
<el-table v-if="activeTab === 'fee'" v-loading="feeState.loading" :data="feeState.list" border stripe style="width: 100%">
|
||||
<el-table
|
||||
v-if="activeTab === 'fee'"
|
||||
v-loading="feeState.loading"
|
||||
:data="feeState.list"
|
||||
border
|
||||
stripe
|
||||
class="plan-data-table"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||
@@ -55,17 +63,27 @@
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.jxbjgbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-actions">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 课时方案 -->
|
||||
<el-table v-else v-loading="coeffState.loading" :data="coeffState.list" border stripe style="width: 100%">
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="coeffState.loading"
|
||||
:data="coeffState.list"
|
||||
border
|
||||
stripe
|
||||
class="plan-data-table"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
|
||||
@@ -100,10 +118,12 @@
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.hbxssx) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-actions">
|
||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -654,6 +674,13 @@ export default {
|
||||
|
||||
// ==================== 2. 数据表格 ====================
|
||||
.table-card {
|
||||
.table-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="clksfbz" label="超量课时费单价" width="110" align="right" header-align="center">
|
||||
<el-table-column prop="clksfbz" label="超量课时费单价" width="120" align="right" header-align="center">
|
||||
<template slot-scope="{ row }">{{ fmtValue(row.clksfbz) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<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>
|
||||
<el-button type="text" class="more-btn" @click="handleMore('notice')">更多 <i class="el-icon-arrow-right"></i></el-button>
|
||||
<el-button v-show="false" type="text" class="more-btn" @click="handleMore('notice')">更多 <i class="el-icon-arrow-right"></i></el-button>
|
||||
</div>
|
||||
<div class="card-body notice-body" v-loading="noticeLoading">
|
||||
<div v-if="noticeList.length === 0" class="empty-state mini-empty">
|
||||
@@ -28,18 +28,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<el-pagination
|
||||
@size-change="handleNoticeSizeChange"
|
||||
@current-change="handleNoticeCurrentChange"
|
||||
:current-page="noticePage.currentPage"
|
||||
:page-sizes="[5, 10, 20, 50]"
|
||||
:page-size="noticePage.pageSize"
|
||||
:total="noticePage.total"
|
||||
layout="total, prev, pager, next"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -49,7 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listNotice } from "@/api/system/notice"
|
||||
import { listNoticeTop } from "@/api/system/notice"
|
||||
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
|
||||
|
||||
export default {
|
||||
@@ -59,12 +47,7 @@ export default {
|
||||
return {
|
||||
version: "3.9.2",
|
||||
noticeList: [],
|
||||
noticeLoading: false,
|
||||
noticePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
}
|
||||
noticeLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -104,12 +87,8 @@ export default {
|
||||
},
|
||||
loadNoticeList() {
|
||||
this.noticeLoading = true
|
||||
listNotice({
|
||||
pageNum: this.noticePage.currentPage,
|
||||
pageSize: this.noticePage.pageSize
|
||||
}).then(response => {
|
||||
this.noticeList = response.rows || []
|
||||
this.noticePage.total = response.total || 0
|
||||
listNoticeTop().then(response => {
|
||||
this.noticeList = response.data || []
|
||||
this.noticeLoading = false
|
||||
}).catch(() => {
|
||||
this.noticeLoading = false
|
||||
@@ -117,15 +96,6 @@ export default {
|
||||
},
|
||||
handleViewNotice(item) {
|
||||
this.$refs.noticeViewRef.open(item)
|
||||
},
|
||||
handleNoticeSizeChange(val) {
|
||||
this.noticePage.pageSize = val
|
||||
this.noticePage.currentPage = 1
|
||||
this.loadNoticeList()
|
||||
},
|
||||
handleNoticeCurrentChange(val) {
|
||||
this.noticePage.currentPage = val
|
||||
this.loadNoticeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
<template>
|
||||
<div class="lock-container">
|
||||
<!-- 动态粒子背景 -->
|
||||
<canvas ref="particleCanvas" class="particle-bg"></canvas>
|
||||
|
||||
<!-- 时钟 -->
|
||||
<div class="lock-time">{{ currentTime }}</div>
|
||||
<div class="lock-date">{{ currentDate }}</div>
|
||||
|
||||
<!-- 锁屏卡片 -->
|
||||
<div class="lock-card">
|
||||
<div class="avatar-wrap">
|
||||
<img :src="avatar" class="lock-avatar" @error="onAvatarError" />
|
||||
<div class="lock-icon">🔒</div>
|
||||
</div>
|
||||
<div class="lock-username">{{ nickName }}</div>
|
||||
<div class="lock-hint">系统已锁定,请输入密码解锁</div>
|
||||
|
||||
<div class="input-wrap" :class="{ shake: isShaking }">
|
||||
<input ref="passwordInput" v-model="password" type="password" placeholder="请输入登录密码" class="lock-input" @keydown.enter="handleUnlock" autocomplete="off" />
|
||||
<button class="unlock-btn" @click="handleUnlock" :disabled="loading">
|
||||
<span v-if="!loading">→</span>
|
||||
<span v-else class="loading-dot">···</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMsg" class="error-msg">{{ errorMsg }}</div>
|
||||
|
||||
<div class="lock-footer">
|
||||
<a href="/login" @click.prevent="goLogin">退出重新登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { unlockScreen } from '@/api/login'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
|
||||
export default {
|
||||
name: 'LockScreen',
|
||||
data() {
|
||||
return {
|
||||
password: '',
|
||||
loading: false,
|
||||
errorMsg: '',
|
||||
isShaking: false,
|
||||
currentTime: '',
|
||||
currentDate: '',
|
||||
timer: null,
|
||||
animationId: null,
|
||||
particles: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['avatar', 'nickName'])
|
||||
},
|
||||
mounted() {
|
||||
this.startClock()
|
||||
this.initParticles()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
cancelAnimationFrame(this.animationId)
|
||||
},
|
||||
methods: {
|
||||
onAvatarError(e) {
|
||||
e.target.src = defAva
|
||||
},
|
||||
startClock() {
|
||||
const update = () => {
|
||||
const now = new Date()
|
||||
const h = String(now.getHours()).padStart(2, '0')
|
||||
const m = String(now.getMinutes()).padStart(2, '0')
|
||||
const s = String(now.getSeconds()).padStart(2, '0')
|
||||
this.currentTime = `${h}:${m}:${s}`
|
||||
const days = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
|
||||
this.currentDate = `${now.getFullYear()}年${now.getMonth()+1}月${now.getDate()}日 ${days[now.getDay()]}`
|
||||
}
|
||||
update()
|
||||
this.timer = setInterval(update, 1000)
|
||||
},
|
||||
async handleUnlock() {
|
||||
if (!this.password) {
|
||||
this.showError('请输入密码')
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMsg = ''
|
||||
try {
|
||||
await unlockScreen(this.password)
|
||||
const lockPath = this.$store.getters.lockPath // 取锁屏前的路径
|
||||
await this.$store.dispatch('lock/unlockScreen')
|
||||
this.$router.replace(lockPath)
|
||||
} catch (err) {
|
||||
const msg = err.message || err.toString()
|
||||
this.showError(msg)
|
||||
this.password = ''
|
||||
this.$refs.passwordInput && this.$refs.passwordInput.focus()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
showError(msg) {
|
||||
this.errorMsg = msg
|
||||
this.isShaking = true
|
||||
setTimeout(() => { this.isShaking = false }, 600)
|
||||
},
|
||||
goLogin() {
|
||||
this.$store.dispatch('lock/unlockScreen')
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push('/login')
|
||||
})
|
||||
},
|
||||
// 粒子背景
|
||||
initParticles() {
|
||||
const canvas = this.$refs.particleCanvas
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
}
|
||||
resize()
|
||||
window.addEventListener('resize', resize)
|
||||
const count = 80
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
r: Math.random() * 2 + 1,
|
||||
dx: (Math.random() - 0.5) * 0.6,
|
||||
dy: (Math.random() - 0.5) * 0.6,
|
||||
alpha: Math.random() * 0.5 + 0.2
|
||||
})
|
||||
}
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
this.particles.forEach(p => {
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2)
|
||||
ctx.fillStyle = `rgba(255,255,255,${p.alpha})`
|
||||
ctx.fill()
|
||||
p.x += p.dx
|
||||
p.y += p.dy
|
||||
if (p.x < 0 || p.x > canvas.width) p.dx *= -1
|
||||
if (p.y < 0 || p.y > canvas.height) p.dy *= -1
|
||||
})
|
||||
// 连线
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
for (let j = i + 1; j < this.particles.length; j++) {
|
||||
const a = this.particles[i], b = this.particles[j]
|
||||
const dist = Math.hypot(a.x - b.x, a.y - b.y)
|
||||
if (dist < 120) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(a.x, a.y)
|
||||
ctx.lineTo(b.x, b.y)
|
||||
ctx.strokeStyle = `rgba(255,255,255,${0.15 * (1 - dist / 120)})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.animationId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lock-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.particle-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.lock-time {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 72px;
|
||||
font-weight: 200;
|
||||
color: #fff;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 0 0 40px rgba(255,255,255,0.3);
|
||||
margin-bottom: 8px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.lock-date {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 15px;
|
||||
color: rgba(255,255,255,0.6);
|
||||
margin-bottom: 48px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.lock-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 24px;
|
||||
padding: 40px 48px;
|
||||
width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 25px 60px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.avatar-wrap {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.lock-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid rgba(255,255,255,0.3);
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.lock-username {
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.lock-hint {
|
||||
color: rgba(255,255,255,0.5);
|
||||
font-size: 13px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
border-radius: 50px;
|
||||
padding: 4px 4px 4px 20px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-wrap:focus-within {
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
background: rgba(255,255,255,0.13);
|
||||
}
|
||||
|
||||
.input-wrap.shake {
|
||||
animation: shake 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20% { transform: translateX(-8px); }
|
||||
40% { transform: translateX(8px); }
|
||||
60% { transform: translateX(-6px); }
|
||||
80% { transform: translateX(6px); }
|
||||
}
|
||||
|
||||
.lock-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.lock-input::placeholder {
|
||||
color: rgba(255,255,255,0.35);
|
||||
}
|
||||
|
||||
.unlock-btn {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unlock-btn:hover:not(:disabled) {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.unlock-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
font-size: 13px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
margin-top: 14px;
|
||||
color: #ff7675;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.lock-footer {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.lock-footer a {
|
||||
color: rgba(255,255,255,0.4);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.lock-footer a:hover {
|
||||
color: rgba(255,255,255,0.8);
|
||||
}
|
||||
</style>
|
||||
+324
-55
@@ -1,48 +1,67 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<el-form-item prop="username">
|
||||
<div class="login-card">
|
||||
<aside class="brand-panel">
|
||||
<div class="brand-main">
|
||||
<div class="brand-mark" aria-hidden="true">教</div>
|
||||
<h1 class="brand-title">{{ title }}</h1>
|
||||
<p class="brand-tagline">开课计划 · 课表编排 · 成绩管理 · 教学分析</p>
|
||||
<div class="brand-illustration" aria-hidden="true">
|
||||
<svg-icon icon-class="education" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="brand-footnote">仅限授权单位内部使用</p>
|
||||
</aside>
|
||||
|
||||
<section class="form-panel">
|
||||
<div class="form-heading">
|
||||
<h2>用户登录</h2>
|
||||
<p>请使用内网账号进入系统</p>
|
||||
</div>
|
||||
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" label-position="top" @submit.native.prevent>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<div class="form-extra">
|
||||
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||
<router-link v-if="register" class="register-link" to="/register">立即注册</router-link>
|
||||
</div>
|
||||
<el-form-item class="login-action">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
class="login-btn"
|
||||
native-type="submit"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right;" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'">立即注册</router-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>{{ footerContent }}</span>
|
||||
|
||||
<p class="access-hint">登录后按账号权限进入对应业务,请妥善保管账号与口令。</p>
|
||||
<div class="role-tags" aria-label="系统角色">
|
||||
<span v-for="role in roles" :key="role.name" class="role-tag" :class="role.tone">{{ role.name }}</span>
|
||||
</div>
|
||||
<p class="form-footer">{{ footerContent }}</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -56,7 +75,14 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
title: process.env.VUE_APP_TITLE,
|
||||
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
|
||||
footerContent: "Copyright © 2018-2026 教学管理信息系统 内部使用",
|
||||
roles: [
|
||||
{ name: "系统管理员", tone: "admin" },
|
||||
{ name: "教务人员", tone: "affairs" },
|
||||
{ name: "教研室", tone: "office" },
|
||||
{ name: "教员", tone: "teacher" },
|
||||
{ name: "学员", tone: "student" }
|
||||
],
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
@@ -71,7 +97,6 @@ export default {
|
||||
]
|
||||
},
|
||||
loading: false,
|
||||
// 注册开关
|
||||
register: false,
|
||||
redirect: undefined
|
||||
}
|
||||
@@ -132,49 +157,293 @@ export default {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
min-height: 100%;
|
||||
padding: 28px 20px;
|
||||
background:
|
||||
radial-gradient(circle at 18% 16%, rgba(11, 107, 85, 0.28), transparent 42%),
|
||||
radial-gradient(circle at 86% 82%, rgba(198, 155, 60, 0.10), transparent 36%),
|
||||
#10241f;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
|
||||
.login-card {
|
||||
display: flex;
|
||||
width: min(1080px, 100%);
|
||||
overflow: hidden;
|
||||
background: linear-gradient(160deg, #0c7a62 0%, #0b6b55 46%, #084c3f 100%);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 22px 56px rgba(8, 36, 30, 0.32);
|
||||
}
|
||||
|
||||
.brand-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 42%;
|
||||
padding: 48px 40px 24px;
|
||||
color: #ffffff;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brand-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
left: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #7a5410;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
background: #e4c56a;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 10px rgba(16, 36, 28, 0.18);
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.brand-tagline {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.brand-illustration {
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
margin-top: 44px;
|
||||
color: rgba(255, 255, 255, 0.12);
|
||||
font-size: 116px;
|
||||
line-height: 1;
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
|
||||
.brand-illustration .svg-icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.brand-footnote {
|
||||
margin: 16px 0 0;
|
||||
color: rgba(255, 255, 255, 0.52);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 56px 56px 28px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.form-heading {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.form-heading h2 {
|
||||
margin: 0 0 8px;
|
||||
color: #172b25;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.form-heading p {
|
||||
margin: 0;
|
||||
color: #7a8a85;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
float: none;
|
||||
display: block;
|
||||
padding: 0 0 8px;
|
||||
line-height: 1.2;
|
||||
color: #172b25;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content {
|
||||
margin-left: 0 !important;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
color: #172b25;
|
||||
background: #f3f6f5;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner:hover {
|
||||
border-color: #c8d8d2;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner:focus {
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
z-index: 1;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
border-color: #0b6b55;
|
||||
box-shadow: 0 0 0 3px rgba(11, 107, 85, 0.10);
|
||||
}
|
||||
|
||||
::v-deep .el-input__suffix {
|
||||
right: 10px;
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
|
||||
::v-deep .el-form-item__error {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
|
||||
.form-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: -6px 0 18px;
|
||||
|
||||
::v-deep .el-checkbox__label {
|
||||
color: #52645f;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
.register-link {
|
||||
color: #0b6b55;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.login-action {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.access-hint {
|
||||
margin: 18px 0 16px;
|
||||
color: #7a8a85;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.role-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.role-tag {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.6;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.role-tag.admin {
|
||||
color: #3d6ea8;
|
||||
background: #e8f1fb;
|
||||
}
|
||||
|
||||
.role-tag.affairs {
|
||||
color: #0b6b55;
|
||||
background: #e8f3ef;
|
||||
}
|
||||
|
||||
.role-tag.office {
|
||||
color: #b77a17;
|
||||
background: #f8f1e4;
|
||||
}
|
||||
|
||||
.role-tag.teacher {
|
||||
color: #6b5b93;
|
||||
background: #f0eaf6;
|
||||
}
|
||||
|
||||
.role-tag.student {
|
||||
color: #9a7424;
|
||||
background: #f7f0de;
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 28px;
|
||||
color: #9aa7a3;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.login {
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.brand-panel {
|
||||
width: 100%;
|
||||
padding: 28px 24px 20px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
position: static;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.brand-illustration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-panel {
|
||||
padding: 32px 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.form-heading h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,12 +74,6 @@
|
||||
<!-- ==================== 2. 操作按钮区 ==================== -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="action-bar">
|
||||
<div class="bar-left">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">
|
||||
【课程表数据文件模板】下载
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="bar-center">
|
||||
<div class="file-area">
|
||||
<input ref="fileInputRef" type="file" accept=".xlsx,.xls" style="display: none" @change="handleFileChange" />
|
||||
@@ -89,10 +83,7 @@
|
||||
</div>
|
||||
|
||||
<div class="bar-right">
|
||||
<el-button type="primary" @click="handleImportToSystem" :loading="importing">缓存数据导入到系统</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>
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="handleImport" :loading="importing">导入到系统</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -153,7 +144,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTeachingPlan, importTeachingPlan, downloadTeachingPlanTemplate } from '@/api/schedule/teachingPlan'
|
||||
import { listTeachingPlan, importTeachingPlan } from '@/api/schedule/teachingPlan'
|
||||
|
||||
export default {
|
||||
name: 'PlanImportIndex',
|
||||
@@ -257,14 +248,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 下载模板 ====================
|
||||
handleDownloadTemplate() {
|
||||
downloadTeachingPlanTemplate().then(blob => {
|
||||
this.downloadBlob(blob, '教学实施计划导入模板.xlsx')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 通用 Blob 下载 ====================
|
||||
downloadBlob(blob, fileName) {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
@@ -277,7 +261,7 @@ export default {
|
||||
},
|
||||
|
||||
// ==================== 导入到系统 ====================
|
||||
handleImportToSystem() {
|
||||
handleImport() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
@@ -300,18 +284,6 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 缓存操作(后端暂未提供) ====================
|
||||
handleUploadToCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleCheckCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleDeleteCache() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,12 +3,22 @@
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="queryForm" label-width="90px" inline class="search-form">
|
||||
<el-form-item label="学员编号">
|
||||
<el-input v-model="queryForm.xybh" placeholder="请输入学员编号" clearable style="width: 200px" />
|
||||
<el-form-item v-if="!isStudentRole" label="学号">
|
||||
<el-input v-model="queryForm.xh" placeholder="请输入学号" clearable style="width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-input v-model="queryForm.nd" placeholder="如 2026" clearable style="width: 140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="课程名称">
|
||||
<el-select v-model="queryForm.kcbh" placeholder="请选择课程名称" clearable filterable style="width: 200px">
|
||||
<el-option v-for="item in kbOptions" :key="item.kbh" :label="item.kmc" :value="item.kbh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isStudentRole" label="班次">
|
||||
<el-select v-model="queryForm.xydbh" placeholder="请选择班次" clearable filterable style="width: 200px">
|
||||
<el-option v-for="item in teamOptions" :key="item.xydbh" :label="item.xydmc" :value="item.xydbh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
</el-form-item>
|
||||
@@ -27,7 +37,6 @@
|
||||
<el-tab-pane label="课程成绩" name="grades">
|
||||
<el-table v-loading="loading" :data="gradesData" stripe border style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
|
||||
<el-table-column prop="kscj" label="考试成绩" min-width="90" align="center" />
|
||||
@@ -37,7 +46,7 @@
|
||||
<el-table-column prop="bkcs" label="补考次数" min-width="90" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" min-width="90" align="center" />
|
||||
<el-table-column prop="qwxf" label="期望学分" min-width="90" align="center" />
|
||||
<el-table-column prop="ytg" label="已通过" min-width="80" align="center" />
|
||||
<el-table-column prop="ytg" label="考试结果" min-width="90" align="center" :formatter="formatExamResult" />
|
||||
<el-table-column prop="nd" label="年度" min-width="80" align="center" />
|
||||
</el-table>
|
||||
<el-empty v-if="!loading && gradesData.length === 0" description="暂无课程成绩数据" :image-size="60" />
|
||||
@@ -47,7 +56,6 @@
|
||||
<el-tab-pane label="课程过程成绩" name="process">
|
||||
<el-table v-loading="loading" :data="processGradesData" stripe border style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
|
||||
<el-table-column prop="yscj" label="原始成绩" min-width="90" align="center" />
|
||||
@@ -78,6 +86,8 @@ import {
|
||||
exportStudentGrades,
|
||||
exportStudentProcessGrades
|
||||
} from "@/api/studentRecords/studentRecords"
|
||||
import { listKb } from "@/api/teachOffice/kb"
|
||||
import { listTeam } from "@/api/studentRecords/team"
|
||||
|
||||
export default {
|
||||
name: 'ScoreIndex',
|
||||
@@ -85,10 +95,16 @@ export default {
|
||||
return {
|
||||
// ==================== 查询条件 ====================
|
||||
queryForm: {
|
||||
xybh: '',
|
||||
nd: ''
|
||||
xh: '',
|
||||
nd: '',
|
||||
kcbh: '',
|
||||
xydbh: ''
|
||||
},
|
||||
|
||||
// ==================== 下拉选项 ====================
|
||||
kbOptions: [],
|
||||
teamOptions: [],
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
// 数据来源:grades 课程成绩 / process 课程过程成绩
|
||||
activeTab: 'grades',
|
||||
@@ -106,16 +122,56 @@ export default {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 学员角色(后端角色键 STUDENT)隐藏学号/班次查询,仅查看本人成绩 */
|
||||
isStudentRole() {
|
||||
const roles = this.$store.getters.roles
|
||||
return Array.isArray(roles) && roles.includes('STUDENT')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadKbOptions()
|
||||
this.loadTeamOptions()
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/** 格式化考试结果:1 已通过,0 不通过 */
|
||||
formatExamResult(row, column, cellValue) {
|
||||
if (cellValue === 1 || cellValue === '1') return '已通过'
|
||||
if (cellValue === 0 || cellValue === '0') return '不通过'
|
||||
return ''
|
||||
},
|
||||
|
||||
/** 加载课程名称下拉选项 */
|
||||
loadKbOptions() {
|
||||
listKb({ pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
const data = res.data || {}
|
||||
this.kbOptions = data.records || []
|
||||
}).catch(() => {
|
||||
this.kbOptions = []
|
||||
})
|
||||
},
|
||||
|
||||
/** 加载班次下拉选项 */
|
||||
loadTeamOptions() {
|
||||
listTeam({ pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
const data = res.data || {}
|
||||
this.teamOptions = data.records || []
|
||||
}).catch(() => {
|
||||
this.teamOptions = []
|
||||
})
|
||||
},
|
||||
|
||||
/** 组装查询参数,仅传非空值 */
|
||||
buildQueryParams(params) {
|
||||
const xybh = this.queryForm.xybh && this.queryForm.xybh.trim()
|
||||
const xh = this.queryForm.xh && this.queryForm.xh.trim()
|
||||
const nd = this.queryForm.nd && this.queryForm.nd.trim()
|
||||
if (xybh) params.xybh = xybh
|
||||
const kcbh = this.queryForm.kcbh && this.queryForm.kcbh.trim()
|
||||
const xydbh = this.queryForm.xydbh && this.queryForm.xydbh.trim()
|
||||
if (xh) params.xh = xh
|
||||
if (nd) params.nd = nd
|
||||
if (kcbh) params.kcbh = kcbh
|
||||
if (xydbh) params.xydbh = xydbh
|
||||
},
|
||||
|
||||
loadData() {
|
||||
@@ -170,23 +226,19 @@ export default {
|
||||
URL.revokeObjectURL(link.href)
|
||||
},
|
||||
|
||||
/** 校验导出所需学员编号 */
|
||||
requireXybh(exportName) {
|
||||
const xybh = this.queryForm.xybh && this.queryForm.xybh.trim()
|
||||
if (!xybh) {
|
||||
this.$message.warning('导出' + exportName + '前请先输入学员编号')
|
||||
return null
|
||||
}
|
||||
return xybh
|
||||
/** 构建导出查询参数 */
|
||||
buildExportQuery() {
|
||||
const query = {}
|
||||
this.buildQueryParams(query)
|
||||
return query
|
||||
},
|
||||
|
||||
/** 导出课程成绩 Excel */
|
||||
handleExportGrades() {
|
||||
const xybh = this.requireXybh('课程成绩')
|
||||
if (!xybh) return
|
||||
const query = this.buildExportQuery()
|
||||
this.exportLoading = true
|
||||
exportStudentGrades(xybh).then(res => {
|
||||
this.downloadBlob(res, `课程成绩_${xybh}.xls`)
|
||||
exportStudentGrades(query).then(res => {
|
||||
this.downloadBlob(res, `课程成绩_${Date.now()}.xls`)
|
||||
this.$message.success('课程成绩导出成功')
|
||||
}).catch(() => { }).finally(() => {
|
||||
this.exportLoading = false
|
||||
@@ -195,11 +247,10 @@ export default {
|
||||
|
||||
/** 导出课程过程成绩 Excel */
|
||||
handleExportProcessGrades() {
|
||||
const xybh = this.requireXybh('课程过程成绩')
|
||||
if (!xybh) return
|
||||
const query = this.buildExportQuery()
|
||||
this.exportLoading = true
|
||||
exportStudentProcessGrades(xybh).then(res => {
|
||||
this.downloadBlob(res, `课程过程成绩_${xybh}.xls`)
|
||||
exportStudentProcessGrades(query).then(res => {
|
||||
this.downloadBlob(res, `课程过程成绩_${Date.now()}.xls`)
|
||||
this.$message.success('课程过程成绩导出成功')
|
||||
}).catch(() => { }).finally(() => {
|
||||
this.exportLoading = false
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="学员队类型">
|
||||
<el-input v-model="searchForm.xydlx" placeholder="请输入学员队类型" clearable @keyup.enter.native="handleSearch" />
|
||||
<el-select v-model="searchForm.xydlx" placeholder="请选择学员队类型" clearable filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in teamTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
@@ -32,8 +39,8 @@
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="虚实类型">
|
||||
<el-select v-model="searchForm.xslx" placeholder="请选择虚实类型" clearable class="w-full">
|
||||
<el-option label="虚" :value="0" />
|
||||
<el-option label="实" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -74,7 +81,7 @@
|
||||
<el-table-column label="虚实类型" width="90" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="Number(row.xslx) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(row.xslx) === 1 ? '实' : '虚' }}
|
||||
{{ Number(row.xslx) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -146,7 +153,21 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代号" prop="zydh">
|
||||
<el-input v-model="form.zydh" placeholder="请输入专业代号" />
|
||||
<el-select
|
||||
v-model="form.zydh"
|
||||
:loading="majorOptionsLoading"
|
||||
placeholder="请选择专业"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in majorOptions"
|
||||
:key="item.zydh"
|
||||
:label="formatMajorLabel(item)"
|
||||
:value="item.zydh"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -156,17 +177,38 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队类型" prop="xydlx">
|
||||
<el-input v-model="form.xydlx" placeholder="请输入学员队类型" />
|
||||
<el-select v-model="form.xydlx" placeholder="请选择学员队类型" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in teamTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="节次类别" prop="jclb">
|
||||
<el-input v-model="form.jclb" placeholder="请输入节次类别" />
|
||||
<el-select v-model="form.jclb" placeholder="请选择节次类别" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in sessionCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="系统模式" prop="xtms">
|
||||
<el-input v-model="form.xtms" placeholder="请输入系统模式" />
|
||||
<el-select v-model="form.xtms" placeholder="请选择系统模式" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in systemModeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -176,12 +218,14 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属单位">
|
||||
<el-input v-model="form.ssdw" placeholder="请输入所属单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="序号">
|
||||
<el-input-number v-model="form.xh" :min="0" controls-position="right" style="width: 100%" />
|
||||
<el-select v-model="form.ssdw" placeholder="请选择所属单位" clearable filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in unitCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -196,14 +240,14 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="虚实类型" prop="xslx">
|
||||
<el-select v-model="form.xslx" class="w-full">
|
||||
<el-option label="虚" :value="0" />
|
||||
<el-option label="实" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主体培训任务" prop="ztpxrw">
|
||||
<el-select v-model="form.ztpxrw" class="w-full">
|
||||
<el-select v-model="form.ztpxrw" class="w-full" @change="handlePrimaryTrainingTaskChange">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
@@ -211,12 +255,26 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训任务标识号">
|
||||
<el-input v-model="form.pxrwbsh" placeholder="请输入培训任务标识号" />
|
||||
<el-select
|
||||
v-model="form.pxrwbsh"
|
||||
:loading="trainingTaskLoading"
|
||||
placeholder="请选择培训任务"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in trainingTaskOptions"
|
||||
:key="item.bsh"
|
||||
:label="formatTrainingTaskLabel(item)"
|
||||
:value="item.bsh"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="在校状态">
|
||||
<el-select v-model="form.zxzt" class="w-full">
|
||||
<el-select v-model="form.zxzt" placeholder="请选择在校状态" clearable class="w-full">
|
||||
<el-option label="在校" :value="1" />
|
||||
<el-option label="毕业" :value="0" />
|
||||
</el-select>
|
||||
@@ -233,9 +291,6 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="JSON字段">
|
||||
<el-input v-model="form.jsonzd" placeholder="请输入 JSON 字段" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
@@ -262,11 +317,10 @@
|
||||
<el-descriptions-item label="系统模式">{{ detailForm.xtms || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="简称">{{ detailForm.jc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属单位">{{ detailForm.ssdw || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="序号">{{ fmtValue(detailForm.xh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类别">{{ detailForm.rwlb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="虚实类型">
|
||||
<el-tag :type="Number(detailForm.xslx) === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ Number(detailForm.xslx) === 1 ? '实' : '虚' }}
|
||||
{{ Number(detailForm.xslx) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="主体培训任务">
|
||||
@@ -282,7 +336,6 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="入学日期">{{ detailForm.rxrq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="毕业日期">{{ detailForm.byrq || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="JSON字段" :span="2">{{ detailForm.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -301,9 +354,19 @@ import {
|
||||
addTeam,
|
||||
updateTeam,
|
||||
delTeam,
|
||||
listTrainingTasks,
|
||||
importTeam,
|
||||
exportTeam
|
||||
exportTeam,
|
||||
downloadTeamTemplate
|
||||
} from '@/api/studentRecords/team'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { listMajor } from '@/api/subjectMajor/major'
|
||||
|
||||
const TEAM_TYPE_DICT_CODE = 'stu_type'
|
||||
const SESSION_CATEGORY_DICT_CODE = 'session_category'
|
||||
const SYSTEM_MODE_DICT_CODE = 'system_mode'
|
||||
const UNIT_CATEGORY_DICT_CODE = 'sys_unit_category'
|
||||
const MAJOR_OPTION_PAGE_SIZE = 10000
|
||||
|
||||
export default {
|
||||
name: 'ShiftTeamIndex',
|
||||
@@ -318,6 +381,14 @@ export default {
|
||||
rwlb: '',
|
||||
xslx: undefined
|
||||
},
|
||||
teamTypeOptions: [],
|
||||
sessionCategoryOptions: [],
|
||||
systemModeOptions: [],
|
||||
unitCategoryOptions: [],
|
||||
majorOptions: [],
|
||||
majorOptionsLoading: false,
|
||||
trainingTaskOptions: [],
|
||||
trainingTaskLoading: false,
|
||||
|
||||
// ==================== 文件导入 ====================
|
||||
selectedFile: null,
|
||||
@@ -340,14 +411,14 @@ export default {
|
||||
xydbh: [{ required: true, message: '请输入学员队编号', trigger: 'blur' }],
|
||||
xydmc: [{ required: true, message: '请输入学员队名称', trigger: 'blur' }],
|
||||
xydrs: [{ required: true, message: '请输入学员队人数', trigger: 'change' }],
|
||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
||||
zydh: [{ required: true, message: '请选择专业', trigger: 'change' }],
|
||||
rwlb: [{ required: true, message: '请输入任务类别', trigger: 'blur' }],
|
||||
xslx: [{ required: true, message: '请选择虚实类型', trigger: 'change' }],
|
||||
rxrq: [{ required: true, message: '请选择入学日期', trigger: 'change' }],
|
||||
byrq: [{ required: true, message: '请选择毕业日期', trigger: 'change' }],
|
||||
xydlx: [{ required: true, message: '请输入学员队类型', trigger: 'blur' }],
|
||||
jclb: [{ required: true, message: '请输入节次类别', trigger: 'blur' }],
|
||||
xtms: [{ required: true, message: '请输入系统模式', trigger: 'blur' }],
|
||||
xydlx: [{ required: true, message: '请选择学员队类型', trigger: 'change' }],
|
||||
jclb: [{ required: true, message: '请选择节次类别', trigger: 'change' }],
|
||||
xtms: [{ required: true, message: '请选择系统模式', trigger: 'change' }],
|
||||
ztpxrw: [{ required: true, message: '请选择主体培训任务', trigger: 'change' }]
|
||||
},
|
||||
|
||||
@@ -363,9 +434,53 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTeamDictionaries()
|
||||
this.loadMajorOptions()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 加载学员队相关字典。 */
|
||||
async loadTeamDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [teamTypeResponse, sessionCategoryResponse, systemModeResponse, unitCategoryResponse] = await Promise.all([
|
||||
getDicts(TEAM_TYPE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(SESSION_CATEGORY_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(SYSTEM_MODE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(UNIT_CATEGORY_DICT_CODE).catch(() => emptyResponse)
|
||||
])
|
||||
this.teamTypeOptions = teamTypeResponse.data || []
|
||||
this.sessionCategoryOptions = sessionCategoryResponse.data || []
|
||||
this.systemModeOptions = systemModeResponse.data || []
|
||||
this.unitCategoryOptions = unitCategoryResponse.data || []
|
||||
},
|
||||
|
||||
/** 加载专业下拉选项,标签同时包含名称、代码和代号,便于直接模糊搜索。 */
|
||||
async loadMajorOptions() {
|
||||
this.majorOptionsLoading = true
|
||||
try {
|
||||
const response = await listMajor({
|
||||
pageNum: 1,
|
||||
pageSize: MAJOR_OPTION_PAGE_SIZE,
|
||||
ty: false
|
||||
})
|
||||
const data = response.data || {}
|
||||
this.majorOptions = Array.isArray(data.records) ? data.records : []
|
||||
} catch (error) {
|
||||
this.majorOptions = []
|
||||
} finally {
|
||||
this.majorOptionsLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
formatMajorLabel(item) {
|
||||
const identifiers = [item.zydm, item.zydh].filter(Boolean).join(' / ')
|
||||
if (!item.zymc) {
|
||||
return identifiers
|
||||
}
|
||||
|
||||
return identifiers ? `${item.zymc}(${identifiers})` : item.zymc
|
||||
},
|
||||
|
||||
createEmptyForm() {
|
||||
return {
|
||||
xydbh: '',
|
||||
@@ -375,8 +490,7 @@ export default {
|
||||
zydh: '',
|
||||
zyjsbh: '',
|
||||
bz: '',
|
||||
zxzt: 1,
|
||||
xh: undefined,
|
||||
zxzt: undefined,
|
||||
rwlb: '',
|
||||
xslx: 1,
|
||||
rxrq: '',
|
||||
@@ -401,6 +515,17 @@ export default {
|
||||
return val === null || val === undefined || val === '' ? '-' : val
|
||||
},
|
||||
|
||||
normalizeSchoolStatus(value) {
|
||||
if (value === 1 || value === '1' || value === '在校') {
|
||||
return 1
|
||||
}
|
||||
if (value === 0 || value === '0' || value === '毕业') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return undefined
|
||||
},
|
||||
|
||||
/** 构建查询条件(不含分页),供列表查询与导出共用 */
|
||||
buildQuery() {
|
||||
const params = {}
|
||||
@@ -463,6 +588,7 @@ export default {
|
||||
this.isAdd = true
|
||||
this.dialogTitle = '新增学员队'
|
||||
this.form = this.createEmptyForm()
|
||||
this.loadTrainingTaskOptions(this.form.ztpxrw)
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
@@ -487,8 +613,7 @@ export default {
|
||||
zydh: d.zydh || '',
|
||||
zyjsbh: d.zyjsbh || '',
|
||||
bz: d.bz || '',
|
||||
zxzt: d.zxzt !== null && d.zxzt !== undefined ? Number(d.zxzt) : 1,
|
||||
xh: d.xh !== null && d.xh !== undefined ? d.xh : undefined,
|
||||
zxzt: this.normalizeSchoolStatus(d.zxzt),
|
||||
rwlb: d.rwlb || '',
|
||||
xslx: d.xslx !== null && d.xslx !== undefined ? Number(d.xslx) : 1,
|
||||
rxrq: this.formatDate(d.rxrq),
|
||||
@@ -502,9 +627,36 @@ export default {
|
||||
ssdw: d.ssdw || '',
|
||||
ztpxrw: d.ztpxrw !== null && d.ztpxrw !== undefined ? Number(d.ztpxrw) : 1
|
||||
}
|
||||
this.loadTrainingTaskOptions(this.form.ztpxrw)
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/** 按主体培训任务标志加载后端培训任务选项。 */
|
||||
async loadTrainingTaskOptions(ztpxrw) {
|
||||
this.trainingTaskLoading = true
|
||||
try {
|
||||
const response = await listTrainingTasks(ztpxrw)
|
||||
this.trainingTaskOptions = Array.isArray(response.data) ? response.data : []
|
||||
} catch (error) {
|
||||
this.trainingTaskOptions = []
|
||||
} finally {
|
||||
this.trainingTaskLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
handlePrimaryTrainingTaskChange(ztpxrw) {
|
||||
this.form.pxrwbsh = ''
|
||||
this.loadTrainingTaskOptions(ztpxrw)
|
||||
},
|
||||
|
||||
formatTrainingTaskLabel(item) {
|
||||
if (!item.mc) {
|
||||
return item.bsh
|
||||
}
|
||||
|
||||
return `${item.mc}(${item.bsh})`
|
||||
},
|
||||
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {
|
||||
@@ -515,16 +667,16 @@ export default {
|
||||
xydlx: f.xydlx,
|
||||
jclb: f.jclb,
|
||||
xtms: f.xtms,
|
||||
zxzt: Number(f.zxzt),
|
||||
xslx: Number(f.xslx),
|
||||
ztpxrw: Number(f.ztpxrw)
|
||||
}
|
||||
// 数值字段
|
||||
if (f.xydrs !== '' && f.xydrs !== null && f.xydrs !== undefined) payload.xydrs = Number(f.xydrs)
|
||||
if (f.xh !== '' && f.xh !== null && f.xh !== undefined) payload.xh = Number(f.xh)
|
||||
// 日期字段
|
||||
if (f.rxrq) payload.rxrq = f.rxrq
|
||||
if (f.byrq) payload.byrq = f.byrq
|
||||
// 字符串字段
|
||||
if (f.zxzt !== '' && f.zxzt !== null && f.zxzt !== undefined) payload.zxzt = String(f.zxzt)
|
||||
// 后端字段为 LocalDateTime,日期选择器值统一补齐当天零点。
|
||||
if (f.rxrq) payload.rxrq = `${this.formatDate(f.rxrq)}T00:00:00`
|
||||
if (f.byrq) payload.byrq = `${this.formatDate(f.byrq)}T00:00:00`
|
||||
// 其余文本字段非空才传
|
||||
;['nj', 'zyjsbh', 'pxrwbsh', 'jsonzd', 'jc', 'ssdw', 'bz'].forEach(key => {
|
||||
if (f[key] !== '' && f[key] !== null && f[key] !== undefined) {
|
||||
@@ -588,8 +740,10 @@ export default {
|
||||
|
||||
// ==================== 模板下载 / 导入 ====================
|
||||
handleDownloadTemplate() {
|
||||
// 后端暂未提供模板下载接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
downloadTeamTemplate().then(blob => {
|
||||
saveAs(blob, '学员队导入模板.xlsx')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
handleChooseFile() {
|
||||
@@ -632,14 +786,14 @@ export default {
|
||||
}
|
||||
|
||||
.shift-team-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.search-actions-row {
|
||||
margin-top: 2px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
|
||||
@@ -69,7 +69,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button v-if="!row.tjsj" type="text" size="small" icon="el-icon-upload2" @click="handleSubmit(row)">提交</el-button>
|
||||
<el-button
|
||||
v-if="!row.tjsj"
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-upload2"
|
||||
@click="handleSubmit(row)"
|
||||
>提交</el-button>
|
||||
<el-button v-else type="text" size="small" icon="el-icon-check" disabled>已提交</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
|
||||
@@ -100,11 +106,19 @@
|
||||
@update:visible="val => formDialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="110px" class="add-form">
|
||||
<el-form-item label="编号" prop="bh">
|
||||
<el-input v-model="form.bh" placeholder="请输入编号" :disabled="!isAdd" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学员编号" prop="xybh">
|
||||
<el-input v-model="form.xybh" placeholder="请输入学员编号" />
|
||||
<el-form-item label="学号" prop="xh">
|
||||
<el-input
|
||||
ref="studentNoInput"
|
||||
v-model.trim="form.xh"
|
||||
placeholder="请输入学号"
|
||||
:disabled="studentVerifying"
|
||||
@input="handleStudentNoInput"
|
||||
@change="handleStudentNoComplete"
|
||||
/>
|
||||
<div v-if="isStudentVerified" class="student-verified-tip">
|
||||
<i class="el-icon-circle-check" />
|
||||
已核验:{{ verifiedStudent.xm || verifiedStudent.xh || form.xh }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请类型" prop="sqlx">
|
||||
<el-select v-model="form.sqlx" placeholder="请选择申请类型" class="w-full">
|
||||
@@ -117,11 +131,6 @@
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="zt">
|
||||
<el-select v-model="form.zt" placeholder="请选择状态" class="w-full">
|
||||
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="Number(key)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="formDialogVisible = false">取消</el-button>
|
||||
@@ -129,7 +138,39 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 4. 详情对话框 ==================== -->
|
||||
<!-- ==================== 4. 学员信息核验对话框 ==================== -->
|
||||
<el-dialog
|
||||
title="请确认学员信息"
|
||||
:visible="studentConfirmVisible"
|
||||
width="560px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => studentConfirmVisible = val"
|
||||
>
|
||||
<el-alert
|
||||
title="请核对以下信息,确认无误后再提交申请。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="student-confirm-alert"
|
||||
/>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ pendingStudent.xh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员编号">{{ pendingStudent.bh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ pendingStudent.xm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ studentSexLabel(pendingStudent.xb) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员队期编号">{{ pendingStudent.xydqbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="当前班次编号">{{ pendingStudent.dqbzbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员类别">{{ pendingStudent.xylb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="政治面貌">{{ pendingStudent.zzmm || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer">
|
||||
<el-button @click="handleStudentReenter">重新输入</el-button>
|
||||
<el-button type="primary" @click="handleStudentConfirm">确认无误</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ==================== 5. 详情对话框 ==================== -->
|
||||
<el-dialog title="学籍异动申请详情" :visible="detailDialogVisible" width="760px" :close-on-click-modal="false"
|
||||
@update:visible="val => detailDialogVisible = val">
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
@@ -168,9 +209,20 @@ import {
|
||||
updateApplication,
|
||||
delApplication
|
||||
} from '@/api/studentRecords/application'
|
||||
import { getStudentRecord, listStudentRecord } from '@/api/studentRecords/studentRecords'
|
||||
|
||||
export default {
|
||||
name: 'StatusChangeIndex',
|
||||
computed: {
|
||||
isStudentVerified() {
|
||||
const studentNo = this.normalizeStudentNo(this.form.xh)
|
||||
return Boolean(
|
||||
studentNo &&
|
||||
studentNo === this.verifiedStudentNo &&
|
||||
this.form.xybh === this.verifiedStudent.bh
|
||||
)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 申请类型选项(学籍异动) ====================
|
||||
@@ -203,13 +255,20 @@ export default {
|
||||
dialogTitle: '新增学籍异动申请',
|
||||
isAdd: true,
|
||||
formSaving: false,
|
||||
studentVerifying: false,
|
||||
studentConfirmVisible: false,
|
||||
pendingStudent: {},
|
||||
verifiedStudent: {},
|
||||
verifiedStudentNo: '',
|
||||
form: this.createEmptyForm(),
|
||||
formRules: {
|
||||
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
|
||||
xybh: [{ required: true, message: '请输入学员编号', trigger: 'blur' }],
|
||||
xh: [
|
||||
{ required: true, message: '请输入学号', trigger: 'blur' },
|
||||
{ max: 50, message: '学号不能超过50个字符', trigger: 'blur' },
|
||||
{ pattern: /^[A-Za-z0-9]+$/, message: '学号只能包含字母和数字', trigger: 'blur' }
|
||||
],
|
||||
sqlx: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
|
||||
sy: [{ required: true, message: '请输入事由', trigger: 'blur' }],
|
||||
zt: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
sy: [{ required: true, message: '请输入事由', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
@@ -225,6 +284,7 @@ export default {
|
||||
createEmptyForm() {
|
||||
return {
|
||||
bh: '',
|
||||
xh: '',
|
||||
xybh: '',
|
||||
sqlx: '',
|
||||
sy: '',
|
||||
@@ -248,6 +308,19 @@ export default {
|
||||
return item ? item.type : 'info'
|
||||
},
|
||||
|
||||
studentSexLabel(value) {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return '-'
|
||||
}
|
||||
if (String(value) === '1') {
|
||||
return '男'
|
||||
}
|
||||
if (String(value) === '0') {
|
||||
return '女'
|
||||
}
|
||||
return String(value)
|
||||
},
|
||||
|
||||
// ==================== 查询列表 ====================
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
@@ -304,6 +377,7 @@ export default {
|
||||
this.isAdd = true
|
||||
this.dialogTitle = '新增学籍异动申请'
|
||||
this.form = this.createEmptyForm()
|
||||
this.resetStudentVerification()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
@@ -314,6 +388,7 @@ export default {
|
||||
this.isAdd = false
|
||||
this.dialogTitle = '编辑学籍异动申请'
|
||||
this.form = this.createEmptyForm()
|
||||
this.resetStudentVerification()
|
||||
this.formDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
@@ -322,23 +397,156 @@ export default {
|
||||
const data = response.data || {}
|
||||
this.form = {
|
||||
bh: data.bh || '',
|
||||
xh: '',
|
||||
xybh: data.xybh || '',
|
||||
sqlx: data.sqlx || '',
|
||||
sy: data.sy || '',
|
||||
bz: data.bz || '',
|
||||
zt: data.zt !== null && data.zt !== undefined ? Number(data.zt) : 0
|
||||
}
|
||||
this.loadExistingStudent(data.xybh)
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
normalizeStudentNo(value) {
|
||||
return value === null || value === undefined ? '' : String(value).trim()
|
||||
},
|
||||
|
||||
resetStudentVerification() {
|
||||
this.studentVerifying = false
|
||||
this.studentConfirmVisible = false
|
||||
this.pendingStudent = {}
|
||||
this.verifiedStudent = {}
|
||||
this.verifiedStudentNo = ''
|
||||
},
|
||||
|
||||
handleStudentNoInput() {
|
||||
const studentNo = this.normalizeStudentNo(this.form.xh)
|
||||
if (studentNo !== this.verifiedStudentNo) {
|
||||
this.verifiedStudent = {}
|
||||
this.verifiedStudentNo = ''
|
||||
this.form.xybh = ''
|
||||
}
|
||||
this.pendingStudent = {}
|
||||
},
|
||||
|
||||
handleStudentNoComplete() {
|
||||
if (this.normalizeStudentNo(this.form.xh)) {
|
||||
this.loadStudentForConfirmation()
|
||||
}
|
||||
},
|
||||
|
||||
async loadStudentForConfirmation() {
|
||||
if (this.studentVerifying) {
|
||||
return false
|
||||
}
|
||||
const studentNo = this.normalizeStudentNo(this.form.xh)
|
||||
if (!studentNo) {
|
||||
this.$refs.formRef && this.$refs.formRef.validateField('xh')
|
||||
return false
|
||||
}
|
||||
|
||||
if (studentNo === this.verifiedStudentNo && this.verifiedStudent.bh) {
|
||||
this.pendingStudent = { ...this.verifiedStudent }
|
||||
this.studentConfirmVisible = true
|
||||
return true
|
||||
}
|
||||
|
||||
this.studentVerifying = true
|
||||
try {
|
||||
const response = await listStudentRecord({ pageNum: 1, pageSize: 2, xh: studentNo })
|
||||
const records = response.data && Array.isArray(response.data.records) ? response.data.records : []
|
||||
|
||||
// 请求返回前用户可能已经修改了学号,旧结果不能覆盖当前输入。
|
||||
if (studentNo !== this.normalizeStudentNo(this.form.xh)) {
|
||||
return false
|
||||
}
|
||||
if (records.length === 0) {
|
||||
this.$message.warning('未查询到该学号对应的学员,请核对学号')
|
||||
this.resetStudentVerification()
|
||||
return false
|
||||
}
|
||||
if (records.length > 1) {
|
||||
this.$message.warning('该学号匹配到多名学员,请联系管理员检查学员数据')
|
||||
this.resetStudentVerification()
|
||||
return false
|
||||
}
|
||||
|
||||
this.pendingStudent = records[0]
|
||||
this.studentConfirmVisible = true
|
||||
return true
|
||||
} catch (error) {
|
||||
if (studentNo === this.normalizeStudentNo(this.form.xh)) {
|
||||
this.resetStudentVerification()
|
||||
}
|
||||
return false
|
||||
} finally {
|
||||
this.studentVerifying = false
|
||||
}
|
||||
},
|
||||
|
||||
handleStudentConfirm() {
|
||||
const studentNo = this.normalizeStudentNo(this.form.xh)
|
||||
if (!this.pendingStudent.bh || this.normalizeStudentNo(this.pendingStudent.xh) !== studentNo) {
|
||||
this.$message.warning('学号已变化,请重新核验')
|
||||
this.studentConfirmVisible = false
|
||||
return
|
||||
}
|
||||
this.verifiedStudent = { ...this.pendingStudent }
|
||||
this.verifiedStudentNo = studentNo
|
||||
this.form.xybh = this.pendingStudent.bh
|
||||
this.studentConfirmVisible = false
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate('xh')
|
||||
this.$message.success('学员信息核验成功')
|
||||
},
|
||||
|
||||
handleStudentReenter() {
|
||||
this.studentConfirmVisible = false
|
||||
this.pendingStudent = {}
|
||||
this.verifiedStudent = {}
|
||||
this.verifiedStudentNo = ''
|
||||
this.form.xybh = ''
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.studentNoInput
|
||||
input && input.focus()
|
||||
})
|
||||
},
|
||||
|
||||
async loadExistingStudent(studentId) {
|
||||
const normalizedStudentId = this.normalizeStudentNo(studentId)
|
||||
if (!normalizedStudentId) {
|
||||
return
|
||||
}
|
||||
this.studentVerifying = true
|
||||
try {
|
||||
const response = await getStudentRecord(normalizedStudentId)
|
||||
const student = response.data || {}
|
||||
if (this.form.xybh !== normalizedStudentId || !student.bh) {
|
||||
return
|
||||
}
|
||||
this.form.xh = student.xh || ''
|
||||
this.verifiedStudentNo = this.normalizeStudentNo(student.xh)
|
||||
this.verifiedStudent = student
|
||||
} catch (error) {
|
||||
this.$message.warning('未能加载原申请的学员信息,请重新输入学号核验')
|
||||
} finally {
|
||||
this.studentVerifying = false
|
||||
}
|
||||
},
|
||||
|
||||
buildPayload() {
|
||||
const f = this.form
|
||||
const payload = {
|
||||
bh: f.bh,
|
||||
xybh: f.xybh,
|
||||
sqlx: f.sqlx,
|
||||
sy: f.sy,
|
||||
zt: Number(f.zt)
|
||||
sy: f.sy
|
||||
}
|
||||
// 新增编号和状态由后端生成;编辑时仅回传记录定位编号和原状态。
|
||||
if (!this.isAdd) {
|
||||
payload.bh = f.bh
|
||||
if (f.zt !== null && f.zt !== undefined && f.zt !== '') {
|
||||
payload.zt = Number(f.zt)
|
||||
}
|
||||
}
|
||||
// 备注留空则移除
|
||||
if (f.bz !== '' && f.bz !== null && f.bz !== undefined) {
|
||||
@@ -347,9 +555,21 @@ export default {
|
||||
return payload
|
||||
},
|
||||
|
||||
handleFormSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
async handleFormSubmit() {
|
||||
const isValid = await new Promise(resolve => {
|
||||
this.$refs.formRef.validate(valid => resolve(valid))
|
||||
})
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
if (!this.isStudentVerified) {
|
||||
const isFound = await this.loadStudentForConfirmation()
|
||||
if (isFound) {
|
||||
this.$message.warning('请先确认学员信息,再提交申请')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.formSaving = true
|
||||
const payload = this.buildPayload()
|
||||
const request = this.isAdd ? addApplication(payload) : updateApplication(payload)
|
||||
@@ -361,7 +581,6 @@ export default {
|
||||
}).finally(() => {
|
||||
this.formSaving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 提交 ====================
|
||||
@@ -369,7 +588,9 @@ export default {
|
||||
formatNow() {
|
||||
const d = new Date()
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
return `${date} ${time}`
|
||||
},
|
||||
|
||||
handleSubmit(row) {
|
||||
@@ -486,9 +707,24 @@ export default {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.student-verified-tip {
|
||||
margin-top: 4px;
|
||||
color: #078267;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.student-confirm-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="app-container student-info-page">
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-card v-if="!isStudentRole" shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="24">
|
||||
<!-- 左栏 -->
|
||||
@@ -22,16 +22,37 @@
|
||||
<el-input v-model="searchForm.zjhm" placeholder="请输入证件号码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="政治面貌">
|
||||
<el-input v-model="searchForm.zzmm" placeholder="请输入政治面貌" clearable />
|
||||
<el-select v-model="searchForm.zzmm" placeholder="请选择政治面貌" clearable filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in politicalStatusOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="民族">
|
||||
<el-input v-model="searchForm.mz" placeholder="请输入民族" clearable />
|
||||
<el-select v-model="searchForm.mz" placeholder="请选择民族" clearable filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in nationOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="籍贯">
|
||||
<el-input v-model="searchForm.jg" placeholder="请输入籍贯" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="学位">
|
||||
<el-input v-model="searchForm.xw" placeholder="请输入学位" clearable />
|
||||
<el-select v-model="searchForm.xw" placeholder="请选择学位" clearable filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in degreeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -44,13 +65,27 @@
|
||||
<el-input v-model="searchForm.sfzh" placeholder="请输入身份证号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="文化程度">
|
||||
<el-input v-model="searchForm.whcd" placeholder="请输入文化程度" clearable />
|
||||
<el-select v-model="searchForm.whcd" placeholder="请选择文化程度" clearable filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in educationLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="本科毕业院校">
|
||||
<el-input v-model="searchForm.bkbyyx" placeholder="请输入本科毕业院校" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签">
|
||||
<el-input v-model="searchForm.bq" placeholder="请输入标签" clearable />
|
||||
<el-form-item label="学员类别">
|
||||
<el-select v-model="searchForm.xylb" placeholder="请选择学员类别" clearable filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in studentCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="searchForm.lxdh" placeholder="请输入联系电话" clearable />
|
||||
@@ -71,29 +106,27 @@
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="list-header">
|
||||
<div class="list-title">学员信息列表</div>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学员</el-button>
|
||||
<el-button v-if="!isStudentRole" type="primary" icon="el-icon-plus" @click="handleAdd">新增学员</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" stripe border>
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="xh" label="学号" width="55" show-overflow-tooltip />
|
||||
<el-table-column prop="xm" label="姓名" width="55" />
|
||||
<el-table-column prop="xb" label="性别" width="55" align="center" />
|
||||
<el-table-column prop="nj" label="年级" width="55" align="center" />
|
||||
<el-table-column prop="zy" label="专业" width="55" show-overflow-tooltip />
|
||||
<el-table-column prop="db" label="队别" width="55" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="55" align="center" />
|
||||
<el-table-column prop="pxlx" label="培训类型" width="85" show-overflow-tooltip />
|
||||
<el-table-column prop="pxcc" label="培训层次" width="85" align="center" />
|
||||
<el-table-column prop="zzmm" label="政治面貌" width="85" align="center" />
|
||||
<el-table-column prop="mz" label="民族" width="55" align="center" />
|
||||
<el-table-column prop="jg" label="籍贯" width="55" align="center" />
|
||||
<el-table-column prop="xw" label="学位" width="55" align="center" />
|
||||
<el-table-column prop="zczt" label="注册状态" width="85" align="center" />
|
||||
<el-table-column prop="xjyd" label="学籍异动状态" width="110" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" width="85" align="center" />
|
||||
<el-table v-loading="loading" :data="tableData" class="student-info-table" stripe border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="xh" label="学号" width="130" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xm" label="姓名" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xb" label="性别" width="70" align="center" />
|
||||
<el-table-column prop="nj" label="年级" width="80" align="center" />
|
||||
<el-table-column prop="zy" label="专业" width="160" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xylb" label="学员类别" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bc" label="班次" width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zzmm" label="政治面貌" width="110" align="center" />
|
||||
<el-table-column prop="mz" label="民族" width="80" align="center" />
|
||||
<el-table-column prop="jg" label="籍贯" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xw" label="学位" width="90" align="center" />
|
||||
<el-table-column prop="zczt" label="注册状态" width="110" align="center" />
|
||||
<el-table-column prop="xjyd" label="学籍异动状态" width="130" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" width="100" align="center" />
|
||||
<el-table-column prop="bjgm" label="不及格门数" width="110" align="center" />
|
||||
<el-table-column prop="gljg" label="管理机构" width="85" align="center" />
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<el-table-column prop="gljg" label="管理机构" width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="190" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" class="text-primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="text" class="text-success" @click="handleGrades(row)">成绩</el-button>
|
||||
@@ -124,7 +157,7 @@
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-row v-if="dialogTitle !== '新增学员'" :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="编号" prop="bh"><el-input v-model="formData.bh" placeholder="请输入编号" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="学号" prop="xh"><el-input v-model="formData.xh" placeholder="请输入学号" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
@@ -134,15 +167,59 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="出生日期" prop="csrq"><el-date-picker v-model="formData.csrq" type="date" placeholder="请选择" value-format="yyyy-MM-dd" class="full-width" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="民族" prop="mz"><el-input v-model="formData.mz" placeholder="请输入民族" /></el-form-item></el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="民族" prop="mz">
|
||||
<el-select v-model="formData.mz" placeholder="请选择民族" filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in nationOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="籍贯" prop="jg"><el-input v-model="formData.jg" placeholder="请输入籍贯" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="政治面貌" prop="zzmm"><el-input v-model="formData.zzmm" placeholder="请输入政治面貌" /></el-form-item></el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="政治面貌" prop="zzmm">
|
||||
<el-select v-model="formData.zzmm" placeholder="请选择政治面貌" filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in politicalStatusOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="文化程度" prop="whcd"><el-input v-model="formData.whcd" placeholder="请输入文化程度" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="学员类别" prop="xylb"><el-input v-model="formData.xylb" placeholder="请输入学员类别" /></el-form-item></el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文化程度" prop="whcd">
|
||||
<el-select v-model="formData.whcd" placeholder="请选择文化程度" filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in educationLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别" prop="xylb">
|
||||
<el-select v-model="formData.xylb" placeholder="请选择学员类别" filterable class="full-width">
|
||||
<el-option
|
||||
v-for="item in studentCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="学员队期编号" prop="xydqbh"><el-input v-model="formData.xydqbh" placeholder="请输入学员队期编号" /></el-form-item></el-col>
|
||||
@@ -154,12 +231,12 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="军人证件类型" prop="jrzjlx"><el-input v-model="formData.jrzjlx" placeholder="请输入军人证件类型" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="退学状态" prop="txzt"><el-input v-model="formData.txzt" placeholder="请输入退学状态" /></el-form-item></el-col>
|
||||
<el-col v-if="dialogTitle !== '新增学员'" :span="12"><el-form-item label="退学状态" prop="txzt"><el-input v-model="formData.txzt" placeholder="请输入退学状态" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="国防生" prop="gfs"><el-select v-model="formData.gfs" placeholder="请选择" class="full-width"><el-option label="是" value="1" /><el-option label="否" value="0" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="留级状态" prop="ljzt"><el-input v-model="formData.ljzt" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="分班状态" prop="fbzt"><el-input v-model="formData.fbzt" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col v-if="dialogTitle !== '新增学员'" :span="8"><el-form-item label="留级状态" prop="ljzt"><el-input v-model="formData.ljzt" placeholder="请输入" /></el-form-item></el-col>
|
||||
<el-col v-if="dialogTitle !== '新增学员'" :span="8"><el-form-item label="分班状态" prop="fbzt"><el-input v-model="formData.fbzt" placeholder="请输入" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="允许查看成绩" prop="yxckcj"><el-select v-model="formData.yxckcj" placeholder="请选择" class="full-width"><el-option label="是" value="1" /><el-option label="否" value="0" /></el-select></el-form-item></el-col>
|
||||
@@ -184,25 +261,64 @@
|
||||
<el-dialog
|
||||
:visible="gradesVisible"
|
||||
title="学员成绩"
|
||||
width="600px"
|
||||
width="80%"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => gradesVisible = val"
|
||||
>
|
||||
<p><strong>学员:</strong>{{ gradesStudent }}</p>
|
||||
<div class="student-summary">
|
||||
<span><strong>学员:</strong>{{ gradesStudent || '-' }}</span>
|
||||
<span><strong>学号:</strong>{{ gradesStudentNo || '-' }}</span>
|
||||
<div class="grades-year-filter">
|
||||
<span class="filter-label">年度:</span>
|
||||
<el-select v-model="gradesYear" size="small" clearable placeholder="全部年度">
|
||||
<el-option
|
||||
v-for="year in gradeYearOptions"
|
||||
:key="year"
|
||||
:label="year"
|
||||
:value="year"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<el-tabs v-model="gradesTab">
|
||||
<el-tab-pane label="成绩" name="grades">
|
||||
<div v-loading="gradesLoading">
|
||||
<pre v-if="gradesData">{{ gradesData }}</pre>
|
||||
</div>
|
||||
<el-table v-loading="gradesLoading" :data="filteredGradesData" stripe border max-height="420">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="nd" label="年度" width="85" align="center" />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" width="90" align="center" />
|
||||
<el-table-column prop="kscj" label="考试成绩" width="90" align="center" />
|
||||
<el-table-column prop="pscj" label="平时成绩" width="90" align="center" />
|
||||
<el-table-column prop="zzcj" label="最终成绩" width="90" align="center" />
|
||||
<el-table-column prop="qwxf" label="期望学分" width="90" align="center" />
|
||||
<el-table-column prop="bkcj" label="补考成绩" width="90" align="center" />
|
||||
<el-table-column prop="bkcs" label="补考次数" width="90" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" width="100" align="center" />
|
||||
<el-table-column label="考试结果" width="90" align="center">
|
||||
<template slot-scope="{ row }">{{ formatPassStatus(row.ytg) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="进程成绩" name="process">
|
||||
<div v-loading="gradesLoading">
|
||||
<pre v-if="processGradesData">{{ processGradesData }}</pre>
|
||||
</div>
|
||||
<el-table v-loading="gradesLoading" :data="filteredProcessGradesData" stripe border max-height="420">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="nd" label="年度" width="85" align="center" />
|
||||
<el-table-column prop="kmbh" label="科目编号" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" width="90" align="center" />
|
||||
<el-table-column prop="yscj" label="原始成绩" width="100" align="center" />
|
||||
<el-table-column prop="zzcj" label="最终成绩" width="90" align="center" />
|
||||
<el-table-column prop="bkcj1" label="补考成绩1" width="100" align="center" />
|
||||
<el-table-column prop="bkcj2" label="补考成绩2" width="100" align="center" />
|
||||
<el-table-column prop="bkcj3" label="补考成绩3" width="100" align="center" />
|
||||
<el-table-column prop="bkcs" label="补考次数" width="90" align="center" />
|
||||
<el-table-column prop="ksqk" label="考试情况" width="100" align="center" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div slot="footer">
|
||||
<el-button type="primary" @click="handleExportGrades">导出成绩</el-button>
|
||||
<el-button type="primary" :loading="gradesExporting" @click="handleExportGrades">
|
||||
{{ gradesTab === 'grades' ? '导出成绩' : '导出进程成绩' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -214,19 +330,89 @@ import {
|
||||
getStudentRecord,
|
||||
addStudentRecord,
|
||||
updateStudentRecord,
|
||||
delStudentRecord
|
||||
delStudentRecord,
|
||||
listStudentGrades,
|
||||
listStudentProcessGrades,
|
||||
exportStudentGrades,
|
||||
exportStudentProcessGrades
|
||||
} from "@/api/studentRecords/studentRecords"
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { optionselect } from '@/api/system/dict/type'
|
||||
|
||||
const POLITICAL_STATUS_DICT_CODE = 'sys_political_status'
|
||||
const NATION_DICT_CODE = 'nation_type'
|
||||
const DEGREE_DICT_CODE = 'sys_degree_type'
|
||||
const EDUCATION_LEVEL_DICT_CODE = 'sys_edu_level'
|
||||
const GRADES_VIEW_DISABLED_VALUES = new Set(['0', 'false', '否', '不允许'])
|
||||
const RESIDENT_IDENTITY_CARD_TYPES = new Set(['居民身份证', '身份证'])
|
||||
const IDENTITY_CARD_CHECK_CODES = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
|
||||
const IDENTITY_CARD_WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
||||
const PHONE_NUMBER_PATTERN = /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/
|
||||
|
||||
function isResidentIdentityCardType(certificateType) {
|
||||
return RESIDENT_IDENTITY_CARD_TYPES.has(String(certificateType || '').trim())
|
||||
}
|
||||
|
||||
function isValidIdentityCard(identityCard) {
|
||||
const normalizedValue = String(identityCard || '').trim().toUpperCase()
|
||||
if (!/^\d{17}[\dX]$/.test(normalizedValue)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const year = Number(normalizedValue.slice(6, 10))
|
||||
const month = Number(normalizedValue.slice(10, 12))
|
||||
const day = Number(normalizedValue.slice(12, 14))
|
||||
const birthDate = new Date(year, month - 1, day)
|
||||
const isValidDate = birthDate.getFullYear() === year &&
|
||||
birthDate.getMonth() === month - 1 && birthDate.getDate() === day
|
||||
if (!isValidDate || birthDate > new Date()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const checksum = IDENTITY_CARD_WEIGHTS.reduce((total, weight, index) => {
|
||||
return total + Number(normalizedValue[index]) * weight
|
||||
}, 0)
|
||||
|
||||
return IDENTITY_CARD_CHECK_CODES[checksum % 11] === normalizedValue[17]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'StudentInfoIndex',
|
||||
data() {
|
||||
const validateCertificateNumber = (rule, value, callback) => {
|
||||
if (value && isResidentIdentityCardType(this.formData.jrzjlx) && !isValidIdentityCard(value)) {
|
||||
callback(new Error('请输入合法的18位居民身份证号码'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
const validateBirthDate = (rule, value, callback) => {
|
||||
if (value && new Date(`${value}T00:00:00`) > new Date()) {
|
||||
callback(new Error('出生日期不能晚于今天'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
const validatePhoneNumber = (rule, value, callback) => {
|
||||
if (value && !PHONE_NUMBER_PATTERN.test(String(value).trim())) {
|
||||
callback(new Error('请输入正确的手机号码或固定电话号码'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
// ==================== 查询条件 ====================
|
||||
// 仅保留后端 XYXX 实体支持的字段:/student-records/list 会按实体字段动态构建查询条件
|
||||
searchForm: {
|
||||
xh: '', xm: '', xb: '', zjhm: '', zzmm: '', mz: '', jg: '', xw: '',
|
||||
ybzb: '', sfzh: '', whcd: '', bkbyyx: '', bq: '', lxdh: '', txdz: ''
|
||||
ybzb: '', sfzh: '', whcd: '', bkbyyx: '', xylb: '', lxdh: '', txdz: ''
|
||||
},
|
||||
politicalStatusOptions: [],
|
||||
nationOptions: [],
|
||||
degreeOptions: [],
|
||||
educationLevelOptions: [],
|
||||
studentCategoryOptions: [],
|
||||
|
||||
// ==================== 数据表格 ====================
|
||||
loading: false,
|
||||
@@ -239,43 +425,134 @@ export default {
|
||||
submitting: false,
|
||||
formData: this.createEmptyForm(),
|
||||
formRules: {
|
||||
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
|
||||
xh: [{ required: true, message: '请输入学号', trigger: 'blur' }],
|
||||
xm: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
xydqbh: [{ required: true, message: '请输入学员队期编号', trigger: 'blur' }],
|
||||
zjhm: [{ required: true, message: '请输入证件号码', trigger: 'blur' }],
|
||||
zzmm: [{ required: true, message: '请输入政治面貌', trigger: 'blur' }],
|
||||
zjhm: [
|
||||
{ required: true, message: '请输入证件号码', trigger: 'blur' },
|
||||
{ validator: validateCertificateNumber, trigger: 'blur' }
|
||||
],
|
||||
zzmm: [{ required: true, message: '请选择政治面貌', trigger: 'change' }],
|
||||
xb: [{ required: true, message: '请选择性别', trigger: 'change' }],
|
||||
csrq: [{ required: true, message: '请选择出生日期', trigger: 'change' }],
|
||||
mz: [{ required: true, message: '请输入民族', trigger: 'blur' }],
|
||||
csrq: [
|
||||
{ required: true, message: '请选择出生日期', trigger: 'change' },
|
||||
{ validator: validateBirthDate, trigger: 'change' }
|
||||
],
|
||||
mz: [{ required: true, message: '请选择民族', trigger: 'change' }],
|
||||
jg: [{ required: true, message: '请输入籍贯', trigger: 'blur' }],
|
||||
whcd: [{ required: true, message: '请输入文化程度', trigger: 'blur' }],
|
||||
xylb: [{ required: true, message: '请输入学员类别', trigger: 'blur' }],
|
||||
whcd: [{ required: true, message: '请选择文化程度', trigger: 'change' }],
|
||||
xylb: [{ required: true, message: '请选择学员类别', trigger: 'change' }],
|
||||
ssjq: [{ required: true, message: '请输入所属军区', trigger: 'blur' }],
|
||||
gfs: [{ required: true, message: '请选择国防生', trigger: 'change' }],
|
||||
txzt: [{ required: true, message: '请输入退学状态', trigger: 'blur' }],
|
||||
ljzt: [{ required: true, message: '请输入留级状态', trigger: 'blur' }],
|
||||
fbzt: [{ required: true, message: '请输入分班状态', trigger: 'blur' }],
|
||||
yxckcj: [{ required: true, message: '请选择允许查看成绩', trigger: 'change' }],
|
||||
dqbzbh: [{ required: true, message: '请输入当前班次编号', trigger: 'blur' }],
|
||||
yzc: [{ required: true, message: '请选择已注册', trigger: 'change' }],
|
||||
jrzjlx: [{ required: true, message: '请输入军人证件类型', trigger: 'blur' }]
|
||||
jrzjlx: [{ required: true, message: '请输入军人证件类型', trigger: 'blur' }],
|
||||
lxdh: [{ validator: validatePhoneNumber, trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// ==================== 成绩弹窗 ====================
|
||||
gradesVisible: false,
|
||||
gradesTab: 'grades',
|
||||
gradesData: null,
|
||||
processGradesData: null,
|
||||
gradesData: [],
|
||||
processGradesData: [],
|
||||
gradesYear: '',
|
||||
gradesStudent: '',
|
||||
gradesStudentNo: '',
|
||||
currentBh: '',
|
||||
gradesLoading: false
|
||||
gradesLoading: false,
|
||||
gradesExporting: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
gradeYearOptions() {
|
||||
const years = [...this.gradesData, ...this.processGradesData]
|
||||
.map(item => item.nd)
|
||||
.filter(year => year !== '' && year !== null && year !== undefined)
|
||||
.map(String)
|
||||
|
||||
return [...new Set(years)].sort((firstYear, secondYear) => secondYear.localeCompare(firstYear))
|
||||
},
|
||||
/** 学员角色(后端角色键 STUDENT)隐藏条件查询栏与新增学员按钮 */
|
||||
isStudentRole() {
|
||||
const roles = this.$store.getters.roles
|
||||
return Array.isArray(roles) && roles.includes('STUDENT')
|
||||
},
|
||||
filteredGradesData() {
|
||||
return this.filterGradesByYear(this.gradesData)
|
||||
},
|
||||
filteredProcessGradesData() {
|
||||
return this.filterGradesByYear(this.processGradesData)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadStudentDictionaries()
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/** 加载学员基础信息字典,业务字段按字典标签查询和提交。 */
|
||||
async loadStudentDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [politicalStatusResponse, nationResponse, degreeResponse, educationLevelResponse, dictTypeResponse] =
|
||||
await Promise.all([
|
||||
getDicts(POLITICAL_STATUS_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(NATION_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(DEGREE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(EDUCATION_LEVEL_DICT_CODE).catch(() => emptyResponse),
|
||||
optionselect().catch(() => emptyResponse)
|
||||
])
|
||||
|
||||
this.politicalStatusOptions = politicalStatusResponse.data || []
|
||||
this.nationOptions = nationResponse.data || []
|
||||
this.degreeOptions = degreeResponse.data || []
|
||||
this.educationLevelOptions = educationLevelResponse.data || []
|
||||
this.studentCategoryOptions = await this.loadNamedDictionary(dictTypeResponse.data || [], '学员类别')
|
||||
this.mergeExistingDictionaryOptions()
|
||||
},
|
||||
|
||||
/** 根据字典名称匹配类型编码,避免业务页面硬编码数据库配置。 */
|
||||
async loadNamedDictionary(dictTypes, dictName) {
|
||||
const dictType = dictTypes.find(item => item.dictName === dictName) ||
|
||||
dictTypes.find(item => String(item.dictName || '').includes(dictName))
|
||||
if (!dictType || !dictType.dictType) {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const response = await getDicts(dictType.dictType)
|
||||
return response.data || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
/** 字典缺项时保留列表已有值,避免历史数据无法作为查询条件。 */
|
||||
mergeExistingDictionaryOptions() {
|
||||
this.politicalStatusOptions = this.mergeFieldOptions(this.politicalStatusOptions, 'zzmm')
|
||||
this.nationOptions = this.mergeFieldOptions(this.nationOptions, 'mz')
|
||||
this.degreeOptions = this.mergeFieldOptions(this.degreeOptions, 'xw')
|
||||
this.educationLevelOptions = this.mergeFieldOptions(this.educationLevelOptions, 'whcd')
|
||||
this.studentCategoryOptions = this.mergeFieldOptions(this.studentCategoryOptions, 'xylb')
|
||||
},
|
||||
|
||||
mergeFieldOptions(dictionaryOptions, fieldName) {
|
||||
const options = dictionaryOptions.slice()
|
||||
const existingLabels = new Set(options.map(item => String(item.dictLabel)))
|
||||
this.tableData.forEach(row => {
|
||||
const value = row[fieldName]
|
||||
if (value === '' || value === null || value === undefined || existingLabels.has(String(value))) {
|
||||
return
|
||||
}
|
||||
options.push({
|
||||
dictLabel: String(value),
|
||||
dictValue: String(value)
|
||||
})
|
||||
existingLabels.add(String(value))
|
||||
})
|
||||
|
||||
return options
|
||||
},
|
||||
|
||||
/** 创建空白表单 */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
@@ -297,6 +574,7 @@ export default {
|
||||
const data = res.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.pagination.total = data.total || 0
|
||||
this.mergeExistingDictionaryOptions()
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
@@ -307,7 +585,9 @@ export default {
|
||||
|
||||
/** 组装查询参数:仅传后端 XYXX 实体支持的字符串字段,空值忽略 */
|
||||
buildQueryParams(params) {
|
||||
const textFields = ['xh', 'xm', 'zjhm', 'zzmm', 'mz', 'jg', 'xw', 'ybzb', 'sfzh', 'whcd', 'bkbyyx', 'bq', 'lxdh', 'txdz']
|
||||
const textFields = [
|
||||
'xh', 'xm', 'zjhm', 'zzmm', 'mz', 'jg', 'xw', 'ybzb', 'sfzh', 'whcd', 'bkbyyx', 'xylb', 'lxdh', 'txdz'
|
||||
]
|
||||
textFields.forEach(key => {
|
||||
const v = this.searchForm[key]
|
||||
if (v !== '' && v !== null && v !== undefined) params[key] = v.trim()
|
||||
@@ -368,6 +648,13 @@ export default {
|
||||
this.submitting = true
|
||||
const isAdd = this.dialogTitle === '新增学员'
|
||||
const payload = { ...this.formData }
|
||||
if (isAdd) {
|
||||
delete payload.bh
|
||||
delete payload.xh
|
||||
delete payload.txzt
|
||||
delete payload.ljzt
|
||||
delete payload.fbzt
|
||||
}
|
||||
const request = isAdd ? addStudentRecord(payload) : updateStudentRecord(payload)
|
||||
request.then(() => {
|
||||
this.$message.success(isAdd ? '新增成功' : '编辑成功')
|
||||
@@ -395,25 +682,86 @@ export default {
|
||||
},
|
||||
|
||||
// ==================== 成绩弹窗 ====================
|
||||
// TODO: 后端接口未提供,成绩数据为前端模拟;接口就绪后替换为 getStudentGrades / getStudentProcessGrades
|
||||
handleGrades(row) {
|
||||
async handleGrades(row) {
|
||||
if (this.isGradesViewDisabled(row.yxckcj)) {
|
||||
this.$alert('不允许查看该学员成绩', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'warning'
|
||||
}).catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
this.gradesStudent = row.xm || ''
|
||||
this.gradesStudentNo = row.xh || ''
|
||||
this.currentBh = row.bh || ''
|
||||
this.gradesData = null
|
||||
this.processGradesData = null
|
||||
this.gradesData = []
|
||||
this.processGradesData = []
|
||||
this.gradesYear = ''
|
||||
this.gradesTab = 'grades'
|
||||
this.gradesVisible = true
|
||||
this.gradesLoading = true
|
||||
setTimeout(() => {
|
||||
this.gradesData = { 学员: row.xm, 学号: row.xh, 课程: '通信原理', 成绩: 92, 学分: 3.0 }
|
||||
this.processGradesData = { 进程成绩: [{ 名称: '平时成绩', 得分: 95 }, { 名称: '期末成绩', 得分: 88 }] }
|
||||
|
||||
const emptyResponse = { data: { records: [] } }
|
||||
const query = { pageNum: 1, pageSize: 1000, xybh: this.currentBh }
|
||||
try {
|
||||
const [gradesResponse, processGradesResponse] = await Promise.all([
|
||||
listStudentGrades(query).catch(() => emptyResponse),
|
||||
listStudentProcessGrades(query).catch(() => emptyResponse)
|
||||
])
|
||||
this.gradesData = gradesResponse.data.records || []
|
||||
this.processGradesData = processGradesResponse.data.records || []
|
||||
} finally {
|
||||
this.gradesLoading = false
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: 后端接口未提供,导出为前端模拟;接口就绪后替换为 exportStudentGrades
|
||||
handleExportGrades() {
|
||||
this.$message.success(`已导出学员${this.gradesStudent}成绩(前端模拟)`)
|
||||
formatPassStatus(value) {
|
||||
if (value === 1 || value === '1') {
|
||||
return '已通过'
|
||||
}
|
||||
if (value === 0 || value === '0') {
|
||||
return '未通过'
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
|
||||
isGradesViewDisabled(value) {
|
||||
return GRADES_VIEW_DISABLED_VALUES.has(String(value).trim().toLowerCase())
|
||||
},
|
||||
|
||||
filterGradesByYear(grades) {
|
||||
if (!this.gradesYear) {
|
||||
return grades
|
||||
}
|
||||
|
||||
return grades.filter(item => String(item.nd) === this.gradesYear)
|
||||
},
|
||||
|
||||
downloadGradesFile(blob, fileName) {
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
},
|
||||
|
||||
async handleExportGrades() {
|
||||
if (!this.currentBh) {
|
||||
this.$message.warning('缺少学员编号,无法导出成绩')
|
||||
return
|
||||
}
|
||||
|
||||
this.gradesExporting = true
|
||||
try {
|
||||
const isProcessGrades = this.gradesTab === 'process'
|
||||
const request = isProcessGrades ? exportStudentProcessGrades : exportStudentGrades
|
||||
const blob = await request({ xybh: this.currentBh })
|
||||
const suffix = isProcessGrades ? '课程过程成绩' : '课程成绩'
|
||||
this.downloadGradesFile(blob, `${this.gradesStudent || '学员'}_${suffix}.xlsx`)
|
||||
this.$message.success(`${suffix}导出成功`)
|
||||
} finally {
|
||||
this.gradesExporting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,6 +821,11 @@ export default {
|
||||
.el-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.student-info-table ::v-deep .cell {
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
@@ -494,5 +847,30 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.student-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
margin-bottom: 8px;
|
||||
color: #303133;
|
||||
line-height: 24px;
|
||||
|
||||
.grades-year-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
|
||||
.filter-label {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,12 +11,26 @@
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="培训类型">
|
||||
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型" clearable @keyup.enter.native="handleSearch" />
|
||||
<el-select v-model="searchForm.pxlx" placeholder="请选择培训类型" clearable filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
<el-form-item label="培训层次">
|
||||
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次" clearable @keyup.enter.native="handleSearch" />
|
||||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
|
||||
@@ -101,12 +115,26 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-input v-model="form.pxlx" placeholder="请输入培训类型" />
|
||||
<el-select v-model="form.pxlx" placeholder="请选择培训类型" filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-input v-model="form.pxcc" placeholder="请输入培训层次" />
|
||||
<el-select v-model="form.pxcc" placeholder="请选择培训层次" filterable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -116,12 +144,10 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="停用" prop="ty">
|
||||
<el-switch v-model="form.ty" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="修改时间" prop="xgsj">
|
||||
<el-date-picker v-model="form.xgsj" type="datetime" placeholder="请选择修改时间" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
|
||||
<el-radio-group v-model="form.ty">
|
||||
<el-radio :label="0">否</el-radio>
|
||||
<el-radio :label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -243,6 +269,10 @@ import {
|
||||
addWarningCondition,
|
||||
updateWarningCondition
|
||||
} from "@/api/studentRecords/warningCondition"
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
|
||||
const TRAINING_TYPE_DICT_CODE = 'train_type'
|
||||
const TRAINING_LEVEL_DICT_CODE = 'train_level'
|
||||
|
||||
export default {
|
||||
name: "WarningCondition",
|
||||
@@ -255,6 +285,8 @@ export default {
|
||||
pxcc: '',
|
||||
ty: 0
|
||||
},
|
||||
trainingTypeOptions: [],
|
||||
trainingLevelOptions: [],
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
loading: false,
|
||||
@@ -272,8 +304,7 @@ export default {
|
||||
formRules: {
|
||||
mc: [{ required: true, message: "名称不能为空", trigger: "blur" }],
|
||||
ty: [{ required: true, message: "停用不能为空", trigger: "change" }],
|
||||
bb: [{ required: true, message: "版本不能为空", trigger: "blur" }],
|
||||
xgsj: [{ required: true, message: "修改时间不能为空", trigger: "change" }]
|
||||
bb: [{ required: true, message: "版本不能为空", trigger: "blur" }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
@@ -283,9 +314,21 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadTrainingDictionaries()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 加载培训类型、培训层次字典。 */
|
||||
async loadTrainingDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [typeResponse, levelResponse] = await Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE).catch(() => emptyResponse)
|
||||
])
|
||||
this.trainingTypeOptions = typeResponse.data || []
|
||||
this.trainingLevelOptions = levelResponse.data || []
|
||||
},
|
||||
|
||||
/** 创建空表单 */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
@@ -305,8 +348,7 @@ export default {
|
||||
qbbjgmssx: undefined,
|
||||
qbbjgmsxx: undefined,
|
||||
ty: 0,
|
||||
bb: '',
|
||||
xgsj: ''
|
||||
bb: ''
|
||||
}
|
||||
},
|
||||
|
||||
@@ -398,7 +440,7 @@ export default {
|
||||
'qbksbjgmssx', 'qbksbjgmsxx',
|
||||
'qbkcbjgmssx', 'qbkcbjgmsxx',
|
||||
'qbbjgmssx', 'qbbjgmsxx',
|
||||
'bb', 'xgsj'
|
||||
'bb'
|
||||
]
|
||||
const result = this.createEmptyForm()
|
||||
fieldKeys.forEach(key => {
|
||||
|
||||
@@ -67,11 +67,17 @@
|
||||
<el-table-column prop="jxgljgbh" label="教学管理机构" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="zydm" label="专业代码" width="200" />
|
||||
<el-table-column prop="zymc" label="专业名称" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="zdyfl" label="分类" width="150" align="center" />
|
||||
<el-table-column prop="xylb" label="学员类别" width="150" align="center" />
|
||||
<el-table-column prop="xnz" label="学年制" width="120" align="center" />
|
||||
<el-table-column prop="xqs" label="学期数" width="120" align="center" />
|
||||
<el-table-column prop="zgzy" label="主干专业" align="center" />
|
||||
<el-table-column label="停用" width="100" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="row.ty === 1 ? 'danger' : 'success'" size="mini">
|
||||
{{ row.ty === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||
@@ -106,98 +112,107 @@
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => formDialogVisible = val"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" label-width="130px" class="add-form">
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="130px" class="add-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规范名称">
|
||||
<el-form-item label="规范名称" prop="gfmc">
|
||||
<el-input v-model="form.gfmc" placeholder="请输入规范名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="序号">
|
||||
<el-input-number v-model="form.xh" :min="0" controls-position="right" style="width: 100%" placeholder="请输入序号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代码">
|
||||
<el-form-item label="专业代码" prop="zydm">
|
||||
<el-input v-model="form.zydm" placeholder="请输入专业代码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业名称">
|
||||
<el-form-item label="专业名称" prop="zymc">
|
||||
<el-input v-model="form.zymc" placeholder="请输入专业名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业方向">
|
||||
<el-form-item label="专业方向" prop="zyfx">
|
||||
<el-input v-model="form.zyfx" placeholder="请输入专业方向" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学年制">
|
||||
<el-form-item label="学年制" prop="xnz">
|
||||
<el-input v-model="form.xnz" placeholder="请输入学年制" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期数">
|
||||
<el-form-item label="学期数" prop="xqs">
|
||||
<el-input-number v-model="form.xqs" :min="0" controls-position="right" style="width: 100%" placeholder="请输入学期数" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训层次">
|
||||
<el-input v-model="form.pxcc" placeholder="请输入培训层次" />
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-select v-model="form.pxcc" placeholder="请选择培训层次" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型">
|
||||
<el-input v-model="form.pxlx" placeholder="请输入培训类型" />
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-select v-model="form.pxlx" placeholder="请选择培训类型" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型2">
|
||||
<el-form-item label="培训类型2" prop="pxlx2">
|
||||
<el-input v-model="form.pxlx2" placeholder="请输入培训类型2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学管理机构编号">
|
||||
<el-form-item label="教学管理机构编号" prop="jxgljgbh">
|
||||
<el-input v-model="form.jxgljgbh" placeholder="请输入教学管理机构编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别">
|
||||
<el-input v-model="form.xylb" placeholder="请输入学员类别" />
|
||||
<el-form-item label="学员类别" prop="xylb">
|
||||
<el-select v-model="form.xylb" placeholder="请选择学员类别" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in studentCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="自定义分类">
|
||||
<el-form-item label="自定义分类" prop="zdyfl">
|
||||
<el-input v-model="form.zdyfl" placeholder="请输入自定义分类" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主干专业">
|
||||
<el-form-item label="主干专业" prop="zgzy">
|
||||
<el-input v-model="form.zgzy" placeholder="请输入主干专业" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="JSON字段">
|
||||
<el-input v-model="form.jsonzd" placeholder="请输入JSON字段" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
@@ -214,7 +229,6 @@
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="规范名称">{{ detailForm.gfmc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="序号">{{ fmtValue(detailForm.xh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">{{ detailForm.zydm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业名称">{{ detailForm.zymc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业方向">{{ detailForm.zyfx || '-' }}</el-descriptions-item>
|
||||
@@ -233,7 +247,6 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="停用时间">{{ detailForm.tysj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="JSON字段" :span="2">{{ detailForm.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -252,6 +265,11 @@ import {
|
||||
updateDiscipline,
|
||||
disableDiscipline
|
||||
} from '@/api/subjectMajor/discipline'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { optionselect } from '@/api/system/dict/type'
|
||||
|
||||
const TRAINING_TYPE_DICT_CODE = 'train_type'
|
||||
const TRAINING_LEVEL_DICT_CODE = 'train_level'
|
||||
|
||||
export default {
|
||||
name: 'DisciplineIndex',
|
||||
@@ -280,6 +298,24 @@ export default {
|
||||
isAdd: true,
|
||||
formSaving: false,
|
||||
form: this.createEmptyForm(),
|
||||
formRules: {
|
||||
gfmc: [{ required: true, message: '请输入规范名称', trigger: 'blur' }],
|
||||
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
|
||||
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
|
||||
zyfx: [{ required: true, message: '请输入专业方向', trigger: 'blur' }],
|
||||
xnz: [{ required: true, message: '请输入学年制', trigger: 'blur' }],
|
||||
xqs: [{ required: true, message: '请输入学期数', trigger: 'change' }],
|
||||
pxcc: [{ required: true, message: '请选择培训层次', trigger: 'change' }],
|
||||
pxlx: [{ required: true, message: '请选择培训类型', trigger: 'change' }],
|
||||
pxlx2: [{ required: true, message: '请输入培训类型2', trigger: 'blur' }],
|
||||
jxgljgbh: [{ required: true, message: '请输入教学管理机构编号', trigger: 'blur' }],
|
||||
xylb: [{ required: true, message: '请选择学员类别', trigger: 'change' }],
|
||||
zdyfl: [{ required: true, message: '请输入自定义分类', trigger: 'blur' }],
|
||||
zgzy: [{ required: true, message: '请输入主干专业', trigger: 'blur' }]
|
||||
},
|
||||
trainingTypeOptions: [],
|
||||
trainingLevelOptions: [],
|
||||
studentCategoryOptions: [],
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailDialogVisible: false,
|
||||
@@ -288,9 +324,38 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDisciplineDictionaries()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 加载学科专业表单字典,业务字段统一提交字典标签。 */
|
||||
async loadDisciplineDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [typeResponse, levelResponse, dictTypeResponse] = await Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE).catch(() => emptyResponse),
|
||||
optionselect().catch(() => emptyResponse)
|
||||
])
|
||||
this.trainingTypeOptions = typeResponse.data || []
|
||||
this.trainingLevelOptions = levelResponse.data || []
|
||||
this.studentCategoryOptions = await this.loadNamedDictionary(dictTypeResponse.data || [], '学员类别')
|
||||
},
|
||||
|
||||
async loadNamedDictionary(dictTypes, dictName) {
|
||||
const dictType = dictTypes.find(item => item.dictName === dictName) ||
|
||||
dictTypes.find(item => String(item.dictName || '').includes(dictName))
|
||||
if (!dictType || !dictType.dictType) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getDicts(dictType.dictType)
|
||||
return response.data || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
/** 序号:按当前页数据索引生成跨页连续的序号 */
|
||||
fmtXh(index) {
|
||||
return (this.pageNum - 1) * this.pageSize + index + 1
|
||||
@@ -300,7 +365,6 @@ export default {
|
||||
return {
|
||||
bsh: '',
|
||||
gfmc: '',
|
||||
xh: undefined,
|
||||
zydm: '',
|
||||
zymc: '',
|
||||
zyfx: '',
|
||||
@@ -391,7 +455,6 @@ export default {
|
||||
this.form = {
|
||||
bsh: data.bsh || '',
|
||||
gfmc: data.gfmc || '',
|
||||
xh: data.xh !== null && data.xh !== undefined ? data.xh : undefined,
|
||||
zydm: data.zydm || '',
|
||||
zymc: data.zymc || '',
|
||||
zyfx: data.zyfx || '',
|
||||
@@ -429,10 +492,7 @@ export default {
|
||||
jsonzd: f.jsonzd,
|
||||
bz: f.bz
|
||||
}
|
||||
// 序号、学期数为数值字段,转为数字后再提交
|
||||
if (f.xh !== '' && f.xh !== null && f.xh !== undefined) {
|
||||
payload.xh = Number(f.xh)
|
||||
}
|
||||
// 学期数为数值字段,转为数字后再提交。
|
||||
if (f.xqs !== '' && f.xqs !== null && f.xqs !== undefined) {
|
||||
payload.xqs = Number(f.xqs)
|
||||
}
|
||||
@@ -440,6 +500,11 @@ export default {
|
||||
},
|
||||
|
||||
handleFormSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
this.formSaving = true
|
||||
const payload = this.buildPayload()
|
||||
const request = this.isAdd ? addDiscipline(payload) : updateDiscipline(payload)
|
||||
@@ -451,6 +516,7 @@ export default {
|
||||
}).finally(() => {
|
||||
this.formSaving = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 停用/启用 ====================
|
||||
|
||||
@@ -96,11 +96,11 @@
|
||||
</el-tag>
|
||||
</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="small" icon="el-icon-view" @click="handleView(scope.row)">详情</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-delete" class="text-danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-delete" class="text-danger" @click="handleDelete(scope.row)">停用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -124,7 +124,7 @@
|
||||
width="900px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="130px" class="form-dialog-form">
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="160px" class="form-dialog-form">
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
@@ -169,12 +169,26 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学科专业信息标识号" prop="xkzyxxbsh">
|
||||
<el-input v-model="form.xkzyxxbsh" placeholder="请输入学科专业信息标识号" />
|
||||
<el-select
|
||||
v-model="form.xkzyxxbsh"
|
||||
:loading="disciplineOptionsLoading"
|
||||
placeholder="请选择学科专业"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in disciplineOptions"
|
||||
:key="item.bsh"
|
||||
:label="formatDisciplineLabel(item)"
|
||||
:value="item.bsh"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学管理机构编号">
|
||||
<el-input v-model="form.jxgljgbh" placeholder="请输入教学管理机构编号" />
|
||||
<el-form-item label="教学管理机构">
|
||||
<el-input v-model="form.jxgljgbh" placeholder="请输入教学管理机构" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -193,12 +207,26 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-input v-model="form.pxcc" placeholder="请输入培训层次" />
|
||||
<el-select v-model="form.pxcc" placeholder="请选择培训层次" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in trainingLevelOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-input v-model="form.pxlx" placeholder="请输入培训类型" />
|
||||
<el-select v-model="form.pxlx" placeholder="请选择培训类型" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in trainingTypeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -208,7 +236,14 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别" prop="xylb">
|
||||
<el-input v-model="form.xylb" placeholder="请输入学员类别" />
|
||||
<el-select v-model="form.xylb" placeholder="请选择学员类别" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in studentCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -226,12 +261,26 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="节次类别" prop="jclb">
|
||||
<el-input v-model="form.jclb" placeholder="请输入节次类别" />
|
||||
<el-select v-model="form.jclb" placeholder="请选择节次类别" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in sessionCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="系统模式" prop="xtms">
|
||||
<el-input v-model="form.xtms" placeholder="请输入系统模式" />
|
||||
<el-select v-model="form.xtms" placeholder="请选择系统模式" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="item in systemModeOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -253,11 +302,6 @@
|
||||
<el-input-number v-model="form.xhbs" :min="0" controls-position="right" style="width: 100%" placeholder="请输入序号标识" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="JSON字段">
|
||||
<el-input v-model="form.jsonzd" placeholder="请输入JSON字段" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="停用" prop="ty">
|
||||
<el-switch v-model="form.ty" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||
@@ -330,7 +374,6 @@
|
||||
{{ viewForm.ty === 1 || viewForm.ty === true ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="JSON字段">{{ viewForm.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培养目标" :span="2">{{ viewForm.pymb || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业备注" :span="2">{{ viewForm.zybz || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业规范" :span="2">{{ viewForm.zygf || '-' }}</el-descriptions-item>
|
||||
@@ -352,6 +395,15 @@ import {
|
||||
delMajor,
|
||||
downloadMajorTemplate
|
||||
} from "@/api/subjectMajor/major"
|
||||
import { listDiscipline } from '@/api/subjectMajor/discipline'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { optionselect } from '@/api/system/dict/type'
|
||||
|
||||
const TRAINING_TYPE_DICT_CODE = 'train_type'
|
||||
const TRAINING_LEVEL_DICT_CODE = 'train_level'
|
||||
const SESSION_CATEGORY_DICT_CODE = 'session_category'
|
||||
const SYSTEM_MODE_DICT_CODE = 'system_mode'
|
||||
const DISCIPLINE_OPTION_PAGE_SIZE = 10000
|
||||
|
||||
export default {
|
||||
name: "Major",
|
||||
@@ -383,6 +435,13 @@ export default {
|
||||
isAdd: true,
|
||||
formSaving: false,
|
||||
form: this.createEmptyForm(),
|
||||
trainingTypeOptions: [],
|
||||
trainingLevelOptions: [],
|
||||
studentCategoryOptions: [],
|
||||
sessionCategoryOptions: [],
|
||||
systemModeOptions: [],
|
||||
disciplineOptions: [],
|
||||
disciplineOptionsLoading: false,
|
||||
formRules: {
|
||||
zydh: [{ required: true, message: "专业代号不能为空", trigger: "blur" }],
|
||||
zymc: [{ required: true, message: "专业名称不能为空", trigger: "blur" }],
|
||||
@@ -392,14 +451,14 @@ export default {
|
||||
xnz: [{ required: true, message: "学年制不能为空", trigger: "blur" }],
|
||||
xqs: [{ required: true, message: "学期数不能为空", trigger: "change" }],
|
||||
zybsh: [{ required: true, message: "专业标识号不能为空", trigger: "blur" }],
|
||||
pxcc: [{ required: true, message: "培训层次不能为空", trigger: "blur" }],
|
||||
pxlx: [{ required: true, message: "培训类型不能为空", trigger: "blur" }],
|
||||
pxcc: [{ required: true, message: "请选择培训层次", trigger: "change" }],
|
||||
pxlx: [{ required: true, message: "请选择培训类型", trigger: "change" }],
|
||||
pxlx2: [{ required: true, message: "培训类型2不能为空", trigger: "blur" }],
|
||||
xylb: [{ required: true, message: "学员类别不能为空", trigger: "blur" }],
|
||||
xylb: [{ required: true, message: "请选择学员类别", trigger: "change" }],
|
||||
zdyfl: [{ required: true, message: "自定义分类不能为空", trigger: "blur" }],
|
||||
zgzy: [{ required: true, message: "主干专业不能为空", trigger: "change" }],
|
||||
jclb: [{ required: true, message: "节次类别不能为空", trigger: "blur" }],
|
||||
xtms: [{ required: true, message: "系统模式不能为空", trigger: "blur" }]
|
||||
jclb: [{ required: true, message: "请选择节次类别", trigger: "change" }],
|
||||
xtms: [{ required: true, message: "请选择系统模式", trigger: "change" }]
|
||||
},
|
||||
|
||||
// ==================== 详情 ====================
|
||||
@@ -409,9 +468,70 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadMajorDictionaries()
|
||||
this.loadDisciplineOptions()
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 加载专业表单中已配置的字典项,文本业务字段统一提交字典标签。 */
|
||||
async loadMajorDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [typeResponse, levelResponse, sessionResponse, modeResponse, dictTypeResponse] = await Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(SESSION_CATEGORY_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(SYSTEM_MODE_DICT_CODE).catch(() => emptyResponse),
|
||||
optionselect().catch(() => emptyResponse)
|
||||
])
|
||||
this.trainingTypeOptions = typeResponse.data || []
|
||||
this.trainingLevelOptions = levelResponse.data || []
|
||||
this.sessionCategoryOptions = sessionResponse.data || []
|
||||
this.systemModeOptions = modeResponse.data || []
|
||||
|
||||
const dictTypes = dictTypeResponse.data || []
|
||||
this.studentCategoryOptions = await this.loadNamedDictionary(dictTypes, '学员类别')
|
||||
},
|
||||
|
||||
async loadNamedDictionary(dictTypes, dictName) {
|
||||
const dictType = dictTypes.find(item => item.dictName === dictName) ||
|
||||
dictTypes.find(item => String(item.dictName || '').includes(dictName))
|
||||
if (!dictType || !dictType.dictType) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getDicts(dictType.dictType)
|
||||
return response.data || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
/** 复用学科专业分页接口加载启用数据,作为全量下拉选项。 */
|
||||
async loadDisciplineOptions() {
|
||||
this.disciplineOptionsLoading = true
|
||||
try {
|
||||
const response = await listDiscipline({
|
||||
pageNum: 1,
|
||||
pageSize: DISCIPLINE_OPTION_PAGE_SIZE,
|
||||
ty: 0
|
||||
})
|
||||
const data = response.data || {}
|
||||
this.disciplineOptions = Array.isArray(data.records) ? data.records : []
|
||||
} catch (error) {
|
||||
this.disciplineOptions = []
|
||||
} finally {
|
||||
this.disciplineOptionsLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
formatDisciplineLabel(item) {
|
||||
const name = item.zymc || item.gfmc || item.zydm || item.bsh
|
||||
const identifiers = [item.zydm, item.bsh].filter(Boolean).join(' / ')
|
||||
|
||||
return identifiers && identifiers !== name ? `${name}(${identifiers})` : name
|
||||
},
|
||||
|
||||
/** 表格序号(跨页连续) */
|
||||
fmtXh(index) {
|
||||
return (this.pageNum - 1) * this.pageSize + index + 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="app-container system-dict-data">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="字典名称" prop="dictType">
|
||||
<el-select v-model="queryParams.dictType">
|
||||
<el-select v-model="queryParams.dictType" filterable>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.dictId"
|
||||
|
||||
@@ -141,14 +141,14 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位">
|
||||
<el-select v-model="form.postIds" multiple placeholder="请选择岗位">
|
||||
<el-select v-model="form.postIds" multiple filterable placeholder="请选择岗位">
|
||||
<el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" :disabled="item.status == 1" ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="角色">
|
||||
<el-select v-model="form.roleIds" multiple placeholder="请选择角色">
|
||||
<el-select v-model="form.roleIds" multiple filterable placeholder="请选择角色">
|
||||
<el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId" :disabled="item.status == 1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
:data-key="cellKey(wIdx, col.colIndex)"
|
||||
@dblclick="onCellDblClick(wIdx, col)"
|
||||
>
|
||||
<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-if="getEvent(wIdx, col.colIndex) && getEvent(wIdx, col.colIndex).name" 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>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
<script>
|
||||
import { getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
|
||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const SLOT_DEFS = [
|
||||
@@ -123,6 +123,16 @@ function fmtDate(d) {
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
}
|
||||
|
||||
// 生成 32 位小写 UUID(去横线),与后端 UuidUtil.getUUID 一致,用于 bh 缺失时兜底
|
||||
function genUUID() {
|
||||
let s = ''
|
||||
const chars = '0123456789abcdef'
|
||||
for (let i = 0; i < 32; i++) {
|
||||
s += chars[Math.floor(Math.random() * 16)]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SchoolCalendarEditor',
|
||||
props: {
|
||||
@@ -134,6 +144,8 @@ export default {
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
// 日历渲染起点(学期当周周一)
|
||||
calendarStart: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
@@ -180,9 +192,10 @@ export default {
|
||||
})
|
||||
return cols
|
||||
},
|
||||
// xqxlb 的年度(nd)为年份(如 2026),由 6 位学期代号(如 202601)截取前 4 位得出
|
||||
// xqxlb 的 nd 后端已统一为「6 位学期代号」(如 202701),与 this.nd 一致。
|
||||
// 不再按日历起点/季度取年份,避免跨年度学期解析出错。
|
||||
xqxlbNd() {
|
||||
return Number(String(this.nd).slice(0, 4))
|
||||
return Number(String(this.nd).slice(0, 6)) || Number(this.nd) || 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -248,6 +261,7 @@ export default {
|
||||
if (kx && jx) {
|
||||
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||
this.calendarStart = null
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
@@ -257,16 +271,34 @@ export default {
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位)
|
||||
// XQXLB 的 nd 存的是「年份」(如 2026),春/夏/秋三学期共用同一批年度记录;
|
||||
// 后端 updateById 只能改已存在的 bh,所以每个格子必须有预生成的记录才能存。
|
||||
// 若本学期日期区间 [kxrq, jsrq] 内还没有任何占位记录(首次进入该学期),
|
||||
// 调用 /xqxlb/add 只按本学期 kxrq/jsrq 预生成这一张网格,再重新拉取,
|
||||
// 这样后续 /xqxlb/update 的 updateById 才能命中每一格。(区间有覆盖则跳过,幂等)
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: this.xqxlbNd }).then(res => {
|
||||
const fetchList = () => listXqxlb({ nd: this.xqxlbNd }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
return list
|
||||
}).then(list => {
|
||||
const loaded = {}
|
||||
return Array.isArray(data) ? data : (data && data.records) || []
|
||||
})
|
||||
const render = (list) => {
|
||||
// 后端 add() 的按「月日」去重对已存的全量日期不生效,同一年度多次打开/刷新会
|
||||
// 积累同一格的重复占位记录(各带不同 bh)。updateById 只改其中一行,其余空记录
|
||||
// 会在重新拉取时把刚保存的事件名覆盖掉。故这里按「周-星期-节次」先去重:
|
||||
// 同一格保留有事件名(jqmc非空)的一条;都没有则保留最后一条兜底(保证有 bh)。
|
||||
const unique = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (!pos) return
|
||||
const stable = this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)
|
||||
const prev = unique[stable]
|
||||
if (!prev || (item.jqmc && !prev.item.jqmc)) {
|
||||
unique[stable] = { item: item, pos: pos }
|
||||
}
|
||||
})
|
||||
const loaded = {}
|
||||
Object.keys(unique).forEach(stable => {
|
||||
const { item, pos } = unique[stable]
|
||||
const ev = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
@@ -286,16 +318,33 @@ export default {
|
||||
})
|
||||
// 合并到现有格子(后端数据覆盖已有状态)
|
||||
this.events = Object.assign({}, this.events, loaded)
|
||||
}).catch(() => {
|
||||
}
|
||||
fetchList().then(list => {
|
||||
const hasCoverage = list.some(item => this.isInRange(item.jqsj))
|
||||
if (!hasCoverage && this.startDate && this.endDate) {
|
||||
// 本学期区间尚无占位记录:仅用本期 kxrq/jsrq 预生成整张网格
|
||||
const startStr = fmtDate(this.startDate)
|
||||
const endStr = fmtDate(this.endDate)
|
||||
return addXqxlb(startStr, endStr).then(() => fetchList())
|
||||
}
|
||||
return list
|
||||
}).then(render).catch(() => {
|
||||
this.$message.warning('校历事件加载失败')
|
||||
})
|
||||
},
|
||||
// 判断某条记录(jqsj 日期)是否落在本学期 [startDate, endDate] 区间内
|
||||
isInRange(jqsj) {
|
||||
if (!this.startDate || !this.endDate || !jqsj) return false
|
||||
const d = new Date(String(jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return false
|
||||
return d >= this.startDate && d <= this.endDate
|
||||
},
|
||||
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
if (!this.calendarStart || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
const offset = Math.round((d - this.calendarStart) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
@@ -315,13 +364,22 @@ export default {
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
// 获取日期所在周的周一(周一为一周起点)
|
||||
getMonday(d) {
|
||||
const date = new Date(d)
|
||||
const day = date.getDay() // 0=周日,1=周一,...,6=周六
|
||||
const diff = date.getDate() - day + (day === 0 ? -6 : 1)
|
||||
date.setDate(diff)
|
||||
return date
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const start = this.getMonday(new Date(this.startDate))
|
||||
const end = new Date(this.endDate)
|
||||
this.calendarStart = start
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
this.dateRangeText = fmtDate(new Date(this.startDate)) + ' 到 ' + fmtDate(new Date(this.endDate))
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
@@ -355,21 +413,18 @@ export default {
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
if (!this.calendarStart) return ''
|
||||
const d = new Date(this.calendarStart)
|
||||
d.setDate(this.calendarStart.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('sce-cell-odd')
|
||||
else classes.push('sce-cell-even')
|
||||
if (this.selectedKeys.includes(this.cellKey(wIdx, col.colIndex))) classes.push('is-selected')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
// 可排课的全部白色;不可排课且事件名非空才标红
|
||||
if (ev && ev.name && !ev.schedulable) {
|
||||
classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
@@ -467,15 +522,18 @@ export default {
|
||||
return
|
||||
}
|
||||
const name = this.toolbar.eventName.trim()
|
||||
// 设定时未填写事件名:提示,不触发清除
|
||||
if (!name) {
|
||||
this.$message.warning('请输入事件名称')
|
||||
this.$message.warning('请输入事件名')
|
||||
return
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
keys.forEach(key => {
|
||||
const prev = this.events[key]
|
||||
this.$set(this.events, key, {
|
||||
bh: prev ? prev.bh : undefined,
|
||||
// 编辑已有事件:沿用后端返回的 bh;新增事件:bh 由 loadXqxlbEvents
|
||||
// 通过 /xqxlb/add 预生成的占位行提供(首次进入学期时已落库)
|
||||
bh: prev && prev.bh ? prev.bh : undefined,
|
||||
name: name,
|
||||
bold: this.toolbar.bold,
|
||||
schedulable: this.toolbar.schedulable,
|
||||
@@ -491,17 +549,27 @@ export default {
|
||||
this.$message.warning('请先选择时间格')
|
||||
return
|
||||
}
|
||||
this.$confirm('确认删除选中单元格的事件吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const keys = [...this.selectedKeys]
|
||||
this.persistDeleteEvents(keys).then(({ ok, fail }) => {
|
||||
if (fail === 0) {
|
||||
keys.forEach(key => {
|
||||
this.$delete(this.events, key)
|
||||
})
|
||||
this.$message.success('已删除 ' + keys.length + ' 个时间格事件')
|
||||
this.selectedKeys = []
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
absorbEvent() {
|
||||
// 吸取:取选择中第一个有事件的格
|
||||
// 吸取:取选择中第一个事件名非空的格
|
||||
let target = null
|
||||
for (const key of this.selectedKeys) {
|
||||
if (this.events[key]) { target = this.events[key]; break }
|
||||
if (this.events[key] && this.events[key].name) { target = this.events[key]; break }
|
||||
}
|
||||
if (!target) {
|
||||
this.$message.warning('所选时间格中无事件可吸取')
|
||||
@@ -517,7 +585,7 @@ export default {
|
||||
},
|
||||
onCellDblClick(wIdx, col) {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
if (ev && ev.name) {
|
||||
this.toolbar.eventName = ev.name
|
||||
this.toolbar.bold = !!ev.bold
|
||||
this.toolbar.schedulable = !!ev.schedulable
|
||||
@@ -545,8 +613,14 @@ export default {
|
||||
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||
if (!payload) return Promise.resolve(false)
|
||||
return updateXqxlb(payload)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
.then(() => {
|
||||
console.warn('[设定] key=', key, 'bh来源=', this.events[key] && this.events[key].bh ? '已有' : '生成', 'bh=', payload.bh, 'nd=', payload.nd, 'name=', payload.jqmc)
|
||||
return true
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('[设定] key=', key, '保存失败', err && (err.message || err))
|
||||
return false
|
||||
})
|
||||
})
|
||||
return Promise.all(tasks).then(results => {
|
||||
const ok = results.filter(r => r === true).length
|
||||
@@ -561,15 +635,42 @@ export default {
|
||||
return { ok: ok, fail: fail }
|
||||
})
|
||||
},
|
||||
// 批量删除选中格事件:调用 update 接口并将 jqmc 置空完成删除
|
||||
persistDeleteEvents(keys) {
|
||||
const tasks = keys
|
||||
.filter(key => this.events[key])
|
||||
.map(key => {
|
||||
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||
if (!payload) return Promise.resolve(false)
|
||||
payload.jqmc = ''
|
||||
return updateXqxlb(payload)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
})
|
||||
return Promise.all(tasks).then(results => {
|
||||
const ok = results.filter(r => r === true).length
|
||||
const fail = results.length - ok
|
||||
if (fail > 0) {
|
||||
this.$message.error(fail + ' 个事件删除失败,请检查后端接口')
|
||||
} else if (ok > 0) {
|
||||
this.$message.success('已删除 ' + ok + ' 个时间格事件')
|
||||
}
|
||||
// 删除成功后重新拉取,同步后端状态
|
||||
this.loadXqxlbEvents()
|
||||
return { ok: ok, fail: fail }
|
||||
})
|
||||
},
|
||||
// 构造 xqxlb 提交数据
|
||||
buildXqxlbPayload(key, ev) {
|
||||
const pos = this.parseCellKey(key)
|
||||
if (!pos) return null
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
||||
const d = new Date(this.calendarStart)
|
||||
d.setDate(this.calendarStart.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
||||
return {
|
||||
delFlag: 0,
|
||||
bh: ev.bh || undefined,
|
||||
// bh 编号必须传:有后端记录则沿用其 bh,否则前端生成 UUID 兜底(避免 payload 缺主键导致 updateById 失效)
|
||||
bh: ev.bh || genUUID(),
|
||||
// nd 后端已统一为 6 位学期代号(如 202701),与查询口径一致,不再取记录日期的年份
|
||||
nd: this.xqxlbNd,
|
||||
jqsj: fmtDate(d),
|
||||
jqmc: ev.name || '',
|
||||
@@ -722,14 +823,20 @@ export default {
|
||||
background: #fff;
|
||||
transition: background 0.15s;
|
||||
|
||||
.sce-date-num { color: #c0c4cc; }
|
||||
.sce-event-name {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白,便于区分星期
|
||||
&.sce-cell-odd { background: #eef6f1; }
|
||||
&.sce-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .sce-event-name { color: #00663e; font-weight: 500; }
|
||||
&.has-event .sce-event-name.is-bold { font-weight: 700; }
|
||||
.sce-date-num {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: #c0c4cc;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// 可排课白色,不可排课且有事件标红
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
|
||||
&.is-selected {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
class="cal-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
>
|
||||
<template v-if="getEvent(wIdx, col.colIndex)">
|
||||
<template v-if="getEvent(wIdx, col.colIndex) && getEvent(wIdx, col.colIndex).name">
|
||||
<span class="cal-event-name">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
</template>
|
||||
<span v-else-if="showDate" class="cal-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
@@ -102,6 +102,8 @@ export default {
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
// 日历渲染起点(学期当周周一)
|
||||
calendarStart: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
@@ -173,6 +175,7 @@ export default {
|
||||
if (!this.nd) return
|
||||
this.startDate = null
|
||||
this.endDate = null
|
||||
this.calendarStart = null
|
||||
getSemester(this.nd)
|
||||
.then(res => {
|
||||
const data = res.data || res || {}
|
||||
@@ -189,16 +192,29 @@ export default {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位)
|
||||
// 从后端拉取校历事件,渲染到对应时间格
|
||||
// xqxlb.nd 后端已统一为 6 位学期代号(如 202701),与 this.nd 一致,一次查询即可
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {
|
||||
if (!this.calendarStart || !this.nd) return
|
||||
listXqxlb({ nd: Number(String(this.nd).slice(0, 6)) || Number(this.nd) }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
const loaded = {}
|
||||
// 同一格可能有多条(后端 add() 去重失效累积的重复占位)。
|
||||
// 按格子去重:同一格优先保留「有事件名(jqmc非空)」的那条,避免空占位把事件盖成空白。
|
||||
const unique = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (pos) {
|
||||
loaded[pos.key] = {
|
||||
if (!pos) return
|
||||
const prev = unique[pos.key]
|
||||
const has = !!item.jqmc
|
||||
if (!prev || (has && !prev.has)) {
|
||||
unique[pos.key] = { has: has, item: item }
|
||||
}
|
||||
})
|
||||
Object.keys(unique).forEach(key => {
|
||||
const { item } = unique[key]
|
||||
loaded[key] = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
schedulable: !!item.kpk,
|
||||
@@ -206,7 +222,6 @@ export default {
|
||||
remarkShow: !!item.bzxs,
|
||||
remark: item.bz || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
this.events = loaded
|
||||
}).catch(() => {
|
||||
@@ -215,10 +230,10 @@ export default {
|
||||
},
|
||||
// 根据假期记录反推时间格 key(jqsj 日期 + courseClass 节次)
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
if (!this.calendarStart || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
const offset = Math.round((d - this.calendarStart) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
@@ -240,13 +255,22 @@ export default {
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
// 获取日期所在周的周一(周一为一周起点)
|
||||
getMonday(d) {
|
||||
const date = new Date(d)
|
||||
const day = date.getDay() // 0=周日,1=周一,...,6=周六
|
||||
const diff = date.getDate() - day + (day === 0 ? -6 : 1)
|
||||
date.setDate(diff)
|
||||
return date
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const start = this.getMonday(new Date(this.startDate))
|
||||
const end = new Date(this.endDate)
|
||||
this.calendarStart = start
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
this.dateRangeText = fmtDate(new Date(this.startDate)) + ' 到 ' + fmtDate(new Date(this.endDate))
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
@@ -276,20 +300,17 @@ export default {
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
if (!this.calendarStart) return ''
|
||||
const d = new Date(this.calendarStart)
|
||||
d.setDate(this.calendarStart.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('cal-cell-odd')
|
||||
else classes.push('cal-cell-even')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
// 可排课的全部白色;不可排课且事件名非空才标红
|
||||
if (ev && ev.name && !ev.schedulable) {
|
||||
classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
@@ -339,7 +360,7 @@ export default {
|
||||
html += '<td>' + week.rangeText + '</td>'
|
||||
this.visibleColumns.forEach(col => {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
if (ev && ev.name) {
|
||||
const remark = ev.remark && ev.remarkShow ? '(' + ev.remark + ')' : ''
|
||||
html += '<td>' + ev.name + remark + '</td>'
|
||||
} else if (this.showDate) {
|
||||
@@ -466,13 +487,20 @@ export default {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
.cal-date-num { color: #c0c4cc; }
|
||||
.cal-event-name {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白
|
||||
&.cal-cell-odd { background: #eef6f1; }
|
||||
&.cal-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .cal-event-name { color: #00663e; font-weight: 500; }
|
||||
.cal-date-num {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: #c0c4cc;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// 可排课白色,不可排课且有事件标红
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="teach-calendar app-container">
|
||||
<school-calendar-editor
|
||||
v-if="nd"
|
||||
:key="nd"
|
||||
:nd="nd"
|
||||
:semester-name="semesterName"
|
||||
@close="handleClose"
|
||||
@@ -26,6 +27,16 @@ export default {
|
||||
this.nd = this.$route.query.nd || ''
|
||||
this.semesterName = this.$route.query.name || ''
|
||||
},
|
||||
watch: {
|
||||
// 编辑校历页被 keep-alive 缓存复用:同路径下 query.nd 变化时不会重新走 created(),
|
||||
// 需在此同步到 nd,并通过 :key="nd" 让编辑器随学期切换全新挂载,读取正确学期数据。
|
||||
'$route.query.nd'(val) {
|
||||
if (val && String(val) !== String(this.nd)) {
|
||||
this.nd = val
|
||||
this.semesterName = this.$route.query.name || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
|
||||
@@ -6,22 +6,16 @@
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.8">
|
||||
<el-button type="success" plain icon="el-icon-date" size="mini" :disabled="!currentSemester"
|
||||
<el-button type="success" plain icon="el-icon-date" size="mini"
|
||||
@click="handleEditCalendar">
|
||||
编辑校历
|
||||
</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-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<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" />
|
||||
@current-change="handleCurrentChange">
|
||||
<el-table-column label="学期名称" align="center" width="250">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getSemesterName(scope.row.nd) }}</span>
|
||||
@@ -43,18 +37,18 @@
|
||||
<span>{{ formatDate(scope.row.jsrq) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周数" align="center" prop="sdzs" width="100" />
|
||||
<el-table-column label="调课不审批" align="center" width="120">
|
||||
<el-table-column label="周数" align="center" prop="sdzs" width="55" />
|
||||
<el-table-column label="调课不审批" align="center" width="100">
|
||||
<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="120">
|
||||
<el-table-column label="禁止调课" align="center" width="80">
|
||||
<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="160">
|
||||
<el-table-column label="终结成绩定及格" align="center" width="130">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
|
||||
}}</el-tag>
|
||||
@@ -214,14 +208,8 @@ export default {
|
||||
total: 0,
|
||||
// 学期列表数据
|
||||
semesterList: [],
|
||||
// 选中的学期数组
|
||||
ids: [],
|
||||
// 当前点击选中的学期
|
||||
currentSemester: null,
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
@@ -275,16 +263,27 @@ export default {
|
||||
beforeDestroy() {
|
||||
this.$root.$off('semester-current-changed', this.handleCurrentChanged)
|
||||
},
|
||||
watch: {
|
||||
'form.kxrq': 'calculateWeeks',
|
||||
'form.jsrq': 'calculateWeeks',
|
||||
// 新增时选择学期类型后,自动带出开学/结束日期
|
||||
'form.xq'(val) {
|
||||
if (this.isAdd) this.autoFillDatesForXq()
|
||||
},
|
||||
'form.xn'(val) {
|
||||
if (this.isAdd) this.autoFillDatesForXq()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 顶栏切换当前学期后自动刷新 */
|
||||
handleCurrentChanged() {
|
||||
this.getList()
|
||||
},
|
||||
/** 初始化学年选项 */
|
||||
/** 初始化学年选项(新增时只能从当前年份往后选择) */
|
||||
initYearOptions() {
|
||||
const current = new Date().getFullYear()
|
||||
const list = []
|
||||
for (let i = current - 10; i <= current + 10; i++) {
|
||||
for (let i = current; i <= current + 10; i++) {
|
||||
list.push(i)
|
||||
}
|
||||
this.yearOptions = list
|
||||
@@ -294,6 +293,23 @@ export default {
|
||||
if (xn === undefined || xn === null || xn === '' || !xq) return ''
|
||||
return String(xn) + String(xq)
|
||||
},
|
||||
/** 根据学年+学期类型自动带出开学/结束日期(春季01:2-5月,夏季02:6-8月,秋季03:9-12月) */
|
||||
autoFillDatesForXq() {
|
||||
const xn = this.form.xn
|
||||
const xq = this.form.xq
|
||||
if (xn === undefined || xn === null || xn === '') return
|
||||
const year = Number(xn)
|
||||
const pad = m => `${year}-${String(m).padStart(2, '0')}`
|
||||
const rules = {
|
||||
'01': { start: pad(2) + '-01', end: pad(5) + '-31' },
|
||||
'02': { start: pad(6) + '-01', end: pad(8) + '-31' },
|
||||
'03': { start: pad(9) + '-01', end: pad(12) + '-31' }
|
||||
}
|
||||
const rule = rules[xq]
|
||||
if (!rule) return
|
||||
this.form.kxrq = rule.start
|
||||
this.form.jsrq = rule.end
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||
getSemesterName(nd) {
|
||||
if (!nd) return ''
|
||||
@@ -331,6 +347,22 @@ export default {
|
||||
if (!value) return ''
|
||||
return String(value).slice(0, 10)
|
||||
},
|
||||
/** 根据开学日期和结束日期自动计算学期周数 */
|
||||
calculateWeeks() {
|
||||
const kxrq = this.form.kxrq
|
||||
const jsrq = this.form.jsrq
|
||||
if (!kxrq || !jsrq) return
|
||||
const start = new Date(String(kxrq).slice(0, 10).replace(/-/g, '/'))
|
||||
const end = new Date(String(jsrq).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(start.getTime()) || isNaN(end.getTime())) return
|
||||
if (end < start) return
|
||||
// 以开学当周周一为起点,向上取整到周
|
||||
const day = start.getDay()
|
||||
const monday = new Date(start)
|
||||
monday.setDate(start.getDate() - (day === 0 ? 6 : day - 1))
|
||||
const days = Math.round((end - monday) / (24 * 3600 * 1000)) + 1
|
||||
this.$set(this.form, 'sdzs', Math.max(1, Math.ceil(days / 7)))
|
||||
},
|
||||
/** 查询学期列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
@@ -345,12 +377,6 @@ export default {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 多选变化 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.nd)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */
|
||||
handleCurrentChange(currentRow) {
|
||||
this.currentSemester = currentRow || null
|
||||
@@ -385,7 +411,7 @@ export default {
|
||||
},
|
||||
/** 编辑按钮 */
|
||||
handleUpdate(row) {
|
||||
const semesterId = row && row.nd ? row.nd : this.ids[0]
|
||||
const semesterId = row && row.nd ? row.nd : ''
|
||||
if (!semesterId) return
|
||||
this.reset()
|
||||
this.isAdd = false
|
||||
@@ -423,21 +449,21 @@ export default {
|
||||
/** 编辑校历按钮:对当前选中的学期编辑校历 */
|
||||
handleEditCalendar() {
|
||||
const row = this.currentSemester
|
||||
if (!row || !row.nd) return
|
||||
if (!row || !row.nd) {
|
||||
this.$modal.msgWarning('请选择学期')
|
||||
return
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/teachBusiness/semester/semesterCalendar',
|
||||
query: { nd: row.nd, name: this.getSemesterName(row.nd) }
|
||||
})
|
||||
},
|
||||
/** 删除按钮 */
|
||||
/** 删除按钮:单行删除 */
|
||||
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 = semesterIds.map(nd => delSemester(nd))
|
||||
return Promise.all(delList)
|
||||
if (!row || !row.nd) return
|
||||
const name = this.getSemesterName(row.nd)
|
||||
this.$modal.confirm('确认删除学期【' + name + '】吗?').then(() => {
|
||||
return delSemester(row.nd)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
|
||||
@@ -46,10 +46,13 @@
|
||||
<el-table v-loading="loading" :data="pagedData" border stripe highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="bh" label="编号" width="170" show-overflow-tooltip align="center" />
|
||||
<el-table-column type="index" label="序号" width="50" align="center" />
|
||||
<el-table-column prop="zydh" label="专业代号" width="150" align="center" />
|
||||
<el-table-column prop="kbh" label="课编号" width="110" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="jc" label="简称" width="100" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="kcdw" label="课程定位" width="120" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="mk" label="模块" width="100" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="jysdh" label="教研室" width="100" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="klx" label="课类型" width="80" align="center" />
|
||||
<el-table-column prop="xqdc" label="学期第次" width="80" align="center" />
|
||||
<el-table-column prop="xs" label="学时" width="70" align="center" />
|
||||
@@ -58,6 +61,13 @@
|
||||
<el-table-column prop="sjxs" label="实践学时" width="80" align="center" />
|
||||
<el-table-column prop="zks" label="周课时" width="70" align="center" />
|
||||
<el-table-column prop="ksks" label="考试课时" width="80" align="center" />
|
||||
<el-table-column prop="cjfz" label="成绩分制" width="90" align="center" />
|
||||
<el-table-column label="不计入平均分" width="110" align="center">
|
||||
<template slot-scope="scope">{{ fmtYesNo(scope.row.bjrxypjf) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考试课时不显示" width="130" align="center">
|
||||
<template slot-scope="scope">{{ fmtYesNo(scope.row.ksksbxs) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="大纲课程" width="90" align="center">
|
||||
<template slot-scope="scope">{{ fmtYesNo(scope.row.dgkc) }}</template>
|
||||
</el-table-column>
|
||||
@@ -278,8 +288,7 @@ import {
|
||||
deleteSyllabus,
|
||||
batchDeleteSyllabus,
|
||||
updateSyllabus,
|
||||
listSyllabus,
|
||||
listSyllabusByZydhAndTy
|
||||
listByZydhAndTy
|
||||
} from '@/api/teachBusiness/syllabus'
|
||||
import { listMajor } from '@/api/subjectMajor/major'
|
||||
import { listKb } from '@/api/teachOffice/kb'
|
||||
@@ -302,6 +311,7 @@ export default {
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
localPaging: false,
|
||||
selection: [],
|
||||
referenceDataLoading: false,
|
||||
majorOptions: [],
|
||||
@@ -326,6 +336,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
pagedData() {
|
||||
if (!this.localPaging) {
|
||||
return this.tableData
|
||||
}
|
||||
const start = (this.pageNum - 1) * this.pageSize
|
||||
return this.tableData.slice(start, start + this.pageSize)
|
||||
}
|
||||
@@ -380,21 +393,11 @@ export default {
|
||||
/* ---------- 列表加载 ---------- */
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
const { zydh, ty } = this.searchForm
|
||||
// 专业代号存在时走后端组合查询;否则查全部后按停用标识过滤
|
||||
const useApiQuery = Boolean(zydh)
|
||||
const req = useApiQuery
|
||||
? listSyllabusByZydhAndTy(zydh, ty)
|
||||
: listSyllabus()
|
||||
req.then(response => {
|
||||
let list = response.data || []
|
||||
if (!useApiQuery) {
|
||||
if (zydh) list = list.filter(i => i.zydh && i.zydh.indexOf(zydh) !== -1)
|
||||
list = list.filter(i => Number(i.ty) === Number(ty))
|
||||
}
|
||||
this.tableData = list
|
||||
this.total = list.length
|
||||
this.pageNum = 1
|
||||
listByZydhAndTy({ zydh: this.searchForm.zydh, ty: this.searchForm.ty }).then(response => {
|
||||
const data = response.data || []
|
||||
this.tableData = Array.isArray(data) ? data : (data.records || [])
|
||||
this.total = this.tableData.length
|
||||
this.localPaging = true
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
@@ -403,10 +406,12 @@ export default {
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = { zydh: '', ty: 0 }
|
||||
this.pageNum = 1
|
||||
this.fetchList()
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
@@ -487,9 +492,15 @@ export default {
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size
|
||||
this.pageNum = 1
|
||||
if (!this.localPaging) {
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pageNum = page
|
||||
if (!this.localPaging) {
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
v-model="searchForm.nd"
|
||||
placeholder="请选择年度"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
<div class="section-card">
|
||||
<el-form :model="queryParams" :inline="true" size="small" class="search-form" @submit.native.prevent>
|
||||
<el-form-item label="年度">
|
||||
<el-select v-model="queryParams.nd" placeholder="请选择年度" clearable style="width: 140px">
|
||||
<el-select
|
||||
v-model="queryParams.nd"
|
||||
placeholder="请选择年度"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -80,7 +86,7 @@
|
||||
<div class="ops-cell">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
|
||||
<el-button v-if="showAuditMenu(scope.row)" type="text" size="mini" @click="openAudit(scope.row)">审批</el-button>
|
||||
<el-button v-if="scope.row.sczt === 0 && scope.row.jyspzzt !== 1" type="text" size="mini" class="danger-btn" @click="handleCancel(scope.row)">撤销</el-button>
|
||||
<el-button v-if="scope.row.sczt === 0 && scope.row.jyspzzt !== 1 && scope.row.jyspzzt !== 3" type="text" size="mini" class="danger-btn" @click="handleCancel(scope.row)">撤销</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -99,7 +105,13 @@
|
||||
<el-dialog title="新增课表调整申请" :visible.sync="addVisible" width="640px" append-to-body>
|
||||
<el-form ref="addForm" :model="addForm" :rules="addRules" label-width="110px">
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="addForm.nd" placeholder="请选择年度" style="width: 100%" @change="handleAddYearChange">
|
||||
<el-select
|
||||
v-model="addForm.nd"
|
||||
placeholder="请选择年度"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleAddYearChange"
|
||||
>
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -194,9 +206,6 @@
|
||||
<el-form-item label="审批意见">
|
||||
<el-input v-model="auditForm.fhyj" type="textarea" :rows="3" placeholder="请输入审批意见(发回/拒绝时必填)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批人编号">
|
||||
<el-input v-model="auditForm.sprbh" placeholder="不填默认取当前登录人" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="auditLoading" @click="submitAudit">确定</el-button>
|
||||
@@ -223,6 +232,7 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室审批人">{{ detailData.jyspzrbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室审批时间">{{ fmtDateTime(detailData.jyspzsj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批意见" :span="2">{{ detailData.fhyj || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室查收人">{{ detailData.jyscsrbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教研室查收时间">{{ fmtDateTime(detailData.jyscssj) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学内容" :span="2">{{ detailData.jxnr || '-' }}</el-descriptions-item>
|
||||
@@ -316,6 +326,7 @@ import {
|
||||
} from '@/api/teachBusiness/courseRunning'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
import { listAllClassroom } from '@/api/teachBusiness/classroom'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
// 节次 -> 节次区间映射
|
||||
const JC_SECTION = {
|
||||
@@ -360,7 +371,7 @@ export default {
|
||||
{ label: '已发回', value: 2 },
|
||||
{ label: '已拒绝', value: 3 }
|
||||
],
|
||||
statCards: [],
|
||||
statCards: this.createStatCards([0, 0, 0, 0]),
|
||||
// 新增申请
|
||||
addVisible: false,
|
||||
addLoading: false,
|
||||
@@ -381,15 +392,27 @@ export default {
|
||||
// 审批
|
||||
auditVisible: false,
|
||||
auditLoading: false,
|
||||
auditForm: { ssdksqbh: '', spzt: 1, fhyj: '', sprbh: '' },
|
||||
auditForm: { ssdksqbh: '', spzt: 1, fhyj: '' },
|
||||
// 详情
|
||||
detailVisible: false,
|
||||
detailLoading: false,
|
||||
detailData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['roles']),
|
||||
isTeacher() {
|
||||
const r = this.roles || []
|
||||
const hasTeacher = r.includes('TEACHER') || r.includes('teacher') || r.includes('ROLE_TEACHER')
|
||||
const hasAdmin = r.includes('admin') || r.includes('ROLE_ADMIN')
|
||||
return hasTeacher && !hasAdmin
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadYearOptions().then(() => this.loadList())
|
||||
this.loadYearOptions().then(() => {
|
||||
this.loadStatistics()
|
||||
this.loadList()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
|
||||
@@ -450,12 +473,10 @@ export default {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.refreshStatCards()
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
this.refreshStatCards()
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
@@ -473,18 +494,56 @@ export default {
|
||||
}
|
||||
this.loadList()
|
||||
},
|
||||
refreshStatCards() {
|
||||
const list = this.tableData
|
||||
this.statCards = [
|
||||
{ title: '本页申请', value: list.length, gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)', filter: undefined },
|
||||
{ title: '教研室待审批', value: list.filter(i => i.jyspzzt === 0).length, gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)', filter: 0 },
|
||||
{ title: '教研室已同意', value: list.filter(i => i.jyspzzt === 1).length, gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)', filter: 1 },
|
||||
{ title: '教研室已拒绝', value: list.filter(i => i.jyspzzt === 3).length, gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)', filter: 3 }
|
||||
loadStatistics() {
|
||||
const baseParams = {
|
||||
nd: this.defaultNd,
|
||||
pageNum: 1,
|
||||
pageSize: 1
|
||||
}
|
||||
const statusFilters = [undefined, 0, 1, 3]
|
||||
const requests = statusFilters.map(status => {
|
||||
const params = Object.assign({}, baseParams)
|
||||
if (status !== undefined) {
|
||||
params.jyspzzt = status
|
||||
}
|
||||
return listScheduleAdjust(params)
|
||||
.then(response => Number((response.data && response.data.total) || 0))
|
||||
.catch(() => 0)
|
||||
})
|
||||
return Promise.all(requests).then(counts => {
|
||||
this.statCards = this.createStatCards(counts)
|
||||
})
|
||||
},
|
||||
createStatCards(counts) {
|
||||
return [
|
||||
{
|
||||
title: '本页申请',
|
||||
value: counts[0],
|
||||
gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)',
|
||||
filter: undefined
|
||||
},
|
||||
{
|
||||
title: '教研室待审批',
|
||||
value: counts[1],
|
||||
gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)',
|
||||
filter: 0
|
||||
},
|
||||
{
|
||||
title: '教研室已同意',
|
||||
value: counts[2],
|
||||
gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)',
|
||||
filter: 1
|
||||
},
|
||||
{
|
||||
title: '教研室已拒绝',
|
||||
value: counts[3],
|
||||
gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)',
|
||||
filter: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
handleStatClick(card) {
|
||||
if (card.filter === undefined) return
|
||||
this.queryParams.jyspzzt = this.queryParams.jyspzzt === card.filter ? undefined : card.filter
|
||||
this.queryParams.jyspzzt = card.filter
|
||||
this.handleQuery()
|
||||
},
|
||||
/* ---------- 新增申请 ---------- */
|
||||
@@ -701,14 +760,15 @@ export default {
|
||||
},
|
||||
/* ---------- 审批 ---------- */
|
||||
showAuditMenu(row) {
|
||||
return row.sczt === 0 && (row.jyspzzt === 0 || row.jyspzzt === 2 || row.jyspzzt === 3)
|
||||
// 教员角色不展示审批入口;已拒绝状态不展示审批
|
||||
if (this.isTeacher) return false
|
||||
return row.sczt === 0 && (row.jyspzzt === 0 || row.jyspzzt === 2)
|
||||
},
|
||||
openAudit(row) {
|
||||
this.auditForm = {
|
||||
ssdksqbh: row.ssdksqbh,
|
||||
spzt: 1,
|
||||
fhyj: '',
|
||||
sprbh: ''
|
||||
fhyj: ''
|
||||
}
|
||||
this.auditVisible = true
|
||||
},
|
||||
@@ -723,7 +783,6 @@ export default {
|
||||
spzt: f.spzt,
|
||||
fhyj: f.fhyj
|
||||
}
|
||||
if (f.sprbh) payload.sprbh = f.sprbh
|
||||
this.auditLoading = true
|
||||
auditByTeachingOffice(payload).then(() => {
|
||||
this.auditLoading = false
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
<div class="header-actions">
|
||||
<el-form inline class="toolbar-form" @submit.native.prevent>
|
||||
<el-form-item label="年度" class="year-item">
|
||||
<el-select v-model="nd" placeholder="请选择年度" style="width: 130px" @change="handleNdChange">
|
||||
<el-select
|
||||
v-model="nd"
|
||||
placeholder="请选择年度"
|
||||
filterable
|
||||
style="width: 130px"
|
||||
@change="handleNdChange"
|
||||
>
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y + ' 年'" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -146,24 +146,27 @@
|
||||
:close-on-click-modal="false">
|
||||
<el-form ref="trainingForm" :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="请输入专业代号" :disabled="dialog.isEdit" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业名称" prop="zymc">
|
||||
<el-input v-model="dialog.form.zymc" placeholder="请输入专业名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代码" prop="zydm">
|
||||
<el-input v-model="dialog.form.zydm" placeholder="请输入专业代码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业方向">
|
||||
<el-input v-model="dialog.form.zyfx" placeholder="请输入专业方向" />
|
||||
<el-select
|
||||
v-model="dialog.form.zyfx"
|
||||
placeholder="请选择专业方向"
|
||||
clearable
|
||||
filterable
|
||||
class="training-dict-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in majorDirectionOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -207,7 +210,20 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别">
|
||||
<el-input v-model="dialog.form.xylb" placeholder="请输入学员类别" />
|
||||
<el-select
|
||||
v-model="dialog.form.xylb"
|
||||
placeholder="请选择学员类别"
|
||||
clearable
|
||||
filterable
|
||||
class="training-dict-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in studentCategoryOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictLabel"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -231,8 +247,21 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学管理机构编号">
|
||||
<el-input v-model="dialog.form.jxgljgbh" placeholder="请输入教学管理机构编号" />
|
||||
<el-form-item label="教学管理机构">
|
||||
<el-select
|
||||
v-model="dialog.form.jxgljgbh"
|
||||
placeholder="请选择教学管理机构"
|
||||
clearable
|
||||
filterable
|
||||
class="training-dict-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in teachingOrganizationOptions"
|
||||
:key="item.dictCode || item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictValue"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -294,7 +323,7 @@
|
||||
<el-descriptions-item label="专业版本">{{ fmtVal(detail.data.zybb) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主干专业">{{ fmtYesNo(detail.data.zgzy) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="自定义分类">{{ fmtVal(detail.data.zdyfl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学管理机构编号">{{ fmtVal(detail.data.jxgljgbh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学管理机构">{{ fmtVal(detail.data.jxgljgbh) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规范名称">{{ fmtVal(detail.data.gfmc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业规范">{{ fmtVal(detail.data.zygf) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="简称">{{ fmtVal(detail.data.jc) }}</el-descriptions-item>
|
||||
@@ -318,14 +347,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
addTraining,
|
||||
disableTraining,
|
||||
updateTraining,
|
||||
getTraining,
|
||||
listTraining
|
||||
listTraining,
|
||||
exportTrainingProgram,
|
||||
downloadTrainingProgramTemplate,
|
||||
importTrainingProgram
|
||||
} from '@/api/teachBusiness/training'
|
||||
import { getDicts } from '@/api/system/dict/data'
|
||||
import { optionselect } from '@/api/system/dict/type'
|
||||
|
||||
const TRAINING_TYPE_DICT_CODE = 'train_type'
|
||||
const TRAINING_LEVEL_DICT_CODE = 'train_level'
|
||||
@@ -344,6 +378,9 @@ export default {
|
||||
},
|
||||
trainingTypeOptions: [],
|
||||
trainingLevelOptions: [],
|
||||
majorDirectionOptions: [],
|
||||
studentCategoryOptions: [],
|
||||
teachingOrganizationOptions: [],
|
||||
// 列表
|
||||
tableData: [],
|
||||
total: 0,
|
||||
@@ -367,9 +404,7 @@ export default {
|
||||
data: {}
|
||||
},
|
||||
rules: {
|
||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
||||
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
|
||||
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
|
||||
pxcc: [{ required: true, message: '请选择培训层次', trigger: 'change' }],
|
||||
pxlx: [{ required: true, message: '请选择培训类型', trigger: 'change' }]
|
||||
}
|
||||
@@ -381,19 +416,66 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 从系统字典加载培训类型和培训层次,提交标签以匹配现有业务数据。
|
||||
* 从系统字典加载表单下拉选项;文本业务字段提交标签,机构字段提交编号。
|
||||
*/
|
||||
loadTrainingDictionaries() {
|
||||
return Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE)
|
||||
]).then(([typeResponse, levelResponse]) => {
|
||||
async loadTrainingDictionaries() {
|
||||
const emptyResponse = { data: [] }
|
||||
const [typeResponse, levelResponse, dictTypeResponse] = await Promise.all([
|
||||
getDicts(TRAINING_TYPE_DICT_CODE).catch(() => emptyResponse),
|
||||
getDicts(TRAINING_LEVEL_DICT_CODE).catch(() => emptyResponse),
|
||||
optionselect().catch(() => emptyResponse)
|
||||
])
|
||||
this.trainingTypeOptions = typeResponse.data || []
|
||||
this.trainingLevelOptions = levelResponse.data || []
|
||||
}).catch(() => {
|
||||
this.trainingTypeOptions = []
|
||||
this.trainingLevelOptions = []
|
||||
|
||||
const dictTypes = dictTypeResponse.data || []
|
||||
const [directionOptions, categoryOptions, organizationOptions] = await Promise.all([
|
||||
this.loadNamedDictionary(dictTypes, '专业方向'),
|
||||
this.loadNamedDictionary(dictTypes, '学员类别'),
|
||||
this.loadNamedDictionary(dictTypes, '教学管理机构')
|
||||
])
|
||||
this.majorDirectionOptions = directionOptions
|
||||
this.studentCategoryOptions = categoryOptions
|
||||
this.teachingOrganizationOptions = organizationOptions
|
||||
this.mergeExistingSelectOptions()
|
||||
},
|
||||
async loadNamedDictionary(dictTypes, dictName) {
|
||||
const dictType = dictTypes.find(item => item.dictName === dictName) ||
|
||||
dictTypes.find(item => String(item.dictName || '').includes(dictName))
|
||||
if (!dictType || !dictType.dictType) {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const response = await getDicts(dictType.dictType)
|
||||
return response.data || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
mergeExistingSelectOptions() {
|
||||
this.majorDirectionOptions = this.mergeTextOptions(this.majorDirectionOptions, 'zyfx', true)
|
||||
this.studentCategoryOptions = this.mergeTextOptions(this.studentCategoryOptions, 'xylb', true)
|
||||
this.teachingOrganizationOptions = this.mergeTextOptions(
|
||||
this.teachingOrganizationOptions,
|
||||
'jxgljgbh',
|
||||
false
|
||||
)
|
||||
},
|
||||
mergeTextOptions(dictionaryOptions, fieldName, usesLabel) {
|
||||
const options = dictionaryOptions.slice()
|
||||
const existingValues = new Set(options.map(item => String(usesLabel ? item.dictLabel : item.dictValue)))
|
||||
this.tableData.forEach(row => {
|
||||
const value = row[fieldName]
|
||||
if (value === '' || value === null || value === undefined || existingValues.has(String(value))) {
|
||||
return
|
||||
}
|
||||
options.push({
|
||||
dictLabel: String(value),
|
||||
dictValue: String(value)
|
||||
})
|
||||
existingValues.add(String(value))
|
||||
})
|
||||
return options
|
||||
},
|
||||
|
||||
/* ---------- 列表加载 ---------- */
|
||||
@@ -413,6 +495,7 @@ export default {
|
||||
const data = response.data || {}
|
||||
this.tableData = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.mergeExistingSelectOptions()
|
||||
}).catch(() => {
|
||||
this.tableData = []
|
||||
this.total = 0
|
||||
@@ -507,12 +590,18 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 下载 / 模板下载 / 上传(后端未提供接口) ---------- */
|
||||
/* ---------- 下载 / 模板下载 / 上传 ---------- */
|
||||
handleDownload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
exportTrainingProgram().then(blob => {
|
||||
saveAs(blob, '人才培养方案课程数据文件.xlsx')
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleTemplateDownload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
downloadTrainingProgramTemplate().then(blob => {
|
||||
saveAs(blob, '人才培养方案课程数据文件模板.xls')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
@@ -523,7 +612,18 @@ export default {
|
||||
this.fileName = file ? file.name : ''
|
||||
},
|
||||
handleUpload() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
importTrainingProgram(this.selectedFile).then(response => {
|
||||
const count = (response && response.data) || 0
|
||||
this.$message.success(`导入成功,共导入 ${count} 条`)
|
||||
this.selectedFile = null
|
||||
this.fileName = ''
|
||||
if (this.$refs.fileInputRef) this.$refs.fileInputRef.value = ''
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<div class="list-title">教室列表</div>
|
||||
<div class="list-actions">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教学场地管理模板】下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
|
||||
@@ -193,12 +194,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
addClassroom,
|
||||
deleteClassroom,
|
||||
updateClassroom,
|
||||
listClassroom,
|
||||
listAllClassroom
|
||||
listAllClassroom,
|
||||
downloadClassroomTemplate
|
||||
} from '@/api/teachBusiness/classroom'
|
||||
|
||||
export default {
|
||||
@@ -369,6 +372,14 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 模板下载 ---------- */
|
||||
handleDownloadTemplate() {
|
||||
downloadClassroomTemplate().then(blob => {
|
||||
saveAs(blob, '教学场地管理模板.xls')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
|
||||
@@ -90,12 +90,9 @@
|
||||
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
|
||||
批量停止报名
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReverseRange">
|
||||
<!-- <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>
|
||||
</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -186,17 +183,23 @@
|
||||
@update:visible="val => (dialogVisible = val)"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="选修班名称" prop="xxbmc">
|
||||
<el-input v-model="form.xxbmc" placeholder="请输入选修班名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="年级">
|
||||
<el-input v-model="form.nj" placeholder="新建选修班时填写,如 2024" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="课程名称" prop="kcmc">
|
||||
<el-input v-model="form.kcmc" placeholder="请输入课程名称" clearable />
|
||||
<el-input v-model="form.kcmc" placeholder="请输入课程名称,提交时按名称查找课编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="实施教员" prop="js">
|
||||
<el-input v-model="form.js" placeholder="请输入实施教员" clearable />
|
||||
<el-input v-model="form.js" placeholder="请输入实施教员,提交时按名称查找教员编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="教材">
|
||||
<el-input v-model="form.jc" placeholder="请输入教材" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="教学场地">
|
||||
<el-input v-model="form.jxcd" placeholder="请输入教学场地" clearable />
|
||||
<el-input v-model="form.jxcd" placeholder="请输入教学场地,提交时按名称查找教室编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划学时">
|
||||
<el-input-number v-model="form.jhsxs" :min="0" :precision="0" class="w-full" />
|
||||
@@ -222,6 +225,7 @@
|
||||
<script>
|
||||
import {
|
||||
listElective,
|
||||
addElective,
|
||||
batchOpenElective,
|
||||
batchCancelOpenElective,
|
||||
batchStopElective,
|
||||
@@ -229,6 +233,9 @@ import {
|
||||
exportElectiveStudentsExcel,
|
||||
exportElectiveStudentsWord
|
||||
} from '@/api/teachOffice/elective'
|
||||
import { listKb } from '@/api/teachOffice/kb'
|
||||
import { listTeacher } from '@/api/teachOffice/teacher'
|
||||
import { listClassroom } from '@/api/teachBusiness/classroom'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
export default {
|
||||
@@ -261,6 +268,8 @@ export default {
|
||||
// ==================== 新建选修课弹窗 ====================
|
||||
dialogVisible: false,
|
||||
form: {
|
||||
xxbmc: '',
|
||||
nj: '',
|
||||
kcmc: '',
|
||||
js: '',
|
||||
jc: '',
|
||||
@@ -271,6 +280,7 @@ export default {
|
||||
jh: 0
|
||||
},
|
||||
rules: {
|
||||
xxbmc: [{ required: true, message: '请输入选修班名称', trigger: 'blur' }],
|
||||
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
||||
js: [{ required: true, message: '请输入实施教员', trigger: 'blur' }]
|
||||
},
|
||||
@@ -399,9 +409,6 @@ export default {
|
||||
handleReverseRange() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleDownloadTemplate() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 文件上传 ====================
|
||||
handleSelectFile() {
|
||||
@@ -425,17 +432,63 @@ export default {
|
||||
|
||||
// ==================== 新建选修课弹窗 ====================
|
||||
handleOpenNew() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
this.form = {
|
||||
xxbmc: '',
|
||||
nj: '',
|
||||
kcmc: '',
|
||||
js: '',
|
||||
jc: '',
|
||||
jxcd: '',
|
||||
jhsxs: 0,
|
||||
yxsxs: 0,
|
||||
xf: 0,
|
||||
jh: 0
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
},
|
||||
handleNewConfirm() {
|
||||
if (!this.$refs.formRef) return
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
console.log('新建选修课:', JSON.parse(JSON.stringify(this.form)))
|
||||
this.$message.success('新建选修课成功(前端模拟)')
|
||||
this.dialogVisible = false
|
||||
this.submitNewElective()
|
||||
})
|
||||
},
|
||||
submitNewElective() {
|
||||
const emptyPage = { data: { records: [] } }
|
||||
Promise.all([
|
||||
listKb({ pageNum: 1, pageSize: 1, kmc: this.form.kcmc }),
|
||||
listTeacher({ pageNum: 1, pageSize: 1, jyxm: this.form.js }),
|
||||
this.form.jxcd ? listClassroom({ pageNum: 1, pageSize: 1, jsmc: this.form.jxcd }) : Promise.resolve(emptyPage)
|
||||
])
|
||||
.then(([kbRes, teacherRes, roomRes]) => {
|
||||
const kbh = kbRes?.data?.records?.[0]?.kbh
|
||||
const jybh = teacherRes?.data?.records?.[0]?.jybh
|
||||
const jsbh = roomRes?.data?.records?.[0]?.jsbh
|
||||
if (!kbh) {
|
||||
this.$message.warning(`未找到课程科目:${this.form.kcmc}`)
|
||||
return
|
||||
}
|
||||
const dto = {
|
||||
kbh: kbh,
|
||||
xxbmc: this.form.xxbmc,
|
||||
nj: this.form.nj || undefined,
|
||||
jybh: jybh || undefined,
|
||||
jsbh: jsbh || undefined,
|
||||
rs: this.form.jh || undefined,
|
||||
xs: this.form.jhsxs || undefined,
|
||||
xf: this.form.xf || undefined
|
||||
}
|
||||
return addElective(dto).then(() => {
|
||||
this.$message.success('新建选修课成功')
|
||||
this.dialogVisible = false
|
||||
this.loadList()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 分页 ====================
|
||||
handlePageChange(page) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="app-container teach-office">
|
||||
<!-- 查询条件区域 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="88px" v-show="showSearch">
|
||||
@@ -14,7 +14,7 @@
|
||||
<el-form-item label="教研室名称" prop="jysmc">
|
||||
<el-input
|
||||
v-model="queryParams.jysmc"
|
||||
placeholder="请输入教研室名称(模糊)"
|
||||
placeholder="请输入教研室名称"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
@@ -31,8 +31,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="停用" prop="ty">
|
||||
<el-radio-group v-model="queryParams.ty">
|
||||
<el-radio :label="0">否</el-radio>
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -49,6 +49,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="openImport">导入</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleDownloadTemplate">模板下载</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
@@ -64,6 +67,11 @@
|
||||
<el-tag :type="scope.row.jgxz ? 'primary' : 'info'" size="mini">{{ scope.row.jgxz ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="停用" align="center" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.ty ? 'danger' : 'success'" size="mini">{{ scope.row.ty ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="bz" min-width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" align="center" min-width="140" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
@@ -144,8 +152,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listOffice, addOffice, updateOffice, disableOffice } from "@/api/teachOffice/office"
|
||||
import { listOffice, addOffice, updateOffice, disableOffice, downloadOfficeTemplate } from "@/api/teachOffice/office"
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
export default {
|
||||
name: "Office",
|
||||
@@ -176,7 +185,7 @@ export default {
|
||||
jysdh: undefined,
|
||||
jysmc: undefined,
|
||||
bx: undefined,
|
||||
ty: 0
|
||||
ty: undefined
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
@@ -334,6 +343,13 @@ export default {
|
||||
/** 导入失败 */
|
||||
handleImportError() {
|
||||
this.$modal.msgError('导入失败,请稍后重试')
|
||||
},
|
||||
/** 下载导入模板 */
|
||||
handleDownloadTemplate() {
|
||||
downloadOfficeTemplate().then(blob => {
|
||||
saveAs(blob, '教研室导入模板.xlsx')
|
||||
this.$modal.msgSuccess('模板下载成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" filterable style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -16,7 +16,14 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-select v-model="filterYear" placeholder="全部年度" clearable style="width: 140px" @change="handleYearChange">
|
||||
<el-select
|
||||
v-model="filterYear"
|
||||
placeholder="全部年度"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 140px"
|
||||
@change="handleYearChange"
|
||||
>
|
||||
<el-option v-for="y in yearFilterOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -131,7 +138,7 @@
|
||||
>
|
||||
<el-form ref="batchAddFormRef" :model="batchAddForm" :rules="batchAddRules" label-width="100px">
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" filterable style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -63,8 +63,6 @@
|
||||
<div class="action-row">
|
||||
<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>
|
||||
@@ -320,7 +318,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listKb as listSubject, addKb as addSubject, updateKb as updateSubject, deleteKb as deleteSubject, getKb } from '@/api/teachOffice/kb'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { listKb as listSubject, addKb as addSubject, updateKb as updateSubject, deleteKb as deleteSubject, getKb, downloadCourseSubjectTemplate } from '@/api/teachOffice/kb'
|
||||
|
||||
export default {
|
||||
name: 'SubjectIndex',
|
||||
@@ -515,15 +514,12 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 下载(后端暂未提供) ====================
|
||||
handleDownloadCourse() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
handleDownloadTextbook() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
},
|
||||
// ==================== 下载 ====================
|
||||
handleDownloadTemplate() {
|
||||
this.$message.warning('后端暂未提供该接口')
|
||||
downloadCourseSubjectTemplate().then(blob => {
|
||||
saveAs(blob, '课程课目数据文件模板.xls')
|
||||
this.$message.success('模板下载成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 文件上传(后端暂未提供) ====================
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="年度">
|
||||
<el-select v-model="searchForm.nd" placeholder="请选择年度" clearable style="width: 140px">
|
||||
<el-select v-model="searchForm.nd" placeholder="请选择年度" clearable filterable style="width: 140px">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -109,7 +109,7 @@
|
||||
<el-input v-model="form.rwmc" placeholder="请输入任务名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="年度" prop="nd">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
|
||||
<el-select v-model="form.nd" placeholder="请选择年度" filterable style="width: 100%">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -38,10 +38,6 @@
|
||||
<div class="left-group">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" @click="handleDeleteSelected">删除所选</el-button>
|
||||
<el-button icon="el-icon-upload" @click="handleImport">导入教员信息</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教员数据文件模板】下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -820,14 +816,6 @@ export default {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleImport() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleDownloadTemplate() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 新增 ====================
|
||||
handleNew() {
|
||||
this.addForm = this.createEmptyAddForm()
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<!-- <el-row :gutter="0">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="改动情况">
|
||||
<div class="change-options">
|
||||
@@ -63,7 +63,7 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-row> -->
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
@@ -114,11 +114,6 @@
|
||||
<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" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
@@ -225,6 +220,11 @@ export default {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
/** 组件方法:包一层全局 formatDate,供模板调用 */
|
||||
formatDate(val) {
|
||||
return formatDate(val)
|
||||
},
|
||||
|
||||
/** 根据已填写的查询值构建实际查询参数 */
|
||||
buildSearchParams() {
|
||||
const form = this.searchForm
|
||||
@@ -255,11 +255,10 @@ export default {
|
||||
this.loading = true
|
||||
return getAuditedTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||
const { list, total } = this.extractListData(res)
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<!-- <el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="改动情况">
|
||||
<div class="change-options">
|
||||
@@ -87,15 +87,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-row> -->
|
||||
</el-form>
|
||||
|
||||
<div class="search-actions">
|
||||
<div class="left-group">
|
||||
<el-button type="danger" plain @click="handleDeleteSelected">删除所选</el-button>
|
||||
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
|
||||
<el-button @click="handleRecheck">重新检查通报变更情况</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
||||
<!-- <el-button @click="handleRecheck">重新检查通报变更情况</el-button> -->
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出</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>
|
||||
@@ -137,7 +136,7 @@
|
||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
<el-button type="text" @click="handleReport(scope.row)">上报</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -190,11 +189,11 @@
|
||||
import { formatDate } from '@/utils/index'
|
||||
import {
|
||||
getTeachingLogList,
|
||||
submitTeachingLog,
|
||||
exportTeachingLog,
|
||||
importTeachingLogExcel,
|
||||
downloadTeachingLogTemplate,
|
||||
getTeachingLogByBh
|
||||
getTeachingLogByBh,
|
||||
batchReportTeachingLog
|
||||
} from '@/api/log'
|
||||
|
||||
export default {
|
||||
@@ -246,6 +245,8 @@ export default {
|
||||
|
||||
// ==================== 导入 ====================
|
||||
importing: false,
|
||||
// 导入成功后是否跳到最后一页(定位到刚导入的数据)
|
||||
importJumpLast: false,
|
||||
|
||||
// ==================== 详情 ====================
|
||||
detailVisible: false,
|
||||
@@ -257,6 +258,10 @@ export default {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
formatDate(val) {
|
||||
return formatDate(val)
|
||||
},
|
||||
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
@@ -293,11 +298,20 @@ export default {
|
||||
this.loading = true
|
||||
return getTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||
const { list, total } = this.extractListData(res)
|
||||
// 导入成功后:跳到最后一页,定位刚导入的数据
|
||||
if (this.importJumpLast) {
|
||||
this.importJumpLast = false
|
||||
const lastPage = total > 0 ? Math.ceil(total / this.pageSize) : 1
|
||||
if (lastPage !== this.pageNum) {
|
||||
this.pageNum = lastPage
|
||||
this.fetchList()
|
||||
return
|
||||
}
|
||||
}
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 错误已由拦截器统一处理
|
||||
@@ -336,11 +350,6 @@ export default {
|
||||
return ids
|
||||
},
|
||||
|
||||
handleDeleteSelected() {
|
||||
// 后端暂未提供删除接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleBatchReport() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
@@ -350,12 +359,8 @@ export default {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
// 提交教学日志审核,逐条上报至「已上报」状态
|
||||
let settled = Promise.resolve()
|
||||
ids.forEach((bh) => {
|
||||
settled = settled.then(() => submitTeachingLog(bh, '已上报'))
|
||||
})
|
||||
return settled
|
||||
// 批量上报教学日志
|
||||
return batchReportTeachingLog(ids)
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`已上报 ${ids.length} 条记录`)
|
||||
@@ -364,6 +369,25 @@ export default {
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleReport(row) {
|
||||
const bh = row && row.bh
|
||||
if (!bh) {
|
||||
this.$message.warning('记录编号不存在,无法上报')
|
||||
return
|
||||
}
|
||||
this.$confirm('确定要上报该条教学日志吗?', '上报确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => batchReportTeachingLog([String(bh)]))
|
||||
.then(() => {
|
||||
this.$message.success('上报成功')
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleRecheck() {
|
||||
// 后端暂未提供重新检查接口,仅作提示
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
@@ -405,6 +429,7 @@ export default {
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
this.$message.success(res.message || '导入成功')
|
||||
this.importJumpLast = true
|
||||
this.fetchList()
|
||||
} else {
|
||||
this.$message.error(res.message || '导入失败')
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="12">
|
||||
<!-- <el-col :span="12">
|
||||
<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> -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务类别">
|
||||
<el-select v-model="searchForm.taskCategory" placeholder="请选择任务类别" clearable class="w-full">
|
||||
@@ -78,11 +78,7 @@
|
||||
|
||||
<div class="search-actions">
|
||||
<div class="left-group">
|
||||
<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="warning" plain @click="handleWithdraw">撤回重填所选</el-button>
|
||||
<el-button @click="handleRecheck">重新检查所选变更情况</el-button>
|
||||
<el-button v-if="!isTeacher" type="primary" @click="handleBatchAudit">审核通过所选</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleExport">导出到 Word</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
@@ -96,7 +92,7 @@
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column v-if="!isTeacher" type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="rwlb" label="任务类别" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
|
||||
@@ -108,21 +104,22 @@
|
||||
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}节` : (scope.row.qsjc || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="70" align="center">
|
||||
<el-table-column prop="kcmc" label="课程名称" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="pxqbxx" label="培训期班信息" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="80" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分组指导教员" width="120" align="center">
|
||||
<el-table-column label="分组指导教员" width="100" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
|
||||
<el-table-column prop="jsfs" label="教学方法" width="80" align="center" />
|
||||
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
|
||||
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="gdqk" label="变动汇总" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
|
||||
<el-table-column v-if="!isTeacher" label="操作" width="160" align="center" fixed="right" class-name="table-action-column">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleDetail(scope.row)">详细</el-button>
|
||||
<el-button type="text" @click="handleAudit(scope.row)">审核</el-button>
|
||||
<el-button type="text" @click="handleReject(scope.row)">退回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -175,14 +172,21 @@
|
||||
import { formatDate } from '@/utils/index'
|
||||
import {
|
||||
getReportedTeachingLogList,
|
||||
approveTeachingLog,
|
||||
rejectTeachingLog,
|
||||
exportTeachingLog,
|
||||
getTeachingLogByBh
|
||||
getTeachingLogByBh,
|
||||
batchAuditTeachingLog
|
||||
} from '@/api/log'
|
||||
|
||||
export default {
|
||||
name: 'ReportedView',
|
||||
props: {
|
||||
/** 当前用户是否为教员角色,用于隐藏审核相关操作 */
|
||||
isTeacher: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
@@ -241,6 +245,11 @@ export default {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
/** 组件方法:包一层全局 formatDate,供模板调用 */
|
||||
formatDate(val) {
|
||||
return formatDate(val)
|
||||
},
|
||||
|
||||
/** 根据已填写的查询值构建实际查询参数 */
|
||||
buildSearchParams() {
|
||||
const form = this.searchForm
|
||||
@@ -268,11 +277,10 @@ export default {
|
||||
this.loading = true
|
||||
return getReportedTeachingLogList(this.buildSearchParams())
|
||||
.then((res) => {
|
||||
if (res.code === 200 || res.code === 0) {
|
||||
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
|
||||
const { list, total } = this.extractListData(res)
|
||||
this.tableData = list
|
||||
this.total = total
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
@@ -326,7 +334,7 @@ export default {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleAudit() {
|
||||
handleBatchAudit() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
this.$confirm(`确定要审核通过选中的 ${ids.length} 条记录吗?`, '审核确认', {
|
||||
@@ -335,12 +343,8 @@ export default {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
// 审核通过教学日志,逐条审核
|
||||
let settled = Promise.resolve()
|
||||
ids.forEach((bh) => {
|
||||
settled = settled.then(() => approveTeachingLog(bh, ''))
|
||||
})
|
||||
return settled
|
||||
// 批量审核教学日志
|
||||
return batchAuditTeachingLog(ids)
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`已审核通过 ${ids.length} 条记录`)
|
||||
@@ -349,6 +353,45 @@ export default {
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleAudit(row) {
|
||||
const bh = row && row.bh
|
||||
if (!bh) {
|
||||
this.$message.warning('记录编号不存在,无法审核')
|
||||
return
|
||||
}
|
||||
this.$confirm('确定要审核通过该条教学日志吗?', '审核确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => batchAuditTeachingLog([String(bh)]))
|
||||
.then(() => {
|
||||
this.$message.success('审核通过成功')
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleReject(row) {
|
||||
const bh = row && row.bh
|
||||
if (!bh) {
|
||||
this.$message.warning('记录编号不存在,无法退回')
|
||||
return
|
||||
}
|
||||
this.$prompt('请输入退回原因/意见', '退回确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '退回原因不能为空'
|
||||
})
|
||||
.then(({ value }) => rejectTeachingLog(String(bh), value.trim()))
|
||||
.then(() => {
|
||||
this.$message.success('退回成功')
|
||||
this.fetchList()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
handleWithdraw() {
|
||||
const ids = this.getSelectedBhs()
|
||||
if (ids.length === 0) return
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
<div class="page-container">
|
||||
<el-card shadow="never" class="tab-card">
|
||||
<el-tabs v-model="activeTab" class="organ-tabs">
|
||||
<el-tab-pane label="教学日志管理查询" name="query">
|
||||
<!-- 教学日志管理查询页:仅管理员/教研室等非教员、非机关人员可见 -->
|
||||
<el-tab-pane v-if="hasQueryAccess" label="教学日志管理查询" name="query">
|
||||
<log-view v-if="activeTab === 'query'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="已上报机关教学日志" name="reported">
|
||||
<reported-view v-if="activeTab === 'reported'" />
|
||||
<reported-view v-if="activeTab === 'reported'" :is-teacher="isTeacher" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="机关已审核教学日志" name="audited">
|
||||
<audited-view v-if="activeTab === 'audited'" />
|
||||
@@ -28,6 +29,29 @@ export default {
|
||||
return {
|
||||
activeTab: 'query'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
roles() {
|
||||
return this.$store.getters.roles || []
|
||||
},
|
||||
/** 教员角色 */
|
||||
isTeacher() {
|
||||
return this.roles.includes('TEACHER')
|
||||
},
|
||||
/** 机关人员角色 */
|
||||
isDepartmentPersonnel() {
|
||||
return this.roles.includes('DEPARTMENT_PERSONNEL')
|
||||
},
|
||||
/** 教学日志管理查询页仅非教员、非机关人员可见 */
|
||||
hasQueryAccess() {
|
||||
return !this.isTeacher && !this.isDepartmentPersonnel
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 教员/机关人员无法查看教学日志管理查询页,默认落在已上报页
|
||||
if (!this.hasQueryAccess) {
|
||||
this.activeTab = 'reported'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="90px" class="search-form">
|
||||
<el-row :gutter="0">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="编号">
|
||||
<el-input v-model="searchForm.bh" placeholder="请输入编号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="教材名称">
|
||||
<el-input v-model="searchForm.mc" placeholder="请输入教材名称" clearable />
|
||||
@@ -67,8 +62,6 @@
|
||||
<div class="list-toolbar">
|
||||
<div class="left-group">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleNew">新建教材信息</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownloadList">下载教材列表</el-button>
|
||||
<el-button icon="el-icon-folder-opened" @click="handleDownloadZip">所选文件资料打包ZIP下载</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【教材数据文件模板】下载</el-button>
|
||||
@@ -629,11 +622,6 @@
|
||||
<el-form-item label="备注"><el-input v-model="stockForm.bz" type="textarea" :rows="2" placeholder="请输入" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="JSONZD"><el-input v-model="stockForm.jsonzd" placeholder="请输入" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
@@ -662,7 +650,6 @@
|
||||
<el-descriptions-item label="审批人编号">{{ stockDetailData.sprbh || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ stockDetailData.zt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ stockDetailData.bz || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="JSONZD">{{ stockDetailData.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ fmtDateTime(stockDetailData.cjsj) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="修改时间">{{ fmtDateTime(stockDetailData.xgsj) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ fmtDateTime(stockDetailData.tjsj) || '-' }}</el-descriptions-item>
|
||||
@@ -725,11 +712,6 @@
|
||||
<el-form-item label="备注"><el-input v-model="detailForm.bz" type="textarea" :rows="2" placeholder="请输入" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="JSONZD"><el-input v-model="detailForm.jsonzd" placeholder="请输入" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
@@ -757,7 +739,6 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="还回完毕时间">{{ fmtDateTime(detailViewData.hwbwsj) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ detailViewData.bz || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="JSONZD">{{ detailViewData.jsonzd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="快照_事务后库存数量">{{ fmtVal(detailViewData.kzswHkcsl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="快照_事务后借出数量">{{ fmtVal(detailViewData.kzswHjcsl) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="快照_事务前库存数量">{{ fmtVal(detailViewData.kzswQkcsl) }}</el-descriptions-item>
|
||||
@@ -804,7 +785,7 @@ export default {
|
||||
|
||||
// ==================== 查询表单(仅后端 Mapper 支持的字段) ====================
|
||||
searchForm: {
|
||||
bh: '', mc: '', zz: '', cbs: '', isbn: '',
|
||||
mc: '', zz: '', cbs: '', isbn: '',
|
||||
jcfl: '', jclx: '', ty: false
|
||||
},
|
||||
|
||||
@@ -951,7 +932,7 @@ export default {
|
||||
// ==================== 列表加载 ====================
|
||||
buildQuery() {
|
||||
const params = {}
|
||||
;['bh', 'mc', 'zz', 'cbs', 'isbn', 'jcfl', 'jclx'].forEach(key => {
|
||||
;['mc', 'zz', 'cbs', 'isbn', 'jcfl', 'jclx'].forEach(key => {
|
||||
const value = this.searchForm[key]
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
params[key] = String(value).trim()
|
||||
@@ -988,7 +969,7 @@ export default {
|
||||
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
bh: '', mc: '', zz: '', cbs: '', isbn: '',
|
||||
mc: '', zz: '', cbs: '', isbn: '',
|
||||
jcfl: '', jclx: '', ty: false
|
||||
}
|
||||
this.pageNum = 1
|
||||
@@ -1005,15 +986,6 @@ export default {
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
// ==================== 无后端接口的操作 ====================
|
||||
handleDownloadList() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
handleDownloadZip() {
|
||||
this.$message.info('后端暂未提供该接口')
|
||||
},
|
||||
|
||||
// ==================== 新建教材 ====================
|
||||
handleNew() {
|
||||
this.addForm = this.createEmptyForm()
|
||||
|
||||
@@ -9,7 +9,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '教学管理信息系统' // 网页标题
|
||||
|
||||
const baseUrl = 'http://10.1.1.17:8080' // 后端接口
|
||||
const baseUrl = 'http://10.1.1.193:8080' // 后端接口
|
||||
|
||||
const port = 80 // 固定开发服务器端口,避免多实例端口混乱
|
||||
|
||||
|
||||
Reference in New Issue
Block a user