Compare commits

...

42 Commits

Author SHA1 Message Date
fb5ee8356b feat(hand-factory): release v1.3.34
- 扫描钢卷功能支持维护异常信息
- 隐藏「设备巡检」菜单入口
- 版本号升级至 1.3.34
2026-06-09 09:04:52 +08:00
a24e369ebb feat: 重构扫码与巡检模块,优化页面路由
1. 新增钢卷详情、扫码页面与设备巡检组件
2. 调整首页跳转逻辑至扫码页面,更新tabBar配置
3. 隐藏旧的分条、其他操作模块,替换钢卷详情弹窗为页面跳转
4. 复用巡检组件到扫码页面与设备巡检页
2026-06-06 14:57:23 +08:00
c17b60dbe0 feat(mine+defect): 新增钢卷异常管理页面及相关功能
1. 个人中心页面新增钢卷异常入口
2. 实现钢卷异常列表页:支持按状态筛选、缺陷代码/程度统计
3. 新增异常新增/编辑弹窗,完善表单校验和字段展示
4. 优化列表项UI展示,新增主缺陷标识和元信息展示
2026-06-05 14:50:47 +08:00
ef873f242f Merge branch 'master' of http://49.232.154.205:10100/DeXun/klp-mono 2026-06-05 14:25:28 +08:00
a8ca4353e7 feat: 新增钢卷缺陷维护功能模块
1. 新增缺陷维护页面路由与入口
2. 实现钢卷缺陷的增删改查及图片上传功能
3. 添加缺陷级别统计与分页展示
4. 集成钢卷查询与缺陷管理API
2026-06-05 14:23:49 +08:00
3102b53b3f chore: 版本升级至1.3.33并替换更新钢卷接口
1. 将配置文件、版本文件和静态默认版本号从1.3.31更新至1.3.33
2. 将编辑和打包页面的updateMaterialCoil接口替换为updateMaterialCoilSimple
2026-05-26 15:53:28 +08:00
f31ffa45dd feat: 新增设备巡检功能模块
1. 新增设备巡检页面路由与入口
2. 添加OSS对象存储与设备巡检相关API
3. 调整首页tab权限配置项数量
4. 在我的页面新增设备巡检跳转入口
2026-05-25 14:06:38 +08:00
王文昊
5890b1d114 refactor(todo): 替换滑动触发加载为点击加载更多
重构了待办页面的加载更多逻辑:移除滚动监听和自动加载逻辑,改为点击按钮触发加载;优化了加载状态提示和UI样式,简化了滚动视图高度计算逻辑,移除了多余的状态变量和防抖冷却逻辑。
2026-05-25 10:10:53 +08:00
王文昊
07848446aa feat(todo页面): 优化待办列表滚动加载体验
1. 新增scroll-view高度动态计算适配不同设备
2. 将原scrolltolower触发改为滑动手势触发加载更多
3. 添加加载冷却期防止重复触发
4. 新增底部滑动提示动画
5. 重构加载更多逻辑并添加调试日志
2026-05-23 17:49:12 +08:00
王文昊
eeff149c17 refactor(todo page): 统一数据字段映射与key绑定
1.  将列表循环的key从item.actionId改为item.coilId,确保唯一标识正确
2.  新增mapDataFields方法统一处理后端字段到前端字段的映射
3.  替换fetchList中原有的直接赋值逻辑,使用映射后的新数据
4.  简化handleViewRecord和handleViewDetail中的coilId获取逻辑
2026-05-23 16:50:46 +08:00
王文昊
8f96197aa5 feat: 添加待办事项模块及相关功能
新增待办事项页面,包含钢卷列表展示、筛选功能、钢卷详情页,支持重贴标签操作和查看改判/调拨记录,同时添加底部 tab 栏入口和对应图标资源
2026-05-20 11:09:08 +08:00
d54955a450 fix(hand-factory): 修正更新弹窗中取消按钮的文字错误
将"推出应用"改为正确的"退出应用",避免用户理解混淆
2026-04-24 13:23:36 +08:00
3f1032713e fix: 更新应用版本至1.3.31并调整更新逻辑
- 将config.js和version.json中的版本号更新为1.3.31
- 移除easycode.vue中钢卷质量状态的发货限制
- 修改update.js的更新提示,取消更新时直接退出应用
2026-04-24 13:23:01 +08:00
砂糖
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
92 changed files with 14118 additions and 751 deletions

View File

