mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
del:删除了HTTP服务器 and 删除了自动化加分的json显示
This commit is contained in:
@@ -31,7 +31,6 @@
|
|||||||
"mica-electron": "^1.5.16",
|
"mica-electron": "^1.5.16",
|
||||||
"os": "^0.1.2",
|
"os": "^0.1.2",
|
||||||
"pinyin-pro": "^3.27.0",
|
"pinyin-pro": "^3.27.0",
|
||||||
"prismjs": "^1.30.0",
|
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"tdesign-react": "^1.16.3",
|
"tdesign-react": "^1.16.3",
|
||||||
|
|||||||
Generated
-9
@@ -33,9 +33,6 @@ importers:
|
|||||||
pinyin-pro:
|
pinyin-pro:
|
||||||
specifier: ^3.27.0
|
specifier: ^3.27.0
|
||||||
version: 3.27.0
|
version: 3.27.0
|
||||||
prismjs:
|
|
||||||
specifier: ^1.30.0
|
|
||||||
version: 1.30.0
|
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.28.0
|
specifier: ^6.28.0
|
||||||
version: 6.30.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 6.30.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
@@ -2636,10 +2633,6 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
prismjs@1.30.0:
|
|
||||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
proc-log@5.0.0:
|
proc-log@5.0.0:
|
||||||
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
|
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
|
||||||
engines: {node: ^18.17.0 || >=20.5.0}
|
engines: {node: ^18.17.0 || >=20.5.0}
|
||||||
@@ -6179,8 +6172,6 @@ snapshots:
|
|||||||
|
|
||||||
prettier@3.7.4: {}
|
prettier@3.7.4: {}
|
||||||
|
|
||||||
prismjs@1.30.0: {}
|
|
||||||
|
|
||||||
proc-log@5.0.0: {}
|
proc-log@5.0.0: {}
|
||||||
|
|
||||||
progress@2.0.3: {}
|
progress@2.0.3: {}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export {
|
|||||||
WindowManagerToken,
|
WindowManagerToken,
|
||||||
TrayServiceToken,
|
TrayServiceToken,
|
||||||
AutoScoreServiceToken,
|
AutoScoreServiceToken,
|
||||||
HttpServerServiceToken,
|
|
||||||
FileSystemServiceToken
|
FileSystemServiceToken
|
||||||
} from './tokens'
|
} from './tokens'
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ export const ThemeServiceToken = Symbol.for('secscore.themeService')
|
|||||||
export const WindowManagerToken = Symbol.for('secscore.windowManager')
|
export const WindowManagerToken = Symbol.for('secscore.windowManager')
|
||||||
export const TrayServiceToken = Symbol.for('secscore.trayService')
|
export const TrayServiceToken = Symbol.for('secscore.trayService')
|
||||||
export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService')
|
export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService')
|
||||||
export const HttpServerServiceToken = Symbol.for('secscore.httpServerService')
|
|
||||||
export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService')
|
export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService')
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { PermissionService } from './services/PermissionService'
|
|||||||
import { AuthService } from './services/AuthService'
|
import { AuthService } from './services/AuthService'
|
||||||
import { DataService } from './services/DataService'
|
import { DataService } from './services/DataService'
|
||||||
import { ThemeService } from './services/ThemeService'
|
import { ThemeService } from './services/ThemeService'
|
||||||
import { HttpServerService } from './services/HttpServerService'
|
|
||||||
import { WindowManager, type windowManagerOptions } from './services/WindowManager'
|
import { WindowManager, type windowManagerOptions } from './services/WindowManager'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
import { AutoScoreService } from './services/AutoScoreService'
|
import { AutoScoreService } from './services/AutoScoreService'
|
||||||
@@ -40,7 +39,6 @@ import {
|
|||||||
WindowManagerToken,
|
WindowManagerToken,
|
||||||
TrayServiceToken,
|
TrayServiceToken,
|
||||||
AutoScoreServiceToken,
|
AutoScoreServiceToken,
|
||||||
HttpServerServiceToken,
|
|
||||||
FileSystemServiceToken
|
FileSystemServiceToken
|
||||||
} from './hosting'
|
} from './hosting'
|
||||||
|
|
||||||
@@ -274,10 +272,6 @@ app.whenReady().then(async () => {
|
|||||||
(p) => new FileSystemService(p.get(MainContext), config.configDir)
|
(p) => new FileSystemService(p.get(MainContext), config.configDir)
|
||||||
)
|
)
|
||||||
services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext)))
|
services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext)))
|
||||||
services.addSingleton(
|
|
||||||
HttpServerServiceToken,
|
|
||||||
(p) => new HttpServerService(p.get(MainContext))
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.configure(async (_builderContext, appCtx) => {
|
.configure(async (_builderContext, appCtx) => {
|
||||||
const services = appCtx.services
|
const services = appCtx.services
|
||||||
@@ -303,7 +297,6 @@ app.whenReady().then(async () => {
|
|||||||
const tray = services.get(TrayServiceToken) as TrayService
|
const tray = services.get(TrayServiceToken) as TrayService
|
||||||
tray.initialize()
|
tray.initialize()
|
||||||
}
|
}
|
||||||
services.get(HttpServerServiceToken)
|
|
||||||
services.get(FileSystemServiceToken)
|
services.get(FileSystemServiceToken)
|
||||||
const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService
|
const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService
|
||||||
await autoScore.initialize?.()
|
await autoScore.initialize?.()
|
||||||
|
|||||||
@@ -619,6 +619,25 @@ export class AutoScoreService extends Service {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sortRules(ruleIds: number[]): Promise<boolean> {
|
||||||
|
const ruleMap = new Map(this.rules.map((r) => [r.id, r]))
|
||||||
|
const sortedRules: AutoScoreRule[] = []
|
||||||
|
for (const id of ruleIds) {
|
||||||
|
const rule = ruleMap.get(id)
|
||||||
|
if (rule) {
|
||||||
|
sortedRules.push(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const rule of this.rules) {
|
||||||
|
if (!ruleIds.includes(rule.id)) {
|
||||||
|
sortedRules.push(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.rules = sortedRules
|
||||||
|
await this.saveRulesToFile()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
getRules(): AutoScoreRule[] {
|
getRules(): AutoScoreRule[] {
|
||||||
return [...this.rules]
|
return [...this.rules]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,359 +0,0 @@
|
|||||||
import { Service } from '../../shared/kernel'
|
|
||||||
import { MainContext } from '../context'
|
|
||||||
import express, { type Express, type Request, type Response, type NextFunction } from 'express'
|
|
||||||
import { StudentEntity } from '../db/entities'
|
|
||||||
import net from 'net'
|
|
||||||
|
|
||||||
declare module '../../shared/kernel' {
|
|
||||||
interface Context {
|
|
||||||
httpServer: HttpServerService
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HttpServerConfig {
|
|
||||||
port: number
|
|
||||||
host: string
|
|
||||||
corsOrigin?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HttpServerService extends Service {
|
|
||||||
private app: Express | null = null
|
|
||||||
private server: any = null
|
|
||||||
private config: HttpServerConfig = {
|
|
||||||
port: 3000,
|
|
||||||
host: 'localhost',
|
|
||||||
corsOrigin: '*' // 默认允许所有跨域请求
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(ctx: MainContext) {
|
|
||||||
super(ctx, 'httpServer')
|
|
||||||
this.registerIpc()
|
|
||||||
}
|
|
||||||
|
|
||||||
private get mainCtx() {
|
|
||||||
return this.ctx as MainContext
|
|
||||||
}
|
|
||||||
|
|
||||||
private get permissions() {
|
|
||||||
return this.ctx.permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerIpc() {
|
|
||||||
// 启动HTTP服务器
|
|
||||||
this.mainCtx.handle('http:server:start', async (event, config?: Partial<HttpServerConfig>) => {
|
|
||||||
if (!this.permissions.requirePermission(event, 'admin')) {
|
|
||||||
return { success: false, message: 'Permission denied' }
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.start(config)
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
url: `http://${this.config.host}:${this.config.port}`,
|
|
||||||
config: this.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
return { success: false, message: `Failed to start HTTP server: ${message}` }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 停止HTTP服务器
|
|
||||||
this.mainCtx.handle('http:server:stop', async (event) => {
|
|
||||||
if (!this.permissions.requirePermission(event, 'admin')) {
|
|
||||||
return { success: false, message: 'Permission denied' }
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.stop()
|
|
||||||
return { success: true }
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
return { success: false, message: `Failed to stop HTTP server: ${message}` }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取HTTP服务器状态
|
|
||||||
this.mainCtx.handle('http:server:status', async (event) => {
|
|
||||||
if (!this.permissions.requirePermission(event, 'admin')) {
|
|
||||||
return { success: false, message: 'Permission denied' }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
isRunning: this.isRunning(),
|
|
||||||
config: this.config,
|
|
||||||
url: this.isRunning() ? `http://${this.config.host}:${this.config.port}` : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(config?: Partial<HttpServerConfig>): Promise<void> {
|
|
||||||
if (this.isRunning()) {
|
|
||||||
throw new Error('HTTP server is already running')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并配置
|
|
||||||
this.config = { ...this.config, ...config }
|
|
||||||
|
|
||||||
// 检查端口是否可用,如果不可用则自动寻找可用端口
|
|
||||||
const originalPort = this.config.port
|
|
||||||
let attempts = 0
|
|
||||||
const maxAttempts = 100 // 最多尝试100个端口
|
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
|
||||||
if (await this.isPortAvailable(this.config.port)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// 端口被占用,尝试下一个端口
|
|
||||||
this.config.port++
|
|
||||||
attempts++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempts === maxAttempts) {
|
|
||||||
throw new Error(
|
|
||||||
`Could not find available port after ${maxAttempts} attempts starting from ${originalPort}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempts > 0) {
|
|
||||||
this.mainCtx.logger.warn(
|
|
||||||
`Original port ${originalPort} was unavailable. Using port ${this.config.port} instead.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建Express应用
|
|
||||||
this.app = express()
|
|
||||||
|
|
||||||
// 配置中间件
|
|
||||||
this.app.use(express.json())
|
|
||||||
this.app.use(express.urlencoded({ extended: true }))
|
|
||||||
|
|
||||||
// 简单的CORS处理
|
|
||||||
this.app.use((_: Request, res: Response, next: NextFunction) => {
|
|
||||||
res.header('Access-Control-Allow-Origin', this.config.corsOrigin || '*')
|
|
||||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
|
|
||||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 设置路由
|
|
||||||
this.setupRoutes()
|
|
||||||
|
|
||||||
// 启动服务器
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.server = this.app!.listen(this.config.port, this.config.host, () => {
|
|
||||||
this.mainCtx.logger.info(
|
|
||||||
`HTTP server started at http://${this.config.host}:${this.config.port}`
|
|
||||||
)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.server.on('error', (error: Error) => {
|
|
||||||
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop(): Promise<void> {
|
|
||||||
if (!this.isRunning()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.server!.close((error?: Error) => {
|
|
||||||
if (error) {
|
|
||||||
this.mainCtx.logger.error(`Failed to stop HTTP server: ${error.message}`)
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
this.mainCtx.logger.info('HTTP server stopped')
|
|
||||||
this.server = null
|
|
||||||
this.app = null
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning(): boolean {
|
|
||||||
return this.server !== null && this.app !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
private async isPortAvailable(port: number): Promise<boolean> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const server = net.createServer()
|
|
||||||
server.listen(port, '127.0.0.1')
|
|
||||||
server.on('error', () => {
|
|
||||||
resolve(false)
|
|
||||||
})
|
|
||||||
server.on('listening', () => {
|
|
||||||
server.close(() => {
|
|
||||||
resolve(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupRoutes() {
|
|
||||||
if (!this.app) return
|
|
||||||
|
|
||||||
// 健康检查端点
|
|
||||||
this.app.get('/health', (_, res: Response) => {
|
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取所有学生分数
|
|
||||||
this.app.get('/api/students/scores', async (_req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
|
|
||||||
const students = await studentRepo.find({
|
|
||||||
select: ['id', 'name', 'score'],
|
|
||||||
order: { score: 'DESC', name: 'ASC' }
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: students.map((student) => ({
|
|
||||||
id: student.id,
|
|
||||||
name: student.name,
|
|
||||||
score: student.score
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.mainCtx.logger.error(`Failed to fetch student scores: ${error}`)
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Failed to fetch student scores'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取单个学生分数
|
|
||||||
this.app.get('/api/students/:id/score', async (_req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const id = parseInt(_req.params.id)
|
|
||||||
if (isNaN(id)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Invalid student ID'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
|
|
||||||
const student = await studentRepo.findOne({
|
|
||||||
where: { id },
|
|
||||||
select: ['id', 'name', 'score']
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!student) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Student not found'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
id: student.id,
|
|
||||||
name: student.name,
|
|
||||||
score: student.score
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.mainCtx.logger.error(`Failed to fetch student score: ${error}`)
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Failed to fetch student score'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 按姓名搜索学生分数
|
|
||||||
this.app.get('/api/students/search', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const name = req.query.name as string
|
|
||||||
if (!name || name.trim() === '') {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Name parameter is required'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
|
|
||||||
const students = await studentRepo.find({
|
|
||||||
where: { name },
|
|
||||||
select: ['id', 'name', 'score']
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: students.map((student) => ({
|
|
||||||
id: student.id,
|
|
||||||
name: student.name,
|
|
||||||
score: student.score
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.mainCtx.logger.error(`Failed to search student: ${error}`)
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Failed to search student'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取排行榜(前N名学生)
|
|
||||||
this.app.get('/api/leaderboard', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const limit = parseInt(req.query.limit as string) || 10
|
|
||||||
const studentRepo = this.mainCtx.db.dataSource.getRepository(StudentEntity)
|
|
||||||
const students = await studentRepo.find({
|
|
||||||
select: ['id', 'name', 'score'],
|
|
||||||
order: { score: 'DESC', name: 'ASC' },
|
|
||||||
take: limit
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: students.map((student) => ({
|
|
||||||
id: student.id,
|
|
||||||
name: student.name,
|
|
||||||
score: student.score
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.mainCtx.logger.error(`Failed to fetch leaderboard: ${error}`)
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Failed to fetch leaderboard'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// stray expression removed
|
|
||||||
// 404处理
|
|
||||||
this.app.use((_req: Request, res: Response) => {
|
|
||||||
res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Endpoint not found'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 错误处理中间件
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
this.app.use((error: Error, _: Request, res: Response, _next: NextFunction) => {
|
|
||||||
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: 'Internal server error'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
TooltipLite
|
TooltipLite
|
||||||
} from 'tdesign-react'
|
} from 'tdesign-react'
|
||||||
import Code from './Code'
|
|
||||||
|
|
||||||
interface AutoScoreRule {
|
interface AutoScoreRule {
|
||||||
id: number
|
id: number
|
||||||
@@ -568,41 +567,6 @@ export const AutoScoreManager: React.FC = () => {
|
|||||||
style={{ color: 'var(--ss-text-main)' }}
|
style={{ color: 'var(--ss-text-main)' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card style={{ marginBottom: '24px' }}>
|
|
||||||
<Code
|
|
||||||
code={(() => {
|
|
||||||
if (editingRuleId !== null) {
|
|
||||||
const values = form.getFieldsValue(true) as unknown as AutoScoreRuleFormValues
|
|
||||||
const studentNames = Array.isArray(values.studentNames) ? values.studentNames : []
|
|
||||||
const triggersPayload = triggerList.map((t) => ({
|
|
||||||
event: t.eventName,
|
|
||||||
value: t.value,
|
|
||||||
relation: t.relation
|
|
||||||
}))
|
|
||||||
const actionsPayload = actionList.map((a) => ({
|
|
||||||
event: a.eventName,
|
|
||||||
value: a.value,
|
|
||||||
reason: a.reason
|
|
||||||
}))
|
|
||||||
|
|
||||||
const currentRule = {
|
|
||||||
id: editingRuleId,
|
|
||||||
enabled: true,
|
|
||||||
name: values.name || '',
|
|
||||||
studentNames,
|
|
||||||
triggers: triggersPayload,
|
|
||||||
actions: actionsPayload
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(currentRule, null, 2)
|
|
||||||
} else {
|
|
||||||
return JSON.stringify(rules, null, 2)
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
language={'json'}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
import Prism from 'prismjs'
|
|
||||||
import { useTheme } from '../contexts/ThemeContext'
|
|
||||||
|
|
||||||
import 'prismjs/themes/prism-okaidia.min.css'
|
|
||||||
|
|
||||||
const Code = ({ code, language }) => {
|
|
||||||
const { currentTheme } = useTheme()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 重新高亮代码
|
|
||||||
Prism.highlightAll()
|
|
||||||
}, [currentTheme, code, language])
|
|
||||||
|
|
||||||
// 根据主题设置不同的类名
|
|
||||||
const isDark = currentTheme?.mode === 'dark'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
opacity: '0.6',
|
|
||||||
// 根据主题动态设置样式
|
|
||||||
backgroundColor: isDark ? '#282c34' : '#f5f2f0',
|
|
||||||
color: isDark ? '#abb2bf' : '#383a42',
|
|
||||||
padding: '16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
overflow: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<pre className={isDark ? 'prism-okaidia' : 'prism-coy'}>
|
|
||||||
<code className={`language-${language}`}>{code}</code>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Code
|
|
||||||
@@ -62,18 +62,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
const [settleDialogVisible, setSettleDialogVisible] = useState(false)
|
const [settleDialogVisible, setSettleDialogVisible] = useState(false)
|
||||||
|
|
||||||
const [urlRegisterLoading, setUrlRegisterLoading] = useState(false)
|
const [urlRegisterLoading, setUrlRegisterLoading] = useState(false)
|
||||||
|
|
||||||
// HTTP Server状态
|
|
||||||
const [httpServerStatus, setHttpServerStatus] = useState<{
|
|
||||||
isRunning: boolean
|
|
||||||
config: { port: number; host: string; corsOrigin?: string }
|
|
||||||
url: string | null
|
|
||||||
} | null>(null)
|
|
||||||
const [httpLoading, setHttpLoading] = useState(false)
|
|
||||||
const [httpPort, setHttpPort] = useState('3000')
|
|
||||||
const [httpHost, setHttpHost] = useState('localhost')
|
|
||||||
const [httpCorsOrigin, setHttpCorsOrigin] = useState('*')
|
|
||||||
|
|
||||||
const canAdmin = permission === 'admin'
|
const canAdmin = permission === 'admin'
|
||||||
|
|
||||||
const permissionTag = useMemo(() => {
|
const permissionTag = useMemo(() => {
|
||||||
@@ -103,7 +91,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAll()
|
loadAll()
|
||||||
refreshHttpServerStatus()
|
|
||||||
if (!(window as any).api) return
|
if (!(window as any).api) return
|
||||||
const unsubscribe = (window as any).api.onSettingChanged((change: any) => {
|
const unsubscribe = (window as any).api.onSettingChanged((change: any) => {
|
||||||
setSettings((prev) => {
|
setSettings((prev) => {
|
||||||
@@ -261,71 +248,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
setSettleDialogVisible(true)
|
setSettleDialogVisible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Server函数
|
|
||||||
const refreshHttpServerStatus = async () => {
|
|
||||||
if (!(window as any).api) return
|
|
||||||
try {
|
|
||||||
const res = await (window as any).api.httpServerStatus()
|
|
||||||
if (res.success && res.data) {
|
|
||||||
setHttpServerStatus(res.data)
|
|
||||||
setHttpPort(res.data.config.port.toString())
|
|
||||||
setHttpHost(res.data.config.host)
|
|
||||||
setHttpCorsOrigin(res.data.config.corsOrigin || '*')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to refresh HTTP server status:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleHttpServerStart = async () => {
|
|
||||||
if (!(window as any).api) return
|
|
||||||
setHttpLoading(true)
|
|
||||||
try {
|
|
||||||
const config: any = {}
|
|
||||||
const portNum = parseInt(httpPort)
|
|
||||||
if (!isNaN(portNum) && portNum > 0 && portNum <= 65535) {
|
|
||||||
config.port = portNum
|
|
||||||
}
|
|
||||||
if (httpHost.trim()) {
|
|
||||||
config.host = httpHost.trim()
|
|
||||||
}
|
|
||||||
if (httpCorsOrigin.trim()) {
|
|
||||||
config.corsOrigin = httpCorsOrigin.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await (window as any).api.httpServerStart(config)
|
|
||||||
if (res.success) {
|
|
||||||
MessagePlugin.success(`HTTP服务器已启动: ${res.data.url}`)
|
|
||||||
await refreshHttpServerStatus()
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error(res.message || '启动失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
MessagePlugin.error('启动HTTP服务器时发生错误')
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
setHttpLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleHttpServerStop = async () => {
|
|
||||||
if (!(window as any).api) return
|
|
||||||
setHttpLoading(true)
|
|
||||||
try {
|
|
||||||
const res = await (window as any).api.httpServerStop()
|
|
||||||
if (res.success) {
|
|
||||||
MessagePlugin.success('HTTP服务器已停止')
|
|
||||||
await refreshHttpServerStatus()
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error(res.message || '停止失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
MessagePlugin.error('停止HTTP服务器时发生错误')
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
setHttpLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
<div style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
||||||
@@ -618,115 +540,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
</Card>
|
</Card>
|
||||||
</Tabs.TabPanel>
|
</Tabs.TabPanel>
|
||||||
|
|
||||||
<Tabs.TabPanel value="http" label="HTTP服务器">
|
|
||||||
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
|
|
||||||
<div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
|
|
||||||
HTTP服务器 API
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginBottom: '12px',
|
|
||||||
fontSize: '13px',
|
|
||||||
color: 'var(--ss-text-secondary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
通过HTTP API提供学生分数查询服务,可在局域网内访问。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form labelWidth={120}>
|
|
||||||
<Form.FormItem label="服务器状态">
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<Tag theme={httpServerStatus?.isRunning ? 'success' : 'default'} variant="light">
|
|
||||||
{httpServerStatus?.isRunning ? '运行中' : '已停止'}
|
|
||||||
</Tag>
|
|
||||||
{httpServerStatus?.isRunning && (
|
|
||||||
<div style={{ fontSize: '12px', color: 'var(--ss-text-secondary)' }}>
|
|
||||||
访问地址: {httpServerStatus.url}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Form.FormItem>
|
|
||||||
|
|
||||||
<Form.FormItem label="端口设置">
|
|
||||||
<Input
|
|
||||||
value={httpPort}
|
|
||||||
onChange={(v) => setHttpPort(v)}
|
|
||||||
placeholder="端口号 (默认: 3000)"
|
|
||||||
style={{ width: '200px' }}
|
|
||||||
disabled={!canAdmin || httpLoading}
|
|
||||||
/>
|
|
||||||
</Form.FormItem>
|
|
||||||
|
|
||||||
<Form.FormItem label="主机地址">
|
|
||||||
<Input
|
|
||||||
value={httpHost}
|
|
||||||
onChange={(v) => setHttpHost(v)}
|
|
||||||
placeholder="主机地址 (默认: localhost)"
|
|
||||||
style={{ width: '200px' }}
|
|
||||||
disabled={!canAdmin || httpLoading}
|
|
||||||
/>
|
|
||||||
</Form.FormItem>
|
|
||||||
|
|
||||||
<Form.FormItem label="CORS来源">
|
|
||||||
<Input
|
|
||||||
value={httpCorsOrigin}
|
|
||||||
onChange={(v) => setHttpCorsOrigin(v)}
|
|
||||||
placeholder="CORS来源 (默认: *)"
|
|
||||||
style={{ width: '200px' }}
|
|
||||||
disabled={!canAdmin || httpLoading}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{ marginTop: '4px', fontSize: '12px', color: 'var(--ss-text-secondary)' }}
|
|
||||||
>
|
|
||||||
* 表示允许所有来源,建议在生产环境中设置具体域名
|
|
||||||
</div>
|
|
||||||
</Form.FormItem>
|
|
||||||
|
|
||||||
<Form.FormItem label="操作">
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
theme="primary"
|
|
||||||
loading={httpLoading}
|
|
||||||
disabled={!canAdmin}
|
|
||||||
onClick={handleHttpServerStart}
|
|
||||||
>
|
|
||||||
{httpServerStatus?.isRunning ? '重启服务器' : '启动服务器'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
theme="danger"
|
|
||||||
variant="outline"
|
|
||||||
loading={httpLoading}
|
|
||||||
disabled={!canAdmin || !httpServerStatus?.isRunning}
|
|
||||||
onClick={handleHttpServerStop}
|
|
||||||
>
|
|
||||||
停止服务器
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
disabled={!canAdmin || httpLoading}
|
|
||||||
onClick={refreshHttpServerStatus}
|
|
||||||
>
|
|
||||||
刷新状态
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.FormItem>
|
|
||||||
|
|
||||||
<Form.FormItem label="API端点">
|
|
||||||
<div
|
|
||||||
style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '12px' }}
|
|
||||||
>
|
|
||||||
<div>• GET /health - 健康检查</div>
|
|
||||||
<div>• GET /api/students/scores - 获取所有学生分数</div>
|
|
||||||
<div>• GET /api/students/:id/score - 获取单个学生分数</div>
|
|
||||||
<div>• GET /api/students/search?name=姓名 - 按姓名搜索</div>
|
|
||||||
<div>• GET /api/leaderboard?limit=10 - 获取排行榜</div>
|
|
||||||
</div>
|
|
||||||
</Form.FormItem>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
</Tabs.TabPanel>
|
|
||||||
|
|
||||||
<Tabs.TabPanel value="url" label="URL 链接">
|
<Tabs.TabPanel value="url" label="URL 链接">
|
||||||
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
|
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
|
<div style={{ fontSize: '16px', fontWeight: 600, marginBottom: '8px' }}>
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ export class AutoScoreService extends Service {
|
|||||||
return await (window as any).api.invoke('auto-score:toggleRule', { ruleId, enabled })
|
return await (window as any).api.invoke('auto-score:toggleRule', { ruleId, enabled })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sortRules(
|
||||||
|
ruleIds: number[]
|
||||||
|
): Promise<{ success: boolean; data?: boolean; message?: string }> {
|
||||||
|
return await (window as any).api.invoke('auto-score:sortRules', ruleIds)
|
||||||
|
}
|
||||||
|
|
||||||
async getStatus(): Promise<{ success: boolean; data?: { enabled: boolean }; message?: string }> {
|
async getStatus(): Promise<{ success: boolean; data?: { enabled: boolean }; message?: string }> {
|
||||||
return await (window as any).api.invoke('auto-score:getStatus', {})
|
return await (window as any).api.invoke('auto-score:getStatus', {})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user