Skip to content

Commit

Permalink
feat(admin): song list
Browse files Browse the repository at this point in the history
Signed-off-by: ZTL-UwU <zhangtianli2006@163.com>
  • Loading branch information
ZTL-UwU committed Dec 23, 2024
1 parent bf7b699 commit 70f78f4
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 47 deletions.
71 changes: 70 additions & 1 deletion app/components/song/SongCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,54 @@
{{ song.creator }}
</CardDescription>
</div>
<Card
v-else-if="type === 'songs'"
>
<CardHeader>
<div class="flex flex-row">
<div>
<CardTitle>
{{ song.name }}
</CardTitle>
<CardDescription>
{{ song.creator }}
</CardDescription>
</div>
<div class="flex-grow" />
<span v-if="song.createdAt" class="text-xs text-muted-foreground">
{{ useTimeAgo(song.createdAt) }}
</span>
</div>
<p v-if="song.message" class="text-xs text-muted-foreground">
留言: {{ song.message }}
</p>

<div v-if="song.state !== 'used' && song.state !== 'dropped'" class="flex gap-1">
<Button
v-if="song.state !== 'approved' && song.id"
variant="outline"
:disable="approvePending"
size="xs"
@click="approve({ id: song.id })"
>
<Icon v-if="approvePending" name="lucide:loader-circle" class="mr-2 animate-spin" />
<Icon name="lucide:check" />
</Button>
<template v-if="song.state !== 'rejected' && song.id">
<Button
variant="outline"
:disable="rejectPending"
size="xs"
@click="reject({ id: song.id, rejectMessage: rejectMessage.trim() })"
>
<Icon v-if="rejectPending" name="lucide:loader-circle" class="mr-2 animate-spin" />
<Icon name="lucide:x" />
</Button>
<Input v-model="rejectMessage" placeholder="拒绝理由" class="h-7 rounded-sm text-xs" />
</template>
</div>
</CardHeader>
</Card>
</template>

<script setup lang="ts">
Expand All @@ -102,7 +150,7 @@ const {
selected = false,
isArrangement = false,
} = defineProps<{
type?: 'public' | 'review';
type?: 'public' | 'review' | 'songs';
selected?: boolean;
song: Partial<RouterOutput['song']['listMine'][0]>;
isArrangement?: boolean;
Expand All @@ -112,4 +160,25 @@ const isOpen = ref(false);
const isDesktop = useMediaQuery('(min-width: 768px)');
const [UseTemplate, SongDrawer] = createReusableTemplate();
const { $trpc } = useNuxtApp();
const queryClient = useQueryClient();
const { mutate: approve, isPending: approvePending } = useMutation({
mutationFn: $trpc.song.review.approve.mutate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['song.list'] });
},
onError: err => useErrorHandler(err),
});
const { mutate: reject, isPending: rejectPending } = useMutation({
mutationFn: $trpc.song.review.reject.mutate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['song.list'] });
},
onError: err => useErrorHandler(err),
});
const rejectMessage = ref('');
</script>
8 changes: 8 additions & 0 deletions app/layouts/admin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
</SidebarMenuButton>
</SidebarMenuItem>
</NuxtLink>
<NuxtLink v-if="userStore.permissions.includes('review')" to="/admin/songs">
<SidebarMenuItem>
<SidebarMenuButton>
<Icon name="lucide:list-music" />
<span>全部歌曲</span>
</SidebarMenuButton>
</SidebarMenuItem>
</NuxtLink>
<NuxtLink v-if="userStore.permissions.includes('arrange')" to="/admin/arrange">
<SidebarMenuItem>
<SidebarMenuButton>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/admin/arrange.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
</template>

<script setup lang="ts">
import type { RouterOutput } from '~~/types';
import type { DateRange } from 'radix-vue';
import type { RouterOutput } from '~~/types';
import { RangeCalendar } from '@/components/ui/range-calendar';
import { type DateValue, getLocalTimeZone, startOfWeek, today } from '@internationalized/date';
Expand Down
85 changes: 85 additions & 0 deletions app/pages/admin/songs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<ResizablePanelGroup id="songs-resizable" direction="horizontal">
<template v-for="(panel, index) in panels" :key="panel">
<ResizablePanel :id="`songs-resizable-resize-${index}`" :default-size="25">
<ScrollArea class="h-[calc(100svh-4rem)]">
<div class="sticky top-0 z-50 flex h-16 items-center border-b px-4" :class="panel.className">
<Icon :name="panel.icon" size="17" class="mr-2" />
<span class="text-sm font-semibold">{{ panel.label }}</span>
</div>

