只改界面文字和注释:网址 /problemset、接口、表名、字段名都不动,老链接照常能打开。 设计稿页名「题单重设计」和已执行的迁移文件保留原样。 线上 3 个成就(题单收藏家 / 入门 / 通关)已同步改名。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
959c326bc9
commit
1b703d13ae
68 files changed
+306
-306
No files matched your search
@@ -25,7 +25,7 @@ const editing = ref<AdminAchievement | null>(null)
|
||||
|
||||
const offline = computed(() => list.value.filter((a) => !a.visible).length)
|
||||
|
||||
/** 按指标分组,指标按后端给的顺序(做题 → 坚持 → 比赛题单 → 趣味),组内按门槛从低到高 */
|
||||
/** 按指标分组,指标按后端给的顺序(做题 → 坚持 → 比赛和专题 → 趣味),组内按门槛从低到高 */
|
||||
const groups = computed(() => {
|
||||
const byMetric = new Map<string, AdminAchievement[]>()
|
||||
for (const a of list.value) byMetric.set(a.metric, [...(byMetric.get(a.metric) ?? []), a])
|
||||
|
||||
@@ -604,7 +604,7 @@ export function updateACMHelperChecked(contest_id: number, rank_id: number, prob
|
||||
} satisfies UpdateAcmHelperRequest)
|
||||
}
|
||||
|
||||
// 题单管理 API
|
||||
// 专题管理 API
|
||||
export function getProblemSetList(offset = 0, limit = 10, keyword = '') {
|
||||
return api.get<AdminProblemSetList>('admin/problem-sets', {
|
||||
params: { offset, limit, keyword },
|
||||
@@ -639,7 +639,7 @@ export function toggleProblemSetVisible(id: number) {
|
||||
return api.put<AdminProblemSet>(`admin/problem-sets/${id}/visibility`)
|
||||
}
|
||||
|
||||
// 题单题目管理 API
|
||||
// 专题题目管理 API
|
||||
export function getProblemSetProblems(problemSetId: number) {
|
||||
return api.get<AdminProblemSetProblem[]>(`admin/problem-sets/${problemSetId}/problems`)
|
||||
}
|
||||
@@ -667,7 +667,7 @@ export function removeProblemFromSet(problemSetId: number, itemId: number) {
|
||||
return api.delete(`admin/problem-sets/${problemSetId}/problems/${itemId}`)
|
||||
}
|
||||
|
||||
// 题单奖章管理 API
|
||||
// 专题奖章管理 API
|
||||
export function getProblemSetBadges(problemSetId: number) {
|
||||
return api.get<AdminProblemSetBadge[]>(`admin/problem-sets/${problemSetId}/badges`)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { getBaseInfo, randomUser10 } from '../api'
|
||||
/**
|
||||
* 后台首页(设计稿「后台首页」AdminHome):一块看板。上面一排看「现在」—— 判题机在不在、
|
||||
* 今天交了多少其中有没有系统错误、现在几个人在线、哪个班在上课;中间一排全站的数(用户、题目、
|
||||
* 提交、比赛、题单、自学、这周做题的人,用户要的「类似一个看板」)和最近 30 天每天交了几次;
|
||||
* 提交、比赛、专题、自学、这周做题的人,用户要的「类似一个看板」)和最近 30 天每天交了几次;
|
||||
* 最下面是常用的四个入口和「抽一个人」。
|
||||
*
|
||||
* 原来是总用户数 / 今日提交 / 近期比赛 / 判题机四个数,加一张要手输班级前缀的班级排名表 ——
|
||||
@@ -52,7 +52,7 @@ const siteTiles = computed(() => {
|
||||
{ label: '提交', value: n(site.submissions), note: `这学期 ${n(site.termSubmissions)} 次` },
|
||||
{ label: '这周做题的人', value: n(site.weekActive), note: '周一起交过题的' },
|
||||
{ label: '比赛', value: n(site.contests), note: '', to: '/admin/contest/list' },
|
||||
{ label: '题单', value: n(site.problemSets), note: '', to: '/admin/problemset/list' },
|
||||
{ label: '专题', value: n(site.problemSets), note: '', to: '/admin/problemset/list' },
|
||||
{ label: '自学', value: n(site.tutorials), note: '课', to: '/admin/tutorial/list' },
|
||||
]
|
||||
})
|
||||
|
||||
@@ -24,11 +24,11 @@ import {
|
||||
import BadgeModal from './components/BadgeModal.vue'
|
||||
|
||||
/**
|
||||
* 新建 / 编辑题单,一屏做完(设计稿「题单重设计」后台那块):左边名字、说明、布置期、公开;
|
||||
* 新建 / 编辑专题,一屏做完(设计稿「题单重设计」后台那块):左边名字、说明、布置期、公开;
|
||||
* 右边题目(搜题号或一次粘贴好几个、拖动排序、点一下切选做)和奖章。
|
||||
*
|
||||
* 布置期:选「布置到哪天」,布置期内这几道题学生以前交的代码先藏起来(防抄),不拦做题。
|
||||
* 新建时题目先攒在页面上,保存时建好题单再一次加进去;编辑时加、删、排、切选做当场生效。
|
||||
* 新建时题目先攒在页面上,保存时建好专题再一次加进去;编辑时加、删、排、切选做当场生效。
|
||||
*/
|
||||
const props = defineProps<{ problemSetId?: string }>()
|
||||
|
||||
@@ -196,7 +196,7 @@ function remove(item: Item) {
|
||||
return
|
||||
}
|
||||
dialog.warning({
|
||||
title: '从题单里拿掉这道题?',
|
||||
title: '从专题里拿掉这道题?',
|
||||
content: `「${item.title}」。已经加入的 ${current.value?.participantCount ?? 0} 个人的进度会重算,题目本身不受影响。`,
|
||||
positiveText: '拿掉',
|
||||
negativeText: '不拿',
|
||||
@@ -312,7 +312,7 @@ async function load() {
|
||||
|
||||
async function submit() {
|
||||
if (!form.title.trim()) {
|
||||
message.error('题单还没起名字')
|
||||
message.error('专题还没起名字')
|
||||
return
|
||||
}
|
||||
if (untilMs.value !== null && untilMs.value <= Date.now()) {
|
||||
@@ -346,7 +346,7 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
}
|
||||
message.success(items.value.length ? `题单建好了,${items.value.length} 道题都加进去了` : '题单建好了')
|
||||
message.success(items.value.length ? `专题建好了,${items.value.length} 道题都加进去了` : '专题建好了')
|
||||
router.replace({ name: 'admin problemset edit', params: { problemSetId: created.id } })
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
@@ -358,7 +358,7 @@ async function submit() {
|
||||
function removeSet() {
|
||||
const set = current.value!
|
||||
dialog.error({
|
||||
title: `删除题单「${set.title}」?`,
|
||||
title: `删除专题「${set.title}」?`,
|
||||
content: set.participantCount
|
||||
? `${set.participantCount} 个人的进度和他们拿到的奖章会一起删掉,找不回来。只是不想让学生看到的话,关掉「公开」就行。`
|
||||
: '还没有人加入。删掉找不回来。',
|
||||
@@ -380,9 +380,9 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader :title="editing ? '编辑题单' : '新建题单'" :back="{ name: 'admin problemset list' }">
|
||||
<PageHeader :title="editing ? '编辑专题' : '新建专题'" :back="{ name: 'admin problemset list' }">
|
||||
<template v-if="editing" #actions>
|
||||
<n-button tag="a" :href="`/problemset/${problemSetId}`" target="_blank"> 到前台看这个题单 </n-button>
|
||||
<n-button tag="a" :href="`/problemset/${problemSetId}`" target="_blank"> 到前台看这个专题 </n-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
@@ -410,7 +410,7 @@ onMounted(load)
|
||||
</n-flex>
|
||||
<span class="hint">{{ assignHint }}</span>
|
||||
<span class="hint">
|
||||
布置期内,题单里这几道题学生以前交的代码先藏起来(不管加没加入),在题单里做对或者到期后再放出来,防止照着旧答案抄。不拦做题,到期后照样能做、照样算进度。
|
||||
布置期内,专题里这几道题学生以前交的代码先藏起来(不管加没加入),在专题里做对或者到期后再放出来,防止照着旧答案抄。不拦做题,到期后照样能做、照样算进度。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -418,7 +418,7 @@ onMounted(load)
|
||||
<div class="field">
|
||||
<span class="label">公开</span>
|
||||
<n-switch v-model:value="form.visible" />
|
||||
<span class="hint">{{ form.visible ? '学生能在题单列表里看到' : '藏起来,学生看不到也进不去' }}</span>
|
||||
<span class="hint">{{ form.visible ? '学生能在专题列表里看到' : '藏起来,学生看不到也进不去' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="current" class="stats">
|
||||
@@ -434,7 +434,7 @@ onMounted(load)
|
||||
><template v-if="editing"> · {{ badges.length }} 个奖章</template></span
|
||||
>
|
||||
<div class="spacer"></div>
|
||||
<n-button v-if="editing" text type="error" @click="removeSet">删除题单</n-button>
|
||||
<n-button v-if="editing" text type="error" @click="removeSet">删除专题</n-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -503,7 +503,7 @@ onMounted(load)
|
||||
<div class="spacer"></div>
|
||||
<n-button v-if="editing" size="small" @click="openBadge(null)">+ 加奖章</n-button>
|
||||
</div>
|
||||
<div v-if="!editing" class="empty hint">先保存题单,再加奖章</div>
|
||||
<div v-if="!editing" class="empty hint">先保存专题,再加奖章</div>
|
||||
<div v-else-if="badges.length" class="list">
|
||||
<div v-for="badge in badges" :key="badge.id" class="item badge">
|
||||
<img :src="badge.icon" alt="" class="badge-icon" />
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useBreakpoints } from 'shared/composables/breakpoints'
|
||||
import { useListLoader } from 'shared/composables/listLoader'
|
||||
|
||||
/**
|
||||
* 后台题单列表。难度、状态两列和筛选拿掉了(线上全是简单 / 活跃,「状态 + 可见」合成了「公开」),
|
||||
* 后台专题列表。难度、状态两列和筛选拿掉了(线上全是简单 / 活跃,「状态 + 可见」合成了「公开」),
|
||||
* 换成老师真正关心的:几道题、多少人加入 / 做完、布置到哪天。点一行进一屏编辑
|
||||
*/
|
||||
const router = useRouter()
|
||||
@@ -42,7 +42,7 @@ function renderVisible(row: AdminProblemSet) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 手机上一个题单一张卡:名字一行(点了编辑),题数 · 加入 / 做完 · 奖章 · 创建者 · 日期一行小字 */
|
||||
/** 手机上一个专题一张卡:名字一行(点了编辑),题数 · 加入 / 做完 · 奖章 · 创建者 · 日期一行小字 */
|
||||
function renderMobileCard(row: AdminProblemSet) {
|
||||
const items = [
|
||||
`${row.problemsCount} 道题`,
|
||||
@@ -62,14 +62,14 @@ function renderMobileCard(row: AdminProblemSet) {
|
||||
}
|
||||
|
||||
const mobileColumns: DataTableColumn<AdminProblemSet>[] = [
|
||||
{ title: '题单', key: 'title', render: renderMobileCard },
|
||||
{ title: '专题', key: 'title', render: renderMobileCard },
|
||||
{ title: '公开', key: 'visible', width: 72, align: 'center', render: renderVisible },
|
||||
]
|
||||
|
||||
const desktopColumns: DataTableColumn<AdminProblemSet>[] = [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{
|
||||
title: '题单',
|
||||
title: '专题',
|
||||
key: 'title',
|
||||
minWidth: 200,
|
||||
render: renderTitle,
|
||||
@@ -131,12 +131,12 @@ watch(() => [query.page, query.limit], listProblemSets)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader title="题单列表">
|
||||
<PageHeader title="专题列表">
|
||||
<template #actions>
|
||||
<n-button type="primary" @click="router.push({ name: 'admin problemset create' })"> 新建题单 </n-button>
|
||||
<n-button type="primary" @click="router.push({ name: 'admin problemset create' })"> 新建专题 </n-button>
|
||||
</template>
|
||||
<template #filters>
|
||||
<n-input v-model:value="query.keyword" placeholder="搜题单名字" clearable @clear="clearQuery" :style="{ width: isMobile ? '100%' : '220px' }" />
|
||||
<n-input v-model:value="query.keyword" placeholder="搜专题名字" clearable @clear="clearQuery" :style="{ width: isMobile ? '100%' : '220px' }" />
|
||||
</template>
|
||||
</PageHeader>
|
||||
<n-data-table :loading="loading" striped class="set-table" :columns="columns" :data="problemSets" />
|
||||
|
||||
Reference in new issue
Block a user