jianghu-ui 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.
Files changed (112) hide show
  1. package/README.md +376 -0
  2. package/dist/jianghu-ui.css +2318 -0
  3. package/dist/jianghu-ui.js +2 -0
  4. package/dist/jianghu-ui.js.LICENSE.txt +1 -0
  5. package/package.json +56 -0
  6. package/src/Design.stories.mdx +195 -0
  7. package/src/Introduction.stories.mdx +148 -0
  8. package/src/components/JhAddressSelect/JhAddressSelect.md +250 -0
  9. package/src/components/JhAddressSelect/JhAddressSelect.stories.js +282 -0
  10. package/src/components/JhAddressSelect/JhAddressSelect.vue +261 -0
  11. package/src/components/JhCard/JhCard.md +246 -0
  12. package/src/components/JhCard/JhCard.stories.js +688 -0
  13. package/src/components/JhCard/JhCard.vue +604 -0
  14. package/src/components/JhCheckCard/JhCheckCard.md +245 -0
  15. package/src/components/JhCheckCard/JhCheckCard.stories.js +750 -0
  16. package/src/components/JhCheckCard/JhCheckCard.vue +476 -0
  17. package/src/components/JhConfirmDialog/JhConfirmDialog.md +70 -0
  18. package/src/components/JhConfirmDialog/JhConfirmDialog.stories.js +550 -0
  19. package/src/components/JhConfirmDialog/JhConfirmDialog.vue +181 -0
  20. package/src/components/JhDateRangePicker/JhDateRangePicker.md +56 -0
  21. package/src/components/JhDateRangePicker/JhDateRangePicker.stories.js +320 -0
  22. package/src/components/JhDateRangePicker/JhDateRangePicker.vue +307 -0
  23. package/src/components/JhDescriptions/JhDescriptions.md +724 -0
  24. package/src/components/JhDescriptions/JhDescriptions.stories.js +858 -0
  25. package/src/components/JhDescriptions/JhDescriptions.vue +933 -0
  26. package/src/components/JhDraggable/JhDraggable.md +66 -0
  27. package/src/components/JhDraggable/JhDraggable.stories.js +161 -0
  28. package/src/components/JhDraggable/JhDraggable.vue +254 -0
  29. package/src/components/JhDrawer/JhDrawer.md +68 -0
  30. package/src/components/JhDrawer/JhDrawer.stories.js +478 -0
  31. package/src/components/JhDrawer/JhDrawer.vue +281 -0
  32. package/src/components/JhDrawerForm/JhDrawerForm.md +69 -0
  33. package/src/components/JhDrawerForm/JhDrawerForm.stories.js +492 -0
  34. package/src/components/JhDrawerForm/JhDrawerForm.vue +297 -0
  35. package/src/components/JhEditableTable/JhEditableTable.md +507 -0
  36. package/src/components/JhEditableTable/JhEditableTable.stories.js +615 -0
  37. package/src/components/JhEditableTable/JhEditableTable.vue +685 -0
  38. package/src/components/JhFileInput/JhFileInput.md +56 -0
  39. package/src/components/JhFileInput/JhFileInput.stories.js +103 -0
  40. package/src/components/JhFileInput/JhFileInput.vue +253 -0
  41. package/src/components/JhForm/JhForm.md +676 -0
  42. package/src/components/JhForm/JhForm.stories.js +1375 -0
  43. package/src/components/JhForm/JhForm.vue +657 -0
  44. package/src/components/JhFormField/JhFormField.stories.js +217 -0
  45. package/src/components/JhFormField/JhFormField.vue +439 -0
  46. package/src/components/JhFormFields/JhFormFields.md +647 -0
  47. package/src/components/JhFormFields/JhFormFields.stories.js +922 -0
  48. package/src/components/JhFormFields/JhFormFields.vue +998 -0
  49. package/src/components/JhFormList/JhFormList.md +303 -0
  50. package/src/components/JhFormList/JhFormList.stories.js +661 -0
  51. package/src/components/JhFormList/JhFormList.vue +1127 -0
  52. package/src/components/JhJsonEditor/JhJsonEditor.md +54 -0
  53. package/src/components/JhJsonEditor/JhJsonEditor.stories.js +157 -0
  54. package/src/components/JhJsonEditor/JhJsonEditor.vue +178 -0
  55. package/src/components/JhLayout/JhLayout.md +580 -0
  56. package/src/components/JhLayout/JhLayout.stories.js +414 -0
  57. package/src/components/JhLayout/JhLayout.vue +387 -0
  58. package/src/components/JhList/JhList.md +441 -0
  59. package/src/components/JhList/JhList.stories.js +524 -0
  60. package/src/components/JhList/JhList.vue +571 -0
  61. package/src/components/JhMarkdownEditor/JhMarkdownEditor.md +56 -0
  62. package/src/components/JhMarkdownEditor/JhMarkdownEditor.stories.js +191 -0
  63. package/src/components/JhMarkdownEditor/JhMarkdownEditor.vue +188 -0
  64. package/src/components/JhMask/JhMask.md +62 -0
  65. package/src/components/JhMask/JhMask.stories.js +270 -0
  66. package/src/components/JhMask/JhMask.vue +123 -0
  67. package/src/components/JhMenu/JhMenu.md +85 -0
  68. package/src/components/JhMenu/JhMenu.stories.js +384 -0
  69. package/src/components/JhMenu/JhMenu.vue +545 -0
  70. package/src/components/JhModal/JhModal.md +68 -0
  71. package/src/components/JhModal/JhModal.stories.js +562 -0
  72. package/src/components/JhModal/JhModal.vue +235 -0
  73. package/src/components/JhModalForm/JhModalForm.md +69 -0
  74. package/src/components/JhModalForm/JhModalForm.stories.js +592 -0
  75. package/src/components/JhModalForm/JhModalForm.vue +298 -0
  76. package/src/components/JhPageContainer/JhPageContainer.md +409 -0
  77. package/src/components/JhPageContainer/JhPageContainer.stories.js +209 -0
  78. package/src/components/JhPageContainer/JhPageContainer.vue +72 -0
  79. package/src/components/JhQueryFilter/JhQueryFilter.md +77 -0
  80. package/src/components/JhQueryFilter/JhQueryFilter.stories.js +684 -0
  81. package/src/components/JhQueryFilter/JhQueryFilter.vue +429 -0
  82. package/src/components/JhScene/JhScene.md +64 -0
  83. package/src/components/JhScene/JhScene.stories.js +317 -0
  84. package/src/components/JhScene/JhScene.vue +376 -0
  85. package/src/components/JhStatisticCard/JhStatisticCard.md +363 -0
  86. package/src/components/JhStatisticCard/JhStatisticCard.stories.js +847 -0
  87. package/src/components/JhStatisticCard/JhStatisticCard.vue +459 -0
  88. package/src/components/JhStepsForm/JhStepsForm.md +666 -0
  89. package/src/components/JhStepsForm/JhStepsForm.stories.js +1224 -0
  90. package/src/components/JhStepsForm/JhStepsForm.vue +749 -0
  91. package/src/components/JhTable/JhTable.md +730 -0
  92. package/src/components/JhTable/JhTable.stories.js +1444 -0
  93. package/src/components/JhTable/JhTable.vue +2298 -0
  94. package/src/components/JhTableAttachment/JhTableAttachment.md +70 -0
  95. package/src/components/JhTableAttachment/JhTableAttachment.stories.js +198 -0
  96. package/src/components/JhTableAttachment/JhTableAttachment.vue +264 -0
  97. package/src/components/JhToast/JhToast.md +67 -0
  98. package/src/components/JhToast/JhToast.stories.js +386 -0
  99. package/src/components/JhToast/JhToast.vue +239 -0
  100. package/src/components/JhTreeSelect/JhTreeSelect.md +82 -0
  101. package/src/components/JhTreeSelect/JhTreeSelect.stories.js +391 -0
  102. package/src/components/JhTreeSelect/JhTreeSelect.vue +727 -0
  103. package/src/components/JhWaterMark/JhWaterMark.md +190 -0
  104. package/src/components/JhWaterMark/JhWaterMark.stories.js +675 -0
  105. package/src/components/JhWaterMark/JhWaterMark.vue +351 -0
  106. package/src/components/README.md +52 -0
  107. package/src/index.js +135 -0
  108. package/src/style/globalCSSJHV4.css +348 -0
  109. package/src/style/globalCSSVuetifyV4.css +637 -0
  110. package/src/style/storybook.css +4 -0
  111. package/src/tailwind.css +3 -0
  112. package/src/utils/vuetify.js +31 -0
