feat(SQL 题): 增删改题的期望结果标出执行后变了的格子,删掉的行写清是哪几行
学生原来只能拿两张表逐格对,看不出要改的是哪几行。现在改过的格子、新插的行标黄, 删掉的行在表下面写「删掉了 2 行(id 是 2、3)」。按第一列配对,对不上就不标。 数据表和期望结果抽成一个组件,后台出题页的预览也用它。 题型判断统一走契约的 isSqlProblem()。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e01dabc6da
commit
25d3296410
12 files changed
+247
-421
No files matched your search
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { isSqlProblem } from "@oj2/contract"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
@@ -8,7 +9,7 @@ import { useSubmissionStore } from "oj/store/submission"
|
||||
import { useDark } from "@vueuse/core"
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
import "md-editor-v3/lib/preview.css"
|
||||
import SQLDataTable from "./SQLDataTable.vue"
|
||||
import SqlProblemData from "./SqlProblemData.vue"
|
||||
import SimilarProblems from "./SimilarProblems.vue"
|
||||
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
||||
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
||||
@@ -25,16 +26,7 @@ const { problem } = storeToRefs(problemStore)
|
||||
const ctx = useProblemPageContext()
|
||||
|
||||
// SQL 题:隐藏输入/输出/例子,改为渲染数据表与期望结果
|
||||
const isSQL = computed(() => !!problem.value?.sqlConfig)
|
||||
const sqlDisplay = computed(() => problem.value?.sqlDisplay ?? 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 isSQL = computed(() => !!problem.value && isSqlProblem(problem.value))
|
||||
|
||||
// 相似题推荐:做对了、或者错了 3 次以上才给(比赛、题单里不给,见 problemPageContext)
|
||||
const showSimilar = computed(
|
||||
@@ -317,41 +309,15 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="isSQL && sqlDisplay">
|
||||
<h3 class="title sql-title">数据表</h3>
|
||||
<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>
|
||||
|
||||
<h3 class="title sql-title">期望结果</h3>
|
||||
<template v-if="sqlExpectedQuery">
|
||||
<SQLDataTable
|
||||
:columns="sqlExpectedQuery.columns"
|
||||
:rows="sqlExpectedQuery.rows"
|
||||
:total-rows="sqlExpectedQuery.total_rows"
|
||||
:truncated="sqlExpectedQuery.truncated"
|
||||
/>
|
||||
<p v-if="!problem.sqlConfig?.order_sensitive" class="sqlNote">结果顺序不限</p>
|
||||
<SqlProblemData
|
||||
v-if="isSQL && problem.sqlDisplay"
|
||||
:display="problem.sqlDisplay"
|
||||
:order-sensitive="!!problem.sqlConfig?.order_sensitive"
|
||||
>
|
||||
<template #title="{ text }">
|
||||
<h3 class="title sql-title">{{ text }}</h3>
|
||||
</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>
|
||||
</SqlProblemData>
|
||||
|
||||
<section v-if="problem.hint" class="block">
|
||||
<h3 class="title">提示</h3>
|
||||
@@ -603,16 +569,4 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
||||
.sql-title {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.sqlTableName {
|
||||
font-weight: 600;
|
||||
margin: 8px 0 4px;
|
||||
font-family: Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.sqlNote {
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { isSqlProblem } from "@oj2/contract"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useSubmissionStore } from "oj/store/submission"
|
||||
@@ -81,10 +82,7 @@ const codeSegments = computed<{ value: Segment; label: string; time: string }[]>
|
||||
const drawing = computed(() => codeStore.code.language === "Flowchart")
|
||||
|
||||
// 试跑走判题机,SQL 题不走那里;流程图也没得跑。这两种只留「提交结果」
|
||||
const canTrial = computed(() => {
|
||||
const languages = problem.value?.languages ?? []
|
||||
return !languages.includes("SQL")
|
||||
})
|
||||
const canTrial = computed(() => !problem.value || !isSqlProblem(problem.value))
|
||||
|
||||
const segments = computed(() => {
|
||||
const list = canTrial.value ? codeSegments.value : codeSegments.value.slice(0, 1)
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import type { SqlDisplayColumn } from "utils/types"
|
||||
import type { TableChanges } from "../utils/sqlChanges"
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
columns: SqlDisplayColumn[]
|
||||
rows: (string | number | null)[][]
|
||||
totalRows?: number
|
||||
truncated?: boolean
|
||||
/** 增删改题执行后的表:变了的格子、新插的行标出来 */
|
||||
changes?: TableChanges | null
|
||||
}>()
|
||||
|
||||
const changed = (i: number, j: number) =>
|
||||
!!props.changes && (props.changes.newRows.has(i) || props.changes.cells.has(`${i}:${j}`))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -29,7 +35,11 @@ defineProps<{
|
||||
<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 }">
|
||||
<td
|
||||
v-for="(v, j) in row"
|
||||
:key="j"
|
||||
:class="{ nullCell: v === null, changed: changed(i, j) }"
|
||||
>
|
||||
{{ v === null ? "NULL" : v }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -63,6 +73,18 @@ defineProps<{
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* 和图例同一个黄:浅底深字,暗色下压暗底、提亮字 */
|
||||
.sqlTable :deep(td.changed) {
|
||||
background-color: #fff4d6;
|
||||
color: #7a4b00;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
html.dark .sqlTable :deep(td.changed) {
|
||||
background-color: rgba(240, 180, 40, 0.18);
|
||||
color: #f5d38a;
|
||||
}
|
||||
|
||||
.nullCell {
|
||||
opacity: 0.45;
|
||||
font-style: italic;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import type { SqlDisplay } from "utils/types"
|
||||
import { tableChanges } from "../utils/sqlChanges"
|
||||
import SQLDataTable from "./SQLDataTable.vue"
|
||||
|
||||
/**
|
||||
* SQL 题题面里的「数据表」和「期望结果」。题目页和后台出题页的预览共用这一份 ——
|
||||
* 出题页右边就是「学生看到的样子」,两边各写一份迟早对不上。
|
||||
*
|
||||
* 小节标题交给调用方的 `#title` 插槽画:两边的标题样式各在自己的 scoped CSS 里。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
display: SqlDisplay
|
||||
orderSensitive: boolean
|
||||
}>()
|
||||
|
||||
defineSlots<{ title(props: { text: string }): any }>()
|
||||
|
||||
const expectedQuery = computed(() =>
|
||||
"columns" in props.display.expected ? props.display.expected : null,
|
||||
)
|
||||
|
||||
/** 增删改题:执行后的每张表配上它比原来变了什么 */
|
||||
const changedTables = computed(() => {
|
||||
const expected = props.display.expected
|
||||
if (!("changed_tables" in expected)) return []
|
||||
return expected.changed_tables.map((table) => ({
|
||||
table,
|
||||
changes: tableChanges(
|
||||
props.display.tables.find((t) => t.name === table.name),
|
||||
table,
|
||||
),
|
||||
}))
|
||||
})
|
||||
|
||||
const anyChanged = computed(() =>
|
||||
changedTables.value.some(
|
||||
({ changes }) => changes && (changes.cells.size > 0 || changes.newRows.size > 0),
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot name="title" text="数据表" />
|
||||
<div v-for="t in display.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>
|
||||
|
||||
<slot name="title" text="期望结果" />
|
||||
<template v-if="expectedQuery">
|
||||
<SQLDataTable
|
||||
:columns="expectedQuery.columns"
|
||||
:rows="expectedQuery.rows"
|
||||
:total-rows="expectedQuery.total_rows"
|
||||
:truncated="expectedQuery.truncated"
|
||||
/>
|
||||
<p v-if="!orderSensitive" class="sqlNote">结果顺序不限</p>
|
||||
</template>
|
||||
<div v-for="{ table, changes } in changedTables" :key="table.name">
|
||||
<p class="sqlTableName">
|
||||
{{ table.dropped ? `${table.name} 表已被删除` : `执行后的 ${table.name} 表` }}
|
||||
</p>
|
||||
<template v-if="!table.dropped">
|
||||
<SQLDataTable
|
||||
:columns="table.columns"
|
||||
:rows="table.rows"
|
||||
:total-rows="table.total_rows"
|
||||
:truncated="table.truncated"
|
||||
:changes="changes"
|
||||
/>
|
||||
<p v-if="changes?.removed.length" class="sqlNote">
|
||||
删掉了 {{ changes.removed.length }} 行({{ table.columns[0]?.name }} 是
|
||||
{{ changes.removed.join("、") }})
|
||||
</p>
|
||||
<p v-else-if="changes && !changes.cells.size && !changes.newRows.size" class="sqlNote">
|
||||
执行后这张表没有变化
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="anyChanged" class="sqlNote legend">
|
||||
<span class="swatch" aria-hidden="true"></span>执行后变了的格子
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sqlTableName {
|
||||
font-weight: 600;
|
||||
margin: 8px 0 4px;
|
||||
font-family: Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.sqlNote {
|
||||
font-size: 13px;
|
||||
opacity: 0.65;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 1px solid #f0d48a;
|
||||
background-color: #fff4d6;
|
||||
}
|
||||
|
||||
html.dark .swatch {
|
||||
border-color: rgba(240, 180, 40, 0.45);
|
||||
background-color: rgba(240, 180, 40, 0.18);
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { HINT_MIN_FAILURES, hintLevelLabel } from "@oj2/contract"
|
||||
import { HINT_MIN_FAILURES, hintLevelLabel, isSqlProblem } from "@oj2/contract"
|
||||
import type { JudgeCaseResult } from "@oj2/contract"
|
||||
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||
import {
|
||||
@@ -215,7 +215,7 @@ const submissionStore = useSubmissionStore()
|
||||
const customRunHint = computed(() => {
|
||||
if (!sampleCheck.value?.passed || props.peer) return ""
|
||||
const problem = problemStore.problem
|
||||
if (!problem || problem.sqlConfig) return ""
|
||||
if (!problem || isSqlProblem(problem)) return ""
|
||||
// 例子里出现过的数不算:例子已经对了,拿它们再试一遍没用
|
||||
const inSamples = new Set(
|
||||
problem.samples.flatMap(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { isSqlProblem } from "@oj2/contract"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
@@ -41,7 +42,7 @@ const { problem } = storeToRefs(useProblemStore())
|
||||
* 例子都对了、错在隐藏测试点 —— 摆「描述」,边界条件多半写在那里。SQL 题两节都不摆
|
||||
*/
|
||||
const requirement = computed(() => {
|
||||
if (!props.full || !problem.value || problem.value.sqlConfig) return null
|
||||
if (!props.full || !problem.value || isSqlProblem(problem.value)) return null
|
||||
const source = props.check.passed ? problem.value.description : problem.value.outputDescription
|
||||
const text = markdownToText(source ?? "", props.check.passed ? 160 : 120)
|
||||
if (!text) return null
|
||||
|
||||
Reference in new issue
Block a user