feat: home page optimize
This commit is contained in:
4
components.d.ts
vendored
4
components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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 组件。
|
||||
:::
|
||||
|
||||
66
src/components/button/ChangeLanguageDropdownButton.vue
Normal file
66
src/components/button/ChangeLanguageDropdownButton.vue
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
49
src/components/menu/NavBar.vue
Normal file
49
src/components/menu/NavBar.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
27
src/hooks/globalLanguageHook.ts
Normal file
27
src/hooks/globalLanguageHook.ts
Normal 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,
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
// 存在一个重要问题: 不清楚 daisyui 是怎么替换主题的 目前没有做任何配置
|
||||
import { useThemeStore, GlobalTheme } from "@/stores/ThemeStore";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
@@ -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>
|
||||
|
||||
31
src/stores/LanguageStore.ts
Normal file
31
src/stores/LanguageStore.ts
Normal 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,
|
||||
},
|
||||
);
|
||||
116
src/style.css
116
src/style.css
@@ -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 变量
|
||||
|
||||
Reference in New Issue
Block a user