km-card-layout-component-miniprogram 0.1.5 → 0.1.6

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.
@@ -1,96 +1,371 @@
1
- import type { CardLayoutInput, RenderNode } from '../../vendor/km-card-layout-core/index'
2
- import { buildRenderResult } from '../../vendor/km-card-layout-core/index'
3
- import { ICON_CODE_MAP } from './icon-map'
1
+ import type {
2
+ CardElement,
3
+ CardLayoutInput,
4
+ CardLayoutSchema,
5
+ IconElement,
6
+ ImageElement,
7
+ LayoutPanelElement,
8
+ TextElement,
9
+ } from '../../vendor/km-card-layout-core/index';
10
+ import {
11
+ addUnit,
12
+ normalizeLayout,
13
+ resolveBindingValue,
14
+ styleObjectToString,
15
+ } from '../../vendor/km-card-layout-core/index';
16
+ import { ICON_CODE_MAP } from './icon-map';
4
17
 
5
18
  type LayoutData = {
6
- user?: Record<string, any>
7
- [key: string]: any
19
+ [key: string]: any;
20
+ };
21
+
22
+ interface RenderIcon {
23
+ name?: string;
24
+ text?: string;
25
+ size?: string;
26
+ color?: string;
27
+ gap?: string;
28
+ align?: 'left' | 'right';
29
+ }
30
+
31
+ interface RenderElement {
32
+ id: string;
33
+ type: CardElement['type'];
34
+ wrapperStyle: string;
35
+ contentStyle: string;
36
+ name?: string;
37
+ text?: string;
38
+ src?: string;
39
+ mode?: string;
40
+ icon?: RenderIcon;
41
+ children?: RenderElement[];
8
42
  }
9
43
 
10
44
  interface RenderCard {
11
- id: string
12
- cardStyle: string
13
- backgroundImage: string
14
- backgroundStyle: string
15
- nodes: RenderNode[]
45
+ id: string;
46
+ cardStyle: string;
47
+ backgroundImage?: string;
48
+ backgroundStyle: string;
49
+ nodes: RenderElement[];
16
50
  }
17
51
 
18
52
  const ensureArray = (input: CardLayoutInput | any): CardLayoutInput => {
19
- if (!input) return []
20
- return Array.isArray(input) ? input : [input]
21
- }
53
+ if (!input) return [];
54
+ return Array.isArray(input) ? input : [input];
55
+ };
22
56
 
