feat: 新增题目导入服务并优化用户端答题流程
- 新增 question-importer 服务,支持从 JSON 文件批量导入题目到题库 - 重构用户端答题流程,整合抽题、答题、结果分析页面状态管理 - 将题目分类从 UI 模板类型切换为业务分类(question_category_name) - 在题库管理页面支持题目批量选择和删除功能 - 优化用户端组别选择,增加“已结束”状态提示和禁用逻辑 - 修复题目新增/更新 API 请求参数格式问题 - 为富文本编辑器配置排除不必要的工具栏按键
This commit is contained in:
44
apps/question-importer/src/clean_ciyu.ts
Normal file
44
apps/question-importer/src/clean_ciyu.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/* eslint-disable no-console */
|
||||
import path from 'node:path'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
const FILE_PATH = path.join(__dirname, '../asset/词语听写.json')
|
||||
|
||||
async function cleanCiyuJson() {
|
||||
try {
|
||||
if (!fs.existsSync(FILE_PATH)) {
|
||||
console.error(`File not found: ${FILE_PATH}`)
|
||||
return
|
||||
}
|
||||
|
||||
const data = await fs.readJSON(FILE_PATH)
|
||||
|
||||
if (data.question_bank && Array.isArray(data.question_bank)) {
|
||||
let modifiedCount = 0
|
||||
data.question_bank.forEach((q: any) => {
|
||||
if (q.Name && typeof q.Name === 'string') {
|
||||
// Remove HTML tags using regex
|
||||
const cleanName = q.Name.replace(/<[^>]*>/g, '').trim()
|
||||
if (cleanName !== q.Name) {
|
||||
console.log(`Cleaning: ${q.Name} -> ${cleanName}`)
|
||||
q.Name = cleanName
|
||||
modifiedCount++
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (modifiedCount > 0) {
|
||||
await fs.writeJSON(FILE_PATH, data, { spaces: 2 })
|
||||
console.log(`Successfully cleaned ${modifiedCount} items in 词语听写.json`)
|
||||
}
|
||||
else {
|
||||
console.log('No changes needed for 词语听写.json')
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error processing file:', error)
|
||||
}
|
||||
}
|
||||
|
||||
cleanCiyuJson()
|
||||
141
apps/question-importer/src/index.ts
Normal file
141
apps/question-importer/src/index.ts
Normal file
@ -0,0 +1,141 @@
|
||||
/* eslint-disable no-console */
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import axios from 'axios'
|
||||
import express from 'express'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
const app = express()
|
||||
const PORT = 3001 // 避免与前端端口冲突(通常是 3000 或 9527)
|
||||
|
||||
console.log('Script started')
|
||||
console.log('Arguments:', process.argv)
|
||||
|
||||
// 配置
|
||||
const API_BASE_URL = 'http://localhost:9527'
|
||||
const IMPORT_API_PATH = '/proxy-default/Base/ActivityMain/AddQuestionListDetail'
|
||||
const ASSET_DIR = path.join(__dirname, '../asset')
|
||||
|
||||
// 来自 curl 命令的请求头
|
||||
const HEADERS = {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Connection': 'keep-alive',
|
||||
'Custom-Platform': 'pc',
|
||||
'Origin': 'http://localhost:9527',
|
||||
'Referer': 'http://localhost:9527/question-store',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
|
||||
'apifoxToken': 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2',
|
||||
'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
}
|
||||
|
||||
interface Question {
|
||||
QuestionId: number
|
||||
Name: string
|
||||
Answer: string
|
||||
Type: string
|
||||
IsGood: number
|
||||
ImageUrl: string
|
||||
}
|
||||
|
||||
interface QuestionBank {
|
||||
question_bank: Question[]
|
||||
}
|
||||
|
||||
async function importQuestions() {
|
||||
try {
|
||||
// 检查资源目录是否存在
|
||||
console.log(`Checking asset directory: ${ASSET_DIR}`)
|
||||
if (!fs.existsSync(ASSET_DIR)) {
|
||||
console.error(`Asset directory not found: ${ASSET_DIR}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取资源目录下的所有 JSON 文件
|
||||
const files = await fs.readdir(ASSET_DIR)
|
||||
console.log(`Files found in directory:`, files)
|
||||
const jsonFiles = files.filter(file => file.endsWith('.json'))
|
||||
|
||||
console.log(`Found ${jsonFiles.length} JSON files in ${ASSET_DIR}`)
|
||||
|
||||
for (const file of jsonFiles) {
|
||||
const filePath = path.join(ASSET_DIR, file)
|
||||
console.log(`\nStarting import for file: ${file}`)
|
||||
|
||||
const data: QuestionBank = await fs.readJSON(filePath)
|
||||
const questions = data.question_bank
|
||||
|
||||
if (!questions || !Array.isArray(questions)) {
|
||||
console.error(`Invalid JSON format in ${file}: question_bank array not found.`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(`Found ${questions.length} questions in ${file}.`)
|
||||
|
||||
for (const [index, question] of questions.entries()) {
|
||||
console.log(`[${file}] Importing question ${index + 1}/${questions.length}: ID ${question.QuestionId}`)
|
||||
|
||||
try {
|
||||
// 构建请求体 (JSON)
|
||||
const body = {
|
||||
QuestionId: question.QuestionId,
|
||||
Name: question.Name,
|
||||
Answer: question.Answer,
|
||||
Type: question.Type,
|
||||
IsGood: question.IsGood,
|
||||
ImageUrl: question.ImageUrl || '',
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
const url = `${API_BASE_URL}${IMPORT_API_PATH}`
|
||||
|
||||
const response = await axios.post(url, body, {
|
||||
headers: {
|
||||
...HEADERS,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`Success: ${response.status} - ${JSON.stringify(response.data)}`)
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error(`Failed to import question ${question.QuestionId}:`, error.message)
|
||||
if (error.response) {
|
||||
console.error('Response data:', error.response.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加短暂延迟以避免服务器过载
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nAll imports completed.')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during import process:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 触发导入的路由
|
||||
app.get('/run-import', async (req, res) => {
|
||||
console.log('Received manual trigger for import...')
|
||||
importQuestions() // 后台运行
|
||||
res.send('Import process started. Check console for logs.')
|
||||
})
|
||||
|
||||
// 如果传递了参数则直接运行导入,不启动 Web 服务
|
||||
if (process.argv.includes('--run-now')) {
|
||||
importQuestions().then(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
else {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Importer service running at http://localhost:${PORT}`)
|
||||
console.log('Ensure questions.json is present in the root of this service.')
|
||||
console.log(`To start import, visit http://localhost:${PORT}/run-import`)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user