feat: home page optimize

This commit is contained in:
2026-01-31 02:54:00 +08:00
parent 9043814c02
commit 9c82e12144
15 changed files with 412 additions and 249 deletions

4
components.d.ts vendored
View File

@@ -25,12 +25,14 @@ declare module 'vue' {
CalendarHeading: typeof import('./src/components/ui/calendar/CalendarHeading.vue')['default'] CalendarHeading: typeof import('./src/components/ui/calendar/CalendarHeading.vue')['default']
CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.vue')['default'] CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.vue')['default']
CalendarPrevButton: typeof import('./src/components/ui/calendar/CalendarPrevButton.vue')['default'] CalendarPrevButton: typeof import('./src/components/ui/calendar/CalendarPrevButton.vue')['default']
ChangeLanguageDropdownButton: typeof import('./src/components/button/ChangeLanguageDropdownButton.vue')['default']
ChangeThemeDropdownButton: typeof import('./src/components/button/ChangeThemeDropdownButton.vue')['default'] ChangeThemeDropdownButton: typeof import('./src/components/button/ChangeThemeDropdownButton.vue')['default']
DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default'] DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
NativeSelect: typeof import('./src/components/ui/native-select/NativeSelect.vue')['default'] NativeSelect: typeof import('./src/components/ui/native-select/NativeSelect.vue')['default']
NativeSelectOptGroup: typeof import('./src/components/ui/native-select/NativeSelectOptGroup.vue')['default'] NativeSelectOptGroup: typeof import('./src/components/ui/native-select/NativeSelectOptGroup.vue')['default']
NativeSelectOption: typeof import('./src/components/ui/native-select/NativeSelectOption.vue')['default'] NativeSelectOption: typeof import('./src/components/ui/native-select/NativeSelectOption.vue')['default']
NavBar: typeof import('./src/components/menu/NavBar.vue')['default']
Popover: typeof import('./src/components/ui/popover/Popover.vue')['default'] Popover: typeof import('./src/components/ui/popover/Popover.vue')['default']
PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.vue')['default'] PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.vue')['default']
PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default'] PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']
@@ -55,12 +57,14 @@ declare global {
const CalendarHeading: typeof import('./src/components/ui/calendar/CalendarHeading.vue')['default'] const CalendarHeading: typeof import('./src/components/ui/calendar/CalendarHeading.vue')['default']
const CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.vue')['default'] const CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.vue')['default']
const CalendarPrevButton: typeof import('./src/components/ui/calendar/CalendarPrevButton.vue')['default'] const CalendarPrevButton: typeof import('./src/components/ui/calendar/CalendarPrevButton.vue')['default']
const ChangeLanguageDropdownButton: typeof import('./src/components/button/ChangeLanguageDropdownButton.vue')['default']
const ChangeThemeDropdownButton: typeof import('./src/components/button/ChangeThemeDropdownButton.vue')['default'] const ChangeThemeDropdownButton: typeof import('./src/components/button/ChangeThemeDropdownButton.vue')['default']
const DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default'] const DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default']
const ElButton: typeof import('element-plus/es')['ElButton'] const ElButton: typeof import('element-plus/es')['ElButton']
const NativeSelect: typeof import('./src/components/ui/native-select/NativeSelect.vue')['default'] const NativeSelect: typeof import('./src/components/ui/native-select/NativeSelect.vue')['default']
const NativeSelectOptGroup: typeof import('./src/components/ui/native-select/NativeSelectOptGroup.vue')['default'] const NativeSelectOptGroup: typeof import('./src/components/ui/native-select/NativeSelectOptGroup.vue')['default']
const NativeSelectOption: typeof import('./src/components/ui/native-select/NativeSelectOption.vue')['default'] const NativeSelectOption: typeof import('./src/components/ui/native-select/NativeSelectOption.vue')['default']
const NavBar: typeof import('./src/components/menu/NavBar.vue')['default']
const Popover: typeof import('./src/components/ui/popover/Popover.vue')['default'] const Popover: typeof import('./src/components/ui/popover/Popover.vue')['default']
const PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.vue')['default'] const PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.vue')['default']
const PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default'] const PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']

View File

