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,545 @@
1
+ <template>
2
+ <div>
3
+ <!-- 手机端左边抽屉菜单 -->
4
+ <v-navigation-drawer
5
+ v-if="!hideDrawers"
6
+ v-model="mobileMenuDrawer"
7
+ app
8
+ clipped
9
+ :temporary="temporary"
10
+ class="jh-page-nav-bar hidden-md-and-up"
11
+ >
12
+ <!-- 页面标题 -->
13
+ <v-toolbar-title class="px-3 jh-toolbar-title">
14
+ <span class="text-base font-medium">{{ appTitle }}</span>
15
+ </v-toolbar-title>
16
+ <v-divider class="jh-divider"></v-divider>
17
+
18
+ <!-- 菜单 -->
19
+ <v-list flat class="py-0">
20
+ <v-list-item-group :value="currentMenuIndex">
21
+ <template v-for="(item, index) in menuList">
22
+ <!-- 有子菜单 -->
23
+ <template v-if="item.children && item.children.length > 0">
24
+ <v-list-group :key="index" :value="item.active" class="px-4">
25
+ <template v-slot:activator>
26
+ <v-list-item class="pl-0" :ripple="false">
27
+ <v-list-item-content>
28
+ <v-list-item-title>{{ item.title }}</v-list-item-title>
29
+ </v-list-item-content>
30
+ </v-list-item>
31
+ </template>
32
+ <template v-slot:appendIcon>
33
+ <v-icon size="18">mdi-chevron-down</v-icon>
34
+ </template>
35
+ <template v-for="(child, childIndex) in item.children">
36
+ <!-- 三级菜单 -->
37
+ <template v-if="child.children && child.children.length > 0">
38
+ <v-list-group :key="childIndex" :value="child.active" class="px-0">
39
+ <template v-slot:activator>
40
+ <v-list-item class="pl-0" :ripple="false">
41
+ <v-list-item-content>
42
+ <v-list-item-title class="pl-6 pl-sm-4">{{ child.title }}</v-list-item-title>
43
+ </v-list-item-content>
44
+ </v-list-item>
45
+ </template>
46
+ <template v-slot:appendIcon>
47
+ <v-icon size="16">mdi-chevron-down</v-icon>
48
+ </template>
49
+ <v-list-item
50
+ v-for="(grandchild, grandchildIndex) in child.children"
51
+ :key="grandchildIndex"
52
+ @click="handleMenuClick(grandchild, index)"
53
+ :class="{ 'second-active': currentSecondMenuId === grandchild.id && currentMenuIndex === index }"
54
+ >
55
+ <v-list-item-content>
56
+ <v-list-item-title class="pl-10 pl-sm-8">{{ grandchild.title }}</v-list-item-title>
57
+ </v-list-item-content>
58
+ </v-list-item>
59
+ </v-list-group>
60
+ </template>
61
+ <!-- 二级菜单(无三级) -->
62
+ <v-list-item
63
+ v-else
64
+ :key="childIndex"
65
+ @click="handleMenuClick(child, index)"
66
+ :class="{ 'second-active': currentSecondMenuId === child.id && currentMenuIndex === index }"
67
+ >
68
+ <v-list-item-content>
69
+ <v-list-item-title class="pl-6 pl-sm-4">{{ child.title }}</v-list-item-title>
70
+ </v-list-item-content>
71
+ </v-list-item>
72
+ </template>
73
+ </v-list-group>
74
+ </template>
75
+ <!-- 无子菜单 -->
76
+ <v-list-item
77
+ v-else
78
+ :key="index"
79
+ @click="handleMenuClick(item, index)"
80
+ class="px-4"
81
+ :class="{ 'second-active': currentMenuIndex === index }"
82
+ >
83
+ <v-list-item-content class="pl-0">
84
+ <v-list-item-title>{{ item.title }}</v-list-item-title>
85
+ </v-list-item-content>
86
+ <v-list-item-icon class="my-3">
87
+ <v-icon size="18">mdi-chevron-right</v-icon>
88
+ </v-list-item-icon>
89
+ </v-list-item>
90
+ </template>
91
+ </v-list-item-group>
92
+ </v-list>
93
+
94
+ <!-- 抽屉关闭按钮 -->
95
+ <v-btn
96
+ elevation="0"
97
+ color="success"
98
+ fab
99
+ absolute
100
+ top
101
+ left
102
+ small
103
+ tile
104
+ class="jh-menu-drawer-close-float-btn"
105
+ @click="mobileMenuDrawer = false"
106
+ >
107
+ <v-icon>mdi-close</v-icon>
108
+ </v-btn>
109
+ </v-navigation-drawer>
110
+
111
+ <!-- 手机端右边抽屉菜单(用户菜单) -->
112
+ <v-navigation-drawer
113
+ v-if="!hideDrawers && showAvatar"
114
+ v-model="mobileUserMenuDrawer"
115
+ app
116
+ clipped
117
+ :temporary="temporary"
118
+ class="jh-page-nav-bar hidden-md-and-up"
119
+ right
120
+ >
121
+ <div class="d-flex flex-column" style="height: calc(100vh - 20px)">
122
+ <div>
123
+ <!-- 用户信息 -->
124
+ <v-toolbar-title class="px-4" style="min-height: 110px">
125
+ <div class="d-flex flex-column pt-3" style="gap: 12px">
126
+ <div class="success--text text-xs text-center" style="cursor: pointer" @click="mobileUserMenuDrawer = false">
127
+ <v-icon size="22" class="success--text">mdi-window-close</v-icon>
128
+ <div class="success--text" style="margin-top: -2px">关闭</div>
129
+ </div>
130
+ <div class="d-flex align-start mb-2" style="gap: 4px">
131
+ <v-icon :size="36">mdi-account-circle</v-icon>
132
+ <div class="flex-1">
133
+ <div class="text-xs">
134
+ {{ userInfo.username }}
135
+ <span class="text-caption grey--text">{{ userInfo.userId }}</span>
136
+ </div>
137
+ <div class="d-flex flex-wrap" style="gap: 4px">
138
+ <span
139
+ v-for="(role, i) in userInfo.roles"
140
+ :key="i"
141
+ class="text-caption px-1 grey--text"
142
+ style="border: 1px solid #ccc; border-radius: 4px"
143
+ >
144
+ {{ role }}
145
+ </span>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </v-toolbar-title>
151
+ <v-divider class="jh-divider"></v-divider>
152
+ </div>
153
+
154
+ <!-- 用户菜单项 -->
155
+ <v-list nav dense class="flex-1">
156
+ <v-list-item v-for="(menu, index) in avatarMenuList" :key="index" @click="handleMenuClick(menu)">
157
+ <v-list-item-icon class="mr-1 mt-1">
158
+ <v-icon size="16" color="grey darken-3">mdi-account-cog-outline</v-icon>
159
+ </v-list-item-icon>
160
+ <v-list-item-content>
161
+ <v-list-item-title color="grey darken-3">{{ menu.title }}</v-list-item-title>
162
+ </v-list-item-content>
163
+ </v-list-item>
164
+ </v-list>
165
+
166
+ <!-- 登出按钮 -->
167
+ <div class="px-4">
168
+ <v-btn block elevation="0" small color="error" @click="handleLogout">
169
+ <v-icon size="16">mdi-logout</v-icon>
170
+ <span class="ml-2">登出</span>
171
+ </v-btn>
172
+ </div>
173
+ </div>
174
+ </v-navigation-drawer>
175
+
176
+ <!-- 页面头部 -->
177
+ <v-app-bar app clipped-left height="52" class="jh-page-header px-8" flat>
178
+ <!-- 手机端左侧菜单开启按钮 -->
179
+ <div
180
+ v-if="!hideDrawers && isMobile"
181
+ class="hidden-md-and-up d-flex flex-column justify-center align-start"
182
+ style="width: 60px; cursor: pointer"
183
+ @click="mobileMenuDrawer = !mobileMenuDrawer"
184
+ >
185
+ <span class="success--text text-caption">
186
+ <v-icon size="24" class="success--text">mdi-menu</v-icon>
187
+ <div style="margin-top: -2px" class="text-xs">目录</div>
188
+ </span>
189
+ </div>
190
+
191
+ <!-- 页面标题 -->
192
+ <v-toolbar-title class="pl-0">
193
+ <span class="hidden-sm-and-down text-h6 font-weight-bold">
194
+ <slot name="title">{{ appTitle }}</slot>
195
+ </span>
196
+ <span class="hidden-md-and-up font-weight-medium text-center">
197
+ <div class="text-base">{{ appTitle }}</div>
198
+ <div class="text-xs grey--text text--darken-2" style="margin-top: -2px">
199
+ <slot name="subtitle"></slot>
200
+ </div>
201
+ </span>
202
+ </v-toolbar-title>
203
+
204
+ <!-- PC端菜单 -->
205
+ <v-spacer class="hidden-sm-and-down"></v-spacer>
206
+ <v-tabs v-if="!hideDrawers" class="hidden-sm-and-down" show-arrows slider-size="0" color="success">
207
+ <template v-for="(item, index) in menuList">
208
+ <!-- 一级菜单 -->
209
+ <template v-if="!item.children || item.children.length === 0">
210
+ <v-tab
211
+ :key="index"
212
+ class="px-2 mx-1 jh-header-tab"
213
+ :class="{ 'jh-header-tab-active': currentMenuIndex === index }"
214
+ @click="handleMenuClick(item, index)"
215
+ >
216
+ {{ item.title }}
217
+ </v-tab>
218
+ </template>
219
+
220
+ <!-- 二级菜单 -->
221
+ <template v-else>
222
+ <v-menu :key="index" offset-y>
223
+ <template v-slot:activator="{ on, attrs }">
224
+ <v-tab
225
+ v-bind="attrs"
226
+ v-on="on"
227
+ class="px-2 mx-1 jh-header-tab"
228
+ :class="{ 'jh-header-tab-active': currentMenuIndex === index }"
229
+ >
230
+ {{ item.title }}
231
+ <v-icon size="12">mdi-chevron-down</v-icon>
232
+ </v-tab>
233
+ </template>
234
+ <v-list nav dense>
235
+ <template v-for="(child, childIndex) in item.children">
236
+ <!-- 三级菜单 -->
237
+ <template v-if="child.children && child.children.length > 0">
238
+ <v-menu :key="childIndex" offset-x right>
239
+ <template v-slot:activator="{ on, attrs }">
240
+ <v-list-item v-bind="attrs" v-on="on">
241
+ <v-list-item-content>
242
+ <v-list-item-title style="color: #41434f">{{ child.title }}</v-list-item-title>
243
+ </v-list-item-content>
244
+ <v-list-item-action>
245
+ <v-icon>mdi-chevron-right</v-icon>
246
+ </v-list-item-action>
247
+ </v-list-item>
248
+ </template>
249
+ <v-list nav dense>
250
+ <v-list-item
251
+ v-for="(grandchild, grandchildIndex) in child.children"
252
+ :key="grandchildIndex"
253
+ @click="handleMenuClick(grandchild, index)"
254
+ :class="{ 'second-active': currentSecondMenuId === grandchild.id && currentMenuIndex === index }"
255
+ >
256
+ <v-list-item-content>
257
+ <v-list-item-title style="color: #41434f">{{ grandchild.title }}</v-list-item-title>
258
+ </v-list-item-content>
259
+ </v-list-item>
260
+ </v-list>
261
+ </v-menu>
262
+ </template>
263
+ <!-- 二级菜单(无三级) -->
264
+ <v-list-item
265
+ v-else
266
+ :key="childIndex"
267
+ @click="handleMenuClick(child, index)"
268
+ :class="{ 'second-active': currentSecondMenuId === child.id && currentMenuIndex === index }"
269
+ >
270
+ <v-list-item-content>
271
+ <v-list-item-title style="color: #41434f">{{ child.title }}</v-list-item-title>
272
+ </v-list-item-content>
273
+ </v-list-item>
274
+ </template>
275
+ </v-list>
276
+ </v-menu>
277
+ </template>
278
+ </template>
279
+ </v-tabs>
280
+
281
+ <!-- 右侧菜单和用户信息 -->
282
+ <div class="d-flex align-center" style="white-space: nowrap">
283
+ <!-- 右侧菜单 -->
284
+ <div v-if="rightMenuList.length > 0" class="d-flex align-center mr-2">
285
+ <div
286
+ v-for="(menu, index) in rightMenuList"
287
+ :key="index"
288
+ class="d-flex align-center ml-4 jh-right-menu"
289
+ style="cursor: pointer"
290
+ @click="handleMenuClick(menu)"
291
+ >
292
+ <v-icon v-if="menu.icon" size="20">{{ menu.icon }}</v-icon>
293
+ <div :class="{ 'ml-1': menu.icon }">{{ menu.title }}</div>
294
+ </div>
295
+ </div>
296
+ <v-divider v-if="rightMenuList.length > 0" vertical class="mx-3 jh-divider"></v-divider>
297
+
298
+ <!-- 用户头像菜单 -->
299
+ <template v-if="showAvatar">
300
+ <!-- 移动端用户信息 -->
301
+ <div
302
+ class="jh-avatar-menu-btn d-flex flex-column justify-center align-end hidden-sm-and-up"
303
+ @click="mobileUserMenuDrawer = !mobileUserMenuDrawer"
304
+ v-if="isMobile"
305
+ >
306
+ <div class="text-center">
307
+ <v-icon :size="24" color="success">mdi-account-circle</v-icon>
308
+ <div style="margin-top: -2px">
309
+ <p class="text-xs success--text mb-0" style="max-width: 60px; overflow: hidden; text-overflow: ellipsis">
310
+ {{ userInfo.username }}
311
+ </p>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
316
+ <!-- PC端用户信息 -->
317
+ <v-menu offset-y>
318
+ <template v-slot:activator="{ on, attrs }">
319
+ <div v-if="!isMobile" v-bind="attrs" v-on="on" class="jh-avatar-menu-btn d-flex align-center px-1 hidden-xs-only">
320
+ <v-icon :size="32" color="grey lighten-2">mdi-account-circle</v-icon>
321
+ <div class="ml-1">
322
+ <p class="caption black--text mb-0">{{ userInfo.username }}</p>
323
+ </div>
324
+ </div>
325
+ </template>
326
+ <v-list nav dense>
327
+ <!-- 用户信息 -->
328
+ <v-list-item>
329
+ <v-list-item-content>
330
+ <p class="caption black--text mb-0">{{ userInfo.username }}</p>
331
+ <p class="caption grey--text mb-0" style="font-size: 10px">{{ userInfo.userId }}</p>
332
+ </v-list-item-content>
333
+ </v-list-item>
334
+
335
+ <!-- 用户角色 -->
336
+ <div v-if="userInfo.roles && userInfo.roles.length > 0" style="max-width: 220px; padding: 0 16px">
337
+ <v-chip
338
+ v-for="(role, i) in userInfo.roles"
339
+ :key="i"
340
+ label
341
+ x-small
342
+ outlined
343
+ class="mr-1 mb-1"
344
+ style="font-size: 10px"
345
+ >
346
+ {{ role }}
347
+ </v-chip>
348
+ </div>
349
+ <v-divider v-if="userInfo.roles && userInfo.roles.length > 0" class="my-1 jh-divider"></v-divider>
350
+
351
+ <!-- 用户菜单项 -->
352
+ <v-list-item v-for="(menu, index) in avatarMenuList" :key="index" @click="handleMenuClick(menu)">
353
+ <v-list-item-icon class="mr-1 mt-1">
354
+ <v-icon size="16" color="grey darken-3">mdi-account-cog-outline</v-icon>
355
+ </v-list-item-icon>
356
+ <v-list-item-content>
357
+ <v-list-item-title color="grey darken-3">{{ menu.title }}</v-list-item-title>
358
+ </v-list-item-content>
359
+ </v-list-item>
360
+
361
+ <!-- 登出 -->
362
+ <v-list-item @click="handleLogout">
363
+ <v-list-item-icon class="mr-1 mt-1">
364
+ <v-icon size="16" color="grey darken-3">mdi-logout</v-icon>
365
+ </v-list-item-icon>
366
+ <v-list-item-content>
367
+ <v-list-item-title color="grey darken-3">登出</v-list-item-title>
368
+ </v-list-item-content>
369
+ </v-list-item>
370
+ </v-list>
371
+ </v-menu>
372
+ </template>
373
+ </div>
374
+ </v-app-bar>
375
+ </div>
376
+ </template>
377
+
378
+ <script>
379
+ export default {
380
+ name: 'JhMenu',
381
+ props: {
382
+ // 应用标题
383
+ appTitle: {
384
+ type: String,
385
+ default: 'JianghuJS',
386
+ },
387
+ // 菜单列表
388
+ menuList: {
389
+ type: Array,
390
+ default: () => [],
391
+ },
392
+ // 右侧菜单列表
393
+ rightMenuList: {
394
+ type: Array,
395
+ default: () => [],
396
+ },
397
+ // 用户头像菜单列表
398
+ avatarMenuList: {
399
+ type: Array,
400
+ default: () => [],
401
+ },
402
+ // 用户信息
403
+ userInfo: {
404
+ type: Object,
405
+ default: () => ({
406
+ username: 'Guest',
407
+ userId: '',
408
+ roles: [],
409
+ }),
410
+ },
411
+ // 是否显示用户头像
412
+ showAvatar: {
413
+ type: Boolean,
414
+ default: true,
415
+ },
416
+ // 当前激活的菜单索引
417
+ activeMenuIndex: {
418
+ type: Number,
419
+ default: -1,
420
+ },
421
+ // 当前激活的二级菜单ID
422
+ activeSecondMenuId: {
423
+ type: String,
424
+ default: null,
425
+ },
426
+ // 移动端抽屉是否临时显示(点击外部关闭)
427
+ temporary: {
428
+ type: Boolean,
429
+ default: true,
430
+ },
431
+ // 隐藏抽屉(用于Storybook预览)
432
+ hideDrawers: {
433
+ type: Boolean,
434
+ default: false,
435
+ },
436
+ },
437
+ data() {
438
+ return {
439
+ mobileMenuDrawer: false,
440
+ mobileUserMenuDrawer: false,
441
+ currentMenuIndex: this.activeMenuIndex,
442
+ currentSecondMenuId: this.activeSecondMenuId,
443
+ isMobile: window.innerWidth < 600
444
+ };
445
+ },
446
+ watch: {
447
+ activeMenuIndex(val) {
448
+ this.currentMenuIndex = val;
449
+ },
450
+ activeSecondMenuId(val) {
451
+ this.currentSecondMenuId = val;
452
+ },
453
+ },
454
+ methods: {
455
+ handleMenuClick(menu, index) {
456
+ if (index !== undefined) {
457
+ this.currentMenuIndex = index;
458
+ }
459
+ if (menu.id) {
460
+ this.currentSecondMenuId = menu.id;
461
+ }
462
+ this.mobileMenuDrawer = false;
463
+ this.mobileUserMenuDrawer = false;
464
+ this.$emit('menu-click', menu, index);
465
+ },
466
+ handleLogout() {
467
+ this.mobileUserMenuDrawer = false;
468
+ this.$emit('logout');
469
+ },
470
+ },
471
+ };
472
+ </script>
473
+
474
+ <style scoped>
475
+ /* 侧边栏菜单 */
476
+ .jh-page-nav-bar >>> .v-list-item,
477
+ .jh-page-nav-bar >>> .v-list-group__header {
478
+ border-bottom: 1px solid rgba(0, 0, 0, 0.03);
479
+ }
480
+
481
+ .jh-page-nav-bar >>> .v-list-group__header .v-list-item {
482
+ border-bottom: none;
483
+ }
484
+
485
+ @media (max-width: 600px) {
486
+ .jh-page-nav-bar >>> .v-list-group .v-list-group__header {
487
+ padding: 0 !important;
488
+ }
489
+
490
+ .jh-page-nav-bar >>> .v-list-item {
491
+ border-top: none;
492
+ }
493
+ }
494
+
495
+ .second-active >>> .v-list-item__title {
496
+ color: #4caf50 !important;
497
+ }
498
+
499
+ .second-active {
500
+ background-color: rgba(76, 175, 80, 0.1) !important;
501
+ }
502
+
503
+ .jh-toolbar-title {
504
+ height: 51px;
505
+ line-height: 51px;
506
+ }
507
+ .v-toolbar__title {
508
+ min-width: 200px;
509
+ }
510
+
511
+ .jh-menu-drawer-close-float-btn {
512
+ top: 120px !important;
513
+ right: -40px;
514
+ position: fixed;
515
+ left: auto !important;
516
+ }
517
+
518
+ .jh-header-tab.v-tab--active {
519
+ color: var(--v-grey-darken4) !important;
520
+ }
521
+
522
+ .jh-header-tab.jh-header-tab-active {
523
+ color: #4caf50 !important;
524
+ }
525
+
526
+ .jh-avatar-menu-btn {
527
+ cursor: pointer;
528
+ transition: all 0.3s;
529
+ border-radius: 5px;
530
+ }
531
+
532
+ .jh-avatar-menu-btn:hover {
533
+ opacity: 0.8;
534
+ }
535
+
536
+ .jh-right-menu >>> svg {
537
+ width: 28px;
538
+ height: 28px;
539
+ vertical-align: middle;
540
+ }
541
+
542
+ .jh-divider {
543
+ border-color: rgba(0, 0, 0, 0.08);
544
+ }
545
+ </style>
@@ -0,0 +1,68 @@
1
+ # JhModal - 通用模态框
2
+
3
+ JhModal 基于 Vuetify `v-dialog`,提供标题、内容、操作区的统一模态框样式,适用于查看详情、信息提示、二次确认等场景。
4
+
5
+ ## 功能特性
6
+
7
+ - 📦 **标准布局**:包含标题区、内容区、底部操作区,并支持粘性头部
8
+ - 🧲 **受控显示**:`v-model` 控制显隐,自动派发 open/close
9
+ - 🧩 **操作插槽**:`actions` 插槽可完全替换底部按钮
10
+ - 🖥️ **全屏/宽度**:通过 `fullscreen` 与 `width` 适配桌面或移动端
11
+ - 🔒 **安全交互**:`persistent` 控制遮罩点击行为,避免误关闭
12
+
13
+ ## 基础用法
14
+
15
+ ```vue
16
+ <template>
17
+ <jh-modal
18
+ v-model="visible"
19
+ title="订单详情"
20
+ width="720"
21
+ :persistent="false"
22
+ @confirm="handleSave"
23
+ >
24
+ <order-detail />
25
+ </jh-modal>
26
+ </template>
27
+ ```
28
+
29
+ ## API
30
+
31
+ ### Props
32
+
33
+ | 参数 | 说明 | 类型 | 默认值 |
34
+ | --- | --- | --- | --- |
35
+ | value | `v-model`,模态框显隐 | boolean | false |
36
+ | title | 标题文本 | string | `弹窗` |
37
+ | width | 最大宽度(px) | number \| string | 600 |
38
+ | fullscreen | 是否全屏展示 | boolean | false |
39
+ | persistent | 是否禁止点击遮罩关闭 | boolean | true |
40
+ | closable | 是否显示右上角关闭按钮 | boolean | true |
41
+ | showActions | 是否显示底部按钮区 | boolean | true |
42
+ | showConfirmButton | 是否显示确认按钮 | boolean | true |
43
+ | showCancelButton | 是否显示取消按钮 | boolean | true |
44
+ | confirmText | 确认按钮文本 | string | `确认` |
45
+ | cancelText | 取消按钮文本 | string | `取消` |
46
+
47
+ ### Events
48
+
49
+ | 事件名 | 说明 | 回调参数 |
50
+ | --- | --- | --- |
51
+ | input | `v-model` 更新事件 | (visible: boolean) |
52
+ | open | 弹窗打开时触发 | - |
53
+ | close | 弹窗关闭后触发 | - |
54
+ | confirm | 点击确认按钮 | - |
55
+ | cancel | 点击取消/关闭按钮 | - |
56
+
57
+ ### Slots
58
+
59
+ | 名称 | 说明 |
60
+ | --- | --- |
61
+ | default | 弹窗主体内容 |
62
+ | actions | 覆盖底部按钮区 |
63
+
64
+ ## 使用建议
65
+
66
+ - 表单类模态框可以关闭 `persistent` 以允许点击遮罩取消
67
+ - 需要长列表滚动时,可在插槽内包裹自定义滚动容器
68
+ - 若不需要按钮区,直接将 `showActions` 设为 false,并自行提供底部内容