feat(platform): connect owned submissions and real browser flows
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user