fix
This commit is contained in:
@@ -287,12 +287,6 @@ export function createAnnouncement(announcement: AnnouncementEdit) {
|
|||||||
return http.post("admin/announcement", announcement)
|
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() {
|
export async function getTutorialList() {
|
||||||
const res = await http.get<Tutorial[]>("admin/tutorial")
|
const res = await http.get<Tutorial[]>("admin/tutorial")
|
||||||
return res.data
|
return res.data
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<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 { Icon } from "@iconify/vue"
|
||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { usePagination } from "shared/composables/pagination"
|
import { usePagination } from "shared/composables/pagination"
|
||||||
import { getTagColor, parseTime } from "utils/functions"
|
import { getTagColor, parseTime } from "utils/functions"
|
||||||
import type { AdminProblemFiltered } from "utils/types"
|
import type { AdminProblemFiltered } from "utils/types"
|
||||||
import { DIFFICULTY } from "utils/constants"
|
import { DIFFICULTY, REACTIONS } from "utils/constants"
|
||||||
import { getProblemList, toggleProblemVisible } from "../api"
|
import { getProblemList, toggleProblemVisible } from "../api"
|
||||||
import Actions from "./components/Actions.vue"
|
import Actions from "./components/Actions.vue"
|
||||||
import Modal from "./components/Modal.vue"
|
import Modal from "./components/Modal.vue"
|
||||||
@@ -135,6 +135,21 @@ const baseColumns: DataTableColumn<AdminProblemFiltered>[] = [
|
|||||||
: null,
|
: 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: "出题人", key: "username", width: 120 },
|
||||||
{
|
{
|
||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
@@ -167,9 +182,10 @@ const baseColumns: DataTableColumn<AdminProblemFiltered>[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 比赛题目接口不返回 top_reaction,这一列只在普通题目列表里显示
|
||||||
const columns = computed<DataTableColumn<AdminProblemFiltered>[]>(() =>
|
const columns = computed<DataTableColumn<AdminProblemFiltered>[]>(() =>
|
||||||
isContestProblemList.value
|
isContestProblemList.value
|
||||||
? baseColumns
|
? baseColumns.filter((it) => !("key" in it) || it.key !== "top_reaction")
|
||||||
: [{ type: "selection" }, ...baseColumns],
|
: [{ 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({
|
confetti({
|
||||||
particleCount: 12,
|
particleCount: 12,
|
||||||
|
angle: 270,
|
||||||
spread: 100,
|
spread: 100,
|
||||||
startVelocity: 12,
|
startVelocity: 12,
|
||||||
gravity: 0.6,
|
gravity: 0.6,
|
||||||
@@ -145,7 +146,7 @@ export function useFireworks() {
|
|||||||
ticks: 200,
|
ticks: 200,
|
||||||
scalar: 1.2,
|
scalar: 1.2,
|
||||||
shapes: ["star"],
|
shapes: ["star"],
|
||||||
origin: { x: Math.random(), y: -0.1 },
|
origin: { x: Math.random(), y: 0 },
|
||||||
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
|
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
|
||||||
})
|
})
|
||||||
}, 150)
|
}, 150)
|
||||||
@@ -285,29 +286,32 @@ export function useFireworks() {
|
|||||||
|
|
||||||
// 效果7: 螺旋上升
|
// 效果7: 螺旋上升
|
||||||
() => {
|
() => {
|
||||||
const defaults = {
|
const steps = 16
|
||||||
spread: 360,
|
const colors = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"]
|
||||||
ticks: 100,
|
|
||||||
gravity: 0,
|
|
||||||
decay: 0.94,
|
|
||||||
startVelocity: 30,
|
|
||||||
}
|
|
||||||
|
|
||||||
function shoot() {
|
for (let step = 0; step < steps; step++) {
|
||||||
confetti({
|
runLater(() => {
|
||||||
...defaults,
|
const progress = step / (steps - 1)
|
||||||
particleCount: 50,
|
const phase = step * (Math.PI / 4)
|
||||||
scalar: 1.2,
|
|
||||||
shapes: ["circle", "square"],
|
|
||||||
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
runLater(shoot, 0)
|
confetti({
|
||||||
runLater(shoot, 100)
|
particleCount: 7,
|
||||||
runLater(shoot, 200)
|
angle: 90 + Math.cos(phase) * 18,
|
||||||
runLater(shoot, 300)
|
spread: 25,
|
||||||
runLater(shoot, 400)
|
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: 表情包烟花
|
// 效果8: 表情包烟花
|
||||||
@@ -315,6 +319,7 @@ export function useFireworks() {
|
|||||||
loadEmojiShapes().then((emojiShapes) => {
|
loadEmojiShapes().then((emojiShapes) => {
|
||||||
confetti({
|
confetti({
|
||||||
shapes: emojiShapes,
|
shapes: emojiShapes,
|
||||||
|
flat: true,
|
||||||
scalar: 6,
|
scalar: 6,
|
||||||
particleCount: 18,
|
particleCount: 18,
|
||||||
spread: 360,
|
spread: 360,
|
||||||
@@ -349,16 +354,28 @@ export function useFireworks() {
|
|||||||
|
|
||||||
// 效果10: 礼花瀑布
|
// 效果10: 礼花瀑布
|
||||||
() => {
|
() => {
|
||||||
confetti({
|
const animationEnd = Date.now() + 1800
|
||||||
particleCount: 150,
|
const colors = ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"]
|
||||||
spread: 180,
|
|
||||||
startVelocity: 0,
|
const interval = setInterval(() => {
|
||||||
gravity: 1.2,
|
if (Date.now() >= animationEnd) return stopTimer(interval)
|
||||||
decay: 0.95,
|
|
||||||
ticks: 300,
|
for (let column = 0; column < 6; column++) {
|
||||||
origin: { x: 0.5, y: 0 },
|
confetti({
|
||||||
colors: ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"],
|
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,
|
props: true,
|
||||||
meta: { requiresSuperAdmin: true },
|
meta: { requiresSuperAdmin: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "reaction/list",
|
|
||||||
name: "admin reaction list",
|
|
||||||
component: () => import("admin/reaction/list.vue"),
|
|
||||||
meta: { requiresSuperAdmin: true },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "message/list",
|
path: "message/list",
|
||||||
name: "admin message list",
|
name: "admin message list",
|
||||||
|
|||||||
@@ -114,15 +114,6 @@ const options = computed<MenuOption[]>(() => {
|
|||||||
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户" }),
|
h(RouterLink, { to: "/admin/user/list" }, { default: () => "用户" }),
|
||||||
key: "admin user list",
|
key: "admin user list",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: () =>
|
|
||||||
h(
|
|
||||||
RouterLink,
|
|
||||||
{ to: "/admin/reaction/list" },
|
|
||||||
{ default: () => "反馈" },
|
|
||||||
),
|
|
||||||
key: "admin reaction list",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () =>
|
||||||
h(
|
h(
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ export interface AdminProblemFiltered {
|
|||||||
has_ast_rules: boolean
|
has_ast_rules: boolean
|
||||||
allow_flowchart: boolean
|
allow_flowchart: boolean
|
||||||
show_flowchart: boolean
|
show_flowchart: boolean
|
||||||
|
// 比赛题目列表接口不返回这个字段
|
||||||
|
top_reaction?: { type: ReactionKey; count: number } | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 题单相关类型
|
// 题单相关类型
|
||||||
@@ -610,13 +612,6 @@ export interface ReactionState {
|
|||||||
counts: ReactionCounts | null
|
counts: ReactionCounts | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReactionStatsRow extends ReactionCounts {
|
|
||||||
pid: string
|
|
||||||
title: string
|
|
||||||
users: number
|
|
||||||
last_time: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tutorial {
|
export interface Tutorial {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
Reference in New Issue
Block a user