This commit is contained in:
2026-03-22 14:00:08 +08:00
parent 69099986e0
commit a072128b17
11 changed files with 2 additions and 770 deletions

View File

@@ -1,14 +1,10 @@
import { BellDot, Bug, Coins, DollarSign, Palette, PictureInPicture2, Receipt, Settings, SquareUserRound, TrendingUp, User, Users, Wrench } from 'lucide-vue-next'
import { Coins, DollarSign, Palette, Receipt, Settings, TrendingUp, Users } from 'lucide-vue-next'
import type { NavGroup } from '@/components/app-sidebar/types'
export function useSidebar() {
const settingsNavItems = [
{ title: 'Profile', url: '/settings/', icon: User },
{ title: 'Account', url: '/settings/account', icon: Wrench },
{ title: 'Appearance', url: '/settings/appearance', icon: Palette },
{ title: 'Notifications', url: '/settings/notifications', icon: BellDot },
{ title: 'Display', url: '/settings/display', icon: PictureInPicture2 },
{ title: '外观设置', url: '/settings/appearance', icon: Palette },
]
const navData = ref<NavGroup[]>([
@@ -22,29 +18,6 @@ export function useSidebar() {
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
],
},
{
title: 'Pages',
items: [
{
title: 'Auth',
icon: SquareUserRound,
items: [
{ title: 'Monisuo Login', url: '/auth/monisuo-sign-in' },
],
},
{
title: 'Errors',
icon: Bug,
items: [
{ title: '401 | Unauthorized', url: '/errors/401' },
{ title: '403 | Forbidden', url: '/errors/403' },
{ title: '404 | Not Found', url: '/errors/404' },
{ title: '500 | Internal Server Error', url: '/errors/500' },
{ title: '503 | Maintenance Error', url: '/errors/503' },
],
},
],
},
{
title: 'Other',
items: [

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
import AccountForm from './components/account-form.vue'
import SettingsLayout from './components/settings-layout.vue'
</script>
<template>
<SettingsLayout>
<AccountForm />
</SettingsLayout>
</template>

View File

@@ -1,181 +0,0 @@
<script setup lang="ts">
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
import { toTypedSchema } from '@vee-validate/zod'
import { CalendarDays, Check, ChevronsUpDown } from 'lucide-vue-next'
import { toDate } from 'reka-ui/date'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'
import { accountValidator } from '../validators/account.validator'
const open = ref(false)
const dateValue = ref()
const placeholder = ref()
const languages = [
{ label: 'English', value: 'en' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
{ label: 'Spanish', value: 'es' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'Russian', value: 'ru' },
{ label: 'Japanese', value: 'ja' },
{ label: 'Korean', value: 'ko' },
{ label: 'Chinese', value: 'zh' },
] as const
const df = new DateFormatter('en-US', {
dateStyle: 'long',
})
const accountFormSchema = toTypedSchema(accountValidator)
// https://github.com/logaretm/vee-validate/issues/3521
// https://github.com/logaretm/vee-validate/discussions/3571
async function onSubmit(values: any) {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<div>
<h3 class="text-lg font-medium">
Account
</h3>
<p class="text-sm text-muted-foreground">
Update your account settings. Set your preferred language and timezone.
</p>
</div>
<Separator class="my-4" />
<Form v-slot="{ setFieldValue }" :validation-schema="accountFormSchema" class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="name">
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Your name" v-bind="componentField" />
</FormControl>
<FormDescription>
This is the name that will be displayed on your profile and in emails.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ field, value }" name="dob">
<FormItem class="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" :class="cn(
'w-[240px] justify-start text-left font-normal',
!value && 'text-muted-foreground',
)"
>
<CalendarDays class="size-4 opacity-50" />
<span>{{ value ? df.format(toDate(dateValue, getLocalTimeZone())) : "Pick a date" }}</span>
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent>
<Calendar
v-model:placeholder="placeholder"
v-model="dateValue"
calendar-label="Date of birth"
initial-focus
:min-value="new CalendarDate(1900, 1, 1)"
:max-value="today(getLocalTimeZone())"
@update:model-value="(v) => {
if (v) {
dateValue = v
setFieldValue('dob', toDate(v).toISOString())
}
else {
dateValue = undefined
setFieldValue('dob', undefined)
}
}"
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
<input type="hidden" v-bind="field">
</FormField>
<FormField v-slot="{ value }" name="language">
<FormItem class="flex flex-col">
<FormLabel>Language</FormLabel>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<FormControl>
<Button
variant="outline" role="combobox" :aria-expanded="open" :class="cn(
'w-[200px] justify-between',
!value && 'text-muted-foreground',
)"
>
{{ value ? languages.find(
(language) => language.value === value,
)?.label : 'Select language...' }}
<ChevronsUpDown class="size-4 ml-2 opacity-50 shrink-0" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>No language found.</CommandEmpty>
<CommandList>
<CommandGroup>
<CommandItem
v-for="language in languages" :key="language.value" :value="language.label"
@select="() => {
setFieldValue('language', language.value)
open = false
}"
>
<Check
:class="cn(
'mr-2 h-4 w-4',
value === language.value ? 'opacity-100' : 'opacity-0',
)"
/>
{{ language.label }}
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the language that will be used in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<div class="flex justify-start">
<Button type="submit">
Update account
</Button>
</div>
</Form>
</template>