@@ -26,4 +26,14 @@
## 关于 Shadcn ## 关于 Shadcn
:::warning 警告
Shadcn 组件的样式将会被 Daisy-ui 组件样式所覆盖。阅读以下文档进行调整。
:::
`components/ui` 目录下是 shadcn-vue 提供的组件代码,您可以在项目中直接调用这些组件,同时也可以根据需要进行调整。 `components/ui` 目录下是 shadcn-vue 提供的组件代码,您可以在项目中直接调用这些组件,同时也可以根据需要进行调整。
由于 daisy-ui 和 shadcn 同时使用类似 --color-primary 的 css 变量来调整主题样式Hucky 删除了 shadcn 在全局样式上做的调整,如果您需要调整 shadcn 中的组件样式,请通过在组件内部的 style 标签中设置 css 变量来实现。
:::tip 提示
尽管 shadcn 组件的样式确实可以调整,但仅建议对 css 掌握度较高的开发者调整。如果您不熟悉 css 及 css 变量,仍然建议您接受 shadcn 的组件使用 daisy-ui 提供的默认样式,或者不使用 shadcn 组件。
:::

View File

@@ -0,0 +1,66 @@
<script setup lang="ts">
import { useGlobalLanguageHook } from "@/hooks/globalLanguageHook";
const { changeGlobalLanguage, curGlobalLanguage, optionalLanguages } =
useGlobalLanguageHook();
</script>
<template>
<div class="dropdown dropdown-end">
<div
tabindex="0"
role="button"
class="btn btn-sm btn-ghost gap-1 px-1.5 font-bold"
aria-label="Language"
title="Change Language"
>
<svg
class="text-base-content/70 size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="2"
fill="none"
stroke="currentColor"
d="M12 21a9 9 0 1 0 0-18m0 18a9 9 0 1 1 0-18m0 18c2.761 0 3.941-5.163 3.941-9S14.761 3 12 3m0 18c-2.761 0-3.941-5.163-3.941-9S9.239 3 12 3M3.5 9h17m-17 6h17"
/>
</svg>
<svg
class="mt-px hidden size-2 fill-current opacity-60 sm:inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2048 2048"
>
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
</svg>
</div>
<div
tabindex="0"
class="dropdown-content bg-base-200 text-base-content rounded-box top-px mt-14 w-56 overflow-y-auto border-[length:var(--border)] border-white/5 shadow-2xl outline-[length:var(--border)] outline-black/5"
>
<ul class="menu menu-sm w-full">
<li class="menu-title text-xs">语言</li>
<li
v-for="optionalLanguage in optionalLanguages"
:key="optionalLanguage.label"
>
<button
:class="[
curGlobalLanguage === optionalLanguage.label ? 'menu-active' : '',
]"
@click="changeGlobalLanguage(optionalLanguage.label)"
>
<span class="pe-4 font-mono font-bold opacity-40">
{{ optionalLanguage.label }}
</span>
<span class="font-[sans-serif]">{{ optionalLanguage.value }}</span>
</button>
</li>
</ul>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -7,13 +7,18 @@ const { changeGlobalTheme, curGlobalTheme, optionalThemes } =
<template> <template>
<div> <div>
<div class="dropdown"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn m-1"> <div tabindex="0" role="button" class="btn btn-sm btn-ghost px-1.5">
主题 <div
class="bg-base-100 group-hover:border-base-content/20 border-base-content/10 grid shrink-0 grid-cols-2 gap-0.5 rounded-md border p-1 transition-colors"
>
<div class="bg-base-content size-1 rounded-full" />
<div class="bg-primary size-1 rounded-full" />
<div class="bg-secondary size-1 rounded-full" />
<div class="bg-accent size-1 rounded-full" />
</div>
<svg <svg
width="12px" class="mt-px hidden size-2 fill-current opacity-60 sm:inline-block"
height="12px"
class="inline-block h-2 w-2 fill-current opacity-60"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2048 2048" viewBox="0 0 2048 2048"
> >
@@ -24,9 +29,14 @@ const { changeGlobalTheme, curGlobalTheme, optionalThemes } =
</div> </div>
<ul <ul
tabindex="-1" tabindex="-1"
class="dropdown-content bg-base-300 rounded-box z-1 w-52 p-2 shadow-2xl max-h-84 overflow-auto" class="dropdown-content bg-base-300 rounded-box z-1 w-52 p-2 shadow-2xl max-h-84 overflow-auto mt-6"
> >
<li v-for="optionalTheme in optionalThemes" :key="optionalTheme"> <li class="menu-title text-xs my-1">主题</li>
<li
v-for="optionalTheme in optionalThemes"
:key="optionalTheme"
class="my-1"
>
<input <input
type="radio" type="radio"
name="theme-dropdown" name="theme-dropdown"

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import ChangeLanguageDropdownButton from "@/components/button/ChangeLanguageDropdownButton.vue";
import ChangeThemeDropdownButton from "@/components/button/ChangeThemeDropdownButton.vue";
</script>
<template>
<div class="navbar bg-base-200 shadow-sm">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabindex="-1"
class="menu dropdown-content bg-base-100 rounded-box z-1 mt-5 w-52 p-2 shadow"
>
<li><a>首页</a></li>
<li><a>关于</a></li>
</ul>
</div>
<a class="btn btn-ghost text-xl">daisyUI</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><a>首页</a></li>
<li><a>关于</a></li>
</ul>
</div>
<div class="navbar-end">
<ChangeThemeDropdownButton />
<ChangeLanguageDropdownButton />
</div>
</div>
</template>
<style scoped></style>

