50
package-lock.json
generated
@ -10,11 +10,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@vee-validate/zod": "^4.15.1",
|
"@vee-validate/zod": "^4.15.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.2.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"embla-carousel-vue": "^8.6.0",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
@ -2370,14 +2371,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/core": {
|
"node_modules/@vueuse/core": {
|
||||||
"version": "14.1.0",
|
"version": "14.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.0.tgz",
|
||||||
"integrity": "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==",
|
"integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/web-bluetooth": "^0.0.21",
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
"@vueuse/metadata": "14.1.0",
|
"@vueuse/metadata": "14.2.0",
|
||||||
"@vueuse/shared": "14.1.0"
|
"@vueuse/shared": "14.2.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
@ -2387,14 +2388,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/metadata": {
|
"node_modules/@vueuse/metadata": {
|
||||||
"version": "14.1.0",
|
"version": "14.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.0.tgz",
|
||||||
|
"integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/shared": {
|
"node_modules/@vueuse/shared": {
|
||||||
"version": "14.1.0",
|
"version": "14.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.0.tgz",
|
||||||
|
"integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
@ -2966,6 +2971,35 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/embla-carousel": {
|
||||||
|
"version": "8.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||||
|
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/embla-carousel-reactive-utils": {
|
||||||
|
"version": "8.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
|
||||||
|
"integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"embla-carousel": "8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/embla-carousel-vue": {
|
||||||
|
"version": "8.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embla-carousel-vue/-/embla-carousel-vue-8.6.0.tgz",
|
||||||
|
"integrity": "sha512-v8UO5UsyLocZnu/LbfQA7Dn2QHuZKurJY93VUmZYP//QRWoCWOsionmvLLAlibkET3pGPs7++03VhJKbWD7vhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"embla-carousel": "8.6.0",
|
||||||
|
"embla-carousel-reactive-utils": "8.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.4",
|
"version": "5.18.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -18,11 +18,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@vee-validate/zod": "^4.15.1",
|
"@vee-validate/zod": "^4.15.1",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.2.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"embla-carousel-vue": "^8.6.0",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
|||||||
BIN
public/fast-video.MP4
Normal file
@ -1,34 +1,37 @@
|
|||||||
import type { VariantProps } from 'class-variance-authority'
|
import type { VariantProps } from "class-variance-authority"
|
||||||
import { cva } from 'class-variance-authority'
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
export { default as Button } from './Button.vue'
|
export { default as Button } from "./Button.vue"
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none 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",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none 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",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
default:
|
||||||
|
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive:
|
destructive:
|
||||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
outline:
|
||||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
secondary:
|
||||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
link: 'text-primary underline-offset-4 hover:underline',
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
icon: 'size-9',
|
"icon": "size-9",
|
||||||
'icon-sm': 'size-8',
|
"icon-sm": "size-8",
|
||||||
'icon-lg': 'size-10',
|
"icon-lg": "size-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'default',
|
variant: "default",
|
||||||
size: 'default',
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
53
src/components/ui/carousel/Carousel.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { CarouselEmits, CarouselProps, WithClassAsProps } from "./interface"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useProvideCarousel } from "./useCarousel"
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||||
|
orientation: "horizontal",
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits<CarouselEmits>()
|
||||||
|
|
||||||
|
const { canScrollNext, canScrollPrev, carouselApi, carouselRef, orientation, scrollNext, scrollPrev } = useProvideCarousel(props, emits)
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
canScrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
carouselApi,
|
||||||
|
carouselRef,
|
||||||
|
orientation,
|
||||||
|
scrollNext,
|
||||||
|
scrollPrev,
|
||||||
|
})
|
||||||
|
|
||||||
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
|
const prevKey = props.orientation === "vertical" ? "ArrowUp" : "ArrowLeft"
|
||||||
|
const nextKey = props.orientation === "vertical" ? "ArrowDown" : "ArrowRight"
|
||||||
|
|
||||||
|
if (event.key === prevKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollPrev()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === nextKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="carousel"
|
||||||
|
:class="cn('relative', props.class)"
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
tabindex="0"
|
||||||
|
@keydown="onKeyDown"
|
||||||
|
>
|
||||||
|
<slot :can-scroll-next :can-scroll-prev :carousel-api :carousel-ref :orientation :scroll-next :scroll-prev />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
33
src/components/ui/carousel/CarouselContent.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WithClassAsProps } from "./interface"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCarousel } from "./useCarousel"
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<WithClassAsProps>()
|
||||||
|
|
||||||
|
const { carouselRef, orientation } = useCarousel()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="carouselRef"
|
||||||
|
data-slot="carousel-content"
|
||||||
|
class="overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex',
|
||||||
|
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
24
src/components/ui/carousel/CarouselItem.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WithClassAsProps } from "./interface"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCarousel } from "./useCarousel"
|
||||||
|
|
||||||
|
const props = defineProps<WithClassAsProps>()
|
||||||
|
|
||||||
|
const { orientation } = useCarousel()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="carousel-item"
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
:class="cn(
|
||||||
|
'min-w-0 shrink-0 grow-0 basis-full',
|
||||||
|
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
29
src/components/ui/carousel/CarouselNext.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WithClassAsProps } from "./interface"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCarousel } from "./useCarousel"
|
||||||
|
import ArrowIcon from '@/pages/Home/modules4/arrow.svg?component'
|
||||||
|
|
||||||
|
const props = defineProps<WithClassAsProps>()
|
||||||
|
|
||||||
|
const { orientation, canScrollNext, scrollNext } = useCarousel()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
data-slot="carousel-next"
|
||||||
|
:class="cn(
|
||||||
|
'absolute cursor-pointer z-10',
|
||||||
|
orientation === 'horizontal'
|
||||||
|
? 'top-1/2 -right-12 -translate-y-1/2'
|
||||||
|
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
@click="scrollNext"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ArrowIcon class="h-[120px] w-auto rotate-180" />
|
||||||
|
<span class="sr-only">Next Slide</span>
|
||||||
|
</slot>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
29
src/components/ui/carousel/CarouselPrevious.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WithClassAsProps } from "./interface"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCarousel } from "./useCarousel"
|
||||||
|
import ArrowIcon from '@/pages/Home/modules4/arrow.svg?component'
|
||||||
|
|
||||||
|
const props = defineProps<WithClassAsProps>()
|
||||||
|
|
||||||
|
const { orientation, canScrollPrev, scrollPrev } = useCarousel()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
data-slot="carousel-previous"
|
||||||
|
:class="cn(
|
||||||
|
'absolute cursor-pointer z-10',
|
||||||
|
orientation === 'horizontal'
|
||||||
|
? 'top-1/2 -left-12 -translate-y-1/2'
|
||||||
|
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
@click="scrollPrev"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ArrowIcon class="h-[120px] w-auto" />
|
||||||
|
<span class="sr-only">Previous Slide</span>
|
||||||
|
</slot>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
10
src/components/ui/carousel/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export { default as Carousel } from "./Carousel.vue"
|
||||||
|
export { default as CarouselContent } from "./CarouselContent.vue"
|
||||||
|
export { default as CarouselItem } from "./CarouselItem.vue"
|
||||||
|
export { default as CarouselNext } from "./CarouselNext.vue"
|
||||||
|
export { default as CarouselPrevious } from "./CarouselPrevious.vue"
|
||||||
|
export type {
|
||||||
|
UnwrapRefCarouselApi as CarouselApi,
|
||||||
|
} from "./interface"
|
||||||
|
|
||||||
|
export { useCarousel } from "./useCarousel"
|
||||||
26
src/components/ui/carousel/interface.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type useEmblaCarousel from "embla-carousel-vue"
|
||||||
|
import type {
|
||||||
|
EmblaCarouselVueType,
|
||||||
|
} from "embla-carousel-vue"
|
||||||
|
import type { HTMLAttributes, UnwrapRef } from "vue"
|
||||||
|
|
||||||
|
type CarouselApi = EmblaCarouselVueType[1]
|
||||||
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||||
|
type CarouselOptions = UseCarouselParameters[0]
|
||||||
|
type CarouselPlugin = UseCarouselParameters[1]
|
||||||
|
|
||||||
|
export type UnwrapRefCarouselApi = UnwrapRef<CarouselApi>
|
||||||
|
|
||||||
|
export interface CarouselProps {
|
||||||
|
opts?: CarouselOptions
|
||||||
|
plugins?: CarouselPlugin
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CarouselEmits {
|
||||||
|
(e: "init-api", payload: UnwrapRefCarouselApi): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithClassAsProps {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
56
src/components/ui/carousel/useCarousel.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import type { UnwrapRefCarouselApi as CarouselApi, CarouselEmits, CarouselProps } from "./interface"
|
||||||
|
import { createInjectionState } from "@vueuse/core"
|
||||||
|
import emblaCarouselVue from "embla-carousel-vue"
|
||||||
|
import { onMounted, ref } from "vue"
|
||||||
|
|
||||||
|
const [useProvideCarousel, useInjectCarousel] = createInjectionState(
|
||||||
|
({
|
||||||
|
opts,
|
||||||
|
orientation,
|
||||||
|
plugins,
|
||||||
|
}: CarouselProps, emits: CarouselEmits) => {
|
||||||
|
const [emblaNode, emblaApi] = emblaCarouselVue({
|
||||||
|
...opts,
|
||||||
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
|
}, plugins)
|
||||||
|
|
||||||
|
function scrollPrev() {
|
||||||
|
emblaApi.value?.scrollPrev()
|
||||||
|
}
|
||||||
|
function scrollNext() {
|
||||||
|
emblaApi.value?.scrollNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
const canScrollNext = ref(false)
|
||||||
|
const canScrollPrev = ref(false)
|
||||||
|
|
||||||
|
function onSelect(api: CarouselApi) {
|
||||||
|
canScrollNext.value = api?.canScrollNext() || false
|
||||||
|
canScrollPrev.value = api?.canScrollPrev() || false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!emblaApi.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
emblaApi.value?.on("init", onSelect)
|
||||||
|
emblaApi.value?.on("reInit", onSelect)
|
||||||
|
emblaApi.value?.on("select", onSelect)
|
||||||
|
|
||||||
|
emits("init-api", emblaApi.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function useCarousel() {
|
||||||
|
const carouselState = useInjectCarousel()
|
||||||
|
|
||||||
|
if (!carouselState)
|
||||||
|
throw new Error("useCarousel must be used within a <Carousel />")
|
||||||
|
|
||||||
|
return carouselState
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useCarousel, useProvideCarousel }
|
||||||
@ -37,8 +37,8 @@
|
|||||||
<!-- Main Content Container -->
|
<!-- Main Content Container -->
|
||||||
<div class="container mx-auto flex flex-col">
|
<div class="container mx-auto flex flex-col">
|
||||||
<main class="pt-10">
|
<main class="pt-10">
|
||||||
<!-- module1 -->
|
<!-- module0 -->
|
||||||
<div class="mb-[80px] flex">
|
<div class="mb-[80px] flex justify-between">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="mb-[20px] ml-[42px] md:ml-[17px]">
|
<div class="mb-[20px] ml-[42px] md:ml-[17px]">
|
||||||
<h2 class="mb-2 text-2xl font-black md:text-8xl">
|
<h2 class="mb-2 text-2xl font-black md:text-8xl">
|
||||||
@ -49,6 +49,11 @@
|
|||||||
<img src="./Group%20133.png" alt="" class="mt-[52px] h-[287px] w-[182px]" />
|
<img src="./Group%20133.png" alt="" class="mt-[52px] h-[287px] w-[182px]" />
|
||||||
</div>
|
</div>
|
||||||
<!-- video -->
|
<!-- video -->
|
||||||
|
<div class="h-[450px] w-[800px] bg-[url('@/pages/Home/modules1/Group-236.png')]"></div>
|
||||||
|
</div>
|
||||||
|
<!-- modules1 -->
|
||||||
|
<div class="mb-[80px] w-full">
|
||||||
|
<img src="./modules1/Group%20235.png" alt="邀请规则" class="mx-auto w-[1024px]" />
|
||||||
</div>
|
</div>
|
||||||
<!-- modules2 -->
|
<!-- modules2 -->
|
||||||
<div class="mb-[80px] w-full rounded-[40px] bg-[#ADFF5B] p-8 pb-2 text-black">
|
<div class="mb-[80px] w-full rounded-[40px] bg-[#ADFF5B] p-8 pb-2 text-black">
|
||||||
@ -71,7 +76,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-center text-[#999999]">给您的客户提供专业视角的建议</div>
|
<div class="text-center text-[#999999]">给您的客户提供专业视角的建议</div>
|
||||||
<div class="mt-[20px] flex justify-center">
|
<div class="mt-[20px] flex justify-center">
|
||||||
<div class="size-[600px] rounded-[40px] border-8 border-[#ADFF5B]"></div>
|
<Carousel class="relative w-full max-w-[700px]" :opts="{ loop: true }">
|
||||||
|
<div class="size-[700px] overflow-hidden rounded-[40px]">
|
||||||
|
<CarouselContent>
|
||||||
|
<CarouselItem v-for="(img, index) in modules4Images" :key="index">
|
||||||
|
<img
|
||||||
|
:src="img"
|
||||||
|
alt=""
|
||||||
|
class="h-[700px] w-[700px] object-cover select-none"
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
|
</CarouselItem>
|
||||||
|
</CarouselContent>
|
||||||
|
</div>
|
||||||
|
<CarouselPrevious class="-left-[100px]" />
|
||||||
|
<CarouselNext class="-right-[100px]" />
|
||||||
|
</Carousel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@ -97,6 +117,13 @@ import { useLocalStorage } from '@vueuse/core'
|
|||||||
import LoginFormModal from './components/LoginFormModal.vue'
|
import LoginFormModal from './components/LoginFormModal.vue'
|
||||||
import Logo from './logo.svg?component'
|
import Logo from './logo.svg?component'
|
||||||
import Group215Icon from './modules3/Group 215.svg?component'
|
import Group215Icon from './modules3/Group 215.svg?component'
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from '@/components/ui/carousel'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import Frame98 from './modules2/Frame 98.png'
|
import Frame98 from './modules2/Frame 98.png'
|
||||||
import Frame99 from './modules2/Frame 99.png'
|
import Frame99 from './modules2/Frame 99.png'
|
||||||
@ -105,7 +132,15 @@ import Frame101 from './modules2/Frame 101.png'
|
|||||||
import Frame104 from './modules2/Frame 104.png'
|
import Frame104 from './modules2/Frame 104.png'
|
||||||
import Frame105 from './modules2/Frame 105.png'
|
import Frame105 from './modules2/Frame 105.png'
|
||||||
|
|
||||||
|
import image1 from './modules4/image1.png'
|
||||||
|
import image2 from './modules4/image2.png'
|
||||||
|
import image3 from './modules4/image3.png'
|
||||||
|
import image4 from './modules4/image4.png'
|
||||||
|
import image5 from './modules4/image5.png'
|
||||||
|
import image6 from './modules4/image6.png'
|
||||||
|
|
||||||
const modules2Image = [Frame98, Frame99, Frame100, Frame101, Frame104, Frame105]
|
const modules2Image = [Frame98, Frame99, Frame100, Frame101, Frame104, Frame105]
|
||||||
|
const modules4Images = [image1, image2, image3, image4, image5, image6]
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
BIN
src/pages/Home/modules1/Group 235.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
src/pages/Home/modules1/Group-236.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
3
src/pages/Home/modules4/arrow.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="48" height="120" viewBox="0 0 48 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 59.9033L37.5049 0L47.4883 4.35156L12.8008 59.9033L47.4883 115.456L37.5049 119.808L0 59.9033Z" fill="#ADFF5B"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 228 B |
BIN
src/pages/Home/modules4/image1.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
src/pages/Home/modules4/image2.png
Normal file
|
After Width: | Height: | Size: 409 KiB |
BIN
src/pages/Home/modules4/image3.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
src/pages/Home/modules4/image4.png
Normal file
|
After Width: | Height: | Size: 579 KiB |
BIN
src/pages/Home/modules4/image5.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
src/pages/Home/modules4/image6.png
Normal file
|
After Width: | Height: | Size: 339 KiB |