活跃度排名
This commit is contained in:
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@@ -10,6 +10,7 @@ declare module 'vue' {
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
@@ -31,6 +32,7 @@ declare module 'vue' {
|
||||
NGradientText: typeof import('naive-ui')['NGradientText']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH2: typeof import('naive-ui')['NH2']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
|
||||
@@ -115,6 +115,12 @@ export function getRank(
|
||||
})
|
||||
}
|
||||
|
||||
export function getActivityRank(start: string) {
|
||||
return http.get("user_activity_rank", {
|
||||
params: { start },
|
||||
})
|
||||
}
|
||||
|
||||
export function getContestList(query: {
|
||||
offset: number
|
||||
limit: number
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { Bar } from "vue-chartjs"
|
||||
import { ChartType } from "~/utils/constants"
|
||||
import { Rank } from "~/utils/types"
|
||||
|
||||
const props = defineProps<{ rankData: Rank[] }>()
|
||||
const props = defineProps<{ rankData: Rank[]; type: ChartType }>()
|
||||
|
||||
const data = computed(() => ({
|
||||
labels: props.rankData.map((rank) => rank.user.username),
|
||||
datasets: [
|
||||
const data = computed(() => {
|
||||
const labels = props.rankData.map((rank) => rank.user.username)
|
||||
const datasets: any[] = [
|
||||
{
|
||||
label: "已解决",
|
||||
label: props.type === ChartType.Rank ? "已解决" : "提交数",
|
||||
data: props.rankData.map((rank) => rank.accepted_number),
|
||||
backgroundColor: [
|
||||
"rgba(255, 99, 132, 0.2)",
|
||||
@@ -60,23 +61,23 @@ const data = computed(() => ({
|
||||
],
|
||||
borderWidth: 2,
|
||||
},
|
||||
{
|
||||
]
|
||||
|
||||
if (props.type === ChartType.Rank) {
|
||||
datasets.push({
|
||||
label: "总提交数",
|
||||
data: props.rankData.map((rank) => rank.submission_number),
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
}
|
||||
return {
|
||||
labels,
|
||||
datasets,
|
||||
}
|
||||
})
|
||||
|
||||
const options = {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
text: "国服前十",
|
||||
display: true,
|
||||
font: { size: 24 },
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
@@ -87,6 +88,6 @@ const options = {
|
||||
<style scoped>
|
||||
.chart {
|
||||
height: 500px;
|
||||
margin-bottom: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { formatISO, sub } from "date-fns"
|
||||
import { NButton } from "naive-ui"
|
||||
import { getRank } from "oj/api"
|
||||
import { getActivityRank, getRank } from "oj/api"
|
||||
import { getACRate } from "utils/functions"
|
||||
import { Rank } from "utils/types"
|
||||
import Pagination from "~/shared/components/Pagination.vue"
|
||||
import { ChartType } from "~/utils/constants"
|
||||
import { renderTableTitle } from "~/utils/renders"
|
||||
import Chart from "./components/Chart.vue"
|
||||
import Index from "./components/Index.vue"
|
||||
@@ -16,15 +18,15 @@ const query = reactive({
|
||||
page: 1,
|
||||
})
|
||||
const chart = ref<Rank[]>([])
|
||||
const chartType = ref(ChartType.Rank)
|
||||
const duration = ref("weeks:1")
|
||||
|
||||
async function listRanks() {
|
||||
async function init() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getRank(offset, query.limit, 100)
|
||||
data.value = res.data.results
|
||||
total.value = res.data.total
|
||||
if (query.page === 1) {
|
||||
chart.value = data.value
|
||||
}
|
||||
return res.data.results
|
||||
}
|
||||
|
||||
const columns: DataTableColumn<Rank>[] = [
|
||||
@@ -83,20 +85,87 @@ const columns: DataTableColumn<Rank>[] = [
|
||||
},
|
||||
]
|
||||
|
||||
watch(() => query.page, listRanks)
|
||||
watch(() => query.page, init)
|
||||
watch(
|
||||
() => query.limit,
|
||||
() => {
|
||||
query.page = 1
|
||||
listRanks()
|
||||
init()
|
||||
},
|
||||
)
|
||||
watch(duration, listActivity)
|
||||
|
||||
onMounted(listRanks)
|
||||
async function listActivity() {
|
||||
chartType.value = ChartType.Activity
|
||||
const current = Date.now()
|
||||
const start = formatISO(sub(current, subOptions.value))
|
||||
const res = await getActivityRank(start)
|
||||
chart.value = res.data.map((d: { username: string; count: number }) => ({
|
||||
user: {
|
||||
username: d.username,
|
||||
},
|
||||
accepted_number: d.count,
|
||||
submission_number: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
async function listRank() {
|
||||
chartType.value = ChartType.Rank
|
||||
const res = await getRank(0, 10, 10)
|
||||
data.value = res.data.results
|
||||
chart.value = data.value
|
||||
}
|
||||
|
||||
const options: SelectOption[] = [
|
||||
{ label: "一周内", value: "weeks:1" },
|
||||
{ label: "一个月内", value: "months:1" },
|
||||
{ label: "两个月内", value: "months:2" },
|
||||
{ label: "本学期", value: "months:6" },
|
||||
{ label: "一年内", value: "years:1" },
|
||||
]
|
||||
|
||||
const subOptions = computed<Duration>(() => {
|
||||
let dur = options.find((it) => it.value === duration.value) ?? options[0]
|
||||
const x = dur.value!.toString().split(":")
|
||||
const unit = x[0]
|
||||
const n = x[1]
|
||||
return { [unit]: parseInt(n) }
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
chart.value = await init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Chart v-if="!!chart.length" :rankData="chart" />
|
||||
<n-flex justify="center">
|
||||
<n-button-group>
|
||||
<n-button
|
||||
@click="listRank"
|
||||
:type="chartType === ChartType.Rank ? 'primary' : 'default'"
|
||||
>
|
||||
天梯排名
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="listActivity"
|
||||
:type="chartType === ChartType.Activity ? 'primary' : 'default'"
|
||||
>
|
||||
活跃度排名
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
<div v-if="chartType === ChartType.Activity">
|
||||
<n-select
|
||||
style="width: 120px"
|
||||
:options="options"
|
||||
v-model:value="duration"
|
||||
/>
|
||||
</div>
|
||||
</n-flex>
|
||||
<Chart v-if="!!chart.length" :type="chartType" :rank-data="chart" />
|
||||
<n-empty v-else style="padding: 20px 0"></n-empty>
|
||||
<n-flex justify="center">
|
||||
<n-h2>全校前100名</n-h2>
|
||||
</n-flex>
|
||||
<n-data-table striped :data="data" :columns="columns" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
@@ -104,5 +173,3 @@ onMounted(listRanks)
|
||||
v-model:limit="query.limit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -304,3 +304,8 @@ export const avatar = ref(AVATARS[Math.floor(Math.random() * AVATARS.length)])
|
||||
export function getRandomAvatar() {
|
||||
avatar.value = AVATARS[Math.floor(Math.random() * AVATARS.length)]
|
||||
}
|
||||
|
||||
export enum ChartType {
|
||||
Rank,
|
||||
Activity,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user