tgo-widget-miniprogram 1.0.0

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 (55) hide show
  1. package/README.md +81 -0
  2. package/miniprogram_dist/adapters/request.js +38 -0
  3. package/miniprogram_dist/adapters/storage.js +42 -0
  4. package/miniprogram_dist/adapters/systemInfo.js +22 -0
  5. package/miniprogram_dist/chat/index.js +164 -0
  6. package/miniprogram_dist/chat/index.json +7 -0
  7. package/miniprogram_dist/chat/index.wxml +48 -0
  8. package/miniprogram_dist/chat/index.wxss +92 -0
  9. package/miniprogram_dist/components/json-render-element/index.js +374 -0
  10. package/miniprogram_dist/components/json-render-element/index.json +6 -0
  11. package/miniprogram_dist/components/json-render-element/index.wxml +218 -0
  12. package/miniprogram_dist/components/json-render-element/index.wxss +450 -0
  13. package/miniprogram_dist/components/json-render-message/index.js +89 -0
  14. package/miniprogram_dist/components/json-render-message/index.json +7 -0
  15. package/miniprogram_dist/components/json-render-message/index.wxml +25 -0
  16. package/miniprogram_dist/components/json-render-message/index.wxss +26 -0
  17. package/miniprogram_dist/components/json-render-surface/index.js +116 -0
  18. package/miniprogram_dist/components/json-render-surface/index.json +6 -0
  19. package/miniprogram_dist/components/json-render-surface/index.wxml +10 -0
  20. package/miniprogram_dist/components/json-render-surface/index.wxss +6 -0
  21. package/miniprogram_dist/components/markdown-text/index.js +23 -0
  22. package/miniprogram_dist/components/markdown-text/index.json +3 -0
  23. package/miniprogram_dist/components/markdown-text/index.wxml +1 -0
  24. package/miniprogram_dist/components/markdown-text/index.wxss +6 -0
  25. package/miniprogram_dist/components/message-bubble/index.js +12 -0
  26. package/miniprogram_dist/components/message-bubble/index.json +3 -0
  27. package/miniprogram_dist/components/message-bubble/index.wxml +3 -0
  28. package/miniprogram_dist/components/message-bubble/index.wxss +17 -0
  29. package/miniprogram_dist/components/message-input/index.js +76 -0
  30. package/miniprogram_dist/components/message-input/index.json +3 -0
  31. package/miniprogram_dist/components/message-input/index.wxml +28 -0
  32. package/miniprogram_dist/components/message-input/index.wxss +56 -0
  33. package/miniprogram_dist/components/message-list/index.js +113 -0
  34. package/miniprogram_dist/components/message-list/index.json +9 -0
  35. package/miniprogram_dist/components/message-list/index.wxml +108 -0
  36. package/miniprogram_dist/components/message-list/index.wxss +113 -0
  37. package/miniprogram_dist/components/system-message/index.js +8 -0
  38. package/miniprogram_dist/components/system-message/index.json +3 -0
  39. package/miniprogram_dist/components/system-message/index.wxml +3 -0
  40. package/miniprogram_dist/components/system-message/index.wxss +15 -0
  41. package/miniprogram_dist/core/chatStore.js +758 -0
  42. package/miniprogram_dist/core/i18n.js +66 -0
  43. package/miniprogram_dist/core/platformStore.js +86 -0
  44. package/miniprogram_dist/core/types.js +192 -0
  45. package/miniprogram_dist/services/chat.js +67 -0
  46. package/miniprogram_dist/services/messageHistory.js +46 -0
  47. package/miniprogram_dist/services/platform.js +27 -0
  48. package/miniprogram_dist/services/upload.js +74 -0
  49. package/miniprogram_dist/services/visitor.js +67 -0
  50. package/miniprogram_dist/services/wukongim.js +183 -0
  51. package/miniprogram_dist/utils/jsonRender.js +158 -0
  52. package/miniprogram_dist/utils/markdown.js +31 -0
  53. package/miniprogram_dist/utils/time.js +85 -0
  54. package/miniprogram_dist/utils/uid.js +11 -0
  55. package/package.json +37 -0
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Self-recursive json-render element component.
3
+ * Renders a single element from the spec by type, recursing for children.
4
+ */
5
+ Component({
6
+ options: {
7
+ virtualHost: true
8
+ },
9
+
10
+ properties: {
11
+ elementKey: {
12
+ type: String,
13
+ value: ''
14
+ },
15
+ spec: {
16
+ type: Object,
17
+ value: null
18
+ },
19
+ stateSnapshot: {
20
+ type: Object,
21
+ value: null
22
+ },
23
+ depth: {
24
+ type: Number,
25
+ value: 0
26
+ }
27
+ },
28
+
29
+ data: {
30
+ el: null,
31
+ elType: '',
32
+ childKeys: [],
33
+ // Text
34
+ textContent: '',
35
+ textVariant: '',
36
+ kvLabel: '',
37
+ kvValue: '',
38
+ isKV: false,
39
+ isSectionTitle: false,
40
+ // Badge
41
+ badgeTone: 'info',
42
+ badgeLabel: '',
43
+ // KV element
44
+ kvElLabel: '',
45
+ kvElValue: '',
46
+ kvHighlight: false,
47
+ // PriceRow
48
+ priceLabel: '',
49
+ priceValue: '',
50
+ priceEmphasis: false,
51
+ priceDiscount: false,
52
+ // OrderItem
53
+ orderName: '',
54
+ orderSku: '',
55
+ orderQty: '1',
56
+ orderPrice: '',
57
+ // Image
58
+ imageSrc: '',
59
+ imageAlt: '',
60
+ // Button
61
+ btnLabel: 'Submit',
62
+ btnVariant: '',
63
+ btnDisabled: false,
64
+ // Card
65
+ cardVariant: '',
66
+ // Section
67
+ sectionTitle: '',
68
+ // Row
69
+ rowJustify: '',
70
+ rowAlign: '',
71
+ rowGap: '',
72
+ rowWrap: false,
73
+ // Column
74
+ colGap: '',
75
+ // Input
76
+ inputLabel: '',
77
+ inputPlaceholder: '',
78
+ inputValue: '',
79
+ inputBindingPath: '',
80
+ // Checkbox
81
+ cbLabel: '',
82
+ cbChecked: false,
83
+ cbBindingPath: '',
84
+ // MultipleChoice
85
+ mcLabel: '',
86
+ mcOptions: [],
87
+ mcOptionLabels: [],
88
+ mcValue: '',
89
+ mcIndex: 0,
90
+ mcBindingPath: '',
91
+ // DateTimeInput
92
+ dtLabel: '',
93
+ dtValue: '',
94
+ dtMode: 'date',
95
+ dtBindingPath: '',
96
+ // Max recursion
97
+ tooDeep: false
98
+ },
99
+
100
+ observers: {
101
+ 'elementKey, spec, stateSnapshot': function (key, spec) {
102
+ this._resolve(key, spec)
103
+ }
104
+ },
105
+
106
+ methods: {
107
+ _resolve: function (key, spec) {
108
+ if (!key || !spec || !spec.elements) {
109
+ this.setData({ el: null, elType: '' })
110
+ return
111
+ }
112
+ if (this.properties.depth > 10) {
113
+ this.setData({ tooDeep: true, elType: '' })
114
+ return
115
+ }
116
+
117
+ var el = spec.elements[key]
118
+ if (!el) {
119
+ this.setData({ el: null, elType: '' })
120
+ return
121
+ }
122
+
123
+ var t = (el.type || '').toLowerCase()
124
+ var props = el.props || {}
125
+ var children = el.children || []
126
+ var setObj = {
127
+ el: el,
128
+ elType: t,
129
+ childKeys: children,
130
+ tooDeep: false
131
+ }
132
+
133
+ // Type-specific data extraction
134
+ if (t === 'text') {
135
+ var text = this._pickStr(props, ['text', 'label', 'value', 'content'])
136
+ var variant = this._str(props.variant).toLowerCase()
137
+ var pair = this._splitKV(text)
138
+ var isST = variant === 'section-title' || this._isSectionTitle(text)
139
+ setObj.textContent = text
140
+ setObj.textVariant = variant
141
+ setObj.isKV = !isST && !!pair && variant !== 'plain'
142
+ setObj.isSectionTitle = isST
143
+ if (pair) {
144
+ setObj.kvLabel = pair.label
145
+ setObj.kvValue = pair.value
146
+ }
147
+ } else if (t === 'badge' || t === 'statusbadge') {
148
+ setObj.elType = 'badge'
149
+ setObj.badgeTone = this._str(props.tone).toLowerCase() || 'info'
150
+ setObj.badgeLabel = this._pickStr(props, ['label', 'text', 'value'])
151
+ } else if (t === 'kv' || t === 'keyvalue') {
152
+ setObj.elType = 'kv'
153
+ setObj.kvElLabel = this._pickStr(props, ['label', 'key', 'name'])
154
+ setObj.kvElValue = this._pickStr(props, ['value', 'text', 'amount'])
155
+ setObj.kvHighlight = this._toBool(props.highlight) || this._isHeadlineKey(setObj.kvElLabel)
156
+ } else if (t === 'pricerow' || t === 'amountrow') {
157
+ setObj.elType = 'pricerow'
158
+ var pLabel = this._pickStr(props, ['label', 'title', 'name'])
159
+ var rawVal = props.amount != null ? props.amount : (props.value != null ? props.value : props.text)
160
+ var valText = this._str(rawVal)
161
+ var numeric = this._toNum(rawVal)
162
+ var currency = this._pickStr(props, ['currency']) || '\u00a5'
163
+ var emphasis = this._toBool(props.emphasis) || this._isHeadlineKey(pLabel)
164
+ var discount = this._toBool(props.discount) || this._isDiscountKey(pLabel) || (numeric !== null && numeric < 0)
165
+ var shownVal = valText || (numeric !== null ? currency + Math.abs(numeric).toFixed(2) : '')
166
+ if (discount && numeric !== null && numeric > 0) {
167
+ shownVal = '-' + currency + numeric.toFixed(2)
168
+ }
169
+ setObj.priceLabel = pLabel
170
+ setObj.priceValue = shownVal
171
+ setObj.priceEmphasis = emphasis
172
+ setObj.priceDiscount = discount
173
+ } else if (t === 'orderitem' || t === 'lineitem') {
174
+ setObj.elType = 'orderitem'
175
+ setObj.orderName = this._pickStr(props, ['name', 'title', 'label'])
176
+ setObj.orderSku = this._pickStr(props, ['sku'])
177
+ setObj.orderQty = this._pickStr(props, ['quantity', 'qty']) || '1'
178
+ setObj.orderPrice = this._pickStr(props, ['subtotal', 'price', 'amount'])
179
+ } else if (t === 'image') {
180
+ setObj.imageSrc = this._pickStr(props, ['url', 'src'])
181
+ setObj.imageAlt = this._pickStr(props, ['alt']) || 'image'
182
+ } else if (t === 'button') {
183
+ setObj.btnLabel = this._pickStr(props, ['label', 'text']) || 'Submit'
184
+ var bv = this._str(props.variant).toLowerCase()
185
+ if (this._toBool(props.primary) || bv === 'primary') bv = 'primary'
186
+ setObj.btnVariant = bv
187
+ setObj.btnDisabled = this._toBool(props.disabled)
188
+ } else if (t === 'buttongroup' || t === 'actions') {
189
+ setObj.elType = 'buttongroup'
190
+ } else if (t === 'card') {
191
+ setObj.cardVariant = this._str(props.variant).toLowerCase()
192
+ } else if (t === 'section' || t === 'sectioncard') {
193
+ setObj.elType = 'section'
194
+ setObj.sectionTitle = this._pickStr(props, ['title', 'label', 'text'])
195
+ } else if (t === 'row') {
196
+ setObj.rowJustify = this._str(props.justify).toLowerCase()
197
+ setObj.rowAlign = this._str(props.align).toLowerCase()
198
+ setObj.rowGap = this._str(props.gap).toLowerCase()
199
+ setObj.rowWrap = this._toBool(props.wrap)
200
+ } else if (t === 'column' || t === 'list') {
201
+ setObj.elType = 'column'
202
+ setObj.colGap = this._str(props.gap).toLowerCase()
203
+ } else if (t === 'input' || t === 'textfield') {
204
+ setObj.elType = 'input'
205
+ setObj.inputLabel = this._str(props.label)
206
+ setObj.inputPlaceholder = this._str(props.placeholder)
207
+ var iBind = (el.bindings && el.bindings.value) || ''
208
+ setObj.inputBindingPath = iBind
209
+ setObj.inputValue = iBind ? this._getState(iBind) : this._str(props.value)
210
+ } else if (t === 'checkbox') {
211
+ var rawCb = props.value != null ? props.value : (props.checked != null ? props.checked : props.selected)
212
+ var cbBind = (el.bindings && (el.bindings.value || el.bindings.checked || el.bindings.selected)) || ''
213
+ setObj.cbLabel = this._str(props.label) || this._str(props.text)
214
+ setObj.cbBindingPath = cbBind
215
+ setObj.cbChecked = cbBind ? this._toBool(this._getState(cbBind)) : this._toBool(rawCb)
216
+ } else if (t === 'multiplechoice') {
217
+ var rawOpts = Array.isArray(props.options) ? props.options : []
218
+ var opts = []
219
+ var optLabels = []
220
+ for (var oi = 0; oi < rawOpts.length; oi++) {
221
+ var item = rawOpts[oi]
222
+ if (typeof item === 'string' && item) {
223
+ opts.push({ label: item, value: item })
224
+ optLabels.push(item)
225
+ } else if (item && typeof item === 'object') {
226
+ var ol = this._str(item.label)
227
+ var ov = this._str(item.value)
228
+ if (ov) {
229
+ opts.push({ label: ol || ov, value: ov })
230
+ optLabels.push(ol || ov)
231
+ }
232
+ }
233
+ }
234
+ var mcRaw = props.value != null ? props.value : (props.selectedValue != null ? props.selectedValue : props.selectedValues)
235
+ var mcInit = Array.isArray(mcRaw) ? this._str(mcRaw[0]) : this._str(mcRaw)
236
+ var mcBind = (el.bindings && (el.bindings.value || el.bindings.selectedValue || el.bindings.selectedValues)) || ''
237
+ var mcVal = mcBind ? this._str(this._getState(mcBind)) : mcInit
238
+ var mcIdx = 0
239
+ for (var mi = 0; mi < opts.length; mi++) {
240
+ if (opts[mi].value === mcVal) { mcIdx = mi; break }
241
+ }
242
+ setObj.mcLabel = this._str(props.label)
243
+ setObj.mcOptions = opts
244
+ setObj.mcOptionLabels = optLabels
245
+ setObj.mcValue = mcVal
246
+ setObj.mcIndex = mcIdx
247
+ setObj.mcBindingPath = mcBind
248
+ } else if (t === 'datetimeinput') {
249
+ var dtBind = (el.bindings && (el.bindings.value || el.bindings.date || el.bindings.selectedDate)) || ''
250
+ var dtRaw = props.value != null ? props.value : (props.date != null ? props.date : props.selectedDate)
251
+ var dtMode = this._str(props.mode).toLowerCase() || this._str(props.type).toLowerCase() || 'date'
252
+ setObj.dtLabel = this._str(props.label)
253
+ setObj.dtBindingPath = dtBind
254
+ setObj.dtValue = dtBind ? this._str(this._getState(dtBind)) : this._str(dtRaw)
255
+ setObj.dtMode = dtMode === 'time' ? 'time' : 'date'
256
+ } else if (t === 'divider') {
257
+ // no extra data needed
258
+ }
259
+
260
+ this.setData(setObj)
261
+ },
262
+
263
+ // -- Helpers --
264
+ _str: function (v) {
265
+ if (typeof v === 'string') return v
266
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v)
267
+ return ''
268
+ },
269
+ _pickStr: function (props, keys) {
270
+ for (var i = 0; i < keys.length; i++) {
271
+ var v = this._str(props[keys[i]])
272
+ if (v) return v
273
+ }
274
+ return ''
275
+ },
276
+ _toBool: function (v) {
277
+ if (typeof v === 'boolean') return v
278
+ if (typeof v === 'string') return v === 'true'
279
+ return false
280
+ },
281
+ _toNum: function (v) {
282
+ if (typeof v === 'number' && isFinite(v)) return v
283
+ if (typeof v === 'string') {
284
+ var n = Number(v.replace(/[^0-9.+-]/g, ''))
285
+ if (isFinite(n)) return n
286
+ }
287
+ return null
288
+ },
289
+ _splitKV: function (text) {
290
+ var t = (text || '').trim()
291
+ if (!t) return null
292
+ var m = t.match(/^([^:\uff1a]{1,24})[:\uff1a]\s*(.+)$/)
293
+ if (!m) return null
294
+ var label = m[1].trim()
295
+ var value = m[2].trim()
296
+ if (!label || !value) return null
297
+ return { label: label, value: value }
298
+ },
299
+ _isSectionTitle: function (text) {
300
+ return /^(商品详情|支付信息|收货信息|订单信息|物流信息|商品明细|支付详情|配送信息|订单详情|items|payment|shipping|order)$/i.test((text || '').trim())
301
+ },
302
+ _isHeadlineKey: function (label) {
303
+ return /(实付金额|应付金额|支付金额|合计|总额|payable|grand total|total)/i.test(label || '')
304
+ },
305
+ _isDiscountKey: function (label) {
306
+ return /(优惠|折扣|减免|discount|coupon)/i.test(label || '')
307
+ },
308
+ _getState: function (path) {
309
+ var ss = this.properties.stateSnapshot
310
+ if (!ss || !path) return ''
311
+ var parts = path.split('.')
312
+ var cur = ss
313
+ for (var i = 0; i < parts.length; i++) {
314
+ if (cur == null || typeof cur !== 'object') return ''
315
+ cur = cur[parts[i]]
316
+ }
317
+ return cur != null ? cur : ''
318
+ },
319
+
320
+ // -- Events --
321
+ onButtonTap: function () {
322
+ if (this.data.btnDisabled) return
323
+ var el = this.data.el
324
+ if (!el || !el.on || !el.on.press) return
325
+ var binding = el.on.press
326
+ var actions = Array.isArray(binding) ? binding : [binding]
327
+ for (var i = 0; i < actions.length; i++) {
328
+ var act = actions[i]
329
+ if (act && act.action) {
330
+ this.triggerEvent('action', {
331
+ actionName: act.action,
332
+ params: act.params || {}
333
+ }, { bubbles: true, composed: true })
334
+ }
335
+ }
336
+ },
337
+ onInputChange: function (e) {
338
+ var val = e.detail.value
339
+ var path = this.data.inputBindingPath
340
+ if (path) {
341
+ this.triggerEvent('statechange', { path: path, value: val }, { bubbles: true, composed: true })
342
+ }
343
+ this.setData({ inputValue: val })
344
+ },
345
+ onCheckboxTap: function () {
346
+ var newVal = !this.data.cbChecked
347
+ this.setData({ cbChecked: newVal })
348
+ var path = this.data.cbBindingPath
349
+ if (path) {
350
+ this.triggerEvent('statechange', { path: path, value: newVal }, { bubbles: true, composed: true })
351
+ }
352
+ },
353
+ onPickerChange: function (e) {
354
+ var idx = parseInt(e.detail.value, 10)
355
+ var opts = this.data.mcOptions
356
+ if (idx >= 0 && idx < opts.length) {
357
+ var val = opts[idx].value
358
+ this.setData({ mcValue: val, mcIndex: idx })
359
+ var path = this.data.mcBindingPath
360
+ if (path) {
361
+ this.triggerEvent('statechange', { path: path, value: val }, { bubbles: true, composed: true })
362
+ }
363
+ }
364
+ },
365
+ onDateChange: function (e) {
366
+ var val = e.detail.value
367
+ this.setData({ dtValue: val })
368
+ var path = this.data.dtBindingPath
369
+ if (path) {
370
+ this.triggerEvent('statechange', { path: path, value: val }, { bubbles: true, composed: true })
371
+ }
372
+ }
373
+ }
374
+ })
@@ -0,0 +1,6 @@
1
+ {
2
+ "component": true,
3
+ "usingComponents": {
4
+ "json-render-element": "./index"
5
+ }
6
+ }
@@ -0,0 +1,218 @@
1
+ <block wx:if="{{tooDeep}}">
2
+ <view class="jr-too-deep"><text>...</text></view>
3
+ </block>
4
+
5
+ <!-- Text -->
6
+ <block wx:elif="{{elType === 'text'}}">
7
+ <block wx:if="{{!textContent}}"></block>
8
+ <view wx:elif="{{isSectionTitle}}" class="jr-section-title">
9
+ <text>{{textContent}}</text>
10
+ </view>
11
+ <view wx:elif="{{isKV}}" class="jr-text-kv">
12
+ <text class="jr-text-kv-label">{{kvLabel}}</text>
13
+ <text class="jr-text-kv-value">{{kvValue}}</text>
14
+ </view>
15
+ <view wx:elif="{{textVariant === 'caption' || textVariant === 'muted'}}" class="jr-text-caption">
16
+ <text>{{textContent}}</text>
17
+ </view>
18
+ <view wx:elif="{{textVariant === 'title'}}" class="jr-text-title">
19
+ <text>{{textContent}}</text>
20
+ </view>
21
+ <block wx:else>
22
+ <view class="jr-text"><text>{{textContent}}</text></view>
23
+ </block>
24
+ </block>
25
+
26
+ <!-- Divider -->
27
+ <view wx:elif="{{elType === 'divider'}}" class="jr-divider"></view>
28
+
29
+ <!-- Badge -->
30
+ <block wx:elif="{{elType === 'badge'}}">
31
+ <view class="jr-badge jr-badge-{{badgeTone}}">
32
+ <text>{{badgeLabel}}</text>
33
+ </view>
34
+ </block>
35
+
36
+ <!-- KV -->
37
+ <block wx:elif="{{elType === 'kv'}}">
38
+ <view class="jr-kv {{kvHighlight ? 'jr-kv-highlight' : ''}}">
39
+ <text class="jr-kv-label">{{kvElLabel}}</text>
40
+ <text class="jr-kv-value {{kvHighlight ? 'jr-kv-value-highlight' : ''}}">{{kvElValue}}</text>
41
+ </view>
42
+ </block>
43
+
44
+ <!-- PriceRow -->
45
+ <block wx:elif="{{elType === 'pricerow'}}">
46
+ <view class="jr-price-row {{priceEmphasis ? 'jr-price-emphasis' : ''}}">
47
+ <text class="jr-price-label {{priceEmphasis ? 'jr-price-label-emphasis' : ''}}">{{priceLabel}}</text>
48
+ <text class="jr-price-value {{priceEmphasis ? 'jr-price-value-emphasis' : ''}} {{priceDiscount ? 'jr-price-discount' : ''}}">{{priceValue}}</text>
49
+ </view>
50
+ </block>
51
+
52
+ <!-- OrderItem -->
53
+ <block wx:elif="{{elType === 'orderitem'}}">
54
+ <view class="jr-order-item">
55
+ <view class="jr-order-info">
56
+ <text class="jr-order-name">{{orderName}}</text>
57
+ <text wx:if="{{orderSku}}" class="jr-order-sku">SKU: {{orderSku}}</text>
58
+ </view>
59
+ <view class="jr-order-right">
60
+ <text class="jr-order-qty">x{{orderQty}}</text>
61
+ <text class="jr-order-price">{{orderPrice}}</text>
62
+ </view>
63
+ </view>
64
+ </block>
65
+
66
+ <!-- Image -->
67
+ <block wx:elif="{{elType === 'image'}}">
68
+ <image wx:if="{{imageSrc}}" src="{{imageSrc}}" mode="widthFix" class="jr-image" />
69
+ </block>
70
+
71
+ <!-- Button -->
72
+ <block wx:elif="{{elType === 'button'}}">
73
+ <view
74
+ class="jr-button jr-button-{{btnVariant || 'default'}} {{btnDisabled ? 'jr-button-disabled' : ''}}"
75
+ bindtap="onButtonTap"
76
+ >
77
+ <text>{{btnLabel}}</text>
78
+ </view>
79
+ </block>
80
+
81
+ <!-- ButtonGroup / Actions -->
82
+ <block wx:elif="{{elType === 'buttongroup'}}">
83
+ <view class="jr-button-group">
84
+ <json-render-element
85
+ wx:for="{{childKeys}}"
86
+ wx:key="*this"
87
+ elementKey="{{item}}"
88
+ spec="{{spec}}"
89
+ stateSnapshot="{{stateSnapshot}}"
90
+ depth="{{depth + 1}}"
91
+ bind:action="onButtonTap"
92
+ bind:statechange="onInputChange"
93
+ />
94
+ </view>
95
+ </block>
96
+
97
+ <!-- Card -->
98
+ <block wx:elif="{{elType === 'card'}}">
99
+ <view class="jr-card {{cardVariant === 'order' || cardVariant === 'invoice' ? 'jr-card-order' : ''}}">
100
+ <json-render-element
101
+ wx:for="{{childKeys}}"
102
+ wx:key="*this"
103
+ elementKey="{{item}}"
104
+ spec="{{spec}}"
105
+ stateSnapshot="{{stateSnapshot}}"
106
+ depth="{{depth + 1}}"
107
+ />
108
+ </view>
109
+ </block>
110
+
111
+ <!-- Section -->
112
+ <block wx:elif="{{elType === 'section'}}">
113
+ <view class="jr-section">
114
+ <view wx:if="{{sectionTitle}}" class="jr-section-header">
115
+ <text class="jr-section-title-text">{{sectionTitle}}</text>
116
+ </view>
117
+ <view class="jr-section-body {{sectionTitle ? 'jr-section-body-with-title' : ''}}">
118
+ <json-render-element
119
+ wx:for="{{childKeys}}"
120
+ wx:key="*this"
121
+ elementKey="{{item}}"
122
+ spec="{{spec}}"
123
+ stateSnapshot="{{stateSnapshot}}"
124
+ depth="{{depth + 1}}"
125
+ />
126
+ </view>
127
+ </view>
128
+ </block>
129
+
130
+ <!-- Row -->
131
+ <block wx:elif="{{elType === 'row'}}">
132
+ <view class="jr-row jr-row-justify-{{rowJustify || 'start'}} jr-row-align-{{rowAlign || 'start'}} jr-row-gap-{{rowGap || 'md'}} {{rowWrap ? 'jr-row-wrap' : ''}}">
133
+ <json-render-element
134
+ wx:for="{{childKeys}}"
135
+ wx:key="*this"
136
+ elementKey="{{item}}"
137
+ spec="{{spec}}"
138
+ stateSnapshot="{{stateSnapshot}}"
139
+ depth="{{depth + 1}}"
140
+ />
141
+ </view>
142
+ </block>
143
+
144
+ <!-- Column / List -->
145
+ <block wx:elif="{{elType === 'column'}}">
146
+ <view class="jr-column jr-col-gap-{{colGap || 'md'}}">
147
+ <json-render-element
148
+ wx:for="{{childKeys}}"
149
+ wx:key="*this"
150
+ elementKey="{{item}}"
151
+ spec="{{spec}}"
152
+ stateSnapshot="{{stateSnapshot}}"
153
+ depth="{{depth + 1}}"
154
+ />
155
+ </view>
156
+ </block>
157
+
158
+ <!-- Input -->
159
+ <block wx:elif="{{elType === 'input'}}">
160
+ <view class="jr-input-wrap">
161
+ <text wx:if="{{inputLabel}}" class="jr-input-label">{{inputLabel}}</text>
162
+ <input
163
+ class="jr-input"
164
+ value="{{inputValue}}"
165
+ placeholder="{{inputPlaceholder}}"
166
+ bindinput="onInputChange"
167
+ />
168
+ </view>
169
+ </block>
170
+
171
+ <!-- Checkbox -->
172
+ <block wx:elif="{{elType === 'checkbox'}}">
173
+ <view class="jr-checkbox" bindtap="onCheckboxTap">
174
+ <view class="jr-checkbox-box {{cbChecked ? 'jr-checkbox-checked' : ''}}">
175
+ <text wx:if="{{cbChecked}}" class="jr-checkbox-mark">\u2713</text>
176
+ </view>
177
+ <text class="jr-checkbox-label">{{cbLabel}}</text>
178
+ </view>
179
+ </block>
180
+
181
+ <!-- MultipleChoice -->
182
+ <block wx:elif="{{elType === 'multiplechoice'}}">
183
+ <view class="jr-mc-wrap">
184
+ <text wx:if="{{mcLabel}}" class="jr-mc-label">{{mcLabel}}</text>
185
+ <picker mode="selector" range="{{mcOptionLabels}}" value="{{mcIndex}}" bindchange="onPickerChange">
186
+ <view class="jr-mc-picker">
187
+ <text>{{mcValue || 'Select'}}</text>
188
+ <text class="jr-mc-arrow">\u25BC</text>
189
+ </view>
190
+ </picker>
191
+ </view>
192
+ </block>
193
+
194
+ <!-- DateTimeInput -->
195
+ <block wx:elif="{{elType === 'datetimeinput'}}">
196
+ <view class="jr-dt-wrap">
197
+ <text wx:if="{{dtLabel}}" class="jr-dt-label">{{dtLabel}}</text>
198
+ <picker mode="{{dtMode}}" value="{{dtValue}}" bindchange="onDateChange">
199
+ <view class="jr-dt-picker">
200
+ <text>{{dtValue || 'Select'}}</text>
201
+ </view>
202
+ </picker>
203
+ </view>
204
+ </block>
205
+
206
+ <!-- Unknown / fallback with children -->
207
+ <block wx:elif="{{elType && childKeys.length > 0}}">
208
+ <view class="jr-unknown">
209
+ <json-render-element
210
+ wx:for="{{childKeys}}"
211
+ wx:key="*this"
212
+ elementKey="{{item}}"
213
+ spec="{{spec}}"
214
+ stateSnapshot="{{stateSnapshot}}"
215
+ depth="{{depth + 1}}"
216
+ />
217
+ </view>
218
+ </block>