Skip to content

Commit f072eb4

Browse files
committed
feat: initial forms support
1 parent 9316e7e commit f072eb4

File tree

10 files changed

+185
-4
lines changed

10 files changed

+185
-4
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ TSIMS_URL=
55
SENTRY_DSN=
66
CLUB_DATA_SECRET_KEY=
77
SENTRY_AUTH_TOKEN=
8+
NOCODB_URL=

components/custom/sidebar.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ const route = useRoute()
1818
</NuxtLink>
1919
</div>
2020
</div>
21+
<div class="px-3 py-2">
22+
<h2 class="relative px-4 text-lg font-semibold tracking-tight">
23+
学校事务
24+
</h2>
25+
<div class="mt-2">
26+
<NuxtLink to="/forms">
27+
<Button :variant="route.name === 'forms' ? 'secondary' : 'ghost'" class="w-full justify-start">
28+
<Icon class="mr-2 h-4 w-4" name="material-symbols:grid-view-outline-rounded" />
29+
表单
30+
</Button>
31+
</NuxtLink>
32+
</div>
33+
</div>
2134
<div class="px-3 py-2">
2235
<h2 class="relative px-4 text-lg font-semibold tracking-tight">
2336
社团信息

data/forms/forms.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
20240930_0001:
3+
title: "Form 1"
4+
description: "This is the first form"
5+
url: "https://wazkw5hf.nocodb.com/#/nc/form/7f4847a8-c4d5-40f9-b052-4222d07b3e2b?embed"
6+
start_date: "2024-09-30"
7+
end_date: "2024-10-30"

layouts/default.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,8 @@ watch(
8181
<!-- Sidebar -->
8282
<Sidebar class="top-0 hidden lg:inline-block h-full w-1/6" />
8383
<!-- Main content -->
84-
<ScrollArea class="h-full px-4 lg:px-8 w-full pt-8">
85-
<div class="pb-24">
86-
<slot />
87-
</div>
84+
<ScrollArea class="h-full px-4 lg:px-8 w-full pt-8 mb-24">
85+
<slot />
8886
</ScrollArea>
8987
</div>
9088
</div>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"vaul-vue": "^0.2.0",
5151
"vee-validate": "^4.13.2",
5252
"vue-clerk": "^0.6.9",
53+
"yaml": "^2.5.1",
5354
"zod": "^3.23.8"
5455
},
5556
"devDependencies": {

pages/forms/[id].vue

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script setup lang="ts">
2+
import { useWindowSize } from '@vueuse/core'
3+
import { useClerk } from 'vue-clerk'
4+
import { toast } from '~/components/ui/toast'
5+
import type { Form } from '~/types/data/forms'
6+
7+
const clerk = useClerk()
8+
9+
const { height } = useWindowSize()
10+
11+
definePageMeta({
12+
middleware: ['auth'],
13+
})
14+
15+
useHead({
16+
title: 'Forms | Enspire',
17+
})
18+
19+
const { data } = await useAsyncData<Form[]>('classroomStatuses', () => {
20+
return $fetch<Form[]>(`/api/forms/open`, {
21+
headers: useRequestHeaders(),
22+
method: 'GET',
23+
})
24+
})
25+
26+
if (!data.value) {
27+
toast({
28+
title: '错误',
29+
description: '获取教室信息出错',
30+
})
31+
}
32+
33+
const form = data.value!.find(form => form.id === useRoute().params.id)
34+
</script>
35+
36+
<template>
37+
<div v-if="form" class="font-bold text-xl">
38+
{{ form.title }}
39+
</div>
40+
<div v-if="form" class="text-muted-foreground text-sm">
41+
{{ form.description }}
42+
</div>
43+
<div v-if="form" class="rounded">
44+
<ClientOnly>
45+
<iframe :height="height - 110" width="100%" class="rounded" :src="`${form.url}&userid=${clerk.user.id}`" />
46+
</ClientOnly>
47+
</div>
48+
</template>
49+
50+
<style scoped>
51+
52+
</style>

pages/forms/index.vue

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script setup lang="ts">
2+
import {
3+
Card,
4+
CardDescription,
5+
CardFooter,
6+
CardHeader,
7+
CardTitle,
8+
} from '@/components/ui/card'
9+
import Toaster from '@/components/ui/toast/Toaster.vue'
10+
import { toast } from '~/components/ui/toast'
11+
import type { Form } from '~/types/data/forms'
12+
13+
definePageMeta({
14+
middleware: ['auth'],
15+
})
16+
17+
useHead({
18+
title: 'Forms | Enspire',
19+
})
20+
21+
const { data } = await useAsyncData<Form[]>('classroomStatuses', () => {
22+
return $fetch<Form[]>(`/api/forms/open`, {
23+
headers: useRequestHeaders(),
24+
method: 'GET',
25+
})
26+
})
27+
28+
if (!data.value) {
29+
toast({
30+
title: '错误',
31+
description: '获取教室信息出错',
32+
})
33+
}
34+
</script>
35+
36+
<template>
37+
<div class="font-bold flex items-center space-x-2 text-lg">
38+
<Icon name="material-symbols:edit-square-outline" />
39+
<div>可填写的表单</div>
40+
</div>
41+
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 mt-2">
42+
<NuxtLink v-for="club in data" :key="club.id" :to="`/forms/${club.id}`">
43+
<Card class="hover:underline">
44+
<CardHeader>
45+
<CardTitle>{{ club.title }}</CardTitle>
46+
<CardDescription>{{ club.description }}</CardDescription>
47+
</CardHeader>
48+
<CardFooter class="text-muted-foreground text-sm flex items-center">
49+
<div>点击填写</div>
50+
<Icon name="material-symbols:arrow-forward" />
51+
</CardFooter>
52+
</Card>
53+
</NuxtLink>
54+
</div>
55+
<Toaster />
56+
</template>
57+
58+
<style scoped>
59+
</style>

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/api/forms/open.get.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import dayjs from 'dayjs'
2+
import { createStorage } from 'unstorage'
3+
import fsLiteDriver from 'unstorage/drivers/fs-lite'
4+
import { parse } from 'yaml'
5+
import type { Form, Forms } from '~/types/data/forms'
6+
7+
const storage = createStorage({
8+
driver: fsLiteDriver({ base: './data' }),
9+
})
10+
11+
export default eventHandler(async (event) => {
12+
const { auth } = event.context
13+
14+
if (!auth.userId) {
15+
setResponseStatus(event, 403)
16+
return
17+
}
18+
19+
const yaml = await storage.getItem('forms:forms.yaml') as string
20+
21+
if (!yaml) {
22+
setResponseStatus(event, 404)
23+
return
24+
}
25+
26+
const yamlForms = parse(yaml) as Forms
27+
28+
const forms: Form[] = (Object.entries(yamlForms).map(([id, entry]) => ({
29+
id,
30+
...entry,
31+
})))
32+
33+
return forms
34+
.filter(entry => dayjs().isBefore(dayjs(entry.end_date)))
35+
})

types/data/forms.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface Form {
2+
id?: string
3+
title: string
4+
description: string
5+
url: string
6+
start_date: string
7+
end_date: string
8+
}
9+
10+
export interface Forms {
11+
[key: string]: Form
12+
}

0 commit comments

Comments
 (0)