fix
This commit is contained in:
@@ -287,12 +287,6 @@ export function createAnnouncement(announcement: AnnouncementEdit) {
|
||||
return http.post("admin/announcement", announcement)
|
||||
}
|
||||
|
||||
export function getReactionStats(offset = 0, limit = 10, problem: string) {
|
||||
return http.get("admin/reaction", {
|
||||
params: { offset, limit, problem },
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTutorialList() {
|
||||
const res = await http.get<Tutorial[]>("admin/tutorial")
|
||||
return res.data
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { NFlex, NSwitch, NTag } from "naive-ui"
|
||||
import { NFlex, NSwitch, NTag, NTooltip } from "naive-ui"
|
||||
import { Icon } from "@iconify/vue"
|
||||
import Pagination from "shared/components/Pagination.vue"
|
||||
import { usePagination } from "shared/composables/pagination"
|
||||
import { getTagColor, parseTime } from "utils/functions"
|
||||
import type { AdminProblemFiltered } from "utils/types"
|
||||
import { DIFFICULTY } from "utils/constants"
|
||||
import { DIFFICULTY, REACTIONS } from "utils/constants"
|
||||
import { getProblemList, toggleProblemVisible } from "../api"
|
||||
import Actions from "./components/Actions.vue"
|
||||
import Modal from "./components/Modal.vue"
|
||||
@@ -135,6 +135,21 @@ const baseColumns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||
: null,
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "反馈",
|
||||
key: "top_reaction",
|
||||
width: 60,
|
||||
render: (row) => {
|
||||
const top = row.top_reaction
|
||||
if (!top) return null
|
||||
const reaction = REACTIONS.find((it) => it.key === top.type)
|
||||
if (!reaction) return null
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => h(Icon, { width: 18, icon: reaction.icon }),
|
||||
default: () => `${reaction.label} ${top.count} 人`,
|
||||
})
|
||||
},
|
||||
},
|
||||
{ title: "出题人", key: "username", width: 120 },
|
||||
{
|
||||
title: "创建时间",
|
||||
@@ -167,9 +182,10 @@ const baseColumns: DataTableColumn<AdminProblemFiltered>[] = [
|
||||
},
|
||||
]
|
||||
|
||||
// 比赛题目接口不返回 top_reaction,这一列只在普通题目列表里显示
|
||||
const columns = computed<DataTableColumn<AdminProblemFiltered>[]>(() =>
|
||||
isContestProblemList.value
|
||||
? baseColumns
|
||||
? baseColumns.filter((it) => !("key" in it) || it.key !== "top_reaction")
|
||||
: [{ type: "selection" }, ...baseColumns],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<template>
|
||||
<n-flex justify="space-between" class="titleWrapper">
|
||||
<h2 class="title">题目反馈统计</h2>
|
||||
<div>
|
||||
<n-input
|
||||
v-model:value="query.problem"
|
||||
clearable
|
||||
placeholder="输入题目序号"
|
||||
/>
|
||||
</div>
|
||||
</n-flex>
|
||||
<n-data-table striped :columns="columns" :data="rows" />
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { NButton, NFlex } from "naive-ui"
|
||||
import Pagination from "shared/components/Pagination.vue"
|
||||
import { REACTIONS } from "utils/constants"
|
||||
import { parseTime } from "utils/functions"
|
||||
import type { ReactionStatsRow } from "utils/types"
|
||||
import { getReactionStats } from "../api"
|
||||
|
||||
const rows = ref<ReactionStatsRow[]>([])
|
||||
const total = ref(0)
|
||||
const query = reactive({
|
||||
limit: 10,
|
||||
page: 1,
|
||||
problem: "",
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<ReactionStatsRow>[] = [
|
||||
{
|
||||
title: "题目",
|
||||
key: "pid",
|
||||
width: 100,
|
||||
fixed: "left",
|
||||
render: (row) =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => window.open("/problem/" + row.pid, "_blank"),
|
||||
},
|
||||
() => row.pid,
|
||||
),
|
||||
},
|
||||
{ title: "标题", key: "title", minWidth: 180 },
|
||||
{ title: "表态人数", key: "users", width: 120 },
|
||||
...REACTIONS.map((item) => ({
|
||||
title: () =>
|
||||
h(NFlex, { align: "center", size: 4, wrap: false }, () => [
|
||||
h(Icon, { icon: item.icon, width: 18 }),
|
||||
item.label,
|
||||
]),
|
||||
key: item.key,
|
||||
width: 120,
|
||||
})),
|
||||
{
|
||||
title: "时间",
|
||||
key: "last_time",
|
||||
width: 180,
|
||||
render: (row) => parseTime(row.last_time, "YYYY-MM-DD HH:mm:ss"),
|
||||
},
|
||||
]
|
||||
|
||||
async function listStats() {
|
||||
const offset = (query.page - 1) * query.limit
|
||||
const res = await getReactionStats(offset, query.limit, query.problem)
|
||||
rows.value = res.data.results
|
||||
total.value = res.data.total
|
||||
}
|
||||
|
||||
onMounted(listStats)
|
||||
watch(() => [query.page, query.limit], listStats)
|
||||
watchDebounced(() => query.problem, listStats, {
|
||||
debounce: 500,
|
||||
maxWait: 1000,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.titleWrapper {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -138,6 +138,7 @@ export function useFireworks() {
|
||||
|
||||
confetti({
|
||||
particleCount: 12,
|
||||
angle: 270,
|
||||
spread: 100,
|
||||
startVelocity: 12,
|
||||
gravity: 0.6,
|
||||
@@ -145,7 +146,7 @@ export function useFireworks() {
|
||||
ticks: 200,
|
||||
scalar: 1.2,
|
||||
shapes: ["star"],
|
||||
origin: { x: Math.random(), y: -0.1 },
|
||||
origin: { x: Math.random(), y: 0 },
|
||||
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
|
||||
})
|
||||
}, 150)
|
||||
@@ -285,29 +286,32 @@ export function useFireworks() {
|
||||
|
||||
// 效果7: 螺旋上升
|
||||
() => {
|
||||
const defaults = {
|
||||
spread: 360,
|
||||
ticks: 100,
|
||||
gravity: 0,
|
||||
decay: 0.94,
|
||||
startVelocity: 30,
|
||||
}
|
||||
const steps = 16
|
||||
const colors = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"]
|
||||
|
||||
function shoot() {
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 50,
|
||||
scalar: 1.2,
|
||||
shapes: ["circle", "square"],
|
||||
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
|
||||
})
|
||||
}
|
||||
for (let step = 0; step < steps; step++) {
|
||||
runLater(() => {
|
||||
const progress = step / (steps - 1)
|
||||
const phase = step * (Math.PI / 4)
|
||||
|
||||
runLater(shoot, 0)
|
||||
runLater(shoot, 100)
|
||||
runLater(shoot, 200)
|
||||
runLater(shoot, 300)
|
||||
runLater(shoot, 400)
|
||||
confetti({
|
||||
particleCount: 7,
|
||||
angle: 90 + Math.cos(phase) * 18,
|
||||
spread: 25,
|
||||
startVelocity: 16,
|
||||
gravity: 0.25,
|
||||
decay: 0.94,
|
||||
ticks: 100,
|
||||
scalar: 0.9,
|
||||
shapes: ["circle", "square"],
|
||||
origin: {
|
||||
x: 0.5 + Math.sin(phase) * 0.14,
|
||||
y: 0.85 - progress * 0.55,
|
||||
},
|
||||
colors,
|
||||
})
|
||||
}, step * 65)
|
||||
}
|
||||
},
|
||||
|
||||
// 效果8: 表情包烟花
|
||||
@@ -315,6 +319,7 @@ export function useFireworks() {
|
||||
loadEmojiShapes().then((emojiShapes) => {
|
||||
confetti({
|
||||
shapes: emojiShapes,
|
||||
flat: true,
|
||||
scalar: 6,
|
||||
particleCount: 18,
|
||||
spread: 360,
|
||||
@@ -349,16 +354,28 @@ export function useFireworks() {
|
||||
|
||||
// 效果10: 礼花瀑布
|
||||
() => {
|
||||
confetti({
|
||||
particleCount: 150,
|
||||
spread: 180,
|
||||
startVelocity: 0,
|
||||
gravity: 1.2,
|
||||
decay: 0.95,
|
||||
ticks: 300,
|
||||
origin: { x: 0.5, y: 0 },
|
||||
colors: ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"],
|
||||
})
|
||||
const animationEnd = Date.now() + 1800
|
||||
const colors = ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"]
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (Date.now() >= animationEnd) return stopTimer(interval)
|
||||
|
||||
for (let column = 0; column < 6; column++) {
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 270,
|
||||
spread: 30,
|
||||
startVelocity: 5,
|
||||
gravity: 1,
|
||||
decay: 0.96,
|
||||
ticks: 240,
|
||||
scalar: 1.1,
|
||||
origin: { x: (column + Math.random()) / 6, y: 0 },
|
||||
colors,
|
||||
})
|
||||
}
|
||||
}, 120)
|
||||
timers.add(interval)
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -257,12 +257,6 @@ export const admins: RouteRecordRaw = {
|
||||
props: true,
|
||||
meta: { requiresSuperAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "reaction/list",
|
||||
name: "admin reaction list",
|
||||
component: () => import("admin/reaction/list.vue"),
|
||||
meta: { requiresSuperAdmin: true },
|
||||
},
|
||||
{
|
||||
path: "message/list",
|
||||
name: "admin message list",
|
||||
|
||||
@@ -114,15 +114,6 @@ const options = computed<MenuOption[]>(() => {
|
||||
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户" }),
|
||||
key: "admin user list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/reaction/list" },
|
||||
{ default: () => "反馈" },
|
||||
),
|
||||
key: "admin reaction list",
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
|
||||
@@ -226,6 +226,8 @@ export interface AdminProblemFiltered {
|
||||
has_ast_rules: boolean
|
||||
allow_flowchart: boolean
|
||||
show_flowchart: boolean
|
||||
// 比赛题目列表接口不返回这个字段
|
||||
top_reaction?: { type: ReactionKey; count: number } | null
|
||||
}
|
||||
|
||||
// 题单相关类型
|
||||
@@ -610,13 +612,6 @@ export interface ReactionState {
|
||||
counts: ReactionCounts | null
|
||||
}
|
||||
|
||||
export interface ReactionStatsRow extends ReactionCounts {
|
||||
pid: string
|
||||
title: string
|
||||
users: number
|
||||
last_time: string
|
||||
}
|
||||
|
||||
export interface Tutorial {
|
||||
id: number
|
||||
title: string
|
||||
|
||||
Reference in New Issue
Block a user