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']
CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.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']
DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
NativeSelect: typeof import('./src/components/ui/native-select/NativeSelect.vue')['default']
NativeSelectOptGroup: typeof import('./src/components/ui/native-select/NativeSelectOptGroup.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']
PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.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 CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.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 DatePicker: typeof import('./src/components/date-picker/DatePicker.vue')['default']
const ElButton: typeof import('element-plus/es')['ElButton']
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 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 PopoverAnchor: typeof import('./src/components/ui/popover/PopoverAnchor.vue')['default']
const PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default']

View File

@@ -26,4 +26,14 @@
## 关于 Shadcn
:::warning 警告
Shadcn 组件的样式将会被 Daisy-ui 组件样式所覆盖。阅读以下文档进行调整。
:::
`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>
<div>
<div class="dropdown">
<div tabindex="0" role="button" class="btn m-1">
主题
<div class="dropdown dropdown-end">
<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
width="12px"
height="12px"
class="inline-block h-2 w-2 fill-current opacity-60"
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"
>
@@ -24,9 +29,14 @@ const { changeGlobalTheme, curGlobalTheme, optionalThemes } =
</div>
<ul
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 class="menu-title text-xs my-1">主题</li>
<li
v-for="optionalTheme in optionalThemes"
:key="optionalTheme"
class="my-1"
>
<li v-for="optionalTheme in optionalThemes" :key="optionalTheme">
<input
type="radio"
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>
import type { CalendarRootEmits, CalendarRootProps, DateValue } from "reka-ui"
import type { HTMLAttributes, Ref } from "vue"
import type { LayoutTypes } from "."
import { getLocalTimeZone, today } from "@internationalized/date"
import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core"
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui"
import { createYear, createYearRange, toDate } from "reka-ui/date"
import { computed, toRaw } from "vue"
import { cn } from "@/lib/utils"
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from "."
import type { CalendarRootEmits, CalendarRootProps, DateValue } from "reka-ui";
import type { HTMLAttributes, Ref } from "vue";
import type { LayoutTypes } from ".";
import { getLocalTimeZone, today } from "@internationalized/date";
import { createReusableTemplate, reactiveOmit, useVModel } from "@vueuse/core";
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from "reka-ui";
import { createYear, createYearRange, toDate } from "reka-ui/date";
import { computed, toRaw } from "vue";
import { cn } from "@/lib/utils";
import {
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(
defineProps<
CalendarRootProps & {
class?: HTMLAttributes["class"];
layout?: LayoutTypes;
yearRange?: DateValue[];
}
>(),
{
modelValue: undefined,
layout: undefined,
})
const emits = defineEmits<CalendarRootEmits>()
},
);
const emits = defineEmits<CalendarRootEmits>();
const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder")
const delegatedProps = reactiveOmit(props, "class", "layout", "placeholder");
const placeholder = useVModel(props, "placeholder", emits, {
passive: true,
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(() => {
return props.yearRange ?? createYearRange({
start: props?.minValue ?? (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone()))
.cycle("year", -100),
return (
props.yearRange ??
createYearRange({
start:
props?.minValue ??
(
toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", -100),
end: props?.maxValue ?? (toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone()))
.cycle("year", 10),
})
end:
props?.maxValue ??
(
toRaw(props.placeholder) ??
props.defaultPlaceholder ??
today(getLocalTimeZone())
).cycle("year", 10),
})
);
});
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{ date: DateValue }>()
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{ date: DateValue }>()
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate<{
date: DateValue;
}>();
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate<{
date: DateValue;
}>();
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DefineMonthTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none">
{{ formatter.custom(toDate(date), { month: 'short' }) }}
<div
class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { month: "short" }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="(e: Event) => {
@change="
(e: Event) => {
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">
{{ formatter.custom(toDate(month), { month: 'short' }) }}
<NativeSelectOption
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>
</NativeSelect>
</div>
@@ -68,19 +118,28 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<DefineYearTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none">
{{ formatter.custom(toDate(date), { year: 'numeric' }) }}
<div
class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none"
>
{{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="(e: Event) => {
@change="
(e: Event) => {
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">
{{ formatter.custom(toDate(year), { year: 'numeric' }) }}
<NativeSelectOption
v-for="year in yearRange"
:key="year.toString()"
:value="year.year"
:selected="date.year === year.year"
>
{{ formatter.custom(toDate(year), { year: "numeric" }) }}
</NativeSelectOption>
</NativeSelect>
</div>
@@ -95,7 +154,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
:class="cn('p-3', props.class)"
>
<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>
<slot name="calendar-prev-icon" />
</CalendarPrevButton>
@@ -104,7 +165,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
</CalendarNextButton>
</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'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
@@ -114,12 +180,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<template v-else-if="layout === 'month-only'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
{{ formatter.custom(toDate(date), { year: 'numeric' }) }}
{{ formatter.custom(toDate(date), { year: "numeric" }) }}
</div>
</template>
<template v-else-if="layout === 'year-only'">
<div class="flex items-center justify-center gap-1">
{{ formatter.custom(toDate(date), { month: 'short' }) }}
{{ formatter.custom(toDate(date), { month: "short" }) }}
<ReuseYearTemplate :date="date" />
</div>
</template>
@@ -133,24 +199,23 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
<CalendarGridHead>
<CalendarGridRow>
<CalendarHeadCell
v-for="day in weekDays" :key="day"
>
<CalendarHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<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
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
/>
<CalendarCellTrigger :day="weekDate" :month="month.value" />
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>

View File

@@ -1,21 +1,28 @@
<script lang="ts" setup>
import type { CalendarCellProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { CalendarCell, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
import type { CalendarCellProps } from "reka-ui";
import type { HTMLAttributes } from "vue";
import { reactiveOmit } from "@vueuse/core";
import { CalendarCell, useForwardProps } from "reka-ui";
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>
<template>
<CalendarCell
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"
>
<slot />

View File

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

View File

@@ -1,26 +1,29 @@
<script setup lang="ts">
import type { AcceptableValue } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit, useVModel } from "@vueuse/core"
import { ChevronDownIcon } from "lucide-vue-next"
import { cn } from "@/lib/utils"
import type { AcceptableValue } from "reka-ui";
import type { HTMLAttributes } from "vue";
import { reactiveOmit, useVModel } from "@vueuse/core";
import { ChevronDownIcon } from "lucide-vue-next";
import { cn } from "@/lib/utils";
defineOptions({
inheritAttrs: false,
})
});
const props = defineProps<{ modelValue?: AcceptableValue | AcceptableValue[], class?: HTMLAttributes["class"] }>()
const props = defineProps<{
modelValue?: AcceptableValue | AcceptableValue[];
class?: HTMLAttributes["class"];
}>();
const emit = defineEmits<{
"update:modelValue": AcceptableValue
}>()
"update:modelValue": AcceptableValue;
}>();
const modelValue = useVModel(props, "modelValue", emit, {
passive: true,
defaultValue: "",
})
});
const delegatedProps = reactiveOmit(props, "class")
const delegatedProps = reactiveOmit(props, "class");
</script>
<template>
@@ -32,12 +35,14 @@ const delegatedProps = reactiveOmit(props, "class")
v-bind="{ ...$attrs, ...delegatedProps }"
v-model="modelValue"
data-slot="native-select"
:class="cn(
:class="
cn(
'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',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class,
)"
)
"
>
<slot />
</select>

View File

@@ -2,14 +2,17 @@
<!-- @strictTemplates true -->
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
import type { HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
const props = defineProps<{ class?: HTMLAttributes["class"] }>();
</script>
<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 />
</option>
</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";
const themeStore = useThemeStore();

View File

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

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);
}
@theme inline {
--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;
}
}
// 关于 shadcn 的主题, 请进入组件内部通过 style 设置 css 变量