- 侧栏按 概览 / 教学内容 / 学情分析 / 站点管理 分组,去掉超管菜单里的重复项; 菜单项的角色与路由守卫一一对应,没有题目权限的教师不再看到点了就被踢回首页的「题目」 - 顶栏:面包屑、回到前台、明暗切换、用户菜单(退出登录);窄屏侧栏自动收起 - 新增 PageHeader:标题 + 右侧主操作 + 下方筛选栏,编辑页带返回按钮 - 卡点分析 / 年度趋势 / 标签管理从题目列表的按钮挪进侧栏 - 首页改成统计卡片 + 班级排名卡片;设置页按钮移到卡片右上角 - 题目、比赛、题单、用户列表的「选项」列固定在右侧,不再被挤出屏幕 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d25b5dbe6d
commit
d3823430f9
25 files changed
+764
-587
No files matched your search
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { getStuckProblems } from "admin/api"
|
||||
import type { StuckProblem } from "utils/types"
|
||||
|
||||
@@ -37,7 +38,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 style="margin-top: 0">学生卡点分析(只分析前40道题目)</h2>
|
||||
<PageHeader title="卡点分析" description="只分析前 40 道题目" />
|
||||
<n-data-table
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import type { AcTrend } from "utils/types"
|
||||
import { Line } from "vue-chartjs"
|
||||
import {
|
||||
@@ -127,31 +128,34 @@ onMounted(fetchData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 style="margin-top: 0">年度趋势</h2>
|
||||
<n-space align="center" style="margin-bottom: 16px">
|
||||
<span>年份范围</span>
|
||||
<n-select
|
||||
v-model:value="sinceYear"
|
||||
:options="yearOptions"
|
||||
style="width: 100px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
<span>—</span>
|
||||
<n-select
|
||||
v-model:value="untilYear"
|
||||
:options="yearOptions"
|
||||
style="width: 100px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
<span>年提交下限</span>
|
||||
<n-select
|
||||
v-model:value="minPerYear"
|
||||
:options="minPerYearOptions"
|
||||
style="width: 90px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
<n-tag type="info" size="small">共 {{ data.length }} 题</n-tag>
|
||||
</n-space>
|
||||
<PageHeader title="年度趋势">
|
||||
<template #extra>
|
||||
<n-tag type="info" size="small" round>共 {{ data.length }} 题</n-tag>
|
||||
</template>
|
||||
<template #filters>
|
||||
<span>年份范围</span>
|
||||
<n-select
|
||||
v-model:value="sinceYear"
|
||||
:options="yearOptions"
|
||||
style="width: 100px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
<span>—</span>
|
||||
<n-select
|
||||
v-model:value="untilYear"
|
||||
:options="yearOptions"
|
||||
style="width: 100px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
<span>年提交下限</span>
|
||||
<n-select
|
||||
v-model:value="minPerYear"
|
||||
:options="minPerYearOptions"
|
||||
style="width: 90px"
|
||||
@update:value="fetchData"
|
||||
/>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<n-spin :show="loading">
|
||||
<div v-if="!loading && data.length === 0" style="text-align: center; padding: 40px">
|
||||
暂无数据
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { PROBLEM_TAG_MAX_LENGTH } from "@oj2/contract"
|
||||
import { getProblemTagList } from "shared/api"
|
||||
@@ -529,10 +530,18 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex>
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<n-button v-if="showClear" @click="clear">清空缓存</n-button>
|
||||
</n-flex>
|
||||
<PageHeader
|
||||
:title="title ?? '题目'"
|
||||
:back="
|
||||
props.contestID
|
||||
? { name: 'admin contest problem list', params: { contestID: props.contestID } }
|
||||
: { name: 'admin problem list' }
|
||||
"
|
||||
>
|
||||
<template #actions>
|
||||
<n-button v-if="showClear" @click="clear">清空缓存</n-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<n-form inline label-placement="left">
|
||||
<n-form-item label="显示编号">
|
||||
<n-input class="w-100" v-model:value="problem._id" />
|
||||
@@ -832,10 +841,6 @@ watch(
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useRouteQuery } from "@vueuse/router"
|
||||
import AuthorSelect from "shared/components/AuthorSelect.vue"
|
||||
import type { DataTableRowKey } from "naive-ui"
|
||||
import BatchTagModal from "./components/BatchTagModal.vue"
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
|
||||
interface Props {
|
||||
contestID?: string
|
||||
@@ -166,6 +167,7 @@ const baseColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
{
|
||||
title: "选项",
|
||||
key: "actions",
|
||||
fixed: "right",
|
||||
width: 320,
|
||||
render: (row) =>
|
||||
h(Actions, {
|
||||
@@ -232,54 +234,41 @@ watch(() => [query.page, query.limit, query.author], listProblems)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex class="titleWrapper" justify="space-between">
|
||||
<n-flex align="center">
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<n-button
|
||||
v-if="!isContestProblemList"
|
||||
type="primary"
|
||||
@click="$router.push({ name: 'admin problem create' })"
|
||||
>
|
||||
新建
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="!isContestProblemList"
|
||||
@click="$router.push({ name: 'admin stuck problems' })"
|
||||
>
|
||||
卡点分析
|
||||
</n-button>
|
||||
<n-button v-if="!isContestProblemList" @click="$router.push({ name: 'admin top ac trend' })">
|
||||
年度趋势
|
||||
</n-button>
|
||||
<n-button v-if="!isContestProblemList" @click="$router.push({ name: 'admin tag list' })">
|
||||
标签管理
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-flex>
|
||||
<template v-if="!isContestProblemList && selectedProblemIds.length">
|
||||
<n-button type="primary" @click="openBatchTag('add')">
|
||||
添加标签({{ selectedProblemIds.length }})
|
||||
<PageHeader
|
||||
:title="title ?? '题目列表'"
|
||||
:back="isContestProblemList ? { name: 'admin contest list' } : undefined"
|
||||
>
|
||||
<template #actions>
|
||||
<template v-if="!isContestProblemList">
|
||||
<template v-if="selectedProblemIds.length">
|
||||
<n-button @click="openBatchTag('remove')">移除标签</n-button>
|
||||
<n-button secondary type="primary" @click="openBatchTag('add')">
|
||||
添加标签({{ selectedProblemIds.length }})
|
||||
</n-button>
|
||||
</template>
|
||||
<n-button type="primary" @click="$router.push({ name: 'admin problem create' })">
|
||||
新建题目
|
||||
</n-button>
|
||||
<n-button @click="openBatchTag('remove')">移除标签</n-button>
|
||||
</template>
|
||||
<n-button v-if="isContestProblemList" @click="createContestProblem"> 新建比赛题目 </n-button>
|
||||
<n-button v-if="isContestProblemList" type="primary" @click="selectProblems">
|
||||
从题目中选择
|
||||
</n-button>
|
||||
<n-flex align="center" v-if="!props.contestID">
|
||||
<span>出题人</span>
|
||||
<template v-else>
|
||||
<n-button @click="createContestProblem">新建比赛题目</n-button>
|
||||
<n-button type="primary" @click="selectProblems">从题库中选择</n-button>
|
||||
</template>
|
||||
</template>
|
||||
<template #filters>
|
||||
<n-input
|
||||
v-model:value="query.keyword"
|
||||
placeholder="输入标题关键字"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
@clear="clearQuery"
|
||||
/>
|
||||
<n-flex v-if="!props.contestID" align="center" :size="8">
|
||||
<n-text depth="3">出题人</n-text>
|
||||
<AuthorSelect v-model:value="query.author" all />
|
||||
</n-flex>
|
||||
<div>
|
||||
<n-input
|
||||
v-model:value="query.keyword"
|
||||
placeholder="输入标题关键字"
|
||||
clearable
|
||||
@clear="clearQuery"
|
||||
/>
|
||||
</div>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<n-data-table
|
||||
striped
|
||||
:columns="columns"
|
||||
@@ -301,13 +290,3 @@ watch(() => [query.page, query.limit, query.author], listProblems)
|
||||
@done="onBatchTagDone"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.titleWrapper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { NButton, NFlex, NInput } from "naive-ui"
|
||||
import type { AdminTag } from "utils/types"
|
||||
import { deleteTag, getTagAdminList, renameTag } from "../api"
|
||||
@@ -144,13 +145,11 @@ watchDebounced(keyword, listTags, { debounce: 500, maxWait: 1000 })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex class="titleWrapper" justify="space-between">
|
||||
<n-flex align="center">
|
||||
<h2 class="title">标签管理</h2>
|
||||
<n-button @click="$router.push({ name: 'admin problem list' })"> 返回题目列表 </n-button>
|
||||
</n-flex>
|
||||
<n-input v-model:value="keyword" style="width: 200px" placeholder="搜索标签" clearable />
|
||||
</n-flex>
|
||||
<PageHeader title="标签管理">
|
||||
<template #filters>
|
||||
<n-input v-model:value="keyword" style="width: 220px" placeholder="搜索标签" clearable />
|
||||
</template>
|
||||
</PageHeader>
|
||||
<n-data-table striped :columns="columns" :data="tags" />
|
||||
<TagProblemsModal
|
||||
v-model:show="showTagProblems"
|
||||
@@ -159,13 +158,3 @@ watchDebounced(keyword, listTags, { debounce: 500, maxWait: 1000 })
|
||||
@changed="listTags"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.titleWrapper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user