@@ -7,6 +7,7 @@
onLaunch: function() {
this.initApp()
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

@@ -0,0 +1,42 @@
import request from '@/utils/request'
// 查询OSS对象存储列表
export function listOss(query) {
return request({
url: '/system/oss/list',
method: 'get',
params: query
})
}
// 查询OSS对象基于id串支持多个ID用逗号分隔或数组
export function listByIds(ossIds) {
// 如果是数组,转换为逗号分隔的字符串
const ids = Array.isArray(ossIds) ? ossIds.join(',') : ossIds
return request({
url: '/system/oss/listByIds/' + ids,
method: 'get'
})
}
// 删除OSS对象存储
export function delOss(ossId) {
return request({
url: '/system/oss/' + ossId,
method: 'delete'
})
}
/**
* 上传文件
*/
export function uploadFile(file) {
const form = new FormData()
form.append('file', file)
return request({
url: '/system/oss/upload',
method: 'post',
data: form,
})
}

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,53 @@
import request from '@/utils/request'
// 查询钢卷异常信息列表
export function listCoilAbnormal(query) {
return request({
url: '/wms/coilAbnormal/list',
method: 'get',
params: query
})
}
// 查询钢卷异常信息详细
export function getCoilAbnormal(abnormalId) {
return request({
url: '/wms/coilAbnormal/' + abnormalId,
method: 'get'
})
}
// 新增钢卷异常信息
export function addCoilAbnormal(data) {
return request({
url: '/wms/coilAbnormal',
method: 'post',
data: data
})
}
// 修改钢卷异常信息
export function updateCoilAbnormal(data) {
return request({
url: '/wms/coilAbnormal',
method: 'put',
data: data
})
}
// 删除钢卷异常信息
export function delCoilAbnormal(abnormalId) {
return request({
url: '/wms/coilAbnormal/' + abnormalId,
method: 'delete'
})
}
// 异常判级
export function judgeAbnormalLevel(params) {
return request({
url: '/wms/coilAbnormal/judge',
method: 'put',
params
})
}

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,44 @@
import request from '@/utils/request'
// 查询设备检验清单列表
export function listEquipmentChecklist(query) {
return request({
url: '/eqp/equipmentChecklist/list',
method: 'get',
params: query
})
}
// 查询设备检验清单详细
export function getEquipmentChecklist(checkId) {
return request({
url: '/eqp/equipmentChecklist/' + checkId,
method: 'get'
})
}
// 新增设备检验清单
export function addEquipmentChecklist(data) {
return request({
url: '/eqp/equipmentChecklist',
method: 'post',
data: data
})
}
// 修改设备检验清单
export function updateEquipmentChecklist(data) {
return request({
url: '/eqp/equipmentChecklist',
method: 'put',
data: data
})
}
// 删除设备检验清单
export function delEquipmentChecklist(checkId) {
return request({
url: '/eqp/equipmentChecklist/' + checkId,
method: 'delete'
})
}

View File

@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询设备巡检记录列表
export function listEquipmentInspectionRecord(query) {
return request({
url: '/eqp/equipmentInspectionRecord/list',
method: 'get',
params: query
})
}
// 查询设备巡检记录详细
export function getEquipmentInspectionRecord(recordId) {
return request({
url: '/eqp/equipmentInspectionRecord/' + recordId,
method: 'get'
})
}
// 新增设备巡检记录
export function addEquipmentInspectionRecord(data) {
return request({
url: '/eqp/equipmentInspectionRecord',
method: 'post',
data: data
})
}
// 修改设备巡检记录
export function updateEquipmentInspectionRecord(data) {
return request({
url: '/eqp/equipmentInspectionRecord',
method: 'put',
data: data
})
}
// 删除设备巡检记录
export function delEquipmentInspectionRecord(recordId) {
return request({
url: '/eqp/equipmentInspectionRecord/' + recordId,
method: 'delete'
})
}

View File

@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询检验部位列表
export function listEquipmentPart(query) {
return request({
url: '/eqp/equipmentPart/list',
method: 'get',
params: query
})
}
// 查询检验部位详细
export function getEquipmentPart(partId) {
return request({
url: '/eqp/equipmentPart/' + partId,
method: 'get'
})
}
// 新增检验部位
export function addEquipmentPart(data) {
return request({
url: '/eqp/equipmentPart',
method: 'post',
data: data
})
}
// 修改检验部位
export function updateEquipmentPart(data) {
return request({
url: '/eqp/equipmentPart',
method: 'put',
data: data
})
}
// 删除检验部位
export function delEquipmentPart(partId) {
return request({
url: '/eqp/equipmentPart/' + partId,
method: 'delete'
})
}

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,19 @@
import request from '@/utils/request'
// 查询改判记录根据钢卷ID
export function listChangeHistory(coilId) {
return request({
url: '/wms/coilQualityRejudge/list',
method: 'get',
params: { coilId }
})
}
// 查询调拨记录根据钢卷ID
export function listTransferHistory(coilId) {
return request({
url: '/wms/transferOrderItem/list',
method: 'get',
params: { coilId }
})
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,886 @@
<template>
<view class="inspection-container">
<!-- 扫码区域 -->
<view class="scan-section" @click="handleScan">
<view class="scan-btn">
<text class="scan-icon">📷</text>
<text class="scan-text">{{ partInfo ? '重新扫码' : '点击扫码' }}</text>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<uni-load-more status="loading" />
</view>
<!-- 已扫码显示巡检信息 -->
<view v-if="partInfo && !loading" class="content-area">
<!-- 部位 + 巡检信息 -->
<view class="part-card">
<view class="part-fields">
<view class="field-row">
<text class="field-label">检验部位</text>
<text class="field-value">{{ partInfo.inspectPart || '-' }}</text>
</view>
<view class="field-row">
<text class="field-label">产线</text>
<text class="field-value">{{ partInfo.productionLine || '-' }}</text>
</view>
<view class="field-row">
<text class="field-label">线别</text>
<text class="field-value">{{ partInfo.lineSection || '-' }}</text>
</view>
<view class="field-row">
<text class="field-label">备注</text>
<text class="field-value">{{ partInfo.remark || '-' }}</text>
</view>
</view>
<view class="inspect-info">
<view class="info-item">
<text class="info-label">巡检人</text>
<text class="info-value">{{ inspector }}</text>
</view>
<view class="info-item">
<text class="info-label">巡检时间</text>
<text class="info-value">{{ inspectTime }}</text>
</view>
</view>
</view>
<!-- 班次选择 -->
<view class="shift-section">
<text class="shift-label">班次</text>
<view class="shift-radio-group">
<view class="shift-radio" :class="{ active: shift === 1 }" @click="shift = 1">
<text class="shift-radio-dot" />
<text>白班</text>
</view>
<view class="shift-radio" :class="{ active: shift === 2 }" @click="shift = 2">
<text class="shift-radio-dot" />
<text>夜班</text>
</view>
</view>
</view>
<!-- 待检项列表 -->
<view class="checklist-section">
<view class="section-title">
<text>待检项{{ checklist.length }}</text>
</view>
<view v-if="checklist.length === 0" class="empty-state">
<text class="empty-text">该部位暂无待检项</text>
</view>
<view v-for="item in checklist" :key="item.checkId" class="check-item-card">
<view class="check-item-info">
<text class="check-item-name">{{ item.checkName || item.checkContent || '检验项' }}</text>
<text v-if="item.checkContent" class="check-item-content">{{ item.checkContent }}</text>
<text v-if="item.standard" class="check-item-standard">标准{{ item.standard }}</text>
</view>
<!-- 照片 -->
<view class="photo-section">
<view v-if="uploadingMap[item.checkId]" class="photo-uploading">
<uni-load-more status="loading" />
</view>
<view v-else-if="photoMap[item.checkId]" class="photo-preview" @click="previewPhoto(item.checkId)">
<image class="photo-img" :src="photoMap[item.checkId]" mode="aspectFill" />
<view class="photo-delete" @click.stop="deletePhoto(item.checkId)"></view>
</view>
<view v-else class="photo-add" @click="choosePhoto(item)">
<text class="photo-add-icon">📷</text>
<text class="photo-add-text">拍照/选图</text>
</view>
</view>
<view class="result-section">
<!-- 已选择通过 -->
<view v-if="selectedResults[item.checkId] === 1" class="result-row">
<view class="result-tag result-pass">
<text class="result-icon"></text>
<text>正常</text>
</view>
<text class="result-time">{{ getResultTime(item.checkId) }}</text>
</view>
<!-- 已选择不通过 -->
<view v-if="selectedResults[item.checkId] === 2" class="result-row">
<view class="result-tag result-fail">
<text class="result-icon"></text>
<text>故障</text>
</view>
<text class="result-time">{{ getResultTime(item.checkId) }}</text>
</view>
<!-- 未选择显示选择按钮 -->
<view v-if="!selectedResults[item.checkId] && !pendingFail[item.checkId]" class="action-btns">
<button class="btn btn-pass" :disabled="uploadingMap[item.checkId]"
@click="handleResult(item, 1)">正常</button>
<button class="btn btn-fail" :disabled="uploadingMap[item.checkId]"
@click="handleFailSelect(item)">故障</button>
</view>
<!-- 选择故障后填写异常描述并提交 -->
<view v-if="pendingFail[item.checkId]" class="abnormal-section">
<textarea v-model="abnormalDescMap[item.checkId]" class="abnormal-input"
placeholder="请输入异常描述(必填)" :maxlength="200" />
<view class="abnormal-actions">
<button class="btn btn-cancel" @click="cancelFail(item)">取消</button>
<button class="btn btn-submit" :disabled="submitting[item.checkId]"
@click="handleResult(item, 2)">
{{ submitting[item.checkId] ? '提交中...' : '确认故障' }}
</button>
</view>
</view>
</view>
<!-- 故障结果展示 -->
<view v-if="selectedResults[item.checkId] === 2" class="abnormal-section result-abnormal">
<text class="abnormal-label">异常描述</text>
<text class="abnormal-text">{{ abnormalDescMap[item.checkId] || '未填写' }}</text>
</view>
</view>
</view>
</view>
<!-- 未扫码初始状态 -->
<view v-if="!partInfo && !loading" class="initial-state">
<text class="initial-icon">🔍</text>
<text class="initial-text">请扫描设备部位二维码开始巡检</text>
</view>
</view>
</template>
<script>
import {
listEquipmentChecklist
} from '@/api/wms/equipmentChecklist'
import {
getEquipmentPart
} from '@/api/wms/equipmentPart'
import {
addEquipmentInspectionRecord
} from '@/api/wms/equipmentInspectionRecord'
import upload from '@/utils/upload'
export default {
data() {
return {
statusBarHeight: 0,
loading: false,
partInfo: null,
checklist: [],
selectedResults: {},
resultTimes: {},
abnormalDescMap: {},
submitting: {},
pendingFail: {},
shift: 1,
inspector: '',
inspectTime: '',
photoMap: {},
uploadingMap: {}
}
},
created() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
this.inspector = this.$store.state.user.nickName || this.$store.state.user.name || '未知'
this.inspectTime = this.formatTime(new Date())
const hour = new Date().getHours()
this.shift = hour >= 6 && hour < 18 ? 1 : 2
console.log('[设备巡检] 初始化 - 巡检人:', this.inspector, '班次:', this.shift, '时间:', this.inspectTime)
},
methods: {
async handleScan() {
try {
const code = await this.scan()
console.log('[设备巡检] 扫码结果:', code)
if (!code) return
if (typeof code !== 'string' || !code.startsWith('eqpPart::')) {
uni.showToast({
title: '二维码无效,请扫描设备部位二维码',
icon: 'none'
})
return
}
const partId = code.replace('eqpPart::', '')
console.log('[设备巡检] 解析 partId:', partId)
if (!partId) {
uni.showToast({
title: '二维码格式错误',
icon: 'none'
})
return
}
this.loading = true
this.partInfo = null
this.checklist = []
this.selectedResults = {}
this.resultTimes = {}
this.abnormalDescMap = {}
this.submitting = {}
this.pendingFail = {}
this.photoMap = {}
this.uploadingMap = {}
try {
console.log('[设备巡检] 请求 getEquipmentPart, partId:', partId)
const partRes = await getEquipmentPart(partId)
console.log('[设备巡检] getEquipmentPart 响应:', partRes)
this.partInfo = partRes.data || { partId }
console.log('[设备巡检] 请求 listEquipmentChecklist, params:', { partId })
const checkRes = await listEquipmentChecklist({ partId })
console.log('[设备巡检] listEquipmentChecklist 响应:', checkRes)
this.checklist = checkRes.rows || []
} catch (err) {
console.error('[设备巡检] 获取数据失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
}
} catch (err) {
// 用户取消扫码
} finally {
this.loading = false
}
},
scan() {
return new Promise((resolve, reject) => {
uni.scanCode({
success(res) {
resolve(res.result)
},
fail() {
reject()
}
})
})
},
formatTime(date) {
const d = new Date(date)
const bj = new Date(d.getTime() + 8 * 60 * 60 * 1000)
const y = bj.getUTCFullYear()
const M = (bj.getUTCMonth() + 1).toString().padStart(2, '0')
const day = bj.getUTCDate().toString().padStart(2, '0')
const h = bj.getUTCHours().toString().padStart(2, '0')
const m = bj.getUTCMinutes().toString().padStart(2, '0')
const s = bj.getUTCSeconds().toString().padStart(2, '0')
return y + '-' + M + '-' + day + ' ' + h + ':' + m + ':' + s
},
getResultTime(checkId) {
return this.resultTimes[checkId] || ''
},
handleFailSelect(item) {
this.pendingFail = {
...this.pendingFail,
[item.checkId]: true
}
this.abnormalDescMap = {
...this.abnormalDescMap,
[item.checkId]: ''
}
},
cancelFail(item) {
const pending = { ...this.pendingFail }
delete pending[item.checkId]
this.pendingFail = pending
},
choosePhoto(item) {
const checkId = item.checkId
uni.chooseImage({
count: 1,
sourceType: ['camera', 'album'],
success: (res) => {
const filePath = res.tempFilePaths[0]
this.uploadPhoto(checkId, filePath)
}
})
},
async uploadPhoto(checkId, filePath) {
this.uploadingMap = { ...this.uploadingMap, [checkId]: true }
try {
console.log('[设备巡检] 上传照片, checkId:', checkId, 'filePath:', filePath)
const res = await upload({
url: '/system/oss/upload',
filePath,
name: 'file'
})
console.log('[设备巡检] 上传照片响应:', res)
const url = res.data?.url || res.url || res.data || ''
if (!url) {
uni.showToast({ title: '上传失败返回URL为空', icon: 'none' })
return
}
this.photoMap = { ...this.photoMap, [checkId]: url }
uni.showToast({ title: '照片上传成功', icon: 'success' })
} catch (err) {
console.error('[设备巡检] 上传照片失败:', err)
uni.showToast({ title: '上传照片失败,请重试', icon: 'none' })
} finally {
this.uploadingMap = { ...this.uploadingMap, [checkId]: false }
}
},
deletePhoto(checkId) {
const map = { ...this.photoMap }
delete map[checkId]
this.photoMap = map
},
previewPhoto(checkId) {
const url = this.photoMap[checkId]
if (url) {
uni.previewImage({ urls: [url] })
}
},
async handleResult(item, runStatus) {
const checkId = item.checkId
if (this.submitting[checkId]) return
console.log('[设备巡检] 提交巡检结果, checkId:', checkId, 'runStatus:', runStatus, 'item:', item)
this.submitting = {
...this.submitting,
[checkId]: true
}
try {
const abnormalDesc = runStatus === 2 ? (this.abnormalDescMap[checkId] || '') : ''
const now = new Date()
const recordData = {
checkId,
runStatus,
abnormalDesc,
shift: this.shift,
inspector: this.inspector,
inspectTime: this.formatTime(now),
photo: this.photoMap[checkId] || ''
}
console.log('[设备巡检] 请求 addEquipmentInspectionRecord, data:', recordData)
const recordRes = await addEquipmentInspectionRecord(recordData)
console.log('[设备巡检] addEquipmentInspectionRecord 响应:', recordRes)
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString()
.padStart(2, '0')
this.selectedResults = {
...this.selectedResults,
[checkId]: runStatus
}
this.resultTimes = {
...this.resultTimes,
[checkId]: timeStr
}
const pending = { ...this.pendingFail }
delete pending[checkId]
this.pendingFail = pending
uni.showToast({
title: runStatus === 1 ? '已标记正常' : '已标记故障',
icon: 'success'
})
} catch (err) {
console.error('[设备巡检] 提交巡检结果失败:', err)
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
} finally {
this.submitting = {
...this.submitting,
[checkId]: false
}
}
}
}
}
</script>
<style scoped lang="scss">
.inspection-container {
min-height: 100vh;
background: #f5f7fa;
display: flex;
flex-direction: column;
.custom-nav-bar {
background: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
}
}
.scan-section {
padding: 40rpx 30rpx;
display: flex;
justify-content: center;
.scan-btn {
width: 100%;
height: 160rpx;
background: linear-gradient(135deg, #1a73e8, #4a90d9);
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(26, 115, 232, 0.3);
.scan-icon {
font-size: 48rpx;
margin-bottom: 8rpx;
}
.scan-text {
font-size: 32rpx;
color: #ffffff;
font-weight: 500;
}
&:active {
opacity: 0.85;
}
}
}
.loading-state {
padding: 100rpx 0;
}
.content-area {
flex: 1;
padding: 0 30rpx;
.part-card {
background: #ffffff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
.part-fields {
.field-row {
display: flex;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 20rpx;
}
.field-label {
font-size: 26rpx;
color: #999999;
width: 120rpx;
flex-shrink: 0;
}
.field-value {
font-size: 26rpx;
color: #333333;
flex: 1;
}
}
}
.inspect-info {
display: flex;
gap: 40rpx;
padding-top: 16rpx;
border-top: 1rpx solid #f0f0f0;
.info-item {
display: flex;
align-items: center;
.info-label {
font-size: 24rpx;
color: #999999;
margin-right: 12rpx;
}
.info-value {
font-size: 26rpx;
color: #333333;
font-weight: 500;
}
}
}
}
.shift-section {
background: #ffffff;
border-radius: 12rpx;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
.shift-label {
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-right: 30rpx;
}
.shift-radio-group {
display: flex;
gap: 24rpx;
.shift-radio {
display: flex;
align-items: center;
padding: 12rpx 24rpx;
border-radius: 8rpx;
background: #f5f5f5;
font-size: 26rpx;
color: #666666;
.shift-radio-dot {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
border: 2rpx solid #cccccc;
margin-right: 10rpx;
}
&.active {
background: #e8f0fe;
color: #1a73e8;
.shift-radio-dot {
background: #1a73e8;
border-color: #1a73e8;
}
}
&:active {
opacity: 0.7;
}
}
}
}
.checklist-section {
.section-title {
font-size: 28rpx;
color: #666666;
margin-bottom: 20rpx;
padding-left: 10rpx;
}
.check-item-card {
background: #ffffff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
.check-item-info {
margin-bottom: 24rpx;
.check-item-name {
font-size: 30rpx;
color: #333333;
font-weight: 500;
display: block;
}
.check-item-content {
font-size: 26rpx;
color: #666666;
margin-top: 10rpx;
display: block;
}
.check-item-standard {
font-size: 24rpx;
color: #999999;
margin-top: 8rpx;
display: block;
}
}
.photo-section {
margin-bottom: 20rpx;
.photo-add {
width: 160rpx;
height: 160rpx;
background: #f5f5f5;
border: 2rpx dashed #cccccc;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.photo-add-icon {
font-size: 40rpx;
margin-bottom: 6rpx;
}
.photo-add-text {
font-size: 22rpx;
color: #999999;
}
&:active {
opacity: 0.7;
}
}
.photo-uploading {
width: 160rpx;
height: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f8f8f8;
border-radius: 12rpx;
}
.photo-preview {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
.photo-img {
width: 100%;
height: 100%;
}
.photo-delete {
position: absolute;
top: 4rpx;
right: 4rpx;
width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #ffffff;
font-size: 22rpx;
border-radius: 50%;
}
&:active {
opacity: 0.85;
}
}
}
.result-section {
.result-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
.result-tag {
display: flex;
align-items: center;
padding: 10rpx 28rpx;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
.result-icon {
margin-right: 8rpx;
font-size: 30rpx;
}
&.result-pass {
background: #e8f5e9;
color: #2e7d32;
}
&.result-fail {
background: #fbe9e7;
color: #c62828;
}
}
.result-time {
font-size: 24rpx;
color: #999999;
}
}
.action-btns {
display: flex;
gap: 20rpx;
.btn {
flex: 1;
height: 76rpx;
line-height: 76rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
&::after {
border: none;
}
&.btn-pass {
background: #e8f5e9;
color: #2e7d32;
&:disabled {
opacity: 0.4;
}
}
&.btn-fail {
background: #fbe9e7;
color: #c62828;
&:disabled {
opacity: 0.4;
}
}
&:active {
opacity: 0.7;
}
}
}
}
.abnormal-section {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.abnormal-input {
width: 100%;
min-height: 120rpx;
background: #f8f8f8;
border-radius: 8rpx;
padding: 20rpx;
font-size: 26rpx;
color: #333333;
box-sizing: border-box;
}
.abnormal-actions {
display: flex;
gap: 20rpx;
margin-top: 20rpx;
.btn {
flex: 1;
height: 68rpx;
line-height: 68rpx;
text-align: center;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
&::after {
border: none;
}
&.btn-cancel {
background: #f5f5f5;
color: #666666;
}
&.btn-submit {
background: #c62828;
color: #ffffff;
&:disabled {
opacity: 0.6;
}
}
&:active {
opacity: 0.7;
}
}
}
&.result-abnormal {
border-top: none;
margin-top: 0;
padding-top: 8rpx;
.abnormal-label {
font-size: 24rpx;
color: #c62828;
}
.abnormal-text {
font-size: 24rpx;
color: #666666;
margin-left: 10rpx;
}
}
}
}
}
}
.initial-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.initial-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.initial-text {
font-size: 30rpx;
color: #999999;
}
}
.empty-state {
padding: 60rpx 0;
.empty-text {
font-size: 28rpx;
color: #999999;
text-align: center;
display: block;
}
}
}
</style>

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.34",
// 应用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,62 @@
"style": {
"navigationBarTitleText": "钢卷收货"
}
},
{
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "钢卷查询"
}
},
{
"path": "pages/meal/meal",
"style": {
"navigationBarTitleText": "报餐情况"
}
},
{
"path": "pages/fahuo/fahuo",
"style": {
"navigationBarTitleText": "发货"
}
},
{
"path": "pages/todo/index",
"style": {
"navigationBarTitleText": "待办事项",
"navigationStyle": "custom"
}
},
{
"path": "pages/todo/coil-detail",
"style": {
"navigationBarTitleText": "钢卷详情"
}
},
{
"path": "pages/eqp/eqp",
"style": {
"navigationBarTitleText": "设备巡检"
}
},
{
"path": "pages/defect/coil-defect",
"style": {
"navigationBarTitleText": "缺陷维护"
}
},
{
"path": "pages/coil-detail/index",
"style": {
"navigationBarTitleText": "钢卷详情"
}
},
{
"path": "pages/scan/scan",
"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",
@@ -144,29 +147,29 @@
"tabBar": {
"selectedColor": "#17abe3",
"list": [
{
"text": "产线",
"pagePath": "pages/line/line",
"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/todo/index",
// "selectedIconPath": "/static/images/tabbar/todo_.png",
// "iconPath": "/static/images/tabbar/todo.png"
// },
{
"text": "扫码",
"pagePath": "pages/easycode/easycode",
"pagePath": "pages/scan/scan",
"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/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 +187,5 @@
"k-(.*)": "@/components/klp-ui/k-$1/k-$1.vue"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
<template>
<view class="container">
<!-- 分条操作区 -->
<view class="section-card split-section">
<!-- <view class="section-card split-section">
<view class="section-title">
<text class="title-icon"></text>
<text class="title-text">分条操作</text>
@@ -12,10 +12,10 @@
{{ item.dictLabel }}
</button>
</view>
</view>
</view> -->
<!-- 其他操作区 -->
<view class="section-card other-section">
<!-- <view class="section-card other-section">
<view class="section-title">
<text class="title-icon">🔧</text>
<text class="title-text">其他操作</text>
@@ -26,7 +26,7 @@
{{ item.dictLabel }}
</button>
</view>
</view>
</view> -->
<!-- 快捷操作区 -->
<view class="section-card quick-section">
@@ -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 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') {
@@ -225,7 +346,7 @@
if (res.rows.length == 1) {
const coilId = res.rows[0].coilId;
uni.navigateTo({
url: '/pages/easycode/editby?coilId=' + coilId
url: '/pages/coil-detail/index?coilId=' + coilId
})
} else {
uni.showToast({
@@ -235,6 +356,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) {
const content = await this.getQRCodeContent()
@@ -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);
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: '只能发货B-以上品质的钢卷',
// icon: 'none'
// });
// return;
// }
// 1. 更新钢卷状态为已发货
const res = await exportCoil(this.coilDetail.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()
});
// 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()
});
uni.showToast({
title: '发货成功',
icon: 'none'
});
} catch (error) {
console.error('发货失败:', error);
uni.showToast({
title: error?.message || '发货失败',
icon: 'none'
});
}
},
fail() {
if (res.code != 200) {
uni.showToast({
title: '已取消发货',
icon: 'none'
icon: 'error',
title: res.message || '发货失败请重试'
})
return;
}
});
uni.hideLoading();
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>
@@ -86,14 +102,15 @@
</template>
<script>
import { getMaterialCoil, updateMaterialCoil } from '@/api/wms/coil';
import { getMaterialCoil, updateMaterialCoilSimple } from '@/api/wms/coil';
import { addPendingAction } from '@/api/wms/pendingAction';
export default {
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, )
})
},
// 获取设备信息(原有方法不变)
@@ -128,7 +151,7 @@
this.loading = true;
// 1. 更新钢卷
console.log('待提交数据', this.form)
await updateMaterialCoil(this.form)
await updateMaterialCoilSimple(this.form)
// 2. 创建操作记录
await addPendingAction({
actionType: 404,

View File

@@ -115,7 +115,7 @@
</template>
<script>
import { getMaterialCoil, updateMaterialCoil } from '@/api/wms/coil';
import { getMaterialCoil, updateMaterialCoilSimple } from '@/api/wms/coil';
import { addPendingAction } from '@/api/wms/pendingAction';
export default {
@@ -157,7 +157,7 @@
this.loading = true;
// 1. 更新钢卷
console.log('待提交数据', this.form)
await updateMaterialCoil(this.form)
await updateMaterialCoilSimple(this.form)
// 2. 创建操作记录
await addPendingAction({
actionType: 405, // 405表示打包

View File

@@ -0,0 +1,15 @@
<template>
<view class="">
<ScanInspect />
</view>
</template>
<script>
import ScanInspect from '@/components/scan-inspect/scan-inspect.vue'
export default {
components: {
ScanInspect
}
}
</script>

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,105 +1,31 @@
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
hasJumped: false // 防止重复跳转的标记
hasJumped: false,
}
},
onShow() {
// 已跳转过则不再执行逻辑
if (this.hasJumped) return;
// 检查用户角色
this.$store.dispatch('GetInfo')
.then(res => {
// 验证返回数据格式
if (!res || !res.data || !Array.isArray(res.data.roles)) {
throw new Error('用户角色信息格式错误');
}
console.log('用户角色信息', res.data.roles)
const roles = res.data.roles;
if (roles.includes('admin')) {
uni.setTabBarItem({
index: 0,
visible: true
});
// 管理员角色跳转
uni.switchTab({
url: '/pages/line/line',
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
});
uni.switchTab({
url: '/pages/easycode/easycode',
success: () => {
this.hasJumped = true; // 标记已跳转
},
fail: (err) => {
console.error('工人页面跳转失败:', err);
uni.showToast({
title: '跳转扫码页面失败',
icon: 'none',
duration: 2000
});
}
});
} else {
// 处理未定义角色(默认角色)
uni.showToast({
title: '检测到未知角色,将跳转至默认页面',
icon: 'none',
duration: 2000
});
uni.setTabBarItem({
index: 0,
visible: false
});
// 延迟跳转,确保提示被用户看到
setTimeout(() => {
uni.switchTab({
url: '/pages/easycode/easycode',
success: () => {
this.hasJumped = true;
},
fail: (err) => {
console.error('默认角色页面跳转失败:', err);
uni.showToast({
title: '跳转默认页面失败',
icon: 'none',
duration: 2000
});
}
});
}, 2000);
}
.then(() => {
uni.switchTab({
url: '/pages/scan/scan',
success: () => {
this.hasJumped = true;
},
fail: (err) => {
console.error('跳转失败:', err);
}
});
})
.catch(err => {
uni.reLaunch({
url: '/pages/login'
})
.catch(() => {
uni.reLaunch({ url: '/pages/login' })
});
}
}

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,34 @@
</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="handleToEqp">
<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="handleToAb">
<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 +75,14 @@
}
},
methods: {
handleToInfo() {
this.$tab.navigateTo('/pages/mine/info/index')
handleToSearch() {
this.$tab.navigateTo('/pages/search/search')
},
handleToEqp() {
this.$tab.navigateTo('/pages/eqp/eqp')
},
handleToAb() {
this.$tab.navigateTo('/pages/defect/coil-defect')
},
handleLogout() {
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
@@ -108,30 +91,8 @@
})
})
},
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,105 @@
<template>
<view class="container">
<!-- Tab 切换栏 -->
<view class="tabs-bar">
<view
class="tab-item"
:class="{ 'tab-active': currentTab === 'coil' }"
@click="currentTab = 'coil'"
>
<text class="tab-text">钢卷操作</text>
</view>
<view
class="tab-item"
:class="{ 'tab-active': currentTab === 'inspect' }"
@click="currentTab = 'inspect'"
>
<text class="tab-text">设备巡检</text>
</view>
</view>
<!-- Tab 内容 -->
<view class="tab-content">
<view v-show="currentTab === 'coil'" class="tab-panel">
<scan-coil ref="scanCoil" />
</view>
<view v-show="currentTab === 'inspect'" class="tab-panel">
<scan-inspect ref="scanInspect" />
</view>
</view>
</view>
</template>
<script>
import ScanCoil from '@/components/scan-coil/scan-coil.vue'
import ScanInspect from '@/components/scan-inspect/scan-inspect.vue'
export default {
components: { ScanCoil, ScanInspect },
data() {
return {
currentTab: 'coil'
}
}
}
</script>
<style scoped lang="scss">
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f7fa;
overflow: hidden;
}
// ========== Tab 栏 ==========
.tabs-bar {
display: flex;
background: #fff;
border-bottom: 2rpx solid #f0f0f0;
flex-shrink: 0;
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
position: relative;
.tab-text {
font-size: 28rpx;
color: #666;
}
&.tab-active {
.tab-text {
color: #1a73e8;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 20%;
right: 20%;
height: 4rpx;
background: #1a73e8;
border-radius: 2rpx;
}
}
}
}
// ========== Tab 内容 ==========
.tab-content {
flex: 1;
overflow: hidden;
}
.tab-panel {
height: 100%;
overflow-y: auto;
padding: 15rpx;
box-sizing: border-box;
}
</style>

