feat(platform): connect owned submissions and real browser flows

This commit is contained in:
2026-10-09 09:30:12 +08:00
parent 19a071573b
commit fb1b161c0d
31 changed files with 2669 additions and 388 deletions
+80 -43
View File
@@ -1,62 +1,99 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { problems, problemTags } from '../data/preview'
import { usePagination } from '../composables/usePagination'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { api } from '../api/client'
import { errorText, unwrap } from '../api/transport'
import type { components } from '../api/generated/schema'
import PageHeading from '../components/PageHeading.vue'
import PanelCard from '../components/PanelCard.vue'
import ProblemTable from '../components/ProblemTable.vue'
const route = useRoute()
const items = ref<components['schemas']['ProblemSummary'][]>([])
const query = ref('')
const difficulty = ref('')
const tag = ref(typeof route.query.tag === 'string' ? route.query.tag : '')
const error = ref('')
const busy = ref(false)
const next = ref<string | null>(null)
const current = ref<string | undefined>()
const previous = ref<(string | undefined)[]>([])
let request: AbortController | undefined
let generation = 0
const filtered = computed(() =>
problems.filter(
(p) =>
(!query.value || (p.id + p.title).toLowerCase().includes(query.value.trim().toLowerCase())) &&
(!difficulty.value || p.difficulty === difficulty.value) &&
(!tag.value || p.tags.includes(tag.value)),
items.value.filter((p) =>
(p.slug + p.title).toLowerCase().includes(query.value.trim().toLowerCase()),
),
)
const { page, pageSize, paged, total } = usePagination(filtered)
function reset() {
query.value = ''
difficulty.value = ''
tag.value = ''
async function load(after?: string) {
request?.abort()
const epoch = ++generation
const controller = new AbortController()
request = controller
error.value = ''
busy.value = true
try {
const page = unwrap(
await api.GET('/api/v1/problems', {
params: { query: { after, limit: 20 } },
signal: controller.signal,
}),
)
if (epoch !== generation) return
items.value = page.items
next.value = page.next_after
current.value = after
} catch (cause) {
if (epoch === generation) {
error.value = errorText(cause)
items.value = []
next.value = null
}
} finally {
if (epoch === generation) busy.value = false
}
}
function forward() {
if (!next.value) return
previous.value.push(current.value)
void load(next.value)
}
function back() {
void load(previous.value.pop())
}
onMounted(() => load())
onBeforeUnmount(() => {
generation++
request?.abort()
})
</script>
<template>
<PageHeading title="题库" eyebrow="PROBLEM BANK" description="挑选一道题,开始今天的练习。"
><RouterLink to="/trainings"><el-button>按题单练习</el-button></RouterLink></PageHeading
>
<PanelCard title="全部题目" icon="problems" subtitle="示例题库">
<PageHeading
title="题库"
eyebrow="PROBLEM BANK"
description="选择已发布的题目,使用支持的语言提交。"
/>
<PanelCard title="已发布题目" icon="problems">
<p v-if="error" role="alert">{{ error }} <el-button @click="load(current)">重试</el-button></p>
<div class="filters">
<el-input
v-model="query"
placeholder="搜索题号或题目名称"
placeholder="筛选当前页题号或标题"
aria-label="搜索题目"
clearable
/><el-select v-model="difficulty" placeholder="全部难度" aria-label="难度" clearable
><el-option
v-for="d in ['入门', '简单', '中等', '困难']"
:key="d"
:label="d"
:value="d" /></el-select
><el-select v-model="tag" placeholder="全部标签" aria-label="题目标签" clearable filterable
><el-option v-for="t in problemTags" :key="t" :label="t" :value="t" /></el-select
><el-button @click="reset">重置</el-button
><span class="filter-label">{{ total }} 道题目</span>
</div>
<ProblemTable :items="paged" />
<div class="pagination-row">
<span class="muted">共 {{ total }} 道题目</span
><el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20]"
:total="total"
layout="prev, pager, next, sizes"
/>
</div>
<el-table :data="filtered" :empty-text="busy ? '正在加载…' : '当前页没有题目'">
<el-table-column prop="slug" label="题号" min-width="160" />
<el-table-column label="题目" min-width="220"
><template #default="{ row }"
><RouterLink :to="'/problems/' + row.version_id" class="text-link">{{
row.title
}}</RouterLink></template
></el-table-column
>
<el-table-column label="支持语言"
><template #default="{ row }">{{ row.languages.join(' / ') }}</template></el-table-column
>
</el-table>
<div class="pagination-row">
<span>当前页 {{ items.length }} 道题目</span
><el-button :disabled="busy || !previous.length" @click="back">上一页</el-button
><el-button :disabled="busy || !next" @click="forward">下一页</el-button>
</div>
</PanelCard>
</template>