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 { import {
skinSiteReviewCatalog, skinSiteReviewCatalog,
type SkinSiteReviewPaper, type SkinSiteReviewPublicPaper,
type SkinSiteReviewQuestion, type SkinSiteReviewPublicQuestion,
type SkinSiteReviewReceipt, type SkinSiteReviewReceipt,
skinSiteSubmitReview, skinSiteSubmitReview,
} from '@/helpers/skin-site-api' } from '@/helpers/skin-site-api'
@@ -46,10 +46,10 @@ const messages = defineMessages({
const emit = defineEmits<{ (e: 'back'): void }>() const emit = defineEmits<{ (e: 'back'): void }>()
const papers = ref<SkinSiteReviewPaper[]>([]) const papers = ref<SkinSiteReviewPublicPaper[]>([])
const loading = ref(true) const loading = ref(true)
const failed = ref(false) const failed = ref(false)
const activePaper = ref<SkinSiteReviewPaper | null>(null) const activePaper = ref<SkinSiteReviewPublicPaper | null>(null)
const email = ref('') const email = ref('')
const inviteCode = ref('') const inviteCode = ref('')
const answers = ref<Record<string, string | string[]>>({}) const answers = ref<Record<string, string | string[]>>({})
@@ -70,7 +70,7 @@ const requiredAnswered = computed(() => {
const canSubmit = computed(() => emailValid.value && requiredAnswered.value && !submitting.value) 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') { if (q.type === 'MULTIPLE' || q.type === 'SINGLE') {
const preset = (q.options ?? []).filter((o) => o.defaultSelected).map((o) => o.id) const preset = (q.options ?? []).filter((o) => o.defaultSelected).map((o) => o.id)
return q.type === 'MULTIPLE' ? preset : (preset[0] ?? '') return q.type === 'MULTIPLE' ? preset : (preset[0] ?? '')
@@ -78,7 +78,7 @@ function defaultAnswer(q: SkinSiteReviewQuestion): string | string[] {
return '' return ''
} }
function openPaper(paper: SkinSiteReviewPaper) { function openPaper(paper: SkinSiteReviewPublicPaper) {
activePaper.value = paper activePaper.value = paper
answers.value = {} answers.value = {}
for (const q of paper.questions) answers.value[q.id] = defaultAnswer(q) for (const q of paper.questions) answers.value[q.id] = defaultAnswer(q)
@@ -93,7 +93,7 @@ function closePaper() {
receipt.value = null 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[]) : [] const current = Array.isArray(answers.value[q.id]) ? (answers.value[q.id] as string[]) : []
answers.value[q.id] = checked answers.value[q.id] = checked
? [...current.filter((id) => id !== optionId), optionId] ? [...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 SkinSiteReviewQuestionType = 'SINGLE' | 'MULTIPLE' | 'TRUE_FALSE' | 'FILL' | 'ESSAY'
export type SkinSiteReviewResult = 'PENDING' | 'PASSED' | 'REJECTED' export type SkinSiteReviewResult = 'PENDING' | 'PASSED' | 'REJECTED'
export interface SkinSiteReviewOption { export interface SkinSiteReviewPublicOption {
id: string id: string
description: string description: string
correct?: boolean correct?: boolean
defaultSelected?: boolean defaultSelected?: boolean
} }
export interface SkinSiteReviewQuestion { export interface SkinSiteReviewPublicQuestion {
id: string id: string
type: SkinSiteReviewQuestionType type: SkinSiteReviewPublicQuestionType
title: string title: string
annotation?: string | null annotation?: string | null
score?: number score?: number
optional?: boolean optional?: boolean
fatal?: boolean fatal?: boolean
options?: SkinSiteReviewOption[] options?: SkinSiteReviewPublicOption[]
answer?: string | null answer?: string | null
maxLength?: number | null maxLength?: number | null
informationQuestion?: boolean informationQuestion?: boolean
} }
export interface SkinSiteReviewPaper { export interface SkinSiteReviewPublicPaper {
id: string id: string
title: string title: string
description?: string description?: string
questions: SkinSiteReviewQuestion[] questions: SkinSiteReviewPublicQuestion[]
[key: string]: unknown [key: string]: unknown
} }
@@ -1039,6 +1039,42 @@ export function skinSiteAdminUserFrequencyLimit(
export type SkinSiteReviewMode = 'AUTO' | 'MANUAL' 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 { export interface SkinSiteReviewAnswer {
id: string id: string
questionId?: string questionId?: string
@@ -1069,6 +1105,7 @@ export interface SkinSiteReviewSubmission {
createdAt: number createdAt: number
reviewedAt?: number | null reviewedAt?: number | null
answers: SkinSiteReviewAnswer[] answers: SkinSiteReviewAnswer[]
paper: SkinSiteReviewPaper
decisionHistory?: Array<{ decisionHistory?: Array<{
previousResult: SkinSiteReviewResult previousResult: SkinSiteReviewResult
result: SkinSiteReviewResult result: SkinSiteReviewResult
@@ -1789,3 +1826,79 @@ export function skinSiteWikiUpdateSettings(
body: settings, 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-page-bytes": {"message": "Max page bytes"},
"app.skin-site.admin.wiki.max-attachment-bytes": {"message": "Max attachment 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.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-page-bytes": {"message": "页面最大字节数"},
"app.skin-site.admin.wiki.max-attachment-bytes": {"message": "附件最大字节数"}, "app.skin-site.admin.wiki.max-attachment-bytes": {"message": "附件最大字节数"},
"app.skin-site.admin.wiki.save-settings": {"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": "开启邮件后必须填写邮件正文。"}
} }