View File

@@ -0,0 +1,798 @@
<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>
<!-- 查询悬浮按钮 - 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 { listMaterialCoil } from '@/api/wms/coil';
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) {
uni.navigateTo({
url: '/pages/coil-detail/index?coilId=' + item.coilId
});
},
// 格式化状态文本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>

View File

@@ -0,0 +1,387 @@
<template>
<view class="detail-container">
<!-- 头部信息卡片 -->
<view class="header-card">
<view class="header-title">
<text class="title-text">钢卷信息</text>
<text class="coil-no">{{ coilInfo.currentCoilNo || '-' }}</text>
</view>
<view class="header-subtitle">
<text class="subtitle-text">入场钢卷号: {{ coilInfo.enterCoilNo || '-' }}</text>
</view>
</view>
<!-- 基础信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">📋</text>
<text class="title-text">基础信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">厂家卷号</text>
<text class="value">{{ coilInfo.factoryCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">物料类型</text>
<text class="value">{{ coilInfo.itemType || '-' }}</text>
</view>
<view class="info-item">
<text class="label">产品名称</text>
<text class="value">{{ coilInfo.itemName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">规格</text>
<text class="value">{{ coilInfo.specification || '-' }}</text>
</view>
<view class="info-item">
<text class="label">材质</text>
<text class="value">{{ coilInfo.material || '-' }}</text>
</view>
<view class="info-item">
<text class="label">厂家</text>
<text class="value">{{ coilInfo.manufacturer || '-' }}</text>
</view>
</view>
</view>
<!-- 库存信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">📦</text>
<text class="title-text">库存信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">逻辑库区</text>
<text class="value">{{ coilInfo.warehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">实际库区</text>
<text class="value">{{ coilInfo.actualWarehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">净重()</text>
<text class="value">{{ coilInfo.netWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="label">毛重()</text>
<text class="value">{{ coilInfo.grossWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="label">长度()</text>
<text class="value">{{ coilInfo.length || '-' }}</text>
</view>
<view class="info-item">
<text class="label">质量状态</text>
<text class="value" :class="getQualityClass(coilInfo.qualityStatus)">
{{ coilInfo.qualityStatus || '-' }}
</text>
</view>
</view>
</view>
<!-- 加工信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon"></text>
<text class="title-text">加工信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">表面处理</text>
<text class="value">{{ coilInfo.surfaceTreatment || '-' }}</text>
</view>
<view class="info-item">
<text class="label">镀层质量</text>
<text class="value">{{ coilInfo.coatingWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="label">包装要求</text>
<text class="value">{{ coilInfo.packaging || '-' }}</text>
</view>
<view class="info-item">
<text class="label">切边</text>
<text class="value">{{ coilInfo.cuttingEdge || '-' }}</text>
</view>
<view class="info-item">
<text class="label">班组</text>
<text class="value">{{ coilInfo.team || '-' }}</text>
</view>
<view class="info-item">
<text class="label">备注</text>
<text class="value">{{ coilInfo.remark || '-' }}</text>
</view>
</view>
</view>
<!-- 操作信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">👤</text>
<text class="title-text">操作信息</text>
</view>
<view class="info-list">
<view class="list-item">
<text class="label">创建人</text>
<text class="value">{{ coilInfo.creator || '-' }}</text>
</view>
<view class="list-item">
<text class="label">创建时间</text>
<text class="value">{{ coilInfo.createTime || '-' }}</text>
</view>
<view class="list-item">
<text class="label">更新人</text>
<text class="value">{{ coilInfo.updater || '-' }}</text>
</view>
<view class="list-item">
<text class="label">更新时间</text>
<text class="value">{{ coilInfo.updateTime || '-' }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="footer-bar" v-if="actionId">
<button class="btn btn-primary" @click="handleComplete">
<text class="btn-icon"></text>
<text class="btn-text">完成待办</text>
</button>
</view>
</view>
</template>
<script>
import { getMaterialCoil } from '@/api/wms/coil'
export default {
data() {
return {
coilId: null,
actionId: null,
coilInfo: {},
loading: false
}
},
onLoad(options) {
this.coilId = options.coilId
this.actionId = options.actionId
if (this.coilId) {
this.fetchDetail()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
methods: {
async fetchDetail() {
this.loading = true
try {
const res = await getMaterialCoil(this.coilId)
this.coilInfo = res.data || {}
} catch (error) {
console.error('获取详情失败:', error)
uni.showToast({
title: '获取详情失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
this.loading = false
}
},
getQualityClass(status) {
if (!status) return ''
const statusMap = {
'A': 'quality-a',
'B': 'quality-b',
'C': 'quality-c',
'D': 'quality-d'
}
return statusMap[status] || ''
},
async handleComplete() {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
}
}
</script>
<style scoped lang="scss">
.detail-container {
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 140rpx;
.header-card {
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
padding: 40rpx 30rpx;
.header-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.title-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.8);
}
.coil-no {
font-size: 36rpx;
color: #ffffff;
font-weight: 600;
}
}
.header-subtitle {
.subtitle-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
}
.info-section {
background: #ffffff;
margin: 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.section-title {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
}
.info-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.info-item {
width: 50%;
padding: 16rpx 10rpx;
box-sizing: border-box;
.label {
display: block;
font-size: 24rpx;
color: #999999;
margin-bottom: 8rpx;
}
.value {
font-size: 28rpx;
color: #333333;
font-weight: 500;
&.quality-a {
color: #52c41a;
}
&.quality-b {
color: #1890ff;
}
&.quality-c {
color: #faad14;
}
&.quality-d {
color: #ff4d4f;
}
}
}
}
.info-list {
padding: 0 30rpx;
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: #666666;
}
.value {
font-size: 28rpx;
color: #333333;
}
}
}
}
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
.btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #1a73e8;
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
border-radius: 12rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
&::after {
border: none;
}
.btn-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.btn-text {
font-size: 32rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,238 @@
<template>
<view class="coil-card">
<!-- 头部信息 -->
<view class="card-header">
<view class="header-item">
<text class="label">入场钢卷号</text>
<text class="value">{{ data.enterCoilNo || '-' }}</text>
</view>
</view>
<!-- 主体信息 -->
<view class="card-body">
<view class="info-row">
<view class="info-item">
<text class="label">当前钢卷号</text>
<text class="value highlight">{{ data.currentCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">产品类型</text>
<text class="value">{{ data.itemName || '-' }}</text>
</view>
</view>
<view class="info-row">
<view class="info-item">
<text class="label">实际库区</text>
<text class="value">{{ data.actualWarehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">备注</text>
<text class="value">{{ data.remark || '-' }}</text>
</view>
</view>
<view class="info-row">
<view class="info-item">
<text class="label">调拨类型</text>
<text class="value tag" v-if="data.transferType">{{ data.transferType }}</text>
<text class="value" v-else>-</text>
</view>
<view class="info-item">
<text class="label">改判原因</text>
<text class="value">{{ data.changeReason || '-' }}</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-footer" v-if="showActions">
<view class="action-btn primary" @click.stop="handleRelabel">
<text class="btn-icon">🏷</text>
<text class="btn-text">重贴标签</text>
</view>
<view class="action-btn" @click.stop="handleViewRecord">
<text class="btn-icon">📋</text>
<text class="btn-text">查看记录</text>
</view>
<view class="action-btn" @click.stop="handleViewDetail">
<text class="btn-icon">👁</text>
<text class="btn-text">查看详情</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'CoilCard',
props: {
data: {
type: Object,
required: true,
default: () => ({})
},
showActions: {
type: Boolean,
default: true
}
},
methods: {
handleRelabel() {
this.$emit('relabel', this.data)
},
handleViewRecord() {
this.$emit('view-record', this.data)
},
handleViewDetail() {
this.$emit('view-detail', this.data)
}
}
}
</script>
<style scoped lang="scss">
.coil-card {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
.card-header {
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
padding: 24rpx 30rpx;
.header-item {
display: flex;
align-items: center;
.label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
margin-right: 16rpx;
}
.value {
font-size: 32rpx;
color: #ffffff;
font-weight: 600;
}
}
}
.card-body {
padding: 24rpx 30rpx;
.info-row {
display: flex;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.info-item {
flex: 1;
display: flex;
flex-direction: column;
.label {
font-size: 24rpx;
color: #999999;
margin-bottom: 8rpx;
}
.value {
font-size: 28rpx;
color: #333333;
word-break: break-all;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.4;
&.highlight {
color: #1a73e8;
font-weight: 500;
}
&.tag {
display: inline-block;
background: #e8f0fe;
color: #1a73e8;
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
white-space: nowrap;
}
}
}
}
}
.card-footer {
display: flex;
padding: 20rpx 30rpx;
border-top: 1rpx solid #f0f0f0;
gap: 16rpx;
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 0;
border-radius: 8rpx;
background: #f5f5f5;
transition: all 0.2s;
&:active {
opacity: 0.8;
}
&.primary {
background: #1a73e8;
.btn-text {
color: #ffffff;
}
}
.btn-icon {
font-size: 28rpx;
margin-right: 8rpx;
}
.btn-text {
font-size: 26rpx;
color: #666666;
}
}
}
}
/* H5 浏览器特定样式 */
/* #ifdef H5 */
.coil-card {
cursor: pointer;
&:hover {
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
}
.card-footer {
.action-btn {
cursor: pointer;
&:hover {
opacity: 0.9;
}
&:active {
opacity: 0.8;
}
}
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,317 @@
<template>
<view class="filter-bar">
<view class="filter-header" @click="toggleExpand">
<view class="filter-title">
<text class="title-icon">🔍</text>
<text class="title-text">筛选条件</text>
</view>
<view class="filter-toggle">
<text class="toggle-text">{{ isExpanded ? '收起' : '展开' }}</text>
<text class="toggle-icon" :class="{ 'expanded': isExpanded }"></text>
</view>
</view>
<view class="filter-content" v-show="isExpanded">
<view class="filter-row">
<view class="filter-item">
<text class="item-label">入场卷号</text>
<input
class="item-input"
v-model="form.enterCoilNo"
placeholder="请输入入场钢卷号"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">当前卷号</text>
<input
class="item-input"
v-model="form.currentCoilNo"
placeholder="请输入当前钢卷号"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-row">
<view class="filter-item">
<text class="item-label">产品名称</text>
<input
class="item-input"
v-model="form.itemName"
placeholder="请选择物料"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">规格</text>
<input
class="item-input"
v-model="form.itemSpecification"
placeholder="请选择规格"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-row">
<view class="filter-item">
<text class="item-label">材质</text>
<input
class="item-input"
v-model="form.itemMaterial"
placeholder="请选择材质"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">厂家</text>
<input
class="item-input"
v-model="form.itemManufacturer"
placeholder="请选择厂家"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-actions">
<button class="btn btn-reset" @click="handleReset" :disabled="loading">
<text v-if="!loading">重置</text>
<text v-else>重置中...</text>
</button>
<button class="btn btn-search" @click="handleSearch" :disabled="loading">
<text v-if="!loading">搜索</text>
<text v-else>搜索中...</text>
</button>
</view>
<!-- 筛选结果统计 -->
<view v-if="showResult" class="filter-result">
<text class="result-text">找到 {{ total }} 条记录</text>
<text class="result-close" @click="hideResult"></text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'FilterBar',
props: {
loading: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 0
}
},
data() {
return {
isExpanded: false,
showResult: false,
form: {
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: ''
}
}
},
methods: {
toggleExpand() {
this.isExpanded = !this.isExpanded
},
handleReset() {
this.form = {
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: ''
}
this.showResult = false
this.$emit('reset')
// 显示重置成功提示
uni.showToast({
title: '已重置',
icon: 'success',
duration: 1500
})
},
handleSearch() {
this.showResult = true
this.isExpanded = false // 搜索后自动收起筛选栏
this.$emit('search', { ...this.form })
},
hideResult() {
this.showResult = false
}
}
}
</script>
<style scoped lang="scss">
.filter-bar {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.filter-title {
display: flex;
align-items: center;
.title-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
}
.filter-toggle {
display: flex;
align-items: center;
.toggle-text {
font-size: 26rpx;
color: #666666;
margin-right: 8rpx;
}
.toggle-icon {
font-size: 24rpx;
color: #999999;
transition: transform 0.2s;
&.expanded {
transform: rotate(180deg);
}
}
}
}
.filter-content {
padding: 24rpx 30rpx;
.filter-row {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.filter-item {
flex: 1;
.item-label {
display: block;
font-size: 24rpx;
color: #666666;
margin-bottom: 12rpx;
}
.item-input {
height: 72rpx;
background: #f8f8f8;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #cccccc;
}
}
.input-placeholder {
color: #cccccc;
}
}
}
.filter-actions {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
&::after {
border: none;
}
&.btn-reset {
background: #f5f5f5;
color: #666666;
}
&.btn-search {
background: #1a73e8;
color: #ffffff;
&:disabled {
opacity: 0.6;
}
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.filter-result {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
padding: 16rpx 20rpx;
background: #e8f4fd;
border-radius: 8rpx;
.result-text {
font-size: 26rpx;
color: #1a73e8;
font-weight: 500;
}
.result-close {
font-size: 28rpx;
color: #666666;
padding: 4rpx 8rpx;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,446 @@
<template>
<uni-popup ref="popup" type="bottom" :safe-area="true">
<view class="popup-container">
<view class="popup-header">
<text class="popup-title">{{ title }}</text>
<text class="popup-close" @click="close"></text>
</view>
<view class="popup-content">
<!-- Tab切换 -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: activeTab === 'change' }"
@click="activeTab = 'change'"
>
<text class="tab-text">改判记录</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'transfer' }"
@click="activeTab = 'transfer'"
>
<text class="tab-text">调拨记录</text>
</view>
</view>
<!-- 改判记录列表 -->
<scroll-view
v-show="activeTab === 'change'"
scroll-y
class="record-list"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="onLoadMoreChange"
>
<view v-if="changeRecords.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无改判记录</text>
</view>
<view
v-else
v-for="(record, index) in changeRecords"
:key="index"
class="record-item"
>
<view class="record-header">
<text class="record-type">{{ getRejudgeTypeText(record.rejudgeType) }}</text>
<text class="record-time">{{ formatTime(record.createTime) }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="label">原值</text>
<text class="value old">{{ record.originalValue }}</text>
</view>
<view class="record-row">
<text class="label">新值</text>
<text class="value new">{{ record.newValue }}</text>
</view>
<view class="record-row">
<text class="label">改判原因</text>
<text class="value">{{ record.rejudgeReason }}</text>
</view>
<view class="record-row">
<text class="label">操作人</text>
<text class="value">{{ record.createBy }}</text>
</view>
<view class="record-row" v-if="record.remark">
<text class="label">备注</text>
<text class="value">{{ record.remark }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 调拨记录列表 -->
<scroll-view
v-show="activeTab === 'transfer'"
scroll-y
class="record-list"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="onLoadMoreTransfer"
>
<view v-if="transferRecords.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无调拨记录</text>
</view>
<view
v-else
v-for="(record, index) in transferRecords"
:key="index"
class="record-item"
>
<view class="record-header">
<text class="record-type">{{ getTransferTypeText(record.transferType) }}</text>
<text class="record-time">{{ formatTime(record.createTime) }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="label">调拨单号</text>
<text class="value">{{ record.transferOrderNo }}</text>
</view>
<view class="record-row">
<text class="label">原库区</text>
<text class="value">{{ record.fromWarehouseName }}</text>
</view>
<view class="record-row">
<text class="label">目标库区</text>
<text class="value new">{{ record.toWarehouseName }}</text>
</view>
<view class="record-row">
<text class="label">调拨数量</text>
<text class="value">{{ record.transferQuantity }}</text>
</view>
<view class="record-row">
<text class="label">操作人</text>
<text class="value">{{ record.createBy }}</text>
</view>
<view class="record-row" v-if="record.remark">
<text class="label">备注</text>
<text class="value">{{ record.remark }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</uni-popup>
</template>
<script>
import { listChangeHistory, listTransferHistory } from '@/api/wms/todo'
export default {
name: 'RecordPopup',
props: {
title: {
type: String,
default: '查看记录'
}
},
data() {
return {
activeTab: 'change',
changeRecords: [],
transferRecords: [],
coilId: null,
scrollHeight: 400 // 默认滚动区域高度
}
},
mounted() {
this.calcScrollHeight()
},
methods: {
open(coilId) {
this.coilId = coilId
this.activeTab = 'change'
this.$refs.popup.open()
// 延迟计算高度和加载数据,确保弹窗已渲染
this.$nextTick(() => {
this.calcScrollHeight()
this.fetchRecords()
})
},
close() {
this.$refs.popup.close()
},
async fetchRecords() {
if (!this.coilId) return
uni.showLoading({ title: '加载中...' })
try {
// 获取改判记录
const changeRes = await listChangeHistory(this.coilId)
this.changeRecords = changeRes.rows || []
// 获取调拨记录
const transferRes = await listTransferHistory(this.coilId)
this.transferRecords = transferRes.rows || []
// 显示记录数量提示
const totalCount = this.changeRecords.length + this.transferRecords.length
if (totalCount > 0) {
uni.showToast({
title: `找到 ${totalCount} 条记录`,
icon: 'none',
duration: 2000
})
}
} catch (error) {
console.error('获取记录失败:', error)
uni.showToast({
title: '获取记录失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
// 获取改判类型文本
getRejudgeTypeText(type) {
const typeMap = {
'quality': '质量改判',
'specification': '规格改判',
'material': '材质改判',
'other': '其他改判'
}
return typeMap[type] || '改判记录'
},
// 获取调拨类型文本
getTransferTypeText(type) {
const typeMap = {
'in': '入库调拨',
'out': '出库调拨',
'move': '库内调拨',
'return': '退货调拨'
}
return typeMap[type] || '调拨记录'
},
// 格式化时间
formatTime(time) {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
},
// 计算滚动区域高度
calcScrollHeight() {
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const safeAreaBottom = systemInfo.safeAreaInsets?.bottom || 0
const tabBarHeight = 50 // TabBar高度
// 弹窗最大高度为屏幕高度的80%减去底部安全区域和TabBar
const popupMaxHeight = windowHeight * 0.8 - safeAreaBottom - tabBarHeight
// 减去:弹窗头部(约60px) + Tab栏(约50px)
const headerHeight = 60
const tabHeight = 50
this.scrollHeight = popupMaxHeight - headerHeight - tabHeight - 20 // 20px为底部padding
console.log('计算滚动高度:', {
windowHeight,
safeAreaBottom,
tabBarHeight,
popupMaxHeight,
scrollHeight: this.scrollHeight
})
},
// 加载更多改判记录
onLoadMoreChange() {
console.log('加载更多改判记录')
// 如果需要分页加载,可以在这里实现
},
// 加载更多调拨记录
onLoadMoreTransfer() {
console.log('加载更多调拨记录')
// 如果需要分页加载,可以在这里实现
}
}
}
</script>
<style scoped lang="scss">
.popup-container {
background: #ffffff;
border-radius: 24rpx 24rpx 0 0;
max-height: 80vh;
display: flex;
flex-direction: column;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.popup-close {
font-size: 36rpx;
color: #999999;
padding: 10rpx;
}
}
.popup-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.tab-bar {
display: flex;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
position: relative;
.tab-text {
font-size: 30rpx;
color: #666666;
}
&.active {
.tab-text {
color: #1a73e8;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #1a73e8;
border-radius: 2rpx;
}
}
}
}
.record-list {
flex: 1;
padding: 20rpx 30rpx;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
.record-item {
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
.record-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #eeeeee;
gap: 20rpx;
.record-type {
font-size: 28rpx;
font-weight: 600;
color: #1a73e8;
flex: 1;
word-break: break-all;
line-height: 1.4;
}
.record-time {
font-size: 24rpx;
color: #999999;
flex-shrink: 0;
white-space: nowrap;
}
}
.record-body {
.record-row {
display: flex;
margin-bottom: 12rpx;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: #666666;
width: 140rpx;
flex-shrink: 0;
line-height: 1.5;
}
.value {
font-size: 26rpx;
color: #333333;
flex: 1;
word-break: break-all;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.5;
&.old {
color: #999999;
text-decoration: line-through;
}
&.new {
color: #1a73e8;
font-weight: 500;
}
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,222 @@
<template>
<uni-popup ref="popup" type="center" :safe-area="true">
<view class="popup-container">
<view class="popup-header">
<text class="popup-title">重贴标签</text>
<text class="popup-close" @click="close"></text>
</view>
<view class="popup-content">
<!-- 钢卷信息展示 -->
<view class="coil-info">
<view class="info-row">
<text class="label">入场钢卷号</text>
<text class="value">{{ coilInfo.enterCoilNo || '-' }}</text>
</view>
<view class="info-row">
<text class="label">当前钢卷号</text>
<text class="value highlight">{{ coilInfo.currentCoilNo || '-' }}</text>
</view>
<view class="info-row">
<text class="label">产品名称</text>
<text class="value">{{ coilInfo.itemName || '-' }}</text>
</view>
</view>
<!-- 操作人信息 -->
<view class="operator-info">
<text class="label">操作人</text>
<text class="value">{{ operatorName }}</text>
</view>
</view>
<view class="popup-footer">
<button class="btn btn-cancel" @click="close">取消</button>
<button class="btn btn-confirm" @click="handleConfirm" :disabled="loading">
{{ loading ? '提交中...' : '确认重贴' }}
</button>
</view>
</view>
</uni-popup>
</template>
<script>
import { relabelCoil, completeTodoAction } from '@/api/wms/todo'
export default {
name: 'RelabelPopup',
data() {
return {
loading: false,
coilInfo: {}
}
},
computed: {
operatorName() {
return this.$store.state.user.nickName || this.$store.state.user.name || '未知'
}
},
methods: {
open(coilInfo) {
this.coilInfo = coilInfo
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
},
async handleConfirm() {
this.loading = true
try {
// 1. 提交重贴标签操作
await relabelCoil({
coilId: this.coilInfo.coilId,
currentCoilNo: this.coilInfo.currentCoilNo,
operator: this.operatorName,
operateTime: new Date().toISOString()
})
// 2. 完成待办操作
if (this.coilInfo.actionId) {
await completeTodoAction(this.coilInfo.actionId)
}
uni.showToast({
title: '重贴标签成功',
icon: 'success'
})
this.close()
this.$emit('success')
} catch (error) {
console.error('重贴标签失败:', error)
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.popup-container {
background: #ffffff;
border-radius: 16rpx;
width: 80vw;
max-width: 600rpx;
overflow: hidden;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.popup-close {
font-size: 36rpx;
color: #999999;
padding: 10rpx;
}
}
.popup-content {
padding: 30rpx;
.coil-info {
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 30rpx;
.info-row {
display: flex;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: #666666;
width: 160rpx;
flex-shrink: 0;
}
.value {
font-size: 26rpx;
color: #333333;
flex: 1;
&.highlight {
color: #1a73e8;
font-weight: 500;
}
}
}
}
.operator-info {
display: flex;
align-items: center;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.label {
font-size: 26rpx;
color: #666666;
}
.value {
font-size: 26rpx;
color: #333333;
font-weight: 500;
}
}
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx 40rpx;
gap: 20rpx;
.btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 8rpx;
font-size: 30rpx;
border: none;
&::after {
border: none;
}
&.btn-cancel {
background: #f5f5f5;
color: #666666;
}
&.btn-confirm {
background: #1a73e8;
color: #ffffff;
&:disabled {
opacity: 0.6;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,612 @@
<template>
<view class="todo-container">
<!-- 自定义导航栏 -->
<view class="custom-nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<text class="nav-title">待办事项</text>
</view>
</view>
<!-- Tab切换 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="handleTabChange(tab.key)"
>
<text class="tab-text">{{ tab.label }}</text>
<text v-if="tab.badge > 0" class="tab-badge">{{ tab.badge }}</text>
</view>
</view>
<!-- 筛选栏 -->
<filter-bar
:loading="loading"
:total="total"
@search="handleSearch"
@reset="handleReset"
/>
<!-- 列表内容 -->
<scroll-view
scroll-y
class="list-container"
:style="{ height: scrollViewHeight + 'px' }"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<!-- 待贴标签列表 -->
<view v-if="activeTab === 'label'" class="coil-list">
<coil-card
v-for="item in list"
:key="item.coilId"
:data="item"
@relabel="handleRelabel"
@view-record="handleViewRecord"
@view-detail="handleViewDetail"
/>
</view>
<!-- 其他Tab占位 -->
<view v-else class="placeholder-page">
<text class="placeholder-icon">🚧</text>
<text class="placeholder-text">功能开发中</text>
</view>
<!-- 空状态 -->
<view v-if="activeTab === 'label' && list.length === 0 && !loading" class="empty-state">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无待办事项</text>
</view>
<!-- 加载状态 -->
<view v-if="loading && list.length === 0" class="loading-state">
<uni-load-more status="loading" />
</view>
<!-- 加载更多 -->
<view v-if="activeTab === 'label' && list.length > 0 && list.length < total" class="load-more-wrapper" @click="onLoadMore">
<view v-if="loading" class="load-more-loading">
<text class="loading-text">加载中...</text>
</view>
<view v-else class="load-more-btn">
<text class="btn-text">点击加载更多</text>
<text class="btn-hint">(已加载 {{ list.length }} / {{ total }} )</text>
</view>
</view>
<!-- 已加载全部 -->
<view v-if="activeTab === 'label' && list.length > 0 && list.length >= total" class="load-more-wrapper no-more">
<text class="no-more-text">已加载全部 {{ total }} 条数据</text>
</view>
</scroll-view>
<!-- 记录弹窗 -->
<record-popup ref="recordPopup" />
<!-- 重贴标签弹窗 -->
<relabel-popup ref="relabelPopup" @success="handleRelabelSuccess" />
</view>
</template>
<script>
import { listMaterialCoil } from '@/api/wms/coil'
import FilterBar from './components/filter-bar.vue'
import CoilCard from './components/coil-card.vue'
import RecordPopup from './components/record-popup.vue'
import RelabelPopup from './components/relabel-popup.vue'
export default {
components: {
FilterBar,
CoilCard,
RecordPopup,
RelabelPopup
},
data() {
return {
statusBarHeight: 0,
scrollViewHeight: 0, // scroll-view 计算高度
tabs: [
{ key: 'label', label: '待贴标签', badge: 0 },
{ key: 'inspect', label: '检验任务', badge: 0 },
{ key: 'approval', label: '质保书审批', badge: 0 },
{ key: 'other', label: '其他代办', badge: 0 }
],
activeTab: 'label',
list: [],
loading: false,
refreshing: false,
query: {
pageNum: 1,
pageSize: 10,
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
hasTransferType: true // 待贴标签只显示有调拨类型的钢卷
},
total: 0,
loadMoreTimer: null // 加载更多防抖定时器
}
},
created() {
// 获取状态栏高度
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
// 计算 scroll-view 高度
this.calcScrollViewHeight()
},
onReady() {
// 页面就绪后再次计算高度(确保布局完成)
this.calcScrollViewHeight()
},
computed: {
loadMoreStatus() {
if (this.loading) return 'loading'
if (this.list.length >= this.total) return 'noMore'
return 'more'
}
},
onLoad() {
this.fetchList()
},
methods: {
// 计算 scroll-view 高度
calcScrollViewHeight() {
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const statusBarHeight = systemInfo.statusBarHeight || 0
// 各区域高度单位px
const navBarHeight = 44 // 导航栏高度
const tabBarHeight = 50 // Tab栏高度
const filterBarHeight = 50 // 筛选栏高度(收起状态)
const bottomSafeArea = systemInfo.safeAreaInsets?.bottom || 0 // 底部安全区域
// 计算 scroll-view 可用高度
this.scrollViewHeight = windowHeight - statusBarHeight - navBarHeight - tabBarHeight - filterBarHeight - bottomSafeArea
console.log('scroll-view 高度计算:', {
windowHeight,
statusBarHeight,
navBarHeight,
tabBarHeight,
filterBarHeight,
bottomSafeArea,
scrollViewHeight: this.scrollViewHeight
})
},
// 切换Tab
handleTabChange(key) {
this.activeTab = key
if (key === 'label') {
this.fetchList()
}
},
// 数据字段映射 - 将后端字段映射到前端使用的字段
// 后端 WmsMaterialCoilVo 字段coilId, enterCoilNo, currentCoilNo,
// itemName, specification, material, manufacturer,
// actualWarehouseName, warehouseName, remark, transferType
mapDataFields(row) {
return {
// 钢卷ID
coilId: row.coilId,
// 钢卷号
enterCoilNo: row.enterCoilNo || '-',
currentCoilNo: row.currentCoilNo || '-',
// 产品信息(注意后端使用 specification/material/manufacturer非 item 前缀)
itemName: row.itemName || '-',
itemSpecification: row.specification || '-',
itemMaterial: row.material || '-',
itemManufacturer: row.manufacturer || '-',
// 库区信息
actualWarehouseName: row.actualWarehouseName || row.warehouseName || '-',
// 其他字段
remark: row.remark || '-',
transferType: row.transferType || '',
// 改判原因(后端 WmsMaterialCoilVo 无此字段,仅通过 listWithRejudge 接口传入)
changeReason: row.changeReason || ''
}
},
// 获取列表数据
async fetchList(isLoadMore = false) {
if (this.loading) return
this.loading = true
try {
console.log('开始获取列表,查询参数:', this.query)
const res = await listMaterialCoil(this.query)
console.log('获取列表响应:', res)
const rows = res.rows || []
this.total = res.total || 0
// 映射数据字段
const mappedRows = rows.map(row => this.mapDataFields(row))
console.log('映射后的数据:', { mappedRows, total: this.total })
if (isLoadMore) {
this.list = [...this.list, ...mappedRows]
} else {
this.list = mappedRows
}
// 更新待贴标签数量
const labelTab = this.tabs.find(t => t.key === 'label')
if (labelTab) {
labelTab.badge = this.total
}
} catch (error) {
console.error('获取列表失败:', error)
uni.showToast({
title: '获取数据失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
this.loading = false
this.refreshing = false
}
},
// 搜索
handleSearch(form) {
this.query = {
...this.query,
...form,
pageNum: 1
}
this.fetchList()
},
// 重置
handleReset() {
this.query = {
pageNum: 1,
pageSize: 10,
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
hasTransferType: true
}
this.fetchList()
},
// 下拉刷新
onRefresh() {
this.refreshing = true
this.query.pageNum = 1
this.fetchList()
},
// 加载更多
onLoadMore() {
console.log('触发加载更多', {
listLength: this.list.length,
total: this.total,
loading: this.loading,
pageNum: this.query.pageNum
})
// 检查是否已加载全部数据
if (this.list.length >= this.total) {
console.log('加载更多被阻止:已加载全部数据')
uni.showToast({
title: '已加载全部数据',
icon: 'none',
duration: 1500
})
return
}
// 检查是否正在加载中
if (this.loading) {
console.log('加载更多被阻止:正在加载中')
return
}
// 防抖保护:防止重复点击
if (this.loadMoreTimer) {
console.log('加载更多被阻止:防抖保护中')
return
}
// 设置防抖定时器
this.loadMoreTimer = setTimeout(() => {
this.loadMoreTimer = null
}, 800)
// 执行加载
this.query.pageNum++
console.log('加载第', this.query.pageNum, '页')
this.fetchList(true)
},
// 重贴标签
handleRelabel(coilInfo) {
this.$refs.relabelPopup.open(coilInfo)
},
// 重贴标签成功回调
handleRelabelSuccess() {
this.fetchList()
},
// 查看记录
handleViewRecord(coilInfo) {
const coilId = coilInfo.coilId
if (!coilId) {
uni.showToast({
title: '无法获取钢卷ID',
icon: 'none'
})
return
}
this.$refs.recordPopup.open(coilId)
},
// 查看详情
handleViewDetail(coilInfo) {
const coilId = coilInfo.coilId
if (!coilId) {
uni.showToast({
title: '无法获取钢卷ID',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages/todo/coil-detail?coilId=${coilId}`
})
}
}
}
</script>
<style scoped lang="scss">
.todo-container {
min-height: 100vh;
background: #f5f7fa;
display: flex;
flex-direction: column;
.custom-nav-bar {
background: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
}
}
.tab-bar {
display: flex;
background: #ffffff;
padding: 0 10rpx;
border-bottom: 1rpx solid #f0f0f0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.tab-item {
flex: 0 0 auto;
min-width: 140rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx 16rpx;
position: relative;
.tab-text {
font-size: 26rpx;
color: #666666;
white-space: nowrap;
line-height: 1.2;
}
.tab-badge {
position: absolute;
top: 8rpx;
right: 8rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
background: #ff4d4f;
color: #ffffff;
font-size: 20rpx;
border-radius: 16rpx;
padding: 0 8rpx;
}
&.active {
.tab-text {
color: #1a73e8;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: #1a73e8;
border-radius: 2rpx;
}
}
}
}
.list-container {
padding: 20rpx;
box-sizing: border-box;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.coil-list {
padding-bottom: 20rpx;
}
.load-more-wrapper {
padding: 16rpx 0;
display: flex;
justify-content: center;
align-items: center;
&:active {
opacity: 0.7;
}
.load-more-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 12rpx 32rpx;
background: #ffffff;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
.btn-text {
font-size: 26rpx;
color: #1a73e8;
font-weight: 500;
}
.btn-hint {
font-size: 20rpx;
color: #999999;
margin-top: 4rpx;
}
}
.load-more-loading {
display: flex;
align-items: center;
padding: 12rpx 32rpx;
.loading-text {
font-size: 26rpx;
color: #999999;
}
}
&.no-more {
padding: 16rpx 0;
.no-more-text {
font-size: 22rpx;
color: #bbbbbb;
}
}
}
.placeholder-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.placeholder-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.placeholder-text {
font-size: 32rpx;
color: #999999;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: #999999;
}
}
.loading-state {
padding: 100rpx 0;
}
}
}
/* 浏览器环境适配 */
@media screen and (min-width: 768px) {
.todo-container {
max-width: 750rpx;
margin: 0 auto;
.tab-bar {
.tab-item {
min-width: 160rpx;
.tab-text {
font-size: 28rpx;
}
}
}
}
}
/* H5 浏览器特定样式 */
/* #ifdef H5 */
.todo-container {
.tab-bar {
.tab-item {
cursor: pointer;
&:hover {
background: rgba(26, 115, 232, 0.05);
}
}
}
.coil-card {
cursor: pointer;
&:hover {
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
}
}
}
/* #endif */
</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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 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

@@ -21,8 +21,6 @@ const request = config => {
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.34'; // 静态默认版本
// 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,26 @@ 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 {
// 直接退出
plus.runtime.quit()
// // 用户取消更新:询问是否忽略该版本
// 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.34",
"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;

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>
@@ -180,9 +176,9 @@
</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 { getRollHistorytList } from '@/api/l2/roller'
@@ -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;
}
}

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

@@ -205,6 +205,7 @@
<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,6 +247,11 @@
<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="请选择钢种">
@@ -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"
@@ -359,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}") {
@@ -402,7 +416,8 @@ export default {
SetupForm,
SetupPane,
PlanRecommendPanel,
ProcessRecommendPanel
ProcessRecommendPanel,
L3PickupRecommendPanel
},
data() {
return {
@@ -435,6 +450,7 @@ export default {
id: null, // 主键ID组件内部用提交时后端自动处理
seqid: null, // 顺序号number接口要求
coilid: "", // 钢卷号string必填
enterCoilNo: "", // 入场钢卷号string
unitCode: "", // 机组号string接口要求
dummyCoilFlag: 0, // 虚卷标识number默认0非虚卷
planid: "", // 计划IDstring接口要求
@@ -628,6 +644,15 @@ export default {
this.form.yieldPoint = payload.yieldPoint
}
},
handleApplyL3PickupRecommendation(payload) {
if (!payload) return
if (payload.coilid !== undefined) {
this.form.coilid = payload.coilid || ''
}
if (payload.enterCoilNo !== undefined) {
this.form.enterCoilNo = payload.enterCoilNo || ''
}
},
handleProcessRecommendation(payload) {
this.processRecommendation = payload
},
@@ -799,6 +824,7 @@ export default {
status: "NEW", // 新增计划默认状态为 新建
planid: "",
planType: "",
enterCoilNo: "",
originCoilid: "",
yieldPoint: null,
zincCoatingThickness: null,

File diff suppressed because it is too large Load Diff

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