View File

@@ -1,64 +1,114 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CalendarRootEmits, CalendarRootProps, DateValue } from "reka-ui" import type { CalendarRootEmits, CalendarRootProps, DateValue } from "reka-ui";
import type { HTMLAttributes, Ref } from "vue" import type { HTMLAttributes, Ref } from "vue";
import type { LayoutTypes } from "." import type { LayoutTypes } from ".";
import { getLocalTimeZone, today } from "@internationalized/date" import { getLocalTimeZone, today } from "@internationalized/date";
import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core" import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core";
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui" import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui";
import { createYear, createYearRange, toDate } from "reka-ui/date" import { createYear, createYearRange, toDate } from "reka-ui/date";
import { computed, toRaw } from "vue" import { computed, toRaw } from "vue";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select' import {
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from "." NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import {
CalendarCell,
CalendarCellTrigger,
CalendarGrid,
CalendarGridBody,
CalendarGridHead,
CalendarGridRow,
CalendarHeadCell,
CalendarHeader,
CalendarHeading,
CalendarNextButton,
CalendarPrevButton,
} from ".";
const props = withDefaults(defineProps<CalendarRootProps & { class?: HTMLAttributes["class"], layout?: LayoutTypes, yearRange?: DateValue[] }>(), { const props = withDefaults(
modelValue: undefined, defineProps<
layout: undefined, CalendarRootProps & {
}) class?: HTMLAttributes["class"];
const emits = defineEmits<CalendarRootEmits>() layout?: LayoutTypes;
yearRange?: DateValue[];
}
>(),
{
modelValue: undefined,
layout: undefined,
},
);
const emits = defineEmits<CalendarRootEmits>();
const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder") const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder");
const placeholder = useVModel(props, "placeholder", emits, { const placeholder = useVModel(props, "placeholder", emits, {
passive: true, passive: true,
defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()), defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone()),
}) as Ref<DateValue> }) as Ref<DateValue>;
const formatter = useDateFormatter(props.locale ?? "en") const formatter = useDateFormatter(props.locale ?? "en");
const yearRange = computed(() => { const yearRange = computed(() => {
return props.yearRange ?? createYearRange({ return (
start: props?.minValue ?? (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone())) props.yearRange ??
.cycle("year", -100), createYearRange({
start:
props?.minValue ??
(
toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", -100),
end: props?.maxValue ?? (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone())) end:
.cycle("year", 10), props?.maxValue ??
}) (
}) toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", 10),
})
);
});
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{ date: DateValue }>() const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{ date: DateValue }>() date: DateValue;
}>();
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{
date: DateValue;
}>();
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script> </script>
<template> <template>
<DefineMonthTemplate v-slot="{ date }"> <DefineMonthTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1"> <div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative"> <div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"> <div
{{ formatter.custom(toDate(date), { month: 'short' }) }} class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { month: "short" }) }}
</div> </div>
<NativeSelect <NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative" class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="(e: Event) => { @change="
placeholder = placeholder.set({ (e: Event) => {
month: Number((e?.target as any)?.value), placeholder = placeholder.set({
}) month: Number((e?.target as any)?.value),
}" });
}
"
> >
<NativeSelectOption v-for="(month) in createYear({ dateObj: date })" :key="month.toString()" :value="month.month" :selected="date.month === month.month"> <NativeSelectOption
{{ formatter.custom(toDate(month), { month: 'short' }) }} v-for="month in createYear({ dateObj: date })"
:key="month.toString()"
:value="month.month"
:selected="date.month === month.month"
>
{{ formatter.custom(toDate(month), { month: "short" }) }}
</NativeSelectOption> </NativeSelectOption>
</NativeSelect> </NativeSelect>
</div> </div>
@@ -68,19 +118,28 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<DefineYearTemplate v-slot="{ date }"> <DefineYearTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1"> <div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative"> <div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"> <div
{{ formatter.custom(toDate(date), { year: 'numeric' }) }} class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div> </div>
<NativeSelect <NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative" class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="(e: Event) => { @change="
placeholder = placeholder.set({ (e: Event) => {
year: Number((e?.target as any)?.value), placeholder = placeholder.set({
}) year: Number((e?.target as any)?.value),
}" });
}
"
> >
<NativeSelectOption v-for="(year) in yearRange" :key="year.toString()" :value="year.year" :selected="date.year === year.year"> <NativeSelectOption
{{ formatter.custom(toDate(year), { year: 'numeric' }) }} v-for="year in yearRange"
:key="year.toString()"
:value="year.year"
:selected="date.year === year.year"
>
{{ formatter.custom(toDate(year), { year: "numeric" }) }}
</NativeSelectOption> </NativeSelectOption>
</NativeSelect> </NativeSelect>
</div> </div>
@@ -95,7 +154,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
:class="cn('p-3', props.class)" :class="cn('p-3', props.class)"
> >
<CalendarHeader class="pt-0"> <CalendarHeader class="pt-0">
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between"> <nav
class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between"
>
<CalendarPrevButton> <CalendarPrevButton>
<slot name="calendar-prev-icon" /> <slot name="calendar-prev-icon" />
</CalendarPrevButton> </CalendarPrevButton>
@@ -104,7 +165,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
</CalendarNextButton> </CalendarNextButton>
</nav> </nav>
<slot name="calendar-heading" :date="date" :month="ReuseMonthTemplate" :year="ReuseYearTemplate"> <slot
name="calendar-heading"
:date="date"
:month="ReuseMonthTemplate"
:year="ReuseYearTemplate"
>
<template v-if="layout === 'month-and-year'"> <template v-if="layout === 'month-and-year'">
<div class="flex items-center justify-center gap-1"> <div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" /> <ReuseMonthTemplate :date="date" />
@@ -114,12 +180,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<template v-else-if="layout === 'month-only'"> <template v-else-if="layout === 'month-only'">
<div class="flex items-center justify-center gap-1"> <div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" /> <ReuseMonthTemplate :date="date" />
{{ formatter.custom(toDate(date), { year: 'numeric' }) }} {{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div> </div>
</template> </template>
<template v-else-if="layout === 'year-only'"> <template v-else-if="layout === 'year-only'">
<div class="flex items-center justify-center gap-1"> <div class="flex items-center justify-center gap-1">
{{ formatter.custom(toDate(date), { month: 'short' }) }} {{ formatter.custom(toDate(date), { month: "short" }) }}
<ReuseYearTemplate :date="date" /> <ReuseYearTemplate :date="date" />
</div> </div>
</template> </template>
@@ -133,24 +199,23 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<CalendarGrid v-for="month in grid" :key="month.value.toString()"> <CalendarGrid v-for="month in grid" :key="month.value.toString()">
<CalendarGridHead> <CalendarGridHead>
<CalendarGridRow> <CalendarGridRow>
<CalendarHeadCell <CalendarHeadCell v-for="day in weekDays" :key="day">
v-for="day in weekDays" :key="day"
>
{{ day }} {{ day }}
</CalendarHeadCell> </CalendarHeadCell>
</CalendarGridRow> </CalendarGridRow>
</CalendarGridHead> </CalendarGridHead>
<CalendarGridBody> <CalendarGridBody>
<CalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full"> <CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<CalendarCell <CalendarCell
v-for="weekDate in weekDates" v-for="weekDate in weekDates"
:key="weekDate.toString()" :key="weekDate.toString()"
:date="weekDate" :date="weekDate"
> >
<CalendarCellTrigger <CalendarCellTrigger :day="weekDate" :month="month.value" />
:day="weekDate"
:month="month.value"
/>
</CalendarCell> </CalendarCell>
</CalendarGridRow> </CalendarGridRow>
</CalendarGridBody> </CalendarGridBody>

View File

@@ -1,21 +1,28 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CalendarCellProps } from "reka-ui" import type { CalendarCellProps } from "reka-ui";
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue";
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from "@vueuse/core";
import { CalendarCell, useForwardProps } from "reka-ui" import { CalendarCell, useForwardProps } from "reka-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<
CalendarCellProps & { class?: HTMLAttributes["class"] }
>();
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, "class");
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>
<CalendarCell <CalendarCell
data-slot="calendar-cell" data-slot="calendar-cell"
:class="cn('relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent', props.class)" :class="
cn(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent',
props.class,
)
"
v-bind="forwardedProps" v-bind="forwardedProps"
> >
<slot /> <slot />

View File

@@ -1,37 +1,42 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CalendarCellTriggerProps } from "reka-ui" import type { CalendarCellTriggerProps } from "reka-ui";
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue";
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from "@vueuse/core";
import { CalendarCellTrigger, useForwardProps } from "reka-ui" import { CalendarCellTrigger, useForwardProps } from "reka-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
const props = withDefaults(defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes["class"] }>(), { const props = withDefaults(
as: "button", defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes["class"] }>(),
}) {
as: "button",
},
);
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, "class");
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps);
</script> </script>
<template> <template>
<CalendarCellTrigger <CalendarCellTrigger
data-slot="calendar-cell-trigger" data-slot="calendar-cell-trigger"
:class="cn( :class="
buttonVariants({ variant: 'ghost' }), cn(
'size-8 p-0 font-normal aria-selected:opacity-100 cursor-default', buttonVariants({ variant: 'ghost' }),
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground', 'size-8 p-0 font-normal aria-selected:opacity-100 cursor-default',
// Selected '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-content',
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground', // Selected
// Disabled 'data-[selected]:bg-primary data-[selected]:text-primary-content data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-content data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-content',
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50', // Disabled
// Unavailable 'data-[disabled]:text-muted-content data-[disabled]:opacity-50',
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through', // Unavailable
// Outside months 'data-[unavailable]:text-destructive-content data-[unavailable]:line-through',
'data-[outside-view]:text-muted-foreground', // Outside months
props.class, 'data-[outside-view]:text-muted-content',
)" props.class,
)
"
v-bind="forwardedProps" v-bind="forwardedProps"
> >
<slot /> <slot />

