Compare commits

...

33 Commits

Author SHA1 Message Date
砂糖
b93d636c8a feat: 更新应用版本至1.3.30并优化报餐统计功能
新增系统配置API模块
移除列表加载时的多余条件判断
优化报餐统计页面样式和逻辑
2026-04-14 11:18:33 +08:00
0e1599809d Merge remote-tracking branch 'origin/master' 2026-04-13 15:09:45 +08:00
3953399d90 更新2级写入,以及前端处理 2026-04-13 15:06:33 +08:00
砂糖
04c98c664b Merge branch 'master' of http://49.232.154.205:10100/liujingchao/klp-mono 2026-03-28 18:02:08 +08:00
砂糖
9fab0ade4d feat(发货): 新增发货功能模块及相关页面
- 添加发货功能页面及API接口
- 更新tabbar图标和路由配置
- 修改物料信息展示逻辑
- 优化页面跳转和权限控制
- 更新应用版本至1.3.29
2026-03-28 18:02:04 +08:00
cafa0a9a6c 二级更新计划新增软推荐,计划录入和实绩内容添加入场钢卷号信息 2026-03-20 14:36:23 +08:00
砂糖
3afed26d7f chore: 更新应用版本号至1.3.28 2026-03-11 17:12:09 +08:00
砂糖
a23a4645dc fix(wms): 修复日期解析问题并添加导出功能
修复日期解析函数中的变量名冲突和空值处理问题,同时在新增和修改操作中添加日期格式化处理。新增钢卷待操作导出功能。
2026-03-11 17:11:11 +08:00
砂糖
0902be0135 feat(报餐): 新增报餐功能模块并更新版本号至1.3.27
- 添加报餐页面及相关API接口
- 实现报餐统计功能,包括有效/无效报餐人数计算
- 新增自定义时间选择器组件
- 更新应用版本号至1.3.27
- 调整角色权限控制,新增食堂角色权限配置
2026-03-07 17:31:46 +08:00
砂糖
ac55c9a4d1 feat(search): 添加分页功能并更新版本号至1.3.26
在搜索页面添加分页控件,包括上一页/下一页按钮和分页信息显示
更新应用版本号至1.3.26,涉及config.js、manifest.json等文件
2026-03-06 13:56:02 +08:00
砂糖
2f47047ae6 feat: 更新应用版本至1.3.25并优化功能
- 更新config.js、manifest.json等文件中的版本号至1.3.25
- 在editby.vue页面添加品名、规格等钢卷信息展示
- 调整easycode.vue中otherTypes的过滤条件
- 修改klp-header.vue中的镀锌机组名称
- 更新update.js中的静态版本号
2026-01-27 09:45:06 +08:00
8e3a26448a app更新对l2数据显示 2026-01-15 20:18:37 +08:00
eed35addb5 二级后端添加数据快照修正 2026-01-15 18:37:39 +08:00
3c4e60bc49 二级后端添加数据快照修正。前端添加数字孪生 2026-01-15 17:37:57 +08:00
砂糖
166afcb959 feat: 更新生产线配置并优化WebSocket连接
- 移除未使用的生产线组件和配置
- 添加WebSocket URL配置项
- 将WebSocket连接改为使用uni.connectSocket
- 添加连接状态日志便于调试
2026-01-15 15:08:12 +08:00
砂糖
dd94606e86 feat(库位管理): 添加强制释放库位功能并更新版本至1.3.24
- 在actualWarehouse.js中添加forceReleaseLocation API
- 在easycode.vue中添加释放库位按钮及相关逻辑
- 更新应用版本号至1.3.24
- 完善钢卷详情显示逻辑,支持显示更多信息
2026-01-15 09:45:45 +08:00
砂糖
5bae1f405b feat: 更新版本号至1.3.23并新增真实库区选择器组件
refactor(warehouse-picker): 重构逻辑库区选择器组件
feat(actual-warehouse-picker): 新增真实库区选择器组件
fix(easycode.vue): 调整钢卷质量状态校验逻辑
style(search.vue): 优化表单样式和字段显示
2026-01-13 15:49:43 +08:00
砂糖
32a2210a5f Merge branch 'master' of http://49.232.154.205:10100/liujingchao/klp-mono 2026-01-13 11:31:01 +08:00
砂糖
a5b36e2f4c feat(钢卷查询): 新增钢卷查询页面及功能
- 添加钢卷查询页面(search.vue)实现条件查询功能
- 新增查询和重新查找悬浮按钮优化用户体验
- 更新版本号至1.3.22
- 添加tabbar搜索图标
- 扩展钢卷状态查询条件支持B/B-等级
2026-01-13 11:30:58 +08:00
0325df38ec 计划列表接口添加分页 2026-01-13 09:56:56 +08:00
67c742acee g29修改参数曲线,修复跟踪逻辑 2026-01-12 14:59:18 +08:00
砂糖
325041ada6 feat: 更新应用版本至1.3.21并扩展钢卷发货品质限制
- 将应用版本从1.3.20升级至1.3.21,更新了相关配置文件
- 修改发货逻辑,允许B和B-品质的钢卷发货
- 调整撤回成功提示的图标显示为'none'
2026-01-12 13:20:33 +08:00
砂糖
983bb0a172 feat(钢卷管理): 添加撤回发货功能并更新版本号至1.3.20
- 在钢卷管理页面新增撤回发货按钮及相关处理逻辑
- 添加撤回发货API接口cancelExportCoil
- 重构发货操作弹窗,支持发货和撤回发货两种操作
- 更新应用版本号至1.3.20,涉及config.js、manifest.json等文件
2026-01-11 19:05:44 +08:00
砂糖
3916546c50 Merge branch 'master' of http://49.232.154.205:10100/liujingchao/klp-mono 2026-01-11 17:21:56 +08:00
砂糖
1c74f99766 feat: 更新应用版本至1.3.19并修复发货逻辑
- 将应用版本从1.3.18升级至1.3.19
- 修复easycode.vue中的发货逻辑,启用原本注释掉的API调用
- 修改质量状态提示信息为更友好的uni.showToast方式
2026-01-11 17:21:50 +08:00
8f535018e5 二级plan新增内径 2026-01-11 16:57:22 +08:00
砂糖
cd491678d9 feat: 更新应用版本至1.3.18并启用更新提示
- 将config.js、manifest.json和version.json中的版本号更新为1.3.18
- 启用update.js中的版本更新提示模态框
- 在easycode.vue中添加发货操作的取消处理逻辑
2026-01-10 10:25:05 +08:00
砂糖
94e67c73b6 Merge branch 'master' of http://49.232.154.205:10100/liujingchao/klp-mono 2026-01-09 09:27:51 +08:00
砂糖
1cfd157c88 feat(FurCurrent): 添加温度标签映射以提升可读性
添加labelMap对象将温度字段映射为中文标签,替换原有的驼峰命名转换逻辑,使界面显示更直观
2026-01-09 09:27:46 +08:00
babf87ffd0 更正前端内容 2026-01-08 20:05:52 +08:00
砂糖
8ff6390613 feat(TensionTable): 为钢种字段添加下拉选择框
添加钢种下拉选择功能,替换原有的输入框。引入钢种列表API并实现数据获取,提升用户体验和数据准确性。
2026-01-08 09:35:47 +08:00
8c58a93e67 更正前端内容 2026-01-07 21:05:48 +08:00
f65a4439ae 更正前端内容 2026-01-07 20:15:34 +08:00
81 changed files with 7452 additions and 1415 deletions

View File

