release
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
9
.vscode/extendsions.json
vendored
Normal file
9
.vscode/extendsions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"christian-kohler.path-intellisense"
|
||||
]
|
||||
}
|
||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
// Vue 组件名称驼峰提示
|
||||
"volar.completion.preferredTagNameCase": "camelCase",
|
||||
// 属性名称驼峰提示
|
||||
"volar.completion.preferredAttrNameCase": "camelCase",
|
||||
// 禁用 Vue 2 兼容性提示
|
||||
"volar.completion.vue2BuiltIn": false,
|
||||
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
}
|
||||
75
.vscode/vue3.code-snippets
vendored
Normal file
75
.vscode/vue3.code-snippets
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
// Place your 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Print Vue3 SFC": {
|
||||
"scope": "vue",
|
||||
"prefix": "v3",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <div class=\"\">$3</div>",
|
||||
"</template>\n",
|
||||
"<script lang=\"ts\" setup>",
|
||||
"defineOptions({",
|
||||
" name: '$1'",
|
||||
"})",
|
||||
"</script>\n",
|
||||
"<style lang=\"scss\" scoped>",
|
||||
"//$4",
|
||||
"</style>\n",
|
||||
],
|
||||
},
|
||||
"Print style": {
|
||||
"scope": "vue",
|
||||
"prefix": "st",
|
||||
"body": [
|
||||
"<style lang=\"scss\" scoped>",
|
||||
"//",
|
||||
"</style>\n"
|
||||
],
|
||||
},
|
||||
"Print script": {
|
||||
"scope": "vue",
|
||||
"prefix": "sc",
|
||||
"body": [
|
||||
"<script lang=\"ts\" setup>",
|
||||
"//$1",
|
||||
"</script>\n"
|
||||
],
|
||||
},
|
||||
"Print script with definePage": {
|
||||
"scope": "vue",
|
||||
"prefix": "scdp",
|
||||
"body": [
|
||||
"<script lang=\"ts\" setup>",
|
||||
"definePage({",
|
||||
" style: {",
|
||||
" navigationBarTitleText: '$1',",
|
||||
" },",
|
||||
"})",
|
||||
"</script>\n"
|
||||
],
|
||||
},
|
||||
"Print template": {
|
||||
"scope": "vue",
|
||||
"prefix": "te",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <div class=\"\">$1</div>",
|
||||
"</template>\n"
|
||||
],
|
||||
},
|
||||
}
|
||||
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
7
app/app.config.ts
Normal file
7
app/app.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
container:{
|
||||
base:""
|
||||
}
|
||||
},
|
||||
});
|
||||
15
app/app.vue
Normal file
15
app/app.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="w-full h-screen">
|
||||
<UApp>
|
||||
<!-- <SearchFilter/> -->
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</UApp>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SearchFilter from './components/search-filter/index.vue'
|
||||
</script>
|
||||
|
||||
42
app/assets/css/global.css
Normal file
42
app/assets/css/global.css
Normal file
@@ -0,0 +1,42 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-primary: #dc5f3f;
|
||||
|
||||
/* text */
|
||||
--color-text-primary: #fff;
|
||||
/* 灰 */
|
||||
--color-gray-1:#ffffff;
|
||||
--color-gray-2:#fafafa;
|
||||
--color-gray-3:#f5f5f5;
|
||||
--color-gray-4:#f0f0f0;
|
||||
--color-gray-5:#d9d9d9;
|
||||
--color-gray-6:#bfbfbf;
|
||||
--color-gray-7:#8c8c8c;
|
||||
--color-gray-8:#595959;
|
||||
--color-gray-9:#434343;
|
||||
--color-gray-10:#262626;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-primary:'#dc5f3f'
|
||||
}
|
||||
|
||||
@theme dark {
|
||||
--color-primary: #dc5f3f;
|
||||
|
||||
--colors-primary: 255, 108, 71;
|
||||
--colors-green: 39, 214, 57;
|
||||
--colors-yellow: 255, 189, 0;
|
||||
--colors-red: 255, 38, 73;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
3
app/assets/css/main.css
Normal file
3
app/assets/css/main.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
@import "./global.css";
|
||||
0
app/assets/css/x/base.css
Normal file
0
app/assets/css/x/base.css
Normal file
BIN
app/assets/videos/beep.mp3
Normal file
BIN
app/assets/videos/beep.mp3
Normal file
Binary file not shown.
18
app/components/adv/index.vue
Normal file
18
app/components/adv/index.vue
Normal file
File diff suppressed because one or more lines are too long
0
app/components/adv/interface.ts
Normal file
0
app/components/adv/interface.ts
Normal file
13
app/components/goods-item/goods-skeleton.vue
Normal file
13
app/components/goods-item/goods-skeleton.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="h-[250px]">
|
||||
<USkeleton>
|
||||
|
||||
</USkeleton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'GoodsSkeleton'
|
||||
})
|
||||
</script>
|
||||
76
app/components/goods-item/index.vue
Normal file
76
app/components/goods-item/index.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<NuxtLink
|
||||
:class="[
|
||||
wrapperCls,
|
||||
exteriorCls,
|
||||
'transition-transform duration-200 ease-out hover:-translate-y-1 active:-translate-y-1 focus:-translate-y-1 '
|
||||
]"
|
||||
|
||||
>
|
||||
<!-- top tags -->
|
||||
<div class="px-2 pt-2 w-full top-0 left-0 flex items-center">
|
||||
<!-- exterior 外观 -->
|
||||
<div class="text-xs flex-1 [&>:nth-child(n+2)::before]:content-['/'] [&>:nth-child(n+2)::before]:mx-2 [&>:nth-child(n+2)::before]:text-text-primary/60 [&>:nth-child(n+2)::before]:font-bold">
|
||||
<span class="">
|
||||
略有磨损
|
||||
</span>
|
||||
<span class=" text-[#c169ff]">ST</span>
|
||||
<span class="text-amber-400">SV</span>
|
||||
</div>
|
||||
<!-- 销售 -->
|
||||
<div class="text-right flex items-center justify-end text-sm text-text-primary/60">
|
||||
<span>88</span>
|
||||
<span class="ml-1 bg-[url('/images/sale.png')] bg-cover bg-center bg-no-repeat w-3 h-3"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- cover -->
|
||||
<div class="relative overflow-hidden">
|
||||
<div class="group-hover:scale-105 flex justify-center items-center h-32 mt-2 mb-2 delay-300 animate-scaletransition delay-150 duration-300 ">
|
||||
<img alt="MP7 | 橘皮涂装" loading="lazy" width="0" height="0" decoding="async" data-nimg="1" class="block " src="https://img.zbt.com/e/steam/item/730/U291dmVuaXIgTVA3IHwgT3JhbmdlIFBlZWwgKEZhY3RvcnkgTmV3KQ==.png?x-oss-process=image/resize,w_220" style="color: transparent; object-fit: contain; width: 162px; height: 122px;">
|
||||
</div>
|
||||
</div>
|
||||
<!-- footer -->
|
||||
<div class="px-3 pb-3">
|
||||
<div class="flex justify-start items-center ">
|
||||
<div class="text-2xl text-text-primary">
|
||||
<span class="pr-0.5">HK$</span>
|
||||
<span class="text-2xl text-text-primary">359.04</span>
|
||||
</div>
|
||||
<!-- sale info -->
|
||||
<div class="ml-4 text-white text-xs w-10 h-4 flex justify-center items-center bg-[url('/images/goods-item/discount.png')] bg-center bg-no-repeat bg-cover" style="background-size: 100% 100%; --ml: 16px;">
|
||||
-38%
|
||||
</div>
|
||||
</div>
|
||||
<!-- suggested price -->
|
||||
<div class="text-xs font-medium text-text-primary/20 truncate flex items-center mt-1 mb-3">
|
||||
<!-- suggested price -->
|
||||
<div :class="false ? 'line-through' : undefined">建议价格 </div>
|
||||
<div class="text-xs text-text-primary/20 pl-1">
|
||||
<span class="pr-0.5">HK$</span>
|
||||
<span class="numFont text-xs text-text-primary/20 ">575.42</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="text-sm font-bold text-text-primary/60 truncate">格洛克 18 型 | 子弹皇后 (久经沙场)</h4></div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'GoodsItem'
|
||||
})
|
||||
const wrapperCls = "w-full h-[266px] cursor-pointer relative bg-linear-to-b from-[rgb(46,49,50)] to-[rgb(35,39,40)] rounded-sm overflow-hidden"
|
||||
|
||||
const exteriorCls = computed(()=>[
|
||||
'text-lime-700'
|
||||
])
|
||||
|
||||
const exteriorClsMap:Record<string,string> = {
|
||||
// 完好
|
||||
"0":'text-teal-200',
|
||||
//
|
||||
"1": 'text-lime-700',
|
||||
"2":""
|
||||
}
|
||||
</script>
|
||||
|
||||
45
app/components/goods-virtual-list/index.vue
Normal file
45
app/components/goods-virtual-list/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<!-- 虚拟列表容器(开启滚动) -->
|
||||
<div
|
||||
class="grid gap-2 h-100"
|
||||
v-bind="containerProps"
|
||||
>
|
||||
<!-- wrapper -->
|
||||
<div
|
||||
v-bind="wrapperProps" class="border border-gray-2 mb-2 w-50 h-[266px] flex items-center justify-center"
|
||||
v-for="{ index , data } in list"
|
||||
>
|
||||
Row {{ index }} <span class="text-gray-1 ml-1">{{ data.content }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
|
||||
defineOptions({
|
||||
name: 'GoodsVirtuaList'
|
||||
})
|
||||
|
||||
// 2. 生成测试数据(1000 条)
|
||||
const totalCount = 100000
|
||||
const data = Array.from({ length: totalCount }, (_, i) => ({
|
||||
id: i,
|
||||
content: `Item ${i + 1}`
|
||||
}))
|
||||
|
||||
|
||||
|
||||
|
||||
// 5. 使用 useVirtualList 计算可视项目
|
||||
const { list, containerProps, wrapperProps, scrollTo , } = useVirtualList(
|
||||
data,
|
||||
{
|
||||
itemHeight:266,
|
||||
overscan: 10,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
</script>
|
||||
13
app/components/goods-wrapper/index.vue
Normal file
13
app/components/goods-wrapper/index.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<!-- wrapper -->
|
||||
<div class="grid gap-1 w-60">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'GoodsWrapper'
|
||||
})
|
||||
</script>
|
||||
|
||||
32
app/components/search-filter/index.vue
Normal file
32
app/components/search-filter/index.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<!-- adv -->
|
||||
|
||||
<!-- search fliter -->
|
||||
<FilterGroup title="价格">
|
||||
<PriceRangeInput v-model="priceRange" :max="6000" />
|
||||
<StatusInput />
|
||||
</FilterGroup>
|
||||
<TypeFilter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FilterGroup from './widgets/filter-group/index.vue'
|
||||
import PriceRangeInput from './widgets/price-range-input/index.vue'
|
||||
import StatusInput from './widgets/status-input/index.vue'
|
||||
import TypeFilter from './widgets/type-filter/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SearchFilter'
|
||||
})
|
||||
|
||||
const priceRange = ref<[number,number]>([0 , 5000])
|
||||
|
||||
const data = reactive({
|
||||
minPrice: 0,
|
||||
maxPrice: 5000,
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
54
app/components/search-filter/widgets/filter-group/index.vue
Normal file
54
app/components/search-filter/widgets/filter-group/index.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<UCollapsible :open="checked" class="relative flex flex-col w-60">
|
||||
<!-- header -->
|
||||
<div class="flex items-center text-sm font-semibold h-10 px-3 group cursor-pointer hover:bg-text-primary/10 relative">
|
||||
<!-- 标题区域 - 点击不触发展开/关闭 -->
|
||||
<span class="flex-1">
|
||||
{{ title }} {{ unit }}
|
||||
</span>
|
||||
<!-- 箭头图标 - 点击触发展开/关闭 -->
|
||||
<button type="button" @click.stop="toggleAccordion" class="w-3 h-3 absolute right-2 top-1/2 -mt-1.5 p-0 m-0 leading-0">
|
||||
<UIcon name="ri:play-large-fill" :class="triggerCls"></UIcon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- content -->
|
||||
<template #content>
|
||||
<div class="h-auto">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
</UCollapsible>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: 'FilterGroup'
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string,
|
||||
unit?: string
|
||||
}>(), {
|
||||
title: '',
|
||||
unit: 'CNY'
|
||||
})
|
||||
|
||||
const checked = ref(false)
|
||||
|
||||
const toggleAccordion = () => {
|
||||
console.log(1111);
|
||||
|
||||
checked.value = !checked.value
|
||||
}
|
||||
|
||||
const triggerCls = computed(() => {
|
||||
return [
|
||||
"size-3 text-text-primary/60 transition-transform duration-250 left-0 top-0",
|
||||
checked.value ? "-rotate-90" : 'rotate-90'
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="w-full relative py-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Slider -->
|
||||
<div class="h-10 flex items-center px-4">
|
||||
<USlider :min="min" :max="max" v-model="value" />
|
||||
</div>
|
||||
|
||||
<!-- input -->
|
||||
<div class="relative flex items-center gap-4 box-content border border-text-primary/40 h-13 rounded-md">
|
||||
<!-- input item -->
|
||||
<div class="flex flex-col justify-start items-start gap-1">
|
||||
<label class="text-text-primary/60 font-bold text-xs ml-2 mt-2 p-0 leading-none">{{ `最小 (RMB)` }}</label>
|
||||
<UInputNumber
|
||||
name="minPrice"
|
||||
:min="min"
|
||||
:max="value[1]"
|
||||
:increment="false"
|
||||
:decrement="false"
|
||||
v-model="value[0]"
|
||||
:ui="{ base:'border-0 focus-visible:ring-0 ring-0 border-text-primary/40 text-text-primary/80 font-medium text-sm bg-[none] py-0' }"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-px "></div>
|
||||
<div class="flex flex-col justify-start items-start gap-1">
|
||||
<label class="text-text-primary/60 font-bold text-xs ml-2 mt-2 p-0 leading-none">{{ `最大 (RMB)` }}</label>
|
||||
<UInputNumber
|
||||
name="minPrice"
|
||||
:min="min"
|
||||
:max="value[1]"
|
||||
:increment="false"
|
||||
:decrement="false"
|
||||
v-model="value[0]"
|
||||
:ui="{ base:'border-0 focus-visible:ring-0 ring-0 border-text-primary/40 text-text-primary/80 font-medium text-sm bg-[none] py-0' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PriceRangeInputProps } from './interface'
|
||||
|
||||
defineOptions({ name: 'PriceRangeInput' })
|
||||
|
||||
const value = defineModel<[number,number]>({default:()=>[0,1600]})
|
||||
|
||||
withDefaults(defineProps<PriceRangeInputProps>(), {
|
||||
min: 0,
|
||||
max: 1600
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface PriceRangeInputProps {
|
||||
// 最小值
|
||||
min?: number;
|
||||
|
||||
// 最大值
|
||||
max?: number;
|
||||
}
|
||||
41
app/components/search-filter/widgets/status-input/index.vue
Normal file
41
app/components/search-filter/widgets/status-input/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="relative bg-black/10">
|
||||
<UCheckboxGroup
|
||||
v-model="value"
|
||||
:items="options"
|
||||
:ui="{
|
||||
item:'h-8 hover:bg-white/10 items-center px-4'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
const options = [
|
||||
{
|
||||
label:"可出售",
|
||||
value:1,
|
||||
},
|
||||
{
|
||||
label:"售卖中",
|
||||
value:2,
|
||||
},
|
||||
{
|
||||
label:"不可出售",
|
||||
value:3,
|
||||
},
|
||||
{
|
||||
label:"冷却期",
|
||||
value:4,
|
||||
}
|
||||
] as Option[]
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import type { Option } from 'types'
|
||||
|
||||
defineOptions({
|
||||
name: 'StatusInput'
|
||||
})
|
||||
|
||||
const value = defineModel<number[]>({default:[]})
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface StatusInputProps {
|
||||
|
||||
}
|
||||
147
app/components/search-filter/widgets/type-filter/index.vue
Normal file
147
app/components/search-filter/widgets/type-filter/index.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<FilterGroup title="类型" :unit="''">
|
||||
<UTree
|
||||
:items="items"
|
||||
@select="onSelect"
|
||||
bubble-select
|
||||
:as="{link:'div'}"
|
||||
propagate-select
|
||||
multiple
|
||||
:nested="false"
|
||||
>
|
||||
<!-- 自定义节点前缀 -->
|
||||
<template #item-leading="{ selected, indeterminate, handleSelect }">
|
||||
<UCheckbox
|
||||
:model-value="indeterminate ? 'indeterminate' : selected"
|
||||
tabindex="-1"
|
||||
@change="handleSelect"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义节点label -->
|
||||
<template #item-label="{ item }" >
|
||||
<span @click.stop>{{ item.label }}</span>
|
||||
</template>
|
||||
</UTree>
|
||||
</FilterGroup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
const items: TreeOption<string>[] = [
|
||||
{
|
||||
label: "武器",
|
||||
value: "weapons",
|
||||
children: [
|
||||
{ label: "长剑", value: "longsword" },
|
||||
{ label: "短剑", value: "shortsword" },
|
||||
{ label: "巨剑", value: "greatsword" },
|
||||
{ label: "武士刀", value: "katana" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "防具",
|
||||
value: "armor",
|
||||
children: [
|
||||
{ label: "头盔", value: "helmet" },
|
||||
{ label: "胸甲", value: "chestplate" },
|
||||
{ label: "护腿", value: "leggings" },
|
||||
{ label: "靴子", value: "boots" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "工具",
|
||||
value: "tools",
|
||||
children: [
|
||||
{ label: "斧头", value: "axe" },
|
||||
{ label: "镐", value: "pickaxe" },
|
||||
{ label: "铲子", value: "shovel" },
|
||||
{ label: "锤子", value: "hammer" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "魔法物品",
|
||||
value: "magic_items",
|
||||
children: [
|
||||
{ label: "法杖", value: "staff" },
|
||||
{ label: "魔杖", value: "wand" },
|
||||
{ label: "魔法书", value: "spellbook" },
|
||||
{ label: "水晶球", value: "crystal_ball" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "消耗品",
|
||||
value: "consumables",
|
||||
children: [
|
||||
{ label: "治疗药水", value: "healing_potion" },
|
||||
{ label: "法力药水", value: "mana_potion" },
|
||||
{ label: "解毒剂", value: "antidote" },
|
||||
{ label: "食物", value: "food" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "材料",
|
||||
value: "materials",
|
||||
children: [
|
||||
{ label: "木材", value: "wood" },
|
||||
{ label: "矿石", value: "ore" },
|
||||
{ label: "皮革", value: "leather" },
|
||||
{ label: "布料", value: "cloth" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "饰品",
|
||||
value: "accessories",
|
||||
children: [
|
||||
{ label: "戒指", value: "ring" },
|
||||
{ label: "项链", value: "necklace" },
|
||||
{ label: "耳环", value: "earring" },
|
||||
{ label: "手镯", value: "bracelet" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "远程武器",
|
||||
value: "ranged_weapons",
|
||||
children: [
|
||||
{ label: "弓", value: "bow" },
|
||||
{ label: "弩", value: "crossbow" },
|
||||
{ label: "飞镖", value: "dart" },
|
||||
{ label: "投石索", value: "sling" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "盾牌",
|
||||
value: "shields",
|
||||
children: [
|
||||
{ label: "圆盾", value: "buckler" },
|
||||
{ label: "塔盾", value: "tower_shield" },
|
||||
{ label: "骑士盾", value: "knight_shield" },
|
||||
{ label: "魔法盾", value: "magic_shield" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "特殊物品",
|
||||
value: "special_items",
|
||||
children: [
|
||||
{ label: "钥匙", value: "key" },
|
||||
{ label: "地图", value: "map" },
|
||||
{ label: "宝石", value: "gem" },
|
||||
{ label: "古董", value: "antique" }
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import type { TreeItemSelectEvent } from 'reka-ui'
|
||||
import type { TreeOption } from 'types'
|
||||
import FilterGroup from '../filter-group/index.vue'
|
||||
|
||||
defineOptions({ name: 'TypeFilter' })
|
||||
|
||||
const onSelect = (e: TreeItemSelectEvent<TreeOption<string>>) => {
|
||||
if (e.detail.originalEvent.type === 'click') {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
256
app/components/ui/vortex/index.vue
Normal file
256
app/components/ui/vortex/index.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div :class="cn('relative h-full w-full', props.containerClass)">
|
||||
<Motion
|
||||
ref="containerRef"
|
||||
as="div"
|
||||
:initial="{ opacity: 0 }"
|
||||
:animate="{ opacity: 1 }"
|
||||
class="absolute inset-0 z-0 flex size-full items-center justify-center bg-transparent"
|
||||
>
|
||||
<canvas ref="canvasRef"></canvas>
|
||||
</Motion>
|
||||
|
||||
<div :class="cn('relative z-10', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { createNoise3D } from "simplex-noise";
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
import { useDebounceFn, templateRef } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TAU = 2 * Math.PI;
|
||||
const BASE_TTL = 50;
|
||||
const RANGE_TTL = 150;
|
||||
const PARTICLE_PROP_COUNT = 9;
|
||||
const RANGE_HUE = 100;
|
||||
const NOISE_STEPS = 3;
|
||||
const X_OFF = 0.00125;
|
||||
const Y_OFF = 0.00125;
|
||||
const Z_OFF = 0.0005;
|
||||
|
||||
interface VortexProps {
|
||||
class?: string;
|
||||
containerClass?: string;
|
||||
particleCount?: number;
|
||||
rangeY?: number;
|
||||
baseHue?: number;
|
||||
baseSpeed?: number;
|
||||
rangeSpeed?: number;
|
||||
baseRadius?: number;
|
||||
rangeRadius?: number;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<VortexProps>(), {
|
||||
particleCount: 700,
|
||||
rangeY: 100,
|
||||
baseSpeed: 0.0,
|
||||
rangeSpeed: 1.5,
|
||||
baseRadius: 1,
|
||||
rangeRadius: 2,
|
||||
baseHue: 220,
|
||||
backgroundColor: "#000000",
|
||||
});
|
||||
|
||||
const tick = ref<number>(0);
|
||||
const animationFrame = ref<number | null>(null);
|
||||
const particleProps = shallowRef<Float32Array | null>(null);
|
||||
const center = ref<[number, number]>([0, 0]);
|
||||
const ctx = shallowRef<CanvasRenderingContext2D | null>(null);
|
||||
|
||||
const canvasRef = templateRef<HTMLCanvasElement | null>("canvasRef");
|
||||
const containerRef = templateRef<HTMLElement | null>("containerRef");
|
||||
|
||||
const particleCache = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
life: 0,
|
||||
ttl: 0,
|
||||
speed: 0,
|
||||
radius: 0,
|
||||
hue: 0,
|
||||
};
|
||||
|
||||
const noise3D = createNoise3D();
|
||||
|
||||
function rand(n: number) {
|
||||
return n * Math.random();
|
||||
}
|
||||
function randRange(n: number): number {
|
||||
return n - rand(2 * n);
|
||||
}
|
||||
function fadeInOut(t: number, m: number): number {
|
||||
const hm = 0.5 * m;
|
||||
return Math.abs(((t + hm) % m) - hm) / hm;
|
||||
}
|
||||
function lerp(n1: number, n2: number, speed: number): number {
|
||||
return (1 - speed) * n1 + speed * n2;
|
||||
}
|
||||
|
||||
function initParticle(i: number) {
|
||||
if (!particleProps.value || !canvasRef.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
particleCache.x = rand(canvas.width);
|
||||
particleCache.y = center.value[1] + randRange(props.rangeY);
|
||||
particleCache.vx = 0;
|
||||
particleCache.vy = 0;
|
||||
particleCache.life = 0;
|
||||
particleCache.ttl = BASE_TTL + rand(RANGE_TTL);
|
||||
particleCache.speed = props.baseSpeed + rand(props.rangeSpeed);
|
||||
particleCache.radius = props.baseRadius + rand(props.rangeRadius);
|
||||
particleCache.hue = props.baseHue + rand(RANGE_HUE);
|
||||
|
||||
particleProps.value.set(
|
||||
[
|
||||
particleCache.x,
|
||||
particleCache.y,
|
||||
particleCache.vx,
|
||||
particleCache.vy,
|
||||
particleCache.life,
|
||||
particleCache.ttl,
|
||||
particleCache.speed,
|
||||
particleCache.radius,
|
||||
particleCache.hue,
|
||||
],
|
||||
i,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function updateParticle(i: number) {
|
||||
if (!particleProps.value || !canvasRef.value || !ctx.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const props = particleProps.value as Record<number,any>;
|
||||
const context = ctx.value;
|
||||
//
|
||||
particleCache.x = props[i];
|
||||
particleCache.y = props[i + 1];
|
||||
particleCache.vx = props[i + 2];
|
||||
particleCache.vy = props[i + 3];
|
||||
particleCache.life = props[i + 4];
|
||||
particleCache.ttl = props[i + 5];
|
||||
particleCache.speed = props[i + 6];
|
||||
particleCache.radius = props[i + 7];
|
||||
particleCache.hue = props[i + 8];
|
||||
|
||||
|
||||
const n =
|
||||
noise3D(particleCache.x * X_OFF, particleCache.y * Y_OFF, tick.value * Z_OFF) *
|
||||
NOISE_STEPS *
|
||||
TAU;
|
||||
|
||||
const nextVx = lerp(particleCache.vx, Math.cos(n), 0.5);
|
||||
const nextVy = lerp(particleCache.vy, Math.sin(n), 0.5);
|
||||
const nextX = particleCache.x + nextVx * particleCache.speed;
|
||||
const nextY = particleCache.y + nextVy * particleCache.speed;
|
||||
|
||||
context.save();
|
||||
context.lineCap = "round";
|
||||
context.lineWidth = particleCache.radius;
|
||||
context.strokeStyle = `hsla(${particleCache.hue},100%,60%,${fadeInOut(
|
||||
particleCache.life,
|
||||
particleCache.ttl,
|
||||
)})`;
|
||||
context.beginPath();
|
||||
context.moveTo(particleCache.x, particleCache.y);
|
||||
context.lineTo(nextX, nextY);
|
||||
context.stroke();
|
||||
context.restore();
|
||||
|
||||
props[i] = nextX;
|
||||
props[i + 1] = nextY;
|
||||
props[i + 2] = nextVx;
|
||||
props[i + 3] = nextVy;
|
||||
props[i + 4] = particleCache.life + 1;
|
||||
|
||||
if (
|
||||
nextX > canvas.width ||
|
||||
nextX < 0 ||
|
||||
nextY > canvas.height ||
|
||||
nextY < 0 ||
|
||||
particleCache.life > particleCache.ttl
|
||||
) {
|
||||
initParticle(i);
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!canvasRef.value || !ctx.value || !particleProps.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const context = ctx.value;
|
||||
|
||||
tick.value++;
|
||||
|
||||
context.fillStyle = props.backgroundColor;
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < particleProps.value.length; i += PARTICLE_PROP_COUNT) {
|
||||
updateParticle(i);
|
||||
}
|
||||
|
||||
context.save();
|
||||
context.filter = "blur(8px) brightness(200%)";
|
||||
context.globalCompositeOperation = "lighter";
|
||||
context.drawImage(canvas, 0, 0);
|
||||
context.restore();
|
||||
|
||||
context.save();
|
||||
context.filter = "blur(4px) brightness(200%)";
|
||||
context.globalCompositeOperation = "lighter";
|
||||
context.drawImage(canvas, 0, 0);
|
||||
context.restore();
|
||||
|
||||
animationFrame.value = requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
const handleResize = useDebounceFn(() => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const { innerWidth, innerHeight } = window;
|
||||
canvas.width = innerWidth;
|
||||
canvas.height = innerHeight;
|
||||
center.value = [0.5 * canvas.width, 0.5 * canvas.height];
|
||||
}, 150);
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = canvasRef.value;
|
||||
if (!canvas) return;
|
||||
|
||||
ctx.value = canvas.getContext("2d");
|
||||
if (!ctx.value) return;
|
||||
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
center.value = [0.5 * canvas.width, 0.5 * canvas.height];
|
||||
|
||||
const particlePropsLength = props.particleCount * PARTICLE_PROP_COUNT;
|
||||
particleProps.value = new Float32Array(particlePropsLength);
|
||||
|
||||
for (let i = 0; i < particlePropsLength; i += PARTICLE_PROP_COUNT) {
|
||||
initParticle(i);
|
||||
}
|
||||
|
||||
draw();
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (animationFrame.value) {
|
||||
cancelAnimationFrame(animationFrame.value);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
|
||||
ctx.value = null;
|
||||
particleProps.value = null;
|
||||
});
|
||||
</script>
|
||||
14
app/error.vue
Normal file
14
app/error.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ error?.statusCode }}</h1>
|
||||
<NuxtLink to="/">Go back home</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const props = defineProps({
|
||||
error: Object as () => NuxtError,
|
||||
})
|
||||
</script>
|
||||
12
app/layouts/components/contairner.vue
Normal file
12
app/layouts/components/contairner.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<UContainer class="w-full max-w-full mx-auto sm:px-0 lg:px-0">
|
||||
<slot />
|
||||
</UContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
58
app/layouts/components/footer.vue
Normal file
58
app/layouts/components/footer.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
<template>
|
||||
<UFooter>
|
||||
<template #left>
|
||||
<p class="text-muted text-sm">Copyright © {{ new Date().getFullYear() }}</p>
|
||||
</template>
|
||||
|
||||
<UNavigationMenu :items="items" variant="link" />
|
||||
|
||||
<template #right>
|
||||
<!-- <UButton
|
||||
icon="i-simple-icons-discord"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="/"
|
||||
target="_blank"
|
||||
aria-label="Discord"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-simple-icons-x"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="/"
|
||||
target="_blank"
|
||||
aria-label="X"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-simple-icons-github"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="/"
|
||||
target="_blank"
|
||||
aria-label="GitHub"
|
||||
/> -->
|
||||
</template>
|
||||
</UFooter>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||
|
||||
const items: NavigationMenuItem[] = [
|
||||
{
|
||||
label: 'Figma Kit',
|
||||
to: 'https://go.nuxt.com/figma-ui',
|
||||
target: '_blank'
|
||||
},
|
||||
{
|
||||
label: 'Playground',
|
||||
to: 'https://stackblitz.com/edit/nuxt-ui',
|
||||
target: '_blank'
|
||||
},
|
||||
{
|
||||
label: 'Releases',
|
||||
to: 'https://github.com/nuxt/ui/releases',
|
||||
target: '_blank'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
54
app/layouts/components/header.vue
Normal file
54
app/layouts/components/header.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<UHeader>
|
||||
<template #title>
|
||||
<div class="h-6 w-auto" />
|
||||
</template>
|
||||
|
||||
<UNavigationMenu :items="items" />
|
||||
|
||||
<template #right>
|
||||
<UColorModeButton />
|
||||
|
||||
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
to="https://github.com/nuxt/ui"
|
||||
target="_blank"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const items = computed<NavigationMenuItem[]>(() => [
|
||||
{
|
||||
label: 'Docs',
|
||||
to: 'https://go.nuxt.com/figma-ui',
|
||||
active: route.path.startsWith('/docs/getting-started')
|
||||
},
|
||||
{
|
||||
label: 'Components',
|
||||
to: 'https://go.nuxt.com/figma-ui',
|
||||
active: route.path.startsWith('/docs/components')
|
||||
},
|
||||
{
|
||||
label: 'Figma',
|
||||
to: 'https://go.nuxt.com/figma-ui',
|
||||
target: '_blank'
|
||||
},
|
||||
{
|
||||
label: 'Releases',
|
||||
to: 'https://github.com/nuxt/ui/releases',
|
||||
target: '_blank'
|
||||
}
|
||||
])
|
||||
|
||||
</script>
|
||||
|
||||
15
app/layouts/default.vue
Normal file
15
app/layouts/default.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="w-full h-screen">
|
||||
<Header />
|
||||
<Contairner >
|
||||
<slot />
|
||||
</Contairner>
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import Header from './components/header.vue'
|
||||
import Footer from './components/footer.vue'
|
||||
import Contairner from './components/contairner.vue';
|
||||
|
||||
</script>
|
||||
7
app/lib/utils.ts
Normal file
7
app/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ClassValue } from "clsx"
|
||||
import { clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
32
app/pages/dev/index.vue
Normal file
32
app/pages/dev/index.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<!-- <GoodsWrapper>
|
||||
<GoodsItem
|
||||
|
||||
/>
|
||||
</GoodsWrapper> -->
|
||||
<GoodsVirtualList />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import SearchFilter from '@/components/search-filter/index.vue'
|
||||
// import GoodsWrapper from '@/components/goods-wrapper/index.vue'
|
||||
// import GoodsItem from '@/components/goods-item/index.vue'
|
||||
// import Adv from '@/components/adv/index.vue'
|
||||
import GoodsVirtualList from '@/components/goods-virtual-list/index.vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '开发',
|
||||
})
|
||||
|
||||
|
||||
const listData = Array.from({ length:100 }).fill(1).map((item, index) => {
|
||||
return {
|
||||
id: index,
|
||||
name: `商品${index}`,
|
||||
price: Math.floor(Math.random() * 1000),
|
||||
img: 'https://picsum.photos/200/300',
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
11
app/pages/index/index.vue
Normal file
11
app/pages/index/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="w-full h-screen">
|
||||
村上春树
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'default'
|
||||
})
|
||||
</script>
|
||||
21
components.json
Normal file
21
components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/assets/css/global.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"composables": "@/composables"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
32
nuxt.config.ts
Normal file
32
nuxt.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
css: [
|
||||
'./app/assets/css/main.css'
|
||||
],
|
||||
vite:{
|
||||
plugins:[
|
||||
tailwindcss()
|
||||
]
|
||||
},
|
||||
modules: [
|
||||
'@nuxt/ui',
|
||||
'motion-v/nuxt',
|
||||
],
|
||||
// 字体
|
||||
fonts:{
|
||||
// 切换google 字体源
|
||||
provider: 'bunny'
|
||||
},
|
||||
router:{
|
||||
// 默认路由模式
|
||||
// options:{
|
||||
// hashMode: true
|
||||
// }
|
||||
},
|
||||
ssr:false
|
||||
})
|
||||
38
package.json
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "app-pc",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "^4.1.0",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.552.0",
|
||||
"motion-v": "^1.7.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"nuxt": "^4.2.0",
|
||||
"reka-ui": "^2.6.0",
|
||||
"shared": "workspace:*",
|
||||
"simplex-noise": "^4.0.3",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"types": "workspace:*"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
4
packages/shared/package.json
Normal file
4
packages/shared/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "shared",
|
||||
"main": "src"
|
||||
}
|
||||
1
packages/shared/src/index.ts
Normal file
1
packages/shared/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {}
|
||||
4
packages/types/package.json
Normal file
4
packages/types/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "types",
|
||||
"main": "src"
|
||||
}
|
||||
1
packages/types/src/common/index.d.ts
vendored
Normal file
1
packages/types/src/common/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './option.d'
|
||||
10
packages/types/src/common/option.d.ts
vendored
Normal file
10
packages/types/src/common/option.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface Option<T = any> {
|
||||
label: string;
|
||||
value:T
|
||||
}
|
||||
|
||||
export interface TreeOption<T = any> {
|
||||
label: string;
|
||||
value:T;
|
||||
children?: TreeOption<T>[];
|
||||
}
|
||||
1
packages/types/src/index.ts
Normal file
1
packages/types/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './common'
|
||||
8006
pnpm-lock.yaml
generated
Normal file
8006
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/images/goods-item/discount.png
Normal file
BIN
public/images/goods-item/discount.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/noData.png
Normal file
BIN
public/images/noData.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
BIN
public/images/sale.png
Normal file
BIN
public/images/sale.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 346 B |
BIN
public/images/sideTopBanner1.jpg
Normal file
BIN
public/images/sideTopBanner1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
BIN
public/images/sideTopBanner2.jpg
Normal file
BIN
public/images/sideTopBanner2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
0
tailwind.config.ts
Normal file
0
tailwind.config.ts
Normal file
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user