st-comp 0.0.44 → 0.0.45

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.
@@ -4,65 +4,99 @@
4
4
  @mousemove="isSelect = true"
5
5
  @mouseout="isSelect = false"
6
6
  >
7
+ <div class="kline-mainTips">
8
+ <MainTips :drawData="drawData" :activeIndex="activeIndex"/>
9
+ </div>
7
10
  <div class="kline-mainChart" ref="mainChartRef"></div>
8
- <div class="kline-subChart" ref="subChartRef"></div>
9
- <div class="kline-slideChart" ref="slideChartRef"></div>
11
+ <div class="kline-sub">
12
+ <KlineSub ref="klineSubRef" v-model="subIndicator" :drawData="drawData" :activeIndex="activeIndex" :subIndicatorList="subIndicatorList" />
13
+ </div>
14
+ <KlineSlide ref="slideChartRef" :data="slideData" @change="slideChange" />
10
15
  </div>
11
16
  </template>
12
17
 
13
18
  <script setup lang="ts">
14
- import { ref, onMounted, onUnmounted } from "vue"
19
+ import { ref, onMounted, onUnmounted, watch, computed } from "vue"
15
20
  import * as echarts from 'echarts'
16
21
  import type { EChartsType } from 'echarts'
17
- import { getMainOption, getSubOption, getSlideOption } from './option.js'
18
- import { getKlineDataApi } from './utils.js'
22
+ import { getMainOption } from './option.js'
23
+ import { getKlineDataApi, formatData, initTalib } from './utils.js'
19
24
  import dayjs from "dayjs"
25
+ import MainTips from "./components/MainTips/index.vue"
26
+ import klineConfig from './klineConfig.js'
27
+ import KlineSlide from "./components/KlineSlide/index.vue"
28
+ import KlineSub from "./components/KlineSub/index.vue"
20
29
 
