officialblock 1.0.2 → 1.0.3

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/src/style.css CHANGED
@@ -59,9 +59,8 @@ button:focus-visible {
59
59
  }
60
60
 
61
61
  #app {
62
- max-width: 1280px;
63
- margin: 0 auto;
64
- padding: 2rem;
62
+ width: 100%;
63
+ /* margin: 0 auto; */
65
64
  }
66
65
 
67
66
  @media (prefers-color-scheme: light) {
@@ -47,7 +47,7 @@ $info-color: #3b82f6;
47
47
  // 现代灰色系
48
48
  $gray-50: #f9fafb;
49
49
  $gray-100: #f3f4f6;
50
- $gray-200: #e5e7eb;
50
+ $gray-200: #F7F7FA;
51
51
  $gray-300: #d1d5db;
52
52
  $gray-400: #9ca3af;
53
53
  $gray-500: #6b7280;
@@ -637,3 +637,33 @@ pre {
637
637
  .mt-10 {
638
638
  margin-top: 10px;
639
639
  }
640
+
641
+ .flex {
642
+ display: flex;
643
+ }
644
+
645
+ .items-center {
646
+ align-items: center;
647
+ }
648
+
649
+ .flex-inline {
650
+ display: inline-flex;
651
+ }
652
+
653
+ .flex-center {
654
+ display: flex;
655
+ align-items: center;
656
+ justify-content: center;
657
+ }
658
+
659
+ .justify-center {
660
+ justify-content: center;
661
+ }
662
+
663
+ .justify-end {
664
+ justify-content: flex-end;
665
+ }
666
+
667
+ .flex-1 {
668
+ flex: 1;
669
+ }
@@ -64,6 +64,9 @@
64
64
  <a-menu-item key="/components/hero-slide">HeroSlide 轮播图</a-menu-item>
65
65
  <a-menu-item key="/components/rich-text-editor">RichTextEditor 富文本编辑器</a-menu-item>
66
66
  <a-menu-item key="/components/theme">Theme 主题系统</a-menu-item>
67
+ <a-menu-item key="/components/sass">Sass 使用演示</a-menu-item>
68
+ <a-menu-item key="/components/drag-sort">拖拽排序演示</a-menu-item>
69
+ <a-menu-item key="/components/drag-limit">拖拽限制演示</a-menu-item>
67
70
  </a-sub-menu>
68
71
  </a-menu>
69
72
  </a-layout-sider>
@@ -192,7 +195,7 @@ const handleMenuClick = (key: string) => {
192
195
  .content {
193
196
  margin-left: 280px;
194
197
  padding: 32px;
195
- background: var(--color-bg-1);
198
+ background: #f3f4f6;
196
199
  min-height: calc(100vh - 60px);
197
200
  }
198
201
 
@@ -25,9 +25,8 @@ const handleDelete = () => {
25
25
 
26
26
  <style scoped>
27
27
  .demo-page {
28
- max-width: 1000px;
28
+ background: #fff;
29
29
  }
30
-
31
30
  .description {
32
31
  font-size: 16px;
33
32
  line-height: 1.6;
@@ -0,0 +1,573 @@
1
+ <template>
2
+ <div class="drag-limit-demo">
3
+ <div class="demo-header">
4
+ <h1>拖拽限制演示</h1>
5
+ <p>展示不同分组的拖拽限制和单项隐藏拖拽图标的功能</p>
6
+ </div>
7
+
8
+ <!-- 分组隔离演示 -->
9
+ <a-card title="分组隔离演示" class="demo-section">
10
+ <div class="group-isolation-demo">
11
+ <div class="drag-groups">
12
+ <div class="drag-group">
13
+ <h4>按钮组 (buttons)</h4>
14
+ <p class="group-desc">只能在按钮组内拖拽排序</p>
15
+ <draggable
16
+ v-model="buttonList"
17
+ :component-data="{
18
+ tag: 'div',
19
+ type: 'transition-group',
20
+ name: !drag ? 'flip-list' : null
21
+ }"
22
+ v-bind="buttonDragOptions"
23
+ :disabled="!shouldShowDragHandle(buttonList)"
24
+ @start="drag = true"
25
+ @end="drag = false"
26
+ item-key="id"
27
+ class="drag-area"
28
+ >
29
+ <template #item="{ element: button }">
30
+ <div
31
+ class="drag-item button-item"
32
+ :class="{ 'sortable-disabled': !shouldShowDragHandle(buttonList) }"
33
+ :key="button.id"
34
+ >
35
+ <div
36
+ v-if="shouldShowDragHandle(buttonList)"
37
+ class="drag-handle"
38
+ >
39
+ <icon-drag-arrow class="drag-icon" />
40
+ </div>
41
+ <div class="item-content">
42
+ <a-button type="primary">{{ button.text }}</a-button>
43
+ </div>
44
+ <button class="delete-btn" @click="removeItem(buttonList, button.id)">
45
+ <icon-delete />
46
+ </button>
47
+ </div>
48
+ </template>
49
+ </draggable>
50
+ <button class="add-btn" @click="addButton">
51
+ <icon-plus /> 添加按钮
52
+ </button>
53
+ </div>
54
+
55
+ <div class="drag-group">
56
+ <h4>链接组 (links)</h4>
57
+ <p class="group-desc">只能在链接组内拖拽排序</p>
58
+ <draggable
59
+ v-model="linkList"
60
+ :component-data="{
61
+ tag: 'div',
62
+ type: 'transition-group',
63
+ name: !drag ? 'flip-list' : null
64
+ }"
65
+ v-bind="linkDragOptions"
66
+ :disabled="!shouldShowDragHandle(linkList)"
67
+ @start="drag = true"
68
+ @end="drag = false"
69
+ item-key="id"
70
+ class="drag-area"
71
+ >
72
+ <template #item="{ element: link }">
73
+ <div
74
+ class="drag-item link-item"
75
+ :class="{ 'sortable-disabled': !shouldShowDragHandle(linkList) }"
76
+ :key="link.id"
77
+ >
78
+ <div
79
+ v-if="shouldShowDragHandle(linkList)"
80
+ class="drag-handle"
81
+ >
82
+ <icon-drag-arrow class="drag-icon" />
83
+ </div>
84
+ <div class="item-content">
85
+ <a-link :href="link.url">{{ link.text }}</a-link>
86
+ </div>
87
+ <button class="delete-btn" @click="removeItem(linkList, link.id)">
88
+ <icon-delete />
89
+ </button>
90
+ </div>
91
+ </template>
92
+ </draggable>
93
+ <button class="add-btn" @click="addLink">
94
+ <icon-plus /> 添加链接
95
+ </button>
96
+ </div>
97
+
98
+ <div class="drag-group">
99
+ <h4>分类组 (categories)</h4>
100
+ <p class="group-desc">只能在分类组内拖拽排序</p>
101
+ <draggable
102
+ v-model="categoryList"
103
+ :component-data="{
104
+ tag: 'div',
105
+ type: 'transition-group',
106
+ name: !drag ? 'flip-list' : null
107
+ }"
108
+ v-bind="categoryDragOptions"
109
+ :disabled="!shouldShowDragHandle(categoryList)"
110
+ @start="drag = true"
111
+ @end="drag = false"
112
+ item-key="id"
113
+ class="drag-area"
114
+ >
115
+ <template #item="{ element: category }">
116
+ <div
117
+ class="drag-item category-item"
118
+ :class="{ 'sortable-disabled': !shouldShowDragHandle(categoryList) }"
119
+ :key="category.id"
120
+ >
121
+ <div
122
+ v-if="shouldShowDragHandle(categoryList)"
123
+ class="drag-handle"
124
+ >
125
+ <icon-drag-arrow class="drag-icon" />
126
+ </div>
127
+ <div class="item-content">
128
+ <a-tag color="blue">{{ category.text }}</a-tag>
129
+ </div>
130
+ <button class="delete-btn" @click="removeItem(categoryList, category.id)">
131
+ <icon-delete />
132
+ </button>
133
+ </div>
134
+ </template>
135
+ </draggable>
136
+ <button class="add-btn" @click="addCategory">
137
+ <icon-plus /> 添加分类
138
+ </button>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </a-card>
143
+
144
+ <!-- 单项隐藏演示 -->
145
+ <a-card title="单项隐藏演示" class="demo-section">
146
+ <div class="single-item-demo">
147
+ <p class="demo-desc">当列表只有一项时,拖拽图标会自动隐藏,且不可拖拽</p>
148
+
149
+ <div class="single-item-groups">
150
+ <div class="single-group">
151
+ <h4>单个按钮 (无拖拽图标)</h4>
152
+ <draggable
153
+ v-model="singleButtonList"
154
+ v-bind="buttonDragOptions"
155
+ :disabled="!shouldShowDragHandle(singleButtonList)"
156
+ item-key="id"
157
+ class="drag-area"
158
+ >
159
+ <template #item="{ element: button }">
160
+ <div
161
+ class="drag-item button-item"
162
+ :class="{ 'sortable-disabled': !shouldShowDragHandle(singleButtonList) }"
163
+ :key="button.id"
164
+ >
165
+ <div
166
+ v-if="shouldShowDragHandle(singleButtonList)"
167
+ class="drag-handle"
168
+ >
169
+ <icon-drag-arrow class="drag-icon" />
170
+ </div>
171
+ <div class="item-content">
172
+ <a-button type="primary">{{ button.text }}</a-button>
173
+ </div>
174
+ <button class="delete-btn" @click="removeItem(singleButtonList, button.id)">
175
+ <icon-delete />
176
+ </button>
177
+ </div>
178
+ </template>
179
+ </draggable>
180
+ <button class="add-btn" @click="addSingleButton">
181
+ <icon-plus /> 添加按钮
182
+ </button>
183
+ </div>
184
+
185
+ <div class="single-group">
186
+ <h4>单个链接 (无拖拽图标)</h4>
187
+ <draggable
188
+ v-model="singleLinkList"
189
+ v-bind="linkDragOptions"
190
+ :disabled="!shouldShowDragHandle(singleLinkList)"
191
+ item-key="id"
192
+ class="drag-area"
193
+ >
194
+ <template #item="{ element: link }">
195
+ <div
196
+ class="drag-item link-item"
197
+ :class="{ 'sortable-disabled': !shouldShowDragHandle(singleLinkList) }"
198
+ :key="link.id"
199
+ >
200
+ <div
201
+ v-if="shouldShowDragHandle(singleLinkList)"
202
+ class="drag-handle"
203
+ >
204
+ <icon-drag-arrow class="drag-icon" />
205
+ </div>
206
+ <div class="item-content">
207
+ <a-link :href="link.url">{{ link.text }}</a-link>
208
+ </div>
209
+ <button class="delete-btn" @click="removeItem(singleLinkList, link.id)">
210
+ <icon-delete />
211
+ </button>
212
+ </div>
213
+ </template>
214
+ </draggable>
215
+ <button class="add-btn" @click="addSingleLink">
216
+ <icon-plus /> 添加链接
217
+ </button>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </a-card>
222
+
223
+ <!-- 功能说明 -->
224
+ <a-card title="功能说明" class="demo-section">
225
+ <div class="feature-explanation">
226
+ <h3>拖拽限制功能</h3>
227
+ <ul>
228
+ <li><strong>分组隔离</strong>:不同分组的列表项不能互相拖拽</li>
229
+ <li><strong>单项隐藏</strong>:当列表只有一项时,自动隐藏拖拽图标</li>
230
+ <li><strong>禁用拖拽</strong>:单项列表不可拖拽,避免无意义的拖拽操作</li>
231
+ <li><strong>视觉反馈</strong>:禁用状态下移除悬停效果</li>
232
+ </ul>
233
+
234
+ <h3>实现原理</h3>
235
+ <ul>
236
+ <li><strong>分组配置</strong>:为不同类型的列表设置不同的 group 名称</li>
237
+ <li><strong>条件渲染</strong>:使用 v-if 条件渲染拖拽图标</li>
238
+ <li><strong>动态禁用</strong>:根据列表长度动态设置 disabled 属性</li>
239
+ <li><strong>样式控制</strong>:通过 CSS 类控制拖拽状态的视觉效果</li>
240
+ </ul>
241
+ </div>
242
+ </a-card>
243
+ </div>
244
+ </template>
245
+
246
+ <script setup lang="ts">
247
+ import { ref, computed } from 'vue'
248
+ import draggable from 'vuedraggable'
249
+
250
+ // 拖拽状态
251
+ const drag = ref(false)
252
+
253
+ // 不同类型的拖拽配置选项
254
+ const buttonDragOptions = computed(() => ({
255
+ animation: 200,
256
+ group: 'buttons', // 按钮专用分组
257
+ disabled: false,
258
+ ghostClass: 'ghost'
259
+ }))
260
+
261
+ const linkDragOptions = computed(() => ({
262
+ animation: 200,
263
+ group: 'links', // 链接专用分组
264
+ disabled: false,
265
+ ghostClass: 'ghost'
266
+ }))
267
+
268
+ const categoryDragOptions = computed(() => ({
269
+ animation: 200,
270
+ group: 'categories', // 分类专用分组
271
+ disabled: false,
272
+ ghostClass: 'ghost'
273
+ }))
274
+
275
+ // 检查是否应该显示拖拽图标
276
+ const shouldShowDragHandle = (list: any[]) => {
277
+ return list && list.length > 1
278
+ }
279
+
280
+ // 分组列表数据
281
+ const buttonList = ref([
282
+ { id: 1, text: '按钮 1' },
283
+ { id: 2, text: '按钮 2' },
284
+ { id: 3, text: '按钮 3' }
285
+ ])
286
+
287
+ const linkList = ref([
288
+ { id: 1, text: '链接 1', url: 'https://example.com/1' },
289
+ { id: 2, text: '链接 2', url: 'https://example.com/2' }
290
+ ])
291
+
292
+ const categoryList = ref([
293
+ { id: 1, text: '分类 1' },
294
+ { id: 2, text: '分类 2' },
295
+ { id: 3, text: '分类 3' }
296
+ ])
297
+
298
+ // 单项列表数据
299
+ const singleButtonList = ref([
300
+ { id: 1, text: '单个按钮' }
301
+ ])
302
+
303
+ const singleLinkList = ref([
304
+ { id: 1, text: '单个链接', url: 'https://example.com' }
305
+ ])
306
+
307
+ // 添加项目的方法
308
+ const addButton = () => {
309
+ const newId = Math.max(...buttonList.value.map(item => item.id)) + 1
310
+ buttonList.value.push({
311
+ id: newId,
312
+ text: `按钮 ${newId}`
313
+ })
314
+ }
315
+
316
+ const addLink = () => {
317
+ const newId = Math.max(...linkList.value.map(item => item.id)) + 1
318
+ linkList.value.push({
319
+ id: newId,
320
+ text: `链接 ${newId}`,
321
+ url: `https://example.com/${newId}`
322
+ })
323
+ }
324
+
325
+ const addCategory = () => {
326
+ const newId = Math.max(...categoryList.value.map(item => item.id)) + 1
327
+ categoryList.value.push({
328
+ id: newId,
329
+ text: `分类 ${newId}`
330
+ })
331
+ }
332
+
333
+ const addSingleButton = () => {
334
+ const newId = Math.max(...singleButtonList.value.map(item => item.id)) + 1
335
+ singleButtonList.value.push({
336
+ id: newId,
337
+ text: `按钮 ${newId}`
338
+ })
339
+ }
340
+
341
+ const addSingleLink = () => {
342
+ const newId = Math.max(...singleLinkList.value.map(item => item.id)) + 1
343
+ singleLinkList.value.push({
344
+ id: newId,
345
+ text: `链接 ${newId}`,
346
+ url: `https://example.com/${newId}`
347
+ })
348
+ }
349
+
350
+ // 删除项目
351
+ const removeItem = (list: any[], id: number) => {
352
+ const index = list.findIndex(item => item.id === id)
353
+ if (index > -1) {
354
+ list.splice(index, 1)
355
+ }
356
+ }
357
+ </script>
358
+
359
+ <style lang="scss" scoped>
360
+ .drag-limit-demo {
361
+ padding: 24px;
362
+ max-width: 1200px;
363
+ margin: 0 auto;
364
+ }
365
+
366
+ .demo-header {
367
+ margin-bottom: 32px;
368
+ text-align: center;
369
+
370
+ h1 {
371
+ font-size: 32px;
372
+ font-weight: bold;
373
+ color: #1f2937;
374
+ margin-bottom: 8px;
375
+ }
376
+
377
+ p {
378
+ font-size: 16px;
379
+ color: #6b7280;
380
+ }
381
+ }
382
+
383
+ .demo-section {
384
+ margin-bottom: 32px;
385
+ }
386
+
387
+ // 分组演示样式
388
+ .drag-groups {
389
+ display: grid;
390
+ grid-template-columns: repeat(3, 1fr);
391
+ gap: 24px;
392
+
393
+ @media (max-width: 768px) {
394
+ grid-template-columns: 1fr;
395
+ }
396
+ }
397
+
398
+ .drag-group {
399
+ h4 {
400
+ margin-bottom: 8px;
401
+ font-weight: 600;
402
+ color: #1f2937;
403
+ }
404
+
405
+ .group-desc {
406
+ font-size: 12px;
407
+ color: #6b7280;
408
+ margin-bottom: 16px;
409
+ }
410
+ }
411
+
412
+ // 单项演示样式
413
+ .single-item-groups {
414
+ display: grid;
415
+ grid-template-columns: repeat(2, 1fr);
416
+ gap: 24px;
417
+
418
+ @media (max-width: 768px) {
419
+ grid-template-columns: 1fr;
420
+ }
421
+ }
422
+
423
+ .single-group {
424
+ h4 {
425
+ margin-bottom: 16px;
426
+ font-weight: 600;
427
+ color: #1f2937;
428
+ }
429
+ }
430
+
431
+ // 拖拽区域样式
432
+ .drag-area {
433
+ min-height: 80px;
434
+ padding: 16px;
435
+ border: 2px dashed #e5e7eb;
436
+ border-radius: 8px;
437
+ background-color: #f9fafb;
438
+ margin-bottom: 16px;
439
+ }
440
+
441
+ // 拖拽项样式
442
+ .drag-item {
443
+ display: flex;
444
+ align-items: center;
445
+ padding: 12px;
446
+ margin-bottom: 8px;
447
+ background: white;
448
+ border-radius: 6px;
449
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
450
+ cursor: move;
451
+ transition: all 0.3s ease;
452
+
453
+ &:hover {
454
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
455
+ transform: translateY(-1px);
456
+ }
457
+
458
+ &.sortable-disabled {
459
+ cursor: default;
460
+
461
+ &:hover {
462
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
463
+ transform: none;
464
+ }
465
+ }
466
+
467
+ .drag-handle {
468
+ margin-right: 12px;
469
+ cursor: grab;
470
+ color: #9ca3af;
471
+
472
+ &:active {
473
+ cursor: grabbing;
474
+ }
475
+
476
+ .drag-icon {
477
+ font-size: 16px;
478
+ }
479
+ }
480
+
481
+ .item-content {
482
+ flex: 1;
483
+ }
484
+
485
+ .delete-btn {
486
+ background: none;
487
+ border: none;
488
+ color: #ef4444;
489
+ cursor: pointer;
490
+ padding: 4px;
491
+ border-radius: 4px;
492
+
493
+ &:hover {
494
+ background-color: #fee2e2;
495
+ }
496
+ }
497
+ }
498
+
499
+ // 不同类型项目的样式
500
+ .button-item {
501
+ border-left: 4px solid #3b82f6;
502
+ }
503
+
504
+ .link-item {
505
+ border-left: 4px solid #10b981;
506
+ }
507
+
508
+ .category-item {
509
+ border-left: 4px solid #f59e0b;
510
+ }
511
+
512
+ // 添加按钮样式
513
+ .add-btn {
514
+ display: flex;
515
+ align-items: center;
516
+ gap: 8px;
517
+ padding: 8px 16px;
518
+ background: #3b82f6;
519
+ color: white;
520
+ border: none;
521
+ border-radius: 6px;
522
+ cursor: pointer;
523
+ font-weight: 500;
524
+ transition: background-color 0.3s ease;
525
+
526
+ &:hover {
527
+ background: #2563eb;
528
+ }
529
+ }
530
+
531
+ // 拖拽动画
532
+ .flip-list-move {
533
+ transition: transform 0.5s;
534
+ }
535
+
536
+ .ghost {
537
+ opacity: 0.5;
538
+ background: #c8ebfb;
539
+ }
540
+
541
+ // 功能说明样式
542
+ .demo-desc {
543
+ font-size: 14px;
544
+ color: #6b7280;
545
+ margin-bottom: 24px;
546
+ padding: 12px;
547
+ background-color: #f3f4f6;
548
+ border-radius: 6px;
549
+ }
550
+
551
+ .feature-explanation {
552
+ h3 {
553
+ margin-bottom: 16px;
554
+ font-weight: 600;
555
+ color: #1f2937;
556
+ }
557
+
558
+ ul {
559
+ margin-bottom: 24px;
560
+ padding-left: 24px;
561
+
562
+ li {
563
+ margin-bottom: 8px;
564
+ line-height: 1.6;
565
+
566
+ strong {
567
+ font-weight: 600;
568
+ color: #1f2937;
569
+ }
570
+ }
571
+ }
572
+ }
573
+ </style>