eoss 0.1.0-alpha.23 → 0.1.0-alpha.25
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/lib/0.js +1 -0
- package/lib/1.js +1 -0
- package/lib/10.js +1 -0
- package/lib/11.js +1 -0
- package/lib/12.js +1 -0
- package/lib/13.js +1 -0
- package/lib/14.js +1 -0
- package/lib/15.js +1 -0
- package/lib/16.js +1 -0
- package/lib/17.js +1 -0
- package/lib/18.js +1 -0
- package/lib/19.js +1 -0
- package/lib/20.js +1 -0
- package/lib/21.js +1 -0
- package/lib/22.js +1 -0
- package/lib/23.js +1 -0
- package/lib/24.js +1 -0
- package/lib/25.js +1 -0
- package/lib/26.js +1 -0
- package/lib/27.js +1 -0
- package/lib/28.js +1 -0
- package/lib/29.js +1 -0
- package/lib/3.js +1 -0
- package/lib/30.js +1 -0
- package/lib/31.js +1 -0
- package/lib/32.js +1 -0
- package/lib/33.js +1 -0
- package/lib/34.js +1 -0
- package/lib/35.js +1 -0
- package/lib/36.js +1 -0
- package/lib/37.js +1 -0
- package/lib/38.js +1 -0
- package/lib/39.js +1 -0
- package/lib/4.js +1 -0
- package/lib/40.js +1 -0
- package/lib/41.js +1 -0
- package/lib/42.js +1 -0
- package/lib/43.js +1 -0
- package/lib/44.js +1 -0
- package/lib/45.js +1 -0
- package/lib/46.js +1 -0
- package/lib/47.js +1 -0
- package/lib/48.js +1 -0
- package/lib/49.js +1 -0
- package/lib/5.js +1 -0
- package/lib/50.js +1 -0
- package/lib/51.js +1 -0
- package/lib/52.js +1 -0
- package/lib/53.js +1 -0
- package/lib/54.js +1 -0
- package/lib/55.js +1 -0
- package/lib/56.js +1 -0
- package/lib/57.js +1 -0
- package/lib/58.js +1 -0
- package/lib/59.js +1 -0
- package/lib/6.js +1 -0
- package/lib/60.js +1 -0
- package/lib/61.js +1 -0
- package/lib/62.js +1 -0
- package/lib/63.js +1 -0
- package/lib/64.js +1 -0
- package/lib/65.js +1 -0
- package/lib/66.js +1 -0
- package/lib/67.js +1 -0
- package/lib/68.js +1 -0
- package/lib/69.js +1 -0
- package/lib/7.js +1 -0
- package/lib/70.js +1 -0
- package/lib/71.js +1 -0
- package/lib/72.js +1 -0
- package/lib/73.js +1 -0
- package/lib/74.js +1 -0
- package/lib/75.js +1 -0
- package/lib/76.js +1 -0
- package/lib/77.js +1 -0
- package/lib/78.js +1 -0
- package/lib/79.js +1 -0
- package/lib/8.js +1 -0
- package/lib/80.js +1 -0
- package/lib/81.js +1 -0
- package/lib/9.js +1 -0
- package/lib/account-login.js +1662 -1557
- package/lib/alert.js +2 -2
- package/lib/aside.js +2 -2
- package/lib/audit-trajectory.js +924 -0
- package/lib/autocomplete.js +2403 -2559
- package/lib/avatar.js +2 -2
- package/lib/backtop.js +2 -2
- package/lib/badge.js +2 -2
- package/lib/breadcrumb-item.js +2 -2
- package/lib/breadcrumb.js +2 -2
- package/lib/button-group.js +13 -13
- package/lib/button.js +2 -2
- package/lib/ca-login.js +1299 -1295
- package/lib/calendar.js +972 -997
- package/lib/card.js +2 -2
- package/lib/carousel-item.js +1295 -1291
- package/lib/carousel.js +1297 -1293
- package/lib/cascader-panel.js +1317 -1313
- package/lib/cascader.js +2423 -2579
- package/lib/checkbox-button.js +2 -2
- package/lib/checkbox-group.js +1691 -1627
- package/lib/checkbox.js +9 -42
- package/lib/col.js +2 -2
- package/lib/collapse-item.js +2849 -2971
- package/lib/collapse.js +2853 -2971
- package/lib/color-picker.js +955 -1115
- package/lib/config/api.js +77 -1
- package/lib/container.js +2 -2
- package/lib/cron.js +2 -2
- package/lib/data-table.js +1800 -1693
- package/lib/date-picker.js +2412 -2568
- package/lib/dialog.js +1485 -1479
- package/lib/divider.js +2 -2
- package/lib/drawer.js +4 -4
- package/lib/dropdown-item.js +2 -2
- package/lib/dropdown-menu.js +944 -1104
- package/lib/dropdown.js +1507 -1503
- package/lib/email-login.js +1651 -1554
- package/lib/empty.js +2 -2
- package/lib/eoss.common.js +23671 -15705
- package/lib/flip.js +2 -2
- package/lib/flow-group.js +1305 -1301
- package/lib/flow.js +1807 -1710
- package/lib/flows.js +1653 -1556
- package/lib/footer.js +2 -2
- package/lib/form-item.js +1282 -1278
- package/lib/form.js +2869 -2982
- package/lib/header.js +2 -2
- package/lib/icon.js +1526 -1409
- package/lib/icons.js +2 -2
- package/lib/image-viewer.js +1369 -1365
- package/lib/image.js +71 -71
- package/lib/index.js +1 -1
- package/lib/infinite-scroll.js +71 -71
- package/lib/input-number.js +4 -4
- package/lib/input.js +1301 -1297
- package/lib/link.js +2 -2
- package/lib/loading.js +5 -5
- package/lib/login.js +1966 -1856
- package/lib/lowcode.js +9705 -4540
- package/lib/main.js +5414 -5317
- package/lib/marquee.js +1306 -1302
- package/lib/menu-item-group.js +2 -2
- package/lib/menu-item.js +2 -2
- package/lib/menu.js +2728 -2884
- package/lib/message-box.js +1370 -1366
- package/lib/message.js +1360 -1356
- package/lib/notification.js +1358 -1354
- package/lib/option-group.js +2 -2
- package/lib/option.js +1366 -1362
- package/lib/page-header.js +2 -2
- package/lib/pagination.js +1299 -1295
- package/lib/phone-login.js +1644 -1547
- package/lib/popconfirm.js +12 -12
- package/lib/popover.js +2289 -2445
- package/lib/process-config.js +7645 -0
- package/lib/process-module.js +7051 -0
- package/lib/process-rule.js +8217 -0
- package/lib/progress.js +2 -2
- package/lib/qr-code.js +7 -7
- package/lib/qrcode-login.js +1643 -1546
- package/lib/radio-button.js +10 -21
- package/lib/radio-group.js +1712 -1644
- package/lib/radio.js +10 -25
- package/lib/rate.js +1360 -1356
- package/lib/row.js +2 -2
- package/lib/scrollbar.js +1360 -1356
- package/lib/select.js +3138 -3200
- package/lib/selector-panel.js +1747 -1712
- package/lib/selector.js +1910 -1823
- package/lib/slider.js +2 -2
- package/lib/static/codicon.805fb6a.ttf +0 -0
- package/lib/step.js +2853 -2971
- package/lib/steps.js +1298 -1294
- package/lib/submenu.js +1454 -1614
- package/lib/switch.js +1297 -1293
- package/lib/tab-pane.js +2 -2
- package/lib/table-column.js +1332 -1328
- package/lib/table.js +3446 -3509
- package/lib/tabs.js +1297 -1293
- package/lib/tag.js +2 -2
- package/lib/theme-chalk/audit-trajectory.css +0 -0
- package/lib/theme-chalk/button-group.css +1 -1
- package/lib/theme-chalk/calendar.css +1 -1
- package/lib/theme-chalk/icons.css +1 -1
- package/lib/theme-chalk/index.css +1 -1
- package/lib/theme-chalk/lowcode.css +1 -1
- package/lib/theme-chalk/process-config.css +0 -0
- package/lib/theme-chalk/process-module.css +0 -0
- package/lib/theme-chalk/process-rule.css +1 -0
- package/lib/theme-chalk/tree.css +1 -1
- package/lib/theme-chalk/update-history.css +0 -0
- package/lib/time-picker.js +1953 -2138
- package/lib/time-select.js +1949 -2134
- package/lib/timeline-item.js +2 -2
- package/lib/timeline.js +2 -2
- package/lib/tips.js +1691 -1594
- package/lib/toolbar.js +6 -6
- package/lib/tooltip.js +2289 -2445
- package/lib/transfer.js +1312 -1308
- package/lib/tree-group.js +1526 -1409
- package/lib/tree.js +1721 -1624
- package/lib/update-history.js +879 -0
- package/lib/upload.js +1731 -1634
- package/lib/utils/ajax.js +85 -106
- package/lib/utils/util.js +33 -5
- package/package.json +1 -1
- package/packages/account-login/src/account-login.vue +20 -6
- package/packages/audit-trajectory/index.js +5 -0
- package/packages/audit-trajectory/src/main.vue +99 -0
- package/packages/button/src/button-group.vue +1 -1
- package/packages/checkbox/src/checkbox.vue +9 -14
- package/packages/data-table/src/data-table.vue +13 -4
- package/packages/dialog/src/dialog.vue +2 -0
- package/packages/form/src/form-item-render.vue +4 -0
- package/packages/form/src/form.vue +2 -2
- package/packages/login/src/login.vue +47 -46
- package/packages/lowcode/src/aside.vue +3 -1
- package/packages/lowcode/src/attribute/bind.vue +459 -233
- package/packages/lowcode/src/attribute/dependency.vue +492 -228
- package/packages/lowcode/src/attribute/function.vue +391 -48
- package/packages/lowcode/src/attribute/javascript.vue +380 -0
- package/packages/lowcode/src/attribute/property.vue +317 -228
- package/packages/lowcode/src/attribute/styles.vue +17 -4
- package/packages/lowcode/src/attribute.vue +787 -16
- package/packages/lowcode/src/canvas.vue +206 -27
- package/packages/lowcode/src/editor.vue +197 -32
- package/packages/lowcode/src/json.vue +63 -18
- package/packages/lowcode/src/lowcode.vue +253 -21
- package/packages/lowcode/src/mixins/history.js +132 -0
- package/packages/lowcode/src/pages.vue +0 -52
- package/packages/lowcode/src/render.vue +108 -8
- package/packages/lowcode/src/source.vue +72 -16
- package/packages/lowcode/src/toolbar.vue +43 -20
- package/packages/lowcode/src/util/json-repair.js +147 -0
- package/packages/lowcode/src/util/monaco-vue-completions.js +344 -0
- package/packages/lowcode/src/util/source-generator.js +535 -0
- package/packages/lowcode/src/util/source-parser.js +461 -0
- package/packages/lowcode/src/view.vue +187 -0
- package/packages/lowcode-view/index.js +5 -0
- package/packages/process-config/index.js +5 -0
- package/packages/process-config/src/FormList.vue +180 -0
- package/packages/process-config/src/NodeConfig.vue +212 -0
- package/packages/process-config/src/NodeForm.vue +237 -0
- package/packages/process-config/src/main.vue +257 -0
- package/packages/process-module/index.js +5 -0
- package/packages/process-module/src/FormList.vue +177 -0
- package/packages/process-module/src/main.vue +233 -0
- package/packages/process-rule/index.js +5 -0
- package/packages/process-rule/src/FormList.vue +614 -0
- package/packages/process-rule/src/companyReportDetail.vue +92 -0
- package/packages/process-rule/src/main.vue +749 -0
- package/packages/radio/src/radio-button.vue +12 -9
- package/packages/radio/src/radio-group.vue +10 -14
- package/packages/radio/src/radio.vue +12 -11
- package/packages/select/src/option-tree.vue +4 -4
- package/packages/select/src/option.vue +2 -2
- package/packages/select/src/select.vue +32 -36
- package/packages/selector/src/selector.vue +10 -12
- package/packages/theme-chalk/src/audit-trajectory.scss +0 -0
- package/packages/theme-chalk/src/button-group.scss +7 -0
- package/packages/theme-chalk/src/icons.scss +1 -1
- package/packages/theme-chalk/src/index.scss +5 -0
- package/packages/theme-chalk/src/lowcode.scss +90 -1
- package/packages/theme-chalk/src/process-config.scss +0 -0
- package/packages/theme-chalk/src/process-module.scss +0 -0
- package/packages/theme-chalk/src/process-rule.scss +57 -0
- package/packages/theme-chalk/src/tree.scss +1 -0
- package/packages/theme-chalk/src/update-history.scss +0 -0
- package/packages/update-history/index.js +5 -0
- package/packages/update-history/src/main.vue +54 -0
- package/src/components/field-render.vue +5 -0
- package/src/config/api.js +77 -0
- package/src/index.js +322 -307
- package/src/utils/ajax.js +90 -115
- package/src/utils/util.js +35 -4
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue 单文件源码 → 低代码页面 JSON 解析器
|
|
3
|
+
* generateVueSource 的反向操作:源码解析失败会抛出带中文说明的 Error
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const VOID_TAGS = ['img', 'input', 'br', 'hr', 'meta', 'link'];
|
|
7
|
+
|
|
8
|
+
/** EsButton → es-button(去掉转换产生的首部连字符) */
|
|
9
|
+
function toKebabCase(name) {
|
|
10
|
+
return String(name)
|
|
11
|
+
.replace(/([A-Z])/g, '-$1')
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/^-/, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 大括号配对定位:从 { 开始找到匹配的 },忽略字符串内的括号 */
|
|
17
|
+
function findBalanced(text, openIndex) {
|
|
18
|
+
let depth = 0;
|
|
19
|
+
let quote = '';
|
|
20
|
+
for (let i = openIndex; i < text.length; i++) {
|
|
21
|
+
const ch = text[i];
|
|
22
|
+
if (quote) {
|
|
23
|
+
if (ch === '\\') {
|
|
24
|
+
i++;
|
|
25
|
+
} else if (ch === quote) {
|
|
26
|
+
quote = '';
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
31
|
+
quote = ch;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (ch === '{') {
|
|
35
|
+
depth++;
|
|
36
|
+
} else if (ch === '}') {
|
|
37
|
+
depth--;
|
|
38
|
+
if (depth === 0) {
|
|
39
|
+
return i;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 解析标签属性:支持单/双引号,返回 { name, value } 列表 */
|
|
47
|
+
function parseAttrs(str) {
|
|
48
|
+
const attrs = [];
|
|
49
|
+
const re = /([^\s=/>]+)(?:\s*=\s*("([^"]*)"|'([^']*)'))?/g;
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = re.exec(str))) {
|
|
52
|
+
if (!match[1]) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const value =
|
|
56
|
+
match[3] !== undefined ? match[3] : match[4] !== undefined ? match[4] : null;
|
|
57
|
+
attrs.push({ name: match[1], value: value });
|
|
58
|
+
}
|
|
59
|
+
return attrs;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 属性列表 → 节点字段(attrs / props / directives / events / style) */
|
|
63
|
+
function applyAttrs(node, attrs) {
|
|
64
|
+
attrs.forEach((attr) => {
|
|
65
|
+
const name = attr.name;
|
|
66
|
+
const value = attr.value === null ? true : attr.value;
|
|
67
|
+
if (name === 'data-id') {
|
|
68
|
+
// 节点 id 由生成器以 data-id 输出,回填后不作为属性
|
|
69
|
+
node.id = String(value);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (name === ':style') {
|
|
73
|
+
// 支持生成器输出的对象字面量形式
|
|
74
|
+
if (typeof value === 'string' && value.trim().charAt(0) === '{') {
|
|
75
|
+
try {
|
|
76
|
+
node.style = new Function('return (' + value + ');')();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error(':style 绑定不是合法的对象:' + value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (name.charAt(0) === '@') {
|
|
84
|
+
node.events = node.events || {};
|
|
85
|
+
node.events[name.slice(1)] = value;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (name.charAt(0) === ':') {
|
|
89
|
+
node.props = node.props || {};
|
|
90
|
+
const key = name.slice(1);
|
|
91
|
+
const trimmed = String(value).trim();
|
|
92
|
+
// 数字/布尔直接还原类型,其它保留表达式字符串
|
|
93
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
94
|
+
node.props[key] = Number(trimmed);
|
|
95
|
+
} else if (trimmed === 'true' || trimmed === 'false') {
|
|
96
|
+
node.props[key] = trimmed === 'true';
|
|
97
|
+
} else {
|
|
98
|
+
node.props[key] = String(value);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Vue 指令(v-model / v-if / v-show 等)存入 directives,键不含 v- 前缀
|
|
103
|
+
if (name.startsWith('v-')) {
|
|
104
|
+
node.directives = node.directives || {};
|
|
105
|
+
node.directives[name.slice(2)] = value;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// 普通 HTML 属性存入 attrs
|
|
109
|
+
node.attrs = node.attrs || {};
|
|
110
|
+
node.attrs[name] = value;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 兼容历史数据:把节点 props 中残留的 v- 指令迁移到 directives
|
|
116
|
+
* (早期版本曾把指令存入 props),迁移后键不含 v- 前缀,原地递归处理整棵节点树
|
|
117
|
+
* @param {Object} node 节点或页面对象
|
|
118
|
+
* @returns {Object} 同一个节点对象
|
|
119
|
+
*/
|
|
120
|
+
export function migrateDirectives(node) {
|
|
121
|
+
if (!node || typeof node !== 'object') {
|
|
122
|
+
return node;
|
|
123
|
+
}
|
|
124
|
+
if (node.props) {
|
|
125
|
+
Object.keys(node.props).forEach((key) => {
|
|
126
|
+
if (key.startsWith('v-')) {
|
|
127
|
+
const dirKey = key.slice(2);
|
|
128
|
+
node.directives = node.directives || {};
|
|
129
|
+
if (node.directives[dirKey] === undefined) {
|
|
130
|
+
node.directives[dirKey] = node.props[key];
|
|
131
|
+
}
|
|
132
|
+
delete node.props[key];
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (node.children && node.children.length) {
|
|
137
|
+
node.children.forEach((child) => migrateDirectives(child));
|
|
138
|
+
}
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** template 内容 → 节点树(根节点返回数组,一般只有一个根元素) */
|
|
143
|
+
export function parseTemplateNodes(inner) {
|
|
144
|
+
const roots = [];
|
|
145
|
+
const stack = [];
|
|
146
|
+
// 开标签 / 闭标签 / 注释
|
|
147
|
+
const tagRe = /<\/([a-zA-Z][\w-]*)\s*>|<([a-zA-Z][\w-]*)((?:[^>"']|"[^"]*"|'[^']*')*)(\/?)>|<!--[\s\S]*?-->/g;
|
|
148
|
+
let last = 0;
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = tagRe.exec(inner))) {
|
|
151
|
+
const text = inner.slice(last, match.index);
|
|
152
|
+
last = tagRe.lastIndex;
|
|
153
|
+
// 两个标签之间的文本:{{xxx}} 存为 text 指令,纯文本存为 description
|
|
154
|
+
const trimmedText = text.trim();
|
|
155
|
+
if (trimmedText && stack.length) {
|
|
156
|
+
const parent = stack[stack.length - 1];
|
|
157
|
+
// 检查是否包含 Vue 模板表达式 {{xxx}}
|
|
158
|
+
const vueExprMatch = trimmedText.match(/^\{\{([\s\S]*?)\}\}$/);
|
|
159
|
+
if (vueExprMatch) {
|
|
160
|
+
// 纯 Vue 表达式,转换为 text 指令
|
|
161
|
+
const expr = vueExprMatch[1].trim();
|
|
162
|
+
parent.directives = parent.directives || {};
|
|
163
|
+
parent.directives['text'] = expr;
|
|
164
|
+
} else {
|
|
165
|
+
// 纯文本存为 description,避免重新生成时被包上 {{}}
|
|
166
|
+
parent.description = trimmedText;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!match[1] && !match[2]) {
|
|
170
|
+
// 注释,跳过
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (match[1]) {
|
|
174
|
+
// 闭标签
|
|
175
|
+
const closing = match[1];
|
|
176
|
+
const top = stack[stack.length - 1];
|
|
177
|
+
if (!top || top.name !== closing) {
|
|
178
|
+
throw new Error('标签未正确闭合:</' + closing + '>');
|
|
179
|
+
}
|
|
180
|
+
stack.pop();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// 开标签
|
|
184
|
+
const node = {
|
|
185
|
+
name: match[2],
|
|
186
|
+
id: '',
|
|
187
|
+
description: ''
|
|
188
|
+
};
|
|
189
|
+
applyAttrs(node, parseAttrs(match[3]));
|
|
190
|
+
if (stack.length) {
|
|
191
|
+
const parent = stack[stack.length - 1];
|
|
192
|
+
parent.children = parent.children || [];
|
|
193
|
+
parent.children.push(node);
|
|
194
|
+
} else {
|
|
195
|
+
roots.push(node);
|
|
196
|
+
}
|
|
197
|
+
// 自闭合或空元素标签不入栈
|
|
198
|
+
if (!match[4] && VOID_TAGS.indexOf(match[2].toLowerCase()) === -1) {
|
|
199
|
+
stack.push(node);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (stack.length) {
|
|
203
|
+
throw new Error('标签未正确闭合:<' + stack[stack.length - 1].name + '>');
|
|
204
|
+
}
|
|
205
|
+
return roots;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 给节点树回填 id(有 id 的保留,没有的按路径生成稳定 id) */
|
|
209
|
+
let idSeed = 0;
|
|
210
|
+
function assignIds(nodes) {
|
|
211
|
+
nodes.forEach((node) => {
|
|
212
|
+
if (!node.id) {
|
|
213
|
+
idSeed += 1;
|
|
214
|
+
node.id = 'node_' + idSeed;
|
|
215
|
+
}
|
|
216
|
+
if (node.children && node.children.length) {
|
|
217
|
+
assignIds(node.children);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** 提取函数体并去掉统一缩进 */
|
|
223
|
+
function extractBody(blockText, key) {
|
|
224
|
+
const re = new RegExp('(?:^|[,\\s{])' + key + '\\s*\\([^)]*\\)\\s*\\{');
|
|
225
|
+
const m = re.exec(blockText);
|
|
226
|
+
if (!m) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const openIndex = blockText.indexOf('{', m.index + m[0].length - 1);
|
|
230
|
+
const closeIndex = findBalanced(blockText, openIndex);
|
|
231
|
+
if (closeIndex === -1) {
|
|
232
|
+
throw new Error(key + ' 的花括号未闭合');
|
|
233
|
+
}
|
|
234
|
+
let body = blockText.slice(openIndex + 1, closeIndex);
|
|
235
|
+
const lines = body.split('\n');
|
|
236
|
+
// 统计非空行的最小缩进,统一去掉
|
|
237
|
+
let minIndent = Infinity;
|
|
238
|
+
lines.forEach((line) => {
|
|
239
|
+
if (line.trim()) {
|
|
240
|
+
const indent = line.match(/^\s*/)[0].length;
|
|
241
|
+
if (indent < minIndent) {
|
|
242
|
+
minIndent = indent;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
if (minIndent !== Infinity && minIndent > 0) {
|
|
247
|
+
body = lines
|
|
248
|
+
.map((line) => line.slice(minIndent))
|
|
249
|
+
.join('\n')
|
|
250
|
+
.replace(/^\n+|\n+$/g, '');
|
|
251
|
+
} else {
|
|
252
|
+
body = body.trim();
|
|
253
|
+
}
|
|
254
|
+
return { body: body };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 提取 methods / computed / watch 块 */
|
|
258
|
+
function extractOption(script, name) {
|
|
259
|
+
const re = new RegExp('(?:^|[,\\s{])' + name + '\\s*:\\s*\\{');
|
|
260
|
+
const m = re.exec(script);
|
|
261
|
+
if (!m) {
|
|
262
|
+
return {};
|
|
263
|
+
}
|
|
264
|
+
const openIndex = script.indexOf('{', m.index + m[0].length - 1);
|
|
265
|
+
const closeIndex = findBalanced(script, openIndex);
|
|
266
|
+
if (closeIndex === -1) {
|
|
267
|
+
throw new Error(name + ' 块的花括号未闭合');
|
|
268
|
+
}
|
|
269
|
+
const blockText = script.slice(openIndex + 1, closeIndex);
|
|
270
|
+
// 按块内的顶层"键() {"或"键: "逐个提取
|
|
271
|
+
const result = {};
|
|
272
|
+
// (行首缩进)(键)(函数形式或冒号),缩进组从 ^ 开始取,避免函数体内部的语句被误认
|
|
273
|
+
const keyRe = /(^|[\n,])([ \t]*)([\w$]+)\s*(\([^)]*\)\s*\{|:)/g;
|
|
274
|
+
let keyMatch;
|
|
275
|
+
while ((keyMatch = keyRe.exec(blockText))) {
|
|
276
|
+
// 只认低缩进的顶层键,避免抓到函数体内部的变量
|
|
277
|
+
if (keyMatch[2].length > 4) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const key = keyMatch[3];
|
|
281
|
+
if (keyMatch[4].charAt(0) === '(') {
|
|
282
|
+
// 函数形式:methods / computed getter / watch
|
|
283
|
+
if (name === 'computed') {
|
|
284
|
+
// computed 生成时包了一层 return (function() {...})();
|
|
285
|
+
const inner = extractBody(blockText.slice(keyMatch.index), key);
|
|
286
|
+
result[key] = inner || { body: '' };
|
|
287
|
+
if (inner && inner.body.indexOf('return (function()') === 0) {
|
|
288
|
+
inner.body = inner.body
|
|
289
|
+
.replace(/^return \(function\(\) \{\n?/, '')
|
|
290
|
+
.replace(/\n?\s*\}\)\(\);?$/, '')
|
|
291
|
+
.replace(/^\s{2}/gm, '');
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
const hook = extractBody(blockText.slice(keyMatch.index), key);
|
|
295
|
+
if (hook) {
|
|
296
|
+
result[key] = hook;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
// 值形式:从冒号后读到下一个顶层键之前
|
|
301
|
+
const start = keyMatch.index + keyMatch[0].length;
|
|
302
|
+
const next = keyRe.exec(blockText);
|
|
303
|
+
const end = next ? next.index : blockText.length;
|
|
304
|
+
const raw = blockText.slice(start, end).replace(/,\s*$/, '').trim();
|
|
305
|
+
try {
|
|
306
|
+
result[key] = new Function('return (' + raw + ');')();
|
|
307
|
+
} catch (e) {
|
|
308
|
+
throw new Error(name + '.' + key + ' 的值无法解析:' + raw);
|
|
309
|
+
}
|
|
310
|
+
if (next) {
|
|
311
|
+
keyRe.lastIndex = next.index;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** 生命周期钩子 */
|
|
319
|
+
function extractHook(script, name) {
|
|
320
|
+
return extractBody(script, name);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** data() 还原为对象 */
|
|
324
|
+
function extractData(script) {
|
|
325
|
+
const m = /data\s*\(\s*\)\s*\{/.exec(script);
|
|
326
|
+
if (!m) {
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
const returnMatch = /return\s*(\{|\[)/.exec(script.slice(m.index));
|
|
330
|
+
if (!returnMatch) {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
const openIndex = m.index + returnMatch.index + returnMatch[0].length - 1;
|
|
334
|
+
const closeIndex = findBalanced(script, openIndex);
|
|
335
|
+
if (closeIndex === -1) {
|
|
336
|
+
throw new Error('data 返回值的花括号未闭合');
|
|
337
|
+
}
|
|
338
|
+
const raw = script.slice(openIndex, closeIndex + 1);
|
|
339
|
+
try {
|
|
340
|
+
return new Function('return (' + raw + ');')();
|
|
341
|
+
} catch (e) {
|
|
342
|
+
throw new Error('data 返回值不是合法对象字面量');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** import 语句 + components 注册 → 页面 imports 数据 */
|
|
347
|
+
function extractImports(script) {
|
|
348
|
+
// 收集所有 import 语句
|
|
349
|
+
const defaultImports = {}; // import X from 'path'
|
|
350
|
+
|
|
351
|
+
// 解析 default import: import X from 'path'
|
|
352
|
+
const defaultRe = /import\s+([\w$]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
353
|
+
let m;
|
|
354
|
+
while ((m = defaultRe.exec(script))) {
|
|
355
|
+
defaultImports[m[1]] = m[2];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 合并为统一的 imports 数组(所有 import 都作为组件处理)
|
|
359
|
+
const result = [];
|
|
360
|
+
|
|
361
|
+
Object.keys(defaultImports).forEach((name) => {
|
|
362
|
+
result.push({
|
|
363
|
+
name: toKebabCase(name),
|
|
364
|
+
path: defaultImports[name]
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 将 Vue 单文件源码解析为页面 JSON(generateVueSource 的逆操作)
|
|
373
|
+
* @param {string} source .vue 源码
|
|
374
|
+
* @param {Object} original 原页面对象,源码不涉及的业务字段
|
|
375
|
+
* (pid/title/type/status/route 等)会从它身上保留,避免保存时丢失
|
|
376
|
+
* @returns {Object} 页面对象
|
|
377
|
+
* @throws {Error} 带中文说明的解析错误
|
|
378
|
+
*/
|
|
379
|
+
export function parseVueSource(source, original) {
|
|
380
|
+
idSeed = 0;
|
|
381
|
+
if (!source || !source.trim()) {
|
|
382
|
+
throw new Error('源码为空');
|
|
383
|
+
}
|
|
384
|
+
const templateMatch = /<template>([\s\S]*?)<\/template>/.exec(source);
|
|
385
|
+
if (!templateMatch) {
|
|
386
|
+
throw new Error('缺少 <template> 部分');
|
|
387
|
+
}
|
|
388
|
+
const scriptMatch = /<script>([\s\S]*?)<\/script>/.exec(source);
|
|
389
|
+
const script = scriptMatch ? scriptMatch[1] : '';
|
|
390
|
+
|
|
391
|
+
// ---------- 结构树 ----------
|
|
392
|
+
const roots = parseTemplateNodes(templateMatch[1].trim());
|
|
393
|
+
if (!roots.length) {
|
|
394
|
+
throw new Error('template 里没有根元素');
|
|
395
|
+
}
|
|
396
|
+
if (roots.length > 1) {
|
|
397
|
+
throw new Error('template 只能有一个根元素');
|
|
398
|
+
}
|
|
399
|
+
const root = roots[0];
|
|
400
|
+
assignIds([root]);
|
|
401
|
+
|
|
402
|
+
// ---------- script 各选项 ----------
|
|
403
|
+
const page = {
|
|
404
|
+
name: '',
|
|
405
|
+
imports: [],
|
|
406
|
+
props: extractOption(script, 'props'),
|
|
407
|
+
data: extractData(script),
|
|
408
|
+
computed: extractOption(script, 'computed'),
|
|
409
|
+
watch: extractOption(script, 'watch'),
|
|
410
|
+
methods: extractOption(script, 'methods'),
|
|
411
|
+
created: extractHook(script, 'created'),
|
|
412
|
+
mounted: extractHook(script, 'mounted'),
|
|
413
|
+
destroyed: extractHook(script, 'destroyed')
|
|
414
|
+
};
|
|
415
|
+
const nameMatch = /name\s*:\s*["']([^"']*)["']/.exec(script);
|
|
416
|
+
page.name = nameMatch ? nameMatch[1] : '';
|
|
417
|
+
page.imports = extractImports(script);
|
|
418
|
+
|
|
419
|
+
// 根节点字段合并进页面对象(根标签名不作为标签输出)
|
|
420
|
+
page.id = root.id;
|
|
421
|
+
page.attrs = root.attrs || {};
|
|
422
|
+
// 注意:不覆盖脚本级别的 props 和 directives
|
|
423
|
+
// 根节点的 props/directives 是模板属性,脚本级别的是组件选项定义
|
|
424
|
+
// 如果根节点有 props/directives,应该合并而不是覆盖
|
|
425
|
+
if (root.props && Object.keys(root.props).length > 0) {
|
|
426
|
+
page.props = { ...page.props, ...root.props };
|
|
427
|
+
}
|
|
428
|
+
if (root.directives && Object.keys(root.directives).length > 0) {
|
|
429
|
+
page.directives = { ...page.directives, ...root.directives };
|
|
430
|
+
}
|
|
431
|
+
page.style = root.style || {};
|
|
432
|
+
page.children = root.children || [];
|
|
433
|
+
page.description = root.description || '';
|
|
434
|
+
|
|
435
|
+
// 原页面上源码不涉及的字段原样保留(pid/title/type/status/route 等)
|
|
436
|
+
if (original && typeof original === 'object') {
|
|
437
|
+
Object.keys(original).forEach((key) => {
|
|
438
|
+
if (!(key in page)) {
|
|
439
|
+
page[key] = original[key];
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 清理空值,保持页面 JSON 干净
|
|
445
|
+
Object.keys(page).forEach((key) => {
|
|
446
|
+
const val = page[key];
|
|
447
|
+
if (val === null || val === undefined) {
|
|
448
|
+
delete page[key];
|
|
449
|
+
} else if (
|
|
450
|
+
typeof val === 'object' &&
|
|
451
|
+
!Array.isArray(val) &&
|
|
452
|
+
!Object.keys(val).length &&
|
|
453
|
+
['props', 'data', 'computed', 'watch', 'methods', 'created', 'mounted', 'destroyed'].indexOf(key) !== -1
|
|
454
|
+
) {
|
|
455
|
+
delete page[key];
|
|
456
|
+
} else if (key === 'imports' && !val.length) {
|
|
457
|
+
delete page[key];
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return page;
|
|
461
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="es-lowcode-view">
|
|
3
|
+
<Render v-if="rootNode" :node="rootNode" :page="resolvedPage" />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import Render from './render.vue';
|
|
9
|
+
import { migrateDirectives } from './util/source-parser';
|
|
10
|
+
import { ajax } from 'eoss/src/utils/ajax';
|
|
11
|
+
import { loading } from 'eoss/src/utils/util';
|
|
12
|
+
import Message from 'eoss/packages/message';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'LowcodeView',
|
|
16
|
+
|
|
17
|
+
components: {
|
|
18
|
+
Render
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
props: {
|
|
22
|
+
/**
|
|
23
|
+
* 页面配置 JSON 对象或 JSON 字符串
|
|
24
|
+
* 与 url 二选一,json 优先
|
|
25
|
+
*/
|
|
26
|
+
json: {
|
|
27
|
+
type: [Object, String],
|
|
28
|
+
default: null
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* 页面配置 JSON 的远程地址
|
|
32
|
+
* 组件会自动请求并解析
|
|
33
|
+
*/
|
|
34
|
+
url: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: ''
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
resolvedPage: null,
|
|
43
|
+
loadingInstance: null
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
computed: {
|
|
48
|
+
/**
|
|
49
|
+
* 构建根节点
|
|
50
|
+
* page 对象本身不是标签节点,需要包一层 div 作为根
|
|
51
|
+
*/
|
|
52
|
+
rootNode() {
|
|
53
|
+
if (!this.resolvedPage) return null;
|
|
54
|
+
const root = {};
|
|
55
|
+
Object.keys(this.resolvedPage).forEach((key) => {
|
|
56
|
+
root[key] = this.resolvedPage[key];
|
|
57
|
+
});
|
|
58
|
+
root.name = 'div';
|
|
59
|
+
return root;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
watch: {
|
|
64
|
+
json: {
|
|
65
|
+
handler(val) {
|
|
66
|
+
if (val) {
|
|
67
|
+
this.loadFromJson(val);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
immediate: true
|
|
71
|
+
},
|
|
72
|
+
url: {
|
|
73
|
+
handler(val) {
|
|
74
|
+
if (val && !this.json) {
|
|
75
|
+
this.loadFromUrl(val);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
immediate: true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
created() {
|
|
83
|
+
if (this.json) {
|
|
84
|
+
this.loadFromJson(this.json);
|
|
85
|
+
} else if (this.url) {
|
|
86
|
+
this.loadFromUrl(this.url);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
methods: {
|
|
91
|
+
/**
|
|
92
|
+
* 从 JSON 对象/字符串加载页面配置
|
|
93
|
+
* @param {Object|String} json
|
|
94
|
+
*/
|
|
95
|
+
loadFromJson(json) {
|
|
96
|
+
try {
|
|
97
|
+
const page = typeof json === 'string' ? JSON.parse(json) : json;
|
|
98
|
+
this.resolvedPage = this.normalizePage(migrateDirectives(page));
|
|
99
|
+
this.$emit('loaded', this.resolvedPage);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
Message.error('JSON 解析失败:' + e.message);
|
|
102
|
+
this.$emit('error', e.message);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* 从远程 URL 加载页面配置
|
|
107
|
+
* @param {String} url
|
|
108
|
+
*/
|
|
109
|
+
loadFromUrl(url) {
|
|
110
|
+
this.loadingInstance = loading('加载中...');
|
|
111
|
+
ajax({
|
|
112
|
+
url: url,
|
|
113
|
+
method: 'GET'
|
|
114
|
+
})
|
|
115
|
+
.then((res) => {
|
|
116
|
+
this.loadingInstance && this.loadingInstance.close();
|
|
117
|
+
this.loadingInstance = null;
|
|
118
|
+
if (res.rCode === 0 || res.code === 200) {
|
|
119
|
+
// 兼容两种响应格式
|
|
120
|
+
const pageData = res.data || res.results || res;
|
|
121
|
+
// 如果返回的是包含 content 字段的记录,解析 content
|
|
122
|
+
if (pageData.content) {
|
|
123
|
+
const content =
|
|
124
|
+
typeof pageData.content === 'string'
|
|
125
|
+
? JSON.parse(pageData.content)
|
|
126
|
+
: pageData.content;
|
|
127
|
+
const page = { ...pageData, ...content };
|
|
128
|
+
delete page.content;
|
|
129
|
+
this.resolvedPage = this.normalizePage(migrateDirectives(page));
|
|
130
|
+
} else {
|
|
131
|
+
this.resolvedPage = this.normalizePage(
|
|
132
|
+
migrateDirectives(pageData)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
this.$emit('loaded', this.resolvedPage);
|
|
136
|
+
} else {
|
|
137
|
+
Message.error(res.msg || '加载页面配置失败');
|
|
138
|
+
this.$emit('error', res.msg);
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.catch((e) => {
|
|
142
|
+
this.loadingInstance && this.loadingInstance.close();
|
|
143
|
+
this.loadingInstance = null;
|
|
144
|
+
Message.error('网络请求失败:' + (e.message || e));
|
|
145
|
+
this.$emit('error', e);
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* 标准化页面对象(确保必要字段存在)
|
|
150
|
+
*/
|
|
151
|
+
normalizePage(page) {
|
|
152
|
+
if (!page) return null;
|
|
153
|
+
return {
|
|
154
|
+
name: page.name || 'LowcodePage',
|
|
155
|
+
children: page.children || [],
|
|
156
|
+
props: page.props || {},
|
|
157
|
+
directives: page.directives || {},
|
|
158
|
+
data: page.data || {},
|
|
159
|
+
computed: page.computed || {},
|
|
160
|
+
methods: page.methods || {},
|
|
161
|
+
events: page.events || {},
|
|
162
|
+
style: page.style || {},
|
|
163
|
+
attrs: page.attrs || {},
|
|
164
|
+
components: page.components || [],
|
|
165
|
+
imports: page.imports || [],
|
|
166
|
+
css: page.css || [],
|
|
167
|
+
script: page.script || [],
|
|
168
|
+
created: page.created || null,
|
|
169
|
+
mounted: page.mounted || null,
|
|
170
|
+
destroyed: page.destroyed || null,
|
|
171
|
+
watch: page.watch || {},
|
|
172
|
+
inject: page.inject || null,
|
|
173
|
+
provide: page.provide || null,
|
|
174
|
+
template: page.template || null,
|
|
175
|
+
description: page.description || ''
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
</script>
|
|
181
|
+
|
|
182
|
+
<style scoped>
|
|
183
|
+
.es-lowcode-view {
|
|
184
|
+
width: 100%;
|
|
185
|
+
min-height: 100%;
|
|
186
|
+
}
|
|
187
|
+
</style>
|