Compare commits

...

25 Commits

Author SHA1 Message Date
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
70 changed files with 7012 additions and 658 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,13 +73,13 @@ function checkStorageSpace() {
function checkUpdate(forceCheck = false) {
// 1. 准备本地版本信息
const localVersion = plus.runtime.version; // 基座版本
const staticVersion = '1.3.19'; // 静态默认版本
const staticVersion = '1.3.31'; // 静态默认版本
// 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()}`, // 加时间戳防缓存
@@ -155,14 +155,16 @@ function checkUpdate(forceCheck = false) {
title: '发现新版本',
content: `检测到新版本(${remoteVersion}),是否立即下载并更新?`,
confirmText: '立即更新',
cancelText: '暂不更新',
cancelText: '退出应用',
showCancel: true,
success: (modalRes) => {
if (modalRes.confirm) {
// 用户确认更新:检查存储空间 -> 下载 -> 安装
handleConfirmUpdate(wgtUrl, remoteVersion);
} else {
// 直接退出
// 直接退出
plus.runtime.quit()
// 用户取消更新:询问是否忽略该版本
// 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.19",
"version": "klp 1.3.31",
"wgtUrl": "http://49.232.154.205:10900/fadapp-update/klp/klp.wgt",
"apkUrl": "http://49.232.154.205:10900/fadapp-update/klp/klp.apk"
}

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

@@ -234,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">
@@ -246,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="请选择钢种">
@@ -366,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}") {
@@ -409,7 +416,8 @@ export default {
SetupForm,
SetupPane,
PlanRecommendPanel,
ProcessRecommendPanel
ProcessRecommendPanel,
L3PickupRecommendPanel
},
data() {
return {
@@ -442,6 +450,7 @@ export default {
id: null, // 主键ID组件内部用提交时后端自动处理
seqid: null, // 顺序号number接口要求
coilid: "", // 钢卷号string必填
enterCoilNo: "", // 入场钢卷号string
unitCode: "", // 机组号string接口要求
dummyCoilFlag: 0, // 虚卷标识number默认0非虚卷
planid: "", // 计划IDstring接口要求
@@ -635,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
},
@@ -806,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

@@ -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