Initial commit
This commit is contained in:
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
dev-server.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
36
Agents/OPFS-持久化文件管理系统设计方案.md
Normal file
36
Agents/OPFS-持久化文件管理系统设计方案.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# OPFS 持久化文件管理系统设计方案
|
||||
|
||||
## Summary
|
||||
- 当前数据只存在 Vue 内存中,刷新会丢失。
|
||||
- 新增浏览器端 OPFS 文件管理层,把设备数据和基本变量数据自动保存到浏览器私有文件系统中。
|
||||
- 页面刷新后优先从 OPFS 读取;如果 OPFS 没有数据,再使用默认 JSON。
|
||||
|
||||
## Key Changes
|
||||
- 新增 OPFS 存储工具:
|
||||
- 提供加载、保存、重置、导出、导入方法。
|
||||
- 页面加载流程调整:
|
||||
- `created` 或 `mounted` 时异步读取 OPFS。
|
||||
- 如果 OPFS 有有效数据则直接加载。
|
||||
- 没有数据时使用默认 JSON,并写入 OPFS 作为初始文件。
|
||||
- 自动保存策略:
|
||||
- 设备新增、编辑、删除后自动保存。
|
||||
- 变量新增、编辑、复制、删除后自动保存。
|
||||
- 新增状态提示:
|
||||
- 顶部显示当前存储状态和最近保存时间。
|
||||
- 增加文件管理按钮:
|
||||
- `导出配置JSON`
|
||||
- `导入配置JSON`
|
||||
- `恢复默认数据`
|
||||
|
||||
## Test Plan
|
||||
- 新增设备后刷新页面,设备仍存在。
|
||||
- 编辑设备后刷新页面,修改仍存在。
|
||||
- 新增变量后刷新页面,变量仍存在。
|
||||
- 删除变量后刷新页面,被删除变量不会恢复。
|
||||
- 点击 `导出配置JSON` 能下载当前配置。
|
||||
- 导入配置 JSON 后刷新页面,导入的数据仍存在。
|
||||
|
||||
## Assumptions
|
||||
- OPFS 保存的是浏览器本地私有文件。
|
||||
- 数据只需要本机浏览器持久化,不需要后端数据库。
|
||||
- OPFS 不可用时可降级到 `localStorage`。
|
||||
30
Agents/v4-每个工位独立维护MES基本变量数据.md
Normal file
30
Agents/v4-每个工位独立维护MES基本变量数据.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# v4:每个工位独立维护 MES 基本变量数据
|
||||
|
||||
## Summary
|
||||
- 将当前“全局变量模板”改为“每个设备/工位拥有自己的 `MES_基本变量原始数据`”。
|
||||
- 左侧点击不同设备时,右侧显示并编辑该设备自己的变量清单,修改后只影响当前工位。
|
||||
- 导出 Excel 时仍只生成两个 Sheet。
|
||||
|
||||
## Key Changes
|
||||
- 数据结构调整:
|
||||
- `devices` 中每个设备增加独立 `variables` 数组。
|
||||
- 旧的全局 `tableData.variables` 作为兼容迁移来源。
|
||||
- 页面行为调整:
|
||||
- 右侧变量表只显示当前选中设备的 `variables`。
|
||||
- 新增、编辑、复制、删除变量只操作当前选中设备。
|
||||
- 未选择设备时,右侧显示空状态。
|
||||
- Excel 生成规则调整:
|
||||
- `设备类型对应表` 继续由 `devices` 生成。
|
||||
- `MES_基本变量原始数据` 由所有设备的独立变量合并生成。
|
||||
- 每行导出前强制覆盖 `工位号 / 数据来源 / IP / DbName`。
|
||||
|
||||
## Test Plan
|
||||
- 选择 ST04,新增一条变量。
|
||||
- 切换到 ST05,确认 ST05 不出现 ST04 新增的变量。
|
||||
- 修改 ST04 某条变量的 `TagName`,确认 ST05 对应变量不变化。
|
||||
- 导出文件只包含两个 Sheet,且每个设备的变量行都正确展开。
|
||||
|
||||
## Assumptions
|
||||
- 右侧变量清单以后是“当前工位独立变量”。
|
||||
- 旧数据迁移时,原全局变量复制到每个设备下。
|
||||
- 导出时以网页设备字段为准。
|
||||
35
Agents/v5-只导出当前选中设备及其变量.md
Normal file
35
Agents/v5-只导出当前选中设备及其变量.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# v5:只导出当前选中设备及其变量
|
||||
|
||||
## Summary
|
||||
- 将“生成 Excel”从导出全部设备,改为只导出左侧当前选中的设备。
|
||||
- Excel 仍只包含两个 Sheet。
|
||||
- `设备类型对应表` 只生成当前选中设备这一行。
|
||||
- `MES_基本变量原始数据` 只生成当前选中设备对应的基本变量原始数据。
|
||||
|
||||
## Key Changes
|
||||
- 导出数据范围:
|
||||
- 新增一个“按设备构造导出数据”的工具逻辑。
|
||||
- 该工具从完整 `tableData.devices` 中找到当前选中设备,只保留这一台设备及其 `variables`。
|
||||
- 页面导出行为:
|
||||
- `handleExport()` 必须检查 `selectedDevice` 是否存在。
|
||||
- 如果未选择设备,提示“请先选择要导出的设备”并阻止导出。
|
||||
- 校验范围:
|
||||
- 点击“生成 Excel”时只校验当前选中设备及其变量。
|
||||
- Excel 内容:
|
||||
- `设备类型对应表` 行数为表头 + 当前选中设备 1 行。
|
||||
- `MES_基本变量原始数据` 行数为表头 + 当前设备变量行。
|
||||
|
||||
## Test Plan
|
||||
- 选择 ST04 后点击生成 Excel:
|
||||
- `设备类型对应表` 只有 ST04 一条设备数据。
|
||||
- `MES_基本变量原始数据` 只有 ST04 对应变量。
|
||||
- 选择 ST05 后点击生成 Excel:
|
||||
- Excel 中不出现 ST04 数据。
|
||||
- 未选择设备时点击生成 Excel:
|
||||
- 页面提示先选择设备。
|
||||
- 不下载 Excel。
|
||||
|
||||
## Assumptions
|
||||
- 页面仍维护所有设备和变量,只是 Excel 导出范围改成当前选中设备。
|
||||
- “校验”按钮可以继续用于检查全量配置。
|
||||
- 文件名仍保持 `SignalTable_CZ.xlsx`。
|
||||
29
Agents/v6-设备模块支持多个工位.md
Normal file
29
Agents/v6-设备模块支持多个工位.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# v6 设计文档:设备模块支持多个工位
|
||||
|
||||
## Summary
|
||||
- 将当前“一个设备 = 一个工位”的设计,调整为“一个设备模块 = 多个工位”。
|
||||
- 左侧设备列表仍展示设备模块;设备编辑中新增“工位管理”能力。
|
||||
- 生成 Excel 时按当前选中的设备模块导出,包含该设备下所有工位及各自对应的 `MES_基本变量原始数据`。
|
||||
|
||||
## Key Changes
|
||||
- 数据层级改为:
|
||||
- `devices[]`:设备模块列表。
|
||||
- `devices[].stations[]`:该设备下的多个工位。
|
||||
- `devices[].stations[].variables[]`:该工位独立的变量数据。
|
||||
- `设备类型对应表` 由当前设备模块下的 `stations[]` 生成多行。
|
||||
- `MES_基本变量原始数据` 由当前设备模块下所有工位的变量合并生成。
|
||||
- UI Design:
|
||||
- 左侧设备列表展示设备模块。
|
||||
- 设备编辑弹窗增加“工位管理”区域。
|
||||
- 点击左侧设备模块后,右侧顶部显示该设备下的工位切换列表。
|
||||
|
||||
## Test Plan
|
||||
- 新建设备模块后,左侧出现一个设备模块。
|
||||
- 在设备模块中新增 ST04、ST05 两个工位。
|
||||
- 当前设备导出 Excel 后,`设备类型对应表` 有 ST04、ST05 两行。
|
||||
- `MES_基本变量原始数据` 同时包含 ST04 和 ST05 的变量数据。
|
||||
- 修改 ST04 的变量,不影响 ST05。
|
||||
|
||||
## Assumptions
|
||||
- 点击“生成 Excel”导出当前选中的设备模块及其全部工位。
|
||||
- 页面仍保存所有设备模块和工位数据;导出时只导出当前选中设备模块。
|
||||
31
Agents/v7-工位管理融合到设备模块新增编辑中.md
Normal file
31
Agents/v7-工位管理融合到设备模块新增编辑中.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# v7:工位管理融合到设备模块新增/编辑中
|
||||
|
||||
## Summary
|
||||
- 保留当前“设备模块 -> 多个工位 -> 共享变量”的数据模型。
|
||||
- 撤回右侧工位增删改设计:右侧不再提供工位管理按钮。
|
||||
- 工位维护全部放入 `新增设备模块 / 编辑设备模块` 弹窗内。
|
||||
- 在设备模块弹窗上方增加一个醒目的 `+` 按钮,点击一次就自动新增一个完整工位。
|
||||
|
||||
## Key Changes
|
||||
- 文档同步更新:
|
||||
- 明确工位管理只存在于设备模块弹窗内。
|
||||
- 删除右侧工位管理按钮相关描述。
|
||||
- 页面交互调整:
|
||||
- 移除右侧新增/编辑/复制/删除工位按钮。
|
||||
- 保留右侧工位预览和切换。
|
||||
- 设备模块弹窗增强:
|
||||
- 弹窗中展示设备字段。
|
||||
- 弹窗内增加工位列表编辑区。
|
||||
- 点击 `+` 自动追加一条完整工位。
|
||||
- 工位字段在设备弹窗中可直接编辑。
|
||||
|
||||
## Test Plan
|
||||
- 打开编辑设备模块弹窗,点击 `+`。
|
||||
- 工位列表新增一条完整工位。
|
||||
- 保存后左侧设备模块显示工位数量增加。
|
||||
- 右侧不再出现工位新增/编辑/删除按钮。
|
||||
|
||||
## Assumptions
|
||||
- 点击 `+` 是快速新增完整工位。
|
||||
- 工位字段在设备模块新增/编辑弹窗内直接编辑。
|
||||
- 右侧只做工位切换和共享变量维护。
|
||||
40
Agents/v8-设备模块弹窗改为整表编辑模式.md
Normal file
40
Agents/v8-设备模块弹窗改为整表编辑模式.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# v8:设备模块弹窗改为整表编辑模式
|
||||
|
||||
## Summary
|
||||
- 将 `新增/编辑设备模块` 弹窗扩大,并改成一个完整的表格式编辑窗口。
|
||||
- 删除弹窗上方独立表单,不再单独摆放 `设备名称 / 设备型号 / 设备IP / 是否启用` 输入框。
|
||||
- 每点一次左上角 `+`,就新增一条完整配置行。
|
||||
|
||||
## Key Changes
|
||||
- 弹窗 UI:
|
||||
- `el-dialog` 宽度扩大。
|
||||
- 弹窗左上角保留醒目的红色 `+`。
|
||||
- 表格列改为:
|
||||
- `设备名称`
|
||||
- `工位名称`
|
||||
- `设备型号`
|
||||
- `设备IP`
|
||||
- `DB`
|
||||
- `是否启用`
|
||||
- `操作`
|
||||
- 数据结构兼容:
|
||||
- 保留 `devices[].variables[]` 作为共享变量。
|
||||
- `devices[].stations[]` 扩展保存每行配置。
|
||||
- 导出规则:
|
||||
- `设备类型对应表` 每一行来自 `stations[]` 的一条完整配置。
|
||||
- `MES_基本变量原始数据` 仍用设备模块共享变量按每条 station 展开。
|
||||
- 默认新增行:
|
||||
- `工位名称` 自动递增。
|
||||
- `DB` 自动递增。
|
||||
- `是否启用` 默认 `1`。
|
||||
|
||||
## Test Plan
|
||||
- 打开新增/编辑设备模块弹窗,只看到一个大表格编辑区域。
|
||||
- 点击 `+` 后新增一行,行内包含完整配置字段。
|
||||
- 保存后刷新页面数据仍存在。
|
||||
- 表格中有 3 行配置时,`设备类型对应表` 导出表头 + 3 行。
|
||||
|
||||
## Assumptions
|
||||
- 弹窗中仍保留 `DB` 列。
|
||||
- 设备模块左侧列表显示第一行配置的设备名称、设备型号、设备IP,并显示总行数。
|
||||
- 每个设备模块仍共享一套 `MES_基本变量原始数据`。
|
||||
27
Agents/修复设备模块弹窗加号不新增配置行.md
Normal file
27
Agents/修复设备模块弹窗加号不新增配置行.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 修复设备模块弹窗加号不新增配置行
|
||||
|
||||
## Summary
|
||||
- 当前问题原因基本明确:`+` 按钮使用 `@click="addStationToDeviceForm"`,Vue 会把点击事件 `MouseEvent` 作为第一个参数传入。
|
||||
- 现有方法 `addStationToDeviceForm(targetForm)` 会把这个事件对象误当成表单对象,导致新增行没有正确写入 `deviceForm.stations`。
|
||||
- 修复目标:新增和编辑设备模块时,点击 `+` 都能立即在弹窗表格中新增一条完整配置行。
|
||||
|
||||
## Key Changes
|
||||
- 修改弹窗 `+` 按钮点击事件:
|
||||
- 从 `@click="addStationToDeviceForm"` 改为明确调用当前表单。
|
||||
- 加固 `addStationToDeviceForm` 方法:
|
||||
- 默认只操作 `this.deviceForm`。
|
||||
- 如果传入参数不是有效表单对象,自动回退到 `this.deviceForm`。
|
||||
- 使用响应式方式确保 `stations` 数组存在。
|
||||
- 保持现有保存逻辑:
|
||||
- 保存后第一行同步为设备模块左侧列表展示信息。
|
||||
- 所有配置行写入 `stations[]`,用于导出 `设备类型对应表` 多行。
|
||||
|
||||
## Test Plan
|
||||
- 打开新增弹窗,默认已有一行。
|
||||
- 点击 `+`,表格立即增加第二行。
|
||||
- 连续点击 `+`,每次都增加一行。
|
||||
- 打开编辑弹窗后点击 `+`,在原有配置后追加一行。
|
||||
|
||||
## Assumptions
|
||||
- 这次只修复 `+` 不新增配置行的问题,不调整整体 UI 布局。
|
||||
- 仍保持 v8 的“弹窗整表编辑模式”。
|
||||
36
Agents/前端第一版修正方案-v3.md
Normal file
36
Agents/前端第一版修正方案-v3.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 设计文档:前端第一版修正方案 v3
|
||||
|
||||
## Summary
|
||||
- 先更新设计文档,再进入代码修改。
|
||||
- 解决 3 个问题:
|
||||
- 点击“生成 Excel”没有下载。
|
||||
- `基本变量清单` 也要支持新增、编辑、删除。
|
||||
- 右侧要完整展示/编辑 `MES_基本变量原始数据` 的 38 个字段,左侧设备管理支持展开/收起,上方当前设备信息区域移除。
|
||||
|
||||
## Key Changes
|
||||
- Excel 下载修正:
|
||||
- 导出函数改为明确下载链路。
|
||||
- 导出失败时页面必须弹出错误提示,不能静默无反应。
|
||||
- 基本变量清单改为可维护表:
|
||||
- 右侧增加 `新增变量`、`编辑变量`、`删除变量`、`复制变量`。
|
||||
- 新增变量时自动生成默认 `TagID`。
|
||||
- 38 字段完整保留:
|
||||
- 右侧表格数据源就是 `MES_基本变量原始数据` 的完整 38 列。
|
||||
- 表格横向滚动展示全部字段。
|
||||
- 页面布局调整:
|
||||
- 左侧设备管理增加 `展开/收起` 按钮。
|
||||
- 收起后左侧只保留窄条按钮。
|
||||
- 删除上方 `当前设备信息` 卡片。
|
||||
|
||||
## Test Plan
|
||||
- 点击 `生成 Excel` 后浏览器必须下载文件。
|
||||
- 新增变量后,右侧表格新增一行。
|
||||
- 编辑变量任一字段后,表格同步更新。
|
||||
- 删除变量后,表格移除该行。
|
||||
- 导出的 `MES_基本变量原始数据` 第一行必须有 38 列。
|
||||
- 点击左侧收起按钮后,右侧变量表宽度变大。
|
||||
|
||||
## Assumptions
|
||||
- `基本变量清单` 的新增/编辑操作维护的是全局变量模板。
|
||||
- 点击不同左侧工位时,右侧表格仍显示同一套变量模板。
|
||||
- 第一版优先保证下载可用和 38 列完整。
|
||||
53
Agents/更新-signal-table-excel-generator-design-v2-两表生成器文档.md
Normal file
53
Agents/更新-signal-table-excel-generator-design-v2-两表生成器文档.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 更新 `signal-table-excel-generator-design.md` 为 v2 两表生成器文档
|
||||
|
||||
## Summary
|
||||
- 只更新 Markdown 设计/测试文档,不编写代码。
|
||||
- 文档目标改为:前端网页用于维护设备和变量配置,点击“生成”后导出标准 Excel。
|
||||
- 最终 Excel 只包含两个 Sheet:
|
||||
- `MES_基本变量原始数据`
|
||||
- `设备类型对应表`
|
||||
- 页面参考微信双栏结构:左侧是设备列表,点击某一行设备/工位,右侧显示该工位对应的基本变量详情。
|
||||
|
||||
## Key Changes
|
||||
- 删除文档中所有“五个 Sheet”相关内容,包括:
|
||||
- `工厂接口配置`
|
||||
- `变量类型代码说明`
|
||||
- `配置_信号`
|
||||
- 重写 Excel 结构说明:
|
||||
- `设备类型对应表` 作为左侧设备/工位配置的数据来源。
|
||||
- `MES_基本变量原始数据` 作为右侧基本变量详情的数据来源。
|
||||
- 左侧设备创建表字段先定义为:
|
||||
- `设备名称`
|
||||
- `工位名称`
|
||||
- `设备型号`
|
||||
- `设备IP`
|
||||
- `是否启用`
|
||||
- 文档中注明:为了兼容现有 SCADA,导出 Excel 时 `设备类型对应表` 仍需要保留程序必读列,如 `DB`、`数据来源`;但第一版页面主界面先不展示,可作为默认值或后续高级字段补充。
|
||||
- 页面行为写清楚:
|
||||
- 左侧一行代表一个设备/工位。
|
||||
- 点击左侧某一行,右侧切换显示该工位的基本变量。
|
||||
- 交互方式参考微信:左侧列表选中项高亮,右侧展示详情。
|
||||
- 未选择设备时,右侧显示空状态。
|
||||
- 文档明确后续规则:
|
||||
- 没有明确要求时,只更新 `.md` 文档。
|
||||
- 不主动进行 Vue 代码实现。
|
||||
|
||||
## Test Plan
|
||||
- 创建设备后,左侧列表出现一行新设备。
|
||||
- 编辑设备后,左侧对应字段同步更新。
|
||||
- 删除设备后,左侧列表移除该设备。
|
||||
- 禁用设备后,导出 Excel 中 `是否启用` 为 `0`。
|
||||
- 点击左侧不同工位,右侧显示对应工位的基本变量。
|
||||
- 未选择左侧设备时,右侧显示空状态。
|
||||
- 导出的 Excel 只包含两个 Sheet。
|
||||
- `MES_基本变量原始数据` Sheet 名称必须完全一致。
|
||||
- `设备类型对应表` Sheet 名称必须完全一致。
|
||||
- 导出前校验设备名称、工位名称、设备型号、设备IP不能为空。
|
||||
- 导出的文件能被当前 SCADA 项目读取。
|
||||
|
||||
## Assumptions
|
||||
- 当前只更新设计文档,不改 Vue 源码。
|
||||
- 第一版网页主界面只展示用户指定的 5 个设备字段。
|
||||
- Excel 导出只生成两个 Sheet。
|
||||
- 右侧基本变量详情由左侧选中的工位触发显示。
|
||||
- 页面布局参考微信双栏交互:左侧列表,右侧详情。
|
||||
35
Agents/更新页面成品设计草图章节.md
Normal file
35
Agents/更新页面成品设计草图章节.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 更新页面成品设计草图章节
|
||||
|
||||
## Summary
|
||||
- 只更新 `docs/signal-table-excel-generator-design.md`。
|
||||
- 将当前文档末尾的“最终页面草图”替换为新版“页面成品设计草图”。
|
||||
- 不修改 Vue 代码。
|
||||
|
||||
## Key Changes
|
||||
- 将原来的最终草图章节调整为:
|
||||
- `## 10. 页面成品设计草图`
|
||||
- `### 10.1 完整页面布局`
|
||||
- `### 10.2 新增/编辑设备弹窗`
|
||||
- `### 10.3 空状态页面`
|
||||
- 保留三组 ASCII 草图:
|
||||
- 完整页面布局
|
||||
- 新增/编辑设备弹窗
|
||||
- 空状态页面
|
||||
- 在草图前补充一句说明:
|
||||
- 页面参考微信式左右分栏,左侧为设备管理与设备列表,右侧为当前设备信息和基本变量清单。
|
||||
- 在弹窗设计中明确 `DB(高级)` 只作为兼容 SCADA 导出字段,主列表不展示。
|
||||
- 页面草图中的字段与前文保持一致:
|
||||
- 左侧设备字段:`设备名称 / 工位名称 / 设备型号 / 设备IP / 是否启用`
|
||||
- 高级字段:`DB`
|
||||
- 右侧变量字段:`TagID / TagName / 类型 / 地址`
|
||||
|
||||
## Test Impact
|
||||
- 测试文档不需要大改。
|
||||
- 只需确认测试用例中的页面描述与新版草图一致:
|
||||
- 左侧按钮包含 `新增 / 编辑 / 删除 / 查询`
|
||||
- 右侧详情包含 `当前设备信息 / 基本变量清单`
|
||||
- 空状态展示设备数量和启用设备数量
|
||||
|
||||
## Assumptions
|
||||
- 这次只替换和完善文档草图章节。
|
||||
- 不新增代码、不调整已有 Vue 文件。
|
||||
45
Agents/第一版前端网页实现与项目清理计划.md
Normal file
45
Agents/第一版前端网页实现与项目清理计划.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 第一版前端网页实现与项目清理计划
|
||||
|
||||
## Summary
|
||||
- 按当前设计文档实现第一版 Vue2 前端网页:左侧设备列表,右侧工位基本变量详情,点击生成标准 `SignalTable_CZ.xlsx`。
|
||||
- Excel 只导出两个 Sheet:`MES_基本变量原始数据`、`设备类型对应表`。
|
||||
- 清理旧大屏项目遗留文件,只保留本工具运行、构建、文档所需内容。
|
||||
|
||||
## Key Changes
|
||||
- 页面实现:
|
||||
- 新增/重写 `src/views/SignalTableGenerator.vue`,实现微信式左右布局。
|
||||
- 左侧支持设备新增、编辑、删除、查询、选中高亮、启用/禁用。
|
||||
- 新增/编辑弹窗包含 `设备名称 / 工位名称 / 设备型号 / 设备IP / 是否启用 / DB(高级)`。
|
||||
- 右侧显示当前设备信息和变量清单;无选中设备时显示空状态与设备统计。
|
||||
- 顶部提供 `校验`、`生成 Excel` 按钮。
|
||||
- 数据与导出:
|
||||
- 重写 `src/data/default-signal-table.json`,只保留 `devices` 和 `variables`。
|
||||
- 重写 `src/utils/signalTableBuilder.js`,只生成两个 Sheet。
|
||||
- 重写 `src/utils/excelWriter.js`,负责 `.xlsx` 下载、基础列宽、表头样式。
|
||||
- `设备类型对应表` 导出列固定为 `设备IP / 设备类型 / DB / 是否启用 / 工位号 / 数据来源`。
|
||||
- `MES_基本变量原始数据` 保持 38 列模板形态。
|
||||
- 入口简化:
|
||||
- 简化 `src/main.js`、`src/App.vue`、`src/router/index.js`。
|
||||
- 依赖简化:
|
||||
- `package.json` 只保留本工具需要的运行依赖和 Vue CLI 构建依赖。
|
||||
- 删除旧大屏相关依赖。
|
||||
|
||||
## Test Plan
|
||||
- 运行 `npm install` 同步精简后的依赖和 `package-lock.json`。
|
||||
- 运行 `npm run build`,确认 Vue 项目可构建。
|
||||
- 页面手测:
|
||||
- 新增设备后左侧出现新行。
|
||||
- 编辑设备后左侧和右侧同步变化。
|
||||
- 删除选中设备后右侧进入空状态。
|
||||
- 查询设备能按设备名称或工位名称过滤。
|
||||
- Excel 校验:
|
||||
- 点击生成后文件名为 `SignalTable_CZ.xlsx`。
|
||||
- 文件只包含两个 Sheet。
|
||||
- `MES_基本变量原始数据` 保持 38 列。
|
||||
- 禁用设备导出为 `是否启用 = 0`。
|
||||
|
||||
## Assumptions
|
||||
- 第一版不接后端,数据只在前端内存中维护。
|
||||
- 第一版不做 Excel 导入,只做网页编辑和 Excel 导出。
|
||||
- `DB` 虽然是高级字段,但导出时必填。
|
||||
- 清理旧文件以当前 Excel 生成器为唯一目标。
|
||||
8
CZGKJ.code-workspace
Normal file
8
CZGKJ.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../CZGKJ/runtime/CZGKJ"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
29
README.md
Normal file
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# gaojingtv
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
545
docs/signal-table-excel-generator-design.md
Normal file
545
docs/signal-table-excel-generator-design.md
Normal file
@@ -0,0 +1,545 @@
|
||||
# SignalTable_CZ Excel 生成器设计文档 v6
|
||||
|
||||
## 1. 项目目标
|
||||
|
||||
本项目基于 `C:\work\Project\Excel` 的 Vue 前端项目,建设一个用于维护并生成标准 `SignalTable_CZ.xlsx` 的网页工具。
|
||||
|
||||
最终目标是:当项目需要生成 SCADA 可读取的信号表 Excel 时,用户打开网页,维护设备模块、工位和变量配置,然后点击 `生成 Excel`,得到标准 Excel 文件。
|
||||
|
||||
当前 v6 设计重点是把原来的:
|
||||
|
||||
```text
|
||||
一个设备 = 一个工位
|
||||
```
|
||||
|
||||
调整为:
|
||||
|
||||
```text
|
||||
一个设备模块 = 多个工位
|
||||
同一个设备模块内多个工位共用同一套 MES_基本变量原始数据
|
||||
```
|
||||
|
||||
本次只更新 `.md` 设计文档,不进行 Vue 代码修改。
|
||||
|
||||
## 2. Excel 输出范围
|
||||
|
||||
最终生成的 Excel 只包含两个 Sheet:
|
||||
|
||||
1. `MES_基本变量原始数据`
|
||||
2. `设备类型对应表`
|
||||
|
||||
不生成以下 Sheet:
|
||||
|
||||
- `工厂接口配置`
|
||||
- `变量类型代码说明`
|
||||
- `配置_信号`
|
||||
|
||||
Sheet 名称必须完全一致,不能改名、不能多空格、不能使用英文替代。
|
||||
|
||||
## 3. 设计变更说明
|
||||
|
||||
### 3.1 旧设计废弃点
|
||||
|
||||
v6 开始废弃以下旧设计:
|
||||
|
||||
- 不再认为左侧设备列表中的一行就是一个工位。
|
||||
- 不再让 `devices[]` 直接对应 `设备类型对应表` 的每一行。
|
||||
- 不再让一个设备模块只能生成一行 `设备类型对应表`。
|
||||
- 不再让一个设备模块只能维护一个工位。
|
||||
|
||||
### 3.2 新设计核心
|
||||
|
||||
v6 中左侧设备列表展示的是设备模块。
|
||||
|
||||
一个设备模块内部可以维护多个工位,工位只维护自身工位参数:
|
||||
|
||||
- 工位名称
|
||||
- 数据来源
|
||||
- DB
|
||||
- 是否启用
|
||||
|
||||
同一个设备模块只维护一份共享 `MES_基本变量原始数据`。设备模块下所有工位导出时都使用这同一套变量,再按各自工位参数展开。
|
||||
|
||||
导出 Excel 时,当前选中的设备模块会导出其内部所有工位。
|
||||
|
||||
## 4. 设备模块与工位关系
|
||||
|
||||
### 4.1 设备模块
|
||||
|
||||
设备模块用于描述一组共用设备信息的工位集合。
|
||||
|
||||
设备模块字段:
|
||||
|
||||
| 字段 | 说明 | 是否必填 |
|
||||
| --- | --- | --- |
|
||||
| 设备名称 | 设备模块显示名称,例如“放油设备” | 是 |
|
||||
| 设备型号 | PLC 或设备型号,例如 `S1500` | 是 |
|
||||
| 设备IP | PLC 或设备通信 IP | 是 |
|
||||
| 是否启用 | 控制该设备模块是否启用 | 是 |
|
||||
|
||||
设备模块示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "dev-001",
|
||||
"deviceName": "放油设备",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"enabled": 1,
|
||||
"stations": [],
|
||||
"variables": []
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 工位
|
||||
|
||||
工位属于设备模块,一个设备模块可以包含多个工位。
|
||||
|
||||
工位字段:
|
||||
|
||||
| 字段 | 说明 | 是否必填 |
|
||||
| --- | --- | --- |
|
||||
| 工位名称 | SCADA 工位号,例如 `ST04` | 是 |
|
||||
| 数据来源 | 默认等于设备名称,写入 `设备类型对应表.数据来源` | 是 |
|
||||
| DB | 写入 `设备类型对应表.DB` 和变量 `DbName` | 是 |
|
||||
| 是否启用 | 写入 `设备类型对应表.是否启用` | 是 |
|
||||
| 基本变量原始数据 | 不保存在工位中,由设备模块统一维护 | 否 |
|
||||
|
||||
工位示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "station-001",
|
||||
"deviceName": "放油前",
|
||||
"stationName": "ST04",
|
||||
"dataSource": "放油前",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"db": "DB10000",
|
||||
"enabled": 1,
|
||||
"variables": []
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 多工位数据结构
|
||||
|
||||
推荐数据结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"id": "dev-001",
|
||||
"deviceName": "放油设备",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"enabled": 1,
|
||||
"stations": [
|
||||
{
|
||||
"id": "station-001",
|
||||
"deviceName": "放油前",
|
||||
"stationName": "ST04",
|
||||
"dataSource": "放油前",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"db": "DB10000",
|
||||
"enabled": 1
|
||||
},
|
||||
{
|
||||
"id": "station-002",
|
||||
"deviceName": "放油后",
|
||||
"stationName": "ST05",
|
||||
"dataSource": "放油后",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"db": "DB10001",
|
||||
"enabled": 1
|
||||
}
|
||||
],
|
||||
"variables": []
|
||||
}
|
||||
],
|
||||
"variables": []
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `devices[]` 保存设备模块。
|
||||
- `devices[].stations[]` 保存该设备模块下的工位。
|
||||
- `devices[].variables[]` 保存该设备模块共享的 38 字段变量。
|
||||
- 顶层 `variables[]` 仅作为全局默认变量模板来源。
|
||||
- 新增设备模块时,从全局默认变量模板复制一份到该设备模块。
|
||||
- 新增工位时不复制变量,只新增工位参数。
|
||||
- 同一设备模块下所有工位共用 `devices[].variables[]`。
|
||||
|
||||
## 6. 页面整体设计
|
||||
|
||||
页面继续采用左右分栏结构。
|
||||
|
||||
左侧:
|
||||
|
||||
- 设备模块列表
|
||||
- 设备模块新增
|
||||
- 设备模块编辑
|
||||
- 设备模块删除
|
||||
- 查询设备模块
|
||||
- 展开/收起设备列表
|
||||
|
||||
右侧:
|
||||
|
||||
- 当前设备模块的工位切换区
|
||||
- 当前设备模块共享的 `MES_基本变量原始数据`
|
||||
|
||||
页面行为:
|
||||
|
||||
- 点击左侧设备模块,右侧显示该设备模块下的工位。
|
||||
- 点击右侧某个工位,变量表用该工位参数预览同一套共享变量。
|
||||
- 右侧只做工位切换和变量预览,不提供工位新增、编辑、删除。
|
||||
- 修改变量表中的任意变量,会影响当前设备模块下所有工位的导出结果。
|
||||
- ST04、ST05 在页面上看到的是同一套变量,只是 `工位号 / 数据来源 / IP / DbName / TagID` 按当前选中工位预览。
|
||||
- 未选择设备模块时,右侧显示空状态。
|
||||
- 选择设备模块但没有工位时,右侧提示先新增工位。
|
||||
|
||||
## 7. 设备模块整表编辑
|
||||
|
||||
设备模块新增/编辑弹窗采用整表编辑模式,不再单独摆放 `设备名称 / 设备型号 / 设备IP / 是否启用` 表单。
|
||||
|
||||
弹窗主体是一张配置表,每一行都是一条完整配置,并会在导出 Excel 时生成 `设备类型对应表` 的一行。
|
||||
|
||||
表格字段:
|
||||
|
||||
```text
|
||||
设备名称
|
||||
工位名称
|
||||
设备型号
|
||||
设备IP
|
||||
DB
|
||||
是否启用
|
||||
操作
|
||||
```
|
||||
|
||||
### 7.1 新增配置行
|
||||
|
||||
点击弹窗左上角 `+` 后,立即新增一条完整配置行。
|
||||
|
||||
默认生成规则:
|
||||
|
||||
- `设备名称` 默认取当前第一行设备名称,若没有则为空。
|
||||
- `工位名称` 自动递增,例如 `ST06`。
|
||||
- `设备型号` 默认取当前第一行设备型号,若没有则为 `S1500`。
|
||||
- `设备IP` 默认取当前第一行设备IP。
|
||||
- `DB` 自动递增,例如 `DB10002`。
|
||||
- `是否启用` 默认为 `1`。
|
||||
|
||||
校验规则:
|
||||
|
||||
- `设备名称` 不能为空。
|
||||
- `工位名称` 不能为空。
|
||||
- `设备型号` 不能为空。
|
||||
- `设备IP` 不能为空。
|
||||
- `DB` 不能为空。
|
||||
|
||||
### 7.2 编辑配置行
|
||||
|
||||
所有字段都在表格单元格中直接编辑。
|
||||
|
||||
保存后:
|
||||
|
||||
- 设备模块左侧列表使用第一行配置作为显示信息。
|
||||
- 导出 Excel 时以每行最新配置为准。
|
||||
- 导出 Excel 时以最新网页设置为准。
|
||||
|
||||
### 7.3 删除配置行
|
||||
|
||||
删除配置行时:
|
||||
|
||||
- 删除该行配置。
|
||||
- 不删除设备模块共享变量。
|
||||
- 导出 Excel 不再包含该行。
|
||||
|
||||
## 8. 右侧基本变量详情设计
|
||||
|
||||
右侧变量表展示当前设备模块共享的 `MES_基本变量原始数据`。
|
||||
|
||||
如果当前选中了某个工位,变量表中的工位相关字段按该工位做预览;但保存变量时仍保存到设备模块共享变量中。
|
||||
|
||||
变量表必须完整保留 38 个字段:
|
||||
|
||||
```text
|
||||
TagID
|
||||
TagName
|
||||
工位号
|
||||
数据来源
|
||||
IP
|
||||
TagTypeID
|
||||
TagLong
|
||||
DbName
|
||||
变量类型
|
||||
变量地址
|
||||
变量其它
|
||||
演示偏移
|
||||
列位置
|
||||
测量位置
|
||||
测量项目
|
||||
测量单位
|
||||
变量类型代码
|
||||
变量类型名称
|
||||
机体类型
|
||||
线体类型
|
||||
TagAdr
|
||||
TagAdrDemo
|
||||
是否启用
|
||||
监控组
|
||||
监控时间间隔
|
||||
OpcServer_OPNAME
|
||||
变量特点
|
||||
是否监控
|
||||
变量排序
|
||||
工序能力
|
||||
监控属性代码
|
||||
测量位置短
|
||||
上限值
|
||||
下限值
|
||||
理论值
|
||||
IsGoodBad
|
||||
IsSaveToSql
|
||||
IsWebSocket
|
||||
```
|
||||
|
||||
变量操作:
|
||||
|
||||
- 新增变量
|
||||
- 编辑变量
|
||||
- 删除变量
|
||||
- 复制变量
|
||||
|
||||
导出前强制覆盖字段:
|
||||
|
||||
```text
|
||||
工位号 = 当前工位.工位名称
|
||||
数据来源 = 当前工位.数据来源
|
||||
IP = 当前设备模块.设备IP
|
||||
DbName = 当前工位.DB
|
||||
```
|
||||
|
||||
`TagID` 导出规则:
|
||||
|
||||
```text
|
||||
TAG_0000001 -> ST04_TAG_0000001
|
||||
```
|
||||
|
||||
如果 `TagID` 已经以当前工位名开头,则不重复添加。
|
||||
|
||||
## 9. Excel 生成规则 v6
|
||||
|
||||
点击 `生成 Excel` 时,只导出当前选中的设备模块。
|
||||
|
||||
### 9.1 设备类型对应表
|
||||
|
||||
导出列固定为:
|
||||
|
||||
```text
|
||||
设备IP
|
||||
设备类型
|
||||
DB
|
||||
是否启用
|
||||
工位号
|
||||
数据来源
|
||||
```
|
||||
|
||||
导出来源:
|
||||
|
||||
| Excel 列 | 来源 |
|
||||
| --- | --- |
|
||||
| 设备IP | 当前配置行.设备IP |
|
||||
| 设备类型 | 当前配置行.设备型号 |
|
||||
| DB | 当前配置行.DB |
|
||||
| 是否启用 | 当前配置行.是否启用 |
|
||||
| 工位号 | 当前配置行.工位名称 |
|
||||
| 数据来源 | 当前配置行.设备名称 |
|
||||
|
||||
如果当前设备模块下有 ST04、ST05 两条配置,则 `设备类型对应表` 生成两行数据。
|
||||
|
||||
### 9.2 MES_基本变量原始数据
|
||||
|
||||
`MES_基本变量原始数据` 由当前设备模块共享变量按所有工位展开生成。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
当前设备模块:放油设备
|
||||
工位:ST04、ST05
|
||||
设备模块共享变量:23 条
|
||||
导出结果:表头 1 行 + ST04 变量 23 行 + ST05 变量 23 行
|
||||
```
|
||||
|
||||
每条变量导出时:
|
||||
|
||||
- 保持 38 列顺序不变。
|
||||
- 对每个工位复制一遍设备模块共享变量。
|
||||
- 使用当前配置行字段覆盖 `工位号 / 数据来源 / DbName / IP`。
|
||||
- 按工位生成 `TagID`。
|
||||
|
||||
### 9.3 导出范围
|
||||
|
||||
导出规则:
|
||||
|
||||
- 只导出当前选中的设备模块。
|
||||
- 导出当前设备模块下全部工位。
|
||||
- 不导出未选中的其他设备模块。
|
||||
- 禁用工位也导出,但 `是否启用 = 0`。
|
||||
- 禁用设备模块是否允许导出,第一版允许导出,导出内容仍以工位启用状态为准。
|
||||
|
||||
## 10. 校验规则
|
||||
|
||||
### 10.1 设备模块校验
|
||||
|
||||
生成 Excel 前校验当前设备模块:
|
||||
|
||||
- 必须选择一个设备模块。
|
||||
- 设备名称不能为空。
|
||||
- 设备型号不能为空。
|
||||
- 设备IP不能为空。
|
||||
- 当前设备模块下至少存在一个工位。
|
||||
|
||||
### 10.2 工位校验
|
||||
|
||||
当前设备模块下所有工位需要校验:
|
||||
|
||||
- 工位名称不能为空。
|
||||
- 数据来源不能为空。
|
||||
- DB 不能为空。
|
||||
- 同一设备模块下工位名称不能重复。
|
||||
- 同一设备模块下 DB 建议不能重复。
|
||||
|
||||
### 10.3 变量校验
|
||||
|
||||
当前设备模块共享变量需要校验:
|
||||
|
||||
- 至少有一条变量。
|
||||
- 至少有一条启用变量。
|
||||
- `TagID` 不能为空。
|
||||
- `TagName` 不能为空。
|
||||
- 变量表导出必须保持 38 列。
|
||||
- Sheet 名称必须为 `MES_基本变量原始数据`。
|
||||
|
||||
### 10.4 Excel 校验
|
||||
|
||||
导出的 Excel 必须满足:
|
||||
|
||||
- 只包含两个 Sheet。
|
||||
- Sheet 名称完全一致。
|
||||
- `设备类型对应表` 行数等于当前设备模块下工位数 + 表头。
|
||||
- `MES_基本变量原始数据` 行数等于当前设备模块工位数 × 共享变量数 + 表头。
|
||||
|
||||
## 11. OPFS 持久化规则
|
||||
|
||||
OPFS 或 localStorage 中保存完整配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"updatedAt": "2026-06-01T10:00:00.000Z",
|
||||
"devices": []
|
||||
}
|
||||
```
|
||||
|
||||
兼容迁移规则:
|
||||
|
||||
- 旧数据中如果是 `一个设备 = 一个工位`,迁移时把旧设备转换为一个设备模块,并在其下创建一个工位。
|
||||
- 旧设备的 `stationName / db / dataSource` 移入新工位。
|
||||
- 旧设备的 `variables` 移入新设备模块,作为该设备模块共享变量。
|
||||
- 旧设备的 `deviceName / deviceModel / deviceIp / enabled` 保留在设备模块。
|
||||
- 顶层 `variables[]` 继续作为新增设备模块的默认变量模板来源。
|
||||
|
||||
## 12. 多工位测试用例
|
||||
|
||||
| 编号 | 测试项 | 操作 | 预期结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| T01 | 新建设备模块 | 点击新增设备,填写设备名称、设备型号、设备IP、是否启用 | 左侧出现一个设备模块 |
|
||||
| T02 | 新增工位 | 在设备模块中新增 ST04、ST05 两个工位 | 设备模块下出现两个工位 |
|
||||
| T03 | 共享变量维护 | 修改当前设备模块共享变量的 `TagName` | ST04、ST05 导出时都使用修改后的变量 |
|
||||
| T04 | 删除工位 | 删除 ST04 | 当前设备模块下不再显示 ST04 |
|
||||
| T05 | 禁用工位 | 将 ST05 设置为禁用 | 导出中 ST05 的 `是否启用 = 0` |
|
||||
| T06 | 导出设备表 | 选择当前设备模块并导出 Excel | `设备类型对应表` 包含 ST04、ST05 两行 |
|
||||
| T07 | 导出变量表 | 当前设备模块有 ST04、ST05,共享变量 23 条 | `MES_基本变量原始数据` 包含 ST04 展开 23 行和 ST05 展开 23 行 |
|
||||
| T08 | 导出范围 | 选择另一个设备模块导出 | Excel 不包含上一个设备模块的工位 |
|
||||
| T09 | 工位重复校验 | 同一设备模块下创建两个 ST04 | 阻止导出并提示工位名称重复 |
|
||||
| T10 | 空工位校验 | 工位名称或 DB 为空 | 阻止导出并提示对应字段不能为空 |
|
||||
|
||||
## 13. 页面草图 v6
|
||||
|
||||
### 13.1 主页面
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SignalTable_CZ Excel 生成器 [校验] [生成 Excel] [导入配置] [导出配置] │
|
||||
├──────────────────────────────┬─────────────────────────────────────────────────────┤
|
||||
│ 左侧:设备模块 │ 右侧:当前设备模块的工位与变量 │
|
||||
│ [新增设备] [编辑设备] [删除] │ │
|
||||
│ [查询设备模块 ] │ 当前设备:放油设备 / S1500 / 192.168.1.60 │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────┐ │ 工位列表: │
|
||||
│ │ ▶ 放油设备 S1500 启用 │ │ ┌────────┬────────┬─────────┬──────┐ │
|
||||
│ │ 注油设备 S1500 启用 │ │ │ ST04 │ 放油前 │ DB10000 │ 启用 │ │
|
||||
│ │ 清洗设备 S1500 禁用 │ │ │ ST05 │ 放油后 │ DB10001 │ 启用 │ │
|
||||
│ └──────────────────────────┘ │ └────────┴────────┴─────────┴──────┘ │
|
||||
│ │ [<] 当前工位 ST04 / 放油前 / DB10000 [>] │
|
||||
│ 共 3 个设备模块 │ │
|
||||
│ │ 共享变量清单,当前预览工位:ST04 / 放油前 / DB10000 │
|
||||
│ │ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ │ TagID TagName 类型 地址 │ │
|
||||
│ │ │ ST04_TAG_000001 工件到位 Bool 0.0 │ │
|
||||
│ │ │ ST04_TAG_000002 读取完成 Bool 0.1 │ │
|
||||
│ │ │ ... │ │
|
||||
│ │ └───────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────┴─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 13.2 新增/编辑设备模块弹窗
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────┐
|
||||
│ 新增设备模块 │
|
||||
├─────────────────────────────────────┤
|
||||
│ + │
|
||||
│ 设备名称: [放油设备 ] │
|
||||
│ 设备型号: [S1500 ] │
|
||||
│ 设备IP: [192.168.1.60 ] │
|
||||
│ 是否启用: ○ 启用 ○ 禁用 │
|
||||
├─────────────────────────────────────┤
|
||||
│ 工位管理 │
|
||||
│ 点击 + 增加完整工位 │
|
||||
│ ┌────────┬────────┬─────────┬────┐ │
|
||||
│ │ ST04 │ 放油前 │ DB10000 │ 启 │ │
|
||||
│ │ ST05 │ 放油后 │ DB10001 │ 启 │ │
|
||||
│ └────────┴────────┴─────────┴────┘ │
|
||||
├─────────────────────────────────────┤
|
||||
│ [取消] [保存] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 13.3 空状态
|
||||
|
||||
```text
|
||||
┌──────────────────────────────┬─────────────────────────────────────────────────────┐
|
||||
│ 左侧:设备模块 │ │
|
||||
│ ┌──────────────────────────┐ │ 请选择左侧设备模块 │
|
||||
│ │ 放油设备 S1500 启用 │ │ │
|
||||
│ │ 注油设备 S1500 启用 │ │ 选择后可维护该设备下的工位 │
|
||||
│ └──────────────────────────┘ │ 和 MES_基本变量原始数据 │
|
||||
└──────────────────────────────┴─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 13.4 无工位状态
|
||||
|
||||
```text
|
||||
┌──────────────────────────────┬─────────────────────────────────────────────────────┐
|
||||
│ 左侧:设备模块 │ 当前设备:放油设备 / S1500 / 192.168.1.60 │
|
||||
│ ┌──────────────────────────┐ │ │
|
||||
│ │ ▶ 放油设备 S1500 启用 │ │ 当前设备模块还没有工位 │
|
||||
│ └──────────────────────────┘ │ │
|
||||
│ │ 请在设备模块编辑中点击 + 新增工位 │
|
||||
└──────────────────────────────┴─────────────────────────────────────────────────────┘
|
||||
```
|
||||
471
docs/variable-template-feature-design.md
Normal file
471
docs/variable-template-feature-design.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# MES 基本变量模板功能设计文档
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
当前网页在新增变量时,有一部分字段适合自动填写,例如 `TagID`、`工位号`、`数据来源`、`IP`、`DbName` 等来源字段;也有一部分字段经常重复填写,例如 `TagTypeID`、`TagLong`、`变量类型`、`监控组`、上下限、保存标记等。
|
||||
|
||||
为了减少重复录入,建议新增“变量模板”功能。用户可以提前维护常用变量模板,在新增变量时直接套用模板,然后只修改少量差异字段。
|
||||
|
||||
模板功能的目标是:
|
||||
|
||||
- 支持新增模板。
|
||||
- 支持编辑模板。
|
||||
- 支持删除模板。
|
||||
- 支持在新增变量时使用模板。
|
||||
- 支持从当前变量保存为模板。
|
||||
- 支持模板数据持久化,刷新页面不丢失。
|
||||
- 不破坏当前设备模块、配置行、工位展开预览和 Excel 导出规则。
|
||||
|
||||
## 2. 功能边界
|
||||
|
||||
模板只负责快速填充变量字段,不直接生成设备配置行。
|
||||
|
||||
设备模块和工位配置仍由设备模块弹窗维护:
|
||||
|
||||
- `设备名称`
|
||||
- `工位名称`
|
||||
- `设备型号`
|
||||
- `设备IP`
|
||||
- `DB`
|
||||
- `是否启用`
|
||||
|
||||
变量模板主要作用于 `MES_基本变量原始数据` 的变量字段。
|
||||
|
||||
模板不直接保存以下最终展开字段:
|
||||
|
||||
- `工位号`
|
||||
- `数据来源`
|
||||
- `IP`
|
||||
- `DbName`
|
||||
|
||||
这些字段仍以当前设备模块配置行为准,在页面预览和导出 Excel 时自动覆盖。
|
||||
|
||||
## 3. 模板类型建议
|
||||
|
||||
第一版建议先做一种模板:`变量模板`。
|
||||
|
||||
后续如果需要,可以扩展为三类:
|
||||
|
||||
| 模板类型 | 用途 | 第一版是否实现 |
|
||||
| --- | --- | --- |
|
||||
| 单变量模板 | 新增一条变量时快速填充字段 | 是 |
|
||||
| 变量组模板 | 一次添加多条常用变量,例如一组握手信号 | 后续 |
|
||||
| 设备模块模板 | 一次创建设备配置行和变量集合 | 后续 |
|
||||
|
||||
第一版先实现单变量模板即可,避免功能一下子过重。
|
||||
|
||||
## 4. 数据结构设计
|
||||
|
||||
建议在 OPFS/localStorage 保存的数据中新增 `variableTemplates` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"updatedAt": "2026-06-01T10:00:00.000Z",
|
||||
"devices": [],
|
||||
"variableTemplates": []
|
||||
}
|
||||
```
|
||||
|
||||
模板对象建议如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "tpl-bool-signal",
|
||||
"templateName": "Bool 信号模板",
|
||||
"description": "常用 Bool 开关量信号",
|
||||
"fields": {
|
||||
"TagTypeID": "11",
|
||||
"TagLong": "1",
|
||||
"变量类型": "Bool",
|
||||
"变量地址": "",
|
||||
"变量其它": "",
|
||||
"是否启用": "1",
|
||||
"监控组": "Monitor",
|
||||
"是否监控": "1",
|
||||
"IsGoodBad": "1",
|
||||
"IsSaveToSql": "0",
|
||||
"IsWebSocket": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `templateName` 用于页面下拉选择和模板管理列表展示。
|
||||
- `description` 用于解释模板用途。
|
||||
- `fields` 只保存模板要填充的字段。
|
||||
- 模板不强制保存 38 个字段,未配置字段保持空白或保留当前变量已有值。
|
||||
|
||||
## 5. 字段填充规则
|
||||
|
||||
新增变量时分三层填充:
|
||||
|
||||
1. 系统自动字段。
|
||||
2. 用户选择的模板字段。
|
||||
3. 用户手动编辑字段。
|
||||
|
||||
优先级从低到高:
|
||||
|
||||
```text
|
||||
空白变量 -> 系统自动字段 -> 模板字段 -> 用户手动修改
|
||||
```
|
||||
|
||||
### 5.1 系统自动字段
|
||||
|
||||
新增变量时自动填入:
|
||||
|
||||
| 字段 | 填充规则 |
|
||||
| --- | --- |
|
||||
| `TagID` | 自动生成下一条基础编号,例如 `TAG_0000005`,允许用户修改 |
|
||||
| `工位号` | 取当前设备模块第一条配置行的 `工位名称`,仅用于预览 |
|
||||
| `数据来源` | 取当前配置行的 `设备名称` 或 `数据来源`,仅用于预览 |
|
||||
| `IP` | 取当前配置行的 `设备IP`,仅用于预览 |
|
||||
| `DbName` | 取当前配置行的 `DB`,仅用于预览 |
|
||||
|
||||
### 5.2 模板字段
|
||||
|
||||
选择模板后,模板中的 `fields` 覆盖当前表单对应字段。
|
||||
|
||||
例如选择 `Bool 信号模板` 后自动填入:
|
||||
|
||||
```text
|
||||
TagTypeID = 11
|
||||
TagLong = 1
|
||||
变量类型 = Bool
|
||||
是否启用 = 1
|
||||
监控组 = Monitor
|
||||
是否监控 = 1
|
||||
```
|
||||
|
||||
### 5.3 用户手动字段
|
||||
|
||||
用户在弹窗中手动输入的内容优先级最高。
|
||||
|
||||
如果用户已经输入了 `TagName`,再切换模板时建议弹出确认:
|
||||
|
||||
```text
|
||||
使用模板会覆盖当前已填写的部分字段,是否继续?
|
||||
```
|
||||
|
||||
第一版也可以简化为:模板只填充空字段,不覆盖已有内容。
|
||||
|
||||
推荐第一版采用“只填空字段”,体验更安全。
|
||||
|
||||
## 6. 页面交互设计
|
||||
|
||||
### 6.1 新增变量弹窗
|
||||
|
||||
在新增变量弹窗顶部增加模板选择区:
|
||||
|
||||
```text
|
||||
模板: [请选择模板 v] [使用模板] [保存为模板]
|
||||
```
|
||||
|
||||
按钮说明:
|
||||
|
||||
| 按钮 | 功能 |
|
||||
| --- | --- |
|
||||
| `使用模板` | 将选中的模板字段填入当前变量表单 |
|
||||
| `保存为模板` | 将当前变量表单中可复用字段保存为一个新模板 |
|
||||
|
||||
`TagID / TagName / TagTypeID / TagLong` 建议保留在基础信息中,其中 `TagTypeID` 和 `TagLong` 必填。
|
||||
|
||||
### 6.2 编辑变量弹窗
|
||||
|
||||
编辑变量时也显示模板区:
|
||||
|
||||
```text
|
||||
模板: [请选择模板 v] [使用模板] [保存为模板]
|
||||
```
|
||||
|
||||
使用模板时,只影响当前编辑的共享变量模板行。保存后当前设备模块下所有工位展开行同步变化。
|
||||
|
||||
### 6.3 模板管理入口
|
||||
|
||||
建议在右侧变量表工具栏增加按钮:
|
||||
|
||||
```text
|
||||
[模板管理] [新增变量] [编辑变量] [复制变量] [删除变量]
|
||||
```
|
||||
|
||||
点击 `模板管理` 打开模板管理弹窗。
|
||||
|
||||
## 7. 模板管理弹窗
|
||||
|
||||
模板管理弹窗建议采用左右结构:
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 变量模板管理 [新增模板] │
|
||||
├───────────────────────┬──────────────────────────────────────┤
|
||||
│ 模板列表 │ 模板详情编辑 │
|
||||
│ ┌───────────────────┐ │ 模板名称:[Bool 信号模板 ] │
|
||||
│ │ Bool 信号模板 │ │ 模板说明:[常用 Bool 开关量信号] │
|
||||
│ │ Int 数值模板 │ │ │
|
||||
│ │ Real 浮点模板 │ │ TagTypeID:[11] TagLong:[1] │
|
||||
│ │ String 字符串模板 │ │ 变量类型:[Bool] │
|
||||
│ └───────────────────┘ │ 是否启用:[1] │
|
||||
│ │ 监控组:[Monitor] │
|
||||
│ │ 是否监控:[1] │
|
||||
├───────────────────────┴──────────────────────────────────────┤
|
||||
│ [删除] [取消] [保存模板] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
模板管理支持:
|
||||
|
||||
- 新增模板。
|
||||
- 编辑模板。
|
||||
- 删除模板。
|
||||
- 复制模板。
|
||||
- 从当前变量生成模板。
|
||||
|
||||
## 8. 默认模板建议
|
||||
|
||||
系统可以内置几条基础模板,用户可编辑或删除。
|
||||
|
||||
### 8.1 Bool 模板
|
||||
|
||||
```json
|
||||
{
|
||||
"templateName": "Bool 信号模板",
|
||||
"fields": {
|
||||
"TagTypeID": "11",
|
||||
"TagLong": "1",
|
||||
"变量类型": "Bool",
|
||||
"是否启用": "1",
|
||||
"是否监控": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Int 模板
|
||||
|
||||
```json
|
||||
{
|
||||
"templateName": "Int 数值模板",
|
||||
"fields": {
|
||||
"TagTypeID": "2",
|
||||
"TagLong": "2",
|
||||
"变量类型": "Int",
|
||||
"是否启用": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Real 模板
|
||||
|
||||
```json
|
||||
{
|
||||
"templateName": "Real 浮点模板",
|
||||
"fields": {
|
||||
"TagTypeID": "4",
|
||||
"TagLong": "4",
|
||||
"变量类型": "Real",
|
||||
"是否启用": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 String 模板
|
||||
|
||||
```json
|
||||
{
|
||||
"templateName": "String 字符串模板",
|
||||
"fields": {
|
||||
"TagTypeID": "8",
|
||||
"TagLong": "60",
|
||||
"变量类型": "String",
|
||||
"变量其它": ".58",
|
||||
"是否启用": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 保存和持久化规则
|
||||
|
||||
模板数据和设备模块数据一起保存到 OPFS/localStorage。
|
||||
|
||||
每次发生以下操作后自动保存:
|
||||
|
||||
- 新增模板。
|
||||
- 编辑模板。
|
||||
- 删除模板。
|
||||
- 从变量保存为模板。
|
||||
- 导入配置 JSON。
|
||||
|
||||
导出配置 JSON 时也应包含 `variableTemplates`,方便用户迁移模板。
|
||||
|
||||
导入旧 JSON 时如果没有 `variableTemplates`,系统自动补充默认模板。
|
||||
|
||||
## 10. Excel 导出影响
|
||||
|
||||
模板不直接影响 Excel 结构。
|
||||
|
||||
Excel 仍只导出两个 Sheet:
|
||||
|
||||
1. `MES_基本变量原始数据`
|
||||
2. `设备类型对应表`
|
||||
|
||||
模板只影响变量创建和编辑后的字段值。最终 Excel 导出仍以当前设备模块中的变量数据为准。
|
||||
|
||||
如果变量字段为空,导出 Excel 时继续按当前规则处理:空字段不强制填默认值。
|
||||
|
||||
## 11. 校验建议
|
||||
|
||||
新增或编辑变量时建议校验:
|
||||
|
||||
- `TagID` 不能为空。
|
||||
- `TagName` 不能为空。
|
||||
- `TagTypeID` 不能为空。
|
||||
- `TagLong` 不能为空。
|
||||
- 同一设备模块内基础 `TagID` 不建议重复。
|
||||
|
||||
模板保存时建议校验:
|
||||
|
||||
- 模板名称不能为空。
|
||||
- 模板名称不能重复。
|
||||
- 模板至少包含一个可填充字段。
|
||||
|
||||
使用模板时建议提示:
|
||||
|
||||
- 模板会填充哪些字段。
|
||||
- 是否覆盖已有字段。
|
||||
|
||||
## 12. 第一版实施建议
|
||||
|
||||
建议新增或修改:
|
||||
|
||||
```text
|
||||
src/data/default-variable-templates.json
|
||||
src/utils/templateBuilder.js
|
||||
src/views/SignalTableGenerator.vue
|
||||
src/utils/opfsStore.js
|
||||
docs/variable-template-feature-design.md
|
||||
```
|
||||
|
||||
职责划分:
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `default-variable-templates.json` | 保存系统默认模板 |
|
||||
| `templateBuilder.js` | 模板规范化、应用模板、从变量生成模板 |
|
||||
| `SignalTableGenerator.vue` | 模板管理 UI、变量弹窗模板选择 |
|
||||
| `opfsStore.js` | 保存和读取 `variableTemplates` |
|
||||
|
||||
## 13. 测试方案
|
||||
|
||||
### 13.1 新增模板
|
||||
|
||||
1. 打开模板管理。
|
||||
2. 点击 `新增模板`。
|
||||
3. 填写模板名称和字段。
|
||||
4. 保存模板。
|
||||
5. 刷新页面后模板仍存在。
|
||||
|
||||
### 13.2 使用模板新增变量
|
||||
|
||||
1. 选择一个设备模块。
|
||||
2. 点击 `新增变量`。
|
||||
3. 选择 `Bool 信号模板`。
|
||||
4. 点击 `使用模板`。
|
||||
5. 确认 `TagTypeID / TagLong / 变量类型` 自动填入。
|
||||
6. 保存变量。
|
||||
7. 右侧展开表格出现新变量。
|
||||
|
||||
### 13.3 编辑变量时使用模板
|
||||
|
||||
1. 选中一条变量。
|
||||
2. 点击 `编辑变量`。
|
||||
3. 选择另一个模板。
|
||||
4. 保存变量。
|
||||
5. 当前设备模块下所有工位对应展开行同步变化。
|
||||
|
||||
### 13.4 从变量保存为模板
|
||||
|
||||
1. 编辑一条变量。
|
||||
2. 点击 `保存为模板`。
|
||||
3. 输入模板名称。
|
||||
4. 保存后在模板管理中可以看到新模板。
|
||||
|
||||
### 13.5 导入导出配置
|
||||
|
||||
1. 新增一个模板。
|
||||
2. 导出配置 JSON。
|
||||
3. 清空或更换浏览器数据。
|
||||
4. 导入配置 JSON。
|
||||
5. 确认模板随配置恢复。
|
||||
|
||||
### 13.6 Excel 导出
|
||||
|
||||
1. 使用模板新增变量。
|
||||
2. 导出 `SignalTable_CZ.xlsx`。
|
||||
3. 确认 Excel 中变量字段来自用户保存后的变量数据。
|
||||
4. 确认 Excel 仍只有两个 Sheet。
|
||||
|
||||
## 14. 页面草图
|
||||
|
||||
### 14.1 变量表工具栏
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MES_基本变量原始数据展开预览 │
|
||||
│ 当前设备模块:共 3 个配置行,右侧显示所有工位展开后的 MES 原始数据 │
|
||||
│ [模板管理] [新增变量] │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ TagID TagName 工位号 数据来源 TagTypeID TagLong │
|
||||
│ ST04_TAG_0000001 PLC_PC_工件到位 ST04 放油前 11 1 │
|
||||
│ ST05_TAG_0000001 PLC_PC_工件到位 ST05 放油后 11 1 │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 14.2 新增变量弹窗
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 新增变量 │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 模板: [Bool 信号模板 v] [使用模板] [保存为模板] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 基础信息 │
|
||||
│ TagID: [TAG_0000001] TagName: [ ] │
|
||||
│ TagTypeID: [11 ] TagLong: [1 ] │
|
||||
│ 工位号: [ST04 ] 数据来源: [放油前 ] │
|
||||
│ IP: [192.168.1.60] DbName: [DB10000 ] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 变量定义 │
|
||||
│ 变量类型: [Bool ] 变量地址:[ ] │
|
||||
│ 变量其它: [ ] 变量类型代码:[ ] │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ [取消] [保存变量] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 14.3 模板管理弹窗
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 变量模板管理 [新增模板] │
|
||||
├───────────────────────┬──────────────────────────────────────┤
|
||||
│ Bool 信号模板 │ 模板名称:[Bool 信号模板 ] │
|
||||
│ Int 数值模板 │ 模板说明:[常用 Bool 开关量信号] │
|
||||
│ Real 浮点模板 │ TagTypeID:[11] TagLong:[1] │
|
||||
│ String 字符串模板 │ 变量类型:[Bool] │
|
||||
│ │ 是否启用:[1] │
|
||||
├───────────────────────┴──────────────────────────────────────┤
|
||||
│ [删除] [取消] [保存模板] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 15. 后续扩展方向
|
||||
|
||||
后续可以继续增强:
|
||||
|
||||
- 支持变量组模板,一次新增多条变量。
|
||||
- 支持模板分类,例如 Bool、Int、Real、String、MES 通讯、PLC 握手。
|
||||
- 支持模板导入导出为单独 JSON 文件。
|
||||
- 支持从现有 Excel 扫描变量并生成模板。
|
||||
- 支持设置默认模板,新建变量时自动应用。
|
||||
- 支持模板字段锁定,例如某些模板强制固定 `TagTypeID` 和 `TagLong`。
|
||||
|
||||
23582
package-lock.json
generated
Normal file
23582
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "signal-table-excel-generator",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-ui": "^2.8.2",
|
||||
"vue": "^2.6.6",
|
||||
"vue-router": "^3.0.1",
|
||||
"xlsx": "^0.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.5.0",
|
||||
"@vue/cli-service": "^3.5.0",
|
||||
"vue-template-compiler": "^2.5.21",
|
||||
"webpack": "4.47.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
26
public/index.html
Normal file
26
public/index.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>设备信息展示平台</title>
|
||||
<script type="text/javascript">
|
||||
document.write("<script src='./config.js?v=" + new Date().getTime() + "'><\/script>");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but gaojingtv doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
<style slot-scope="true">
|
||||
body{
|
||||
/*background-color: #5B9BD5;*/
|
||||
/*background:rgba(0,0,0,0.2);*/
|
||||
}
|
||||
</style>
|
||||
25
src/App.vue
Normal file
25
src/App.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
color: #1f2d3d;
|
||||
background: #f3f5f8;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
376
src/data/default-signal-table.json
Normal file
376
src/data/default-signal-table.json
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"id": "dev-001",
|
||||
"deviceName": "放油设备",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"enabled": 1,
|
||||
"stations": [
|
||||
{
|
||||
"id": "station-001",
|
||||
"deviceName": "放油前",
|
||||
"stationName": "ST04",
|
||||
"dataSource": "放油前",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"db": "DB10000",
|
||||
"enabled": 1
|
||||
},
|
||||
{
|
||||
"id": "station-002",
|
||||
"deviceName": "放油后",
|
||||
"stationName": "ST05",
|
||||
"dataSource": "放油后",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.60",
|
||||
"db": "DB10001",
|
||||
"enabled": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dev-002",
|
||||
"deviceName": "注油设备",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.61",
|
||||
"enabled": 0,
|
||||
"stations": [
|
||||
{
|
||||
"id": "station-003",
|
||||
"deviceName": "注油前",
|
||||
"stationName": "ST06",
|
||||
"dataSource": "注油前",
|
||||
"deviceModel": "S1500",
|
||||
"deviceIp": "192.168.1.61",
|
||||
"db": "DB10002",
|
||||
"enabled": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{
|
||||
"tagId": "TAG_0000001",
|
||||
"tagName": "PLC_PC_工件到位",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 0,
|
||||
"variableExtra": ".0",
|
||||
"typeCode": "2",
|
||||
"typeName": "PC不使用,仅做信号显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000002",
|
||||
"tagName": "PLC_PC_读取完成",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 0,
|
||||
"variableExtra": ".1",
|
||||
"typeCode": "100001",
|
||||
"typeName": "PC不使用,仅做信号显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000003",
|
||||
"tagName": "PLC_PC_开始称重",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 0,
|
||||
"variableExtra": ".2",
|
||||
"typeCode": "100002",
|
||||
"typeName": "PC不使用,仅做信号显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000004",
|
||||
"tagName": "PLC_PC_称重完成",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 0,
|
||||
"variableExtra": ".3",
|
||||
"typeCode": "100003",
|
||||
"typeName": "PC不使用,仅做信号显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000005",
|
||||
"tagName": "PLC_PC_等待放行",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 0,
|
||||
"variableExtra": ".4",
|
||||
"typeCode": "100004",
|
||||
"typeName": "PC不使用,仅做信号显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000006",
|
||||
"tagName": "PLC_PC_系统健康",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".0",
|
||||
"typeCode": "14",
|
||||
"typeName": "心跳处理:PLC按延时5秒,取反写回PLC,PLC监控PC心跳,如果10秒值不改变报通讯故障。",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000007",
|
||||
"tagName": "PC_PLC_系统健康",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".1",
|
||||
"typeCode": "15",
|
||||
"typeName": "收到PLC心跳信号后,PC按原值回写。",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000008",
|
||||
"tagName": "PLC_PC_请求工作",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".2",
|
||||
"typeCode": "44",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000009",
|
||||
"tagName": "PC_PLC_允许工作",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".3",
|
||||
"typeCode": "66",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000010",
|
||||
"tagName": "PLC_PC_请求保存",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".4",
|
||||
"typeCode": "19",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000011",
|
||||
"tagName": "PC_PLC_保存完成",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".5",
|
||||
"typeCode": "20",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000012",
|
||||
"tagName": "PC_PLC_扫码枪扫码完成",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".6",
|
||||
"typeCode": "100016",
|
||||
"typeName": "有扫码枪的工位,扫码完成PC置位(1秒后PC复位),PLC根据自己逻辑判断适当时候取码值。",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000013",
|
||||
"tagName": "PC_PLC_称重清零触发",
|
||||
"tagTypeId": 11,
|
||||
"tagLong": 1,
|
||||
"variableType": "Bool",
|
||||
"variableAddress": 2,
|
||||
"variableExtra": ".7",
|
||||
"typeCode": "100017",
|
||||
"typeName": "点击清空按钮置位(1秒后PC复位),PLC根据自己逻辑判断适当时候清除。",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "Monitor",
|
||||
"isMonitor": 1
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000014",
|
||||
"tagName": "PC_PLC_报警代码",
|
||||
"tagTypeId": 2,
|
||||
"tagLong": 2,
|
||||
"variableType": "Int",
|
||||
"variableAddress": "10",
|
||||
"variableExtra": "",
|
||||
"typeCode": "112",
|
||||
"typeName": "报警时PC赋值,消除报警时,PLC复位;代码说明:0-无报警,1-获取工厂数据接口调用失败,2-上传工厂数据接口调用失败,3-发动机号错误",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000015",
|
||||
"tagName": "PLC_PC_合格标志",
|
||||
"tagTypeId": 2,
|
||||
"tagLong": 2,
|
||||
"variableType": "Int",
|
||||
"variableAddress": "12",
|
||||
"variableExtra": "",
|
||||
"typeCode": "21",
|
||||
"typeName": "合格标志:0-没有工作;1-合格 ;2-不合格",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000016",
|
||||
"tagName": "PLC_PC_称重重量",
|
||||
"tagTypeId": 4,
|
||||
"tagLong": 4,
|
||||
"variableType": "Real",
|
||||
"variableAddress": "14",
|
||||
"variableExtra": "",
|
||||
"typeCode": "100140",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000017",
|
||||
"tagName": "PLC_PC_水含量实时值",
|
||||
"tagTypeId": 4,
|
||||
"tagLong": 4,
|
||||
"variableType": "Real",
|
||||
"variableAddress": "18",
|
||||
"variableExtra": "",
|
||||
"typeCode": "100180",
|
||||
"typeName": "PC页面实时显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000018",
|
||||
"tagName": "PLC_PC_重量实时值",
|
||||
"tagTypeId": 4,
|
||||
"tagLong": 4,
|
||||
"variableType": "Real",
|
||||
"variableAddress": "22",
|
||||
"variableExtra": "",
|
||||
"typeCode": "100220",
|
||||
"typeName": "PC页面实时显示",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000019",
|
||||
"tagName": "PLC_PC_总成编号",
|
||||
"tagTypeId": 8,
|
||||
"tagLong": 60,
|
||||
"variableType": "String",
|
||||
"variableAddress": "50",
|
||||
"variableExtra": ".58",
|
||||
"typeCode": "10",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000020",
|
||||
"tagName": "PLC_PC_机型号",
|
||||
"tagTypeId": 8,
|
||||
"tagLong": 60,
|
||||
"variableType": "String",
|
||||
"variableAddress": "110",
|
||||
"variableExtra": ".58",
|
||||
"typeCode": "11",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000021",
|
||||
"tagName": "PLC_PC_工单号",
|
||||
"tagTypeId": 8,
|
||||
"tagLong": 60,
|
||||
"variableType": "String",
|
||||
"variableAddress": "170",
|
||||
"variableExtra": ".58",
|
||||
"typeCode": "39",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000022",
|
||||
"tagName": "PLC_PC_托盘号",
|
||||
"tagTypeId": 8,
|
||||
"tagLong": 20,
|
||||
"variableType": "String",
|
||||
"variableAddress": "230",
|
||||
"variableExtra": ".18",
|
||||
"typeCode": "80",
|
||||
"typeName": "",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
},
|
||||
{
|
||||
"tagId": "TAG_0000023",
|
||||
"tagName": "PC_PLC_扫码枪值",
|
||||
"tagTypeId": 8,
|
||||
"tagLong": 100,
|
||||
"variableType": "String",
|
||||
"variableAddress": "250",
|
||||
"variableExtra": ".98",
|
||||
"typeCode": "102500",
|
||||
"typeName": "扫码枪扫码值",
|
||||
"enabled": 1,
|
||||
"monitorGroup": "NoMonitor",
|
||||
"isMonitor": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
48
src/data/default-variable-templates.json
Normal file
48
src/data/default-variable-templates.json
Normal file
@@ -0,0 +1,48 @@
|
||||
[
|
||||
{
|
||||
"id": "tpl-bool-signal",
|
||||
"templateName": "Bool 信号模板",
|
||||
"description": "常用 Bool 开关量信号",
|
||||
"fields": {
|
||||
"TagTypeID": "11",
|
||||
"TagLong": "1",
|
||||
"变量类型": "Bool",
|
||||
"是否启用": "1",
|
||||
"是否监控": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tpl-int-signal",
|
||||
"templateName": "Int 数值模板",
|
||||
"description": "常用 Int 数值信号",
|
||||
"fields": {
|
||||
"TagTypeID": "2",
|
||||
"TagLong": "2",
|
||||
"变量类型": "Int",
|
||||
"是否启用": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tpl-real-signal",
|
||||
"templateName": "Real 浮点模板",
|
||||
"description": "常用 Real 浮点信号",
|
||||
"fields": {
|
||||
"TagTypeID": "4",
|
||||
"TagLong": "4",
|
||||
"变量类型": "Real",
|
||||
"是否启用": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tpl-string-signal",
|
||||
"templateName": "String 字符串模板",
|
||||
"description": "常用 String 字符串信号",
|
||||
"fields": {
|
||||
"TagTypeID": "8",
|
||||
"TagLong": "60",
|
||||
"变量类型": "String",
|
||||
"变量其它": ".58",
|
||||
"是否启用": "1"
|
||||
}
|
||||
}
|
||||
]
|
||||
13
src/main.js
Normal file
13
src/main.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Vue from 'vue'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
Vue.use(ElementUI)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
15
src/router/index.js
Normal file
15
src/router/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import SignalTableGenerator from '@/views/SignalTableGenerator.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'SignalTableGenerator',
|
||||
component: SignalTableGenerator
|
||||
}
|
||||
]
|
||||
})
|
||||
35
src/utils/excelWriter.js
Normal file
35
src/utils/excelWriter.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import XLSX from 'xlsx'
|
||||
import { buildWorkbookSheets } from './signalTableBuilder'
|
||||
import { saveBlob } from './filePicker'
|
||||
|
||||
export function createSignalTableWorkbook(data) {
|
||||
const workbook = XLSX.utils.book_new()
|
||||
|
||||
buildWorkbookSheets(data).forEach(sheet => {
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(sheet.rows)
|
||||
worksheet['!cols'] = (sheet.cols || []).map(wch => ({ wch }))
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, sheet.name)
|
||||
})
|
||||
|
||||
return workbook
|
||||
}
|
||||
|
||||
export async function exportSignalTable(data, filename = 'SignalTable_CZ.xlsx') {
|
||||
const workbook = createSignalTableWorkbook(data)
|
||||
const content = XLSX.write(workbook, {
|
||||
bookType: 'xlsx',
|
||||
type: 'array'
|
||||
})
|
||||
const blob = new Blob([content], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
|
||||
return saveBlob(blob, filename, [
|
||||
{
|
||||
description: 'Excel 工作簿',
|
||||
accept: {
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx']
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
57
src/utils/filePicker.js
Normal file
57
src/utils/filePicker.js
Normal file
@@ -0,0 +1,57 @@
|
||||
export function isAbortError(error) {
|
||||
return error && error.name === 'AbortError'
|
||||
}
|
||||
|
||||
export function isSavePickerSupported() {
|
||||
return typeof window !== 'undefined' && typeof window.showSaveFilePicker === 'function'
|
||||
}
|
||||
|
||||
export function isOpenPickerSupported() {
|
||||
return typeof window !== 'undefined' && typeof window.showOpenFilePicker === 'function'
|
||||
}
|
||||
|
||||
function downloadBlob(blob, filename) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
export async function saveBlob(blob, filename, types) {
|
||||
if (isSavePickerSupported()) {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: filename,
|
||||
types
|
||||
})
|
||||
const writable = await handle.createWritable()
|
||||
await writable.write(blob)
|
||||
await writable.close()
|
||||
return { method: 'picker' }
|
||||
}
|
||||
|
||||
downloadBlob(blob, filename)
|
||||
return { method: 'download' }
|
||||
}
|
||||
|
||||
export async function openJsonFile() {
|
||||
if (!isOpenPickerSupported()) return null
|
||||
|
||||
const handles = await window.showOpenFilePicker({
|
||||
multiple: false,
|
||||
types: [
|
||||
{
|
||||
description: 'JSON 配置文件',
|
||||
accept: {
|
||||
'application/json': ['.json']
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (!handles.length) return null
|
||||
return handles[0].getFile()
|
||||
}
|
||||
125
src/utils/opfsStore.js
Normal file
125
src/utils/opfsStore.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import { saveBlob } from './filePicker'
|
||||
|
||||
const CONFIG_FILE_NAME = 'signal-table-config.json'
|
||||
const LOCAL_STORAGE_KEY = 'signal-table-config'
|
||||
|
||||
export function isOpfsSupported() {
|
||||
return Boolean(navigator.storage && navigator.storage.getDirectory)
|
||||
}
|
||||
|
||||
function wrapConfig(data) {
|
||||
return {
|
||||
version: 2,
|
||||
updatedAt: new Date().toISOString(),
|
||||
devices: Array.isArray(data.devices) ? data.devices : [],
|
||||
variables: Array.isArray(data.variables) ? data.variables : [],
|
||||
variableTemplates: Array.isArray(data.variableTemplates) ? data.variableTemplates : []
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfigFileHandle(create) {
|
||||
const root = await navigator.storage.getDirectory()
|
||||
return root.getFileHandle(CONFIG_FILE_NAME, { create })
|
||||
}
|
||||
|
||||
async function readFromOpfs() {
|
||||
const handle = await getConfigFileHandle(false)
|
||||
const file = await handle.getFile()
|
||||
const text = await file.text()
|
||||
return JSON.parse(text)
|
||||
}
|
||||
|
||||
async function writeToOpfs(data) {
|
||||
const handle = await getConfigFileHandle(true)
|
||||
const writable = await handle.createWritable()
|
||||
await writable.write(JSON.stringify(wrapConfig(data), null, 2))
|
||||
await writable.close()
|
||||
}
|
||||
|
||||
function readFromLocalStorage() {
|
||||
const text = window.localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
return text ? JSON.parse(text) : null
|
||||
}
|
||||
|
||||
function writeToLocalStorage(data) {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(wrapConfig(data), null, 2))
|
||||
}
|
||||
|
||||
export async function loadConfigFromOpfs() {
|
||||
if (isOpfsSupported()) {
|
||||
try {
|
||||
const data = await readFromOpfs()
|
||||
return { data, storageType: 'OPFS', existed: true }
|
||||
} catch (error) {
|
||||
if (error && error.name !== 'NotFoundError') throw error
|
||||
}
|
||||
}
|
||||
|
||||
const localData = readFromLocalStorage()
|
||||
return {
|
||||
data: localData,
|
||||
storageType: isOpfsSupported() ? 'OPFS' : 'localStorage',
|
||||
existed: Boolean(localData)
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveConfigToOpfs(data) {
|
||||
if (isOpfsSupported()) {
|
||||
await writeToOpfs(data)
|
||||
return { storageType: 'OPFS', updatedAt: new Date().toISOString() }
|
||||
}
|
||||
|
||||
writeToLocalStorage(data)
|
||||
return { storageType: 'localStorage', updatedAt: new Date().toISOString() }
|
||||
}
|
||||
|
||||
export async function resetConfigInOpfs(defaultData) {
|
||||
return saveConfigToOpfs(defaultData)
|
||||
}
|
||||
|
||||
export function createConfigJsonBlob(data) {
|
||||
return new Blob([JSON.stringify(wrapConfig(data), null, 2)], {
|
||||
type: 'application/json;charset=utf-8'
|
||||
})
|
||||
}
|
||||
|
||||
export async function exportConfigJson(data, filename = 'signal-table-config.json') {
|
||||
const blob = createConfigJsonBlob(data)
|
||||
return saveBlob(blob, filename, [
|
||||
{
|
||||
description: 'JSON 配置文件',
|
||||
accept: {
|
||||
'application/json': ['.json']
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
export function downloadConfigJson(data, filename = 'signal-table-config.json') {
|
||||
const blob = new Blob([JSON.stringify(wrapConfig(data), null, 2)], {
|
||||
type: 'application/json;charset=utf-8'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
export function importConfigJson(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = event => {
|
||||
try {
|
||||
resolve(JSON.parse(event.target.result))
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
reader.onerror = () => reject(reader.error)
|
||||
reader.readAsText(file, 'utf-8')
|
||||
})
|
||||
}
|
||||
472
src/utils/signalTableBuilder.js
Normal file
472
src/utils/signalTableBuilder.js
Normal file
@@ -0,0 +1,472 @@
|
||||
export const MES_HEADERS = [
|
||||
'TagID',
|
||||
'TagName',
|
||||
'工位号',
|
||||
'数据来源',
|
||||
'IP',
|
||||
'TagTypeID',
|
||||
'TagLong',
|
||||
'DbName',
|
||||
'变量类型',
|
||||
'变量地址',
|
||||
'变量其它',
|
||||
'演示偏移',
|
||||
'列位置',
|
||||
'测量位置',
|
||||
'测量项目',
|
||||
'测量单位',
|
||||
'变量类型代码',
|
||||
'变量类型名称',
|
||||
'机体类型',
|
||||
'线体类型',
|
||||
'TagAdr',
|
||||
'TagAdrDemo',
|
||||
'是否启用',
|
||||
'监控组',
|
||||
'监控时间间隔',
|
||||
'OpcServer_OPNAME',
|
||||
'变量特点',
|
||||
'是否监控',
|
||||
'变量排序',
|
||||
'工序能力',
|
||||
'监控属性代码',
|
||||
'测量位置短',
|
||||
'上限值',
|
||||
'下限值',
|
||||
'理论值',
|
||||
'IsGoodBad',
|
||||
'IsSaveToSql',
|
||||
'IsWebSocket'
|
||||
]
|
||||
|
||||
export const DEVICE_HEADERS = ['设备IP', '设备类型', 'DB', '是否启用', '工位号', '数据来源']
|
||||
|
||||
export const VARIABLE_FIELD_GROUPS = [
|
||||
{
|
||||
title: '基础信息',
|
||||
fields: ['TagID', 'TagName', 'TagTypeID', 'TagLong', '工位号', '数据来源', 'IP', 'DbName', '是否启用']
|
||||
},
|
||||
{
|
||||
title: '变量定义',
|
||||
fields: ['变量类型', '变量地址', '变量其它', '变量类型代码', '变量类型名称']
|
||||
},
|
||||
{
|
||||
title: '测量与监控',
|
||||
fields: ['演示偏移', '列位置', '测量位置', '测量项目', '测量单位', '监控组', '监控时间间隔', '是否监控', '变量排序']
|
||||
},
|
||||
{
|
||||
title: '扩展字段',
|
||||
fields: ['机体类型', '线体类型', 'TagAdr', 'TagAdrDemo', 'OpcServer_OPNAME', '变量特点', '工序能力', '监控属性代码', '测量位置短']
|
||||
},
|
||||
{
|
||||
title: '阈值与保存',
|
||||
fields: ['上限值', '下限值', '理论值', 'IsGoodBad', 'IsSaveToSql', 'IsWebSocket']
|
||||
}
|
||||
]
|
||||
|
||||
const TEMPLATE_DEFAULTS = {
|
||||
'工位号': 'OPNAME',
|
||||
'数据来源': 'OPNAME',
|
||||
IP: 'IP',
|
||||
DbName: 'DB',
|
||||
'演示偏移': 0,
|
||||
'列位置': 1,
|
||||
'测量位置': '',
|
||||
'测量单位': '',
|
||||
'机体类型': '127',
|
||||
'线体类型': '',
|
||||
TagAdr: '无',
|
||||
TagAdrDemo: '无',
|
||||
'是否启用': 1,
|
||||
'监控组': 'NoMonitor',
|
||||
'监控时间间隔': '100',
|
||||
OpcServer_OPNAME: '0',
|
||||
'变量特点': '0',
|
||||
'是否监控': 0,
|
||||
'工序能力': '0',
|
||||
'监控属性代码': '1',
|
||||
'测量位置短': '0',
|
||||
'上限值': '100',
|
||||
'下限值': '0',
|
||||
'理论值': '50',
|
||||
IsGoodBad: '1',
|
||||
IsSaveToSql: '0',
|
||||
IsWebSocket: '0'
|
||||
}
|
||||
|
||||
const LEGACY_MAP = {
|
||||
tagId: 'TagID',
|
||||
tagName: 'TagName',
|
||||
tagTypeId: 'TagTypeID',
|
||||
tagLong: 'TagLong',
|
||||
variableType: '变量类型',
|
||||
variableAddress: '变量地址',
|
||||
variableExtra: '变量其它',
|
||||
typeCode: '变量类型代码',
|
||||
typeName: '变量类型名称',
|
||||
enabled: '是否启用',
|
||||
monitorGroup: '监控组',
|
||||
monitorInterval: '监控时间间隔',
|
||||
isMonitor: '是否监控',
|
||||
feature: '变量特点',
|
||||
measureItem: '测量项目'
|
||||
}
|
||||
|
||||
export function cloneData(data) {
|
||||
return JSON.parse(JSON.stringify(data || {}))
|
||||
}
|
||||
|
||||
export function nextTagId(variables) {
|
||||
const max = (variables || []).reduce((value, variable) => {
|
||||
const match = String(variable.TagID || variable.tagId || '').match(/TAG_(\d+)/)
|
||||
return match ? Math.max(value, Number(match[1])) : value
|
||||
}, 0)
|
||||
return `TAG_${String(max + 1).padStart(7, '0')}`
|
||||
}
|
||||
|
||||
function normalizeTagIdForStation(tagId, stationName) {
|
||||
const text = String(tagId || '').trim()
|
||||
const station = String(stationName || '').trim()
|
||||
if (!text || !station) return text
|
||||
if (text.startsWith(`${station}_`)) return text
|
||||
|
||||
const tagIndex = text.indexOf('TAG_')
|
||||
const tagPart = tagIndex === -1 ? text : text.slice(tagIndex)
|
||||
return `${station}_${tagPart}`
|
||||
}
|
||||
|
||||
function normalizeVariable(variable = {}, index = 0) {
|
||||
const row = {}
|
||||
MES_HEADERS.forEach(header => {
|
||||
row[header] = Object.prototype.hasOwnProperty.call(TEMPLATE_DEFAULTS, header) ? TEMPLATE_DEFAULTS[header] : ''
|
||||
})
|
||||
|
||||
Object.keys(LEGACY_MAP).forEach(key => {
|
||||
const target = LEGACY_MAP[key]
|
||||
if (Object.prototype.hasOwnProperty.call(variable, key)) row[target] = variable[key]
|
||||
})
|
||||
|
||||
MES_HEADERS.forEach(header => {
|
||||
if (Object.prototype.hasOwnProperty.call(variable, header)) row[header] = variable[header]
|
||||
})
|
||||
|
||||
row.TagID = row.TagID || nextTagId([])
|
||||
row.TagName = row.TagName || ''
|
||||
row['变量排序'] = Object.prototype.hasOwnProperty.call(variable, '变量排序') ? variable['变量排序'] : String(index)
|
||||
row['测量项目'] = row['测量项目'] || row.TagName
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
function hasMeaningfulVariableData(variable = {}) {
|
||||
const ignoredFields = new Set([
|
||||
'TagID',
|
||||
'工位号',
|
||||
'数据来源',
|
||||
'IP',
|
||||
'DbName',
|
||||
'是否启用',
|
||||
'变量排序'
|
||||
])
|
||||
|
||||
return MES_HEADERS.some(header => {
|
||||
if (ignoredFields.has(header)) return false
|
||||
const value = variable[header]
|
||||
return value !== undefined && value !== null && String(value).trim() !== ''
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeStation(station = {}, index = 0, legacyDevice = {}) {
|
||||
return {
|
||||
id: station.id || `station-${index + 1}`,
|
||||
deviceName: station.deviceName || legacyDevice.deviceName || '',
|
||||
stationName: station.stationName || legacyDevice.stationName || '',
|
||||
dataSource: station.deviceName || station.dataSource || legacyDevice.dataSource || legacyDevice.deviceName || '',
|
||||
deviceModel: station.deviceModel || legacyDevice.deviceModel || '',
|
||||
deviceIp: station.deviceIp || legacyDevice.deviceIp || '',
|
||||
db: station.db || legacyDevice.db || '',
|
||||
enabled: Number(Object.prototype.hasOwnProperty.call(station, 'enabled') ? station.enabled : legacyDevice.enabled) === 0 ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDevice(device = {}, index = 0, templateVariables = []) {
|
||||
const legacyStation = !Array.isArray(device.stations) && (device.stationName || device.db || device.dataSource)
|
||||
? [normalizeStation({}, 0, device)]
|
||||
: []
|
||||
const stations = Array.isArray(device.stations)
|
||||
? device.stations.map((station, stationIndex) => normalizeStation(station, stationIndex, device))
|
||||
: legacyStation
|
||||
const variableSource = Array.isArray(device.variables) ? device.variables : templateVariables
|
||||
|
||||
return {
|
||||
id: device.id || `dev-${index + 1}`,
|
||||
deviceName: device.deviceName || '',
|
||||
deviceModel: device.deviceModel || '',
|
||||
deviceIp: device.deviceIp || '',
|
||||
enabled: Number(device.enabled) === 1 ? 1 : 0,
|
||||
stations,
|
||||
variables: (variableSource || []).map(normalizeVariable)
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeData(data) {
|
||||
const source = cloneData(data)
|
||||
const templateVariables = Array.isArray(source.variables) ? source.variables.map(normalizeVariable) : []
|
||||
const devices = Array.isArray(source.devices)
|
||||
? source.devices.map((device, index) => normalizeDevice(device, index, templateVariables))
|
||||
: []
|
||||
const fallbackVariables = devices[0] && Array.isArray(devices[0].variables) ? devices[0].variables : []
|
||||
|
||||
return {
|
||||
devices,
|
||||
variables: templateVariables.length ? templateVariables : fallbackVariables.map(normalizeVariable),
|
||||
variableTemplates: Array.isArray(source.variableTemplates) ? cloneData(source.variableTemplates) : []
|
||||
}
|
||||
}
|
||||
|
||||
export function createVariableTemplate(variables, device, station) {
|
||||
const variable = normalizeVariable({
|
||||
TagID: nextTagId(variables),
|
||||
TagName: '',
|
||||
TagTypeID: 11,
|
||||
TagLong: 1,
|
||||
'变量类型': 'Bool',
|
||||
'变量地址': 0,
|
||||
'变量其它': '.0',
|
||||
'变量类型代码': '',
|
||||
'变量类型名称': '',
|
||||
'是否启用': 1,
|
||||
'监控组': 'Monitor',
|
||||
'是否监控': 1
|
||||
}, (variables || []).length)
|
||||
|
||||
return device && station ? applyStationToVariable(variable, device, station) : variable
|
||||
}
|
||||
|
||||
export function createBlankVariableTemplate(variables, device, station) {
|
||||
const variable = {}
|
||||
MES_HEADERS.forEach(header => {
|
||||
variable[header] = ''
|
||||
})
|
||||
|
||||
variable.TagID = nextTagId(variables)
|
||||
variable.TagName = ''
|
||||
variable['是否启用'] = 1
|
||||
|
||||
if (station) {
|
||||
variable['工位号'] = station.stationName || ''
|
||||
variable['数据来源'] = station.deviceName || station.dataSource || ''
|
||||
variable.IP = station.deviceIp || (device && device.deviceIp) || ''
|
||||
variable.DbName = station.db || ''
|
||||
}
|
||||
|
||||
return variable
|
||||
}
|
||||
|
||||
export function createDeviceVariables(variables) {
|
||||
return (variables || []).map(normalizeVariable)
|
||||
}
|
||||
|
||||
export function createStationTemplate(device, index = 0) {
|
||||
const firstStation = device && device.stations && device.stations[0] ? device.stations[0] : {}
|
||||
return {
|
||||
id: `station-${Date.now()}-${index}`,
|
||||
deviceName: firstStation.deviceName || (device && device.deviceName) || '',
|
||||
stationName: '',
|
||||
dataSource: firstStation.deviceName || (device && device.deviceName) || '',
|
||||
deviceModel: firstStation.deviceModel || (device && device.deviceModel) || 'S1500',
|
||||
deviceIp: firstStation.deviceIp || (device && device.deviceIp) || '',
|
||||
db: '',
|
||||
enabled: 1
|
||||
}
|
||||
}
|
||||
|
||||
export function applyStationToVariable(variable, device, station) {
|
||||
const row = normalizeVariable(variable)
|
||||
const stationName = station && station.stationName ? station.stationName : row['工位号']
|
||||
|
||||
return {
|
||||
...row,
|
||||
TagID: normalizeTagIdForStation(row.TagID, stationName),
|
||||
'工位号': stationName || '',
|
||||
'数据来源': station && (station.deviceName || station.dataSource) ? (station.deviceName || station.dataSource) : row['数据来源'],
|
||||
IP: station && station.deviceIp ? station.deviceIp : (device && device.deviceIp ? device.deviceIp : row.IP),
|
||||
DbName: station && station.db ? station.db : row.DbName
|
||||
}
|
||||
}
|
||||
|
||||
export function variablesForDisplay(device, station) {
|
||||
if (!device) return []
|
||||
return (device.variables || []).map(variable => {
|
||||
return station ? applyStationToVariable(variable, device, station) : normalizeVariable(variable)
|
||||
})
|
||||
}
|
||||
|
||||
export function expandedVariablesForDisplay(device) {
|
||||
if (!device) return []
|
||||
const stations = Array.isArray(device.stations) ? device.stations : []
|
||||
const variables = Array.isArray(device.variables) ? device.variables.filter(hasMeaningfulVariableData) : []
|
||||
if (!stations.length) return variables.map(normalizeVariable)
|
||||
|
||||
return stations.flatMap(station => {
|
||||
return variables.map((variable, index) => ({
|
||||
...applyStationToVariable(variable, device, station),
|
||||
_templateIndex: index,
|
||||
_stationId: station.id
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
export function buildDeviceRows(data) {
|
||||
const rows = [DEVICE_HEADERS]
|
||||
normalizeData(data).devices.forEach(device => {
|
||||
;(device.stations || []).forEach(station => {
|
||||
rows.push([
|
||||
station.deviceIp || device.deviceIp || '',
|
||||
station.deviceModel || device.deviceModel || '',
|
||||
station.db || '',
|
||||
Number(station.enabled) === 1 ? 1 : 0,
|
||||
station.stationName || '',
|
||||
station.deviceName || station.dataSource || ''
|
||||
])
|
||||
})
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
export function buildMesRows(data) {
|
||||
const rows = [MES_HEADERS]
|
||||
normalizeData(data).devices.forEach(device => {
|
||||
;(device.stations || []).forEach(station => {
|
||||
;(device.variables || []).filter(hasMeaningfulVariableData).forEach(variable => {
|
||||
const row = applyStationToVariable(variable, device, station)
|
||||
rows.push(MES_HEADERS.map(header => row[header]))
|
||||
})
|
||||
})
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
function removeEmptyColumns(rows, cols = []) {
|
||||
if (!rows.length) return { rows, cols }
|
||||
const dataRows = rows.slice(1)
|
||||
if (!dataRows.length) return { rows, cols }
|
||||
|
||||
const keepIndexes = rows[0]
|
||||
.map((header, index) => ({ header, index }))
|
||||
.filter(({ index }) => {
|
||||
return dataRows.some(row => {
|
||||
const value = row[index]
|
||||
return value !== undefined && value !== null && String(value).trim() !== ''
|
||||
})
|
||||
})
|
||||
.map(item => item.index)
|
||||
|
||||
return {
|
||||
rows: rows.map(row => keepIndexes.map(index => row[index])),
|
||||
cols: keepIndexes.map(index => cols[index]).filter(width => width !== undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildWorkbookSheets(data) {
|
||||
const mesSheet = removeEmptyColumns(
|
||||
buildMesRows(data),
|
||||
[18, 28, 16, 16, 12, 12, 9, 12, 12, 10, 10, 8, 8, 12, 24, 10, 14, 58, 8, 8, 10, 8, 10, 20, 10, 14, 12, 12, 12, 10, 12, 10, 10, 10, 10, 12, 12, 12]
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'MES_基本变量原始数据',
|
||||
rows: mesSheet.rows,
|
||||
cols: mesSheet.cols
|
||||
},
|
||||
{
|
||||
name: '设备类型对应表',
|
||||
rows: buildDeviceRows(data),
|
||||
cols: [18, 14, 12, 12, 12, 18]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function buildSingleDeviceExportData(data, selectedDeviceId) {
|
||||
const normalized = normalizeData(data)
|
||||
const selectedDevice = normalized.devices.find(device => device.id === selectedDeviceId)
|
||||
return selectedDevice
|
||||
? {
|
||||
devices: [cloneData(selectedDevice)]
|
||||
}
|
||||
: {
|
||||
devices: []
|
||||
}
|
||||
}
|
||||
|
||||
function validateDeviceModule(device, errors) {
|
||||
const label = device.deviceName || '当前设备模块'
|
||||
const stationSet = new Set()
|
||||
const dbSet = new Set()
|
||||
|
||||
if (!String(device.deviceName || '').trim()) errors.push(`${label} 的设备名称不能为空。`)
|
||||
if (!String(device.deviceModel || '').trim()) errors.push(`${label} 的设备型号不能为空。`)
|
||||
if (!String(device.deviceIp || '').trim()) errors.push(`${label} 的设备IP不能为空。`)
|
||||
if (!device.stations.length) errors.push(`${label} 至少需要一个工位。`)
|
||||
device.stations.forEach((station, stationIndex) => {
|
||||
const stationLabel = station.stationName || `${label} 的第 ${stationIndex + 1} 个工位`
|
||||
if (!String(station.deviceName || '').trim()) errors.push(`${stationLabel} 的设备名称不能为空。`)
|
||||
if (!String(station.stationName || '').trim()) errors.push(`${stationLabel} 的工位名称不能为空。`)
|
||||
if (!String(station.deviceModel || '').trim()) errors.push(`${stationLabel} 的设备型号不能为空。`)
|
||||
if (!String(station.deviceIp || '').trim()) errors.push(`${stationLabel} 的设备IP不能为空。`)
|
||||
if (!String(station.dataSource || '').trim()) errors.push(`${stationLabel} 的数据来源不能为空。`)
|
||||
if (!String(station.db || '').trim()) errors.push(`${stationLabel} 的DB不能为空。`)
|
||||
|
||||
const stationKey = String(station.stationName || '').trim().toLowerCase()
|
||||
if (stationKey && stationSet.has(stationKey)) errors.push(`${label} 下工位名称重复:${station.stationName}。`)
|
||||
if (stationKey) stationSet.add(stationKey)
|
||||
|
||||
const dbKey = String(station.db || '').trim().toLowerCase()
|
||||
if (dbKey && dbSet.has(dbKey)) errors.push(`${label} 下DB重复:${station.db}。`)
|
||||
if (dbKey) dbSet.add(dbKey)
|
||||
})
|
||||
|
||||
device.variables.forEach((variable, index) => {
|
||||
const variableLabel = `${label} 的第 ${index + 1} 条变量`
|
||||
if (!String(variable.TagID || '').trim()) errors.push(`${variableLabel} 缺少 TagID。`)
|
||||
if (!String(variable.TagName || '').trim()) errors.push(`${variableLabel} 缺少 TagName。`)
|
||||
})
|
||||
}
|
||||
|
||||
export function validateSingleDeviceExportData(data, selectedDeviceId) {
|
||||
const exportData = buildSingleDeviceExportData(data, selectedDeviceId)
|
||||
const device = exportData.devices[0]
|
||||
const errors = []
|
||||
|
||||
if (!device) {
|
||||
errors.push('请先选择要导出的设备模块。')
|
||||
return {
|
||||
valid: false,
|
||||
errors,
|
||||
warnings: []
|
||||
}
|
||||
}
|
||||
|
||||
validateDeviceModule(device, errors)
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings: ['本次只导出当前选中设备模块及其全部工位。']
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSignalTableData(data) {
|
||||
const normalized = normalizeData(data)
|
||||
const errors = []
|
||||
|
||||
if (!normalized.devices.length) errors.push('至少需要一个设备模块。')
|
||||
normalized.devices.forEach(device => validateDeviceModule(device, errors))
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings: ['请确认 App.config 中 Station1OpName / Station2OpName 与启用工位名称一致。']
|
||||
}
|
||||
}
|
||||
102
src/utils/templateBuilder.js
Normal file
102
src/utils/templateBuilder.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { cloneData, MES_HEADERS } from './signalTableBuilder'
|
||||
|
||||
const SOURCE_FIELDS = new Set(['工位号', '数据来源', 'IP', 'DbName'])
|
||||
const NON_TEMPLATE_FIELDS = new Set(['TagID', 'TagName', ...SOURCE_FIELDS])
|
||||
|
||||
export function normalizeVariableTemplates(templates, fallbackTemplates = []) {
|
||||
const source = Array.isArray(templates) && templates.length ? templates : fallbackTemplates
|
||||
|
||||
return (source || []).map((template, index) => {
|
||||
const fields = {}
|
||||
const rawFields = template && template.fields && typeof template.fields === 'object' ? template.fields : {}
|
||||
|
||||
MES_HEADERS.forEach(header => {
|
||||
if (Object.prototype.hasOwnProperty.call(rawFields, header)) {
|
||||
fields[header] = rawFields[header]
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
id: template.id || `tpl-${Date.now()}-${index}`,
|
||||
templateName: template.templateName || `变量模板${index + 1}`,
|
||||
description: template.description || '',
|
||||
fields
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createEmptyTemplate() {
|
||||
return {
|
||||
id: '',
|
||||
templateName: '',
|
||||
description: '',
|
||||
fields: {}
|
||||
}
|
||||
}
|
||||
|
||||
export function createTemplateFromVariable(variable, templateName, description = '') {
|
||||
const fields = {}
|
||||
|
||||
MES_HEADERS.forEach(header => {
|
||||
if (NON_TEMPLATE_FIELDS.has(header)) return
|
||||
const value = variable ? variable[header] : ''
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
fields[header] = value
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
id: `tpl-${Date.now()}`,
|
||||
templateName,
|
||||
description,
|
||||
fields
|
||||
}
|
||||
}
|
||||
|
||||
export function applyTemplateToVariableForm(form, template, options = {}) {
|
||||
const next = cloneData(form)
|
||||
const overwrite = Boolean(options.overwrite)
|
||||
const fields = template && template.fields ? template.fields : {}
|
||||
|
||||
Object.keys(fields).forEach(field => {
|
||||
if (!MES_HEADERS.includes(field)) return
|
||||
if (!overwrite) {
|
||||
const value = next[field]
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') return
|
||||
}
|
||||
next[field] = fields[field]
|
||||
})
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
export function templateHasFields(template) {
|
||||
return Boolean(template && template.fields && Object.keys(template.fields).length)
|
||||
}
|
||||
|
||||
export function templateNameExists(templates, name, ignoreId = '') {
|
||||
const target = String(name || '').trim().toLowerCase()
|
||||
return (templates || []).some(template => {
|
||||
return template.id !== ignoreId && String(template.templateName || '').trim().toLowerCase() === target
|
||||
})
|
||||
}
|
||||
|
||||
export function sanitizeTemplateForSave(template) {
|
||||
const next = createEmptyTemplate()
|
||||
next.id = template.id || `tpl-${Date.now()}`
|
||||
next.templateName = String(template.templateName || '').trim()
|
||||
next.description = String(template.description || '').trim()
|
||||
|
||||
const rawFields = template.fields || {}
|
||||
MES_HEADERS.forEach(header => {
|
||||
if (Object.prototype.hasOwnProperty.call(rawFields, header)) {
|
||||
const value = rawFields[header]
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
next.fields[header] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
1380
src/views/SignalTableGenerator.vue
Normal file
1380
src/views/SignalTableGenerator.vue
Normal file
File diff suppressed because it is too large
Load Diff
3
vue.config.js
Normal file
3
vue.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
publicPath: '/'
|
||||
}
|
||||
Reference in New Issue
Block a user