View File

@@ -1,105 +0,0 @@
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Separator } from '@/components/ui/separator'
import { displayValidator } from '../validators/display.validator'
const items = [
{
id: 'recents',
label: 'Recents',
},
{
id: 'home',
label: 'Home',
},
{
id: 'applications',
label: 'Applications',
},
{
id: 'desktop',
label: 'Desktop',
},
{
id: 'downloads',
label: 'Downloads',
},
{
id: 'documents',
label: 'Documents',
},
] as const
const displayFormSchema = toTypedSchema(displayValidator)
const { handleSubmit } = useForm({
validationSchema: displayFormSchema,
initialValues: {
items: ['recents', 'home'],
},
})
const onSubmit = handleSubmit((values) => {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
})
</script>
<template>
<div>
<h3 class="text-lg font-medium">
Display
</h3>
<p class="text-sm text-muted-foreground">
Turn items on or off to control what's displayed in the app.
</p>
</div>
<Separator class="my-4" />
<form @submit="onSubmit">
<FormField name="items">
<FormItem>
<div class="mb-4">
<FormLabel class="text-base">
Sidebar
</FormLabel>
<FormDescription>
Select the items you want to display in the sidebar.
</FormDescription>
</div>
<FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" name="items">
<FormItem :key="item.id" class="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
:model-value="value.includes(item.id)"
@update:model-value="(checked: boolean | 'indeterminate') => {
if (Array.isArray(value)) {
handleChange(checked ? [...value, item.id] : value.filter(id => id !== item.id))
}
}"
/>
</FormControl>
<FormLabel class="font-normal">
{{ item.label }}
</FormLabel>
</FormItem>
</FormField>
<FormMessage />
</FormItem>
</FormField>
<div class="flex justify-start mt-4">
<Button type="submit">
Update display
</Button>
</div>
</form>
</template>

View File

