最近部门领导有个需求,要在金蝶中展示ai帮我们生成的html看板。
我研究了一下,可以在开发平台新建应用、新建页面,然后有几个方案可以选择:html控件、iframe控件、自定义控件。
html控件就是直接粘贴html源码进去,优点是傻瓜操作,缺点是css、js基本上都会冲突,而且十分不方便。
iframe+src到外部链接的优点是非常方便,缺点是需要外部服务器,并且无法在金蝶中设置权限,容易被爬。
自定义控件就很合适了,相当于用api帮我们渲染出一个页面来,非常丝滑。缺点是配置稍微麻烦一点,而且不能直接上传html,要稍微转化一下。
可以用本文这个Python 脚本,将完整的index.html自动转换成金蝶自定义控件所需要的index.js。
一、文件结构
首先准备一个目录:
项目目录/ ├── index.html └── convert.py
其中index.html就是需要在金蝶云星瀚中展示的完整 HTML 页面。
运行 Python 脚本后,会自动生成:
项目目录/ ├── index.html ├── convert.py └── index.js
最终只需要将index.js用于金蝶自定义控件即可。
二、转换原理
脚本不会把 HTML 手工拆成 HTML、CSS、JavaScript 三部分,而是直接读取整个index.html:
index.html
↓
读取完整 HTML
↓
转换成 JavaScript 字符串
↓
写入 iframe.srcdoc
↓
生成 index.js
生成的自定义控件结构如下:
金蝶页面
└── 自定义控件
└── iframe
└── index.html
├── HTML
├── CSS
└── JavaScript
这样原来的 HTML 页面拥有相对独立的运行环境,可以减少与金蝶页面之间的 CSS、JavaScript 冲突。
三、完整 Python 转换程序
将下面代码保存为convert.py,与index.html放在同一目录。
import json
from pathlib import Path
# ============================================================
# 配置
# ============================================================
INPUT_HTML = "index.html"
OUTPUT_JS = "index.js"
# 金蝶自定义控件 Schema ID
SCHEMA_ID = "bank_account_dashboard"
# index.html 的设计尺寸
DESIGN_WIDTH = 1920
DESIGN_HEIGHT = 1080
# ============================================================
# 读取 index.html
# ============================================================
input_path = Path(INPUT_HTML)
if not input_path.exists():
raise FileNotFoundError(
"找不到 index.html,请将 Python 脚本和 index.html 放在同一个目录。"
)
html = input_path.read_text(
encoding="utf-8"
)
# ============================================================
# 将完整 HTML 转换成 JavaScript 字符串
#
# 使用 json.dumps,而不是 Python f-string。
# 这样 HTML、CSS、JavaScript 中的 {} 不会造成语法错误。
# ============================================================
html_json = json.dumps(
html,
ensure_ascii=False
)
# ============================================================
# 生成金蝶自定义控件
# ============================================================
js_template = r"""
(function () {
var SCHEMA_ID = "__SCHEMA_ID__";
var DESIGN_WIDTH = __DESIGN_WIDTH__;
var DESIGN_HEIGHT = __DESIGN_HEIGHT__;
/*
* 完整的 index.html
*/
var HTML_CONTENT = __HTML_CONTENT__;
/*
* ========================================================
* 自定义控件
* ========================================================
*/
function MyComponent(model) {
this.model = model;
this.iframe = null;
this.resizeObserver = null;
this.resizeHandler = null;
}
/*
* ========================================================
* 初始化
* ========================================================
*/
MyComponent.prototype.init = function (props) {
this.render(props);
};
/*
* ========================================================
* 更新
* ========================================================
*/
MyComponent.prototype.update = function (props) {
if (this.iframe) {
this.resize();
return;
}
this.render(props);
};
/*
* ========================================================
* 创建 iframe
* ========================================================
*/
MyComponent.prototype.render = function (props) {
var model = this.model;
if (!model || !model.dom) {
return;
}
/*
* 限制自定义控件自身边界
*/
model.dom.style.position = "relative";
model.dom.style.width = "100%";
model.dom.style.height = "100%";
model.dom.style.margin = "0";
model.dom.style.padding = "0";
model.dom.style.overflow = "hidden";
model.dom.style.boxSizing = "border-box";
/*
* 清空原控件
*/
model.dom.innerHTML = "";
/*
* 外层容器
*/
var root = document.createElement("div");
root.style.position = "absolute";
root.style.left = "0";
root.style.top = "0";
root.style.width = "100%";
root.style.height = "100%";
root.style.margin = "0";
root.style.padding = "0";
root.style.overflow = "hidden";
root.style.boxSizing = "border-box";
/*
* 创建 iframe
*/
var iframe = document.createElement("iframe");
iframe.style.position = "absolute";
iframe.style.left = "50%";
iframe.style.top = "50%";
/*
* iframe 使用原页面设计尺寸
*/
iframe.style.width =
DESIGN_WIDTH + "px";
iframe.style.height =
DESIGN_HEIGHT + "px";
iframe.style.margin = "0";
iframe.style.padding = "0";
iframe.style.border = "0";
iframe.style.display = "block";
iframe.style.boxSizing = "border-box";
iframe.style.transformOrigin =
"center center";
iframe.style.outline = "none";
/*
* 禁止 iframe 滚动条
*/
iframe.setAttribute(
"frameborder",
"0"
);
iframe.setAttribute(
"scrolling",
"no"
);
/*
* 插入 iframe
*/
root.appendChild(iframe);
model.dom.appendChild(root);
/*
* 保存引用
*/
this.iframe = iframe;
this.root = root;
/*
* 使用 srcdoc 加载完整 HTML
*/
try {
iframe.srcdoc = HTML_CONTENT;
} catch (e) {
console.error(
"[KDE Custom Control] srcdoc 加载失败:",
e
);
}
/*
* 监听尺寸变化
*/
this.bindResize();
/*
* 第一次调整尺寸
*/
this.resize();
/*
* 金蝶页面布局可能是异步的,
* 延迟重新计算几次。
*/
var self = this;
setTimeout(function () {
self.resize();
}, 50);
setTimeout(function () {
self.resize();
}, 200);
setTimeout(function () {
self.resize();
}, 500);
setTimeout(function () {
self.resize();
}, 1000);
};
/*
* ========================================================
* 自适应缩放
*
* 保持原页面比例,不裁剪。
* ========================================================
*/
MyComponent.prototype.resize = function () {
var model = this.model;
var iframe = this.iframe;
if (!model || !model.dom || !iframe) {
return;
}
var width =
model.dom.clientWidth;
var height =
model.dom.clientHeight;
if (
width <= 0 ||
height <= 0
) {
return;
}
/*
* 横向缩放比例
*/
var scaleX =
width / DESIGN_WIDTH;
/*
* 纵向缩放比例
*/
var scaleY =
height / DESIGN_HEIGHT;
/*
* 取较小值。
*
* 确保整个页面都能显示,
* 不裁剪、不变形。
*/
var scale =
Math.min(
scaleX,
scaleY
);
/*
* 居中显示
*/
iframe.style.left = "50%";
iframe.style.top = "50%";
iframe.style.transform =
"translate(-50%, -50%) scale(" +
scale +
")";
};
/*
* ========================================================
* 监听控件尺寸变化
* ========================================================
*/
MyComponent.prototype.bindResize = function () {
var self = this;
/*
* 解除旧监听
*/
this.unbindResize();
/*
* 浏览器窗口变化
*/
this.resizeHandler = function () {
self.resize();
};
window.addEventListener(
"resize",
this.resizeHandler
);
/*
* 监听金蝶自定义控件本身的尺寸变化
*/
if (
typeof ResizeObserver !== "undefined"
) {
this.resizeObserver =
new ResizeObserver(
function () {
self.resize();
}
);
this.resizeObserver.observe(
this.model.dom
);
}
};
/*
* ========================================================
* 解除监听
* ========================================================
*/
MyComponent.prototype.unbindResize = function () {
if (this.resizeHandler) {
window.removeEventListener(
"resize",
this.resizeHandler
);
this.resizeHandler = null;
}
if (this.resizeObserver) {
try {
this.resizeObserver.disconnect();
} catch (e) {}
this.resizeObserver = null;
}
};
/*
* ========================================================
* 销毁
* ========================================================
*/
MyComponent.prototype.destroyed = function () {
this.unbindResize();
this.iframe = null;
this.root = null;
};
/*
* ========================================================
* 注册金蝶自定义控件
* ========================================================
*/
KDApi.register(
SCHEMA_ID,
MyComponent
);
})();
"""
# ============================================================
# 替换变量
#
# 不使用 f-string,
# 避免 JavaScript 中的 {} 引发 Python 语法错误。
# ============================================================
js_code = js_template
js_code = js_code.replace(
"__SCHEMA_ID__",
SCHEMA_ID
)
js_code = js_code.replace(
"__DESIGN_WIDTH__",
str(DESIGN_WIDTH)
)
js_code = js_code.replace(
"__DESIGN_HEIGHT__",
str(DESIGN_HEIGHT)
)
js_code = js_code.replace(
"__HTML_CONTENT__",
html_json
)
# ============================================================
# 输出 index.js
# ============================================================
output_path = Path(OUTPUT_JS)
output_path.write_text(
js_code,
encoding="utf-8"
)
# ============================================================
# 输出结果
# ============================================================
print("=" * 60)
print("转换成功")
print("=" * 60)
print()
print("输入:")
print(input_path.resolve())
print()
print("输出:")
print(output_path.resolve())
print()
print(
"设计尺寸:{} × {}".format(
DESIGN_WIDTH,
DESIGN_HEIGHT
)
)
print()
print("模式:iframe + srcdoc")
print("HTML:完整内嵌")
print("外部 HTML:无")
print("保持比例:是")
print("裁剪:否")
print("自动居中:是")
print("自动缩放:是")
print()
print("=" * 60)
四、运行脚本
在当前目录执行:
python convert.py
正常情况下会看到:
============================================================ 转换成功 ============================================================ 输入: D:\xxx\index.html 输出: D:\xxx\index.js 设计尺寸:1920 × 1080 模式:iframe + srcdoc HTML:完整内嵌 外部 HTML:无 保持比例:是 裁剪:否 自动居中:是 自动缩放:是 ============================================================
然后目录中就会生成:
index.js
五、上传到金蝶
把index.js上传到自定义控件的方案中,注意我这个控件方案的SCHEMA_ID是bank_account_dashboard(银行资金看板),所以我的方案id也得是bank_account_dashboard。
上传成功后点击“返回数据”,保存控件和页面即可。



Comments | NOTHING