diff --git a/yuthon_bpd/__manifest__.py b/yuthon_bpd/__manifest__.py index aebd092..7e21068 100644 --- a/yuthon_bpd/__manifest__.py +++ b/yuthon_bpd/__manifest__.py @@ -1,10 +1,17 @@ { 'name': 'Yuthon BPD 业务流程图', 'summary': '泳道式业务流程图(可拖拽新建关联流程)', - 'version': '18.0.1.0.0', + 'version': '18.0.1.1.1', 'category': 'Tools', 'license': 'OPL-1', 'depends': ['base', 'web'], + 'assets': { + 'web.assets_backend': [ + 'yuthon_bpd/static/src/js/bpd_visual_action.js', + 'yuthon_bpd/static/src/xml/bpd_visual_action.xml', + 'yuthon_bpd/static/src/scss/bpd_visual_action.scss', + ], + }, 'data': [ 'security/ir.model.access.csv', # 注意顺序:menu.xml 里的 会在解析时立即 diff --git a/yuthon_bpd/models/bpd_diagram.py b/yuthon_bpd/models/bpd_diagram.py index bea2a55..ecbbd21 100644 --- a/yuthon_bpd/models/bpd_diagram.py +++ b/yuthon_bpd/models/bpd_diagram.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import json import logging +import os from odoo import api, fields, models @@ -9,9 +10,15 @@ try: # Odoo 各版本导出位置略有差异,双保险 except ImportError: # pragma: no cover from odoo.tools.misc import file_open +try: # Odoo 18 推荐用 tools.misc.file_path(get_module_resource 已弃用) + from odoo.tools.misc import file_path +except ImportError: # pragma: no cover + file_path = None + _logger = logging.getLogger(__name__) TEMPLATE_FILE = 'yuthon_bpd/static/src/bpd_default.json' +VIEW_FILE = 'yuthon_bpd/static/src/bpd_view.html' class BpdDiagram(models.Model): @@ -146,9 +153,50 @@ class BpdDiagram(models.Model): or self.search([], limit=1, order='id') return rec.to_json() if rec else None - # ---------------- 入口:后台跳前端 ---------------- + # ---------------- 入口:后台打开前端 ---------------- + @api.model + def view_asset_version(self): + """前端静态页的版本号(取文件 mtime)。 + + bpd_view.html 的响应头是 Cache-Control: max-age=604800(7 天), + 浏览器可能一直用旧副本。拿它拼 ?v=... 可打破缓存。 + """ + if not file_path: + return 0 + try: + return int(os.path.getmtime(file_path(VIEW_FILE, filter_ext=('.html',)))) + except (OSError, ValueError): + return 0 + + @api.model + def refresh_visual_action_params(self): + """升级模块时把最新版本号写进 client action 的 params(供菜单入口用)。 + + 数据 XML 里用 调用,故不受 noupdate 影响,每次升级都会刷新。 + """ + action = self.env.ref('yuthon_bpd.yuthon_bpd_action_visual', + raise_if_not_found=False) + if action: + action.params = {'v': self.view_asset_version()} + def action_open_visual(self): - """后台表单按钮:在新标签打开该图的可视化编辑页(带 diagram id)。""" + """后台表单按钮:在 Odoo 内部打开该图的可视化视图(不再跳走)。 + + 返回客户端动作 yuthon_bpd.visual,由前端 OWL 组件用 iframe 承载 + bpd_view.html 并撑满内容区;顶部面包屑可返回原表单。 + """ + self.ensure_one() + return { + 'type': 'ir.actions.client', + 'tag': 'yuthon_bpd.visual', + 'name': self.name, + 'params': {'diagram_id': self.id, 'v': self.view_asset_version()}, + 'target': 'current', + 'context': dict(self.env.context, active_id=self.id, active_model=self._name), + } + + def action_open_visual_url(self): + """后台表单按钮:在新标签打开独立页面(多屏对照时用)。""" self.ensure_one() return { 'type': 'ir.actions.act_url', diff --git a/yuthon_bpd/static/src/js/bpd_visual_action.js b/yuthon_bpd/static/src/js/bpd_visual_action.js new file mode 100644 index 0000000..c0bc8f9 --- /dev/null +++ b/yuthon_bpd/static/src/js/bpd_visual_action.js @@ -0,0 +1,47 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { standardActionServiceProps } from "@web/webclient/actions/action_service"; + +import { Component } from "@odoo/owl"; + +/** + * 业务流程图前端页面(静态 HTML,独立实现,保持原样不动)。 + */ +const VIEW_URL = "/yuthon_bpd/static/src/bpd_view.html"; + +/** + * 客户端动作:把业务流程图嵌进 Odoo 后端内容区(不再新开标签页)。 + * + * - 图形渲染与拖拽逻辑仍由 bpd_view.html 自己负责,这里只给它一块 + * 撑满内容区的容器,并借 Layout 拿到原生面包屑(可点返回回到表单)。 + * - 图 id 优先取 action.params.diagram_id(表单按钮传入), + * 其次取 action.context.active_id;都没有时由页面回落默认图。 + */ +export class BpdVisualAction extends Component { + static template = "yuthon_bpd.BpdVisualAction"; + static components = { Layout }; + static props = { ...standardActionServiceProps }; + + setup() { + const action = this.props.action || {}; + const params = action.params || {}; + const context = action.context || {}; + const rawId = params.diagram_id || context.active_id; + const diagramId = Number.parseInt(rawId, 10); + const query = []; + if (Number.isInteger(diagramId) && diagramId > 0) { + query.push(`id=${diagramId}`); + } + if (params.v) { + // 静态页缓存 7 天,带上服务端给的版本号避免看到旧副本 + query.push(`v=${encodeURIComponent(params.v)}`); + } + this.url = query.length ? `${VIEW_URL}?${query.join("&")}` : VIEW_URL; + // 只要面包屑,不要搜索栏等控件 + this.display = { controlPanel: {} }; + } +} + +registry.category("actions").add("yuthon_bpd.visual", BpdVisualAction); diff --git a/yuthon_bpd/static/src/scss/bpd_visual_action.scss b/yuthon_bpd/static/src/scss/bpd_visual_action.scss new file mode 100644 index 0000000..ec0bf38 --- /dev/null +++ b/yuthon_bpd/static/src/scss/bpd_visual_action.scss @@ -0,0 +1,21 @@ +// 业务流程图嵌入后端时的容器样式。 +// +// .o_content 在 Odoo 里是 position: relative(见 web/static/src/webclient/webclient_layout.scss), +// 官方全屏内容(.o_view_controller)正是用 `position: absolute; inset: 0` 吃掉整块高度的。 +// 这里沿用同一套路,而不是靠 height: 100% 逐层传递 —— 只要链上有一环高度是 auto +// (例如模板根节点没有 .o_action 时),百分比高度就会失效、iframe 会掉回默认 150px。 +.o_bpd_visual { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: #fff; + + .o_bpd_visual_frame { + display: block; + width: 100%; + height: 100%; + border: 0; + } +} diff --git a/yuthon_bpd/static/src/xml/bpd_visual_action.xml b/yuthon_bpd/static/src/xml/bpd_visual_action.xml new file mode 100644 index 0000000..388d600 --- /dev/null +++ b/yuthon_bpd/static/src/xml/bpd_visual_action.xml @@ -0,0 +1,23 @@ + + + + + +
+ +
+