View File

@@ -1,26 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AcceptableValue } from "reka-ui" import type { AcceptableValue } from "reka-ui";
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue";
import { reactiveOmit, useVModel } from "@vueuse/core" import { reactiveOmit, useVModel } from "@vueuse/core";
import { ChevronDownIcon } from "lucide-vue-next" import { ChevronDownIcon } from "lucide-vue-next";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) });
const props = defineProps<{ modelValue?: AcceptableValue | AcceptableValue[], class?: HTMLAttributes["class"] }>() const props = defineProps<{
modelValue?: AcceptableValue | AcceptableValue[];
class?: HTMLAttributes["class"];
}>();
const emit = defineEmits<{ const emit = defineEmits<{
"update:modelValue": AcceptableValue "update:modelValue": AcceptableValue;
}>() }>();
const modelValue = useVModel(props, "modelValue", emit, { const modelValue = useVModel(props, "modelValue", emit, {
passive: true, passive: true,
defaultValue: "", defaultValue: "",
}) });
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, "class");
</script> </script>
<template> <template>
@@ -32,12 +35,14 @@ const delegatedProps = reactiveOmit(props, "class")
v-bind="{ ...$attrs, ...delegatedProps }" v-bind="{ ...$attrs, ...delegatedProps }"
v-model="modelValue" v-model="modelValue"
data-slot="native-select" data-slot="native-select"
:class="cn( :class="
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed', cn(
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', 'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive', 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
props.class, 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
)" props.class,
)
"
> >
<slot /> <slot />
</select> </select>