23
57
  const pickCardId = (layout: any, idx: number) => {
24
- if (layout && (layout.name || layout.id)) return layout.name || layout.id
25
- return `card-${idx}`
26
- }
58
+ if (layout && (layout.name || layout.id)) return layout.name || layout.id;
59
+ return `card-${idx}`;
60
+ };
61
+
62
+ const withUnit = (value: any, unit: 'px' | 'rpx') => addUnit(value, unit);
63
+
64
+ const buildCardStyle = (layout: CardLayoutSchema, unit: 'px' | 'rpx') =>
65
+ styleObjectToString(
66
+ {
67
+ width: withUnit(layout.width, unit),
68
+ height: withUnit(layout.height, unit),
69
+ color: layout.fontColor,
70
+ borderRadius:
71
+ layout.borderRadius !== undefined
72
+ ? withUnit(layout.borderRadius, unit)
73
+ : undefined,
74
+ padding:
75
+ layout.padding !== undefined ? withUnit(layout.padding, unit) : undefined,
76
+ position: 'relative',
77
+ overflow: 'hidden',
78
+ boxSizing: 'border-box',
79
+ backgroundColor: 'transparent',
80
+ },
81
+ unit,
82
+ );
83
+
84
+ const buildBackgroundStyle = (layout: CardLayoutSchema, unit: 'px' | 'rpx') =>
85
+ styleObjectToString(
86
+ {
87
+ zIndex: layout.backgroundZIndex,
88
+ borderRadius:
89
+ layout.borderRadius !== undefined
90
+ ? withUnit(layout.borderRadius, unit)
91
+ : undefined,
92
+ width: '100%',
93
+ height: '100%',
94
+ position: 'absolute',
95
+ left: 0,
96
+ top: 0,
97
+ },
98
+ unit,
99
+ );
100
+
101
+ const buildWrapperStyle = (el: CardElement, unit: 'px' | 'rpx') => {
102
+ if (!el.layout || el.layout.mode !== 'absolute') return '';
103
+ return styleObjectToString(
104
+ {
105
+ position: 'absolute',
106
+ left: withUnit(el.layout.x, unit),
107
+ top: withUnit(el.layout.y, unit),
108
+ width: withUnit(el.layout.width, unit),
109
+ height: withUnit(el.layout.height, unit),
110
+ zIndex: el.layout.zIndex,
111
+ boxSizing: 'border-box',
112
+ display: 'flex',
113
+ alignItems: 'center',
114
+ },
115
+ unit,
116
+ );
117
+ };
118
+
119
+ const buildPanelContentStyle = (
120
+ el: LayoutPanelElement,
121
+ unit: 'px' | 'rpx',
122
+ ) =>
123
+ styleObjectToString(
124
+ {
125
+ position: 'relative',
126
+ width: '100%',
127
+ height: '100%',
128
+ display: 'block',
129
+ boxSizing: 'border-box',
130
+ ...(el.style || {}),
131
+ },
132
+ unit,
133
+ );
134
+
135
+ const buildTextContentStyle = (el: TextElement, unit: 'px' | 'rpx') => {
136
+ const textAlign =
137
+ (el.style?.textAlign as string | undefined) || el.align || undefined;
138
+ const style: Record<string, any> = {
139
+ ...el.style,
140
+ whiteSpace: 'pre-wrap',
141
+ wordBreak: 'break-word',
142
+ lineHeight:
143
+ el.style?.lineHeight !== undefined && el.style?.lineHeight !== null
144
+ ? el.style.lineHeight
145
+ : '1.2',
146
+ display: 'inline-flex',
147
+ alignItems: 'center',
148
+ };
149
+ if (textAlign) style.textAlign = textAlign;
150
+ return styleObjectToString(style, unit);
151
+ };
152
+
153
+ const buildBaseContentStyle = (el: CardElement, unit: 'px' | 'rpx') =>
154
+ styleObjectToString(
155
+ {
156
+ ...(el.style || {}),
157
+ boxSizing: 'border-box',
158
+ },
159
+ unit,
160
+ );
161
+
162
+ const mapIconGlyph = (name?: string, fallback?: string) => {
163
+ if (!name) return fallback;
164
+ const glyph = ICON_CODE_MAP[name];
165
+ if (glyph) return String.fromCharCode(parseInt(glyph, 16));
166
+ return fallback || name;
167
+ };
168
+
169
+ const buildTextIcon = (
170
+ el: TextElement,
171
+ unit: 'px' | 'rpx',
172
+ ): RenderIcon | undefined => {
173
+ const icon = el.icon;
174
+ if (!icon || icon.enable === false) return undefined;
175
+
176
+ const style = icon.style || 'fill';
177
+ const baseName = el.key || el.binding || el.id;
178
+ let name: string | undefined;
179
+ if (style === 'dot') name = 'round';
180
+ else if (style === 'line') name = baseName ? `${baseName}-line` : undefined;
181
+ else name = baseName || undefined;
182
+ if (!name) return undefined;
183
+
184
+ const size =
185
+ icon.size !== undefined && icon.size !== null
186
+ ? icon.size
187
+ : (el.style?.fontSize as number | string | undefined);
188
+ const gap = icon.gap !== undefined && icon.gap !== null ? icon.gap : 4;
189
+ const color =
190
+ icon.color ?? (el.style?.color as string | undefined) ?? undefined;
191
+
192
+ const text = mapIconGlyph(name, name);
193
+ return {
194
+ name,
195
+ text,
196
+ size: size !== undefined ? withUnit(size, unit) : undefined,
197
+ gap: gap !== undefined ? withUnit(gap, unit) : undefined,
198
+ color,
199
+ align: icon.align || 'left',
200
+ };
201
+ };
202
+
203
+ const buildRenderNode = (
204
+ el: CardElement,
205
+ data: LayoutData,
206
+ unit: 'px' | 'rpx',
207
+ ): RenderElement | null => {
208
+ if (!el || el.visible === false) return null;
209
+ const wrapperStyle = buildWrapperStyle(el, unit);
210
+
211
+ if (el.type === 'layout-panel') {
212
+ const panel = el as LayoutPanelElement;
213
+ return {
214
+ id: el.id,
215
+ type: el.type,
216
+ wrapperStyle,
217
+ contentStyle: buildPanelContentStyle(panel, unit),
218
+ children: buildRenderNodes(panel.children || [], data, unit),
219
+ };
220
+ }
221
+
222
+ if (el.type === 'text') {
223
+ const textValue =
224
+ resolveBindingValue(el.binding, data) ?? el.defaultValue ?? '';
225
+ return {
226
+ id: el.id,
227
+ type: el.type,
228
+ wrapperStyle,
229
+ contentStyle: buildTextContentStyle(el as TextElement, unit),
230
+ text: `${textValue}`,
231
+ icon: buildTextIcon(el as TextElement, unit),
232
+ };
233
+ }
234
+
235
+ if (el.type === 'image') {
236
+ const style: Record<string, any> = { ...(el.style || {}) };
237
+ const borderWidth = Number(style.borderWidth);
238
+ if (Number.isFinite(borderWidth) && borderWidth > 0) {
239
+ if (!style.borderStyle) style.borderStyle = 'solid';
240
+ if (!style.borderColor) style.borderColor = '#000000';
241
+ }
242
+
243
+ const src =
244
+ resolveBindingValue(el.binding, data) ??
245
+ (el as ImageElement).defaultUrl ??
246
+ el.defaultValue ??
247
+ '';
248
+ const mode =
249
+ (el as ImageElement).fit === 'contain' ? 'aspectFit' : 'aspectFill';
250
+ return {
251
+ id: el.id,
252
+ type: el.type,
253
+ wrapperStyle,
254
+ contentStyle: styleObjectToString(style, unit),
255
+ src,
256
+ mode,
257
+ };
258
+ }
259
+
260
+ if (el.type === 'icon') {
261
+ const resolved =
262
+ resolveBindingValue(el.binding, data) ??
263
+ (el as IconElement).name ??
264
+ el.defaultValue ??
265
+ '';
266
+ const text = mapIconGlyph(`${resolved}`, `${resolved}`);
267
+ return {
268
+ id: el.id,
269
+ type: el.type,
270
+ wrapperStyle,
271
+ contentStyle: buildBaseContentStyle(el, unit),
272
+ name: `${resolved}`,
273
+ text,
274
+ };
275
+ }
276
+
277
+ if (el.type === 'custom') {
278
+ return {
279
+ id: el.id,
280
+ type: el.type,
281
+ wrapperStyle,
282
+ contentStyle: buildBaseContentStyle(el, unit),
283
+ };
284
+ }
285
+
286
+ return null;
287
+ };
288
+
289
+ const buildRenderNodes = (
290
+ children: CardElement[],
291
+ data: LayoutData,
292
+ unit: 'px' | 'rpx',
293
+ ) => {
294
+ if (!Array.isArray(children)) return [];
295
+ const nodes: RenderElement[] = [];
296
+ children.forEach(el => {
297
+ if (!el || el.visible === false) return;
298
+ const node = buildRenderNode(el, data, unit);
299
+ if (node) nodes.push(node);
300
+ });
301
+ return nodes;
302
+ };
303
+
304
+ const buildCards = (
305
+ layouts: CardLayoutInput,
306
+ data: LayoutData,
307
+ unit: 'px' | 'rpx',
308
+ ) =>
309
+ layouts.map(layout => ({
310
+ id: layout.name || (layout as any).id || '',
311
+ cardStyle: buildCardStyle(layout, unit),
312
+ backgroundImage: layout.backgroundImage || '',
313
+ backgroundStyle: buildBackgroundStyle(layout, unit),
314
+ nodes: buildRenderNodes(layout.children || [], data, unit),
315
+ }));
27
316
 