@@ -0,0 +1,386 @@
1
+ import JhToast from './JhToast.vue';
2
+
3
+ export default {
4
+ title: '基础组件/JhToast - 消息提示',
5
+ component: JhToast,
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ value: {
9
+ control: 'boolean',
10
+ description: '是否显示',
11
+ table: {
12
+ defaultValue: { summary: 'false' },
13
+ },
14
+ },
15
+ message: {
16
+ control: 'text',
17
+ description: '消息内容',
18
+ table: {
19
+ defaultValue: { summary: '' },
20
+ },
21
+ },
22
+ type: {
23
+ control: 'select',
24
+ options: ['success', 'error', 'warning', 'info', 'loading'],
25
+ description: '消息类型',
26
+ table: {
27
+ defaultValue: { summary: 'success' },
28
+ },
29
+ },
30
+ position: {
31
+ control: 'select',
32
+ options: ['top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
33
+ description: '显示位置',
34
+ table: {
35
+ defaultValue: { summary: 'top' },
36
+ },
37
+ },
38
+ timeout: {
39
+ control: 'number',
40
+ description: '自动关闭时间(毫秒), -1表示不自动关闭',
41
+ table: {
42
+ defaultValue: { summary: '3000' },
43
+ },
44
+ },
45
+ showCloseButton: {
46
+ control: 'boolean',
47
+ description: '是否显示关闭按钮',
48
+ table: {
49
+ defaultValue: { summary: 'true' },
50
+ },
51
+ },
52
+ },
53
+ };
54
+
55
+ // 基础示例
56
+ export const 基础示例 = {
57
+ render: () => ({
58
+ components: { JhToast },
59
+ data() {
60
+ return {
61
+ show: false,
62
+ };
63
+ },
64
+ template: `
65
+ <div>
66
+ <v-btn @click="show = true">显示提示</v-btn>
67
+ <jh-toast
68
+ v-model="show"
69
+ message="这是一条成功消息"
70
+ type="success"
71
+ />
72
+ </div>
73
+ `,
74
+ }),
75
+ };
76
+
77
+ // 不同类型
78
+ export const 不同类型 = {
79
+ render: () => ({
80
+ components: { JhToast },
81
+ data() {
82
+ return {
83
+ showSuccess: false,
84
+ showError: false,
85
+ showWarning: false,
86
+ showInfo: false,
87
+ showLoading: false,
88
+ };
89
+ },
90
+ template: `
91
+ <div>
92
+ <v-btn class="mr-2 mb-2" color="success" @click="showSuccess = true">Success</v-btn>
93
+ <v-btn class="mr-2 mb-2" color="error" @click="showError = true">Error</v-btn>
94
+ <v-btn class="mr-2 mb-2" color="warning" @click="showWarning = true">Warning</v-btn>
95
+ <v-btn class="mr-2 mb-2" color="info" @click="showInfo = true">Info</v-btn>
96
+ <v-btn class="mb-2" color="primary" @click="showLoading = true">Loading</v-btn>
97
+
98
+ <jh-toast
99
+ v-model="showSuccess"
100
+ message="操作成功完成!"
101
+ type="success"
102
+ />
103
+ <jh-toast
104
+ v-model="showError"
105
+ message="操作失败,请重试"
106
+ type="error"
107
+ />
108
+ <jh-toast
109
+ v-model="showWarning"
110
+ message="请注意此操作的风险"
111
+ type="warning"
112
+ />
113
+ <jh-toast
114
+ v-model="showInfo"
115
+ message="这是一条提示信息"
116
+ type="info"
117
+ />
118
+ <jh-toast
119
+ v-model="showLoading"
120
+ message="正在处理中..."
121
+ type="loading"
122
+ />
123
+ </div>
124
+ `,
125
+ }),
126
+ };
127
+
128
+ // 不同位置
129
+ export const 不同位置 = {
130
+ render: () => ({
131
+ components: { JhToast },
132
+ data() {
133
+ return {
134
+ showTop: false,
135
+ showBottom: false,
136
+ showTopLeft: false,
137
+ showTopRight: false,
138
+ showBottomLeft: false,
139
+ showBottomRight: false,
140
+ };
141
+ },
142
+ template: `
143
+ <div>
144
+ <div class="mb-2">
145
+ <v-btn class="mr-2" @click="showTopLeft = true">Top Left</v-btn>
146
+ <v-btn class="mr-2" @click="showTop = true">Top</v-btn>
147
+ <v-btn @click="showTopRight = true">Top Right</v-btn>
148
+ </div>
149
+ <div>
150
+ <v-btn class="mr-2" @click="showBottomLeft = true">Bottom Left</v-btn>
151
+ <v-btn class="mr-2" @click="showBottom = true">Bottom</v-btn>
152
+ <v-btn @click="showBottomRight = true">Bottom Right</v-btn>
153
+ </div>
154
+
155
+ <jh-toast v-model="showTop" message="顶部居中" position="top" />
156
+ <jh-toast v-model="showBottom" message="底部居中" position="bottom" />
157
+ <jh-toast v-model="showTopLeft" message="顶部左侧" position="top-left" />
158
+ <jh-toast v-model="showTopRight" message="顶部右侧" position="top-right" />
159
+ <jh-toast v-model="showBottomLeft" message="底部左侧" position="bottom-left" />
160
+ <jh-toast v-model="showBottomRight" message="底部右侧" position="bottom-right" />
161
+ </div>
162
+ `,
163
+ }),
164
+ };
165
+
166
+ // 带操作按钮
167
+ export const 带操作按钮 = {
168
+ render: () => ({
169
+ components: { JhToast },
170
+ data() {
171
+ return {
172
+ show: false,
173
+ actionCount: 0,
174
+ };
175
+ },
176
+ template: `
177
+ <div>
178
+ <v-btn @click="show = true">显示带操作的提示</v-btn>
179
+ <p class="mt-2">操作按钮点击次数: {{ actionCount }}</p>
180
+ <jh-toast
181
+ v-model="show"
182
+ message="文件已删除"
183
+ type="warning"
184
+ action-text="撤销"
185
+ @action="actionCount++; show = false"
186
+ />
187
+ </div>
188
+ `,
189
+ }),
190
+ };
191
+
192
+ // 自定义图标
193
+ export const 自定义图标 = {
194
+ render: () => ({
195
+ components: { JhToast },
196
+ data() {
197
+ return {
198
+ show1: false,
199
+ show2: false,
200
+ show3: false,
201
+ };
202
+ },
203
+ template: `
204
+ <div>
205
+ <v-btn class="mr-2" @click="show1 = true">自定义图标</v-btn>
206
+ <v-btn class="mr-2" @click="show2 = true">自定义颜色</v-btn>
207
+ <v-btn @click="show3 = true">自定义图标+颜色</v-btn>
208
+
209
+ <jh-toast
210
+ v-model="show1"
211
+ message="有新消息"
212
+ icon="mdi-email"
213
+ />
214
+ <jh-toast
215
+ v-model="show2"
216
+ message="自定义紫色"
217
+ color="purple"
218
+ />
219
+ <jh-toast
220
+ v-model="show3"
221
+ message="点赞成功"
222
+ icon="mdi-heart"
223
+ color="pink"
224
+ />
225
+ </div>
226
+ `,
227
+ }),
228
+ };
229
+
230
+ // 长文本消息
231
+ export const 长文本消息 = {
232
+ render: () => ({
233
+ components: { JhToast },
234
+ data() {
235
+ return {
236
+ show1: false,
237
+ show2: false,
238
+ };
239
+ },
240
+ template: `
241
+ <div>
242
+ <v-btn class="mr-2" @click="show1 = true">中等长度消息</v-btn>
243
+ <v-btn @click="show2 = true">超长消息(会被截断)</v-btn>
244
+
245
+ <jh-toast
246
+ v-model="show1"
247
+ message="这是一条中等长度的消息,用于测试消息内容的显示效果"
248
+ type="info"
249
+ />
250
+ <jh-toast
251
+ v-model="show2"
252
+ message="这是一条非常非常长的消息内容,主要用于测试当消息内容超过最大长度限制时的显示效果,多余的内容会被自动截断并添加省略号。这样可以保证界面的整洁性,避免消息过长影响用户体验。"
253
+ type="warning"
254
+ :max-length="50"
255
+ />
256
+ </div>
257
+ `,
258
+ }),
259
+ };
260
+
261
+ // 不自动关闭
262
+ export const 不自动关闭 = {
263
+ render: () => ({
264
+ components: { JhToast },
265
+ data() {
266
+ return {
267
+ show: false,
268
+ };
269
+ },
270
+ template: `
271
+ <div>
272
+ <v-btn @click="show = true">显示不自动关闭的提示</v-btn>
273
+ <p class="mt-2 text-caption">需要手动点击关闭按钮</p>
274
+ <jh-toast
275
+ v-model="show"
276
+ message="重要提示:此消息不会自动关闭"
277
+ type="warning"
278
+ :timeout="-1"
279
+ />
280
+ </div>
281
+ `,
282
+ }),
283
+ };
284
+
285
+ // 垂直布局
286
+ export const 垂直布局 = {
287
+ render: () => ({
288
+ components: { JhToast },
289
+ data() {
290
+ return {
291
+ show: false,
292
+ };
293
+ },
294
+ template: `
295
+ <div>
296
+ <v-btn @click="show = true">显示垂直布局</v-btn>
297
+ <jh-toast
298
+ v-model="show"
299
+ message="这是一条使用垂直布局的消息,按钮会显示在下方"
300
+ type="info"
301
+ vertical
302
+ action-text="查看详情"
303
+ />
304
+ </div>
305
+ `,
306
+ }),
307
+ };
308
+
309
+ // 无关闭按钮
310
+ export const 无关闭按钮 = {
311
+ render: () => ({
312
+ components: { JhToast },
313
+ data() {
314
+ return {
315
+ show: false,
316
+ };
317
+ },
318
+ template: `
319
+ <div>
320
+ <v-btn @click="show = true">显示无关闭按钮的提示</v-btn>
321
+ <p class="mt-2 text-caption">3秒后自动关闭</p>
322
+ <jh-toast
323
+ v-model="show"
324
+ message="此消息没有关闭按钮,会自动关闭"
325
+ type="success"
326
+ :show-close-button="false"
327
+ />
328
+ </div>
329
+ `,
330
+ }),
331
+ };
332
+
333
+ // 实际应用
334
+ export const 实际应用 = {
335
+ render: () => ({
336
+ components: { JhToast },
337
+ data() {
338
+ return {
339
+ showToast: false,
340
+ toastMessage: '',
341
+ toastType: 'success',
342
+ isProcessing: false,
343
+ };
344
+ },
345
+ methods: {
346
+ async simulateSave() {
347
+ this.isProcessing = true;
348
+ this.toastMessage = '正在保存...';
349
+ this.toastType = 'loading';
350
+ this.showToast = true;
351
+
352
+ // 模拟异步操作
353
+ setTimeout(() => {
354
+ const success = Math.random() > 0.3;
355
+ this.toastMessage = success ? '保存成功!' : '保存失败,请重试';
356
+ this.toastType = success ? 'success' : 'error';
357
+ this.isProcessing = false;
358
+ }, 2000);
359
+ },
360
+ async simulateDelete() {
361
+ const result = await this.confirmDelete();
362
+ if (result) {
363
+ this.toastMessage = '删除成功';
364
+ this.toastType = 'warning';
365
+ this.showToast = true;
366
+ }
367
+ },
368
+ async confirmDelete() {
369
+ // 这里应该使用 confirm dialog,这里简化处理
370
+ return window.confirm('确定要删除吗?');
371
+ },
372
+ },
373
+ template: `
374
+ <div>
375
+ <v-btn class="mr-2" @click="simulateSave" :loading="isProcessing">保存数据</v-btn>
376
+ <v-btn @click="simulateDelete">删除数据</v-btn>
377
+
378
+ <jh-toast
379
+ v-model="showToast"
380
+ :message="toastMessage"
381
+ :type="toastType"
382
+ />
383
+ </div>
384
+ `,
385
+ }),
386
+ };
@@ -0,0 +1,239 @@
1
+ <template>
2
+ <v-snackbar
3
+ v-model="isVisible"
4
+ :color="currentColor"
5
+ :timeout="currentTimeout"
6
+ :top="position.includes('top')"
7
+ :bottom="position.includes('bottom')"
8
+ :left="position.includes('left')"
9
+ :right="position.includes('right')"
10
+ :vertical="vertical"
11
+ :elevation="elevation"
12
+ :class="['jh-toast', `jh-toast--${type}`]"
13
+ @input="handleInput"
14
+ >
15
+ <div class="jh-toast__content">
16
+ <v-progress-circular
17
+ v-if="type === 'loading'"
18
+ :size="18"
19
+ :width="3"
20
+ color="white"
21
+ indeterminate
22
+ class="jh-toast__icon"
23
+ ></v-progress-circular>
24
+ <v-icon
25
+ v-else-if="currentIcon"
26
+ :color="iconColor"
27
+ class="jh-toast__icon"
28
+ >
29
+ {{ currentIcon }}
30
+ </v-icon>
31
+ <span class="jh-toast__message">{{ currentMessage }}</span>
32
+ </div>
33
+
34
+ <template v-slot:action="{ attrs }">
35
+ <v-btn
36
+ v-if="showCloseButton"
37
+ :color="closeButtonColor"
38
+ text
39
+ small
40
+ v-bind="attrs"
41
+ @click="close"
42
+ >
43
+ {{ closeButtonText }}
44
+ </v-btn>
45
+ <v-btn
46
+ v-if="actionText"
47
+ :color="actionButtonColor"
48
+ text
49
+ small
50
+ v-bind="attrs"
51
+ @click="handleAction"
52
+ >
53
+ {{ actionText }}
54
+ </v-btn>
55
+ </template>
56
+ </v-snackbar>
57
+ </template>
58
+
59
+ <script>
60
+ export default {
61
+ name: 'JhToast',
62
+ props: {
63
+ // 是否显示
64
+ value: {
65
+ type: Boolean,
66
+ default: false,
67
+ },
68
+ // 消息内容
69
+ message: {
70
+ type: String,
71
+ default: '',
72
+ },
73
+ // 类型: success, error, warning, info, loading
74
+ type: {
75
+ type: String,
76
+ default: 'success',
77
+ validator: (v) => ['success', 'error', 'warning', 'info', 'loading'].includes(v),
78
+ },
79
+ // 位置: top, bottom, top-left, top-right, bottom-left, bottom-right
80
+ position: {
81
+ type: String,
82
+ default: 'top',
83
+ validator: (v) => ['top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'].includes(v),
84
+ },
85
+ // 自动关闭时间(毫秒), -1 表示不自动关闭
86
+ timeout: {
87
+ type: Number,
88
+ default: 3000,
89
+ },
90
+ // 自定义颜色
91
+ color: {
92
+ type: String,
93
+ default: '',
94
+ },
95
+ // 自定义图标
96
+ icon: {
97
+ type: String,
98
+ default: '',
99
+ },
100
+ // 图标颜色
101
+ iconColor: {
102
+ type: String,
103
+ default: 'white',
104
+ },
105
+ // 是否显示关闭按钮
106
+ showCloseButton: {
107
+ type: Boolean,
108
+ default: true,
109
+ },
110
+ // 关闭按钮文本
111
+ closeButtonText: {
112
+ type: String,
113
+ default: '关闭',
114
+ },
115
+ // 关闭按钮颜色
116
+ closeButtonColor: {
117
+ type: String,
118
+ default: 'white',
119
+ },
120
+ // 操作按钮文本
121
+ actionText: {
122
+ type: String,
123
+ default: '',
124
+ },
125
+ // 操作按钮颜色
126
+ actionButtonColor: {
127
+ type: String,
128
+ default: 'white',
129
+ },
130
+ // 是否垂直布局
131
+ vertical: {
132
+ type: Boolean,
133
+ default: false,
134
+ },
135
+ // 阴影高度
136
+ elevation: {
137
+ type: [Number, String],
138
+ default: 24,
139
+ },
140
+ // 消息最大长度
141
+ maxLength: {
142
+ type: Number,
143
+ default: 100,
144
+ },
145
+ },
146
+ data() {
147
+ return {
148
+ isVisible: false,
149
+ };
150
+ },
151
+ computed: {
152
+ currentMessage() {
153
+ let msg = this.message || '';
154
+ if (msg.length > this.maxLength) {
155
+ msg = msg.substring(0, this.maxLength) + '...';
156
+ }
157
+ return msg;
158
+ },
159
+ currentColor() {
160
+ if (this.color) return this.color;
161
+
162
+ const colorMap = {
163
+ success: 'success',
164
+ error: 'error',
165
+ warning: 'warning',
166
+ info: 'info',
167
+ loading: 'primary',
168
+ };
169
+ return colorMap[this.type] || 'success';
170
+ },
171
+ currentIcon() {
172
+ if (this.icon) return this.icon;
173
+ if (this.type === 'loading') return '';
174
+
175
+ const iconMap = {
176
+ success: 'mdi-check-circle',
177
+ error: 'mdi-alert-circle',
178
+ warning: 'mdi-alert',
179
+ info: 'mdi-information',
180
+ };
181
+ return iconMap[this.type] || 'mdi-check-circle';
182
+ },
183
+ currentTimeout() {
184
+ return this.type === 'loading' ? -1 : this.timeout;
185
+ },
186
+ },
187
+ watch: {
188
+ value(newVal) {
189
+ this.isVisible = newVal;
190
+ },
191
+ isVisible(newVal) {
192
+ if (!newVal) {
193
+ this.$emit('input', false);
194
+ }
195
+ },
196
+ },
197
+ mounted() {
198
+ this.isVisible = this.value;
199
+ },
200
+ methods: {
201
+ handleInput(value) {
202
+ this.$emit('input', value);
203
+ if (!value) {
204
+ this.$emit('close');
205
+ }
206
+ },
207
+ close() {
208
+ this.isVisible = false;
209
+ },
210
+ handleAction() {
211
+ this.$emit('action');
212
+ },
213
+ },
214
+ };
215
+ </script>
216
+
217
+ <style scoped>
218
+ .jh-toast__content {
219
+ display: flex;
220
+ align-items: center;
221
+ flex: 1;
222
+ }
223
+
224
+ .jh-toast__icon {
225
+ margin-right: 12px;
226
+ }
227
+
228
+ .jh-toast__message {
229
+ flex: 1;
230
+ word-break: break-word;
231
+ }
232
+
233
+ /* 响应式样式 */
234
+ @media screen and (max-width: 600px) {
235
+ .jh-toast__message {
236
+ max-width: calc(100vw - 200px);
237
+ }
238
+ }
239
+ </style>
@@ -0,0 +1,82 @@
1
+ # JhTreeSelect - 树形选择器
2
+
3
+ JhTreeSelect 提供多选/单选树对话框,集成搜索、全选、移动端适配和已选列表,适用于组织、权限、分类等层级数据的选择。
4
+
5
+ ## 功能特性
6
+
7
+ - 🌳 **多模式**:支持 `multiple`/`single` 两种模式,可选择节点或仅选择叶子
8
+ - 🔍 **即时搜索**:关键字过滤树节点,并支持“全选当前搜索结果”
9
+ - 📱 **响应式**:小屏自动切换全屏弹窗,已选区域在移动端折叠展示
10
+ - 🧺 **批量操作**:内置全选、清除、批量删除已选项
11
+ - 🔁 **受控对话框**:`visible` + `v-model` 统一控制,确认再回传
12
+
13
+ ## 基础用法
14
+
15
+ ```vue
16
+ <template>
17
+ <jh-tree-select
18
+ v-model="selectedNodes"
19
+ :visible.sync="dialogVisible"
20
+ :data="treeData"
21
+ mode="multiple"
22
+ :allow-select-node="true"
23
+ title="选择部门"
24
+ @confirm="handleConfirm"
25
+ />
26
+ </template>
27
+ ```
28
+
29
+ ## API
30
+
31
+ ### Props
32
+
33
+ | 参数 | 说明 | 类型 | 默认值 |
34
+ | --- | --- | --- | --- |
35
+ | value | `v-model`,选中节点数组 | Array | [] |
36
+ | visible | 弹窗是否显示 | boolean | false |
37
+ | mode | 选择模式 `multiple/single` | string | `multiple` |
38
+ | title | 对话框标题 | string | `选择节点` |
39
+ | placeholder | 搜索框占位符 | string | `搜索节点` |
40
+ | maxWidth | 弹窗最大宽度 | string | `1000px` |
41
+ | data | 树形数据源 | Array | [] |
42
+ | nodeKey | 节点唯一 key 字段名 | string | `id` |
43
+ | nodeLabel | 节点显示字段名 | string | `label` |
44
+ | nodeChildren | 子节点字段名 | string | `children` |
45
+ | allowSelectNode | 是否允许点击节点本身选择 | boolean | false |
46
+ | showSearch | 是否展示搜索框 | boolean | true |
47
+ | showSelectAll | 是否展示全选按钮(仅 multiple) | boolean | true |
48
+ | loading | 数据加载状态 | boolean | false |
49
+
50
+ ### Events
51
+
52
+ | 事件名 | 说明 | 回调参数 |
53
+ | --- | --- | --- |
54
+ | input | `v-model` 更新,返回选中节点数组 | (nodes: Array) |
55
+ | update:visible | 同步弹窗开关 | (visible: boolean) |
56
+ | confirm | 点击“确定”后触发 | (nodes: Array) |
57
+ | cancel | 点击“取消”或关闭图标后触发 | - |
58
+
59
+ ### Slots
60
+
61
+ 组件暂未开放插槽。
62
+
63
+ ## 数据结构示例
64
+
65
+ ```js
66
+ const treeData = [
67
+ {
68
+ id: 'dept-1',
69
+ label: '总部',
70
+ children: [
71
+ { id: 'dept-1-1', label: '技术部' },
72
+ { id: 'dept-1-2', label: '运营部' }
73
+ ]
74
+ }
75
+ ];
76
+ ```
77
+
78
+ ## 使用建议
79
+
80
+ - 如果只允许选择叶子节点,可将 `allowSelectNode` 设为 false,并在 `toggleNode` 中自定义策略
81
+ - 需要默认展开其他层级时,可修改组件数据或 fork 组件以暴露更多控制属性
82
+ - 回传的数据为节点对象,包含原始字段,可直接提交给后端或提取 ID