/** * 版权所有,内部使用。 * * @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 })