View File

@@ -2,14 +2,17 @@
<!-- @strictTemplates true --> <!-- @strictTemplates true -->
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const props = defineProps<{ class?: HTMLAttributes["class"] }>() const props = defineProps<{ class?: HTMLAttributes["class"] }>();
</script> </script>
<template> <template>
<option data-slot="native-select-option" :class="cn('bg-popover text-popover-foreground', props.class)"> <option
data-slot="native-select-option"
:class="cn('bg-popover text-popover-foreground text-black', props.class)"
>
<slot /> <slot />
</option> </option>
</template> </template>

View File

@@ -0,0 +1,27 @@
import {
useLanguageStore,
GlobalLanguage,
languageMap,
} from "@/stores/LanguageStore";
const languageStore = useLanguageStore();
export const optionalLanguages = Object.entries(languageMap).map(
([key, value]) => ({
label: key as GlobalLanguage,
value,
}),
);
// 一定不要通过改变 curGlobalLanguage 来改变语言,而要通过 changeGlobalLanguage 来改变语言
const curGlobalLanguage = computed(() => languageStore.language);
function changeGlobalLanguage(language: GlobalLanguage) {
languageStore.setLanguage(language);
}
export const useGlobalLanguageHook = () => ({
optionalLanguages,
curGlobalLanguage,
changeGlobalLanguage,
});