@@ -1,194 +0,0 @@
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Separator } from '@/components/ui/separator'
import { Switch } from '@/components/ui/switch'
import { notificationsValidator } from '../validators/notifications.validator'
const notificationsFormSchema = toTypedSchema(notificationsValidator)
const { handleSubmit } = useForm({
validationSchema: notificationsFormSchema,
initialValues: {
communication_emails: false,
marketing_emails: false,
social_emails: true,
security_emails: true,
},
})
const onSubmit = handleSubmit((values) => {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
})
</script>
<template>
<div>
<h3 class="text-lg font-medium">
Notifications
</h3>
<p class="text-sm text-muted-foreground">
Configure how you receive notifications.
</p>
</div>
<Separator class="my-4" />
<form class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField }" type="radio" name="type">
<FormItem class="space-y-3">
<FormLabel>Notify me about...</FormLabel>
<FormControl>
<RadioGroup
class="flex flex-col space-y-1"
v-bind="componentField"
>
<FormItem class="flex items-center space-y-0">
<FormControl>
<RadioGroupItem value="all" />
</FormControl>
<FormLabel class="font-normal">
All new messages
</FormLabel>
</FormItem>
<FormItem class="flex items-center space-y-0">
<FormControl>
<RadioGroupItem value="mentions" />
</FormControl>
<FormLabel class="font-normal">
Direct messages and mentions
</FormLabel>
</FormItem>
<FormItem class="flex items-center space-y-0">
<FormControl>
<RadioGroupItem value="none" />
</FormControl>
<FormLabel class="font-normal">
Nothing
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<div>
<h3 class="mb-4 text-lg font-medium">
Email Notifications
</h3>
<div class="space-y-4">
<FormField v-slot="{ handleChange, value }" type="checkbox" name="communication_emails">
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
<div class="space-y-0.5">
<FormLabel class="text-base">
Communication emails
</FormLabel>
<FormDescription>
Receive emails about your account activity.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="marketing_emails">
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
<div class="space-y-0.5">
<FormLabel class="text-base">
Marketing emails
</FormLabel>
<FormDescription>
Receive emails about new products, features, and more.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="social_emails">
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
<div class="space-y-0.5">
<FormLabel class="text-base">
Social emails
</FormLabel>
<FormDescription>
Receive emails for friend requests, follows, and more.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="security_emails">
<FormItem class="flex flex-row items-center justify-between p-4 border rounded-lg">
<div class="space-y-0.5">
<FormLabel class="text-base">
Security emails
</FormLabel>
<FormDescription>
Receive emails about your account activity and security.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
</div>
</div>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="mobile">
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
:model-value="value"
@update:model-value="handleChange"
/>
</FormControl>
<div class="space-y-1 leading-none">
<FormLabel>
Use different settings for my mobile devices
</FormLabel>
<FormDescription>
You can manage your mobile notifications in the
<a href="/examples/forms">
mobile settings
</a> page.
</FormDescription>
</div>
</FormItem>
</FormField>
<div class="flex justify-start">
<Button type="submit">
Update notifications
</Button>
</div>
</form>
</template>

View File

