forked from liweijie/education
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ae7a7947 | |||
| b2d27c87d2 |
@@ -8,7 +8,8 @@
|
|||||||
"dev": "vue-cli-service serve",
|
"dev": "vue-cli-service serve",
|
||||||
"build:prod": "vue-cli-service build",
|
"build:prod": "vue-cli-service build",
|
||||||
"build:stage": "vue-cli-service build --mode staging",
|
"build:stage": "vue-cli-service build --mode staging",
|
||||||
"preview": "node build/index.js --preview"
|
"preview": "node build/index.js --preview",
|
||||||
|
"dict:init-course-type": "node scripts/init-course-type-dict.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"vue",
|
"vue",
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/**
|
||||||
|
* 版权所有,内部使用。
|
||||||
|
*
|
||||||
|
* @description 初始化教学大纲使用的课类型字典,可重复执行且不会重复插入。
|
||||||
|
* @author 软件研发部
|
||||||
|
* @since 2026-08-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DICT_TYPE = 'course_type'
|
||||||
|
const DICT_NAME = '课类型'
|
||||||
|
const DEFAULT_API_BASE_URL = 'http://localhost/api'
|
||||||
|
const DEFAULT_ADMIN_USERNAME = 'admin'
|
||||||
|
const DICT_ITEMS = [
|
||||||
|
{ label: '必修', value: '必修' },
|
||||||
|
{ label: '选修', value: '选修' },
|
||||||
|
{ label: '实践', value: '实践' }
|
||||||
|
]
|
||||||
|
|
||||||
|
let authToken = ''
|
||||||
|
|
||||||
|
function readHiddenInput(prompt) {
|
||||||
|
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
||||||
|
throw new Error('当前终端不支持隐藏输入,请在 PowerShell 中直接运行该脚本')
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let input = ''
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
process.stdin.removeListener('data', handleInput)
|
||||||
|
process.stdin.setRawMode(false)
|
||||||
|
process.stdin.pause()
|
||||||
|
process.stdout.write('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(characters) {
|
||||||
|
for (const character of characters) {
|
||||||
|
if (character === '\u0003') {
|
||||||
|
cleanup()
|
||||||
|
reject(new Error('用户取消执行'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (character === '\r' || character === '\n') {
|
||||||
|
cleanup()
|
||||||
|
resolve(input)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (character === '\u0008' || character === '\u007f') {
|
||||||
|
input = input.slice(0, -1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (character >= ' ') {
|
||||||
|
input += character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(prompt)
|
||||||
|
process.stdin.setEncoding('utf8')
|
||||||
|
process.stdin.setRawMode(true)
|
||||||
|
process.stdin.resume()
|
||||||
|
process.stdin.on('data', handleInput)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authenticate() {
|
||||||
|
const environmentToken = process.env.EDUCATION_API_TOKEN
|
||||||
|
if (environmentToken) {
|
||||||
|
authToken = environmentToken.replace(/^Bearer\s+/i, '')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = process.env.EDUCATION_ADMIN_USERNAME || DEFAULT_ADMIN_USERNAME
|
||||||
|
let password = await readHiddenInput(`请输入管理员 ${username} 的密码:`)
|
||||||
|
const response = await fetch(getApiBaseUrl() + '/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||||
|
body: JSON.stringify({ username, password, code: '', uuid: '' })
|
||||||
|
})
|
||||||
|
password = ''
|
||||||
|
const result = await response.json()
|
||||||
|
|
||||||
|
if (!response.ok || result.code !== 200 || !result.token) {
|
||||||
|
throw new Error(result.msg || `登录失败:HTTP ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken = result.token
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiBaseUrl() {
|
||||||
|
const configuredUrl = process.env.EDUCATION_API_BASE_URL || DEFAULT_API_BASE_URL
|
||||||
|
|
||||||
|
return configuredUrl.replace(/\/+$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function request(path, options = {}) {
|
||||||
|
if (!authToken) {
|
||||||
|
throw new Error('尚未完成管理员认证')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(getApiBaseUrl() + path, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + authToken,
|
||||||
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
|
...(options.headers || {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const result = await response.json()
|
||||||
|
|
||||||
|
if (!response.ok || result.code !== 200) {
|
||||||
|
throw new Error(result.msg || `请求失败:HTTP ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureDictType() {
|
||||||
|
const query = new URLSearchParams({
|
||||||
|
pageNum: '1',
|
||||||
|
pageSize: '10',
|
||||||
|
dictType: DICT_TYPE
|
||||||
|
})
|
||||||
|
const result = await request('/system/dict/type/list?' + query.toString())
|
||||||
|
const hasDictType = (result.rows || []).some(item => item.dictType === DICT_TYPE)
|
||||||
|
|
||||||
|
if (hasDictType) {
|
||||||
|
console.log(`字典类型 ${DICT_TYPE} 已存在,跳过新增`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('/system/dict/type', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
dictName: DICT_NAME,
|
||||||
|
dictType: DICT_TYPE,
|
||||||
|
status: '0',
|
||||||
|
remark: '教学大纲课类型列表'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(`已新增字典类型 ${DICT_TYPE}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureDictItems() {
|
||||||
|
const query = new URLSearchParams({
|
||||||
|
pageNum: '1',
|
||||||
|
pageSize: '100',
|
||||||
|
dictType: DICT_TYPE
|
||||||
|
})
|
||||||
|
const result = await request('/system/dict/data/list?' + query.toString())
|
||||||
|
const existingValues = new Set((result.rows || []).map(item => item.dictValue))
|
||||||
|
|
||||||
|
for (const [index, item] of DICT_ITEMS.entries()) {
|
||||||
|
if (existingValues.has(item.value)) {
|
||||||
|
console.log(`字典项 ${item.label} 已存在,跳过新增`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('/system/dict/data', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
dictSort: index + 1,
|
||||||
|
dictLabel: item.label,
|
||||||
|
dictValue: item.value,
|
||||||
|
dictType: DICT_TYPE,
|
||||||
|
cssClass: '',
|
||||||
|
listClass: 'default',
|
||||||
|
isDefault: 'N',
|
||||||
|
status: '0',
|
||||||
|
remark: '教学大纲课类型'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(`已新增字典项 ${item.label}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshDictCache() {
|
||||||
|
await request('/system/dict/type/refreshCache', { method: 'DELETE' })
|
||||||
|
console.log('字典缓存已刷新')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await authenticate()
|
||||||
|
await ensureDictType()
|
||||||
|
await ensureDictItems()
|
||||||
|
await refreshDictCache()
|
||||||
|
console.log('课类型字典初始化完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(error => {
|
||||||
|
console.error('课类型字典初始化失败:' + error.message)
|
||||||
|
process.exitCode = 1
|
||||||
|
})
|
||||||
@@ -100,13 +100,41 @@
|
|||||||
<el-form ref="syllabusForm" :model="dialog.form" :rules="rules" label-width="130px">
|
<el-form ref="syllabusForm" :model="dialog.form" :rules="rules" label-width="130px">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="专业代号" prop="zydh">
|
<el-form-item label="专业" prop="zydh">
|
||||||
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" />
|
<el-select
|
||||||
|
v-model="dialog.form.zydh"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择专业"
|
||||||
|
class="w-full"
|
||||||
|
:loading="referenceDataLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in majorOptions"
|
||||||
|
:key="item.zydh"
|
||||||
|
:label="item.zymc"
|
||||||
|
:value="item.zydh"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="课编号" prop="kbh">
|
<el-form-item label="课程" prop="kbh">
|
||||||
<el-input v-model="dialog.form.kbh" placeholder="请输入课编号" />
|
<el-select
|
||||||
|
v-model="dialog.form.kbh"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择课程"
|
||||||
|
class="w-full"
|
||||||
|
:loading="referenceDataLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in subjectOptions"
|
||||||
|
:key="item.kbh"
|
||||||
|
:label="item.kmc"
|
||||||
|
:value="item.kbh"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -116,7 +144,21 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="课类型" prop="klx">
|
<el-form-item label="课类型" prop="klx">
|
||||||
<el-input v-model="dialog.form.klx" placeholder="请输入课类型" />
|
<el-select
|
||||||
|
v-model="dialog.form.klx"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择课类型"
|
||||||
|
class="w-full"
|
||||||
|
:loading="referenceDataLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in courseTypeOptions"
|
||||||
|
:key="item.dictValue"
|
||||||
|
:label="item.dictLabel"
|
||||||
|
:value="item.dictValue"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -158,8 +200,22 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="教研室代号">
|
<el-form-item label="教研室">
|
||||||
<el-input v-model="dialog.form.jysdh" placeholder="请输入教研室代号" />
|
<el-select
|
||||||
|
v-model="dialog.form.jysdh"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择教研室"
|
||||||
|
class="w-full"
|
||||||
|
:loading="referenceDataLoading"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in officeOptions"
|
||||||
|
:key="item.jysdh"
|
||||||
|
:label="item.jysmc"
|
||||||
|
:value="item.jysdh"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -225,6 +281,13 @@ import {
|
|||||||
listSyllabus,
|
listSyllabus,
|
||||||
listSyllabusByZydhAndTy
|
listSyllabusByZydhAndTy
|
||||||
} from '@/api/teachBusiness/syllabus'
|
} from '@/api/teachBusiness/syllabus'
|
||||||
|
import { listMajor } from '@/api/subjectMajor/major'
|
||||||
|
import { listKb } from '@/api/teachOffice/kb'
|
||||||
|
import { listOffice } from '@/api/teachOffice/office'
|
||||||
|
import { getDicts } from '@/api/system/dict/data'
|
||||||
|
|
||||||
|
const REFERENCE_DATA_PAGE_SIZE = 10000
|
||||||
|
const COURSE_TYPE_DICT_CODE = 'course_type'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SyllabusIndex',
|
name: 'SyllabusIndex',
|
||||||
@@ -240,6 +303,11 @@ export default {
|
|||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
selection: [],
|
selection: [],
|
||||||
|
referenceDataLoading: false,
|
||||||
|
majorOptions: [],
|
||||||
|
subjectOptions: [],
|
||||||
|
courseTypeOptions: [],
|
||||||
|
officeOptions: [],
|
||||||
dialog: {
|
dialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
title: '',
|
title: '',
|
||||||
@@ -247,10 +315,10 @@ export default {
|
|||||||
form: this.createEmptyForm()
|
form: this.createEmptyForm()
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
zydh: [{ required: true, message: '请选择专业', trigger: 'change' }],
|
||||||
kbh: [{ required: true, message: '请输入课编号', trigger: 'blur' }],
|
kbh: [{ required: true, message: '请选择课程', trigger: 'change' }],
|
||||||
xqdc: [{ required: true, message: '请输入学期第次', trigger: 'blur' }],
|
xqdc: [{ required: true, message: '请输入学期第次', trigger: 'blur' }],
|
||||||
klx: [{ required: true, message: '请输入课类型', trigger: 'blur' }],
|
klx: [{ required: true, message: '请选择课类型', trigger: 'change' }],
|
||||||
xs: [{ required: true, message: '请输入学时', trigger: 'blur' }],
|
xs: [{ required: true, message: '请输入学时', trigger: 'blur' }],
|
||||||
ty: [{ required: true, message: '请选择停用标识', trigger: 'change' }]
|
ty: [{ required: true, message: '请选择停用标识', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
@@ -264,8 +332,51 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchList()
|
this.fetchList()
|
||||||
|
this.loadReferenceData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadReferenceData() {
|
||||||
|
this.referenceDataLoading = true
|
||||||
|
const query = { pageNum: 1, pageSize: REFERENCE_DATA_PAGE_SIZE }
|
||||||
|
const requests = [
|
||||||
|
listMajor(query),
|
||||||
|
listKb(query),
|
||||||
|
listOffice(query),
|
||||||
|
getDicts(COURSE_TYPE_DICT_CODE)
|
||||||
|
].map(request => {
|
||||||
|
return request
|
||||||
|
.then(response => this.getResponseRecords(response))
|
||||||
|
.catch(() => [])
|
||||||
|
})
|
||||||
|
|
||||||
|
Promise.all(requests).then(([majors, subjects, offices, courseTypes]) => {
|
||||||
|
this.majorOptions = this.getUniqueOptions(majors, 'zydh', 'zymc')
|
||||||
|
this.subjectOptions = this.getUniqueOptions(subjects, 'kbh', 'kmc')
|
||||||
|
this.officeOptions = this.getUniqueOptions(offices, 'jysdh', 'jysmc')
|
||||||
|
this.courseTypeOptions = this.getUniqueOptions(courseTypes, 'dictValue', 'dictLabel')
|
||||||
|
}).finally(() => {
|
||||||
|
this.referenceDataLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getResponseRecords(response) {
|
||||||
|
const data = response.data || {}
|
||||||
|
|
||||||
|
return Array.isArray(data) ? data : data.records || []
|
||||||
|
},
|
||||||
|
getUniqueOptions(items, valueKey, labelKey) {
|
||||||
|
const values = new Set()
|
||||||
|
|
||||||
|
return items.filter(item => {
|
||||||
|
const value = item[valueKey]
|
||||||
|
const label = item[labelKey]
|
||||||
|
if (!value || !label || values.has(value)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
values.add(value)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
/* ---------- 列表加载 ---------- */
|
/* ---------- 列表加载 ---------- */
|
||||||
fetchList() {
|
fetchList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|||||||
Reference in New Issue
Block a user