feat: SQL 题展示数据表与期望结果
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { useDark } from "@vueuse/core"
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
import "md-editor-v3/lib/preview.css"
|
||||
import { getSimilarProblems } from "oj/api"
|
||||
import SQLDataTable from "./SQLDataTable.vue"
|
||||
|
||||
type Sample = Problem["samples"][number] & {
|
||||
id: number
|
||||
@@ -30,6 +31,18 @@ const { problem } = storeToRefs(problemStore)
|
||||
|
||||
const problemSetId = computed(() => route.params.problemSetId)
|
||||
|
||||
// SQL 题:隐藏输入/输出/例子,改为渲染数据表与期望结果
|
||||
const isSQL = computed(() => !!problem.value?.sql_config)
|
||||
const sqlDisplay = computed(() => problem.value?.sql_display ?? null)
|
||||
const sqlExpectedQuery = computed(() => {
|
||||
const exp = sqlDisplay.value?.expected
|
||||
return exp && "columns" in exp ? exp : null
|
||||
})
|
||||
const sqlChangedTables = computed(() => {
|
||||
const exp = sqlDisplay.value?.expected
|
||||
return exp && "changed_tables" in exp ? exp.changed_tables : []
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 相似题目推荐
|
||||
@@ -269,6 +282,7 @@ function type(status: ProblemStatus) {
|
||||
:theme="isDark ? 'dark' : 'light'"
|
||||
/>
|
||||
|
||||
<template v-if="!isSQL">
|
||||
<p class="title" :style="style">
|
||||
<n-flex align="center">
|
||||
<Icon icon="streamline-ultimate-color:envelope-back-front"></Icon>
|
||||
@@ -292,6 +306,55 @@ function type(status: ProblemStatus) {
|
||||
:model-value="problem.output_description"
|
||||
:theme="isDark ? 'dark' : 'light'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="isSQL && sqlDisplay">
|
||||
<p class="title" :style="style">
|
||||
<n-flex align="center">
|
||||
<Icon icon="devicon:sqlite"></Icon>
|
||||
数据表
|
||||
</n-flex>
|
||||
</p>
|
||||
<div v-for="t in sqlDisplay.tables" :key="t.name">
|
||||
<p class="sqlTableName">{{ t.name }}</p>
|
||||
<SQLDataTable
|
||||
:columns="t.columns"
|
||||
:rows="t.rows"
|
||||
:total-rows="t.total_rows"
|
||||
:truncated="t.truncated"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="title" :style="style">
|
||||
<n-flex align="center">
|
||||
<Icon icon="streamline-ultimate-color:check-button"></Icon>
|
||||
期望结果
|
||||
</n-flex>
|
||||
</p>
|
||||
<template v-if="sqlExpectedQuery">
|
||||
<SQLDataTable
|
||||
:columns="sqlExpectedQuery.columns.map((name) => ({ name }))"
|
||||
:rows="sqlExpectedQuery.rows"
|
||||
:total-rows="sqlExpectedQuery.total_rows"
|
||||
:truncated="sqlExpectedQuery.truncated"
|
||||
/>
|
||||
<p v-if="!problem.sql_config?.order_sensitive" class="sqlNote">
|
||||
结果顺序不限
|
||||
</p>
|
||||
</template>
|
||||
<div v-for="t in sqlChangedTables" :key="t.name">
|
||||
<p class="sqlTableName">
|
||||
{{ t.dropped ? `${t.name} 表已被删除` : `执行后的 ${t.name} 表` }}
|
||||
</p>
|
||||
<SQLDataTable
|
||||
v-if="!t.dropped"
|
||||
:columns="t.columns"
|
||||
:rows="t.rows"
|
||||
:total-rows="t.total_rows"
|
||||
:truncated="t.truncated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="problem.hint">
|
||||
<p class="title" :style="style">
|
||||
@@ -334,6 +397,7 @@ function type(status: ProblemStatus) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!isSQL">
|
||||
<div v-for="(sample, index) of samples" :key="index">
|
||||
<n-flex align="center">
|
||||
<p class="title" :style="style">
|
||||
@@ -378,6 +442,7 @@ function type(status: ProblemStatus) {
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="problem.source">
|
||||
<p class="title" :style="style">
|
||||
@@ -470,4 +535,16 @@ function type(status: ProblemStatus) {
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.sqlTableName {
|
||||
font-weight: 600;
|
||||
margin: 8px 0 4px;
|
||||
font-family: "Monaco";
|
||||
}
|
||||
|
||||
.sqlNote {
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
60
src/oj/problem/components/SQLDataTable.vue
Normal file
60
src/oj/problem/components/SQLDataTable.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import type { SQLDisplayColumn } from "utils/types"
|
||||
|
||||
defineProps<{
|
||||
columns: SQLDisplayColumn[]
|
||||
rows: (string | number | null)[][]
|
||||
totalRows?: number
|
||||
truncated?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-table class="sqlTable" size="small" :single-line="false">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(col, i) in columns" :key="i">
|
||||
{{ col.name }}
|
||||
<span v-if="col.type" class="colType">{{ col.type }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="rows.length === 0">
|
||||
<td :colspan="columns.length" class="nullCell">(空表)</td>
|
||||
</tr>
|
||||
<tr v-for="(row, i) in rows" :key="i">
|
||||
<td v-for="(v, j) in row" :key="j" :class="{ nullCell: v === null }">
|
||||
{{ v === null ? "NULL" : v }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
<p v-if="truncated" class="truncNote">
|
||||
共 {{ totalRows }} 行,仅展示前 {{ rows.length }} 行
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sqlTable {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.colType {
|
||||
font-size: 12px;
|
||||
opacity: 0.55;
|
||||
margin-left: 4px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.nullCell {
|
||||
opacity: 0.45;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.truncNote {
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -34,10 +34,7 @@ export interface Profile {
|
||||
}
|
||||
|
||||
export type UserAdminType =
|
||||
| "Regular User"
|
||||
| "Student Admin"
|
||||
| "Teacher Admin"
|
||||
| "Super Admin"
|
||||
"Regular User" | "Student Admin" | "Teacher Admin" | "Super Admin"
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
@@ -72,23 +69,37 @@ export interface SQLConfig {
|
||||
order_sensitive: boolean
|
||||
}
|
||||
|
||||
export interface SQLDisplayColumn {
|
||||
name: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface SQLDisplayTable {
|
||||
name: string
|
||||
columns: SQLDisplayColumn[]
|
||||
rows: (string | number | null)[][]
|
||||
total_rows: number
|
||||
truncated: boolean
|
||||
dropped?: boolean
|
||||
}
|
||||
|
||||
export interface SQLDisplay {
|
||||
tables: SQLDisplayTable[]
|
||||
expected:
|
||||
| {
|
||||
columns: string[]
|
||||
rows: (string | number | null)[][]
|
||||
total_rows: number
|
||||
truncated: boolean
|
||||
}
|
||||
| { changed_tables: SQLDisplayTable[] }
|
||||
}
|
||||
|
||||
export type LANGUAGE_SHOW_LABEL =
|
||||
(typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE]
|
||||
|
||||
export type SUBMISSION_RESULT =
|
||||
| -2
|
||||
| -1
|
||||
| 0
|
||||
| 1
|
||||
| 2
|
||||
| 3
|
||||
| 4
|
||||
| 5
|
||||
| 6
|
||||
| 7
|
||||
| 8
|
||||
| 9
|
||||
| 10
|
||||
-2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
|
||||
|
||||
export type ProblemStatus = "passed" | "failed" | "not_test"
|
||||
|
||||
@@ -173,6 +184,9 @@ export interface Problem {
|
||||
|
||||
// SQL 题配置(非 SQL 题为 null)
|
||||
sql_config?: SQLConfig | null
|
||||
|
||||
// SQL 题展示数据(后端保存题目时自动生成)
|
||||
sql_display?: SQLDisplay | null
|
||||
}
|
||||
|
||||
export type AdminProblem = Problem & AlterProblem
|
||||
@@ -664,13 +678,7 @@ export interface ExerciseGroupData {
|
||||
}
|
||||
|
||||
export type ExerciseType =
|
||||
| "mcq"
|
||||
| "sort"
|
||||
| "fill"
|
||||
| "match"
|
||||
| "predict"
|
||||
| "debug"
|
||||
| "group"
|
||||
"mcq" | "sort" | "fill" | "match" | "predict" | "debug" | "group"
|
||||
|
||||
export interface Exercise {
|
||||
id: number
|
||||
|
||||
Reference in New Issue
Block a user