feat: 完成玩家审核试卷编辑器
Sync source to CNB / Sync Git ref (push) Skipped

- 新增试卷管理 Tab:列表展示、启用状态、题数/总分/通过分、目标权限标签、复制直链
- 新增试卷编辑弹窗:标题、描述、阅卷模式、通过分数、启用开关
- 目标权限多选、前置身份开关 + 判断方式(ANY/ALL)+ 权限多选
- 题目编辑:五种题型、上下移、删除、信息题、选答题、生死题、自动批复
- 选择题选项增删改、默认选项、正确选项、多选计分模式、选项得分/扣分
- 填空/简答作答字数上限与标准答案
- 查重开关与通过邮件配置(标题/正文/补充说明/附带邀请码)
- 保存前校验:标题、题目、权限、通过分、邮件必填项
- 公开端审核类型改名 Public 前缀,避免与后台同名冲突
- 补充中英文 locale 共 37 个键
This commit is contained in:
AxTps committed 2026-10-18 10:25:48 +08:00
1 parent 8d73689356
commit 7b3a5212e6
5 files changed
+1534 -201

No files matched your search

@@ -5,8 +5,8 @@ import { computed, onMounted, ref } from 'vue'
import {
skinSiteReviewCatalog,
type SkinSiteReviewPaper,
type SkinSiteReviewQuestion,
type SkinSiteReviewPublicPaper,
type SkinSiteReviewPublicQuestion,
type SkinSiteReviewReceipt,
skinSiteSubmitReview,
} from '@/helpers/skin-site-api'
@@ -46,10 +46,10 @@ const messages = defineMessages({
const emit = defineEmits<{ (e: 'back'): void }>()
const papers = ref<SkinSiteReviewPaper[]>([])
const papers = ref<SkinSiteReviewPublicPaper[]>([])
const loading = ref(true)
const failed = ref(false)
const activePaper = ref<SkinSiteReviewPaper | null>(null)
const activePaper = ref<SkinSiteReviewPublicPaper | null>(null)
const email = ref('')
const inviteCode = ref('')
const answers = ref<Record<string, string | string[]>>({})
@@ -70,7 +70,7 @@ const requiredAnswered = computed(() => {
const canSubmit = computed(() => emailValid.value && requiredAnswered.value && !submitting.value)
function defaultAnswer(q: SkinSiteReviewQuestion): string | string[] {
function defaultAnswer(q: SkinSiteReviewPublicQuestion): string | string[] {
if (q.type === 'MULTIPLE' || q.type === 'SINGLE') {
const preset = (q.options ?? []).filter((o) => o.defaultSelected).map((o) => o.id)
return q.type === 'MULTIPLE' ? preset : (preset[0] ?? '')
@@ -78,7 +78,7 @@ function defaultAnswer(q: SkinSiteReviewQuestion): string | string[] {
return ''
}
function openPaper(paper: SkinSiteReviewPaper) {
function openPaper(paper: SkinSiteReviewPublicPaper) {
activePaper.value = paper
answers.value = {}
for (const q of paper.questions) answers.value[q.id] = defaultAnswer(q)
@@ -93,7 +93,7 @@ function closePaper() {
receipt.value = null
}
function toggleMultiple(q: SkinSiteReviewQuestion, optionId: string, checked: boolean) {
function toggleMultiple(q: SkinSiteReviewPublicQuestion, optionId: string, checked: boolean) {
const current = Array.isArray(answers.value[q.id]) ? (answers.value[q.id] as string[]) : []
answers.value[q.id] = checked
? [...current.filter((id) => id !== optionId), optionId]
File diff suppressed because it is too large. Load diff
+119 -6
View File
@@ -564,32 +564,32 @@ export function skinSitePublicMapServers(): Promise<SkinSiteResponse<SkinSitePub
export type SkinSiteReviewQuestionType = 'SINGLE' | 'MULTIPLE' | 'TRUE_FALSE' | 'FILL' | 'ESSAY'
export type SkinSiteReviewResult = 'PENDING' | 'PASSED' | 'REJECTED'
export interface SkinSiteReviewOption {
export interface SkinSiteReviewPublicOption {
id: string
description: string
correct?: boolean
defaultSelected?: boolean
}
export interface SkinSiteReviewQuestion {
export interface SkinSiteReviewPublicQuestion {
id: string
type: SkinSiteReviewQuestionType
type: SkinSiteReviewPublicQuestionType
title: string
annotation?: string | null
score?: number
optional?: boolean
fatal?: boolean
options?: SkinSiteReviewOption[]
options?: SkinSiteReviewPublicOption[]
answer?: string | null
maxLength?: number | null
informationQuestion?: boolean
}
export interface SkinSiteReviewPaper {
export interface SkinSiteReviewPublicPaper {
id: string
title: string
description?: string
questions: SkinSiteReviewQuestion[]
questions: SkinSiteReviewPublicQuestion[]
[key: string]: unknown
}
@@ -1039,6 +1039,42 @@ export function skinSiteAdminUserFrequencyLimit(
export type SkinSiteReviewMode = 'AUTO' | 'MANUAL'
export type SkinSiteReviewQuestionType = 'SINGLE' | 'MULTIPLE' | 'TRUE_FALSE' | 'FILL' | 'ESSAY'
export interface SkinSiteReviewOption {
id: string
description: string
correct: boolean
defaultSelected: boolean
score: number
}
export interface SkinSiteReviewQuestion {
id: string
type: SkinSiteReviewQuestionType
title: string
annotation?: string | null
score: number
optional: boolean
fatal: boolean
autoReview: boolean
options: SkinSiteReviewOption[]
answer?: string | null
maxLength?: number | null
informationQuestion?: boolean
informationAlias?: string | null
}
export interface SkinSiteReviewPaper {
id: string
title: string
annotation?: string | null
mode: SkinSiteReviewMode
passScore: number
version: number
questions: SkinSiteReviewQuestion[]
}
export interface SkinSiteReviewAnswer {
id: string
questionId?: string
@@ -1069,6 +1105,7 @@ export interface SkinSiteReviewSubmission {
createdAt: number
reviewedAt?: number | null
answers: SkinSiteReviewAnswer[]
paper: SkinSiteReviewPaper
decisionHistory?: Array<{
previousResult: SkinSiteReviewResult
result: SkinSiteReviewResult
@@ -1789,3 +1826,79 @@ export function skinSiteWikiUpdateSettings(
body: settings,
})
}
export interface SkinSiteReviewPaperSummary {
id: string
enabled: boolean
title: string
annotation?: string | null
mode: SkinSiteReviewMode
passScore: number
targetPermissions: string[]
version: number
questionCount: number
totalScore: number
updatedAt: number
prerequisiteMode: 'ANY' | 'ALL'
prerequisitePermissions: string[]
eligible: boolean
}
export interface SkinSiteReviewMailConfig {
enabled: boolean
subject: string
message: string
includeInviteCode: boolean
extraText: string
}
export interface SkinSiteReviewConfigData {
preventDuplicate?: boolean
mail?: SkinSiteReviewMailConfig
id: string
enabled: boolean
title: string
annotation?: string | null
mode: SkinSiteReviewMode
passScore: number
targetPermissions: string[]
version: number
questions: SkinSiteReviewQuestion[]
updatedAt: number
prerequisiteMode: 'ANY' | 'ALL'
prerequisitePermissions: string[]
}
export function skinSiteReviewPaperList(): Promise<
SkinSiteResponse<SkinSiteReviewPaperSummary[]>
> {
return skinSiteRequest<SkinSiteReviewPaperSummary[]>('review/admin/papers')
}
export function skinSiteReviewPaperAdmin(
paperId: string,
): Promise<SkinSiteResponse<SkinSiteReviewConfigData>> {
return skinSiteRequest<SkinSiteReviewConfigData>(
'review/admin/papers/' + encodeURIComponent(paperId),
)
}
export function skinSiteCreateReviewPaperAdmin(
data: SkinSiteReviewConfigData,
): Promise<SkinSiteResponse<SkinSiteReviewConfigData>> {
return skinSiteRequest<SkinSiteReviewConfigData>('review/admin/papers', {
method: 'POST',
body: data,
})
}
export function skinSiteUpdateReviewPaperAdmin(
paperId: string,
data: SkinSiteReviewConfigData,
): Promise<SkinSiteResponse<SkinSiteReviewConfigData>> {
return skinSiteRequest<SkinSiteReviewConfigData>(
'review/admin/papers/' + encodeURIComponent(paperId),
{ method: 'PUT', body: data },
)
}
+38 -1
View File
@@ -12141,5 +12141,42 @@
"app.skin-site.admin.wiki.max-page-bytes": {"message": "Max page bytes"},
"app.skin-site.admin.wiki.max-attachment-bytes": {"message": "Max attachment bytes"},
"app.skin-site.admin.wiki.save-settings": {"message": "Save settings"},
"app.skin-site.admin.wiki.settings-saved": {"message": "Settings saved."}
"app.skin-site.admin.wiki.settings-saved": {"message": "Settings saved."},
"app.skin-site.admin.review.tab-list": {"message": "Submissions"},
"app.skin-site.admin.review.tab-papers": {"message": "Papers"},
"app.skin-site.admin.review.papers-empty": {"message": "No review papers yet."},
"app.skin-site.admin.review.new-paper": {"message": "New paper"},
"app.skin-site.admin.review.edit-paper": {"message": "Edit"},
"app.skin-site.admin.review.paper-title": {"message": "Title"},
"app.skin-site.admin.review.paper-annotation": {"message": "Description"},
"app.skin-site.admin.review.paper-enabled": {"message": "Enabled"},
"app.skin-site.admin.review.paper-mode": {"message": "Mode"},
"app.skin-site.admin.review.paper-pass-score": {"message": "Pass score"},
"app.skin-site.admin.review.paper-target-permissions": {"message": "Target permissions"},
"app.skin-site.admin.review.paper-questions": {"message": "Questions"},
"app.skin-site.admin.review.add-question": {"message": "Add question"},
"app.skin-site.admin.review.question-title": {"message": "Question title"},
"app.skin-site.admin.review.question-type": {"message": "Type"},
"app.skin-site.admin.review.question-score": {"message": "Score"},
"app.skin-site.admin.review.question-annotation": {"message": "Annotation"},
"app.skin-site.admin.review.add-option": {"message": "Add option"},
"app.skin-site.admin.review.option-description": {"message": "Option text"},
"app.skin-site.admin.review.option-correct": {"message": "Correct"},
"app.skin-site.admin.review.remove-question": {"message": "Remove"},
"app.skin-site.admin.review.question-no": {"message": "Question {n}"},
"app.skin-site.admin.review.paper-created": {"message": "Paper created."},
"app.skin-site.admin.review.paper-saved": {"message": "Paper saved."},
"app.skin-site.admin.review.mail-title": {"message": "Pass mail"},
"app.skin-site.admin.review.mail-enabled": {"message": "Send mail on pass"},
"app.skin-site.admin.review.mail-subject": {"message": "Subject"},
"app.skin-site.admin.review.mail-message": {"message": "Body"},
"app.skin-site.admin.review.mail-extra": {"message": "Extra text"},
"app.skin-site.admin.review.mail-include-invite": {"message": "Include invite code"},
"app.skin-site.admin.review.prevent-duplicate": {"message": "Reject duplicate submissions"},
"app.skin-site.admin.review.copy-link": {"message": "Copy link"},
"app.skin-site.admin.review.link-copied": {"message": "Link copied."},
"app.skin-site.admin.review.move-up": {"message": "Move up"},
"app.skin-site.admin.review.move-down": {"message": "Move down"},
"app.skin-site.admin.review.mail-subject-required": {"message": "Subject is required when mail is enabled."},
"app.skin-site.admin.review.mail-message-required": {"message": "Body is required when mail is enabled."}
}
+38 -1
View File
@@ -11972,5 +11972,42 @@
"app.skin-site.admin.wiki.max-page-bytes": {"message": "页面最大字节数"},
"app.skin-site.admin.wiki.max-attachment-bytes": {"message": "附件最大字节数"},
"app.skin-site.admin.wiki.save-settings": {"message": "保存设置"},
"app.skin-site.admin.wiki.settings-saved": {"message": "设置已保存。"}
"app.skin-site.admin.wiki.settings-saved": {"message": "设置已保存。"},
"app.skin-site.admin.review.tab-list": {"message": "申请列表"},
"app.skin-site.admin.review.tab-papers": {"message": "试卷管理"},
"app.skin-site.admin.review.papers-empty": {"message": "还没有审核试卷。"},
"app.skin-site.admin.review.new-paper": {"message": "新建试卷"},
"app.skin-site.admin.review.edit-paper": {"message": "编辑"},
"app.skin-site.admin.review.paper-title": {"message": "试卷标题"},
"app.skin-site.admin.review.paper-annotation": {"message": "描述"},
"app.skin-site.admin.review.paper-enabled": {"message": "启用"},
"app.skin-site.admin.review.paper-mode": {"message": "阅卷模式"},
"app.skin-site.admin.review.paper-pass-score": {"message": "通过分数"},
"app.skin-site.admin.review.paper-target-permissions": {"message": "通过后获得的身份"},
"app.skin-site.admin.review.paper-questions": {"message": "题目"},
"app.skin-site.admin.review.add-question": {"message": "添加题目"},
"app.skin-site.admin.review.question-title": {"message": "题目"},
"app.skin-site.admin.review.question-type": {"message": "题型"},
"app.skin-site.admin.review.question-score": {"message": "分数"},
"app.skin-site.admin.review.question-annotation": {"message": "题目注解"},
"app.skin-site.admin.review.add-option": {"message": "添加选项"},
"app.skin-site.admin.review.option-description": {"message": "选项文本"},
"app.skin-site.admin.review.option-correct": {"message": "正确"},
"app.skin-site.admin.review.remove-question": {"message": "删除"},
"app.skin-site.admin.review.question-no": {"message": "第 {n} 题"},
"app.skin-site.admin.review.paper-created": {"message": "试卷已创建。"},
"app.skin-site.admin.review.paper-saved": {"message": "试卷已保存。"},
"app.skin-site.admin.review.mail-title": {"message": "通过邮件"},
"app.skin-site.admin.review.mail-enabled": {"message": "审核通过后发送邮件"},
"app.skin-site.admin.review.mail-subject": {"message": "邮件标题"},
"app.skin-site.admin.review.mail-message": {"message": "邮件正文"},
"app.skin-site.admin.review.mail-extra": {"message": "邮件补充说明"},
"app.skin-site.admin.review.mail-include-invite": {"message": "附带邀请码"},
"app.skin-site.admin.review.prevent-duplicate": {"message": "禁止重复作答"},
"app.skin-site.admin.review.copy-link": {"message": "复制直链"},
"app.skin-site.admin.review.link-copied": {"message": "直链已复制。"},
"app.skin-site.admin.review.move-up": {"message": "上移"},
"app.skin-site.admin.review.move-down": {"message": "下移"},
"app.skin-site.admin.review.mail-subject-required": {"message": "开启邮件后必须填写邮件标题。"},
"app.skin-site.admin.review.mail-message-required": {"message": "开启邮件后必须填写邮件正文。"}
}