fix(后台): 用户、题目、比赛、公告、标签几张列表的操作不再收进 ⋯,直接摆在行尾;名字和题目那列不再吃掉整行的空,创建日期不再折成两行
1366 宽的屏上题目列表、比赛列表的按钮都放得下;题目的知识点多于两个时只摆一个加「+N」。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd3a57468f
commit
6470f56f87
9 files changed
+119
-158
No files matched your search
@@ -1,13 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { errorCode, errorMessage } from "utils/api"
|
||||
import { deleteContestProblem, deleteProblem, makeProblemPublic } from "admin/api"
|
||||
import download from "utils/download"
|
||||
|
||||
/**
|
||||
* 题目列表一行的操作(设计稿「后台题目列表」PlA):常用的「编辑」常驻,其余收进 ⋯ ——
|
||||
* 原来是四颗彩色按钮(编辑 / 查看 / 删除 / 下载)并排,1366 宽的屏上把「可见」开关挤出了屏幕。
|
||||
* 有人交过的题删不掉(服务端也拦),「删除」直接灰掉并写明为什么,不用点了才知道
|
||||
* 题目列表一行的操作:编辑、在前台看、下载测试数据、删除直接摆出来(用户说的:不要收进 ⋯)。
|
||||
* 原来是四颗彩色按钮,现在一律素色,删除用浅色的危险按钮。
|
||||
* 有人交过的题删不掉(服务端也拦),「删除」直接灰掉、鼠标放上去写明为什么
|
||||
*/
|
||||
interface Props {
|
||||
problemID: number
|
||||
@@ -69,29 +68,14 @@ function goCheck() {
|
||||
window.open(data.href, "_blank")
|
||||
}
|
||||
|
||||
const menuOptions = computed<DropdownOption[]>(() => [
|
||||
{ label: "在前台看", key: "view" },
|
||||
{ label: "下载测试数据", key: "download" },
|
||||
...(isContestProblem.value ? [{ label: "转成公开题", key: "public" }] : []),
|
||||
{ type: "divider", key: "divider" },
|
||||
props.triedUsers
|
||||
? { label: `删除(有 ${props.triedUsers} 人交过)`, key: "delete", disabled: true }
|
||||
: { label: "删除", key: "delete" },
|
||||
])
|
||||
|
||||
function onMenu(key: string) {
|
||||
if (key === "view") goCheck()
|
||||
else if (key === "download") downloads()
|
||||
else if (key === "public") openMakePublicModal()
|
||||
else if (key === "delete") {
|
||||
dialog.warning({
|
||||
title: `删除 ${props.problemDisplayID}?`,
|
||||
content: "删掉就找不回来了。",
|
||||
positiveText: "删除",
|
||||
negativeText: "不删",
|
||||
onPositiveClick: handleDeleteProblem,
|
||||
})
|
||||
}
|
||||
function confirmDelete() {
|
||||
dialog.warning({
|
||||
title: `删除 ${props.problemDisplayID}?`,
|
||||
content: "删掉就找不回来了。",
|
||||
positiveText: "删除",
|
||||
negativeText: "不删",
|
||||
onPositiveClick: handleDeleteProblem,
|
||||
})
|
||||
}
|
||||
|
||||
function openMakePublicModal() {
|
||||
@@ -122,13 +106,24 @@ async function handleMakePublic() {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-flex :size="4" justify="end" :wrap="false">
|
||||
<n-flex :size="6" :wrap="false">
|
||||
<n-button size="small" @click="goEdit">编辑</n-button>
|
||||
<n-dropdown trigger="click" placement="bottom-end" :options="menuOptions" @select="onMenu">
|
||||
<n-button size="small" quaternary aria-label="更多">
|
||||
<template #icon><Icon icon="ph:dots-three-bold" /></template>
|
||||
<n-button size="small" title="在前台打开这道题" @click="goCheck">前台看</n-button>
|
||||
<n-button size="small" title="下载测试数据" @click="downloads">测试数据</n-button>
|
||||
<n-button v-if="isContestProblem" size="small" @click="openMakePublicModal">
|
||||
转成公开题
|
||||
</n-button>
|
||||
<span :title="props.triedUsers ? `有 ${props.triedUsers} 人交过,删不掉` : undefined">
|
||||
<n-button
|
||||
size="small"
|
||||
quaternary
|
||||
type="error"
|
||||
:disabled="!!props.triedUsers"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
删除
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</span>
|
||||
</n-flex>
|
||||
|
||||
<n-modal
|
||||
|
||||
@@ -102,12 +102,18 @@ const visibleTabs = computed(() => [
|
||||
{ value: "0" as const, label: "藏着的", count: counts.value?.hidden },
|
||||
])
|
||||
|
||||
/** 知识点只摆前两个,多的写 +N,鼠标放上去看全部(P073 这类题挂了六个) */
|
||||
/**
|
||||
* 知识点:两个以内全摆,多了只摆第一个 + 「+N」,鼠标放上去看全部(P073 这类题挂了六个)。
|
||||
* 列宽只放得下两个,摆两个再加 +N 的话 +N 会被截掉
|
||||
*/
|
||||
function renderTags(tags: string[]) {
|
||||
if (!tags.length) return h("span", { class: "muted" }, "—")
|
||||
const shown = tags.length > 2 ? tags.slice(0, 1) : tags
|
||||
return h("span", { class: "tags", title: tags.join("、") }, [
|
||||
...tags.slice(0, 2).map((tag) => h("span", { class: "tag", key: tag }, tag)),
|
||||
tags.length > 2 ? h("span", { class: "muted more" }, `+${tags.length - 2}`) : null,
|
||||
...shown.map((tag) => h("span", { class: "tag", key: tag }, tag)),
|
||||
tags.length > shown.length
|
||||
? h("span", { class: "muted more" }, `+${tags.length - shown.length}`)
|
||||
: null,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -146,15 +152,17 @@ const baseColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
{
|
||||
title: "题号",
|
||||
key: "_id",
|
||||
width: 84,
|
||||
width: 70,
|
||||
render: (row) => h("span", { class: "did", title: `内部编号 ${row.id}` }, row._id),
|
||||
},
|
||||
{ title: "题目", key: "title", minWidth: 220, ellipsis: false, render: renderTitle },
|
||||
{ title: "知识点", key: "tags", width: 190, render: (row) => renderTags(row.tags) },
|
||||
// 题目定宽:p90 也就 12 个字加一两个类型标签。原来 minWidth 让它吃掉整行剩余空间(用户嫌空太多),
|
||||
// 剩下的宽度给最后的操作列
|
||||
{ title: "题目", key: "title", width: 196, ellipsis: false, render: renderTitle },
|
||||
{ title: "知识点", key: "tags", width: 158, render: (row) => renderTags(row.tags) },
|
||||
{
|
||||
title: "做对 / 做过",
|
||||
key: "users",
|
||||
width: 100,
|
||||
width: 96,
|
||||
render: (row) =>
|
||||
row.triedUsers
|
||||
? h("span", { class: "num" }, [
|
||||
@@ -163,11 +171,11 @@ const baseColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
])
|
||||
: h("span", { class: "muted" }, "没人做过"),
|
||||
},
|
||||
{ title: "出题人", key: "username", width: 100, ellipsis: { tooltip: true } },
|
||||
{ title: "出题人", key: "username", width: 86, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: "创建",
|
||||
key: "create_time",
|
||||
width: 96,
|
||||
width: 100,
|
||||
render: (row) => h("span", { class: "num muted" }, parseTime(row.createTime, "YYYY-MM-DD")),
|
||||
},
|
||||
{
|
||||
@@ -185,7 +193,7 @@ const baseColumns: DataTableColumn<AdminProblemRow>[] = [
|
||||
{
|
||||
title: "",
|
||||
key: "actions",
|
||||
width: 104,
|
||||
minWidth: 240,
|
||||
render: (row) =>
|
||||
h(Actions, {
|
||||
problemID: row.id,
|
||||
@@ -385,6 +393,11 @@ watch(() => [query.page, query.limit, query.author, query.kind, query.visible],
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 表头不折行:「做对 / 做过」「学生看得到」折成两行时整张表头变高 */
|
||||
.problem-table :deep(.n-data-table-th) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.problem-table :deep(.did) {
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -409,10 +422,11 @@ watch(() => [query.page, query.limit, query.author, query.kind, query.visible],
|
||||
}
|
||||
|
||||
.problem-table :deep(.tags) {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.problem-table :deep(.tag) {
|
||||
@@ -435,5 +449,6 @@ watch(() => [query.page, query.limit, query.author, query.kind, query.visible],
|
||||
|
||||
.problem-table :deep(.num) {
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import type { AdminTag, TagCategory } from "utils/types"
|
||||
@@ -30,7 +29,8 @@ function openTagProblems(tag: AdminTag) {
|
||||
/**
|
||||
* 两栏:知识点 / 主题(设计稿「后台标签」TagA)。原来是一张宽表,每行一组单选按钮切分类、
|
||||
* 两颗按钮,ID 一列、页头一段说明。现在一行一个标签 + 挂了几道题(条的长短看出哪个用得多),
|
||||
* 点名字看这个标签下的题,改名 / 换分类 / 删除收进 ⋯。还没归类的单独一条放最上面
|
||||
* 点名字看这个标签下的题,改名 / 换分类 / 删除直接摆在行尾(用户说的:不要收进 ⋯)。
|
||||
* 还没归类的单独一条放最上面
|
||||
*/
|
||||
const columnsOf = computed(() => {
|
||||
const confirmed = visibleTags.value.filter((t) => t.categoryConfirmed)
|
||||
@@ -49,25 +49,6 @@ const columnsOf = computed(() => {
|
||||
})
|
||||
const pendingTags = computed(() => visibleTags.value.filter((t) => !t.categoryConfirmed))
|
||||
|
||||
function menuOf(tag: AdminTag): DropdownOption[] {
|
||||
return [
|
||||
{ label: "改名(同名就合并)", key: "rename" },
|
||||
{ label: tag.category === "theme" ? "改成知识点" : "改成主题", key: "move" },
|
||||
{ type: "divider", key: "divider" },
|
||||
{
|
||||
label: tag.problemCount ? `删除(${tag.problemCount} 道题会摘掉它)` : "删除",
|
||||
key: "delete",
|
||||
props: { style: `color: ${theme.value.errorColor}` },
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function onMenu(tag: AdminTag, key: string) {
|
||||
if (key === "rename") startEdit(tag)
|
||||
else if (key === "move") changeCategory(tag, tag.category === "theme" ? "knowledge" : "theme")
|
||||
else if (key === "delete") confirmDelete(tag)
|
||||
}
|
||||
|
||||
async function listTags() {
|
||||
const res = await getTagAdminList(keyword.value)
|
||||
tags.value = res
|
||||
@@ -177,16 +158,17 @@ watchDebounced(keyword, listTags, { debounce: 500, maxWait: 1000 })
|
||||
><i :style="{ width: `${(tag.problemCount / col.max) * 100}%` }"></i
|
||||
></span>
|
||||
<span class="count">{{ tag.problemCount }} 道</span>
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
placement="bottom-end"
|
||||
:options="menuOf(tag)"
|
||||
@select="(key: string) => onMenu(tag, key)"
|
||||
<n-button size="tiny" quaternary @click="startEdit(tag)">改名</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
@click="changeCategory(tag, tag.category === 'theme' ? 'knowledge' : 'theme')"
|
||||
>
|
||||
{{ tag.category === "theme" ? "改成知识点" : "改成主题" }}
|
||||
</n-button>
|
||||
<n-button size="tiny" quaternary type="error" @click="confirmDelete(tag)"
|
||||
>删除</n-button
|
||||
>
|
||||
<n-button size="small" quaternary aria-label="更多">
|
||||
<template #icon><Icon icon="ph:dots-three-bold" /></template>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,7 +241,7 @@ watchDebounced(keyword, listTags, { debounce: 500, maxWait: 1000 })
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 0 0 140px;
|
||||
flex: 0 0 120px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
Reference in new issue
Block a user