View File

@@ -1,3 +1,4 @@
// 存在一个重要问题: 不清楚 daisyui 是怎么替换主题的 目前没有做任何配置
import { useThemeStore, GlobalTheme } from "@/stores/ThemeStore"; import { useThemeStore, GlobalTheme } from "@/stores/ThemeStore";
const themeStore = useThemeStore(); const themeStore = useThemeStore();

View File

@@ -1,20 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import ChangeThemeDropdownButton from "@/components/button/ChangeThemeDropdownButton.vue";
import DatePicker from "@/components/date-picker/DatePicker.vue"; import DatePicker from "@/components/date-picker/DatePicker.vue";
import { useGlobalThemeHook } from "@/hooks/globalThemeHook"; import NavBar from "@/components/menu/NavBar.vue";
const { curGlobalTheme } = useGlobalThemeHook();
</script> </script>
<template> <template>
<div class="hero min-h-screen" :data-theme="curGlobalTheme"> <div>
<div class="hero-content text-center"> <NavBar />
<div class="max-w-md"> <div class="mt-4">
<h1 class="text-5xl font-bold pb-6">Hucky</h1> <div>路由系统加载进度100%</div>
<p class="pb-6">自动路由系统已加载</p> <DatePicker />
<ChangeThemeDropdownButton class="pb-6" />
<DatePicker />
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,31 @@
import { ref } from "vue";
import { defineStore } from "pinia";
export enum GlobalLanguage {
ZH = "ZH",
EN = "EN",
}
export const languageMap = {
[GlobalLanguage.ZH]: "简体中文",
[GlobalLanguage.EN]: "English",
};
export const useLanguageStore = defineStore(
"language",
() => {
const language = ref<GlobalLanguage>(GlobalLanguage.ZH);
const setLanguage = (newLanguage: GlobalLanguage) => {
language.value = newLanguage;
};
return {
language,
setLanguage,
};
},
{
persist: true,
},
);

View File

@@ -33,118 +33,4 @@
scrollbar-color: rgba(0, 0, 0, 0.2) rgba(0, 0, 0, 0.05); scrollbar-color: rgba(0, 0, 0, 0.2) rgba(0, 0, 0, 0.05);
} }
@theme inline { // 关于 shadcn 的主题, 请进入组件内部通过 style 设置 css 变量
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}