<TransitionGroup name="list" tag="ul" class="flex flex-col gap-3 p-4">
<li v-for="song in songList?.filter((x) => x.state === panel.value)" :key="song.id">
<SongCard :song type="songs" />
</li>
</TransitionGroup>
</ScrollArea>
</ResizablePanel>
<ResizableHandle :id="`songs-resizable-resize-${index}`" with-handle />
</template>
<ResizablePanel id="songs-resizable-panel-4" :default-size="25">
<ScrollArea class="h-[calc(100svh-4rem)]">
<div class="sticky top-0 flex h-16 items-center border-b bg-background px-4">
<Tabs v-model="selectedTab" class="w-full" default-value="used">
<TabsList class="grid grid-cols-2">
<TabsTrigger value="used">
入选
</TabsTrigger>
<TabsTrigger value="dropped">
落选
</TabsTrigger>
</TabsList>
</Tabs>
</div>

<TransitionGroup v-if="selectedTab === 'used'" name="list" tag="ul" class="flex flex-col gap-3 p-4">
<li v-for="song in songList?.filter((x) => x.state === 'used')" :key="song.id">
<SongCard :song type="songs" />
</li>
</TransitionGroup>
<TransitionGroup v-if="selectedTab === 'dropped'" name="list" tag="ul" class="flex flex-col gap-3 p-4">
<li v-for="song in songList?.filter((x) => x.state === 'dropped')" :key="song.id">
<SongCard :song type="songs" />
</li>
</TransitionGroup>
</ScrollArea>
</ResizablePanel>
</ResizablePanelGroup>
</template>