28
317
  Component({
29
318
  options: {
30
- styleIsolation: 'apply-shared'
319
+ styleIsolation: 'apply-shared',
31
320
  },
32
321
  properties: {
33
322
  layout: {
34
323
  type: Array,
35
324
  optionalTypes: [Object],
36
- value: []
325
+ value: [],
37
326
  },
38
327
  data: {
39
328
  type: Object,
40
329
  value: {
41
- user: {}
42
- }
43
- }
330
+ user: {},
331
+ },
332
+ },
44
333
  },
45
334
  data: {
46
- cards: [] as RenderCard[]
335
+ cards: [] as RenderCard[],
47
336
  },
48
337
  observers: {
49
338
  layout() {
50
- this.rebuild()
339
+ this.rebuild();
51
340
  },
52
341
  data() {
53
- this.rebuild()
54
- }
342
+ this.rebuild();
343
+ },
55
344
  },
56
345
  lifetimes: {
57
346
  attached() {
58
- this.rebuild()
59
- }
347
+ this.rebuild();
348
+ },
60
349
  },
61
350
  methods: {
62
- normalizeIconGlyph(nodes: RenderNode[]): RenderNode[] {
63
- return nodes.map(node => {
64
- if (node.type === 'icon') {
65
- const glyph = ICON_CODE_MAP[node.name || node.text || '']
66
- const text = glyph ? String.fromCharCode(parseInt(glyph, 16)) : node.text
67
- return { ...node, text }
68
- }
69
- if (node.children && node.children.length) {
70
- return { ...node, children: this.normalizeIconGlyph(node.children) }
71
- }
72
- return node
73
- })
74
- },
75
351
  rebuild() {
76
- const layoutInput = ensureArray(this.data.layout as CardLayoutInput)
77
- const dataInput = (this.data.data || {}) as LayoutData
352
+ const layoutInput = ensureArray(this.data.layout as CardLayoutInput);
353
+ const dataInput = (this.data.data || {}) as LayoutData;
78
354
 
79
355
  if (!layoutInput.length) {
80
- this.setData({ cards: [] })
81
- return
356
+ this.setData({ cards: [] });
357
+ return;
82
358
  }
83
359
 
84
- const rendered = buildRenderResult(layoutInput, dataInput, 'rpx')
85
- const cards = rendered.map((card, idx) => ({
86
- id: pickCardId(layoutInput[idx], idx),
87
- cardStyle: card.cardStyle,
88
- backgroundImage: card.backgroundImage,
89
- backgroundStyle: card.backgroundStyle,
90
- nodes: this.normalizeIconGlyph(card.renderTree)
91
- }))
360
+ const normalizedLayouts = normalizeLayout(layoutInput);
361
+ const cards = buildCards(normalizedLayouts, dataInput, 'rpx').map(
362
+ (card, idx) => ({
363
+ ...card,
364
+ id: pickCardId(layoutInput[idx], idx),
365
+ }),
366
+ );
92
367
 
93
- this.setData({ cards })
94
- }
95
- }
96
- })
368
+ this.setData({ cards });
369
+ },
370
+ },
371
+ });
@@ -1,6 +1,6 @@
1
- <view class="km-card-layout-list">
1
+ <view class="km-card-layout">
2
2
  <block wx:for="{{cards}}" wx:key="id">
