mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
e83f17af56
feat(theme): 增强侧边栏选中项样式配置 style: 优化主题变量和样式覆盖逻辑 chore: 清理未使用的依赖和配置文件
220 lines
6.9 KiB
TypeScript
220 lines
6.9 KiB
TypeScript
import { Service } from '../../shared/kernel'
|
|
import { MainContext } from '../context'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { IsNull } from 'typeorm'
|
|
import { ScoreEventEntity, StudentEntity } from '../db/entities'
|
|
|
|
export interface scoreEvent {
|
|
id: number
|
|
uuid: string
|
|
student_name: string
|
|
reason_content: string
|
|
delta: number
|
|
val_prev: number
|
|
val_curr: number
|
|
event_time: string
|
|
settlement_id?: number | null
|
|
}
|
|
|
|
declare module '../../shared/kernel' {
|
|
interface Context {
|
|
events: EventRepository
|
|
}
|
|
}
|
|
|
|
export class EventRepository extends Service {
|
|
constructor(ctx: MainContext) {
|
|
super(ctx, 'events')
|
|
this.registerIpc()
|
|
}
|
|
|
|
private get mainCtx() {
|
|
return this.ctx as MainContext
|
|
}
|
|
|
|
private registerIpc() {
|
|
this.mainCtx.handle('db:event:query', async (_, params) => {
|
|
try {
|
|
return { success: true, data: await this.findAll(params?.limit) }
|
|
} catch (err: any) {
|
|
return { success: false, message: err.message }
|
|
}
|
|
})
|
|
|
|
this.mainCtx.handle('db:event:delete', async (event, uuid) => {
|
|
try {
|
|
if (!this.mainCtx.permissions.requirePermission(event, 'points'))
|
|
return { success: false, message: 'Permission denied' }
|
|
await this.deleteByUuid(uuid)
|
|
return { success: true }
|
|
} catch (err: any) {
|
|
return { success: false, message: err.message }
|
|
}
|
|
})
|
|
|
|
this.mainCtx.handle('db:event:create', async (event, data) => {
|
|
try {
|
|
if (!this.mainCtx.permissions.requirePermission(event, 'points'))
|
|
return { success: false, message: 'Permission denied' }
|
|
const id = await this.create(data)
|
|
return { success: true, data: id }
|
|
} catch (e: any) {
|
|
return { success: false, message: e.message }
|
|
}
|
|
})
|
|
|
|
this.mainCtx.handle('db:event:queryByStudent', async (_, params) => {
|
|
try {
|
|
const limit = Number(params?.limit ?? 50)
|
|
const studentName = String(params?.student_name ?? '')
|
|
const startTime = params?.startTime ? String(params.startTime) : null
|
|
if (!studentName) return { success: true, data: [] }
|
|
const rows = await this.queryByStudent(studentName, startTime, limit)
|
|
return { success: true, data: rows }
|
|
} catch (err: any) {
|
|
return { success: false, message: err.message }
|
|
}
|
|
})
|
|
|
|
this.mainCtx.handle('db:leaderboard:query', async (_, params) => {
|
|
try {
|
|
const range = String(params?.range ?? 'today')
|
|
const data = await this.queryLeaderboard(range)
|
|
return { success: true, data }
|
|
} catch (err: any) {
|
|
return { success: false, message: err.message }
|
|
}
|
|
})
|
|
}
|
|
|
|
async findAll(limit = 100) {
|
|
const repo = this.ctx.db.dataSource.getRepository(ScoreEventEntity)
|
|
return await repo.find({
|
|
where: { settlement_id: IsNull() },
|
|
order: { event_time: 'DESC' },
|
|
take: limit
|
|
})
|
|
}
|
|
|
|
async create(event: { student_name: string; reason_content: string; delta: number }) {
|
|
const ds = this.ctx.db.dataSource
|
|
return await ds.transaction(async (manager) => {
|
|
const studentName = String(event?.student_name ?? '').trim()
|
|
const reasonContent = String(event?.reason_content ?? '').trim()
|
|
const delta = Number(event?.delta ?? 0)
|
|
|
|
const studentRepo = manager.getRepository(StudentEntity)
|
|
const existingStudent = await studentRepo.findOne({ where: { name: studentName } })
|
|
if (!existingStudent) throw new Error('Student not found')
|
|
|
|
const val_prev = Number(existingStudent.score ?? 0)
|
|
const val_curr = val_prev + (Number.isFinite(delta) ? delta : 0)
|
|
const uuid = uuidv4()
|
|
const event_time = new Date().toISOString()
|
|
|
|
const eventsRepo = manager.getRepository(ScoreEventEntity)
|
|
const saved = await eventsRepo.save(
|
|
eventsRepo.create({
|
|
uuid,
|
|
student_name: studentName,
|
|
reason_content: reasonContent,
|
|
delta: Number.isFinite(delta) ? delta : 0,
|
|
val_prev,
|
|
val_curr,
|
|
event_time,
|
|
settlement_id: null
|
|
})
|
|
)
|
|
|
|
await studentRepo.update(
|
|
{ id: existingStudent.id },
|
|
{ score: val_curr, updated_at: new Date().toISOString() }
|
|
)
|
|
|
|
return saved.id
|
|
})
|
|
}
|
|
|
|
async deleteByUuid(uuid: string) {
|
|
const ds = this.ctx.db.dataSource
|
|
await ds.transaction(async (manager) => {
|
|
const eventsRepo = manager.getRepository(ScoreEventEntity)
|
|
const ev = await eventsRepo.findOne({ where: { uuid: String(uuid ?? '').trim() } })
|
|
if (!ev) return
|
|
if (ev.settlement_id !== null && ev.settlement_id !== undefined) {
|
|
throw new Error('该记录已结算,无法撤销')
|
|
}
|
|
|
|
const studentRepo = manager.getRepository(StudentEntity)
|
|
const student = await studentRepo.findOne({ where: { name: ev.student_name } })
|
|
if (student) {
|
|
await studentRepo.update(
|
|
{ id: student.id },
|
|
{
|
|
score: Number(student.score ?? 0) - Number(ev.delta ?? 0),
|
|
updated_at: new Date().toISOString()
|
|
}
|
|
)
|
|
}
|
|
|
|
await eventsRepo.delete({ uuid: ev.uuid })
|
|
})
|
|
}
|
|
|
|
async queryByStudent(studentName: string, startTime: string | null, limit: number) {
|
|
const repo = this.ctx.db.dataSource.getRepository(ScoreEventEntity)
|
|
const qb = repo
|
|
.createQueryBuilder('e')
|
|
.where('e.student_name = :studentName', { studentName })
|
|
.andWhere('e.settlement_id IS NULL')
|
|
.orderBy('e.event_time', 'DESC')
|
|
.limit(limit)
|
|
if (startTime) {
|
|
qb.andWhere('julianday(e.event_time) >= julianday(:startTime)', { startTime })
|
|
}
|
|
return await qb.getMany()
|
|
}
|
|
|
|
async queryLeaderboard(range: string) {
|
|
const now = new Date()
|
|
let start = new Date(now)
|
|
if (range === 'today') {
|
|
start.setHours(0, 0, 0, 0)
|
|
} else if (range === 'week') {
|
|
const day = start.getDay()
|
|
const diff = (day === 0 ? -6 : 1) - day
|
|
start.setDate(start.getDate() + diff)
|
|
start.setHours(0, 0, 0, 0)
|
|
} else if (range === 'month') {
|
|
start = new Date(start.getFullYear(), start.getMonth(), 1)
|
|
start.setHours(0, 0, 0, 0)
|
|
} else {
|
|
start.setHours(0, 0, 0, 0)
|
|
}
|
|
const startTime = start.toISOString()
|
|
|
|
const qb = this.ctx.db.dataSource
|
|
.getRepository(StudentEntity)
|
|
.createQueryBuilder('s')
|
|
.leftJoin(
|
|
ScoreEventEntity,
|
|
'e',
|
|
'e.student_name = s.name AND e.settlement_id IS NULL AND julianday(e.event_time) >= julianday(:startTime)',
|
|
{ startTime }
|
|
)
|
|
.select('s.id', 'id')
|
|
.addSelect('s.name', 'name')
|
|
.addSelect('s.score', 'score')
|
|
.addSelect('COALESCE(SUM(e.delta), 0)', 'range_change')
|
|
.groupBy('s.id')
|
|
.addGroupBy('s.name')
|
|
.addGroupBy('s.score')
|
|
.orderBy('s.score', 'DESC')
|
|
.addOrderBy('range_change', 'DESC')
|
|
.addOrderBy('s.name', 'ASC')
|
|
|
|
const rows = await qb.getRawMany()
|
|
return { startTime, rows }
|
|
}
|
|
}
|