update
This commit is contained in:
@@ -35,7 +35,12 @@
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi :span="5">
|
<n-gi :span="5">
|
||||||
<AI v-if="aiStore.detailsData.solved.length > 0 && aiStore.detailsData.solved.length < 10" />
|
<AI
|
||||||
|
v-if="
|
||||||
|
aiStore.detailsData.solved.length > 0 &&
|
||||||
|
aiStore.detailsData.solved.length < 10
|
||||||
|
"
|
||||||
|
/>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ watch(
|
|||||||
:deep(.md-editor-preview h1) {
|
:deep(.md-editor-preview h1) {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ ChartJS.register(
|
|||||||
const aiStore = useAIStore()
|
const aiStore = useAIStore()
|
||||||
|
|
||||||
const show = computed(() => {
|
const show = computed(() => {
|
||||||
return Object.values(aiStore.detailsData.difficulty).reduce((a, b) => a + b, 0) > 0
|
return (
|
||||||
|
Object.values(aiStore.detailsData.difficulty).reduce((a, b) => a + b, 0) > 0
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = computed(() => {
|
const data = computed(() => {
|
||||||
|
|||||||
@@ -138,11 +138,10 @@ const options = computed<ChartOptions<"bar" | "line">>(() => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chart {
|
.chart {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,7 +14,12 @@
|
|||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-for="(day, i) in WEEK_DAYS" :key="i">
|
<g v-for="(day, i) in WEEK_DAYS" :key="i">
|
||||||
<text :x="0" :y="MONTH_HEIGHT + i * CELL_TOTAL + 8" class="label" font-size="9">
|
<text
|
||||||
|
:x="0"
|
||||||
|
:y="MONTH_HEIGHT + i * CELL_TOTAL + 8"
|
||||||
|
class="label"
|
||||||
|
font-size="9"
|
||||||
|
>
|
||||||
{{ day }}
|
{{ day }}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
@@ -39,7 +44,11 @@
|
|||||||
<div class="legend">
|
<div class="legend">
|
||||||
<span>少</span>
|
<span>少</span>
|
||||||
<div class="legend-colors">
|
<div class="legend-colors">
|
||||||
<div v-for="(color, i) in COLORS" :key="i" :style="{ backgroundColor: color }" />
|
<div
|
||||||
|
v-for="(color, i) in COLORS"
|
||||||
|
:key="i"
|
||||||
|
:style="{ backgroundColor: color }"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span>多</span>
|
<span>多</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,13 +81,18 @@ const LEGEND_HEIGHT = 20
|
|||||||
const COLORS = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"]
|
const COLORS = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"]
|
||||||
const WEEK_DAYS = ["", "一", "", "三", "", "五", ""]
|
const WEEK_DAYS = ["", "一", "", "三", "", "五", ""]
|
||||||
|
|
||||||
const getColor = (count: number) =>
|
const getColor = (count: number) =>
|
||||||
count === 0 ? COLORS[0] :
|
count === 0
|
||||||
count <= 2 ? COLORS[1] :
|
? COLORS[0]
|
||||||
count <= 4 ? COLORS[2] :
|
: count <= 2
|
||||||
count <= 7 ? COLORS[3] : COLORS[4]
|
? COLORS[1]
|
||||||
|
: count <= 4
|
||||||
|
? COLORS[2]
|
||||||
|
: count <= 7
|
||||||
|
? COLORS[3]
|
||||||
|
: COLORS[4]
|
||||||
|
|
||||||
const cells = computed(() =>
|
const cells = computed(() =>
|
||||||
aiStore.heatmapData.map((item, i) => ({
|
aiStore.heatmapData.map((item, i) => ({
|
||||||
date: new Date(item.timestamp),
|
date: new Date(item.timestamp),
|
||||||
count: item.value,
|
count: item.value,
|
||||||
@@ -87,17 +101,17 @@ const cells = computed(() =>
|
|||||||
day: i % 7,
|
day: i % 7,
|
||||||
x: Math.floor(i / 7) * CELL_TOTAL,
|
x: Math.floor(i / 7) * CELL_TOTAL,
|
||||||
y: (i % 7) * CELL_TOTAL,
|
y: (i % 7) * CELL_TOTAL,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
const monthLabels = computed(() => {
|
const monthLabels = computed(() => {
|
||||||
const labels: { text: string; x: number }[] = []
|
const labels: { text: string; x: number }[] = []
|
||||||
let lastMonth = -1
|
let lastMonth = -1
|
||||||
|
|
||||||
cells.value.forEach((cell, i) => {
|
cells.value.forEach((cell, i) => {
|
||||||
const month = cell.date.getMonth()
|
const month = cell.date.getMonth()
|
||||||
const isWeekStart = cell.date.getDay() === 0 || i === 0
|
const isWeekStart = cell.date.getDay() === 0 || i === 0
|
||||||
|
|
||||||
if (month !== lastMonth && (isWeekStart || cell.date.getDay() <= 3)) {
|
if (month !== lastMonth && (isWeekStart || cell.date.getDay() <= 3)) {
|
||||||
labels.push({
|
labels.push({
|
||||||
text: `${month + 1}月`,
|
text: `${month + 1}月`,
|
||||||
@@ -106,17 +120,16 @@ const monthLabels = computed(() => {
|
|||||||
lastMonth = month
|
lastMonth = month
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return labels
|
return labels
|
||||||
})
|
})
|
||||||
|
|
||||||
const svgWidth = computed(() =>
|
const svgWidth = computed(
|
||||||
DAY_WIDTH + Math.ceil(cells.value.length / 7) * CELL_TOTAL + RIGHT_PADDING
|
() =>
|
||||||
|
DAY_WIDTH + Math.ceil(cells.value.length / 7) * CELL_TOTAL + RIGHT_PADDING,
|
||||||
)
|
)
|
||||||
|
|
||||||
const svgHeight = computed(() =>
|
const svgHeight = computed(() => MONTH_HEIGHT + 7 * CELL_TOTAL + LEGEND_HEIGHT)
|
||||||
MONTH_HEIGHT + 7 * CELL_TOTAL + LEGEND_HEIGHT
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Cell {
|
interface Cell {
|
||||||
date: Date
|
date: Date
|
||||||
@@ -141,13 +154,13 @@ const tooltipStyle = computed(() => ({
|
|||||||
top: `${tooltip.value?.y}px`,
|
top: `${tooltip.value?.y}px`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const getTooltipText = (count: number) =>
|
const getTooltipText = (count: number) =>
|
||||||
count === 0 ? "没有提交记录" : `提交了 ${count} 次`
|
count === 0 ? "没有提交记录" : `提交了 ${count} 次`
|
||||||
|
|
||||||
const showTooltip = (e: MouseEvent, cell: Cell) => {
|
const showTooltip = (e: MouseEvent, cell: Cell) => {
|
||||||
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
||||||
const containerRect = containerRef.value?.getBoundingClientRect()
|
const containerRect = containerRef.value?.getBoundingClientRect()
|
||||||
|
|
||||||
if (containerRect) {
|
if (containerRect) {
|
||||||
tooltip.value = {
|
tooltip.value = {
|
||||||
x: rect.left - containerRect.left + rect.width / 2,
|
x: rect.left - containerRect.left + rect.width / 2,
|
||||||
|
|||||||
@@ -130,42 +130,40 @@ function rowProps(row: Contest) {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<n-flex vertical size="large">
|
<n-flex vertical size="large">
|
||||||
<n-card embedded>
|
<n-space>
|
||||||
<n-space>
|
<n-form :show-feedback="false" label-placement="left" inline>
|
||||||
<n-form :show-feedback="false" label-placement="left" inline>
|
<n-form-item label="比赛状态">
|
||||||
<n-form-item label="比赛状态">
|
<n-select
|
||||||
<n-select
|
style="width: 120px"
|
||||||
style="width: 120px"
|
:options="options"
|
||||||
:options="options"
|
v-model:value="query.status"
|
||||||
v-model:value="query.status"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item label="标签">
|
||||||
<n-form-item label="标签">
|
<n-select
|
||||||
<n-select
|
style="width: 120px"
|
||||||
style="width: 120px"
|
:options="tags"
|
||||||
:options="tags"
|
v-model:value="query.tag"
|
||||||
v-model:value="query.tag"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
<n-form :show-feedback="false" label-placement="left" inline>
|
||||||
<n-form :show-feedback="false" label-placement="left" inline>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-input
|
||||||
<n-input
|
style="width: 180px"
|
||||||
style="width: 200px"
|
clearable
|
||||||
clearable
|
v-model:value="query.keyword"
|
||||||
v-model:value="query.keyword"
|
placeholder="比赛标题"
|
||||||
placeholder="比赛标题"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-flex :wrap="false">
|
||||||
<n-flex>
|
<n-button @click="search(query.keyword)">搜索</n-button>
|
||||||
<n-button @click="search(query.keyword)">搜索</n-button>
|
<n-button @click="clear" quaternary>重置</n-button>
|
||||||
<n-button @click="clear" quaternary>重置</n-button>
|
</n-flex>
|
||||||
</n-flex>
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
</n-space>
|
||||||
</n-space>
|
|
||||||
</n-card>
|
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -195,54 +195,52 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-flex vertical size="large">
|
<n-flex vertical size="large">
|
||||||
<n-card embedded>
|
<n-flex justify="space-between">
|
||||||
<n-flex justify="space-between">
|
<n-space>
|
||||||
<n-space>
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form-item label="难度">
|
||||||
<n-form-item label="难度">
|
<n-select
|
||||||
<n-select
|
style="width: 120px"
|
||||||
style="width: 120px"
|
v-model:value="query.difficulty"
|
||||||
v-model:value="query.difficulty"
|
:options="difficultyOptions"
|
||||||
:options="difficultyOptions"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item label="出题者">
|
||||||
<n-form-item label="出题者">
|
<AuthorSelect v-model:value="query.author" />
|
||||||
<AuthorSelect v-model:value="query.author" />
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-input
|
||||||
<n-input
|
clearable
|
||||||
clearable
|
style="width: 200px"
|
||||||
style="width: 200px"
|
v-model:value="query.keyword"
|
||||||
v-model:value="query.keyword"
|
placeholder="编号或者标题"
|
||||||
placeholder="编号或者标题"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-button @click="clearQuery" quaternary>重置</n-button>
|
||||||
<n-button @click="clearQuery" quaternary>重置</n-button>
|
</n-form-item>
|
||||||
</n-form-item>
|
<!-- <n-form-item>
|
||||||
<!-- <n-form-item>
|
|
||||||
<n-button @click="getRandom" quaternary>随机</n-button>
|
<n-button @click="getRandom" quaternary>随机</n-button>
|
||||||
</n-form-item> -->
|
</n-form-item> -->
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-button
|
<n-button
|
||||||
@click="toggleShowTag()"
|
@click="toggleShowTag()"
|
||||||
quaternary
|
quaternary
|
||||||
icon-placement="right"
|
icon-placement="right"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Icon v-if="showTag" icon="ph:caret-down"></Icon>
|
<Icon v-if="showTag" icon="ph:caret-down"></Icon>
|
||||||
<Icon v-else icon="ph:caret-up"></Icon>
|
<Icon v-else icon="ph:caret-up"></Icon>
|
||||||
</template>
|
</template>
|
||||||
标签
|
标签
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-space>
|
</n-space>
|
||||||
<Hitokoto v-if="isDesktop" />
|
<Hitokoto v-if="isDesktop" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-card>
|
|
||||||
<n-collapse-transition :show="showTag">
|
<n-collapse-transition :show="showTag">
|
||||||
<n-flex>
|
<n-flex>
|
||||||
<n-tag
|
<n-tag
|
||||||
|
|||||||
@@ -51,7 +51,11 @@ export const useAIStore = defineStore("ai", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 统一获取分析数据(details + duration)
|
// 统一获取分析数据(details + duration)
|
||||||
async function fetchAnalysisData(start: string, end: string, duration: string) {
|
async function fetchAnalysisData(
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
duration: string,
|
||||||
|
) {
|
||||||
loading.fetching = true
|
loading.fetching = true
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@@ -240,84 +240,87 @@ const columns = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<n-flex vertical size="large">
|
<n-flex vertical size="large">
|
||||||
<n-card embedded>
|
<n-space>
|
||||||
<n-space>
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form-item v-if="isDesktop && userStore.isAuthed" label="只看自己">
|
||||||
<n-form-item v-if="isDesktop && userStore.isAuthed" label="只看自己">
|
<n-switch
|
||||||
<n-switch
|
v-model:value="query.myself"
|
||||||
v-model:value="query.myself"
|
checked-value="1"
|
||||||
checked-value="1"
|
unchecked-value="0"
|
||||||
unchecked-value="0"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item label="状态">
|
||||||
<n-form-item label="状态">
|
<n-select
|
||||||
<n-select
|
class="select"
|
||||||
class="select"
|
v-model:value="query.result"
|
||||||
v-model:value="query.result"
|
:options="resultOptions"
|
||||||
:options="resultOptions"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item label="语言" v-if="route.name !== 'contest submissions'">
|
||||||
<n-form-item label="语言" v-if="route.name !== 'contest submissions'">
|
<n-select
|
||||||
<n-select
|
class="select"
|
||||||
class="select"
|
v-model:value="query.language"
|
||||||
v-model:value="query.language"
|
:options="languageOptions"
|
||||||
:options="languageOptions"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-input
|
||||||
<n-input
|
class="input"
|
||||||
class="input"
|
clearable
|
||||||
clearable
|
v-model:value="query.username"
|
||||||
v-model:value="query.username"
|
placeholder="用户"
|
||||||
placeholder="用户"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-input
|
||||||
<n-input
|
class="input"
|
||||||
class="input"
|
clearable
|
||||||
clearable
|
v-model:value="query.problem"
|
||||||
v-model:value="query.problem"
|
placeholder="题号"
|
||||||
placeholder="题号"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
<n-form :show-feedback="false" inline label-placement="left">
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
<n-form-item v-if="isMobile && userStore.isAuthed" label="只看自己">
|
||||||
<n-form-item v-if="isMobile && userStore.isAuthed" label="只看自己">
|
<n-switch
|
||||||
<n-switch
|
v-model:value="query.myself"
|
||||||
v-model:value="query.myself"
|
checked-value="1"
|
||||||
checked-value="1"
|
unchecked-value="0"
|
||||||
unchecked-value="0"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-button @click="search(query.username, query.problem)">
|
||||||
<n-button @click="search(query.username, query.problem)">
|
搜索
|
||||||
搜索
|
</n-button>
|
||||||
</n-button>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
<n-form-item>
|
<n-button @click="clear" quaternary>重置</n-button>
|
||||||
<n-button @click="clear" quaternary>重置</n-button>
|
</n-form-item>
|
||||||
</n-form-item>
|
<n-form-item
|
||||||
<n-form-item
|
v-if="userStore.isSuperAdmin && route.name === 'submissions'"
|
||||||
v-if="userStore.isSuperAdmin && route.name === 'submissions'"
|
>
|
||||||
>
|
<IconButton
|
||||||
<IconButton
|
icon="streamline-emojis:bar-chart"
|
||||||
icon="streamline-emojis:bar-chart"
|
tip="数据统计"
|
||||||
tip="数据统计"
|
@click="toggleStatisticPanel(true)"
|
||||||
@click="toggleStatisticPanel(true)"
|
/>
|
||||||
/>
|
</n-form-item>
|
||||||
</n-form-item>
|
</n-form>
|
||||||
</n-form>
|
<n-form
|
||||||
<n-form :show-feedback="false" inline label-placement="left">
|
:show-feedback="false"
|
||||||
<n-form-item v-if="todayCount > 0">
|
inline
|
||||||
<component :is="isDesktop ? NH2 : NText" class="todayCount">
|
label-placement="left"
|
||||||
<n-gradient-text>今日提交数:{{ todayCount }}</n-gradient-text>
|
v-if="todayCount > 0"
|
||||||
</component>
|
>
|
||||||
</n-form-item>
|
<n-form-item>
|
||||||
</n-form>
|
<component :is="isDesktop ? NH2 : NText" class="todayCount">
|
||||||
</n-space>
|
<n-gradient-text>今日提交数:{{ todayCount }}</n-gradient-text>
|
||||||
</n-card>
|
</component>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-space>
|
||||||
<n-data-table :bordered="false" :columns="columns" :data="submissions" />
|
<n-data-table :bordered="false" :columns="columns" :data="submissions" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
|||||||
Reference in New Issue
Block a user