Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
02e2fa7d9b
commit
859a537804
3 files changed
+63
-8
No files matched your search
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 账号名单下成表格:用户名、密码两列。批量建号建完下名单、用户列表按班下名单共用这一份。
|
||||
* 开头带 BOM,Excel 打开中文才不乱码
|
||||
*/
|
||||
export function downloadAccountsCsv(
|
||||
rows: { username: string; password: string }[],
|
||||
filename: string,
|
||||
) {
|
||||
const cell = (text: string) => (/[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text)
|
||||
const csv =
|
||||
"用户名,密码\n" + rows.map((it) => `${cell(it.username)},${cell(it.password)}`).join("\n")
|
||||
const link = document.createElement("a")
|
||||
link.href = URL.createObjectURL(new Blob([csv], { type: "text/csv;charset=utf-8" }))
|
||||
link.download = `${filename}.csv`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { downloadAccountsCsv } from "./accountsCsv"
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { classLabel } from "oj/submission/utils"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
@@ -151,15 +152,9 @@ function again() {
|
||||
passwords.clear()
|
||||
}
|
||||
|
||||
/** 下载名单:用户名、密码两列。开头带 BOM,Excel 打开中文才不乱码 */
|
||||
function download() {
|
||||
if (!done.value) return
|
||||
const csv = "用户名,密码\n" + done.value.map((it) => `${it.username},${it.password}`).join("\n")
|
||||
const link = document.createElement("a")
|
||||
link.href = URL.createObjectURL(new Blob([csv], { type: "text/csv;charset=utf-8" }))
|
||||
link.download = `${prefix.value || "账号"}.csv`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
downloadAccountsCsv(done.value, prefix.value || "账号")
|
||||
}
|
||||
|
||||
/** 打印账号条:一人一张,一页四列,打出来剪开发下去 */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import PageHeader from "admin/components/PageHeader.vue"
|
||||
import { errorMessage } from "utils/api"
|
||||
import { DataTableRowKey, SelectOption, useThemeVars } from "naive-ui"
|
||||
@@ -12,7 +13,8 @@ import UserNameCell from "./components/UserNameCell.vue"
|
||||
import PasswordCell from "./components/PasswordCell.vue"
|
||||
import { PROBLEM_PERMISSION, USER_TYPE } from "utils/constants"
|
||||
import { useRouteQuery } from "@vueuse/router"
|
||||
import { NO_CLASS } from "@oj2/contract"
|
||||
import { NO_CLASS, STUDENT_ROLES } from "@oj2/contract"
|
||||
import { downloadAccountsCsv } from "./accountsCsv"
|
||||
import { classLabel, groupClassOptions } from "oj/submission/utils"
|
||||
import { useConfigStore } from "shared/store/config"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
@@ -218,6 +220,39 @@ async function listUsers() {
|
||||
revealedPasswords.value.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 选了一个班就能把全班的用户名、密码下成表格(发给班主任、打印)。只下学生,不管搜索和角色筛选;
|
||||
* 一页最多 250 人,一个班 60 人以内,循环只是保险。老班级(23x 及更早)很多人没存明文密码,那格空着
|
||||
*/
|
||||
const downloadingClass = ref(false)
|
||||
async function downloadClassAccounts() {
|
||||
const className = query.className
|
||||
downloadingClass.value = true
|
||||
try {
|
||||
const rows: AdminUserForm[] = []
|
||||
for (let offset = 0; ; offset += 250) {
|
||||
const res = await getUserList(offset, 250, "", "", "username", className)
|
||||
rows.push(...res.results)
|
||||
if (!res.results.length || rows.length >= res.total) break
|
||||
}
|
||||
const students = rows.filter((it) =>
|
||||
(STUDENT_ROLES as readonly string[]).includes(it.adminType),
|
||||
)
|
||||
if (!students.length) {
|
||||
message.warning("这个班没有学生")
|
||||
return
|
||||
}
|
||||
downloadAccountsCsv(
|
||||
students.map((it) => ({ username: it.username, password: it.rawPassword ?? "" })),
|
||||
`${classLabel(className)}账号`,
|
||||
)
|
||||
} catch (err) {
|
||||
message.error(errorMessage(err))
|
||||
} finally {
|
||||
downloadingClass.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function chooseUsers(rowKeys: DataTableRowKey[]) {
|
||||
userIDs.value = rowKeys
|
||||
}
|
||||
@@ -366,6 +401,14 @@ watch(() => [query.page, query.limit, query.type, query.orderBy, query.className
|
||||
aria-label="班级"
|
||||
style="width: 150px"
|
||||
/>
|
||||
<n-button
|
||||
v-if="query.className && query.className !== NO_CLASS"
|
||||
:loading="downloadingClass"
|
||||
@click="downloadClassAccounts"
|
||||
>
|
||||
<template #icon><Icon icon="ph:download-simple" /></template>
|
||||
下载这个班的账号
|
||||
</n-button>
|
||||
<n-select
|
||||
v-model:value="query.type"
|
||||
:options="adminOptions"
|
||||
|
||||
Reference in new issue
Block a user