21
- const config = {
22
- defaultShowDays: 50, // 默认展示天数
23
- preLoadDays: 50, // 预加载天数
30
+ const props = defineProps({
31
+ code: { type: String, required: true }, // 品种代码
32
+ freqId: { type: String, required: true }, // 周期
33
+ freqDict: { type: Object, required: true }, // 周期字典
34
+ mainIndicator: { type: String, required: true }, // 主图指标
35
+ mainIndicatorList: { type: Array, required: true }, // 主图指标列表
36
+ subIndicatorList: { type: Array, required: true }, // 副图指标列表
37
+ config: { type: Object, default: () => ({}) }, // 配置
38
+ })
39
+
40
+ const defaultconfig = {
41
+ defaultShowDays: 50, // 默认展示天数,会根据不同周期进行赋值
42
+ preLoadDays: 50, // 预加载天数,会分局不同周期进行赋值
43
+ maxShowDays: 500, // 最大展示天数,会分局不同周期进行赋值
44
+ defaultShowYears: 10, // 默认展示多少年数据
24
45
  preLoadCount: 800, // 预加载数据条数(用于计算指标线)
25
46
  loadCheckCount: 500, // 加载数据检查条数
47
+ loadAddCount: 2000, // 加载数据条数
26
48
  }
27
49
  let mainChart: EChartsType
28
- let subChart: EChartsType
29
- let slideChart: EChartsType
30
- let mainDataZoomTimer: any = null
31
- let slideDataZoomTimer: any = null
50
+ let mainDataZoomTimer: any = null // 主图数据缩放定时器
32
51
  let isLoadHistory: boolean = false // 是否正在加载历史数据
33
52
  let isloadAllHistory: boolean = false // 是否加载完全部历史数据
34
53
  let isLoadNew: boolean = false // 是否正在加载新数据
35
54
  let isloadAllNew: boolean = false // 是否加载完全部新数据
36
- let resizeRo: any
37
- let slideData: any[] = [] // 拖动轴数据
38
- let klineData: any[] = [] // 时间轴数据
55
+ let resizeRo: any // dom元素监听事件
56
+ let highlightTimer: any = null // 高亮处理事件的定时器
39
57
 
40
- const mainChartRef = ref<HTMLElement>()
41
- const subChartRef = ref<HTMLElement>()
42
- const slideChartRef = ref<HTMLElement>()
43
- const isSelect = ref(false)
58
+ const mainChartRef = ref<HTMLElement>() // 主图
59
+ const klineSubRef = ref<HTMLElement>() // 副图
60
+ const slideChartRef = ref<HTMLElement>() // 拖动轴
61
+ const isSelect = ref(false) // 是否选中,选中可通过键盘按键操作
62
+ const drawData = ref({
63
+ originData: [], // 原数据
64
+ xAxisData: [], // 时间数据
65
+ candlestickData: [], // k线数据
66
+ mainIndicatorData: [], // 主图指标线数据
67
+ subIndicatorData: [], // 副图数据
68
+ length: 0, // 数据长度
69
+ startTime: '', // 开始时间
70
+ endTime: '', // 结束时间
71
+ }) // 绘图数据
72
+ const slideData = ref([])
73
+ const activeIndex = ref(0) // 显示的index
74
+ const subIndicator = ref('VOL') // 副图指标
75
+
76
+ const config = computed(() => {
77
+ const freqName = props.freqDict.find((item: any) => item.value === props.freqId).label
78
+ return {
79
+ ...defaultconfig,
80
+ ...klineConfig.freqConfig[freqName],
81
+ ...props.config,
82
+ }
83
+ })
84
+
85
+ watch(
86
+ () => [props.code, props.freqId, props.mainIndicator, subIndicator],
87
+ () => {
88
+ slideChartRef.value?.reset()
89
+ },
90
+ { deep: true }
91
+ )
44
92
 
45
93
  onMounted(async () => {
46
- // 请求滚动条数据
47
- slideData = await getKlineData({
48
- queryType: '0',
49
- startTime: `${dayjs().subtract(5, 'year').format("YYYY-MM-DD")} 00:00:00`,
50
- endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
51
- cycle: '6',
52
- })
53
- // 请求首屏k线数据
54
- const { startTime, showStartTime, endTime, showEndTime } = getTime(slideData.length - 1 - config.defaultShowDays, slideData.length - 1)
55
- klineData = await getKlineData({
56
- queryType: '0',
57
- startTime: `${startTime} 00:00:00`,
58
- endTime: `${endTime} 24:00:00`,
59
- cycle: '5',
60
- })
61
- init()
94
+ // 初始化指标计算方法
95
+ await initTalib()
96
+ mainChart = echarts.init(mainChartRef.value)
97
+ klineSubRef.value.connect(mainChart)
62
98
  addEventListener()
63
- draw('main', { showStartTime, showEndTime })
64
- draw('sub', { showStartTime, showEndTime })
65
- draw('slide')
99
+ initChart()
66
100
  // 绑定resize事件
67
101
  let isFirst: boolean | null = true
68
102
  resizeRo = new ResizeObserver(() => {
@@ -71,8 +105,6 @@ onMounted(async () => {
71
105
  return
72
106
  }
73
107
  mainChart.resize()
74
- subChart.resize()
75
- slideChart.resize()
76
108
  })
77
109
  resizeRo.observe(mainChartRef.value)
78
110
  window.addEventListener('keydown', keyDownEvent)
@@ -81,206 +113,224 @@ onMounted(async () => {
81
113
  onUnmounted(() => {
82
114
  window.removeEventListener('keydown', keyDownEvent)
83
115
  mainChart.dispose()
84
- subChart.dispose()
85
- slideChart.dispose()
86
116
  resizeRo.disconnect()
87
117
  resizeRo = null
88
118
  })
89
119
 
90
- const init = () => {
91
- mainChart = echarts.init(mainChartRef.value)
92
- subChart = echarts.init(subChartRef.value)
93
- slideChart = echarts.init(slideChartRef.value)
94
- echarts.connect([mainChart, subChart])
120
+ // 项目初始化
121
+ const initChart = async () => {
122
+ // 配置初始化
123
+ isLoadHistory = false
124
+ isloadAllHistory = false
125
+ isLoadNew = false
126
+ isloadAllNew = false
127
+ const { defaultShowYears } = config.value
128
+ // 请求滚动条数据
129
+ await getKlineData({
130
+ queryType: '0',
131
+ startTime: `${dayjs().subtract(defaultShowYears, 'year').format("YYYY-MM-DD")} 00:00:00`,
132
+ endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
133
+ cycle: '6',
134
+ })
95
135
  }
96
136
 
137
+ // 绑定事件
97
138
  const addEventListener = () => {
98
139
  mainChart.on('datazoom', (params: any) => {
99
140
  clearTimeout(mainDataZoomTimer)
100
141
  mainDataZoomTimer = setTimeout(() => {
142
+ const { loadCheckCount, loadAddCount, preLoadCount } = config.value
101
143
  let startIndex = 0
102
144
  let endIndex = 0
103
145
  if (params.batch) {
104
146
  const { start, end } = params.batch[0]
105
- startIndex = Math.floor(start * klineData.length / 100)
106
- endIndex = end === 100 ? klineData.length - 1 : Math.floor(end * klineData.length / 100)
147
+ startIndex = Math.floor(start * drawData.value.length / 100)
148
+ endIndex = end === 100 ? drawData.value.length - 1 : Math.floor(end * drawData.value.length / 100)
107
149
  } else {
108
150
  startIndex = params.startValue
109
151
  endIndex = params.endValue
110
152
  }
111
153
  // 加载数据
112
- if (startIndex < config.loadCheckCount && isLoadHistory === false && isloadAllHistory === false) {
154
+ if (startIndex < loadCheckCount && isLoadHistory === false && isloadAllHistory === false) {
113
155
  // 左侧数据小于1000条,加载左侧数据
114
156
  isLoadHistory = true
115
157
  getMoreData({
116
158
  queryType: '1',
117
- endTime: klineData[0][0],
118
- cycle: '5',
119
- limit: 2000,
159
+ endTime: drawData.value.startTime,
160
+ cycle: props.freqId,
161
+ limit: loadAddCount + preLoadCount,
120
162
  })
121
163
  }
122
- if (endIndex > klineData.length - config.loadCheckCount && isLoadNew === false && isloadAllNew === false) {
164
+ if (endIndex > drawData.value.length - loadCheckCount && isLoadNew === false && isloadAllNew === false) {
123
165
  // 右侧数据小于1000条,加载右侧数据
124
166
  isLoadNew = true
125
167
  getMoreData({
126
168
  queryType: '2',
127
- startTime: klineData[klineData.length - 1][0],
128
- cycle: '5',
129
- limit: 2000,
169
+ startTime: drawData.value.endTime,
170
+ cycle: props.freqId,
171
+ limit: loadAddCount,
172
+ preLimit: preLoadCount, // 向前多查询数量
130
173
  })
131
174
  }
132
175
  // 重置滚动条
133
176
  const { startValue, endValue } = (mainChart.getOption().dataZoom as any[])[0]
134
- const startTime = (klineData[startValue][0] as string).split(' ')[0]
135
- const endTime = (klineData[endValue][0] as string).split(' ')[0]
136
- const slideTime = slideData.map(item => (item[0] as string).split(' ')[0])
137
- slideChart.dispatchAction({
138
- type: 'dataZoom',
139
- startValue: slideTime.indexOf(startTime),
140
- endValue: slideTime.indexOf(endTime)
141
- })
177
+ const klineDataTime = (mainChart.getOption().xAxis as any)[0].data
178
+ const startTime = (klineDataTime[startValue] as string).split(' ')[0]
179
+ const endTime = (klineDataTime[endValue] as string).split(' ')[0]
180
+ slideChartRef.value.resetSlide(startTime, endTime)
142
181
  clearTimeout(mainDataZoomTimer)
143
- }, 300)
144
- })
145
- slideChart.on('datazoom', (params: any) => {
146
- if(!params.dataZoomId) return
147
- clearTimeout(slideDataZoomTimer)
148
- slideDataZoomTimer = setTimeout(async() => {
149
- const { start, end } = params
150
- const startIndex = Math.floor(start * slideData.length / 100)
151
- const endIndex = end === 100 ? slideData.length - 1 : Math.floor(end * slideData.length / 100)
152
- const { startTime, showStartTime, endTime, showEndTime } = getTime(startIndex, endIndex)
153
- klineData = await getKlineData({
154
- queryType: '0',
155
- startTime: `${startTime} 00:00:00`,
156
- endTime: `${endTime} 24:00:00`,
157
- cycle: '5',
158
- })
159
- isLoadHistory = false
160
- isloadAllHistory = false
161
- isLoadNew = false
162
- isloadAllNew = false
163
- draw('main', { showStartTime, showEndTime })
164
- draw('sub', { showStartTime, showEndTime })
165
- clearTimeout(slideDataZoomTimer)
166
182
  }, 100)
167
183
  })
184
+ // 高亮事件
185
+ mainChart.on('highlight', (data: any) => {
186
+ let index = data.dataIndex || -1
187
+ if (data.batch ) {
188
+ index = typeof data?.batch[0]?.dataIndex === 'number' ? data?.batch[0]?.dataIndex : -1
189
+ }
190
+ clearTimeout(highlightTimer)
191
+ highlightTimer = setTimeout(() => {
192
+ activeIndex.value = index
193
+ clearTimeout(highlightTimer)
194
+ }, 20)
195
+ })
196
+ // 移出图表事件
197
+ mainChart.on('globalout', () => {
198
+ const index = mainChart.getOption().dataZoom[0].endValue
199
+ activeIndex.value = index
200
+ })
168
201
  }
169
202
 
170
- // 键盘事件
171
- const keyDownEvent = e => {
172
- // 只有选中或者按ctrl才激活按键
173
- if (!(e.ctrlKey || isSelect.value)) return
174
- const option = mainChart.getOption()
175
- let { startValue, endValue } = (option.dataZoom as any)[0]
176
- if (e.code === 'ArrowLeft') {
177
- // 左按键
178
- if (startValue === 0) {
179
- return
180
- }
181
- startValue = startValue - 1
182
- endValue = endValue - 1
183
- mainChart.dispatchAction({
184
- type: 'highlight',
185
- dataIndex: endValue,
186
- })
187
- } else if (e.code === 'ArrowRight') {
188
- // 右按键
189
- if (endValue === (option.xAxis as any)[0].data.length - 1) {
190
- return
191
- }
192
- startValue = startValue + 1
193
- endValue = endValue + 1
194
- mainChart.dispatchAction({
195
- type: 'highlight',
196
- dataIndex: endValue,
197
- })
198
- } else if (e.code === 'ArrowUp') {
199
- // 上按键-放大 最少保持5条数据
200
- if(endValue - startValue < 5) {
201
- return
202
- }
203
- const diff = Math.floor((endValue - startValue) / 2) + 1
204
- startValue = startValue + diff
205
- if (endValue - startValue < 5) {
206
- startValue = endValue - 4
207
- }
208
- } else if (e.code === 'ArrowDown') {
209
- // 下按键-缩小
210
- const diff = Math.min(500, (endValue - startValue))
211
- startValue = startValue - diff - 1
203
+ // 键盘事件
204
+ const keyDownEvent = e => {
205
+ // 只有选中或者按ctrl才激活按键
206
+ if (!(e.ctrlKey || isSelect.value)) return
207
+ const option = mainChart.getOption()
208
+ let { startValue, endValue } = (option.dataZoom as any)[0]
209
+ if (e.code === 'ArrowLeft') {
210
+ // 左按键
211
+ if (startValue === 0) {
212
+ return
212
213
  }
214
+ startValue = startValue - 1
215
+ endValue = endValue - 1
213
216
  mainChart.dispatchAction({
214
- type: 'dataZoom',
215
- startValue,
216
- endValue
217
+ type: 'highlight',
218
+ dataIndex: endValue,
217
219
  })
220
+ } else if (e.code === 'ArrowRight') {
221
+ // 右按键
222
+ if (endValue === (option.xAxis as any)[0].data.length - 1) {
223
+ return
224
+ }
225
+ startValue = startValue + 1
226
+ endValue = endValue + 1
227
+ mainChart.dispatchAction({
228
+ type: 'highlight',
229
+ dataIndex: endValue,
230
+ })
231
+ } else if (e.code === 'ArrowUp') {
232
+ // 上按键-放大 最少保持5条数据
233
+ if(endValue - startValue < 5) {
234
+ return
235
+ }
236
+ const diff = Math.floor((endValue - startValue) / 2) + 1
237
+ startValue = startValue + diff
238
+ if (endValue - startValue < 5) {
239
+ startValue = endValue - 4
240
+ }
241
+ } else if (e.code === 'ArrowDown') {
242
+ // 下按键-缩小
243
+ const diff = Math.min(500, (endValue - startValue))
244
+ startValue = startValue - diff - 1
218
245
  }
246
+ mainChart.dispatchAction({
247
+ type: 'dataZoom',
248
+ startValue,
249
+ endValue
250
+ })
251
+ }
219
252
 
220
- const getTime = (startIndex: number, endIndex: number) => {
221
- const startTime = (slideData[startIndex - config.preLoadDays < 0 ? 0 : startIndex - config.preLoadDays][0] as string).split(' ')[0]
222
- const showStartTime = (slideData[startIndex][0] as string).split(' ')[0]
223
- const endTime = (slideData[endIndex + config.preLoadDays > slideData.length ? slideData.length - 1 : endIndex + config.preLoadDays][0] as string).split(' ')[0]
224
- const showEndTime = (slideData[endIndex][0] as string).split(' ')[0]
225
- return { startTime, showStartTime, endTime, showEndTime }
253
+ // 拖动轴发生变化
254
+ const slideChange = async({ startTime, showStartTime, endTime, showEndTime }) => {
255
+ const { preLoadCount } = config.value
256
+ await getKlineData({
257
+ queryType: '0',
258
+ startTime,
259
+ endTime,
260
+ preLimit: preLoadCount, // 向前多查询数量
261
+ cycle: props.freqId,
262
+ })
263
+ draw('main', { showStartTime, showEndTime })
264
+ draw('sub', { showStartTime, showEndTime })
226
265
  }
227
266
 
267
+ // 获取更多数据
228
268
  const getMoreData = async(params: any) => {
229
- const { startValue, endValue } = (mainChart.getOption().dataZoom as any[])[0]
230
- const showStartTime = klineData[startValue][0]
231
- const showEndTime = klineData[endValue][0]
269
+ const { preLoadCount } = config.value
232
270
  const res = await getKlineData(params)
271
+ const { startValue, endValue } = (mainChart.getOption().dataZoom as any[])[0]
272
+ const klineDataTime = (mainChart.getOption().xAxis as any)[0].data
273
+ const showStartTime = klineDataTime[startValue]
274
+ const showEndTime = klineDataTime[endValue]
233
275
  if (params.queryType === '1') {
234
276
  // 加载历史数据
235
- if (new Date(slideData[0][0]) > new Date(res[0][0])) {
277
+ if (new Date(slideData.value[0][0]) > new Date(res[0][0])) {
236
278
  // 表示历史数据已加载完
237
279
  isloadAllHistory = true
238
- klineData = [
239
- ...res.filter(item => new Date(item[0]) >= new Date(slideData[0][0])),
240
- ...klineData.slice(1)
241
- ]
242
- } else {
243
- klineData = [...res, ...klineData.slice(1)]
244
280
  }
245
281
  isLoadHistory = false
246
282
  } else {
247
283
  // 加载新数据
248
- if (res.length === 0) {
284
+ if (res.length < 1 + preLoadCount) {
249
285
  // 表示已经加载完全部新数据
250
286
  isloadAllNew = true
251
287
  isLoadNew = false
252
288
  return
253
289
  }
254
- klineData = [...klineData, ...res.slice(1)]
290
+ isLoadNew = false
255
291
  }
256
292
  draw('main', { showStartTime, showEndTime })
257
293
  draw('sub', { showStartTime, showEndTime })
258
294
  }
259
295
 
296
+ // 获取k线数据
260
297
  const getKlineData = async(params: any) => {
261
- const res = await getKlineDataApi({
298
+ const apiParams = {
262
299
  contractType: 0, // 合约类型 0-主连 1-加权
263
- variety: "RU", // 品种
264
- preLimit: 0, // 向前多查询数量
265
- postLimit: 0, // 向后多查询数量
266
- right: 0, // 0-不复权 1-前复权 2-后复权
300
+ variety: props.code, // 品种
301
+ right: 1, // 0-不复权 1-前复权 2-后复权
267
302
  ...params,
303
+ }
304
+ const res = await getKlineDataApi(apiParams)
305
+ if (slideData.value.length === 0) {
306
+ slideData.value = res.map(i => [i[0], i[4]])
307
+ return res
308
+ }
309
+ drawData.value = formatData({
310
+ originDrawData: drawData.value,
311
+ addData: res,
312
+ params: apiParams,
313
+ mainIndicator: props.mainIndicator,
314
+ mainIndicatorList: props.mainIndicatorList,
315
+ subIndicator: subIndicator.value,
316
+ subIndicatorList: props.subIndicatorList,
317
+ config: config.value,
318
+ slideData: slideData.value,
268
319
  })
269
320
  return res
270
321
  }
271
322
 
323
+ // 绘制函数
272
324
  const draw = (type: string, params: any = {}) => {
273
325
  const { showStartTime, showEndTime } = params
274
- const { defaultShowDays } = config
326
+ const { maxShowDays } = config.value
327
+ const { subIndicatorList } = props
275
328
  const callBackMap = new Map([
276
329
  [ "main", () => {
277
- mainChart.setOption(getMainOption({ data: klineData, showStartTime, showEndTime }))
330
+ mainChart.setOption(getMainOption({ showStartTime, showEndTime, drawData: drawData.value, maxShowDays }), true)
278
331
  }],
279
332
  [ "sub", () => {
280
- subChart.setOption(getSubOption({ data: klineData, showStartTime, showEndTime }))
281
- }],
282
- [ "slide", () => {
283
- slideChart.setOption(getSlideOption({ data: slideData, defaultShowDays }))
333
+ klineSubRef.value.draw({ showStartTime, showEndTime, drawData: drawData.value , subIndicator: subIndicator.value, subIndicatorList, maxShowDays })
284
334
  }],
285
335
  ]);
286
336
  const callBack = callBackMap.get(type);
@@ -294,17 +344,20 @@ const draw = (type: string, params: any = {}) => {
294
344
  .kline {
295
345
  width: 100%;
296
346
  height: 100%;
347
+ background: #000;
348
+ &-mainTips {
349
+ height: 26px;
350
+ }
297
351
  &-mainChart {
298
352
  width: 100%;
299
- height: 60%;
353
+ height: calc(65% - 26px);
300
354
  }
301
- &-subChart {
302
- width: 100%;
303
- height: calc(40% - 50px);
355
+ &-subTips {
356
+ height: 16px;
304
357
  }
305
- &-slideChart {
358
+ &-sub {
306
359
  width: 100%;
307
- height: 50px;
360
+ height: calc(35% - 50px - 16px);
308
361
  }
309
362
  }
310
363
  </style>
@@ -0,0 +1,44 @@
1
+ export default {
2
+ freqConfig: {
3
+ '1m': {
4
+ defaultShowDays: 2, // 默认展示天数
5
+ preLoadDays: 4, // 预加载天数
6
+ maxShowDays: 20, // 最大展示天数
7
+ },
8
+ '5m': {
9
+ defaultShowDays: 4, // 默认展示天数
10
+ preLoadDays: 10, // 预加载天数
11
+ maxShowDays: 20, // 最大展示天数
12
+ },
13
+ '15m': {
14
+ defaultShowDays: 12, // 默认展示天数
15
+ preLoadDays: 24, // 预加载天数
16
+ maxShowDays: 50, // 最大展示天数
17
+ },
18
+ '30m': {
19
+ defaultShowDays: 25, // 默认展示天数
20
+ preLoadDays: 50, // 预加载天数
21
+ maxShowDays: 100, // 最大展示天数
22
+ },
23
+ '60m': {
24
+ defaultShowDays: 50, // 默认展示天数
25
+ preLoadDays: 100, // 预加载天数
26
+ maxShowDays: 200, // 最大展示天数
27
+ },
28
+ '1d': {
29
+ defaultShowDays: 200, // 默认展示天数
30
+ preLoadDays: 400, // 预加载天数
31
+ maxShowDays: 800, // 最大展示天数
32
+ },
33
+ '1w': {
34
+ defaultShowDays: 1000, // 默认展示天数
35
+ preLoadDays: 2000, // 预加载天数
36
+ maxShowDays: 4000, // 最大展示天数
37
+ },
38
+ '1mon': {
39
+ defaultShowDays: 4000, // 默认展示天数
40
+ preLoadDays: 8000, // 预加载天数
41
+ maxShowDays: 16000, // 最大展示天数
42
+ }
43
+ }
44
+ }