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,317 @@
1
+ import JhScene from './JhScene.vue';
2
+
3
+ // 示例场景数据
4
+ const sampleScenes = [
5
+ {
6
+ id: 'all',
7
+ name: '全部',
8
+ default: true,
9
+ system: true,
10
+ form: {},
11
+ },
12
+ {
13
+ id: 'active',
14
+ name: '活跃用户',
15
+ system: true,
16
+ form: {
17
+ status: 'active',
18
+ },
19
+ },
20
+ {
21
+ id: 'inactive',
22
+ name: '不活跃',
23
+ system: true,
24
+ form: {
25
+ status: 'inactive',
26
+ },
27
+ },
28
+ ];
29
+
30
+ export default {
31
+ title: '基础组件/JhScene - 场景管理',
32
+ component: JhScene,
33
+ tags: ['autodocs'],
34
+ argTypes: {
35
+ showActionBtn: {
36
+ control: 'boolean',
37
+ description: '是否显示操作按钮(新建场景、场景管理)',
38
+ },
39
+ useLocalStorage: {
40
+ control: 'boolean',
41
+ description: '是否使用 localStorage 持久化场景数据',
42
+ },
43
+ storageKey: {
44
+ control: 'text',
45
+ description: 'localStorage 存储的键名',
46
+ },
47
+ },
48
+ parameters: {
49
+ docs: {
50
+ description: {
51
+ component: `
52
+
53
+ 场景管理组件,用于快速切换不同的数据筛选场景。
54
+
55
+ ## 功能特性
56
+
57
+ - 场景快速切换
58
+ - 自定义场景创建
59
+ - 场景管理(删除)
60
+ - 支持 localStorage 持久化
61
+ - 支持表单插槽自定义筛选条件
62
+
63
+ ## 使用场景
64
+
65
+ 适用于需要频繁切换筛选条件的列表页面,如:
66
+ - 用户列表(全部、活跃、待审核等)
67
+ - 订单列表(全部、待付款、待发货等)
68
+ - 工单列表(全部、处理中、已完成等)
69
+
70
+ ## 事件
71
+
72
+ - \`scene-change\`: 场景切换时触发,参数为场景对象
73
+ - \`scene-created\`: 创建场景时触发,参数为新场景对象
74
+ - \`scene-deleted\`: 删除场景时触发,参数为被删除的场景对象
75
+ - \`error\`: 操作出错时触发,参数为错误消息
76
+
77
+ ## 插槽
78
+
79
+ - \`form\`: 自定义筛选条件表单,作用域插槽,可访问 \`form\` 对象
80
+ `,
81
+ },
82
+ },
83
+ },
84
+ };
85
+
86
+ // 基础示例
87
+ export const 基础示例 = {
88
+ args: {
89
+ scenes: sampleScenes,
90
+ showActionBtn: true,
91
+ useLocalStorage: false,
92
+ storageKey: 'jh-scene-list',
93
+ initFormData: {},
94
+ },
95
+ render: (args) => ({
96
+ components: { JhScene },
97
+ data() {
98
+ return {
99
+ args,
100
+ currentScene: null,
101
+ };
102
+ },
103
+ template: `
104
+ <div>
105
+ <jh-scene
106
+ v-bind="args"
107
+ @scene-change="handleSceneChange"
108
+ @scene-created="handleSceneCreated"
109
+ @scene-deleted="handleSceneDeleted"
110
+ @error="handleError"
111
+ />
112
+ <v-card class="mt-4" outlined>
113
+ <v-card-text>
114
+ <h4>当前场景信息:</h4>
115
+ <pre>{{ currentScene }}</pre>
116
+ </v-card-text>
117
+ </v-card>
118
+ </div>
119
+ `,
120
+ methods: {
121
+ handleSceneChange(scene) {
122
+ console.log('Scene changed:', scene);
123
+ this.currentScene = scene;
124
+ },
125
+ handleSceneCreated(scene) {
126
+ console.log('Scene created:', scene);
127
+ alert('场景创建成功: ' + scene.name);
128
+ },
129
+ handleSceneDeleted(scene) {
130
+ console.log('Scene deleted:', scene);
131
+ alert('场景已删除: ' + scene.name);
132
+ },
133
+ handleError(message) {
134
+ console.error('Error:', message);
135
+ alert('错误: ' + message);
136
+ },
137
+ },
138
+ }),
139
+ };
140
+
141
+ // 带表单插槽
142
+ export const 带表单插槽 = {
143
+ args: {
144
+ ...基础示例.args,
145
+ initFormData: {
146
+ status: '',
147
+ keyword: '',
148
+ },
149
+ },
150
+ render: (args) => ({
151
+ components: { JhScene },
152
+ data() {
153
+ return {
154
+ args,
155
+ currentScene: null,
156
+ };
157
+ },
158
+ template: `
159
+ <div>
160
+ <jh-scene
161
+ v-bind="args"
162
+ @scene-change="handleSceneChange"
163
+ @scene-created="handleSceneCreated"
164
+ @scene-deleted="handleSceneDeleted"
165
+ @error="handleError"
166
+ >
167
+ <template v-slot:form="{ form }">
168
+ <v-select
169
+ v-model="form.status"
170
+ :items="statusOptions"
171
+ label="用户状态"
172
+ dense
173
+ outlined
174
+ hide-details
175
+ class="mb-2"
176
+ ></v-select>
177
+ <v-text-field
178
+ v-model="form.keyword"
179
+ label="关键词"
180
+ dense
181
+ outlined
182
+ hide-details
183
+ placeholder="输入搜索关键词"
184
+ ></v-text-field>
185
+ </template>
186
+ </jh-scene>
187
+ <v-card class="mt-4" outlined>
188
+ <v-card-text>
189
+ <h4>当前场景信息:</h4>
190
+ <pre>{{ currentScene }}</pre>
191
+ </v-card-text>
192
+ </v-card>
193
+ </div>
194
+ `,
195
+ computed: {
196
+ statusOptions() {
197
+ return [
198
+ { text: '全部', value: '' },
199
+ { text: '活跃', value: 'active' },
200
+ { text: '不活跃', value: 'inactive' },
201
+ { text: '待审核', value: 'pending' },
202
+ ];
203
+ },
204
+ },
205
+ methods: {
206
+ handleSceneChange(scene) {
207
+ console.log('Scene changed:', scene);
208
+ this.currentScene = scene;
209
+ },
210
+ handleSceneCreated(scene) {
211
+ console.log('Scene created:', scene);
212
+ alert('场景创建成功: ' + scene.name);
213
+ },
214
+ handleSceneDeleted(scene) {
215
+ console.log('Scene deleted:', scene);
216
+ alert('场景已删除: ' + scene.name);
217
+ },
218
+ handleError(message) {
219
+ console.error('Error:', message);
220
+ alert('错误: ' + message);
221
+ },
222
+ },
223
+ }),
224
+ };
225
+
226
+ // 无操作按钮
227
+ export const 无操作按钮 = {
228
+ args: {
229
+ ...基础示例.args,
230
+ showActionBtn: false,
231
+ },
232
+ render: 基础示例.render,
233
+ };
234
+
235
+ // 本地存储
236
+ export const 本地存储 = {
237
+ args: {
238
+ ...基础示例.args,
239
+ useLocalStorage: true,
240
+ storageKey: 'demo-scene-list',
241
+ },
242
+ render: (args) => ({
243
+ components: { JhScene },
244
+ data() {
245
+ return {
246
+ args,
247
+ currentScene: null,
248
+ };
249
+ },
250
+ template: `
251
+ <div>
252
+ <v-alert type="info" dense text class="mb-4">
253
+ <strong>提示:</strong>此示例使用 localStorage 持久化场景数据,刷新页面后场景数据不会丢失。
254
+ </v-alert>
255
+ <jh-scene
256
+ v-bind="args"
257
+ @scene-change="handleSceneChange"
258
+ @scene-created="handleSceneCreated"
259
+ @scene-deleted="handleSceneDeleted"
260
+ @error="handleError"
261
+ />
262
+ <v-card class="mt-4" outlined>
263
+ <v-card-text>
264
+ <h4>当前场景信息:</h4>
265
+ <pre>{{ currentScene }}</pre>
266
+ </v-card-text>
267
+ </v-card>
268
+ </div>
269
+ `,
270
+ methods: {
271
+ handleSceneChange(scene) {
272
+ console.log('Scene changed:', scene);
273
+ this.currentScene = scene;
274
+ },
275
+ handleSceneCreated(scene) {
276
+ console.log('Scene created:', scene);
277
+ alert('场景创建成功: ' + scene.name);
278
+ },
279
+ handleSceneDeleted(scene) {
280
+ console.log('Scene deleted:', scene);
281
+ alert('场景已删除: ' + scene.name);
282
+ },
283
+ handleError(message) {
284
+ console.error('Error:', message);
285
+ alert('错误: ' + message);
286
+ },
287
+ },
288
+ }),
289
+ };
290
+
291
+ // 多场景
292
+ export const 多场景 = {
293
+ args: {
294
+ scenes: [
295
+ { id: 'all', name: '全部', default: true, system: true, form: {} },
296
+ { id: 'new', name: '最新', system: true, form: { sort: 'date_desc' } },
297
+ { id: 'hot', name: '热门', system: true, form: { sort: 'views_desc' } },
298
+ { id: 'pending', name: '待审核', system: true, form: { status: 'pending' } },
299
+ { id: 'approved', name: '已通过', system: true, form: { status: 'approved' } },
300
+ { id: 'rejected', name: '已拒绝', system: true, form: { status: 'rejected' } },
301
+ { id: 'draft', name: '草稿', system: true, form: { status: 'draft' } },
302
+ ],
303
+ showActionBtn: true,
304
+ useLocalStorage: false,
305
+ },
306
+ render: 基础示例.render,
307
+ };
308
+
309
+ // 单个场景
310
+ export const 单个场景 = {
311
+ args: {
312
+ scenes: [{ id: 'all', name: '全部数据', default: true, system: true, form: {} }],
313
+ showActionBtn: true,
314
+ useLocalStorage: false,
315
+ },
316
+ render: 基础示例.render,
317
+ };
@@ -0,0 +1,376 @@
1
+ <template>
2
+ <div class="jh-scene">
3
+ <v-row class="ma-0">
4
+ <!-- 场景列表 -->
5
+ <v-btn-toggle
6
+ :value="currentSceneIndex"
7
+ mandatory
8
+ dense
9
+ :class="{ 'mr-md-2': showActionBtn }"
10
+ color="success"
11
+ @change="handleSceneChange"
12
+ >
13
+ <v-btn small v-for="(scene, index) in sceneList" :key="index">{{ scene.name }}</v-btn>
14
+ </v-btn-toggle>
15
+
16
+ <!-- 场景操作菜单 -->
17
+ <v-menu v-if="showActionBtn" offset-y v-model="isSceneOperationShown">
18
+ <template v-slot:activator="{ on, attrs }">
19
+ <v-btn-toggle dense color="white">
20
+ <v-btn small v-bind="attrs" v-on="on">
21
+ <v-icon small class="mx-0">mdi-chevron-down</v-icon>
22
+ </v-btn>
23
+ </v-btn-toggle>
24
+ </template>
25
+ <v-list dense class="pb-0">
26
+ <v-list-item @click="openCreateSceneDialog">
27
+ <v-list-item-title class="success--text">
28
+ <v-icon small class="success--text">mdi-plus</v-icon>
29
+ <span class="success--text">新建场景</span>
30
+ </v-list-item-title>
31
+ </v-list-item>
32
+ <v-list-item @click="openSceneListDialog">
33
+ <v-list-item-title class="success--text">
34
+ <v-icon small class="success--text">mdi-cog-outline</v-icon>
35
+ <span class="success--text">场景管理</span>
36
+ </v-list-item-title>
37
+ </v-list-item>
38
+ </v-list>
39
+ </v-menu>
40
+ </v-row>
41
+
42
+ <!-- 新建场景dialog -->
43
+ <v-dialog v-model="isCreateSceneDialogShown" persistent width="360px">
44
+ <v-card>
45
+ <v-card-title>
46
+ <v-row class="ma-0">
47
+ <div style="font-size: 16px">新建场景</div>
48
+ <v-spacer></v-spacer>
49
+ <v-btn class="elevation-0" fab x-small @click="closeCreateSceneDialog">
50
+ <v-icon dark>mdi-close</v-icon>
51
+ </v-btn>
52
+ </v-row>
53
+ </v-card-title>
54
+ <v-card-text>
55
+ <div class="mb-2">
56
+ <span class="jh-input-label">场景名称</span>
57
+ <v-text-field
58
+ class="jh-v-input"
59
+ dense
60
+ single-line
61
+ filled
62
+ hide-details
63
+ v-model="createItem.name"
64
+ placeholder="场景名称,最多10个字符"
65
+ :rules="validationRules.requireRules"
66
+ ></v-text-field>
67
+ </div>
68
+ <div v-if="hasFormSlot" class="mb-2">
69
+ <span class="jh-input-label">筛选条件</span>
70
+ <slot name="form" :form="createItem.form"></slot>
71
+ </div>
72
+ </v-card-text>
73
+ <v-card-actions class="pb-4">
74
+ <v-row class="ma-0 pa-6 pt-2 justify-end">
75
+ <v-btn class="ml-2" @click="doCreateScene" small color="success">保存</v-btn>
76
+ <v-btn class="ml-2" @click="closeCreateSceneDialog" small>取消</v-btn>
77
+ </v-row>
78
+ </v-card-actions>
79
+ </v-card>
80
+ </v-dialog>
81
+
82
+ <!-- 场景列表dialog -->
83
+ <v-dialog v-model="isSceneListShown" persistent width="360px">
84
+ <v-card class="scene-manage">
85
+ <v-card-title>
86
+ <v-row class="ma-0">
87
+ <div style="font-size: 16px">场景管理</div>
88
+ <v-spacer></v-spacer>
89
+ <v-btn class="elevation-0" fab x-small @click="closeSceneListDialog">
90
+ <v-icon dark>mdi-close</v-icon>
91
+ </v-btn>
92
+ </v-row>
93
+ </v-card-title>
94
+ <v-card-text class="pb-2">
95
+ <v-card outlined>
96
+ <v-list dense class="scene-list">
97
+ <!-- 场景列表数据 -->
98
+ <template v-if="sceneCustomList && sceneCustomList.length">
99
+ <v-list-item class="scene-item px-2" v-for="item in sceneCustomList" :key="item.id">
100
+ <v-list-item-content>{{ item.name }}</v-list-item-content>
101
+ <v-list-item-icon @click.stop="doDeleteScene(item)">
102
+ <v-icon size="22">mdi-delete</v-icon>
103
+ </v-list-item-icon>
104
+ </v-list-item>
105
+ </template>
106
+ <template v-else>
107
+ <div class="text-center grey--text" style="line-height: 100px">暂无自定义场景</div>
108
+ </template>
109
+ </v-list>
110
+ </v-card>
111
+ </v-card-text>
112
+ <v-card-actions class="pb-4">
113
+ <v-row class="ma-0 pa-6 pt-2 justify-end">
114
+ <v-btn class="ml-2" @click="changeToCreateDialog" small color="success">新建场景</v-btn>
115
+ <v-btn class="ml-2" @click="closeSceneListDialog" small>取消</v-btn>
116
+ </v-row>
117
+ </v-card-actions>
118
+ </v-card>
119
+ </v-dialog>
120
+ </div>
121
+ </template>
122
+
123
+ <script>
124
+ export default {
125
+ name: 'JhScene',
126
+ props: {
127
+ // 初始表单数据
128
+ initFormData: {
129
+ type: Object,
130
+ default: () => ({}),
131
+ },
132
+ // localStorage 存储的 key
133
+ storageKey: {
134
+ type: String,
135
+ default: 'jh-scene-list',
136
+ },
137
+ // 初始场景列表
138
+ scenes: {
139
+ type: Array,
140
+ default: () => [],
141
+ },
142
+ // 当前场景ID
143
+ currentSceneId: {
144
+ type: String,
145
+ default: null,
146
+ },
147
+ // 是否显示操作按钮
148
+ showActionBtn: {
149
+ type: Boolean,
150
+ default: true,
151
+ },
152
+ // 是否使用 localStorage
153
+ useLocalStorage: {
154
+ type: Boolean,
155
+ default: false,
156
+ },
157
+ },
158
+ data() {
159
+ return {
160
+ validationRules: {
161
+ requireRules: [(v) => !!v || '此项必填'],
162
+ },
163
+ // 全部场景列表
164
+ sceneList: [],
165
+ // 自定义场景列表
166
+ sceneCustomList: [],
167
+ // 场景操作数据
168
+ deleteItem: {},
169
+ createItem: {
170
+ name: '',
171
+ form: {},
172
+ },
173
+ // 新建场景弹窗
174
+ isCreateSceneDialogShown: false,
175
+ // 场景操作菜单
176
+ isSceneOperationShown: false,
177
+ // 场景管理弹窗
178
+ isSceneListShown: false,
179
+ // 当前场景索引
180
+ currentSceneIndex: 0,
181
+ };
182
+ },
183
+ computed: {
184
+ hasFormSlot() {
185
+ return !!this.$scopedSlots.form;
186
+ },
187
+ },
188
+ watch: {
189
+ scenes: {
190
+ handler(newVal) {
191
+ if (newVal && newVal.length > 0) {
192
+ this.sceneList = [...newVal];
193
+ this.updateCustomSceneList();
194
+ }
195
+ },
196
+ immediate: true,
197
+ },
198
+ },
199
+ created() {
200
+ this.initSceneList();
201
+ },
202
+ methods: {
203
+ // 初始化场景列表
204
+ initSceneList() {
205
+ if (this.useLocalStorage) {
206
+ this.getSceneListFromStorage();
207
+ } else if (this.scenes && this.scenes.length > 0) {
208
+ this.sceneList = [...this.scenes];
209
+ }
210
+ this.updateCustomSceneList();
211
+ this.useDefaultScene();
212
+ },
213
+
214
+ // 从 localStorage 获取场景列表
215
+ getSceneListFromStorage() {
216
+ let localSceneList = localStorage.getItem(this.storageKey);
217
+ if (localSceneList == null) localSceneList = '[]';
218
+ localSceneList = JSON.parse(localSceneList);
219
+ this.sceneList = localSceneList;
220
+ },
221
+
222
+ // 保存场景列表到 localStorage
223
+ saveSceneListToStorage() {
224
+ if (this.useLocalStorage) {
225
+ localStorage.setItem(this.storageKey, JSON.stringify(this.sceneList));
226
+ }
227
+ },
228
+
229
+ // 更新自定义场景列表
230
+ updateCustomSceneList() {
231
+ this.sceneCustomList = this.sceneList.filter((item) => !item.system);
232
+ },
233
+
234
+ // 使用默认场景
235
+ useDefaultScene() {
236
+ let defaultScene = this.sceneList.find((item) => item.default === true);
237
+ if (!defaultScene && this.sceneList.length > 0) {
238
+ defaultScene = this.sceneList[0];
239
+ }
240
+ if (defaultScene) {
241
+ this.currentSceneIndex = this.sceneList.findIndex((item) => item.id === defaultScene.id);
242
+ this.useScene(defaultScene);
243
+ }
244
+ },
245
+
246
+ // 使用场景
247
+ useScene(scene) {
248
+ this.$emit('scene-change', scene);
249
+ },
250
+
251
+ // 处理场景切换
252
+ handleSceneChange(index) {
253
+ this.currentSceneIndex = index;
254
+ if (this.sceneList[index]) {
255
+ this.useScene(this.sceneList[index]);
256
+ }
257
+ },
258
+
259
+ // 生成UUID
260
+ getUUID(prefix) {
261
+ const s = [];
262
+ const hexDigits = '0123456789abcdef';
263
+ for (let i = 0; i < 10; i++) {
264
+ s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
265
+ }
266
+ s[14] = '4';
267
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
268
+ const uuid = s.join('');
269
+ return prefix + '-' + uuid;
270
+ },
271
+
272
+ // 准备新建场景数据
273
+ prepareCreateSceneData() {
274
+ this.createItem = {
275
+ name: '',
276
+ form: { ...this.initFormData },
277
+ id: this.getUUID('scene'),
278
+ };
279
+ },
280
+
281
+ // 打开新建场景对话框
282
+ openCreateSceneDialog() {
283
+ this.prepareCreateSceneData();
284
+ this.isCreateSceneDialogShown = true;
285
+ this.isSceneOperationShown = false;
286
+ },
287
+
288
+ // 关闭新建场景对话框
289
+ closeCreateSceneDialog() {
290
+ this.isCreateSceneDialogShown = false;
291
+ },
292
+
293
+ // 验证场景名称
294
+ validateSceneName() {
295
+ const newSceneName = this.createItem.name;
296
+ if (!newSceneName) {
297
+ throw new Error('场景名称不能为空');
298
+ }
299
+ const nameExist = this.sceneList.some((item) => item.name === newSceneName);
300
+ if (nameExist) {
301
+ throw new Error('场景名称重复');
302
+ }
303
+ },
304
+
305
+ // 创建场景
306
+ doCreateScene() {
307
+ try {
308
+ this.validateSceneName();
309
+ const newScene = { ...this.createItem };
310
+ this.sceneList.push(newScene);
311
+ this.saveSceneListToStorage();
312
+ this.updateCustomSceneList();
313
+ this.closeCreateSceneDialog();
314
+
315
+ // 使用新创建的场景
316
+ this.currentSceneIndex = this.sceneList.length - 1;
317
+ this.useScene(newScene);
318
+
319
+ this.$emit('scene-created', newScene);
320
+ } catch (error) {
321
+ this.$emit('error', error.message);
322
+ }
323
+ },
324
+
325
+ // 打开场景列表对话框
326
+ openSceneListDialog() {
327
+ this.isSceneListShown = true;
328
+ this.isSceneOperationShown = false;
329
+ },
330
+
331
+ // 关闭场景列表对话框
332
+ closeSceneListDialog() {
333
+ this.isSceneListShown = false;
334
+ },
335
+
336
+ // 切换到新建对话框
337
+ changeToCreateDialog() {
338
+ this.closeSceneListDialog();
339
+ this.openCreateSceneDialog();
340
+ },
341
+
342
+ // 删除场景
343
+ doDeleteScene(item) {
344
+ this.deleteItem = { ...item };
345
+
346
+ // 如果删除的是当前场景,切换到默认场景
347
+ if (this.sceneList[this.currentSceneIndex]?.id === item.id) {
348
+ this.useDefaultScene();
349
+ }
350
+
351
+ this.sceneList = this.sceneList.filter((scene) => scene.id !== item.id);
352
+ this.saveSceneListToStorage();
353
+ this.updateCustomSceneList();
354
+
355
+ this.$emit('scene-deleted', item);
356
+ },
357
+ },
358
+ };
359
+ </script>
360
+
361
+ <style scoped>
362
+ .jh-scene {
363
+ display: inline-block;
364
+ }
365
+
366
+ .scene-manage >>> .scene-item:not(:last-child) {
367
+ border-bottom: 1px solid #eeeeee;
368
+ }
369
+
370
+ .jh-input-label {
371
+ font-size: 14px;
372
+ color: rgba(0, 0, 0, 0.6);
373
+ display: block;
374
+ margin-bottom: 8px;
375
+ }
376
+ </style>