forked from liweijie/education
Merge remote-tracking branch 'origin/main'
# Conflicts: # frontend/src/layout/components/SemesterBar.vue # frontend/src/views/classHour/plan/index.vue
This commit is contained in:
@@ -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
|
|
||||||
})
|
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<el-table-column prop="xmlx" label="项目类型" width="80" align="center" show-overflow-tooltip />
|
<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="sm" label="说明" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="bz" 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 }">
|
<template slot-scope="{ row }">
|
||||||
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
|
||||||
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right" header-align="center">
|
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right" header-align="center">
|
||||||
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
|
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
|
||||||
</el-table-column>
|
</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>
|
<template slot-scope="{ row }">{{ fmtValue(row.clksfbz) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|||||||
@@ -67,11 +67,17 @@
|
|||||||
<el-table-column prop="jxgljgbh" label="教学管理机构" width="200" show-overflow-tooltip />
|
<el-table-column prop="jxgljgbh" label="教学管理机构" width="200" show-overflow-tooltip />
|
||||||
<el-table-column prop="zydm" label="专业代码" width="200" />
|
<el-table-column prop="zydm" label="专业代码" width="200" />
|
||||||
<el-table-column prop="zymc" label="专业名称" width="200" show-overflow-tooltip />
|
<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="xylb" label="学员类别" width="150" align="center" />
|
||||||
<el-table-column prop="xnz" label="学年制" width="120" 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="xqs" label="学期数" width="120" align="center" />
|
||||||
<el-table-column prop="zgzy" label="主干专业" 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">
|
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||||||
<template slot-scope="{ row }">
|
<template slot-scope="{ row }">
|
||||||
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
<template slot-scope="scope">
|
<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-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-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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|||||||
Reference in New Issue
Block a user