更新采购订单、发货、合同查询、盘点、入库和配置文件,新增零改单和文档

This commit is contained in:
Developer
2026-06-26 08:13:30 +08:00
parent 0c8c6587bd
commit bb30ba76d0
21 changed files with 3290 additions and 50 deletions

View File

@@ -0,0 +1,291 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>JY1.0 立库详细界面功能说明书</title>
<style>
@page { size: A4; margin: 16mm 13mm 15mm; }
body { font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif; color:#222; line-height:1.62; font-size:13.5px; }
h1,h2,h3,h4 { color:#111; margin: 1.1em 0 0.5em; page-break-after: avoid; }
h1 { font-size: 25px; border-bottom: 2px solid #2f6fed; padding-bottom: 10px; }
h2 { font-size: 19px; border-left: 4px solid #2f6fed; padding-left: 10px; }
h3 { font-size: 16px; }
p { margin: 0.35em 0 0.75em; }
ul,ol { margin: 0.35em 0 0.75em 1.4em; }
li { margin: 0.18em 0; }
code { background:#f5f7fa; padding:2px 6px; border-radius:4px; font-family: Consolas, monospace; font-size: 12px; }
table { width:100%; border-collapse: collapse; margin: 10px 0 16px; font-size: 13px; }
th,td { border:1px solid #dcdfe6; padding:8px 10px; vertical-align: top; }
th { background:#f5f7fa; }
tr:nth-child(even) td { background:#fafafa; }
figure { margin: 10px 0 18px; page-break-inside: avoid; }
img { display:block; width:100%; max-width:100%; border:1px solid #dcdfe6; border-radius:4px; box-shadow:0 2px 8px rgba(0,0,0,.08); }
strong { color:#111; }
</style>
</head>
<body>
<h1>JY1.0 立库详细界面功能说明书</h1>
<h2>1. 文档目的</h2>
<p>本文档用于整理 JY1.0 项目中“立库查询/立库可视化”组件目前已经实现的界面结构、后端数据来源、交互逻辑和现有功能范围,便于后续培训、验收、维护和功能扩展。</p>
<p>本文档依据当前前端实现整理,核心代码位置如下:</p>
<ul><li><code>src/views/WarehouseManagement/WarehouseVisualization/index.vue</code></li><li><code>static/config.js</code></li></ul>
<h2>2. 页面定位</h2>
<p>立库详细界面当前对应的页面名称为:</p>
<ul><li>页面组件名:<code>WarehouseVisualizationSystem</code></li><li>页面标签名:<code>立库可视化</code></li><li>目录位置:<code>src/views/WarehouseManagement/WarehouseVisualization/index.vue</code></li></ul>
<p>该页面用于展示立体仓库库位占用情况,并支持按巷道查看、按行层筛选、单击库位查看库位详情或物料明细。</p>
<h2>3. 页面总体结构</h2>
<p><strong>图 1立库可视化主界面</strong></p>
<figure><img src="assets/warehouse-visualization/main.png" alt="立库可视化主界面" /></figure>
<p>页面整体采用标准的 <code>div.app-container + el-card</code> 布局,主体包含以下区域:</p>
<ol><li>顶部筛选与操作区</li><li>统计信息卡片区</li><li>状态图例区</li><li>立库可视化表格区</li><li>分页区</li><li>库位详情弹窗</li><li>物料信息弹窗</li></ol>
<p>当前页面只有一个主标签页:</p>
<ul><li><code>立库可视化</code></li></ul>
<p>代码中保留了图表分析相关逻辑,但当前模板中未实际启用“分析”页签。</p>
<h2>4. 顶部筛选与操作区说明</h2>
<p><strong>图 2筛选区与显示模式切换示意</strong></p>
<figure><img src="assets/warehouse-visualization/filter.png" alt="筛选区与显示模式切换" /></figure>
<p>顶部区域已经实现以下控件:</p>
<h3>4.1 巷道筛选</h3>
<p>控件类型:<code>el-select</code></p>
<p>可选项固定为:</p>
<ul><li>第一巷道</li><li>第二巷道</li><li>第三巷道</li><li>第四巷道</li></ul>
<p>对应值为:</p>
<ul><li><code>1</code></li><li><code>2</code></li><li><code>3</code></li><li><code>4</code></li></ul>
<p>用途:</p>
<ul><li>用户选择巷道后,点击“搜索”,页面会调用立库接口重新获取该巷道数据。</li></ul>
<h3>4.2 行层筛选</h3>
<p>控件类型:<code>el-select</code></p>
<p>用途:</p>
<ul><li>根据当前已加载的立库数据,自动生成“行-层”下拉选项。</li><li>用户可筛选某一行层,只显示对应行数据。</li></ul>
<p>特点:</p>
<ul><li>选项从接口返回数据动态提取。</li><li>会自动去重。</li><li>按数值倒序排列。</li></ul>
<h3>4.3 搜索按钮</h3>
<p>按钮文字:<code>搜索</code></p>
<p>作用:</p>
<ul><li>重置当前页码为第一页。</li><li>先执行前端筛选。</li><li>再根据当前巷道值调用 <code>GetPosition</code> 获取最新立库数据。</li></ul>
<p>说明:</p>
<ul><li>当前“搜索”动作的核心是按巷道重新取数。</li><li>代码里存在 <code>searchKeyword</code> 变量,但页面上没有对应输入框,因此当前没有“按列关键字搜索”的实际入口。</li></ul>
<h3>4.4 重置按钮</h3>
<p>按钮文字:<code>重置</code></p>
<p>作用:</p>
<ul><li>清空状态筛选值</li><li>清空行层筛选值</li><li>清空搜索关键字</li><li>页码回到第一页</li><li>重新执行前端筛选</li></ul>
<p>页面提示:</p>
<ul><li>执行后弹出“筛选条件已重置”。</li></ul>
<p>注意:</p>
<ul><li>当前重置不会主动重新按巷道取数,只是重置前端筛选状态。</li></ul>
<h3>4.5 刷新数据按钮</h3>
<p>按钮文字:<code>刷新数据</code></p>
<p>作用:</p>
<ul><li>触发加载动画</li><li>500ms 后重新装载当前 <code>defaultWarehouseData</code></li><li>更新时间戳</li><li>弹出“数据刷新成功”</li></ul>
<p>说明:</p>
<ul><li>刷新逻辑最终仍依赖最近一次 <code>GetPosition</code> 返回并写入的 <code>defaultWarehouseData</code></li><li>如果前面已经切换过巷道,刷新展示的是当前缓存的该次结果。</li></ul>
<h3>4.6 显示模式切换开关</h3>
<p>控件类型:<code>el-switch</code></p>
<p>两种模式:</p>
<ul><li>显示文字:<code>text</code></li><li>显示图标:<code>icon</code></li></ul>
<p>作用:</p>
<ul><li>控制库位单元格显示简称文字还是图标。</li></ul>
<p>切换效果:</p>
<ul><li>文字模式显示:<code>物 / 盘 / 空 / 锁</code></li><li>图标模式显示:</li></ul>
<p>- 锁库:<code>el-icon-lock</code> - 有物料有托盘:<code>el-icon-box</code> - 有托盘无物料:<code>el-icon-tickets</code> - 空库位:<code>el-icon-minus</code></p>
<p>页面提示:</p>
<ul><li>切换后会提示当前已切换到文字模式或图标模式。</li></ul>
<h2>5. 统计信息卡片区说明</h2>
<p>页面中部已实现 6 个统计卡片:</p>
<h3>5.1 总库位数</h3>
<p>计算方式:</p>
<ul><li><code>立库行数 * 列数</code></li></ul>
<h3>5.2 有物料有托盘</h3>
<p>统计状态值为 <code>2</code> 的库位数量。</p>
<h3>5.3 有托盘无物料</h3>
<p>统计状态值为 <code>1</code> 的库位数量。</p>
<h3>5.4 空闲库位</h3>
<p>统计状态值为 <code>0</code> 的库位数量。</p>
<h3>5.5 使用率</h3>
<p>计算方式:</p>
<ul><li><code>(有物料有托盘 + 有托盘无物料) / 总库位数 * 100%</code></li></ul>
<p>保留 1 位小数。</p>
<h3>5.6 数据行数</h3>
<p>显示当前 <code>warehouseData.length</code>,即当前已加载的立库行数。</p>
<h2>6. 状态图例说明</h2>
<p>页面已实现状态图例,用于帮助用户理解库位颜色含义:</p>
<ul><li>绿色:有物料有托盘</li><li>橙色:有托盘无物料</li><li>灰色:空库位</li><li>蓝色边框:当前选中库位</li></ul>
<p>另外,代码中还支持:</p>
<ul><li>蓝色底:锁库(状态值 <code>9</code></li></ul>
<p>该状态在单元格样式和状态文字中已实现,但图例区未单独展示“锁库”说明。</p>
<h2>7. 立库可视化表格区说明</h2>
<p>这是页面的核心区域。</p>
<h3>7.1 展示形式</h3>
<p>页面使用原生 <code>table</code> 绘制立库矩阵,不是 <code>el-table</code></p>
<p>结构包含:</p>
<ul><li>表头列号</li><li>左侧行层标识</li><li>中间库位矩阵单元格</li></ul>
<h3>7.2 行列组织方式</h3>
<h4></h4>
<p>每一行对应一个“行-层”值,例如:</p>
<ul><li><code>01-01</code></li><li><code>03-12</code></li></ul>
<p>页面左侧固定显示该行层编号。</p>
<h4></h4>
<p>每列按照数字列号展示,从 <code>1</code><code>columnCount</code></p>
<p><code>columnCount</code> 的计算规则为:</p>
<ul><li>读取首行对象字段</li><li>排除 <code>行-层</code></li><li>其余字段个数即为列数</li></ul>
<h3>7.3 单元格状态值</h3>
<p>当前库位单元格支持以下状态:</p>
<table><thead><tr><th>状态值</th><th>含义</th><th>前端显示简称</th><th>颜色</th></tr></thead><tbody><tr><td><code>0</code></td><td>空库位</td><td></td><td>灰色</td></tr><tr><td><code>1</code></td><td>有托盘无物料</td><td></td><td>橙色</td></tr><tr><td><code>2</code></td><td>有物料有托盘</td><td></td><td>绿色</td></tr><tr><td><code>9</code></td><td>锁库/在存取</td><td></td><td>蓝色</td></tr></tbody></table>
<h3>7.4 单元格交互效果</h3>
<p>当前已实现以下交互:</p>
<ul><li>鼠标悬停单元格时放大</li><li>鼠标悬停显示状态浮层</li><li>点击后高亮选中库位</li><li>点击后高亮整行对应的行层</li><li>点击某列后,对应列表头也会高亮</li></ul>
<h3>7.5 空数据处理</h3>
<p><code>warehouseData.length === 0</code> 时,页面显示空白占位区域。</p>
<p>说明:</p>
<ul><li>代码里曾预留 <code>el-empty</code> 结构,但目前已注释,没有实际“暂无数据”文案和“加载示例数据”按钮。</li></ul>
<h2>8. 分页功能说明</h2>
<p>页面底部已实现分页控件,条件为:</p>
<ul><li><code>totalPages &gt; 1</code> 时显示</li></ul>
<p>分页能力包括:</p>
<ul><li>查看总数</li><li>切换每页条数</li><li>上一页/下一页</li><li>页码跳转</li><li>直接输入页码跳转</li></ul>
<p>每页可选条数:</p>
<ul><li><code>5</code></li><li><code>8</code></li><li><code>10</code></li><li><code>15</code></li></ul>
<p>说明:</p>
<ul><li>分页针对的是“行数据”,不是单元格。</li><li>也就是一次显示若干个“行-层”。</li></ul>
<h2>9. 库位详情弹窗说明</h2>
<p><strong>图 3库位详情弹窗示意</strong></p>
<figure><img src="assets/warehouse-visualization/detail.png" alt="库位详情弹窗" /></figure>
<p>点击非物料状态库位时,会弹出“库位详情”窗口。</p>
<p>触发条件:</p>
<ul><li>单元格状态不等于 <code>2</code></li></ul>
<h3>9.1 弹窗标题</h3>
<p>格式:</p>
<ul><li><code>库位详情 + 库位编号</code></li></ul>
<p>例如:</p>
<ul><li><code>库位详情 03-12-01</code></li></ul>
<h3>9.2 弹窗内容</h3>
<p>当前弹窗内容为库位状态说明文本,即 <code>selectedCellDetail.description</code></p>
<p>不同状态说明如下:</p>
<ul><li><code>0</code>:空闲库位,可存放物料</li><li><code>1</code>:有托盘但无物料,需补充物料</li><li><code>2</code>:有托盘且有物料,正常使用中</li><li><code>9</code>:在存取,库位已锁</li></ul>
<h3>9.3 当前实现特点</h3>
<p>目前该弹窗属于轻量说明弹窗,展示内容较简洁,没有进一步展示:</p>
<ul><li>仓库编号</li><li>创建时间</li><li>最近操作人</li><li>任务锁定来源</li></ul>
<p>这些字段在前端对象构造里有部分预留,但未在弹窗模板中完整展示。</p>
<h2>10. 物料信息弹窗说明</h2>
<p><strong>图 4物料信息弹窗示意</strong></p>
<figure><img src="assets/warehouse-visualization/material.png" alt="物料信息弹窗" /></figure>
<p>点击状态为 <code>2</code> 的库位时,会弹出“库位信息”窗口。</p>
<p>触发条件:</p>
<ul><li>单元格状态等于 <code>2</code></li></ul>
<h3>10.1 弹窗标题</h3>
<p>格式:</p>
<ul><li><code>库位信息 + pno</code></li></ul>
<p>即使用返回数据第一条的库位编号作为标题显示。</p>
<h3>10.2 数据来源</h3>
<p>点击单元格后会调用 <code>StockDetail(rowLayer, column)</code>,查询数据库视图:</p>
<ul><li><code>StockDetailView</code></li></ul>
<p>查询方式为手写 SQL</p>
<ul><li><code>select ... from StockDetailView where pno='xx-xx-xx'</code></li></ul>
<h3>10.3 库位编号拼接规则</h3>
<p>点击单元格时,前端会把界面上的 <code>行-层</code> 和列号转换为后端查询用的 <code>pno</code></p>
<ol><li>将列号补足 2 位</li><li><code>行-层</code><code>-</code> 拆分</li><li><code>行-列-层</code> 重新拼装</li></ol>
<p>例如:</p>
<ul><li>页面行层:<code>03-01</code></li><li>点击列:<code>12</code></li><li>后端查询编码:<code>03-12-01</code></li></ul>
<h3>10.4 弹窗字段</h3>
<p>当前弹窗表格已展示以下字段:</p>
<ul><li>物料编号 <code>mno</code></li><li>物料名称 <code>mname</code></li><li>图号 <code>spec</code></li><li>型号 <code>thao</code></li><li>库存数量 <code>qty</code></li><li>锁定库存 <code>lockqty</code></li><li>批次 <code>batch</code></li></ul>
<p>其中数量字段已实现:</p>
<ul><li>保留三位小数格式化</li><li>大于 0 时显示强调色</li><li>在数量下方显示单位 <code>munit</code></li></ul>
<h3>10.5 返回数据兼容处理</h3>
<p>前端对接口返回做了多种兼容判断,支持以下几类格式:</p>
<ol><li><code>response.data</code> 直接就是数组</li><li><code>response.data.data</code> 是数组</li><li><code>response.data.rows</code> 是数组</li><li><code>response.data</code> 是单对象,且对象里包含 <code>mno</code><code>pno</code></li></ol>
<p>说明:</p>
<ul><li>这意味着当前页面对后端返回结构的容错较强。</li></ul>
<h3>10.6 无数据提示</h3>
<p>如果接口无返回数据,则提示:</p>
<ul><li><code>未查询到数据</code></li></ul>
<h2>11. 后端接口与数据来源说明</h2>
<p>当前页面实际对接了两类立库相关数据。</p>
<h3>11.1 立库布局/状态数据</h3>
<p>请求方法:</p>
<ul><li><code>GetPosition(hang)</code></li></ul>
<p>请求构造:</p>
<ul><li><code>this.CreateData('11', 'GetPositionView', param)</code></li></ul>
<p>参数内容:</p>
<ul><li><code>hang</code>:巷道编号</li><li><code>potvar</code>:空字符串</li><li><code>tt</code>:固定值 <code>1</code></li></ul>
<p>调用方式:</p>
<ul><li><code>this.ExecDatabase1(Data)</code></li></ul>
<p>返回结果用途:</p>
<ul><li>写入 <code>defaultWarehouseData</code></li><li>再触发 <code>refreshData()</code></li><li>最终更新主界面矩阵</li></ul>
<h3>11.2 库位物料明细数据</h3>
<p>请求方法:</p>
<ul><li><code>StockDetail(rowLayer, column)</code></li></ul>
<p>请求方式:</p>
<ul><li>直接构造 SQL 查询 <code>StockDetailView</code></li></ul>
<p>调用方式:</p>
<ul><li><code>this.ExecDatabase1(Data)</code></li></ul>
<p>返回结果用途:</p>
<ul><li>写入 <code>materialInfoList</code></li><li>打开物料信息弹窗</li></ul>
<h3>11.3 接口地址配置</h3>
<p>立库接口使用独立配置地址:</p>
<ul><li><code>static/config.js</code></li><li><code>url1 = http://192.168.1.27:10006</code></li><li><code>request_config1 = url1 + '/submit/MESCommonBase.ashx'</code></li></ul>
<p>说明:</p>
<ul><li>这表明立库数据并未走默认 MES 主接口,而是走单独的立库数据库服务入口。</li></ul>
<h2>12. 已实现功能清单</h2>
<p>基于当前页面代码,已经实现并可确认的功能如下。</p>
<h3>12.1 页面展示类功能</h3>
<ul><li>立库矩阵可视化展示</li><li>行层与列号展示</li><li>库位状态颜色区分</li><li>文字/图标两种展示模式切换</li><li>统计卡片展示</li><li>状态图例展示</li><li>分页展示</li></ul>
<h3>12.2 数据查询类功能</h3>
<ul><li>默认加载立库数据</li><li>按巷道重新查询立库数据</li><li>点击状态为 <code>2</code> 的库位查询物料明细</li><li>点击非 <code>2</code> 状态库位查看库位状态说明</li></ul>
<h3>12.3 筛选与交互类功能</h3>
<ul><li>按行层筛选</li><li>点击搜索按钮重新按巷道加载</li><li>重置筛选条件</li><li>刷新当前缓存数据</li><li>鼠标悬停显示状态提示</li><li>单元格选中高亮</li><li>行高亮</li><li>列高亮</li></ul>
<h3>12.4 数据展示增强功能</h3>
<ul><li>数量保留 3 位小数</li><li>锁定库存单独显示</li><li>单位显示</li><li>物料列表支持多条记录显示</li><li>接口返回结构兼容处理</li></ul>
<h2>13. 代码中存在但当前未完整启用的能力</h2>
<p>以下能力在代码中已有部分实现或预留,但从当前模板和实际入口看,尚未完整开放为可见功能。</p>
<h3>13.1 状态筛选</h3>
<p>页面 <code>data</code> 中存在:</p>
<ul><li><code>statusFilter</code></li></ul>
<p>筛选逻辑中也保留了状态筛选分支,但顶部没有对应状态下拉框,因此用户当前无法直接按状态筛选。</p>
<h3>13.2 关键字搜索</h3>
<p>页面 <code>data</code> 中存在:</p>
<ul><li><code>searchKeyword</code></li></ul>
<p>并保留了按列搜索的逻辑入口,但模板中没有对应输入框,因此当前不构成实际功能。</p>
<h3>13.3 图表分析功能</h3>
<p>代码中已实现:</p>
<ul><li>状态分布饼图</li><li>行列使用率图</li><li>ECharts 初始化与销毁</li></ul>
<p>但模板中没有对应图表容器,也没有实际启用 <code>warehouse-analysis</code> 页签,因此当前属于预留代码。</p>
<h3>13.4 数据导出功能</h3>
<p>代码中已实现 <code>exportWarehouseData()</code>,可以把当前 <code>warehouseData</code> 导出为 JSON 文件。</p>
<p>但当前页面模板没有挂出“导出”按钮,因此用户在界面上无法直接使用。</p>
<h3>13.5 打印报表功能</h3>
<p>代码中存在 <code>printWarehouseReport()</code>,但当前只是提示“功能开发中”,没有实际打印逻辑。</p>
<h3>13.6 物料标签打印</h3>
<p>代码中存在 <code>printMaterialInfo()</code>,当前同样仅提示“功能开发中”。</p>
<h3>13.7 焦点定位功能</h3>
<p>代码中存在 <code>focusOnCell(rowLayer, column)</code>,可自动滚动到指定单元格位置。</p>
<p>但当前页面没有表格明细区或其他入口去调用它,因此属于预留能力。</p>
<h2>14. 现有业务规则总结</h2>
<p>从当前实现可归纳出以下业务规则。</p>
<h3>14.1 单元格状态规则</h3>
<ul><li><code>2</code> 代表该库位已有物料,应进入物料明细查询流程</li><li><code>2</code> 状态不查物料,进入库位说明流程</li><li><code>9</code> 表示库位正在存取或已锁定</li></ul>
<h3>14.2 巷道数据规则</h3>
<ul><li>页面默认在 <code>mounted</code> 时调用 <code>GetPosition(1)</code>,默认先加载第一巷道</li><li>用户再通过顶部选择器切换其他巷道</li></ul>
<h3>14.3 编码转换规则</h3>
<p>界面展示编码与后端查询编码不是同一个结构:</p>
<ul><li>界面主要使用 <code>行-层</code></li><li>后端明细查询使用 <code>行-列-层</code></li></ul>
<p>因此点击库位时必须做一次编号重组。</p>
<h2>15. 使用流程说明</h2>
<p>典型操作流程如下:</p>
<ol><li>打开立库可视化页面</li><li>系统默认加载第一巷道数据</li><li>页面显示统计信息、图例和库位矩阵</li><li>用户可切换巷道并点击“搜索”</li><li>用户可按“行-层”筛选关注区域</li><li>用户可切换文字/图标显示模式</li><li>鼠标悬停可查看库位状态提示</li><li>点击空库位/托盘位/锁定位,查看库位说明</li><li>点击有物料库位,查看该库位物料明细</li><li>需要恢复视图时可点击“重置”或“刷新数据”</li></ol>
<h2>16. 当前界面优点</h2>
<p>从现有实现看,该页面已经具备以下优点:</p>
<ul><li>视觉上能直接反映立库占用状态</li><li>巷道切换清晰</li><li>物料库位点击查询路径明确</li><li>状态颜色和图标表达直观</li><li>对后端返回结构适配较灵活</li><li>前端交互响应比较轻量</li></ul>
<h2>17. 当前界面限制</h2>
<p>从代码现状看,也存在一些限制:</p>
<ul><li>状态筛选未开放界面入口</li><li>关键字搜索未开放界面入口</li><li>导出功能未开放按钮</li><li>图表分析功能未在页面中启用</li><li>空数据时缺少明确提示文案</li><li>锁库状态没有放入图例区</li><li>库位详情弹窗信息量偏少</li><li>刷新逻辑更多是前端缓存刷新,不是强制重新拉取所有条件数据</li></ul>
<h2>18. 维护与扩展建议</h2>
<p>如果后续继续完善该页面,建议优先考虑以下方向:</p>
<ol><li>增加状态筛选下拉框,补齐 <code>statusFilter</code> 实际入口</li><li>增加库位搜索输入框,支持直接定位 <code>行-层-列</code></li><li>在界面上开放“导出 JSON”按钮</li><li>补上锁库状态图例</li><li>完善空数据占位提示</li><li>把图表分析页签正式接入模板</li><li>在库位详情弹窗中增加更多业务字段</li><li>将明细查询 SQL 封装为标准后端接口名,减少前端直接拼 SQL 的耦合</li></ol>
<h2>19. 结论</h2>
<p>当前“立库详细界面”已经不是简单静态展示,而是一个具备实际业务查询能力的立库可视化页面。它已经实现了:</p>
<ul><li>按巷道加载立库状态</li><li>立库矩阵可视化展示</li><li>库位状态识别</li><li>库位详情查看</li><li>库位物料明细查询</li><li>筛选、分页、刷新、显示模式切换等基础交互</li></ul>
<p>同时,代码中还保留了导出、图表分析、状态筛选、定位等扩展能力,说明该页面已经具备进一步升级为完整“立库监控与查询中心”的基础。</p>
</body>
</html>

View File

@@ -0,0 +1,672 @@
# JY1.0 立库详细界面功能说明书
## 1. 文档目的
本文档用于整理 JY1.0 项目中“立库查询/立库可视化”组件目前已经实现的界面结构、后端数据来源、交互逻辑和现有功能范围,便于后续培训、验收、维护和功能扩展。
本文档依据当前前端实现整理,核心代码位置如下:
- `src/views/WarehouseManagement/WarehouseVisualization/index.vue`
- `static/config.js`
## 2. 页面定位
立库详细界面当前对应的页面名称为:
- 页面组件名:`WarehouseVisualizationSystem`
- 页面标签名:`立库可视化`
- 目录位置:`src/views/WarehouseManagement/WarehouseVisualization/index.vue`
该页面用于展示立体仓库库位占用情况,并支持按巷道查看、按行层筛选、单击库位查看库位详情或物料明细。
## 3. 页面总体结构
**图 1立库可视化主界面**
![立库可视化主界面](assets/warehouse-visualization/main.png)
页面整体采用标准的 `div.app-container + el-card` 布局,主体包含以下区域:
1. 顶部筛选与操作区
2. 统计信息卡片区
3. 状态图例区
4. 立库可视化表格区
5. 分页区
6. 库位详情弹窗
7. 物料信息弹窗
当前页面只有一个主标签页:
- `立库可视化`
代码中保留了图表分析相关逻辑,但当前模板中未实际启用“分析”页签。
## 4. 顶部筛选与操作区说明
**图 2筛选区与显示模式切换示意**
![筛选区与显示模式切换](assets/warehouse-visualization/filter.png)
顶部区域已经实现以下控件:
### 4.1 巷道筛选
控件类型:`el-select`
可选项固定为:
- 第一巷道
- 第二巷道
- 第三巷道
- 第四巷道
对应值为:
- `1`
- `2`
- `3`
- `4`
用途:
- 用户选择巷道后,点击“搜索”,页面会调用立库接口重新获取该巷道数据。
### 4.2 行层筛选
控件类型:`el-select`
用途:
- 根据当前已加载的立库数据,自动生成“行-层”下拉选项。
- 用户可筛选某一行层,只显示对应行数据。
特点:
- 选项从接口返回数据动态提取。
- 会自动去重。
- 按数值倒序排列。
### 4.3 搜索按钮
按钮文字:`搜索`
作用:
- 重置当前页码为第一页。
- 先执行前端筛选。
- 再根据当前巷道值调用 `GetPosition` 获取最新立库数据。
说明:
- 当前“搜索”动作的核心是按巷道重新取数。
- 代码里存在 `searchKeyword` 变量,但页面上没有对应输入框,因此当前没有“按列关键字搜索”的实际入口。
### 4.4 重置按钮
按钮文字:`重置`
作用:
- 清空状态筛选值
- 清空行层筛选值
- 清空搜索关键字
- 页码回到第一页
- 重新执行前端筛选
页面提示:
- 执行后弹出“筛选条件已重置”。
注意:
- 当前重置不会主动重新按巷道取数,只是重置前端筛选状态。
### 4.5 刷新数据按钮
按钮文字:`刷新数据`
作用:
- 触发加载动画
- 500ms 后重新装载当前 `defaultWarehouseData`
- 更新时间戳
- 弹出“数据刷新成功”
说明:
- 刷新逻辑最终仍依赖最近一次 `GetPosition` 返回并写入的 `defaultWarehouseData`
- 如果前面已经切换过巷道,刷新展示的是当前缓存的该次结果。
### 4.6 显示模式切换开关
控件类型:`el-switch`
两种模式:
- 显示文字:`text`
- 显示图标:`icon`
作用:
- 控制库位单元格显示简称文字还是图标。
切换效果:
- 文字模式显示:`物 / 盘 / 空 / 锁`
- 图标模式显示:
- 锁库:`el-icon-lock`
- 有物料有托盘:`el-icon-box`
- 有托盘无物料:`el-icon-tickets`
- 空库位:`el-icon-minus`
页面提示:
- 切换后会提示当前已切换到文字模式或图标模式。
## 5. 统计信息卡片区说明
页面中部已实现 6 个统计卡片:
### 5.1 总库位数
计算方式:
- `立库行数 * 列数`
### 5.2 有物料有托盘
统计状态值为 `2` 的库位数量。
### 5.3 有托盘无物料
统计状态值为 `1` 的库位数量。
### 5.4 空闲库位
统计状态值为 `0` 的库位数量。
### 5.5 使用率
计算方式:
- `(有物料有托盘 + 有托盘无物料) / 总库位数 * 100%`
保留 1 位小数。
### 5.6 数据行数
显示当前 `warehouseData.length`,即当前已加载的立库行数。
## 6. 状态图例说明
页面已实现状态图例,用于帮助用户理解库位颜色含义:
- 绿色:有物料有托盘
- 橙色:有托盘无物料
- 灰色:空库位
- 蓝色边框:当前选中库位
另外,代码中还支持:
- 蓝色底:锁库(状态值 `9`
该状态在单元格样式和状态文字中已实现,但图例区未单独展示“锁库”说明。
## 7. 立库可视化表格区说明
这是页面的核心区域。
### 7.1 展示形式
页面使用原生 `table` 绘制立库矩阵,不是 `el-table`
结构包含:
- 表头列号
- 左侧行层标识
- 中间库位矩阵单元格
### 7.2 行列组织方式
#### 行
每一行对应一个“行-层”值,例如:
- `01-01`
- `03-12`
页面左侧固定显示该行层编号。
#### 列
每列按照数字列号展示,从 `1``columnCount`
`columnCount` 的计算规则为:
- 读取首行对象字段
- 排除 `行-层`
- 其余字段个数即为列数
### 7.3 单元格状态值
当前库位单元格支持以下状态:
| 状态值 | 含义 | 前端显示简称 | 颜色 |
| --- | --- | --- | --- |
| `0` | 空库位 | 空 | 灰色 |
| `1` | 有托盘无物料 | 盘 | 橙色 |
| `2` | 有物料有托盘 | 物 | 绿色 |
| `9` | 锁库/在存取 | 锁 | 蓝色 |
### 7.4 单元格交互效果
当前已实现以下交互:
- 鼠标悬停单元格时放大
- 鼠标悬停显示状态浮层
- 点击后高亮选中库位
- 点击后高亮整行对应的行层
- 点击某列后,对应列表头也会高亮
### 7.5 空数据处理
`warehouseData.length === 0` 时,页面显示空白占位区域。
说明:
- 代码里曾预留 `el-empty` 结构,但目前已注释,没有实际“暂无数据”文案和“加载示例数据”按钮。
## 8. 分页功能说明
页面底部已实现分页控件,条件为:
- `totalPages > 1` 时显示
分页能力包括:
- 查看总数
- 切换每页条数
- 上一页/下一页
- 页码跳转
- 直接输入页码跳转
每页可选条数:
- `5`
- `8`
- `10`
- `15`
说明:
- 分页针对的是“行数据”,不是单元格。
- 也就是一次显示若干个“行-层”。
## 9. 库位详情弹窗说明
**图 3库位详情弹窗示意**
![库位详情弹窗](assets/warehouse-visualization/detail.png)
点击非物料状态库位时,会弹出“库位详情”窗口。
触发条件:
- 单元格状态不等于 `2`
### 9.1 弹窗标题
格式:
- `库位详情 + 库位编号`
例如:
- `库位详情 03-12-01`
### 9.2 弹窗内容
当前弹窗内容为库位状态说明文本,即 `selectedCellDetail.description`
不同状态说明如下:
- `0`:空闲库位,可存放物料
- `1`:有托盘但无物料,需补充物料
- `2`:有托盘且有物料,正常使用中
- `9`:在存取,库位已锁
### 9.3 当前实现特点
目前该弹窗属于轻量说明弹窗,展示内容较简洁,没有进一步展示:
- 仓库编号
- 创建时间
- 最近操作人
- 任务锁定来源
这些字段在前端对象构造里有部分预留,但未在弹窗模板中完整展示。
## 10. 物料信息弹窗说明
**图 4物料信息弹窗示意**
![物料信息弹窗](assets/warehouse-visualization/material.png)
点击状态为 `2` 的库位时,会弹出“库位信息”窗口。
触发条件:
- 单元格状态等于 `2`
### 10.1 弹窗标题
格式:
- `库位信息 + pno`
即使用返回数据第一条的库位编号作为标题显示。
### 10.2 数据来源
点击单元格后会调用 `StockDetail(rowLayer, column)`,查询数据库视图:
- `StockDetailView`
查询方式为手写 SQL
- `select ... from StockDetailView where pno='xx-xx-xx'`
### 10.3 库位编号拼接规则
点击单元格时,前端会把界面上的 `行-层` 和列号转换为后端查询用的 `pno`
1. 将列号补足 2 位
2.`行-层``-` 拆分
3.`行-列-层` 重新拼装
例如:
- 页面行层:`03-01`
- 点击列:`12`
- 后端查询编码:`03-12-01`
### 10.4 弹窗字段
当前弹窗表格已展示以下字段:
- 物料编号 `mno`
- 物料名称 `mname`
- 图号 `spec`
- 型号 `thao`
- 库存数量 `qty`
- 锁定库存 `lockqty`
- 批次 `batch`
其中数量字段已实现:
- 保留三位小数格式化
- 大于 0 时显示强调色
- 在数量下方显示单位 `munit`
### 10.5 返回数据兼容处理
前端对接口返回做了多种兼容判断,支持以下几类格式:
1. `response.data` 直接就是数组
2. `response.data.data` 是数组
3. `response.data.rows` 是数组
4. `response.data` 是单对象,且对象里包含 `mno``pno`
说明:
- 这意味着当前页面对后端返回结构的容错较强。
### 10.6 无数据提示
如果接口无返回数据,则提示:
- `未查询到数据`
## 11. 后端接口与数据来源说明
当前页面实际对接了两类立库相关数据。
### 11.1 立库布局/状态数据
请求方法:
- `GetPosition(hang)`
请求构造:
- `this.CreateData('11', 'GetPositionView', param)`
参数内容:
- `hang`:巷道编号
- `potvar`:空字符串
- `tt`:固定值 `1`
调用方式:
- `this.ExecDatabase1(Data)`
返回结果用途:
- 写入 `defaultWarehouseData`
- 再触发 `refreshData()`
- 最终更新主界面矩阵
### 11.2 库位物料明细数据
请求方法:
- `StockDetail(rowLayer, column)`
请求方式:
- 直接构造 SQL 查询 `StockDetailView`
调用方式:
- `this.ExecDatabase1(Data)`
返回结果用途:
- 写入 `materialInfoList`
- 打开物料信息弹窗
### 11.3 接口地址配置
立库接口使用独立配置地址:
- `static/config.js`
- `url1 = http://192.168.1.27:10006`
- `request_config1 = url1 + '/submit/MESCommonBase.ashx'`
说明:
- 这表明立库数据并未走默认 MES 主接口,而是走单独的立库数据库服务入口。
## 12. 已实现功能清单
基于当前页面代码,已经实现并可确认的功能如下。
### 12.1 页面展示类功能
- 立库矩阵可视化展示
- 行层与列号展示
- 库位状态颜色区分
- 文字/图标两种展示模式切换
- 统计卡片展示
- 状态图例展示
- 分页展示
### 12.2 数据查询类功能
- 默认加载立库数据
- 按巷道重新查询立库数据
- 点击状态为 `2` 的库位查询物料明细
- 点击非 `2` 状态库位查看库位状态说明
### 12.3 筛选与交互类功能
- 按行层筛选
- 点击搜索按钮重新按巷道加载
- 重置筛选条件
- 刷新当前缓存数据
- 鼠标悬停显示状态提示
- 单元格选中高亮
- 行高亮
- 列高亮
### 12.4 数据展示增强功能
- 数量保留 3 位小数
- 锁定库存单独显示
- 单位显示
- 物料列表支持多条记录显示
- 接口返回结构兼容处理
## 13. 代码中存在但当前未完整启用的能力
以下能力在代码中已有部分实现或预留,但从当前模板和实际入口看,尚未完整开放为可见功能。
### 13.1 状态筛选
页面 `data` 中存在:
- `statusFilter`
筛选逻辑中也保留了状态筛选分支,但顶部没有对应状态下拉框,因此用户当前无法直接按状态筛选。
### 13.2 关键字搜索
页面 `data` 中存在:
- `searchKeyword`
并保留了按列搜索的逻辑入口,但模板中没有对应输入框,因此当前不构成实际功能。
### 13.3 图表分析功能
代码中已实现:
- 状态分布饼图
- 行列使用率图
- ECharts 初始化与销毁
但模板中没有对应图表容器,也没有实际启用 `warehouse-analysis` 页签,因此当前属于预留代码。
### 13.4 数据导出功能
代码中已实现 `exportWarehouseData()`,可以把当前 `warehouseData` 导出为 JSON 文件。
但当前页面模板没有挂出“导出”按钮,因此用户在界面上无法直接使用。
### 13.5 打印报表功能
代码中存在 `printWarehouseReport()`,但当前只是提示“功能开发中”,没有实际打印逻辑。
### 13.6 物料标签打印
代码中存在 `printMaterialInfo()`,当前同样仅提示“功能开发中”。
### 13.7 焦点定位功能
代码中存在 `focusOnCell(rowLayer, column)`,可自动滚动到指定单元格位置。
但当前页面没有表格明细区或其他入口去调用它,因此属于预留能力。
## 14. 现有业务规则总结
从当前实现可归纳出以下业务规则。
### 14.1 单元格状态规则
- `2` 代表该库位已有物料,应进入物料明细查询流程
-`2` 状态不查物料,进入库位说明流程
- `9` 表示库位正在存取或已锁定
### 14.2 巷道数据规则
- 页面默认在 `mounted` 时调用 `GetPosition(1)`,默认先加载第一巷道
- 用户再通过顶部选择器切换其他巷道
### 14.3 编码转换规则
界面展示编码与后端查询编码不是同一个结构:
- 界面主要使用 `行-层`
- 后端明细查询使用 `行-列-层`
因此点击库位时必须做一次编号重组。
## 15. 使用流程说明
典型操作流程如下:
1. 打开立库可视化页面
2. 系统默认加载第一巷道数据
3. 页面显示统计信息、图例和库位矩阵
4. 用户可切换巷道并点击“搜索”
5. 用户可按“行-层”筛选关注区域
6. 用户可切换文字/图标显示模式
7. 鼠标悬停可查看库位状态提示
8. 点击空库位/托盘位/锁定位,查看库位说明
9. 点击有物料库位,查看该库位物料明细
10. 需要恢复视图时可点击“重置”或“刷新数据”
## 16. 当前界面优点
从现有实现看,该页面已经具备以下优点:
- 视觉上能直接反映立库占用状态
- 巷道切换清晰
- 物料库位点击查询路径明确
- 状态颜色和图标表达直观
- 对后端返回结构适配较灵活
- 前端交互响应比较轻量
## 17. 当前界面限制
从代码现状看,也存在一些限制:
- 状态筛选未开放界面入口
- 关键字搜索未开放界面入口
- 导出功能未开放按钮
- 图表分析功能未在页面中启用
- 空数据时缺少明确提示文案
- 锁库状态没有放入图例区
- 库位详情弹窗信息量偏少
- 刷新逻辑更多是前端缓存刷新,不是强制重新拉取所有条件数据
## 18. 维护与扩展建议
如果后续继续完善该页面,建议优先考虑以下方向:
1. 增加状态筛选下拉框,补齐 `statusFilter` 实际入口
2. 增加库位搜索输入框,支持直接定位 `行-层-列`
3. 在界面上开放“导出 JSON”按钮
4. 补上锁库状态图例
5. 完善空数据占位提示
6. 把图表分析页签正式接入模板
7. 在库位详情弹窗中增加更多业务字段
8. 将明细查询 SQL 封装为标准后端接口名,减少前端直接拼 SQL 的耦合
## 19. 结论
当前“立库详细界面”已经不是简单静态展示,而是一个具备实际业务查询能力的立库可视化页面。它已经实现了:
- 按巷道加载立库状态
- 立库矩阵可视化展示
- 库位状态识别
- 库位详情查看
- 库位物料明细查询
- 筛选、分页、刷新、显示模式切换等基础交互
同时,代码中还保留了导出、图表分析、状态筛选、定位等扩展能力,说明该页面已经具备进一步升级为完整“立库监控与查询中心”的基础。

Binary file not shown.

View File

@@ -0,0 +1,366 @@
# JY1.0 采购件入库查询卡顿修改方案
## 1. 背景说明
仓储管理模块页面 `src/views/WarehouseManagement/PurchasePartsStorage/index.vue` 在执行查询操作时,存在页面明显卡顿、浏览器长时间无响应、严重时看起来像页面崩溃的问题。
本方案用于整理当前排查结论、明确问题成因,并提出分阶段修改建议。本文档只给出修改方案,不直接改动代码。
## 2. 问题现象
当前页面包含采购件入库、自制件入库、外协到货等多个页签,其中采购件入库页签在选择供应商或合同后会自动执行查询。
现场表现主要包括:
- 点击查询后页面长时间卡住。
- 表格数据返回后界面渲染明显变慢。
- 数据量较大时浏览器出现假死或无响应。
- 用户主观感受为“查询就崩”。
## 3. 涉及文件
- `src/views/WarehouseManagement/PurchasePartsStorage/index.vue`
- `src/main.js`
- `src/utils/request.js`
重点排查方法:
- `searchTable1()`
- `searchTable3()`
- `getSummaries()`
- `getSummaries1()`
- `handleSelectionChange()`
- `recalcSelection()`
## 4. 原因分析
### 4.1 采购件入库查询未分页,前端一次性加载全量数据
`searchTable1()` 中,查询请求通过 `CreateData('11', ..., param)` 发送,但未传入分页参数。
这会带来两个问题:
- 后端可能直接返回该供应商或合同下的全部数据。
- 前端需要一次性渲染整张大表,数据量较大时会明显拖慢页面。
相比之下,自制件入库的 `searchTable3()` 已传入 `pageSize1``pageCurrent1`,说明当前页面内部本身就存在两种不同的数据加载策略,采购件入库部分的实现明显更重。
### 4.2 查询完成后自动逐行全选,进一步放大渲染压力
`searchTable1()` 中,查询成功后会遍历 `tableData1`,并对满足条件的行逐个执行:
- `this.$refs.table.toggleRowSelection(row, true)`
虽然代码里使用了 `_suppressSelectionChange` 来避免自定义选中计算被重复触发,但 Element UI 表格内部仍需要为每一行更新选中状态、复选框状态和相关渲染结果。
当查询结果很多时,这段“逐行自动勾选”的逻辑会成为非常重的同步操作,容易造成主线程阻塞。
### 4.3 表格每行挂载多个重量级组件,导致大表渲染成本过高
采购件入库与自制件入库表格中,每行都包含较多交互组件,例如:
- `el-select`
- `el-input-number`
- 选择列
- 按钮列
其中 `el-input-number``el-select` 在大数据表格中本身就属于渲染和响应式开销较高的组件。若查询结果达到数百行甚至更多组件实例数、watcher 数量、DOM 节点数量都会迅速增大。
### 4.4 表格启用汇总行,汇总函数会对数据做多轮遍历
页面使用了:
- `show-summary`
- `summary-method`
`getSummaries()``getSummaries1()` 中对多个列执行了多次 `map + reduce` 计算。
这意味着:
- 查询后首次渲染要做汇总计算。
- 表格数据变化、选中变化、输入变化后,汇总有机会被再次触发。
- 当数据量大时,汇总计算会持续放大卡顿感。
### 4.5 查询动作与下拉联动查询存在叠加请求
例如自制件入库页签中,订单切换时会同时触发:
- `searchTable3()`
- `getnumberSelection()`
这类逻辑虽然不是采购件入库“卡死”的唯一主因,但会增加同一时间段内的请求数和页面状态更新次数,让用户更容易感知到页面迟滞。
### 4.6 当前超时配置较大,不容易快速暴露性能问题
`src/utils/request.js` 中 Axios `timeout` 设置为 `1500000`
这会导致:
- 如果接口慢,页面会长时间停留在等待状态。
- 如果前端渲染本身很重,用户更容易把“卡顿”感知为“页面崩溃”。
这不是根因,但会放大问题表现。
## 5. 结论判断
综合代码结构,当前“查询就卡页面崩溃”的高概率原因不是单一点故障,而是以下因素叠加:
1. 采购件入库查询无分页。
2. 查询完成后自动逐行勾选可入库数据。
3. 大表格中每行挂载多个重量级输入组件。
4. 汇总行对全量数据重复做统计计算。
其中,优先级最高、最值得首先处理的是前两项。
## 6. 修改目标
本次优化建议以“先止血,再优化体验”为原则,目标如下:
- 将查询后的页面卡顿控制在可接受范围内。
- 避免浏览器因大数据量渲染出现假死。
- 保持现有业务流程基本不变。
- 尽量复用项目现有分页、查询、表格处理模式。
- 将改动范围控制在 `PurchasePartsStorage` 页面内部,避免影响其他模块。
## 7. 修改方案
### 7.1 方案一:为采购件入库查询增加分页
这是首要方案。
#### 修改思路
- 为采购件入库表格引入与自制件入库一致的分页机制。
-`searchTable1()` 中调用 `CreateData()` 时补充分页参数。
- 页面新增采购件入库对应的分页组件与分页状态。
- 后端查询过程若已支持分页,则直接接入。
- 后端若尚未支持分页,需要同步补充分页查询能力。
#### 预期收益
- 避免一次性返回几百或几千条数据。
- 大幅降低首屏渲染压力。
- 为后续汇总优化、选择优化提供基础。
#### 风险点
- 需要确认后端 `仓储管理_采购入库合同明细_查询` 是否支持分页返回 `rows/total`
- 如果当前接口只返回普通数组,前后端都需要调整返回结构。
### 7.2 方案二:取消查询后自动全选所有可入库行
这是与分页并列的高优先级方案。
#### 修改思路
- 查询完成后不再自动执行 `toggleRowSelection` 遍历全表。
- 保留手工勾选模式,由用户自主选择入库数据。
- 如业务上确实需要批量选择,可增加单独按钮,例如“勾选当前页可入库项”。
- 如仍需保留自动选择能力,建议限制为只处理当前页数据,而不是全量结果。
#### 预期收益
- 避免一次查询后产生大量同步 UI 更新。
- 显著降低 Element 表格内部状态维护成本。
- 使查询动作本身更纯粹,减少联动副作用。
#### 风险点
- 用户操作习惯会发生轻微变化。
- 若一线人员依赖默认全选,需要补充一个显式批量勾选按钮作为替代。
### 7.3 方案三:降低表格每行组件数量
这是第二阶段优化方案。
#### 修改思路
- 默认以文本方式展示数值和货位。
- 仅在用户点击某行或某单元格后切换为可编辑组件。
- 或将数量、货位编辑能力迁移到弹窗/侧边表单中进行。
#### 可选实现方式
- 方式 A保留表格编辑但只对当前编辑行渲染 `el-input-number``el-select`
- 方式 B表格只展示编辑统一在“设置货位/修改本次到货数”弹窗内完成。
#### 预期收益
- 大幅减少页面初次渲染和更新渲染的组件实例数。
- 降低响应式系统负担。
#### 风险点
- 交互形式改变较大,需业务确认。
- 改动量大于分页和取消自动全选。
### 7.4 方案四:优化汇总逻辑
这是辅助优化方案。
#### 修改思路
- 汇总只统计当前页数据,不统计全量数据。
- 或在查询成功后一次性计算并缓存汇总结果,避免在渲染阶段多次 `map + reduce`
- 对不必要展示汇总的列取消统计。
#### 预期收益
- 降低表格重渲染时的计算开销。
- 对大页数据场景效果明显。
#### 风险点
- 需确认业务是否要求“全量合计”还是“当前页合计”。
### 7.5 方案五:收敛筛选联动查询,减少重复请求
这是体验优化方案。
#### 修改思路
- 将“切换筛选项即自动查询”改为“选择条件后点击查询按钮再执行”。
- 对必须联动的下拉,仅查询下一级选项,不立即刷新大表。
- 对输入型查询条件增加节流或按回车触发。
#### 预期收益
- 避免用户频繁切换条件时重复打接口和重复渲染。
- 页面行为更可控。
#### 风险点
- 与当前“选择即查”的使用习惯不完全一致。
## 8. 推荐实施顺序
建议按以下顺序推进:
### 第一阶段:快速止血
目标是在不大改页面交互的前提下,尽快解决“查询卡死”。
建议执行:
1. 采购件入库增加分页。
2. 取消查询后自动全选。
3. 采购件入库增加显式“勾选当前页可入库项”按钮,作为自动全选替代。
### 第二阶段:性能优化
目标是继续提升大数据场景下的流畅度。
建议执行:
1. 优化汇总函数。
2. 减少表格内 `el-input-number``el-select` 的常驻渲染数量。
3. 调整筛选联动逻辑,减少重复请求。
### 第三阶段:体验收敛
目标是统一仓储大表页面的实现模式。
建议执行:
1. 对采购件入库、自制件入库、外协到货的查询行为做统一规范。
2. 明确哪些页签必须分页、哪些页签允许全量查。
3. 统一表格汇总、批量勾选、行编辑的实现方式。
## 9. 建议的具体改动点
以下为建议修改点,不代表本次已经改动:
### 9.1 页面状态
`PurchasePartsStorage/index.vue` 中补充采购件入库分页状态,例如:
- `pageCurrentRK`
- `pageSizeRK`
- `totalRK`
或直接复用现有命名体系,保持与页面其他分区一致。
### 9.2 查询方法
重点调整:
- `searchTable1()`
建议:
- 查询请求增加分页参数。
- 查询结果改为接收分页结构。
- 删除查询成功后的逐行 `toggleRowSelection` 自动勾选逻辑。
### 9.3 表格区域
采购件入库表格下方增加分页组件,行为对齐自制件入库分页实现。
### 9.4 批量选择逻辑
新增显式按钮:
- “勾选当前页可入库”
- “取消当前页勾选”
这样既能保留批量能力,也能避免查询完成后立即执行重计算。
### 9.5 汇总逻辑
重构:
- `getSummaries()`
- `getSummaries1()`
建议尽量减少重复遍历次数,并明确统计口径为“当前页合计”。
## 10. 验证方案
修改完成后建议按以下方式验证:
### 10.1 功能验证
- 选择供应商后能正常查询采购件入库数据。
- 切换合同后能正常刷新结果。
- 分页切换后数据正确。
- 手工勾选、批量勾选后入库功能正常。
- 货位选择、数量输入、打印功能不受影响。
### 10.2 性能验证
- 查询 50 条、100 条、300 条时页面响应是否明显改善。
- 查询后页面是否仍出现长时间卡死。
- 批量勾选当前页时是否可接受。
- 输入数量、选择货位时是否仍然卡顿。
### 10.3 回归验证
- 自制件入库页签行为不受影响。
- 外协到货页签行为不受影响。
- 入库单弹窗显示和合并逻辑不受影响。
## 11. 风险与注意事项
- 若后端接口当前不支持分页,本次优化需要前后端联动处理。
- 若业务习惯依赖“查询即默认勾选全部可入库项”,需要提前通知用户交互变更。
- 若汇总口径从全量改为当前页,需业务确认是否接受。
- 若后续计划继续扩展该页面,建议优先统一仓储大表的分页和编辑规范,避免重复出现同类问题。
## 12. 最终建议
推荐采用“先小改止血,再做结构优化”的策略:
1. 先做采购件入库分页。
2. 同时取消查询后的自动逐行全选。
3. 再根据现场体验决定是否继续推进表格轻量化和汇总优化。
若只能先做一项,优先级最高的是:
- `searchTable1()` 增加分页能力。
若可以同时做两项,最佳组合是:
- 采购件入库分页。
- 取消查询后自动勾选。
这两项组合最有希望直接解决当前“查询就卡页面崩溃”的核心问题。

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

22
docs/preview-server.js Normal file
View File

@@ -0,0 +1,22 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const root = __dirname;
const types = { '.html': 'text/html; charset=utf-8', '.png': 'image/png', '.css': 'text/css; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.md': 'text/plain; charset=utf-8' };
const server = http.createServer((req, res) => {
try {
const url = new URL(req.url, 'http://127.0.0.1:18765');
const rel = decodeURIComponent(url.pathname).replace(/^\/+/, '');
const filePath = path.resolve(root, rel);
if (!filePath.startsWith(path.resolve(root))) {
res.writeHead(403); res.end('Forbidden'); return;
}
fs.readFile(filePath, (err, data) => {
if (err) { res.writeHead(404); res.end('Not found: ' + filePath); return; }
res.writeHead(200, { 'Content-Type': types[path.extname(filePath).toLowerCase()] || 'application/octet-stream' });
res.end(data);
});
} catch (e) { res.writeHead(500); res.end(String(e)); }
});
server.listen(18765, '127.0.0.1', () => console.log('preview server on 18765'));
setInterval(() => {}, 1000);

View File

@@ -0,0 +1,482 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>立库可视化功能截图预览</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Microsoft YaHei", Arial, sans-serif;
background: #eef2f7;
color: #303133;
}
.app-container { padding: 20px; }
.card {
height: 850px;
background: #fff;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 12px rgba(0,0,0,.06);
}
.tab {
height: 44px;
border-bottom: 1px solid #dcdfe6;
background: #f5f7fa;
display: flex;
align-items: end;
padding-left: 0;
}
.tab span {
display: inline-flex;
align-items: center;
height: 44px;
padding: 0 22px;
background: #fff;
border-right: 1px solid #dcdfe6;
border-top: 2px solid #409eff;
font-size: 14px;
color: #409eff;
}
.toolbar {
margin: 7px 0;
padding: 0 12px;
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
height: 42px;
}
.toolbar label { font-size: 13px; color: #000; }
.select, .button, .switch {
height: 32px;
border-radius: 4px;
font-size: 13px;
display: inline-flex;
align-items: center;
}
.select {
width: 120px;
border: 1px solid #dcdfe6;
padding: 0 10px;
justify-content: space-between;
color: #606266;
background: #fff;
}
.select::after { content: "⌄"; color: #909399; }
.button {
border: 1px solid transparent;
padding: 0 12px;
color: #fff;
gap: 4px;
}
.primary { background: #409eff; }
.default { background: #fff; color: #606266; border-color: #dcdfe6; }
.success { background: #67c23a; }
.switch {
margin-left: 10px;
gap: 8px;
color: #606266;
}
.switch-track {
width: 42px;
height: 22px;
border-radius: 12px;
background: #409eff;
position: relative;
}
.switch-track::after {
content: "";
position: absolute;
right: 2px;
top: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
}
.stats {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 10px;
margin: 10px 12px;
}
.stat {
border: 1px solid #ebeef5;
border-radius: 4px;
text-align: center;
padding: 10px 0;
box-shadow: 0 2px 8px rgba(0,0,0,.04);
}
.stat .label { color: #909399; font-size: 12px; }
.stat .value { font-size: 20px; font-weight: bold; margin-top: 4px; }
.legend {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
font-size: 12px;
}
.legend-color {
width: 20px;
height: 20px;
border-radius: 4px;
border: 1px solid #ddd;
margin-right: 5px;
}
.warehouse-area {
height: 600px;
overflow: auto;
border: 1px solid #e4e7ed;
border-radius: 4px;
margin: 0 12px;
padding: 15px;
background: #f5f7fa;
}
.warehouse-group {
margin-bottom: 30px;
padding: 15px;
background: white;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,.1);
}
.group-title {
text-align: center;
margin-bottom: 10px;
color: #303133;
font-weight: bold;
background: #e8f4fd;
padding: 5px;
border-radius: 4px;
}
table {
width: 100%;
min-width: 1200px;
border-collapse: collapse;
margin: 0 auto;
table-layout: fixed;
}
th, td {
border: 1px solid #e4e7ed;
text-align: center;
height: 40px;
min-width: 40px;
font-size: 12px;
position: relative;
}
th {
background: #f5f7fa;
font-weight: bold;
color: #303133;
}
.row-layer-header, .row-layer-cell {
width: 80px;
min-width: 80px;
background: #e8f4fd;
color: #2196f3;
font-weight: bold;
}
.row-layer-cell { background: #f5f7fa; color: #303133; }
.highlight-row .row-layer-cell {
background: #bbdefb;
color: #1976d2;
}
.highlight-col { background: #bbdefb; color: #1976d2; }
.cell {
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all .2s;
}
.cell:hover {
transform: scale(1.08);
z-index: 2;
box-shadow: 0 2px 8px rgba(0,0,0,.2);
}
.status-2 { background: #4caf50; color: #fff; }
.status-1 { background: #ff9800; color: #fff; }
.status-0 { background: #e0e0e0; color: #666; }
.status-9 { background: skyblue; color: #fff; }
.selected {
box-shadow: 0 0 0 2px #2196f3 inset;
outline: 2px solid #2196f3;
outline-offset: -2px;
}
.tooltip {
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,.82);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 4;
font-weight: normal;
}
.pagination {
text-align: center;
margin-top: 10px;
color: #606266;
font-size: 13px;
}
.page-box {
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid #ebeef5;
padding: 6px 10px;
border-radius: 4px;
background: #fff;
}
.page-num {
min-width: 28px;
height: 28px;
border-radius: 3px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.page-current { background: #409eff; color: #fff; }
.modal-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,.2);
align-items: center;
justify-content: center;
z-index: 10;
}
body.show-detail #detailModal,
body.show-material #materialModal { display: flex; }
.dialog {
background: #fff;
border-radius: 2px;
box-shadow: 0 4px 18px rgba(0,0,0,.18);
overflow: hidden;
}
.detail-dialog { width: 500px; }
.material-dialog { width: 941px; }
.dialog-title {
height: 54px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
border-bottom: 1px solid #ebeef5;
}
.dialog-body { padding: 20px; }
.detail-box {
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
line-height: 1.7;
}
.dialog-footer {
border-top: 1px solid #ebeef5;
padding: 10px 20px;
text-align: right;
}
.close-button {
height: 32px;
padding: 0 15px;
border: 1px solid #dcdfe6;
background: #fff;
border-radius: 4px;
color: #606266;
}
.material-table {
min-width: 0;
table-layout: auto;
font-size: 13px;
}
.material-table th {
color: #606266;
height: 38px;
background: #f5f7fa;
}
.material-table td { height: 46px; }
.qty { color: #67c23a; font-weight: bold; }
.lockqty { color: #e6a23c; font-weight: bold; }
.unit { font-size: 10px; color: #909399; }
.mode-icon .cell { font-size: 18px; }
.mode-icon .text-symbol { display: none; }
.mode-icon .icon-symbol { display: inline; }
.icon-symbol { display: none; }
.callout {
position: absolute;
right: 30px;
top: 105px;
background: #fffbe6;
border: 1px solid #ffe58f;
color: #6b4c00;
padding: 8px 12px;
border-radius: 4px;
font-size: 13px;
z-index: 5;
display: none;
}
body.show-filter-tip .callout { display: block; }
</style>
</head>
<body>
<div class="app-container">
<div class="card">
<div class="tab"><span>立库可视化</span></div>
<div class="toolbar">
<label>库位筛选</label>
<div class="select" id="aisleSelect">第一巷道</div>
<div class="select" id="rowSelect">03-01</div>
<div class="button primary">🔍 搜索</div>
<div class="button default">↻ 重置</div>
<div class="button success">⟲ 刷新数据</div>
<div class="switch"><span id="modeText">显示文字</span><span class="switch-track"></span></div>
</div>
<div class="callout">筛选区支持按巷道重新取数、按行-层收窄视图,并可切换文字/图标显示模式。</div>
<div class="stats">
<div class="stat"><div class="label">总库位数</div><div class="value">384</div></div>
<div class="stat"><div class="label">有物料有托盘</div><div class="value" style="color:#4caf50">138</div></div>
<div class="stat"><div class="label">有托盘无物料</div><div class="value" style="color:#ff9800">76</div></div>
<div class="stat"><div class="label">空闲库位</div><div class="value" style="color:#9e9e9e">154</div></div>
<div class="stat"><div class="label">使用率</div><div class="value" style="color:#2196f3">55.7%</div></div>
<div class="stat"><div class="label">数据行数</div><div class="value" style="color:#9c27b0">8</div></div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-color" style="background:#4caf50"></div>有物料有托盘 (物)</div>
<div class="legend-item"><div class="legend-color" style="background:#ff9800"></div>有托盘无物料 (盘)</div>
<div class="legend-item"><div class="legend-color" style="background:#e0e0e0"></div>空库位 (空)</div>
<div class="legend-item"><div class="legend-color" style="background:#bbdefb;border:2px solid #2196f3"></div>选中库位</div>
</div>
<div class="warehouse-area">
<div class="warehouse-group">
<div class="group-title">立库组</div>
<table id="warehouseTable">
<thead>
<tr id="headRow"><th class="row-layer-header">行-层</th></tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<div style="text-align:center;margin-top:20px;color:#909399;font-size:12px">显示 8 行数据</div>
</div>
<div class="pagination">
<div class="page-box">
<span>共 8 条</span>
<span>8条/页</span>
<span></span>
<span class="page-num page-current">1</span>
<span></span>
<span>前往 1 页</span>
</div>
</div>
</div>
</div>
<div class="modal-backdrop" id="detailModal">
<div class="dialog detail-dialog">
<div class="dialog-title">库位详情 03-08-01</div>
<div class="dialog-body">
<div class="detail-box">有托盘但无物料,需补充物料。当前库位已被识别为托盘占用状态,适合仓储人员核对托盘与物料绑定情况。</div>
</div>
<div class="dialog-footer"><button class="close-button">关闭</button></div>
</div>
</div>
<div class="modal-backdrop" id="materialModal">
<div class="dialog material-dialog">
<div class="dialog-title">库位信息 03-12-01</div>
<div class="dialog-body">
<table class="material-table">
<thead>
<tr>
<th>物料编号</th>
<th>物料名称</th>
<th>图号</th>
<th>型号</th>
<th>库存数量</th>
<th>锁定库存</th>
<th>批次</th>
</tr>
</thead>
<tbody>
<tr>
<td>02.009.001.0012</td>
<td>直线导轨组件</td>
<td>LRS3056-0312-01</td>
<td>HGW25CA</td>
<td><div class="qty">14.000</div><div class="unit"></div></td>
<td><div class="lockqty">2.000</div><div class="unit"></div></td>
<td>BATCH031201</td>
</tr>
<tr>
<td>02.009.001.0013</td>
<td>滑块组件</td>
<td>LRS3056-0312-02</td>
<td>HGW25CC</td>
<td><div class="qty">6.000</div><div class="unit"></div></td>
<td><div style="color:#909399">0.000</div><div class="unit"></div></td>
<td>BATCH031202</td>
</tr>
</tbody>
</table>
</div>
<div class="dialog-footer"><button class="close-button">关闭</button></div>
</div>
</div>
<script>
const columns = 48;
const rows = ['04-04','04-03','04-02','04-01','03-04','03-03','03-02','03-01'];
const statuses = [2,0,1,2,0,0,2,1,0,2,1,2,0,9,2,0,1,0,2,2,0,1,0,2,0,0,1,2,0,2,0,1,2,0,0,9,2,1,0,2,0,1,2,0,0,2,1,0];
const head = document.getElementById('headRow');
for (let c = 1; c <= columns; c++) {
const th = document.createElement('th');
th.textContent = c;
if (c === 12) th.className = 'highlight-col';
head.appendChild(th);
}
const tbody = document.getElementById('tableBody');
rows.forEach((row, rIndex) => {
const tr = document.createElement('tr');
if (row === '03-01') tr.className = 'highlight-row';
const rowCell = document.createElement('td');
rowCell.className = 'row-layer-cell';
rowCell.textContent = row;
tr.appendChild(rowCell);
for (let c = 1; c <= columns; c++) {
const status = statuses[(c + rIndex * 5) % statuses.length];
const td = document.createElement('td');
td.className = 'cell status-' + status;
if (row === '03-01' && c === 12) td.className += ' selected';
const text = status === 2 ? '物' : status === 1 ? '盘' : status === 9 ? '锁' : '空';
const icon = status === 2 ? '▣' : status === 1 ? '▤' : status === 9 ? '🔒' : '';
td.innerHTML = '<span class="text-symbol">' + text + '</span><span class="icon-symbol">' + icon + '</span>';
if (row === '03-01' && c === 12) {
const tip = document.createElement('span');
tip.className = 'tooltip';
tip.textContent = '有物料有托盘';
td.appendChild(tip);
}
tr.appendChild(td);
}
tbody.appendChild(tr);
});
const params = new URLSearchParams(location.search);
const shot = params.get('shot');
if (shot === 'filter') {
document.body.classList.add('show-filter-tip');
document.body.classList.add('mode-icon');
document.getElementById('modeText').textContent = '显示图标';
document.getElementById('aisleSelect').textContent = '第三巷道';
document.getElementById('rowSelect').textContent = '03-01';
}
if (shot === 'detail') document.body.classList.add('show-detail');
if (shot === 'material') document.body.classList.add('show-material');
</script>
</body>
</html>

237
scripts/markdown-to-pdf.js Normal file
View File

@@ -0,0 +1,237 @@
const fs = require('fs')
const path = require('path')
const input = process.argv[2]
const output = process.argv[3]
if (!input || !output) {
console.error('Usage: node scripts/markdown-to-pdf.js <input.md> <output.html>')
process.exit(1)
}
const markdown = fs.readFileSync(input, 'utf8')
function escapeHtml(value) {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
function inlineFormat(value) {
return escapeHtml(value)
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
}
function renderTable(lines) {
const rows = lines
.filter(line => line.trim())
.map(line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => inlineFormat(cell.trim())))
if (rows.length < 2) return ''
const head = rows[0]
const body = rows.slice(2)
return [
'<table>',
'<thead><tr>' + head.map(cell => '<th>' + cell + '</th>').join('') + '</tr></thead>',
'<tbody>',
body.map(row => '<tr>' + row.map(cell => '<td>' + cell + '</td>').join('') + '</tr>').join('\n'),
'</tbody>',
'</table>'
].join('\n')
}
function renderMarkdown(text) {
const lines = text.replace(/\r\n/g, '\n').split('\n')
const html = []
let paragraph = []
let list = []
let code = []
let inCode = false
let table = []
function flushParagraph() {
if (paragraph.length) {
html.push('<p>' + inlineFormat(paragraph.join(' ')) + '</p>')
paragraph = []
}
}
function flushList() {
if (list.length) {
html.push('<ul>' + list.map(item => '<li>' + inlineFormat(item) + '</li>').join('') + '</ul>')
list = []
}
}
function flushTable() {
if (table.length) {
html.push(renderTable(table))
table = []
}
}
lines.forEach(line => {
if (/^```/.test(line.trim())) {
if (inCode) {
html.push('<pre><code>' + escapeHtml(code.join('\n')) + '</code></pre>')
code = []
inCode = false
} else {
flushParagraph()
flushList()
flushTable()
inCode = true
}
return
}
if (inCode) {
code.push(line)
return
}
if (/^\|.*\|$/.test(line.trim())) {
flushParagraph()
flushList()
table.push(line)
return
}
flushTable()
if (!line.trim()) {
flushParagraph()
flushList()
return
}
const heading = line.match(/^(#{1,6})\s+(.+)$/)
if (heading) {
flushParagraph()
flushList()
const level = heading[1].length
html.push('<h' + level + '>' + inlineFormat(heading[2]) + '</h' + level + '>')
return
}
const image = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
if (image) {
flushParagraph()
flushList()
html.push('<figure><img src="' + escapeHtml(image[2]) + '" alt="' + escapeHtml(image[1]) + '"><figcaption>' + inlineFormat(image[1]) + '</figcaption></figure>')
return
}
const listItem = line.match(/^- (.+)$/)
if (listItem) {
flushParagraph()
list.push(listItem[1])
return
}
paragraph.push(line.trim())
})
flushParagraph()
flushList()
flushTable()
return html.join('\n')
}
const body = renderMarkdown(markdown)
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>零件改制功能设计方案</title>
<style>
@page { size: A4; margin: 18mm 16mm; }
body {
font-family: "Microsoft YaHei", "SimSun", Arial, sans-serif;
color: #1f2933;
font-size: 13px;
line-height: 1.65;
}
h1 {
font-size: 26px;
text-align: center;
margin: 0 0 24px;
padding-bottom: 12px;
border-bottom: 2px solid #2f80ed;
}
h2 {
font-size: 19px;
margin: 24px 0 10px;
padding-left: 8px;
border-left: 4px solid #2f80ed;
page-break-after: avoid;
}
h3 {
font-size: 16px;
margin: 18px 0 8px;
page-break-after: avoid;
}
p { margin: 7px 0; }
ul { margin: 6px 0 10px 22px; padding: 0; }
li { margin: 3px 0; }
table {
width: 100%;
border-collapse: collapse;
margin: 10px 0 16px;
page-break-inside: avoid;
}
th, td {
border: 1px solid #cbd5e1;
padding: 6px 8px;
text-align: left;
vertical-align: top;
word-break: break-word;
}
th {
background: #eef4ff;
font-weight: 600;
}
code {
font-family: Consolas, "Microsoft YaHei", monospace;
background: #f3f4f6;
padding: 1px 4px;
border-radius: 3px;
}
pre {
background: #f8fafc;
border: 1px solid #d8dee9;
padding: 10px;
overflow-wrap: break-word;
white-space: pre-wrap;
page-break-inside: avoid;
}
figure {
margin: 14px 0 20px;
page-break-inside: avoid;
}
figure img {
display: block;
width: 100%;
max-width: 100%;
height: auto;
border: 1px solid #d8dee9;
}
figcaption {
margin-top: 6px;
color: #64748b;
font-size: 12px;
text-align: center;
}
</style>
</head>
<body>
${body}
</body>
</html>
`
fs.writeFileSync(output, html, 'utf8')

View File

@@ -28,19 +28,36 @@
<el-table
v-loading="loading"
:data="tableData"
:show-header="false"
:row-style="{height:'70px'}"
border
stripe
size="mini"
height="750">
<el-table-column type="index" width="50" label="号" align="center"/>
<el-table-column label="状态" prop="状态" align="center" show-overflow-tooltip width="100"/>
<el-table-column label="订单号" prop="订单编号" align="center" show-overflow-tooltip width="150"/>
<el-table-column label="物料编码" prop="物料编码" align="center" show-overflow-tooltip width="150"/>
<el-table-column label="物料名称" prop="物料名称" align="center" show-overflow-tooltip min-width="100"/>
<el-table-column label="图号" prop="图号或型号" align="center" show-overflow-tooltip width="260"/>
<el-table-column label="采购数" prop="数量" align="center" show-overflow-tooltip width="100"/>
<el-table-column label="已入库数量" prop="已到货数量" align="center" show-overflow-tooltip width="100"/>
<el-table-column label="采购合同" prop="采购合同编号" align="center" show-overflow-tooltip min-width="150"/>
<el-table-column align="center" label="订单号" width="170px" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.订单编号 }}
</template>
</el-table-column>
<el-table-column align="center" width="150px" label="物料名称" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.物料名称 }}
</template>
</el-table-column>
<el-table-column align="center" label="采购进度" width="770px">
<template slot-scope="scope">
<div class="steps-container">
<div v-for="(item, index) in scope.row.names" :key="index" :class="stepClass(item, index, scope.row.进度)">
<div style="margin-left: 40px;">{{ item.item }}</div>
<div :class="triangleClass(item, index, scope.row.进度)"/>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80px">
<template slot-scope="scope">
<el-button type="text" size="small" @click="details(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="block" style="margin-top: 10px;">
<el-pagination
@@ -54,12 +71,59 @@
@current-change="handleCurrentChanges"/>
</div>
</el-card>
<el-dialog title="采购进度详情" :visible.sync="dialogFormVisible" center width="650px" custom-class="detail-dialog">
<div class="detail-table-wrapper">
<table class="detail-table" cellspacing="0" cellpadding="0">
<tr>
<td class="detail-label">状态</td>
<td class="detail-value">
<el-tag :type="statusTagType(detailRow.状态)" size="small" effect="dark">{{ detailRow.状态 }}</el-tag>
</td>
<td class="detail-label">订单号</td>
<td class="detail-value">{{ detailRow.订单编号 }}</td>
</tr>
<tr>
<td class="detail-label">物料编码</td>
<td class="detail-value">{{ detailRow.物料编码 }}</td>
<td class="detail-label">物料名称</td>
<td class="detail-value">{{ detailRow.物料名称 }}</td>
</tr>
<tr>
<td class="detail-label">图号或型号</td>
<td class="detail-value" colspan="3">{{ detailRow.图号或型号 }}</td>
</tr>
<tr>
<td class="detail-label">采购数</td>
<td class="detail-value">{{ detailRow.数量 }}</td>
<td class="detail-label">已入库数量</td>
<td class="detail-value">{{ detailRow.已到货数量 }}</td>
</tr>
<tr>
<td class="detail-label">采购合同</td>
<td class="detail-value" colspan="3">{{ detailRow.采购合同编号 }}</td>
</tr>
</table>
</div>
</el-dialog>
</div>
</template>
<script>
import { getMaterialPurchaseProgress } from '@/api/PurchasingManagement/MaterialPurchaseProgress'
const STATUS_MAP = { '未提交': 1, '未到货': 2, '已到货': 3, '质检中': 3, '部分到': 4, '已入库': 4 }
function getStepNames(status) {
var step3 = (status === '质检中') ? '质检中' : '已到货'
var step4 = (status === '部分到') ? '部分到' : '已入库'
return [
{ item: '未提交', value: 1 },
{ item: '未到货', value: 2 },
{ item: step3, value: 3 },
{ item: step4, value: 4 }
]
}
export default {
data() {
return {
@@ -70,7 +134,9 @@ export default {
tableData: [],
total: 0,
pageCurrent: 1,
pageSize: 50
pageSize: 50,
dialogFormVisible: false,
detailRow: {}
}
},
created() {
@@ -85,13 +151,42 @@ export default {
param[2] = ['图号', this.drawingNo]
var requestData = this.CreateData('11', '物料_采购进度_查询', param, this.pageSize, this.pageCurrent)
this.ExecDatabase(requestData).then(response => {
this.tableData = response.data.rows || []
var rows = response.data.rows || []
this.tableData = rows.map(function(row) {
var progress = STATUS_MAP[row.状态] || 1
var names = getStepNames(row.状态)
return Object.assign({}, row, { 进度: progress, names: names })
})
this.total = response.data.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
stepClass(item, index, progress) {
var cls = 'title' + item.value
if (progress > index) {
cls = 'active' + item.value
}
return cls
},
triangleClass(item, index, progress) {
if (progress > index) {
return 'triangle-active' + item.value
}
if (progress === index + 1) {
return 'arrow-active' + item.value
}
return 'triangle' + item.value
},
details(row) {
this.detailRow = row
this.dialogFormVisible = true
},
statusTagType(status) {
var map = { '未提交': 'info', '未到货': 'warning', '质检中': '', '已到货': '', '部分到': 'success', '已入库': 'success' }
return map[status] || 'info'
},
search() {
this.pageCurrent = 1
this.fetchData()
@@ -108,3 +203,231 @@ export default {
}
}
</script>
<style scoped>
.el-card {
margin: 0px;
}
.detail-table-wrapper {
padding: 0 10px;
}
.detail-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.detail-table td {
border: 1px solid #dcdfe6;
padding: 12px 16px;
height: 44px;
}
.detail-label {
background: #f5f7fa;
color: #606266;
font-weight: 500;
width: 110px;
text-align: center;
}
.detail-value {
color: #303133;
width: 195px;
}
</style>
<style scoped lang="scss">
/deep/ .el-table__row {
height: 94px !important;
}
/deep/ .el-table__row > td {
border: none;
}
/deep/ .el-table th.is-leaf {
border: none;
}
/deep/ .el-table::before {
height: 0px;
}
/deep/ .el-table--border {
border: none;
}
</style>
<style scoped lang="stylus">
.steps-container
display flex
overflow hidden
.title1
width 140px
height 85px
display flex
justify-content center
align-items center
background #F2F2F2
font-size 14px
font-weight 500
color #969191
position relative
.active1
width 140px
height 85px
display flex
justify-content center
align-items center
background #E9F0FA
font-size 14px
font-weight 500
color black
position relative
.title2
width 140px
height 85px
display flex
justify-content center
align-items center
background #F2F2F2
font-size 14px
font-weight 500
color #969191
position relative
.active2
width 140px
height 85px
display flex
justify-content center
align-items center
background #B7D7F0
font-size 14px
font-weight 500
color black
position relative
.title3
width 140px
height 85px
display flex
justify-content center
align-items center
background #F2F2F2
font-size 14px
font-weight 500
color #969191
position relative
.active3
width 140px
height 85px
display flex
justify-content center
align-items center
background #6EABE1
font-size 14px
font-weight 500
color black
position relative
.title4
width 140px
height 85px
display flex
justify-content center
align-items center
background #F2F2F2
font-size 14px
font-weight 500
color #969191
position relative
.active4
width 140px
height 85px
display flex
justify-content center
align-items center
background #2682D5
font-size 14px
font-weight 500
color black
position relative
.triangle1
position absolute
right -85px
z-index 10
top -1px
width 0
height 0
border-width 43px
border-style solid
border-color transparent transparent transparent #ffffff
.triangle1::after
content ''
position absolute
left -43px
top -40px
border-width 40px
border-style solid
border-color transparent transparent transparent #F2F2F2
.arrow-active1
border-color transparent transparent transparent #ffffff
.triangle-active1::after
border-color transparent transparent transparent #E9F0FA
.triangle2
position absolute
right -85px
z-index 10
top -1px
width 0
height 0
border-width 43px
border-style solid
border-color transparent transparent transparent #ffffff
.triangle2::after
content ''
position absolute
left -43px
top -40px
border-width 40px
border-style solid
border-color transparent transparent transparent #F2F2F2
.arrow-active2
border-color transparent transparent transparent #ffffff
.triangle-active2::after
border-color transparent transparent transparent #B7D7F0
.triangle3
position absolute
right -85px
z-index 10
top -1px
width 0
height 0
border-width 43px
border-style solid
border-color transparent transparent transparent #ffffff
.triangle3::after
content ''
position absolute
left -43px
top -40px
border-width 40px
border-style solid
border-color transparent transparent transparent #F2F2F2
.arrow-active3
border-color transparent transparent transparent #ffffff
.triangle-active3::after
border-color transparent transparent transparent #6EABE1
.triangle4
position absolute
right -85px
z-index 10
top -1px
width 0
height 0
border-width 43px
border-style solid
border-color transparent transparent transparent #ffffff
.triangle4::after
content ''
position absolute
left -43px
top -40px
border-width 40px
border-style solid
border-color transparent transparent transparent #F2F2F2
.arrow-active4
border-color transparent transparent transparent #ffffff
.triangle-active4::after
border-color transparent transparent transparent #2682D5
</style>

View File

@@ -681,7 +681,7 @@
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible2 = false">取消</el-button>
<el-button type="primary" style="width: 70px" @click="submitPicture()">确定</el-button>
<el-button :loading="uploadSaving" type="primary" style="width: 70px" @click="submitPicture()">确定</el-button>
</div>
</el-dialog>
<el-dialog v-el-drag-dialog :visible.sync="dialogVisible" width="350px">
@@ -880,6 +880,10 @@ export default {
limit: 100,
Data: {},
upload: url,
uploadSaving: false,
uploadFileTotal: 0,
uploadFileFinished: 0,
uploadFileFailed: 0,
pageCurrent: 1, // 分页当前页
pageSize: 100, // 分页每页显示条数
pageCurrent1: 1, // 分页当前页
@@ -1324,20 +1328,39 @@ export default {
if (response.data && response.data[0] && response.data[0].result === '1') {
console.log('文件保存成功:', savePath)
} else {
this.uploadFileFailed++
console.error('文件保存失败:', response.data)
}
}).catch(err => {
this.uploadFileFailed++
console.error('文件保存异常:', err)
})
}).finally(() => {
this.uploadFileFinished++
if (this.uploadFileFinished >= this.uploadFileTotal) {
this.uploadSaving = false
if (this.uploadFileFailed === 0) {
this.$message.success('图片上传成功')
this.dialogFormVisible2 = false
this.$refs.upload.clearFiles()
} else {
this.$message.error('部分图片上传失败,请重新上传失败图片')
}
}
})
},
updatePicture(row) {
this.dialogFormVisible2 = true
this.purchaseContractID = row.采购合同流水号
},
submitPicture() {
if (!this.$refs.upload || this.$refs.upload.uploadFiles.length === 0) {
this.$message.warning('请选择图片')
return
}
this.uploadSaving = true
this.uploadFileTotal = this.$refs.upload.uploadFiles.length
this.uploadFileFinished = 0
this.uploadFileFailed = 0
var param = []
param[0] = ['采购合同流水号', this.purchaseContractID]
param[1] = ['文件名称', '']
@@ -1346,7 +1369,6 @@ export default {
var Data1 = this.CreateData('15', '采购合同查询_上传图片', param)
this.Data.param = Data1
this.$refs.upload.submit()
this.dialogFormVisible2 = false
},
getData() {
if (this.PartType === '物料采购') {

View File

@@ -1207,7 +1207,8 @@
<script>
import { mapGetters } from 'vuex'
import { MESDownloadFile, MESUploadFileProject, url } from '@/utils/request'
import { MESDownloadFile, MESDownloadFileNew, MESUploadFileProject, url } from '@/utils/request'
import axios from 'axios'
import $ from 'jquery'
export default {
@@ -1472,8 +1473,19 @@ export default {
var Data = this.CreateData('11', '发货单_图片_批量查询数据', param)
this.ExecDatabase(Data).then(response => {
for (let i = 0; i < response.data.length; i++) {
if (response.data[i].文件内容 !== null) {
this.urls2.push('data:image/png;base64,' + response.data[i].文件内容)
const item = response.data[i]
if (item.文件路径) {
axios.get(MESDownloadFileNew, {
params: { filePath: item.文件路径 }
}).then(res => {
if (res.data && res.data[0] && res.data[0].文件内容) {
this.urls2.push('data:image/png;base64,' + res.data[0].文件内容)
}
}).catch(err => {
console.error('图片加载失败:', err)
})
} else if (item.文件内容 !== null) {
this.urls2.push('data:image/png;base64,' + item.文件内容)
}
}
})
@@ -2968,4 +2980,3 @@ a {
color: #999;
}
</style>

View File

@@ -188,7 +188,7 @@
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogFormVisible2=false">取消</el-button>
<el-button type="distribution" size="small" @click="submit2('form')">确定</el-button>
<el-button :loading="uploadSaving" type="distribution" size="small" @click="submit2('form')">确定</el-button>
</div>
</el-dialog>
</div>
@@ -204,6 +204,10 @@ export default {
Data: {},
limit: 1000,
upload: url,
uploadSaving: false,
uploadFileTotal: 0,
uploadFileFinished: 0,
uploadFileFailed: 0,
dialogFormVisible1: false,
dialogFormVisible2: false,
listValue: '',
@@ -319,16 +323,36 @@ export default {
if (response.data && response.data[0] && response.data[0].result === '1') {
console.log('文件保存成功:', savePath)
} else {
this.uploadFileFailed++
console.error('文件保存失败:', response.data)
}
}).catch(err => {
this.uploadFileFailed++
console.error('文件保存异常:', err)
})
}).finally(() => {
this.uploadFileFinished++
if (this.uploadFileFinished >= this.uploadFileTotal) {
this.uploadSaving = false
if (this.uploadFileFailed === 0) {
this.$message.success('图片上传成功')
this.dialogFormVisible2 = false
this.$refs.upload.clearFiles()
this.searchTable()
} else {
this.$message.error('部分图片上传失败,请重新上传失败图片')
}
}
})
},
submit2() {
if (!this.$refs.upload || this.$refs.upload.uploadFiles.length === 0) {
this.$message.warning('请选择图片')
return
}
this.uploadSaving = true
this.uploadFileTotal = this.$refs.upload.uploadFiles.length
this.uploadFileFinished = 0
this.uploadFileFailed = 0
var param = []
param[0] = ['发货单流水号', this.listValue]
param[1] = ['文件名称', '']
@@ -337,10 +361,6 @@ export default {
var Data1 = this.CreateData('15', '发货管理_发货单_上传图片', param)
this.Data.param = Data1
this.$refs.upload.submit()
this.dialogFormVisible2 = false
this.$nextTick(() => {
this.searchTable()
})
},
uploadPicture(row) {
this.dialogFormVisible2 = true

View File

@@ -479,7 +479,7 @@
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogFormVisible1 = false">取消</el-button>
<el-button size="small" type="distribution" @click="submit2('form')">确定</el-button>
<el-button :loading="uploadSaving" size="small" type="distribution" @click="submit2('form')">确定</el-button>
</div>
</el-dialog>
<el-dialog v-el-drag-dialog :visible.sync="dialogFormVisibleCreatePickingList" center width="740px">
@@ -581,6 +581,10 @@ export default {
upload: url,
limit: 20,
Data: {},
uploadSaving: false,
uploadFileTotal: 0,
uploadFileFinished: 0,
uploadFileFailed: 0,
form: {
发货单号: '',
发货日期: '',
@@ -1256,6 +1260,14 @@ export default {
this.listValue = row.发货单流水号
},
submit2() {
if (!this.$refs.upload || this.$refs.upload.uploadFiles.length === 0) {
this.$message.warning('请选择图片')
return
}
this.uploadSaving = true
this.uploadFileTotal = this.$refs.upload.uploadFiles.length
this.uploadFileFinished = 0
this.uploadFileFailed = 0
var param = []
param[0] = ['发货单流水号', this.listValue]
param[1] = ['文件名称', '']
@@ -1264,10 +1276,6 @@ export default {
var Data1 = this.CreateData('15', '发货管理_发货单_上传图片', param)
this.Data.param = Data1
this.$refs.upload.submit()
this.dialogFormVisible1 = false
this.$nextTick(() => {
this.searchTable()
})
},
uploadSuccess(res, file) { // 上传成功回调
// 生成文件保存路径: 基础路径/DeliveryRecord/年月日时分秒毫秒-流水号.后缀
@@ -1302,14 +1310,26 @@ export default {
if (response.data && response.data[0] && response.data[0].result === '1') {
console.log('文件保存成功:', savePath)
} else {
this.uploadFileFailed++
console.error('文件保存失败:', response.data)
}
}).catch(err => {
this.uploadFileFailed++
console.error('文件保存异常:', err)
})
}).finally(() => {
this.uploadFileFinished++
if (this.uploadFileFinished >= this.uploadFileTotal) {
this.uploadSaving = false
if (this.uploadFileFailed === 0) {
this.$message.success('图片上传成功')
this.dialogFormVisible1 = false
this.$refs.upload.clearFiles()
this.searchTable()
} else {
this.$message.error('部分图片上传失败,请重新上传失败图片')
}
}
})
},
beforeUpload(file) { // 上传前检测
const isJPG = /^image\/.*/.test(file.type)

View File

@@ -84,7 +84,7 @@
</el-table-column>
<el-table-column :width="setColumnWidth('订单编号')" label="订单编号" align="center" prop="订单号" sortable="订单号" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.订单号 }}
{{ scope.row.订单号1 }}
</template>
</el-table-column>
<!-- <el-table-column :width="setColumnWidth('姓名')" label="收货人" align="center" show-overflow-tooltip>

View File

@@ -0,0 +1,628 @@
<template>
<div class="app-container">
<el-card style="height: 850px">
<el-row :gutter="10">
<el-col :span="10">
<div style="margin-top: 7px; margin-bottom: 7px">
<el-select
v-model="orderValue"
:remote-method="getOrderOptions"
style="width: 180px"
placeholder="订单编号"
size="small"
filterable
clearable
remote
@change="pageCurrent = 1; getOrderList()">
<el-option
v-for="item in orderOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<el-input
v-model="productKeyword"
style="width: 180px"
size="small"
placeholder="产品名称"
clearable
@keyup.enter.native="pageCurrent = 1; getOrderList()"/>
<el-button
type="primary"
plain
icon="el-icon-search"
size="small"
style="margin-left: 10px"
@click="pageCurrent = 1; getOrderList()">查询
</el-button>
</div>
<el-table
v-loading="loadingOrder"
ref="orderTable"
:data="orderTableData"
highlight-current-row
border
stripe
size="mini"
height="740px"
style="width: 100%"
@row-click="handleOrderClick">
<el-table-column width="55px" label="序号" type="index" align="center"/>
<el-table-column width="130px" label="订单编号" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.订单编号 }}
</template>
</el-table-column>
<el-table-column label="产品名称" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.产品名称 }}
</template>
</el-table-column>
<el-table-column width="90px" label="类型" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.类型 }}
</template>
</el-table-column>
</el-table>
<div class="block" style="margin-top: 10px;">
<el-pagination
:current-page="pageCurrent"
:page-sizes="[10, 20, 30, 40, 100]"
:page-size="pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChanges"
@current-change="handleCurrentChanges"/>
</div>
</el-col>
<el-col :span="14">
<div style="margin-top: 7px; margin-bottom: 7px">
<el-input
v-model="partKeyword"
:disabled="!currentOrderId"
style="width: 180px"
size="small"
placeholder="零件名称"
clearable
@keyup.enter.native="getPartList()"/>
<el-input
v-model="drawingNo"
:disabled="!currentOrderId"
style="width: 160px"
size="small"
placeholder="图号"
clearable
@keyup.enter.native="getPartList()"/>
<span style="font-size: 13px; color: black; margin-left: 12px">
当前产品{{ currentProductName || '未选择' }}
</span>
<el-button
:disabled="!currentOrderId"
type="primary"
plain
icon="el-icon-search"
size="small"
style="margin-left: 10px"
@click="getPartList()">刷新零件
</el-button>
</div>
<el-table
v-loading="loadingPart"
ref="partTable"
:data="partTableData"
highlight-current-row
border
stripe
size="mini"
height="780px"
style="width: auto">
<el-table-column width="55px" label="序号" type="index" align="center"/>
<el-table-column width="130px" label="产品/部件" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.产品名称 }}
</template>
</el-table-column>
<el-table-column width="200px" label="零件名称" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.零件名称 }}
</template>
</el-table-column>
<el-table-column width="200px" label="零件图号" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.图号或型号 }}
</template>
</el-table-column>
<el-table-column width="70px" label="数量" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.数量 }}
</template>
</el-table-column>
<el-table-column width="110px" label="操作" align="center">
<template slot-scope="scope">
<el-tooltip :open-delay="700" effect="dark" content="查看图纸" placement="top">
<el-button
type="text"
icon="el-icon-picture-outline"
size="mini"
@click="getNum(scope.row)"/>
</el-tooltip>
<el-tooltip :open-delay="700" effect="dark" content="改制" placement="top">
<el-button
type="text"
icon="el-icon-edit"
size="mini"
@click="openDialog(scope.row)"/>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-card>
<el-dialog
v-el-drag-dialog
:visible.sync="dialogFormVisible"
title="发起零件改制"
center
width="900px">
<el-form
ref="form"
:model="form"
:rules="rules"
label-position="right"
label-width="110px"
class="demo-ruleForm">
<div class="dialog-block">
<div class="dialog-block-title">原零件信息</div>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="原订单编号">
<el-input v-model="form.原订单编号" size="small" disabled/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="原零件名称">
<el-input v-model="form.原零件名称" size="small" disabled/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="原零件图号">
<el-input v-model="form.原零件图号" size="small" disabled/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="原数量">
<el-input v-model="form.原数量" size="small" disabled/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="dialog-block">
<div class="dialog-block-title">改制目标</div>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="改制零件名称" prop="改制零件名称">
<el-input v-model="form.改制零件名称" size="small" clearable/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="改制零件图号" prop="改制零件图号">
<el-input v-model="form.改制零件图号" size="small" clearable/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="改制数量" prop="改制数量">
<el-input-number
v-model="form.改制数量"
:min="1"
:max="Number(form.原数量 || 1)"
size="small"
style="width: 100%"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="精工期限" prop="精工期限">
<el-date-picker
v-model="form.精工期限"
size="small"
type="date"
style="width: 100%"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
placeholder="选择日期"/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" size="small" @click="submitForm('form')">确定</el-button>
</div>
</el-dialog>
<el-dialog v-el-drag-dialog :visible.sync="dialogVisible" width="350px">
<div v-for="(file, key) in hadFile" :key="key">
<div class="wrapper">
<div class="content">
<div v-if="file.suffix === 'pdf'" class="icon"><svg-icon icon-class="pdf"/></div>
<div v-else-if="file.suffix === 'docx' || file.suffix === 'doc'" class="icon"><svg-icon icon-class="doc"/></div>
<div v-else-if="file.suffix === 'xlsx' || file.suffix === 'xls'" class="icon"><svg-icon icon-class="excel"/></div>
<div v-else-if="file.suffix === 'pptx' || file.suffix === 'ppt'" class="icon"><svg-icon icon-class="ppt"/></div>
<div v-else-if="file.suffix === 'jpg' || file.suffix === 'jpeg' || file.suffix === 'png'" class="icon"><svg-icon icon-class="pic"/></div>
<div v-else class="icon"><svg-icon icon-class="file"/></div>
<a id="appUrl" :href="file.url" target="view_window"><div class="info">{{ file.name }}</div></a>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import request, { MESDownloadFile } from '@/utils/request'
export default {
data() {
var validateMaterial = (rule, value, callback) => {
if (this.form.改制目标 === '非毛坯物料改制' && !value) {
callback(new Error('请选择物料'))
} else {
callback()
}
}
return {
loadingOrder: false,
loadingPart: false,
dialogFormVisible: false,
dialogVisible: false,
hadFile: [],
total: 0,
pageCurrent: 1,
pageSize: 100,
orderValue: '',
orderOptions: [],
productKeyword: '',
orderTableData: [],
currentOrderId: '',
currentOrderNo: '',
currentProductId: '',
currentProductName: '',
partKeyword: '',
drawingNo: '',
partTableData: [],
targetList: [
{ value: '原现自制订单改制', label: '原现自制订单改制' },
{ value: '毛坯改制', label: '毛坯改制' },
{ value: '非毛坯物料改制', label: '非毛坯物料改制' }
],
materialName: '',
materialSpec: '',
materialNew: '',
materialNewValue: '',
tableData1: [],
pageCurrent1: 1,
pageSize1: 10,
total1: 0,
form: {
原订单流水号: '',
原组件流水号: '',
原基本件流水号: '',
原工艺计划流水号: '',
原订单编号: '',
原零件名称: '',
原零件图号: '',
原数量: '',
改制目标: '',
改制零件名称: '',
改制零件图号: '',
改制数量: 1,
是否毛坯: '',
物料编号: '',
物料名称: '',
改制原因: '',
改制要求: '',
技术备注: '',
精工期限: ''
},
rules: {
改制目标: [{ required: true, message: '请选择改制目标', trigger: 'change' }],
改制零件名称: [{ required: true, message: '请输入改制零件名称', trigger: 'blur' }],
改制零件图号: [{ required: true, message: '请输入改制零件图号', trigger: 'blur' }],
改制数量: [{ required: true, message: '请输入改制数量', trigger: 'change' }],
是否毛坯: [{ required: true, message: '请选择是否毛坯', trigger: 'change' }],
物料编号: [{ validator: validateMaterial, trigger: 'change' }],
改制原因: [{ required: true, message: '请输入改制原因', trigger: 'blur' }],
改制要求: [{ required: true, message: '请输入改制要求', trigger: 'blur' }],
精工期限: [{ required: true, message: '请选择精工期限', trigger: 'change' }]
}
}
},
computed: {
...mapGetters([
'id',
'token'
])
},
updated() {
this.$nextTick(() => {
if (this.$refs.orderTable) {
this.$refs.orderTable.doLayout()
}
if (this.$refs.partTable) {
this.$refs.partTable.doLayout()
}
})
},
created() {
this.getOrderOptions()
this.getOrderList()
},
methods: {
handleOrderClick(row) {
this.currentOrderId = row.订单流水号 || ''
this.currentOrderNo = row.订单编号 || ''
this.currentProductId = row.组件流水号 || ''
this.currentProductName = row.产品名称 || ''
this.getPartList()
},
resetMaterialSelect() {
this.materialName = ''
this.materialSpec = ''
this.materialNew = ''
this.materialNewValue = ''
this.tableData1 = []
this.pageCurrent1 = 1
this.pageSize1 = 10
this.total1 = 0
this.form.物料编号 = ''
this.form.物料名称 = ''
},
targetChange(value) {
this.resetMaterialSelect()
if (value === '原现自制订单改制') {
this.form.是否毛坯 = 0
} else if (value === '毛坯改制') {
this.form.是否毛坯 = 1
} else if (value === '非毛坯物料改制') {
this.form.是否毛坯 = 0
this.searchMaterial()
} else {
this.form.是否毛坯 = ''
}
},
openDialog(row) {
this.form = {
原订单流水号: this.currentOrderId,
原组件流水号: row.组件流水号 || '',
原基本件流水号: row.基本件流水号 || '',
原工艺计划流水号: row.工艺计划流水号 || '',
原订单编号: this.currentOrderNo,
原零件名称: row.零件名称 || '',
原零件图号: row.零件图号 || row.图号或型号 || '',
原数量: row.数量 || '',
改制目标: '',
改制零件名称: row.零件名称 || '',
改制零件图号: row.零件图号 || row.图号或型号 || '',
改制数量: row.数量 ? Number(row.数量) : 1,
是否毛坯: '',
物料编号: '',
物料名称: '',
改制原因: '',
改制要求: '',
技术备注: '',
精工期限: row.精工期限 || ''
}
this.resetMaterialSelect()
this.dialogFormVisible = true
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate()
}
})
},
selectNewMaterial(row) {
this.materialNewValue = row.物料流水号 || ''
this.materialNew = (row.名称 || '') + ' ' + (row.代号 || '')
this.form.物料编号 = row.编码 || row.代号 || ''
this.form.物料名称 = row.名称 || ''
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.validateField('物料编号')
}
})
},
searchMaterial() {
var param = []
param[0] = ['名称', this.materialName]
param[1] = ['图号型号', this.materialSpec]
var Data = this.CreateData('11', '物料信息_查询', param, this.pageSize1, this.pageCurrent1)
this.ExecDatabase(Data).then(response => {
if (response.data.rows) {
this.tableData1 = response.data.rows
this.total1 = response.data.total
} else {
this.tableData1 = response.data
this.total1 = response.data.length
}
})
},
handleSizeChange1(val) {
this.pageCurrent1 = 1
this.pageSize1 = val
this.searchMaterial()
},
handleCurrentChange1(val) {
this.pageCurrent1 = val
this.searchMaterial()
},
submitForm(formName) {
var oldDrawingNo = (this.form.原零件图号 || '').toString().trim()
var newDrawingNo = (this.form.改制零件图号 || '').toString().trim()
if (oldDrawingNo && newDrawingNo && oldDrawingNo === newDrawingNo) {
this.$message.warning('改制零件图号不能与原零件图号相同')
return
}
this.$refs[formName].validate((valid) => {
if (valid) {
var param = []
param[0] = ['原订单流水号', this.form.原订单流水号]
param[1] = ['原组件流水号', this.form.原组件流水号]
param[2] = ['改制零件名称', this.form.改制零件名称]
param[3] = ['改制零件图号', this.form.改制零件图号]
param[4] = ['改制数量', this.form.改制数量]
param[5] = ['精工期限', this.form.精工期限]
param[6] = ['发起人', this.id]
param[7] = ['原零件名称', this.form.原零件名称]
param[8] = ['原零件图号', this.form.原零件图号]
var Data = this.CreateData('12', '技术中心_改制订单_发起改制', param)
this.ExecDatabase(Data).then(response => {
if (response.data[0].result === '1') {
this.$message.success('改制订单发起成功')
this.dialogFormVisible = false
this.getOrderList()
this.getPartList()
} else {
this.$message.error(response.data[0].msg || '改制订单发起失败')
}
})
} else {
return false
}
})
},
getOrderOptions(query) {
this.orderOptions = []
var param = []
param[0] = ['客户名称', '']
param[1] = ['订单编号', query || '']
var Data = this.CreateData('11', '订单信息_列表_查询数据_bak', param)
this.ExecDatabase(Data).then(response => {
for (var i = 0; i < response.data.length; i++) {
this.orderOptions.push({
label: response.data[i].订单编号,
value: response.data[i].订单流水号
})
}
})
},
getOrderList() {
var param = []
param[0] = ['订单流水号', this.orderValue]
param[1] = ['产品名称', this.productKeyword]
this.loadingOrder = true
var Data = this.CreateData('11', '技术中心_改制订单_订单_查询', param, this.pageSize, this.pageCurrent)
this.ExecDatabase(Data).then(response => {
this.loadingOrder = false
if (response.data.rows) {
this.orderTableData = response.data.rows
this.total = response.data.total
} else {
this.orderTableData = response.data
this.total = response.data.length
}
this.currentOrderId = ''
this.currentOrderNo = ''
this.currentProductId = ''
this.currentProductName = ''
this.partTableData = []
}).catch(() => {
this.loadingOrder = false
})
},
getPartList() {
var param = []
param[0] = ['组件流水号', this.currentProductId]
this.loadingPart = true
var Data = this.CreateData('11', '技术中心_改制订单_零件_查询', param)
this.ExecDatabase(Data).then(response => {
this.loadingPart = false
this.partTableData = response.data
}).catch(() => {
this.loadingPart = false
})
},
getNum(row) {
this.dialogVisible = true
this.getFileList(row.零件图号 || row.图号或型号)
},
getFileList(value) {
this.hadFile = []
if (value) {
request({
url: '',
method: 'post',
data: {
type: '3',
name: "select * from 机加工MES_零件图纸PDF表 where 是否启用 = 1 and name = '" + value + "'"
}
}).then(data => {
if (data.data.length > 0) {
var server = ('' + MESDownloadFile).split('/')
data.data.map(item => {
this.hadFile.push({
url: 'http://' + server[2] + '/webpage/part/' + item.uid + '.' + item.suffix,
name: item.name + '.' + item.suffix,
suffix: item.suffix,
id: item.uid
})
})
}
})
}
},
handleSizeChanges(val) {
this.pageCurrent = 1
this.pageSize = val
this.getOrderList()
},
handleCurrentChanges(val) {
this.pageCurrent = val
this.getOrderList()
}
}
}
</script>
<style scoped>
.el-form-item {
margin-bottom: 15px;
}
.dialog-block {
padding: 12px 14px 4px 14px;
margin-bottom: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.dialog-block-title {
margin-bottom: 12px;
font-size: 14px;
font-weight: 600;
color: #303133;
}
.material-summary {
font-size: 16px;
color: red;
}
.wrapper {
width: 90%;
height: 50px;
overflow: hidden;
border: 1px solid #b8c7d9;
margin: 10px;
}
.content {
position: relative;
padding: 8px 8px 0 8px;
}
</style>

View File

@@ -7,6 +7,7 @@
<el-input v-model="specificationsModel" style="width: 150px " size="small" clearable placeholder="图号或型号"/>
<el-input v-model="LocationName" style="width: 100px " size="small" clearable placeholder="库位"/>
<el-button style="margin-left: 7px;width: 80px" type="primary" plain size="small" @click="pageCurrent=1;searchTable()">查询</el-button>
<el-button v-if="Number(token) === 2" type="success" plain icon="el-icon-download" size="small" style="margin-left: 7px" @click="exportExcel()">导出</el-button>
<el-button type="distribution" size="small" @click="addMaterial()">增加物料存货</el-button>
<el-button type="primary" icon="el-icon-printer" style="margin-left: 20px" plain size="small" @click="Pronters()">批量打印</el-button>
<!--<el-button type="distribution" size="small" @click="addProduct()">增加通用存货</el-button>-->
@@ -56,7 +57,22 @@
</el-table-column>
<el-table-column width="90" label="库位" align="center" sortable="货位名称" prop="货位名称">
<template slot-scope="scope">
{{ scope.row.货位名称 }}
<el-select
v-if="scope.row.edit"
v-model="scope.row.货位流水号"
size="mini"
style="width: 100%"
clearable
filterable
placeholder="请选择库位"
@change="handleLocationChange(scope.row)">
<el-option
v-for="item in storageLocation"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<span v-else>{{ scope.row.货位名称 }}</span>
</template>
</el-table-column>
<el-table-column label="批次编号" align="center" sortable="批次" prop="批次">
@@ -346,6 +362,27 @@ export default {
}
})
},
exportExcel() {
if (this.tableData.length === 0) {
this.$message.warning('暂无数据')
return
}
let materialName_check, specificationsModel_check, LocationName_check
this.materialName ? materialName_check = 1 : materialName_check = 0
this.specificationsModel ? specificationsModel_check = 1 : specificationsModel_check = 0
this.LocationName ? LocationName_check = 1 : LocationName_check = 0
var param = []
param[0] = ['物料名称_check', materialName_check]
param[1] = ['物料名称', this.materialName]
param[2] = ['图号或型号_check', specificationsModel_check]
param[3] = ['图号或型号', this.specificationsModel]
param[4] = ['库位_check', LocationName_check]
param[5] = ['库位', this.LocationName]
param[6] = ['排序名称', this.orderName]
param[7] = ['排序方式', this.order]
var Data = this.CreateData('2001', '仓储管理_库存盘点_查询', param)
this.exportExcel_NPOI(Data)
},
searchTable() {
this.loading = true
this.tableData = []
@@ -376,11 +413,14 @@ export default {
类型: response.data.rows[i].类型,
单位: response.data.rows[i].单位,
库存: response.data.rows[i].库存,
货位流水号: response.data.rows[i].货位流水号,
货位名称: response.data.rows[i].货位名称,
批次编号: response.data.rows[i].批次编号,
编辑人: response.data.rows[i].编辑人,
编辑时间: response.data.rows[i].编辑时间,
库存_before: response.data.rows[i].库存,
货位流水号_before: response.data.rows[i].货位流水号,
货位名称_before: response.data.rows[i].货位名称,
打印条码数: 1,
edit: false
})
@@ -502,12 +542,17 @@ export default {
})
},
confirmEdit(row) {
if (!row.货位流水号) {
this.$message.warning('请选择库位')
return
}
var param = []
param[0] = ['物料与货位流水号', row.物料与货位流水号]
param[1] = ['原数量', row.库存_before]
param[2] = ['更改后数量', row.库存]
param[3] = ['操作者', this.id]
param[4] = ['类型', row.类型]
param[5] = ['货位流水号', row.货位流水号]
var Data = this.CreateData('12', '仓储管理_库存盘点_货位数量_编辑数据', param)
this.ExecDatabase(Data).then(response => {
if (response.data[0].result === '1') {
@@ -518,10 +563,21 @@ export default {
}
})
},
handleLocationChange(row) {
for (let i = 0; i < this.storageLocation.length; i++) {
if (this.storageLocation[i].value === row.货位流水号) {
row.货位名称 = this.storageLocation[i].label
return
}
}
row.货位名称 = ''
},
// 取消编辑
cancelEdit(index, row) {
row.edit = false
row.库存 = row.库存_before
row.货位流水号 = row.货位流水号_before
row.货位名称 = row.货位名称_before
this.$message('取消修改')
},
handleSizeChanges(val) {
@@ -545,4 +601,3 @@ export default {
}
}
</script>

View File

@@ -123,7 +123,7 @@
<template slot-scope="scope">
<el-select v-if="scope.row._editing" v-model="scope.row.货位流水号" style="width:100%" placeholder="选择库位" size="mini" filterable clearable @change="onLocateChange(scope.row)">
<el-option
v-for="item in locateName"
v-for="item in scope.row.locateName"
:key="item.value"
:label="item.label"
:value="item.value"/>
@@ -161,9 +161,9 @@
<el-input-number v-model="scope.row.打印条码数" :min="1" size="mini" style="margin: 0;width: 100%"/>
</template>
</el-table-column>
<el-table-column width="80px" label="操作" align="center" show-overflow-tooltip>
<el-table-column width="140px" label="操作" align="center" show-overflow-tooltip fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="toggleRowEdit(scope.row)">{{ scope.row._editing ? '完成' : '编辑' }}</el-button>
<el-button type="text" size="small" @click="toggleRowEdit(scope.row)">{{ scope.row._editing ? '完成' : '库位' }}</el-button>
<el-button type="text" icon="el-icon-printer" size="small" @click="Pronter(scope.row)"/>
</template>
</el-table-column>
@@ -329,7 +329,7 @@
<template slot-scope="scope">
<el-select v-if="scope.row._editing" v-model="scope.row.货位流水号" style="width:100%" placeholder="选择库位" size="mini" filterable clearable @change="onLocateChange(scope.row)">
<el-option
v-for="item in locateName"
v-for="item in scope.row.locateName"
:key="item.value"
:label="item.label"
:value="item.value"/>
@@ -751,7 +751,6 @@ export default {
}
},
created() {
this.getLocateName()
this.getSupplierSelection()
this.getPeople()
this.getSelectTheOutsourcingManufacturers()
@@ -1058,10 +1057,90 @@ export default {
})
},
onLocateChange(row) {
var obj = this.locateName.find(function(item) {
var locateName = row.locateName || []
var obj = locateName.find(function(item) {
return item.value === row.货位流水号
})
row.货位名称 = obj ? obj.label : ''
this.$set(row, 'selectedLocate', obj || null)
},
confirmLocateChange(row) {
var locate = row.selectedLocate
if (!locate && row.货位流水号 !== null && row.货位流水号 !== undefined && row.货位流水号 !== '') {
locate = this.getLocateItemByValue(row.locateName, row.货位流水号)
}
this.$set(row, '货位名称', locate ? locate.name : '')
this.$set(row, '货位流水号', locate ? String(locate.value) : '')
this.updateSelectedLocate(row)
},
getLocateItemByValue(locateName, value) {
var list = locateName || []
for (let i = 0; i < list.length; i++) {
if (String(list[i].value) === String(value)) {
return list[i]
}
}
return null
},
syncLocateValueByName(row) {
var locateName = row.locateName || []
for (let i = 0; i < locateName.length; i++) {
if (locateName[i].name === row.货位名称) {
this.$set(row, '货位流水号', String(locateName[i].value))
return
}
}
},
updateSelectedLocate(row) {
for (let i = 0; i < this.multipleSelection.length; i++) {
if (this.multipleSelection[i] === row) {
this.multipleSelection[i].货位流水号 = row.货位流水号
this.multipleSelection[i].货位名称 = row.货位名称
}
}
for (let i = 0; i < this.multipleSelection2.length; i++) {
if (this.multipleSelection2[i] === row) {
this.multipleSelection2[i].货位流水号 = row.货位流水号
this.multipleSelection2[i].货位名称 = row.货位名称
}
}
this.refreshReturnsLocate(row)
},
refreshReturnsLocate(row) {
for (let i = 0; i < this.returns.length; i++) {
if (this.returns[i].物料编码 === row.物料编码 && this.returns[i].物料名称 === row.物料名称 && this.returns[i].规格型号 === row.图号或型号) {
this.returns[i].库位 = row.货位名称
}
}
},
getInventoryLocateName(row) {
var materialName = row.物料名称 || row.零件名称 || ''
var specificationsModel = row.图号或型号 || row.零件图号 || ''
let materialName_check, specificationsModel_check
materialName ? materialName_check = 1 : materialName_check = 0
specificationsModel ? specificationsModel_check = 1 : specificationsModel_check = 0
var param = []
param[0] = ['物料名称_check', materialName_check]
param[1] = ['物料名称', materialName]
param[2] = ['图号或型号_check', specificationsModel_check]
param[3] = ['图号或型号', specificationsModel]
param[4] = ['库位_check', 0]
param[5] = ['库位', '']
param[6] = ['排序名称', '']
param[7] = ['排序方式', '']
var Data = this.CreateData('11', '仓储管理_库存盘点_查询', param)
this.ExecDatabase(Data).then(response => {
var locateName = []
var data = response.data.rows || response.data
for (let i = 0; i < data.length; i++) {
var inventory = data[i].库存 || data[i].货位存量 || 0
locateName.push({
label: data[i].货位名称 + '(库存:' + inventory + '',
name: data[i].货位名称,
value: String(data[i].货位流水号)
})
}
this.$set(row, 'locateName', locateName)
})
},
getData() {
if (this.PartType === '采购入库') {
@@ -1119,6 +1198,8 @@ export default {
if (row.货位流水号 !== null && row.货位流水号 !== undefined && row.货位流水号 !== '') {
row.货位流水号 = String(row.货位流水号)
}
this.$set(row, 'locateName', [])
this.$set(row, 'selectedLocate', null)
this.$set(row, '_editing', false)
}
if (this.tableData1.length === 0) {
@@ -1141,7 +1222,13 @@ export default {
}.bind(this))
},
toggleRowEdit(row) {
this.$set(row, '_editing', !row._editing)
if (row._editing) {
this.confirmLocateChange(row)
this.$set(row, '_editing', false)
} else {
this.$set(row, '_editing', true)
this.getInventoryLocateName(row)
}
},
submitInStorage() {
// this.exportTable()
@@ -1171,6 +1258,7 @@ export default {
this.multipleSelection_variable = this.multipleSelection
this.multipleSelection = []
for (let i = 0; i < this.multipleSelection_variable.length; i++) {
this.syncLocateValueByName(this.multipleSelection_variable[i])
if (!this.multipleSelection_variable[i].货位流水号 && this.multipleSelection_variable[i].货位流水号 !== 0) {
this.$message.warning('存在未绑定货位的物料')
return
@@ -1285,6 +1373,8 @@ export default {
if (row.货位流水号 !== null && row.货位流水号 !== undefined && row.货位流水号 !== '') {
row.货位流水号 = String(row.货位流水号)
}
this.$set(row, 'locateName', [])
this.$set(row, 'selectedLocate', null)
this.$set(row, '_editing', false)
}
})
@@ -1307,6 +1397,7 @@ export default {
this.multipleSelection_variable2 = this.multipleSelection2
this.multipleSelection2 = []
for (let i = 0; i < this.multipleSelection_variable2.length; i++) {
this.syncLocateValueByName(this.multipleSelection_variable2[i])
if (!this.multipleSelection_variable2[i].货位流水号 && this.multipleSelection_variable2[i].货位流水号 !== 0) {
this.$message.warning('存在未绑定货位的物料')
return

View File

@@ -4,8 +4,8 @@ export const name1_config = `OP1000`
export const name3_config = `OP2000`
export const name4_config = `OP9999`
//
// var url = `http://192.168.1.27:8030` // 测试环境 接口端口号 http://192.168.1.27:8031 测试环境管理系统 0001 654321
var url = `http://192.168.1.27:8010` // 正式环境 接口端口号 http://192.168.1.27:8001 正式环境管理系统 0000 0000
var url = `http://192.168.1.27:8030` // 测试环境 接口端口号 http://192.168.1.27:8031 测试环境管理系统 0001 654321
// var url = `http://192.168.1.27:8010` // 正式环境 接口端口号 http://192.168.1.27:8001 正式环境管理系统 0000 0000
var url1 = `http://192.168.1.27:10006` // 立库数据库的接口端口号
// var url = `http://127.0.0.1:10250`
// var url = `http://124.220.15.242:8602`