3
- <view class="km-card-layout-item">
3
+ <view class="km-card-layout__item">
4
4
  <view class="km-card" style="{{item.cardStyle}}">
5
5
  <image
6
6
  wx:if="{{item.backgroundImage}}"
@@ -31,7 +31,7 @@
31
31
  <block wx:elif="{{node.type === 'icon'}}">
32
32
  <view class="km-node km-node--icon" style="{{node.wrapperStyle}}">
33
33
  <view
34
- wx:if="{{node.name === 'dot'}}"
34
+ wx:if="{{node.name === 'dot' || node.name === 'round'}}"
35
35
  class="km-node__icon-dot"
36
36
  style="{{node.contentStyle}}"
37
37
  />
@@ -39,7 +39,7 @@
39
39
  wx:else
40
40
  class="km-node__icon icon"
41
41
  style="{{node.contentStyle}}"
42
- >{{node.text}}</view>
42
+ >{{node.text || ''}}</view>
43
43
  </view>
44
44
  </block>
45
45
  <block wx:elif="{{node.type === 'layout-panel'}}">
@@ -60,7 +60,23 @@
60
60
  </block>
61
61
  <block wx:else>
62
62
  <view class="km-node km-node--text" style="{{node.wrapperStyle}}">
63
- <view style="{{node.contentStyle}}">{{node.text}}</view>
63
+ <view class="km-node__text" style="{{node.contentStyle}}">
64
+ <block wx:if="{{node.icon && node.icon.name}}">
65
+ <view
66
+ class="km-node__text-content"
67
+ style="flex-direction: {{node.icon.align === 'right' ? 'row-reverse' : 'row'}};">
68
+ <text
69
+ class="km-node__text-icon icon"
70
+ style="font-size: {{node.icon.size || '16px'}}; color: {{node.icon.color || ''}}; margin-right: {{node.icon.align !== 'right' ? (node.icon.gap || '') : ''}}; margin-left: {{node.icon.align === 'right' ? (node.icon.gap || '') : ''}};">
71
+ {{node.icon.text || node.icon.name || ''}}
72
+ </text>
73
+ <text class="km-node__text-value">{{node.text || ''}}</text>
74
+ </view>
75
+ </block>
76
+ <block wx:else>
77
+ <text class="km-node__text-value">{{node.text || ''}}</text>
78
+ </block>
79
+ </view>
64
80
  </view>
