mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
del:删除了HTTP服务器 and 删除了自动化加分的json显示
This commit is contained in:
@@ -31,7 +31,6 @@
|
||||
"mica-electron": "^1.5.16",
|
||||
"os": "^0.1.2",
|
||||
"pinyin-pro": "^3.27.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"tdesign-react": "^1.16.3",
|
||||
|
||||
Generated
-9
@@ -33,9 +33,6 @@ importers:
|
||||
pinyin-pro:
|
||||
specifier: ^3.27.0
|
||||
version: 3.27.0
|
||||
prismjs:
|
||||
specifier: ^1.30.0
|
||||
version: 1.30.0
|
||||
react-router-dom:
|
||||
specifier: ^6.28.0
|
||||
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'}
|
||||
hasBin: true
|
||||
|
||||
prismjs@1.30.0:
|
||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
proc-log@5.0.0:
|
||||
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
@@ -6179,8 +6172,6 @@ snapshots:
|
||||
|
||||
prettier@3.7.4: {}
|
||||
|
||||
prismjs@1.30.0: {}
|
||||
|
||||
proc-log@5.0.0: {}
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
@@ -17,7 +17,6 @@ export {
|
||||
WindowManagerToken,
|
||||
TrayServiceToken,
|
||||
AutoScoreServiceToken,
|
||||
HttpServerServiceToken,
|
||||
FileSystemServiceToken
|
||||
} from './tokens'
|
||||
export type {
|
||||
|
||||
@@ -15,5 +15,4 @@ export const ThemeServiceToken = Symbol.for('secscore.themeService')
|
||||
export const WindowManagerToken = Symbol.for('secscore.windowManager')
|
||||
export const TrayServiceToken = Symbol.for('secscore.trayService')
|
||||
export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService')
|
||||
export const HttpServerServiceToken = Symbol.for('secscore.httpServerService')
|
||||
export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService')
|
||||
|
||||
@@ -13,7 +13,6 @@ import { PermissionService } from './services/PermissionService'
|
||||
import { AuthService } from './services/AuthService'
|
||||
import { DataService } from './services/DataService'
|
||||
import { ThemeService } from './services/ThemeService'
|
||||
import { HttpServerService } from './services/HttpServerService'
|
||||
import { WindowManager, type windowManagerOptions } from './services/WindowManager'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { AutoScoreService } from './services/AutoScoreService'
|
||||
@@ -40,7 +39,6 @@ import {
|
||||
WindowManagerToken,
|
||||
TrayServiceToken,
|
||||
AutoScoreServiceToken,
|
||||
HttpServerServiceToken,
|
||||
FileSystemServiceToken
|
||||
} from './hosting'
|
||||
|
||||
@@ -274,10 +272,6 @@ app.whenReady().then(async () => {
|
||||
(p) => new FileSystemService(p.get(MainContext), config.configDir)
|
||||
)
|
||||
services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext)))
|
||||
services.addSingleton(
|
||||
HttpServerServiceToken,
|
||||
(p) => new HttpServerService(p.get(MainContext))
|
||||
)
|
||||
})
|
||||
.configure(async (_builderContext, appCtx) => {
|
||||
const services = appCtx.services
|
||||
@@ -303,7 +297,6 @@ app.whenReady().then(async () => {
|
||||
const tray = services.get(TrayServiceToken) as TrayService
|
||||
tray.initialize()
|
||||
}
|
||||
services.get(HttpServerServiceToken)
|
||||
services.get(FileSystemServiceToken)
|
||||
const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService
|
||||
await autoScore.initialize?.()
|
||||
|
||||
@@ -619,6 +619,25 @@ export class AutoScoreService extends Service {
|
||||
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[] {
|
||||
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,
|
||||
TooltipLite
|
||||
} from 'tdesign-react'
|
||||
import Code from './Code'
|
||||
|
||||
interface AutoScoreRule {
|
||||
id: number
|
||||
@@ -568,41 +567,6 @@ export const AutoScoreManager: React.FC = () => {
|
||||
style={{ color: 'var(--ss-text-main)' }}
|
||||
/>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 [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 permissionTag = useMemo(() => {
|
||||
@@ -103,7 +91,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
|
||||
useEffect(() => {
|
||||
loadAll()
|
||||
refreshHttpServerStatus()
|
||||
if (!(window as any).api) return
|
||||
const unsubscribe = (window as any).api.onSettingChanged((change: any) => {
|
||||
setSettings((prev) => {
|
||||
@@ -261,71 +248,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
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()
|
||||
return (
|
||||
<div style={{ padding: '24px', maxWidth: '900px', margin: '0 auto' }}>
|
||||
@@ -618,115 +540,6 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
||||
</Card>
|
||||
</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 链接">
|
||||
<Card style={{ backgroundColor: 'var(--ss-card-bg)', color: 'var(--ss-text-main)' }}>
|
||||
<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 })
|
||||
}
|
||||
|
||||
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 }> {
|
||||
return await (window as any).api.invoke('auto-score:getStatus', {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user