@neatui/nuxt 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BUILD.md +128 -0
- package/IMPORT_GUIDE.md +142 -0
- package/README.md +98 -1
- package/SSR_COMPATIBILITY.md +201 -0
- package/USAGE.md +291 -0
- package/nuxt.config.example.ts +37 -0
- package/package.json +34 -11
- package/src/components/basic/IDraggable.vue +87 -65
- package/src/components/basic/IFollowView.vue +32 -23
- package/src/components/basic/IRouterView.vue +38 -23
- package/src/components/basic/IScrollView.vue +11 -7
- package/src/components/basic/Icon.vue +17 -17
- package/src/components/basic/LayerView/Layer.vue +33 -106
- package/src/components/basic/follow.ts +133 -0
- package/src/components/display/Calendar.vue +14 -14
- package/src/components/display/CalendarReg.vue +14 -14
- package/src/components/display/Image.vue +8 -8
- package/src/components/display/PhotoEditor.vue +36 -36
- package/src/components/display/PhotoViewer.vue +8 -8
- package/src/components/display/Tree.vue +6 -6
- package/src/components/display/TreeView.vue +4 -4
- package/src/components/display/index.ts +2 -2
- package/src/components/form/Cascader.vue +19 -19
- package/src/components/form/Checkbox.vue +64 -0
- package/src/components/form/DatePicker.vue +6 -7
- package/src/components/form/DateRangePicker@v3.vue +4 -4
- package/src/components/form/DateRangeView@v3.vue +18 -19
- package/src/components/form/DateView.vue +14 -14
- package/src/components/form/DateView@v2.vue +14 -14
- package/src/components/form/DateView@v3.vue +331 -318
- package/src/components/form/ImgUpload.vue +7 -7
- package/src/components/form/Input@v3.vue +11 -11
- package/src/components/form/MoreSelect@v3.vue +87 -17
- package/src/components/form/MoreSelectList.vue +8 -8
- package/src/components/form/MoreSelectPanel@v3.vue +3 -3
- package/src/components/form/MoreSelectPicker.vue +7 -7
- package/src/components/form/MoreSelectTags.vue +8 -8
- package/src/components/form/PageMoreSelect.vue +14 -14
- package/src/components/form/PageSelect.vue +16 -16
- package/src/components/form/SearchMoreSelect.vue +12 -12
- package/src/components/form/SearchSelect@v3.vue +3 -3
- package/src/components/form/Select@v3.vue +229 -23
- package/src/components/form/SelectList.vue +8 -8
- package/src/components/form/SelectPicker.vue +6 -6
- package/src/components/form/SelectTags.vue +7 -7
- package/src/components/form/SelectTree/SelectTree@v1.vue +5 -5
- package/src/components/form/Switch.vue +38 -103
- package/src/components/form/TextArea.vue +18 -18
- package/src/components/form/Textarea@v2.vue +275 -0
- package/src/components/form/TimeView.vue +14 -14
- package/src/components/form/Upload.vue +806 -297
- package/src/components/form/date.ts +321 -0
- package/src/components/form/index.ts +7 -5
- package/src/components/form/number.ts +3 -0
- package/src/components/form/type.ts +224 -0
- package/src/components/icon/OrderIcon.vue +113 -0
- package/src/components/loader/FormLoader/FormLoader@v2.vue +193 -195
- package/src/components/loader/FormLoader/FormLoader@v3.vue.backup +372 -291
- package/src/components/loader/FormLoader/FormRender@v3.vue.backup +4 -0
- package/src/components/loader/FormLoader/NodeLoader.vue +85 -0
- package/src/components/loader/FormLoader@v1/FormLoader.vue +1 -1
- package/src/components/loader/FormLoader@v1/FormRender.vue +49 -24
- package/src/components/loader/LayerLoader/LayerLoader.vue +318 -0
- package/src/components/loader/LayerLoader/index.ts +2 -0
- package/src/components/loader/LayerLoader/style.scss +77 -0
- package/src/components/loader/LimitLoader/LimitLoader@v3.vue +39 -28
- package/src/components/loader/MoveLoader/MoveLoader.vue +628 -0
- package/src/components/loader/MoveLoader/index.ts +2 -0
- package/src/components/loader/MoveLoader/style.scss +77 -0
- package/src/components/loader/TableLoader/TableLoader.vue +227 -195
- package/src/components/loader/TableLoader/TableRender.vue +141 -0
- package/src/components/loader/TableLoader/index.ts +47 -0
- package/src/components/loader/index.ts +3 -2
- package/src/components/tools/Pagination@a.vue +17 -18
- package/src/components/tools/Pagination@b.vue +16 -16
- package/src/index.ts +2 -1
- package/src/module.ts +169 -0
- package/src/runtime/types.d.ts +36 -0
- package/src/store/{myui.ts → frame.ts} +4 -4
- package/src/utils/theme.ts +14 -0
- package/tsconfig.json +1 -1
- package/src/components/loader/FormLoader/index.ts +0 -2
- package/src/components/loader/LimitLoader/LimitLoader.vue.backup +0 -131
- package/src/components/loader/LimitLoader/LimitLoader@v2.vue.backup +0 -174
- package/src/components/loader/TableLoader/TableColView.vue +0 -115
package/USAGE.md
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
# @neatui/nuxt 使用指南
|
2
|
+
|
3
|
+
## 📦 安装
|
4
|
+
|
5
|
+
```bash
|
6
|
+
npm install @neatui/nuxt
|
7
|
+
```
|
8
|
+
|
9
|
+
## ⚙️ 配置
|
10
|
+
|
11
|
+
在 `nuxt.config.ts` 中添加模块:
|
12
|
+
|
13
|
+
```ts
|
14
|
+
export default defineNuxtConfig({
|
15
|
+
modules: [
|
16
|
+
'@neatui/nuxt'
|
17
|
+
],
|
18
|
+
neatui: {
|
19
|
+
prefix: 'Neat', // 组件前缀
|
20
|
+
theme: 'default' // 主题
|
21
|
+
}
|
22
|
+
})
|
23
|
+
```
|
24
|
+
|
25
|
+
## 🎯 基础使用
|
26
|
+
|
27
|
+
### 方式1: 自动导入 (推荐)
|
28
|
+
|
29
|
+
配置模块后,组件会自动注册,直接在模板中使用:
|
30
|
+
|
31
|
+
#### 表单组件
|
32
|
+
|
33
|
+
```vue
|
34
|
+
<template>
|
35
|
+
<div>
|
36
|
+
<!-- 输入框 -->
|
37
|
+
<NeatInput
|
38
|
+
v-model="form.name"
|
39
|
+
placeholder="请输入姓名"
|
40
|
+
:rules="[{ required: true, message: '姓名必填' }]"
|
41
|
+
/>
|
42
|
+
|
43
|
+
<!-- 选择器 -->
|
44
|
+
<NeatSelect
|
45
|
+
v-model="form.city"
|
46
|
+
:options="cityOptions"
|
47
|
+
placeholder="选择城市"
|
48
|
+
/>
|
49
|
+
|
50
|
+
<!-- 日期选择 -->
|
51
|
+
<NeatDatePicker
|
52
|
+
v-model="form.date"
|
53
|
+
placeholder="选择日期"
|
54
|
+
/>
|
55
|
+
|
56
|
+
<!-- 上传组件 -->
|
57
|
+
<NeatUpload
|
58
|
+
v-model="form.files"
|
59
|
+
:limit="3"
|
60
|
+
accept="image/*"
|
61
|
+
/>
|
62
|
+
|
63
|
+
<!-- 按钮 -->
|
64
|
+
<NeatButton @click="submit" type="primary">
|
65
|
+
提交
|
66
|
+
</NeatButton>
|
67
|
+
</div>
|
68
|
+
</template>
|
69
|
+
|
70
|
+
<script setup>
|
71
|
+
const form = reactive({
|
72
|
+
name: '',
|
73
|
+
city: '',
|
74
|
+
date: '',
|
75
|
+
files: []
|
76
|
+
})
|
77
|
+
|
78
|
+
const cityOptions = [
|
79
|
+
{ label: '北京', value: 'beijing' },
|
80
|
+
{ label: '上海', value: 'shanghai' },
|
81
|
+
{ label: '广州', value: 'guangzhou' }
|
82
|
+
]
|
83
|
+
|
84
|
+
const submit = () => {
|
85
|
+
console.log('提交数据:', form)
|
86
|
+
}
|
87
|
+
</script>
|
88
|
+
```
|
89
|
+
|
90
|
+
### 展示组件
|
91
|
+
|
92
|
+
```vue
|
93
|
+
<template>
|
94
|
+
<div>
|
95
|
+
<!-- 表格组件 -->
|
96
|
+
<NeatTableLoader
|
97
|
+
:data="tableData"
|
98
|
+
:columns="columns"
|
99
|
+
:loading="loading"
|
100
|
+
/>
|
101
|
+
|
102
|
+
<!-- 分页组件 -->
|
103
|
+
<NeatPageSelect
|
104
|
+
v-model="currentPage"
|
105
|
+
:total="total"
|
106
|
+
:page-size="pageSize"
|
107
|
+
/>
|
108
|
+
</div>
|
109
|
+
</template>
|
110
|
+
|
111
|
+
<script setup>
|
112
|
+
const tableData = ref([])
|
113
|
+
const loading = ref(false)
|
114
|
+
const currentPage = ref(1)
|
115
|
+
const total = ref(0)
|
116
|
+
const pageSize = ref(10)
|
117
|
+
|
118
|
+
const columns = [
|
119
|
+
{ key: 'name', title: '姓名' },
|
120
|
+
{ key: 'age', title: '年龄' },
|
121
|
+
{ key: 'city', title: '城市' }
|
122
|
+
]
|
123
|
+
</script>
|
124
|
+
```
|
125
|
+
|
126
|
+
### 表单构建器
|
127
|
+
|
128
|
+
```vue
|
129
|
+
<template>
|
130
|
+
<div>
|
131
|
+
<!-- 动态表单 -->
|
132
|
+
<NeatFormLoader
|
133
|
+
v-model="formData"
|
134
|
+
:form="formConfig"
|
135
|
+
:rows="2"
|
136
|
+
/>
|
137
|
+
</div>
|
138
|
+
</template>
|
139
|
+
|
140
|
+
<script setup>
|
141
|
+
const formData = ref({})
|
142
|
+
|
143
|
+
const formConfig = [
|
144
|
+
{
|
145
|
+
field: 'name',
|
146
|
+
label: '姓名',
|
147
|
+
model: ['Input', { placeholder: '请输入姓名' }],
|
148
|
+
rules: [{ required: true, message: '姓名必填' }],
|
149
|
+
media: 'col-6'
|
150
|
+
},
|
151
|
+
{
|
152
|
+
field: 'email',
|
153
|
+
label: '邮箱',
|
154
|
+
model: ['Input', { type: 'email', placeholder: '请输入邮箱' }],
|
155
|
+
rules: [{ required: true, type: 'email', message: '邮箱格式错误' }],
|
156
|
+
media: 'col-6'
|
157
|
+
},
|
158
|
+
{
|
159
|
+
field: 'city',
|
160
|
+
label: '城市',
|
161
|
+
model: ['Select', { placeholder: '选择城市' }],
|
162
|
+
enums: [
|
163
|
+
{ label: '北京', value: 'beijing' },
|
164
|
+
{ label: '上海', value: 'shanghai' }
|
165
|
+
],
|
166
|
+
media: 'col-12'
|
167
|
+
}
|
168
|
+
]
|
169
|
+
</script>
|
170
|
+
```
|
171
|
+
|
172
|
+
### 方式2: 手动导入
|
173
|
+
|
174
|
+
如果不想使用自动导入,可以手动导入组件:
|
175
|
+
|
176
|
+
```vue
|
177
|
+
<template>
|
178
|
+
<div>
|
179
|
+
<Textarea v-model="content" placeholder="请输入内容" />
|
180
|
+
<Input v-model="name" placeholder="请输入姓名" />
|
181
|
+
<Select v-model="city" :options="cities" />
|
182
|
+
</div>
|
183
|
+
</template>
|
184
|
+
|
185
|
+
<script setup>
|
186
|
+
// 手动导入需要的组件
|
187
|
+
import { Textarea, Input, Select } from '@neatui/nuxt/components'
|
188
|
+
|
189
|
+
const content = ref('')
|
190
|
+
const name = ref('')
|
191
|
+
const city = ref('')
|
192
|
+
const cities = [
|
193
|
+
{ label: '北京', value: 'beijing' },
|
194
|
+
{ label: '上海', value: 'shanghai' }
|
195
|
+
]
|
196
|
+
</script>
|
197
|
+
```
|
198
|
+
|
199
|
+
## 🎨 主题定制
|
200
|
+
|
201
|
+
### 使用CSS变量
|
202
|
+
|
203
|
+
```ts
|
204
|
+
// nuxt.config.ts
|
205
|
+
export default defineNuxtConfig({
|
206
|
+
neatui: {
|
207
|
+
css: {
|
208
|
+
variables: {
|
209
|
+
'primary-color': '#007bff',
|
210
|
+
'border-radius': '6px',
|
211
|
+
'font-size': '14px',
|
212
|
+
'spacing-sm': '8px',
|
213
|
+
'spacing-md': '16px'
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
})
|
218
|
+
```
|
219
|
+
|
220
|
+
### 自定义主题
|
221
|
+
|
222
|
+
```css
|
223
|
+
/* assets/css/custom-theme.css */
|
224
|
+
:root {
|
225
|
+
--neat-primary-color: #007bff;
|
226
|
+
--neat-success-color: #28a745;
|
227
|
+
--neat-warning-color: #ffc107;
|
228
|
+
--neat-danger-color: #dc3545;
|
229
|
+
--neat-border-radius: 6px;
|
230
|
+
}
|
231
|
+
```
|
232
|
+
|
233
|
+
## 🔧 高级配置
|
234
|
+
|
235
|
+
### 按需导入
|
236
|
+
|
237
|
+
```ts
|
238
|
+
// nuxt.config.ts
|
239
|
+
export default defineNuxtConfig({
|
240
|
+
neatui: {
|
241
|
+
// 只导入需要的组件
|
242
|
+
exclude: [
|
243
|
+
'SomeHeavyComponent',
|
244
|
+
'AnotherComponent'
|
245
|
+
]
|
246
|
+
}
|
247
|
+
})
|
248
|
+
```
|
249
|
+
|
250
|
+
### 自定义前缀
|
251
|
+
|
252
|
+
```ts
|
253
|
+
// nuxt.config.ts
|
254
|
+
export default defineNuxtConfig({
|
255
|
+
neatui: {
|
256
|
+
prefix: 'My' // 组件将变成 <MyButton>, <MyInput>
|
257
|
+
}
|
258
|
+
})
|
259
|
+
```
|
260
|
+
|
261
|
+
## 📱 SSR 注意事项
|
262
|
+
|
263
|
+
某些组件可能需要客户端渲染:
|
264
|
+
|
265
|
+
```vue
|
266
|
+
<template>
|
267
|
+
<div>
|
268
|
+
<!-- 对于依赖浏览器API的组件,使用 ClientOnly -->
|
269
|
+
<ClientOnly>
|
270
|
+
<NeatUpload v-model="files" />
|
271
|
+
<template #fallback>
|
272
|
+
<div>加载中...</div>
|
273
|
+
</template>
|
274
|
+
</ClientOnly>
|
275
|
+
</div>
|
276
|
+
</template>
|
277
|
+
```
|
278
|
+
|
279
|
+
## 🐛 常见问题
|
280
|
+
|
281
|
+
### Q: 组件不显示?
|
282
|
+
A: 确保已正确安装并在 nuxt.config.ts 中配置模块
|
283
|
+
|
284
|
+
### Q: 类型提示不完整?
|
285
|
+
A: 重启 TypeScript 服务或重新生成类型
|
286
|
+
|
287
|
+
### Q: 样式没有加载?
|
288
|
+
A: 检查是否正确导入了样式文件
|
289
|
+
|
290
|
+
### Q: SSR 报错?
|
291
|
+
A: 使用 `<ClientOnly>` 包裹客户端专用组件
|
@@ -0,0 +1,37 @@
|
|
1
|
+
// nuxt.config.ts 使用示例
|
2
|
+
export default defineNuxtConfig({
|
3
|
+
modules: [
|
4
|
+
'@neatui/nuxt'
|
5
|
+
],
|
6
|
+
|
7
|
+
// NeatUI 配置
|
8
|
+
neatui: {
|
9
|
+
// 组件前缀 (可选)
|
10
|
+
prefix: 'Neat', // 组件将以 <NeatButton>, <NeatInput> 等形式使用
|
11
|
+
|
12
|
+
// 主题设置 (可选)
|
13
|
+
theme: 'default', // 'default' | 'dark' | 自定义主题名
|
14
|
+
|
15
|
+
// 自动导入开关 (可选)
|
16
|
+
autoImport: true, // 是否自动导入组件
|
17
|
+
|
18
|
+
// 排除特定组件 (可选)
|
19
|
+
exclude: [
|
20
|
+
// 'SomeComponent' // 不自动导入的组件名
|
21
|
+
],
|
22
|
+
|
23
|
+
// CSS 变量自定义 (可选)
|
24
|
+
css: {
|
25
|
+
variables: {
|
26
|
+
// 'primary-color': '#007bff',
|
27
|
+
// 'border-radius': '4px',
|
28
|
+
// 'font-size': '14px'
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
|
33
|
+
// 如果需要手动添加样式 (通常不需要,模块会自动处理)
|
34
|
+
css: [
|
35
|
+
// '@neatui/nuxt/dist/style.css'
|
36
|
+
]
|
37
|
+
})
|
package/package.json
CHANGED
@@ -1,33 +1,56 @@
|
|
1
1
|
{
|
2
2
|
"name": "@neatui/nuxt",
|
3
|
-
"version": "0.1
|
4
|
-
"description": "
|
5
|
-
"main": "./src/
|
3
|
+
"version": "1.0.1",
|
4
|
+
"description": "NeatUI component library for Nuxt 3",
|
5
|
+
"main": "./src/module.ts",
|
6
|
+
"types": "./src/module.ts",
|
7
|
+
"exports": {
|
8
|
+
".": {
|
9
|
+
"types": "./src/module.ts",
|
10
|
+
"import": "./src/module.ts",
|
11
|
+
"require": "./src/module.ts"
|
12
|
+
},
|
13
|
+
"./components": {
|
14
|
+
"types": "./src/index.ts",
|
15
|
+
"import": "./src/index.ts",
|
16
|
+
"require": "./src/index.ts"
|
17
|
+
}
|
18
|
+
},
|
6
19
|
"license": "MIT",
|
7
20
|
"author": "xiaojunbo",
|
21
|
+
"keywords": ["nuxt", "nuxt-module", "vue", "ui", "components"],
|
8
22
|
"peerDependencies": {
|
23
|
+
"@nuxt/kit": "^3.0.0",
|
24
|
+
"nuxt": "^3.0.0",
|
9
25
|
"vue": "^3.2.0"
|
10
26
|
},
|
11
27
|
"scripts": {
|
12
|
-
"
|
28
|
+
"dev": "nuxi dev playground",
|
29
|
+
"dev:prepare": "nuxi prepare playground",
|
30
|
+
"release": "npm publish",
|
31
|
+
"lint": "echo 'No lint configured'",
|
32
|
+
"test": "echo 'No tests configured'"
|
13
33
|
},
|
14
34
|
"devDependencies": {
|
35
|
+
"@nuxt/kit": "^3.0.0",
|
36
|
+
"@nuxt/schema": "^3.0.0",
|
15
37
|
"@rollup/plugin-typescript": "^11.1.6",
|
38
|
+
"@types/sortablejs": "^1.15.8",
|
39
|
+
"nuxt": "^3.0.0",
|
16
40
|
"postcss-scss": "^4.0.9",
|
17
41
|
"rollup-plugin-scss": "^4.0.0",
|
18
42
|
"rollup-plugin-terser": "^7.0.2",
|
19
43
|
"typescript": "^5.1.6"
|
20
44
|
},
|
21
45
|
"dependencies": {
|
22
|
-
"@fekit/floor": "^
|
23
|
-
"@fekit/follow": "^1.0
|
46
|
+
"@fekit/floor": "^3.0.1",
|
47
|
+
"@fekit/follow": "^1.7.0",
|
24
48
|
"@fekit/itable": "^1.1.6",
|
25
49
|
"@fekit/pullload": "^1.3.5",
|
26
50
|
"@fekit/scale": "^2.0.1",
|
27
51
|
"@fekit/scrollto": "^3.0.3",
|
28
|
-
"@fekit/toast": "^2.2
|
29
|
-
"@fekit/
|
30
|
-
"@fekit/utils": "^2.10.21",
|
52
|
+
"@fekit/toast": "^2.7.2",
|
53
|
+
"@fekit/utils": "^4.3.1",
|
31
54
|
"@rollup/plugin-commonjs": "^26.0.1",
|
32
55
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
33
56
|
"@vue/compiler-sfc": "^3.4.30",
|
@@ -36,8 +59,8 @@
|
|
36
59
|
"rollup": "^4.18.0",
|
37
60
|
"rollup-plugin-postcss": "^4.0.2",
|
38
61
|
"rollup-plugin-vue": "^6.0.0",
|
62
|
+
"sortablejs": "^1.15.6",
|
39
63
|
"swiper": "^11.0.4",
|
40
|
-
"vue-cropper": "^0.6.4"
|
41
|
-
"vuedraggable": "^4.1.0"
|
64
|
+
"vue-cropper": "^0.6.4"
|
42
65
|
}
|
43
66
|
}
|
@@ -1,84 +1,106 @@
|
|
1
1
|
<template>
|
2
|
-
<ul>
|
3
|
-
<li
|
4
|
-
v-for="(item, idx) in list || []"
|
5
|
-
:key="idx"
|
6
|
-
class="pr drag-page-move n-sm"
|
7
|
-
draggable="true"
|
8
|
-
@dragstart.self="fMoveSta($event, list, idx)"
|
9
|
-
@dragend.self="fMoveEnd($event, list, idx)"
|
10
|
-
@dragenter.self="fMoveInt($event, list, idx)"
|
11
|
-
@dragover.prevent="fMoveIng($event)"
|
12
|
-
@dragleave="fMoveOut($event)"
|
13
|
-
>
|
2
|
+
<ul ref="listRef" class="drag-list">
|
3
|
+
<li v-for="(item, idx) in props.lists" :key="item.id ?? item.field ?? idx" class="pr drag-page-move">
|
14
4
|
<slot :item="item" :idx="idx">{{ item.label }}</slot>
|
15
5
|
</li>
|
16
6
|
</ul>
|
17
7
|
</template>
|
18
8
|
|
19
9
|
<script setup lang="ts">
|
20
|
-
import {
|
10
|
+
import { ref, PropType, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
11
|
+
import Sortable, { SortableEvent } from 'sortablejs';
|
12
|
+
|
21
13
|
const props = defineProps({
|
22
|
-
|
14
|
+
lists: {
|
23
15
|
type: Array as PropType<any[]>,
|
24
|
-
default: () => []
|
25
|
-
}
|
16
|
+
default: () => [],
|
17
|
+
},
|
26
18
|
});
|
27
|
-
const
|
19
|
+
const emit = defineEmits(['update:lists']);
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
21
|
+
const listRef = ref<HTMLElement | null>(null);
|
22
|
+
let sortable: Sortable | null = null;
|
23
|
+
|
24
|
+
// 添加调试日志
|
25
|
+
watch(
|
26
|
+
() => props.lists,
|
27
|
+
(newVal, oldVal) => {
|
28
|
+
// console.log('IDraggable props.lists changed:', {
|
29
|
+
// newLength: newVal?.length,
|
30
|
+
// oldLength: oldVal?.length,
|
31
|
+
// newVal: newVal?.map((item) => item.label || item.field),
|
32
|
+
// oldVal: oldVal?.map((item) => item.label || item.field)
|
33
|
+
// });
|
34
|
+
},
|
35
|
+
{ deep: true },
|
36
|
+
);
|
37
|
+
|
38
|
+
onMounted(() => {
|
39
|
+
nextTick(() => {
|
40
|
+
if (listRef.value) {
|
41
|
+
sortable = Sortable.create(listRef.value, {
|
42
|
+
animation: 200,
|
43
|
+
ghostClass: 'drag-ghost',
|
44
|
+
chosenClass: 'drag-chosen',
|
45
|
+
dragClass: 'drag-dragging',
|
46
|
+
onEnd(evt: SortableEvent) {
|
47
|
+
console.log('Sortable onEnd:', { oldIndex: evt.oldIndex, newIndex: evt.newIndex });
|
48
|
+
|
49
|
+
if (evt.oldIndex !== undefined && evt.newIndex !== undefined && evt.oldIndex !== evt.newIndex) {
|
50
|
+
// 创建新的数组顺序
|
51
|
+
const newList = [...props.lists];
|
52
|
+
const moved = newList.splice(evt.oldIndex, 1)[0];
|
53
|
+
newList.splice(evt.newIndex, 0, moved);
|
54
|
+
|
55
|
+
console.log(
|
56
|
+
'Emitting new list:',
|
57
|
+
newList.map((item) => item.label || item.field),
|
58
|
+
);
|
59
|
+
|
60
|
+
// 通知父组件更新数据
|
61
|
+
emit('update:lists', newList);
|
62
|
+
}
|
63
|
+
},
|
64
|
+
});
|
70
65
|
}
|
71
|
-
|
66
|
+
});
|
67
|
+
});
|
68
|
+
|
69
|
+
onBeforeUnmount(() => {
|
70
|
+
if (sortable) {
|
71
|
+
sortable.destroy();
|
72
|
+
sortable = null;
|
72
73
|
}
|
73
|
-
};
|
74
|
+
});
|
74
75
|
</script>
|
76
|
+
|
75
77
|
<style lang="scss">
|
78
|
+
.drag-list {
|
79
|
+
list-style: none;
|
80
|
+
padding: 0;
|
81
|
+
margin: 0;
|
82
|
+
}
|
76
83
|
.drag-page-move {
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
84
|
+
cursor: move;
|
85
|
+
transition: background-color 0.2s ease;
|
86
|
+
|
87
|
+
&:hover {
|
88
|
+
background: rgba(0, 0, 0, 0.02);
|
82
89
|
}
|
83
90
|
}
|
91
|
+
.drag-ghost {
|
92
|
+
opacity: 0.4;
|
93
|
+
background: rgba(0, 0, 0, 0.05);
|
94
|
+
border: 2px dashed #ddd;
|
95
|
+
}
|
96
|
+
.drag-chosen {
|
97
|
+
background: rgba(24, 144, 255, 0.08);
|
98
|
+
}
|
99
|
+
.drag-dragging {
|
100
|
+
opacity: 0.8;
|
101
|
+
background: #fff;
|
102
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
103
|
+
border-radius: 4px;
|
104
|
+
z-index: 1000;
|
105
|
+
}
|
84
106
|
</style>
|
@@ -3,23 +3,24 @@
|
|
3
3
|
<slot>
|
4
4
|
<button ui-btn="@a s case :border"><i class="icon icon-dropdown"></i></button>
|
5
5
|
</slot>
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
6
|
+
<Teleport to="#ifollow" v-if="$slots.tips">
|
7
|
+
<div
|
8
|
+
ref="tip"
|
9
|
+
:class="tipClass"
|
10
|
+
:style="`position: fixed; pointer-events: none; ${tipStyle}`"
|
11
|
+
ui-tips="@a"
|
12
|
+
:ui-tips-view="state.show ? 1 : 0"
|
13
|
+
@mouseover="hovershow"
|
14
|
+
@mouseout="hoverhide"
|
15
|
+
@click="clickhide"
|
16
|
+
>
|
17
|
+
<div ref="box" :ui-tips-box="state.pos" :class="tipBoxClass" :style="tipBoxStyle" @mousedown="downdom">
|
18
|
+
<slot name="tips"></slot>
|
19
|
+
<div v-if="arrow" ui-tips-arrow="" v-bind="arrow"></div>
|
20
|
+
</div>
|
20
21
|
</div>
|
21
|
-
</
|
22
|
-
</
|
22
|
+
</Teleport>
|
23
|
+
</div>
|
23
24
|
</template>
|
24
25
|
<script setup lang="ts">
|
25
26
|
import { reactive, ref, onMounted, onUnmounted, watch } from 'vue';
|
@@ -46,6 +47,7 @@
|
|
46
47
|
tipBoxClass?: string;
|
47
48
|
tipBoxStyle?: string;
|
48
49
|
arrow?: any;
|
50
|
+
clickTipHide?: boolean;
|
49
51
|
}
|
50
52
|
|
51
53
|
// 入参
|
@@ -54,12 +56,13 @@
|
|
54
56
|
pos: 'bl',
|
55
57
|
event: 'click',
|
56
58
|
btnClass: 'dib',
|
57
|
-
btnStyle: '
|
59
|
+
btnStyle: '',
|
58
60
|
tipClass: '',
|
59
61
|
tipStyle: '',
|
60
62
|
tipBoxClass: '',
|
61
63
|
tipBoxStyle: '',
|
62
|
-
arrow: {}
|
64
|
+
arrow: {},
|
65
|
+
clickTipHide: false,
|
63
66
|
});
|
64
67
|
|
65
68
|
const dom: any = ref(null);
|
@@ -70,7 +73,7 @@
|
|
70
73
|
show: 0,
|
71
74
|
pos: props.pos,
|
72
75
|
hover: 0,
|
73
|
-
tipdom: false
|
76
|
+
tipdom: false,
|
74
77
|
});
|
75
78
|
|
76
79
|
const showTimer: any = ref(null);
|
@@ -89,7 +92,7 @@
|
|
89
92
|
state.show = 0;
|
90
93
|
}, 100);
|
91
94
|
}
|
92
|
-
}
|
95
|
+
},
|
93
96
|
);
|
94
97
|
|
95
98
|
// 开关
|
@@ -109,7 +112,7 @@
|
|
109
112
|
}
|
110
113
|
emits('update:modelValue', state.show);
|
111
114
|
},
|
112
|
-
{ deep: true }
|
115
|
+
{ deep: true },
|
113
116
|
);
|
114
117
|
|
115
118
|
const hovershow = () => {
|
@@ -124,6 +127,12 @@
|
|
124
127
|
}
|
125
128
|
};
|
126
129
|
|
130
|
+
const clickhide = () => {
|
131
|
+
if (props.clickTipHide) {
|
132
|
+
state.show = 0;
|
133
|
+
}
|
134
|
+
};
|
135
|
+
|
127
136
|
const show = () => {
|
128
137
|
state.show = 1;
|
129
138
|
};
|
@@ -199,13 +208,13 @@
|
|
199
208
|
ex.value.remove();
|
200
209
|
}
|
201
210
|
}
|
202
|
-
}
|
211
|
+
},
|
203
212
|
);
|
204
213
|
|
205
214
|
defineExpose({
|
206
215
|
toggle,
|
207
216
|
show,
|
208
217
|
hide,
|
209
|
-
cancel
|
218
|
+
cancel,
|
210
219
|
});
|
211
220
|
</script>
|