@@ -6,7 +6,8 @@
export default {
onLaunch: function() {
this.initApp()
updateManager.checkUpdate();
updateManager.checkUpdate();
plus.navigator.closeSplashscreen()
},
methods: {
// 初始化应用
@@ -17,14 +18,7 @@
//#ifdef H5
this.checkLogin()
//#endif
// uni.hideTabBar()
},
// mounted() {
// uni.hideTabBar()
// },
// onShow() {
// uni.hideTabBar()
// },
initConfig() {
this.globalData.config = config
},

View File

@@ -0,0 +1,18 @@
import zinc1Request from '@/utils/zinc1Request'
// 获取当前生产中的计划信息
export function getCurrentPlan() {
return zinc1Request({
url: '/api/business/dashboard/currentPlan',
method: 'get'
})
}
// 获取当前生产卷的关键工艺参数
export function getCurrentProcess() {
return zinc1Request({
url: '/api/business/dashboard/currentProcess',
method: 'get'
})
}

View File

@@ -0,0 +1,20 @@
import zinc1Request from '@/utils/zinc1Request'
// 生产实绩汇总
export function getReportSummary(params) {
return zinc1Request({
url: '/api/report/summary',
method: 'get',
params
})
}
// 生产实绩明细
export function getReportDetails(params) {
return zinc1Request({
url: '/api/report/details',
method: 'get',
params
})
}

View File

@@ -0,0 +1,11 @@
import zinc1Request from '@/utils/zinc1Request'
// 停机记录列表
export function listStoppage(data) {
return zinc1Request({
url: '/api/stoppage/list',
method: 'post',
data
})
}

View File

@@ -1,4 +1,6 @@
import request from '@/utils/request'
import errorCode from '@/utils/errorCode'
import { toast, tansParams } from '@/utils/common'
// 登录方法
export function login(username, password, code, uuid) {
@@ -18,6 +20,50 @@ export function login(username, password, code, uuid) {
})
}
// Zinc1系统登录方法直接使用uni.request因为登录接口不需要token完全静默处理
export function loginZinc1(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
const baseUrl = 'http://140.143.206.120:10082/prod-api'
const timeout = 10000
return new Promise((resolve, reject) => {
uni.request({
method: 'post',
timeout: timeout,
url: baseUrl + '/login',
data: data,
header: {
'Content-Type': 'application/json'
},
dataType: 'json'
}).then(response => {
let [error, res] = response
if (error) {
// 静默失败,不显示任何提示
reject('Zinc1系统连接异常')
return
}
const code = res.data.code || 200
if (code === 200 && res.data && res.data.token) {
// 只有成功时才resolve
resolve(res.data)
} else {
// 其他情况静默失败
reject('Zinc1登录失败')
}
}).catch(error => {
// 静默失败,不显示任何提示
reject(error)
})
})
}
// 注册方法
export function register(data) {
return request({

View File

@@ -1,7 +1,7 @@
import request from '@/utils/request'
import zinc1Request from '@/utils/zinc1Request'
export function listDeviceEnumAll() {
return request({
return zinc1Request({
url: '/api/deviceEnum/all',
method: 'get'
})

View File

@@ -1,7 +1,7 @@
import request from '@/utils/request'
import zinc1Request from '@/utils/zinc1Request'
export function getDeviceFieldMetaAll() {
return request({
return zinc1Request({
url: '/api/deviceFieldMeta/all',
method: 'get'
})

View File

@@ -1,8 +1,8 @@
import request from '@/utils/request'
import zinc1Request from '@/utils/zinc1Request'
// 获取最新N条设备快照
export function listDeviceSnapshotLatest(params) {
return request({
return zinc1Request({
url: '/api/deviceSnapshot/latest',
method: 'get',
params
@@ -11,7 +11,7 @@ export function listDeviceSnapshotLatest(params) {
// 按时间范围查询设备快照
export function listDeviceSnapshotRange(params) {
return request({
return zinc1Request({
url: '/api/deviceSnapshot/range',
method: 'get',
params

View File

@@ -0,0 +1,73 @@
import request from '@/utils/request'
// 查询参数列表
export function listConfig(query) {
return request({
url: '/system/config/list',
method: 'get',
params: query
})
}
// 查询参数详细
export function getConfig(configId) {
return request({
url: '/system/config/' + configId,
method: 'get'
})
}
// 根据参数键名查询参数值
export function getConfigKey(configKey) {
return request({
url: '/system/config/configKey/' + configKey,
method: 'get'
})
}
// 新增参数配置
export function addConfig(data) {
return request({
url: '/system/config',
method: 'post',
data: data
})
}
// 修改参数配置
export function updateConfig(data) {
return request({
url: '/system/config',
method: 'put',
data: data
})
}
// 修改参数配置
export function updateConfigByKey(key, value) {
return request({
url: '/system/config/updateByKey',
method: 'put',
data: {
configKey: key,
configValue: value
}
})
}
// 删除参数配置
export function delConfig(configId) {
return request({
url: '/system/config/' + configId,
method: 'delete'
})
}
// 刷新参数缓存
export function refreshCache() {
return request({
url: '/system/config/refreshCache',
method: 'delete'
})
}

View File

@@ -42,3 +42,26 @@ export function delActualWarehouse(actualWarehouseId) {
method: 'delete'
})
}
// 获取两级的树结构
export function treeActualWarehouseTwoLevel(query) {
return request({
url: '/wms/actualWarehouse/levelTwo',
method: 'get',
params: query
})
}
/**
* 强制释放库位
*/
export function forceReleaseLocation(actualWarehouseId) {
if (!actualWarehouseId) {
throw new Error('actualWarehouseId is required');
}
return request({
url: '/wms/actualWarehouse/release/' + actualWarehouseId,
method: 'put',
})
}

View File

@@ -75,3 +75,11 @@ export function exportCoil(coilId) {
method: 'get'
})
}
// 撤回钢卷发货
export function cancelExportCoil(coilId) {
return request({
url: '/wms/materialCoil/withdrawExportCoil/' + coilId,
method: 'get'
})
}

View File

@@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询发货单主列表
export function listDeliveryWaybill(query) {
return request({
url: '/wms/deliveryWaybill/list',
method: 'get',
params: query
})
}
// 查询发货单主详细
export function getDeliveryWaybill(waybillId) {
return request({
url: '/wms/deliveryWaybill/' + waybillId,
method: 'get'
})
}
// 新增发货单主
export function addDeliveryWaybill(data) {
return request({
url: '/wms/deliveryWaybill',
method: 'post',
data: data
})
}
// 修改发货单主
export function updateDeliveryWaybill(data) {
return request({
url: '/wms/deliveryWaybill',
method: 'put',
data: data
})
}
// 删除发货单主
export function delDeliveryWaybill(waybillId) {
return request({
url: '/wms/deliveryWaybill/' + waybillId,
method: 'delete'
})
}
export function updateDeliveryWaybillStatus(data) {
return request({
url: '/wms/deliveryWaybill/status',
method: 'put',
data: data
})
}

View File

@@ -0,0 +1,62 @@
import request from '@/utils/request'
// 查询发货单明细列表
export function listDeliveryWaybillDetail(query) {
return request({
url: '/wms/deliveryWaybillDetail/list',
method: 'get',
params: query
})
}
// 查询发货单明细详细
export function getDeliveryWaybillDetail(detailId) {
return request({
url: '/wms/deliveryWaybillDetail/' + detailId,
method: 'get'
})
}
// 新增发货单明细
export function addDeliveryWaybillDetail(data) {
return request({
url: '/wms/deliveryWaybillDetail',
method: 'post',
data: data
})
}
// 修改发货单明细
export function updateDeliveryWaybillDetail(data) {
return request({
url: '/wms/deliveryWaybillDetail',
method: 'put',
data: data
})
}
// 删除发货单明细
export function delDeliveryWaybillDetail(detailId) {
return request({
url: '/wms/deliveryWaybillDetail/' + detailId,
method: 'delete'
})
}
// 批量新增发货单明细
export function batchAddDeliveryWaybillDetail(data) {
return request({
url: '/wms/deliveryWaybillDetail/batch',
method: 'post',
data: data
})
}
// 查询已绑定发货的钢卷列表
export function listBoundCoil(query) {
return request({
url: '/wms/deliveryWaybillDetail/boundCoilList',
method: 'get',
params: query
})
}

View File

@@ -0,0 +1,72 @@
import request from '@/utils/request'
// 查询部门报餐主列表
export function listMealReport(query) {
return request({
url: '/wms/mealReport/list',
method: 'get',
params: query
})
}
// 查询部门报餐主详细
export function getMealReport(reportId) {
return request({
url: '/wms/mealReport/' + reportId,
method: 'get'
})
}
// 新增部门报餐主
export function addMealReport(data) {
return request({
url: '/wms/mealReport',
method: 'post',
data: data
})
}
// 修改部门报餐主
export function updateMealReport(data) {
return request({
url: '/wms/mealReport',
method: 'put',
data: data
})
}
// 删除部门报餐主
export function delMealReport(reportId) {
return request({
url: '/wms/mealReport/' + reportId,
method: 'delete'
})
}
// 报表相关接口
// 按餐别统计
export function getMealTypeCount(query) {
return request({
url: '/wms/mealReport/report/mealType',
method: 'get',
params: query
})
}
// 按部门统计
export function getDeptCount(query) {
return request({
url: '/wms/mealReport/report/dept',
method: 'get',
params: query
})
}
// 按日期统计
export function getDateCount(query) {
return request({
url: '/wms/mealReport/report/date',
method: 'get',
params: query
})
}

View File

@@ -1,5 +1,23 @@
import request from '@/utils/request'
function parseDate(date) {
// 修复1参数名和内部变量名冲突改用tempDate
// 修复2如果传入的date为空/无效,默认使用当前时间
const tempDate = date ? new Date(date) : new Date();
// 获取年、月、日、时、分、秒(补零处理,确保是两位数)
const year = tempDate.getFullYear();
// 月份从0开始所以要+1不足两位补0
const month = String(tempDate.getMonth() + 1).padStart(2, '0');
const day = String(tempDate.getDate()).padStart(2, '0');
const hours = String(tempDate.getHours()).padStart(2, '0');
const minutes = String(tempDate.getMinutes()).padStart(2, '0');
const seconds = String(tempDate.getSeconds()).padStart(2, '0');
// 格式化为YYYY-mm-dd HH:mm:ss并返回
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 查询钢卷待操作列表
export function listPendingAction(query) {
return request({
@@ -19,19 +37,33 @@ export function getPendingAction(actionId) {
// 新增钢卷待操作
export function addPendingAction(data) {
const payload = { ...data }
if (payload.processTime) {
payload.processTime = parseDate(payload.processTime)
}
if (payload.completeTime) {
payload.completeTime = parseDate(payload.completeTime)
}
return request({
url: '/wms/coilPendingAction',
method: 'post',
data: data
data: payload
})
}
// 修改钢卷待操作
export function updatePendingAction(data) {
const payload = { ...data }
if (payload.processTime) {
payload.processTime = parseDate(payload.processTime)
}
if (payload.completeTime) {
payload.completeTime = parseDate(payload.completeTime)
}
return request({
url: '/wms/coilPendingAction',
method: 'put',
data: data
data: payload
})
}
@@ -75,3 +107,12 @@ export function cancelAction(actionId) {
})
}
// 导出钢卷待操作
export function exportPendingAction(query) {
return request({
url: '/wms/coilPendingAction/export',
method: 'post',
params: query
})
}

View File

@@ -0,0 +1,402 @@
<template>
<view class="klp-actual-warehouse-picker">
<!-- 输入框触发器 - 全部修复阻止冒泡+固定右侧按钮+文字颜色差异化 -->
<view class="picker-input" @click="openPopup">
<!-- 文本区域未选择=浅灰色(占位色) 选中=深灰色(内容色) -->
<span class="picker-text" :class="{selected: selectedNode.actualWarehouseCode}">
{{ selectedNode.actualWarehouseCode || '请选择真实库区' }}
</span>
<!-- 终极防冒泡原生view包裹图标 + stop.prevent 双重阻断 + 绝对定位固定右侧 -->
<view class="clear-btn-wrap" v-show="selectedNode.actualWarehouseCode" @click.stop.prevent="handleClear">
<uni-icons type="clear" size="24" color="#999"></uni-icons>
</view>
</view>
<!-- uni-popup 原生组件 纯属性使用 无任何样式 -->
<uni-popup ref="popup" type="bottom" background-color="#fff" :mask-click="true" @mask-click="closePopup">
<!-- 弹窗内部根容器 承载所有样式 固定高度核心 -->
<view class="popup-inner-container">
<!-- 弹窗头部标题 -->
<view class="popup-header">选择实际库区</view>
<!-- 第一层横向滚动tab -->
<scroll-view scroll-x class="tab-scroll-view" scroll-with-animation>
<view class="tab-item-wrap">
<view class="tab-item" :class="{active: activeFirstId === item.actualWarehouseId}"
@click="handleFirstTabClick(item)" v-for="item in treeData" :key="item.actualWarehouseId">
{{ item.actualWarehouseName }}
</view>
</view>
</scroll-view>
<!-- 第二层横向滚动tab点选第一层tab后才出现 -->
<scroll-view v-if="activeFirstId" scroll-x class="tab-scroll-view tab-second" scroll-with-animation>
<view class="tab-item-wrap">
<view class="tab-item" :class="{active: activeSecondId === item.actualWarehouseId}"
@click="handleSecondTabClick(item)" v-for="item in secondLevelList" :key="item.actualWarehouseId">
{{ item.actualWarehouseName }}
</view>
</view>
</scroll-view>
<!-- 核心修改筛选框抽离出来 独立节点 固定置顶 永不滚动 -->
<view class="search-box">
<uni-icons type="search" size="24" color="#999"></uni-icons>
<input type="text" v-model="searchKeyword" placeholder="输入编码搜索仓库" class="search-input"
placeholder-class="search-placeholder" />
<uni-icons v-if="searchKeyword" type="clear" size="24" color="#999" class="clear-icon"
@click="searchKeyword = ''"></uni-icons>
</view>
<!-- 仅这个区域滚动纯数据列表区 -->
<view class="select-scroll-content">
<!-- 加载中状态 -->
<view class="loading" v-if="loading">加载中...</view>
<!-- 情况1只选中第一层 展示筛选后的二级数据 -->
<view v-else-if="activeFirstId && !activeSecondId" class="select-list">
<view class="select-item" @click="handleSelectSecondItem(item)" v-for="item in filterSecondList"
:key="item.actualWarehouseId">
{{ item.name }}
<text class="code">{{ item.actualWarehouseCode }}</text>
</view>
</view>
<!-- 情况2选中第二层 展示筛选后的三级数据 -->
<view v-else-if="activeSecondId" class="select-list">
<view class="select-item" @click="handleSelectThirdItem(item)" v-for="item in filterThirdList"
:key="item.actualWarehouseId">
{{ item.name }}
<text class="code">{{ item.actualWarehouseCode }}</text>
</view>
</view>
<!-- 空数据展示 -->
<view class="empty" v-else-if="showEmpty">暂无仓库数据</view>
<!-- 筛选后无数据的兜底提示 -->
<view class="empty"
v-else-if="searchKeyword && (filterSecondList.length === 0 && filterThirdList.length === 0)">暂无匹配的仓库编码
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
treeActualWarehouseTwoLevel,
listActualWarehouse
} from '@/api/wms/actualWarehouse.js'
export default {
name: "klp-actual-warehouse-picker",
props: {
showEmpty: {
default: false,
type: Boolean
},
value: {
default: undefined,
}
},
data() {
return {
treeData: [],
secondLevelList: [],
thirdLevelList: [],
selectedNode: {},
activeFirstId: '',
activeSecondId: '',
loading: false,
searchKeyword: '' // 筛选关键词
};
},
computed: {
// 完美双向绑定
innerValue: {
get() {
return this.value
},
set(val) {
this.$emit('input', val);
this.$emit('change', this.selectedNode);
}
},
// ✅ 精准匹配:只做 actualWarehouseCode 仓库编码的模糊筛选(纯编码匹配,无名称)
filterSecondList() {
if (!this.searchKeyword) return this.secondLevelList
const keyword = this.searchKeyword.trim().toLowerCase()
return this.secondLevelList.filter(item => {
const code = item.actualWarehouseCode?.toLowerCase() || ''
return code.includes(keyword)
})
},
// ✅ 精准匹配:只做 actualWarehouseCode 仓库编码的模糊筛选(纯编码匹配,无名称)
filterThirdList() {
if (!this.searchKeyword) return this.thirdLevelList
const keyword = this.searchKeyword.trim().toLowerCase()
return this.thirdLevelList.filter(item => {
const code = item.actualWarehouseCode?.toLowerCase() || ''
return code.includes(keyword)
})
}
},
created() {
this.getTwoLevelData()
},
methods: {
async getTwoLevelData() {
try {
this.loading = true
const res = await treeActualWarehouseTwoLevel()
this.treeData = res.data || []
} catch (err) {
uni.showToast({
title: '加载仓库数据失败',
icon: 'none'
})
console.error('仓库数据加载异常:', err)
} finally {
this.loading = false
}
},
// ✅ 新增:清空选中的仓库数据 + 重置所有状态
handleClear() {
// 1. 清空选中的仓库节点数据
this.selectedNode = {}
// 2. 双向绑定同步清空父组件v-model能拿到空值
this.innerValue = undefined
// 3. 重置一级/二级选中的tab id
this.activeFirstId = ''
this.activeSecondId = ''
// 4. 清空二级、三级仓库列表数据
this.secondLevelList = []
this.thirdLevelList = []
// 5. 清空搜索关键词
this.searchKeyword = ''
// 可选:清空成功的轻提示,和你原有提示风格一致
uni.showToast({
title: '已清空选择',
icon: 'none',
duration: 1200
})
},
openPopup() {
this.$refs.popup.open()
},
closePopup() {
this.$refs.popup.close()
this.searchKeyword = ''
},
handleFirstTabClick(item) {
this.activeFirstId = item.actualWarehouseId
this.activeSecondId = ''
this.thirdLevelList = []
this.searchKeyword = ''
this.secondLevelList = item.children || []
},
async handleSecondTabClick(item) {
this.activeSecondId = item.actualWarehouseId
this.thirdLevelList = []
this.searchKeyword = ''
try {
this.loading = true
const res = await listActualWarehouse({
parentId: item.actualWarehouseId
})
this.thirdLevelList = res.data || []
} catch (err) {
uni.showToast({
title: '加载库区数据失败',
icon: 'none'
})
console.error('三级仓库加载异常:', err)
} finally {
this.loading = false
}
},
handleSelectSecondItem(item) {
this.selectedNode = item
this.innerValue = item.actualWarehouseId
this.closePopup()
},
handleSelectThirdItem(item) {
this.selectedNode = item
this.innerValue = item.actualWarehouseId
this.closePopup()
}
}
}
</script>
<style scoped lang="scss">
.klp-actual-warehouse-picker {
width: 100%;
box-sizing: border-box;
}
// 输入框触发器样式 - 全部优化:父容器相对定位 + 文本颜色区分 + 按钮绝对定位固定右侧
.picker-input {
width: 100%;
height: 88rpx;
line-height: 88rpx;
padding: 0 30rpx;
// ✅ 给右侧清除按钮预留空间,防止文本和按钮重叠
padding-right: 70rpx;
border: 1px solid #e5e5e5;
background-color: #f8f9fa;
border-radius: 12rpx;
font-size: 28rpx;
box-sizing: border-box;
// ✅ 关键:父容器相对定位,为子元素绝对定位做铺垫
position: relative;
// ✅ 文本颜色差异化核心样式
.picker-text {
// 未选择仓库 - 占位文本颜色浅灰色placeholder色
color: #999;
&.selected {
// 选中仓库后 - 内容文本颜色(深灰色,主色)
color: #333;
}
}
// ✅ 清除按钮:绝对定位 死死固定在输入框最右侧 永不偏移
.clear-btn-wrap {
position: absolute;
right: 20rpx; // 距离右侧固定间距
top: 50%;
transform: translateY(-50%); // 垂直居中完美对齐
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
// 按钮点击按压反馈,和你原有风格一致
&:active {
background-color: #e5e5e5;
}
}
}
// ✅ 弹窗内部根容器 - 固定高度核心 样式100%生效
.popup-inner-container {
width: 100%;
height: 80vh; // 弹窗固定高度,适配所有手机
// max-height: 900rpx; // 大屏高度上限,防止过高
overflow: hidden; // 禁止整体滚动
display: flex;
flex-direction: column; // 垂直布局核心
}
// 弹窗头部标题 - 固定不滚动
.popup-header {
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
border-bottom: 1px solid #f5f5f5;
flex-shrink: 0; // 固定高度 不被压缩
}
// 横向滚动tab容器 - 固定不滚动
.tab-scroll-view {
width: 100%;
white-space: nowrap;
padding: 20rpx 0;
border-bottom: 1px solid #f5f5f5;
flex-shrink: 0; // 固定高度 不被压缩
.tab-item-wrap {
display: inline-block;
padding: 0 20rpx;
}
.tab-item {
display: inline-block;
padding: 12rpx 30rpx;
margin: 0 10rpx;
font-size: 28rpx;
border-radius: 20rpx;
color: #666;
&.active {
background: #007aff;
color: #fff;
}
}
}
// 二级tab区分隔线
.tab-second {
border-top: 1px solid #f5f5f5;
}
// ✅✅✅ 筛选框样式 - 固定置顶 永不滚动 核心样式
.search-box {
display: flex;
align-items: center;
padding: 16rpx 30rpx;
margin: 10rpx 20rpx 0;
background: #f8f8f8;
border-radius: 30rpx;
flex-shrink: 0; // 关键:固定高度 不参与滚动 不被压缩
.search-input {
flex: 1;
height: 50rpx;
line-height: 50rpx;
font-size: 26rpx;
color: #333;
margin: 0 10rpx;
}
.search-placeholder {
font-size: 26rpx;
color: #999;
}
.clear-icon {
padding: 0 8rpx;
}
}
// ✅✅✅ 唯一滚动区域:纯数据列表区
.select-scroll-content {
flex: 1; // 占满弹窗剩余所有高度
overflow-y: auto; // 仅此处滚动 核心!
padding: 10rpx 0;
box-sizing: border-box;
.select-list {
.select-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
font-size: 40rpx;
border-bottom: 1px solid #f5f5f5;
&:active {
background-color: #f5f5f5;
}
.code {
color: #000;
font-size: 30rpx;
}
}
}
.loading,
.empty {
text-align: center;
padding: 50rpx 0;
font-size: 28rpx;
color: #999;
}
}
</style>

View File

@@ -37,10 +37,10 @@ export default {
// 产线列表原Tab列表改造可外部传入此处保留默认值
lineList: [
{ name: "科伦普重工-酸轧机组", key: "acidity" },
{ name: "科伦普重工-彩涂机组", key: "paint" },
{ name: "科伦普重工-镀锌线一组", key: "zinc1" },
{ name: "科伦普重工-镀锌线二组", key: "zinc2" },
{ name: "科伦普重工-镀锌线三组", key: "zinc3" },
// { name: "科伦普重工-彩涂机组", key: "paint" },
{ name: "科伦普重工-镀锌组", key: "zinc1" },
// { name: "科伦普重工-镀锌线二组", key: "zinc2" },
// { name: "科伦普重工-镀锌线三组", key: "zinc3" },
],
// 当前选中的产线索引关联picker
currentLineIndex: 0,

View File

@@ -7,7 +7,9 @@
<text class="picker-text" :class="{ 'picker-placeholder': !itemType }">
{{ itemType === 'product' ? '成品' : itemType === 'raw_material' ? '原料' : '请选择物品类型' }}
</text>
<text class="picker-arrow" v-if="!disabled"></text>
<!-- 互斥显示选中类型=清除按钮 未选中=下拉箭头 -->
<!-- <text class="picker-clear" v-if="itemType && !disabled" @click.stop.prevent="handleClearItemType"></text> -->
<text class="picker-arrow"></text>
</view>
</view>
@@ -18,7 +20,9 @@
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ loadingProducts ? '加载中...' : selectedName || '请选择产品' }}
</text>
<text class="picker-arrow" v-if="!disabled && !loadingProducts"></text>
<!-- 互斥显示选中产品=清除按钮 未选中/加载中=下拉箭头 -->
<text class="picker-clear" v-if="selectedName && !disabled && !loadingProducts" @click.stop.prevent="handleClearProduct"></text>
<text class="picker-arrow" v-else-if="!disabled && !loadingProducts"></text>
</view>
</view>
@@ -29,7 +33,9 @@
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ loadingRawMaterials ? '加载中...' : selectedName || '请选择原材料' }}
</text>
<text class="picker-arrow" v-if="!disabled && !loadingRawMaterials"></text>
<!-- 互斥显示选中原料=清除按钮 未选中/加载中=下拉箭头 -->
<text class="picker-clear" v-if="selectedName && !disabled && !loadingRawMaterials" @click.stop.prevent="handleClearRawMaterial"></text>
<text class="picker-arrow" v-else-if="!disabled && !loadingRawMaterials"></text>
</view>
</view>
@@ -259,7 +265,6 @@
const keyword = this.rawMaterialSearchKeyword.trim().toLowerCase();
this.filteredRawMaterials = keyword
? this.rawMaterials.filter(m => {
console.log(m.specification)
// 只基于原材料名称过滤,且确保是原材料数据
return (m.rawMaterialName && m.rawMaterialName.toLowerCase().includes(keyword))
|| m?.specification?.toLowerCase()?.includes(keyword)
@@ -324,13 +329,36 @@
},
closeRawMaterialPicker() {
this.$refs.rawMaterialPopup.close();
},
// ✅ 新增核心清除方法1清空物品类型 + 所有关联数据(完全重置)
handleClearItemType() {
this.$emit('update:itemType', '');
this.$emit('update:itemId', undefined);
this.$emit('update:materialType', undefined);
this.selectedName = '';
uni.showToast({ title: '已清空物品类型', icon: 'none' });
},
// ✅ 新增核心清除方法2仅清空选中的产品保留物品类型为成品
handleClearProduct() {
this.$emit('update:itemId', undefined);
this.selectedName = '';
uni.showToast({ title: '已清空产品选择', icon: 'none' });
},
// ✅ 新增核心清除方法3仅清空选中的原材料保留物品类型为原料
handleClearRawMaterial() {
this.$emit('update:itemId', undefined);
this.selectedName = '';
uni.showToast({ title: '已清空原材料选择', icon: 'none' });
}
}
};
</script>
<style scoped lang="scss">
/* 样式保持不变 */
/* 样式保持不变 + 新增清除按钮样式 */
.form-item-optional {
margin-bottom: 30rpx;
@@ -347,6 +375,7 @@
width: 100%;
height: 88rpx;
padding: 0 24rpx;
padding-right: 60rpx; // ✅ 新增:给清除按钮/箭头预留空间,防止文本遮挡
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
@@ -382,6 +411,23 @@
color: #999;
margin-left: 10rpx;
}
// ✅ 新增:清除按钮样式,与箭头风格统一,点击有按压反馈
.picker-clear {
font-size: 28rpx;
color: #999;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 8rpx;
border-radius: 50%;
&:active {
background-color: #e8e8e8;
color: #666;
}
}
}
.warehouse-popup {

View File

@@ -1,6 +1,6 @@
<template>
<view>
<!-- 选择器触发按钮 -->
<!-- 选择器触发按钮 - 核心互斥未选中=箭头 / 选中=清除按钮二选一 -->
<view
class="picker-input"
@click="handleOpen"
@@ -9,15 +9,21 @@
<text class="picker-text" :class="{ 'picker-placeholder': !selectedName }">
{{ selectedName || placeholder }}
</text>
<text class="picker-arrow" v-if="!disabled"></text>
<!-- 互斥核心选中库区 清除按钮 | 未选中 下拉箭头永不共存 -->
<text
class="picker-clear"
v-if="selectedName && !disabled"
@click.stop.prevent="handleClear"
></text>
<text class="picker-arrow" v-else-if="!disabled"></text>
</view>
<!-- 弹窗内容 -->
<!-- 弹窗内容 - 固定逻辑库区标题无任何真实库区相关内容 -->
<uni-popup ref="popup" type="bottom" @close="handlePopupClose">
<view class="warehouse-popup">
<!-- 弹窗头部 -->
<!-- 弹窗头部 - 固定标题选择逻辑库区 -->
<view class="popup-header">
<text class="popup-title">{{ title || (wareType === 'virtual' ? '选择逻辑库区' : '选择真实库区') }}</text>
<text class="popup-title">选择逻辑库区</text>
<text class="popup-close" @click="handleClose"></text>
</view>
@@ -42,11 +48,11 @@
<view
class="warehouse-item"
v-for="item in filteredList"
:key="getItemId(item)"
:key="item.warehouseId"
@click="handleSelect(item)"
>
<text class="warehouse-name">{{ getItemName(item) }}</text>
<text class="warehouse-check" v-if="getItemId(item) === selectedId"></text>
<text class="warehouse-name">{{ item.warehouseName }}</text>
<text class="warehouse-check" v-if="item.warehouseId === selectedId"></text>
</view>
<view class="empty-tip" v-if="filteredList.length === 0">
<text>未找到匹配的库区</text>
@@ -65,13 +71,13 @@
</template>
<script>
// ✅ 只保留逻辑库区接口,彻底删除真实库区接口引入
import { listWarehouse } from '@/api/wms/warehouse.js'
import { listActualWarehouse } from '@/api/wms/actualWarehouse.js'
export default {
name: 'WarehousePicker',
props: {
// 已选中的库区ID用于回显
// 已选中的逻辑库区ID用于回显
value: {
type: [String, Number],
default: ''
@@ -86,25 +92,17 @@ export default {
type: String,
default: '请选择库区'
},
// 弹窗标题
// 弹窗标题(保留该属性,如需自定义可传,默认固定为选择逻辑库区)
title: {
type: String,
default: ''
},
// 库区类型virtual-逻辑库区actual-真实库区
wareType: {
type: String,
default: 'virtual',
validator: (value) => {
return ['virtual', 'actual'].includes(value)
}
}
},
data() {
return {
// 弹窗显示状态
showPopup: false,
// 所有库区列表
// 所有逻辑库区列表
dataList: [],
// 过滤后的库区列表
filteredList: [],
@@ -137,31 +135,17 @@ export default {
immediate: true,
handler(val) {
if (val && this.dataList.length) {
const matched = this.dataList.find(item => this.getItemId(item) === val)
this.selectedName = matched ? this.getItemName(matched) : ''
const matched = this.dataList.find(item => item.warehouseId === val)
this.selectedName = matched ? matched.warehouseName : ''
}
}
},
// 监听库区类型变化,重新加载数据
wareType: {
immediate: true,
handler() {
this.loadWarehouses()
}
}
},
mounted() {
this.loadWarehouses()
},
methods: {
// 根据类型获取项目ID
getItemId(item) {
return this.wareType === 'virtual' ? item.warehouseId : item.actualWarehouseId
},
// 根据类型获取项目名称
getItemName(item) {
return this.wareType === 'virtual' ? item.warehouseName : item.actualWarehouseName
},
// 加载库区列表
// 加载逻辑库区列表 ✅ 精简后:只保留逻辑库区接口请求,无任何类型判断
async loadWarehouses() {
if (this.loading) return
@@ -170,23 +154,18 @@ export default {
this.errorMsg = ''
try {
// 根据类型选择不同的API
const res = this.wareType === 'virtual'
? await listWarehouse({ pageNum: 1, pageSize: 1000 })
: await listActualWarehouse({ pageNum: 1, pageSize: 1000 })
const res = await listWarehouse({ pageNum: 1, pageSize: 1000 })
if (res.code === 200) {
this.dataList = res[this.wareType === 'virtual' ? 'data' : 'rows'] || []
this.dataList = res.data || []
this.filteredList = [...this.dataList]
// 初始化选中项名称
this.updateSelectedName()
} else {
throw new Error(res.msg || `${this.wareType === 'virtual' ? '逻辑' : '真实'}库区数据加载失败`)
throw new Error(res.msg || '逻辑库区数据加载失败')
}
} catch (err) {
this.error = true
this.errorMsg = err.message
console.error(`${this.wareType === 'virtual' ? '逻辑' : '真实'}库区加载失败:`, err)
console.error('逻辑库区加载失败:', err)
} finally {
this.loading = false
}
@@ -196,29 +175,33 @@ export default {
updateSelectedName() {
if (!this.selectedId) return
const matched = this.dataList.find(
item => this.getItemId(item) === this.selectedId
)
this.selectedName = matched ? this.getItemName(matched) : ''
const matched = this.dataList.find(item => item.warehouseId === this.selectedId)
this.selectedName = matched ? matched.warehouseName : ''
},
// 搜索过滤
// 搜索过滤:只匹配逻辑库区名称
handleSearch() {
const keyword = this.searchKeyword.trim().toLowerCase()
if (!keyword) {
this.filteredList = [...this.dataList]
} else {
this.filteredList = this.dataList.filter(item =>
this.getItemName(item).toLowerCase().includes(keyword)
item.warehouseName.toLowerCase().includes(keyword)
)
}
},
// 选择库区
// 选择逻辑库区:保留禁用库区过滤逻辑
handleSelect(item) {
this.selectedId = this.getItemId(item)
this.selectedName = this.getItemName(item)
// 通知父组件选中的完整信息
if (item.isEnabled == 0) {
uni.showToast({
title: '该库区已禁用',
icon: 'none'
});
return;
}
this.selectedId = item.warehouseId
this.selectedName = item.warehouseName
this.$emit('change', item)
this.handleClose()
},
@@ -226,7 +209,6 @@ export default {
// 打开弹窗
handleOpen() {
if (this.disabled) return
// 每次打开前重置搜索
this.searchKeyword = ''
this.filteredList = [...this.dataList]
this.showPopup = true
@@ -242,17 +224,25 @@ export default {
// 弹窗关闭回调
handlePopupClose() {
this.showPopup = false
},
// ✅ 清空选中的库区数据清空ID+名称,双向绑定同步,触发空回调
handleClear() {
this.selectedId = '';
this.selectedName = '';
this.$emit('change', {});
}
}
}
</script>
<style scoped lang="scss">
/* 保持原有样式不变 */
/* 保持原有样式不变,样式适配互斥显示,无需任何修改 */
.picker-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
padding-right: 60rpx; // 预留按钮/箭头空间,防止文本遮挡
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
@@ -283,11 +273,29 @@ export default {
}
}
// 下拉箭头样式
.picker-arrow {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
// 清除按钮样式 (和箭头位置一致,样式协调)
.picker-clear {
font-size: 28rpx;
color: #999;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 8rpx;
border-radius: 50%;
&:active {
background-color: #e8e8e8;
color: #666;
}
}
}
.warehouse-popup {
@@ -417,7 +425,7 @@ export default {
.empty-tip {
text-align: center;
padding: 60rpx 0;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}

View File

@@ -404,7 +404,7 @@ export default {
gridColor: "#e4e7ed",
showTitle: true,
fontSize: 10,
data: [{ min: 0, title: "温度(°C)" }]
data: [{ min: 0, title: "m/min" }]
},
extra: {
line: {

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,14 @@
module.exports = {
// baseUrl: 'http://192.168.31.116:8080',
baseUrl: 'http://140.143.206.120:8080',
wsUrl: 'ws://140.143.206.120:18081',
// baseUrl: 'http://localhost:8080',
// 应用信息
appInfo: {
// 应用名称
name: "ruoyi-app",
// 应用版本
version: "1.3.17",
version: "1.3.30",
// 应用logo
logo: "/static/logo.jpg",
// 官方网站

View File

@@ -2,7 +2,7 @@
"name" : "科伦普",
"appid" : "__UNI__E781B49",
"description" : "",
"versionName" : "1.3.17",
"versionName" : "1.3.28",
"versionCode" : 1,
"transformPx" : false,
"app-plus" : {

View File

@@ -82,59 +82,25 @@
"style": {
"navigationBarTitleText": "钢卷收货"
}
},
{
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "钢卷查询"
}
},
{
"path": "pages/meal/meal",
"style": {
"navigationBarTitleText": "报餐情况"
}
},
{
"path": "pages/fahuo/fahuo",
"style": {
"navigationBarTitleText": "发货"
}
}
// {
// "path": "pages/register",
// "style": {
// "navigationBarTitleText": "注册"
// }
// }, {
// "path": "pages/work/index",
// "style": {
// "navigationBarTitleText": "工作台"
// }
// },
// {
// "path": "pages/mine/avatar/index",
// "style": {
// "navigationBarTitleText": "修改头像"
// }
// }, {
// "path": "pages/mine/info/edit",
// "style": {
// "navigationBarTitleText": "编辑资料"
// }
// }, {
// "path": "pages/mine/pwd/index",
// "style": {
// "navigationBarTitleText": "修改密码"
// }
// }, {
// "path": "pages/mine/setting/index",
// "style": {
// "navigationBarTitleText": "应用设置"
// }
// }, {
// "path": "pages/mine/help/index",
// "style": {
// "navigationBarTitleText": "常见问题"
// }
// }, {
// "path": "pages/mine/about/index",
// "style": {
// "navigationBarTitleText": "关于我们"
// }
// }, {
// "path": "pages/common/webview/index",
// "style": {
// "navigationBarTitleText": "浏览网页"
// }
// }, {
// "path": "pages/common/textview/index",
// "style": {
// "navigationBarTitleText": "浏览文本"
// }
// },
],
"globalStyle": {
"navigationBarTextStyle": "black",
@@ -150,23 +116,35 @@
"selectedIconPath": "/static/images/tabbar/home_.png",
"iconPath": "/static/images/tabbar/home.png"
},
// {
// "text": "扫码",
// "pagePath": "pages/code/code",
// "selectedIconPath": "/static/images/tabbar/work_.png",
// "iconPath": "/static/images/tabbar/work.png"
// },
{
"text": "扫码",
"pagePath": "pages/easycode/easycode",
"selectedIconPath": "/static/images/tabbar/work_.png",
"iconPath": "/static/images/tabbar/work.png"
},
// {
// "text": "收货",
// "pagePath": "pages/receive/receive",
// "selectedIconPath": "/static/images/tabbar/receive_.png",
// "iconPath": "/static/images/tabbar/receive.png"
// },
// {
// "text": "查找",
// "pagePath": "pages/search/search",
// "selectedIconPath": "/static/images/tabbar/search_.png",
// "iconPath": "/static/images/tabbar/search.png"
// },
{
"text": "收货",
"pagePath": "pages/receive/receive",
"selectedIconPath": "/static/images/tabbar/receive_.png",
"iconPath": "/static/images/tabbar/receive.png"
"text": "报餐",
"pagePath": "pages/meal/meal",
"selectedIconPath": "/static/images/tabbar/meal_.png",
"iconPath": "/static/images/tabbar/meal.png"
},
{
"text": "发货",
"pagePath": "pages/fahuo/fahuo",
"selectedIconPath": "/static/images/tabbar/fahuo_.png",
"iconPath": "/static/images/tabbar/fahuo.png"
},
{
"text": "我的",
@@ -184,4 +162,5 @@
"k-(.*)": "@/components/klp-ui/k-$1/k-$1.vue"
}
}
}

View File

@@ -36,9 +36,11 @@
</view>
<view class="btn-grid quick-grid">
<button class="type-btn ship-btn" @click="handleShip">发货</button>
<button class="type-btn cancelship-btn" @click="handleCancelShip">撤回发货</button>
<button class="type-btn move-btn" @click="handleTranfer">移库</button>
<button class="type-btn see-btn" @click="handleSee">查看存储钢卷</button>
<button class="type-btn packing-btn" @click="handlePack">打包</button>
<button class="type-btn release-btn" @click="handleRelease">释放库位</button>
</view>
</view>
@@ -108,6 +110,73 @@
<!-- <klp-scaner></klp-scaner> -->
<qs-scanlistener ref='pda'></qs-scanlistener>
<uni-popup ref="shipPopup" type="bottom">
<view style="background-color: white; padding: 20rpx;">
<view class="info-card" v-if="coilDetail.coilId">
<view class="info-grid">
<view class="info-item">
<text class="item-label">入场钢卷号</text>
<text class="item-value">{{ coilDetail.enterCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">当前钢卷号</text>
<text class="item-value">{{ coilDetail.currentCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">逻辑库区</text>
<text class="item-value">{{ coilDetail.warehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">实际库区</text>
<text class="item-value">{{ coilDetail.actualWarehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">重量</text>
<text class="item-value">{{ coilDetail.netWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家</text>
<text class="item-value">{{ (coilDetail.manufacturer) || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">材质</text>
<text class="item-value">{{ (coilDetail.material) || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">规格</text>
<text class="item-value">{{ (coilDetail.specification) || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">品名</text>
<text class="item-value">{{ (coilDetail.itemName) || '-' }}</text>
</view>
</view>
</view>
<button
v-if="currentAction == 'withdrawShip'"
:disabled="buttonLoading"
:loading="buttonLoading"
@click="handleCancelShipSubmit">
撤回发货
</button>
<button
v-if="currentAction == 'ship'"
:disabled="buttonLoading"
:loading="buttonLoading"
@click="handleShipSubmit">
发货
</button>
<button
v-if="currentAction == 'release'"
:disabled="buttonLoading"
:loading="buttonLoading"
@click="handleReleaseSubmit">
释放库位
</button>
</view>
</uni-popup>
</view>
</template>
@@ -123,13 +192,15 @@
updateMaterialCoilSimple,
listMaterialCoil,
updateMaterialCoil,
exportCoil
exportCoil,
cancelExportCoil
} from '@/api/wms/coil.js'
import {
addPendingAction
} from '@/api/wms/pendingAction.js'
import {
getActualWarehouse
getActualWarehouse,
forceReleaseLocation
} from '@/api/wms/actualWarehouse.js'
export default {
@@ -141,7 +212,9 @@
form: {},
targetWarehouse: null, // 存储选中的目标库区信息
bomDialogShow: false, // BOM参数弹窗控制原有功能补充
mode: 'pda' // pda或camera
mode: 'pda', // pda或camera
currentAction: '', // ship,withdrawShip,release
buttonLoading: false,
}
},
computed: {
@@ -156,7 +229,7 @@
otherTypes() {
return this.types.filter(item => {
const value = parseInt(item.dictValue);
return value < 100 || value > 199;
return value < 100 || (value > 199 && value < 400);
});
}
},
@@ -169,6 +242,54 @@
})
},
async handleCancelShip() {
// 撤回发货
this.currentAction = 'withdrawShip';
const content = await this.getQRCodeContent()
let coilId = content.current_coil_id && content.current_coil_id !== 'null' ? content
.current_coil_id : null;
if (!coilId) {
uni.showToast({
title: '二维码异常',
icon: 'none'
})
return;
}
const coilRes = await getMaterialCoil(coilId);
if (coilRes.data.status == 0) {
uni.showToast({
title: '钢卷还未发货',
icon: 'error'
})
return;
}
this.coilDetail = coilRes.data;
this.$refs.shipPopup.open('bottom');
},
async handleCancelShipSubmit() {
if (!this.coilDetail.coilId) {
uni.showToast({
title: '钢卷标识缺失',
icon: 'none'
})
}
const res = await cancelExportCoil(this.coilDetail.coilId)
console.log(res)
if (res.code != 200) {
uni.showToast({
icon: 'error',
title: res.message || '撤回失败请重试'
})
return;
}
uni.showToast({
title: this.coilDetail.currentCoilNo + '发货已撤回',
icon: 'none'
})
this.$refs.shipPopup.close()
},
scan(mode = 'camera') {
return new Promise((resolve, reject) => {
if (mode == 'camera') {
@@ -234,6 +355,55 @@
})
}
},
async handleRelease() {
const actualWarehouseId = await this.scan();
// 查询真实库区在此处的钢卷
const res = await listMaterialCoil({
actualWarehouseId,
dataType: 1
})
if (res.total == 0) {
uni.showToast({
title: '该库区未发现钢卷',
icon: 'none'
});
return;
};
if (res.rows.length == 1) {
this.coilDetail = res.rows[0]
this.currentAction = 'release';
this.$refs.shipPopup.open('bottom');
} else {
uni.showToast({
title: '数据异常',
icon: 'none'
})
}
},
async handleReleaseSubmit() {
this.buttonLoading = true;
try {
const res = await forceReleaseLocation(this.coilDetail.actualWarehouseId);
if (res.code == 200) {
uni.showToast({
title: '释放成功',
icon: 'none'
})
this.$refs.shipPopup.close()
} else {
throw new Error()
}
} catch {
uni.showToast({
title: '库区释放失败',
icon: 'none'
})
} finally {
this.buttonLoading = false;
}
},
// 扫码并创建待操作(原有方法不变)
async handleScan(actionType) {
@@ -380,7 +550,7 @@
if (coilRes.code !== 200) {
throw new Error(coilRes.msg || '查询钢卷信息失败');
}
if (!coilRes.data) {
throw new Error('未找到钢卷信息');
}
@@ -393,57 +563,63 @@
})
return;
}
this.$refs.shipPopup.open('bottom');
this.currentAction = 'ship';
this.coilDetail = coilRes.data
},
uni.showModal({
cancelText: '取消',
confirmText: '确认',
title: '确定要将钢卷号为:' + coilRes.data.currentCoilNo + '发货吗?',
showCancel: true,
success: async () => {
try {
// 判断钢卷的质量状态必须是A+, AA-, B+其中之一
if (!['A+', 'A', 'A-', 'B+'].includes(coilRes.data.qualityStatus)) {
this.$message.warning('钢卷质量状态需在B+及以上');
return;
}
// 1. 更新钢卷状态为已发货
await exportCoil(coilRes.data.coilId);
// 2. 插入一条已完成的待操作记录
await addPendingAction({
coilId: coilRes.data.coilId,
currentCoilNo: coilRes.data.currentCoilNo,
actionType: 402, // 402=发货
actionStatus: 2, // 直接标记为完成状态
scanTime: new Date(),
scanDevice: this.getDeviceInfo(),
priority: 0, // 0=普通
sourceType: 'scan',
warehouseId: coilRes.data.warehouseId,
processTime: new Date(),
completeTime: new Date()
});
uni.showToast({
title: '发货成功',
icon: 'none'
});
} catch (error) {
console.error('发货失败:', error);
uni.showToast({
title: error?.message || '发货失败',
icon: 'none'
});
}
},
fail() {
async handleShipSubmit() {
try {
// 判断钢卷的质量状态必须是A+, AA-, B+,B,B-其中之一
if (!['A+', 'A', 'A-', 'B+', 'B', 'B-'].includes(this.coilDetail.qualityStatus)
&& !(this.coilDetail.qualityStatus == null
|| this.coilDetail.qualityStatus == undefined
|| this.coilDetail.qualityStatus == '')
) {
uni.showToast({
title: '已取消发货',
title: '只能发货B-以上品质的钢卷',
icon: 'none'
})
});
return;
}
});
uni.hideLoading();
// 1. 更新钢卷状态为已发货
const res = await exportCoil(this.coilDetail.coilId);
// 2. 插入一条已完成的待操作记录
await addPendingAction({
coilId: this.coilDetail.coilId,
currentCoilNo: this.coilDetail.currentCoilNo,
actionType: 402, // 402=发货
actionStatus: 2, // 直接标记为完成状态
scanTime: new Date(),
scanDevice: this.getDeviceInfo(),
priority: 0, // 0=普通
sourceType: 'scan',
warehouseId: this.coilDetail.warehouseId,
processTime: new Date(),
completeTime: new Date()
});
if (res.code != 200) {
uni.showToast({
icon: 'error',
title: res.message || '发货失败请重试'
})
return;
}
uni.showToast({
title: '发货成功',
icon: 'success'
});
this.$refs.shipPopup.close()
} catch (error) {
console.error('发货失败:', error);
uni.showToast({
title: error?.message || '发货失败',
icon: 'none'
});
}
},
// 移库操作:第一步 - 扫描钢卷,打开移库弹窗(原有方法优化)
@@ -671,6 +847,7 @@
},
mounted() {
getDicts('action_type').then(res => {
console.log(res.data)
this.types = res.data
})
}

View File

@@ -1,6 +1,22 @@
<template>
<view class="typing-container">
<view class="info-grid">
<view class="info-item">
<text class="item-label">品名</text>
<text class="item-value">{{ coilDetail.itemName }}</text>
</view>
<view class="info-item">
<text class="item-label">规格</text>
<text class="item-value">{{ coilDetail.itemSpecification }}</text>
</view>
<view class="info-item">
<text class="item-label">材质</text>
<text class="item-value">{{ coilDetail.itemMaterial }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家</text>
<text class="item-value">{{ coilDetail.itemManufacturer }}</text>
</view>
<view class="info-item">
<text class="item-label">入场钢卷号</text>
<text class="item-value">{{ coilDetail.enterCoilNo || '-' }}</text>
@@ -93,7 +109,8 @@
data() {
return {
form: {},
coilDetail: {},
coilDetail: {
},
loading: false,
}
},
@@ -107,8 +124,14 @@
fetchCoil(coilId) {
getMaterialCoil(coilId).then(res => {
this.form = res.data;
this.coilDetail = res.data;
console.log(this.form, '钢卷信息')
this.coilDetail = {
...res.data,
itemName: res.data.itemName,
itemSpecification: res.data.specification,
itemManufacturer: res.data.manufacturer,
itemMaterial: res.data.material
};
console.log('钢卷信息', this.form, )
})
},
// 获取设备信息(原有方法不变)

View File

@@ -0,0 +1,792 @@
<template>
<view class="container">
<!-- 筛选栏 -->
<view class="filter-bar">
<!-- 左侧发货计划选择 -->
<view class="plan-select" @click="openPlanPopup">
<uni-icons type="shop" size="20" color="#666"></uni-icons>
<text class="plan-text">{{ currentPlan.planNo || '选择发货单据' }}</text>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
</view>
<!-- 发货计划选择悬浮窗 -->
<uni-popup ref="planPopup" type="bottom" :mask-click="true" height="70%">
<view class="popup-container">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">选择发货单据</text>
<uni-icons type="close" size="20" @click="closePlanPopup"></uni-icons>
</view>
<!-- 计划筛选输入框 -->
<view class="plan-search">
<input v-model="planKeyword" placeholder="请输入发货单号筛选" clearable class="plan-search-input"
@confirm="fetchDeliveryPlan"></input>
<uni-icons type="search" size="18" color="#666" @click="fetchDeliveryPlan(true)"></uni-icons>
</view>
<!-- 发货计划列表 -->
<scroll-view class="detail-scroll" scroll-y>
<view class="form-card">
<uni-list v-if="planList.length">
<uni-list-item v-for="(item, index) in planList" :key="index"
:title="item.waybillName + '-' + item.consigneeUnit"
:note="item.licensePlate + '(' + item.principal + ')'"
clickable
@click="selectPlan(item)"></uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无发货计划数据</view>
</view>
<!-- 加载更多 -->
<uni-load-more :status="planHasMore ? 'more' : 'noMore'" @clickLoadMore="fetchDeliveryPlan(false)"
v-if="planList.length"></uni-load-more>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref='viewPopup' type="bottom" :mask-click="true" height="70%">
<view class="popup-container">
<!-- 弹窗头部标题+关闭按钮 -->
<view class="popup-header">
<text class="popup-title">钢卷详情信息</text>
<uni-icons type="close" size="20" @click="closeViewPopup"></uni-icons>
</view>
<!-- 详情内容区域滚动布局适配多内容 -->
<scroll-view class="detail-scroll" scroll-y>
<view class="form-card" v-if="form.coilId">
<!-- 基础信息网格 -->
<view class="info-grid">
<view class="info-item">
<text class="item-label">入场钢卷号</text>
<text class="item-value">{{ form.enterCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">钢卷号</text>
<text class="item-value">{{ form.currentCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">班组</text>
<text class="item-value">{{ form.team || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">数据类型</text>
<text
class="item-value">{{ form.dataType === 0 ? '默认数据' : form.dataType === 10 ? '待发货数据' : '-' }}</text>
</view>
<view class="info-item full-width">
<text class="item-label">逻辑库位</text>
<text class="item-value">{{ form.warehouseName || '-' }}</text>
</view>
<view class="info-item full-width">
<text class="item-label">实际库位</text>
<text class="item-value">{{ form.actualWarehouseName || '-' }}</text>
</view>
</view>
<!-- 物料信息 -->
<view class="card-title" style="margin-top: 30rpx;">
<text class="title-dot"></text>
<text class="title-text">物料信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="item-label">物品名称</text>
<text class="item-value">{{ form.itemName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">规格</text>
<text class="item-value">{{ form.specification || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">材质</text>
<text class="item-value">{{ form.material || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家</text>
<text class="item-value">{{ form.manufacturer || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">表面处理</text>
<text class="item-value">{{ form.surfaceTreatmentDesc || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">镀层质量</text>
<text class="item-value">{{ form.zincLayer || '-' }}</text>
</view>
</view>
<!-- 重量信息 -->
<view class="card-title" style="margin-top: 30rpx;">
<text class="title-dot"></text>
<text class="title-text">数据信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="item-label">毛重</text>
<text class="item-value">{{ form.grossWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">净重</text>
<text class="item-value">{{ form.netWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">长度m</text>
<text class="item-value">{{ form.length || '-' }}</text>
</view>
</view>
<!-- 操作信息 -->
<view class="card-title" style="margin-top: 30rpx;">
<text class="title-dot"></text>
<text class="title-text">操作信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="item-label">操作人</text>
<text class="item-value">{{ operatorName }}</text>
</view>
<view class="info-item">
<text class="item-label">更新时间</text>
<text class="item-value">{{ currentAction.updateTime || '-' }}</text>
</view>
<view class="info-item full-width">
<text class="item-label">操作状态</text>
<text
class="item-value">{{ currentAction.actionStatus === 0 ? '未开始' : currentAction.actionStatus === 2 ? '已完成' : '-' }}</text>
</view>
</view>
</view>
<!-- 空数据提示 -->
<view class="empty-tip" v-else>
暂无钢卷详情数据
</view>
</scroll-view>
</view>
</uni-popup>
<!-- 主列表区域 -->
<view class="main-list">
<scroll-view class="list-scroll" scroll-y>
<uni-list v-if="list.length">
<uni-list-item v-for="(item, index) in list" :key="index" @click=""
:title="`钢卷号:${item.coilNo || '-'}`" :note="`操作时间:${item.updateTime || '-'}`">
<template v-slot:footer>
<button style="margin-right: 10rpx;" size="mini" type="primary" plain="true" @click="openViewPopup(item)">查看</button>
</template>
</uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无待操作数据</view>
<!-- 加载更多 -->
<uni-load-more :status="hasMore ? 'more' : 'noMore'" @clickLoadMore="fetchList(false)"
v-if="list.length"></uni-load-more>
</scroll-view>
</view>
</view>
</template>
<script>
import {
listDeliveryWaybill
} from '@/api/wms/deliveryWaybill.js'
import {
listDeliveryPlan
} from '@/api/wms/deliveryPlan.js';
import {
listDeliveryWaybillDetail
} from '@/api/wms/deliveryWaybillDetail.js'
import {
updateMaterialCoilSimple,
getMaterialCoil
} from '@/api/wms/coil.js'
// 入库操作编码
const ACTION_TYPE = 401;
// 发货计划类型编码
const RECEIVE_PLAN_TYPE = 2;
// 未开始操作状态
const ACTION_STATUS = 0;
export default {
data() {
return {
// 主列表数据
list: [],
currentPlan: {}, // 当前选中的发货计划
currentCoilNo: undefined, // 当前输入的钢卷号
hasMore: true, // 是否有更多主列表数据
pageNum: 1, // 主列表页码
pageSize: 10, // 每页条数
refreshing: false, // 下拉刷新状态
// 发货计划弹窗相关
planKeyword: '', // 计划筛选关键词
planList: [], // 发货计划列表
planHasMore: true, // 计划列表是否有更多
planPageNum: 1, // 计划列表页码
popupShow: false, // 弹窗显示状态
form: {},
loading: false,
currentAction: {},
loadingDetail: false,
};
},
computed: {
// 获取当前操作者昵称
operatorName() {
return this.$store.state.user.nickName || this.$store.state.user.name || '未知'
}
},
onShow() {
// 页面显示时初始化加载数据
this.fetchList(true);
},
methods: {
/**
* 获取待操作列表数据
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchList(isRefresh = false) {
try {
// 刷新时重置页码和加载状态
if (isRefresh) {
this.pageNum = 1;
this.refreshing = true;
}
// 构造请求参数
const params = {
pageNum: this.pageNum,
waybillId: this.currentPlan.waybillId,
pageSize: this.pageSize
};
// 请求接口
const res = await listDeliveryWaybillDetail(params);
if (res.code === 200) {
const list = res.rows || [];
// 刷新时替换数据,加载更多时追加数据
this.list = isRefresh ? list : [...this.list, ...list];
// 判断是否有更多数据
this.hasMore = this.pageNum * this.pageSize < res.total;
}
console.log(this.list, '需要渲染的数据')
} catch (err) {
console.error('获取待操作列表失败:', err);
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
} finally {
// 结束下拉刷新状态
this.refreshing = false;
// 加载更多时页码+1
if (!isRefresh) this.pageNum++;
}
},
/**
* 获取发货计划列表
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchDeliveryPlan(isRefresh = false) {
try {
if (isRefresh) {
this.planPageNum = 1;
}
// 构造请求参数
const params = {
waybillName: this.planKeyword || '', // 计划编号筛选
pageNum: this.planPageNum,
pageSize: this.pageSize,
};
const res = await listDeliveryWaybill(params);
if (res.code === 200) {
const list = res.rows || [];
this.planList = isRefresh ? list : [...this.planList, ...list];
this.planHasMore = this.planPageNum * this.pageSize < res.total;
}
} catch (err) {
console.error('获取发货单据失败:', err);
uni.showToast({
title: '计划加载失败',
icon: 'none'
});
} finally {
if (!isRefresh) this.planPageNum++;
}
},
/**
* 打开发货计划弹窗
*/
openPlanPopup() {
this.$refs.planPopup.open();
// 打开弹窗时加载计划数据
this.fetchDeliveryPlan(true);
},
/**
* 关闭发货计划弹窗
*/
closePlanPopup() {
this.$refs.planPopup.close();
},
/**
* 打开发货弹窗
*/
openReceivePopup(row) {
this.$refs.receivePopup.open('bottom')
// this.loadingDetail = false;
uni.showLoading({
title: '正在加载发货详情'
})
getMaterialCoil(row.coilId).then(res => {
this.form = res.data;
this.currentAction = row;
// this.loadingDetail = true
uni.hideLoading()
})
},
openViewPopup(row) {
this.$refs.viewPopup.open('bottom')
// this.loadingDetail = false;
uni.showLoading({
title: '正在加载发货详情'
})
getMaterialCoil(row.coilId).then(res => {
console.log(res.data)
this.form = res.data;
this.currentAction = row;
uni.hideLoading()
// this.loadingDetail = true
})
},
/**
* 关闭钢卷详情弹窗
*/
closeViewPopup() {
this.$refs.viewPopup.close();
// 可选:清空表单临时数据(根据业务需求决定是否保留)
this.form = {};
this.currentAction = {};
},
/**
* 确认发货
*/
confirmReceive(row) {
const currentAction = this.currentAction;
const form = this.form;
const that = this;
uni.showModal({
title: '确定要发货吗?',
success() {
// console.log(currentAction, form)
that.loading = true;
Promise.all([
updatePendingAction({
...currentAction,
actionStatus: 2
}),
updateMaterialCoilSimple({
...form,
dataType: 1
})
]).then(_ => {
uni.showToast({
title: '钢卷已发货'
});
that.fetchList(true);
that.loading = false;
that.$refs.receivePopup.close()
})
}
})
},
/**
* 选择发货计划
* @param {Object} plan 选中的计划对象
*/
selectPlan(plan) {
this.currentPlan = plan;
this.closePlanPopup();
// 选择计划后重新加载主列表
this.fetchList(true);
},
/**
* 下拉刷新触发(修正后事件可正常绑定)
*/
onPullDownRefresh() {
this.fetchList(true);
},
/**
* 页面上拉加载(可选:补充页面级上拉加载)
*/
onReachBottom() {
if (this.hasMore) {
this.fetchList(false);
}
}
},
// 补充页面级上拉加载钩子(可选,增强体验)
onReachBottom() {
this.onReachBottom();
}
};
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 筛选栏样式 */
.filter-bar {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
background-color: #fff;
border-bottom: 1px solid #eee;
}
.plan-select {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 15rpx;
background-color: #f8f8f8;
border-radius: 6rpx;
margin-right: 20rpx;
min-width: 200rpx;
}
.plan-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.coil-filter {
display: flex;
align-items: center;
flex: 1;
background-color: #f8f8f8;
border-radius: 6rpx;
padding: 0 15rpx;
}
.coil-input {
flex: 1;
font-size: 28rpx;
}
/* 弹窗样式 */
.popup-container {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.plan-search {
display: flex;
align-items: center;
gap: 10rpx;
padding: 15rpx 20rpx;
border-bottom: 1px solid #eee;
}
.plan-search-input {
flex: 1;
font-size: 28rpx;
}
// .plan-list {
// flex: 1;
// overflow-y: auto;
// padding: 10rpx;
// }
/* 列表样式 */
.main-list {
flex: 1;
overflow: hidden;
}
.list-scroll {
width: 100%;
height: 100%;
}
.uni-list {
background-color: #fff;
margin: 10rpx;
border-radius: 8rpx;
}
.uni-list-item {
font-size: 28rpx;
}
/* 空数据提示 */
.empty-tip {
text-align: center;
padding: 50rpx 0;
font-size: 28rpx;
color: #999;
}
/* 加载更多样式 */
.uni-load-more {
margin: 20rpx 0;
}
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
overflow: scroll;
height: 70vh;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #007aff;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.status-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
margin-right: 10rpx;
&.status-1 {
background: #d1f2eb;
color: #0c6957;
}
&.status-0 {
background: #f8d7da;
color: #721c24;
}
}
.more-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
background: #f0f7ff;
border-radius: 20rpx;
border: 1rpx solid #007aff;
.icon-more {
font-size: 24rpx;
color: #007aff;
}
.more-text {
font-size: 24rpx;
color: #007aff;
}
}
}
/* 信息网格 */
.info-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.info-item {
flex: 1;
min-width: 45%;
background: #f8f9fa;
padding: 20rpx;
border-radius: 12rpx;
&.full-width {
flex: 0 0 100%;
}
.item-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 10rpx;
}
.item-value {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
/* 表单项 */
.form-item {
margin-bottom: 30rpx;
&:last-of-type {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
&::before {
content: '*';
color: #ff4d4f;
margin-right: 6rpx;
}
}
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
transition: all 0.3s;
&:focus {
background: #fff;
border-color: #007aff;
}
&.form-input-disabled {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
}
}
}
/* 操作者信息 */
.operator-info {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
margin-top: 20rpx;
.operator-label {
font-size: 26rpx;
color: #999;
}
.operator-name {
font-size: 26rpx;
color: #007aff;
font-weight: 500;
}
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
.btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
transition: all 0.2s;
&:active {
transform: scale(0.98);
}
&[disabled] {
opacity: 0.6;
}
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-primary {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
}
}
</style>

View File

@@ -1,11 +1,17 @@
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
hasJumped: false // 防止重复跳转的标记
tabVisible: {
admin: [true, true, false, true, true],
canteen: [false, false, true, false, true],
worker: [false, true, false, true, true]
},
hasJumped: false, // 防止重复跳转的标记
}
},
onShow() {
@@ -25,10 +31,13 @@ export default {
const roles = res.data.roles;
if (roles.includes('admin')) {
uni.setTabBarItem({
index: 0,
visible: true
});
for (let i = 0; i < this.tabVisible.admin.length; i++) {
const v = this.tabVisible.admin[i]
uni.setTabBarItem({
index: i,
visible: v
});
}
// 管理员角色跳转
uni.switchTab({
url: '/pages/line/line',
@@ -44,14 +53,40 @@ export default {
});
}
});
} else if (roles.includes('canteen')) {
for (let i = 0; i < this.tabVisible.canteen.length; i++) {
const v = this.tabVisible.canteen[i]
uni.setTabBarItem({
index: i,
visible: v
});
}
// 食堂角色跳转
uni.switchTab({
url: '/pages/meal/meal',
success: () => {
this.hasJumped = true; // 标记已跳转
},
fail: (err) => {
console.error('管理员页面跳转失败:', err);
uni.showToast({
title: '跳转产线页面失败',
icon: 'none',
duration: 2000
});
}
});
} else if (roles.includes('worker')) {
// 工人角色跳转
// 设置tab内容
// 设置产线和复杂扫码不可见
uni.setTabBarItem({
index: 0,
visible: false
});
for (let i = 0; i < this.tabVisible.worker.length; i++) {
const v = this.tabVisible.worker[i]
uni.setTabBarItem({
index: i,
visible: v
});
}
uni.switchTab({
url: '/pages/easycode/easycode',
success: () => {
@@ -67,16 +102,13 @@ export default {
}
});
} else {
// 处理未定义角色(默认角色)
uni.showToast({
title: '检测到未知角色,将跳转至默认页面',
icon: 'none',
duration: 2000
});
uni.setTabBarItem({
index: 0,
visible: false
});
for (let i = 0; i < this.tabVisible.worker.length; i++) {
const v = this.tabVisible.worker[i]
uni.setTabBarItem({
index: i,
visible: v
});
}
// 延迟跳转,确保提示被用户看到
setTimeout(() => {
uni.switchTab({

View File

@@ -5,10 +5,10 @@
<view class="content-wrapper">
<Acidity v-if="active == 0"/>
<Paint v-else-if="active == 1"/>
<Zinc1 v-else-if="active == 2"></Zinc1>
<Zinc2 v-else-if="active == 3"></Zinc2>
<Zinc3 v-else-if="active == 4"></Zinc3>
<!-- <Paint v-else-if="active == 1"/> -->
<Zinc1 v-else-if="active == 1"></Zinc1>
<!-- <Zinc2 v-else-if="active == 3"></Zinc2> -->
<!-- <Zinc3 v-else-if="active == 4"></Zinc3> -->
</view>
</view>
</template>

View File

@@ -24,15 +24,6 @@
<view class="action-btn">
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
</view>
<!-- <view class="reg text-center" v-if="register">
<text class="text-grey1">没有账号</text>
<text @click="handleUserRegister" class="text-blue">立即注册</text>
</view> -->
<!-- <view class="xieyi text-center">
<text class="text-grey1">登录即代表同意</text>
<text @click="handleUserAgrement" class="text-blue">用户协议</text>
<text @click="handlePrivacy" class="text-blue">隐私协议</text>
</view> -->
</view>
</view>

View File

@@ -0,0 +1,524 @@
<template>
<view class="meal-report-container">
<!-- 日期和时间选择区域 -->
<view class="date-time-wrap">
<view class="picker-item">
<text class="picker-label">餐别</text>
<uni-data-select v-model="queryParams.mealType" :localdata="range" @change="getList"></uni-data-select>
</view>
<!-- 报餐日期选择 -->
<view class="picker-item">
<text class="picker-label">报餐日期</text>
<uni-datetime-picker v-model="queryParams.reportDate" type="date" placeholder="选择日期" @change="onDateConfirm"
class="picker-input"></uni-datetime-picker>
</view>
<!-- 自定义截止时间选择 -->
<view class="picker-item">
<text class="picker-label">截止时间</text>
<view class="custom-time-picker" @click="openTimePopup">
<text class="time-text">{{ formattedDeadlineTime }}</text>
<uni-icons type="arrowdown" size="14" color="#999"></uni-icons>
</view>
</view>
</view>
<!-- 报餐统计卡片 -->
<view class="statistics-card">
<view class="card-title">报餐人数统计</view>
<view class="stats-grid">
<!-- 第一行 -->
<view class="stats-item">
<text class="item-label">堂食人数</text>
<text class="item-value">{{ validDineIn + invalidDineIn }}</text>
</view>
<view class="stats-item">
<text class="item-label">打包人数</text>
<text class="item-value">{{ validTakeout + invalidTakeout }}</text>
</view>
<view class="stats-item">
<text class="item-label">总人数</text>
<text class="item-value">{{ validTotal + invalidTotal }}</text>
</view>
<!-- 第二行 -->
<view class="stats-item">
<text class="item-label">有效堂食人数</text>
<text class="item-value">{{ validDineIn }}</text>
</view>
<view class="stats-item">
<text class="item-label">有效打包人数</text>
<text class="item-value">{{ validTakeout }}</text>
</view>
<view class="stats-item">
<text class="item-label">有效总人数</text>
<text class="item-value">{{ validTotal }}</text>
</view>
<!-- 第三行 -->
<view class="stats-item">
<text class="item-label">无效堂食人数</text>
<text class="item-value">{{ invalidDineIn }}</text>
</view>
<view class="stats-item">
<text class="item-label">无效打包人数</text>
<text class="item-value">{{ invalidTakeout }}</text>
</view>
<view class="stats-item">
<text class="item-label">无效总人数</text>
<text class="item-value">{{ invalidTotal }}</text>
</view>
</view>
</view>
<!-- 自定义时分秒选择弹窗 -->
<uni-popup ref="timePopup" type="bottom" border-radius="10px 10px 0 0">
<view class="time-popup-content">
<!-- 弹窗标题 -->
<view class="popup-header">
<text class="popup-title">选择截止时间</text>
<view class="popup-btns">
<button class="cancel-btn" @click="closeTimePopup">取消</button>
<button class="confirm-btn" @click="confirmTime">确认</button>
</view>
</view>
<!-- 时分秒选择区域 -->
<view class="time-select-wrap">
<!-- 小时选择 -->
<view class="time-unit">
<text class="unit-label"></text>
<uni-number-box v-model="timeSelect.hour" :min="0" :max="23" :step="1"
@change="handleTimeChange"></uni-number-box>
</view>
<!-- 分钟选择 -->
<view class="time-unit">
<text class="unit-label"></text>
<uni-number-box v-model="timeSelect.minute" :min="0" :max="59" :step="1"
@change="handleTimeChange"></uni-number-box>
</view>
<!-- 秒选择 -->
<view class="time-unit">
<text class="unit-label"></text>
<uni-number-box v-model="timeSelect.second" :min="0" :max="59" :step="1"
@change="handleTimeChange"></uni-number-box>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
listMealReport
} from "@/api/wms/mealReport";
import {
getDicts
} from '@/api/system/dict/data.js'
import {
getConfigKey
} from '@/api/system/config.js'
export default {
name: 'MealReportStatistics',
data() {
return {
queryParams: {
mealType: '',
reportDate: '',
pageSize: 9999,
pageNum: 1
},
deadlineDate: '', // 截止日期
deadlineTime: '12:00:00', // 截止时间(原始值)
// 时分秒选择器临时变量
timeSelect: {
hour: 12,
minute: 0,
second: 0
},
list: [],
loading: false,
// 统计数据
validDineIn: 0,
validTakeout: 0,
validTotal: 0,
invalidDineIn: 0,
invalidTakeout: 0,
invalidTotal: 0,
range: []
}
},
computed: {
// 格式化截止时间显示(补零)
formattedDeadlineTime() {
const [hour, minute, second] = this.deadlineTime.split(':');
return `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
}
},
onLoad() {
// 初始化今日日期
this.initTodayDate();
// 初始化截止日期为今日
this.deadlineDate = this.queryParams.reportDate;
// 获取餐别字典数据
this.getRangeData();
// 加载报餐数据
this.getList();
this.getDeadlineConfig();
},
methods: {
/** 初始化今日日期为yyyy-MM-dd格式 */
initTodayDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
this.queryParams.reportDate = `${year}-${month}-${day}`;
},
getDeadlineConfig() {
getConfigKey('hrm.meal.deadline').then(response => {
this.queryParams.deadlineTime = response.msg || '16:00:00'
})
},
/** 获取餐别字典数据 */
getRangeData() {
getDicts('hrm_meal_type').then(res => {
this.range = res.data.map(item => ({
text: item.dictLabel,
value: item.dictValue
}));
});
},
/** 查询部门报餐列表 */
getList() {
this.loading = true;
listMealReport(this.queryParams).then(response => {
this.list = response.rows || [];
this.loading = false;
this.calcTableSum();
}).catch(error => {
console.error('获取报餐数据失败:', error);
this.loading = false;
});
},
/** 核心逻辑:区分有效/无效报餐统计 */
calcTableSum() {
let validDine = 0,
validTake = 0,
validAll = 0;
let invalidDine = 0,
invalidTake = 0,
invalidAll = 0;
this.list.forEach(item => {
// 处理空值,转为数字
const dine = item.dineInPeople ? Number(item.dineInPeople) : 0;
const take = item.takeoutPeople ? Number(item.takeoutPeople) : 0;
const total = item.totalPeople ? Number(item.totalPeople) : 0;
// 判断当前报餐是否有效
if (this.isValidMealReport(item.createTime)) {
validDine += dine;
validTake += take;
validAll += total;
} else {
invalidDine += dine;
invalidTake += take;
invalidAll += total;
}
});
// 赋值到统计变量
this.validDineIn = validDine;
this.validTakeout = validTake;
this.validTotal = validAll;
this.invalidDineIn = invalidDine;
this.invalidTakeout = invalidTake;
this.invalidTotal = invalidAll;
},
/** 判断报餐是否有效:创建时间在截止时间之前则有效 */
isValidMealReport(createTime) {
if (!createTime) return false;
// 拼接完整的截止时间字符串
const deadlineDateTime = `${this.deadlineDate || this.queryParams.reportDate} ${this.deadlineTime}`;
// 比较时间
const createTimeObj = new Date(createTime);
const deadlineTimeObj = new Date(deadlineDateTime);
return createTimeObj <= deadlineTimeObj;
},
/** 报餐日期选择确认 */
onDateConfirm(e) {
this.queryParams.reportDate = e;
// 如果截止日期未选择,同步为报餐日期
if (!this.deadlineDate) {
this.deadlineDate = e;
}
this.getList();
},
/** 截止日期选择确认 */
onDeadlineDateConfirm(e) {
this.deadlineDate = e;
this.calcTableSum();
},
// ===== 自定义时间选择器逻辑 =====
/** 打开时间选择弹窗 */
openTimePopup() {
// 解析当前时间到选择器
const [hour, minute, second] = this.deadlineTime.split(':').map(Number);
this.timeSelect = {
hour,
minute,
second
};
// 打开弹窗
this.$refs.timePopup.open('center');
},
/** 关闭时间选择弹窗 */
closeTimePopup() {
this.$refs.timePopup.close();
},
/** 处理时分秒数值变化 */
handleTimeChange() {
// 确保数值在合法范围内
this.timeSelect.hour = Math.max(0, Math.min(23, this.timeSelect.hour));
this.timeSelect.minute = Math.max(0, Math.min(59, this.timeSelect.minute));
this.timeSelect.second = Math.max(0, Math.min(59, this.timeSelect.second));
},
/** 确认选择的时间 */
confirmTime() {
// 格式化时分秒(补零)
const hour = String(this.timeSelect.hour).padStart(2, '0');
const minute = String(this.timeSelect.minute).padStart(2, '0');
const second = String(this.timeSelect.second).padStart(2, '0');
// 更新截止时间
this.deadlineTime = `${hour}:${minute}:${second}`;
// 重新计算统计数据
this.calcTableSum();
// 关闭弹窗
this.closeTimePopup();
}
}
}
</script>
<style scoped>
/* 容器样式 */
.meal-report-container {
padding: 15px;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 日期时间选择区域 */
.date-time-wrap {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
background-color: #fff;
padding: 15px;
border-radius: 8px;
}
.picker-item {
display: flex;
align-items: center;
gap: 8px;
}
.picker-label {
font-size: 14px;
color: #333;
min-width: 80px;
}
.picker-input {
flex: 1;
}
/* 自定义时间选择器样式 */
.custom-time-picker {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
border: 1px solid #eee;
border-radius: 4px;
background-color: #fff;
}
.time-text {
font-size: 14px;
color: #333;
}
/* 统计卡片样式 */
.statistics-card {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
padding: 12px 15px;
border-bottom: 1px solid #eee;
}
/* 网格布局3列 */
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
border: 1px solid #eee;
border-top: none;
}
/* 统计项样式 */
.stats-item {
padding: 12px 8px;
text-align: center;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
box-sizing: border-box;
font-size: 14px;
}
/* 去掉最后一列右边框 */
.stats-grid .stats-item:nth-child(3n) {
border-right: none;
}
/* 去掉最后一行下边框 */
.stats-grid .stats-item:last-child,
.stats-grid .stats-item:nth-last-child(2),
.stats-grid .stats-item:nth-last-child(3) {
border-bottom: none;
}
/* 标签和值样式 */
.item-label {
display: block;
color: #666;
font-size: 12px;
margin-bottom: 4px;
}
.item-value {
display: block;
color: #333;
font-size: 15px;
font-weight: 500;
}
/* 时间弹窗样式 */
.time-popup-content {
background-color: #fff;
padding-bottom: 20px;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.popup-btns {
display: flex;
gap: 10px;
}
.cancel-btn,
.confirm-btn {
padding: 6px 12px;
font-size: 14px;
border-radius: 4px;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666;
border: 1px solid #eee;
}
.confirm-btn {
background-color: #007aff;
color: #fff;
border: none;
}
/* 时分秒选择区域 */
.time-select-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 20px 15px;
}
.time-unit {
display: flex;
align-items: center;
gap: 8px;
}
.unit-label {
font-size: 12px;
color: #999;
}
.colon {
font-size: 20px;
color: #333;
margin: 0 5px;
}
/* 适配小屏手机 */
@media (max-width: 375px) {
.stats-item {
padding: 10px 5px;
}
.item-label {
font-size: 11px;
}
.item-value {
font-size: 14px;
}
.time-select-wrap {
gap: 5px;
padding: 15px 10px;
}
}
</style>

View File

@@ -25,57 +25,20 @@
</view>
</view>
<!-- <view class="content-section">
<view class="mine-actions grid col-4 text-center">
<view class="action-item" @click="handleJiaoLiuQun">
<view class="iconfont icon-friendfill text-pink icon"></view>
<text class="text">交流群</text>
</view>
<view class="action-item" @click="handleBuilding">
<view class="iconfont icon-service text-blue icon"></view>
<text class="text">在线客服</text>
</view>
<view class="action-item" @click="handleBuilding">
<view class="iconfont icon-community text-mauve icon"></view>
<text class="text">反馈社区</text>
</view>
<view class="action-item" @click="handleBuilding">
<view class="iconfont icon-dianzan text-green icon"></view>
<text class="text">点赞我们</text>
</view>
</view> -->
<view class="menu-list">
<view class="list-cell list-cell-arrow" @click="handleToSearch">
<view class="menu-item-box">
<view class="iconfont icon-setting menu-icon"></view>
<view>钢卷查找</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleLogout">
<view class="menu-item-box">
<view class="iconfont icon-setting menu-icon"></view>
<view>退出登录</view>
</view>
</view>
<!-- <view class="list-cell list-cell-arrow" @click="handleToEditInfo">
<view class="menu-item-box">
<view class="iconfont icon-user menu-icon"></view>
<view>编辑资料</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleHelp">
<view class="menu-item-box">
<view class="iconfont icon-help menu-icon"></view>
<view>常见问题</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleAbout">
<view class="menu-item-box">
<view class="iconfont icon-aixin menu-icon"></view>
<view>关于我们</view>
</view>
</view>
<view class="list-cell list-cell-arrow" @click="handleToSetting">
<view class="menu-item-box">
<view class="iconfont icon-setting menu-icon"></view>
<view>应用设置</view>
</view>
</view> -->
</view>
</view>
@@ -98,8 +61,8 @@
}
},
methods: {
handleToInfo() {
this.$tab.navigateTo('/pages/mine/info/index')
handleToSearch() {
this.$tab.navigateTo('/pages/search/search')
},
handleLogout() {
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
@@ -108,30 +71,7 @@
})
})
},
handleToEditInfo() {
this.$tab.navigateTo('/pages/mine/info/edit')
},
handleToSetting() {
this.$tab.navigateTo('/pages/mine/setting/index')
},
handleToLogin() {
this.$tab.reLaunch('/pages/login')
},
handleToAvatar() {
this.$tab.navigateTo('/pages/mine/avatar/index')
},
handleHelp() {
this.$tab.navigateTo('/pages/mine/help/index')
},
handleAbout() {
this.$tab.navigateTo('/pages/mine/about/index')
},
handleJiaoLiuQun() {
this.$modal.showToast('QQ群①133713780(满)、②146013835(满)、③189091635')
},
handleBuilding() {
this.$modal.showToast('模块建设中~')
}
}
}
</script>

View File

@@ -166,33 +166,29 @@
<text class="title-text">物料信息</text>
</view>
<view class="info-grid">
<!-- <view class="info-item">
<text class="item-label">物品类型</text>
<text class="item-value">{{ form.itemType || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">物品ID</text>
<text class="item-value">{{ form.itemId || '-' }}</text>
</view>
<view class="info-item full-width">
<text class="item-label">物料名称</text>
<text class="item-value">{{ form.itemName || '-' }}</text>
</view> -->
<view class="info-item">
<text class="item-label">物品名称</text>
<text class="item-value">{{ form.rawMaterial.rawMaterialName || form.product.productName || '-' }}</text>
<text class="item-value">{{ form.itemName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">规格</text>
<text class="item-value">{{ form.rawMaterial.specification || form.product.specification || '-' }}</text>
<text class="item-value">{{ form.specification || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">材质</text>
<text class="item-value">{{ form.rawMaterial.material || form.product.material || '-' }}</text>
<text class="item-value">{{ form.material || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家</text>
<text class="item-value">{{ form.rawMaterial.manufacturer || form.product.manufacturer || '-' }}</text>
<text class="item-value">{{ form.manufacturer || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">表面处理</text>
<text class="item-value">{{ form.surfaceTreatmentDesc || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">镀层质量</text>
<text class="item-value">{{ form.zincLayer || '-' }}</text>
</view>
</view>

View File

@@ -0,0 +1,875 @@
<template>
<view class="typing-container">
<view class="form-card" v-show='currentView == "search"'>
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">设置条件查询钢卷</text>
</view>
<!-- 当前钢卷号 -->
<view class="form-item form-item-optional">
<text class="form-label">入场钢卷号</text>
<input v-model="form.enterCoilNo" placeholder="请输入当前钢卷号" class="form-input"
:disabled="coilDetail.dataType === 0" />
</view>
<view class="form-item form-item-optional">
<text class="form-label">当前钢卷号</text>
<input v-model="form.currentCoilNo" placeholder="请输入当前钢卷号" class="form-input"
:disabled="coilDetail.dataType === 0" :class="{ 'form-input-disabled': coilDetail.dataType === 0 }" />
</view>
<!-- 班组 -->
<view class="form-item form-item-optional">
<text class="form-label">班组</text>
<input v-model="form.team" placeholder="请输入班组名称" class="form-input" />
</view>
<!-- 库区选择 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">逻辑库区</text>
<klp-warehouse-picker v-model="form.warehouseId" :disabled="coilDetail.dataType === 0" placeholder="请选择目标库区" />
</view>
<view class="form-item form-item-optional">
<text class="form-label-optional">真实库区</text>
<klp-actual-warehouse-picker v-model="form.actualWarehouseId" :disabled="coilDetail.dataType === 0"
placeholder="请选择目标库区" />
</view>
<!-- 物品类型产品原材料选择使用封装组件 -->
<klp-material-picker
:item-type.sync="form.itemType"
:item-id.sync="form.itemId"
:material-type.sync="form.materialType"
:disabled="coilDetail.dataType === 0"
:page-size="2000"
/>
<view class="form-item form-item-optional">
<text class="form-label-optional">品名</text>
<input v-model="form.itemName" placeholder="请输入品名" class="form-input" />
</view>
<view class="form-item form-item-optional">
<text class="form-label-optional">规格</text>
<input v-model="form.itemSpecification" placeholder="请输入规格" class="form-input" />
</view>
<view class="form-item form-item-optional">
<text class="form-label-optional">材质</text>
<input v-model="form.itemMaterial" placeholder="请输入材质" class="form-input" />
</view>
<view class="form-item form-item-optional">
<text class="form-label-optional">厂家</text>
<input v-model="form.itemManufacturer" placeholder="请输入厂家" class="form-input" />
</view>
<!-- 毛重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">毛重 ()</text>
<input v-model="form.grossWeight" type="digit" placeholder="请输入毛重" class="form-input"
:disabled="coilDetail.dataType === 0" :class="{ 'form-input-disabled': coilDetail.dataType === 0 }" />
</view>
<!-- 净重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">净重 ()</text>
<input v-model="form.netWeight" type="digit" placeholder="请输入净重" class="form-input"
:disabled="coilDetail.dataType === 0" :class="{ 'form-input-disabled': coilDetail.dataType === 0 }" />
</view>
<!-- 长度 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">长度 ()</text>
<input v-model="form.length" type="digit" placeholder="请输入长度" class="form-input"
:disabled="coilDetail.dataType === 0" :class="{ 'form-input-disabled': coilDetail.dataType === 0 }" />
</view>
</view>
<view v-show='currentView == "list"' class="list-wrap">
<!-- 列表中尽可能多的展示信息 -->
<uni-list v-if="list.length > 0">
<uni-list-item
v-for="item in list"
:key="item.coilId"
:title="item.currentCoilNo"
:note="`入场号:${item.enterCoilNo} | 净重:${item.netWeight}吨 | ${item.warehouseName} | ${item.materialType} | ${getStatusText(item.status)}`"
clickable
@click="showCoilDetail(item)"
></uni-list-item>
</uni-list>
<!-- 空列表兜底 -->
<view class="empty-list" v-else>
<text>暂无符合条件的钢卷数据</text>
</view>
<!-- 分页控件 -->
<view class="pagination" v-if="total > 0">
<view class="pagination-info">
<text> {{ total }} / {{ totalPages }} </text>
<text>当前第 {{ pager.pageNum }} </text>
</view>
<view class="pagination-btns">
<button
class="page-btn"
@click="prevPage"
:disabled="pager.pageNum <= 1"
>
上一页
</button>
<button
class="page-btn"
@click="nextPage"
:disabled="pager.pageNum >= totalPages"
>
下一页
</button>
</view>
</view>
</view>
<!-- 钢卷详情弹窗 -->
<uni-popup ref="shipPopup" type="bottom">
<view style="background-color: white; padding: 20rpx;">
<!-- 弹窗标题+关闭按钮 -->
<view class="popup-header flex justify-between align-center mb-20">
<text class="font-32 font-bold">钢卷详情</text>
<text class="icon-close font-40" @click="closePopup"></text>
</view>
<view class="info-card" v-if="coilDetail.coilId">
<view class="info-grid">
<view class="info-item">
<text class="item-label">入场钢卷号</text>
<text class="item-value">{{ coilDetail.enterCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">当前钢卷号</text>
<text class="item-value">{{ coilDetail.currentCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家卷号</text>
<text class="item-value">{{ coilDetail.supplierCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">状态</text>
<text class="item-value">{{ getStatusText(coilDetail.status) }}</text>
</view>
<view class="info-item">
<text class="item-label">毛重 ()</text>
<text class="item-value">{{ coilDetail.grossWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">净重 ()</text>
<text class="item-value">{{ coilDetail.netWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">逻辑库区</text>
<text class="item-value">{{ coilDetail.warehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">真实库区</text>
<text class="item-value">{{ coilDetail.actualWarehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">班组</text>
<text class="item-value">{{ coilDetail.team || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">物料类型</text>
<text class="item-value">{{ coilDetail.materialType || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">品名</text>
<text class="item-value">{{ coilDetail.itemName || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">规格</text>
<text class="item-value">{{ coilDetail.specification || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">材质</text>
<text class="item-value">{{ coilDetail.material || '-' }}</text>
</view>
<view class="info-item">
<text class="item-label">厂家</text>
<text class="item-value">{{ coilDetail.manufacturer || '-' }}</text>
</view>
</view>
</view>
</view>
</uni-popup>
<!-- 新增查询悬浮按钮 - search视图显示 -->
<view class="float-btn search-btn" v-show="currentView === 'search'" @click="searchCoilList">
<text class="btn-text">查询</text>
</view>
<!-- 新增重新查找悬浮按钮 - list视图显示 -->
<view class="float-btn back-btn" v-show="currentView === 'list'" @click="backToSearch">
<text class="btn-text">重新查找</text>
</view>
</view>
</template>
<script>
import { getMaterialCoil, listMaterialCoil } from '@/api/wms/coil';
import { addPendingAction } from '@/api/wms/pendingAction';
export default {
data() {
return {
// 补全表单所有绑定字段的初始值,解决双向绑定警告
form: {
enterCoilNo: '',
currentCoilNo: '',
team: '',
warehouseId: '',
actualWarehouseId: '',
itemType: 'product',
itemId: '',
materialType: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
grossWeight: '',
netWeight: '',
length: '',
},
pager: {
pageNum: 1,
pageSize: 20,
},
total: 0,
coilDetail: {}, // 钢卷详情数据
list: [], // 钢卷列表数据
loading: false,
currentView: 'search' // 视图切换search=查询页list=列表页
}
},
computed: {
// 计算总页数
totalPages() {
if (this.total === 0) return 0;
return Math.ceil(this.total / this.pager.pageSize);
}
},
methods: {
// ✅ 新增封装通用滚动到顶部方法uniapp全端兼容
scrollToPageTop() {
uni.pageScrollTo({
scrollTop: 0,
duration: 300, // 顺滑滚动,毫秒值
fail: () => {} // 容错处理,防止报错
})
},
// 获取设备信息(原有方法不变)
getDeviceInfo() {
try {
const systemInfo = uni.getSystemInfoSync();
return `${systemInfo.platform} ${systemInfo.model}`;
} catch (e) {
return 'Unknown Device';
}
},
// 查询钢卷列表(原有方法优化+✅新增滚动到顶部)
async searchCoilList() {
// 重置页码为1
this.pager.pageNum = 1;
uni.showLoading({
title: '正在查询,请稍后'
});
try {
const res = await listMaterialCoil({
...this.form,
...this.pager,
selectType: this.form.itemType
});
this.list = res.rows || [];
this.total = res.total || 0;
console.log(this.list)
this.currentView = 'list'
// ✅ 切换视图后,滚动到页面顶部
this.scrollToPageTop()
} catch (err) {
console.error('查询钢卷列表失败:', err);
uni.showToast({
title: '查询失败,检查网络后重试',
icon: 'none',
duration: 2000
})
} finally {
uni.hideLoading()
}
},
// 上一页
async prevPage() {
if (this.pager.pageNum <= 1) return;
this.pager.pageNum -= 1;
await this.loadPageData();
},
// 下一页
async nextPage() {
if (this.pager.pageNum >= this.totalPages) return;
this.pager.pageNum += 1;
await this.loadPageData();
},
// 加载指定页码的数据
async loadPageData() {
uni.showLoading({
title: '加载中...'
});
try {
const res = await listMaterialCoil({
...this.form,
...this.pager,
selectType: this.form.itemType
});
this.list = res.rows || [];
// 滚动到列表顶部
this.scrollToPageTop();
} catch (err) {
console.error('加载分页数据失败:', err);
uni.showToast({
title: '加载失败,请重试',
icon: 'none',
duration: 2000
})
} finally {
uni.hideLoading();
}
},
// 回到查询页面(原有方法+✅新增滚动到顶部)
backToSearch() {
this.currentView = 'search';
// 可选:重置表单数据,如需保留查询条件则注释此行
// this.form = this.$options.data().form;
// ✅ 切换视图后,滚动到页面顶部
this.scrollToPageTop()
},
// 点击列表项-展示钢卷详情弹窗
showCoilDetail(item) {
this.coilDetail = { ...item }; // 深拷贝防止原数据被修改
this.$refs.shipPopup.open(); // 打开底部弹窗
},
// 关闭弹窗
closePopup() {
this.$refs.shipPopup.close();
},
// 格式化状态文本0=在库 1=已出库
getStatusText(status) {
return status === 0 ? '在库' : status === 1 ? '已出库' : '未知';
}
},
}
</script>
<style scoped lang="scss">
.typing-container {
padding: 20rpx;
padding-bottom: 30rpx;
min-height: 100vh;
}
// 列表页样式补充
.list-wrap {
// ✅ 移除原按钮样式,无需保留
.empty-list {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
// 分页样式
.pagination {
margin-top: 40rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
.pagination-info {
display: flex;
gap: 30rpx;
font-size: 26rpx;
color: #666;
text {
display: block;
}
}
.pagination-btns {
display: flex;
gap: 20rpx;
.page-btn {
width: 160rpx;
height: 70rpx;
line-height: 70rpx;
background: #f0f7ff;
border: 1rpx solid #007aff;
border-radius: 8rpx;
color: #007aff;
font-size: 28rpx;
&:disabled {
background: #f5f5f5;
border-color: #ddd;
color: #999;
}
}
}
}
}
// ✅ 核心新增:悬浮按钮样式(全局通用)
.float-btn {
position: fixed;
right: 30rpx;
bottom: 60rpx;
z-index: 9999; // 置顶层级,永不遮挡
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 28rpx;
font-weight: 500;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.15);
transition: all 0.2s ease;
&:active {
transform: scale(0.95); // 按压缩放动效
opacity: 0.9;
}
.btn-text {
color: #fff;
}
}
// 查询悬浮按钮-主色调(渐变蓝,和原有按钮风格一致)
.search-btn {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
}
// 重新查找悬浮按钮-次要色调(灰色系,区分查询按钮)
.back-btn {
background: linear-gradient(135deg, #666 0%, #333 100%);
}
// 弹窗样式补充
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.mb-20 {margin-bottom: 20rpx;}
.font-32 {font-size:32rpx;}
.font-40 {font-size:40rpx;}
.font-bold {font-weight: bold;}
.icon-close {color:#999;}
/* 扫码区域 */
.scan-section {
padding: 80rpx 40rpx;
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.scan-btn-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
.scan-icon {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(0, 122, 255, 0.3);
&:active {
transform: scale(0.95);
}
}
.icon-camera {
font-size: 70rpx;
}
.scan-tip {
font-size: 28rpx;
color: #666;
}
}
}
/* 卡片样式 */
.info-card,
.material-card,
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
/* 警告卡片 */
.warning-card {
background: #fff2f0;
border: 2rpx solid #ffccc7;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
gap: 20rpx;
.warning-icon {
font-size: 48rpx;
flex-shrink: 0;
}
.warning-content {
flex: 1;
.warning-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #cf1322;
margin-bottom: 8rpx;
}
.warning-desc {
display: block;
font-size: 24rpx;
color: #a8071a;
line-height: 1.4;
}
}
}
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #007aff;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.status-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
margin-right: 10rpx;
&.status-1 {
background: #d1f2eb;
color: #0c6957;
}
&.status-0 {
background: #f8d7da;
color: #721c24;
}
}
.more-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
background: #f0f7ff;
border-radius: 20rpx;
border: 1rpx solid #007aff;
.icon-more {
font-size: 24rpx;
color: #007aff;
}
.more-text {
font-size: 24rpx;
color: #007aff;
}
}
} // ✅ 修复补全card-title的闭合大括号
/* 信息网格 */
.info-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.info-item {
flex: 1;
min-width: 45%;
background: #f8f9fa;
padding: 20rpx;
border-radius: 12rpx;
&.full-width {
flex: 0 0 100%;
}
.item-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 10rpx;
}
.item-value {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
/* 表单项 */
.form-item {
margin-bottom: 30rpx;
&:last-of-type {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
// &::before {
// content: '*';
// color: #ff4d4f;
// margin-right: 6rpx;
// }
}
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
transition: all 0.3s;
&:focus {
background: #fff;
border-color: #007aff;
}
&.form-input-disabled {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
}
}
}
/* 操作者信息 */
.operator-info {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
margin-top: 20rpx;
.operator-label {
font-size: 26rpx;
color: #999;
}
.operator-name {
font-size: 26rpx;
color: #007aff;
font-weight: 500;
}
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
.btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
transition: all 0.2s;
&:active {
transform: scale(0.98);
}
&[disabled] {
opacity: 0.6;
}
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-primary {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
}
}
/* BOM弹窗 */
.bom-popup {
background: #fff;
border-radius: 24rpx 24rpx 0 0;
max-height: 70vh;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
.popup-close {
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.popup-body {
padding: 30rpx;
max-height: 500rpx;
.bom-item {
display: flex;
gap: 20rpx;
padding: 24rpx;
background: #f8f9fa;
border-radius: 12rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.bom-index {
width: 60rpx;
height: 60rpx;
background: #007aff;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: bold;
flex-shrink: 0;
}
.bom-content {
flex: 1;
.bom-row {
display: flex;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.bom-label {
font-size: 26rpx;
color: #666;
min-width: 120rpx;
}
.bom-value {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
}
}
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -7,13 +7,17 @@ import {
} from "@/utils/validate"
import {
login,
loginZinc1,
logout,
getInfo
} from '@/api/login'
import {
getToken,
setToken,
removeToken
removeToken,
getZinc1Token,
setZinc1Token,
removeZinc1Token
} from '@/utils/auth'
import defAva from '@/static/images/avatar.png'
@@ -56,25 +60,41 @@ const user = {
},
actions: {
// 登录
Login({
commit
}, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
console.log('token', res)
setToken(res.data.token)
commit('SET_TOKEN', res.data.token)
resolve()
}).catch(error => {
reject(error)
// 登录主系统登录成功即算登录成功Zinc1系统静默登录
Login({
commit
}, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
// 先执行主系统登录
login(username, password, code, uuid).then(mainRes => {
// 主系统登录成功立即保存token并resolve
if (mainRes && mainRes.data && mainRes.data.token) {
setToken(mainRes.data.token)
commit('SET_TOKEN', mainRes.data.token)
}
// 主系统登录成功后静默执行Zinc1登录不等待结果不显示任何错误
loginZinc1(username, password, code, uuid).then(zinc1Res => {
// Zinc1登录成功静默保存token
if (zinc1Res && zinc1Res.data && zinc1Res.data.token) {
setZinc1Token(zinc1Res.data.token)
}
}).catch(() => {
// Zinc1登录失败完全静默处理不做任何提示
})
// 主系统登录成功即返回
resolve()
}).catch(error => {
// 只有主系统登录失败才reject
reject(error)
})
},
})
},
// 获取用户信息
GetInfo({
@@ -106,24 +126,25 @@ const user = {
})
},
// 退出系统
LogOut({
commit,
state
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
// 退出系统
LogOut({
commit,
state
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
removeZinc1Token() // 同时清除Zinc1 token
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
}
})
}
}
}

View File

@@ -1,5 +1,5 @@
{
"uni-load-more.contentdown": "上拉显示更多",
"uni-load-more.contentdown": "点击加载更多",
"uni-load-more.contentrefresh": "正在加载...",
"uni-load-more.contentnomore": "没有更多数据了"
}

View File

@@ -1,4 +1,5 @@
const TokenKey = 'App-Token'
const Zinc1TokenKey = 'App-Zinc1-Token'
export function getToken() {
return uni.getStorageSync(TokenKey)
@@ -11,3 +12,16 @@ export function setToken(token) {
export function removeToken() {
return uni.removeStorageSync(TokenKey)
}
// Zinc1系统的token管理
export function getZinc1Token() {
return uni.getStorageSync(Zinc1TokenKey)
}
export function setZinc1Token(token) {
return uni.setStorageSync(Zinc1TokenKey, token)
}
export function removeZinc1Token() {
return uni.removeStorageSync(Zinc1TokenKey)
}

View File

@@ -20,9 +20,7 @@ const request = config => {
url = url.slice(0, -1)
config.url = url
}
console.log('请求参数[' + config.method + config.url + ']', config)
return new Promise((resolve, reject) => {
uni.request({
method: config.method || 'get',

View File

@@ -13,50 +13,142 @@ export function createMeasureSocket({
} = {}) {
let socket = null
let manualClose = false
// 保存回调函数的引用,允许清空
let callbacks = {
onOpen,
onClose,
onError,
onMessage
}
const wsBase = (config.wsUrl || config.baseUrl || '').replace(/^http/, 'ws')
const url = `${wsBase}/websocket?type=${type}`
console.log(url)
function connect() {
manualClose = false
socket = new WebSocket(url)
socket.onopen = () => {
onOpen && onOpen()
// 使用 uni.connectSocket 建立连接(部分运行环境可能不支持 WebSocket会直接抛异常
try {
socket = uni.connectSocket({
url,
success() {
console.log('connectSocket 调用成功,等待 onOpen 回调')
},
fail(err) {
console.error('connectSocket 调用失败:', err)
callbacks.onError && callbacks.onError(err)
}
})
} catch (err) {
console.error('connectSocket 执行异常(可能环境不支持 WebSocket:', err)
callbacks.onError && callbacks.onError(err)
return
}
socket.onmessage = (evt) => {
onMessage && onMessage(evt.data)
if (!socket) {
console.error('connectSocket 未返回有效 socketTask')
return
}
socket.onerror = (err) => {
onError && onError(err)
}
// 正确的事件注册方式socketTask.onOpen / onMessage / onError / onClose
socket.onOpen((res) => {
console.log('WebSocket 已打开', res)
// 检查回调是否已被清空
if (callbacks && callbacks.onOpen) {
try {
callbacks.onOpen(res)
} catch (e) {
// 忽略回调错误
}
}
})
socket.onclose = (evt) => {
onClose && onClose(evt)
if (!manualClose) {
socket.onMessage((evt) => {
// H5 为 evt.data小程序为 evt.data统一兼容
const data = evt && (evt.data || evt)
// 检查回调是否已被清空
if (callbacks && callbacks.onMessage) {
try {
callbacks.onMessage(data)
} catch (e) {
// 忽略回调错误,可能是组件已销毁
}
}
})
socket.onError((err) => {
console.error('WebSocket 发生错误:', err)
// 检查回调是否已被清空
if (callbacks && callbacks.onError) {
try {
callbacks.onError(err)
} catch (e) {
// 忽略回调错误
}
}
})
socket.onClose((evt) => {
console.log('WebSocket 已关闭', evt)
// 检查回调是否已被清空
if (callbacks && callbacks.onClose) {
try {
callbacks.onClose(evt)
} catch (e) {
// 忽略回调错误
}
}
if (!manualClose && callbacks) {
setTimeout(connect, 3000)
}
}
})
}
function close() {
manualClose = true
// 清空所有回调,防止在关闭过程中或关闭后触发回调
callbacks = {
onOpen: null,
onClose: null,
onError: null,
onMessage: null
}
if (socket) {
socket.close(1000, 'client close')
try {
socket.close(1000, 'client close')
} catch (e) {
// ignore
}
socket = null
}
}
function send(data) {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(data));
} else {
console.error('WebSocket is not open. Cannot send message.');
if (!socket) {
console.error('WebSocket 未初始化,无法发送消息')
return
}
// uni-app 的 socketTask.send 使用对象形式
socket.send({
data: JSON.stringify(data),
fail(err) {
console.error('发送 WebSocket 消息失败:', err)
}
})
}
// 清空回调函数的方法
function clearCallbacks() {
callbacks = {
onOpen: null,
onClose: null,
onError: null,
onMessage: null
}
}
return { connect, close, send }
return { connect, close, send, clearCallbacks }
}

View File

@@ -73,13 +73,13 @@ function checkStorageSpace() {
function checkUpdate(forceCheck = false) {
// 1. 准备本地版本信息
const localVersion = plus.runtime.version; // 基座版本
const staticVersion = '1.0.0'; // 静态默认版本
const staticVersion = '1.3.30'; // 静态默认版本
// const localWgtVersion = staticVersion;
const localWgtVersion = uni.getStorageSync('wgtVersion') || staticVersion; // 本地wgt版本从存储获取或用默认
const currentVersion = compareVersion(localWgtVersion, localVersion) > 0
? localWgtVersion
: localVersion; // 当前有效版本(取两者较高者)
// 2. 请求远程版本信息
uni.request({
url: `${baseURL}/version.json?t=${Date.now()}`, // 加时间戳防缓存
@@ -151,24 +151,24 @@ function checkUpdate(forceCheck = false) {
// 显示更新提示模态框
function showUpdateModal(remoteVersion, wgtUrl) {
// uni.showModal({
// title: '发现新版本',
// content: `检测到新版本(${remoteVersion}),是否立即下载并更新?`,
// confirmText: '立即更新',
// cancelText: '退出',
// showCancel: true,
// success: (modalRes) => {
// if (modalRes.confirm) {
// // 用户确认更新:检查存储空间 -> 下载 -> 安装
// handleConfirmUpdate(wgtUrl, remoteVersion);
// } else {
// // 直接退出
uni.showModal({
title: '发现新版本',
content: `检测到新版本(${remoteVersion}),是否立即下载并更新?`,
confirmText: '立即更新',
cancelText: '暂不更新',
showCancel: true,
success: (modalRes) => {
if (modalRes.confirm) {
// 用户确认更新:检查存储空间 -> 下载 -> 安装
handleConfirmUpdate(wgtUrl, remoteVersion);
} else {
// 直接退出
// // 用户取消更新:询问是否忽略该版本
// handleCancelUpdate(remoteVersion);
// }
// }
// });
// 用户取消更新:询问是否忽略该版本
// handleCancelUpdate(remoteVersion);
}
}
});
}

View File

@@ -0,0 +1,70 @@
import store from '@/store'
import config from '@/config'
import { getZinc1Token } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { toast, showConfirm, tansParams } from '@/utils/common'
let timeout = 10000
// 固定使用 zinc1 的 baseUrl
const baseUrl = 'http://140.143.206.120:10082/prod-api'
const zinc1Request = config => {
// 是否需要设置 token
const isToken = (config.header || {}).isToken === false
config.header = config.header || {}
// 使用Zinc1系统的token
if (getZinc1Token() && !isToken) {
config.header['Authorization'] = 'Bearer ' + getZinc1Token()
}
// get请求映射params参数
if (config.params) {
let url = config.url + '?' + tansParams(config.params)
url = url.slice(0, -1)
config.url = url
}
return new Promise((resolve, reject) => {
uni.request({
method: config.method || 'get',
timeout: config.timeout || timeout,
url: baseUrl + config.url,
data: config.data,
header: config.header,
dataType: 'json'
}).then(response => {
let [error, res] = response
if (error) {
toast('后端接口连接异常')
reject('后端接口连接异常')
return
}
const code = res.data.code || 200
const msg = errorCode[code] || res.data.msg || errorCode['default']
if (code === 401) {
// Zinc1系统是可选的如果返回401静默处理不显示任何提示
reject('Zinc1系统登录状态已过期')
} else if (code === 500) {
toast(msg)
reject('500')
} else if (code !== 200) {
toast(msg)
reject(code)
}
resolve(res.data)
})
.catch(error => {
let { message } = error
if (message === 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
toast(message)
reject(error)
})
})
}
export default zinc1Request

View File

@@ -1,5 +1,5 @@
{
"version": "klp 1.3.17",
"version": "klp 1.3.30",
"wgtUrl": "http://49.232.154.205:10900/fadapp-update/klp/klp.wgt",
"apkUrl": "http://49.232.154.205:10900/fadapp-update/klp/klp.apk"
}

View File

@@ -54,6 +54,7 @@
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"splitpanes": "2.4.1",
"three": "^0.158.0",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -29,6 +29,14 @@ export function listPlan(query) {
})
}
export function getL3PickupRecommend(limit = 10) {
return l2Request({
url: '/api/pdi/l3PickupRecommend',
method: 'get',
params: { limit }
})
}
// 查询计划详细
export function getPlan(coilId) {
return l2Request({

View File

@@ -0,0 +1,278 @@
// 产线配置数据(替代后端 API
// 轨道节点配置说明:
// trackNodes: 轨道节点数组,按顺序连接
// 每个节点包含:
// id: 节点唯一标识
// name: 节点名称(可选,用于显示)
// position: { x, y, z } 节点位置坐标
// width: 轨道宽度可选默认1.2
// waypoints: 中间路径点数组(可选),用于定义从该节点到下一个节点的中间转弯点
// 每个waypoint包含{ x, y, z } 位置坐标
// 示例waypoints: [{ x: 10, y: 0.5, z: 5 }, { x: 15, y: 0.5, z: 5 }]
// 轨道会依次经过:节点 -> waypoint1 -> waypoint2 -> ... -> 下一个节点
export const productionLineConfig = {
name: "产线展示",
description: "基于节点的轨道系统,可手动调节每个节点的位置和高度。",
// 运动扁钢(压钢带)参数
steelFlow: {
count: 4, // 扁钢数量(同时在轨道上运动的条数)
length: 8, // 每条扁钢长度(沿轨道方向)
widthRatio: 0.85, // 扁钢宽度=轨道宽度*比例
thickness: 0.10, // 扁钢厚度
speed: 6 // 运动速度(数值越大越快)
},
// 轨道节点配置 - 按顺序连接,轨道会平滑通过这些节点
// 可以在节点之间添加 waypoints 来定义中间转弯点
trackNodes: [
{ id: "node_1", name: "起点", position: { x: 5.0, y: 5.0, z: 0.0 }, width: 1.8 },
{ id: "node_2", name: "1#开卷机后", position: { x: 23.0, y: 5.2, z: 0.0 }, width: 1.2 },
{ id: "node_3", name: "2#开卷机前", position: { x: 28.0, y: 5.2, z: 0.0 }, width: 1.2 },
{ id: "node_4", name: "2#开卷机后", position: { x: 42.0, y: 5.2, z: 0.0 }, width: 1.2 },
{
id: "node_5",
name: "夹送矫直前",
position: { x: 46, y: 6.0, z: 0.0 },
width: 1.2,
// 示例:从上一个节点到当前节点,经过多个转弯点
// waypoints: [
// { x: -13.0, y: 3.0, z: 0.0 }, // 第一个转弯点
// { x: -12.5, y: 4.5, z: 0.0 } // 第二个转弯点
// ]
},
{ id: "node_6", name: "夹送矫直后", position: { x:55.0, y: 5.5, z: 0.0 }, width: 1.2 },
{
id: "node_7",
name: "清洗段前",
position: { x: 64, y: 2, z: 1.6 },
width: 1.2,
// 从清洗段前到清洗段后的中间转弯点
// 轨道会依次经过node_7 -> waypoint1 -> waypoint2 -> ... -> node_8
waypoints: [
// 在这里添加转弯点,例如:
{ x: 70, y: 2.0, z: 0.0 }, // 转弯点1
{ x: 72, y: 2.0, z: 13.0 }, // 转弯点2
// { x: 90, y: 0.6, z: 12.0 } // 转弯点3
]
},
{
id: "node_8",
name: "清洗段后",
position: { x: 92, y: 0.6, z: 13.6 },
width: 1.2,
// 示例:清洗段内部可能有多次转弯
waypoints: [
// { x: -2.0, y: 0.6, z: 5.0 }, // 转弯点1
// { x: 0.0, y: 0.6, z: 9.0 }, // 转弯点2
// { x: 1.0, y: 0.6, z: 11.5 } // 转弯点3
]
},
{ id: "node_9", name: "炉火段前", position: { x:92, y:3, z: 2.2 }, width: 1.2 },
{ id: "node_10", name: "炉火段后", position: { x: 92, y: 3, z: 2.2 }, width: 1.2,
waypoints: [
{ x: 92.0, y:5, z: 0.0 }, // 转弯点1
{ x: 92.0, y: 5, z: -10.0 }, // 转弯点2
// { x: 1.0, y: 0.6, z: 11.5 } // 转弯点3
]
},
{ id: "node_11", name: "锌锅前", position: { x: 109, y: 3, z: -10.0 }, width: 1.2 },
{ id: "node_12", name: "锌锅后", position: { x: 109, y:7, z: 4.0 }, width: 1.2,
waypoints: [
{ x: 109.0, y: 5, z: 13.0 }, // 转弯点1
]
},
{ id: "node_13", name: "光整机前",position: { x: 127.0, y: 5, z: 15.0 }, width: 1.2 },
{ id: "node_14", name: "光整机后", position: { x: 130.0, y: 5, z: 15.0 }, width: 1.2 },
{ id: "node_15", name: "拉矫机前", position: { x: 134.0, y: 7, z: 15.0 }, width: 1.2 },
{ id: "node_16", name: "拉矫机后", position: { x: 142.0, y: 7, z: 15.0 }, width: 1.2 },
{ id: "node_17", name: "剪切机前", position: { x:148.0, y: 5, z: 15.0 }, width: 1.2 },
{ id: "node_18", name: "剪切机后", position: { x: 158.0, y: 5, z: 15.0 }, width: 1.2 },
{ id: "node_19", name: "卷取机前", position: { x: 160.0, y: 7, z: 15.0 }, width: 1.2 },
{ id: "node_20", name: "终点", position: { x: 165.0, y: 7, z: 15.0 }, width: 1.2 },
{ id: "node_21", name: "结束", position: { x: 170.0, y: 2, z: 15.0 }, width: 1.2 },
],
// 设备配置(独立于轨道)
models: [
// 入口段(示例:开卷机可复用两次)
{
id: "unjcoiler_1",
name: "1#开卷机",
deviceCode: "POR1",
file: "/models/开卷机.glb",
position: { x: -24.0, y: 0.0, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut:2,
leadIn: 0,
entryOffset: { y: 0 },
exitOffset: { y: 1.2 },
conveyor: { enabled: true, yOffset: 5.5, width: 1.2 }
}
},
{
id: "unjcoiler_2",
name: "2#开卷机",
deviceCode: "POR2",
file: "/models/开卷机.glb",
position: { x: -18.0, y: 0.0, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 6,
leadIn: 1,
entryOffset: { y: 0.2 },
exitOffset: { y: 0.2 },
conveyor: { enabled: true, yOffset: 5.5, width: 1.2 }
}
},
// 夹送矫直
{
id: "pinch_leveler",
name: "夹送矫直机",
// 暂无对应的实时设备编码,可后续按需补充
file: "/models/夹送矫直机.glb",
position: { x: -12.0, y: 0.0, z: 0.0 },
rotation: { x: 0.0, y: 3.1415926, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 0,
leadIn: 2.0,
entryOffset: { y: 0.25 },
exitOffset: { y: 0.25 },
rampLength: 0,
conveyor: { enabled: true, yOffset: 6, width: 1.2 }
}
},
// 清洗/炉火
{
id: "clean",
name: "清洗段",
deviceCode: "CLEAN",
file: "/models/清洗段.glb",
position: { x: -6, y: 0.0, z: 5.6 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 8.0,
leadIn: 0,
entryOffset: { y: 0, z:-4},
exitOffset: { y: 0, z: 8 },
rampLength: 4.0,
conveyor: { enabled: false, yOffset: 0.6, zOffset: 0, width: 1.2 }
}
},
{
id: "furnace",
name: "炉火段",
deviceCode: "FUR1",
file: "/models/炉火段.glb",
position: { x: 0.0, y: 0.0, z: 2.2 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 2.0,
leadIn: 2.0,
entryOffset: { y: 0.25 },
exitOffset: { y: 0.25 },
rampLength: 3.5,
conveyor: { enabled: true, yOffset: 0.25, width: 1.2 }
}
},
// 锌锅/后处理
{
id: "pot",
name: "锌锅",
deviceCode: "POT",
file: "/models/锌锅.glb",
position: { x: 6.0, y: 0.0, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+z",
leadOut: 1.8,
leadIn: 2.2,
entryOffset: { y: 0.35 },
exitOffset: { y: 0.35, x: -0.6 },
rampLength: 3.0,
conveyor: { enabled: true, yOffset: 0.35, width: 1.2 }
}
},
{
id: "skinpass",
name: "光整机",
deviceCode: "TM",
file: "/models/光整机.glb",
position: { x: 6.0, y: 0.0, z: 18.0 },
rotation: { x: 0.0, y: Math.PI / 2, z: 0.0 },
io: {
entryDir: "-z",
exitDir: "+z",
leadOut: 2.2,
leadIn: 2.2,
entryOffset: { y: 0.25 },
exitOffset: { y: 0.25 },
rampLength: 3.5,
conveyor: { enabled: true, yOffset: 0.25, width: 1.2 }
}
},
{
id: "bridle",
name: "拉矫机",
deviceCode: "TL",
file: "/models/拉轿机.glb",
rotation: { x: 0.0, y: Math.PI / 2, z: 0.0 },
position: { x: 8.0, y: 0.0, z: 15.0 },
io: {
entryDir: "-z",
exitDir: "+x",
leadOut: 2.0,
leadIn: 2.0,
entryOffset: { y: 0.25 },
exitOffset: { y: 0.25 },
rampLength: 3.0,
conveyor: { enabled: true, yOffset: 0.25, width: 1.2 }
}
},
// 剪切/卷取
{
id: "shear",
name: "剪切机",
deviceCode: "EXC",
file: "/models/剪切机.glb",
position: { x: 16.0, y: 0.0, z: 15.0 },
rotation: { x: 0.0, y: Math.PI / 2, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 2.0,
leadIn: 2.0,
entryOffset: { y: 0.3 },
exitOffset: { y: 0.3 },
rampLength: 3.0,
conveyor: { enabled: true, yOffset: 0.3, width: 1.2 }
}
},
{
id: "coiler",
name: "卷取机",
deviceCode: "TR",
file: "/models/卷取机.glb",
position: { x: 24.0, y: 0.0, z: 22.0 },
rotation: { x: 0.0, y: Math.PI / 2, z: 0.0 },
io: {
entryDir: "-x",
exitDir: "+x",
leadOut: 2.0,
leadIn: 2.0,
entryOffset: { y: 0.3 },
exitOffset: { y: 0.3 },
conveyor: { enabled: true, yOffset: 0.3, width: 1.2 }
}
},
]
}

View File

@@ -57,7 +57,7 @@ $theme-text-gray: #a1a6af;
// 仅维护组件自身样式,无定位属性
.current-time {
padding: 1.2vw 1.8vw;
background: white;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(8px);
border: 1px solid rgba(167, 172, 180, 0.3);
border-radius: 8px;
@@ -91,4 +91,4 @@ $theme-text-gray: #a1a6af;
}
}
}
</style>
</style>

View File

@@ -1,13 +1,9 @@
<template>
<div class="industrial-dashboard">
<!-- Header: 系统标题和当前时间 -->
<div class="dashboard-header">
<div class="header-left">
<h1 class="system-title">生产监控大屏</h1>
<p class="system-subtitle">实时生产数据与设备状态</p>
</div>
<div class="header-right">
<CurrentTime />
<!-- 顶部产线三维展示时间显示放入3D组件内部 -->
<div class="production-line-section">
<div class="production-line-view">
<ProductionLine />
</div>
</div>
@@ -82,9 +78,9 @@
<span>系统告警信息</span>
<el-badge :value="alarmData.length" class="alarm-badge" v-if="alarmData.length > 0"></el-badge>
</div>
<MiniTable
<MiniTable
v-loading="tableLoading"
:columns="alarmColumns"
:columns="alarmColumns"
:data="alarmData"
:highlightOnRowClick="true"
tableHeight="280px"
@@ -96,7 +92,7 @@
<div slot="header" class="card-header">
<span>换辊信息</span>
</div>
<MiniTable
<MiniTable
v-loading="rollHistoryLoading"
:columns="rollHistoryColumns"
:data="rollHistoryData"
@@ -116,7 +112,7 @@
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
<MiniTable
<MiniTable
v-loading="planLoading"
:columns="planColumns"
:data="planData"
@@ -137,7 +133,7 @@
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
<TrackMeasure
<TrackMeasure
v-loading="measureLoading"
:columns="measureColumns"
:data="measureData"
@@ -155,9 +151,9 @@
<span>快速访问</span>
</div>
<div class="quick-access-grid">
<div
class="access-item"
v-for="(card, index) in featureCards"
<div
class="access-item"
v-for="(card, index) in featureCards"
:key="index"
@click="$router.push(card.path)"
>
@@ -177,14 +173,14 @@
</el-col>
</el-row>
</div>
</template>
</template>
<script>
import CurrentTime from "./components/CurrentTime.vue";
import HomeMain from "./components/HomeMain.vue";
import MiniTable from "./components/MiniTable.vue";
import ProductionLine from "./l2/productLine/ProductLine.vue";
// 引入日志API / 生产相关API
import { getLogDataPage } from "@/api/l2/log";
import { getLogDataPage } from "@/api/l2/log";
import { getRollHistorytList } from '@/api/l2/roller'
import { listPlan } from "@/api/l2/plan";
import TrackMeasure from "@/components/TrackMeasure/index.vue";
@@ -192,7 +188,7 @@ import { getCurrentProducingPlan, getCurrentProcessParams } from "@/api/business
export default {
name: "Index",
components: { CurrentTime, HomeMain, MiniTable, TrackMeasure },
components: { HomeMain, MiniTable, TrackMeasure, ProductionLine },
data() {
return {
// KPI指标数据
@@ -431,30 +427,16 @@ $theme-text-gray: #c9cdcf;
min-height: calc(100vh - 60px);
}
/* 仪表板头部 */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
/* 顶部产线展示区 */
.production-line-section {
margin-bottom: 20px;
padding: 20px;
background: #ffffff; // 简洁白色背景,避免低级渐变色
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
.header-left {
.system-title {
color: #000;
font-size: 28px;
font-weight: 600;
margin: 0 0 8px 0;
}
.system-subtitle {
color: #000;
font-size: 14px;
margin: 0;
}
.production-line-view {
background: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 0;
overflow: hidden;
}
}
@@ -707,4 +689,4 @@ $theme-text-gray: #c9cdcf;
}
}
}
</style>
</style>

View File

@@ -56,7 +56,7 @@
<!-- 数据表格 -->
<el-table v-loading="tableLoading" :data="tableData" border style="width: 100%; margin-top: 20px;"
@row-click="handleRowClick" highlight-current-row>
<el-table-column prop="seqid" label="序号" width="80" align="center"></el-table-column>
<el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
<el-table-column prop="timestamp" label="发生时间" width="180" align="center"></el-table-column>
<el-table-column prop="module" label="报警模块" align="center">
<template slot-scope="scope">
@@ -120,7 +120,7 @@ export default {
logtext: '',
status: '',
pageNum: 1,
pageSize: 10
pageSize: 50
},
// 表格数据
tableData: [],

View File

@@ -18,6 +18,12 @@
<el-input v-model="formData.entryMatId"></el-input>
</el-form-item>
</el-col>
<!-- 入场钢卷号 -->
<el-col :span="8">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="formData.enterCoilNo"></el-input>
</el-form-item>
</el-col>
<!-- 分切数 -->
<el-col :span="8">
<el-form-item label="分切数" prop="subId">

View File

@@ -53,22 +53,13 @@ export default {
{ label: '带钢速度', value: 'stripSpeed' },
{ label: '1#开卷机张力', value: 'tensionPorBr1' },
{ label: '2#开卷机张力', value: 'tensionPorBr2' },
{ label: '清洗电压', value: 'cleaningVoltage' },
{ label: '清洗电流', value: 'cleaningCurrent' },
{ label: '碱液浓度', value: 'alkaliConcentration' },
{ label: '碱液温度', value: 'alkaliTemperature' },
{ label: '预热段出口板温', value: 'phfExitStripTemp' },
{ label: '加热段出口板温', value: 'rtfExitStripTemp' },
{ label: '冷却段出口板温', value: 'jcsExitStripTemp' },
{ label: '均热段出口板温', value: 'scsExitStripTemp' },
{ label: '锌锅温度', value: 'potTemperature' },
{ label: '锌锅功率', value: 'zincPotPower' },
{ label: '燃气消耗量', value: 'gasConsumption' },
{ label: '冷却塔带钢温度', value: 'coolingTowerStripTemp' },
{ label: '光整机张力', value: 'tensionBr5Tm' },
{ label: '光整机出口速度', value: 'stripSpeedTmExit' },
{ label: '拉矫机延伸率', value: 'tlElongation' },
{ label: '拉矫机张力', value: 'tensionTlBr7' }
{ label: 'BR1-BR2张力', value: 'tensionBr1Br2' },
{ label: 'BR2-BR3张力', value: 'tensionBr2Br3' },
{ label: '入口活套套量', value: 'celCapacity' },
{ label: '出口活套套量', value: 'cxlCapacity' },
{ label: 'PH炉温度', value: 'phFurnaceTemperatureActual' },
{ label: 'NOF1炉温度', value: 'nof1FurnaceTemperatureActual' },
{ label: 'NOF1炉控制输出', value: 'nof1FurnaceTemperatureControlOutput' }
],
treeProps: {
children: 'children',

View File

@@ -37,9 +37,10 @@
<!-- 卡片网格布局 -->
<div v-loading="tableLoading">
<el-table :data="tableData" style="width: 100%" border stripe @row-click="handleRowClick" highlight-current-row height="300px">
<el-table :data="tableData" style="width: 100%" border stripe @row-click="handleRowClick" highlight-current-row>
<el-table-column prop="exitMatId" label="成品卷号"></el-table-column>
<el-table-column prop="entryMatId" label="来料卷号"></el-table-column>
<el-table-column prop="enterCoilNo" label="入场钢卷号"></el-table-column>
<el-table-column prop="planNo" label="计划单号"></el-table-column>
<el-table-column prop="status" label="状态"></el-table-column>
@@ -74,6 +75,18 @@
<div v-if="tableData.length === 0 && !tableLoading" class="empty-data">
<el-empty description="暂无数据"></el-empty>
</div>
<!-- 分页组件 -->
<el-pagination
v-if="total > 0"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryForm.pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="queryForm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 15px; text-align: right;"
></el-pagination>
</div>
<div>
@@ -143,10 +156,17 @@ export default {
data() {
return {
activeTab: 'basicInfo',
queryForm: { coilid: '', startDate: '', endDate: '' },
queryForm: {
coilid: '',
startDate: '',
endDate: '',
pageNum: 1,
pageSize: 20
},
printOpen: false,
certificateVisible: false, // 质保书对话框显示状态
tableData: [],
total: 0, // 总记录数
tableLoading: false,
btnLoading: false,
dialogVisible: false,
@@ -171,27 +191,51 @@ export default {
this.getPdoList()
},
methods: {
// 获取列表数据(保持不变
// 获取列表数据(支持分页
getPdoList() {
this.tableLoading = true
getPdoList(this.queryForm).then(res => {
this.tableData = res.data.map(item => ({ ...item, deleteLoading: false }))
if (res.code === 200) {
// 处理分页数据
this.tableData = (res.rows || res.data || []).map(item => ({ ...item, deleteLoading: false }))
this.total = res.total || 0
} else {
this.$message.error(res.msg || '获取数据失败')
this.tableData = []
this.total = 0
}
}).catch(err => {
console.error(err)
this.$message.error('获取数据失败')
this.tableData = []
this.total = 0
}).finally(() => {
this.tableLoading = false
this.btnLoading = false
})
},
// 分页大小改变
handleSizeChange(val) {
this.queryForm.pageSize = val
this.queryForm.pageNum = 1 // 重置到第一页
this.getPdoList()
},
// 当前页改变
handleCurrentChange(val) {
this.queryForm.pageNum = val
this.getPdoList()
},
// 查询(保持不变)
handleQuery() {
this.btnLoading = true;
this.queryForm.pageNum = 1; // 重置到第一页
this.getPdoList()
},
// 重置(保持不变)
handleReset() {
this.$refs.queryForm.resetFields();
this.queryForm.pageNum = 1;
this.queryForm.pageSize = 20;
this.getPdoList()
},
// 行点击(保持不变)
@@ -209,6 +253,7 @@ export default {
this.dialogTitle = '新增实绩';
// 初始化空表单数据(传递给子组件)
this.formData = {
enterCoilNo: '',
subId: 0,
startPosition: 0,
endPosition: 0,

View File

@@ -0,0 +1,113 @@
<template>
<div class="l3-pickup-recommend-panel">
<div class="section-header">
<div>
<div class="section-title">三级领料推荐</div>
<div class="section-subtitle">来自L3待处理领料队列actionType=501, actionStatus=0</div>
</div>
<el-button type="text" size="mini" @click="fetchList">刷新</el-button>
</div>
<div class="section-body" v-loading="loading">
<div v-if="recommendations.length" class="card-list">
<div
v-for="item in recommendations"
:key="item.actionId"
class="recommend-card"
@click="apply(item)"
>
<div class="card-title">{{ item.coilId || '-' }}</div>
<div class="card-meta">
<span>入场钢卷号{{ item.enterCoilNo || '-' }}</span>
<span>优先级{{ item.priority != null ? item.priority : '-' }}</span>
</div>
<div class="card-meta">
<span>来源coilId{{ item.sourceCoilId || '-' }}</span>
<span>扫码时间{{ formatDate(item.scanTime) }}</span>
</div>
<div class="card-footer">
<span>点击应用到左侧表单</span>
<i class="el-icon-top-right" />
</div>
</div>
</div>
<el-empty v-else description="暂无三级领料推荐" :image-size="90" />
</div>
</div>
</template>
<script>
import { parseTime } from '@/utils/ruoyi'
import { getL3PickupRecommend } from '@/api/l2/plan'
export default {
name: 'L3PickupRecommendPanel',
data() {
return {
loading: false,
recommendations: []
}
},
mounted() {
this.fetchList()
},
methods: {
async fetchList() {
this.loading = true
try {
const res = await getL3PickupRecommend(10)
this.recommendations = Array.isArray(res.data) ? res.data : []
} catch (e) {
console.error('fetch l3 pickup recommend error:', e)
this.$message?.error('获取三级领料推荐失败')
this.recommendations = []
} finally {
this.loading = false
}
},
apply(item) {
this.$emit('apply-recommendation', {
coilid: item.coilId || '',
enterCoilNo: item.enterCoilNo || ''
})
},
formatDate(v) {
if (!v) return '-'
return parseTime(v, '{y}-{m}-{d} {h}:{i}:{s}')
}
}
}
</script>
<style scoped>
.l3-pickup-recommend-panel {
background: #f9fafc;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 12px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.section-title { font-weight: 600; color: #303133; }
.section-subtitle { font-size: 12px; color: #909399; }
.section-body { max-height: 220px; overflow-y: auto; }
.card-list { display: grid; gap: 8px; }
.recommend-card {
border: 1px solid #dcdfe6;
border-radius: 6px;
background: #fff;
padding: 10px;
cursor: pointer;
}
.recommend-card:hover {
border-color: #409eff;
box-shadow: 0 6px 18px rgba(64, 158, 255, 0.15);
}
.card-title { font-weight: 600; color: #303133; margin-bottom: 6px; }
.card-meta { display: flex; justify-content: space-between; font-size: 12px; color: #606266; margin-bottom: 2px; }
.card-footer { display: flex; justify-content: space-between; font-size: 12px; color: #909399; margin-top: 4px; }
</style>

View File

@@ -1,16 +1,17 @@
<template>
<div class="plan-recommend-panel">
<!-- 钢种推荐 -->
<div class="recommend-section steel-panel">
<div class="section-header">
<div>
<div class="section-title">钢种推荐</div>
<div class="section-subtitle">锁定钢种后自动调取面板库中的典型工艺主数据</div>
<div class="section-subtitle">根据钢种查询历史工艺参数组合</div>
</div>
<el-tag v-if="steelGradeName" size="mini" effect="dark">{{ steelGradeName }}</el-tag>
</div>
<div class="section-body" v-loading="loadingSteel">
<template v-if="steelRecommendations.length">
<div class="card-list two-cols">
<div class="card-list">
<div
class="recommend-card"
v-for="item in steelRecommendations"
@@ -18,27 +19,13 @@
@click="emitSteelGrade(item)"
>
<div class="card-header">
<div class="card-title">
{{ item.steelGradeLabel || '钢种' }}
</div>
<el-tag size="mini" type="info">
厚度 {{ formatNumber(item.entryThick) }} mm
</el-tag>
<div class="card-title">{{ item.steelGradeLabel || '钢种' }}</div>
<el-tag size="mini" type="info">厚度 {{ formatNumber(item.thick) }} mm</el-tag>
</div>
<div class="card-meta">
<div>
<span class="label">屈服</span>
<span class="value">{{ formatNumber(item.yieldPoint) }}</span>
</div>
<div>
<span class="label">延伸率</span>
<span class="value">{{ formatNumber(item.spmElongation) }}</span>
</div>
</div>
<div class="card-meta">
<div>
<span class="label">轧制力</span>
<span class="value">{{ formatNumber(item.spmRollforce) }}</span>
<span class="label">屈服强度</span>
<span class="value">{{ formatNumber(item.yieldStren) }}</span>
</div>
<div>
<span class="label">更新时间</span>
@@ -46,169 +33,13 @@
</div>
</div>
<div class="card-footer">
<span>点击写入钢种及主键</span>
<span>点击应用此组合</span>
<i class="el-icon-top-right" />
</div>
</div>
</div>
</template>
<el-empty
v-else
description="选择钢种后即可联动查询"
:image-size="100"
/>
</div>
</div>
<div class="recommend-section thickness-panel">
<div class="section-header">
<div>
<div class="section-title">厚度/屈服点联动</div>
<div class="section-subtitle">输入厚度后自动搜集多面板库对应的屈服点档案</div>
</div>
<el-tag v-if="entryThick" size="mini" effect="plain">{{ formatNumber(entryThick) }} mm</el-tag>
</div>
<div class="section-body" v-loading="loadingThickness">
<template v-if="thicknessRecommendations.length">
<div class="card-list">
<div
class="recommend-card"
v-for="item in thicknessRecommendations"
:key="item._id"
@click="emitThickness(item)"
>
<div class="card-header">
<div class="card-title">
{{ formatNumber(item.entryThick) }} mm
</div>
<el-tag size="mini" type="success">
屈服点 {{ formatNumber(item.yieldPoint) }}
</el-tag>
</div>
<div class="card-meta">
<div>
<span class="label">钢种</span>
<span class="value">{{ item.steelGradeLabel || '-' }}</span>
</div>
<div>
<span class="label">数据来源</span>
<span class="value">{{ item.sourceLabel }}</span>
</div>
</div>
<div class="card-footer">
<span>{{ item.updateTime ? '最近更新 ' + formatDate(item.updateTime) : '点击写入厚度/屈服点' }}</span>
<i class="el-icon-top-right" />
</div>
</div>
</div>
</template>
<el-empty
v-else
description="填写厚度后可获取推荐屈服点"
:image-size="100"
/>
</div>
</div>
<div class="recommend-section width-panel">
<div class="section-header">
<div>
<div class="section-title">宽度/弯辊力联动</div>
<div class="section-subtitle">根据宽度&轧制力匹配弯辊力与宽度组合</div>
</div>
<el-tag v-if="entryWidth" size="mini" type="info">{{ formatNumber(entryWidth) }} mm</el-tag>
</div>
<div class="section-body" v-loading="loadingWidth">
<template v-if="widthRecommendations.length">
<div class="card-list">
<div
class="recommend-card"
v-for="item in widthRecommendations"
:key="item._id"
@click="emitWidth(item)"
>
<div class="card-header">
<div class="card-title">{{ formatNumber(item.width) }} mm</div>
<el-tag size="mini" effect="plain">轧制力 {{ formatNumber(item.rollForce) }}</el-tag>
</div>
<div class="card-meta">
<div>
<span class="label">弯辊力</span>
<span class="value">{{ formatNumber(item.bendForce) }}</span>
</div>
<div>
<span class="label">数据来源</span>
<span class="value">{{ item.sourceLabel }}</span>
</div>
</div>
<div class="card-footer">
<span>点击写入宽度/轧制力</span>
<i class="el-icon-top-right" />
</div>
</div>
</div>
</template>
<el-empty
v-else
description="填写宽度后可获取推荐数据"
:image-size="100"
/>
</div>
</div>
<div class="recommend-section rollforce-panel">
<div class="section-header">
<div>
<div class="section-title">轧制力反查</div>
<div class="section-subtitle">输入轧制力后反查对应厚度/屈服点组合</div>
</div>
<el-tag v-if="spmRollforce" size="mini" type="warning">{{ formatNumber(spmRollforce) }}</el-tag>
</div>
<div class="section-body" v-loading="loadingRollforce">
<template v-if="rollforceFocusRecommendations.length">
<div class="card-list">
<div
class="recommend-card"
v-for="item in rollforceFocusRecommendations"
:key="item._id"
@click="emitRollforce(item)"
>
<div class="card-header">
<div class="card-title">{{ formatNumber(item.spmRollforce) }}</div>
<el-tag size="mini" type="success">{{ item.sourceLabel }}</el-tag>
</div>
<div class="card-meta">
<div>
<span class="label">厚度</span>
<span class="value">{{ formatNumber(item.entryThick) }} mm</span>
</div>
<div>
<span class="label">屈服点</span>
<span class="value">{{ formatNumber(item.yieldPoint) }}</span>
</div>
</div>
<div class="card-meta">
<div>
<span class="label">延伸率</span>
<span class="value">{{ formatNumber(item.spmElongation) }}</span>
</div>
<div>
<span class="label">钢种</span>
<span class="value">{{ item.steelGradeLabel || '-' }}</span>
</div>
</div>
<div class="card-footer">
<span>点击写入相关字段</span>
<i class="el-icon-top-right" />
</div>
</div>
</div>
</template>
<el-empty
v-else
description="填写轧制力后可获取推荐数据"
:image-size="100"
/>
<el-empty v-else description="选择钢种后即可联动查询" :image-size="100" />
</div>
</div>
</div>
@@ -216,9 +47,10 @@
<script>
import { parseTime } from '@/utils/ruoyi'
import { listRollforce } from '@/api/business/rollforce'
import { listTension } from '@/api/business/tension'
import { listBendforce } from '@/api/business/bendforce'
import { listTensionAllLine } from '@/api/business/fullLineTension'
import { listTensionLeveler } from '@/api/business/levelerTension'
import { listTensionStraightener } from '@/api/business/straightenerTension'
import { listTensionAnnealingFurnace } from '@/api/business/annealingFurnaceTension'
let uid = 0
const createId = () => ++uid
@@ -226,45 +58,20 @@ const createId = () => ++uid
export default {
name: 'PlanRecommendPanel',
props: {
steelGrade: {
type: [String, Number],
default: null
},
entryThick: {
type: [String, Number],
default: null
},
entryWidth: {
type: [String, Number],
default: null
},
spmRollforce: {
type: [String, Number],
default: null
},
steelGradeList: {
type: Array,
default: () => []
}
steelGrade: { type: [String, Number], default: null },
entryThick: { type: [String, Number], default: null },
steelGradeList: { type: Array, default: () => [] }
},
data() {
return {
steelRecommendations: [],
thicknessRecommendations: [],
widthRecommendations: [],
rollforceFocusRecommendations: [],
loadingSteel: false,
loadingThickness: false,
loadingWidth: false,
loadingRollforce: false,
steelFetchToken: 0,
thicknessFetchToken: 0,
widthFetchToken: 0,
rollforceFetchToken: 0,
steelDebounceTimer: null,
thicknessDebounceTimer: null,
widthDebounceTimer: null,
rollforceDebounceTimer: null
thicknessDebounceTimer: null
}
},
computed: {
@@ -276,92 +83,85 @@ export default {
steelGrade: {
immediate: true,
handler(val) {
if (val === undefined || val === null || val === '') {
if (!val) {
this.steelRecommendations = []
return
}
clearTimeout(this.steelDebounceTimer)
this.steelDebounceTimer = setTimeout(() => {
this.fetchSteelRecommendations()
}, 200)
this.steelDebounceTimer = setTimeout(() => this.fetchSteelRecommendations(), 200)
}
},
entryThick: {
immediate: true,
handler(val) {
if (val === undefined || val === null || val === '') {
if (!val) {
this.thicknessRecommendations = []
return
}
clearTimeout(this.thicknessDebounceTimer)
this.thicknessDebounceTimer = setTimeout(() => {
this.fetchThicknessRecommendations()
}, 200)
}
},
entryWidth: {
immediate: true,
handler(val) {
if (val === undefined || val === null || val === '') {
this.widthRecommendations = []
return
}
clearTimeout(this.widthDebounceTimer)
this.widthDebounceTimer = setTimeout(() => {
this.fetchWidthRecommendations()
}, 200)
}
},
spmRollforce: {
immediate: true,
handler(val) {
if (val === undefined || val === null || val === '') {
this.rollforceFocusRecommendations = []
return
}
clearTimeout(this.rollforceDebounceTimer)
this.rollforceDebounceTimer = setTimeout(() => {
this.fetchRollforceFocus()
}, 200)
this.thicknessDebounceTimer = setTimeout(() => this.fetchThicknessRecommendations(), 200)
}
}
},
beforeDestroy() {
clearTimeout(this.steelDebounceTimer)
clearTimeout(this.thicknessDebounceTimer)
clearTimeout(this.widthDebounceTimer)
clearTimeout(this.rollforceDebounceTimer)
},
methods: {
getSteelGradeLabel(gradeId) {
if (gradeId === undefined || gradeId === null) return ''
const gid = String(gradeId)
getSteelGradeLabel(grade) {
if (grade === undefined || grade === null) return ''
const gradeStr = String(grade)
const match = this.steelGradeList.find(
item =>
String(item.gradeid) === gid ||
String(item.gradeId) === gid ||
String(item.name) === gid ||
String(item.gradeName || '') === gid
(item) => String(item.gradeid) === gradeStr ||
String(item.gradeId) === gradeStr ||
String(item.name || '') === gradeStr ||
String(item.gradeName || '') === gradeStr
)
return match ? (match.name || match.gradeName || gid) : gid
return match ? match.name || match.gradeName : gradeStr
},
async fetchAllTensionData(query) {
const [allLineRes, levelerRes, straightenerRes, furnaceRes] = await Promise.allSettled([
listTensionAllLine(query),
listTensionLeveler(query),
listTensionStraightener(query),
listTensionAnnealingFurnace(query)
])
const records = []
if (allLineRes.status === 'fulfilled') {
records.push(...(allLineRes.value.rows || []).map(r => ({ ...r, sourceLabel: '全线张力' })))
}
if (levelerRes.status === 'fulfilled') {
records.push(...(levelerRes.value.rows || []).map(r => ({ ...r, sourceLabel: '平整机张力' })))
}
if (straightenerRes.status === 'fulfilled') {
records.push(...(straightenerRes.value.rows || []).map(r => ({ ...r, sourceLabel: '矫直机张力' })))
}
if (furnaceRes.status === 'fulfilled') {
records.push(...(furnaceRes.value.rows || []).map(r => ({ ...r, sourceLabel: '退火炉张力' })))
}
return records
},
async fetchSteelRecommendations() {
const token = ++this.steelFetchToken
this.loadingSteel = true
try {
const gradeLabel = this.getSteelGradeLabel(this.steelGrade)
const response = await listRollforce({
const allRecords = await this.fetchAllTensionData({
pageNum: 1,
pageSize: 8,
steelGrade: gradeLabel || this.steelGrade,
gradeId: this.steelGrade
pageSize: 50, // Fetch more to get variety
steelGrade: gradeLabel || this.steelGrade
})
if (token !== this.steelFetchToken) return
const rows = Array.isArray(response.rows) ? response.rows : []
this.steelRecommendations = rows.map(row => this.mapRollforceRow(row))
const mapped = allRecords.map(row => this.mapTensionRow(row))
this.steelRecommendations = this.uniqueRecords(mapped, ['steelGrade', 'thick', 'yieldStren']).slice(0, 8)
} catch (error) {
console.error('fetchSteelRecommendations error:', error)
this.$message?.error('钢种推荐获取失败,请稍后重试')
this.$message?.error('钢种推荐获取失败')
this.steelRecommendations = []
} finally {
if (token === this.steelFetchToken) {
@@ -369,38 +169,24 @@ export default {
}
}
},
async fetchThicknessRecommendations() {
const rawValue = this.entryThick
if (rawValue === undefined || rawValue === null || rawValue === '') {
this.thicknessRecommendations = []
return
}
const token = ++this.thicknessFetchToken
this.loadingThickness = true
try {
const query = {
const allRecords = await this.fetchAllTensionData({
pageNum: 1,
pageSize: 8,
thick: rawValue
}
const [rollforceRes, tensionRes] = await Promise.allSettled([
listRollforce(query),
listTension(query)
])
pageSize: 50,
thick: this.entryThick
})
if (token !== this.thicknessFetchToken) return
const records = []
if (rollforceRes.status === 'fulfilled') {
const rows = Array.isArray(rollforceRes.value.rows) ? rollforceRes.value.rows : []
records.push(...rows.map(row => this.mapRollforceRow(row, '光整机轧制力')))
}
if (tensionRes.status === 'fulfilled') {
const rows = Array.isArray(tensionRes.value.rows) ? tensionRes.value.rows : []
records.push(...rows.map(row => this.mapTensionRow(row)))
}
this.thicknessRecommendations = this.uniqueRecords(records).slice(0, 8)
const mapped = allRecords.map(row => this.mapTensionRow(row))
this.thicknessRecommendations = this.uniqueRecords(mapped, ['steelGrade', 'thick', 'yieldStren']).slice(0, 8)
} catch (error) {
console.error('fetchThicknessRecommendations error:', error)
this.$message?.error('厚度推荐获取失败,请稍后重试')
this.$message?.error('厚度推荐获取失败')
this.thicknessRecommendations = []
} finally {
if (token === this.thicknessFetchToken) {
@@ -408,173 +194,53 @@ export default {
}
}
},
async fetchWidthRecommendations() {
const widthVal = Number(this.entryWidth)
if (Number.isNaN(widthVal)) {
this.widthRecommendations = []
return
}
const token = ++this.widthFetchToken
this.loadingWidth = true
try {
const response = await listBendforce({
pageNum: 1,
pageSize: 50,
width: widthVal
})
if (token !== this.widthFetchToken) return
const rows = Array.isArray(response.rows) ? response.rows : []
const tolerance = Math.max(2, widthVal * 0.01)
const matches = rows
.filter(row => {
const value = Number(row.width)
return !Number.isNaN(value) && Math.abs(value - widthVal) <= tolerance
})
.map(row => this.mapBendforceRow(row))
this.widthRecommendations = matches.slice(0, 6)
} catch (error) {
console.error('fetchWidthRecommendations error:', error)
this.$message?.error('宽度推荐获取失败,请稍后重试')
this.widthRecommendations = []
} finally {
if (token === this.widthFetchToken) {
this.loadingWidth = false
}
}
},
async fetchRollforceFocus() {
const rollforceVal = Number(this.spmRollforce)
if (Number.isNaN(rollforceVal)) {
this.rollforceFocusRecommendations = []
return
}
const token = ++this.rollforceFetchToken
this.loadingRollforce = true
try {
const response = await listRollforce({
pageNum: 1,
pageSize: 50,
value1: rollforceVal
})
if (token !== this.rollforceFetchToken) return
const rows = Array.isArray(response.rows) ? response.rows : []
const tolerance = Math.max(5, rollforceVal * 0.02)
const matches = rows
.filter(row => {
const value = Number(row.value1)
return !Number.isNaN(value) && Math.abs(value - rollforceVal) <= tolerance
})
.map(row => this.mapRollforceByValueRow(row))
this.rollforceFocusRecommendations = matches.slice(0, 6)
} catch (error) {
console.error('fetchRollforceFocus error:', error)
this.$message?.error('轧制力推荐获取失败,请稍后重试')
this.rollforceFocusRecommendations = []
} finally {
if (token === this.rollforceFetchToken) {
this.loadingRollforce = false
}
}
},
mapRollforceRow(row, sourceLabel = '光整机轧制力') {
const gradeLabel =
row.steelGradeName ||
row.steelGradeLabel ||
this.getSteelGradeLabel(row.steelGrade) ||
row.steelGrade
return {
_id: `rollforce-${row.id || createId()}`,
sourceLabel,
steelGrade: row.steelGrade,
steelGradeLabel: gradeLabel,
entryThick: row.thick,
yieldPoint: row.yieldStren,
spmElongation: row.elong,
spmRollforce: row.value1,
updateTime: row.updateTime || row.createTime
}
},
mapRollforceByValueRow(row) {
return {
...this.mapRollforceRow(row, '轧制力面板'),
_id: `rollforce-focus-${row.id || createId()}`,
spmRollforce: row.value1
}
},
mapTensionRow(row) {
const gradeLabel = row.steelGradeName || this.getSteelGradeLabel(row.steelGrade) || row.steelGrade
const gradeLabel = this.getSteelGradeLabel(row.steelGrade)
return {
_id: `tension-${row.id || createId()}`,
sourceLabel: '全线张力',
sourceLabel: row.sourceLabel,
steelGrade: row.steelGrade,
steelGradeLabel: gradeLabel || '',
entryThick: row.thick,
yieldPoint: row.yieldStren,
spmElongation: row.elong,
spmRollforce: row.value1,
steelGradeLabel: gradeLabel,
thick: row.thick,
yieldStren: row.yieldStren,
updateTime: row.updateTime || row.createTime
}
},
mapBendforceRow(row) {
return {
_id: `bendforce-${row.id || createId()}`,
sourceLabel: '弯辊力面板',
width: row.width,
rollForce: row.rollForce,
bendForce: row.value1,
updateTime: row.updateTime || row.createTime
}
},
uniqueRecords(records) {
uniqueRecords(records, keys) {
const seen = new Set()
return records.filter(item => {
const key = [
item.steelGrade || '',
item.entryThick || '',
item.yieldPoint || '',
item.sourceLabel
].join('|')
const key = keys.map(k => item[k] || '').join('|')
if (seen.has(key)) return false
seen.add(key)
return true
})
},
emitSteelGrade(item) {
this.$emit('apply-steel-grade', {
this.$emit('apply-recommendation', {
steelGrade: item.steelGrade,
entryThick: item.entryThick,
yieldPoint: item.yieldPoint,
spmElongation: item.spmElongation,
spmRollforce: item.spmRollforce
entryThick: item.thick,
yieldPoint: item.yieldStren
})
},
emitThickness(item) {
this.$emit('apply-thickness', {
entryThick: item.entryThick,
yieldPoint: item.yieldPoint
})
},
emitWidth(item) {
this.$emit('apply-width', {
entryWidth: item.width,
spmRollforce: item.rollForce
})
},
emitRollforce(item) {
this.$emit('apply-rollforce', {
spmRollforce: item.spmRollforce,
entryThick: item.entryThick,
yieldPoint: item.yieldPoint,
spmElongation: item.spmElongation,
steelGrade: item.steelGrade
this.$emit('apply-recommendation', {
steelGrade: item.steelGrade,
entryThick: item.thick,
yieldPoint: item.yieldStren
})
},
formatNumber(value) {
if (value === null || value === undefined || value === '') return '-'
const num = Number(value)
if (Number.isNaN(num)) return value
return Number.isInteger(num) ? num.toString() : num.toFixed(2)
},
formatDate(value) {
if (!value) return '-'
return parseTime(value, '{y}-{m}-{d}')
@@ -586,7 +252,7 @@ export default {
<style scoped>
.plan-recommend-panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 16px;
height: 100%;
padding-left: 16px;
@@ -599,9 +265,6 @@ export default {
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 12px;
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
@@ -631,14 +294,8 @@ export default {
}
.card-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.card-list.two-cols {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 10px;
}
@@ -671,31 +328,21 @@ export default {
.card-title {
font-weight: 600;
color: #303133;
flex: 1 1 100%;
line-height: 1.4;
word-break: break-all;
}
.card-header .el-tag {
margin-top: 2px;
}
.card-meta {
display: flex;
flex-direction: column;
gap: 4px;
justify-content: space-between;
font-size: 12px;
}
.card-meta .label {
color: #909399;
margin-right: 4px;
min-width: 54px;
}
.card-meta .value {
color: #303133;
font-weight: 600;
word-break: break-all;
}
.card-footer {
@@ -703,17 +350,7 @@ export default {
color: #606266;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 6px;
flex-wrap: wrap;
align-items: center;
margin-top: 4px;
}
.steel-panel .card-list.two-cols {
grid-template-columns: 1fr;
}
.thickness-panel .card-list {
display: flex;
flex-direction: column;
}
</style>
</style>

View File

@@ -12,9 +12,9 @@
</div>
<el-row v-else :gutter="16">
<el-col :span="14">
<el-form :model="form" label-width="150px" v-loading="loading">
<el-row>
<el-col>
<el-form :model="form" v-loading="loading">
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="开卷机张力">
<el-input v-model="form.porTension" @change="syncModal" />
@@ -48,7 +48,7 @@
</el-col>
</el-row>
<el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="平整机入口张力">
<el-input v-model="form.levelerEntryTension" @change="syncModal" />
@@ -66,7 +66,7 @@
</el-col>
</el-row>
<el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="炉区张力">
<el-input v-model="form.furTension" @change="syncModal" />
@@ -130,11 +130,14 @@ const REQUIRED_FIELDS = ['entryThick', 'yieldPoint', 'steelGrade']
const FIELD_LABEL_MAP = {
entryThick: '入口厚度',
yieldPoint: '屈服',
yieldPoint: '屈服强度',
steelGrade: '钢种'
}
const createDefaultForm = () => ({
// 关键基础字段(用于后端入库/关联;同时不影响推荐计算)
steelGrade: undefined,
// 全线张力
porTension: undefined,
celTension: undefined,
@@ -217,7 +220,10 @@ export default {
},
generateSignature() {
// 只要这些字段变化,就重新推荐
return REQUIRED_FIELDS.map((key) => (this.income && this.income[key]) ?? '').join('|')
return REQUIRED_FIELDS.map((key) => {
const v = this.income && this.income[key]
return v === null || v === undefined ? '' : v
}).join('|')
},
async handleIncomeChange() {
if (!this.canGenerate) {
@@ -234,11 +240,11 @@ export default {
// 评分排序:优先 steelGrade 匹配,其次厚度差、屈服差、最后按更新时间
scoreRow(row, cond) {
const rowSteel = (row.steelGrade ?? row.steel_grade ?? '').toString()
const rowSteel = ((row.steelGrade !== undefined && row.steelGrade !== null) ? row.steelGrade : (row.steel_grade !== undefined && row.steel_grade !== null ? row.steel_grade : '')).toString()
const rowThick = Number(row.thick)
const rowYield = Number(row.yieldStren ?? row.yield_stren)
const rowYield = Number((row.yieldStren !== undefined && row.yieldStren !== null) ? row.yieldStren : row.yield_stren)
const condSteel = (cond.steelGrade ?? '').toString()
const condSteel = ((cond.steelGrade === null || cond.steelGrade === undefined) ? '' : cond.steelGrade).toString()
const condThick = Number(cond.thick)
const condYield = Number(cond.yieldStren)
@@ -280,9 +286,7 @@ export default {
// list 接口支持条件过滤的话会减少数据量(后端已支持三字段)
steelGrade,
thick: entryThick,
yieldStren: yieldPoint,
pageNum: 1,
pageSize: 10
yieldStren: yieldPoint
}
const [allLineRes, levelerRes, straightenerRes, furnaceRes] = await Promise.allSettled([
@@ -352,6 +356,11 @@ export default {
this.syncModal()
},
syncModal() {
// 注意:父组件保存工艺参数时需要基础字段(钢种/厚度/屈服强度)
// 这里将 income 中的基础字段合并进 form再回传给父组件
this.$set(this.form, 'steelGrade', this.income && this.income.steelGrade)
this.$set(this.form, 'thick', this.income && this.income.entryThick)
this.$set(this.form, 'yieldStren', this.income && this.income.yieldPoint)
this.$emit('input', this.form)
}
}

View File

@@ -3,22 +3,22 @@
<el-tab-pane label="全线张力">
<el-descriptions :column="2">
<el-descriptions-item label="开卷机张力">
{{ setupForm.porTension ?? '-' }}
{{ setupForm.porTension == null ? '-' : setupForm.porTension }}
</el-descriptions-item>
<el-descriptions-item label="入口活套张力">
{{ setupForm.celTension ?? '-' }}
{{ setupForm.celTension == null ? '-' : setupForm.celTension }}
</el-descriptions-item>
<el-descriptions-item label="清洗段张力">
{{ setupForm.cleanTension ?? '-' }}
{{ setupForm.cleanTension == null ? '-' : setupForm.cleanTension }}
</el-descriptions-item>
<el-descriptions-item label="钝化段张力">
{{ setupForm.passivationTension ?? '-' }}
{{ setupForm.passivationTension == null ? '-' : setupForm.passivationTension }}
</el-descriptions-item>
<el-descriptions-item label="出口活套张力">
{{ setupForm.cxlTension ?? '-' }}
{{ setupForm.cxlTension == null ? '-' : setupForm.cxlTension }}
</el-descriptions-item>
<el-descriptions-item label="卷取机张力">
{{ setupForm.trTension ?? '-' }}
{{ setupForm.trTension == null ? '-' : setupForm.trTension }}
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
@@ -26,10 +26,10 @@
<el-tab-pane label="平整机张力">
<el-descriptions :column="1">
<el-descriptions-item label="平整机入口张力">
{{ setupForm.levelerEntryTension ?? '-' }}
{{ setupForm.levelerEntryTension == null ? '-' : setupForm.levelerEntryTension }}
</el-descriptions-item>
<el-descriptions-item label="平整机出口张力">
{{ setupForm.levelerExitTension ?? '-' }}
{{ setupForm.levelerExitTension == null ? '-' : setupForm.levelerExitTension }}
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
@@ -37,7 +37,7 @@
<el-tab-pane label="矫直机张力">
<el-descriptions :column="1">
<el-descriptions-item label="矫直机出口张力">
{{ setupForm.straightenerExitTension ?? '-' }}
{{ setupForm.straightenerExitTension == null ? '-' : setupForm.straightenerExitTension }}
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
@@ -45,10 +45,10 @@
<el-tab-pane label="退火炉张力">
<el-descriptions :column="1">
<el-descriptions-item label="炉区张力">
{{ setupForm.furTension ?? '-' }}
{{ setupForm.furTension == null ? '-' : setupForm.furTension }}
</el-descriptions-item>
<el-descriptions-item label="冷却塔张力">
{{ setupForm.towerTension ?? '-' }}
{{ setupForm.towerTension == null ? '-' : setupForm.towerTension }}
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>

View File

@@ -200,11 +200,12 @@
<el-descriptions-item label="计划号">{{ currentRow.planid }}</el-descriptions-item>
<el-descriptions-item label="钢卷号">{{ currentRow.coilid }}</el-descriptions-item>
<el-descriptions-item label="钢种">{{ currentRow.steelGrade }}</el-descriptions-item>
<el-descriptions-item label="屈服">{{ currentRow.yieldPoint }}</el-descriptions-item>
<el-descriptions-item label="屈服强度">{{ currentRow.yieldPoint }}</el-descriptions-item>
<el-descriptions-item label="重量(t)">{{ currentRow.entryWeight }}</el-descriptions-item>
<el-descriptions-item label="厚度(mm)">{{ currentRow.entryThick }}</el-descriptions-item>
<el-descriptions-item label="宽度(mm)">{{ currentRow.entryWidth }}</el-descriptions-item>
<el-descriptions-item label="长度(mm)">{{ currentRow.entryLength }}</el-descriptions-item>
<el-descriptions-item label="内径(mm)">{{ currentRow.entryInnerDiameter }}</el-descriptions-item>
<el-descriptions-item label="外径(mm)">{{ currentRow.entryOuterDiameter }}</el-descriptions-item>
<el-descriptions-item label="延伸率(%)">{{ currentRow.spmElongation }}</el-descriptions-item>
<el-descriptions-item label="SPM轧制力">{{ currentRow.spmRollforce }}</el-descriptions-item>
@@ -233,6 +234,7 @@
append-to-body>
<div class="plan-dialog-body">
<div class="plan-dialog-left">
<l3-pickup-recommend-panel @apply-recommendation="handleApplyL3PickupRecommendation" />
<el-form :model="form" ref="form" label-width="90px" :rules="rules" size="mini" class="plan-base-form">
<el-row :gutter="16">
<el-col :span="12">
@@ -245,11 +247,16 @@
<el-input v-model="form.coilid" placeholder="请输入钢卷号" maxLength="32"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入场钢卷号" prop="enterCoilNo">
<el-input v-model="form.enterCoilNo" placeholder="请输入入场钢卷号" maxLength="255"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="钢种" prop="steelGrade">
<el-select v-model="form.steelGrade" placeholder="请选择钢种">
<el-option v-for="item in steelGradeList" :key="item.gradeid" :label="item.name"
:value="item.gradeid"></el-option>
:value="item.name"></el-option>
</el-select>
</el-form-item>
</el-col>
@@ -264,8 +271,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="屈服" prop="yieldPoint">
<el-input v-model="form.yieldPoint" placeholder="请输入屈服" type="number" step="0.01" min="0"></el-input>
<el-form-item label="屈服强度" prop="yieldPoint">
<el-input v-model="form.yieldPoint" placeholder="请输入屈服强度" type="number" step="0.01" min="0"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -283,6 +290,12 @@
<el-input v-model="form.entryLength" placeholder="请输入入口长度" type="number" step="1" min="0"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="内径(mm)" prop="entryInnerDiameter">
<el-input v-model="form.entryInnerDiameter" placeholder="请输入入口内径" type="number" step="1"
min="0"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="外径(mm)" prop="entryOuterDiameter">
<el-input v-model="form.entryOuterDiameter" placeholder="请输入入口外径" type="number" step="1"
@@ -331,10 +344,13 @@
<div class="plan-dialog-right">
<process-recommend-panel v-if="spmFlag" :recommendation="processRecommendation"
@apply-one="handleProcessApplyOne" @apply-all="handleProcessApplyAll" />
<plan-recommend-panel v-else :steel-grade="form.steelGrade" :entry-thick="form.entryThick"
:entry-width="form.entryWidth" :spm-rollforce="form.spmRollforce" :steel-grade-list="steelGradeList"
@apply-steel-grade="handleApplySteelGrade" @apply-thickness="handleApplyThickness"
@apply-width="handleApplyWidth" @apply-rollforce="handleApplyRollforce" />
<plan-recommend-panel
v-else
:steel-grade="form.steelGrade"
:entry-thick="form.entryThick"
:steel-grade-list="steelGradeList"
@apply-recommendation="handleApplyRecommendation"
/>
</div>
</div>
<!-- 弹窗底部按钮 -->
@@ -356,6 +372,7 @@ import SetupForm from './components/setupForm.vue'
import SetupPane from './components/setupPane.vue'
import PlanRecommendPanel from './components/PlanRecommendPanel.vue'
import ProcessRecommendPanel from './components/ProcessRecommendPanel.vue'
import L3PickupRecommendPanel from './components/L3PickupRecommendPanel.vue'
// 标准日期格式化方法(优化时间处理逻辑,适配接口日期格式)
function parseTime(time, format = "{yyyy}-{mm}-{dd} {hh}:{ii}:{ss}") {
@@ -399,7 +416,8 @@ export default {
SetupForm,
SetupPane,
PlanRecommendPanel,
ProcessRecommendPanel
ProcessRecommendPanel,
L3PickupRecommendPanel
},
data() {
return {
@@ -432,6 +450,7 @@ export default {
id: null, // 主键ID组件内部用提交时后端自动处理
seqid: null, // 顺序号number接口要求
coilid: "", // 钢卷号string必填
enterCoilNo: "", // 入场钢卷号string
unitCode: "", // 机组号string接口要求
dummyCoilFlag: 0, // 虚卷标识number默认0非虚卷
planid: "", // 计划IDstring接口要求
@@ -602,9 +621,11 @@ export default {
this.steelGradeList = res.data
})
},
handleApplySteelGrade(payload) {
handleApplyRecommendation(payload) {
if (!payload) return
if (payload.steelGrade) {
// PlanRecommendPanel 传回 steelGrade 可能是钢种名,也可能是 gradeid
if (payload.steelGrade !== undefined && payload.steelGrade !== null && payload.steelGrade !== '') {
const gid = String(payload.steelGrade)
const match = this.steelGradeList.find(item =>
String(item.gradeid) === gid ||
@@ -613,31 +634,24 @@ export default {
String(item.gradeName || '') === gid
)
// 下拉选择器以 gradeid 为 value确保回填 id 以显示名称
this.form.steelGrade = match ? match.gradeid : payload.steelGrade
this.form.steelGrade = match ? (match.name || match.gradeName || payload.steelGrade) : payload.steelGrade
}
if (payload.entryThick !== undefined && payload.entryThick !== null && payload.entryThick !== '') {
this.form.entryThick = payload.entryThick
}
if (payload.yieldPoint !== undefined && payload.yieldPoint !== null && payload.yieldPoint !== '') {
this.form.yieldPoint = payload.yieldPoint
}
if (payload.entryThick) this.form.entryThick = payload.entryThick
if (payload.entryWidth) this.form.entryWidth = payload.entryWidth
if (payload.yieldPoint) this.form.yieldPoint = payload.yieldPoint
if (payload.spmElongation) this.form.spmElongation = payload.spmElongation
if (payload.spmRollforce) this.form.spmRollforce = payload.spmRollforce
},
handleApplyThickness(payload) {
handleApplyL3PickupRecommendation(payload) {
if (!payload) return
if (payload.entryThick) this.form.entryThick = payload.entryThick
if (payload.yieldPoint) this.form.yieldPoint = payload.yieldPoint
},
handleApplyWidth(payload) {
if (!payload) return
if (payload.entryWidth) this.form.entryWidth = payload.entryWidth
if (payload.spmRollforce) this.form.spmRollforce = payload.spmRollforce
},
handleApplyRollforce(payload) {
if (!payload) return
if (payload.spmRollforce) this.form.spmRollforce = payload.spmRollforce
if (payload.entryThick) this.form.entryThick = payload.entryThick
if (payload.yieldPoint) this.form.yieldPoint = payload.yieldPoint
if (payload.spmElongation) this.form.spmElongation = payload.spmElongation
if (payload.steelGrade) this.form.steelGrade = payload.steelGrade
if (payload.coilid !== undefined) {
this.form.coilid = payload.coilid || ''
}
if (payload.enterCoilNo !== undefined) {
this.form.enterCoilNo = payload.enterCoilNo || ''
}
},
handleProcessRecommendation(payload) {
this.processRecommendation = payload
@@ -810,6 +824,7 @@ export default {
status: "NEW", // 新增计划默认状态为 新建
planid: "",
planType: "",
enterCoilNo: "",
originCoilid: "",
yieldPoint: null,
zincCoatingThickness: null,
@@ -914,6 +929,14 @@ export default {
this.dialogTitle = "编辑计划"; // 设置弹窗标题
// 深拷贝行数据(避免修改原表格数据)
this.form = JSON.parse(JSON.stringify(row));
// 确保回显时form.steelGrade 始终是钢种名称,以便 el-select 正确显示
if (this.form.steelGrade) {
const gradeId = String(this.form.steelGrade);
const match = this.steelGradeList.find(item => String(item.gradeid) === gradeId);
if (match) {
this.form.steelGrade = match.name;
}
}
this.spmFlag = false
// 1. ID转字符串避免精度丢失
@@ -1057,11 +1080,19 @@ export default {
},
generateSpmParams() {
addSetup({
// 注意:工艺参数入库需要 steelGrade/thick/yieldStren 等基础字段。
// 这里 thick/yieldStren 后端字段名对应 pdi_setup.thick / pdi_setup.yield_stren
// 前端基础信息里分别是 entryThick / yieldPoint
let data = {
coilid: this.form.coilid,
planid: this.form.planid,
steelGrade: this.form.steelGrade,
thick: this.form.entryThick,
yieldStren: this.form.yieldPoint,
...this.setupForm
}).then(res => {
}
console.log(data)
addSetup(data).then(res => {
this.$message.success("工艺参数创建成功");
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@
<el-row :gutter="20">
<el-col
v-for="setup in setups"
:key="setup.ID"
:key="setup.id || setup.ID || setup.planid || setup.coilid"
:xs="24"
:sm="12"
:md="8"
@@ -85,22 +85,9 @@
</div>
<div class="card-subtitle">
<span>入口厚度: {{ setup.entryThick || '-' }}</span>
<span>入口宽: {{ setup.entryWidth || '-' }}</span>
</div>
<div class="card-subtitle">
<span>入口重量: {{ setup.entryWeight || '-' }}</span>
<span>入口长度: {{ setup.entryLength || '-' }}</span>
</div>
<div class="card-subtitle">
<span>拉伸机延伸率: {{ setup.tlElong || '-' }}</span>
<span>轧机轧制力: {{ setup.tmRollforce || '-' }}</span>
</div>
<div class="card-subtitle">
<span>轧机弯辊力: {{ setup.tmBendforce || '-' }}</span>
<span>钢种: {{ setup.steelGrade || '-' }}</span>
<span>: {{ setup.thick || '-' }}</span>
<span>屈服强度: {{ setup.yieldStren || '-' }}</span>
<span v-if="setup.updateTime">更新时间: {{ formatTime(setup.updateTime) }}</span>
</div>
</div>
@@ -166,56 +153,47 @@ import { listPlan } from '@/api/l2/plan'
// 传动字段定义(中文界面,贴合工业场景)
const DRIVE_FIELDS = [
// 全线张力(与 pdi_setup / PdiSetups 字段对齐)
{ key: 'porTension', label: '开卷机张力' },
{ key: 'celTension', label: '入口活套张力' },
{ key: 'cleanTension', label: '清洗段张力' },
{ key: 'furTension', label: '炉区张力' },
{ key: 'towerTension', label: '冷却塔张力' },
{ key: 'tmNoneTension', label: '轧机无张力' },
{ key: 'tmEntryTension', label: '轧机入口张力' },
{ key: 'tmExitTension', label: '轧机出口张力' },
{ key: 'tlNoneTension', label: '拉伸机无张力' },
{ key: 'tlExitTension', label: '拉伸机出口张力' },
{ key: 'coatTension', label: '后处理段张力' },
{ key: 'passivationTension', label: '钝化段张力' },
{ key: 'cxlTension', label: '出口活套张力' },
{ key: 'trTension', label: '卷取机张力' },
{ key: 'tlElong', label: '拉伸机延伸率' },
{ key: 'tlLvlMesh1', label: '拉伸机矫直辊间隙1' },
{ key: 'tlLvlMesh2', label: '拉伸机矫直辊间隙2' },
{ key: 'tlAcbMesh', label: '拉伸机防侧弯间隙' },
// 平整机
{ key: 'levelerEntryTension', label: '平整机入口张力' },
{ key: 'levelerExitTension', label: '平整机出口张力' },
{ key: 'tmBendforce', label: '轧机弯辊力' },
{ key: 'tmAcrMesh', label: '轧机防皱辊间隙' },
{ key: 'tmBrMesh', label: '轧机防颤辊间隙' },
{ key: 'tmRollforce', label: '轧机轧制力' }
// 矫直机
{ key: 'straightenerExitTension', label: '矫直机出口张力' },
// 退火炉
{ key: 'furTension', label: '炉区张力' },
{ key: 'towerTension', label: '冷却塔张力' }
]
// OPC地址映射保持原有配置不影响功能
const DRIVE_ADDRESS = {
// 全线张力
porTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionPorBR1',
celTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR3',
cleanTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR1BR2',
furTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur1',
towerTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur2',
tmNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5BR6',
tmEntryTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5TM',
tmExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTMBR6',
tlNoneTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR6BR7',
tlExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTLBR7',
coatTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR7BR8',
// 原系统里 passivation 对应“BR7-BR8”段原 coatTension 地址),这里沿用该地址
passivationTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR7BR8',
cxlTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR8BR9',
trTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR9TR',
tlElong: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TLElongation',
tlLvlMesh1: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh1',
tlLvlMesh2: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.LevelingMesh2',
tlAcbMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.AntiCrossBowUnitMesh',
// 平整机(原 tm* 地址沿用)
levelerEntryTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionBR5TM',
levelerExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTMBR6',
tmBendforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMBendforce',
tmAcrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.ACRMesh',
tmBrMesh: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.BRMesh',
tmRollforce: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.TMRollforce'
// 矫直机(原 tl* 地址沿用)
straightenerExitTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionTLBR7',
// 退火炉
furTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur1',
towerTension: 'ns=2;s=ProcessCGL.PLCLine.L2Setup.tensionFur2'
}
export default {
@@ -295,13 +273,14 @@ export default {
this.setups = setupList.map(s => {
const params = {}
this.driveFields.forEach(f => {
// s 是后端 listSetup 返回的 PdiSetups字段名与 driveFields.key 对齐)
const fromSetup = s ? s[f.key] : undefined
const fromLast = this.lastSuccess?.values?.[f.key]
// 优先级:当前配置值 > 上次成功值 > 空字符串
if (fromSetup !== undefined && fromSetup !== null && String(fromSetup) !== '') {
params[f.key] = String(fromSetup)
} else if (fromLast !== undefined && fromLast !== null) {
} else if (fromLast !== undefined && fromLast !== null && String(fromLast) !== '') {
params[f.key] = String(fromLast)
} else {
params[f.key] = ''

View File

@@ -46,45 +46,60 @@
</div>
</div>
<!-- 按模板渲染可编辑表单 -->
<el-form :model="form" label-position="top" size="mini">
<div class="group-list">
<div v-for="group in groupedItems" :key="group.groupKey" class="group-section">
<div class="group-header">
<span class="group-title">{{ group.groupTitle }}</span>
<span class="group-count">({{ group.items.length }} )</span>
</div>
<!-- 表格形式展示/编辑无下拉分组直接按组+序号排序展示 -->
<el-form :model="form" size="mini">
<el-table
:data="tableItems"
border
stripe
size="mini"
row-key="__rowKey"
height="calc(100vh - 260px)"
:cell-class-name="cellClassName"
class="param-table"
>
<el-table-column prop="groupTitle" label="分组" min-width="90" />
<el-table-column prop="itemNo" label="序号" width="70" />
<el-table-column prop="labelEn" label="参数" min-width="160" show-overflow-tooltip />
<el-table-column prop="paramCode" label="编码" min-width="140" show-overflow-tooltip />
<!-- 每行三个输入框 -->
<el-row :gutter="20">
<el-col :span="8" v-for="item in group.items" :key="item.templateItemId || item.paramCode">
<el-form-item :label="item.labelEn">
<el-input v-model="form[item.paramCode]" :placeholder="getPlaceholder(item)"
:class="{ 'is-changed': isChangedFromLast(item) }" />
<el-table-column label="当前值" min-width="180">
<template slot-scope="{ row }">
<el-input
v-model="form[row.paramCode]"
:placeholder="getPlaceholder(row)"
size="mini"
/>
</template>
</el-table-column>
<!-- 辅助信息上次/默认值常驻不会像 placeholder 一样消失 -->
<div class="field-hint">
<span v-if="getLastValue(item) !== undefined" class="hint-item">
上次成功<b>{{ getLastValue(item) }}</b>
</span>
<span v-if="getDefaultValue(item) !== undefined" class="hint-item">
默认值<b>{{ getDefaultValue(item) }}</b>
</span>
<span v-if="isChangedFromLast(item)" class="hint-item changed">
已修改
</span>
</div>
<el-table-column label="上次成功" min-width="120">
<template slot-scope="{ row }">
<span v-if="getLastValue(row) !== undefined">{{ getLastValue(row) }}</span>
<span v-else class="muted">-</span>
</template>
</el-table-column>
<!-- 编辑点位 -->
<div v-if="editTemplate" class="addr-inline">
<span class="addr-label">点位地址</span>
<el-input v-model="item.address" size="mini" placeholder="ns=2;s=..." />
</div>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
<el-table-column label="默认值" min-width="120">
<template slot-scope="{ row }">
<span v-if="getDefaultValue(row) !== undefined">{{ getDefaultValue(row) }}</span>
<span v-else class="muted">-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="{ row }">
<el-tag v-if="isChangedFromLast(row)" type="warning" size="mini">已修改</el-tag>
<span v-else class="muted">-</span>
</template>
</el-table-column>
<el-table-column v-if="editTemplate" label="点位地址" min-width="220">
<template slot-scope="{ row }">
<el-input v-model="row.address" size="mini" placeholder="ns=2;s=..." />
</template>
</el-table-column>
</el-table>
</el-form>
<div v-if="!loading && templateItems.length === 0" class="empty-data">
@@ -139,29 +154,34 @@ export default {
if (String(en) === '0') return false
return true
})
.sort((a, b) => (a.itemNo || 0) - (b.itemNo || 0))
},
// 按 paramCode 前缀分组(如 NOF1 / NOF2 / RTF1 / SF ...
// 表格数据(扁平化所有分组,添加组标题
tableItems() {
return this.templateItems
.map(item => ({
...item,
groupKey: this.getGroupKey(item),
groupTitle: this.getGroupTitle(this.getGroupKey(item)),
__rowKey: `${item.paramCode}_${item.templateItemId || ''}`
}))
.sort((a, b) => {
// 先按组名排序
const groupCompare = String(a.groupKey).localeCompare(
String(b.groupKey),
undefined,
{ numeric: true }
)
if (groupCompare !== 0) return groupCompare
// 同组内按 itemNo 排序
return (a.itemNo || 0) - (b.itemNo || 0)
})
},
// 兼容旧代码,保留但不再使用
groupedItems() {
const groupsMap = new Map()
const items = this.templateItems
items.forEach(it => {
const key = this.getGroupKey(it)
if (!groupsMap.has(key)) groupsMap.set(key, [])
groupsMap.get(key).push(it)
})
// Map -> Array并按组名排序NOF1, NOF2... 这种会自然排序更好)
const groups = Array.from(groupsMap.entries()).map(([groupKey, groupItems]) => ({
groupKey,
groupTitle: this.getGroupTitle(groupKey),
items: groupItems
}))
groups.sort((a, b) => String(a.groupKey).localeCompare(String(b.groupKey), undefined, { numeric: true }))
return groups
return []
}
},
created() {
@@ -169,6 +189,13 @@ export default {
this.reload()
},
methods: {
cellClassName({ row, column }) {
// 高亮:当前值与上次成功不一致时,给“当前值”这一列加底色
if (column && column.label === '当前值' && this.isChangedFromLast(row)) {
return 'cell-changed'
}
return ''
},
pickItemFields(it) {
if (!it) return {}
// 仅挑后端支持保存的字段,避免把多余字段/响应结构带回去
@@ -513,58 +540,21 @@ export default {
font-size: 12px;
}
.group-list {
/* 表格辅助样式 */
.param-table {
margin-top: 8px;
}
.group-section {
padding: 10px 0 6px;
border-top: 1px solid #ebeef5;
.muted {
color: #c0c4cc;
}
.group-section:first-child {
border-top: none;
padding-top: 0;
}
.group-header {
display: flex;
align-items: baseline;
gap: 8px;
margin: 4px 0 10px;
}
.group-title {
font-weight: 600;
}
.group-count {
color: #909399;
font-size: 12px;
margin-left: 6px;
}
/* 字段提示信息 */
.field-hint {
font-size: 12px;
line-height: 1.4;
margin-top: 4px;
color: #909399;
}
.hint-item {
display: inline-block;
margin-right: 10px;
}
.hint-item.changed {
color: #e6a23c;
font-weight: 500;
}
/* 修改过的输入框高亮 */
:deep(.el-input.is-changed .el-input__inner) {
border-color: #e6a23c;
/* 修改过的单元格高亮(当前值列) */
:deep(.el-table .cell-changed) {
background-color: #fdf6ec;
}
:deep(.el-table .cell-changed .el-input__inner) {
border-color: #e6a23c;
}
</style>

View File

@@ -142,35 +142,35 @@ export default {
// 字段schema表格列/表单)
schemaAllLine: [
{ prop: 'steelGrade', label: '钢种', required: true },
{ prop: 'thick', label: '厚度', required: true },
{ prop: 'yieldStren', label: '屈服强度', required: true },
{ prop: 'value1', label: '开卷机张力' },
{ prop: 'value2', label: '入口活套张力' },
{ prop: 'value3', label: '清洗段张力' },
{ prop: 'value4', label: '钝化段张力' },
{ prop: 'value5', label: '出口活套张力' },
{ prop: 'value6', label: '卷取机张力' }
{ prop: 'steelGrade', label: '钢种', required: true, minWidth: 65 },
{ prop: 'thick', label: '厚度', required: true, minWidth: 65 },
{ prop: 'yieldStren', label: '屈服强度', required: true, minWidth: 90 },
{ prop: 'value1', label: '开卷机张力', minWidth: 120 },
{ prop: 'value2', label: '入口活套张力', minWidth: 120 },
{ prop: 'value3', label: '清洗段张力', minWidth: 120 },
{ prop: 'value4', label: '钝化段张力', minWidth: 120 },
{ prop: 'value5', label: '出口活套张力', minWidth: 120 },
{ prop: 'value6', label: '卷取机张力', minWidth: 120 }
],
schemaLeveler: [
{ prop: 'steelGrade', label: '钢种', required: true },
{ prop: 'thick', label: '厚度', required: true },
{ prop: 'yieldStren', label: '屈服强度', required: true },
{ prop: 'value1', label: '平整机入口张力' },
{ prop: 'value2', label: '平整机出口张力' }
{ prop: 'steelGrade', label: '钢种', required: true, minWidth: 65 },
{ prop: 'thick', label: '厚度', required: true, minWidth: 65 },
{ prop: 'yieldStren', label: '屈服强度', required: true, minWidth: 90 },
{ prop: 'value1', label: '平整机入口张力', minWidth: 120 },
{ prop: 'value2', label: '平整机出口张力', minWidth: 120 }
],
schemaStraightener: [
{ prop: 'steelGrade', label: '钢种', required: true },
{ prop: 'thick', label: '厚度', required: true },
{ prop: 'yieldStren', label: '屈服强度', required: true },
{ prop: 'value1', label: '矫直机出口张力' }
{ prop: 'steelGrade', label: '钢种', required: true, minWidth: 65 },
{ prop: 'thick', label: '厚度', required: true, minWidth: 65 },
{ prop: 'yieldStren', label: '屈服强度', required: true, minWidth: 90 },
{ prop: 'value1', label: '矫直机出口张力', minWidth: 120 }
],
schemaAnnealingFurnace: [
{ prop: 'steelGrade', label: '钢种', required: true },
{ prop: 'thick', label: '厚度', required: true },
{ prop: 'yieldStren', label: '屈服强度', required: true },
{ prop: 'value1', label: '炉区张力' },
{ prop: 'value2', label: '冷却塔张力' }
{ prop: 'steelGrade', label: '钢种', required: true, minWidth: 65 },
{ prop: 'thick', label: '厚度', required: true, minWidth: 65 },
{ prop: 'yieldStren', label: '屈服强度', required: true, minWidth: 90 },
{ prop: 'value1', label: '炉区张力', minWidth: 120 },
{ prop: 'value2', label: '冷却塔张力', minWidth: 120 }
]
}
},

View File

@@ -1,14 +1,25 @@
<template>
<div class="tension-table-container">
<div class="table-body">
<!-- 查询区域 -->
<el-form :inline="true" size="mini" class="query-form" @submit.native.prevent>
<el-form-item v-for="k in acceptKeys" :key="k" :label="getLabel(k)">
<el-input
v-if="k !== 'steelGrade'"
v-model="queryParams[k]"
clearable
:placeholder="`请输入${getLabel(k)}`"
@keyup.enter.native="handleQuery"
/>
<el-select
v-else
v-model="queryParams[k]"
clearable
:placeholder="`请选择${getLabel(k)}`"
@keyup.enter.native="handleQuery"
>
<el-option v-for="item in steelGradeList" :key="item.gradeid" :label="item.name" :value="item.name" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
@@ -26,19 +37,17 @@
</div>
<!-- 表格 -->
<el-table v-loading="loading" :data="list" border stripe height="calc(100% - 148px)">
<el-table-column type="index" label="#" width="50" />
<el-table v-loading="loading" :data="list" border stripe height="100%">
<el-table-column
v-for="col in schema"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:min-width="col.minWidth || 120"
:min-width="col.minWidth || 85"
show-overflow-tooltip
/>
<el-table-column label="操作" width="180" fixed="right">
<el-table-column label="操作" width="130">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
@@ -46,6 +55,8 @@
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<pagination
v-show="total > 0"
@@ -61,7 +72,10 @@
<el-row :gutter="12">
<el-col :span="12" v-for="col in schema" :key="'f-' + col.prop">
<el-form-item :label="col.label" :prop="col.prop">
<el-input v-model="form[col.prop]" :disabled="isEdit && acceptKeys.includes(col.prop)" />
<el-input v-if="col.prop !== 'steelGrade'" v-model="form[col.prop]" :disabled="isEdit && acceptKeys.includes(col.prop)" />
<el-select v-else v-model="form[col.prop]" :disabled="isEdit && acceptKeys.includes(col.prop)" placeholder="请选择钢种">
<el-option v-for="item in steelGradeList" :key="item.gradeid" :label="item.name" :value="item.name" />
</el-select>
</el-form-item>
</el-col>
</el-row>
@@ -75,6 +89,8 @@
</template>
<script>
import { getSteelGradeList, addSteelGrade, updateSteelGrade, deleteSteelGrade, getSteelGradeInfo } from '@/api/l2/steelGrade'
export default {
name: 'TensionTable',
props: {
@@ -108,14 +124,21 @@ export default {
isEdit: false,
form: fm,
rules: {},
submitLoading: false
submitLoading: false,
steelGradeList: []
}
},
created() {
this.buildRules()
this.getList()
this.fetchSteelGradeList()
},
methods: {
fetchSteelGradeList() {
getSteelGradeList().then(res => {
this.steelGradeList = res.rows || res.data || []
})
},
getLabel(prop) {
return (this.schema.find(s => s.prop === prop) || {}).label || prop
},
@@ -217,6 +240,22 @@ export default {
display: flex;
flex-direction: column;
}
.table-body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
/* 让 el-table 真正占满剩余高度,避免出现大量空白行/操作列重复显示 */
.table-body ::v-deep .el-table {
flex: 1;
}
.table-body ::v-deep .el-table__body-wrapper {
flex: 1;
}
.query-form {
padding: 10px 10px 0;
border: 1px solid #ebeef5;

View File

@@ -74,29 +74,29 @@
<div class="param-row">
<div class="param-item">
<span class="param-label">开卷机张力:</span>
<span class="param-value">{{ currentRow.porTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.porTension == null ? '-' : currentRow.porTension }}</span>
</div>
<div class="param-item">
<span class="param-label">入口活套张力:</span>
<span class="param-value">{{ currentRow.celTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.celTension == null ? '-' : currentRow.celTension }}</span>
</div>
<div class="param-item">
<span class="param-label">清洗段张力:</span>
<span class="param-value">{{ currentRow.cleanTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.cleanTension == null ? '-' : currentRow.cleanTension }}</span>
</div>
<div class="param-item">
<span class="param-label">钝化段张力:</span>
<span class="param-value">{{ currentRow.passivationTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.passivationTension == null ? '-' : currentRow.passivationTension }}</span>
</div>
</div>
<div class="param-row">
<div class="param-item">
<span class="param-label">出口活套张力:</span>
<span class="param-value">{{ currentRow.cxlTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.cxlTension == null ? '-' : currentRow.cxlTension }}</span>
</div>
<div class="param-item">
<span class="param-label">卷取机张力:</span>
<span class="param-value">{{ currentRow.trTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.trTension == null ? '-' : currentRow.trTension }}</span>
</div>
<div class="param-item"></div>
<div class="param-item"></div>
@@ -109,11 +109,11 @@
<div class="param-row">
<div class="param-item">
<span class="param-label">平整机入口张力:</span>
<span class="param-value">{{ currentRow.levelerEntryTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.levelerEntryTension == null ? '-' : currentRow.levelerEntryTension }}</span>
</div>
<div class="param-item">
<span class="param-label">平整机出口张力:</span>
<span class="param-value">{{ currentRow.levelerExitTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.levelerExitTension == null ? '-' : currentRow.levelerExitTension }}</span>
</div>
<div class="param-item"></div>
<div class="param-item"></div>
@@ -126,7 +126,7 @@
<div class="param-row">
<div class="param-item">
<span class="param-label">矫直机出口张力:</span>
<span class="param-value">{{ currentRow.straightenerExitTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.straightenerExitTension == null ? '-' : currentRow.straightenerExitTension }}</span>
</div>
<div class="param-item"></div>
<div class="param-item"></div>
@@ -140,11 +140,11 @@
<div class="param-row">
<div class="param-item">
<span class="param-label">炉区张力:</span>
<span class="param-value">{{ currentRow.furTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.furTension == null ? '-' : currentRow.furTension }}</span>
</div>
<div class="param-item">
<span class="param-label">冷却塔张力:</span>
<span class="param-value">{{ currentRow.towerTension ?? '-' }}</span>
<span class="param-value">{{ currentRow.towerTension == null ? '-' : currentRow.towerTension }}</span>
</div>
<div class="param-item"></div>
<div class="param-item"></div>
@@ -223,25 +223,12 @@ export default {
cleanTension: null,
furTension: null,
towerTension: null,
tmNoneTension: null,
tmEntryTension: null,
tmExitTension: null,
tmRollforce: null,
tmBendforce: null,
tmAcrMesh: null,
tmBrMesh: null,
tlNoneTension: null,
tlExitTension: null,
tlElong: null,
tlLvlMesh1: null,
tlLvlMesh2: null,
tlAcbMesh: null,
coatTension: null,
// 已废弃的旧字段
cxlTension: null,
trTension: null,
createTime: null,
updateTime: null,
TYPE: null
// TYPE: null旧字段
},
// 表单参数
form: {},
@@ -281,25 +268,12 @@ export default {
cleanTension: null,
furTension: null,
towerTension: null,
tmNoneTension: null,
tmEntryTension: null,
tmExitTension: null,
tmRollforce: null,
tmBendforce: null,
tmAcrMesh: null,
tmBrMesh: null,
tlNoneTension: null,
tlExitTension: null,
tlElong: null,
tlLvlMesh1: null,
tlLvlMesh2: null,
tlAcbMesh: null,
coatTension: null,
// 已废弃的旧字段
cxlTension: null,
trTension: null,
createTime: null,
updateTime: null,
TYPE: null
// TYPE: null旧字段
};
this.resetForm("form");
},

View File

@@ -207,12 +207,23 @@ import { listStoppage, updateStoppage, deleteStoppage } from '@/api/l2/stop'; //
export default {
name: 'StoppageManagement',
data() {
// 计算默认时间范围近一个月从本月1号到今天
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1; // getMonth() 返回 0-11需要加1
const day = now.getDate();
// 开始时间本月1号
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
// 结束时间:今天
const endDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
return {
// 查询表单数据
queryForm: {
// 只保留年月日YYYY-mm-dd格式
startDate: '2023-08-13',
endDate: '2025-08-20'
startDate: startDate,
endDate: endDate
},
// 表格数据
tableData: [],
@@ -264,7 +275,17 @@ export default {
// 获取停机记录列表
getStoppageList() {
this.tableLoading = true;
listStoppage(this.queryForm)
// 构建查询参数结束时间设置为当天的23:59:59
const queryParams = {
...this.queryForm
};
if (queryParams.endDate) {
queryParams.endDate = queryParams.endDate + ' 23:59:59';
}
if (queryParams.startDate) {
queryParams.startDate = queryParams.startDate + ' 00:00:00';
}
listStoppage(queryParams)
.then(response => {
this.tableLoading = false;
this.btnLoading = false;

View File

@@ -31,7 +31,23 @@ export default {
return {
prevDriveData: {}, // 存储上一次的驱动数据,用于对比变化
blinkKeyMap: {}, // 每个key独立的闪烁状态对象形式{ key1: true, key2: false }
timerMap: {} // 每个key独立的定时器缓存用于清除旧定时器
timerMap: {}, // 每个key独立的定时器缓存用于清除旧定时器
labelMap: {
jcf1FurnaceTemperatureActual: 'jcf1炉温(℃)',
jcf2FurnaceTemperatureActual: 'jcf2炉温(℃)',
lbzFurnaceTemperatureActual: 'lbz炉温(℃)',
lthFurnaceTemperatureActual: 'lth炉温(℃)',
nof1FurnaceTemperatureActual: 'nof1炉温(℃)',
nof2FurnaceTemperatureActual: 'nof2炉温(℃)',
nof3FurnaceTemperatureActual: 'nof3炉温(℃)',
nof4FurnaceTemperatureActual: 'nof4炉温(℃)',
nof5FurnaceTemperatureActual: 'nof5炉温(℃)',
phFurnaceTemperatureActual: 'ph炉温(℃)',
rtf1FurnaceTemperatureActual: 'rtf1炉温(℃)',
rtf2FurnaceTemperatureActual: 'rtf2炉温(℃)',
sfFurnaceTemperatureActual: 'sf炉温(℃)',
tdsFurnaceTemperatureActual: 'tds炉温(℃)'
}
}
},
// 筛选包含Actual不区分大小写的键值对
@@ -92,10 +108,7 @@ export default {
// 格式化标签名(将驼峰命名转为中文式分段,提升可读性)
formatLabel(key) {
if (!key) return '';
// 驼峰命名转空格分隔
const result = key.replace(/([A-Z])/g, ' $1');
// 首字母大写
return result.charAt(0).toUpperCase() + result.slice(1);
return this.labelMap[key] || key;
},
// 格式化值数字类型保留4位小数提升展示美观度
formatValue(value) {

View File

@@ -907,7 +907,7 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
return this.buildSectionMetrics('COAT', [
'stripSpeedTmExit', 'avrCoatingWeightTop', 'avrCoatingWeightBottom',
'tmElongation', 'tlElongation', 'tensionBr6toBr7Br8', 'tensionBr8Tm',
'tensionTmBr9', 'tlElongation'])
'tensionTmBr9'])
},
exitSectionMetrics() {
return this.buildSectionMetrics('EXIT', ['speedExitSection', 'coilLength', 'cxlLength', 'cxlCapacity', 'tensionBr9Tr'])
@@ -1310,14 +1310,14 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
title: '甩尾操作提示',
type: 'warning',
duration: 5000,
needAlert: true
needAlert: false
},
THROW_TAIL: {
icon: '⚠️',
title: '甩尾操作提示',
type: 'warning',
duration: 5000,
needAlert: true
needAlert: false
},
ALL_RETURN: {
icon: '↩️',

7
pnpm-lock.yaml generated
View File

@@ -83,6 +83,9 @@ importers:
splitpanes:
specifier: 2.4.1
version: 2.4.1
three:
specifier: ^0.158.0
version: 0.158.0
vue:
specifier: 2.6.12
version: 2.6.12
@@ -14235,6 +14238,10 @@ packages:
webpack: 4.47.0
dev: true
/three@0.158.0:
resolution: {integrity: sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ==}
dev: false
/three@0.180.0:
resolution: {integrity: sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==}
dev: false