cardbuddy 0.23.0 → 0.25.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.
- package/README.md +2 -2
- package/dist/init.js +9 -2
- package/dist/preview-editor/assets/{index-C7N5n_qW.js → index-DL4ZCJCA.js} +77 -76
- package/dist/preview-editor/assets/{index-7RzNd7tf.css → index-MWKPP9nd.css} +1 -1
- package/dist/preview-editor/index.html +2 -2
- package/dist/skills-upgrade.js +59 -0
- package/dist/vendored/server/core/browser.js +7 -4
- package/dist/vendored/server/core/cardCompile.js +202 -0
- package/dist/vendored/server/core/flameCompile/client.js +466 -0
- package/dist/vendored/server/core/flameCompile/clientElements.js +372 -0
- package/dist/vendored/server/core/flameCompile/common.js +253 -0
- package/dist/vendored/server/core/flameCompile/h5.js +326 -0
- package/dist/vendored/server/core/flameCompile/index.js +126 -0
- package/dist/vendored/server/core/flameCompile/model.js +162 -0
- package/dist/vendored/server/core/index.js +6 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
- package/dist/vendored/skills/mbank-design-gen/tools/screenshot.mjs +15 -7
- package/dist/vendored/skills/platform-card-sync/scripts/api_batch_sync.mjs +12 -4
- package/dist/vendored/skills/platform-session-navigator/scripts/package-lock.json +1 -16
- package/dist/vendored/skills/platform-session-navigator/scripts/package.json +2 -2
- package/dist/vendored/skills/platform-session-navigator/scripts/platform_login.mjs +12 -1
- package/dist/vendored/skills/platform-session-navigator/scripts/session-manager.mjs +1 -1
- package/package.json +2 -4
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H5 产物编译(ai-assistant-h5 的卡片 js)
|
|
3
|
+
*
|
|
4
|
+
* 1:1 迁移自 flame-release-mange(Java)的
|
|
5
|
+
* h5/H5Engine.java
|
|
6
|
+
* h5/convert/{CardConvert,ComponentConvert,StyleConvert,PropertyConvert,
|
|
7
|
+
* ExpressionConvert,EventActionConvert,DirectiveConvert,DataConvert}
|
|
8
|
+
* h5/entity/*(JsFileEntify / JsFileComponentEntify / ...)
|
|
9
|
+
*
|
|
10
|
+
* 产物:单文件 AMD 模块
|
|
11
|
+
* define(function(){ return {...} });
|
|
12
|
+
* 返回一个 Vue 组件选项对象,由 @ynet/card 的 m-card 通过 require 装载后 `h(content, {props:{data}})`
|
|
13
|
+
* 渲染。组件不自带 h:一律 `this.getCreateElement(this, _h)` 取宿主注入的创建器(Vue2/Vue3 双兼容),
|
|
14
|
+
* 元素组件通过 `this.getElement('Text')` 取宿主 elements 注册表。
|
|
15
|
+
*
|
|
16
|
+
* 关键契约(照抄平台侧,改动会直接导致真机渲染空白):
|
|
17
|
+
* - 数据挂在 `s = c.state`,表达式里的 `$foo` 已改写为 `s.$foo`;
|
|
18
|
+
* - `data` 里必须同时给 defaultState / state / n(n 是刷新因子,模板里靠它触发重算);
|
|
19
|
+
* - watch.data 重新合并 state,宿主下发新数据时才能刷新;
|
|
20
|
+
* - 每个事件的实现体被搬到 methods 里,on 只留转发(避免闭包捕获旧 state)。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
collectProps,
|
|
25
|
+
convertCondition,
|
|
26
|
+
convertExpression,
|
|
27
|
+
convertPropsClient,
|
|
28
|
+
convertPropsH5,
|
|
29
|
+
hasText,
|
|
30
|
+
jsKey,
|
|
31
|
+
pickVarProps,
|
|
32
|
+
} from './common.js'
|
|
33
|
+
import { elementOf } from './clientElements.js'
|
|
34
|
+
|
|
35
|
+
const INJECTS = {
|
|
36
|
+
getObject: { type: 'Function', default: 'function(){return function(){}}' },
|
|
37
|
+
getElement: { type: 'Function', default: 'function(){return function(n){return n}}' },
|
|
38
|
+
getCreateElement: { type: 'Function', default: 'function(){return function(vm){ return vm.$createElement;}}' },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createH5Compiler(card, { warnings = [] } = {}) {
|
|
42
|
+
let counter = 0
|
|
43
|
+
const nextVal = (prefix) => `${prefix}${counter++}`
|
|
44
|
+
|
|
45
|
+
/** 生命周期代码桶 */
|
|
46
|
+
const lifecycles = { created: [], destroyed: [], unmounted: [] }
|
|
47
|
+
const methods = []
|
|
48
|
+
const components = []
|
|
49
|
+
|
|
50
|
+
/* ---------------------------- 卡片级上下文 ---------------------------- */
|
|
51
|
+
|
|
52
|
+
lifecycles.created.push(`this.$emit('info',${JSON.stringify({ width: card.width })});`)
|
|
53
|
+
|
|
54
|
+
// data 定义:入参默认值 + 运行态 state + 刷新因子
|
|
55
|
+
const defaultState = {}
|
|
56
|
+
for (const field of card.data.props) defaultState[field.key] = field.defaultValue
|
|
57
|
+
|
|
58
|
+
lifecycles.created.push('this.state = Object.assign({}, this.defaultState, this.data);')
|
|
59
|
+
|
|
60
|
+
lifecycles.created.push("var CC = this.getObject('context');")
|
|
61
|
+
lifecycles.created.push('CC.init&&CC.init(this);')
|
|
62
|
+
lifecycles.destroyed.push("var CC = this.getObject('context');")
|
|
63
|
+
lifecycles.destroyed.push('CC.destroy&&CC.destroy(this);')
|
|
64
|
+
lifecycles.unmounted.push("var CC = this.getObject('context');")
|
|
65
|
+
lifecycles.unmounted.push('CC.destroy&&CC.destroy(this);')
|
|
66
|
+
|
|
67
|
+
const watch = { data: 'this.state = Object.assign({}, this.defaultState, this.data);' }
|
|
68
|
+
|
|
69
|
+
/* ------------------------------ 组件树 ------------------------------ */
|
|
70
|
+
|
|
71
|
+
const root = {
|
|
72
|
+
id: 'root',
|
|
73
|
+
name: card.className,
|
|
74
|
+
style: {},
|
|
75
|
+
props: convertPropsH5(card.properties),
|
|
76
|
+
styleProps: convertPropsH5(card.style.properties),
|
|
77
|
+
events: [],
|
|
78
|
+
slots: [],
|
|
79
|
+
}
|
|
80
|
+
components.push(root)
|
|
81
|
+
|
|
82
|
+
// 根节点不处理 directives —— 与平台 h5/convert/CardConvert 一致(该处只转 style + 事件)。
|
|
83
|
+
// 早期版本这里调过一次 applyDirectives,但卡片模型本就没有 directives 字段,属于死代码;
|
|
84
|
+
// 更要紧的是平台产物不含根层显隐,本地产物若多出来就会与线上不一致。
|
|
85
|
+
for (const ea of card.eventactions) addEvent(root, ea)
|
|
86
|
+
|
|
87
|
+
const slot = { slotKey: 'default', slotScope: false, components: [] }
|
|
88
|
+
root.slots.push(slot)
|
|
89
|
+
for (const child of card.components) slot.components.push(convertComponent(child))
|
|
90
|
+
|
|
91
|
+
function convertComponent(component) {
|
|
92
|
+
const layer = {
|
|
93
|
+
id: component.componentId,
|
|
94
|
+
name: component.className,
|
|
95
|
+
style: {},
|
|
96
|
+
props: convertPropsH5(component.properties),
|
|
97
|
+
styleProps: convertPropsH5(component.style.properties),
|
|
98
|
+
events: [],
|
|
99
|
+
slots: [],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
applyDirectives(component, layer, warnings)
|
|
103
|
+
// 双向绑定必须先于事件动作入队:平台的 ea 编号是按 events 列表顺序分配的,
|
|
104
|
+
// 顺序错了会让 on 里的转发名与 methods 里的实现体错位。
|
|
105
|
+
convertTwoWayBinding(component, layer)
|
|
106
|
+
for (const ea of component.eventactions) addEvent(layer, ea)
|
|
107
|
+
|
|
108
|
+
const childSlot = component.slots && component.slots[0]
|
|
109
|
+
if (childSlot) {
|
|
110
|
+
const s = {
|
|
111
|
+
slotKey: childSlot.slotKey,
|
|
112
|
+
// 列表 / 网格的插槽是作用域插槽(每项独立上下文)
|
|
113
|
+
slotScope: component.className === 'ListLayout' || component.className === 'GridLayout',
|
|
114
|
+
components: [],
|
|
115
|
+
}
|
|
116
|
+
layer.slots.push(s)
|
|
117
|
+
for (const child of childSlot.components) s.components.push(convertComponent(child))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return layer
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* v-show → 内联 display
|
|
125
|
+
*
|
|
126
|
+
* 平台侧(h5/DirectiveConvert)对「表达式型条件」走改写(`$x` → `s.$x`),
|
|
127
|
+
* 对「结构化条件」直接拼未改写的结果。这里统一改写:结构化条件的 val 也可能是 `$x`,
|
|
128
|
+
* 不改写会在真机上抛 ReferenceError(平台侧缺陷,属修正而非偏离)。
|
|
129
|
+
*/
|
|
130
|
+
function applyDirectives(component, layer, warnings) {
|
|
131
|
+
const { const: constProps } = convertPropsClient(component.directives)
|
|
132
|
+
const showProps = constProps['v-show']
|
|
133
|
+
if (!showProps || typeof showProps !== 'object') return
|
|
134
|
+
const showType = showProps.showType
|
|
135
|
+
if (showType === '00') return
|
|
136
|
+
|
|
137
|
+
const condition = showProps.condition
|
|
138
|
+
if (!condition || typeof condition !== 'object') return
|
|
139
|
+
|
|
140
|
+
let expression = null
|
|
141
|
+
if (hasText(condition.expression)) {
|
|
142
|
+
expression = convertExpression(condition.expression)
|
|
143
|
+
} else if (Array.isArray(condition.group) && condition.group.length > 0) {
|
|
144
|
+
expression = convertExpression(convertCondition(condition.group))
|
|
145
|
+
}
|
|
146
|
+
if (!expression) {
|
|
147
|
+
warnings.push(`组件 ${component.componentId || 'root'} 的 v-show 条件为空,已忽略`)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (showType === '01') layer.style.display = `(${expression}) ? 'block' : 'none'`
|
|
152
|
+
else if (showType === '02') layer.style.display = `(${expression}) ? 'none' : 'block'`
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 双向绑定:value 动态绑定时补一条 input 监听,把宿主事件值写回状态
|
|
157
|
+
*
|
|
158
|
+
* 迁移自 h5/convert/element/{Input,CheckGroup,RadioButton,ProgressSlider}Transfer.doUpdatePropEvent ——
|
|
159
|
+
* 四个组件的实现逐字相同:事件名固定 `input`,实现体是 `this.setData({"$value":event}, '1')`。
|
|
160
|
+
* 注意键名走 JSON.stringify(与属性段的裸写口径不同),因为 $value 这类键在平台侧就是这么发的。
|
|
161
|
+
*/
|
|
162
|
+
function convertTwoWayBinding(component, layer) {
|
|
163
|
+
if (!elementOf(component.className).twoWayBinding) return
|
|
164
|
+
const modelVar = pickVarProps(component.properties).value
|
|
165
|
+
if (!hasText(modelVar)) return
|
|
166
|
+
const name = nextVal('ea')
|
|
167
|
+
methods.push({
|
|
168
|
+
name,
|
|
169
|
+
code: `this.setData({${JSON.stringify(modelVar.trim())}:event}, '1');`,
|
|
170
|
+
})
|
|
171
|
+
layer.events.push({ name: 'input', handler: name })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 事件:实现体进 methods,on 里只做转发 */
|
|
175
|
+
function addEvent(layer, eventAction) {
|
|
176
|
+
if (!hasText(eventAction.eventKey) || !hasText(eventAction.actionType)) return
|
|
177
|
+
const name = nextVal('ea')
|
|
178
|
+
const body = buildEventBody(eventAction, layer)
|
|
179
|
+
methods.push({ name, code: body })
|
|
180
|
+
layer.events.push({ name: eventAction.eventKey, handler: name })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildEventBody(eventAction, layer) {
|
|
184
|
+
const actionType = eventAction.actionType
|
|
185
|
+
// 事件参数:走平台口径的 JSON 序列化(动态值原样进 JSON,不改写)
|
|
186
|
+
const propsMap = collectProps(eventAction.properties)
|
|
187
|
+
const { const: constProps } = convertPropsClient(eventAction.properties)
|
|
188
|
+
|
|
189
|
+
if (actionType === 'customAction') {
|
|
190
|
+
const fn = constProps.function
|
|
191
|
+
const code = fn && typeof fn === 'object' ? fn.code : null
|
|
192
|
+
const compiled = fn && typeof fn === 'object' ? fn.compiled : null
|
|
193
|
+
// 平台侧优先用 compiled(ES5 转译版),缺失才回退手写 code
|
|
194
|
+
if (hasText(compiled)) return compiled
|
|
195
|
+
if (hasText(code)) return code
|
|
196
|
+
warnings.push(`事件 ${eventAction.eventKey} 的 customAction 缺少 code,已跳过`)
|
|
197
|
+
return ''
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (actionType === 'closeAlertCard') {
|
|
201
|
+
return `ynet.callJSAPI('${actionType}',${JSON.stringify({ cardCode: card.code })});`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!hasText(actionType)) return ''
|
|
205
|
+
// 其余动作统一走 JSAPI,参数是属性对象
|
|
206
|
+
return `ynet.callJSAPI('${actionType}',${JSON.stringify(propsMap)});`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ------------------------------ 序列化 ------------------------------ */
|
|
210
|
+
|
|
211
|
+
const serializeComponent = (layer) => {
|
|
212
|
+
const args = [`e(${JSON.stringify(layer.name)})`]
|
|
213
|
+
const options = []
|
|
214
|
+
|
|
215
|
+
options.push(`ref:${JSON.stringify(layer.id)}`)
|
|
216
|
+
|
|
217
|
+
if (Object.keys(layer.style).length) {
|
|
218
|
+
const styleCode = Object.entries(layer.style)
|
|
219
|
+
.map(([k, v]) => `${jsKey(k)}:${v}`)
|
|
220
|
+
.join(',')
|
|
221
|
+
options.push(`style:{${styleCode}}`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (hasText(layer.props) && layer.props !== '{}') {
|
|
225
|
+
options.push(`props:{styleProps:${layer.styleProps}${stripBraces(layer.props)}}`)
|
|
226
|
+
} else if (hasText(layer.styleProps) && layer.styleProps !== '{}') {
|
|
227
|
+
options.push(`props:{styleProps:${layer.styleProps}}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (layer.events.length) {
|
|
231
|
+
const code = layer.events
|
|
232
|
+
.map((ev) => `${jsKey(ev.name)}:function(event){_vm.${ev.handler}(c,event)}`)
|
|
233
|
+
.join(',')
|
|
234
|
+
options.push(`on:{${code}}`)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const normalSlots = layer.slots.filter((s) => !s.slotScope)
|
|
238
|
+
const scopeSlots = layer.slots.filter((s) => s.slotScope)
|
|
239
|
+
|
|
240
|
+
if (scopeSlots.length) {
|
|
241
|
+
const code = scopeSlots
|
|
242
|
+
.map(
|
|
243
|
+
(s) =>
|
|
244
|
+
`${jsKey(s.slotKey)}:function(_sd_){return (function(c){var s = c.state; return [${s.components
|
|
245
|
+
.map(serializeComponent)
|
|
246
|
+
.join(',')}]})(new CC(_vm,_sd_,c))}`
|
|
247
|
+
)
|
|
248
|
+
.join(',')
|
|
249
|
+
options.push(`scopedSlots:{${code}}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
args.push(`{${options.join(',')}}`)
|
|
253
|
+
|
|
254
|
+
if (normalSlots.length) {
|
|
255
|
+
const slots = normalSlots
|
|
256
|
+
.map(
|
|
257
|
+
(s) =>
|
|
258
|
+
`h('template', {slot: ${JSON.stringify(s.slotKey)}},[${s.components.map(serializeComponent).join(',')}])`
|
|
259
|
+
)
|
|
260
|
+
.join(',')
|
|
261
|
+
args.push(`[${slots}]`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return `h(${args.join(',')})`
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const renderBody =
|
|
268
|
+
'render: function (_h) {' +
|
|
269
|
+
'var _vm = this;' +
|
|
270
|
+
'var h = this.getCreateElement(this,_h);' +
|
|
271
|
+
"var CC = this.getObject('context');" +
|
|
272
|
+
'var e = this.getElement;' +
|
|
273
|
+
'var c = new CC(this,this);' +
|
|
274
|
+
'var s = c.state;' +
|
|
275
|
+
'var n = this.n;' +
|
|
276
|
+
`return ${components.map(serializeComponent).join('')}` +
|
|
277
|
+
'}'
|
|
278
|
+
|
|
279
|
+
const parts = []
|
|
280
|
+
|
|
281
|
+
const injectCode = Object.entries(INJECTS)
|
|
282
|
+
.map(([k, v]) => `${k}:{type:${v.type},default:${v.default}}`)
|
|
283
|
+
.join(',')
|
|
284
|
+
parts.push(`inject:{${injectCode}}`)
|
|
285
|
+
|
|
286
|
+
parts.push('props:{data:{type:Object,default:function(){ return {} }}}')
|
|
287
|
+
|
|
288
|
+
const dataCode = [
|
|
289
|
+
`defaultState:${JSON.stringify(defaultState)}`,
|
|
290
|
+
'state:{}',
|
|
291
|
+
'n:0',
|
|
292
|
+
].join(',')
|
|
293
|
+
parts.push(`data:function(){ return {${dataCode}}}`)
|
|
294
|
+
|
|
295
|
+
for (const key of ['created', 'destroyed', 'unmounted']) {
|
|
296
|
+
if (lifecycles[key].length) parts.push(`${key}:function(){${lifecycles[key].join('')}}`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (Object.keys(watch).length) {
|
|
300
|
+
const code = Object.entries(watch)
|
|
301
|
+
.map(([k, v]) => `${k}:function(){${v}}`)
|
|
302
|
+
.join(',')
|
|
303
|
+
parts.push(`watch:{${code}}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
parts.push(renderBody)
|
|
307
|
+
|
|
308
|
+
if (methods.length) {
|
|
309
|
+
const code = methods
|
|
310
|
+
.map(
|
|
311
|
+
(m) =>
|
|
312
|
+
`${m.name}:function(c, event){var ynet=this.getObject('ynet');(function(){\n${m.code}\n}).apply(c);}`
|
|
313
|
+
)
|
|
314
|
+
.join(',')
|
|
315
|
+
parts.push(`methods:{${code}}`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 结尾与平台侧一致:`return {…}});`(对象闭合后直接跟 `});`,无空格)
|
|
319
|
+
return `define(function(){ return {${parts.join(',')}}});`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function stripBraces(literal) {
|
|
323
|
+
if (!literal) return ''
|
|
324
|
+
const body = literal.replace(/^\{/, '').replace(/\}$/, '')
|
|
325
|
+
return body ? `,${body}` : ''
|
|
326
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 卡片编译入口:schema.json → 客户端(GaiaX)产物 + H5 产物
|
|
3
|
+
*
|
|
4
|
+
* 编译逻辑迁移自 flame-release-mange(Java)的 card/exporter 包,
|
|
5
|
+
* 本文件只做「两套引擎的编排 + 文件序列化 + 加密」,不含业务判断。
|
|
6
|
+
*
|
|
7
|
+
* 返回:
|
|
8
|
+
* {
|
|
9
|
+
* cardCode, version,
|
|
10
|
+
* client: [{ code, files: { 'index.json': string, ... } }],
|
|
11
|
+
* h5: string,
|
|
12
|
+
* warnings: string[],
|
|
13
|
+
* stats: { nodes, templates, clientBytes, h5Bytes }
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import crypto from 'node:crypto'
|
|
18
|
+
import { parseCard } from './model.js'
|
|
19
|
+
import {
|
|
20
|
+
createClientCompiler,
|
|
21
|
+
serializeTemplateCss,
|
|
22
|
+
serializeTemplateData,
|
|
23
|
+
serializeTemplateDataBinding,
|
|
24
|
+
serializeTemplateJs,
|
|
25
|
+
serializeTemplateJson,
|
|
26
|
+
} from './client.js'
|
|
27
|
+
import { createH5Compiler } from './h5.js'
|
|
28
|
+
|
|
29
|
+
/** 平台侧 SM4 参数(CBC / PKCS5Padding),与 NativeEngine 内的常量一致 */
|
|
30
|
+
const SM4_KEY = '95330f00edb563a34de2200bcf745147'
|
|
31
|
+
const SM4_IV = 'f2e7f925855c66f75e4f070e782f7ed4'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* index.style:index.js 的密文(iOS 侧仅在明文 index.js 缺失时才读它)
|
|
35
|
+
*
|
|
36
|
+
* 平台侧是 `SM4/CBC/PKCS5Padding` 的 `doFinal` 结果直接落盘 —— 即**二进制密文**,
|
|
37
|
+
* 不是 base64 文本。Node 的 `sm4-cbc` 默认填充同为 PKCS#7(16 字节块下与 PKCS#5 等价),
|
|
38
|
+
* 所以这里也必须输出 Buffer,编码成 base64 会让 iOS 侧解不出来。
|
|
39
|
+
*
|
|
40
|
+
* @returns {Buffer|null} 密文字节;运行环境不支持 SM4 时返回 null
|
|
41
|
+
*/
|
|
42
|
+
export function encryptStyle(jsCode) {
|
|
43
|
+
try {
|
|
44
|
+
const cipher = crypto.createCipheriv('sm4-cbc', Buffer.from(SM4_KEY, 'hex'), Buffer.from(SM4_IV, 'hex'))
|
|
45
|
+
return Buffer.concat([cipher.update(jsCode, 'utf8'), cipher.final()])
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 单个产物的字节数(index.style 是 Buffer,其余是字符串) */
|
|
52
|
+
function byteSize(value) {
|
|
53
|
+
return Buffer.isBuffer(value) ? value.length : Buffer.byteLength(value, 'utf8')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 统计视图树节点数(含子模板),用于产物报告 */
|
|
57
|
+
function countNodes(components) {
|
|
58
|
+
let n = 0
|
|
59
|
+
const walk = (list) => {
|
|
60
|
+
for (const c of list) {
|
|
61
|
+
n += 1
|
|
62
|
+
for (const s of c.slots || []) walk(s.components)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
walk(components)
|
|
66
|
+
return n
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 编译一张卡片
|
|
71
|
+
* @param {Object} schema schema.json 内容
|
|
72
|
+
* @param {Object} opts
|
|
73
|
+
* @param {string} opts.cardCode 卡片编码(= 本地目录名)
|
|
74
|
+
* @param {string} [opts.version] 模板版本(写进 package.version,缺省取 schema.version)
|
|
75
|
+
* @param {boolean} [opts.encryptStyle] 是否额外产出 index.style,默认 true
|
|
76
|
+
*/
|
|
77
|
+
export function compileCard(schema, { cardCode, version, encryptStyle: withEncryptedStyle = true } = {}) {
|
|
78
|
+
const warnings = []
|
|
79
|
+
const ver = String(version || schema.version || '1.0.0')
|
|
80
|
+
|
|
81
|
+
// 两份独立模型:客户端编译会改写列表类组件的 componentId(平台侧同款副作用),
|
|
82
|
+
// 若与 H5 共用同一份模型,H5 产物的 ref 会变成子模板编码。
|
|
83
|
+
const clientCard = parseCard(schema, cardCode)
|
|
84
|
+
const h5Card = parseCard(schema, cardCode)
|
|
85
|
+
|
|
86
|
+
const clientEngine = createClientCompiler(clientCard, { version: ver, warnings })
|
|
87
|
+
|
|
88
|
+
const client = clientEngine.templates.map((template) => {
|
|
89
|
+
const files = {
|
|
90
|
+
'index.json': serializeTemplateJson(template),
|
|
91
|
+
'index.css': serializeTemplateCss(template),
|
|
92
|
+
'index.databinding': serializeTemplateDataBinding(template),
|
|
93
|
+
'index.data': serializeTemplateData(template),
|
|
94
|
+
'index.js': serializeTemplateJs(template),
|
|
95
|
+
}
|
|
96
|
+
if (withEncryptedStyle) {
|
|
97
|
+
const encrypted = encryptStyle(files['index.js'])
|
|
98
|
+
if (encrypted) files['index.style'] = encrypted
|
|
99
|
+
else warnings.push('当前 Node 不支持 SM4,已跳过 index.style')
|
|
100
|
+
}
|
|
101
|
+
return { code: template.code, files }
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const h5 = createH5Compiler(h5Card, { warnings })
|
|
105
|
+
|
|
106
|
+
const clientBytes = client.reduce(
|
|
107
|
+
(sum, t) => sum + Object.values(t.files).reduce((s, v) => s + byteSize(v), 0),
|
|
108
|
+
0
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
cardCode,
|
|
113
|
+
version: ver,
|
|
114
|
+
client,
|
|
115
|
+
h5,
|
|
116
|
+
warnings,
|
|
117
|
+
stats: {
|
|
118
|
+
nodes: countNodes(clientCard.components),
|
|
119
|
+
templates: client.length,
|
|
120
|
+
clientBytes,
|
|
121
|
+
h5Bytes: Buffer.byteLength(h5, 'utf8'),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default compileCard
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema.json → 编译模型
|
|
3
|
+
*
|
|
4
|
+
* 1:1 迁移自 flame-release-mange(Java)的
|
|
5
|
+
* com.ynet.inmanage.application.card.model.{CardModel,ComponentModel,StyleModel,
|
|
6
|
+
* PropertyModel,PropertyGroupModel,DataModel,FieldModel,SlotModel,EventActionModel}
|
|
7
|
+
*
|
|
8
|
+
* 输入契约与 dsl-card 的 src/cards/<id>/schema.json **完全同源**:
|
|
9
|
+
* { platform, dictionary:{dictionaryList,inputParams}, view:{ width, className, style,
|
|
10
|
+
* properties, eventactions, components:[{ componentId, className, style, properties,
|
|
11
|
+
* directives, eventactions, slots:[{ slotKey, components }] }] } }
|
|
12
|
+
* 因此不需要任何字段重命名,也不需要经过编辑器的 buildPreview(那会灌入 meta 默认值,
|
|
13
|
+
* 与平台侧编译结果不一致)。
|
|
14
|
+
*
|
|
15
|
+
* 设计约束:本文件只做**结构解析**,不判断语义;所有取值一律走 `pick`,
|
|
16
|
+
* 缺字段时给 Java 侧同样的空值(`''` / `[]` / `{}`),保证下游转换分支与 Java 等价。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** 取字符串(Java getString 语义:非字符串值转字符串,null 保持 null) */
|
|
20
|
+
export function str(v) {
|
|
21
|
+
if (v === undefined || v === null) return null
|
|
22
|
+
if (typeof v === 'string') return v
|
|
23
|
+
return String(v)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 取 JSON 对象(对应 Java getJSONObject:非对象返回 null) */
|
|
27
|
+
export function obj(v) {
|
|
28
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 取 JSON 数组(对应 Java getJSONArray:非数组返回 null) */
|
|
32
|
+
export function arr(v) {
|
|
33
|
+
return Array.isArray(v) ? v : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ------------------------------ Property ------------------------------ */
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 属性节点
|
|
40
|
+
* @typedef {Object} PropertyModel
|
|
41
|
+
* @property {string} itemKey
|
|
42
|
+
* @property {string} itemValueFrom '0' 静态 / '1' 动态表达式 / '2' 资源地址
|
|
43
|
+
* @property {*} itemValue
|
|
44
|
+
* @property {*} itemDefaultValue
|
|
45
|
+
* @property {PropertyModel[]} children 对象属性的子项(itemChildren.properties)
|
|
46
|
+
* @property {PropertyModel[][]} list 数组属性(itemList[].properties)
|
|
47
|
+
*/
|
|
48
|
+
export function parseProperty(p = {}) {
|
|
49
|
+
const children = obj(p.itemChildren)
|
|
50
|
+
const list = arr(p.itemList)
|
|
51
|
+
return {
|
|
52
|
+
itemKey: str(p.itemKey),
|
|
53
|
+
itemValueFrom: str(p.itemValueFrom),
|
|
54
|
+
itemValue: p.itemValue === undefined ? null : p.itemValue,
|
|
55
|
+
itemDefaultValue: p.itemDefaultValue === undefined ? null : p.itemDefaultValue,
|
|
56
|
+
children: children ? parsePropertyArray(children.properties) : [],
|
|
57
|
+
list: list ? list.map((g) => parsePropertyArray(obj(g) ? g.properties : null)) : [],
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function parsePropertyArray(list) {
|
|
62
|
+
const a = arr(list)
|
|
63
|
+
return a ? a.map((p) => parseProperty(p || {})) : []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 样式定义(StyleModel):实质就是一组属性 */
|
|
67
|
+
export function parseStyle(style) {
|
|
68
|
+
const s = obj(style)
|
|
69
|
+
return { properties: s ? parsePropertyArray(s.properties) : [] }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ------------------------------ Component ----------------------------- */
|
|
73
|
+
|
|
74
|
+
export function parseSlot(slot = {}) {
|
|
75
|
+
return {
|
|
76
|
+
slotKey: str(slot.slotKey) || 'default',
|
|
77
|
+
slotScope: slot.slotScope === true || slot.slotScope === 'true',
|
|
78
|
+
components: parseComponentArray(slot.components),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function parseComponent(c = {}) {
|
|
83
|
+
const slots = arr(c.slots)
|
|
84
|
+
return {
|
|
85
|
+
componentId: str(c.componentId),
|
|
86
|
+
className: str(c.className),
|
|
87
|
+
name: str(c.name),
|
|
88
|
+
style: parseStyle(c.style),
|
|
89
|
+
properties: parsePropertyArray(c.properties),
|
|
90
|
+
directives: parsePropertyArray(c.directives),
|
|
91
|
+
eventactions: parseEventActionArray(c.eventactions),
|
|
92
|
+
slots: slots ? slots.map((s) => parseSlot(s || {})) : [],
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseComponentArray(list) {
|
|
97
|
+
const a = arr(list)
|
|
98
|
+
return a ? a.map((c) => parseComponent(c || {})) : []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ------------------------------ EventAction --------------------------- */
|
|
102
|
+
|
|
103
|
+
export function parseEventAction(ea = {}) {
|
|
104
|
+
const event = obj(ea.event) || {}
|
|
105
|
+
const action = obj(ea.action) || {}
|
|
106
|
+
return {
|
|
107
|
+
eventKey: str(event.eventKey),
|
|
108
|
+
actionType: str(action.actionType),
|
|
109
|
+
properties: parsePropertyArray(action.properties),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function parseEventActionArray(list) {
|
|
114
|
+
const a = arr(list)
|
|
115
|
+
return a ? a.map((ea) => parseEventAction(ea || {})) : []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* -------------------------------- Field ------------------------------- */
|
|
119
|
+
|
|
120
|
+
export function parseField(f = {}) {
|
|
121
|
+
const children = arr(f.children)
|
|
122
|
+
return {
|
|
123
|
+
key: str(f.key),
|
|
124
|
+
name: str(f.name),
|
|
125
|
+
type: str(f.type),
|
|
126
|
+
defaultValue: f.defaultValue === undefined ? null : f.defaultValue,
|
|
127
|
+
children: children ? children.map((c) => parseField(c || {})) : [],
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function parseFieldArray(list) {
|
|
132
|
+
const a = arr(list)
|
|
133
|
+
return a ? a.map((f) => parseField(f || {})) : []
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* --------------------------------- Card ------------------------------- */
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 卡片模型
|
|
140
|
+
* @param {Object} schema schema.json 内容
|
|
141
|
+
* @param {string} code 卡片编码(= 本地卡片目录名)
|
|
142
|
+
*/
|
|
143
|
+
export function parseCard(schema = {}, code = '') {
|
|
144
|
+
const dictionary = obj(schema.dictionary) || {}
|
|
145
|
+
const view = obj(schema.view) || {}
|
|
146
|
+
return {
|
|
147
|
+
code,
|
|
148
|
+
platform: str(schema.platform),
|
|
149
|
+
width: str(view.width),
|
|
150
|
+
className: str(view.className) || 'Root',
|
|
151
|
+
data: {
|
|
152
|
+
// 数据域定义(下发数据)
|
|
153
|
+
fields: parseFieldArray(dictionary.dictionaryList),
|
|
154
|
+
// 入参定义(默认值即 index.data)
|
|
155
|
+
props: parseFieldArray(dictionary.inputParams),
|
|
156
|
+
},
|
|
157
|
+
style: parseStyle(view.style),
|
|
158
|
+
properties: parsePropertyArray(view.properties),
|
|
159
|
+
components: parseComponentArray(view.components),
|
|
160
|
+
eventactions: parseEventActionArray(view.eventactions),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -19,6 +19,7 @@ import { createSliceUploadMiddleware } from './sliceUpload.js'
|
|
|
19
19
|
import { createRemoteCardMiddleware } from './remoteCard.js'
|
|
20
20
|
import { createCardFilesMiddleware, refreshCardList } from './cardFiles.js'
|
|
21
21
|
import { createExportMiddleware } from './cardExport.js'
|
|
22
|
+
import { createCompileMiddleware } from './cardCompile.js'
|
|
22
23
|
import { createSaveMiddleware } from './cardSave.js'
|
|
23
24
|
import { createImportMetaMiddleware } from './cardImportMeta.js'
|
|
24
25
|
import { createHistoryMiddleware } from './cardHistory.js'
|
|
@@ -58,6 +59,7 @@ export {
|
|
|
58
59
|
createSaveMiddleware,
|
|
59
60
|
createImportMetaMiddleware,
|
|
60
61
|
createExportMiddleware,
|
|
62
|
+
createCompileMiddleware,
|
|
61
63
|
createHistoryMiddleware,
|
|
62
64
|
createValidateMiddleware,
|
|
63
65
|
createRenderStatusMiddleware,
|
|
@@ -126,6 +128,10 @@ export function mountCoreMiddlewares(app, { root, viteServer = null, upstream =
|
|
|
126
128
|
// 与 docs/卡片导出产物逆向分析与导出功能.md;previewSchema 经 viteServer.ssrLoadModule 复用
|
|
127
129
|
// 编辑器同源 buildPreview,故本中间件需要 viteServer(无则退化为 schema 原样序列化)。
|
|
128
130
|
app.use(createExportMiddleware({ root, viteServer, upstream }))
|
|
131
|
+
// 卡片编译(编辑器「网格预览 → 勾选 → 编译选中卡片」):把 schema 编译成客户端 GaiaX 模板包
|
|
132
|
+
// 与 H5 卡片 js,产物落 <root>/flame-build/{client,h5}。编译规则迁移自 flame-release-mange
|
|
133
|
+
// 的 card/exporter(见 server/core/flameCompile/ 各文件头注释)。
|
|
134
|
+
app.use(createCompileMiddleware({ root }))
|
|
129
135
|
app.use(createHistoryMiddleware({ root }))
|
|
130
136
|
app.use(createValidateMiddleware({ root }))
|
|
131
137
|
app.use(createRenderStatusMiddleware({ root, viteServer }))
|
|
@@ -169,7 +169,7 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
169
169
|
```js
|
|
170
170
|
// 仅当需要自定义页面交互/逐样式断言且宿主 dev server(5180) 已启动时使用;
|
|
171
171
|
// 本 skill 的离线 render_card.mjs 不提供任意页面交互,复制 skill 到其他项目后该配方按需自建。
|
|
172
|
-
import { chromium } from 'playwright';
|
|
172
|
+
import { chromium } from 'playwright-core';
|
|
173
173
|
const ts = Date.now();
|
|
174
174
|
const url = `http://localhost:5180/?card=<id>&t=${ts}`;
|
|
175
175
|
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
|