@lcap/nasl
Version:
NetEase Application Specific Language
1,211 lines (1,195 loc) • 65.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOneFiles = exports.genFrontendBundleFiles = exports.genBundleFiles = exports.stringifyMetaData = exports.checkOfficalPermissTemplate = exports.officalEntityList37 = exports.entityList = void 0;
const JSON5 = __importStar(require("json5"));
const VueCompiler = __importStar(require("vue-template-compiler"));
const vue_template_es2015_compiler_1 = __importDefault(require("vue-template-es2015-compiler"));
const utils = __importStar(require("../utils"));
const config_1 = require("../config");
const breakpoint_1 = require("../breakpoint");
const nasl_log_1 = require("@lcap/nasl-log");
const nasl_utils_1 = require("@lcap/nasl-utils");
const nasl_translator_1 = require("@lcap/nasl-translator");
const concepts_1 = require("../concepts");
const compileComponent_1 = require("./compileComponent");
const nasl_unified_frontend_generator_1 = require("@lcap/nasl-unified-frontend-generator");
const microApp_1 = require("./microApp");
// @ts-ignore FIXME wudengke 类型在构建前找不到的问题
const nasl_unified_frontend_generator_2 = require("@lcap/nasl-unified-frontend-generator");
const axios_1 = __importDefault(require("axios"));
const officalEntityMap = {
LCAPUser: ['id', 'createdTime', 'updatedTime', 'userId', 'userName', 'password', 'phone', 'email', 'displayName', 'status', 'source'],
LCAPLogicViewMapping: ['id', 'logicIdentifier', 'resourceName', 'resourceType', 'group', 'changeTime'],
LCAPRolePerMapping: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'roleId', 'permissionId'],
LCAPPerResMapping: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'permissionId', 'resourceId'],
LCAPUserRoleMapping: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'userId', 'roleId', 'userName', 'source'],
LCAPRole: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'uuid', 'name', 'description', 'roleStatus', 'editable'],
LCAPPermission: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'uuid', 'name', 'description'],
LCAPResource: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'uuid', 'name', 'description', 'type', 'clientType'],
LCAPUserDeptMapping: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'userId', 'deptId', 'isDeptLeader'],
LCAPDepartment: ['id', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'name', 'deptId', 'parentDeptId'],
};
exports.entityList = Object.keys(officalEntityMap);
exports.officalEntityList37 = exports.entityList.filter(it => it !== "LCAPLogicViewMapping");
function checkOfficalPermissTemplate(app) {
let defaultDS = app.dataSources.find((ds) => ds.name === 'defaultDS');
let appEntityList = defaultDS?.entities?.map((it) => it.name) ?? [];
let isReact = false;
const pcFrontendType = app?.frontendTypes?.find((it) => it?.kind === 'pc');
isReact = pcFrontendType?.frameworkKind === 'react';
if (isReact) {
// 兼容:react 权限模板无人更新
// 临时方案可以特殊处理react 不判断这两张表
exports.officalEntityList37 = exports.officalEntityList37.filter(it => !['LCAPUserDeptMapping', 'LCAPDepartment'].includes(it));
}
return exports.officalEntityList37.every((entity) => appEntityList.includes(entity));
}
exports.checkOfficalPermissTemplate = checkOfficalPermissTemplate;
// 将metaData转成字符串
function stringifyMetaData(obj) {
// 处理对象类型
if (typeof obj === 'object' && obj !== null) {
if (Array.isArray(obj)) {
// 处理数组类型
const elements = obj.map(element => stringifyMetaData(element));
return `[${elements.join(',')}]`;
}
let newObj = obj;
const properties = [];
if (obj.concept === 'FrontendVariable') { // speed consideration
newObj = obj.toJSON() || {};
newObj.defaultCode = obj.defaultCode;
const defaultValueFn = obj.defaultValue?.toJS((0, nasl_translator_1.createCompilerState)('', { getVuePrototype: true }));
if (obj.defaultCode.executeCode && defaultValueFn) {
properties.push(`"defaultValueFn": (Vue) => {
return ${defaultValueFn}
}`);
}
}
Object.keys(newObj).forEach(key => {
const value = stringifyMetaData(newObj[key]);
if (value !== undefined)
properties.push(`${stringifyMetaData(key)}: ${value}`);
});
return `{
${properties.join(',')}
}`;
}
return JSON5.stringify(obj);
}
exports.stringifyMetaData = stringifyMetaData;
// 编译模板, 类似 vue-loader 的 compileTemplate
function compileTemplate(template) {
const toFunction = (code) => {
return `function () {${code}}`;
};
let code;
try {
console.time('compileTemplate');
const { render, staticRenderFns } = VueCompiler.compile(template);
code = (0, vue_template_es2015_compiler_1.default)(`var __render__ = ${toFunction(render)}\n` + `var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`, { transforms: { stripWithFunctional: false } });
console.timeEnd('compileTemplate');
}
catch (error) {
console.error('编译模板失败:', error);
}
return code;
}
// 生成组件
function genComponentCode(component) {
let renderCode = compileTemplate(component.template);
const optionCode = renderCode
? `render: __render__,\n staticRenderFns: __staticRenderFns__,`
: `template: \`${component.template.replace(/[`$]/g, (m) => `\\${m}`)}\`,`;
return `(function(){
var componentOptions = ${component.script
? `(function(){\n${component.script.trim().replace(/export default |module\.exports +=/, 'return ')}\n})()`
: '{}'};
${renderCode}
Object.assign(componentOptions, {
${optionCode}
});
return componentOptions;
})()`;
}
// 生成导入组件代码
function genImportComponetCode(componentPath) {
return `() => importComponent('${componentPath}')`;
}
// 生成导出组件代码
function genExportComponetCode(component) {
let renderCode = compileTemplate(component.template);
const optionCode = renderCode
? `render: __render__,\n staticRenderFns: __staticRenderFns__,`
: `template: \`${component.template.replace(/[`$]/g, (m) => `\\${m}`)}\`,`;
return `var componentOptions = ${component.script ? `(function(){\n${component.script.trim().replace(/export default |module\.exports +=/, 'return ')}\n})()` : '{}'};
${renderCode}
Object.assign(componentOptions, {
${optionCode}
});
return componentOptions;`;
}
// 获取文件路径
// name 或者 文件 至少有一个必填
const getCompletePath = (name, fileContent, config) => {
let fileName = '';
if (name) {
fileName += `${name}.`;
}
if (fileContent) {
fileName += `${(0, nasl_utils_1.genHash)(fileContent)}.`;
}
fileName += `min.js`;
// if(config?.isExport && !fileName.includes('bundle')&& !fileName.includes('router')){
// fileName =`${config.sysPrefixPath}/${fileName}`
// }
return fileName;
};
function genRouterFileContent(routes, defaultRoute) {
function routeToString(route) {
let content = `{
path: '${route.path}',\n`;
if (route?.lazyPath) {
const routeLazyPath = route.lazyPath;
content += `lazyPath: '${routeLazyPath}',\n`;
content += `component: ${genImportComponetCode(route.lazyPath)},\n`;
}
if (route?.meta) {
content += `meta: ${stringifyMetaData(route.meta)},`;
}
if (route?.children?.length) {
content += `children: [
${route.children.map(routeToString).join(',\n')}
],\n`;
}
if (route?.redirect) {
content += `redirect: '${route?.redirect}',\n`;
}
content += '}';
return content;
}
// 生成路由配置
let routesStr = `const routers = [`;
routes.forEach((route) => {
routesStr += `${routeToString(route)},\n`;
});
if (defaultRoute) {
routesStr += `{
path: '*',
redirect: '${defaultRoute}',
}\n`;
}
routesStr += `];
return routers;`;
return routesStr;
}
// 生成路由文件
function genRouteFiles(routes, defaultRoute, config) {
// 生成路由文件列表
const routeFiles = [];
function routeToFile(route) {
if (route?.component?.script) {
const content = genExportComponetCode(route.component);
const lazyPath = getCompletePath(null, content, config);
route.lazyPath = lazyPath;
// let outFilePath =`${config?.sysPrefixPath? config?.sysPrefixPath +'/' :''}${lazyPath}`;
routeFiles.push({
name: lazyPath,
content,
});
}
if (route?.children?.length) {
route.children.map(routeToFile);
}
}
routes.forEach((route) => {
routeToFile(route);
});
let routerPath = getCompletePath('router', null, config);
routeFiles.push({
name: routerPath,
isRouterFile: true,
content: genRouterFileContent(routes, defaultRoute),
});
return routeFiles;
}
async function genBundleFiles(app, frontend, config) {
config.sysPrefixPath = app?.sysPrefixPath;
config.isPureFeMode = app?.preferenceMap?.viewMode === 'fe';
// 获取端类型
const frontendType = frontend.getAncestor('FrontendType');
// 获取业务组件
const businessComponents = frontendType?.businessComponents || [];
const configEntrancePort = config?.entrancePort;
const configLowcodeDomain = config?.lowcodeDomain;
const fnNuimsDomain = config?.envNuimsDomain?.[config?.env] || config?.nuimsDomain;
const fnLowcodeDomain = config?.envLcpDomain?.[config?.env]?.lcpDomain || config?.lowcodeDomain;
const modules = [];
const views = [];
const STATIC_URL = config.isExport ? '' : config.STATIC_URL;
app.dependencies && modules.push(...app.dependencies);
app.interfaceDependencies && modules.push(...app.interfaceDependencies);
app.connectorDependencies && modules.push(...app.connectorDependencies);
frontend.views && views.push(...frontend.views);
modules.forEach((module) => {
module.views && views.push(...module.views);
});
const componentMap = {};
// 需要放在清除断点节点之前
let compRegStr = '';
try {
if ((!config?.isExport && config?.env === 'dev') || config?.debug) {
(0, nasl_log_1.genLogs)(app);
}
config?.debug && (0, breakpoint_1.genBreakpoints)(app);
app.curDeployEnv = config.env;
console.time('toVueOptions');
let diffNodePaths = [];
// 如果不需要全量发布,而且有配置diffNodePaths,表示只导出配置的节点,就只发列表中的
// 其余的情况都算需要发布
if (!config?.isFull && config?.diffNodePaths?.length) {
diffNodePaths = config.diffNodePaths;
}
for (const view of views) {
const toVueOptionsLam = ((nd) => {
if (nd.concept !== 'View') {
return;
}
let viewFlag = false;
if (nd.toVueOptions) {
if (diffNodePaths.length) {
// 如果当前有列表长度,而且当前节点路径在列表中,表示需要生成代码
if (diffNodePaths.includes(nd.nodePath)) {
viewFlag = true;
}
}
else {
// 如果没有配置diffNodePaths,表示全部导出
// 每个页面都发就对了
viewFlag = true;
}
}
if (viewFlag) {
componentMap[nd.id] = nd.toVueOptions({
isExport: !!config.isExport,
isRelease: !!config.realRelease,
});
}
});
// @ts-ignore
await traverseForToVueOptions(view.__v_raw ?? view, toVueOptionsLam);
}
console.timeEnd('toVueOptions');
businessComponents.forEach((businessComponent) => {
const frontendI18nEnabled = frontend?.i18nInfo?.enabled;
const vueOptions = businessComponent.toVueOptions({ frontendI18nEnabled });
const component = (0, compileComponent_1.compileComponent)(vueOptions);
compRegStr += `window.Vue.component('bs-${businessComponent.name}', ${genComponentCode(component)},\n);`;
});
}
finally {
config?.debug && (0, breakpoint_1.clearBreakpoints)(app);
if ((!config?.isExport && config?.env === 'dev') || config?.debug) {
(0, nasl_log_1.clearLogs)(app);
}
}
const getBundleFileName = () => {
if (config.isPreviewFe && config.previewVersion) {
return `mockBundle-${config.previewVersion}`;
}
else {
return 'bundle';
}
};
let baseUrl = `${config.USER_STATIC_URL}/${config.tenant}/${app.id}/${config.env}`;
let { basePath } = frontend;
if (config.isExport) {
baseUrl = '';
// basePath = '';
}
if (config.isPreviewFe) {
baseUrl = `${config.USER_STATIC_URL}/asset-center/preview/${config.tenant}/${app.id}/${config.env}`;
}
let completePath = `${baseUrl}${basePath || ''}/`;
/**
* vue.config.js page options
*/
const routes = [];
// 开启了权限的页面
const authResourceViews = [];
const baseResourcePaths = [];
const rootViewData = [];
let defaultRoute = '';
let viewCount = 0;
// 遍历页面
const traverseViews = (view, isParentViewAuth) => {
viewCount++;
if (viewCount === 15) {
// 预留时间做 minor gc,防止峰值内存过高
utils.delay(55);
viewCount = 0;
}
const parentNode = view.parentNode || {};
const isRootView = parentNode.concept !== 'View';
const viewName = view.name;
// 页面是否需要权限,如果父页面需要权限,子页面也一定需要
const isViewAuth = isParentViewAuth || view.auth;
// 路由地址
let routePath = viewName;
if (isRootView) {
rootViewData.push({ name: viewName, title: view.title || viewName, isIndex: view.isIndex });
routePath = `${frontend.prefixPath}/${viewName}`;
if (!isViewAuth) {
if (viewName === 'notFound') {
defaultRoute = routePath;
}
if (view.isIndex) {
if (!defaultRoute) {
defaultRoute = routePath;
}
}
}
}
const route = {
path: routePath,
component: (0, compileComponent_1.compileComponent)(componentMap[view.id]),
children: [],
meta: view.getRouteMeta(),
};
const viewChildren = view.children;
if (Array.isArray(viewChildren) && viewChildren.length) {
for (let i = 0; i < viewChildren.length; i++) {
const subView = viewChildren[i];
// @ts-ignore
const { route: subViewRoute } = traverseViews(subView.__v_raw ?? subView, isViewAuth);
route.children.push(subViewRoute);
if (subView.isIndex) {
route.children.push({
path: '',
redirect: subView.name,
});
}
}
}
const viewPath = view.path;
if (isViewAuth) {
authResourceViews.push(view);
}
if (!isViewAuth) {
baseResourcePaths.push(viewPath);
}
return {
route,
isViewAuth,
};
};
// 页面
views.forEach((view) => {
// @ts-ignore
const { route } = traverseViews(view.__v_raw ?? view);
routes.push(route);
if (view.isIndex) {
routes.push({
path: frontend.prefixPath ? frontend.prefixPath : '/',
redirect: `${frontend.prefixPath}/${view.name}`,
});
}
});
if (frontend.prefixPath) {
routes.push({
path: '/',
redirect: frontend.prefixPath,
});
}
let authResourcePathMap = {};
// 默认跳转子页面开启权限的情况,需要把父页面也都加入权限校验列表
if (Array.isArray(authResourceViews)) {
authResourceViews.forEach((authResourceView) => {
if (authResourceView) {
authResourcePathMap[authResourceView.path] = true;
let viewNode = authResourceView;
while (viewNode.concept === 'View' && viewNode.isIndex) {
const parentViewNode = viewNode.parentNode;
if (parentViewNode.concept === 'View') {
authResourcePathMap[parentViewNode.path] = true;
}
else {
// viewNode是根页面
if (frontend.prefixPath) {
authResourcePathMap[frontend.prefixPath] = true;
}
authResourcePathMap['/'] = true;
}
viewNode = parentViewNode;
}
}
});
}
if (!checkOfficalPermissTemplate(app)) {
// authResourcePathMap={}
}
const authResourcePaths = Object.keys(authResourcePathMap);
const routerFiles = genRouteFiles(routes, defaultRoute, config);
routerFiles.forEach(item => {
let temp = completePath;
// if(completePath=='/'){temp=''}else{temp = `${completePath}/`}
if (config?.isExport && config?.sysPrefixPath?.length > 0) {
temp = config?.sysPrefixPath + temp;
}
item.name = temp + item.name;
});
let fileI18nInfo = {
enabled: false,
locale: 'zh-CN',
messages: {},
I18nList: [],
};
if (frontend?.i18nInfo?.enabled) {
const naslI18nInfo = frontend.i18nInfo?.toJSON();
// 因为内部还有对象嵌套,先序列化一下
fileI18nInfo = JSON.parse(JSON.stringify(naslI18nInfo));
fileI18nInfo.messages = fileI18nInfo.messageMap;
delete fileI18nInfo.messageMap;
const i18nMessages = fileI18nInfo.messages || {};
// 发布前公布下最新文本翻译
const frontEndI18nNodes = frontend.getI18nKeyNodes();
const frontEndI18nMap = {};
frontEndI18nNodes.forEach((node) => {
frontEndI18nMap[node['i18nKey']] = node.value;
});
// 如果其他语言内容这里的没有设置,就复用中文的翻译
const infoZhMessages = i18nMessages['zh-CN'];
const AllZhMessages = { ...infoZhMessages, ...frontEndI18nMap };
// 中文列表直接覆盖
i18nMessages['zh-CN'] = AllZhMessages;
// 遍历所有语言,然后把 别的语言没有的文本,赋值为中文的文本
// 循环遍历messages,中文的message 赋值到别的语言中
Object.keys(i18nMessages).forEach((key) => {
if (key !== 'zh-CN') {
const i18nMessage = i18nMessages[key];
Object.keys(AllZhMessages).forEach((zhKey) => {
// 如果发布的时候国际化节点存储的内容和文本的不一样,其余语言的内容都要替换成文本的内容
// 前提是这个是nasl节点的内容
if (frontEndI18nMap[zhKey] && infoZhMessages[zhKey] !== frontEndI18nMap[zhKey]) {
i18nMessage[zhKey] = AllZhMessages[zhKey];
}
else if (!i18nMessage[zhKey]) {
// 如果单纯的没配置,就用主语言的
i18nMessage[zhKey] = AllZhMessages[zhKey];
}
});
}
});
fileI18nInfo.I18nList = frontend.getCurrentLanguageList();
}
const platformConfig = JSON5.stringify({
appConfig: {
id: app.id,
project: app.title,
domainName: app.name,
nuimsDomain: fnNuimsDomain,
entrancePort: configEntrancePort,
lowcodeDomain: configLowcodeDomain,
envNuimsDomain: config.envNuimsDomain,
tenantType: config.tenantType,
tenantLevel: config.tenantLevel,
extendedConfig: config.extendedConfig,
envConfig: {
lowcodeDomain: fnLowcodeDomain,
},
tenant: config.tenant,
documentTitle: frontend.documentTitle,
rootViewData,
basePath: frontend.prefixPath,
frontendName: frontend.name,
// 加上统一前缀
sysPrefixPath: app.sysPrefixPath,
// 应用时区
appTimeZone: app?.appTimeZone,
// 国际化
i18nInfo: fileI18nInfo,
},
dnsAddr: app.dnsAddr,
devDnsAddr: config.devDnsAddr,
tenant: config.tenant,
env: config.env,
hasUserCenter: app.hasUserCenter,
hasAuth: app.hasAuth,
authResourcePaths,
baseResourcePaths,
miniEnable: config.miniEnable,
isPreviewFe: config?.isPreviewFe,
}, null, 4);
config?.debug && (0, breakpoint_1.genBreakpoints)(app);
let metaData;
let metaDataStr;
try {
metaData = (0, nasl_unified_frontend_generator_1.genMetaData)(app, frontend, config);
// 这个 genBundleFiles 里面不能写 await utils.delay,写了切出去后就暂停了……
if (utils.isDebugMode) {
console.error('[DEBUG] metaData sanitycheck: 启用');
function sanityCheckMetaData(obj) {
if (typeof obj !== 'object' || obj == null) {
return;
}
// 如果obj instanceof BaseNode,或者 obj.__v_raw 非空,那么报错。否则检查属性。
if (obj instanceof concepts_1.BaseNode || obj.__v_raw) {
console.error('metaData 中不能包含 BaseNode 或 Proxied 的对象', obj);
throw new Error('metaData 中不能包含 BaseNode 或 Proxied 的对象');
}
Object.keys(obj).forEach(key => sanityCheckMetaData(obj[key]));
}
sanityCheckMetaData({ ...metaData, frontendVariables: undefined });
}
metaDataStr = stringifyMetaData(metaData);
metaData = null;
}
catch (error) {
throw error;
}
finally {
config?.debug && (0, breakpoint_1.clearBreakpoints)(app);
}
const assetsInfo = app.genAllAssetsInfo(STATIC_URL, frontend.type, frontendType.frameworkKind);
const customNames = JSON5.stringify(assetsInfo.custom.names);
let content1 = `(async function(){
`;
let contentScale = '';
if (frontend.globalScaleEnabled)
contentScale = `{
// mobile 用 viewport 缩放,pc 和 ipad 用 iframe 缩放
if(navigator.userAgent.match(/mobile/i)) {
// 使用viewport缩放,用js计算viewport的缩放比例
// 创建meta标签
const meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no';
function scalePage() {
// 计算缩放比例
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const baseWidth = ${frontend.canvasWidth || frontend.defaultCanvasWidth};
const scale = windowWidth / baseWidth;
// 设置viewport缩放
document.querySelector('meta[name="viewport"]').content = 'width=' + baseWidth + ' , initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no';
}
// 初始化缩放
scalePage();
} else {
// 如果是顶层页面,才使用iframe缩放,否则会死循环
if(!window.frameElement || !window.frameElement.getAttribute('isScaledFrame')){
// 顶层页面里有个iframe,用来缩放页面
document.body.innerHTML = '<iframe src="' + location.href + '"></iframe>';
const iframe = document.querySelector('iframe');
function scalePage() {
// 计算缩放比例
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const baseWidth = ${frontend.canvasWidth || frontend.defaultCanvasWidth};;
const scale = windowWidth / baseWidth;
// iframe 按比例缩放
iframe.style.transform = 'scale(' + scale + ')';
iframe.style.transformOrigin = '0 0';
// iframe 的宽高需要缩放回去,使其等于顶层宽高
iframe.style.width = baseWidth + 'px';
iframe.style.height = 100 / scale + 'vh';
// 消除底部白边,参考:stackoverflow.com/a/21025344/5710452
iframe.style.display = 'block';
let marginRight = '0px';
let marginBottom = '0px';
if(scale < 1){
marginRight = (scale - 1)/scale * 100 + '%';
// 由于 marginBottom 的百分比也是基于宽度的,因此需要乘以 windowHeight / windowWidth
marginBottom = (scale - 1)/scale * windowHeight / windowWidth * 100 + '%';
}
iframe.style.marginRight = marginRight;
iframe.style.marginBottom = marginBottom;
}
// 监听窗口变化
window.addEventListener('resize', scalePage);
// 初始化缩放
scalePage();
// 禁止滚动条,原因:缩放后会出现滚动条
document.documentElement.style.setProperty('--scrollbar-size', 0);
// 移除 iframe 边框
iframe.style.border = 'none';
// 标记全局缩放的iframe,原因:全局缩放页面可能在 iframe 中
iframe.setAttribute('isScaledFrame', 'true');
return;
} else {
// 点击跳转到外部页面时,通知顶层页面
document.body.addEventListener('click', (e) => {
if(!e.target.href) return;
if(typeof e.target.href !== 'string') return;
if(e.target.tagName !== 'A') return;
if(e.target.download) return;
try {
const url = new URL(e.target.href);
// 只有绝对路径且不是新窗口打开的链接修改顶层页面地址
if(e.target.attributes.href.value.startsWith('http') && e.target.target !== '_blank'){
parent.location.href = e.target.href;
}
} catch(err) {
console.error(err);
}
});
}
}
}`;
let themeCSS = frontend.genThemeCSS();
let contentStyleCss = themeCSS ? `{
const el = document.createElement('style');
el.id = 'theme';
el.innerHTML = \`${themeCSS}\`;
document.head.appendChild(el);
}
` : '';
themeCSS = null;
let contentDocIcon = frontend.documentIcon ? `{
const link = document.createElement('link');
link.rel = 'shortcut icon';
link.href = \`${frontend.documentIcon}\`;
document.head.appendChild(link);
}` : '';
let contentCustomNames = `
var customNames = ${customNames};
for(var i=0;i<customNames.length;i++){
var name = window.kebab2Camel(customNames[i]);
if(window[name]){
const install = window.LcapInstall || (window.CloudUI && window.CloudUI.install) || (() => { console.error('未提供全局组件install方法') })
install(window.Vue, window[name]);
}
}`;
let contentImport = `
var platformConfig = ${platformConfig};
var metaData = ${metaDataStr};
var getCompletePath = (path) => {
return '${config?.isExport ? config.sysPrefixPath : ''}${completePath}' + path;
}
function createErrorLayout(error) {
if (!error || !error.stack) {
return;
}
// 创建异常提示层
const errorRoot = document.createElement('div');
errorRoot.style.position = 'fixed';
errorRoot.style.margin = '0 auto';
errorRoot.style.width = '0';
errorRoot.style.left = '0';
errorRoot.style.right = '0';
errorRoot.style.top = '10px';
const errorItem = document.createElement('div');
errorItem.style.width = '200px';
errorItem.style.transform = 'translateX(-50%)';
errorItem.style.backgroundColor = '#303030cc';
errorItem.style.fontSize = '14px';
errorItem.style.color = 'white';
errorItem.style.borderRadius = '4px';
errorItem.style.textAlign = 'center';
errorItem.style.display = 'flex';
errorItem.style.alignItems = 'center';
errorItem.style.justifyContent = 'center';
// 初始化异常文本
const textDiv = document.createElement('div');
textDiv.textContent = '初始化异常';
textDiv.style.padding = '10px';
// 复制按钮
const copyDiv = document.createElement('div');
copyDiv.textContent = '复制异常信息';
copyDiv.style.position = 'relative';
copyDiv.style.cursor = 'pointer';
copyDiv.style.padding = '10px';
copyDiv.onclick = function () {
const message = '初始化异常提醒仅预览环境生效。出错堆栈信息如下:' + (error && error.stack);
const input = document.createElement('input');
input.style.position = 'fixed';
input.style.top = '-10000px';
input.style.zIndex = '-999';
document.body.appendChild(input);
input.value = message;
input.focus();
input.select();
try {
let result = document.execCommand('copy');
document.body.removeChild(input);
if (!result) {
alert('复制失败');
} else {
alert('复制成功');
}
} catch {
document.body.removeChild(input);
alert('当前浏览器不支持复制功能,请检查更新或更换其他浏览器操作');
}
};
// copy 按钮左侧竖线
const before = document.createElement('span');
before.style.position = 'absolute';
before.style.top = '0';
before.style.left = '0';
before.style.width = '1px';
before.style.height = '100%';
before.style.opacity = '.5';
before.style.background = 'currentColor';
copyDiv.appendChild(before);
errorItem.appendChild(textDiv);
errorItem.appendChild(copyDiv);
errorRoot.appendChild(errorItem);
document.body.appendChild(errorRoot);
// 3秒后自动隐藏
setTimeout(() => {
if (errorRoot.parentNode) {
errorRoot.parentNode.removeChild(errorRoot);
}
}, 3000);
}
// 低版本
function importXMLHttpRequestComponent (scriptUrl) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', scriptUrl, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var scriptContent = xhr.responseText;
try {
var func = new Function('importComponent', 'window', scriptContent + '\\n//# sourceURL=' + scriptUrl);
var result = func(window.importComponent, window);
resolve(result);
} catch (error) {
createErrorLayout(error);
reject(error);
}
} else {
reject(new Error('Failed to fetch script'));
}
}
};
xhr.send();
});
};
function importFetchComponent(scriptUrl) {
return fetch(scriptUrl)
.then(function(response) {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
}).then(function(scriptText) {
var func = new Function('importComponent', 'window', scriptText + '\\n//# sourceURL=' + scriptUrl);
var result = func(window.importComponent, window);
return result;
})
.catch(error => {
console.error('Error loading script:', error);
createErrorLayout(error);
});
}
window.importComponent = (scriptUrl) => {
scriptUrl = getCompletePath(scriptUrl);
// 浏览器支持fetch,直接使用fetch
if (window.fetch) {
return importFetchComponent(scriptUrl);
}
return importXMLHttpRequestComponent(scriptUrl);
}
var routes = await importComponent('${getCompletePath('router', null, config)}?t=' + Date.now());
`;
// 按页面维度发布先不考虑鉴权,页面维度新增的页面都算是不鉴权的数组中
// 只有完全页面维度新增的才会进入到这个数组中
utils.delay(100);
let contentLogReportCode = config.env === 'dev' ? (0, nasl_log_1.logReportCode)() : '';
let contentRouter = config.env === 'dev' ? `
function iterateRouters(routers, parentPath = '') {
routers.forEach(function(route) {
var fullPath = parentPath + route.path;
if (fullPath === '*') return;
if (!(platformConfig.authResourcePaths.includes(fullPath) || platformConfig.baseResourcePaths.includes(fullPath))) {
platformConfig.baseResourcePaths.push(fullPath);
}
if (route.children) {
iterateRouters(route.children, fullPath + '/');
}
});
}
iterateRouters(routes);` : '';
let contentCreateLCAPApp = `
window.createLcapApp = () => {
${compRegStr}
appVM = window.cloudAdminDesigner.init(platformConfig.appConfig, platformConfig, routes, metaData);
try {
var push = appVM.$router.history.push;
appVM.$router.history.push = function (a, b) {
push.apply(this, [a, b, console.warn]);
};
} catch (e) { console.error(e) }
return window.appVM = appVM;
};
window.createLcapApp();`;
let contentPCIframe = frontend.globalScaleEnabled ? `
// 同步逻辑仅在 iframe 内部执行
if(window.frameElement && window.frameElement.getAttribute('isScaledFrame')) {
/**
* iframe 路由同步的几种情况:
* 1. 跳转时,iframe 同步到顶层
* 2. 回退时,顶层同步到 iframe,经测试,即便在 iframe 里调用 back 也会先触发顶层回退
* 3. 前进时,iframe 同步到顶层
* 原因:顶层永远在 iframe 后面
*/
// 自增ID,用于判断路由是否回退
let incrementID = 0;
// 当前历史ID,用于判断路由是否回退
let curHID = 0;
// 当前顶层历史ID,用于判断顶层路由是否回退
let curParentHID = 0;
// 是否是回退
let isBack = false;
// 是否是前进
let isForward = false;
appVM.$router.afterEach((to, from)=> {
if(isBack) {
// 重置回退状态
isBack = false;
return;
}
// iframe 前进时,需要让顶层页面也前进
if(isForward) {
isForward = false;
window.parent.history.forward();
return;
}
// 自增ID,用于判断路由是否回退
incrementID++;
window.history.replaceState({HID: incrementID}, '');
// iframe 路由变化时,需要同步顶层页面的路由
window.parent.history.pushState({HID: incrementID}, '', to.fullPath);
// iframe 路由变化时,title 也要同步
window.parent.document.title = document.title;
// 更新当前历史ID
curHID = incrementID;
curParentHID = incrementID;
});
window.addEventListener('popstate', function (e) {
if(curHID > (e.state ? e.state.HID : undefined)){
isBack = true;
} else {
isForward = true;
}
// 更新当前历史ID
curHID = e.state? e.state.HID : 0;
});
// 顶层页面路由回退时,需要同步 iframe 的路由
window.parent.addEventListener('popstate', function (e) {
if(curParentHID > (e.state ? e.state.HID : undefined)){
appVM.$router.back();
}
// 更新当前顶层历史ID
curParentHID = (e.state ? e.state.HID : undefined) || 0;
});
// 首次加载同步 title 和 shortcut icon
window.parent.document.title = document.title;
const shortcutIconLink = document.querySelector('link[rel="shortcut icon"]');
if(shortcutIconLink) parent.document.head.appendChild(shortcutIconLink);
}
` : '';
let content10 = `
})();`;
let contentDevOnly = config.env === 'dev' ? `
var _div = document.createElement('div');
_div.classList = "div-load"
_div.style.display = 'none';
_div.innerHTML = "<div class='loading-container'></div><div>正在更新最新发布内容...</div>"
document.getElementsByTagName('body')[0].appendChild(_div);
window.showLoading = function(){
document.querySelector(".div-load").style.display ="flex"
}
window.hideLoading = function(){
document.querySelector(".div-load").style.display ="none"
}
var style = document.createElement('style');
style.innerHTML=\`.div-load{
width: 208px;
height: 40px;
background: rgba(48, 48, 48, 0.8);
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
border-radius: 4px;
position: fixed;
top: 120px;
left: 50%;
margin-left: -104px;
color: #FFFFFF;
font-size: 14px;
justify-content: center;
align-items: center;
}
.loading-container{
width: 12px;
height: 12px;
margin-right: 10px;
animation: loading-animation 0.8s infinite linear;
border: 2px solid #f3f3f3;
border-top: 2px solid rgb(109, 108, 108);
border-right: 2px solid rgb(109, 108, 108);
border-bottom: 2px solid rgb(109, 108, 108);
border-radius: 50%;
}
@keyframes loading-animation{
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}\`
document.getElementsByTagName('body')[0].appendChild(style);
window.addEventListener('message', function (e) {
if(e.data ==="release-start"){
showLoading()
}
if(e.data==="release-end"){
hideLoading()
window.location.reload();
}
})
` : '';
let annoDep = app?.dependencies?.filter(dep => dep?.annotations?.length > 0);
let contentVueDirectives = annoDep?.map(dep => dep?.annotations?.filter(anno => anno.applyTo.includes('ViewElement'))
.map(anno => {
let depName = dep.name;
let annoName = anno.name;
let annoStr = `
const ${annoName} = {
async handle(el, binding, vnode, oldVnode) {
if(window.Vue.prototype.$library && window.Vue.prototype.$library['${depName}']){
window.Vue.prototype.$library['${depName}'].${annoName}(el, binding, vnode, oldVnode)
}
},
bind(el, binding, vnode, oldVnode) {
${annoName}.handle(el, binding, vnode, oldVnode);
},
update(el, binding, vnode, oldVnode) {
${annoName}.handle(el, binding, vnode, oldVnode);
},
};
window.Vue.directive('${annoName}', ${annoName});`;
return annoStr;
}));
let hasViewElementAnno = annoDep?.filter(dep => dep?.annotations?.filter(anno => anno.applyTo.includes('ViewElement'))?.length > 0)?.length > 0;
let contentViewElemAnno = hasViewElementAnno ? `
function fetchAnnoData() {
return new Promise(async (resolve, reject) => {
if (window.annotationAllData) {
resolve(window.annotationAllData);
} else {
let sysPrefixPath = ${config?.sysPrefixPath ? "'" + config?.sysPrefixPath + "'" : "'" + "'"} ||'';
let urlEntity = sysPrefixPath + '/api/system/annotation/entityAll'
let urlLogic = sysPrefixPath + '/api/system/annotation/logicAll'
Promise.all([
fetch(urlEntity).then(response => response.json()),
fetch(urlLogic).then(response => response.json())
])
.then(([entityAll, logicAll]) => {
let entityAllData = (entityAll && entityAll.Data) || entityAll
let logicAllData = (logicAll && logicAll.Data) || logicAll
return [entityAllData, logicAllData]
})
.then(([entityAll, logicAll]) => {
window.annotationAllData = {
entityAll,
logicAll
};
resolve(window.annotationAllData);
}).catch(err => {
})
}
});
};
fetchAnnoData();
` : '';
let content = [content1, contentScale, contentStyleCss, contentDocIcon, contentCustomNames, contentImport,
contentLogReportCode, contentRouter, contentCreateLCAPApp, contentPCIframe, content10,
contentDevOnly, contentVueDirectives.join(''), contentViewElemAnno].join('');
content1 = null;
contentScale = null;
contentStyleCss = null;
contentDocIcon = null;
contentCustomNames = null;
contentImport = null;
contentLogReportCode = null;
contentRouter = null;
contentCreateLCAPApp = null;
contentPCIframe = null;
content10 = null;
contentDevOnly = null;
contentVueDirectives = null;
contentViewElemAnno = null;
utils.delay(100);
let bundleMinPath = completePath + getCompletePath(getBundleFileName(), content, config);
const otherJsList = frontend?.appletsConfig?.enable ? ['//res.wx.qq.com/open/js/jweixin-1.3.2.js'] : [];
const microAppIntegration = (0, microApp_1.integrateMicroApp)(frontend);
if (config?.debug) {
otherJsList.push(`${STATIC_URL}/packages/@lcap/breakpoint-client@1.0.0/dist/index.js?time=${Date.now()}`);
}
let jsAssetsList = assetsInfo.basic.js.concat(assetsInfo.custom.js, otherJsList).concat([bundleMinPath]);
let cssAssetsList = assetsInfo.basic.css.concat(assetsInfo.custom.css);
if (config.isPreviewFe) {
const changeToRelativePath = (str) => {
const { pathname } = new URL(`http:${str}`);
let arr = pathname.split('/').filter(v => v);
if (['packages', config?.tenant].includes(arr[0])) {
return `${arr.join('/')}`;
}
else {
return `${arr.slice(1).join('/')}`;
}
};
jsAssetsList = jsAssetsList.map(changeToRelativePath);
cssAssetsList = cssAssetsList.map(changeToRelativePath);
}
// 导出源码才处理
if (config.isExport) {
// 以`${STATIC_URL}/packages`开头的正则
const prefixPackagesReg = new RegExp(`^${STATIC_URL}/packages/`);
const prefixHostReg = new RegExp(`^${STATIC_URL}`);
jsAssetsList = (jsAssetsList || []).map(url => {
// packages下的需要下载
if (prefixPackagesReg.test(url)) {
// 去除`?`及后面的内容
// eslint-disable-next-line prefer-destructuring
const noQueryUrl = url.split('?')[0];
const path = noQueryUrl.replace(prefixHostReg, '');
const dir = path.split('/').slice(0, -1).join('/');
config_1.config.frontendPackagesResource.push({
path: dir, // 下载后存放的路径
isDir: true,
url: `${config.STATIC_URL}${dir}`,
});
const noHostUrl = url.replace(prefixHostReg, '');
return `${noHostUrl}`;
}
const prefixBasePathReg = new RegExp(`${bundleMinPath}`);
if (prefixBasePathReg.test(url)) {
// bundle.js
return `${url}`;
}
return url;
});
cssAssetsList = (cssAssetsList || []).map(url => {
// 去除`?`及后面的内容
// eslint-disable-next-line prefer-destructuring
const noQueryUrl = url.split('?')[0];
if (prefixPackagesReg.test(noQueryUrl)) {
const path = noQueryUrl.replace(prefixHostReg, '');
const dir = path.split('/').slice(0, -1).join('/');
if (!config_1.config.frontendPackagesResource.some(item => item.path === dir)) {
config_1.config.frontendPackagesResource.push({
path: dir, // 下载后存放的路径
isDir: true,
url: `${config.STATIC_URL}${dir}`,
});
}
const noHostUrl = url.replace(prefixHostReg, '');
return `${noHostUrl}`;
}
return url;
});
}
// 分类 jsAssets
// 框架代码文件
const isFrameWorkReg = /\/(vue|react)\.(min\.)?js$/;
// 包含 @lcap/pc-template @lcap/mobile-template 的 js
const isTemplateLibReg = /\/(@lcap\/(pc-template|mobile-template))/;
// 包含 @lcap/pc-ui @lcap/mobile-ui @lcap/element-ui 的 js
const isUiLibReg = /\/(@lcap\/(pc-ui|mobile-ui|element-ui))/;
// 应用代码文件
const isBundleReg = /\/bundle\.[a-zA-Z0-9]+\.(min\.)?js$/;
const frameworkLibs = jsAssetsList.filter(asset => isFrameWorkReg.test(asset));
const templateLibs = jsAssetsList.filter(asset => isTemplateLibReg.test(asset));
const uiLibs = jsAssetsList.filter(asset => isUiLibReg.test(asset));
const otherLibs = jsAssetsList.filter(asset => !isFrameWorkReg.test(asset) && !isTemplateLibReg.test(asset) && !isUiLibReg.test(asset) && !isBundleReg.test(asset));
const bundleLibs = jsAssetsList.filter(asset => isBundleReg.test(asset));
let loadAssets = `
const t = ${Date.now()};
const loadAssets = () => {
${frameworkLibs.length > 0} && LazyLoad.js(${JSON5.stringify(frameworkLibs)});
${templateLibs.length > 0} && LazyLoad.js(${JSON5.stringify(templateLibs)}.map(url => url + '?t=' + t));
${uiLibs.length > 0} && LazyLoad.js(${JSON5.stringify(uiLibs)}.map(url => url + '?t=' + t));
${otherLibs.length > 0} && LazyLoad.js(${JSON5.stringify(otherLibs)});
${bundleLibs.length > 0} && LazyLoad.js(${JSON5.stringify(bundleLibs)});
${cssAssetsList.length > 0} && LazyLoad.css(${JSON5.stringify(cssAssetsList)});
}
`;
// 导出源码才处理
if (config.isExport) {
loadAssets = `const loadJSWrap = (arr)=>{
LazyLoad.js(arr.map(url => (window.UIBasePath || '') + url));
}
const loadCSSWrap = (arr)=>{
LazyLoad.css(arr.map(url => (window.UIBasePath || '') + url));
}
const loadAssets = () => {
${frameworkLibs.length > 0} && loadJSWrap(${JSON5.stringify(frameworkLibs)});
${templateLibs.length > 0} && loadJSWrap(${JSON5.stringify(templateLibs)});
${uiLibs.length > 0} && loadJSWrap(${JSON5.stringify(uiLibs)});
${otherLibs.length > 0} && loadJSWrap(${JSON5.stringify(otherLibs)});
${bundleLibs.length > 0} && loadJSWrap(${JSON5.stringify(bundleLibs)});
${cssAssetsList.length > 0} && loadCSSWrap(${JSON5.stringify(cssAssetsList)});
}`;
}
const loadMockAssets = `
const loadJSWrap = (arr)=>{
LazyLoad.js(arr.map(url=>{
if(url.includes('mockBundle')){
return window.userAssetsDomain + url
}else {
return window.assetsDomain + url
}
}));
}
const loadCSSWrap = (arr)=>{
LazyLoad.css(arr.map(url=>window.assetsDomain + url));
}
const loadAssets = () => {
${frameworkLibs.length > 0} && loadJSWrap(${JSON5.stringify(frameworkLibs)});
${templateLibs.length > 0} && loadJSWrap(${JSON5.stringify(templateLibs)});
${uiLibs.length > 0} && loadJSWrap(${JSON5.stringify(uiLibs)});
${otherLibs.length > 0} && loadJSWrap(${JSON5.stringify(otherLibs)});
${bundleLibs.length > 0} && loadJSWrap(${JSON5.stringify(bundleLibs)});
${cssAssetsList.length > 0} && loadCSSWrap(${JSON5.stringify(cssAssetsList)});
}`;
jsAssetsList = null;
cssAssetsList = null;
const h5Debugger = () => {
let code = `try {
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.get('lcap_debug') === 'true') {
LazyLoad.js([
'https://cdn.jsdelivr.net/npm/eruda',
'https://cdn.jsdelivr.net/npm/eruda-monitor@1.0.2',
'https://cdn.jsdelivr.net/npm/eruda-timi