176 lines
4.2 KiB
Vue
176 lines
4.2 KiB
Vue
<script setup lang="ts">
|
|
import { NTag } from "naive-ui"
|
|
import { getContestList } from "oj/api"
|
|
import { filterEmptyValue, parseTime, duration } from "utils/functions"
|
|
import { Contest } from "utils/types"
|
|
import { ContestType, CONTEST_STATUS } from "~/utils/constants"
|
|
import ContestTitle from "~/shared/components/ContestTitle.vue"
|
|
import Pagination from "~/shared/components/Pagination.vue"
|
|
import { useUserStore } from "~/shared/store/user"
|
|
import { toggleLogin } from "~/shared/composables/modal"
|
|
import { renderTableTitle } from "~/utils/renders"
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const userStore = useUserStore()
|
|
const query = reactive({
|
|
page: parseInt(<string>route.query.page) || 1,
|
|
limit: parseInt(<string>route.query.limit) || 10,
|
|
keyword: <string>route.query.keyword ?? "",
|
|
status: <string>route.query.status ?? "",
|
|
})
|
|
const data = ref<Contest[]>([])
|
|
const total = ref(0)
|
|
|
|
const options: SelectOption[] = [
|
|
{ label: "全部", value: "" },
|
|
{ label: "未开始", value: "1" },
|
|
{ label: "进行中", value: "0" },
|
|
{ label: "已结束", value: "-1" },
|
|
]
|
|
|
|
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: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,
|
|
})
|
|
data.value = res.data.results
|
|
total.value = res.data.total
|
|
}
|
|
|
|
function routerPush() {
|
|
router.push({
|
|
path: route.path,
|
|
query: filterEmptyValue(query),
|
|
})
|
|
}
|
|
|
|
function search(value: string) {
|
|
query.keyword = value
|
|
}
|
|
|
|
function clear() {
|
|
query.keyword = ""
|
|
query.status = ""
|
|
}
|
|
|
|
onMounted(listContests)
|
|
watch(() => query.page, routerPush)
|
|
watch(
|
|
() => [query.limit, query.status],
|
|
() => {
|
|
query.page = 1
|
|
routerPush()
|
|
},
|
|
)
|
|
watchDebounced(
|
|
() => [query.keyword],
|
|
() => {
|
|
query.page = 1
|
|
routerPush()
|
|
},
|
|
{ debounce: 500, maxWait: 1000 },
|
|
)
|
|
watch(
|
|
() => route.name === "contests" && route.query,
|
|
(newVal) => {
|
|
if (newVal) listContests()
|
|
},
|
|
)
|
|
|
|
function rowProps(row: Contest) {
|
|
return {
|
|
style: "cursor: pointer",
|
|
onClick() {
|
|
if (!userStore.isAuthed && row.contest_type === ContestType.private) {
|
|
toggleLogin(true)
|
|
} else {
|
|
router.push("/contest/" + row.id)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
<template>
|
|
<n-space vertical size="large">
|
|
<n-space>
|
|
<n-form :show-feedback="false" label-placement="left" inline>
|
|
<n-form-item label="比赛状态">
|
|
<n-select
|
|
class="select"
|
|
:options="options"
|
|
v-model:value="query.status"
|
|
/>
|
|
</n-form-item>
|
|
<n-form-item>
|
|
<n-input
|
|
clearable
|
|
v-model:value="query.keyword"
|
|
placeholder="比赛标题"
|
|
/>
|
|
</n-form-item>
|
|
</n-form>
|
|
<n-form :show-feedback="false" label-placement="left" inline>
|
|
<n-form-item>
|
|
<n-space>
|
|
<n-button @click="search(query.keyword)">搜索</n-button>
|
|
<n-button @click="clear" quaternary>重置</n-button>
|
|
</n-space>
|
|
</n-form-item>
|
|
</n-form>
|
|
</n-space>
|
|
<n-data-table
|
|
striped
|
|
:columns="columns"
|
|
:data="data"
|
|
:row-props="rowProps"
|
|
/>
|
|
</n-space>
|
|
<Pagination
|
|
v-model:limit="query.limit"
|
|
v-model:page="query.page"
|
|
:total="total"
|
|
/>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.select {
|
|
width: 120px;
|
|
}
|
|
</style>
|