feat(阶段1): 搬入 ojnext 为 apps/web,未改业务代码
This commit is contained in:
180
apps/web/src/oj/contest/list.vue
Normal file
180
apps/web/src/oj/contest/list.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouteQuery } from "@vueuse/router"
|
||||
import { NTag } from "naive-ui"
|
||||
import { getContestList } from "oj/api"
|
||||
import { duration, parseTime } from "utils/functions"
|
||||
import type { Contest } from "utils/types"
|
||||
import ContestTitle from "shared/components/ContestTitle.vue"
|
||||
import Pagination from "shared/components/Pagination.vue"
|
||||
import { useAuthModalStore } from "shared/store/authModal"
|
||||
import { usePagination } from "shared/composables/pagination"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import { CONTEST_STATUS, ContestType } from "utils/constants"
|
||||
import { renderTableTitle } from "utils/renders"
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthModalStore()
|
||||
|
||||
interface ContestQuery {
|
||||
keyword: string
|
||||
status: string
|
||||
tag: string
|
||||
}
|
||||
|
||||
// 使用分页 composable
|
||||
const { query, clearQuery } = usePagination<ContestQuery>({
|
||||
keyword: useRouteQuery("keyword", "").value,
|
||||
status: useRouteQuery("status", "").value,
|
||||
tag: useRouteQuery("tag", "").value,
|
||||
})
|
||||
|
||||
const data = ref<Contest[]>([])
|
||||
const total = ref(0)
|
||||
|
||||
const options: SelectOption[] = [
|
||||
{ label: "全部", value: "" },
|
||||
{ label: "未开始", value: "1" },
|
||||
{ label: "进行中", value: "0" },
|
||||
{ label: "已结束", value: "-1" },
|
||||
]
|
||||
|
||||
const tags: SelectOption[] = [
|
||||
{ label: "全部", value: "" },
|
||||
{ label: "练习", value: "练习" },
|
||||
{ label: "期中", value: "期中" },
|
||||
{ label: "期末", value: "期末" },
|
||||
]
|
||||
|
||||
const columns: DataTableColumn<Contest>[] = [
|
||||
{
|
||||
title: renderTableTitle("状态", "streamline-emojis:collision"),
|
||||
key: "status",
|
||||
width: 100,
|
||||
render: (row) =>
|
||||
h(
|
||||
NTag,
|
||||
{ type: CONTEST_STATUS[row.status]["type"] },
|
||||
() => CONTEST_STATUS[row.status]["name"],
|
||||
),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle("比赛", "streamline-emojis:bouquet"),
|
||||
key: "title",
|
||||
minWidth: 360,
|
||||
render: (row) => h(ContestTitle, { contest: row }),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle("标签", "fluent-emoji-flat:keycap-hashtag"),
|
||||
key: "tag",
|
||||
width: 100,
|
||||
render: (row) => h(NTag, () => row.tag),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle("开始时间", "fluent-emoji-flat:eleven-thirty"),
|
||||
key: "start_time",
|
||||
width: 180,
|
||||
render: (row) => parseTime(row.start_time),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle("比赛时长", "streamline-emojis:fishing-pole"),
|
||||
key: "duration",
|
||||
width: 180,
|
||||
render: (row) => duration(row.start_time, row.end_time),
|
||||
},
|
||||
]
|
||||
|
||||
async function listContests() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getContestList({
|
||||
offset,
|
||||
limit: query.limit,
|
||||
keyword: query.keyword,
|
||||
status: query.status,
|
||||
tag: query.tag,
|
||||
})
|
||||
data.value = res.data.results
|
||||
total.value = res.data.total
|
||||
}
|
||||
|
||||
function search(value: string) {
|
||||
query.keyword = value
|
||||
}
|
||||
|
||||
function clear() {
|
||||
clearQuery()
|
||||
}
|
||||
|
||||
onMounted(listContests)
|
||||
|
||||
// 监听搜索关键词变化(防抖)
|
||||
watchDebounced(() => query.keyword, listContests, {
|
||||
debounce: 500,
|
||||
maxWait: 1000,
|
||||
})
|
||||
|
||||
// 监听其他查询条件变化
|
||||
watch(() => [query.page, query.limit, query.status, query.tag], listContests)
|
||||
|
||||
function rowProps(row: Contest) {
|
||||
return {
|
||||
style: "cursor: pointer",
|
||||
onClick() {
|
||||
if (!userStore.isAuthed && row.contest_type === ContestType.private) {
|
||||
authStore.openLoginModal()
|
||||
} else {
|
||||
router.push("/contest/" + row.id)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-flex vertical size="large">
|
||||
<n-space>
|
||||
<n-form :show-feedback="false" label-placement="left" inline>
|
||||
<n-form-item label="比赛状态">
|
||||
<n-select
|
||||
style="width: 120px"
|
||||
:options="options"
|
||||
v-model:value="query.status"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="标签">
|
||||
<n-select
|
||||
style="width: 120px"
|
||||
:options="tags"
|
||||
v-model:value="query.tag"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-form :show-feedback="false" label-placement="left" inline>
|
||||
<n-form-item>
|
||||
<n-input
|
||||
style="width: 180px"
|
||||
clearable
|
||||
v-model:value="query.keyword"
|
||||
placeholder="比赛标题"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-flex :wrap="false">
|
||||
<n-button @click="search(query.keyword)">搜索</n-button>
|
||||
<n-button @click="clear" quaternary>重置</n-button>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-space>
|
||||
<n-data-table
|
||||
:bordered="false"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-props="rowProps"
|
||||
/>
|
||||
</n-flex>
|
||||
<Pagination
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
:total="total"
|
||||
/>
|
||||
</template>
|
||||
Reference in New Issue
Block a user