@@ -1,158 +0,0 @@
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { X } from 'lucide-vue-next'
import { FieldArray, useForm } from 'vee-validate'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
import { Textarea } from '@/components/ui/textarea'
import { cn } from '@/lib/utils'
import { profileValidator } from '../validators/profile.validator'
const verifiedEmails = ref(['m@example.com', 'm@google.com', 'm@support.com'])
const profileFormSchema = toTypedSchema(profileValidator)
const { handleSubmit, resetForm } = useForm({
validationSchema: profileFormSchema,
initialValues: {
bio: 'I own a computer.',
urls: [
{ value: 'https://shadcn.com' },
{ value: 'http://twitter.com/shadcn' },
],
},
})
const onSubmit = handleSubmit((values) => {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
})
</script>
<template>
<div>
<h3 class="text-lg font-medium">
Profile
</h3>
<p class="text-sm text-muted-foreground">
This is how others will see you on the site.
</p>
</div>
<Separator orientation="horizontal" class="my-4" />
<form class="space-y-8" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="username">
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input type="text" placeholder="shadcn" v-bind="componentField" />
</FormControl>
<FormDescription>
This is your public display name. It can be your real name or a pseudonym. You can only change this once every 30 days.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<Select v-bind="componentField">
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select an email" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectGroup>
<SelectItem v-for="email in verifiedEmails" :key="email" :value="email">
{{ email }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<FormDescription>
You can manage verified email addresses in your email settings.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="bio">
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea placeholder="Tell us a little bit about yourself" v-bind="componentField" />
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations to link to them.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<div>
<FieldArray v-slot="{ fields, push, remove }" name="urls">
<div v-for="(field, index) in fields" :key="`urls-${field.key}`" class="mb-2">
<FormField v-slot="{ componentField }" :name="`urls[${index}].value`">
<FormItem>
<FormLabel :class="cn(index !== 0 && 'sr-only')">
URLs
</FormLabel>
<FormDescription :class="cn(index !== 0 && 'sr-only')">
Add links to your website, blog, or social media profiles.
</FormDescription>
<div class="relative flex items-center">
<FormControl>
<Input type="url" v-bind="componentField" />
</FormControl>
<button type="button" class="absolute py-2 pe-3 end-0 text-muted-foreground" @click="remove(index)">
<X class="w-3" />
</button>
</div>
<FormMessage />
</FormItem>
</FormField>
</div>
<Button
type="button"
variant="outline"
size="sm"
class="w-20 mt-2 text-xs"
@click="push({ value: '' })"
>
Add URL
</Button>
</FieldArray>
</div>
<div class="flex justify-start gap-2">
<Button type="submit">
Update profile
</Button>
<Button
type="button"
variant="outline"
@click="resetForm"
>
Reset form
</Button>
</div>
</form>
</template>

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
import DisplayForm from './components/display-form.vue'
import SettingsLayout from './components/settings-layout.vue'
</script>
<template>
<SettingsLayout>
<DisplayForm />
</SettingsLayout>
</template>

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
import ProfileForm from './components/profile-form.vue'
import SettingsLayout from './components/settings-layout.vue'
</script>
<template>
<SettingsLayout>
<ProfileForm />
</SettingsLayout>
</template>

View File

@@ -1,10 +0,0 @@
<script setup lang="ts">
import NotificationsForm from './components/notifications-form.vue'
import SettingsLayout from './components/settings-layout.vue'
</script>
<template>
<SettingsLayout>
<NotificationsForm />
</SettingsLayout>
</template>

View File

@@ -1,11 +0,0 @@
import { z } from 'zod'
export const displayValidator = z.object({
items: z
.array(z.string())
.refine(value => value.some(item => item), {
error: 'You have to select at least one item.',
}),
})
export type DisplayValidator = z.infer<typeof displayValidator>

View File

@@ -82,20 +82,6 @@ declare module 'vue-router/auto-routes' {
Record<never, never>,
| never
>,
'/settings/': RouteRecordInfo<
'/settings/',
'/settings',
Record<never, never>,
Record<never, never>,
| never
>,
'/settings/account': RouteRecordInfo<
'/settings/account',
'/settings/account',
Record<never, never>,
Record<never, never>,
| never
>,
'/settings/appearance': RouteRecordInfo<
'/settings/appearance',
'/settings/appearance',
@@ -103,20 +89,6 @@ declare module 'vue-router/auto-routes' {
Record<never, never>,
| never
>,
'/settings/display': RouteRecordInfo<
'/settings/display',
'/settings/display',
Record<never, never>,
Record<never, never>,
| never
>,
'/settings/notifications': RouteRecordInfo<
'/settings/notifications',
'/settings/notifications',
Record<never, never>,
Record<never, never>,
| never
>,
'/tasks/': RouteRecordInfo<
'/tasks/',
'/tasks',
@@ -192,36 +164,12 @@ declare module 'vue-router/auto-routes' {
views:
| never
}
'src/pages/settings/index.vue': {
routes:
| '/settings/'
views:
| never
}
'src/pages/settings/account.vue': {
routes:
| '/settings/account'
views:
| never
}
'src/pages/settings/appearance.vue': {
routes:
| '/settings/appearance'
views:
| never
}
'src/pages/settings/display.vue': {
routes:
| '/settings/display'
views:
| never
}
'src/pages/settings/notifications.vue': {
routes:
| '/settings/notifications'
views:
| never
}
'src/pages/tasks/index.vue': {
routes:
| '/tasks/'