<script setup lang="ts">
definePageMeta({
layout: 'admin',
});
const { $trpc } = useNuxtApp();
const { data: songList, suspense } = useQuery({
queryFn: () => $trpc.song.list.query(),
queryKey: ['song.list'],
refetchOnWindowFocus: false,
});
await suspense();
const panels = ref([
{
label: '审核中',
value: 'pending',
icon: 'lucide:clock',
className: 'text-amber-700 dark:text-amber-200',
},
{
label: '审核通过',
value: 'approved',
icon: 'lucide:thumbs-up',
className: 'text-green-700 dark:text-green-200',
},
{
label: '审核未通过',
value: 'rejected',
icon: 'lucide:thumbs-down',
className: 'text-red-700 dark:text-red-200',
},
]);
const selectedTab = ref<'used' | 'dropped'>('used');
</script>
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export const breadCrumb: Record<string, string> = {
arrange: '排歌列表',
time: '开放时间',
words: '屏蔽词',
songs: '全部歌曲',
};
58 changes: 29 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,59 +19,59 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@nuxt/icon": "^1.8.2",
"@nuxt/icon": "^1.10.3",
"@nuxt/image": "^1.8.1",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/tailwindcss": "^6.12.2",
"@pinia/nuxt": "^0.7.0",
"@pinia/nuxt": "^0.9.0",
"@popperjs/core": "^2.11.8",
"@radix-icons/vue": "^1.0.0",
"@tanstack/vue-query": "^5.61.3",
"@tanstack/vue-query": "^5.62.8",
"@tanstack/vue-table": "^8.20.5",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"@unovis/ts": "^1.4.5",
"@unovis/vue": "^1.4.5",
"@vee-validate/nuxt": "^4.14.7",
"@vee-validate/zod": "^4.14.7",
"@vueuse/integrations": "^12.0.0",
"@unovis/ts": "^1.5.0",
"@unovis/vue": "^1.5.0",
"@vee-validate/nuxt": "^4.15.0",
"@vee-validate/zod": "^4.15.0",
"@vueuse/integrations": "^12.1.0",
"@ztl-uwu/v-calendar": "^3.1.3",
"d3": "^7.9.0",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.36.4",
"drizzle-seed": "^0.1.1",
"fuse.js": "^7",
"dotenv": "^16.4.7",
"drizzle-orm": "^0.38.2",
"drizzle-seed": "^0.1.3",
"fuse.js": "^7.0.0",
"jose": "^5.9.6",
"nuxt": "^3.14.1592",
"pg": "^8.13.1",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
"radix-vue": "^1.9.10",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"radix-vue": "^1.9.11",
"shadcn-nuxt": "^0.11.3",
"superjson": "^2.2.1",
"superjson": "^2.2.2",
"trpc-nuxt": "^0.10.22",
"vaul-vue": "^0.2.0",
"vee-validate": "^4.14.7",
"vee-validate": "^4.15.0",
"vue": "latest",
"vue-sonner": "^1.3.0",
"zod": "^3.23.8"
"zod": "^3.24.1"
},
"devDependencies": {
"@antfu/eslint-config": "^3.9.2",
"@electric-sql/pglite": "^0.2.13",
"@iconify-json/lucide": "^1.2.15",
"@antfu/eslint-config": "^3.12.0",
"@electric-sql/pglite": "^0.2.15",
"@iconify-json/lucide": "^1.2.20",
"@tailwindcss/typography": "^0.5.15",
"@types/pg": "^8.11.10",
"@unocss/eslint-plugin": "^0.64.1",
"@vueuse/core": "^11.3.0",
"@vueuse/nuxt": "^11.3.0",
"class-variance-authority": "^0.7.0",
"@unocss/eslint-plugin": "^0.65.2",
"@vueuse/core": "^12.1.0",
"@vueuse/nuxt": "^12.1.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-kit": "^0.28.1",
"eslint": "^9.15.0",
"eslint-plugin-format": "^0.1.2",
"drizzle-kit": "^0.30.1",
"eslint": "^9.17.0",
"eslint-plugin-format": "^0.1.3",
"eslint-plugin-tailwindcss": "^3.17.5",
"tailwind-merge": "^2.5.4",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.19.2",
"typescript": "5.6.3",
Expand Down
4 changes: 2 additions & 2 deletions scripts/reset.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import { reset } from 'drizzle-seed';
// path to a file with schema you want to reset
import * as schema from '~~/server/db/schema';
import { env } from '~~/server/env';
import { drizzle } from 'drizzle-orm/node-postgres';
import { reset } from 'drizzle-seed';

async function main() {
const db = drizzle(env.DATABASE_URL);
Expand Down
4 changes: 2 additions & 2 deletions scripts/seed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as schema from '~~/server/db/schema';
import { env } from '~~/server/env';
import { drizzle } from 'drizzle-orm/node-postgres';
import { seed } from 'drizzle-seed';
import * as schema from '~~/server/db/schema';
import { env } from '~~/server/env';

async function main() {
const db = drizzle(env.DATABASE_URL);
Expand Down
4 changes: 2 additions & 2 deletions server/trpc/routers/arrangements.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { parseDate } from '@internationalized/date';
import { TRPCError } from '@trpc/server';
import { db } from '~~/server/db';
import { arrangements, songs } from '~~/server/db/schema';
import { desc, eq, sql } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '~~/server/db';
import { arrangements, songs } from '~~/server/db/schema';
import { adminProcedure, protectedProcedure, requirePermission, router } from '../trpc';
import { fitsInTime } from './time';

Expand Down
6 changes: 4 additions & 2 deletions server/trpc/routers/song.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TRPCError } from '@trpc/server';
import { db } from '~~/server/db';
import { songs } from '~~/server/db/schema';
import { desc, eq, gt } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '~~/server/db';
import { songs } from '~~/server/db/schema';
import { adminProcedure, protectedProcedure, requirePermission, router } from '../trpc';
import { fitsInTime } from './time';

Expand Down Expand Up @@ -56,6 +56,7 @@ export const songRouter = router({
}),

list: adminProcedure
.use(requirePermission(['review']))
.query(async () => {
return await db.query.songs.findMany({
orderBy: desc(songs.createdAt),
Expand All @@ -72,6 +73,7 @@ export const songRouter = router({
}),

listReview: adminProcedure
.use(requirePermission(['review']))
.query(async () => {
return await db.query.songs.findMany({
where: eq(songs.state, 'pending'),
Expand Down
2 changes: 1 addition & 1 deletion server/trpc/routers/stats.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSongState } from '~~/types';
import { count } from 'drizzle-orm';
import { db } from '~~/server/db';
import { users } from '~~/server/db/schema';
import { count } from 'drizzle-orm';
import { adminProcedure, router } from '../trpc';

export const statsRouter = router({
Expand Down
4 changes: 2 additions & 2 deletions server/trpc/routers/time.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '~~/server/db';
import { times } from '~~/server/db/schema';
import { asc, eq } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '~~/server/db';
import { times } from '~~/server/db/schema';
import { adminProcedure, protectedProcedure, requirePermission, router } from '../trpc';

export async function fitsInTime(t: Date) {
Expand Down
4 changes: 2 additions & 2 deletions server/trpc/routers/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TRPCError } from '@trpc/server';
import { db } from '~~/server/db';
import { users } from '~~/server/db/schema';
import { desc, eq } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '~~/server/db';
import { users } from '~~/server/db/schema';
import { adminProcedure, protectedProcedure, publicProcedure, requirePermission, router } from '../trpc';

export const userRouter = router({
Expand Down
4 changes: 2 additions & 2 deletions server/trpc/routers/words.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '~~/server/db';
import { blockWords } from '~~/server/db/schema';
import { desc, eq } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '~~/server/db';
import { blockWords } from '~~/server/db/schema';
import { adminProcedure, requirePermission, router } from '../trpc';

export const blockWordsRouter = router({
Expand Down
Loading

0 comments on commit 70f78f4

Please sign in to comment.