feat: 添加 InspiraUI 组件和模式背景功能,更新依赖项
This commit is contained in:
@ -1 +1,2 @@
|
|||||||
/src/shadcn
|
/src/shadcn
|
||||||
|
/src/components/InspiraUI/
|
@ -47,6 +47,7 @@
|
|||||||
"@intlify/unplugin-vue-i18n": "^6.0.5",
|
"@intlify/unplugin-vue-i18n": "^6.0.5",
|
||||||
"@pinia/colada": "^0.14.2",
|
"@pinia/colada": "^0.14.2",
|
||||||
"@primeuix/themes": "^1.0.0",
|
"@primeuix/themes": "^1.0.0",
|
||||||
|
"@splinetool/runtime": "^1.9.82",
|
||||||
"@unhead/vue": "^2.0.2",
|
"@unhead/vue": "^2.0.2",
|
||||||
"@vant/use": "^1.6.0",
|
"@vant/use": "^1.6.0",
|
||||||
"@vueuse/core": "^13.0.0",
|
"@vueuse/core": "^13.0.0",
|
||||||
|
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -28,6 +28,9 @@ importers:
|
|||||||
'@primeuix/themes':
|
'@primeuix/themes':
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
'@splinetool/runtime':
|
||||||
|
specifier: ^1.9.82
|
||||||
|
version: 1.9.82
|
||||||
'@unhead/vue':
|
'@unhead/vue':
|
||||||
specifier: ^2.0.2
|
specifier: ^2.0.2
|
||||||
version: 2.0.2(vue@3.5.13(typescript@5.8.2))
|
version: 2.0.2(vue@3.5.13(typescript@5.8.2))
|
||||||
@ -1346,6 +1349,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@splinetool/runtime@1.9.82':
|
||||||
|
resolution: {integrity: sha512-qfNTnezSGuh7nPUPgRVfpH+g2FYywvYmHzOLmp8dahMxuY7HshOwLEhKVapaaWxIpq4h/WweU83pBWb48bwA8A==}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||||
|
|
||||||
@ -3511,6 +3517,10 @@ packages:
|
|||||||
ohash@2.0.11:
|
ohash@2.0.11:
|
||||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||||
|
|
||||||
|
on-change@4.0.2:
|
||||||
|
resolution: {integrity: sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
onetime@6.0.0:
|
onetime@6.0.0:
|
||||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -5707,6 +5717,11 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@sindresorhus/merge-streams@4.0.0': {}
|
||||||
|
|
||||||
|
'@splinetool/runtime@1.9.82':
|
||||||
|
dependencies:
|
||||||
|
on-change: 4.0.2
|
||||||
|
semver-compare: 1.0.0
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@ -8149,6 +8164,8 @@ snapshots:
|
|||||||
|
|
||||||
ohash@2.0.11: {}
|
ohash@2.0.11: {}
|
||||||
|
|
||||||
|
on-change@4.0.2: {}
|
||||||
|
|
||||||
onetime@6.0.0:
|
onetime@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn: 4.0.0
|
mimic-fn: 4.0.0
|
||||||
|
1323
src/components/InspiraUI/FluidCursor.vue
Normal file
1323
src/components/InspiraUI/FluidCursor.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
cn(patternBackgroundVariants({ variant, size }), ` ${animate ? 'move move-' + direction : ''} `, props.class)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'absolute pointer-events-none inset-0 flex items-center justify-center',
|
||||||
|
patternBackgroundMaskVariants({ mask }),
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/shadcn/lib/utils';
|
||||||
|
import type { BaseProps as Props } from '.';
|
||||||
|
import {
|
||||||
|
PATTERN_BACKGROUND_DIRECTION,
|
||||||
|
PATTERN_BACKGROUND_SPEED,
|
||||||
|
PATTERN_BACKGROUND_VARIANT,
|
||||||
|
patternBackgroundMaskVariants,
|
||||||
|
patternBackgroundVariants,
|
||||||
|
} from '.';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
direction: () => PATTERN_BACKGROUND_DIRECTION.Top,
|
||||||
|
variant: () => PATTERN_BACKGROUND_VARIANT.Grid,
|
||||||
|
speed: () => PATTERN_BACKGROUND_SPEED.Default,
|
||||||
|
size: undefined,
|
||||||
|
mask: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const durationFormSpeed = computed(() => `${props.speed}ms`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes to-top {
|
||||||
|
0% {
|
||||||
|
background-position: 0 100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-bottom {
|
||||||
|
0% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-right {
|
||||||
|
0% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-left {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-top-right {
|
||||||
|
0% {
|
||||||
|
background-position: 0 100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-top-left {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-bottom-right {
|
||||||
|
0% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes to-bottom-left {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.move {
|
||||||
|
animation-duration: v-bind(durationFormSpeed);
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-top {
|
||||||
|
animation-name: to-top;
|
||||||
|
}
|
||||||
|
.move-bottom {
|
||||||
|
animation-name: to-bottom;
|
||||||
|
}
|
||||||
|
.move-right {
|
||||||
|
animation-name: to-right;
|
||||||
|
}
|
||||||
|
.move-left {
|
||||||
|
animation-name: to-left;
|
||||||
|
}
|
||||||
|
.move-top-right {
|
||||||
|
animation-name: to-top-right;
|
||||||
|
}
|
||||||
|
.move-top-left {
|
||||||
|
animation-name: to-top-left;
|
||||||
|
}
|
||||||
|
.move-bottom-right {
|
||||||
|
animation-name: to-bottom-right;
|
||||||
|
}
|
||||||
|
.move-bottom-left {
|
||||||
|
animation-name: to-bottom-left;
|
||||||
|
}
|
||||||
|
</style>
|
87
src/components/InspiraUI/pattern-background/index.ts
Normal file
87
src/components/InspiraUI/pattern-background/index.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
|
||||||
|
type ObjectValues<T> = T[keyof T];
|
||||||
|
|
||||||
|
export const PATTERN_BACKGROUND_DIRECTION = {
|
||||||
|
Top: "top",
|
||||||
|
Bottom: "bottom",
|
||||||
|
Left: "left",
|
||||||
|
Right: "right",
|
||||||
|
TopLeft: "top-left",
|
||||||
|
TopRight: "top-right",
|
||||||
|
BottomLeft: "bottom-left",
|
||||||
|
BottomRight: "bottom-right",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PatternBackgroundDirection = ObjectValues<typeof PATTERN_BACKGROUND_DIRECTION>;
|
||||||
|
|
||||||
|
export interface BaseProps {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
animate?: boolean;
|
||||||
|
direction?: PatternBackgroundDirection;
|
||||||
|
variant?: PatternBackgroundVariants["variant"];
|
||||||
|
size?: PatternBackgroundVariants["size"];
|
||||||
|
mask?: PatternBackgroundMaskVariants["mask"];
|
||||||
|
speed?: ObjectValues<typeof PATTERN_BACKGROUND_SPEED>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PATTERN_BACKGROUND_VARIANT = {
|
||||||
|
Grid: "grid",
|
||||||
|
Dot: "dot",
|
||||||
|
BigDot: "big-dot",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const PATTERN_BACKGROUND_SPEED = {
|
||||||
|
Default: 10000,
|
||||||
|
Slow: 25000,
|
||||||
|
Fast: 5000,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const patternBackgroundVariants = cva("relative text-clip", {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
[PATTERN_BACKGROUND_VARIANT.Grid]:
|
||||||
|
"bg-[linear-gradient(to_right,hsl(var(--foreground)/0.3)_1px,transparent_1px),linear-gradient(to_bottom,hsl(var(--foreground)/0.3)_1px,transparent_1px)]",
|
||||||
|
[PATTERN_BACKGROUND_VARIANT.Dot]:
|
||||||
|
"bg-[radial-gradient(hsl(var(--foreground)/0.3)_1px,transparent_1px)]",
|
||||||
|
[PATTERN_BACKGROUND_VARIANT.BigDot]:
|
||||||
|
"bg-[radial-gradient(hsl(var(--foreground)/0.3)_3px,transparent_3px)]",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
xs: "bg-[size:8px_8px]",
|
||||||
|
sm: "bg-[size:16px_16px]",
|
||||||
|
md: "bg-[size:24px_24px]",
|
||||||
|
lg: "bg-[size:32px_32px]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "grid",
|
||||||
|
size: "md",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PatternBackgroundVariants = VariantProps<typeof patternBackgroundVariants>;
|
||||||
|
|
||||||
|
export const PATTERN_BACKGROUND_MASK = {
|
||||||
|
Ellipse: "ellipse",
|
||||||
|
EllipseTop: "ellipse-top",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const patternBackgroundMaskVariants = cva("bg-background", {
|
||||||
|
variants: {
|
||||||
|
mask: {
|
||||||
|
[PATTERN_BACKGROUND_MASK.Ellipse]:
|
||||||
|
"[mask-image:radial-gradient(ellipse_at_center,transparent,black_80%)]",
|
||||||
|
[PATTERN_BACKGROUND_MASK.EllipseTop]:
|
||||||
|
"[mask-image:radial-gradient(ellipse_at_top,transparent,black_80%)]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
mask: "ellipse",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PatternBackgroundMaskVariants = VariantProps<typeof patternBackgroundMaskVariants>;
|
||||||
|
|
||||||
|
export { default as PatternBackground } from "./PatternBackground.vue";
|
80
src/components/InspiraUI/spline/ParentSize.vue
Normal file
80
src/components/InspiraUI/spline/ParentSize.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!-- ParentSize.vue -->
|
||||||
|
<template>
|
||||||
|
<div ref="target" :style="mergedStyles" :class="cn('w-full h-full', props.class)" v-bind="attrsWithoutClassAndStyle">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, useAttrs } from 'vue';
|
||||||
|
import { useDebounceFn, useResizeObserver } from '@vueuse/core';
|
||||||
|
import { cn } from '@/shadcn/lib/utils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
class: String,
|
||||||
|
debounceTime: {
|
||||||
|
type: Number,
|
||||||
|
default: 300,
|
||||||
|
},
|
||||||
|
ignoreDimensions: {
|
||||||
|
type: [Array, String],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
parentSizeStyles: Object,
|
||||||
|
enableDebounceLeadingCall: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const target = ref<HTMLElement | null>(null);
|
||||||
|
const state = reactive({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergedStyles = computed(() => ({
|
||||||
|
...props.parentSizeStyles,
|
||||||
|
...(attrs.style as object),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mergedClass = computed(() => ['w-full h-full', props.class]);
|
||||||
|
|
||||||
|
const attrsWithoutClassAndStyle = computed(() => {
|
||||||
|
const { class: _, style: __, ...rest } = attrs;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedIgnore = computed(() =>
|
||||||
|
Array.isArray(props.ignoreDimensions) ? props.ignoreDimensions : [props.ignoreDimensions],
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateDimensions(rect: DOMRectReadOnly) {
|
||||||
|
const { width, height, top, left } = rect;
|
||||||
|
const newState = { width, height, top, left };
|
||||||
|
|
||||||
|
const hasChange = Object.keys(newState).some(
|
||||||
|
(key) => state[key as keyof typeof state] !== newState[key as keyof typeof state],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasChange) return;
|
||||||
|
|
||||||
|
const shouldUpdate = !Object.keys(newState).every((key) =>
|
||||||
|
normalizedIgnore.value.includes(key as keyof typeof state),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
Object.assign(state, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedUpdate = useDebounceFn(updateDimensions, props.debounceTime);
|
||||||
|
|
||||||
|
useResizeObserver(target, (entries) => {
|
||||||
|
const entry = entries[0];
|
||||||
|
if (entry) debouncedUpdate(entry.contentRect);
|
||||||
|
});
|
||||||
|
</script>
|
112
src/components/InspiraUI/spline/Spline.vue
Normal file
112
src/components/InspiraUI/spline/Spline.vue
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<!-- Spline.vue -->
|
||||||
|
<template>
|
||||||
|
<ParentSize
|
||||||
|
:parent-size-styles="parentSizeStyles"
|
||||||
|
:debounce-time="50"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<canvas
|
||||||
|
ref="canvasRef"
|
||||||
|
:style="canvasStyle"
|
||||||
|
/>
|
||||||
|
<slot v-if="isLoading" />
|
||||||
|
</template>
|
||||||
|
</ParentSize>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
|
import { Application, type SplineEventName } from "@splinetool/runtime";
|
||||||
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
|
import ParentSize from "./ParentSize.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
scene: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
onLoad: Function,
|
||||||
|
renderOnDemand: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
style: Object,
|
||||||
|
});
|
||||||
|
|
||||||
|
let cleanUpFns: any[] = [];
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
"error",
|
||||||
|
...[
|
||||||
|
"spline-mouse-down",
|
||||||
|
"spline-mouse-up",
|
||||||
|
"spline-mouse-hover",
|
||||||
|
"spline-key-down",
|
||||||
|
"spline-key-up",
|
||||||
|
"spline-start",
|
||||||
|
"spline-look-at",
|
||||||
|
"spline-follow",
|
||||||
|
"spline-scroll",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||||
|
const isLoading = ref(true);
|
||||||
|
const splineApp = ref<Application | null>(null);
|
||||||
|
|
||||||
|
const parentSizeStyles = computed(() => ({
|
||||||
|
overflow: "hidden",
|
||||||
|
...props.style,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const canvasStyle = computed(() => ({
|
||||||
|
display: isLoading.value ? "none" : "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}));
|
||||||
|
|
||||||
|
function eventHandler(name: SplineEventName, handler?: (e: any) => void) {
|
||||||
|
if (!handler || !splineApp.value) return;
|
||||||
|
const debouncedHandler = useDebounceFn(handler, 50, {
|
||||||
|
maxWait: 100,
|
||||||
|
});
|
||||||
|
splineApp.value.addEventListener(name, debouncedHandler);
|
||||||
|
return () => splineApp.value?.removeEventListener(name, debouncedHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!canvasRef.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
splineApp.value = new Application(canvasRef.value, {
|
||||||
|
renderOnDemand: props.renderOnDemand,
|
||||||
|
});
|
||||||
|
|
||||||
|
await splineApp.value.load(props.scene);
|
||||||
|
|
||||||
|
cleanUpFns = [
|
||||||
|
eventHandler("mouseDown", (e: any) => emit("spline-mouse-down", e)),
|
||||||
|
eventHandler("mouseUp", (e: any) => emit("spline-mouse-up", e)),
|
||||||
|
eventHandler("mouseHover", (e: any) => emit("spline-mouse-hover", e)),
|
||||||
|
eventHandler("keyDown", (e: any) => emit("spline-key-down", e)),
|
||||||
|
eventHandler("keyUp", (e: any) => emit("spline-key-up", e)),
|
||||||
|
eventHandler("start", (e: any) => emit("spline-start", e)),
|
||||||
|
eventHandler("lookAt", (e: any) => emit("spline-look-at", e)),
|
||||||
|
eventHandler("follow", (e: any) => emit("spline-follow", e)),
|
||||||
|
eventHandler("scroll", (e: any) => emit("spline-scroll", e)),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
isLoading.value = false;
|
||||||
|
props.onLoad?.(splineApp.value);
|
||||||
|
} catch (err) {
|
||||||
|
emit("error", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cleanUpFns.forEach((fn) => fn?.());
|
||||||
|
splineApp.value?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
2
src/components/InspiraUI/spline/index.ts
Normal file
2
src/components/InspiraUI/spline/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Spline } from "./Spline.vue";
|
||||||
|
export { default as ParentSize } from "./ParentSize.vue";
|
@ -1,4 +1,4 @@
|
|||||||
import { computed, reactive, watch } from 'vue';
|
import { computed, reactive, watch, type WatchHandle } from 'vue';
|
||||||
|
|
||||||
const layoutConfig = reactive({
|
const layoutConfig = reactive({
|
||||||
darkTheme: false,
|
darkTheme: false,
|
||||||
@ -23,15 +23,17 @@ const layoutState = reactive({
|
|||||||
staticMenuDesktopInactive: getStoredMenuState(),
|
staticMenuDesktopInactive: getStoredMenuState(),
|
||||||
staticMenuMobileActive: false,
|
staticMenuMobileActive: false,
|
||||||
});
|
});
|
||||||
|
let _watcher: WatchHandle;
|
||||||
export function useLayout() {
|
export function useLayout() {
|
||||||
// 监听 staticMenuDesktopInactive 的变化并保存到 localStorage
|
if (!_watcher) {
|
||||||
watch(
|
// 监听 staticMenuDesktopInactive 的变化并保存到 localStorage
|
||||||
() => layoutState.staticMenuDesktopInactive,
|
_watcher = watch(
|
||||||
(newValue) => {
|
() => layoutState.staticMenuDesktopInactive,
|
||||||
localStorage.setItem('staticMenuDesktopInactive', JSON.stringify(newValue));
|
(newValue) => {
|
||||||
},
|
localStorage.setItem('staticMenuDesktopInactive', JSON.stringify(newValue));
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const setActiveMenuItem = (item: Record<string, never>) => {
|
const setActiveMenuItem = (item: Record<string, never>) => {
|
||||||
layoutState.activeMenuItem = item.value || item;
|
layoutState.activeMenuItem = item.value || item;
|
||||||
|
38
src/pages/index-new.page.vue
Normal file
38
src/pages/index-new.page.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
PATTERN_BACKGROUND_DIRECTION,
|
||||||
|
PATTERN_BACKGROUND_SPEED,
|
||||||
|
PATTERN_BACKGROUND_VARIANT,
|
||||||
|
} from '@/components/InspiraUI/pattern-background';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div bg-red v-if="false">
|
||||||
|
<div class="bg-blue h-full">A</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-screen w-screen p-4" v-else>
|
||||||
|
<PatternBackground
|
||||||
|
:animate="true"
|
||||||
|
:direction="PATTERN_BACKGROUND_DIRECTION.TopRight"
|
||||||
|
:variant="PATTERN_BACKGROUND_VARIANT.Dot"
|
||||||
|
class="flex h-full w-full flex-col items-center gap-4 overflow-hidden rounded-lg border px-4 py-8"
|
||||||
|
:speed="PATTERN_BACKGROUND_SPEED.Slow"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="relative z-20 bg-gradient-to-b from-neutral-200 to-neutral-500 bg-clip-text py-8 text-3xl font-bold text-transparent"
|
||||||
|
>
|
||||||
|
Spline
|
||||||
|
</h3>
|
||||||
|
<Spline scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode" />
|
||||||
|
</PatternBackground>
|
||||||
|
<FluidCursor />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
layout: false
|
||||||
|
</route>
|
1
typed-router.d.ts
vendored
1
typed-router.d.ts
vendored
@ -23,6 +23,7 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||||
'DataLoadersIdSub1UserId': RouteRecordInfo<'DataLoadersIdSub1UserId', '/data-loaders/:id/sub-1/:userId', { id: ParamValue<true>, userId: ParamValue<true> }, { id: ParamValue<false>, userId: ParamValue<false> }>,
|
'DataLoadersIdSub1UserId': RouteRecordInfo<'DataLoadersIdSub1UserId', '/data-loaders/:id/sub-1/:userId', { id: ParamValue<true>, userId: ParamValue<true> }, { id: ParamValue<false>, userId: ParamValue<false> }>,
|
||||||
'FlowbiteSidebar': RouteRecordInfo<'FlowbiteSidebar', '/FlowbiteSidebar', Record<never, never>, Record<never, never>>,
|
'FlowbiteSidebar': RouteRecordInfo<'FlowbiteSidebar', '/FlowbiteSidebar', Record<never, never>, Record<never, never>>,
|
||||||
|
'IndexNew': RouteRecordInfo<'IndexNew', '/index-new', Record<never, never>, Record<never, never>>,
|
||||||
'PageAPI': RouteRecordInfo<'PageAPI', '/Page/API', Record<never, never>, Record<never, never>>,
|
'PageAPI': RouteRecordInfo<'PageAPI', '/Page/API', Record<never, never>, Record<never, never>>,
|
||||||
'PageFonts': RouteRecordInfo<'PageFonts', '/Page/fonts', Record<never, never>, Record<never, never>>,
|
'PageFonts': RouteRecordInfo<'PageFonts', '/Page/fonts', Record<never, never>, Record<never, never>>,
|
||||||
'PageIcons': RouteRecordInfo<'PageIcons', '/Page/Icons', Record<never, never>, Record<never, never>>,
|
'PageIcons': RouteRecordInfo<'PageIcons', '/Page/Icons', Record<never, never>, Record<never, never>>,
|
||||||
|
@ -15,6 +15,7 @@ export default defineConfig({
|
|||||||
// the default
|
// the default
|
||||||
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,
|
/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/,
|
||||||
'src/shadcn/components/**/*.{js,ts}',
|
'src/shadcn/components/**/*.{js,ts}',
|
||||||
|
'src/components/InspiraUI/**/*.{js,ts}',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user