65
81
  </block>
66
82
  </template>
@@ -1,11 +1,12 @@
1
1
 
2
2
 
3
- .km-card-layout-list {
3
+ .km-card-layout {
4
4
  display: flex;
5
5
  flex-direction: column;
6
+ gap: 16rpx;
6
7
  }
7
8
 
8
- .km-card-layout-item {
9
+ .km-card-layout__item {
9
10
  width: 100%;
10
11
  }
11
12
 
@@ -15,7 +16,7 @@
15
16
  box-sizing: border-box;
16
17
  color: inherit;
17
18
  font-family: 'PingFang SC', 'Microsoft Yahei', sans-serif;
18
- background: #0f1115;
19
+ background: transparent;
19
20
  }
20
21
 
21
22
  .km-card__bg {
@@ -32,11 +33,12 @@
32
33
  color: inherit;
33
34
  }
34
35
 
35
- .km-node--icon{
36
+ .km-node--icon {
36
37
  display: flex;
37
38
  align-items: center;
38
- justify-items: center;
39
+ justify-content: center;
39
40
  }
41
+
40
42
  .km-node__image {
41
43
  width: 100%;
42
44
  height: 100%;
@@ -73,8 +75,27 @@
73
75
  box-sizing: border-box;
74
76
  }
75
77
 
78
+ .km-node__text {
79
+ width: 100%;
80
+ height: 100%;
81
+ display: flex;
82
+ align-items: center;
83
+ }
84
+
85
+ .km-node__text-content {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ }
89
+
90
+ .km-node__text-value {
91
+ display: inline-block;
92
+ white-space: pre-wrap;
93
+ word-break: break-word;
94
+ }
95
+
76
96
  .km-node--text text {
77
97
  display: block;
98
+ white-space: pre-wrap;
78
99
  word-break: break-word;
79
100
  }
80
101