wui-components-v2 1.1.33 → 1.1.34

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/api/sys.ts ADDED
@@ -0,0 +1,7 @@
1
+ import req from './core/index'
2
+ // 获取语言包
3
+ export function language(language:string): Promise<any> {
4
+ return req({
5
+ url: `/v3/multilingual?language=${language}`,
6
+ })
7
+ }
@@ -238,9 +238,9 @@ function select({ item }) {
238
238
  <wd-button v-if="props.item.buttons.includes('singleDelete')" size="small" type="error" @click="del()">
239
239
  删除
240
240
  </wd-button>
241
- <ActionPopup
242
- v-model:show="actionItemShow" :enum-column="enumColumn" :zpaging="props.zpaging"
243
- :field-group="actionItem" :code="props.code"
241
+ <ActionPopup
242
+ v-model:show="actionItemShow" :enum-column="props.enumColumn" :zpaging="props.zpaging"
243
+ :field-group="actionItem" :code="props.code"
244
244
  />
245
245
  <!-- 更多按钮 -->
246
246
  <wd-action-sheet v-model="showMoreButn" :actions="morebutns" @close="closeMoreButn" @select="select" />
@@ -107,7 +107,7 @@ function toggleCollapse(contentId: string) {
107
107
  </view>
108
108
 
109
109
  <!-- 订单内容 -->
110
- <view v-if="props.model === 'complex'" class="p-4">
110
+ <view v-if="props.model === 'complex'" class="p-4 pt-0">
111
111
  <!-- 商品列表 -->
112
112
  <view class="mb-3">
113
113
  <!-- 订单信息 -->
@@ -238,7 +238,18 @@ defineExpose({
238
238
  filterable
239
239
  />
240
240
  <wd-calendar
241
- v-else-if="ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'datetime' || ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'date'"
241
+ v-else-if=" ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'date'"
242
+ v-model="model[item.sourceId]"
243
+ type="date"
244
+ :label="item.title"
245
+ label-width="100px"
246
+ :prop="item.sourceId"
247
+ :readonly="item.disabled || item.rowEditType === 'readonly'"
248
+ :clearable="!item.disabled"
249
+ :placeholder="`请输入${item.title}`"
250
+ />
251
+ <wd-calendar
252
+ v-else-if="ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'datetime'"
242
253
  v-model="model[item.sourceId]"
243
254
  type="datetime"
244
255
  :label="item.title"
@@ -6,7 +6,7 @@ import ControlTypeSupportor from '../../utils/control-type-supportor'
6
6
  import VideoPlay from '../video-play/video-play.vue'
7
7
  import AudioPlay from '../audio-play/audio-play.vue'
8
8
  import { useManualTheme } from '../../composables/useManualTheme'
9
- import openValueMore from './open-value-more.vue'
9
+ import openValueMore from './open-value-more.vue'
10
10
 
11
11
  defineOptions({
12
12
  name: 'LabelValue',
@@ -51,8 +51,8 @@ function isControlType(item: Columns): string {
51
51
  :src="uitem.url"
52
52
  >
53
53
  <template #error>
54
- <view class="flex items-center pt-4px">
55
- 暂无图片~
54
+ <view class="flex items-center pt-4px">
55
+ 暂无图片~
56
56
  </view>
57
57
  </template>
58
58
  <template #loading>
@@ -10,6 +10,7 @@ import CardBotomButtons from '../card-botom-buttons/card-botom-buttons.vue'
10
10
  import { generateHighResolutionID } from '../../utils/index'
11
11
  import productCard from '../product-card/product-card.vue'
12
12
  import tabSearch from '../tab-search/tab-search.vue'
13
+ import ControlTypeSupportor from '../../utils/control-type-supportor'
13
14
 
14
15
  defineOptions({
15
16
  name: 'WuiList',
@@ -152,17 +153,17 @@ async function getEnums() {
152
153
  return [...acc, ...cur.writes]
153
154
  }, []) || []
154
155
  columns.forEach((item: any) => {
155
- if (item.controlType === 'select' || item.controlType === 'multiselect') {
156
+ if (ControlTypeSupportor.getControlType(item) === 'select' || ControlTypeSupportor.getControlType(item) === 'multiselect') {
156
157
  params.push(`mstrucIds=${item.mstrucId}`)
157
158
  }
158
159
  })
159
160
  criterias.forEach((item: any) => {
160
- if (item.controlType === 'select' || item.controlType === 'multiselect') {
161
+ if (ControlTypeSupportor.getControlType(item) === 'select' || ControlTypeSupportor.getControlType(item) === 'multiselect') {
161
162
  params.push(`mstrucIds=${item.mstrucId}`)
162
163
  }
163
164
  })
164
165
  writes.forEach((item: any) => {
165
- if (item.controlType === 'select' || item.controlType === 'multiselect') {
166
+ if (ControlTypeSupportor.getControlType(item) === 'select' || ControlTypeSupportor.getControlType(item) === 'multiselect') {
166
167
  params.push(`mstrucIds=${item.mstrucId}`)
167
168
  }
168
169
  })
@@ -191,7 +192,7 @@ function submitSearch(data: any) {
191
192
  function tabSearchClick(data: any) {
192
193
  if (searchData.value) {
193
194
  tabSearchData.value = `${searchData.value}&${data}`
194
- }
195
+ }
195
196
  else {
196
197
  tabSearchData.value = data
197
198
  }
@@ -218,8 +219,8 @@ function tabSearchClick(data: any) {
218
219
  :page-title="pageTitle" :page-type="pageType"
219
220
  />
220
221
  </view>
221
- <view v-if="config.split2TabCriterias">
222
- <tabSearch :split2-tab-criterias="config.split2TabCriterias" :enums="enumColumn" @click="tabSearchClick" />
222
+ <view v-if="config.split2TabCriterias">
223
+ <tabSearch :split2-tab-criterias="config.split2TabCriterias" :enums="enumColumn" @click="tabSearchClick" />
223
224
  </view>
224
225
  </template>
225
226
  <view v-if="config.showType === 'cardList'">
@@ -0,0 +1,320 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * BannerCarousel.vue
4
+ * 原生实现的 Banner 轮播组件
5
+ * 功能:自动播放、手动滑动切换、鼠标悬停暂停、分页器点击跳转
6
+ */
7
+ import { onMounted, onUnmounted, ref } from 'vue'
8
+
9
+ /** 从 mockData 获取轮播数据 */
10
+ const banners: any = [
11
+ {
12
+ id: 1,
13
+ title: '欢迎使用企业门户',
14
+ subtitle: '高效协同,智慧办公',
15
+ bgColor: 'bg-gradient-to-r from-indigo-500 to-purple-600',
16
+ icon: 'building-office',
17
+ },
18
+ {
19
+ id: 2,
20
+ title: '本周会议安排',
21
+ subtitle: '3场会议待参加',
22
+ bgColor: 'bg-gradient-to-r from-emerald-500 to-teal-600',
23
+ icon: 'calendar',
24
+ },
25
+ {
26
+ id: 3,
27
+ title: '系统升级通知',
28
+ subtitle: 'v2.5.0 新功能上线',
29
+ bgColor: 'bg-gradient-to-r from-orange-500 to-amber-600',
30
+ icon: 'sparkles',
31
+ },
32
+ ]
33
+
34
+ /**
35
+ * 图标映射表
36
+ * 根据 banner.icon 字段映射对应的 Heroicons 组件
37
+ */
38
+ // const iconMap: Record<string, any> = {
39
+ // 'building-office': BuildingOfficeIcon,
40
+ // 'calendar': CalendarIcon,
41
+ // 'sparkles': SparklesIcon
42
+ // }
43
+
44
+ /** 当前显示的轮播项索引 */
45
+ const currentIndex = ref(0)
46
+
47
+ /** 鼠标是否悬停在轮播区域 */
48
+ const isHovered = ref(false)
49
+
50
+ /** 是否正在拖动(手动滑动中) */
51
+ const isDragging = ref(false)
52
+
53
+ /** 拖动偏移量(px),用于实时跟随手指/鼠标移动 */
54
+ const dragOffset = ref(0)
55
+
56
+ /** 自动播放的定时器引用 */
57
+ let autoplayTimer: ReturnType<typeof setInterval> | null = null
58
+
59
+ /** 拖动开始时的 X 坐标 */
60
+ let startX = 0
61
+
62
+ /**
63
+ * 启动自动播放
64
+ * 每 3 秒切换到下一张,循环播放
65
+ */
66
+ function startAutoplay() {
67
+ if (autoplayTimer)
68
+ clearInterval(autoplayTimer)
69
+ autoplayTimer = setInterval(() => {
70
+ currentIndex.value = (currentIndex.value + 1) % banners.length
71
+ }, 10000)
72
+ }
73
+
74
+ /**
75
+ * 停止自动播放
76
+ * 清除定时器并置空引用
77
+ */
78
+ function stopAutoplay() {
79
+ if (autoplayTimer) {
80
+ clearInterval(autoplayTimer)
81
+ autoplayTimer = null
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 跳转到指定索引的轮播项
87
+ * @param index 目标索引
88
+ */
89
+ function goToSlide(index: number) {
90
+ currentIndex.value = index
91
+ }
92
+
93
+ /**
94
+ * 切换到下一张轮播项
95
+ * 使用取模实现循环(最后一张 -> 第一张)
96
+ */
97
+ function nextSlide() {
98
+ currentIndex.value = (currentIndex.value + 1) % banners.length
99
+ }
100
+
101
+ /**
102
+ * 切换到上一张轮播项
103
+ * 使用取模实现循环(第一张 -> 最后一张)
104
+ */
105
+ function prevSlide() {
106
+ currentIndex.value = (currentIndex.value - 1 + banners.length) % banners.length
107
+ }
108
+
109
+ /**
110
+ * 鼠标进入轮播区域
111
+ * 停止自动播放
112
+ */
113
+ function onMouseEnter() {
114
+ isHovered.value = true
115
+ stopAutoplay()
116
+ }
117
+
118
+ /**
119
+ * 拖动开始
120
+ * 记录起始 X 坐标,重置拖动偏移量
121
+ * @param clientX 鼠标/触摸的 X 坐标
122
+ */
123
+ function onDragStart(clientX: number) {
124
+ isDragging.value = true
125
+ startX = clientX
126
+ dragOffset.value = 0
127
+ }
128
+
129
+ /**
130
+ * 拖动中
131
+ * 实时更新 dragOffset,实现跟随效果
132
+ * @param clientX 鼠标/触摸的当前 X 坐标
133
+ */
134
+ function onDragMove(clientX: number) {
135
+ if (!isDragging.value)
136
+ return
137
+ dragOffset.value = clientX - startX
138
+ }
139
+
140
+ /**
141
+ * 拖动结束
142
+ * 判断滑动方向和距离:
143
+ * - 左滑 > 50px:切换下一张
144
+ * - 右滑 > 50px:切换上一张
145
+ * - 否则:回弹到原位
146
+ * 拖动结束后恢复自动播放
147
+ */
148
+ function onDragEnd() {
149
+ if (!isDragging.value)
150
+ return
151
+ isDragging.value = false
152
+
153
+ const threshold = 50
154
+ if (dragOffset.value < -threshold) {
155
+ nextSlide()
156
+ }
157
+ else if (dragOffset.value > threshold) {
158
+ prevSlide()
159
+ }
160
+
161
+ dragOffset.value = 0
162
+ startAutoplay()
163
+ }
164
+
165
+ /** 触摸开始事件(移动端) */
166
+ function onTouchStart(e: TouchEvent) {
167
+ onDragStart(e.touches[0]?.clientX ?? 0)
168
+ }
169
+
170
+ /** 触摸移动事件(移动端) */
171
+ function onTouchMove(e: TouchEvent) {
172
+ onDragMove(e.touches[0]?.clientX ?? 0)
173
+ }
174
+
175
+ /** 触摸结束事件(移动端) */
176
+ function onTouchEnd() {
177
+ onDragEnd()
178
+ }
179
+
180
+ /** 鼠标按下事件(桌面端) */
181
+ function onMouseDown(e: MouseEvent) {
182
+ onDragStart(e.clientX)
183
+ }
184
+
185
+ /** 鼠标移动事件(桌面端拖动中) */
186
+ function onMouseMove(e: MouseEvent) {
187
+ onDragMove(e.clientX)
188
+ }
189
+
190
+ /** 鼠标释放事件(桌面端拖动结束) */
191
+ function onMouseUp() {
192
+ onDragEnd()
193
+ }
194
+
195
+ /**
196
+ * 鼠标离开轮播区域
197
+ * 如果正在拖动中则触发拖动结束
198
+ * 恢复自动播放
199
+ */
200
+ function onMouseLeave() {
201
+ if (isDragging.value) {
202
+ onDragEnd()
203
+ }
204
+ isHovered.value = false
205
+ startAutoplay()
206
+ }
207
+
208
+ /** 组件挂载时启动自动播放 */
209
+ onMounted(() => {
210
+ startAutoplay()
211
+ })
212
+
213
+ /** 组件卸载时停止自动播放,防止内存泄漏 */
214
+ onUnmounted(() => {
215
+ stopAutoplay()
216
+ })
217
+ </script>
218
+
219
+ <template>
220
+ <!--
221
+ 外层容器:
222
+ - select-none:防止拖动时选中文本
223
+ - 监听 mouseenter/mouseleave 用于悬停暂停
224
+ -->
225
+ <view
226
+ class="mt-6 select-none overflow-hidden rounded-2xl pb-2"
227
+ @mouseenter="onMouseEnter"
228
+ @mouseleave="onMouseLeave"
229
+ >
230
+ <!--
231
+ 轮播内容区:
232
+ - overflow-hidden:裁剪只显示当前 slide
233
+ - cursor-grab/active:拖动时显示抓取手势
234
+ - 监听 mousedown/mousemove/mouseup(桌面端)
235
+ - 监听 touchstart/touchmove/touchend(移动端)
236
+ -->
237
+ <view
238
+ class="cursor-grab overflow-hidden active:cursor-grabbing"
239
+ @mousedown="onMouseDown"
240
+ @mousemove="onMouseMove"
241
+ @mouseup="onMouseUp"
242
+ @mouseleave="onMouseLeave"
243
+ @touchstart="onTouchStart"
244
+ @touchmove="onTouchMove"
245
+ @touchend="onTouchEnd"
246
+ >
247
+ <!--
248
+ 轮播轨道:
249
+ - flex:水平排列所有 slide
250
+ - transition-transform:
251
+ - 拖动中:transition-none(实时跟随,无动画)
252
+ - 正常切换:transition-transform duration-[600ms] ease(600ms 动画)
253
+ - translateX 计算:
254
+ - 基准位置:-${currentIndex * 100}%
255
+ - 拖动偏移:+ ${dragOffset}px
256
+ -->
257
+ <view
258
+ class="flex"
259
+ :class="isDragging ? 'transition-none' : 'transition-transform duration-[600ms] ease'"
260
+ :style="{ transform: `translateX(calc(-${currentIndex * 100}% + ${dragOffset}px))` }"
261
+ >
262
+ <!--
263
+ 单个轮播项:
264
+ - w-full flex-shrink-0:每个 slide 占满宽度且不压缩
265
+ -->
266
+ <view
267
+ v-for="banner in banners"
268
+ :key="banner.id"
269
+ class="w-full flex-shrink-0 cursor-pointer"
270
+ >
271
+ <!--
272
+ Banner 内容卡片:
273
+ - 动态背景色(bgColor)
274
+ - h-36:固定高度
275
+ - flex items-center:垂直居中
276
+ - px-6:水平内边距
277
+ - rounded-2xl:圆角
278
+ -->
279
+ <view
280
+ :class="banner.bgColor"
281
+ class="h-36 flex items-center rounded-2xl px-6"
282
+ >
283
+ <!-- 左侧文字区域 -->
284
+ <view class="flex-1">
285
+ <h3 class="text-lg text-white font-semibold">
286
+ {{ banner.title }}
287
+ </h3>
288
+ <p class="mt-1 text-sm text-white/80">
289
+ {{ banner.subtitle }}
290
+ </p>
291
+ </view>
292
+
293
+ <!-- 右侧图标区域 -->
294
+ <view class="h-14 w-14 flex items-center justify-center rounded-full bg-white/20">
295
+ <!-- <component :is="iconMap[banner.icon]" class="w-8 h-8 text-white" /> -->
296
+ <wd-icon name="calendar" size="26px" />
297
+ </view>
298
+ </view>
299
+ </view>
300
+ </view>
301
+ </view>
302
+
303
+ <!--
304
+ 分页器:
305
+ - flex justify-center gap-2:水平居中,dot 之间 8px 间距
306
+ - mt-4:与轮播内容保持间距
307
+ -->
308
+ <view class="mt-4 flex justify-center gap-2">
309
+ <view
310
+ v-for="(_, index) in banners"
311
+ :key="index"
312
+ class="h-1 w-1 cursor-pointer rounded-full transition-all duration-300"
313
+ :class="index === currentIndex
314
+ ? 'bg-white opacity-100'
315
+ : 'bg-white/50'"
316
+ @click="goToSlide(Number(index))"
317
+ />
318
+ </view>
319
+ </view>
320
+ </template>
@@ -0,0 +1,67 @@
1
+ <script lang="ts" setup>
2
+ import { defineOptions, defineProps } from 'vue'
3
+ import { useManualTheme } from '../../../composables/useManualTheme'
4
+ import { desaturateColor, getFirstChar } from '../../../utils'
5
+
6
+ import { useUser } from '../../../composables/useUser'
7
+ import { useMenus } from '../../../composables/useMenus'
8
+ import { useSectionMenus } from '../../../composables/useSectionMenus'
9
+
10
+ defineOptions({
11
+ name: 'Navbar',
12
+ })
13
+ defineProps<{
14
+ navTitle: string
15
+ }>()
16
+ const {
17
+ currentThemeColor,
18
+ } = useManualTheme()
19
+ const { messageBar } = useSectionMenus(true)
20
+ const { gotoPage } = useMenus()
21
+ const { nickName } = useUser()
22
+
23
+ function messageAction() {
24
+ if (messageBar.value) {
25
+ gotoPage(messageBar.value)
26
+ }
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <view class="bg-#fff pa-3 backdrop-filter .dark:bg-#000">
32
+ <view class="mx-auto max-w-7xl p-x-1">
33
+ <view class="flex items-center justify-between">
34
+ <view
35
+ class="bg-clip-text font-size-5 text-transparent font-600 font-sans"
36
+ :style="{ color: currentThemeColor.primary }"
37
+ >
38
+ {{ navTitle }}
39
+ </view>
40
+ <view class="flex items-center gap-2">
41
+ <button
42
+ v-if="messageBar"
43
+ :style="{ background: `linear-gradient(135deg, ${desaturateColor(currentThemeColor.primary, 0.1)} 0%, ${desaturateColor(currentThemeColor.primary, 0.1)} 100%)` }"
44
+ class="relative h-10 w-10 flex cursor-pointer items-center justify-center rounded-full border-none transition-all duration-300"
45
+ @click="messageAction"
46
+ >
47
+ <wd-icon name="notification" size="40rpx" :color="currentThemeColor.primary" />
48
+ <span
49
+ v-if="messageBar.count"
50
+ class="absolute right-[6px] top-[6px] h-[10px] w-[10px] animate-pulse border-2 border-b-blue rounded-full bg-red-6"
51
+ />
52
+ </button>
53
+ <view
54
+ :style="{ background: `linear-gradient(135deg, ${currentThemeColor.primary} 0%, ${currentThemeColor.primary} 100%)` }"
55
+ class="h-9 w-9 flex cursor-pointer items-center justify-center rounded-full text-sm text-white font-semibold shadow-sm"
56
+ >
57
+ {{ getFirstChar(nickName || 'U') }}
58
+ </view>
59
+ </view>
60
+ </view>
61
+ </view>
62
+ </view>
63
+ </template>
64
+
65
+ <style scoped>
66
+
67
+ </style>
@@ -0,0 +1,54 @@
1
+ <script lang="ts" setup>
2
+ import { computed, defineOptions } from 'vue'
3
+ import { RainbowColorGenerator } from '../../../utils'
4
+ import { useSectionMenus } from '../../../composables/useSectionMenus'
5
+ import {useLanguageStore} from '../../../store/language'
6
+ defineOptions({
7
+ name: 'QuickPanel',
8
+ })
9
+ const { quickPanel } = useSectionMenus(true)
10
+ const languageStore = useLanguageStore()
11
+ const colorGenerator = new RainbowColorGenerator()
12
+ const cstatistics = computed(() => {
13
+ return quickPanel.value.map((item) => {
14
+ const color = colorGenerator.getNextColor()
15
+ return {
16
+ ...item,
17
+ color,
18
+ }
19
+ })
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <view v-if="cstatistics.length > 0" class="pb-2">
25
+ <view>
26
+ <view class="mb-2 flex items-center gap-2 font-semibold .dark:text-white .light:color-#1f2937">
27
+ {{ languageStore.t('数据统计') }}
28
+ </view>
29
+ <view class="grid grid-cols-2 gap-3">
30
+ <view v-for="(item) in cstatistics" :key="item.id" :style="{ '--wbg-color': item.color }" class="relative relative overflow-hidden rounded-2xl bg-white p-5 shadow-[0_4px_16px_rgba(99,102,241,0.1)] transition-250 before:absolute before:right-0 before:top-0 before:h-20 before:w-20 before:translate-x-30% before:rounded-full before:bg-[var(--wbg-color)] dark:bg-black before:opacity-10 before:content-[''] before:-translate-y-30%">
31
+ <view class="mb-3 flex items-center justify-between">
32
+ <view class="h-8 w-8 flex items-center justify-center rounded-2" :style="{ background: item.color }">
33
+ <svg class="h-4 w-4 .dark:text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
35
+ </svg>
36
+ </view>
37
+ </view>
38
+ <view class="flex flex-col items-center justify-center">
39
+ <view class="text-size-2xl fw-800 .dark:text-white .light:color-dark">
40
+ {{ item.count }}
41
+ </view>
42
+ <view class="text-size-3 color-gray">
43
+ {{ languageStore.t(item.title)}}
44
+ </view>
45
+ </view>
46
+ </view>
47
+ </view>
48
+ </view>
49
+ </view>
50
+ </template>
51
+
52
+ <style scoped>
53
+
54
+ </style>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { useRouter } from 'uni-mini-router'
4
+
5
+ const router = useRouter()
6
+ const searchQuery = ref('')
7
+ const isFocused = ref(false)
8
+
9
+ function handleFocus() {
10
+ isFocused.value = true
11
+ }
12
+
13
+ function handleBlur() {
14
+ isFocused.value = false
15
+ }
16
+
17
+ // const handleSearch = () => {
18
+ // if (searchQuery.value.trim()) {
19
+ // console.log('Searching:', searchQuery.value)
20
+ // }
21
+ // }
22
+
23
+ function goToSearch() {
24
+ router.push('/pages/search-tabbar/index')
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <div
30
+ class="relative mt-4"
31
+ >
32
+ <div
33
+ class="h-12 flex items-center rounded-xl bg-white px-4 transition-all duration-200"
34
+ :class="isFocused ? 'ring-2 ring-indigo-500 shadow-lg' : 'shadow-md'"
35
+ >
36
+ <wd-icon name="search" class="h-5 w-5 flex-shrink-0 text-slate-400" size="22px" />
37
+ <input
38
+ v-model="searchQuery"
39
+ type="text"
40
+ placeholder="搜索菜单、功能..."
41
+ class="ml-3 flex-1 cursor-pointer bg-transparent text-base text-slate-800 outline-none placeholder:text-slate-400"
42
+ readonly
43
+ @click="goToSearch"
44
+ @focus="handleFocus"
45
+ @blur="handleBlur"
46
+ >
47
+ </div>
48
+ </div>
49
+ </template>